wasmi-1.1.0/.cargo_vcs_info.json0000644000000001521046102023000121730ustar { "git": { "sha1": "8273dfb09d493971b7bb12fe614d740cdc857175" }, "path_in_vcs": "crates/wasmi" }wasmi-1.1.0/Cargo.lock0000644000000403141046102023000101520ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "anes" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "assert_matches" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bitflags" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "bumpalo" version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "ciborium" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", "serde", ] [[package]] name = "ciborium-io" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", "half", ] [[package]] name = "clap" version = "4.5.39" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" version = "4.5.39" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" dependencies = [ "anstyle", "clap_lex", ] [[package]] name = "clap_lex" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "criterion" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" dependencies = [ "anes", "cast", "ciborium", "clap", "criterion-plot", "is-terminal", "itertools", "num-traits", "once_cell", "oorandom", "regex", "serde", "serde_derive", "serde_json", "tinytemplate", "walkdir", ] [[package]] name = "criterion-plot" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", "itertools", ] [[package]] name = "crunchy" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "half" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ "cfg-if", "crunchy", ] [[package]] name = "hashbrown" version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" dependencies = [ "foldhash", ] [[package]] name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "hermit-abi" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08" [[package]] name = "indexmap" version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", "hashbrown 0.16.1", ] [[package]] name = "is-terminal" version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi", "libc", "windows-sys", ] [[package]] name = "itertools" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "leb128fmt" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libm" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "oorandom" version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "proc-macro2" version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] [[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "serde" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", ] [[package]] name = "serde_core" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "string-interner" version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23de088478b31c349c9ba67816fa55d9355232d63c3afea8bf513e31f0f1d2c0" dependencies = [ "hashbrown 0.15.3", "serde", ] [[package]] name = "syn" version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tinytemplate" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" dependencies = [ "serde", "serde_json", ] [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-width" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "wasm-encoder" version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" dependencies = [ "leb128fmt", "wasmparser 0.244.0", ] [[package]] name = "wasmi" version = "1.1.0" dependencies = [ "assert_matches", "criterion", "spin", "wasmi_collections", "wasmi_core", "wasmi_ir", "wasmparser 0.239.0", "wat", ] [[package]] name = "wasmi_collections" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8a8c42a2a76148d43097b1d7cc2a5bf33d5c23bd4dd69015fc887e311767884" dependencies = [ "hashbrown 0.15.3", "string-interner", ] [[package]] name = "wasmi_core" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9013136083d988725953390bf668b64b7a218fabf26f8b913bbc59546b97ee27" dependencies = [ "libm", ] [[package]] name = "wasmi_ir" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba1fa003f79156f406d62ef0e1464dc03e11ace37170e9fa7524299a75ad8f68" dependencies = [ "wasmi_core", ] [[package]] name = "wasmparser" version = "0.239.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" dependencies = [ "bitflags", "hashbrown 0.15.3", "indexmap", ] [[package]] name = "wasmparser" version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ "bitflags", "indexmap", ] [[package]] name = "wast" version = "244.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2e7b9f9e23311275920e3d6b56d64137c160cf8af4f84a7283b36cfecbf4acb" dependencies = [ "bumpalo", "leb128fmt", "memchr", "unicode-width", "wasm-encoder", ] [[package]] name = "wat" version = "1.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbf35b87ed352f9ab6cd0732abde5a67dd6153dfd02c493e61459218b19456fa" dependencies = [ "wast", ] [[package]] name = "winapi-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ "windows-sys", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" wasmi-1.1.0/Cargo.toml0000644000000047331046102023000102020ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.86" name = "wasmi" version = "1.1.0" authors = ["Robin Freyler "] build = false exclude = [ "benches/wat", "benches/wasm", "tests/spec/testsuite", "**.wast", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "WebAssembly interpreter" documentation = "https://docs.rs/wasmi/" readme = "README.md" keywords = [ "wasm", "webassembly", "interpreter", "vm", ] categories = [ "wasm", "no-std", "virtualization", ] license = "MIT/Apache-2.0" repository = "https://github.com/wasmi-labs/wasmi" [package.metadata.docs.rs] features = [ "std", "wat", "simd", ] [features] default = [ "std", "wat", ] extra-checks = [] hash-collections = [ "wasmi_collections/hash-collections", "wasmparser/hash-collections", ] prefer-btree-collections = [ "wasmi_collections/prefer-btree-collections", "wasmparser/prefer-btree-collections", ] simd = [ "wasmi_core/simd", "wasmi_ir/simd", "wasmparser/simd", ] std = [ "wasmi_core/std", "wasmi_collections/std", "wasmparser/std", "spin/std", ] wat = [ "dep:wat", "std", ] [lib] name = "wasmi" path = "src/lib.rs" [[test]] name = "mod" path = "tests/mod.rs" [[bench]] name = "benches" path = "benches/benches.rs" harness = false [dependencies.spin] version = "0.9" features = [ "mutex", "spin_mutex", "rwlock", ] default-features = false [dependencies.wasmi_collections] version = "1.1.0" default-features = false [dependencies.wasmi_core] version = "1.1.0" default-features = false [dependencies.wasmi_ir] version = "1.1.0" default-features = false [dependencies.wasmparser] version = "0.239.0" features = [ "validate", "features", ] default-features = false [dependencies.wat] version = "1.239.0" optional = true default-features = false [dev-dependencies.assert_matches] version = "1.5" [dev-dependencies.criterion] version = "0.5" default-features = false wasmi-1.1.0/Cargo.toml.orig000064400000000000000000000035661046102023000136440ustar 00000000000000[package] name = "wasmi" version.workspace = true rust-version.workspace = true documentation = "https://docs.rs/wasmi/" description = "WebAssembly interpreter" authors.workspace = true repository.workspace = true edition.workspace = true readme.workspace = true license.workspace = true keywords.workspace = true categories.workspace = true exclude = [ "benches/wat", "benches/wasm", "tests/spec/testsuite", "**.wast", ] [dependencies] wasmi_core = { workspace = true } wasmi_collections = { workspace = true } wasmi_ir = { workspace = true } wasmparser = { workspace = true, features = ["validate", "features"] } wat = { workspace = true, optional = true } spin = { version = "0.9", default-features = false, features = [ "mutex", "spin_mutex", "rwlock", ] } [dev-dependencies] assert_matches = "1.5" criterion = { version = "0.5", default-features = false } [features] default = ["std", "wat"] std = [ "wasmi_core/std", "wasmi_collections/std", "wasmparser/std", "spin/std", ] hash-collections = [ "wasmi_collections/hash-collections", "wasmparser/hash-collections", ] prefer-btree-collections = [ "wasmi_collections/prefer-btree-collections", "wasmparser/prefer-btree-collections", ] wat = ["dep:wat", "std"] simd = ["wasmi_core/simd", "wasmi_ir/simd", "wasmparser/simd"] # Enables extra checks performed during Wasmi bytecode execution. # # These checks are unnecessary as long as Wasmi translation works as intended. # If Wasmi translation invariants are broken due to bugs, these checks prevent # Wasmi execution to exhibit undefined behavior (UB) in certain cases. # # Expected execution overhead is upt to 20%, if enabled. # # - Enable if your focus is on safety. # - Disable if your focus is on execution speed. extra-checks = [] [[bench]] name = "benches" harness = false [package.metadata.docs.rs] features = ["std", "wat", "simd"] wasmi-1.1.0/README.md000064400000000000000000000247611046102023000122340ustar 00000000000000 | Continuous Integration | Test Coverage | Documentation | Crates.io | |:----------------------:|:--------------------:|:----------------:|:--------------------:| | [![ci][1]][2] | [![codecov][3]][4] | [![docs][5]][6] | [![crates][7]][8] | [1]: https://github.com/wasmi-labs/wasmi/actions/workflows/rust.yml/badge.svg [2]: https://github.com/wasmi-labs/wasmi/actions/workflows/rust.yml [3]: https://codecov.io/gh/wasmi-labs/wasmi/branch/main/badge.svg [4]: https://codecov.io/gh/wasmi-labs/wasmi/branch/main [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 and lightweight WebAssembly interpreter with a focus on constrained and embedded systems. ## Distinct Features - Simple, correct and deterministic execution of WebAssembly. - Efficient and cross-platform WebAssembly runtime for [`no_std` embedded environments](https://doc.rust-lang.org/stable/rustc/platform-support.html). - Compiler/JIT bomb resisting translation. - Loosely mirrors the [Wasmtime API](https://docs.rs/wasmtime/) to act as drop-in replacement. - 100% WebAssembly spec testsuite compliance. - Built-in support for fuel metering. - Supports the official [Wasm C-API](https://github.com/WebAssembly/wasm-c-api). ## Security Audits Wasmi is suitable for safety critical use cases and has been audited twice. | Wasmi Version(s) | Auditor | Contractor | Report | |--:|:--|:--|:--| | `0.36.0`-`0.38.0` | [Runtime Verification Inc.] | [Stellar Development Foundation] | [PDF](./resources/audit-2024-11-27.pdf) | | `0.31.0` | [SRLabs] | [Parity Technologies] | [PDF](./resources/audit-2023-12-20.pdf) | [Wasmtime]: https://github.com/bytecodealliance/wasmtime [SRLabs]: https://www.srlabs.de/ [Runtime Verification Inc.]: https://runtimeverification.com/ [Stellar Development Foundation]: https://stellar.org/foundation [Parity Technologies]: https://www.parity.io/ ## Docs - 📖 [Usage Guide](./docs/usage.md): learn how to use the [Wasmi API](https://crates.io/crates/wasmi) properly. - 🛠️ [Development Guide](./docs/developement.md): learn how to develop for Wasmi. - ✨ [Crate Features](https://docs.rs/wasmi/latest/wasmi/#crate-features): learn about `wasmi` crate features. ## WebAssembly Features | | WebAssembly Proposal | | | | WebAssembly Proposal | | |:-:|:--|:--|:-:|:--|:--|:--| | ✅ | [`mutable-global`] | ≥ `0.14.0` | | ✅ | [`custom-page-sizes`] | [≥ `0.41.0`][(#1197)] | | ✅ | [`saturating-float-to-int`] | ≥ `0.14.0` | | ✅ | [`memory64`] | [≥ `0.41.0`][(#1357)] | | ✅ | [`sign-extension`] | ≥ `0.14.0` | | ✅ | [`wide-arithmetic`] | [≥ `0.42.0`][(#1369)] | | ✅ | [`multi-value`] | ≥ `0.14.0` | | ✅ | [`simd`] | [≥ `0.43.0`][(#1364)] | | ✅ | [`bulk-memory`] | [≥ `0.24.0`][(#628)] | | ✅ | [`relaxed-simd`] | [≥ `0.44.0`][(#1443)] | | ✅ | [`reference-types`] | [≥ `0.24.0`][(#635)] | | 📅 | [`function-references`] | [Tracking Issue][(#774)] | | ✅ | [`tail-calls`] | [≥ `0.28.0`][(#683)] | | 📅 | [`gc`] | [Tracking Issue][(#775)] | | ✅ | [`extended-const`] | [≥ `0.29.0`][(#707)] | | 📅 | [`threads`] | [Tracking Issue][(#777)] | | ✅ | [`multi-memory`] | [≥ `0.37.0`][(#1191)] | | 📅 | [`exception-handling`] | [Tracking Issue][(#1037)] | | | Embeddings | | |:-:|:--|:--| | ✅ | [WASI] | WASI (`wasip1`) support via the [`wasmi_wasi` crate]. | | ✅ | [C-API] | Official Wasm C-API support via the [`wasmi_c_api_impl` crate]. | [`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 [`function-references`]: https://github.com/WebAssembly/function-references [`gc`]: https://github.com/WebAssembly/gc [`multi-memory`]: https://github.com/WebAssembly/multi-memory [`threads`]: https://github.com/WebAssembly/threads [`relaxed-simd`]: https://github.com/WebAssembly/relaxed-simd [`exception-handling`]: https://github.com/WebAssembly/exception-handling [`custom-page-sizes`]: https://github.com/WebAssembly/custom-page-sizes [`memory64`]: https://github.com/WebAssembly/memory64 [`wide-arithmetic`]: https://github.com/WebAssembly/wide-arithmetic [WASI]: https://github.com/WebAssembly/WASI [C-API]: https://github.com/WebAssembly/wasm-c-api [`wasmi_wasi` crate]: ./crates/wasi [`wasmi_c_api_impl` crate]: ./crates/c_api [(#363)]: https://github.com/wasmi-labs/wasmi/issues/363 [(#364)]: https://github.com/wasmi-labs/wasmi/issues/364 [(#496)]: https://github.com/wasmi-labs/wasmi/issues/496 [(#628)]: https://github.com/wasmi-labs/wasmi/pull/628 [(#635)]: https://github.com/wasmi-labs/wasmi/pull/635 [(#638)]: https://github.com/wasmi-labs/wasmi/pull/638 [(#683)]: https://github.com/wasmi-labs/wasmi/pull/683 [(#707)]: https://github.com/wasmi-labs/wasmi/pull/707 [(#774)]: https://github.com/wasmi-labs/wasmi/pull/774 [(#775)]: https://github.com/wasmi-labs/wasmi/pull/775 [(#776)]: https://github.com/wasmi-labs/wasmi/pull/776 [(#777)]: https://github.com/wasmi-labs/wasmi/pull/777 [(#1037)]: https://github.com/wasmi-labs/wasmi/issues/1137 [(#1197)]: https://github.com/wasmi-labs/wasmi/issues/1197 [(#1191)]: https://github.com/wasmi-labs/wasmi/issues/1191 [(#1357)]: https://github.com/wasmi-labs/wasmi/issues/1357 [(#1364)]: https://github.com/wasmi-labs/wasmi/issues/1364 [(#1369)]: https://github.com/wasmi-labs/wasmi/issues/1369 [(#1443)]: https://github.com/wasmi-labs/wasmi/pull/1443 ## Used by If you want your project on this list [please inform me](mailto:robin.freyler@gmail.com) about you project and how Wasmi is used. Stellar Soroban   Wasmer   Firefly Zero   Typst   Orbitinghail   Smoldot   Munal OS   icu4x   Ayaka   Project Oak   ## Sponsors Special thanks to the past and present sponsors of the Wasmi project.
Sponsoring since Oct. 2024
Sponsored until Oct. 2024
## License Licensed under either of * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) * MIT license ([LICENSE-MIT](LICENSE-MIT) or ) at your option. ## Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. wasmi-1.1.0/src/engine/block_type.rs000064400000000000000000000063371046102023000155110ustar 00000000000000use crate::{ engine::DedupFuncType, module::{utils::WasmiValueType, FuncTypeIdx, ModuleHeader}, Engine, FuncType, ValType, }; /// The type of a Wasm control flow block. #[derive(Debug, Copy, Clone)] pub struct BlockType { inner: BlockTypeInner, } /// The inner workings of the [`BlockType`]. #[derive(Debug, Copy, Clone)] pub enum BlockTypeInner { /// A block type with no parameters and no results. Empty, /// A block type with no parameters and exactly one result. Returns(ValType), /// A general block type with parameters and results. FuncType(DedupFuncType), } impl BlockType { /// Creates a new [`BlockType`] from the given [`wasmparser::BlockType`]. /// /// # Errors /// /// If the conversion is not valid or unsupported. pub fn new(block_type: wasmparser::BlockType, res: &ModuleHeader) -> Self { match block_type { wasmparser::BlockType::Empty => Self::empty(), wasmparser::BlockType::Type(return_type) => { let return_type = WasmiValueType::from(return_type).into_inner(); Self::returns(return_type) } wasmparser::BlockType::FuncType(func_type_idx) => { let dedup_func_type = res.get_func_type(FuncTypeIdx::from(func_type_idx)); Self::func_type(dedup_func_type) } } } /// Creates a [`BlockType`] from the underlying type. fn from_inner(inner: BlockTypeInner) -> Self { Self { inner } } /// Creates a [`BlockType`] with no parameter and no results. fn empty() -> Self { Self::from_inner(BlockTypeInner::Empty) } /// Creates a [`BlockType`] with no parameters and a single result type. fn returns(return_type: ValType) -> Self { Self::from_inner(BlockTypeInner::Returns(return_type)) } /// Creates a [`BlockType`] with parameters and results. pub(crate) fn func_type(func_type: &DedupFuncType) -> Self { Self::from_inner(BlockTypeInner::FuncType(*func_type)) } /// Returns the number of parameters of the [`BlockType`]. pub fn len_params(&self, engine: &Engine) -> u16 { match &self.inner { BlockTypeInner::Empty | BlockTypeInner::Returns(_) => 0, BlockTypeInner::FuncType(func_type) => { engine.resolve_func_type(func_type, FuncType::len_params) } } } /// Returns the number of results of the [`BlockType`]. pub fn len_results(&self, engine: &Engine) -> u16 { match &self.inner { BlockTypeInner::Empty => 0, BlockTypeInner::Returns(_) => 1, BlockTypeInner::FuncType(func_type) => { engine.resolve_func_type(func_type, FuncType::len_results) } } } /// Applies `f` to `self`'s [`FuncType`] and returns the result. pub fn func_type_with(&self, engine: &Engine, f: impl for<'a> FnOnce(&FuncType) -> R) -> R { match &self.inner { BlockTypeInner::Empty => f(&FuncType::new([], [])), BlockTypeInner::Returns(return_type) => f(&FuncType::new([], [*return_type])), BlockTypeInner::FuncType(func_type) => engine.resolve_func_type(func_type, f), } } } wasmi-1.1.0/src/engine/code_map.rs000064400000000000000000000675561046102023000151370ustar 00000000000000//! Data structure storing information about compiled functions. //! //! # Note //! //! This is the data structure specialized to handle compiled //! register machine based bytecode functions. use super::{FuncTranslationDriver, FuncTranslator, TranslationError, ValidatingFuncTranslator}; use crate::{ collections::arena::{Arena, ArenaIndex}, core::{Fuel, FuelCostsProvider, UntypedVal}, engine::{utils::unreachable_unchecked, ResumableOutOfFuelError}, errors::FuelError, ir::{index::InternalFunc, Op}, module::{FuncIdx, ModuleHeader}, Config, Error, TrapCode, }; use alloc::boxed::Box; use core::{ fmt, mem::{self, MaybeUninit}, ops::{self, Range}, pin::Pin, slice, }; use spin::Mutex; use wasmparser::{FuncToValidate, ValidatorResources, WasmFeatures}; /// A reference to a compiled function stored in the [`CodeMap`] of an [`Engine`](crate::Engine). #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct EngineFunc(u32); impl From for InternalFunc { fn from(value: EngineFunc) -> Self { InternalFunc::from(value.0) } } impl From for EngineFunc { fn from(index: InternalFunc) -> Self { Self(u32::from(index)) } } impl EngineFunc { /// Creates a new [`EngineFunc`] from the given `u32` index. /// /// # Note /// /// This is a test-only API and not meant for code outside of tests. #[cfg(test)] pub fn from_u32(index: u32) -> Self { Self(index) } } impl ArenaIndex for EngineFunc { fn into_usize(self) -> usize { self.0 as usize } fn from_usize(index: usize) -> Self { let Ok(index) = u32::try_from(index) else { panic!("out of bounds compiled func index: {index}") }; Self(index) } } /// Datastructure to efficiently store information about compiled functions. #[derive(Debug)] pub struct CodeMap { funcs: Mutex>, features: WasmFeatures, } /// A range of [`EngineFunc`]s with contiguous indices. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct EngineFuncSpan { start: EngineFunc, end: EngineFunc, } impl Default for EngineFuncSpan { #[inline] fn default() -> Self { Self::empty() } } impl EngineFuncSpan { /// Creates a new [`EngineFuncSpan`] for `start..end`. /// /// # Panics /// /// If `start` index is not less than or equal to `end` index. pub fn new(start: EngineFunc, end: EngineFunc) -> Self { assert!(start <= end); Self { start, end } } /// Creates an empty [`EngineFuncSpan`]. #[inline] pub fn empty() -> Self { Self { start: EngineFunc(0), end: EngineFunc(0), } } /// Returns `true` if `self` is empty. #[inline] pub fn is_empty(&self) -> bool { debug_assert!(self.start <= self.end); self.start == self.end } /// Returns the number of [`EngineFunc`] in `self`. pub fn len(&self) -> u32 { debug_assert!(self.start <= self.end); let start = self.start.0; let end = self.end.0; end - start } /// Returns the n-th [`EngineFunc`] in `self`, if any. /// /// Returns `None` if `n` is out of bounds. pub fn get(&self, n: u32) -> Option { debug_assert!(self.start <= self.end); if n >= self.len() { return None; } Some(EngineFunc(self.start.0 + n)) } /// Returns the `u32` index of the [`EngineFunc`] in `self` if any. /// /// Returns `None` if `func` is not contained in `self`. pub fn position(&self, func: EngineFunc) -> Option { debug_assert!(self.start <= self.end); if func < self.start || func >= self.end { return None; } Some(func.0 - self.start.0) } /// Returns the n-th [`EngineFunc`] in `self`, if any. /// /// # Panics /// /// If `n` is out of bounds. #[track_caller] pub fn get_or_panic(&self, n: u32) -> EngineFunc { debug_assert!(self.start <= self.end); self.get(n) .unwrap_or_else(|| panic!("out of bounds `EngineFunc` index: {n}")) } /// Returns an iterator over the [`EngineFunc`]s in `self`. #[inline] pub fn iter(&self) -> EngineFuncSpanIter { debug_assert!(self.start <= self.end); EngineFuncSpanIter { span: *self } } } #[derive(Debug)] pub struct EngineFuncSpanIter { span: EngineFuncSpan, } impl Iterator for EngineFuncSpanIter { type Item = EngineFunc; #[inline] fn next(&mut self) -> Option { if self.span.is_empty() { return None; } let func = self.span.start; self.span.start = EngineFunc(self.span.start.0 + 1); Some(func) } #[inline] fn size_hint(&self) -> (usize, Option) { let remaining = self.span.len() as usize; (remaining, Some(remaining)) } } impl DoubleEndedIterator for EngineFuncSpanIter { #[inline] fn next_back(&mut self) -> Option { if self.span.is_empty() { return None; } self.span.end = EngineFunc(self.span.end.0 - 1); Some(self.span.end) } } impl ExactSizeIterator for EngineFuncSpanIter { #[inline] fn len(&self) -> usize { self.span.len() as usize } } impl CodeMap { /// Creates a new [`CodeMap`]. pub fn new(config: &Config) -> Self { Self { funcs: Mutex::new(Arena::default()), features: config.wasm_features(), } } /// Allocates `amount` new uninitialized [`EngineFunc`] to the [`CodeMap`]. /// /// # Note /// /// Before using the [`CodeMap`] all [`EngineFunc`]s must be initialized with either of: /// /// - [`CodeMap::init_func_as_compiled`] /// - [`CodeMap::init_func_as_uncompiled`] pub fn alloc_funcs(&self, amount: usize) -> EngineFuncSpan { let Range { start, end } = self.funcs.lock().alloc_many(amount); EngineFuncSpan::new(start, end) } /// Initializes the [`EngineFunc`] with its [`CompiledFuncEntity`]. /// /// # Panics /// /// - If `func` is an invalid [`EngineFunc`] reference for this [`CodeMap`]. /// - If `func` refers to an already initialized [`EngineFunc`]. pub fn init_func_as_compiled(&self, func: EngineFunc, entity: CompiledFuncEntity) { let mut funcs = self.funcs.lock(); let Some(func) = funcs.get_mut(func) else { panic!("encountered invalid internal function: {func:?}") }; func.init_compiled(entity); } /// Initializes the [`EngineFunc`] for lazy translation. /// /// # Panics /// /// - If `func` is an invalid [`EngineFunc`] reference for this [`CodeMap`]. /// - If `func` refers to an already initialized [`EngineFunc`]. pub fn init_func_as_uncompiled( &self, func: EngineFunc, func_idx: FuncIdx, bytes: &[u8], module: &ModuleHeader, func_to_validate: Option>, ) { let mut funcs = self.funcs.lock(); let Some(func) = funcs.get_mut(func) else { panic!("encountered invalid internal function: {func:?}") }; func.init_uncompiled(UncompiledFuncEntity::new( func_idx, bytes, module.clone(), func_to_validate, )); } /// Returns the [`FuncEntity`] of the [`EngineFunc`]. /// /// # Errors /// /// - If translation or Wasm validation of `func` failed. /// - If `ctx` ran out of fuel in case fuel consumption is enabled. #[track_caller] #[inline] pub fn get<'a>( &'a self, fuel: Option<&mut Fuel>, func: EngineFunc, ) -> Result, Error> { match self.get_compiled(func) { Some(cref) => Ok(cref), None => self.compile_or_wait(fuel, func), } } /// Compile `func` or wait for result if another process already started compilation. /// /// # Errors /// /// - If translation or Wasm validation of `func` failed. /// - If `ctx` ran out of fuel in case fuel consumption is enabled. #[cold] #[inline] fn compile_or_wait<'a>( &'a self, fuel: Option<&mut Fuel>, func: EngineFunc, ) -> Result, Error> { match self.get_uncompiled(func) { Some(entity) => self.compile(fuel, func, entity), None => self.wait_for_compilation(func), } } /// Returns the [`CompiledFuncRef`] of `func` if possible, otherwise returns `None`. #[inline] fn get_compiled(&self, func: EngineFunc) -> Option> { let funcs = self.funcs.lock(); let Some(entity) = funcs.get(func) else { // Safety: this is just called internally with function indices // that are known to be valid. Since this is a performance // critical path we need to leave out this check. unsafe { unreachable_unchecked!("encountered invalid function index for engine: {func:?}") } }; let cref = entity.get_compiled()?; Some(self.adjust_cref_lifetime(cref)) } /// Returns the [`UncompiledFuncEntity`] of `func` if possible, otherwise returns `None`. /// /// After this operation `func` will be in [`FuncEntity::Compiling`] state. #[inline] fn get_uncompiled(&self, func: EngineFunc) -> Option { let mut funcs = self.funcs.lock(); let Some(entity) = funcs.get_mut(func) else { panic!("encountered invalid internal function: {func:?}") }; entity.get_uncompiled() } /// Prolongs the lifetime of `cref` to `self`. /// /// # Safety /// /// This is safe since /// /// - [`CompiledFuncRef`] only references `Pin`ned data /// - [`CodeMap`] is an append-only data structure /// /// Thus any shared [`CompiledFuncRef`] can safely outlive the internal `Mutex` lock. #[inline] fn adjust_cref_lifetime<'a>(&'a self, cref: CompiledFuncRef<'_>) -> CompiledFuncRef<'a> { // Safety: we cast the lifetime of `cref` to match `&self` instead of the inner // `MutexGuard` which is safe because `CodeMap` is append-only and the // returned `CompiledFuncRef` only references `Pin`ned data. unsafe { mem::transmute::, CompiledFuncRef<'a>>(cref) } } /// Compile and validate the [`UncompiledFuncEntity`] identified by `func`. /// /// # Errors /// /// - If translation or Wasm validation of `func` failed. /// - If `ctx` ran out of fuel in case fuel consumption is enabled. #[inline] fn compile<'a>( &'a self, fuel: Option<&mut Fuel>, func: EngineFunc, mut uncompiled: UncompiledFuncEntity, ) -> Result, Error> { // Note: It is important that compilation happens without locking the `CodeMap` // since compilation can take a prolonged time. let compiled_func = uncompiled.compile(fuel, &self.features); let mut funcs = self.funcs.lock(); let Some(entity) = funcs.get_mut(func) else { panic!("encountered invalid internal function: {func:?}") }; match compiled_func { Ok(compiled_func) => { let cref = entity.set_compiled(compiled_func); Ok(self.adjust_cref_lifetime(cref)) } Err(error) if error.as_trap_code() == Some(TrapCode::OutOfFuel) => { entity.set_uncompiled(uncompiled); Err(error) } Err(error) => { entity.set_failed_to_compile(); Err(error) } } } /// Wait until `func` has finished compilation. /// /// In this case compilation of `func` is driven by another thread. /// /// # Errors /// /// - If translation or Wasm validation of `func` failed. /// - If `ctx` ran out of fuel in case fuel consumption is enabled. #[cold] #[inline(never)] fn wait_for_compilation(&self, func: EngineFunc) -> Result, Error> { 'wait: loop { let funcs = self.funcs.lock(); let Some(entity) = funcs.get(func) else { panic!("encountered invalid internal function: {func:?}") }; match entity { FuncEntity::Compiling => continue 'wait, FuncEntity::Compiled(func) => { let cref = CompiledFuncRef::from(func); return Ok(self.adjust_cref_lifetime(cref)); } FuncEntity::FailedToCompile => { return Err(Error::from(TranslationError::LazyCompilationFailed)) } FuncEntity::Uncompiled(_) | FuncEntity::Uninit => { panic!("unexpected function state: {entity:?}") } } } } } /// An internal function entity. /// /// Either an already compiled or still uncompiled function entity. #[derive(Debug)] enum FuncEntity { /// The function entity has not yet been initialized. Uninit, /// An internal function that has not yet been compiled. Uncompiled(UncompiledFuncEntity), /// The function entity is currently compiling. Compiling, /// The function entity failed to compile lazily. FailedToCompile, /// An internal function that has already been compiled. Compiled(CompiledFuncEntity), } impl Default for FuncEntity { #[inline] fn default() -> Self { Self::Uninit } } impl FuncEntity { /// Initializes the [`FuncEntity`] with a [`CompiledFuncEntity`]. /// /// # Panics /// /// If `func` has already been initialized. #[inline] pub fn init_compiled(&mut self, entity: CompiledFuncEntity) { assert!(matches!(self, Self::Uninit)); *self = Self::Compiled(entity); } /// Initializes the [`FuncEntity`] to an uncompiled state. /// /// # Panics /// /// If `func` has already been initialized. #[inline] pub fn init_uncompiled(&mut self, entity: UncompiledFuncEntity) { assert!(matches!(self, Self::Uninit)); *self = Self::Uncompiled(entity); } /// Returns the [`CompiledFuncEntity`] if possible. /// /// Returns `None` if the [`FuncEntity`] has not yet been compiled. #[inline] pub fn get_compiled(&self) -> Option> { match self { FuncEntity::Compiled(func) => Some(func.into()), _ => None, } } /// Returns the [`UncompiledFuncEntity`] if possible. /// /// # Errors /// /// Returns a proper error if the [`FuncEntity`] is not uncompiled. #[inline] pub fn get_uncompiled(&mut self) -> Option { match self { Self::Uncompiled(_) => {} _ => return None, }; match mem::replace(self, Self::Compiling) { Self::Uncompiled(func) => Some(func), _ => { // Safety: we just asserted that `self` must be an uncompiled function // since otherwise we would have returned `None` above. // Since this is a performance critical path we need to leave out this check. unsafe { unreachable_unchecked!("expected uncompiled function but found: {self:?}") } } } } /// Sets the state back to [`UncompiledFuncEntity`] if possible. /// /// # Panics /// /// If the current state was not [`FuncEntity::Compiling`]. #[inline] pub fn set_uncompiled(&mut self, uncompiled: UncompiledFuncEntity) { match mem::replace(self, FuncEntity::Uncompiled(uncompiled)) { Self::Compiling => {} unexpected => { // Safety: we just asserted that `self` must be an uncompiled function // since otherwise we would have returned `None` above. // Since this is a performance critical path we need to leave out this check. unsafe { unreachable_unchecked!( "can only set `Compiling` back to `UncompiledFuncEntity` but found: {unexpected:?}" ) } } } } /// Sets the [`FuncEntity`] as [`CompiledFuncEntity`]. /// /// Returns a [`CompiledFuncRef`] to the [`CompiledFuncEntity`]. /// /// # Panics /// /// If `func` has already been initialized. #[inline] pub fn set_compiled(&mut self, entity: CompiledFuncEntity) -> CompiledFuncRef<'_> { assert!(matches!(self, Self::Compiling)); *self = Self::Compiled(entity); let Self::Compiled(entity) = self else { panic!("just initialized `self` as compiled") }; CompiledFuncRef::from(&*entity) } /// Signals a failed compilation for the [`FuncEntity`]. /// /// # Panics /// /// If `func` is not in compiling state. #[inline] pub fn set_failed_to_compile(&mut self) { assert!(matches!(self, Self::Compiling)); *self = Self::FailedToCompile; } } /// A function type index into the Wasm module. #[derive(Debug, Copy, Clone)] #[repr(transparent)] pub struct TypeIndex(u32); /// An internal uncompiled function entity. pub struct UncompiledFuncEntity { /// The index of the function within the Wasm module. func_index: FuncIdx, /// The Wasm binary bytes. bytes: SmallByteSlice, /// The Wasm module of the Wasm function. /// /// This is required for Wasm module related information in order /// to compile the Wasm function body. module: ModuleHeader, /// Optional Wasm validation information. /// /// This is `Some` if the [`UncompiledFuncEntity`] is to be validated upon compilation. validation: Option<(TypeIndex, ValidatorResources)>, } impl UncompiledFuncEntity { /// Creates a new [`UncompiledFuncEntity`]. pub fn new( func_index: FuncIdx, bytes: &[u8], module: ModuleHeader, func_to_validate: impl Into>>, ) -> Self { let validation = func_to_validate.into().map(|func_to_validate| { assert_eq!( func_to_validate.index, func_index.into_u32(), "Wasmi function index ({}) does not match with Wasm validation function index ({})", func_to_validate.index, func_index.into_u32(), ); (TypeIndex(func_to_validate.ty), func_to_validate.resources) }); let bytes = bytes.into(); Self { func_index, bytes, module, validation, } } /// Compile the [`UncompiledFuncEntity`]. /// /// # Panics /// /// - If the `func` unexpectedly has already been compiled. /// - If the `engine` unexpectedly no longer exists due to weak referencing. /// /// # Errors /// /// - If function translation failed. /// - If `ctx` ran out of fuel in case fuel consumption is enabled. fn compile( &mut self, fuel: Option<&mut Fuel>, features: &WasmFeatures, ) -> Result { /// The amount of fuel required to compile a function body per byte. /// /// This does _not_ include validation. /// /// # Note /// /// This fuel amount was chosen after extensive worst-case translation benchmarking. const COMPILE_FUEL_PER_BYTE: u64 = 7; /// The amount of fuel required to validate a function body per byte. /// /// This does _not_ include compilation. /// /// # Note /// /// This fuel amount was chosen after extensive worst-case translation benchmarking. const VALIDATE_FUEL_PER_BYTE: u64 = 2; /// The amount of fuel required to validate and compile a function body per byte. const VALIDATE_AND_COMPILE_FUEL_PER_BYTE: u64 = VALIDATE_FUEL_PER_BYTE + COMPILE_FUEL_PER_BYTE; let func_idx = self.func_index; let wasm_bytes = self.bytes.as_slice(); let needs_validation = self.validation.is_some(); let compilation_fuel = |_costs: &FuelCostsProvider| { let len_bytes = wasm_bytes.len() as u64; let fuel_per_byte = match needs_validation { false => COMPILE_FUEL_PER_BYTE, true => VALIDATE_AND_COMPILE_FUEL_PER_BYTE, }; len_bytes.saturating_mul(fuel_per_byte) }; if let Some(fuel) = fuel { match fuel.consume_fuel(compilation_fuel) { Err(FuelError::OutOfFuel { required_fuel }) => { return Err(Error::from(ResumableOutOfFuelError::new(required_fuel))) } Ok(_) | Err(FuelError::FuelMeteringDisabled) => {} } } let module = self.module.clone(); let Some(engine) = module.engine().upgrade() else { panic!( "cannot compile function lazily since engine does no longer exist: {:?}", module.engine() ) }; let mut result = MaybeUninit::uninit(); match self.validation.take() { Some((type_index, resources)) => { let allocs = engine.get_allocs(); let translator = FuncTranslator::new(func_idx, module, allocs.0)?; let func_to_validate = FuncToValidate { resources, index: func_idx.into_u32(), ty: type_index.0, features: *features, }; let validator = func_to_validate.into_validator(allocs.1); let translator = ValidatingFuncTranslator::new(validator, translator)?; let allocs = FuncTranslationDriver::new(0, wasm_bytes, translator)?.translate( |compiled_func| { result.write(compiled_func); }, )?; engine.recycle_allocs(allocs.translation, allocs.validation); } None => { let allocs = engine.get_translation_allocs(); let translator = FuncTranslator::new(func_idx, module, allocs)?; let allocs = FuncTranslationDriver::new(0, wasm_bytes, translator)?.translate( |compiled_func| { result.write(compiled_func); }, )?; engine.recycle_translation_allocs(allocs); } }; Ok(unsafe { result.assume_init() }) } } impl fmt::Debug for UncompiledFuncEntity { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("UncompiledFuncEntity") .field("func_idx", &self.func_index) .field("bytes", &self.bytes) .field("module", &self.module) .field("validate", &self.validation.is_some()) .finish() } } /// A boxed byte slice that can store some bytes inline. #[derive(Debug)] pub enum SmallByteSlice { /// The byte slice fits in the inline buffer. Small { /// The length of the byte slice. len: u8, /// The bytes stored inline. /// /// Bytes beyond `len` are zeroed. bytes: [u8; Self::MAX_INLINE_SIZE], }, /// The byte slice is too big and allocated on the heap. Big(Box<[u8]>), } impl Default for SmallByteSlice { fn default() -> Self { Self::Small { len: 0, bytes: [0x00; Self::MAX_INLINE_SIZE], } } } impl SmallByteSlice { /// The maximum amount of bytes that can be stored inline. /// /// This value was chosen because it allows for the maximum /// amount of bytes to be stored inline with minimal `size_of`. const MAX_INLINE_SIZE: usize = 22; /// Returns the underlying slice of bytes. #[inline] pub fn as_slice(&self) -> &[u8] { match self { SmallByteSlice::Small { len, bytes } => &bytes[..usize::from(*len)], SmallByteSlice::Big(bytes) => &bytes[..], } } } impl ops::Index for SmallByteSlice where I: slice::SliceIndex<[u8]>, { type Output = >::Output; #[inline] fn index(&self, index: I) -> &Self::Output { self.as_slice().index(index) } } impl<'a> From<&'a [u8]> for SmallByteSlice { fn from(bytes: &'a [u8]) -> Self { if bytes.len() <= Self::MAX_INLINE_SIZE { let len = bytes.len() as u8; let mut buffer = [0x00_u8; Self::MAX_INLINE_SIZE]; buffer[..usize::from(len)].copy_from_slice(bytes); return Self::Small { len, bytes: buffer }; } Self::Big(bytes.into()) } } /// Meta information about a [`EngineFunc`]. #[derive(Debug)] pub struct CompiledFuncEntity { /// The sequence of [`Op`] of the [`CompiledFuncEntity`]. instrs: Pin>, /// The constant values local to the [`EngineFunc`]. consts: Pin>, /// The number of stack slots used by the [`EngineFunc`] in total. /// /// # Note /// /// This includes stack slots to store the function local constant values, /// function parameters, function locals and dynamically used stack slots. len_stack_slots: u16, } impl CompiledFuncEntity { /// Create a new initialized [`CompiledFuncEntity`]. /// /// # Panics /// /// - If `instrs` is empty. /// - If `instrs` contains more than `i32::MAX` instructions. pub fn new(len_stack_slots: u16, instrs: I, consts: C) -> Self where I: IntoIterator, C: IntoIterator, { let instrs: Pin> = Pin::new(instrs.into_iter().collect()); let consts: Pin> = Pin::new(consts.into_iter().collect()); assert!( !instrs.is_empty(), "compiled functions must have at least one instruction" ); assert!( // Generally, Wasmi has no issues with more than `i32::MAX` instructions. // However, Wasmi's branch instructions can jump across at most `i32::MAX` // forwards or `i32::MIN` instructions backwards and thus having more than // `i32::MAX` instructions might introduce problems. instrs.len() <= i32::MAX as usize, "compiled function has too many instructions: {}", instrs.len(), ); Self { instrs, consts, len_stack_slots, } } } /// A shared reference to the data of a [`EngineFunc`]. #[derive(Debug, Copy, Clone)] pub struct CompiledFuncRef<'a> { /// The sequence of [`Op`] of the [`CompiledFuncEntity`]. instrs: Pin<&'a [Op]>, /// The constant values local to the [`EngineFunc`]. consts: Pin<&'a [UntypedVal]>, /// The number of stack slots used by the [`EngineFunc`] in total. len_stack_slots: u16, } impl<'a> From<&'a CompiledFuncEntity> for CompiledFuncRef<'a> { #[inline] fn from(func: &'a CompiledFuncEntity) -> Self { Self { instrs: func.instrs.as_ref(), consts: func.consts.as_ref(), len_stack_slots: func.len_stack_slots, } } } impl<'a> CompiledFuncRef<'a> { /// Returns the sequence of [`Op`] of the [`EngineFunc`]. #[inline] pub fn instrs(&self) -> &'a [Op] { self.instrs.get_ref() } /// Returns the number of stack slots used by the [`EngineFunc`]. #[inline] pub fn len_stack_slots(&self) -> u16 { self.len_stack_slots } /// Returns the function local constant values of the [`EngineFunc`]. #[inline] pub fn consts(&self) -> &'a [UntypedVal] { self.consts.get_ref() } } wasmi-1.1.0/src/engine/config.rs000064400000000000000000000326631046102023000146240ustar 00000000000000use super::{EnforcedLimits, StackConfig}; use crate::core::FuelCostsProvider; use wasmparser::WasmFeatures; /// Configuration for an [`Engine`]. /// /// [`Engine`]: [`crate::Engine`] #[derive(Debug, Clone)] pub struct Config { /// The limits set on the value stack and call stack. pub(crate) stack: StackConfig, /// The Wasm features used when validating or translating functions. features: WasmFeatures, /// Is `true` if Wasmi executions shall consume fuel. consume_fuel: bool, /// Is `true` if Wasmi shall ignore Wasm custom sections when parsing Wasm modules. ignore_custom_sections: bool, /// The configured fuel costs of all Wasmi bytecode instructions. fuel_costs: FuelCostsProvider, /// The mode of Wasm to Wasmi bytecode compilation. compilation_mode: CompilationMode, /// Enforced limits for Wasm module parsing and compilation. limits: EnforcedLimits, } /// The chosen mode of Wasm to Wasmi bytecode compilation. #[derive(Debug, Default, Copy, Clone)] pub enum CompilationMode { /// The Wasm code is compiled eagerly to Wasmi bytecode. Eager, /// The Wasm code is validated eagerly and translated lazily on first use. #[default] LazyTranslation, /// The Wasm code is validated and translated lazily on first use. /// /// # Note /// /// This mode must not be used if the result of Wasm execution /// must be deterministic amongst multiple Wasm implementations. Lazy, } impl Default for Config { fn default() -> Self { Self { stack: StackConfig::default(), features: Self::default_features(), consume_fuel: false, ignore_custom_sections: false, fuel_costs: FuelCostsProvider::default(), compilation_mode: CompilationMode::default(), limits: EnforcedLimits::default(), } } } impl Config { /// Returns the default [`WasmFeatures`]. fn default_features() -> WasmFeatures { let mut features = WasmFeatures::empty(); features.set(WasmFeatures::MUTABLE_GLOBAL, true); features.set(WasmFeatures::MULTI_VALUE, true); features.set(WasmFeatures::MULTI_MEMORY, true); features.set(WasmFeatures::SATURATING_FLOAT_TO_INT, true); features.set(WasmFeatures::SIGN_EXTENSION, true); features.set(WasmFeatures::BULK_MEMORY, true); features.set(WasmFeatures::REFERENCE_TYPES, true); features.set(WasmFeatures::GC_TYPES, true); // required by reference-types features.set(WasmFeatures::TAIL_CALL, true); features.set(WasmFeatures::EXTENDED_CONST, true); features.set(WasmFeatures::FLOATS, true); features.set(WasmFeatures::CUSTOM_PAGE_SIZES, false); features.set(WasmFeatures::MEMORY64, true); features.set(WasmFeatures::WIDE_ARITHMETIC, false); features.set(WasmFeatures::SIMD, cfg!(feature = "simd")); features.set(WasmFeatures::RELAXED_SIMD, cfg!(feature = "simd")); features } /// Sets the maximum recursion depth of the [`Engine`]'s stack during execution. /// /// # Note /// /// An execution traps if it exceeds this limits. /// /// [`Engine`]: [`crate::Engine`] pub fn set_max_recursion_depth(&mut self, value: usize) -> &mut Self { self.stack.set_max_recursion_depth(value); self } /// Sets the minimum (or initial) height of the [`Engine`]'s value stack in bytes. /// /// # Note /// /// - Lower initial heights may improve memory consumption. /// - Higher initial heights may improve cold start times. /// /// # Panics /// /// If `value` is greater than the current maximum height of the value stack. /// /// [`Engine`]: [`crate::Engine`] pub fn set_min_stack_height(&mut self, value: usize) -> &mut Self { if self.stack.set_min_stack_height(value).is_err() { let max = self.stack.max_stack_height(); panic!("minimum stack height exceeds maximum: min={value}, max={max}"); } self } /// Sets the maximum height of the [`Engine`]'s value stack in bytes. /// /// # Note /// /// An execution traps if it exceeds this limits. /// /// # Panics /// /// If `value` is less than the current minimum height of the value stack. /// /// [`Engine`]: [`crate::Engine`] pub fn set_max_stack_height(&mut self, value: usize) -> &mut Self { if self.stack.set_max_stack_height(value).is_err() { let min = self.stack.min_stack_height(); panic!("maximum stack height is lower than minimum: min={min}, max={value}"); } self } /// Sets the maximum number of cached stacks for reuse for the [`Config`]. /// /// # Note /// /// - A higher value may improve execution performance. /// - A lower value may improve memory consumption. pub fn set_max_cached_stacks(&mut self, value: usize) -> &mut Self { self.stack.set_max_cached_stacks(value); self } /// 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.features.set(WasmFeatures::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.features.set(WasmFeatures::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.features .set(WasmFeatures::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.features.set(WasmFeatures::MULTI_VALUE, enable); self } /// Enable or disable the [`multi-memory`] Wasm proposal for the [`Config`]. /// /// # Note /// /// Enabled by default. /// /// [`multi-memory`]: https://github.com/WebAssembly/multi-memory pub fn wasm_multi_memory(&mut self, enable: bool) -> &mut Self { self.features.set(WasmFeatures::MULTI_MEMORY, 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.features.set(WasmFeatures::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.features.set(WasmFeatures::REFERENCE_TYPES, enable); self.features.set(WasmFeatures::GC_TYPES, enable); self } /// Enable or disable the [`tail-call`] Wasm proposal for the [`Config`]. /// /// # Note /// /// Enabled by default. /// /// [`tail-call`]: https://github.com/WebAssembly/tail-call pub fn wasm_tail_call(&mut self, enable: bool) -> &mut Self { self.features.set(WasmFeatures::TAIL_CALL, enable); self } /// Enable or disable the [`extended-const`] Wasm proposal for the [`Config`]. /// /// # Note /// /// Enabled by default. /// /// [`extended-const`]: https://github.com/WebAssembly/extended-const pub fn wasm_extended_const(&mut self, enable: bool) -> &mut Self { self.features.set(WasmFeatures::EXTENDED_CONST, enable); self } /// Enable or disable the [`custom-page-sizes`] Wasm proposal for the [`Config`]. /// /// # Note /// /// Disabled by default. /// /// [`custom-page-sizes`]: https://github.com/WebAssembly/custom-page-sizes pub fn wasm_custom_page_sizes(&mut self, enable: bool) -> &mut Self { self.features.set(WasmFeatures::CUSTOM_PAGE_SIZES, enable); self } /// Enable or disable the [`memory64`] Wasm proposal for the [`Config`]. /// /// # Note /// /// Disabled by default. /// /// [`memory64`]: https://github.com/WebAssembly/memory64 pub fn wasm_memory64(&mut self, enable: bool) -> &mut Self { self.features.set(WasmFeatures::MEMORY64, enable); self } /// Enable or disable the [`wide-arithmetic`] Wasm proposal for the [`Config`]. /// /// Disabled by default. /// /// [`wide-arithmetic`]: https://github.com/WebAssembly/wide-arithmetic pub fn wasm_wide_arithmetic(&mut self, enable: bool) -> &mut Self { self.features.set(WasmFeatures::WIDE_ARITHMETIC, enable); self } /// Enable or disable the [`simd`] Wasm proposal for the [`Config`]. /// /// Enabled by default. /// /// [`simd`]: https://github.com/WebAssembly/simd #[cfg(feature = "simd")] pub fn wasm_simd(&mut self, enable: bool) -> &mut Self { self.features.set(WasmFeatures::SIMD, enable); self } /// Enable or disable the [`relaxed-simd`] Wasm proposal for the [`Config`]. /// /// Enabled by default. /// /// [`relaxed-simd`]: https://github.com/WebAssembly/relaxed-simd #[cfg(feature = "simd")] pub fn wasm_relaxed_simd(&mut self, enable: bool) -> &mut Self { self.features.set(WasmFeatures::RELAXED_SIMD, 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.features.set(WasmFeatures::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::TrapCode::OutOfFuel) trap is raised. /// This way users can deterministically halt or yield the execution of WebAssembly code. /// /// - Use [`Store::set_fuel`](crate::Store::set_fuel) to set the remaining fuel of the [`Store`] before /// executing some code as the [`Store`] start with no fuel. /// - Use [`Caller::set_fuel`](crate::Caller::set_fuel) to update the remaining fuel when executing 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 } /// Returns `true` if the [`Config`] enables fuel consumption by the [`Engine`]. /// /// [`Engine`]: crate::Engine pub(crate) fn get_consume_fuel(&self) -> bool { self.consume_fuel } /// Configures whether Wasmi will ignore custom sections when parsing Wasm modules. /// /// Default value: `false` pub fn ignore_custom_sections(&mut self, enable: bool) -> &mut Self { self.ignore_custom_sections = enable; self } /// Returns `true` if the [`Config`] mandates to ignore Wasm custom sections when parsing Wasm modules. pub(crate) fn get_ignore_custom_sections(&self) -> bool { self.ignore_custom_sections } /// Returns the configured [`FuelCostsProvider`]. pub(crate) fn fuel_costs(&self) -> &FuelCostsProvider { &self.fuel_costs } /// Sets the [`CompilationMode`] used for the [`Engine`]. /// /// By default [`CompilationMode::LazyTranslation`] is used. /// /// [`Engine`]: crate::Engine pub fn compilation_mode(&mut self, mode: CompilationMode) -> &mut Self { self.compilation_mode = mode; self } /// Returns the [`CompilationMode`] used for the [`Engine`]. /// /// [`Engine`]: crate::Engine pub(super) fn get_compilation_mode(&self) -> CompilationMode { self.compilation_mode } /// Sets the [`EnforcedLimits`] enforced by the [`Engine`] for Wasm module parsing and compilation. /// /// By default no limits are enforced. /// /// [`Engine`]: crate::Engine pub fn enforced_limits(&mut self, limits: EnforcedLimits) -> &mut Self { self.limits = limits; self } /// Returns the [`EnforcedLimits`] used for the [`Engine`]. /// /// [`Engine`]: crate::Engine pub(crate) fn get_enforced_limits(&self) -> &EnforcedLimits { &self.limits } /// Returns the [`WasmFeatures`] represented by the [`Config`]. pub(crate) fn wasm_features(&self) -> WasmFeatures { self.features } } wasmi-1.1.0/src/engine/executor/cache.rs000064400000000000000000000241511046102023000162510ustar 00000000000000use crate::{ core::UntypedVal, engine::DedupFuncType, instance::InstanceEntity, ir::index, memory::DataSegment, module::DEFAULT_MEMORY_INDEX, store::StoreInner, table::ElementSegment, Func, Global, Instance, Memory, Table, }; use core::ptr::{self, NonNull}; /// Cached WebAssembly instance. #[derive(Debug)] pub struct CachedInstance { /// The currently used instance. instance: NonNull, /// The cached bytes of the default linear memory. pub memory: CachedMemory, /// The cached value of the global variable at index 0. pub global: CachedGlobal, } impl CachedInstance { /// Creates a new [`CachedInstance`]. #[inline] pub fn new(ctx: &mut StoreInner, instance: &Instance) -> Self { let (instance, memory, global) = Self::load_caches(ctx, instance); Self { instance, memory, global, } } /// Loads the [`InstanceEntity`] from the [`StoreInner`]. #[inline] fn load_instance<'ctx>(ctx: &'ctx mut StoreInner, instance: &Instance) -> &'ctx InstanceEntity { ctx.resolve_instance(instance) } /// Loads the cached global and linear memory. #[inline] fn load_caches( ctx: &mut StoreInner, instance: &Instance, ) -> (NonNull, CachedMemory, CachedGlobal) { let entity = Self::load_instance(ctx, instance); let memory = entity.get_memory(DEFAULT_MEMORY_INDEX); let global = entity.get_global(0); let instance = entity.into(); let memory = memory .map(|memory| CachedMemory::new(ctx, &memory)) .unwrap_or_default(); let global = global .map(|global| CachedGlobal::new(ctx, &global)) .unwrap_or_default(); (instance, memory, global) } /// Update the cached instance, linear memory and global variable. #[inline] pub fn update(&mut self, ctx: &mut StoreInner, instance: &Instance) { (self.instance, self.memory, self.global) = Self::load_caches(ctx, instance); } /// Returns a shared reference to the cached [`InstanceEntity`]. /// /// # Safety /// /// It is the callers responsibility to use this method only when the caches are fresh. #[inline] unsafe fn as_ref(&self) -> &InstanceEntity { unsafe { self.instance.as_ref() } } /// Updates the [`CachedMemory`]'s linear memory data pointer. /// /// # Note /// /// This needs to be called whenever the cached pointer might have changed. /// /// The linear memory pointer might change when ... /// /// - calling a host function /// - successfully growing the default linear memory /// - calling functions defined in other instances via imported or indirect calls /// - returning from functions that changed the currently used instance /// /// # Safety /// /// It is the callers responsibility to use this method only when the caches are fresh. #[inline] pub unsafe fn update_memory(&mut self, ctx: &mut StoreInner) { let instance = unsafe { self.as_ref() }; self.memory = instance .get_memory(DEFAULT_MEMORY_INDEX) .map(|memory| CachedMemory::new(ctx, &memory)) .unwrap_or_default(); } /// Returns the [`Func`] at the `index` if any. /// /// # Safety /// /// It is the callers responsibility to use this method only when the caches are fresh. #[inline] pub unsafe fn get_func(&self, index: index::Func) -> Option { let instance = unsafe { self.as_ref() }; instance.get_func(u32::from(index)) } /// Returns the [`Memory`] at the `index` if any. /// /// # Safety /// /// It is the callers responsibility to use this method only when the caches are fresh. #[inline] pub unsafe fn get_memory(&self, index: index::Memory) -> Option { let instance = unsafe { self.as_ref() }; instance.get_memory(u32::from(index)) } /// Returns the [`Table`] at the `index` if any. /// /// # Safety /// /// It is the callers responsibility to use this method only when the caches are fresh. #[inline] pub unsafe fn get_table(&self, index: index::Table) -> Option { let instance = unsafe { self.as_ref() }; instance.get_table(u32::from(index)) } /// Returns the [`Global`] at the `index` if any. /// /// # Safety /// /// It is the callers responsibility to use this method only when the caches are fresh. #[inline] pub unsafe fn get_global(&self, index: index::Global) -> Option { let instance = unsafe { self.as_ref() }; instance.get_global(u32::from(index)) } /// Returns the [`DataSegment`] at the `index` if any. /// /// # Safety /// /// It is the callers responsibility to use this method only when the caches are fresh. #[inline] pub unsafe fn get_data_segment(&self, index: index::Data) -> Option { let instance = unsafe { self.as_ref() }; instance.get_data_segment(u32::from(index)) } /// Returns the [`ElementSegment`] at the `index` if any. /// /// # Safety /// /// It is the callers responsibility to use this method only when the caches are fresh. #[inline] pub unsafe fn get_element_segment(&self, index: index::Elem) -> Option { let instance = unsafe { self.as_ref() }; instance.get_element_segment(u32::from(index)) } /// Returns the [`DedupFuncType`] at the `index` if any. /// /// # Safety /// /// It is the callers responsibility to use this method only when the caches are fresh. #[inline] pub unsafe fn get_func_type_dedup(&self, index: index::FuncType) -> Option { let instance = unsafe { self.as_ref() }; instance.get_signature(u32::from(index)).copied() } } /// Cached default linear memory bytes. #[derive(Debug)] pub struct CachedMemory { data: NonNull<[u8]>, } impl Default for CachedMemory { #[inline] fn default() -> Self { Self { data: NonNull::from(&mut []), } } } impl CachedMemory { /// Create a new [`CachedMemory`]. #[inline] fn new(ctx: &mut StoreInner, instance: &Memory) -> Self { let data = Self::load_default_memory(ctx, instance); Self { data } } /// Loads the default [`Memory`] of the currently used [`Instance`]. /// /// # Note /// /// Must be called whenever the heap allocation of the [`CachedMemory`] /// could have been changed and thus the cached pointer invalidated. /// /// # Panics /// /// If the currently used [`Instance`] does not have a default linear memory. /// /// [`Memory`]: crate::Memory #[inline] fn load_default_memory(ctx: &mut StoreInner, memory: &Memory) -> NonNull<[u8]> { ctx.resolve_memory_mut(memory).data_mut().into() } /// Returns a shared slice to the bytes of the cached default linear memory. /// /// # Safety /// /// The user is required to call [`CachedMemory::load_default_memory`] according to its specification. #[inline] pub unsafe fn data(&self) -> &[u8] { unsafe { self.data.as_ref() } } /// Returns an exclusive slice to the bytes of the cached default linear memory. /// /// # Safety /// /// The user is required to call [`CachedMemory::load_default_memory`] according to its specification. #[inline] pub unsafe fn data_mut(&mut self) -> &mut [u8] { unsafe { self.data.as_mut() } } } /// Cached default global variable value. #[derive(Debug)] pub struct CachedGlobal { // Dev. Note: we cannot use `NonNull` here, yet. // // The advantage is that we could safely use a static fallback value // which would be safer than using a null pointer since it would // only read or overwrite the fallback value instead of reading or // writing a null pointer which is UB. // // We cannot use `NonNull` because it requires pointers // to mutable statics which have just been allowed in Rust 1.78 but // not in Rust 1.77 which is Wasmi's MSRV. // // We can and should use `NonNull` here once we bump the MSRV. data: *mut UntypedVal, } impl Default for CachedGlobal { #[inline] fn default() -> Self { Self { data: ptr::null_mut(), } } } impl CachedGlobal { /// Create a new [`CachedGlobal`]. #[inline] fn new(ctx: &mut StoreInner, global: &Global) -> Self { let data = Self::load_global(ctx, global); Self { data } } /// Loads the default [`Global`] of the currently used [`Instance`]. /// /// # Note /// /// Must be called whenever the heap allocation of the [`CachedGlobal`] /// could have been changed and thus the cached pointer invalidated. /// /// # Panics /// /// If the currently used [`Instance`] does not have a default linear memory. /// /// [`Global`]: crate::Global #[inline] fn load_global(ctx: &mut StoreInner, global: &Global) -> *mut UntypedVal { ctx.resolve_global_mut(global).get_untyped_ptr().as_ptr() } /// Returns the value of the cached global variable. /// /// # Safety /// /// The user is required to call [`CachedGlobal::load_global`] according to its specification. #[inline] pub unsafe fn get(&self) -> UntypedVal { // SAFETY: This API guarantees to always write to a valid pointer // as long as `update` is called when needed by the user. unsafe { self.data.read() } } /// Sets the value of the cached global variable to `new_value`. /// /// # Safety /// /// The user is required to call [`CachedGlobal::load_global`] according to its specification. #[inline] pub unsafe fn set(&mut self, new_value: UntypedVal) { // SAFETY: This API guarantees to always write to a valid pointer // as long as `update` is called when needed by the user. unsafe { self.data.write(new_value) }; } } wasmi-1.1.0/src/engine/executor/instr_ptr.rs000064400000000000000000000044221046102023000172310ustar 00000000000000use crate::ir::Op; /// The instruction pointer to the instruction of a function on the call stack. #[derive(Debug, Copy, Clone)] pub struct InstructionPtr { /// The pointer to the instruction. ptr: *const Op, } /// It is safe to send an [`InstructionPtr`] to another thread. /// /// The access to the pointed-to [`Op`] is read-only and /// [`Op`] 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 Op) -> Self { Self { ptr } } /// 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) }; } #[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) }; } /// Returns a shared reference to the currently pointed at [`Op`]. /// /// # 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) -> &Op { // 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 } } } wasmi-1.1.0/src/engine/executor/instrs/binary.rs000064400000000000000000000327461046102023000200250ustar 00000000000000use super::{Executor, UntypedValueExt}; use crate::{ core::wasm, ir::{Const16, ShiftAmount, Sign, Slot}, Error, TrapCode, }; use core::num::{NonZeroI32, NonZeroI64, NonZeroU32, NonZeroU64}; #[cfg(doc)] use crate::ir::Op; impl Executor<'_> { impl_binary_executors! { (Op::I32Add, execute_i32_add, wasm::i32_add), (Op::I32Sub, execute_i32_sub, wasm::i32_sub), (Op::I32Mul, execute_i32_mul, wasm::i32_mul), (Op::I32BitAnd, execute_i32_bitand, wasm::i32_bitand), (Op::I32BitOr, execute_i32_bitor, wasm::i32_bitor), (Op::I32BitXor, execute_i32_bitxor, wasm::i32_bitxor), (Op::I32And, execute_i32_and, ::and), (Op::I32Or, execute_i32_or, ::or), (Op::I32Nand, execute_i32_nand, ::nand), (Op::I32Nor, execute_i32_nor, ::nor), (Op::I64Add, execute_i64_add, wasm::i64_add), (Op::I64Sub, execute_i64_sub, wasm::i64_sub), (Op::I64Mul, execute_i64_mul, wasm::i64_mul), (Op::I64BitAnd, execute_i64_bitand, wasm::i64_bitand), (Op::I64BitOr, execute_i64_bitor, wasm::i64_bitor), (Op::I64BitXor, execute_i64_bitxor, wasm::i64_bitxor), (Op::I64And, execute_i64_and, ::and), (Op::I64Or, execute_i64_or, ::or), (Op::I64Nand, execute_i64_nand, ::nand), (Op::I64Nor, execute_i64_nor, ::nor), (Op::I32Shl, execute_i32_shl, wasm::i32_shl), (Op::I32ShrU, execute_i32_shr_u, wasm::i32_shr_u), (Op::I32ShrS, execute_i32_shr_s, wasm::i32_shr_s), (Op::I32Rotl, execute_i32_rotl, wasm::i32_rotl), (Op::I32Rotr, execute_i32_rotr, wasm::i32_rotr), (Op::I64Shl, execute_i64_shl, wasm::i64_shl), (Op::I64ShrU, execute_i64_shr_u, wasm::i64_shr_u), (Op::I64ShrS, execute_i64_shr_s, wasm::i64_shr_s), (Op::I64Rotl, execute_i64_rotl, wasm::i64_rotl), (Op::I64Rotr, execute_i64_rotr, wasm::i64_rotr), (Op::F32Add, execute_f32_add, wasm::f32_add), (Op::F32Sub, execute_f32_sub, wasm::f32_sub), (Op::F32Mul, execute_f32_mul, wasm::f32_mul), (Op::F32Div, execute_f32_div, wasm::f32_div), (Op::F32Min, execute_f32_min, wasm::f32_min), (Op::F32Max, execute_f32_max, wasm::f32_max), (Op::F32Copysign, execute_f32_copysign, wasm::f32_copysign), (Op::F64Add, execute_f64_add, wasm::f64_add), (Op::F64Sub, execute_f64_sub, wasm::f64_sub), (Op::F64Mul, execute_f64_mul, wasm::f64_mul), (Op::F64Div, execute_f64_div, wasm::f64_div), (Op::F64Min, execute_f64_min, wasm::f64_min), (Op::F64Max, execute_f64_max, wasm::f64_max), (Op::F64Copysign, execute_f64_copysign, wasm::f64_copysign), } } macro_rules! impl_binary_imm16 { ( $( ($ty:ty, Op::$var_name:ident, $fn_name:ident, $op:expr) ),* $(,)? ) => { $( #[doc = concat!("Executes an [`Op::", stringify!($var_name), "`].")] pub fn $fn_name(&mut self, result: Slot, lhs: Slot, rhs: Const16<$ty>) { self.execute_binary_imm16_rhs(result, lhs, rhs, $op) } )* }; } impl Executor<'_> { impl_binary_imm16! { (i32, Op::I32AddImm16, execute_i32_add_imm16, wasm::i32_add), (i32, Op::I32MulImm16, execute_i32_mul_imm16, wasm::i32_mul), (i32, Op::I32BitAndImm16, execute_i32_bitand_imm16, wasm::i32_bitand), (i32, Op::I32BitOrImm16, execute_i32_bitor_imm16, wasm::i32_bitor), (i32, Op::I32BitXorImm16, execute_i32_bitxor_imm16, wasm::i32_bitxor), (i32, Op::I32AndImm16, execute_i32_and_imm16, ::and), (i32, Op::I32OrImm16, execute_i32_or_imm16, ::or), (i32, Op::I32NandImm16, execute_i32_nand_imm16, ::nand), (i32, Op::I32NorImm16, execute_i32_nor_imm16, ::nor), (i64, Op::I64AddImm16, execute_i64_add_imm16, wasm::i64_add), (i64, Op::I64MulImm16, execute_i64_mul_imm16, wasm::i64_mul), (i64, Op::I64BitAndImm16, execute_i64_bitand_imm16, wasm::i64_bitand), (i64, Op::I64BitOrImm16, execute_i64_bitor_imm16, wasm::i64_bitor), (i64, Op::I64BitXorImm16, execute_i64_bitxor_imm16, wasm::i64_bitxor), (i64, Op::I64AndImm16, execute_i64_and_imm16, ::and), (i64, Op::I64OrImm16, execute_i64_or_imm16, ::or), (i64, Op::I64NandImm16, execute_i64_nand_imm16, ::nand), (i64, Op::I64NorImm16, execute_i64_nor_imm16, ::nor), } } macro_rules! impl_shift_by { ( $( ($ty:ty, Op::$var_name:ident, $fn_name:ident, $op:expr) ),* $(,)? ) => { $( #[doc = concat!("Executes an [`Op::", stringify!($var_name), "`].")] pub fn $fn_name(&mut self, result: Slot, lhs: Slot, rhs: ShiftAmount<$ty>) { self.execute_shift_by(result, lhs, rhs, $op) } )* }; } impl Executor<'_> { impl_shift_by! { (i32, Op::I32ShlBy, execute_i32_shl_by, wasm::i32_shl), (i32, Op::I32ShrUBy, execute_i32_shr_u_by, wasm::i32_shr_u), (i32, Op::I32ShrSBy, execute_i32_shr_s_by, wasm::i32_shr_s), (i32, Op::I32RotlBy, execute_i32_rotl_by, wasm::i32_rotl), (i32, Op::I32RotrBy, execute_i32_rotr_by, wasm::i32_rotr), (i64, Op::I64ShlBy, execute_i64_shl_by, wasm::i64_shl), (i64, Op::I64ShrUBy, execute_i64_shr_u_by, wasm::i64_shr_u), (i64, Op::I64ShrSBy, execute_i64_shr_s_by, wasm::i64_shr_s), (i64, Op::I64RotlBy, execute_i64_rotl_by, wasm::i64_rotl), (i64, Op::I64RotrBy, execute_i64_rotr_by, wasm::i64_rotr), } } macro_rules! impl_binary_imm16_lhs { ( $( ($ty:ty, Op::$var_name:ident, $fn_name:ident, $op:expr) ),* $(,)? ) => { $( #[doc = concat!("Executes an [`Op::", stringify!($var_name), "`].")] pub fn $fn_name(&mut self, result: Slot, lhs: Const16<$ty>, rhs: Slot) { self.execute_binary_imm16_lhs(result, lhs, rhs, $op) } )* }; } impl Executor<'_> { impl_binary_imm16_lhs! { (i32, Op::I32SubImm16Lhs, execute_i32_sub_imm16_lhs, wasm::i32_sub), (i64, Op::I64SubImm16Lhs, execute_i64_sub_imm16_lhs, wasm::i64_sub), (i32, Op::I32ShlImm16, execute_i32_shl_imm16, wasm::i32_shl), (i32, Op::I32ShrUImm16, execute_i32_shr_u_imm16, wasm::i32_shr_u), (i32, Op::I32ShrSImm16, execute_i32_shr_s_imm16, wasm::i32_shr_s), (i32, Op::I32RotlImm16, execute_i32_rotl_imm16, wasm::i32_rotl), (i32, Op::I32RotrImm16, execute_i32_rotr_imm16, wasm::i32_rotr), (i64, Op::I64ShlImm16, execute_i64_shl_imm16, wasm::i64_shl), (i64, Op::I64ShrUImm16, execute_i64_shr_u_imm16, wasm::i64_shr_u), (i64, Op::I64ShrSImm16, execute_i64_shr_s_imm16, wasm::i64_shr_s), (i64, Op::I64RotlImm16, execute_i64_rotl_imm16, wasm::i64_rotl), (i64, Op::I64RotrImm16, execute_i64_rotr_imm16, wasm::i64_rotr), } } macro_rules! impl_fallible_binary { ( $( (Op::$var_name:ident, $fn_name:ident, $op:expr) ),* $(,)? ) => { $( #[doc = concat!("Executes an [`Op::", stringify!($var_name), "`].")] pub fn $fn_name(&mut self, result: Slot, lhs: Slot, rhs: Slot) -> Result<(), Error> { self.try_execute_binary(result, lhs, rhs, $op).map_err(Error::from) } )* }; } impl Executor<'_> { impl_fallible_binary! { (Op::I32DivS, execute_i32_div_s, wasm::i32_div_s), (Op::I32DivU, execute_i32_div_u, wasm::i32_div_u), (Op::I32RemS, execute_i32_rem_s, wasm::i32_rem_s), (Op::I32RemU, execute_i32_rem_u, wasm::i32_rem_u), (Op::I64DivS, execute_i64_div_s, wasm::i64_div_s), (Op::I64DivU, execute_i64_div_u, wasm::i64_div_u), (Op::I64RemS, execute_i64_rem_s, wasm::i64_rem_s), (Op::I64RemU, execute_i64_rem_u, wasm::i64_rem_u), } } /// Extension trait to provide more optimized divide and remainder implementations. pub trait DivRemExt: Sized { /// Signed non-zero value type. type NonZeroS; /// Unsigned non-zero value type. type NonZeroU; /// Optimized variant of Wasm `i{32,64}.div_s` for immutable non-zero `rhs` values. fn div_s(self, rhs: Self::NonZeroS) -> Result; /// Optimized variant of Wasm `i{32,64}.div_u` for immutable non-zero `rhs` values. fn div_u(self, rhs: Self::NonZeroU) -> Self; /// Optimized variant of Wasm `i{32,64}.rem_s` for immutable non-zero `rhs` values. fn rem_s(self, rhs: Self::NonZeroS) -> Result; /// Optimized variant of Wasm `i{32,64}.rem_u` for immutable non-zero `rhs` values. fn rem_u(self, rhs: Self::NonZeroU) -> Self; } impl DivRemExt for i32 { type NonZeroS = NonZeroI32; type NonZeroU = NonZeroU32; fn div_s(self, rhs: Self::NonZeroS) -> Result { self.checked_div(rhs.get()) .ok_or_else(|| Error::from(TrapCode::IntegerOverflow)) } fn div_u(self, rhs: Self::NonZeroU) -> Self { ((self as u32) / rhs) as Self } fn rem_s(self, rhs: Self::NonZeroS) -> Result { wasm::i32_rem_s(self, rhs.get()).map_err(Error::from) } fn rem_u(self, rhs: Self::NonZeroU) -> Self { ((self as u32) % rhs) as Self } } impl DivRemExt for i64 { type NonZeroS = NonZeroI64; type NonZeroU = NonZeroU64; fn div_s(self, rhs: Self::NonZeroS) -> Result { self.checked_div(rhs.get()) .ok_or_else(|| Error::from(TrapCode::IntegerOverflow)) } fn div_u(self, rhs: Self::NonZeroU) -> Self { ((self as u64) / rhs) as Self } fn rem_s(self, rhs: Self::NonZeroS) -> Result { wasm::i64_rem_s(self, rhs.get()).map_err(Error::from) } fn rem_u(self, rhs: Self::NonZeroU) -> Self { ((self as u64) % rhs) as Self } } macro_rules! impl_divrem_s_imm16_rhs { ( $( ($ty:ty, Op::$var_name:ident, $fn_name:ident, $op:expr) ),* $(,)? ) => { $( #[doc = concat!("Executes an [`Op::", stringify!($var_name), "`].")] pub fn $fn_name(&mut self, result: Slot, lhs: Slot, rhs: Const16<$ty>) -> Result<(), Error> { self.try_execute_divrem_imm16_rhs(result, lhs, rhs, $op) } )* }; } impl Executor<'_> { impl_divrem_s_imm16_rhs! { (NonZeroI32, Op::I32DivSImm16Rhs, execute_i32_div_s_imm16_rhs, ::div_s), (NonZeroI32, Op::I32RemSImm16Rhs, execute_i32_rem_s_imm16_rhs, ::rem_s), (NonZeroI64, Op::I64DivSImm16Rhs, execute_i64_div_s_imm16_rhs, ::div_s), (NonZeroI64, Op::I64RemSImm16Rhs, execute_i64_rem_s_imm16_rhs, ::rem_s), } } macro_rules! impl_divrem_u_imm16_rhs { ( $( ($ty:ty, Op::$var_name:ident, $fn_name:ident, $op:expr) ),* $(,)? ) => { $( #[doc = concat!("Executes an [`Op::", stringify!($var_name), "`].")] pub fn $fn_name(&mut self, result: Slot, lhs: Slot, rhs: Const16<$ty>) { self.execute_divrem_imm16_rhs(result, lhs, rhs, $op) } )* }; } impl Executor<'_> { impl_divrem_u_imm16_rhs! { (NonZeroU32, Op::I32DivUImm16Rhs, execute_i32_div_u_imm16_rhs, ::div_u), (NonZeroU32, Op::I32RemUImm16Rhs, execute_i32_rem_u_imm16_rhs, ::rem_u), (NonZeroU64, Op::I64DivUImm16Rhs, execute_i64_div_u_imm16_rhs, ::div_u), (NonZeroU64, Op::I64RemUImm16Rhs, execute_i64_rem_u_imm16_rhs, ::rem_u), } } macro_rules! impl_fallible_binary_imm16_lhs { ( $( ($ty:ty, Op::$var_name:ident, $fn_name:ident, $op:expr) ),* $(,)? ) => { $( #[doc = concat!("Executes an [`Op::", stringify!($var_name), "`].")] pub fn $fn_name(&mut self, result: Slot, lhs: Const16<$ty>, rhs: Slot) -> Result<(), Error> { self.try_execute_binary_imm16_lhs(result, lhs, rhs, $op).map_err(Error::from) } )* }; } impl Executor<'_> { impl_fallible_binary_imm16_lhs! { (i32, Op::I32DivSImm16Lhs, execute_i32_div_s_imm16_lhs, wasm::i32_div_s), (u32, Op::I32DivUImm16Lhs, execute_i32_div_u_imm16_lhs, wasm::i32_div_u), (i32, Op::I32RemSImm16Lhs, execute_i32_rem_s_imm16_lhs, wasm::i32_rem_s), (u32, Op::I32RemUImm16Lhs, execute_i32_rem_u_imm16_lhs, wasm::i32_rem_u), (i64, Op::I64DivSImm16Lhs, execute_i64_div_s_imm16_lhs, wasm::i64_div_s), (u64, Op::I64DivUImm16Lhs, execute_i64_div_u_imm16_lhs, wasm::i64_div_u), (i64, Op::I64RemSImm16Lhs, execute_i64_rem_s_imm16_lhs, wasm::i64_rem_s), (u64, Op::I64RemUImm16Lhs, execute_i64_rem_u_imm16_lhs, wasm::i64_rem_u), } } impl Executor<'_> { /// Executes an [`Op::F32CopysignImm`]. pub fn execute_f32_copysign_imm(&mut self, result: Slot, lhs: Slot, rhs: Sign) { let lhs = self.get_stack_slot_as::(lhs); let rhs = f32::from(rhs); self.set_stack_slot_as::(result, wasm::f32_copysign(lhs, rhs)); self.next_instr() } /// Executes an [`Op::F64CopysignImm`]. pub fn execute_f64_copysign_imm(&mut self, result: Slot, lhs: Slot, rhs: Sign) { let lhs = self.get_stack_slot_as::(lhs); let rhs = f64::from(rhs); self.set_stack_slot_as::(result, wasm::f64_copysign(lhs, rhs)); self.next_instr() } } wasmi-1.1.0/src/engine/executor/instrs/branch.rs000064400000000000000000000342301046102023000177640ustar 00000000000000use super::{Executor, UntypedValueCmpExt, UntypedValueExt}; use crate::{ core::{ReadAs, UntypedVal}, engine::utils::unreachable_unchecked, ir::{BranchOffset, BranchOffset16, Comparator, ComparatorAndOffset, Const16, Op, Slot}, }; use core::cmp; impl Executor<'_> { /// Branches and adjusts the value stack. /// /// # Note /// /// Offsets the instruction pointer using the given [`BranchOffset`]. 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`]. fn branch_to16(&mut self, offset: BranchOffset16) { self.ip.offset(offset.to_i16() as isize) } pub fn execute_branch(&mut self, offset: BranchOffset) { self.branch_to(offset) } /// Fetches the branch table index value and normalizes it to clamp between `0..len_targets`. fn fetch_branch_table_offset(&self, index: Slot, len_targets: u32) -> usize { let index: u32 = self.get_stack_slot_as::(index); // The index of the default target which is the last target of the slice. let max_index = len_targets - 1; // A normalized index will always yield a target without panicking. cmp::min(index, max_index) as usize + 1 } pub fn execute_branch_table_0(&mut self, index: Slot, len_targets: u32) { let offset = self.fetch_branch_table_offset(index, len_targets); self.ip.add(offset); } pub fn execute_branch_table_span(&mut self, index: Slot, len_targets: u32) { let offset = self.fetch_branch_table_offset(index, len_targets); self.ip.add(1); let values = match *self.ip.get() { Op::SlotSpan { span } => span, unexpected => { // Safety: Wasmi translation guarantees that `Op::SlotSpan` follows. unsafe { unreachable_unchecked!("expected `Op::SlotSpan` but found: {unexpected:?}") } } }; let len = values.len(); let values = values.span(); self.ip.add(offset); match *self.ip.get() { // Note: we explicitly do _not_ handle branch table returns here for technical reasons. // They are executed as the next conventional instruction in the pipeline, no special treatment required. Op::BranchTableTarget { results, offset } => { self.execute_copy_span_impl(results, values, len); self.execute_branch(offset) } unexpected => { // Safety: Wasmi translator guarantees that one of the above `Op` variants exists. unsafe { unreachable_unchecked!( "expected target for `Op::BranchTableSpan` but found: {unexpected:?}" ) } } } } /// Executes a generic fused compare and branch instruction with raw inputs. #[inline(always)] fn execute_branch_binop( &mut self, lhs: Slot, rhs: Slot, offset: impl Into, f: fn(T, T) -> bool, ) where UntypedVal: ReadAs, { let lhs: T = self.get_stack_slot_as(lhs); let rhs: T = self.get_stack_slot_as(rhs); if f(lhs, rhs) { return self.branch_to(offset.into()); } self.next_instr() } /// Executes a generic fused compare and branch instruction with immediate `rhs` operand. fn execute_branch_binop_imm16_rhs( &mut self, lhs: Slot, rhs: Const16, offset: BranchOffset16, f: fn(T, T) -> bool, ) where T: From>, UntypedVal: ReadAs, { let lhs: T = self.get_stack_slot_as(lhs); let rhs = T::from(rhs); if f(lhs, rhs) { return self.branch_to16(offset); } self.next_instr() } /// Executes a generic fused compare and branch instruction with immediate `rhs` operand. fn execute_branch_binop_imm16_lhs( &mut self, lhs: Const16, rhs: Slot, offset: BranchOffset16, f: fn(T, T) -> bool, ) where T: From>, UntypedVal: ReadAs, { let lhs = T::from(lhs); let rhs: T = self.get_stack_slot_as(rhs); if f(lhs, rhs) { return self.branch_to16(offset); } self.next_instr() } } fn cmp_eq(a: T, b: T) -> bool where T: PartialEq, { a == b } fn cmp_ne(a: T, b: T) -> bool where T: PartialEq, { a != b } fn cmp_lt(a: T, b: T) -> bool where T: PartialOrd, { a < b } fn cmp_le(a: T, b: T) -> bool where T: PartialOrd, { a <= b } macro_rules! impl_execute_branch_binop { ( $( ($ty:ty, Op::$op_name:ident, $fn_name:ident, $op:expr) ),* $(,)? ) => { impl<'engine> Executor<'engine> { $( #[doc = concat!("Executes an [`Op::", stringify!($op_name), "`].")] #[inline(always)] pub fn $fn_name(&mut self, lhs: Slot, rhs: Slot, offset: BranchOffset16) { self.execute_branch_binop::<$ty>(lhs, rhs, offset, $op) } )* } } } impl_execute_branch_binop! { (i32, Op::BranchI32And, execute_branch_i32_and, UntypedValueExt::and), (i32, Op::BranchI32Or, execute_branch_i32_or, UntypedValueExt::or), (i32, Op::BranchI32Nand, execute_branch_i32_nand, UntypedValueExt::nand), (i32, Op::BranchI32Nor, execute_branch_i32_nor, UntypedValueExt::nor), (i32, Op::BranchI32Eq, execute_branch_i32_eq, cmp_eq), (i32, Op::BranchI32Ne, execute_branch_i32_ne, cmp_ne), (i32, Op::BranchI32LtS, execute_branch_i32_lt_s, cmp_lt), (u32, Op::BranchI32LtU, execute_branch_i32_lt_u, cmp_lt), (i32, Op::BranchI32LeS, execute_branch_i32_le_s, cmp_le), (u32, Op::BranchI32LeU, execute_branch_i32_le_u, cmp_le), (i64, Op::BranchI64And, execute_branch_i64_and, UntypedValueExt::and), (i64, Op::BranchI64Or, execute_branch_i64_or, UntypedValueExt::or), (i64, Op::BranchI64Nand, execute_branch_i64_nand, UntypedValueExt::nand), (i64, Op::BranchI64Nor, execute_branch_i64_nor, UntypedValueExt::nor), (i64, Op::BranchI64Eq, execute_branch_i64_eq, cmp_eq), (i64, Op::BranchI64Ne, execute_branch_i64_ne, cmp_ne), (i64, Op::BranchI64LtS, execute_branch_i64_lt_s, cmp_lt), (u64, Op::BranchI64LtU, execute_branch_i64_lt_u, cmp_lt), (i64, Op::BranchI64LeS, execute_branch_i64_le_s, cmp_le), (u64, Op::BranchI64LeU, execute_branch_i64_le_u, cmp_le), (f32, Op::BranchF32Eq, execute_branch_f32_eq, cmp_eq), (f32, Op::BranchF32Ne, execute_branch_f32_ne, cmp_ne), (f32, Op::BranchF32Lt, execute_branch_f32_lt, cmp_lt), (f32, Op::BranchF32Le, execute_branch_f32_le, cmp_le), (f32, Op::BranchF32NotLt, execute_branch_f32_not_lt, UntypedValueCmpExt::not_lt), (f32, Op::BranchF32NotLe, execute_branch_f32_not_le, UntypedValueCmpExt::not_le), (f64, Op::BranchF64Eq, execute_branch_f64_eq, cmp_eq), (f64, Op::BranchF64Ne, execute_branch_f64_ne, cmp_ne), (f64, Op::BranchF64Lt, execute_branch_f64_lt, cmp_lt), (f64, Op::BranchF64Le, execute_branch_f64_le, cmp_le), (f64, Op::BranchF64NotLt, execute_branch_f64_not_lt, UntypedValueCmpExt::not_lt), (f64, Op::BranchF64NotLe, execute_branch_f64_not_le, UntypedValueCmpExt::not_le), } macro_rules! impl_execute_branch_binop_imm16_rhs { ( $( ($ty:ty, Op::$op_name:ident, $fn_name:ident, $op:expr) ),* $(,)? ) => { impl<'engine> Executor<'engine> { $( #[doc = concat!("Executes an [`Op::", stringify!($op_name), "`].")] pub fn $fn_name(&mut self, lhs: Slot, rhs: Const16<$ty>, offset: BranchOffset16) { self.execute_branch_binop_imm16_rhs::<$ty>(lhs, rhs, offset, $op) } )* } } } impl_execute_branch_binop_imm16_rhs! { (i32, Op::BranchI32AndImm16, execute_branch_i32_and_imm16, UntypedValueExt::and), (i32, Op::BranchI32OrImm16, execute_branch_i32_or_imm16, UntypedValueExt::or), (i32, Op::BranchI32NandImm16, execute_branch_i32_nand_imm16, UntypedValueExt::nand), (i32, Op::BranchI32NorImm16, execute_branch_i32_nor_imm16, UntypedValueExt::nor), (i32, Op::BranchI32EqImm16, execute_branch_i32_eq_imm16, cmp_eq), (i32, Op::BranchI32NeImm16, execute_branch_i32_ne_imm16, cmp_ne), (i32, Op::BranchI32LtSImm16Rhs, execute_branch_i32_lt_s_imm16_rhs, cmp_lt), (u32, Op::BranchI32LtUImm16Rhs, execute_branch_i32_lt_u_imm16_rhs, cmp_lt), (i32, Op::BranchI32LeSImm16Rhs, execute_branch_i32_le_s_imm16_rhs, cmp_le), (u32, Op::BranchI32LeUImm16Rhs, execute_branch_i32_le_u_imm16_rhs, cmp_le), (i64, Op::BranchI64AndImm16, execute_branch_i64_and_imm16, UntypedValueExt::and), (i64, Op::BranchI64OrImm16, execute_branch_i64_or_imm16, UntypedValueExt::or), (i64, Op::BranchI64NandImm16, execute_branch_i64_nand_imm16, UntypedValueExt::nand), (i64, Op::BranchI64NorImm16, execute_branch_i64_nor_imm16, UntypedValueExt::nor), (i64, Op::BranchI64EqImm16, execute_branch_i64_eq_imm16, cmp_eq), (i64, Op::BranchI64NeImm16, execute_branch_i64_ne_imm16, cmp_ne), (i64, Op::BranchI64LtSImm16Rhs, execute_branch_i64_lt_s_imm16_rhs, cmp_lt), (u64, Op::BranchI64LtUImm16Rhs, execute_branch_i64_lt_u_imm16_rhs, cmp_lt), (i64, Op::BranchI64LeSImm16Rhs, execute_branch_i64_le_s_imm16_rhs, cmp_le), (u64, Op::BranchI64LeUImm16Rhs, execute_branch_i64_le_u_imm16_rhs, cmp_le), } macro_rules! impl_execute_branch_binop_imm16_lhs { ( $( ($ty:ty, Op::$op_name:ident, $fn_name:ident, $op:expr) ),* $(,)? ) => { impl<'engine> Executor<'engine> { $( #[doc = concat!("Executes an [`Op::", stringify!($op_name), "`].")] pub fn $fn_name(&mut self, lhs: Const16<$ty>, rhs: Slot, offset: BranchOffset16) { self.execute_branch_binop_imm16_lhs::<$ty>(lhs, rhs, offset, $op) } )* } } } impl_execute_branch_binop_imm16_lhs! { (i32, Op::BranchI32LtSImm16Lhs, execute_branch_i32_lt_s_imm16_lhs, cmp_lt), (u32, Op::BranchI32LtUImm16Lhs, execute_branch_i32_lt_u_imm16_lhs, cmp_lt), (i32, Op::BranchI32LeSImm16Lhs, execute_branch_i32_le_s_imm16_lhs, cmp_le), (u32, Op::BranchI32LeUImm16Lhs, execute_branch_i32_le_u_imm16_lhs, cmp_le), (i64, Op::BranchI64LtSImm16Lhs, execute_branch_i64_lt_s_imm16_lhs, cmp_lt), (u64, Op::BranchI64LtUImm16Lhs, execute_branch_i64_lt_u_imm16_lhs, cmp_lt), (i64, Op::BranchI64LeSImm16Lhs, execute_branch_i64_le_s_imm16_lhs, cmp_le), (u64, Op::BranchI64LeUImm16Lhs, execute_branch_i64_le_u_imm16_lhs, cmp_le), } impl Executor<'_> { /// Executes an [`Op::BranchCmpFallback`]. pub fn execute_branch_cmp_fallback(&mut self, lhs: Slot, rhs: Slot, params: Slot) { use Comparator as C; let params: u64 = self.get_stack_slot_as(params); let Some(params) = ComparatorAndOffset::from_u64(params) else { panic!("encountered invalidaly encoded ComparatorOffsetParam: {params:?}") }; let offset = params.offset; match params.cmp { C::I32Eq => self.execute_branch_binop::(lhs, rhs, offset, cmp_eq), C::I32Ne => self.execute_branch_binop::(lhs, rhs, offset, cmp_ne), C::I32LtS => self.execute_branch_binop::(lhs, rhs, offset, cmp_lt), C::I32LtU => self.execute_branch_binop::(lhs, rhs, offset, cmp_lt), C::I32LeS => self.execute_branch_binop::(lhs, rhs, offset, cmp_le), C::I32LeU => self.execute_branch_binop::(lhs, rhs, offset, cmp_le), C::I32And => self.execute_branch_binop::(lhs, rhs, offset, UntypedValueExt::and), C::I32Or => self.execute_branch_binop::(lhs, rhs, offset, UntypedValueExt::or), C::I32Nand => self.execute_branch_binop::(lhs, rhs, offset, UntypedValueExt::nand), C::I32Nor => self.execute_branch_binop::(lhs, rhs, offset, UntypedValueExt::nor), C::I64Eq => self.execute_branch_binop::(lhs, rhs, offset, cmp_eq), C::I64Ne => self.execute_branch_binop::(lhs, rhs, offset, cmp_ne), C::I64LtS => self.execute_branch_binop::(lhs, rhs, offset, cmp_lt), C::I64LtU => self.execute_branch_binop::(lhs, rhs, offset, cmp_lt), C::I64LeS => self.execute_branch_binop::(lhs, rhs, offset, cmp_le), C::I64LeU => self.execute_branch_binop::(lhs, rhs, offset, cmp_le), C::I64And => self.execute_branch_binop::(lhs, rhs, offset, UntypedValueExt::and), C::I64Or => self.execute_branch_binop::(lhs, rhs, offset, UntypedValueExt::or), C::I64Nand => self.execute_branch_binop::(lhs, rhs, offset, UntypedValueExt::nand), C::I64Nor => self.execute_branch_binop::(lhs, rhs, offset, UntypedValueExt::nor), C::F32Eq => self.execute_branch_binop::(lhs, rhs, offset, cmp_eq), C::F32Ne => self.execute_branch_binop::(lhs, rhs, offset, cmp_ne), C::F32Lt => self.execute_branch_binop::(lhs, rhs, offset, cmp_lt), C::F32Le => self.execute_branch_binop::(lhs, rhs, offset, cmp_le), C::F32NotLt => { self.execute_branch_binop::(lhs, rhs, offset, UntypedValueCmpExt::not_lt) } C::F32NotLe => { self.execute_branch_binop::(lhs, rhs, offset, UntypedValueCmpExt::not_le) } C::F64Eq => self.execute_branch_binop::(lhs, rhs, offset, cmp_eq), C::F64Ne => self.execute_branch_binop::(lhs, rhs, offset, cmp_ne), C::F64Lt => self.execute_branch_binop::(lhs, rhs, offset, cmp_lt), C::F64Le => self.execute_branch_binop::(lhs, rhs, offset, cmp_le), C::F64NotLt => { self.execute_branch_binop::(lhs, rhs, offset, UntypedValueCmpExt::not_lt) } C::F64NotLe => { self.execute_branch_binop::(lhs, rhs, offset, UntypedValueCmpExt::not_le) } }; } } wasmi-1.1.0/src/engine/executor/instrs/call.rs000064400000000000000000000641431046102023000174500ustar 00000000000000use super::{ControlFlow, Executor, InstructionPtr}; use crate::{ engine::{ code_map::CompiledFuncRef, executor::stack::{CallFrame, FrameParams, ValueStack}, utils::unreachable_unchecked, EngineFunc, FuncInOut, ResumableHostTrapError, }, func::{FuncEntity, HostFuncEntity}, ir::{index, Op, Slot, SlotSpan}, store::{CallHooks, PrunedStore, StoreError, StoreInner}, Error, Func, Instance, Ref, TrapCode, }; use core::array; /// Dispatches and executes the host function. /// /// Returns the number of parameters and results of the called host function. /// /// # Errors /// /// Returns the error of the host function if an error occurred. pub fn dispatch_host_func( store: &mut PrunedStore, value_stack: &mut ValueStack, host_func: HostFuncEntity, instance: Option<&Instance>, call_hooks: CallHooks, ) -> Result<(u16, u16), Error> { let len_params = host_func.len_params(); let len_results = host_func.len_results(); let max_inout = len_params.max(len_results); let values = value_stack.as_slice_mut(); let params_results = FuncInOut::new( values.split_at_mut(values.len() - usize::from(max_inout)).1, usize::from(len_params), usize::from(len_results), ); match store.call_host_func(&host_func, instance, params_results, call_hooks) { Err(StoreError::Internal(error)) => { panic!("`call.host`: internal interpreter error: {error}") } Err(StoreError::External(error)) => { // Note: We drop the values that have been temporarily added to // the stack to act as parameter and result buffer for the // called host function. Since the host function failed we // need to clean up the temporary buffer values here. // This is required for resumable calls to work properly. value_stack.drop(usize::from(max_inout)); return Err(error); } _ => {} } Ok((len_params, len_results)) } /// The kind of a function call. #[derive(Debug, Copy, Clone)] pub enum CallKind { /// A nested function call. Nested, /// A tailing function call. Tail, } trait CallContext { const KIND: CallKind; const HAS_PARAMS: bool; } trait ReturnCallContext: CallContext {} mod marker { use super::{CallContext, CallKind, ReturnCallContext}; pub enum ReturnCall0 {} impl CallContext for ReturnCall0 { const KIND: CallKind = CallKind::Tail; const HAS_PARAMS: bool = false; } impl ReturnCallContext for ReturnCall0 {} pub enum ReturnCall {} impl CallContext for ReturnCall { const KIND: CallKind = CallKind::Tail; const HAS_PARAMS: bool = true; } impl ReturnCallContext for ReturnCall {} pub enum NestedCall0 {} impl CallContext for NestedCall0 { const KIND: CallKind = CallKind::Nested; const HAS_PARAMS: bool = false; } pub enum NestedCall {} impl CallContext for NestedCall { const KIND: CallKind = CallKind::Nested; const HAS_PARAMS: bool = true; } } impl Executor<'_> { /// Updates the [`InstructionPtr`] of the caller [`CallFrame`] before dispatching a call. /// /// # Note /// /// The `offset` denotes how many [`Op`] words make up the call instruction. #[inline(always)] fn update_instr_ptr_at(&mut self, offset: usize) { // Note: we explicitly do not mutate `self.ip` since that would make // other parts of the code more fragile with respect to instruction ordering. self.ip.add(offset); let caller = self .stack .calls .peek_mut() .expect("caller call frame must be on the stack"); caller.update_instr_ptr(self.ip); } /// Fetches the [`Op::CallIndirectParams`] parameter for a call [`Op`]. /// /// # Note /// /// - This advances the [`InstructionPtr`] to the next [`Op`]. /// - This is done by encoding an [`Op::TableGet`] instruction /// word following the actual instruction where the [`index::Table`] /// parameter belongs to. /// - This is required for some instructions that do not fit into /// a single instruction word and store a [`index::Table`] value in /// another instruction word. fn pull_call_indirect_params(&mut self) -> (u64, index::Table) { self.ip.add(1); match *self.ip.get() { Op::CallIndirectParams { index, table } => { let index: u64 = self.get_stack_slot_as(index); (index, table) } unexpected => { // Safety: Wasmi translation guarantees that correct instruction parameter follows. unsafe { unreachable_unchecked!( "expected `Op::CallIndirectParams` but found {unexpected:?}" ) } } } } /// Fetches the [`Op::CallIndirectParamsImm16`] parameter for a call [`Op`]. /// /// # Note /// /// - This advances the [`InstructionPtr`] to the next [`Op`]. /// - This is done by encoding an [`Op::TableGet`] instruction /// word following the actual instruction where the [`index::Table`] /// parameter belongs to. /// - This is required for some instructions that do not fit into /// a single instruction word and store a [`index::Table`] value in /// another instruction word. fn pull_call_indirect_params_imm16(&mut self) -> (u64, index::Table) { self.ip.add(1); match *self.ip.get() { Op::CallIndirectParamsImm16 { index, table } => { let index: u64 = index.into(); (index, table) } unexpected => { // Safety: Wasmi translation guarantees that correct instruction parameter follows. unsafe { unreachable_unchecked!( "expected `Op::CallIndirectParamsImm16` but found {unexpected:?}" ) } } } } /// Creates a [`CallFrame`] for calling the [`EngineFunc`]. #[inline(always)] fn dispatch_compiled_func( &mut self, results: SlotSpan, func: CompiledFuncRef, ) -> Result { // We have to reinstantiate the `self.sp` [`FrameSlots`] since we just called // [`ValueStack::alloc_call_frame`] which might invalidate all live [`FrameSlots`]. let caller = self .stack .calls .peek() .expect("need to have a caller on the call stack"); let (mut uninit_params, offsets) = self.stack.values.alloc_call_frame(func, |this| { // Safety: We use the base offset of a live call frame on the call stack. self.sp = unsafe { this.stack_ptr_at(caller.base_offset()) }; })?; let instr_ptr = InstructionPtr::new(func.instrs().as_ptr()); let frame = CallFrame::new(instr_ptr, offsets, results); if ::HAS_PARAMS { self.copy_call_params(&mut uninit_params); } uninit_params.init_zeroes(); Ok(frame) } /// Copies the parameters from caller for the callee [`CallFrame`]. /// /// This will also adjust the instruction pointer to point to the /// last call parameter [`Op`] if any. fn copy_call_params(&mut self, uninit_params: &mut FrameParams) { self.ip.add(1); if let Op::SlotList { .. } = self.ip.get() { self.copy_call_params_list(uninit_params); } match self.ip.get() { Op::Slot { slot } => { self.copy_regs(uninit_params, array::from_ref(slot)); } Op::Slot2 { slots } => { self.copy_regs(uninit_params, slots); } Op::Slot3 { slots } => { self.copy_regs(uninit_params, slots); } unexpected => { // Safety: Wasmi translation guarantees that register list finalizer exists. unsafe { unreachable_unchecked!( "expected register-list finalizer but found: {unexpected:?}" ) } } } } /// Copies an array of [`Slot`] to the `dst` [`Slot`] span. fn copy_regs(&self, uninit_params: &mut FrameParams, regs: &[Slot; N]) { for value in regs { let value = self.get_stack_slot(*value); // Safety: The `callee.results()` always refer to a span of valid // registers of the `caller` that does not overlap with the // registers of the callee since they reside in different // call frames. Therefore this access is safe. unsafe { uninit_params.init_next(value) } } } /// Copies a list of [`Op::SlotList`] to the `dst` [`Slot`] span. /// Copies the parameters from `src` for the called [`CallFrame`]. /// /// This will make the [`InstructionPtr`] point to the [`Op`] following the /// last [`Op::SlotList`] if any. #[cold] fn copy_call_params_list(&mut self, uninit_params: &mut FrameParams) { while let Op::SlotList { regs } = self.ip.get() { self.copy_regs(uninit_params, regs); self.ip.add(1); } } /// Prepares a [`EngineFunc`] call with optional call parameters. #[inline(always)] fn prepare_compiled_func_call( &mut self, store: &mut StoreInner, results: SlotSpan, func: EngineFunc, mut instance: Option, ) -> Result<(), Error> { let func = self.code_map.get(Some(store.fuel_mut()), func)?; let mut called = self.dispatch_compiled_func::(results, func)?; match ::KIND { CallKind::Nested => { // We need to update the instruction pointer of the caller call frame. self.update_instr_ptr_at(1); } CallKind::Tail => { // In case of a tail call we have to remove the caller call frame after // allocating the callee call frame. This moves all cells of the callee frame // and may invalidate pointers to it. // // Safety: // // We provide `merge_call_frames` properly with `frame` that has just been allocated // on the value stack which is what the function expects. After this operation we ensure // that `self.sp` is adjusted via a call to `init_call_frame` since it may have been // invalidated by this method. let caller_instance = unsafe { self.stack.merge_call_frames(&mut called) }; if let Some(caller_instance) = caller_instance { instance.get_or_insert(caller_instance); } } } self.init_call_frame(&called); self.stack.calls.push(called, instance)?; Ok(()) } /// Executes an [`Op::ReturnCallInternal0`]. #[inline(always)] pub fn execute_return_call_internal_0( &mut self, store: &mut StoreInner, func: EngineFunc, ) -> Result<(), Error> { self.execute_return_call_internal_impl::(store, func) } /// Executes an [`Op::ReturnCallInternal`]. #[inline(always)] pub fn execute_return_call_internal( &mut self, store: &mut StoreInner, func: EngineFunc, ) -> Result<(), Error> { self.execute_return_call_internal_impl::(store, func) } /// Executes an [`Op::ReturnCallInternal`] or [`Op::ReturnCallInternal0`]. #[inline(always)] fn execute_return_call_internal_impl( &mut self, store: &mut StoreInner, func: EngineFunc, ) -> Result<(), Error> { let results = self.caller_results(); self.prepare_compiled_func_call::(store, results, func, None) } /// Returns the `results` [`SlotSpan`] of the top-most [`CallFrame`] on the [`CallStack`]. /// /// # Note /// /// We refer to the top-most [`CallFrame`] as the `caller` since this method is used for /// tail call instructions for which the top-most [`CallFrame`] is the caller. /// /// [`CallStack`]: crate::engine::executor::stack::CallStack #[inline(always)] fn caller_results(&self) -> SlotSpan { self.stack .calls .peek() .expect("must have caller on the stack") .results() } /// Executes an [`Op::CallInternal0`]. #[inline(always)] pub fn execute_call_internal_0( &mut self, store: &mut StoreInner, results: SlotSpan, func: EngineFunc, ) -> Result<(), Error> { self.prepare_compiled_func_call::(store, results, func, None) } /// Executes an [`Op::CallInternal`]. #[inline(always)] pub fn execute_call_internal( &mut self, store: &mut StoreInner, results: SlotSpan, func: EngineFunc, ) -> Result<(), Error> { self.prepare_compiled_func_call::(store, results, func, None) } /// Executes an [`Op::ReturnCallImported0`]. pub fn execute_return_call_imported_0( &mut self, store: &mut PrunedStore, func: index::Func, ) -> Result { self.execute_return_call_imported_impl::(store, func) } /// Executes an [`Op::ReturnCallImported`]. pub fn execute_return_call_imported( &mut self, store: &mut PrunedStore, func: index::Func, ) -> Result { self.execute_return_call_imported_impl::(store, func) } /// Executes an [`Op::ReturnCallImported`] or [`Op::ReturnCallImported0`]. fn execute_return_call_imported_impl( &mut self, store: &mut PrunedStore, func: index::Func, ) -> Result { let func = self.get_func(func); self.execute_call_imported_impl::(store, None, &func) } /// Executes an [`Op::CallImported0`]. pub fn execute_call_imported_0( &mut self, store: &mut PrunedStore, results: SlotSpan, func: index::Func, ) -> Result<(), Error> { let func = self.get_func(func); _ = self.execute_call_imported_impl::(store, Some(results), &func)?; Ok(()) } /// Executes an [`Op::CallImported`]. pub fn execute_call_imported( &mut self, store: &mut PrunedStore, results: SlotSpan, func: index::Func, ) -> Result<(), Error> { let func = self.get_func(func); _ = self.execute_call_imported_impl::(store, Some(results), &func)?; Ok(()) } /// Executes an imported or indirect (tail) call instruction. fn execute_call_imported_impl( &mut self, store: &mut PrunedStore, results: Option, func: &Func, ) -> Result { match store.inner().resolve_func(func) { FuncEntity::Wasm(func) => { let instance = *func.instance(); let func_body = func.func_body(); let results = results.unwrap_or_else(|| self.caller_results()); self.prepare_compiled_func_call::( store.inner_mut(), results, func_body, Some(instance), )?; self.cache.update(store.inner_mut(), &instance); Ok(ControlFlow::Continue(())) } FuncEntity::Host(host_func) => { let host_func = *host_func; self.execute_host_func::(store, results, func, host_func) } } } /// Executes a host function. /// /// # Note /// /// This uses the value stack to store parameters and results of the host function call. /// Returns an [`ErrorKind::ResumableHostTrap`] variant if the host function returned an error /// and there are still call frames on the call stack making it possible to resume the /// execution at a later point in time. /// /// [`ErrorKind::ResumableHostTrap`]: crate::error::ErrorKind::ResumableHostTrap fn execute_host_func( &mut self, store: &mut PrunedStore, results: Option, func: &Func, host_func: HostFuncEntity, ) -> Result { let len_params = host_func.len_params(); let len_results = host_func.len_results(); let max_inout = usize::from(len_params.max(len_results)); let instance = *self.stack.calls.instance_expect(); // We have to reinstantiate the `self.sp` [`FrameSlots`] since we just called // [`ValueStack::reserve`] which might invalidate all live [`FrameSlots`]. let (caller, popped_instance) = match ::KIND { CallKind::Nested => self.stack.calls.peek().copied().map(|frame| (frame, None)), CallKind::Tail => self.stack.calls.pop(), } .expect("need to have a caller on the call stack"); let buffer = self.stack.values.extend_by(max_inout, |this| { // Safety: we use the base offset of a live call frame on the call stack. self.sp = unsafe { this.stack_ptr_at(caller.base_offset()) }; })?; if ::HAS_PARAMS { let mut uninit_params = FrameParams::new(buffer); self.copy_call_params(&mut uninit_params); } if matches!(::KIND, CallKind::Nested) { self.update_instr_ptr_at(1); } let results = results.unwrap_or_else(|| caller.results()); self.dispatch_host_func(store, host_func, &instance) .map_err(|error| match self.stack.calls.is_empty() { true => error, false => ResumableHostTrapError::new(error, *func, results).into(), })?; self.cache.update(store.inner_mut(), &instance); let results = results.iter(len_results); match ::KIND { CallKind::Nested => { let returned = self.stack.values.drop_return(max_inout); for (result, value) in results.zip(returned) { // # Safety (1) // // We can safely acquire the stack pointer to the caller's and callee's (host) // call frames because we just allocated the host call frame and can be sure that // they are different. // In the following we make sure to not access registers out of bounds of each // call frame since we rely on Wasm validation and proper Wasm translation to // provide us with valid result registers. unsafe { self.sp.set(result, *value) }; } Ok(ControlFlow::Continue(())) } CallKind::Tail => { let (mut regs, cf) = match self.stack.calls.peek() { Some(frame) => { // Case: return the caller's caller frame registers. let sp = unsafe { self.stack.values.stack_ptr_at(frame.base_offset()) }; (sp, ControlFlow::Continue(())) } None => { // Case: call stack is empty -> return the root frame registers. let sp = self.stack.values.root_stack_ptr(); (sp, ControlFlow::Break(())) } }; let returned = self.stack.values.drop_return(max_inout); for (result, value) in results.zip(returned) { // # Safety (1) // // We can safely acquire the stack pointer to the caller's and callee's (host) // call frames because we just allocated the host call frame and can be sure that // they are different. // In the following we make sure to not access registers out of bounds of each // call frame since we rely on Wasm validation and proper Wasm translation to // provide us with valid result registers. unsafe { regs.set(result, *value) }; } self.stack.values.truncate(caller.frame_offset()); let new_instance = popped_instance.and_then(|_| self.stack.calls.instance()); if let Some(new_instance) = new_instance { self.cache.update(store.inner_mut(), new_instance); } if let Some(caller) = self.stack.calls.peek() { Self::init_call_frame_impl( &mut self.stack.values, &mut self.sp, &mut self.ip, caller, ); } Ok(cf) } } } /// Convenience forwarder to [`dispatch_host_func`]. fn dispatch_host_func( &mut self, store: &mut PrunedStore, host_func: HostFuncEntity, instance: &Instance, ) -> Result<(u16, u16), Error> { dispatch_host_func( store, &mut self.stack.values, host_func, Some(instance), CallHooks::Call, ) } /// Executes an [`Op::CallIndirect0`]. pub fn execute_return_call_indirect_0( &mut self, store: &mut PrunedStore, func_type: index::FuncType, ) -> Result { let (index, table) = self.pull_call_indirect_params(); self.execute_call_indirect_impl::(store, None, func_type, index, table) } /// Executes an [`Op::CallIndirect0Imm16`]. pub fn execute_return_call_indirect_0_imm16( &mut self, store: &mut PrunedStore, func_type: index::FuncType, ) -> Result { let (index, table) = self.pull_call_indirect_params_imm16(); self.execute_call_indirect_impl::(store, None, func_type, index, table) } /// Executes an [`Op::CallIndirect0`]. pub fn execute_return_call_indirect( &mut self, store: &mut PrunedStore, func_type: index::FuncType, ) -> Result { let (index, table) = self.pull_call_indirect_params(); self.execute_call_indirect_impl::(store, None, func_type, index, table) } /// Executes an [`Op::CallIndirect0Imm16`]. pub fn execute_return_call_indirect_imm16( &mut self, store: &mut PrunedStore, func_type: index::FuncType, ) -> Result { let (index, table) = self.pull_call_indirect_params_imm16(); self.execute_call_indirect_impl::(store, None, func_type, index, table) } /// Executes an [`Op::CallIndirect0`]. pub fn execute_call_indirect_0( &mut self, store: &mut PrunedStore, results: SlotSpan, func_type: index::FuncType, ) -> Result<(), Error> { let (index, table) = self.pull_call_indirect_params(); _ = self.execute_call_indirect_impl::( store, Some(results), func_type, index, table, )?; Ok(()) } /// Executes an [`Op::CallIndirect0Imm16`]. pub fn execute_call_indirect_0_imm16( &mut self, store: &mut PrunedStore, results: SlotSpan, func_type: index::FuncType, ) -> Result<(), Error> { let (index, table) = self.pull_call_indirect_params_imm16(); _ = self.execute_call_indirect_impl::( store, Some(results), func_type, index, table, )?; Ok(()) } /// Executes an [`Op::CallIndirect`]. pub fn execute_call_indirect( &mut self, store: &mut PrunedStore, results: SlotSpan, func_type: index::FuncType, ) -> Result<(), Error> { let (index, table) = self.pull_call_indirect_params(); _ = self.execute_call_indirect_impl::( store, Some(results), func_type, index, table, )?; Ok(()) } /// Executes an [`Op::CallIndirectImm16`]. pub fn execute_call_indirect_imm16( &mut self, store: &mut PrunedStore, results: SlotSpan, func_type: index::FuncType, ) -> Result<(), Error> { let (index, table) = self.pull_call_indirect_params_imm16(); _ = self.execute_call_indirect_impl::( store, Some(results), func_type, index, table, )?; Ok(()) } /// Executes an [`Op::CallIndirect`] and [`Op::CallIndirect0`]. fn execute_call_indirect_impl( &mut self, store: &mut PrunedStore, results: Option, func_type: index::FuncType, index: u64, table: index::Table, ) -> Result { let table = self.get_table(table); let funcref = store .inner() .resolve_table(&table) .get_untyped(index) .map(>::from) .ok_or(TrapCode::TableOutOfBounds)?; let func = funcref.val().ok_or(TrapCode::IndirectCallToNull)?; let actual_signature = store.inner().resolve_func(func).ty_dedup(); let expected_signature = &self.get_func_type_dedup(func_type); if actual_signature != expected_signature { return Err(Error::from(TrapCode::BadSignature)); } self.execute_call_imported_impl::(store, results, func) } } wasmi-1.1.0/src/engine/executor/instrs/comparison.rs000064400000000000000000000077131046102023000207070ustar 00000000000000use super::{Executor, UntypedValueCmpExt}; use crate::{ core::wasm, ir::{Const16, Slot}, }; #[cfg(doc)] use crate::ir::Op; impl Executor<'_> { impl_binary_executors! { (Op::I32Eq, execute_i32_eq, wasm::i32_eq), (Op::I32Ne, execute_i32_ne, wasm::i32_ne), (Op::I32LtS, execute_i32_lt_s, wasm::i32_lt_s), (Op::I32LtU, execute_i32_lt_u, wasm::i32_lt_u), (Op::I32LeS, execute_i32_le_s, wasm::i32_le_s), (Op::I32LeU, execute_i32_le_u, wasm::i32_le_u), (Op::I64Eq, execute_i64_eq, wasm::i64_eq), (Op::I64Ne, execute_i64_ne, wasm::i64_ne), (Op::I64LtS, execute_i64_lt_s, wasm::i64_lt_s), (Op::I64LtU, execute_i64_lt_u, wasm::i64_lt_u), (Op::I64LeS, execute_i64_le_s, wasm::i64_le_s), (Op::I64LeU, execute_i64_le_u, wasm::i64_le_u), (Op::F32Eq, execute_f32_eq, wasm::f32_eq), (Op::F32Ne, execute_f32_ne, wasm::f32_ne), (Op::F32Lt, execute_f32_lt, wasm::f32_lt), (Op::F32Le, execute_f32_le, wasm::f32_le), (Op::F32NotLt, execute_f32_not_lt, ::not_lt), (Op::F32NotLe, execute_f32_not_le, ::not_le), (Op::F64Eq, execute_f64_eq, wasm::f64_eq), (Op::F64Ne, execute_f64_ne, wasm::f64_ne), (Op::F64Lt, execute_f64_lt, wasm::f64_lt), (Op::F64Le, execute_f64_le, wasm::f64_le), (Op::F64NotLt, execute_f64_not_lt, ::not_lt), (Op::F64NotLe, execute_f64_not_le, ::not_le), } } macro_rules! impl_comparison_imm16_rhs { ( $( ($ty:ty, Op::$var_name:ident, $fn_name:ident, $op:expr) ),* $(,)? ) => { $( #[doc = concat!("Executes an [`Op::", stringify!($var_name), "`].")] pub fn $fn_name(&mut self, result: Slot, lhs: Slot, rhs: Const16<$ty>) { self.execute_binary_imm16_rhs(result, lhs, rhs, $op) } )* }; } impl Executor<'_> { impl_comparison_imm16_rhs! { (i32, Op::I32EqImm16, execute_i32_eq_imm16, wasm::i32_eq), (i32, Op::I32NeImm16, execute_i32_ne_imm16, wasm::i32_ne), (i32, Op::I32LtSImm16Rhs, execute_i32_lt_s_imm16_rhs, wasm::i32_lt_s), (u32, Op::I32LtUImm16Rhs, execute_i32_lt_u_imm16_rhs, wasm::i32_lt_u), (i32, Op::I32LeSImm16Rhs, execute_i32_le_s_imm16_rhs, wasm::i32_le_s), (u32, Op::I32LeUImm16Rhs, execute_i32_le_u_imm16_rhs, wasm::i32_le_u), (i64, Op::I64EqImm16, execute_i64_eq_imm16, wasm::i64_eq), (i64, Op::I64NeImm16, execute_i64_ne_imm16, wasm::i64_ne), (i64, Op::I64LtSImm16Rhs, execute_i64_lt_s_imm16_rhs, wasm::i64_lt_s), (u64, Op::I64LtUImm16Rhs, execute_i64_lt_u_imm16_rhs, wasm::i64_lt_u), (i64, Op::I64LeSImm16Rhs, execute_i64_le_s_imm16_rhs, wasm::i64_le_s), (u64, Op::I64LeUImm16Rhs, execute_i64_le_u_imm16_rhs, wasm::i64_le_u), } } macro_rules! impl_comparison_imm16_lhs { ( $( ($ty:ty, Op::$var_name:ident, $fn_name:ident, $op:expr) ),* $(,)? ) => { $( #[doc = concat!("Executes an [`Op::", stringify!($var_name), "`].")] pub fn $fn_name(&mut self, result: Slot, lhs: Const16<$ty>, rhs: Slot) { self.execute_binary_imm16_lhs(result, lhs, rhs, $op) } )* }; } impl Executor<'_> { impl_comparison_imm16_lhs! { (i32, Op::I32LtSImm16Lhs, execute_i32_lt_s_imm16_lhs, wasm::i32_lt_s), (u32, Op::I32LtUImm16Lhs, execute_i32_lt_u_imm16_lhs, wasm::i32_lt_u), (i32, Op::I32LeSImm16Lhs, execute_i32_le_s_imm16_lhs, wasm::i32_le_s), (u32, Op::I32LeUImm16Lhs, execute_i32_le_u_imm16_lhs, wasm::i32_le_u), (i64, Op::I64LtSImm16Lhs, execute_i64_lt_s_imm16_lhs, wasm::i64_lt_s), (u64, Op::I64LtUImm16Lhs, execute_i64_lt_u_imm16_lhs, wasm::i64_lt_u), (i64, Op::I64LeSImm16Lhs, execute_i64_le_s_imm16_lhs, wasm::i64_le_s), (u64, Op::I64LeUImm16Lhs, execute_i64_le_u_imm16_lhs, wasm::i64_le_u), } } wasmi-1.1.0/src/engine/executor/instrs/conversion.rs000064400000000000000000000061031046102023000207120ustar 00000000000000use super::Executor; use crate::{core::wasm, ir::Slot, Error}; #[cfg(doc)] use crate::ir::Op; macro_rules! impl_fallible_conversion_impls { ( $( (Op::$var_name:ident, $fn_name:ident, $op:expr) ),* $(,)? ) => { $( #[doc = concat!("Executes an [`Op::", stringify!($var_name), "`].")] pub fn $fn_name(&mut self, result: Slot, input: Slot) -> Result<(), Error> { self.try_execute_unary(result, input, $op) } )* }; } impl Executor<'_> { impl_unary_executors! { (Op::I32WrapI64, execute_i32_wrap_i64, wasm::i32_wrap_i64), (Op::I32TruncSatF32S, execute_i32_trunc_sat_f32_s, wasm::i32_trunc_sat_f32_s), (Op::I32TruncSatF32U, execute_i32_trunc_sat_f32_u, wasm::i32_trunc_sat_f32_u), (Op::I32TruncSatF64S, execute_i32_trunc_sat_f64_s, wasm::i32_trunc_sat_f64_s), (Op::I32TruncSatF64U, execute_i32_trunc_sat_f64_u, wasm::i32_trunc_sat_f64_u), (Op::I64TruncSatF32S, execute_i64_trunc_sat_f32_s, wasm::i64_trunc_sat_f32_s), (Op::I64TruncSatF32U, execute_i64_trunc_sat_f32_u, wasm::i64_trunc_sat_f32_u), (Op::I64TruncSatF64S, execute_i64_trunc_sat_f64_s, wasm::i64_trunc_sat_f64_s), (Op::I64TruncSatF64U, execute_i64_trunc_sat_f64_u, wasm::i64_trunc_sat_f64_u), (Op::I32Extend8S, execute_i32_extend8_s, wasm::i32_extend8_s), (Op::I32Extend16S, execute_i32_extend16_s, wasm::i32_extend16_s), (Op::I64Extend8S, execute_i64_extend8_s, wasm::i64_extend8_s), (Op::I64Extend16S, execute_i64_extend16_s, wasm::i64_extend16_s), (Op::I64Extend32S, execute_i64_extend32_s, wasm::i64_extend32_s), (Op::F32DemoteF64, execute_f32_demote_f64, wasm::f32_demote_f64), (Op::F64PromoteF32, execute_f64_promote_f32, wasm::f64_promote_f32), (Op::F32ConvertI32S, execute_f32_convert_i32_s, wasm::f32_convert_i32_s), (Op::F32ConvertI32U, execute_f32_convert_i32_u, wasm::f32_convert_i32_u), (Op::F32ConvertI64S, execute_f32_convert_i64_s, wasm::f32_convert_i64_s), (Op::F32ConvertI64U, execute_f32_convert_i64_u, wasm::f32_convert_i64_u), (Op::F64ConvertI32S, execute_f64_convert_i32_s, wasm::f64_convert_i32_s), (Op::F64ConvertI32U, execute_f64_convert_i32_u, wasm::f64_convert_i32_u), (Op::F64ConvertI64S, execute_f64_convert_i64_s, wasm::f64_convert_i64_s), (Op::F64ConvertI64U, execute_f64_convert_i64_u, wasm::f64_convert_i64_u), } impl_fallible_conversion_impls! { (Op::I32TruncF32S, execute_i32_trunc_f32_s, wasm::i32_trunc_f32_s), (Op::I32TruncF32U, execute_i32_trunc_f32_u, wasm::i32_trunc_f32_u), (Op::I32TruncF64S, execute_i32_trunc_f64_s, wasm::i32_trunc_f64_s), (Op::I32TruncF64U, execute_i32_trunc_f64_u, wasm::i32_trunc_f64_u), (Op::I64TruncF32S, execute_i64_trunc_f32_s, wasm::i64_trunc_f32_s), (Op::I64TruncF32U, execute_i64_trunc_f32_u, wasm::i64_trunc_f32_u), (Op::I64TruncF64S, execute_i64_trunc_f64_s, wasm::i64_trunc_f64_s), (Op::I64TruncF64U, execute_i64_trunc_f64_u, wasm::i64_trunc_f64_u), } } wasmi-1.1.0/src/engine/executor/instrs/copy.rs000064400000000000000000000104641046102023000175040ustar 00000000000000use super::{Executor, InstructionPtr}; use crate::{ core::UntypedVal, engine::utils::unreachable_unchecked, ir::{AnyConst32, Const32, FixedSlotSpan, Op, Slot, SlotSpan}, }; use core::slice; impl Executor<'_> { /// Executes a generic `copy` [`Op`]. fn execute_copy_impl(&mut self, result: Slot, value: T, f: fn(&mut Self, T) -> UntypedVal) { let value = f(self, value); self.set_stack_slot(result, value); self.next_instr() } /// Executes an [`Op::Copy`]. pub fn execute_copy(&mut self, result: Slot, value: Slot) { self.execute_copy_impl(result, value, |this, value| this.get_stack_slot(value)) } /// Executes an [`Op::Copy2`]. pub fn execute_copy_2(&mut self, results: FixedSlotSpan<2>, values: [Slot; 2]) { self.execute_copy_2_impl(results, values); self.next_instr() } /// Internal implementation of [`Op::Copy2`] execution. fn execute_copy_2_impl(&mut self, results: FixedSlotSpan<2>, values: [Slot; 2]) { let result0 = results.span().head(); let result1 = result0.next(); // We need `tmp` in case `results[0] == values[1]` to avoid overwriting `values[1]` before reading it. let tmp = self.get_stack_slot(values[1]); self.set_stack_slot(result0, self.get_stack_slot(values[0])); self.set_stack_slot(result1, tmp); } /// Executes an [`Op::CopyImm32`]. pub fn execute_copy_imm32(&mut self, result: Slot, value: AnyConst32) { self.execute_copy_impl(result, value, |_, value| UntypedVal::from(u32::from(value))) } /// Executes an [`Op::CopyI64Imm32`]. pub fn execute_copy_i64imm32(&mut self, result: Slot, value: Const32) { self.execute_copy_impl(result, value, |_, value| UntypedVal::from(i64::from(value))) } /// Executes an [`Op::CopyF64Imm32`]. pub fn execute_copy_f64imm32(&mut self, result: Slot, value: Const32) { self.execute_copy_impl(result, value, |_, value| UntypedVal::from(f64::from(value))) } /// Executes an [`Op::CopySpan`]. /// /// # Note /// /// - This instruction assumes that `results` and `values` do _not_ overlap /// and thus can copy all the elements without a costly temporary buffer. /// - If `results` and `values` _do_ overlap [`Op::CopySpan`] is used. pub fn execute_copy_span(&mut self, results: SlotSpan, values: SlotSpan, len: u16) { self.execute_copy_span_impl(results, values, len); self.next_instr(); } /// Internal implementation of [`Op::CopySpan`] execution. pub fn execute_copy_span_impl(&mut self, results: SlotSpan, values: SlotSpan, len: u16) { let results = results.iter(len); let values = values.iter(len); for (result, value) in results.into_iter().zip(values) { let value = self.get_stack_slot(value); self.set_stack_slot(result, value); } } /// Executes an [`Op::CopyMany`]. pub fn execute_copy_many(&mut self, results: SlotSpan, values: [Slot; 2]) { self.ip.add(1); self.ip = self.execute_copy_many_impl(self.ip, results, &values); self.next_instr() } /// Internal implementation of [`Op::CopyMany`] execution. pub fn execute_copy_many_impl( &mut self, ip: InstructionPtr, results: SlotSpan, values: &[Slot], ) -> InstructionPtr { let mut ip = ip; let mut result = results.head(); let mut copy_values = |values: &[Slot]| { for &value in values { let value = self.get_stack_slot(value); self.set_stack_slot(result, value); result = result.next(); } }; copy_values(values); while let Op::SlotList { regs } = ip.get() { copy_values(regs); ip.add(1); } let values = match ip.get() { Op::Slot { slot } => slice::from_ref(slot), Op::Slot2 { slots } => slots, Op::Slot3 { slots } => slots, unexpected => { // Safety: Wasmi translator guarantees that slot-list finalizer exists. unsafe { unreachable_unchecked!("expected slot-list finalizer but found: {unexpected:?}") } } }; copy_values(values); ip } } wasmi-1.1.0/src/engine/executor/instrs/global.rs000064400000000000000000000046521046102023000177740ustar 00000000000000use super::Executor; use crate::{ core::{hint, UntypedVal}, ir::{index, Const16, Slot}, store::StoreInner, }; #[cfg(doc)] use crate::ir::Op; impl Executor<'_> { /// Executes an [`Op::GlobalGet`]. pub fn execute_global_get(&mut self, store: &StoreInner, result: Slot, global: index::Global) { let value = match u32::from(global) { 0 => unsafe { self.cache.global.get() }, _ => { hint::cold(); let global = self.get_global(global); *store.resolve_global(&global).get_untyped() } }; self.set_stack_slot(result, value); self.next_instr() } /// Executes an [`Op::GlobalSet`]. pub fn execute_global_set( &mut self, store: &mut StoreInner, global: index::Global, input: Slot, ) { let input = self.get_stack_slot(input); self.execute_global_set_impl(store, global, input) } /// Executes an [`Op::GlobalSetI32Imm16`]. pub fn execute_global_set_i32imm16( &mut self, store: &mut StoreInner, global: index::Global, input: Const16, ) { let input = i32::from(input).into(); self.execute_global_set_impl(store, global, input) } /// Executes an [`Op::GlobalSetI64Imm16`]. pub fn execute_global_set_i64imm16( &mut self, store: &mut StoreInner, global: index::Global, input: Const16, ) { let input = i64::from(input).into(); self.execute_global_set_impl(store, global, input) } /// Executes a generic `global.set` instruction. fn execute_global_set_impl( &mut self, store: &mut StoreInner, global: index::Global, new_value: UntypedVal, ) { match u32::from(global) { 0 => unsafe { self.cache.global.set(new_value) }, _ => { hint::cold(); let global = self.get_global(global); let mut ptr = store.resolve_global_mut(&global).get_untyped_ptr(); // Safety: // - Wasmi translation won't create `global.set` instructions for immutable globals. // - Wasm validation ensures that values with matching types are written to globals. unsafe { *ptr.as_mut() = new_value; } } }; self.next_instr() } } wasmi-1.1.0/src/engine/executor/instrs/load.rs000064400000000000000000000326771046102023000174630ustar 00000000000000use super::Executor; use crate::{ core::{wasm, UntypedVal, WriteAs}, ir::{index::Memory, Address32, Offset16, Offset64, Offset64Hi, Offset64Lo, Slot}, store::StoreInner, Error, TrapCode, }; #[cfg(feature = "simd")] use crate::{core::simd, V128}; #[cfg(doc)] use crate::ir::Op; /// The function signature of Wasm load operations. type WasmLoadOp = fn(memory: &[u8], ptr: u64, offset: u64) -> Result; /// The function signature of Wasm load operations. type WasmLoadAtOp = fn(memory: &[u8], address: usize) -> Result; impl Executor<'_> { /// Returns the register `value` and `offset` parameters for a `load` [`Op`]. fn fetch_ptr_and_offset_hi(&self) -> (Slot, Offset64Hi) { // Safety: Wasmi translation guarantees that `Op::SlotAndImm32` exists. unsafe { self.fetch_reg_and_offset_hi() } } /// Executes a generic Wasm `load[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` fn execute_load_extend( &mut self, store: &StoreInner, memory: Memory, result: Slot, address: u64, offset: Offset64, load_extend: WasmLoadOp, ) -> Result<(), Error> where UntypedVal: WriteAs, { let memory = self.fetch_memory_bytes(memory, store); let loaded_value = load_extend(memory, address, u64::from(offset))?; self.set_stack_slot_as::(result, loaded_value); Ok(()) } /// Executes a generic Wasm `load[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` fn execute_load_extend_at( &mut self, store: &StoreInner, memory: Memory, result: Slot, address: Address32, load_extend_at: WasmLoadAtOp, ) -> Result<(), Error> where UntypedVal: WriteAs, { let memory = self.fetch_memory_bytes(memory, store); let loaded_value = load_extend_at(memory, usize::from(address))?; self.set_stack_slot_as::(result, loaded_value); Ok(()) } /// Executes a generic Wasm `store[N_{s|u}]` operation on the default memory. /// /// # 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` fn execute_load_extend_mem0( &mut self, result: Slot, address: u64, offset: Offset64, load_extend: WasmLoadOp, ) -> Result<(), Error> where UntypedVal: WriteAs, { let memory = self.fetch_default_memory_bytes(); let loaded_value = load_extend(memory, address, u64::from(offset))?; self.set_stack_slot_as::(result, loaded_value); Ok(()) } /// Executes a generic `load` [`Op`]. fn execute_load_impl( &mut self, store: &StoreInner, result: Slot, offset_lo: Offset64Lo, load_extend: WasmLoadOp, ) -> Result<(), Error> where UntypedVal: WriteAs, { let (ptr, offset_hi) = self.fetch_ptr_and_offset_hi(); let memory = self.fetch_optional_memory(2); let address = self.get_stack_slot_as::(ptr); let offset = Offset64::combine(offset_hi, offset_lo); self.execute_load_extend::(store, memory, result, address, offset, load_extend)?; self.try_next_instr_at(2) } /// Executes a generic `load_at` [`Op`]. fn execute_load_at_impl( &mut self, store: &StoreInner, result: Slot, address: Address32, load_extend_at: WasmLoadAtOp, ) -> Result<(), Error> where UntypedVal: WriteAs, { let memory = self.fetch_optional_memory(1); self.execute_load_extend_at::(store, memory, result, address, load_extend_at)?; self.try_next_instr() } /// Executes a generic `load_offset16` [`Op`]. fn execute_load_offset16_impl( &mut self, result: Slot, ptr: Slot, offset: Offset16, load_extend: WasmLoadOp, ) -> Result<(), Error> where UntypedVal: WriteAs, { let address = self.get_stack_slot_as::(ptr); let offset = Offset64::from(offset); self.execute_load_extend_mem0::(result, address, offset, load_extend)?; self.try_next_instr() } } macro_rules! impl_execute_load { ( $( ( $ty:ty, (Op::$var_load:expr, $fn_load:ident), (Op::$var_load_at:expr, $fn_load_at:ident), (Op::$var_load_off16:expr, $fn_load_off16:ident), $load_fn:expr, $load_at_fn:expr $(,)? ) ),* $(,)? ) => { $( #[doc = concat!("Executes an [`Op::", stringify!($var_load), "`].")] pub fn $fn_load(&mut self, store: &StoreInner, result: Slot, offset_lo: Offset64Lo) -> Result<(), Error> { self.execute_load_impl(store, result, offset_lo, $load_fn) } #[doc = concat!("Executes an [`Op::", stringify!($var_load_at), "`].")] pub fn $fn_load_at(&mut self, store: &StoreInner, result: Slot, address: Address32) -> Result<(), Error> { self.execute_load_at_impl(store, result, address, $load_at_fn) } #[doc = concat!("Executes an [`Op::", stringify!($var_load_off16), "`].")] pub fn $fn_load_off16(&mut self, result: Slot, ptr: Slot, offset: Offset16) -> Result<(), Error> { self.execute_load_offset16_impl::<$ty>(result, ptr, offset, $load_fn) } )* } } impl Executor<'_> { #[cfg(feature = "simd")] impl_execute_load! { ( V128, (Op::V128Load, execute_v128_load), (Op::V128LoadAt, execute_v128_load_at), (Op::V128LoadOffset16, execute_v128_load_offset16), simd::v128_load, simd::v128_load_at, ), ( V128, (Op::V128Load8x8S, execute_v128_load8x8_s), (Op::V128Load8x8SAt, execute_v128_load8x8_s_at), (Op::V128Load8x8SOffset16, execute_v128_load8x8_s_offset16), simd::v128_load8x8_s, simd::v128_load8x8_s_at, ), ( V128, (Op::V128Load8x8U, execute_v128_load8x8_u), (Op::V128Load8x8UAt, execute_v128_load8x8_u_at), (Op::V128Load8x8UOffset16, execute_v128_load8x8_u_offset16), simd::v128_load8x8_u, simd::v128_load8x8_u_at, ), ( V128, (Op::V128Load16x4S, execute_v128_load16x4_s), (Op::V128Load16x4SAt, execute_v128_load16x4_s_at), (Op::V128Load16x4SOffset16, execute_v128_load16x4_s_offset16), simd::v128_load16x4_s, simd::v128_load16x4_s_at, ), ( V128, (Op::V128Load16x4U, execute_v128_load16x4_u), (Op::V128Load16x4UAt, execute_v128_load16x4_u_at), (Op::V128Load16x4UOffset16, execute_v128_load16x4_u_offset16), simd::v128_load16x4_u, simd::v128_load16x4_u_at, ), ( V128, (Op::V128Load32x2S, execute_v128_load32x2_s), (Op::V128Load32x2SAt, execute_v128_load32x2_s_at), (Op::V128Load32x2SOffset16, execute_v128_load32x2_s_offset16), simd::v128_load32x2_s, simd::v128_load32x2_s_at, ), ( V128, (Op::V128Load32x2U, execute_v128_load32x2_u), (Op::V128Load32x2UAt, execute_v128_load32x2_u_at), (Op::V128Load32x2UOffset16, execute_v128_load32x2_u_offset16), simd::v128_load32x2_u, simd::v128_load32x2_u_at, ), ( V128, (Op::V128Load8Splat, execute_v128_load8_splat), (Op::V128Load8SplatAt, execute_v128_load8_splat_at), (Op::V128Load8SplatOffset16, execute_v128_load8_splat_offset16), simd::v128_load8_splat, simd::v128_load8_splat_at, ), ( V128, (Op::V128Load16Splat, execute_v128_load16_splat), (Op::V128Load16SplatAt, execute_v128_load16_splat_at), (Op::V128Load16SplatOffset16, execute_v128_load16_splat_offset16), simd::v128_load16_splat, simd::v128_load16_splat_at, ), ( V128, (Op::V128Load32Splat, execute_v128_load32_splat), (Op::V128Load32SplatAt, execute_v128_load32_splat_at), (Op::V128Load32SplatOffset16, execute_v128_load32_splat_offset16), simd::v128_load32_splat, simd::v128_load32_splat_at, ), ( V128, (Op::V128Load64Splat, execute_v128_load64_splat), (Op::V128Load64SplatAt, execute_v128_load64_splat_at), (Op::V128Load64SplatOffset16, execute_v128_load64_splat_offset16), simd::v128_load64_splat, simd::v128_load64_splat_at, ), ( V128, (Op::V128Load32Zero, execute_v128_load32_zero), (Op::V128Load32ZeroAt, execute_v128_load32_zero_at), (Op::V128Load32ZeroOffset16, execute_v128_load32_zero_offset16), simd::v128_load32_zero, simd::v128_load32_zero_at, ), ( V128, (Op::V128Load64Zero, execute_v128_load64_zero), (Op::V128Load64ZeroAt, execute_v128_load64_zero_at), (Op::V128Load64ZeroOffset16, execute_v128_load64_zero_offset16), simd::v128_load64_zero, simd::v128_load64_zero_at, ), } impl_execute_load! { ( u32, (Op::Load32, execute_load32), (Op::Load32At, execute_load32_at), (Op::Load32Offset16, execute_load32_offset16), wasm::load32, wasm::load32_at, ), ( u64, (Op::Load64, execute_load64), (Op::Load64At, execute_load64_at), (Op::Load64Offset16, execute_load64_offset16), wasm::load64, wasm::load64_at, ), ( i32, (Op::I32Load8s, execute_i32_load8_s), (Op::I32Load8sAt, execute_i32_load8_s_at), (Op::I32Load8sOffset16, execute_i32_load8_s_offset16), wasm::i32_load8_s, wasm::i32_load8_s_at, ), ( i32, (Op::I32Load8u, execute_i32_load8_u), (Op::I32Load8uAt, execute_i32_load8_u_at), (Op::I32Load8uOffset16, execute_i32_load8_u_offset16), wasm::i32_load8_u, wasm::i32_load8_u_at, ), ( i32, (Op::I32Load16s, execute_i32_load16_s), (Op::I32Load16sAt, execute_i32_load16_s_at), (Op::I32Load16sOffset16, execute_i32_load16_s_offset16), wasm::i32_load16_s, wasm::i32_load16_s_at, ), ( i32, (Op::I32Load16u, execute_i32_load16_u), (Op::I32Load16uAt, execute_i32_load16_u_at), (Op::I32Load16uOffset16, execute_i32_load16_u_offset16), wasm::i32_load16_u, wasm::i32_load16_u_at, ), ( i64, (Op::I64Load8s, execute_i64_load8_s), (Op::I64Load8sAt, execute_i64_load8_s_at), (Op::I64Load8sOffset16, execute_i64_load8_s_offset16), wasm::i64_load8_s, wasm::i64_load8_s_at, ), ( i64, (Op::I64Load8u, execute_i64_load8_u), (Op::I64Load8uAt, execute_i64_load8_u_at), (Op::I64Load8uOffset16, execute_i64_load8_u_offset16), wasm::i64_load8_u, wasm::i64_load8_u_at, ), ( i64, (Op::I64Load16s, execute_i64_load16_s), (Op::I64Load16sAt, execute_i64_load16_s_at), (Op::I64Load16sOffset16, execute_i64_load16_s_offset16), wasm::i64_load16_s, wasm::i64_load16_s_at, ), ( i64, (Op::I64Load16u, execute_i64_load16_u), (Op::I64Load16uAt, execute_i64_load16_u_at), (Op::I64Load16uOffset16, execute_i64_load16_u_offset16), wasm::i64_load16_u, wasm::i64_load16_u_at, ), ( i64, (Op::I64Load32s, execute_i64_load32_s), (Op::I64Load32sAt, execute_i64_load32_s_at), (Op::I64Load32sOffset16, execute_i64_load32_s_offset16), wasm::i64_load32_s, wasm::i64_load32_s_at, ), ( i64, (Op::I64Load32u, execute_i64_load32_u), (Op::I64Load32uAt, execute_i64_load32_u_at), (Op::I64Load32uOffset16, execute_i64_load32_u_offset16), wasm::i64_load32_u, wasm::i64_load32_u_at, ), } } wasmi-1.1.0/src/engine/executor/instrs/memory.rs000064400000000000000000000245051046102023000200430ustar 00000000000000use super::{Executor, InstructionPtr}; use crate::{ engine::{utils::unreachable_unchecked, ResumableOutOfFuelError}, errors::MemoryError, ir::{ index::{Data, Memory}, Op, Slot, }, store::{PrunedStore, StoreError, StoreInner}, Error, TrapCode, }; impl Executor<'_> { /// Returns the [`Op::MemoryIndex`] parameter for an [`Op`]. fn fetch_memory_index(&self, offset: usize) -> Memory { let mut addr: InstructionPtr = self.ip; addr.add(offset); match *addr.get() { Op::MemoryIndex { index } => index, unexpected => { // Safety: Wasmi translation guarantees that [`Op::MemoryIndex`] exists. unsafe { unreachable_unchecked!("expected `Op::MemoryIndex` but found: {unexpected:?}") } } } } /// Returns the [`Op::DataIndex`] parameter for an [`Op`]. fn fetch_data_segment_index(&self, offset: usize) -> Data { let mut addr: InstructionPtr = self.ip; addr.add(offset); match *addr.get() { Op::DataIndex { index } => index, unexpected => { // Safety: Wasmi translation guarantees that [`Op::DataIndex`] exists. unsafe { unreachable_unchecked!("expected `Op::DataIndex` but found: {unexpected:?}") } } } } /// Executes an [`Op::DataDrop`]. pub fn execute_data_drop(&mut self, store: &mut StoreInner, segment_index: Data) { let segment = self.get_data_segment(segment_index); store.resolve_data_mut(&segment).drop_bytes(); self.next_instr(); } /// Executes an [`Op::MemorySize`]. pub fn execute_memory_size(&mut self, store: &StoreInner, result: Slot, memory: Memory) { self.execute_memory_size_impl(store, result, memory); self.next_instr() } /// Underlying implementation of [`Op::MemorySize`]. fn execute_memory_size_impl(&mut self, store: &StoreInner, result: Slot, memory: Memory) { let memory = self.get_memory(memory); let size = store.resolve_memory(&memory).size(); self.set_stack_slot(result, size); } /// Executes an [`Op::MemoryGrow`]. pub fn execute_memory_grow( &mut self, store: &mut PrunedStore, result: Slot, delta: Slot, ) -> Result<(), Error> { let delta: u64 = self.get_stack_slot_as(delta); let memory = self.fetch_memory_index(1); if delta == 0 { // Case: growing by 0 pages means there is nothing to do self.execute_memory_size_impl(store.inner(), result, memory); return self.try_next_instr_at(2); } let memory = self.get_memory(memory); let return_value = match store.grow_memory(&memory, delta) { Ok(return_value) => { // 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. // // Safety: the instance has not changed thus calling this is valid. unsafe { self.cache.update_memory(store.inner_mut()) }; return_value } Err(StoreError::External( MemoryError::OutOfBoundsGrowth | MemoryError::OutOfSystemMemory, )) => { let memory_ty = store.inner().resolve_memory(&memory).ty(); match memory_ty.is_64() { true => u64::MAX, false => u64::from(u32::MAX), } } Err(StoreError::External(MemoryError::OutOfFuel { required_fuel })) => { return Err(Error::from(ResumableOutOfFuelError::new(required_fuel))) } Err(StoreError::External(MemoryError::ResourceLimiterDeniedAllocation)) => { return Err(Error::from(TrapCode::GrowthOperationLimited)) } Err(error) => { panic!("`table.grow`: internal interpreter error: {error}") } }; self.set_stack_slot(result, return_value); self.try_next_instr_at(2) } /// Executes an [`Op::MemoryCopy`]. pub fn execute_memory_copy( &mut self, store: &mut StoreInner, dst: Slot, src: Slot, len: Slot, ) -> Result<(), Error> { let dst: u64 = self.get_stack_slot_as(dst); let src: u64 = self.get_stack_slot_as(src); let len: u64 = self.get_stack_slot_as(len); let Ok(dst_index) = usize::try_from(dst) else { return Err(Error::from(TrapCode::MemoryOutOfBounds)); }; let Ok(src_index) = usize::try_from(src) else { return Err(Error::from(TrapCode::MemoryOutOfBounds)); }; let Ok(len) = usize::try_from(len) else { return Err(Error::from(TrapCode::MemoryOutOfBounds)); }; let dst_memory = self.fetch_memory_index(1); let src_memory = self.fetch_memory_index(2); if src_memory == dst_memory { return self .execute_memory_copy_within_impl(store, src_memory, dst_index, src_index, len); } let (src_memory, dst_memory, fuel) = store.resolve_memory_pair_and_fuel( &self.get_memory(src_memory), &self.get_memory(dst_memory), ); // These accesses just perform the bounds checks required by the Wasm spec. let src_bytes = src_memory .data() .get(src_index..) .and_then(|memory| memory.get(..len)) .ok_or(TrapCode::MemoryOutOfBounds)?; let dst_bytes = dst_memory .data_mut() .get_mut(dst_index..) .and_then(|memory| memory.get_mut(..len)) .ok_or(TrapCode::MemoryOutOfBounds)?; fuel.consume_fuel_if(|costs| costs.fuel_for_copying_bytes(len as u64))?; dst_bytes.copy_from_slice(src_bytes); self.try_next_instr_at(3) } /// Executes a generic `memory.copy` instruction. fn execute_memory_copy_within_impl( &mut self, store: &mut StoreInner, memory: Memory, dst_index: usize, src_index: usize, len: usize, ) -> Result<(), Error> { let memory = self.get_memory(memory); let (memory, fuel) = store.resolve_memory_and_fuel_mut(&memory); let bytes = memory.data_mut(); // These accesses just perform the bounds checks required by the Wasm spec. bytes .get(src_index..) .and_then(|memory| memory.get(..len)) .ok_or(TrapCode::MemoryOutOfBounds)?; bytes .get(dst_index..) .and_then(|memory| memory.get(..len)) .ok_or(TrapCode::MemoryOutOfBounds)?; fuel.consume_fuel_if(|costs| costs.fuel_for_copying_bytes(len as u64))?; bytes.copy_within(src_index..src_index.wrapping_add(len), dst_index); self.try_next_instr_at(3) } /// Executes an [`Op::MemoryFill`]. pub fn execute_memory_fill( &mut self, store: &mut StoreInner, dst: Slot, value: Slot, len: Slot, ) -> Result<(), Error> { let dst: u64 = self.get_stack_slot_as(dst); let value: u8 = self.get_stack_slot_as(value); let len: u64 = self.get_stack_slot_as(len); self.execute_memory_fill_impl(store, dst, value, len) } /// Executes an [`Op::MemoryFillImm`]. pub fn execute_memory_fill_imm( &mut self, store: &mut StoreInner, dst: Slot, value: u8, len: Slot, ) -> Result<(), Error> { let dst: u64 = self.get_stack_slot_as(dst); let len: u64 = self.get_stack_slot_as(len); self.execute_memory_fill_impl(store, dst, value, len) } /// Executes a generic `memory.fill` instruction. #[inline(never)] fn execute_memory_fill_impl( &mut self, store: &mut StoreInner, dst: u64, value: u8, len: u64, ) -> Result<(), Error> { let Ok(dst) = usize::try_from(dst) else { return Err(Error::from(TrapCode::MemoryOutOfBounds)); }; let Ok(len) = usize::try_from(len) else { return Err(Error::from(TrapCode::MemoryOutOfBounds)); }; let memory = self.fetch_memory_index(1); let memory = self.get_memory(memory); let (memory, fuel) = store.resolve_memory_and_fuel_mut(&memory); let slice = memory .data_mut() .get_mut(dst..) .and_then(|memory| memory.get_mut(..len)) .ok_or(TrapCode::MemoryOutOfBounds)?; fuel.consume_fuel_if(|costs| costs.fuel_for_copying_bytes(len as u64))?; slice.fill(value); self.try_next_instr_at(2) } /// Executes an [`Op::MemoryInit`]. pub fn execute_memory_init( &mut self, store: &mut StoreInner, dst: Slot, src: Slot, len: Slot, ) -> Result<(), Error> { let dst: u64 = self.get_stack_slot_as(dst); let src: u32 = self.get_stack_slot_as(src); let len: u32 = self.get_stack_slot_as(len); let Ok(dst_index) = usize::try_from(dst) else { return Err(Error::from(TrapCode::MemoryOutOfBounds)); }; let Ok(src_index) = usize::try_from(src) else { return Err(Error::from(TrapCode::MemoryOutOfBounds)); }; let Ok(len) = usize::try_from(len) else { return Err(Error::from(TrapCode::MemoryOutOfBounds)); }; let memory_index: Memory = self.fetch_memory_index(1); let data_index: Data = self.fetch_data_segment_index(2); let (memory, data, fuel) = store.resolve_memory_init_params( &self.get_memory(memory_index), &self.get_data_segment(data_index), ); let memory = memory .data_mut() .get_mut(dst_index..) .and_then(|memory| memory.get_mut(..len)) .ok_or(TrapCode::MemoryOutOfBounds)?; let data = data .bytes() .get(src_index..) .and_then(|data| data.get(..len)) .ok_or(TrapCode::MemoryOutOfBounds)?; fuel.consume_fuel_if(|costs| costs.fuel_for_copying_bytes(len as u64))?; memory.copy_from_slice(data); self.try_next_instr_at(3) } } wasmi-1.1.0/src/engine/executor/instrs/return_.rs000064400000000000000000000217451046102023000202140ustar 00000000000000use super::{ControlFlow, Executor, InstructionPtr}; use crate::{ core::UntypedVal, engine::{executor::stack::FrameSlots, utils::unreachable_unchecked}, ir::{AnyConst32, BoundedSlotSpan, Const32, Op, Slot, SlotSpan}, store::StoreInner, }; use core::slice; impl Executor<'_> { /// Returns the execution to the caller. /// /// Any return values are expected to already have been transferred /// from the returning callee to the caller. fn return_impl(&mut self, store: &mut StoreInner) -> ControlFlow { let (returned, popped_instance) = self .stack .calls .pop() .expect("the executing call frame is always on the stack"); self.stack.values.truncate(returned.frame_offset()); let new_instance = popped_instance.and_then(|_| self.stack.calls.instance()); if let Some(new_instance) = new_instance { self.cache.update(store, new_instance); } match self.stack.calls.peek() { Some(caller) => { Self::init_call_frame_impl( &mut self.stack.values, &mut self.sp, &mut self.ip, caller, ); ControlFlow::Continue(()) } None => ControlFlow::Break(()), } } /// Execute an [`Op::Return`]. pub fn execute_return(&mut self, store: &mut StoreInner) -> ControlFlow { self.return_impl(store) } /// Returns the [`FrameSlots`] of the caller and the [`SlotSpan`] of the results. /// /// The returned [`FrameSlots`] is valid for all [`Slot`] in the returned [`SlotSpan`]. fn return_caller_results(&mut self) -> (FrameSlots, SlotSpan) { let (callee, caller) = self .stack .calls .peek_2() .expect("the callee must exist on the call stack"); match caller { Some(caller) => { // Case: we need to return the `value` back to the caller frame. // // In this case we transfer the single return `value` to the `results` // register span of the caller's call frame. // // Safety: The caller call frame is still live on the value stack // and therefore it is safe to acquire its value stack pointer. let caller_sp = unsafe { self.stack.values.stack_ptr_at(caller.base_offset()) }; let results = callee.results(); (caller_sp, results) } None => { // Case: the root call frame is returning. // // In this case we transfer the single return `value` to the root // register span of the entire value stack which is simply its zero index. let dst_sp = self.stack.values.root_stack_ptr(); let results = SlotSpan::new(Slot::from(0)); (dst_sp, results) } } } /// Execute a generic return [`Op`] returning a single value. fn execute_return_value( &mut self, store: &mut StoreInner, value: T, f: fn(&Self, T) -> UntypedVal, ) -> ControlFlow { let (mut caller_sp, results) = self.return_caller_results(); let value = f(self, value); // Safety: The `callee.results()` always refer to a span of valid // registers of the `caller` that does not overlap with the // registers of the callee since they reside in different // call frames. Therefore this access is safe. unsafe { caller_sp.set(results.head(), value) } self.return_impl(store) } /// Execute an [`Op::ReturnSlot`] returning a single [`Slot`] value. pub fn execute_return_reg(&mut self, store: &mut StoreInner, value: Slot) -> ControlFlow { self.execute_return_value(store, value, Self::get_stack_slot) } /// Execute an [`Op::ReturnSlot2`] returning two [`Slot`] values. pub fn execute_return_reg2( &mut self, store: &mut StoreInner, values: [Slot; 2], ) -> ControlFlow { self.execute_return_reg_n_impl::<2>(store, values) } /// Execute an [`Op::ReturnSlot3`] returning three [`Slot`] values. pub fn execute_return_reg3( &mut self, store: &mut StoreInner, values: [Slot; 3], ) -> ControlFlow { self.execute_return_reg_n_impl::<3>(store, values) } /// Executes an [`Op::ReturnSlot2`] or [`Op::ReturnSlot3`] generically. fn execute_return_reg_n_impl( &mut self, store: &mut StoreInner, values: [Slot; N], ) -> ControlFlow { let (mut caller_sp, results) = self.return_caller_results(); debug_assert!(u16::try_from(N).is_ok()); for (result, value) in results.iter(N as u16).zip(values) { let value = self.get_stack_slot(value); // Safety: The `callee.results()` always refer to a span of valid // registers of the `caller` that does not overlap with the // registers of the callee since they reside in different // call frames. Therefore this access is safe. unsafe { caller_sp.set(result, value) } } self.return_impl(store) } /// Execute an [`Op::ReturnImm32`] returning a single 32-bit value. pub fn execute_return_imm32( &mut self, store: &mut StoreInner, value: AnyConst32, ) -> ControlFlow { self.execute_return_value(store, value, |_, value| u32::from(value).into()) } /// Execute an [`Op::ReturnI64Imm32`] returning a single 32-bit encoded `i64` value. pub fn execute_return_i64imm32( &mut self, store: &mut StoreInner, value: Const32, ) -> ControlFlow { self.execute_return_value(store, value, |_, value| i64::from(value).into()) } /// Execute an [`Op::ReturnF64Imm32`] returning a single 32-bit encoded `f64` value. pub fn execute_return_f64imm32( &mut self, store: &mut StoreInner, value: Const32, ) -> ControlFlow { self.execute_return_value(store, value, |_, value| f64::from(value).into()) } /// Execute an [`Op::ReturnSpan`] returning many values. pub fn execute_return_span( &mut self, store: &mut StoreInner, values: BoundedSlotSpan, ) -> ControlFlow { let (mut caller_sp, results) = self.return_caller_results(); let results = results.iter(values.len()); for (result, value) in results.zip(values) { // Safety: The `callee.results()` always refer to a span of valid // registers of the `caller` that does not overlap with the // registers of the callee since they reside in different // call frames. Therefore this access is safe. let value = self.get_stack_slot(value); unsafe { caller_sp.set(result, value) } } self.return_impl(store) } /// Execute an [`Op::ReturnMany`] returning many values. pub fn execute_return_many( &mut self, store: &mut StoreInner, values: [Slot; 3], ) -> ControlFlow { self.ip.add(1); self.copy_many_return_values(self.ip, &values); self.return_impl(store) } /// Copies many return values to the caller frame. /// /// # Note /// /// Used by the execution logic for /// /// - [`Op::ReturnMany`] pub fn copy_many_return_values(&mut self, ip: InstructionPtr, values: &[Slot]) { let (mut caller_sp, results) = self.return_caller_results(); let mut result = results.head(); let mut copy_results = |values: &[Slot]| { for value in values { let value = self.get_stack_slot(*value); // Safety: The `callee.results()` always refer to a span of valid // registers of the `caller` that does not overlap with the // registers of the callee since they reside in different // call frames. Therefore this access is safe. unsafe { caller_sp.set(result, value) } result = result.next(); } }; copy_results(values); let mut ip = ip; while let Op::SlotList { regs } = ip.get() { copy_results(regs); ip.add(1); } let values = match ip.get() { Op::Slot { slot } => slice::from_ref(slot), Op::Slot2 { slots } => slots, Op::Slot3 { slots } => slots, unexpected => { // Safety: Wasmi translation guarantees that a slot-list finalizer exists. unsafe { unreachable_unchecked!( "unexpected slot-list finalizer but found: {unexpected:?}" ) } } }; copy_results(values); } } wasmi-1.1.0/src/engine/executor/instrs/select.rs000064400000000000000000000130551046102023000200100ustar 00000000000000use super::{Executor, InstructionPtr, UntypedValueExt}; use crate::{ core::{wasm, ReadAs, UntypedVal}, engine::utils::unreachable_unchecked, ir::{Const16, Op, Slot}, }; impl Executor<'_> { /// Fetches two [`Slot`]s. fn fetch_register_2(&self) -> (Slot, Slot) { let mut addr: InstructionPtr = self.ip; addr.add(1); match *addr.get() { Op::Slot2 { slots: [slot0, slot1], } => (slot0, slot1), unexpected => { // Safety: Wasmi translation guarantees that [`Op::Slot2`] exists. unsafe { unreachable_unchecked!("expected `Op::Slot2` but found {unexpected:?}") } } } } /// Executes a fused `cmp`+`select` instruction. #[inline(always)] fn execute_cmp_select_impl( &mut self, result: Slot, lhs: Slot, rhs: Slot, f: fn(T, T) -> bool, ) where UntypedVal: ReadAs, { let (true_val, false_val) = self.fetch_register_2(); let lhs: T = self.get_stack_slot_as(lhs); let rhs: T = self.get_stack_slot_as(rhs); let selected = self.get_stack_slot(match f(lhs, rhs) { true => true_val, false => false_val, }); self.set_stack_slot(result, selected); self.next_instr_at(2); } /// Executes a fused `cmp`+`select` instruction with immediate `rhs` parameter. #[inline(always)] fn execute_cmp_select_imm_rhs_impl( &mut self, result: Slot, lhs: Slot, rhs: Const16, f: fn(T, T) -> bool, ) where UntypedVal: ReadAs, T: From>, { let (true_val, false_val) = self.fetch_register_2(); let lhs: T = self.get_stack_slot_as(lhs); let rhs: T = rhs.into(); let selected = self.get_stack_slot(match f(lhs, rhs) { true => true_val, false => false_val, }); self.set_stack_slot(result, selected); self.next_instr_at(2); } } macro_rules! impl_cmp_select_for { ( $( (Op::$doc_name:ident, $fn_name:ident, $op:expr) ),* $(,)? ) => { $( #[doc = concat!("Executes an [`Op::", stringify!($doc_name), "`].")] pub fn $fn_name(&mut self, result: Slot, lhs: Slot, rhs: Slot) { self.execute_cmp_select_impl(result, lhs, rhs, $op) } )* }; } macro_rules! impl_cmp_select_imm_rhs_for { ( $( ($ty:ty, Op::$doc_name:ident, $fn_name:ident, $op:expr) ),* $(,)? ) => { $( #[doc = concat!("Executes an [`Op::", stringify!($doc_name), "`].")] pub fn $fn_name(&mut self, result: Slot, lhs: Slot, rhs: Const16<$ty>) { self.execute_cmp_select_imm_rhs_impl::<$ty>(result, lhs, rhs, $op) } )* }; } impl Executor<'_> { impl_cmp_select_for! { (Op::SelectI32Eq, execute_select_i32_eq, wasm::i32_eq), (Op::SelectI32LtS, execute_select_i32_lt_s, wasm::i32_lt_s), (Op::SelectI32LtU, execute_select_i32_lt_u, wasm::i32_lt_u), (Op::SelectI32LeS, execute_select_i32_le_s, wasm::i32_le_s), (Op::SelectI32LeU, execute_select_i32_le_u, wasm::i32_le_u), (Op::SelectI32And, execute_select_i32_and, ::and), (Op::SelectI32Or, execute_select_i32_or, ::or), (Op::SelectI64Eq, execute_select_i64_eq, wasm::i64_eq), (Op::SelectI64LtS, execute_select_i64_lt_s, wasm::i64_lt_s), (Op::SelectI64LtU, execute_select_i64_lt_u, wasm::i64_lt_u), (Op::SelectI64LeS, execute_select_i64_le_s, wasm::i64_le_s), (Op::SelectI64LeU, execute_select_i64_le_u, wasm::i64_le_u), (Op::SelectI64And, execute_select_i64_and, ::and), (Op::SelectI64Or, execute_select_i64_or, ::or), (Op::SelectF32Eq, execute_select_f32_eq, wasm::f32_eq), (Op::SelectF32Lt, execute_select_f32_lt, wasm::f32_lt), (Op::SelectF32Le, execute_select_f32_le, wasm::f32_le), (Op::SelectF64Eq, execute_select_f64_eq, wasm::f64_eq), (Op::SelectF64Lt, execute_select_f64_lt, wasm::f64_lt), (Op::SelectF64Le, execute_select_f64_le, wasm::f64_le), } impl_cmp_select_imm_rhs_for! { (i32, Op::SelectI32EqImm16, execute_select_i32_eq_imm16, wasm::i32_eq), (i32, Op::SelectI32LtSImm16Rhs, execute_select_i32_lt_s_imm16_rhs, wasm::i32_lt_s), (u32, Op::SelectI32LtUImm16Rhs, execute_select_i32_lt_u_imm16_rhs, wasm::i32_lt_u), (i32, Op::SelectI32LeSImm16Rhs, execute_select_i32_le_s_imm16_rhs, wasm::i32_le_s), (u32, Op::SelectI32LeUImm16Rhs, execute_select_i32_le_u_imm16_rhs, wasm::i32_le_u), (i32, Op::SelectI32AndImm16, execute_select_i32_and_imm16, UntypedValueExt::and), (i32, Op::SelectI32OrImm16, execute_select_i32_or_imm16, UntypedValueExt::or), (i64, Op::SelectI64EqImm16, execute_select_i64_eq_imm16, wasm::i64_eq), (i64, Op::SelectI64LtSImm16Rhs, execute_select_i64_lt_s_imm16_rhs, wasm::i64_lt_s), (u64, Op::SelectI64LtUImm16Rhs, execute_select_i64_lt_u_imm16_rhs, wasm::i64_lt_u), (i64, Op::SelectI64LeSImm16Rhs, execute_select_i64_le_s_imm16_rhs, wasm::i64_le_s), (u64, Op::SelectI64LeUImm16Rhs, execute_select_i64_le_u_imm16_rhs, wasm::i64_le_u), (i64, Op::SelectI64AndImm16, execute_select_i64_and_imm16, UntypedValueExt::and), (i64, Op::SelectI64OrImm16, execute_select_i64_or_imm16, UntypedValueExt::or), } } wasmi-1.1.0/src/engine/executor/instrs/simd.rs000064400000000000000000001062111046102023000174620ustar 00000000000000use super::Executor; use crate::{ core::{ simd::{ self, ImmLaneIdx16, ImmLaneIdx2, ImmLaneIdx32, ImmLaneIdx4, ImmLaneIdx8, IntoLaneIdx, }, UntypedVal, WriteAs, }, engine::{executor::InstructionPtr, utils::unreachable_unchecked}, ir::{index, Address32, AnyConst32, Offset64, Offset64Lo, Offset8, Op, ShiftAmount, Slot}, store::StoreInner, Error, TrapCode, V128, }; #[cfg(doc)] use crate::ir::Offset64Hi; impl Executor<'_> { /// Fetches a [`Slot`] from an [`Op::Slot`] instruction parameter. fn fetch_register(&self) -> Slot { let mut addr: InstructionPtr = self.ip; addr.add(1); match *addr.get() { Op::Slot { slot } => slot, unexpected => { // Safety: Wasmi translation guarantees that [`Op::Slot`] exists. unsafe { unreachable_unchecked!("expected `Op::Slot` but found {unexpected:?}") } } } } /// Fetches the [`Slot`] and [`Offset64Hi`] parameters for a load or store [`Op`]. unsafe fn fetch_reg_and_lane(&self, delta: usize) -> (Slot, LaneType) where LaneType: TryFrom, { let mut addr: InstructionPtr = self.ip; addr.add(delta); match addr.get().filter_register_and_lane::() { Ok(value) => value, Err(instr) => unsafe { unreachable_unchecked!("expected an `Op::SlotAndImm32` but found: {instr:?}") }, } } /// Returns the register `value` and `lane` parameters for a `load` [`Op`]. pub fn fetch_value_and_lane(&self, delta: usize) -> (Slot, LaneType) where LaneType: TryFrom, { // Safety: Wasmi translation guarantees that `Op::SlotAndImm32` exists. unsafe { self.fetch_reg_and_lane::(delta) } } /// Fetches a [`Slot`] from an [`Op::Const32`] instruction parameter. fn fetch_const32_as(&self) -> T where T: From, { let mut addr: InstructionPtr = self.ip; addr.add(1); match *addr.get() { Op::Const32 { value } => value.into(), unexpected => { // Safety: Wasmi translation guarantees that [`Op::Const32`] exists. unsafe { unreachable_unchecked!("expected `Op::Const32` but found {unexpected:?}") } } } } /// Fetches a [`Slot`] from an [`Op::I64Const32`] instruction parameter. fn fetch_i64const32(&self) -> i64 { let mut addr: InstructionPtr = self.ip; addr.add(1); match *addr.get() { Op::I64Const32 { value } => value.into(), unexpected => { // Safety: Wasmi translation guarantees that [`Op::I64Const32`] exists. unsafe { unreachable_unchecked!("expected `Op::I64Const32` but found {unexpected:?}") } } } } /// Fetches a [`Slot`] from an [`Op::F64Const32`] instruction parameter. fn fetch_f64const32(&self) -> f64 { let mut addr: InstructionPtr = self.ip; addr.add(1); match *addr.get() { Op::F64Const32 { value } => value.into(), unexpected => { // Safety: Wasmi translation guarantees that [`Op::F64Const32`] exists. unsafe { unreachable_unchecked!("expected `Op::F64Const32` but found {unexpected:?}") } } } } /// Executes an [`Op::I8x16Shuffle`] instruction. pub fn execute_i8x16_shuffle(&mut self, result: Slot, lhs: Slot, rhs: Slot) { let selector = self.fetch_register(); let lhs = self.get_stack_slot_as::(lhs); let rhs = self.get_stack_slot_as::(rhs); let selector = self .get_stack_slot_as::(selector) .as_u128() .to_ne_bytes() .map(|lane| { match ImmLaneIdx32::try_from(lane) { Ok(lane) => lane, Err(_) => { // Safety: Wasmi translation guarantees that the indices are within bounds. unsafe { unreachable_unchecked!("unexpected out of bounds index: {lane}") } } } }); self.set_stack_slot_as::(result, simd::i8x16_shuffle(lhs, rhs, selector)); self.next_instr_at(2); } } macro_rules! impl_ternary_simd_executors { ( $( (Op::$var_name:ident, $fn_name:ident, $op:expr $(,)?) ),* $(,)? ) => { $( #[doc = concat!("Executes an [`Op::", stringify!($var_name), "`].")] pub fn $fn_name(&mut self, result: Slot, a: Slot, b: Slot) { let c = self.fetch_register(); let a = self.get_stack_slot_as::(a); let b = self.get_stack_slot_as::(b); let c = self.get_stack_slot_as::(c); self.set_stack_slot_as::(result, $op(a, b, c)); self.next_instr_at(2); } )* }; } impl Executor<'_> { impl_ternary_simd_executors! { (Op::V128Bitselect, execute_v128_bitselect, simd::v128_bitselect), ( Op::I32x4RelaxedDotI8x16I7x16AddS, execute_i32x4_relaxed_dot_i8x16_i7x16_add_s, simd::i32x4_relaxed_dot_i8x16_i7x16_add_s, ), (Op::F32x4RelaxedMadd, execute_f32x4_relaxed_madd, simd::f32x4_relaxed_madd), (Op::F32x4RelaxedNmadd, execute_f32x4_relaxed_nmadd, simd::f32x4_relaxed_nmadd), (Op::F64x2RelaxedMadd, execute_f64x2_relaxed_madd, simd::f64x2_relaxed_madd), (Op::F64x2RelaxedNmadd, execute_f64x2_relaxed_nmadd, simd::f64x2_relaxed_nmadd), } } macro_rules! impl_replace_lane_ops { ( $( ($ty:ty, Op::$instr_name:ident, $exec_name:ident, $execute:expr) ),* $(,)? ) => { $( #[doc = concat!("Executes an [`Op::", stringify!($instr_name), "`].")] pub fn $exec_name(&mut self, result: Slot, input: Slot, lane: <$ty as IntoLaneIdx>::LaneIdx) { let value = self.fetch_register(); let input = self.get_stack_slot_as::(input); let value = self.get_stack_slot_as::<$ty>(value); self.set_stack_slot_as::(result, $execute(input, lane, value)); self.next_instr_at(2); } )* }; } impl Executor<'_> { impl_replace_lane_ops! { (i8, Op::I8x16ReplaceLane, execute_i8x16_replace_lane, simd::i8x16_replace_lane), (i16, Op::I16x8ReplaceLane, execute_i16x8_replace_lane, simd::i16x8_replace_lane), (i32, Op::I32x4ReplaceLane, execute_i32x4_replace_lane, simd::i32x4_replace_lane), (i64, Op::I64x2ReplaceLane, execute_i64x2_replace_lane, simd::i64x2_replace_lane), (f32, Op::F32x4ReplaceLane, execute_f32x4_replace_lane, simd::f32x4_replace_lane), (f64, Op::F64x2ReplaceLane, execute_f64x2_replace_lane, simd::f64x2_replace_lane), } /// Executes an [`Op::I8x16ReplaceLaneImm`] instruction. pub fn execute_i8x16_replace_lane_imm( &mut self, result: Slot, input: Slot, lane: ImmLaneIdx16, value: i8, ) { self.execute_replace_lane_impl(result, input, lane, value, 1, simd::i8x16_replace_lane) } /// Executes an [`Op::I16x8ReplaceLaneImm`] instruction. pub fn execute_i16x8_replace_lane_imm(&mut self, result: Slot, input: Slot, lane: ImmLaneIdx8) { let value = self.fetch_const32_as::() as i16; self.execute_replace_lane_impl(result, input, lane, value, 2, simd::i16x8_replace_lane) } /// Executes an [`Op::I32x4ReplaceLaneImm`] instruction. pub fn execute_i32x4_replace_lane_imm(&mut self, result: Slot, input: Slot, lane: ImmLaneIdx4) { let value = self.fetch_const32_as::(); self.execute_replace_lane_impl(result, input, lane, value, 2, simd::i32x4_replace_lane) } /// Executes an [`Op::I64x2ReplaceLaneImm32`] instruction. pub fn execute_i64x2_replace_lane_imm32( &mut self, result: Slot, input: Slot, lane: ImmLaneIdx2, ) { let value = self.fetch_i64const32(); self.execute_replace_lane_impl(result, input, lane, value, 2, simd::i64x2_replace_lane) } /// Executes an [`Op::F32x4ReplaceLaneImm`] instruction. pub fn execute_f32x4_replace_lane_imm(&mut self, result: Slot, input: Slot, lane: ImmLaneIdx4) { let value = self.fetch_const32_as::(); self.execute_replace_lane_impl(result, input, lane, value, 2, simd::f32x4_replace_lane) } /// Executes an [`Op::F64x2ReplaceLaneImm32`] instruction. pub fn execute_f64x2_replace_lane_imm32( &mut self, result: Slot, input: Slot, lane: ImmLaneIdx2, ) { let value = self.fetch_f64const32(); self.execute_replace_lane_impl(result, input, lane, value, 2, simd::f64x2_replace_lane) } /// Generically execute a SIMD replace lane instruction. fn execute_replace_lane_impl( &mut self, result: Slot, input: Slot, lane: LaneType, value: T, delta: usize, eval: fn(V128, LaneType, T) -> V128, ) { let input = self.get_stack_slot_as::(input); self.set_stack_slot_as::(result, eval(input, lane, value)); self.next_instr_at(delta); } impl_unary_executors! { (Op::V128AnyTrue, execute_v128_any_true, simd::v128_any_true), (Op::I8x16AllTrue, execute_i8x16_all_true, simd::i8x16_all_true), (Op::I8x16Bitmask, execute_i8x16_bitmask, simd::i8x16_bitmask), (Op::I16x8AllTrue, execute_i16x8_all_true, simd::i16x8_all_true), (Op::I16x8Bitmask, execute_i16x8_bitmask, simd::i16x8_bitmask), (Op::I32x4AllTrue, execute_i32x4_all_true, simd::i32x4_all_true), (Op::I32x4Bitmask, execute_i32x4_bitmask, simd::i32x4_bitmask), (Op::I64x2AllTrue, execute_i64x2_all_true, simd::i64x2_all_true), (Op::I64x2Bitmask, execute_i64x2_bitmask, simd::i64x2_bitmask), (Op::I8x16Neg, execute_i8x16_neg, simd::i8x16_neg), (Op::I16x8Neg, execute_i16x8_neg, simd::i16x8_neg), (Op::I16x8Neg, execute_i32x4_neg, simd::i32x4_neg), (Op::I16x8Neg, execute_i64x2_neg, simd::i64x2_neg), (Op::I16x8Neg, execute_f32x4_neg, simd::f32x4_neg), (Op::I16x8Neg, execute_f64x2_neg, simd::f64x2_neg), (Op::I8x16Abs, execute_i8x16_abs, simd::i8x16_abs), (Op::I16x8Abs, execute_i16x8_abs, simd::i16x8_abs), (Op::I16x8Abs, execute_i32x4_abs, simd::i32x4_abs), (Op::I16x8Abs, execute_i64x2_abs, simd::i64x2_abs), (Op::I16x8Abs, execute_f32x4_abs, simd::f32x4_abs), (Op::I16x8Abs, execute_f64x2_abs, simd::f64x2_abs), (Op::I8x16Splat, execute_i8x16_splat, simd::i8x16_splat), (Op::I16x8Splat, execute_i16x8_splat, simd::i16x8_splat), (Op::I32x4Splat, execute_i32x4_splat, simd::i32x4_splat), (Op::I64x2Splat, execute_i64x2_splat, simd::i64x2_splat), (Op::F32x4Splat, execute_f32x4_splat, simd::f32x4_splat), (Op::F64x2Splat, execute_f64x2_splat, simd::f64x2_splat), (Op::I16x8ExtaddPairwiseI8x16S, execute_i16x8_extadd_pairwise_i8x16_s, simd::i16x8_extadd_pairwise_i8x16_s), (Op::I16x8ExtaddPairwiseI8x16U, execute_i16x8_extadd_pairwise_i8x16_u, simd::i16x8_extadd_pairwise_i8x16_u), (Op::I32x4ExtaddPairwiseI16x8S, execute_i32x4_extadd_pairwise_i16x8_s, simd::i32x4_extadd_pairwise_i16x8_s), (Op::I32x4ExtaddPairwiseI16x8U, execute_i32x4_extadd_pairwise_i16x8_u, simd::i32x4_extadd_pairwise_i16x8_u), (Op::F32x4Ceil, execute_f32x4_ceil, simd::f32x4_ceil), (Op::F32x4Floor, execute_f32x4_floor, simd::f32x4_floor), (Op::F32x4Trunc, execute_f32x4_trunc, simd::f32x4_trunc), (Op::F32x4Nearest, execute_f32x4_nearest, simd::f32x4_nearest), (Op::F32x4Sqrt, execute_f32x4_sqrt, simd::f32x4_sqrt), (Op::F64x2Ceil, execute_f64x2_ceil, simd::f64x2_ceil), (Op::F64x2Floor, execute_f64x2_floor, simd::f64x2_floor), (Op::F64x2Trunc, execute_f64x2_trunc, simd::f64x2_trunc), (Op::F64x2Nearest, execute_f64x2_nearest, simd::f64x2_nearest), (Op::F64x2Sqrt, execute_f64x2_sqrt, simd::f64x2_sqrt), (Op::V128Not, execute_v128_not, simd::v128_not), (Op::I8x16Popcnt, execute_i8x16_popcnt, simd::i8x16_popcnt), (Op::i16x8_extend_low_i8x16_s, execute_i16x8_extend_low_i8x16_s, simd::i16x8_extend_low_i8x16_s), (Op::i16x8_extend_high_i8x16_s, execute_i16x8_extend_high_i8x16_s, simd::i16x8_extend_high_i8x16_s), (Op::i16x8_extend_low_i8x16_u, execute_i16x8_extend_low_i8x16_u, simd::i16x8_extend_low_i8x16_u), (Op::i16x8_extend_high_i8x16_u, execute_i16x8_extend_high_i8x16_u, simd::i16x8_extend_high_i8x16_u), (Op::i32x4_extend_low_i16x8_s, execute_i32x4_extend_low_i16x8_s, simd::i32x4_extend_low_i16x8_s), (Op::i32x4_extend_high_i16x8_s, execute_i32x4_extend_high_i16x8_s, simd::i32x4_extend_high_i16x8_s), (Op::i32x4_extend_low_i16x8_u, execute_i32x4_extend_low_i16x8_u, simd::i32x4_extend_low_i16x8_u), (Op::i32x4_extend_high_i16x8_u, execute_i32x4_extend_high_i16x8_u, simd::i32x4_extend_high_i16x8_u), (Op::i64x2_extend_low_i32x4_s, execute_i64x2_extend_low_i32x4_s, simd::i64x2_extend_low_i32x4_s), (Op::i64x2_extend_high_i32x4_s, execute_i64x2_extend_high_i32x4_s, simd::i64x2_extend_high_i32x4_s), (Op::i64x2_extend_low_i32x4_u, execute_i64x2_extend_low_i32x4_u, simd::i64x2_extend_low_i32x4_u), (Op::i64x2_extend_high_i32x4_u, execute_i64x2_extend_high_i32x4_u, simd::i64x2_extend_high_i32x4_u), (Op::I32x4TruncSatF32x4S, execute_i32x4_trunc_sat_f32x4_s, simd::i32x4_trunc_sat_f32x4_s), (Op::I32x4TruncSatF32x4U, execute_i32x4_trunc_sat_f32x4_u, simd::i32x4_trunc_sat_f32x4_u), (Op::F32x4ConvertI32x4S, execute_f32x4_convert_i32x4_s, simd::f32x4_convert_i32x4_s), (Op::F32x4ConvertI32x4U, execute_f32x4_convert_i32x4_u, simd::f32x4_convert_i32x4_u), (Op::I32x4TruncSatF64x2SZero, execute_i32x4_trunc_sat_f64x2_s_zero, simd::i32x4_trunc_sat_f64x2_s_zero), (Op::I32x4TruncSatF64x2UZero, execute_i32x4_trunc_sat_f64x2_u_zero, simd::i32x4_trunc_sat_f64x2_u_zero), (Op::F64x2ConvertLowI32x4S, execute_f64x2_convert_low_i32x4_s, simd::f64x2_convert_low_i32x4_s), (Op::F64x2ConvertLowI32x4U, execute_f64x2_convert_low_i32x4_u, simd::f64x2_convert_low_i32x4_u), (Op::F32x4DemoteF64x2Zero, execute_f32x4_demote_f64x2_zero, simd::f32x4_demote_f64x2_zero), (Op::F64x2PromoteLowF32x4, execute_f64x2_promote_low_f32x4, simd::f64x2_promote_low_f32x4), } impl_binary_executors! { (Op::I8x16Swizzle, execute_i8x16_swizzle, simd::i8x16_swizzle), (Op::I16x8Q15MulrSatS, execute_i16x8_q15mulr_sat_s, simd::i16x8_q15mulr_sat_s), (Op::I32x4DotI16x8S, execute_i32x4_dot_i16x8_s, simd::i32x4_dot_i16x8_s), (Op::I16x8RelaxedDotI8x16I7x16S, execute_i16x8_relaxed_dot_i8x16_i7x16_s, simd::i16x8_relaxed_dot_i8x16_i7x16_s), (Op::I16x8ExtmulLowI8x16S, execute_i16x8_extmul_low_i8x16_s, simd::i16x8_extmul_low_i8x16_s), (Op::I16x8ExtmulHighI8x16S, execute_i16x8_extmul_high_i8x16_s, simd::i16x8_extmul_high_i8x16_s), (Op::I16x8ExtmulLowI8x16U, execute_i16x8_extmul_low_i8x16_u, simd::i16x8_extmul_low_i8x16_u), (Op::I16x8ExtmulHighI8x16U, execute_i16x8_extmul_high_i8x16_u, simd::i16x8_extmul_high_i8x16_u), (Op::I32x4ExtmulLowI16x8S, execute_i32x4_extmul_low_i16x8_s, simd::i32x4_extmul_low_i16x8_s), (Op::I32x4ExtmulHighI16x8S, execute_i32x4_extmul_high_i16x8_s, simd::i32x4_extmul_high_i16x8_s), (Op::I32x4ExtmulLowI16x8U, execute_i32x4_extmul_low_i16x8_u, simd::i32x4_extmul_low_i16x8_u), (Op::I32x4ExtmulHighI16x8U, execute_i32x4_extmul_high_i16x8_u, simd::i32x4_extmul_high_i16x8_u), (Op::I64x2ExtmulLowI32x4S, execute_i64x2_extmul_low_i32x4_s, simd::i64x2_extmul_low_i32x4_s), (Op::I64x2ExtmulHighI32x4S, execute_i64x2_extmul_high_i32x4_s, simd::i64x2_extmul_high_i32x4_s), (Op::I64x2ExtmulLowI32x4U, execute_i64x2_extmul_low_i32x4_u, simd::i64x2_extmul_low_i32x4_u), (Op::I64x2ExtmulHighI32x4U, execute_i64x2_extmul_high_i32x4_u, simd::i64x2_extmul_high_i32x4_u), (Op::I32x4Add, execute_i32x4_add, simd::i32x4_add), (Op::I32x4Sub, execute_i32x4_sub, simd::i32x4_sub), (Op::I32x4Mul, execute_i32x4_mul, simd::i32x4_mul), (Op::I64x2Add, execute_i64x2_add, simd::i64x2_add), (Op::I64x2Sub, execute_i64x2_sub, simd::i64x2_sub), (Op::I64x2Mul, execute_i64x2_mul, simd::i64x2_mul), (Op::I8x16Eq, execute_i8x16_eq, simd::i8x16_eq), (Op::I8x16Ne, execute_i8x16_ne, simd::i8x16_ne), (Op::I8x16LtS, execute_i8x16_lt_s, simd::i8x16_lt_s), (Op::I8x16LtU, execute_i8x16_lt_u, simd::i8x16_lt_u), (Op::I8x16LeS, execute_i8x16_le_s, simd::i8x16_le_s), (Op::I8x16LeU, execute_i8x16_le_u, simd::i8x16_le_u), (Op::I16x8Eq, execute_i16x8_eq, simd::i16x8_eq), (Op::I16x8Ne, execute_i16x8_ne, simd::i16x8_ne), (Op::I16x8LtS, execute_i16x8_lt_s, simd::i16x8_lt_s), (Op::I16x8LtU, execute_i16x8_lt_u, simd::i16x8_lt_u), (Op::I16x8LeS, execute_i16x8_le_s, simd::i16x8_le_s), (Op::I16x8LeU, execute_i16x8_le_u, simd::i16x8_le_u), (Op::I32x4Eq, execute_i32x4_eq, simd::i32x4_eq), (Op::I32x4Ne, execute_i32x4_ne, simd::i32x4_ne), (Op::I32x4LtS, execute_i32x4_lt_s, simd::i32x4_lt_s), (Op::I32x4LtU, execute_i32x4_lt_u, simd::i32x4_lt_u), (Op::I32x4LeS, execute_i32x4_le_s, simd::i32x4_le_s), (Op::I32x4LeU, execute_i32x4_le_u, simd::i32x4_le_u), (Op::I64x2Eq, execute_i64x2_eq, simd::i64x2_eq), (Op::I64x2Ne, execute_i64x2_ne, simd::i64x2_ne), (Op::I64x2LtS, execute_i64x2_lt_s, simd::i64x2_lt_s), (Op::I64x2LeS, execute_i64x2_le_s, simd::i64x2_le_s), (Op::F32x4Eq, execute_f32x4_eq, simd::f32x4_eq), (Op::F32x4Ne, execute_f32x4_ne, simd::f32x4_ne), (Op::F32x4Lt, execute_f32x4_lt, simd::f32x4_lt), (Op::F32x4Le, execute_f32x4_le, simd::f32x4_le), (Op::F64x2Eq, execute_f64x2_eq, simd::f64x2_eq), (Op::F64x2Ne, execute_f64x2_ne, simd::f64x2_ne), (Op::F64x2Lt, execute_f64x2_lt, simd::f64x2_lt), (Op::F64x2Le, execute_f64x2_le, simd::f64x2_le), (Op::I8x16MinS, execute_i8x16_min_s, simd::i8x16_min_s), (Op::I8x16MinU, execute_i8x16_min_u, simd::i8x16_min_u), (Op::I8x16MaxS, execute_i8x16_max_s, simd::i8x16_max_s), (Op::I8x16MaxU, execute_i8x16_max_u, simd::i8x16_max_u), (Op::I8x16AvgrU, execute_i8x16_avgr_u, simd::i8x16_avgr_u), (Op::I16x8MinS, execute_i16x8_min_s, simd::i16x8_min_s), (Op::I16x8MinU, execute_i16x8_min_u, simd::i16x8_min_u), (Op::I16x8MaxS, execute_i16x8_max_s, simd::i16x8_max_s), (Op::I16x8MaxU, execute_i16x8_max_u, simd::i16x8_max_u), (Op::I16x8AvgrU, execute_i16x8_avgr_u, simd::i16x8_avgr_u), (Op::I32x4MinS, execute_i32x4_min_s, simd::i32x4_min_s), (Op::I32x4MinU, execute_i32x4_min_u, simd::i32x4_min_u), (Op::I32x4MaxS, execute_i32x4_max_s, simd::i32x4_max_s), (Op::I32x4MaxU, execute_i32x4_max_u, simd::i32x4_max_u), (Op::I8x16Shl, execute_i8x16_shl, simd::i8x16_shl), (Op::I8x16ShrS, execute_i8x16_shr_s, simd::i8x16_shr_s), (Op::I8x16ShrU, execute_i8x16_shr_u, simd::i8x16_shr_u), (Op::I16x8Shl, execute_i16x8_shl, simd::i16x8_shl), (Op::I16x8ShrS, execute_i16x8_shr_s, simd::i16x8_shr_s), (Op::I16x8ShrU, execute_i16x8_shr_u, simd::i16x8_shr_u), (Op::I32x4Shl, execute_i32x4_shl, simd::i32x4_shl), (Op::I32x4ShrS, execute_i32x4_shr_s, simd::i32x4_shr_s), (Op::I32x4ShrU, execute_i32x4_shr_u, simd::i32x4_shr_u), (Op::I64x2Shl, execute_i64x2_shl, simd::i64x2_shl), (Op::I64x2ShrS, execute_i64x2_shr_s, simd::i64x2_shr_s), (Op::I64x2ShrU, execute_i64x2_shr_u, simd::i64x2_shr_u), (Op::I8x16Add, execute_i8x16_add, simd::i8x16_add), (Op::I8x16AddSatS, execute_i8x16_add_sat_s, simd::i8x16_add_sat_s), (Op::I8x16AddSatU, execute_i8x16_add_sat_u, simd::i8x16_add_sat_u), (Op::I8x16Sub, execute_i8x16_sub, simd::i8x16_sub), (Op::I8x16SubSatS, execute_i8x16_sub_sat_s, simd::i8x16_sub_sat_s), (Op::I8x16SubSatU, execute_i8x16_sub_sat_u, simd::i8x16_sub_sat_u), (Op::I16x8Add, execute_i16x8_add, simd::i16x8_add), (Op::I16x8AddSatS, execute_i16x8_add_sat_s, simd::i16x8_add_sat_s), (Op::I16x8AddSatU, execute_i16x8_add_sat_u, simd::i16x8_add_sat_u), (Op::I16x8Sub, execute_i16x8_sub, simd::i16x8_sub), (Op::I16x8SubSatS, execute_i16x8_sub_sat_s, simd::i16x8_sub_sat_s), (Op::I16x8SubSatU, execute_i16x8_sub_sat_u, simd::i16x8_sub_sat_u), (Op::I16x8Sub, execute_i16x8_mul, simd::i16x8_mul), (Op::V128And, execute_v128_and, simd::v128_and), (Op::V128Andnot, execute_v128_andnot, simd::v128_andnot), (Op::V128Or, execute_v128_or, simd::v128_or), (Op::V128Xor, execute_v128_xor, simd::v128_xor), (Op::F32x4Add, execute_f32x4_add, simd::f32x4_add), (Op::F32x4Sub, execute_f32x4_sub, simd::f32x4_sub), (Op::F32x4Mul, execute_f32x4_mul, simd::f32x4_mul), (Op::F32x4Div, execute_f32x4_div, simd::f32x4_div), (Op::F32x4Min, execute_f32x4_min, simd::f32x4_min), (Op::F32x4Max, execute_f32x4_max, simd::f32x4_max), (Op::F32x4Pmin, execute_f32x4_pmin, simd::f32x4_pmin), (Op::F32x4Pmax, execute_f32x4_pmax, simd::f32x4_pmax), (Op::F64x2Add, execute_f64x2_add, simd::f64x2_add), (Op::F64x2Sub, execute_f64x2_sub, simd::f64x2_sub), (Op::F64x2Mul, execute_f64x2_mul, simd::f64x2_mul), (Op::F64x2Div, execute_f64x2_div, simd::f64x2_div), (Op::F64x2Min, execute_f64x2_min, simd::f64x2_min), (Op::F64x2Max, execute_f64x2_max, simd::f64x2_max), (Op::F64x2Pmin, execute_f64x2_pmin, simd::f64x2_pmin), (Op::F64x2Pmax, execute_f64x2_pmax, simd::f64x2_pmax), (Op::I8x16NarrowI16x8S, execute_i8x16_narrow_i16x8_s, simd::i8x16_narrow_i16x8_s), (Op::I8x16NarrowI16x8U, execute_i8x16_narrow_i16x8_u, simd::i8x16_narrow_i16x8_u), (Op::I16x8NarrowI32x4S, execute_i16x8_narrow_i32x4_s, simd::i16x8_narrow_i32x4_s), (Op::I16x8NarrowI32x4U, execute_i16x8_narrow_i32x4_u, simd::i16x8_narrow_i32x4_u), } } impl Executor<'_> { /// Executes a generic SIMD extract-lane [`Op`]. #[inline(always)] fn execute_extract_lane( &mut self, result: Slot, input: Slot, lane: Lane, op: fn(V128, Lane) -> T, ) where UntypedVal: WriteAs, { let input = self.get_stack_slot_as::(input); self.set_stack_slot_as::(result, op(input, lane)); self.next_instr(); } } macro_rules! impl_extract_lane_executors { ( $( ($ty:ty, Op::$var_name:ident, $fn_name:ident, $op:expr) ),* $(,)? ) => { $( #[doc = concat!("Executes an [`Op::", stringify!($var_name), "`].")] pub fn $fn_name(&mut self, result: Slot, input: Slot, lane: <$ty as IntoLaneIdx>::LaneIdx) { self.execute_extract_lane(result, input, lane, $op) } )* }; } impl Executor<'_> { impl_extract_lane_executors! { (i8, Op::I8x16ExtractLaneS, i8x16_extract_lane_s, simd::i8x16_extract_lane_s), (u8, Op::I8x16ExtractLaneU, i8x16_extract_lane_u, simd::i8x16_extract_lane_u), (i16, Op::I16x8ExtractLaneS, i16x8_extract_lane_s, simd::i16x8_extract_lane_s), (u16, Op::I16x8ExtractLaneU, i16x8_extract_lane_u, simd::i16x8_extract_lane_u), (i32, Op::I32x4ExtractLane, i32x4_extract_lane, simd::i32x4_extract_lane), (u32, Op::F32x4ExtractLane, f32x4_extract_lane, simd::f32x4_extract_lane), (i64, Op::I64x2ExtractLane, i64x2_extract_lane, simd::i64x2_extract_lane), (u64, Op::F64x2ExtractLane, f64x2_extract_lane, simd::f64x2_extract_lane), } } impl Executor<'_> { /// Generically execute a SIMD shift operation with immediate shift amount. #[inline(always)] fn execute_simd_shift_by( &mut self, result: Slot, lhs: Slot, rhs: ShiftAmount, op: fn(V128, u32) -> V128, ) { let lhs = self.get_stack_slot_as::(lhs); let rhs = rhs.into(); self.set_stack_slot_as::(result, op(lhs, rhs)); self.next_instr(); } } macro_rules! impl_simd_shift_executors { ( $( (Op::$var_name:ident, $fn_name:ident, $op:expr) ),* $(,)? ) => { $( #[doc = concat!("Executes an [`Op::", stringify!($var_name), "`].")] pub fn $fn_name(&mut self, result: Slot, lhs: Slot, rhs: ShiftAmount) { self.execute_simd_shift_by(result, lhs, rhs, $op) } )* }; } impl Executor<'_> { impl_simd_shift_executors! { (Op::I8x16ShlBy, execute_i8x16_shl_by, simd::i8x16_shl), (Op::I8x16ShrSBy, execute_i8x16_shr_s_by, simd::i8x16_shr_s), (Op::I8x16ShrUBy, execute_i8x16_shr_u_by, simd::i8x16_shr_u), (Op::I16x8ShlBy, execute_i16x8_shl_by, simd::i16x8_shl), (Op::I16x8ShrSBy, execute_i16x8_shr_s_by, simd::i16x8_shr_s), (Op::I16x8ShrUBy, execute_i16x8_shr_u_by, simd::i16x8_shr_u), (Op::I32x4ShlBy, execute_i32x4_shl_by, simd::i32x4_shl), (Op::I32x4ShrSBy, execute_i32x4_shr_s_by, simd::i32x4_shr_s), (Op::I32x4ShrUBy, execute_i32x4_shr_u_by, simd::i32x4_shr_u), (Op::I64x2ShlBy, execute_i64x2_shl_by, simd::i64x2_shl), (Op::I64x2ShrSBy, execute_i64x2_shr_s_by, simd::i64x2_shr_s), (Op::I64x2ShrUBy, execute_i64x2_shr_u_by, simd::i64x2_shr_u), } } impl Executor<'_> { /// Returns the optional `memory` parameter for a `load_at` [`Op`]. /// /// # Note /// /// - Returns the default [`index::Memory`] if the parameter is missing. /// - Bumps `self.ip` if a [`Op::MemoryIndex`] parameter was found. #[inline(always)] fn fetch_lane_and_memory(&mut self, delta: usize) -> (LaneType, index::Memory) where LaneType: TryFrom, { let mut addr: InstructionPtr = self.ip; addr.add(delta); match addr.get().filter_lane_and_memory() { Ok(value) => value, Err(instr) => unsafe { unreachable_unchecked!("expected an `Op::Imm16AndImm32` but found: {instr:?}") }, } } } type V128LoadLane = fn(memory: &[u8], ptr: u64, offset: u64, x: V128, lane: LaneType) -> Result; type V128LoadLaneAt = fn(memory: &[u8], address: usize, x: V128, lane: LaneType) -> Result; macro_rules! impl_execute_v128_load_lane { ( $( ($ty:ty, Op::$op:ident, $exec:ident, $eval:expr) ),* $(,)? ) => { $( #[doc = concat!("Executes an [`Op::", stringify!($op), "`] instruction.")] pub fn $exec( &mut self, store: &StoreInner, result: Slot, offset_lo: Offset64Lo, ) -> Result<(), Error> { self.execute_v128_load_lane_impl::<<$ty as IntoLaneIdx>::LaneIdx>(store, result, offset_lo, $eval) } )* }; } macro_rules! impl_execute_v128_load_lane_at { ( $( ($ty:ty, Op::$op:ident, $exec:ident, $eval:expr) ),* $(,)? ) => { $( #[doc = concat!("Executes an [`Op::", stringify!($op), "`] instruction.")] pub fn $exec( &mut self, store: &StoreInner, result: Slot, address: Address32, ) -> Result<(), Error> { self.execute_v128_load_lane_at_impl::<<$ty as IntoLaneIdx>::LaneIdx>(store, result, address, $eval) } )* }; } impl Executor<'_> { fn execute_v128_load_lane_impl( &mut self, store: &StoreInner, result: Slot, offset_lo: Offset64Lo, load: V128LoadLane, ) -> Result<(), Error> where LaneType: TryFrom + Into + Copy, { let (ptr, offset_hi) = self.fetch_value_and_offset_hi(); let (v128, lane) = self.fetch_value_and_lane::(2); let memory = self.fetch_optional_memory(3); let offset = Offset64::combine(offset_hi, offset_lo); let ptr = self.get_stack_slot_as::(ptr); let v128 = self.get_stack_slot_as::(v128); let memory = self.fetch_memory_bytes(memory, store); let loaded = load(memory, ptr, u64::from(offset), v128, lane)?; self.set_stack_slot_as::(result, loaded); self.try_next_instr_at(3) } impl_execute_v128_load_lane! { (u8, Op::V128Load8Lane, execute_v128_load8_lane, simd::v128_load8_lane), (u16, Op::V128Load16Lane, execute_v128_load16_lane, simd::v128_load16_lane), (u32, Op::V128Load32Lane, execute_v128_load32_lane, simd::v128_load32_lane), (u64, Op::V128Load64Lane, execute_v128_load64_lane, simd::v128_load64_lane), } fn execute_v128_load_lane_at_impl( &mut self, store: &StoreInner, result: Slot, address: Address32, load_at: V128LoadLaneAt, ) -> Result<(), Error> where LaneType: TryFrom + Into + Copy, { let (v128, lane) = self.fetch_value_and_lane::(1); let memory = self.fetch_optional_memory(2); let v128 = self.get_stack_slot_as::(v128); let memory = self.fetch_memory_bytes(memory, store); let loaded = load_at(memory, usize::from(address), v128, lane)?; self.set_stack_slot_as::(result, loaded); self.try_next_instr_at(2) } impl_execute_v128_load_lane_at! { (u8, Op::V128Load8LaneAt, execute_v128_load8_lane_at, simd::v128_load8_lane_at), (u16, Op::V128Load16LaneAt, execute_v128_load16_lane_at, simd::v128_load16_lane_at), (u32, Op::V128Load32LaneAt, execute_v128_load32_lane_at, simd::v128_load32_lane_at), (u64, Op::V128Load64LaneAt, execute_v128_load64_lane_at, simd::v128_load64_lane_at), } } macro_rules! impl_execute_v128_store_lane { ( $( ($ty:ty, Op::$op:ident, $exec:ident, $eval:expr) ),* $(,)? ) => { $( #[doc = concat!("Executes an [`Op::", stringify!($op), "`] instruction.")] pub fn $exec( &mut self, store: &mut StoreInner, ptr: Slot, offset_lo: Offset64Lo, ) -> Result<(), Error> { self.execute_v128_store_lane::<$ty>(store, ptr, offset_lo, $eval) } )* }; } macro_rules! impl_execute_v128_store_lane_offset16 { ( $( ($ty:ty, Op::$op:ident, $exec:ident, $eval:expr) ),* $(,)? ) => { $( #[doc = concat!("Executes an [`Op::", stringify!($op), "`] instruction.")] pub fn $exec( &mut self, ptr: Slot, value: Slot, offset: Offset8, lane: <$ty as IntoLaneIdx>::LaneIdx, ) -> Result<(), Error> { self.execute_v128_store_lane_offset8::<$ty>(ptr, value, offset, lane, $eval) } )* }; } macro_rules! impl_execute_v128_store_lane_at { ( $( ($ty:ty, Op::$op:ident, $exec:ident, $eval:expr) ),* $(,)? ) => { $( #[doc = concat!("Executes an [`Op::", stringify!($op), "`] instruction.")] pub fn $exec( &mut self, store: &mut StoreInner, value: Slot, address: Address32, ) -> Result<(), Error> { self.execute_v128_store_lane_at::<$ty>(store, value, address, $eval) } )* }; } type V128StoreLane = fn( memory: &mut [u8], ptr: u64, offset: u64, value: V128, lane: LaneType, ) -> Result<(), TrapCode>; type V128StoreLaneAt = fn(memory: &mut [u8], address: usize, value: V128, lane: LaneType) -> Result<(), TrapCode>; impl Executor<'_> { fn execute_v128_store_lane( &mut self, store: &mut StoreInner, ptr: Slot, offset_lo: Offset64Lo, eval: V128StoreLane, ) -> Result<(), Error> { let (value, offset_hi) = self.fetch_value_and_offset_hi(); let (lane, memory) = self.fetch_lane_and_memory(2); let offset = Offset64::combine(offset_hi, offset_lo); let ptr = self.get_stack_slot_as::(ptr); let v128 = self.get_stack_slot_as::(value); let memory = self.fetch_memory_bytes_mut(memory, store); eval(memory, ptr, u64::from(offset), v128, lane)?; self.try_next_instr_at(3) } impl_execute_v128_store_lane! { (u8, Op::V128Store8Lane, execute_v128_store8_lane, simd::v128_store8_lane), (u16, Op::V128Store16Lane, execute_v128_store16_lane, simd::v128_store16_lane), (u32, Op::V128Store32Lane, execute_v128_store32_lane, simd::v128_store32_lane), (u64, Op::V128Store64Lane, execute_v128_store64_lane, simd::v128_store64_lane), } fn execute_v128_store_lane_offset8( &mut self, ptr: Slot, value: Slot, offset: Offset8, lane: T::LaneIdx, eval: V128StoreLane, ) -> Result<(), Error> { let ptr = self.get_stack_slot_as::(ptr); let offset = u64::from(Offset64::from(offset)); let v128 = self.get_stack_slot_as::(value); let memory = self.fetch_default_memory_bytes_mut(); eval(memory, ptr, offset, v128, lane)?; self.try_next_instr() } impl_execute_v128_store_lane_offset16! { (u8, Op::V128Store8LaneOffset8, execute_v128_store8_lane_offset8, simd::v128_store8_lane), (u16, Op::V128Store16LaneOffset8, execute_v128_store16_lane_offset8, simd::v128_store16_lane), (u32, Op::V128Store32LaneOffset8, execute_v128_store32_lane_offset8, simd::v128_store32_lane), (u64, Op::V128Store64LaneOffset8, execute_v128_store64_lane_offset8, simd::v128_store64_lane), } fn execute_v128_store_lane_at( &mut self, store: &mut StoreInner, value: Slot, address: Address32, eval: V128StoreLaneAt, ) -> Result<(), Error> where T::LaneIdx: TryFrom + Into, { let (lane, memory) = self.fetch_lane_and_memory::(1); let v128 = self.get_stack_slot_as::(value); let memory = self.fetch_memory_bytes_mut(memory, store); eval(memory, usize::from(address), v128, lane)?; self.try_next_instr_at(2) } impl_execute_v128_store_lane_at! { (u8, Op::V128Store8LaneAt, execute_v128_store8_lane_at, simd::v128_store8_lane_at), (u16, Op::V128Store16LaneAt, execute_v128_store16_lane_at, simd::v128_store16_lane_at), (u32, Op::V128Store32LaneAt, execute_v128_store32_lane_at, simd::v128_store32_lane_at), (u64, Op::V128Store64LaneAt, execute_v128_store64_lane_at, simd::v128_store64_lane_at), } } wasmi-1.1.0/src/engine/executor/instrs/store.rs000064400000000000000000000367101046102023000176700ustar 00000000000000use super::{Executor, InstructionPtr}; use crate::{ core::{wasm, ReadAs, UntypedVal}, engine::utils::unreachable_unchecked, ir::{ index::Memory, Address32, AnyConst16, Const16, Offset16, Offset64, Offset64Hi, Offset64Lo, Slot, }, store::StoreInner, Error, TrapCode, }; #[cfg(feature = "simd")] use crate::{core::simd, V128}; #[cfg(doc)] use crate::ir::Op; /// The function signature of Wasm store operations. type WasmStoreOp = fn(memory: &mut [u8], address: u64, offset: u64, value: T) -> Result<(), TrapCode>; /// The function signature of Wasm store operations. type WasmStoreAtOp = fn(memory: &mut [u8], address: usize, value: T) -> Result<(), TrapCode>; impl Executor<'_> { /// Returns the immediate `value` and `offset_hi` parameters for a `load` [`Op`]. fn fetch_value_and_offset_imm(&self) -> (T, Offset64Hi) where T: From, { let mut addr: InstructionPtr = self.ip; addr.add(1); match addr.get().filter_imm16_and_offset_hi::() { Ok(value) => value, Err(instr) => unsafe { unreachable_unchecked!("expected an `Op::SlotAndImm32` but found: {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` pub(super) fn execute_store_wrap( &mut self, store: &mut StoreInner, memory: Memory, address: u64, offset: Offset64, value: T, store_wrap: WasmStoreOp, ) -> Result<(), Error> where UntypedVal: ReadAs, { let memory = self.fetch_memory_bytes_mut(memory, store); store_wrap(memory, address, u64::from(offset), value)?; Ok(()) } /// 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` fn execute_store_wrap_at( &mut self, store: &mut StoreInner, memory: Memory, address: Address32, value: T, store_wrap_at: WasmStoreAtOp, ) -> Result<(), Error> { let memory = self.fetch_memory_bytes_mut(memory, store); store_wrap_at(memory, usize::from(address), value)?; Ok(()) } /// Executes a generic Wasm `store[N]` operation for the default memory. /// /// # Note /// /// This can be used to emulate the following Wasm operands: /// /// - `{i32, i64, f32, f64}.store` /// - `{i32, i64}.store8` /// - `{i32, i64}.store16` /// - `i64.store32` fn execute_store_wrap_mem0( &mut self, address: u64, offset: Offset64, value: T, store_wrap: WasmStoreOp, ) -> Result<(), Error> where UntypedVal: ReadAs, { let memory = self.fetch_default_memory_bytes_mut(); store_wrap(memory, address, u64::from(offset), value)?; Ok(()) } fn execute_store( &mut self, store: &mut StoreInner, ptr: Slot, offset_lo: Offset64Lo, store_op: WasmStoreOp, ) -> Result<(), Error> where UntypedVal: ReadAs, { let (value, offset_hi) = self.fetch_value_and_offset_hi(); let memory = self.fetch_optional_memory(2); let offset = Offset64::combine(offset_hi, offset_lo); let ptr = self.get_stack_slot_as::(ptr); let value = self.get_stack_slot_as::(value); self.execute_store_wrap::(store, memory, ptr, offset, value, store_op)?; self.try_next_instr_at(2) } fn execute_store_imm( &mut self, store: &mut StoreInner, ptr: Slot, offset_lo: Offset64Lo, offset_hi: Offset64Hi, value: T, store_op: WasmStoreOp, ) -> Result<(), Error> where UntypedVal: ReadAs, { let memory = self.fetch_optional_memory(2); let offset = Offset64::combine(offset_hi, offset_lo); let ptr = self.get_stack_slot_as::(ptr); self.execute_store_wrap::(store, memory, ptr, offset, value, store_op)?; self.try_next_instr_at(2) } fn execute_store_offset16( &mut self, ptr: Slot, offset: Offset16, value: Slot, store_op: WasmStoreOp, ) -> Result<(), Error> where UntypedVal: ReadAs, { let ptr = self.get_stack_slot_as::(ptr); let value = self.get_stack_slot_as::(value); self.execute_store_wrap_mem0::(ptr, Offset64::from(offset), value, store_op)?; self.try_next_instr() } fn execute_store_offset16_imm16( &mut self, ptr: Slot, offset: Offset16, value: T, store_op: WasmStoreOp, ) -> Result<(), Error> where UntypedVal: ReadAs, { let ptr = self.get_stack_slot_as::(ptr); self.execute_store_wrap_mem0::(ptr, Offset64::from(offset), value, store_op)?; self.try_next_instr() } fn execute_store_at( &mut self, store: &mut StoreInner, address: Address32, value: Slot, store_at_op: WasmStoreAtOp, ) -> Result<(), Error> where UntypedVal: ReadAs, { let memory = self.fetch_optional_memory(1); self.execute_store_wrap_at::( store, memory, address, self.get_stack_slot_as::(value), store_at_op, )?; self.try_next_instr() } fn execute_store_at_imm16( &mut self, store: &mut StoreInner, address: Address32, value: T, store_at_op: WasmStoreAtOp, ) -> Result<(), Error> where UntypedVal: ReadAs, { let memory = self.fetch_optional_memory(1); self.execute_store_wrap_at::(store, memory, address, value, store_at_op)?; self.try_next_instr() } } macro_rules! impl_execute_istore { ( $( ( $ty:ty, ($from_ty:ty => $to_ty:ty), (Op::$var_store_imm:ident, $fn_store_imm:ident), (Op::$var_store_off16_imm16:ident, $fn_store_off16_imm16:ident), (Op::$var_store_at_imm16:ident, $fn_store_at_imm16:ident), $store_fn:expr, $store_at_fn:expr $(,)? ) ),* $(,)? ) => { $( #[doc = concat!("Executes an [`Op::", stringify!($var_store_imm), "`].")] #[allow(clippy::cast_lossless)] pub fn $fn_store_imm(&mut self, store: &mut StoreInner, ptr: Slot, offset_lo: Offset64Lo) -> Result<(), Error> { let (value, offset_hi) = self.fetch_value_and_offset_imm::<$to_ty>(); self.execute_store_imm::<$ty>(store, ptr, offset_lo, offset_hi, value as $ty, $store_fn) } #[doc = concat!("Executes an [`Op::", stringify!($var_store_off16_imm16), "`].")] #[allow(clippy::cast_lossless)] pub fn $fn_store_off16_imm16( &mut self, ptr: Slot, offset: Offset16, value: $from_ty, ) -> Result<(), Error> { self.execute_store_offset16_imm16::<$ty>(ptr, offset, <$to_ty>::from(value) as _, $store_fn) } #[doc = concat!("Executes an [`Op::", stringify!($var_store_at_imm16), "`].")] #[allow(clippy::cast_lossless)] pub fn $fn_store_at_imm16( &mut self, store: &mut StoreInner, address: Address32, value: $from_ty, ) -> Result<(), Error> { #[allow(clippy::cast_lossless)] self.execute_store_at_imm16::<$ty>(store, address, <$to_ty>::from(value) as _, $store_at_fn) } )* }; } impl Executor<'_> { impl_execute_istore! { ( u32, (Const16 => i32), (Op::I32StoreImm16, execute_i32_store_imm16), (Op::I32StoreOffset16Imm16, execute_i32_store_offset16_imm16), (Op::I32StoreAtImm16, execute_i32_store_at_imm16), wasm::store32, wasm::store32_at, ), ( u64, (Const16 => i64), (Op::I64StoreImm16, execute_i64_store_imm16), (Op::I64StoreOffset16Imm16, execute_i64_store_offset16_imm16), (Op::I64StoreAtImm16, execute_i64_store_at_imm16), wasm::store64, wasm::store64_at, ), } } macro_rules! impl_execute_istore_trunc { ( $( ( $ty:ty, ($from_ty:ty => $to_ty:ty), (Op::$var_store:ident, $fn_store:ident), (Op::$var_store_imm:ident, $fn_store_imm:ident), (Op::$var_store_off16:ident, $fn_store_off16:ident), (Op::$var_store_off16_imm16:ident, $fn_store_off16_imm16:ident), (Op::$var_store_at:ident, $fn_store_at:ident), (Op::$var_store_at_imm16:ident, $fn_store_at_imm16:ident), $store_fn:expr, $store_at_fn:expr $(,)? ) ),* $(,)? ) => { $( impl_execute_istore! { ( $ty, ($from_ty => $to_ty), (Op::$var_store_imm, $fn_store_imm), (Op::$var_store_off16_imm16, $fn_store_off16_imm16), (Op::$var_store_at_imm16, $fn_store_at_imm16), $store_fn, $store_at_fn, ) } #[doc = concat!("Executes an [`Op::", stringify!($var_store), "`].")] pub fn $fn_store(&mut self, store: &mut StoreInner, ptr: Slot, offset_lo: Offset64Lo) -> Result<(), Error> { self.execute_store::<$ty>(store, ptr, offset_lo, $store_fn) } #[doc = concat!("Executes an [`Op::", stringify!($var_store_off16), "`].")] pub fn $fn_store_off16( &mut self, ptr: Slot, offset: Offset16, value: Slot, ) -> Result<(), Error> { self.execute_store_offset16::<$ty>(ptr, offset, value, $store_fn) } #[doc = concat!("Executes an [`Op::", stringify!($var_store_at), "`].")] pub fn $fn_store_at(&mut self, store: &mut StoreInner, address: Address32, value: Slot) -> Result<(), Error> { self.execute_store_at::<$ty>(store, address, value, $store_at_fn) } )* }; } impl Executor<'_> { impl_execute_istore_trunc! { ( i32, (i8 => i8), (Op::I32Store8, execute_i32_store8), (Op::I32Store8Imm, execute_i32_store8_imm), (Op::I32Store8Offset16, execute_i32_store8_offset16), (Op::I32Store8Offset16Imm, execute_i32_store8_offset16_imm), (Op::I32Store8At, execute_i32_store8_at), (Op::I32Store8AtImm, execute_i32_store8_at_imm), wasm::i32_store8, wasm::i32_store8_at, ), ( i32, (i16 => i16), (Op::I32Store16, execute_i32_store16), (Op::I32Store16Imm, execute_i32_store16_imm), (Op::I32Store16Offset16, execute_i32_store16_offset16), (Op::I32Store16Offset16Imm, execute_i32_store16_offset16_imm), (Op::I32Store16At, execute_i32_store16_at), (Op::I32Store16AtImm, execute_i32_store16_at_imm), wasm::i32_store16, wasm::i32_store16_at, ), ( i64, (i8 => i8), (Op::I64Store8, execute_i64_store8), (Op::I64Store8Imm, execute_i64_store8_imm), (Op::I64Store8Offset16, execute_i64_store8_offset16), (Op::I64Store8Offset16Imm, execute_i64_store8_offset16_imm), (Op::I64Store8At, execute_i64_store8_at), (Op::I64Store8AtImm, execute_i64_store8_at_imm), wasm::i64_store8, wasm::i64_store8_at, ), ( i64, (i16 => i16), (Op::I64Store16, execute_i64_store16), (Op::I64Store16Imm, execute_i64_store16_imm), (Op::I64Store16Offset16, execute_i64_store16_offset16), (Op::I64Store16Offset16Imm, execute_i64_store16_offset16_imm), (Op::I64Store16At, execute_i64_store16_at), (Op::I64Store16AtImm, execute_i64_store16_at_imm), wasm::i64_store16, wasm::i64_store16_at, ), ( i64, (Const16 => i32), (Op::I64Store32, execute_i64_store32), (Op::I64Store32Imm16, execute_i64_store32_imm16), (Op::I64Store32Offset16, execute_i64_store32_offset16), (Op::I64Store32Offset16Imm16, execute_i64_store32_offset16_imm16), (Op::I64Store32At, execute_i64_store32_at), (Op::I64Store32AtImm16, execute_i64_store32_at_imm16), wasm::i64_store32, wasm::i64_store32_at, ), } } macro_rules! impl_execute_store { ( $( ( $ty:ty, (Op::$var_store:ident, $fn_store:ident), (Op::$var_store_off16:ident, $fn_store_off16:ident), (Op::$var_store_at:ident, $fn_store_at:ident), $store_fn:expr, $store_at_fn:expr $(,)? ) ),* $(,)? ) => { $( #[doc = concat!("Executes an [`Op::", stringify!($var_store), "`].")] pub fn $fn_store(&mut self, store: &mut StoreInner, ptr: Slot, offset_lo: Offset64Lo) -> Result<(), Error> { self.execute_store::<$ty>(store, ptr, offset_lo, $store_fn) } #[doc = concat!("Executes an [`Op::", stringify!($var_store_off16), "`].")] pub fn $fn_store_off16( &mut self, ptr: Slot, offset: Offset16, value: Slot, ) -> Result<(), Error> { self.execute_store_offset16::<$ty>(ptr, offset, value, $store_fn) } #[doc = concat!("Executes an [`Op::", stringify!($var_store_at), "`].")] pub fn $fn_store_at(&mut self, store: &mut StoreInner, address: Address32, value: Slot) -> Result<(), Error> { self.execute_store_at::<$ty>(store, address, value, $store_at_fn) } )* } } impl Executor<'_> { #[cfg(feature = "simd")] impl_execute_store! { ( V128, (Op::V128Store, execute_v128_store), (Op::V128StoreOffset16, execute_v128_store_offset16), (Op::V128StoreAt, execute_v128_store_at), simd::v128_store, simd::v128_store_at, ), } impl_execute_store! { ( u32, (Op::Store32, execute_store32), (Op::Store32Offset16, execute_store32_offset16), (Op::Store32At, execute_store32_at), wasm::store32, wasm::store32_at, ), ( u64, (Op::Store64, execute_store64), (Op::Store64Offset16, execute_store64_offset16), (Op::Store64At, execute_store64_at), wasm::store64, wasm::store64_at, ), } } wasmi-1.1.0/src/engine/executor/instrs/table.rs000064400000000000000000000214351046102023000176210ustar 00000000000000use super::{Executor, InstructionPtr}; use crate::{ core::CoreTable, engine::{utils::unreachable_unchecked, ResumableOutOfFuelError}, errors::TableError, ir::{ index::{Elem, Table}, Const32, Op, Slot, }, store::{PrunedStore, StoreError, StoreInner}, Error, TrapCode, }; impl Executor<'_> { /// Returns the [`Op::TableIndex`] parameter for an [`Op`]. fn fetch_table_index(&self, offset: usize) -> Table { let mut addr: InstructionPtr = self.ip; addr.add(offset); match *addr.get() { Op::TableIndex { index } => index, unexpected => { // Safety: Wasmi translation guarantees that [`Op::TableIndex`] exists. unsafe { unreachable_unchecked!("expected `Op::TableIndex` but found: {unexpected:?}") } } } } /// Returns the [`Op::ElemIndex`] parameter for an [`Op`]. fn fetch_element_segment_index(&self, offset: usize) -> Elem { let mut addr: InstructionPtr = self.ip; addr.add(offset); match *addr.get() { Op::ElemIndex { index } => index, unexpected => { // Safety: Wasmi translation guarantees that [`Op::ElemIndex`] exists. unsafe { unreachable_unchecked!("expected `Op::ElemIndex` but found: {unexpected:?}") } } } } /// Executes an [`Op::TableGet`]. pub fn execute_table_get( &mut self, store: &StoreInner, result: Slot, index: Slot, ) -> Result<(), Error> { let index: u64 = self.get_stack_slot_as(index); self.execute_table_get_impl(store, result, index) } /// Executes an [`Op::TableGetImm`]. pub fn execute_table_get_imm( &mut self, store: &StoreInner, result: Slot, index: Const32, ) -> Result<(), Error> { let index: u64 = index.into(); self.execute_table_get_impl(store, result, index) } /// Executes a `table.get` instruction generically. fn execute_table_get_impl( &mut self, store: &StoreInner, result: Slot, index: u64, ) -> Result<(), Error> { let table_index = self.fetch_table_index(1); let table = self.get_table(table_index); let value = store .resolve_table(&table) .get_untyped(index) .ok_or(TrapCode::TableOutOfBounds)?; self.set_stack_slot(result, value); self.try_next_instr_at(2) } /// Executes an [`Op::TableSize`]. pub fn execute_table_size(&mut self, store: &StoreInner, result: Slot, table_index: Table) { self.execute_table_size_impl(store, result, table_index); self.next_instr(); } /// Executes a generic `table.size` instruction. fn execute_table_size_impl(&mut self, store: &StoreInner, result: Slot, table_index: Table) { let table = self.get_table(table_index); let size = store.resolve_table(&table).size(); self.set_stack_slot(result, size); } /// Executes an [`Op::TableSet`]. pub fn execute_table_set( &mut self, store: &mut StoreInner, index: Slot, value: Slot, ) -> Result<(), Error> { let index: u64 = self.get_stack_slot_as(index); self.execute_table_set_impl(store, index, value) } /// Executes an [`Op::TableSetAt`]. pub fn execute_table_set_at( &mut self, store: &mut StoreInner, index: Const32, value: Slot, ) -> Result<(), Error> { let index: u64 = index.into(); self.execute_table_set_impl(store, index, value) } /// Executes a generic `table.set` instruction. fn execute_table_set_impl( &mut self, store: &mut StoreInner, index: u64, value: Slot, ) -> Result<(), Error> { let table_index = self.fetch_table_index(1); let table = self.get_table(table_index); let value = self.get_stack_slot(value); store .resolve_table_mut(&table) .set_untyped(index, value) .map_err(|_| TrapCode::TableOutOfBounds)?; self.try_next_instr_at(2) } /// Executes an [`Op::TableCopy`]. pub fn execute_table_copy( &mut self, store: &mut StoreInner, dst: Slot, src: Slot, len: Slot, ) -> Result<(), Error> { let dst: u64 = self.get_stack_slot_as(dst); let src: u64 = self.get_stack_slot_as(src); let len: u64 = self.get_stack_slot_as(len); let dst_table_index = self.fetch_table_index(1); let src_table_index = self.fetch_table_index(2); if dst_table_index == src_table_index { // Case: copy within the same table let table = self.get_table(dst_table_index); let (table, fuel) = store.resolve_table_and_fuel_mut(&table); table.copy_within(dst, src, len, Some(fuel))?; } else { // Case: copy between two different tables let dst_table = self.get_table(dst_table_index); let src_table = self.get_table(src_table_index); // Copy from one table to another table: let (dst_table, src_table, fuel) = store.resolve_table_pair_and_fuel(&dst_table, &src_table); CoreTable::copy(dst_table, dst, src_table, src, len, Some(fuel))?; } self.try_next_instr_at(3) } /// Executes an [`Op::TableInit`]. pub fn execute_table_init( &mut self, store: &mut StoreInner, dst: Slot, src: Slot, len: Slot, ) -> Result<(), Error> { let dst: u64 = self.get_stack_slot_as(dst); let src: u32 = self.get_stack_slot_as(src); let len: u32 = self.get_stack_slot_as(len); let table_index = self.fetch_table_index(1); let element_index = self.fetch_element_segment_index(2); let (table, element, fuel) = store.resolve_table_init_params( &self.get_table(table_index), &self.get_element_segment(element_index), ); table.init(element.as_ref(), dst, src, len, Some(fuel))?; self.try_next_instr_at(3) } /// Executes an [`Op::TableFill`]. pub fn execute_table_fill( &mut self, store: &mut StoreInner, dst: Slot, len: Slot, value: Slot, ) -> Result<(), Error> { let dst: u64 = self.get_stack_slot_as(dst); let len: u64 = self.get_stack_slot_as(len); let table_index = self.fetch_table_index(1); let value = self.get_stack_slot(value); let table = self.get_table(table_index); let (table, fuel) = store.resolve_table_and_fuel_mut(&table); table.fill_untyped(dst, value, len, Some(fuel))?; self.try_next_instr_at(2) } /// Executes an [`Op::TableGrow`]. pub fn execute_table_grow( &mut self, store: &mut PrunedStore, result: Slot, delta: Slot, value: Slot, ) -> Result<(), Error> { let delta: u64 = self.get_stack_slot_as(delta); let table_index = self.fetch_table_index(1); if delta == 0 { // Case: growing by 0 elements means there is nothing to do self.execute_table_size_impl(store.inner(), result, table_index); return self.try_next_instr_at(2); } let table = self.get_table(table_index); let value = self.get_stack_slot(value); let return_value = match store.grow_table(&table, delta, value) { Ok(return_value) => return_value, Err(StoreError::External( TableError::GrowOutOfBounds | TableError::OutOfSystemMemory, )) => { let table_ty = store.inner().resolve_table(&table).ty(); match table_ty.is_64() { true => u64::MAX, false => u64::from(u32::MAX), } } Err(StoreError::External(TableError::OutOfFuel { required_fuel })) => { return Err(Error::from(ResumableOutOfFuelError::new(required_fuel))) } Err(StoreError::External(TableError::ResourceLimiterDeniedAllocation)) => { return Err(Error::from(TrapCode::GrowthOperationLimited)) } Err(error) => { panic!("`memory.grow`: internal interpreter error: {error}") } }; self.set_stack_slot(result, return_value); self.try_next_instr_at(2) } /// Executes an [`Op::ElemDrop`]. pub fn execute_element_drop(&mut self, store: &mut StoreInner, segment_index: Elem) { let segment = self.get_element_segment(segment_index); store.resolve_element_mut(&segment).drop_items(); self.next_instr(); } } wasmi-1.1.0/src/engine/executor/instrs/unary.rs000064400000000000000000000024321046102023000176640ustar 00000000000000use super::Executor; use crate::{core::wasm, ir::Slot}; #[cfg(doc)] use crate::ir::Op; impl Executor<'_> { impl_unary_executors! { (Op::I32Clz, execute_i32_clz, wasm::i32_clz), (Op::I32Ctz, execute_i32_ctz, wasm::i32_ctz), (Op::I32Popcnt, execute_i32_popcnt, wasm::i32_popcnt), (Op::I64Clz, execute_i64_clz, wasm::i64_clz), (Op::I64Ctz, execute_i64_ctz, wasm::i64_ctz), (Op::I64Popcnt, execute_i64_popcnt, wasm::i64_popcnt), (Op::F32Abs, execute_f32_abs, wasm::f32_abs), (Op::F32Neg, execute_f32_neg, wasm::f32_neg), (Op::F32Ceil, execute_f32_ceil, wasm::f32_ceil), (Op::F32Floor, execute_f32_floor, wasm::f32_floor), (Op::F32Trunc, execute_f32_trunc, wasm::f32_trunc), (Op::F32Nearest, execute_f32_nearest, wasm::f32_nearest), (Op::F32Sqrt, execute_f32_sqrt, wasm::f32_sqrt), (Op::F64Abs, execute_f64_abs, wasm::f64_abs), (Op::F64Neg, execute_f64_neg, wasm::f64_neg), (Op::F64Ceil, execute_f64_ceil, wasm::f64_ceil), (Op::F64Floor, execute_f64_floor, wasm::f64_floor), (Op::F64Trunc, execute_f64_trunc, wasm::f64_trunc), (Op::F64Nearest, execute_f64_nearest, wasm::f64_nearest), (Op::F64Sqrt, execute_f64_sqrt, wasm::f64_sqrt), } } wasmi-1.1.0/src/engine/executor/instrs/utils.rs000064400000000000000000000070421046102023000176700ustar 00000000000000use super::Executor; use crate::{ ir::{index::Memory, Offset64Hi, Slot}, store::StoreInner, }; #[cfg(doc)] use crate::ir::Op; macro_rules! impl_unary_executors { ( $( (Op::$var_name:ident, $fn_name:ident, $op:expr) ),* $(,)? ) => { $( #[doc = concat!("Executes an [`Op::", stringify!($var_name), "`].")] pub fn $fn_name(&mut self, result: Slot, input: Slot) { self.execute_unary(result, input, $op) } )* }; } macro_rules! impl_binary_executors { ( $( (Op::$var_name:ident, $fn_name:ident, $op:expr) ),* $(,)? ) => { $( #[doc = concat!("Executes an [`Op::", stringify!($var_name), "`].")] pub fn $fn_name(&mut self, result: Slot, lhs: Slot, rhs: Slot) { self.execute_binary(result, lhs, rhs, $op) } )* }; } impl Executor<'_> { /// Returns the register `value` and `offset` parameters for a `load` [`Op`]. pub fn fetch_value_and_offset_hi(&self) -> (Slot, Offset64Hi) { // Safety: Wasmi translation guarantees that `Op::SlotAndImm32` exists. unsafe { self.fetch_reg_and_offset_hi() } } /// Fetches the bytes of the default memory at index 0. pub fn fetch_default_memory_bytes(&self) -> &[u8] { // Safety: the `self.cache.memory` pointer is always synchronized // conservatively whenever it could have been invalidated. unsafe { self.cache.memory.data() } } /// Fetches the bytes of the given `memory`. pub fn fetch_memory_bytes<'exec, 'store, 'bytes>( &'exec self, memory: Memory, store: &'store StoreInner, ) -> &'bytes [u8] where 'exec: 'bytes, 'store: 'bytes, { match memory.is_default() { true => self.fetch_default_memory_bytes(), false => self.fetch_non_default_memory_bytes(memory, store), } } /// Fetches the bytes of the given non-default `memory`. #[cold] pub fn fetch_non_default_memory_bytes<'exec, 'store, 'bytes>( &'exec self, memory: Memory, store: &'store StoreInner, ) -> &'bytes [u8] where 'exec: 'bytes, 'store: 'bytes, { let memory = self.get_memory(memory); store.resolve_memory(&memory).data() } /// Fetches the bytes of the default memory at index 0. #[inline] pub fn fetch_default_memory_bytes_mut(&mut self) -> &mut [u8] { // Safety: the `self.cache.memory` pointer is always synchronized // conservatively whenever it could have been invalidated. unsafe { self.cache.memory.data_mut() } } /// Fetches the bytes of the given `memory`. #[inline] pub fn fetch_memory_bytes_mut<'exec, 'store, 'bytes>( &'exec mut self, memory: Memory, store: &'store mut StoreInner, ) -> &'bytes mut [u8] where 'exec: 'bytes, 'store: 'bytes, { match memory.is_default() { true => self.fetch_default_memory_bytes_mut(), false => self.fetch_non_default_memory_bytes_mut(memory, store), } } /// Fetches the bytes of the given non-default `memory`. #[cold] #[inline] pub fn fetch_non_default_memory_bytes_mut<'exec, 'store, 'bytes>( &'exec mut self, memory: Memory, store: &'store mut StoreInner, ) -> &'bytes mut [u8] where 'exec: 'bytes, 'store: 'bytes, { let memory = self.get_memory(memory); store.resolve_memory_mut(&memory).data_mut() } } wasmi-1.1.0/src/engine/executor/instrs/wide_arithmetic.rs000064400000000000000000000067611046102023000217000ustar 00000000000000use super::{Executor, InstructionPtr}; use crate::{ core::wasm, engine::utils::unreachable_unchecked, ir::{FixedSlotSpan, Op, Slot}, }; /// Parameters for the `i64.add128` and `i64.sub128` instructions. struct Params128 { /// The register storing the high 64-bit part of the `lhs` parameter value. lhs_hi: Slot, /// The register storing the low 64-bit part of the `rhs` parameter value. rhs_lo: Slot, /// The register storing the low 64-bit part of the `rhs` parameter value. rhs_hi: Slot, } /// Function signature for `i64.binop128` handlers. type BinOp128Fn = fn(lhs_lo: i64, lhs_hi: i64, rhs_lo: i64, rhs_hi: i64) -> (i64, i64); /// Function signature for `i64.mul_wide_sx` handlers. type I64MulWideFn = fn(lhs: i64, rhs: i64) -> (i64, i64); impl Executor<'_> { /// Fetches the parameters required by the `i64.add128` and `i64.sub128` instructions. fn fetch_params128(&self) -> Params128 { let mut addr: InstructionPtr = self.ip; addr.add(1); match *addr.get() { Op::Slot3 { slots } => Params128 { lhs_hi: slots[0], rhs_lo: slots[1], rhs_hi: slots[2], }, unexpected => { // Safety: Wasmi translation guarantees that [`Op::MemoryIndex`] exists. unsafe { unreachable_unchecked!("expected `Op::Slot3` but found: {unexpected:?}") } } } } /// Executes a generic Wasm `i64.binop128` instruction. fn execute_i64_binop128(&mut self, results: [Slot; 2], lhs_lo: Slot, binop: BinOp128Fn) { let Params128 { lhs_hi, rhs_lo, rhs_hi, } = self.fetch_params128(); let lhs_lo: i64 = self.get_stack_slot_as(lhs_lo); let lhs_hi: i64 = self.get_stack_slot_as(lhs_hi); let rhs_lo: i64 = self.get_stack_slot_as(rhs_lo); let rhs_hi: i64 = self.get_stack_slot_as(rhs_hi); let (result_lo, result_hi) = binop(lhs_lo, lhs_hi, rhs_lo, rhs_hi); self.set_stack_slot(results[0], result_lo); self.set_stack_slot(results[1], result_hi); self.next_instr_at(2) } /// Executes an [`Op::I64Add128`]. pub fn execute_i64_add128(&mut self, results: [Slot; 2], lhs_lo: Slot) { self.execute_i64_binop128(results, lhs_lo, wasm::i64_add128) } /// Executes an [`Op::I64Sub128`]. pub fn execute_i64_sub128(&mut self, results: [Slot; 2], lhs_lo: Slot) { self.execute_i64_binop128(results, lhs_lo, wasm::i64_sub128) } /// Executes a generic Wasm `i64.mul_wide_sx` instruction. fn execute_i64_mul_wide_sx( &mut self, results: FixedSlotSpan<2>, lhs: Slot, rhs: Slot, mul_wide: I64MulWideFn, ) { let lhs: i64 = self.get_stack_slot_as(lhs); let rhs: i64 = self.get_stack_slot_as(rhs); let (result_lo, result_hi) = mul_wide(lhs, rhs); let results = results.to_array(); self.set_stack_slot(results[0], result_lo); self.set_stack_slot(results[1], result_hi); self.next_instr() } /// Executes an [`Op::I64MulWideS`]. pub fn execute_i64_mul_wide_s(&mut self, results: FixedSlotSpan<2>, lhs: Slot, rhs: Slot) { self.execute_i64_mul_wide_sx(results, lhs, rhs, wasm::i64_mul_wide_s) } /// Executes an [`Op::I64MulWideU`]. pub fn execute_i64_mul_wide_u(&mut self, results: FixedSlotSpan<2>, lhs: Slot, rhs: Slot) { self.execute_i64_mul_wide_sx(results, lhs, rhs, wasm::i64_mul_wide_u) } } wasmi-1.1.0/src/engine/executor/instrs.rs000064400000000000000000004032711046102023000165340ustar 00000000000000pub use self::call::dispatch_host_func; use super::{cache::CachedInstance, InstructionPtr, Stack}; use crate::{ core::{hint, wasm, ReadAs, UntypedVal, WriteAs}, engine::{ code_map::CodeMap, executor::stack::{CallFrame, FrameSlots, ValueStack}, utils::unreachable_unchecked, DedupFuncType, EngineFunc, }, ir::{index, BlockFuel, Const16, Offset64Hi, Op, ShiftAmount, Slot}, memory::DataSegment, store::{PrunedStore, StoreInner}, table::ElementSegment, Error, Func, Global, Memory, Ref, Table, TrapCode, }; #[cfg(doc)] use crate::Instance; #[macro_use] mod utils; #[cfg(feature = "simd")] mod simd; mod binary; mod branch; mod call; mod comparison; mod conversion; mod copy; mod global; mod load; mod memory; mod return_; mod select; mod store; mod table; mod unary; mod wide_arithmetic; macro_rules! forward_return { ($expr:expr) => {{ if hint::unlikely($expr.is_break()) { return Ok(()); } }}; } /// Tells if execution loop shall continue or break (return) to the execution's caller. type ControlFlow = ::core::ops::ControlFlow<(), ()>; /// Executes compiled function instructions until execution returns from the root function. /// /// # Errors /// /// If the execution encounters a trap. #[inline(never)] pub fn execute_instrs<'engine>( store: &mut PrunedStore, stack: &'engine mut Stack, code_map: &'engine CodeMap, ) -> Result<(), Error> { let instance = stack.calls.instance_expect(); let cache = CachedInstance::new(store.inner_mut(), instance); let mut executor = Executor::new(stack, code_map, cache); if let Err(error) = executor.execute(store) { if error.is_out_of_fuel() { if let Some(frame) = executor.stack.calls.peek_mut() { // Note: we need to update the instruction pointer to make it possible to // resume execution at the current instruction after running out of fuel. frame.update_instr_ptr(executor.ip); } } return Err(error); } Ok(()) } /// An execution context for executing a Wasmi function frame. #[derive(Debug)] struct Executor<'engine> { /// Stores the value stack of live values on the Wasm stack. sp: FrameSlots, /// The pointer to the currently executed instruction. ip: InstructionPtr, /// The cached instance and instance related data. cache: CachedInstance, /// The value and call stacks. stack: &'engine mut Stack, /// The static resources of an [`Engine`]. /// /// [`Engine`]: crate::Engine code_map: &'engine CodeMap, } impl<'engine> Executor<'engine> { /// Creates a new [`Executor`] for executing a Wasmi function frame. #[inline(always)] pub fn new( stack: &'engine mut Stack, code_map: &'engine CodeMap, cache: CachedInstance, ) -> Self { let frame = stack .calls .peek() .expect("must have call frame on the call stack"); // Safety: We are using the frame's own base offset as input because it is // guaranteed by the Wasm validation and translation phase to be // valid for all register indices used by the associated function body. let sp = unsafe { stack.values.stack_ptr_at(frame.base_offset()) }; let ip = frame.instr_ptr(); Self { sp, ip, cache, stack, code_map, } } /// Executes the function frame until it returns or traps. #[inline(always)] fn execute(&mut self, store: &mut PrunedStore) -> Result<(), Error> { use Op as Instr; loop { match *self.ip.get() { Instr::Trap { trap_code } => self.execute_trap(trap_code)?, Instr::ConsumeFuel { block_fuel } => { self.execute_consume_fuel(store.inner_mut(), block_fuel)? } Instr::Return => { forward_return!(self.execute_return(store.inner_mut())) } Instr::ReturnSlot { value } => { forward_return!(self.execute_return_reg(store.inner_mut(), value)) } Instr::ReturnSlot2 { values } => { forward_return!(self.execute_return_reg2(store.inner_mut(), values)) } Instr::ReturnSlot3 { values } => { forward_return!(self.execute_return_reg3(store.inner_mut(), values)) } Instr::ReturnImm32 { value } => { forward_return!(self.execute_return_imm32(store.inner_mut(), value)) } Instr::ReturnI64Imm32 { value } => { forward_return!(self.execute_return_i64imm32(store.inner_mut(), value)) } Instr::ReturnF64Imm32 { value } => { forward_return!(self.execute_return_f64imm32(store.inner_mut(), value)) } Instr::ReturnSpan { values } => { forward_return!(self.execute_return_span(store.inner_mut(), values)) } Instr::ReturnMany { values } => { forward_return!(self.execute_return_many(store.inner_mut(), values)) } Instr::Branch { offset } => self.execute_branch(offset), Instr::BranchTable0 { index, len_targets } => { self.execute_branch_table_0(index, len_targets) } Instr::BranchTableSpan { index, len_targets } => { self.execute_branch_table_span(index, len_targets) } Instr::BranchCmpFallback { lhs, rhs, params } => { self.execute_branch_cmp_fallback(lhs, rhs, params) } Instr::BranchI32And { lhs, rhs, offset } => { self.execute_branch_i32_and(lhs, rhs, offset) } Instr::BranchI32AndImm16 { lhs, rhs, offset } => { self.execute_branch_i32_and_imm16(lhs, rhs, offset) } Instr::BranchI32Or { lhs, rhs, offset } => { self.execute_branch_i32_or(lhs, rhs, offset) } Instr::BranchI32OrImm16 { lhs, rhs, offset } => { self.execute_branch_i32_or_imm16(lhs, rhs, offset) } Instr::BranchI32Nand { lhs, rhs, offset } => { self.execute_branch_i32_nand(lhs, rhs, offset) } Instr::BranchI32NandImm16 { lhs, rhs, offset } => { self.execute_branch_i32_nand_imm16(lhs, rhs, offset) } Instr::BranchI32Nor { lhs, rhs, offset } => { self.execute_branch_i32_nor(lhs, rhs, offset) } Instr::BranchI32NorImm16 { lhs, rhs, offset } => { self.execute_branch_i32_nor_imm16(lhs, rhs, offset) } Instr::BranchI32Eq { lhs, rhs, offset } => { self.execute_branch_i32_eq(lhs, rhs, offset) } Instr::BranchI32EqImm16 { lhs, rhs, offset } => { self.execute_branch_i32_eq_imm16(lhs, rhs, offset) } Instr::BranchI32Ne { lhs, rhs, offset } => { self.execute_branch_i32_ne(lhs, rhs, offset) } Instr::BranchI32NeImm16 { lhs, rhs, offset } => { self.execute_branch_i32_ne_imm16(lhs, rhs, offset) } Instr::BranchI32LtS { lhs, rhs, offset } => { self.execute_branch_i32_lt_s(lhs, rhs, offset) } Instr::BranchI32LtSImm16Lhs { lhs, rhs, offset } => { self.execute_branch_i32_lt_s_imm16_lhs(lhs, rhs, offset) } Instr::BranchI32LtSImm16Rhs { lhs, rhs, offset } => { self.execute_branch_i32_lt_s_imm16_rhs(lhs, rhs, offset) } Instr::BranchI32LtU { lhs, rhs, offset } => { self.execute_branch_i32_lt_u(lhs, rhs, offset) } Instr::BranchI32LtUImm16Lhs { lhs, rhs, offset } => { self.execute_branch_i32_lt_u_imm16_lhs(lhs, rhs, offset) } Instr::BranchI32LtUImm16Rhs { lhs, rhs, offset } => { self.execute_branch_i32_lt_u_imm16_rhs(lhs, rhs, offset) } Instr::BranchI32LeS { lhs, rhs, offset } => { self.execute_branch_i32_le_s(lhs, rhs, offset) } Instr::BranchI32LeSImm16Lhs { lhs, rhs, offset } => { self.execute_branch_i32_le_s_imm16_lhs(lhs, rhs, offset) } Instr::BranchI32LeSImm16Rhs { lhs, rhs, offset } => { self.execute_branch_i32_le_s_imm16_rhs(lhs, rhs, offset) } Instr::BranchI32LeU { lhs, rhs, offset } => { self.execute_branch_i32_le_u(lhs, rhs, offset) } Instr::BranchI32LeUImm16Lhs { lhs, rhs, offset } => { self.execute_branch_i32_le_u_imm16_lhs(lhs, rhs, offset) } Instr::BranchI32LeUImm16Rhs { lhs, rhs, offset } => { self.execute_branch_i32_le_u_imm16_rhs(lhs, rhs, offset) } Instr::BranchI64And { lhs, rhs, offset } => { self.execute_branch_i64_and(lhs, rhs, offset) } Instr::BranchI64AndImm16 { lhs, rhs, offset } => { self.execute_branch_i64_and_imm16(lhs, rhs, offset) } Instr::BranchI64Or { lhs, rhs, offset } => { self.execute_branch_i64_or(lhs, rhs, offset) } Instr::BranchI64OrImm16 { lhs, rhs, offset } => { self.execute_branch_i64_or_imm16(lhs, rhs, offset) } Instr::BranchI64Nand { lhs, rhs, offset } => { self.execute_branch_i64_nand(lhs, rhs, offset) } Instr::BranchI64NandImm16 { lhs, rhs, offset } => { self.execute_branch_i64_nand_imm16(lhs, rhs, offset) } Instr::BranchI64Nor { lhs, rhs, offset } => { self.execute_branch_i64_nor(lhs, rhs, offset) } Instr::BranchI64NorImm16 { lhs, rhs, offset } => { self.execute_branch_i64_nor_imm16(lhs, rhs, offset) } Instr::BranchI64Eq { lhs, rhs, offset } => { self.execute_branch_i64_eq(lhs, rhs, offset) } Instr::BranchI64EqImm16 { lhs, rhs, offset } => { self.execute_branch_i64_eq_imm16(lhs, rhs, offset) } Instr::BranchI64Ne { lhs, rhs, offset } => { self.execute_branch_i64_ne(lhs, rhs, offset) } Instr::BranchI64NeImm16 { lhs, rhs, offset } => { self.execute_branch_i64_ne_imm16(lhs, rhs, offset) } Instr::BranchI64LtS { lhs, rhs, offset } => { self.execute_branch_i64_lt_s(lhs, rhs, offset) } Instr::BranchI64LtSImm16Lhs { lhs, rhs, offset } => { self.execute_branch_i64_lt_s_imm16_lhs(lhs, rhs, offset) } Instr::BranchI64LtSImm16Rhs { lhs, rhs, offset } => { self.execute_branch_i64_lt_s_imm16_rhs(lhs, rhs, offset) } Instr::BranchI64LtU { lhs, rhs, offset } => { self.execute_branch_i64_lt_u(lhs, rhs, offset) } Instr::BranchI64LtUImm16Lhs { lhs, rhs, offset } => { self.execute_branch_i64_lt_u_imm16_lhs(lhs, rhs, offset) } Instr::BranchI64LtUImm16Rhs { lhs, rhs, offset } => { self.execute_branch_i64_lt_u_imm16_rhs(lhs, rhs, offset) } Instr::BranchI64LeS { lhs, rhs, offset } => { self.execute_branch_i64_le_s(lhs, rhs, offset) } Instr::BranchI64LeSImm16Lhs { lhs, rhs, offset } => { self.execute_branch_i64_le_s_imm16_lhs(lhs, rhs, offset) } Instr::BranchI64LeSImm16Rhs { lhs, rhs, offset } => { self.execute_branch_i64_le_s_imm16_rhs(lhs, rhs, offset) } Instr::BranchI64LeU { lhs, rhs, offset } => { self.execute_branch_i64_le_u(lhs, rhs, offset) } Instr::BranchI64LeUImm16Lhs { lhs, rhs, offset } => { self.execute_branch_i64_le_u_imm16_lhs(lhs, rhs, offset) } Instr::BranchI64LeUImm16Rhs { lhs, rhs, offset } => { self.execute_branch_i64_le_u_imm16_rhs(lhs, rhs, offset) } Instr::BranchF32Eq { lhs, rhs, offset } => { self.execute_branch_f32_eq(lhs, rhs, offset) } Instr::BranchF32Ne { lhs, rhs, offset } => { self.execute_branch_f32_ne(lhs, rhs, offset) } Instr::BranchF32Lt { lhs, rhs, offset } => { self.execute_branch_f32_lt(lhs, rhs, offset) } Instr::BranchF32Le { lhs, rhs, offset } => { self.execute_branch_f32_le(lhs, rhs, offset) } Instr::BranchF32NotLt { lhs, rhs, offset } => { self.execute_branch_f32_not_lt(lhs, rhs, offset) } Instr::BranchF32NotLe { lhs, rhs, offset } => { self.execute_branch_f32_not_le(lhs, rhs, offset) } Instr::BranchF64Eq { lhs, rhs, offset } => { self.execute_branch_f64_eq(lhs, rhs, offset) } Instr::BranchF64Ne { lhs, rhs, offset } => { self.execute_branch_f64_ne(lhs, rhs, offset) } Instr::BranchF64Lt { lhs, rhs, offset } => { self.execute_branch_f64_lt(lhs, rhs, offset) } Instr::BranchF64Le { lhs, rhs, offset } => { self.execute_branch_f64_le(lhs, rhs, offset) } Instr::BranchF64NotLt { lhs, rhs, offset } => { self.execute_branch_f64_not_lt(lhs, rhs, offset) } Instr::BranchF64NotLe { lhs, rhs, offset } => { self.execute_branch_f64_not_le(lhs, rhs, offset) } Instr::Copy { result, value } => self.execute_copy(result, value), Instr::Copy2 { results, values } => self.execute_copy_2(results, values), Instr::CopyImm32 { result, value } => self.execute_copy_imm32(result, value), Instr::CopyI64Imm32 { result, value } => self.execute_copy_i64imm32(result, value), Instr::CopyF64Imm32 { result, value } => self.execute_copy_f64imm32(result, value), Instr::CopySpan { results, values, len, } => self.execute_copy_span(results, values, len), Instr::CopyMany { results, values } => self.execute_copy_many(results, values), Instr::ReturnCallInternal0 { func } => { self.execute_return_call_internal_0(store.inner_mut(), EngineFunc::from(func))? } Instr::ReturnCallInternal { func } => { self.execute_return_call_internal(store.inner_mut(), EngineFunc::from(func))? } Instr::ReturnCallImported0 { func } => { forward_return!(self.execute_return_call_imported_0(store, func)?) } Instr::ReturnCallImported { func } => { forward_return!(self.execute_return_call_imported(store, func)?) } Instr::ReturnCallIndirect0 { func_type } => { forward_return!(self.execute_return_call_indirect_0(store, func_type)?) } Instr::ReturnCallIndirect0Imm16 { func_type } => { forward_return!(self.execute_return_call_indirect_0_imm16(store, func_type)?) } Instr::ReturnCallIndirect { func_type } => { forward_return!(self.execute_return_call_indirect(store, func_type)?) } Instr::ReturnCallIndirectImm16 { func_type } => { forward_return!(self.execute_return_call_indirect_imm16(store, func_type)?) } Instr::CallInternal0 { results, func } => self.execute_call_internal_0( store.inner_mut(), results, EngineFunc::from(func), )?, Instr::CallInternal { results, func } => { self.execute_call_internal(store.inner_mut(), results, EngineFunc::from(func))? } Instr::CallImported0 { results, func } => { self.execute_call_imported_0(store, results, func)? } Instr::CallImported { results, func } => { self.execute_call_imported(store, results, func)? } Instr::CallIndirect0 { results, func_type } => { self.execute_call_indirect_0(store, results, func_type)? } Instr::CallIndirect0Imm16 { results, func_type } => { self.execute_call_indirect_0_imm16(store, results, func_type)? } Instr::CallIndirect { results, func_type } => { self.execute_call_indirect(store, results, func_type)? } Instr::CallIndirectImm16 { results, func_type } => { self.execute_call_indirect_imm16(store, results, func_type)? } Instr::SelectI32Eq { result, lhs, rhs } => { self.execute_select_i32_eq(result, lhs, rhs) } Instr::SelectI32EqImm16 { result, lhs, rhs } => { self.execute_select_i32_eq_imm16(result, lhs, rhs) } Instr::SelectI32LtS { result, lhs, rhs } => { self.execute_select_i32_lt_s(result, lhs, rhs) } Instr::SelectI32LtSImm16Rhs { result, lhs, rhs } => { self.execute_select_i32_lt_s_imm16_rhs(result, lhs, rhs) } Instr::SelectI32LtU { result, lhs, rhs } => { self.execute_select_i32_lt_u(result, lhs, rhs) } Instr::SelectI32LtUImm16Rhs { result, lhs, rhs } => { self.execute_select_i32_lt_u_imm16_rhs(result, lhs, rhs) } Instr::SelectI32LeS { result, lhs, rhs } => { self.execute_select_i32_le_s(result, lhs, rhs) } Instr::SelectI32LeSImm16Rhs { result, lhs, rhs } => { self.execute_select_i32_le_s_imm16_rhs(result, lhs, rhs) } Instr::SelectI32LeU { result, lhs, rhs } => { self.execute_select_i32_le_u(result, lhs, rhs) } Instr::SelectI32LeUImm16Rhs { result, lhs, rhs } => { self.execute_select_i32_le_u_imm16_rhs(result, lhs, rhs) } Instr::SelectI32And { result, lhs, rhs } => { self.execute_select_i32_and(result, lhs, rhs) } Instr::SelectI32AndImm16 { result, lhs, rhs } => { self.execute_select_i32_and_imm16(result, lhs, rhs) } Instr::SelectI32Or { result, lhs, rhs } => { self.execute_select_i32_or(result, lhs, rhs) } Instr::SelectI32OrImm16 { result, lhs, rhs } => { self.execute_select_i32_or_imm16(result, lhs, rhs) } Instr::SelectI64Eq { result, lhs, rhs } => { self.execute_select_i64_eq(result, lhs, rhs) } Instr::SelectI64EqImm16 { result, lhs, rhs } => { self.execute_select_i64_eq_imm16(result, lhs, rhs) } Instr::SelectI64LtS { result, lhs, rhs } => { self.execute_select_i64_lt_s(result, lhs, rhs) } Instr::SelectI64LtSImm16Rhs { result, lhs, rhs } => { self.execute_select_i64_lt_s_imm16_rhs(result, lhs, rhs) } Instr::SelectI64LtU { result, lhs, rhs } => { self.execute_select_i64_lt_u(result, lhs, rhs) } Instr::SelectI64LtUImm16Rhs { result, lhs, rhs } => { self.execute_select_i64_lt_u_imm16_rhs(result, lhs, rhs) } Instr::SelectI64LeS { result, lhs, rhs } => { self.execute_select_i64_le_s(result, lhs, rhs) } Instr::SelectI64LeSImm16Rhs { result, lhs, rhs } => { self.execute_select_i64_le_s_imm16_rhs(result, lhs, rhs) } Instr::SelectI64LeU { result, lhs, rhs } => { self.execute_select_i64_le_u(result, lhs, rhs) } Instr::SelectI64LeUImm16Rhs { result, lhs, rhs } => { self.execute_select_i64_le_u_imm16_rhs(result, lhs, rhs) } Instr::SelectI64And { result, lhs, rhs } => { self.execute_select_i64_and(result, lhs, rhs) } Instr::SelectI64AndImm16 { result, lhs, rhs } => { self.execute_select_i64_and_imm16(result, lhs, rhs) } Instr::SelectI64Or { result, lhs, rhs } => { self.execute_select_i64_or(result, lhs, rhs) } Instr::SelectI64OrImm16 { result, lhs, rhs } => { self.execute_select_i64_or_imm16(result, lhs, rhs) } Instr::SelectF32Eq { result, lhs, rhs } => { self.execute_select_f32_eq(result, lhs, rhs) } Instr::SelectF32Lt { result, lhs, rhs } => { self.execute_select_f32_lt(result, lhs, rhs) } Instr::SelectF32Le { result, lhs, rhs } => { self.execute_select_f32_le(result, lhs, rhs) } Instr::SelectF64Eq { result, lhs, rhs } => { self.execute_select_f64_eq(result, lhs, rhs) } Instr::SelectF64Lt { result, lhs, rhs } => { self.execute_select_f64_lt(result, lhs, rhs) } Instr::SelectF64Le { result, lhs, rhs } => { self.execute_select_f64_le(result, lhs, rhs) } Instr::RefFunc { result, func } => self.execute_ref_func(result, func), Instr::GlobalGet { result, global } => { self.execute_global_get(store.inner(), result, global) } Instr::GlobalSet { global, input } => { self.execute_global_set(store.inner_mut(), global, input) } Instr::GlobalSetI32Imm16 { global, input } => { self.execute_global_set_i32imm16(store.inner_mut(), global, input) } Instr::GlobalSetI64Imm16 { global, input } => { self.execute_global_set_i64imm16(store.inner_mut(), global, input) } Instr::Load32 { result, offset_lo } => { self.execute_load32(store.inner(), result, offset_lo)? } Instr::Load32At { result, address } => { self.execute_load32_at(store.inner(), result, address)? } Instr::Load32Offset16 { result, ptr, offset, } => self.execute_load32_offset16(result, ptr, offset)?, Instr::Load64 { result, offset_lo } => { self.execute_load64(store.inner(), result, offset_lo)? } Instr::Load64At { result, address } => { self.execute_load64_at(store.inner(), result, address)? } Instr::Load64Offset16 { result, ptr, offset, } => self.execute_load64_offset16(result, ptr, offset)?, Instr::I32Load8s { result, offset_lo } => { self.execute_i32_load8_s(store.inner(), result, offset_lo)? } Instr::I32Load8sAt { result, address } => { self.execute_i32_load8_s_at(store.inner(), result, address)? } Instr::I32Load8sOffset16 { result, ptr, offset, } => self.execute_i32_load8_s_offset16(result, ptr, offset)?, Instr::I32Load8u { result, offset_lo } => { self.execute_i32_load8_u(store.inner(), result, offset_lo)? } Instr::I32Load8uAt { result, address } => { self.execute_i32_load8_u_at(store.inner(), result, address)? } Instr::I32Load8uOffset16 { result, ptr, offset, } => self.execute_i32_load8_u_offset16(result, ptr, offset)?, Instr::I32Load16s { result, offset_lo } => { self.execute_i32_load16_s(store.inner(), result, offset_lo)? } Instr::I32Load16sAt { result, address } => { self.execute_i32_load16_s_at(store.inner(), result, address)? } Instr::I32Load16sOffset16 { result, ptr, offset, } => self.execute_i32_load16_s_offset16(result, ptr, offset)?, Instr::I32Load16u { result, offset_lo } => { self.execute_i32_load16_u(store.inner(), result, offset_lo)? } Instr::I32Load16uAt { result, address } => { self.execute_i32_load16_u_at(store.inner(), result, address)? } Instr::I32Load16uOffset16 { result, ptr, offset, } => self.execute_i32_load16_u_offset16(result, ptr, offset)?, Instr::I64Load8s { result, offset_lo } => { self.execute_i64_load8_s(store.inner(), result, offset_lo)? } Instr::I64Load8sAt { result, address } => { self.execute_i64_load8_s_at(store.inner(), result, address)? } Instr::I64Load8sOffset16 { result, ptr, offset, } => self.execute_i64_load8_s_offset16(result, ptr, offset)?, Instr::I64Load8u { result, offset_lo } => { self.execute_i64_load8_u(store.inner(), result, offset_lo)? } Instr::I64Load8uAt { result, address } => { self.execute_i64_load8_u_at(store.inner(), result, address)? } Instr::I64Load8uOffset16 { result, ptr, offset, } => self.execute_i64_load8_u_offset16(result, ptr, offset)?, Instr::I64Load16s { result, offset_lo } => { self.execute_i64_load16_s(store.inner(), result, offset_lo)? } Instr::I64Load16sAt { result, address } => { self.execute_i64_load16_s_at(store.inner(), result, address)? } Instr::I64Load16sOffset16 { result, ptr, offset, } => self.execute_i64_load16_s_offset16(result, ptr, offset)?, Instr::I64Load16u { result, offset_lo } => { self.execute_i64_load16_u(store.inner(), result, offset_lo)? } Instr::I64Load16uAt { result, address } => { self.execute_i64_load16_u_at(store.inner(), result, address)? } Instr::I64Load16uOffset16 { result, ptr, offset, } => self.execute_i64_load16_u_offset16(result, ptr, offset)?, Instr::I64Load32s { result, offset_lo } => { self.execute_i64_load32_s(store.inner(), result, offset_lo)? } Instr::I64Load32sAt { result, address } => { self.execute_i64_load32_s_at(store.inner(), result, address)? } Instr::I64Load32sOffset16 { result, ptr, offset, } => self.execute_i64_load32_s_offset16(result, ptr, offset)?, Instr::I64Load32u { result, offset_lo } => { self.execute_i64_load32_u(store.inner(), result, offset_lo)? } Instr::I64Load32uAt { result, address } => { self.execute_i64_load32_u_at(store.inner(), result, address)? } Instr::I64Load32uOffset16 { result, ptr, offset, } => self.execute_i64_load32_u_offset16(result, ptr, offset)?, Instr::Store32 { ptr, offset_lo } => { self.execute_store32(store.inner_mut(), ptr, offset_lo)? } Instr::Store32Offset16 { ptr, offset, value } => { self.execute_store32_offset16(ptr, offset, value)? } Instr::Store32At { address, value } => { self.execute_store32_at(store.inner_mut(), address, value)? } Instr::Store64 { ptr, offset_lo } => { self.execute_store64(store.inner_mut(), ptr, offset_lo)? } Instr::Store64Offset16 { ptr, offset, value } => { self.execute_store64_offset16(ptr, offset, value)? } Instr::Store64At { address, value } => { self.execute_store64_at(store.inner_mut(), address, value)? } Instr::I32StoreImm16 { ptr, offset_lo } => { self.execute_i32_store_imm16(store.inner_mut(), ptr, offset_lo)? } Instr::I32StoreOffset16Imm16 { ptr, offset, value } => { self.execute_i32_store_offset16_imm16(ptr, offset, value)? } Instr::I32StoreAtImm16 { address, value } => { self.execute_i32_store_at_imm16(store.inner_mut(), address, value)? } Instr::I32Store8 { ptr, offset_lo } => { self.execute_i32_store8(store.inner_mut(), ptr, offset_lo)? } Instr::I32Store8Imm { ptr, offset_lo } => { self.execute_i32_store8_imm(store.inner_mut(), ptr, offset_lo)? } Instr::I32Store8Offset16 { ptr, offset, value } => { self.execute_i32_store8_offset16(ptr, offset, value)? } Instr::I32Store8Offset16Imm { ptr, offset, value } => { self.execute_i32_store8_offset16_imm(ptr, offset, value)? } Instr::I32Store8At { address, value } => { self.execute_i32_store8_at(store.inner_mut(), address, value)? } Instr::I32Store8AtImm { address, value } => { self.execute_i32_store8_at_imm(store.inner_mut(), address, value)? } Instr::I32Store16 { ptr, offset_lo } => { self.execute_i32_store16(store.inner_mut(), ptr, offset_lo)? } Instr::I32Store16Imm { ptr, offset_lo } => { self.execute_i32_store16_imm(store.inner_mut(), ptr, offset_lo)? } Instr::I32Store16Offset16 { ptr, offset, value } => { self.execute_i32_store16_offset16(ptr, offset, value)? } Instr::I32Store16Offset16Imm { ptr, offset, value } => { self.execute_i32_store16_offset16_imm(ptr, offset, value)? } Instr::I32Store16At { address, value } => { self.execute_i32_store16_at(store.inner_mut(), address, value)? } Instr::I32Store16AtImm { address, value } => { self.execute_i32_store16_at_imm(store.inner_mut(), address, value)? } Instr::I64StoreImm16 { ptr, offset_lo } => { self.execute_i64_store_imm16(store.inner_mut(), ptr, offset_lo)? } Instr::I64StoreOffset16Imm16 { ptr, offset, value } => { self.execute_i64_store_offset16_imm16(ptr, offset, value)? } Instr::I64StoreAtImm16 { address, value } => { self.execute_i64_store_at_imm16(store.inner_mut(), address, value)? } Instr::I64Store8 { ptr, offset_lo } => { self.execute_i64_store8(store.inner_mut(), ptr, offset_lo)? } Instr::I64Store8Imm { ptr, offset_lo } => { self.execute_i64_store8_imm(store.inner_mut(), ptr, offset_lo)? } Instr::I64Store8Offset16 { ptr, offset, value } => { self.execute_i64_store8_offset16(ptr, offset, value)? } Instr::I64Store8Offset16Imm { ptr, offset, value } => { self.execute_i64_store8_offset16_imm(ptr, offset, value)? } Instr::I64Store8At { address, value } => { self.execute_i64_store8_at(store.inner_mut(), address, value)? } Instr::I64Store8AtImm { address, value } => { self.execute_i64_store8_at_imm(store.inner_mut(), address, value)? } Instr::I64Store16 { ptr, offset_lo } => { self.execute_i64_store16(store.inner_mut(), ptr, offset_lo)? } Instr::I64Store16Imm { ptr, offset_lo } => { self.execute_i64_store16_imm(store.inner_mut(), ptr, offset_lo)? } Instr::I64Store16Offset16 { ptr, offset, value } => { self.execute_i64_store16_offset16(ptr, offset, value)? } Instr::I64Store16Offset16Imm { ptr, offset, value } => { self.execute_i64_store16_offset16_imm(ptr, offset, value)? } Instr::I64Store16At { address, value } => { self.execute_i64_store16_at(store.inner_mut(), address, value)? } Instr::I64Store16AtImm { address, value } => { self.execute_i64_store16_at_imm(store.inner_mut(), address, value)? } Instr::I64Store32 { ptr, offset_lo } => { self.execute_i64_store32(store.inner_mut(), ptr, offset_lo)? } Instr::I64Store32Imm16 { ptr, offset_lo } => { self.execute_i64_store32_imm16(store.inner_mut(), ptr, offset_lo)? } Instr::I64Store32Offset16 { ptr, offset, value } => { self.execute_i64_store32_offset16(ptr, offset, value)? } Instr::I64Store32Offset16Imm16 { ptr, offset, value } => { self.execute_i64_store32_offset16_imm16(ptr, offset, value)? } Instr::I64Store32At { address, value } => { self.execute_i64_store32_at(store.inner_mut(), address, value)? } Instr::I64Store32AtImm16 { address, value } => { self.execute_i64_store32_at_imm16(store.inner_mut(), address, value)? } Instr::I32Eq { result, lhs, rhs } => self.execute_i32_eq(result, lhs, rhs), Instr::I32EqImm16 { result, lhs, rhs } => { self.execute_i32_eq_imm16(result, lhs, rhs) } Instr::I32Ne { result, lhs, rhs } => self.execute_i32_ne(result, lhs, rhs), Instr::I32NeImm16 { result, lhs, rhs } => { self.execute_i32_ne_imm16(result, lhs, rhs) } Instr::I32LtS { result, lhs, rhs } => self.execute_i32_lt_s(result, lhs, rhs), Instr::I32LtSImm16Lhs { result, lhs, rhs } => { self.execute_i32_lt_s_imm16_lhs(result, lhs, rhs) } Instr::I32LtSImm16Rhs { result, lhs, rhs } => { self.execute_i32_lt_s_imm16_rhs(result, lhs, rhs) } Instr::I32LtU { result, lhs, rhs } => self.execute_i32_lt_u(result, lhs, rhs), Instr::I32LtUImm16Lhs { result, lhs, rhs } => { self.execute_i32_lt_u_imm16_lhs(result, lhs, rhs) } Instr::I32LtUImm16Rhs { result, lhs, rhs } => { self.execute_i32_lt_u_imm16_rhs(result, lhs, rhs) } Instr::I32LeS { result, lhs, rhs } => self.execute_i32_le_s(result, lhs, rhs), Instr::I32LeSImm16Lhs { result, lhs, rhs } => { self.execute_i32_le_s_imm16_lhs(result, lhs, rhs) } Instr::I32LeSImm16Rhs { result, lhs, rhs } => { self.execute_i32_le_s_imm16_rhs(result, lhs, rhs) } Instr::I32LeU { result, lhs, rhs } => self.execute_i32_le_u(result, lhs, rhs), Instr::I32LeUImm16Lhs { result, lhs, rhs } => { self.execute_i32_le_u_imm16_lhs(result, lhs, rhs) } Instr::I32LeUImm16Rhs { result, lhs, rhs } => { self.execute_i32_le_u_imm16_rhs(result, lhs, rhs) } Instr::I64Eq { result, lhs, rhs } => self.execute_i64_eq(result, lhs, rhs), Instr::I64EqImm16 { result, lhs, rhs } => { self.execute_i64_eq_imm16(result, lhs, rhs) } Instr::I64Ne { result, lhs, rhs } => self.execute_i64_ne(result, lhs, rhs), Instr::I64NeImm16 { result, lhs, rhs } => { self.execute_i64_ne_imm16(result, lhs, rhs) } Instr::I64LtS { result, lhs, rhs } => self.execute_i64_lt_s(result, lhs, rhs), Instr::I64LtSImm16Lhs { result, lhs, rhs } => { self.execute_i64_lt_s_imm16_lhs(result, lhs, rhs) } Instr::I64LtSImm16Rhs { result, lhs, rhs } => { self.execute_i64_lt_s_imm16_rhs(result, lhs, rhs) } Instr::I64LtU { result, lhs, rhs } => self.execute_i64_lt_u(result, lhs, rhs), Instr::I64LtUImm16Lhs { result, lhs, rhs } => { self.execute_i64_lt_u_imm16_lhs(result, lhs, rhs) } Instr::I64LtUImm16Rhs { result, lhs, rhs } => { self.execute_i64_lt_u_imm16_rhs(result, lhs, rhs) } Instr::I64LeS { result, lhs, rhs } => self.execute_i64_le_s(result, lhs, rhs), Instr::I64LeSImm16Lhs { result, lhs, rhs } => { self.execute_i64_le_s_imm16_lhs(result, lhs, rhs) } Instr::I64LeSImm16Rhs { result, lhs, rhs } => { self.execute_i64_le_s_imm16_rhs(result, lhs, rhs) } Instr::I64LeU { result, lhs, rhs } => self.execute_i64_le_u(result, lhs, rhs), Instr::I64LeUImm16Lhs { result, lhs, rhs } => { self.execute_i64_le_u_imm16_lhs(result, lhs, rhs) } Instr::I64LeUImm16Rhs { result, lhs, rhs } => { self.execute_i64_le_u_imm16_rhs(result, lhs, rhs) } Instr::F32Eq { result, lhs, rhs } => self.execute_f32_eq(result, lhs, rhs), Instr::F32Ne { result, lhs, rhs } => self.execute_f32_ne(result, lhs, rhs), Instr::F32Lt { result, lhs, rhs } => self.execute_f32_lt(result, lhs, rhs), Instr::F32Le { result, lhs, rhs } => self.execute_f32_le(result, lhs, rhs), Instr::F32NotLt { result, lhs, rhs } => self.execute_f32_not_lt(result, lhs, rhs), Instr::F32NotLe { result, lhs, rhs } => self.execute_f32_not_le(result, lhs, rhs), Instr::F64Eq { result, lhs, rhs } => self.execute_f64_eq(result, lhs, rhs), Instr::F64Ne { result, lhs, rhs } => self.execute_f64_ne(result, lhs, rhs), Instr::F64Lt { result, lhs, rhs } => self.execute_f64_lt(result, lhs, rhs), Instr::F64Le { result, lhs, rhs } => self.execute_f64_le(result, lhs, rhs), Instr::F64NotLt { result, lhs, rhs } => self.execute_f64_not_lt(result, lhs, rhs), Instr::F64NotLe { result, lhs, rhs } => self.execute_f64_not_le(result, lhs, rhs), Instr::I32Clz { result, input } => self.execute_i32_clz(result, input), Instr::I32Ctz { result, input } => self.execute_i32_ctz(result, input), Instr::I32Popcnt { result, input } => self.execute_i32_popcnt(result, input), Instr::I32Add { result, lhs, rhs } => self.execute_i32_add(result, lhs, rhs), Instr::I32AddImm16 { result, lhs, rhs } => { self.execute_i32_add_imm16(result, lhs, rhs) } Instr::I32Sub { result, lhs, rhs } => self.execute_i32_sub(result, lhs, rhs), Instr::I32SubImm16Lhs { result, lhs, rhs } => { self.execute_i32_sub_imm16_lhs(result, lhs, rhs) } Instr::I32Mul { result, lhs, rhs } => self.execute_i32_mul(result, lhs, rhs), Instr::I32MulImm16 { result, lhs, rhs } => { self.execute_i32_mul_imm16(result, lhs, rhs) } Instr::I32DivS { result, lhs, rhs } => self.execute_i32_div_s(result, lhs, rhs)?, Instr::I32DivSImm16Rhs { result, lhs, rhs } => { self.execute_i32_div_s_imm16_rhs(result, lhs, rhs)? } Instr::I32DivSImm16Lhs { result, lhs, rhs } => { self.execute_i32_div_s_imm16_lhs(result, lhs, rhs)? } Instr::I32DivU { result, lhs, rhs } => self.execute_i32_div_u(result, lhs, rhs)?, Instr::I32DivUImm16Rhs { result, lhs, rhs } => { self.execute_i32_div_u_imm16_rhs(result, lhs, rhs) } Instr::I32DivUImm16Lhs { result, lhs, rhs } => { self.execute_i32_div_u_imm16_lhs(result, lhs, rhs)? } Instr::I32RemS { result, lhs, rhs } => self.execute_i32_rem_s(result, lhs, rhs)?, Instr::I32RemSImm16Rhs { result, lhs, rhs } => { self.execute_i32_rem_s_imm16_rhs(result, lhs, rhs)? } Instr::I32RemSImm16Lhs { result, lhs, rhs } => { self.execute_i32_rem_s_imm16_lhs(result, lhs, rhs)? } Instr::I32RemU { result, lhs, rhs } => self.execute_i32_rem_u(result, lhs, rhs)?, Instr::I32RemUImm16Rhs { result, lhs, rhs } => { self.execute_i32_rem_u_imm16_rhs(result, lhs, rhs) } Instr::I32RemUImm16Lhs { result, lhs, rhs } => { self.execute_i32_rem_u_imm16_lhs(result, lhs, rhs)? } Instr::I32BitAnd { result, lhs, rhs } => self.execute_i32_bitand(result, lhs, rhs), Instr::I32BitAndImm16 { result, lhs, rhs } => { self.execute_i32_bitand_imm16(result, lhs, rhs) } Instr::I32BitOr { result, lhs, rhs } => self.execute_i32_bitor(result, lhs, rhs), Instr::I32BitOrImm16 { result, lhs, rhs } => { self.execute_i32_bitor_imm16(result, lhs, rhs) } Instr::I32BitXor { result, lhs, rhs } => self.execute_i32_bitxor(result, lhs, rhs), Instr::I32BitXorImm16 { result, lhs, rhs } => { self.execute_i32_bitxor_imm16(result, lhs, rhs) } Instr::I32And { result, lhs, rhs } => self.execute_i32_and(result, lhs, rhs), Instr::I32AndImm16 { result, lhs, rhs } => { self.execute_i32_and_imm16(result, lhs, rhs) } Instr::I32Or { result, lhs, rhs } => self.execute_i32_or(result, lhs, rhs), Instr::I32OrImm16 { result, lhs, rhs } => { self.execute_i32_or_imm16(result, lhs, rhs) } Instr::I32Nand { result, lhs, rhs } => self.execute_i32_nand(result, lhs, rhs), Instr::I32NandImm16 { result, lhs, rhs } => { self.execute_i32_nand_imm16(result, lhs, rhs) } Instr::I32Nor { result, lhs, rhs } => self.execute_i32_nor(result, lhs, rhs), Instr::I32NorImm16 { result, lhs, rhs } => { self.execute_i32_nor_imm16(result, lhs, rhs) } Instr::I32Shl { result, lhs, rhs } => self.execute_i32_shl(result, lhs, rhs), Instr::I32ShlBy { result, lhs, rhs } => self.execute_i32_shl_by(result, lhs, rhs), Instr::I32ShlImm16 { result, lhs, rhs } => { self.execute_i32_shl_imm16(result, lhs, rhs) } Instr::I32ShrU { result, lhs, rhs } => self.execute_i32_shr_u(result, lhs, rhs), Instr::I32ShrUBy { result, lhs, rhs } => { self.execute_i32_shr_u_by(result, lhs, rhs) } Instr::I32ShrUImm16 { result, lhs, rhs } => { self.execute_i32_shr_u_imm16(result, lhs, rhs) } Instr::I32ShrS { result, lhs, rhs } => self.execute_i32_shr_s(result, lhs, rhs), Instr::I32ShrSBy { result, lhs, rhs } => { self.execute_i32_shr_s_by(result, lhs, rhs) } Instr::I32ShrSImm16 { result, lhs, rhs } => { self.execute_i32_shr_s_imm16(result, lhs, rhs) } Instr::I32Rotl { result, lhs, rhs } => self.execute_i32_rotl(result, lhs, rhs), Instr::I32RotlBy { result, lhs, rhs } => self.execute_i32_rotl_by(result, lhs, rhs), Instr::I32RotlImm16 { result, lhs, rhs } => { self.execute_i32_rotl_imm16(result, lhs, rhs) } Instr::I32Rotr { result, lhs, rhs } => self.execute_i32_rotr(result, lhs, rhs), Instr::I32RotrBy { result, lhs, rhs } => self.execute_i32_rotr_by(result, lhs, rhs), Instr::I32RotrImm16 { result, lhs, rhs } => { self.execute_i32_rotr_imm16(result, lhs, rhs) } Instr::I64Clz { result, input } => self.execute_i64_clz(result, input), Instr::I64Ctz { result, input } => self.execute_i64_ctz(result, input), Instr::I64Popcnt { result, input } => self.execute_i64_popcnt(result, input), Instr::I64Add { result, lhs, rhs } => self.execute_i64_add(result, lhs, rhs), Instr::I64AddImm16 { result, lhs, rhs } => { self.execute_i64_add_imm16(result, lhs, rhs) } Instr::I64Sub { result, lhs, rhs } => self.execute_i64_sub(result, lhs, rhs), Instr::I64SubImm16Lhs { result, lhs, rhs } => { self.execute_i64_sub_imm16_lhs(result, lhs, rhs) } Instr::I64Mul { result, lhs, rhs } => self.execute_i64_mul(result, lhs, rhs), Instr::I64MulImm16 { result, lhs, rhs } => { self.execute_i64_mul_imm16(result, lhs, rhs) } Instr::I64DivS { result, lhs, rhs } => self.execute_i64_div_s(result, lhs, rhs)?, Instr::I64DivSImm16Rhs { result, lhs, rhs } => { self.execute_i64_div_s_imm16_rhs(result, lhs, rhs)? } Instr::I64DivSImm16Lhs { result, lhs, rhs } => { self.execute_i64_div_s_imm16_lhs(result, lhs, rhs)? } Instr::I64DivU { result, lhs, rhs } => self.execute_i64_div_u(result, lhs, rhs)?, Instr::I64DivUImm16Rhs { result, lhs, rhs } => { self.execute_i64_div_u_imm16_rhs(result, lhs, rhs) } Instr::I64DivUImm16Lhs { result, lhs, rhs } => { self.execute_i64_div_u_imm16_lhs(result, lhs, rhs)? } Instr::I64RemS { result, lhs, rhs } => self.execute_i64_rem_s(result, lhs, rhs)?, Instr::I64RemSImm16Rhs { result, lhs, rhs } => { self.execute_i64_rem_s_imm16_rhs(result, lhs, rhs)? } Instr::I64RemSImm16Lhs { result, lhs, rhs } => { self.execute_i64_rem_s_imm16_lhs(result, lhs, rhs)? } Instr::I64RemU { result, lhs, rhs } => self.execute_i64_rem_u(result, lhs, rhs)?, Instr::I64RemUImm16Rhs { result, lhs, rhs } => { self.execute_i64_rem_u_imm16_rhs(result, lhs, rhs) } Instr::I64RemUImm16Lhs { result, lhs, rhs } => { self.execute_i64_rem_u_imm16_lhs(result, lhs, rhs)? } Instr::I64BitAnd { result, lhs, rhs } => self.execute_i64_bitand(result, lhs, rhs), Instr::I64BitAndImm16 { result, lhs, rhs } => { self.execute_i64_bitand_imm16(result, lhs, rhs) } Instr::I64BitOr { result, lhs, rhs } => self.execute_i64_bitor(result, lhs, rhs), Instr::I64BitOrImm16 { result, lhs, rhs } => { self.execute_i64_bitor_imm16(result, lhs, rhs) } Instr::I64BitXor { result, lhs, rhs } => self.execute_i64_bitxor(result, lhs, rhs), Instr::I64BitXorImm16 { result, lhs, rhs } => { self.execute_i64_bitxor_imm16(result, lhs, rhs) } Instr::I64And { result, lhs, rhs } => self.execute_i64_and(result, lhs, rhs), Instr::I64AndImm16 { result, lhs, rhs } => { self.execute_i64_and_imm16(result, lhs, rhs) } Instr::I64Or { result, lhs, rhs } => self.execute_i64_or(result, lhs, rhs), Instr::I64OrImm16 { result, lhs, rhs } => { self.execute_i64_or_imm16(result, lhs, rhs) } Instr::I64Nand { result, lhs, rhs } => self.execute_i64_nand(result, lhs, rhs), Instr::I64NandImm16 { result, lhs, rhs } => { self.execute_i64_nand_imm16(result, lhs, rhs) } Instr::I64Nor { result, lhs, rhs } => self.execute_i64_nor(result, lhs, rhs), Instr::I64NorImm16 { result, lhs, rhs } => { self.execute_i64_nor_imm16(result, lhs, rhs) } Instr::I64Shl { result, lhs, rhs } => self.execute_i64_shl(result, lhs, rhs), Instr::I64ShlBy { result, lhs, rhs } => self.execute_i64_shl_by(result, lhs, rhs), Instr::I64ShlImm16 { result, lhs, rhs } => { self.execute_i64_shl_imm16(result, lhs, rhs) } Instr::I64ShrU { result, lhs, rhs } => self.execute_i64_shr_u(result, lhs, rhs), Instr::I64ShrUBy { result, lhs, rhs } => { self.execute_i64_shr_u_by(result, lhs, rhs) } Instr::I64ShrUImm16 { result, lhs, rhs } => { self.execute_i64_shr_u_imm16(result, lhs, rhs) } Instr::I64ShrS { result, lhs, rhs } => self.execute_i64_shr_s(result, lhs, rhs), Instr::I64ShrSBy { result, lhs, rhs } => { self.execute_i64_shr_s_by(result, lhs, rhs) } Instr::I64ShrSImm16 { result, lhs, rhs } => { self.execute_i64_shr_s_imm16(result, lhs, rhs) } Instr::I64Rotl { result, lhs, rhs } => self.execute_i64_rotl(result, lhs, rhs), Instr::I64RotlBy { result, lhs, rhs } => self.execute_i64_rotl_by(result, lhs, rhs), Instr::I64RotlImm16 { result, lhs, rhs } => { self.execute_i64_rotl_imm16(result, lhs, rhs) } Instr::I64Rotr { result, lhs, rhs } => self.execute_i64_rotr(result, lhs, rhs), Instr::I64RotrBy { result, lhs, rhs } => self.execute_i64_rotr_by(result, lhs, rhs), Instr::I64RotrImm16 { result, lhs, rhs } => { self.execute_i64_rotr_imm16(result, lhs, rhs) } Instr::I64Add128 { results, lhs_lo } => self.execute_i64_add128(results, lhs_lo), Instr::I64Sub128 { results, lhs_lo } => self.execute_i64_sub128(results, lhs_lo), Instr::I64MulWideS { results, lhs, rhs } => { self.execute_i64_mul_wide_s(results, lhs, rhs) } Instr::I64MulWideU { results, lhs, rhs } => { self.execute_i64_mul_wide_u(results, lhs, rhs) } Instr::I32WrapI64 { result, input } => self.execute_i32_wrap_i64(result, input), Instr::I32Extend8S { result, input } => self.execute_i32_extend8_s(result, input), Instr::I32Extend16S { result, input } => self.execute_i32_extend16_s(result, input), Instr::I64Extend8S { result, input } => self.execute_i64_extend8_s(result, input), Instr::I64Extend16S { result, input } => self.execute_i64_extend16_s(result, input), Instr::I64Extend32S { result, input } => self.execute_i64_extend32_s(result, input), Instr::F32Abs { result, input } => self.execute_f32_abs(result, input), Instr::F32Neg { result, input } => self.execute_f32_neg(result, input), Instr::F32Ceil { result, input } => self.execute_f32_ceil(result, input), Instr::F32Floor { result, input } => self.execute_f32_floor(result, input), Instr::F32Trunc { result, input } => self.execute_f32_trunc(result, input), Instr::F32Nearest { result, input } => self.execute_f32_nearest(result, input), Instr::F32Sqrt { result, input } => self.execute_f32_sqrt(result, input), Instr::F32Add { result, lhs, rhs } => self.execute_f32_add(result, lhs, rhs), Instr::F32Sub { result, lhs, rhs } => self.execute_f32_sub(result, lhs, rhs), Instr::F32Mul { result, lhs, rhs } => self.execute_f32_mul(result, lhs, rhs), Instr::F32Div { result, lhs, rhs } => self.execute_f32_div(result, lhs, rhs), Instr::F32Min { result, lhs, rhs } => self.execute_f32_min(result, lhs, rhs), Instr::F32Max { result, lhs, rhs } => self.execute_f32_max(result, lhs, rhs), Instr::F32Copysign { result, lhs, rhs } => { self.execute_f32_copysign(result, lhs, rhs) } Instr::F32CopysignImm { result, lhs, rhs } => { self.execute_f32_copysign_imm(result, lhs, rhs) } Instr::F64Abs { result, input } => self.execute_f64_abs(result, input), Instr::F64Neg { result, input } => self.execute_f64_neg(result, input), Instr::F64Ceil { result, input } => self.execute_f64_ceil(result, input), Instr::F64Floor { result, input } => self.execute_f64_floor(result, input), Instr::F64Trunc { result, input } => self.execute_f64_trunc(result, input), Instr::F64Nearest { result, input } => self.execute_f64_nearest(result, input), Instr::F64Sqrt { result, input } => self.execute_f64_sqrt(result, input), Instr::F64Add { result, lhs, rhs } => self.execute_f64_add(result, lhs, rhs), Instr::F64Sub { result, lhs, rhs } => self.execute_f64_sub(result, lhs, rhs), Instr::F64Mul { result, lhs, rhs } => self.execute_f64_mul(result, lhs, rhs), Instr::F64Div { result, lhs, rhs } => self.execute_f64_div(result, lhs, rhs), Instr::F64Min { result, lhs, rhs } => self.execute_f64_min(result, lhs, rhs), Instr::F64Max { result, lhs, rhs } => self.execute_f64_max(result, lhs, rhs), Instr::F64Copysign { result, lhs, rhs } => { self.execute_f64_copysign(result, lhs, rhs) } Instr::F64CopysignImm { result, lhs, rhs } => { self.execute_f64_copysign_imm(result, lhs, rhs) } Instr::I32TruncF32S { result, input } => { self.execute_i32_trunc_f32_s(result, input)? } Instr::I32TruncF32U { result, input } => { self.execute_i32_trunc_f32_u(result, input)? } Instr::I32TruncF64S { result, input } => { self.execute_i32_trunc_f64_s(result, input)? } Instr::I32TruncF64U { result, input } => { self.execute_i32_trunc_f64_u(result, input)? } Instr::I64TruncF32S { result, input } => { self.execute_i64_trunc_f32_s(result, input)? } Instr::I64TruncF32U { result, input } => { self.execute_i64_trunc_f32_u(result, input)? } Instr::I64TruncF64S { result, input } => { self.execute_i64_trunc_f64_s(result, input)? } Instr::I64TruncF64U { result, input } => { self.execute_i64_trunc_f64_u(result, input)? } Instr::I32TruncSatF32S { result, input } => { self.execute_i32_trunc_sat_f32_s(result, input) } Instr::I32TruncSatF32U { result, input } => { self.execute_i32_trunc_sat_f32_u(result, input) } Instr::I32TruncSatF64S { result, input } => { self.execute_i32_trunc_sat_f64_s(result, input) } Instr::I32TruncSatF64U { result, input } => { self.execute_i32_trunc_sat_f64_u(result, input) } Instr::I64TruncSatF32S { result, input } => { self.execute_i64_trunc_sat_f32_s(result, input) } Instr::I64TruncSatF32U { result, input } => { self.execute_i64_trunc_sat_f32_u(result, input) } Instr::I64TruncSatF64S { result, input } => { self.execute_i64_trunc_sat_f64_s(result, input) } Instr::I64TruncSatF64U { result, input } => { self.execute_i64_trunc_sat_f64_u(result, input) } Instr::F32DemoteF64 { result, input } => self.execute_f32_demote_f64(result, input), Instr::F64PromoteF32 { result, input } => { self.execute_f64_promote_f32(result, input) } Instr::F32ConvertI32S { result, input } => { self.execute_f32_convert_i32_s(result, input) } Instr::F32ConvertI32U { result, input } => { self.execute_f32_convert_i32_u(result, input) } Instr::F32ConvertI64S { result, input } => { self.execute_f32_convert_i64_s(result, input) } Instr::F32ConvertI64U { result, input } => { self.execute_f32_convert_i64_u(result, input) } Instr::F64ConvertI32S { result, input } => { self.execute_f64_convert_i32_s(result, input) } Instr::F64ConvertI32U { result, input } => { self.execute_f64_convert_i32_u(result, input) } Instr::F64ConvertI64S { result, input } => { self.execute_f64_convert_i64_s(result, input) } Instr::F64ConvertI64U { result, input } => { self.execute_f64_convert_i64_u(result, input) } Instr::TableGet { result, index } => { self.execute_table_get(store.inner(), result, index)? } Instr::TableGetImm { result, index } => { self.execute_table_get_imm(store.inner(), result, index)? } Instr::TableSize { result, table } => { self.execute_table_size(store.inner(), result, table) } Instr::TableSet { index, value } => { self.execute_table_set(store.inner_mut(), index, value)? } Instr::TableSetAt { index, value } => { self.execute_table_set_at(store.inner_mut(), index, value)? } Instr::TableCopy { dst, src, len } => { self.execute_table_copy(store.inner_mut(), dst, src, len)? } Instr::TableInit { dst, src, len } => { self.execute_table_init(store.inner_mut(), dst, src, len)? } Instr::TableFill { dst, len, value } => { self.execute_table_fill(store.inner_mut(), dst, len, value)? } Instr::TableGrow { result, delta, value, } => self.execute_table_grow(store, result, delta, value)?, Instr::ElemDrop { index } => self.execute_element_drop(store.inner_mut(), index), Instr::DataDrop { index } => self.execute_data_drop(store.inner_mut(), index), Instr::MemorySize { result, memory } => { self.execute_memory_size(store.inner(), result, memory) } Instr::MemoryGrow { result, delta } => { self.execute_memory_grow(store, result, delta)? } Instr::MemoryCopy { dst, src, len } => { self.execute_memory_copy(store.inner_mut(), dst, src, len)? } Instr::MemoryFill { dst, value, len } => { self.execute_memory_fill(store.inner_mut(), dst, value, len)? } Instr::MemoryFillImm { dst, value, len } => { self.execute_memory_fill_imm(store.inner_mut(), dst, value, len)? } Instr::MemoryInit { dst, src, len } => { self.execute_memory_init(store.inner_mut(), dst, src, len)? } Instr::TableIndex { .. } | Instr::MemoryIndex { .. } | Instr::DataIndex { .. } | Instr::ElemIndex { .. } | Instr::Const32 { .. } | Instr::I64Const32 { .. } | Instr::F64Const32 { .. } | Instr::BranchTableTarget { .. } | Instr::Slot { .. } | Instr::Slot2 { .. } | Instr::Slot3 { .. } | Instr::SlotAndImm32 { .. } | Instr::Imm16AndImm32 { .. } | Instr::SlotSpan { .. } | Instr::SlotList { .. } | Instr::CallIndirectParams { .. } | Instr::CallIndirectParamsImm16 { .. } => self.invalid_instruction_word()?, #[cfg(feature = "simd")] Instr::I8x16Splat { result, value } => self.execute_i8x16_splat(result, value), #[cfg(feature = "simd")] Instr::I16x8Splat { result, value } => self.execute_i16x8_splat(result, value), #[cfg(feature = "simd")] Instr::I32x4Splat { result, value } => self.execute_i32x4_splat(result, value), #[cfg(feature = "simd")] Instr::I64x2Splat { result, value } => self.execute_i64x2_splat(result, value), #[cfg(feature = "simd")] Instr::F32x4Splat { result, value } => self.execute_f32x4_splat(result, value), #[cfg(feature = "simd")] Instr::F64x2Splat { result, value } => self.execute_f64x2_splat(result, value), #[cfg(feature = "simd")] Instr::I8x16ExtractLaneS { result, value, lane, } => self.i8x16_extract_lane_s(result, value, lane), #[cfg(feature = "simd")] Instr::I8x16ExtractLaneU { result, value, lane, } => self.i8x16_extract_lane_u(result, value, lane), #[cfg(feature = "simd")] Instr::I16x8ExtractLaneS { result, value, lane, } => self.i16x8_extract_lane_s(result, value, lane), #[cfg(feature = "simd")] Instr::I16x8ExtractLaneU { result, value, lane, } => self.i16x8_extract_lane_u(result, value, lane), #[cfg(feature = "simd")] Instr::I32x4ExtractLane { result, value, lane, } => self.i32x4_extract_lane(result, value, lane), #[cfg(feature = "simd")] Instr::I64x2ExtractLane { result, value, lane, } => self.i64x2_extract_lane(result, value, lane), #[cfg(feature = "simd")] Instr::F32x4ExtractLane { result, value, lane, } => self.f32x4_extract_lane(result, value, lane), #[cfg(feature = "simd")] Instr::F64x2ExtractLane { result, value, lane, } => self.f64x2_extract_lane(result, value, lane), #[cfg(feature = "simd")] Instr::I8x16ReplaceLane { result, input, lane, } => self.execute_i8x16_replace_lane(result, input, lane), #[cfg(feature = "simd")] Instr::I8x16ReplaceLaneImm { result, input, lane, value, } => self.execute_i8x16_replace_lane_imm(result, input, lane, value), #[cfg(feature = "simd")] Instr::I16x8ReplaceLane { result, input, lane, } => self.execute_i16x8_replace_lane(result, input, lane), #[cfg(feature = "simd")] Instr::I16x8ReplaceLaneImm { result, input, lane, } => self.execute_i16x8_replace_lane_imm(result, input, lane), #[cfg(feature = "simd")] Instr::I32x4ReplaceLane { result, input, lane, } => self.execute_i32x4_replace_lane(result, input, lane), #[cfg(feature = "simd")] Instr::I32x4ReplaceLaneImm { result, input, lane, } => self.execute_i32x4_replace_lane_imm(result, input, lane), #[cfg(feature = "simd")] Instr::I64x2ReplaceLane { result, input, lane, } => self.execute_i64x2_replace_lane(result, input, lane), #[cfg(feature = "simd")] Instr::I64x2ReplaceLaneImm32 { result, input, lane, } => self.execute_i64x2_replace_lane_imm32(result, input, lane), #[cfg(feature = "simd")] Instr::F32x4ReplaceLane { result, input, lane, } => self.execute_f32x4_replace_lane(result, input, lane), #[cfg(feature = "simd")] Instr::F32x4ReplaceLaneImm { result, input, lane, } => self.execute_f32x4_replace_lane_imm(result, input, lane), #[cfg(feature = "simd")] Instr::F64x2ReplaceLane { result, input, lane, } => self.execute_f64x2_replace_lane(result, input, lane), #[cfg(feature = "simd")] Instr::F64x2ReplaceLaneImm32 { result, input, lane, } => self.execute_f64x2_replace_lane_imm32(result, input, lane), #[cfg(feature = "simd")] Instr::I8x16Shuffle { result, lhs, rhs } => { self.execute_i8x16_shuffle(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I8x16Swizzle { result, input, selector, } => self.execute_i8x16_swizzle(result, input, selector), #[cfg(feature = "simd")] Instr::I8x16Add { result, lhs, rhs } => self.execute_i8x16_add(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I16x8Add { result, lhs, rhs } => self.execute_i16x8_add(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I32x4Add { result, lhs, rhs } => self.execute_i32x4_add(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I64x2Add { result, lhs, rhs } => self.execute_i64x2_add(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I8x16Sub { result, lhs, rhs } => self.execute_i8x16_sub(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I16x8Sub { result, lhs, rhs } => self.execute_i16x8_sub(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I32x4Sub { result, lhs, rhs } => self.execute_i32x4_sub(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I64x2Sub { result, lhs, rhs } => self.execute_i64x2_sub(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I16x8Mul { result, lhs, rhs } => self.execute_i16x8_mul(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I32x4Mul { result, lhs, rhs } => self.execute_i32x4_mul(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I64x2Mul { result, lhs, rhs } => self.execute_i64x2_mul(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I32x4DotI16x8S { result, lhs, rhs } => { self.execute_i32x4_dot_i16x8_s(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I8x16Neg { result, input } => self.execute_i8x16_neg(result, input), #[cfg(feature = "simd")] Instr::I16x8Neg { result, input } => self.execute_i16x8_neg(result, input), #[cfg(feature = "simd")] Instr::I32x4Neg { result, input } => self.execute_i32x4_neg(result, input), #[cfg(feature = "simd")] Instr::I64x2Neg { result, input } => self.execute_i64x2_neg(result, input), #[cfg(feature = "simd")] Instr::I16x8ExtmulLowI8x16S { result, lhs, rhs } => { self.execute_i16x8_extmul_low_i8x16_s(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I16x8ExtmulHighI8x16S { result, lhs, rhs } => { self.execute_i16x8_extmul_high_i8x16_s(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I16x8ExtmulLowI8x16U { result, lhs, rhs } => { self.execute_i16x8_extmul_low_i8x16_u(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I16x8ExtmulHighI8x16U { result, lhs, rhs } => { self.execute_i16x8_extmul_high_i8x16_u(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I32x4ExtmulLowI16x8S { result, lhs, rhs } => { self.execute_i32x4_extmul_low_i16x8_s(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I32x4ExtmulHighI16x8S { result, lhs, rhs } => { self.execute_i32x4_extmul_high_i16x8_s(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I32x4ExtmulLowI16x8U { result, lhs, rhs } => { self.execute_i32x4_extmul_low_i16x8_u(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I32x4ExtmulHighI16x8U { result, lhs, rhs } => { self.execute_i32x4_extmul_high_i16x8_u(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I64x2ExtmulLowI32x4S { result, lhs, rhs } => { self.execute_i64x2_extmul_low_i32x4_s(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I64x2ExtmulHighI32x4S { result, lhs, rhs } => { self.execute_i64x2_extmul_high_i32x4_s(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I64x2ExtmulLowI32x4U { result, lhs, rhs } => { self.execute_i64x2_extmul_low_i32x4_u(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I64x2ExtmulHighI32x4U { result, lhs, rhs } => { self.execute_i64x2_extmul_high_i32x4_u(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I16x8ExtaddPairwiseI8x16S { result, input } => { self.execute_i16x8_extadd_pairwise_i8x16_s(result, input) } #[cfg(feature = "simd")] Instr::I16x8ExtaddPairwiseI8x16U { result, input } => { self.execute_i16x8_extadd_pairwise_i8x16_u(result, input) } #[cfg(feature = "simd")] Instr::I32x4ExtaddPairwiseI16x8S { result, input } => { self.execute_i32x4_extadd_pairwise_i16x8_s(result, input) } #[cfg(feature = "simd")] Instr::I32x4ExtaddPairwiseI16x8U { result, input } => { self.execute_i32x4_extadd_pairwise_i16x8_u(result, input) } #[cfg(feature = "simd")] Instr::I8x16AddSatS { result, lhs, rhs } => { self.execute_i8x16_add_sat_s(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I8x16AddSatU { result, lhs, rhs } => { self.execute_i8x16_add_sat_u(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I16x8AddSatS { result, lhs, rhs } => { self.execute_i16x8_add_sat_s(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I16x8AddSatU { result, lhs, rhs } => { self.execute_i16x8_add_sat_u(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I8x16SubSatS { result, lhs, rhs } => { self.execute_i8x16_sub_sat_s(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I8x16SubSatU { result, lhs, rhs } => { self.execute_i8x16_sub_sat_u(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I16x8SubSatS { result, lhs, rhs } => { self.execute_i16x8_sub_sat_s(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I16x8SubSatU { result, lhs, rhs } => { self.execute_i16x8_sub_sat_u(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I16x8Q15MulrSatS { result, lhs, rhs } => { self.execute_i16x8_q15mulr_sat_s(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I8x16MinS { result, lhs, rhs } => self.execute_i8x16_min_s(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I8x16MinU { result, lhs, rhs } => self.execute_i8x16_min_u(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I16x8MinS { result, lhs, rhs } => self.execute_i16x8_min_s(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I16x8MinU { result, lhs, rhs } => self.execute_i16x8_min_u(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I32x4MinS { result, lhs, rhs } => self.execute_i32x4_min_s(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I32x4MinU { result, lhs, rhs } => self.execute_i32x4_min_u(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I8x16MaxS { result, lhs, rhs } => self.execute_i8x16_max_s(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I8x16MaxU { result, lhs, rhs } => self.execute_i8x16_max_u(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I16x8MaxS { result, lhs, rhs } => self.execute_i16x8_max_s(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I16x8MaxU { result, lhs, rhs } => self.execute_i16x8_max_u(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I32x4MaxS { result, lhs, rhs } => self.execute_i32x4_max_s(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I32x4MaxU { result, lhs, rhs } => self.execute_i32x4_max_u(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I8x16AvgrU { result, lhs, rhs } => { self.execute_i8x16_avgr_u(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I16x8AvgrU { result, lhs, rhs } => { self.execute_i16x8_avgr_u(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I8x16Abs { result, input } => self.execute_i8x16_abs(result, input), #[cfg(feature = "simd")] Instr::I16x8Abs { result, input } => self.execute_i16x8_abs(result, input), #[cfg(feature = "simd")] Instr::I32x4Abs { result, input } => self.execute_i32x4_abs(result, input), #[cfg(feature = "simd")] Instr::I64x2Abs { result, input } => self.execute_i64x2_abs(result, input), #[cfg(feature = "simd")] Instr::I8x16Shl { result, lhs, rhs } => self.execute_i8x16_shl(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I8x16ShlBy { result, lhs, rhs } => { self.execute_i8x16_shl_by(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I16x8Shl { result, lhs, rhs } => self.execute_i16x8_shl(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I16x8ShlBy { result, lhs, rhs } => { self.execute_i16x8_shl_by(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I32x4Shl { result, lhs, rhs } => self.execute_i32x4_shl(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I32x4ShlBy { result, lhs, rhs } => { self.execute_i32x4_shl_by(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I64x2Shl { result, lhs, rhs } => self.execute_i64x2_shl(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I64x2ShlBy { result, lhs, rhs } => { self.execute_i64x2_shl_by(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I8x16ShrS { result, lhs, rhs } => self.execute_i8x16_shr_s(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I8x16ShrSBy { result, lhs, rhs } => { self.execute_i8x16_shr_s_by(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I8x16ShrU { result, lhs, rhs } => self.execute_i8x16_shr_u(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I8x16ShrUBy { result, lhs, rhs } => { self.execute_i8x16_shr_u_by(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I16x8ShrS { result, lhs, rhs } => self.execute_i16x8_shr_s(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I16x8ShrSBy { result, lhs, rhs } => { self.execute_i16x8_shr_s_by(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I16x8ShrU { result, lhs, rhs } => self.execute_i16x8_shr_u(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I16x8ShrUBy { result, lhs, rhs } => { self.execute_i16x8_shr_u_by(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I32x4ShrS { result, lhs, rhs } => self.execute_i32x4_shr_s(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I32x4ShrSBy { result, lhs, rhs } => { self.execute_i32x4_shr_s_by(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I32x4ShrU { result, lhs, rhs } => self.execute_i32x4_shr_u(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I32x4ShrUBy { result, lhs, rhs } => { self.execute_i32x4_shr_u_by(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I64x2ShrS { result, lhs, rhs } => self.execute_i64x2_shr_s(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I64x2ShrSBy { result, lhs, rhs } => { self.execute_i64x2_shr_s_by(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I64x2ShrU { result, lhs, rhs } => self.execute_i64x2_shr_u(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I64x2ShrUBy { result, lhs, rhs } => { self.execute_i64x2_shr_u_by(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::V128And { result, lhs, rhs } => self.execute_v128_and(result, lhs, rhs), #[cfg(feature = "simd")] Instr::V128Or { result, lhs, rhs } => self.execute_v128_or(result, lhs, rhs), #[cfg(feature = "simd")] Instr::V128Xor { result, lhs, rhs } => self.execute_v128_xor(result, lhs, rhs), #[cfg(feature = "simd")] Instr::V128Andnot { result, lhs, rhs } => { self.execute_v128_andnot(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::V128Not { result, input } => self.execute_v128_not(result, input), #[cfg(feature = "simd")] Instr::V128Bitselect { result, lhs, rhs } => { self.execute_v128_bitselect(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I8x16Popcnt { result, input } => self.execute_i8x16_popcnt(result, input), #[cfg(feature = "simd")] Instr::V128AnyTrue { result, input } => self.execute_v128_any_true(result, input), #[cfg(feature = "simd")] Instr::I8x16AllTrue { result, input } => self.execute_i8x16_all_true(result, input), #[cfg(feature = "simd")] Instr::I16x8AllTrue { result, input } => self.execute_i16x8_all_true(result, input), #[cfg(feature = "simd")] Instr::I32x4AllTrue { result, input } => self.execute_i32x4_all_true(result, input), #[cfg(feature = "simd")] Instr::I64x2AllTrue { result, input } => self.execute_i64x2_all_true(result, input), #[cfg(feature = "simd")] Instr::I8x16Bitmask { result, input } => self.execute_i8x16_bitmask(result, input), #[cfg(feature = "simd")] Instr::I16x8Bitmask { result, input } => self.execute_i16x8_bitmask(result, input), #[cfg(feature = "simd")] Instr::I32x4Bitmask { result, input } => self.execute_i32x4_bitmask(result, input), #[cfg(feature = "simd")] Instr::I64x2Bitmask { result, input } => self.execute_i64x2_bitmask(result, input), #[cfg(feature = "simd")] Instr::I8x16Eq { result, lhs, rhs } => self.execute_i8x16_eq(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I16x8Eq { result, lhs, rhs } => self.execute_i16x8_eq(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I32x4Eq { result, lhs, rhs } => self.execute_i32x4_eq(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I64x2Eq { result, lhs, rhs } => self.execute_i64x2_eq(result, lhs, rhs), #[cfg(feature = "simd")] Instr::F32x4Eq { result, lhs, rhs } => self.execute_f32x4_eq(result, lhs, rhs), #[cfg(feature = "simd")] Instr::F64x2Eq { result, lhs, rhs } => self.execute_f64x2_eq(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I8x16Ne { result, lhs, rhs } => self.execute_i8x16_ne(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I16x8Ne { result, lhs, rhs } => self.execute_i16x8_ne(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I32x4Ne { result, lhs, rhs } => self.execute_i32x4_ne(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I64x2Ne { result, lhs, rhs } => self.execute_i64x2_ne(result, lhs, rhs), #[cfg(feature = "simd")] Instr::F32x4Ne { result, lhs, rhs } => self.execute_f32x4_ne(result, lhs, rhs), #[cfg(feature = "simd")] Instr::F64x2Ne { result, lhs, rhs } => self.execute_f64x2_ne(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I8x16LtS { result, lhs, rhs } => self.execute_i8x16_lt_s(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I8x16LtU { result, lhs, rhs } => self.execute_i8x16_lt_u(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I16x8LtS { result, lhs, rhs } => self.execute_i16x8_lt_s(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I16x8LtU { result, lhs, rhs } => self.execute_i16x8_lt_u(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I32x4LtS { result, lhs, rhs } => self.execute_i32x4_lt_s(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I32x4LtU { result, lhs, rhs } => self.execute_i32x4_lt_u(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I64x2LtS { result, lhs, rhs } => self.execute_i64x2_lt_s(result, lhs, rhs), #[cfg(feature = "simd")] Instr::F32x4Lt { result, lhs, rhs } => self.execute_f32x4_lt(result, lhs, rhs), #[cfg(feature = "simd")] Instr::F64x2Lt { result, lhs, rhs } => self.execute_f64x2_lt(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I8x16LeS { result, lhs, rhs } => self.execute_i8x16_le_s(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I8x16LeU { result, lhs, rhs } => self.execute_i8x16_le_u(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I16x8LeS { result, lhs, rhs } => self.execute_i16x8_le_s(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I16x8LeU { result, lhs, rhs } => self.execute_i16x8_le_u(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I32x4LeS { result, lhs, rhs } => self.execute_i32x4_le_s(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I32x4LeU { result, lhs, rhs } => self.execute_i32x4_le_u(result, lhs, rhs), #[cfg(feature = "simd")] Instr::I64x2LeS { result, lhs, rhs } => self.execute_i64x2_le_s(result, lhs, rhs), #[cfg(feature = "simd")] Instr::F32x4Le { result, lhs, rhs } => self.execute_f32x4_le(result, lhs, rhs), #[cfg(feature = "simd")] Instr::F64x2Le { result, lhs, rhs } => self.execute_f64x2_le(result, lhs, rhs), #[cfg(feature = "simd")] Instr::F32x4Neg { result, input } => self.execute_f32x4_neg(result, input), #[cfg(feature = "simd")] Instr::F64x2Neg { result, input } => self.execute_f64x2_neg(result, input), #[cfg(feature = "simd")] Instr::F32x4Abs { result, input } => self.execute_f32x4_abs(result, input), #[cfg(feature = "simd")] Instr::F64x2Abs { result, input } => self.execute_f64x2_abs(result, input), #[cfg(feature = "simd")] Instr::F32x4Min { result, lhs, rhs } => self.execute_f32x4_min(result, lhs, rhs), #[cfg(feature = "simd")] Instr::F64x2Min { result, lhs, rhs } => self.execute_f64x2_min(result, lhs, rhs), #[cfg(feature = "simd")] Instr::F32x4Max { result, lhs, rhs } => self.execute_f32x4_max(result, lhs, rhs), #[cfg(feature = "simd")] Instr::F64x2Max { result, lhs, rhs } => self.execute_f64x2_max(result, lhs, rhs), #[cfg(feature = "simd")] Instr::F32x4Pmin { result, lhs, rhs } => self.execute_f32x4_pmin(result, lhs, rhs), #[cfg(feature = "simd")] Instr::F64x2Pmin { result, lhs, rhs } => self.execute_f64x2_pmin(result, lhs, rhs), #[cfg(feature = "simd")] Instr::F32x4Pmax { result, lhs, rhs } => self.execute_f32x4_pmax(result, lhs, rhs), #[cfg(feature = "simd")] Instr::F64x2Pmax { result, lhs, rhs } => self.execute_f64x2_pmax(result, lhs, rhs), #[cfg(feature = "simd")] Instr::F32x4Add { result, lhs, rhs } => self.execute_f32x4_add(result, lhs, rhs), #[cfg(feature = "simd")] Instr::F64x2Add { result, lhs, rhs } => self.execute_f64x2_add(result, lhs, rhs), #[cfg(feature = "simd")] Instr::F32x4Sub { result, lhs, rhs } => self.execute_f32x4_sub(result, lhs, rhs), #[cfg(feature = "simd")] Instr::F64x2Sub { result, lhs, rhs } => self.execute_f64x2_sub(result, lhs, rhs), #[cfg(feature = "simd")] Instr::F32x4Div { result, lhs, rhs } => self.execute_f32x4_div(result, lhs, rhs), #[cfg(feature = "simd")] Instr::F64x2Div { result, lhs, rhs } => self.execute_f64x2_div(result, lhs, rhs), #[cfg(feature = "simd")] Instr::F32x4Mul { result, lhs, rhs } => self.execute_f32x4_mul(result, lhs, rhs), #[cfg(feature = "simd")] Instr::F64x2Mul { result, lhs, rhs } => self.execute_f64x2_mul(result, lhs, rhs), #[cfg(feature = "simd")] Instr::F32x4Sqrt { result, input } => self.execute_f32x4_sqrt(result, input), #[cfg(feature = "simd")] Instr::F64x2Sqrt { result, input } => self.execute_f64x2_sqrt(result, input), #[cfg(feature = "simd")] Instr::F32x4Ceil { result, input } => self.execute_f32x4_ceil(result, input), #[cfg(feature = "simd")] Instr::F64x2Ceil { result, input } => self.execute_f64x2_ceil(result, input), #[cfg(feature = "simd")] Instr::F32x4Floor { result, input } => self.execute_f32x4_floor(result, input), #[cfg(feature = "simd")] Instr::F64x2Floor { result, input } => self.execute_f64x2_floor(result, input), #[cfg(feature = "simd")] Instr::F32x4Trunc { result, input } => self.execute_f32x4_trunc(result, input), #[cfg(feature = "simd")] Instr::F64x2Trunc { result, input } => self.execute_f64x2_trunc(result, input), #[cfg(feature = "simd")] Instr::F32x4Nearest { result, input } => self.execute_f32x4_nearest(result, input), #[cfg(feature = "simd")] Instr::F64x2Nearest { result, input } => self.execute_f64x2_nearest(result, input), #[cfg(feature = "simd")] Instr::F32x4ConvertI32x4S { result, input } => { self.execute_f32x4_convert_i32x4_s(result, input) } #[cfg(feature = "simd")] Instr::F32x4ConvertI32x4U { result, input } => { self.execute_f32x4_convert_i32x4_u(result, input) } #[cfg(feature = "simd")] Instr::F64x2ConvertLowI32x4S { result, input } => { self.execute_f64x2_convert_low_i32x4_s(result, input) } #[cfg(feature = "simd")] Instr::F64x2ConvertLowI32x4U { result, input } => { self.execute_f64x2_convert_low_i32x4_u(result, input) } #[cfg(feature = "simd")] Instr::I32x4TruncSatF32x4S { result, input } => { self.execute_i32x4_trunc_sat_f32x4_s(result, input) } #[cfg(feature = "simd")] Instr::I32x4TruncSatF32x4U { result, input } => { self.execute_i32x4_trunc_sat_f32x4_u(result, input) } #[cfg(feature = "simd")] Instr::I32x4TruncSatF64x2SZero { result, input } => { self.execute_i32x4_trunc_sat_f64x2_s_zero(result, input) } #[cfg(feature = "simd")] Instr::I32x4TruncSatF64x2UZero { result, input } => { self.execute_i32x4_trunc_sat_f64x2_u_zero(result, input) } #[cfg(feature = "simd")] Instr::F32x4DemoteF64x2Zero { result, input } => { self.execute_f32x4_demote_f64x2_zero(result, input) } #[cfg(feature = "simd")] Instr::F64x2PromoteLowF32x4 { result, input } => { self.execute_f64x2_promote_low_f32x4(result, input) } #[cfg(feature = "simd")] Instr::I8x16NarrowI16x8S { result, lhs, rhs } => { self.execute_i8x16_narrow_i16x8_s(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I8x16NarrowI16x8U { result, lhs, rhs } => { self.execute_i8x16_narrow_i16x8_u(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I16x8NarrowI32x4S { result, lhs, rhs } => { self.execute_i16x8_narrow_i32x4_s(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I16x8NarrowI32x4U { result, lhs, rhs } => { self.execute_i16x8_narrow_i32x4_u(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I16x8ExtendLowI8x16S { result, input } => { self.execute_i16x8_extend_low_i8x16_s(result, input) } #[cfg(feature = "simd")] Instr::I16x8ExtendHighI8x16S { result, input } => { self.execute_i16x8_extend_high_i8x16_s(result, input) } #[cfg(feature = "simd")] Instr::I16x8ExtendLowI8x16U { result, input } => { self.execute_i16x8_extend_low_i8x16_u(result, input) } #[cfg(feature = "simd")] Instr::I16x8ExtendHighI8x16U { result, input } => { self.execute_i16x8_extend_high_i8x16_u(result, input) } #[cfg(feature = "simd")] Instr::I32x4ExtendLowI16x8S { result, input } => { self.execute_i32x4_extend_low_i16x8_s(result, input) } #[cfg(feature = "simd")] Instr::I32x4ExtendHighI16x8S { result, input } => { self.execute_i32x4_extend_high_i16x8_s(result, input) } #[cfg(feature = "simd")] Instr::I32x4ExtendLowI16x8U { result, input } => { self.execute_i32x4_extend_low_i16x8_u(result, input) } #[cfg(feature = "simd")] Instr::I32x4ExtendHighI16x8U { result, input } => { self.execute_i32x4_extend_high_i16x8_u(result, input) } #[cfg(feature = "simd")] Instr::I64x2ExtendLowI32x4S { result, input } => { self.execute_i64x2_extend_low_i32x4_s(result, input) } #[cfg(feature = "simd")] Instr::I64x2ExtendHighI32x4S { result, input } => { self.execute_i64x2_extend_high_i32x4_s(result, input) } #[cfg(feature = "simd")] Instr::I64x2ExtendLowI32x4U { result, input } => { self.execute_i64x2_extend_low_i32x4_u(result, input) } #[cfg(feature = "simd")] Instr::I64x2ExtendHighI32x4U { result, input } => { self.execute_i64x2_extend_high_i32x4_u(result, input) } #[cfg(feature = "simd")] Instr::V128Store { ptr, offset_lo } => { self.execute_v128_store(store.inner_mut(), ptr, offset_lo)? } #[cfg(feature = "simd")] Instr::V128StoreOffset16 { ptr, value, offset } => { self.execute_v128_store_offset16(ptr, offset, value)? } #[cfg(feature = "simd")] Instr::V128StoreAt { value, address } => { self.execute_v128_store_at(store.inner_mut(), address, value)? } #[cfg(feature = "simd")] Instr::V128Store8Lane { ptr, offset_lo } => { self.execute_v128_store8_lane(store.inner_mut(), ptr, offset_lo)? } #[cfg(feature = "simd")] Instr::V128Store8LaneOffset8 { ptr, value, offset, lane, } => self.execute_v128_store8_lane_offset8(ptr, value, offset, lane)?, #[cfg(feature = "simd")] Instr::V128Store8LaneAt { value, address } => { self.execute_v128_store8_lane_at(store.inner_mut(), value, address)? } #[cfg(feature = "simd")] Instr::V128Store16Lane { ptr, offset_lo } => { self.execute_v128_store16_lane(store.inner_mut(), ptr, offset_lo)? } #[cfg(feature = "simd")] Instr::V128Store16LaneOffset8 { ptr, value, offset, lane, } => self.execute_v128_store16_lane_offset8(ptr, value, offset, lane)?, #[cfg(feature = "simd")] Instr::V128Store16LaneAt { value, address } => { self.execute_v128_store16_lane_at(store.inner_mut(), value, address)? } #[cfg(feature = "simd")] Instr::V128Store32Lane { ptr, offset_lo } => { self.execute_v128_store32_lane(store.inner_mut(), ptr, offset_lo)? } #[cfg(feature = "simd")] Instr::V128Store32LaneOffset8 { ptr, value, offset, lane, } => self.execute_v128_store32_lane_offset8(ptr, value, offset, lane)?, #[cfg(feature = "simd")] Instr::V128Store32LaneAt { value, address } => { self.execute_v128_store32_lane_at(store.inner_mut(), value, address)? } #[cfg(feature = "simd")] Instr::V128Store64Lane { ptr, offset_lo } => { self.execute_v128_store64_lane(store.inner_mut(), ptr, offset_lo)? } #[cfg(feature = "simd")] Instr::V128Store64LaneOffset8 { ptr, value, offset, lane, } => self.execute_v128_store64_lane_offset8(ptr, value, offset, lane)?, #[cfg(feature = "simd")] Instr::V128Store64LaneAt { value, address } => { self.execute_v128_store64_lane_at(store.inner_mut(), value, address)? } #[cfg(feature = "simd")] Instr::V128Load { result, offset_lo } => { self.execute_v128_load(store.inner(), result, offset_lo)? } #[cfg(feature = "simd")] Instr::V128LoadAt { result, address } => { self.execute_v128_load_at(store.inner(), result, address)? } #[cfg(feature = "simd")] Instr::V128LoadOffset16 { result, ptr, offset, } => self.execute_v128_load_offset16(result, ptr, offset)?, #[cfg(feature = "simd")] Instr::V128Load32Zero { result, offset_lo } => { self.execute_v128_load32_zero(store.inner(), result, offset_lo)? } #[cfg(feature = "simd")] Instr::V128Load32ZeroAt { result, address } => { self.execute_v128_load32_zero_at(store.inner(), result, address)? } #[cfg(feature = "simd")] Instr::V128Load32ZeroOffset16 { result, ptr, offset, } => self.execute_v128_load32_zero_offset16(result, ptr, offset)?, #[cfg(feature = "simd")] Instr::V128Load64Zero { result, offset_lo } => { self.execute_v128_load64_zero(store.inner(), result, offset_lo)? } #[cfg(feature = "simd")] Instr::V128Load64ZeroAt { result, address } => { self.execute_v128_load64_zero_at(store.inner(), result, address)? } #[cfg(feature = "simd")] Instr::V128Load64ZeroOffset16 { result, ptr, offset, } => self.execute_v128_load64_zero_offset16(result, ptr, offset)?, #[cfg(feature = "simd")] Instr::V128Load8Splat { result, offset_lo } => { self.execute_v128_load8_splat(store.inner(), result, offset_lo)? } #[cfg(feature = "simd")] Instr::V128Load8SplatAt { result, address } => { self.execute_v128_load8_splat_at(store.inner(), result, address)? } #[cfg(feature = "simd")] Instr::V128Load8SplatOffset16 { result, ptr, offset, } => self.execute_v128_load8_splat_offset16(result, ptr, offset)?, #[cfg(feature = "simd")] Instr::V128Load16Splat { result, offset_lo } => { self.execute_v128_load16_splat(store.inner(), result, offset_lo)? } #[cfg(feature = "simd")] Instr::V128Load16SplatAt { result, address } => { self.execute_v128_load16_splat_at(store.inner(), result, address)? } #[cfg(feature = "simd")] Instr::V128Load16SplatOffset16 { result, ptr, offset, } => self.execute_v128_load16_splat_offset16(result, ptr, offset)?, #[cfg(feature = "simd")] Instr::V128Load32Splat { result, offset_lo } => { self.execute_v128_load32_splat(store.inner(), result, offset_lo)? } #[cfg(feature = "simd")] Instr::V128Load32SplatAt { result, address } => { self.execute_v128_load32_splat_at(store.inner(), result, address)? } #[cfg(feature = "simd")] Instr::V128Load32SplatOffset16 { result, ptr, offset, } => self.execute_v128_load32_splat_offset16(result, ptr, offset)?, #[cfg(feature = "simd")] Instr::V128Load64Splat { result, offset_lo } => { self.execute_v128_load64_splat(store.inner(), result, offset_lo)? } #[cfg(feature = "simd")] Instr::V128Load64SplatAt { result, address } => { self.execute_v128_load64_splat_at(store.inner(), result, address)? } #[cfg(feature = "simd")] Instr::V128Load64SplatOffset16 { result, ptr, offset, } => self.execute_v128_load64_splat_offset16(result, ptr, offset)?, #[cfg(feature = "simd")] Instr::V128Load8x8S { result, offset_lo } => { self.execute_v128_load8x8_s(store.inner(), result, offset_lo)? } #[cfg(feature = "simd")] Instr::V128Load8x8SAt { result, address } => { self.execute_v128_load8x8_s_at(store.inner(), result, address)? } #[cfg(feature = "simd")] Instr::V128Load8x8SOffset16 { result, ptr, offset, } => self.execute_v128_load8x8_s_offset16(result, ptr, offset)?, #[cfg(feature = "simd")] Instr::V128Load8x8U { result, offset_lo } => { self.execute_v128_load8x8_u(store.inner(), result, offset_lo)? } #[cfg(feature = "simd")] Instr::V128Load8x8UAt { result, address } => { self.execute_v128_load8x8_u_at(store.inner(), result, address)? } #[cfg(feature = "simd")] Instr::V128Load8x8UOffset16 { result, ptr, offset, } => self.execute_v128_load8x8_u_offset16(result, ptr, offset)?, #[cfg(feature = "simd")] Instr::V128Load16x4S { result, offset_lo } => { self.execute_v128_load16x4_s(store.inner(), result, offset_lo)? } #[cfg(feature = "simd")] Instr::V128Load16x4SAt { result, address } => { self.execute_v128_load16x4_s_at(store.inner(), result, address)? } #[cfg(feature = "simd")] Instr::V128Load16x4SOffset16 { result, ptr, offset, } => self.execute_v128_load16x4_s_offset16(result, ptr, offset)?, #[cfg(feature = "simd")] Instr::V128Load16x4U { result, offset_lo } => { self.execute_v128_load16x4_u(store.inner(), result, offset_lo)? } #[cfg(feature = "simd")] Instr::V128Load16x4UAt { result, address } => { self.execute_v128_load16x4_u_at(store.inner(), result, address)? } #[cfg(feature = "simd")] Instr::V128Load16x4UOffset16 { result, ptr, offset, } => self.execute_v128_load16x4_u_offset16(result, ptr, offset)?, #[cfg(feature = "simd")] Instr::V128Load32x2S { result, offset_lo } => { self.execute_v128_load32x2_s(store.inner(), result, offset_lo)? } #[cfg(feature = "simd")] Instr::V128Load32x2SAt { result, address } => { self.execute_v128_load32x2_s_at(store.inner(), result, address)? } #[cfg(feature = "simd")] Instr::V128Load32x2SOffset16 { result, ptr, offset, } => self.execute_v128_load32x2_s_offset16(result, ptr, offset)?, #[cfg(feature = "simd")] Instr::V128Load32x2U { result, offset_lo } => { self.execute_v128_load32x2_u(store.inner(), result, offset_lo)? } #[cfg(feature = "simd")] Instr::V128Load32x2UAt { result, address } => { self.execute_v128_load32x2_u_at(store.inner(), result, address)? } #[cfg(feature = "simd")] Instr::V128Load32x2UOffset16 { result, ptr, offset, } => self.execute_v128_load32x2_u_offset16(result, ptr, offset)?, #[cfg(feature = "simd")] Instr::V128Load8Lane { result, offset_lo } => { self.execute_v128_load8_lane(store.inner(), result, offset_lo)? } #[cfg(feature = "simd")] Instr::V128Load8LaneAt { result, address } => { self.execute_v128_load8_lane_at(store.inner(), result, address)? } #[cfg(feature = "simd")] Instr::V128Load16Lane { result, offset_lo } => { self.execute_v128_load16_lane(store.inner(), result, offset_lo)? } #[cfg(feature = "simd")] Instr::V128Load16LaneAt { result, address } => { self.execute_v128_load16_lane_at(store.inner(), result, address)? } #[cfg(feature = "simd")] Instr::V128Load32Lane { result, offset_lo } => { self.execute_v128_load32_lane(store.inner(), result, offset_lo)? } #[cfg(feature = "simd")] Instr::V128Load32LaneAt { result, address } => { self.execute_v128_load32_lane_at(store.inner(), result, address)? } #[cfg(feature = "simd")] Instr::V128Load64Lane { result, offset_lo } => { self.execute_v128_load64_lane(store.inner(), result, offset_lo)? } #[cfg(feature = "simd")] Instr::V128Load64LaneAt { result, address } => { self.execute_v128_load64_lane_at(store.inner(), result, address)? } #[cfg(feature = "simd")] Instr::I16x8RelaxedDotI8x16I7x16S { result, lhs, rhs } => { self.execute_i16x8_relaxed_dot_i8x16_i7x16_s(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::I32x4RelaxedDotI8x16I7x16AddS { result, lhs, rhs } => { self.execute_i32x4_relaxed_dot_i8x16_i7x16_add_s(result, lhs, rhs) } #[cfg(feature = "simd")] Instr::F32x4RelaxedMadd { result, a, b } => { self.execute_f32x4_relaxed_madd(result, a, b) } #[cfg(feature = "simd")] Instr::F32x4RelaxedNmadd { result, a, b } => { self.execute_f32x4_relaxed_nmadd(result, a, b) } #[cfg(feature = "simd")] Instr::F64x2RelaxedMadd { result, a, b } => { self.execute_f64x2_relaxed_madd(result, a, b) } #[cfg(feature = "simd")] Instr::F64x2RelaxedNmadd { result, a, b } => { self.execute_f64x2_relaxed_nmadd(result, a, b) } unsupported => panic!("encountered unsupported Wasmi instruction: {unsupported:?}"), } } } } macro_rules! get_entity { ( $( fn $name:ident(&self, index: $index_ty:ty) -> $id_ty:ty; )* ) => { $( #[doc = ::core::concat!( "Returns the [`", ::core::stringify!($id_ty), "`] at `index` for the currently used [`Instance`].\n\n", "# Panics\n\n", "- If there is no [`", ::core::stringify!($id_ty), "`] at `index` for the currently used [`Instance`] in `store`." )] #[inline] fn $name(&self, index: $index_ty) -> $id_ty { unsafe { self.cache.$name(index) } .unwrap_or_else(|| { const ENTITY_NAME: &'static str = ::core::stringify!($id_ty); // Safety: within the Wasmi executor it is assumed that store entity // indices within the Wasmi bytecode are always valid for the // store. This is an invariant of the Wasmi translation. unsafe { unreachable_unchecked!( "missing {ENTITY_NAME} at index {index:?} for the currently used instance", ) } }) } )* } } impl Executor<'_> { get_entity! { fn get_func(&self, index: index::Func) -> Func; fn get_func_type_dedup(&self, index: index::FuncType) -> DedupFuncType; fn get_memory(&self, index: index::Memory) -> Memory; fn get_table(&self, index: index::Table) -> Table; fn get_global(&self, index: index::Global) -> Global; fn get_data_segment(&self, index: index::Data) -> DataSegment; fn get_element_segment(&self, index: index::Elem) -> ElementSegment; } /// Returns the [`Slot`] value. fn get_stack_slot(&self, slot: Slot) -> UntypedVal { // Safety: - It is the responsibility of the `Executor` // implementation to keep the `sp` pointer valid // whenever this method is accessed. // - This is done by updating the `sp` pointer whenever // the heap underlying the value stack is changed. unsafe { self.sp.get(slot) } } /// Returns the [`Slot`] value as type `T`. fn get_stack_slot_as(&self, slot: Slot) -> T where UntypedVal: ReadAs, { // Safety: - It is the responsibility of the `Executor` // implementation to keep the `sp` pointer valid // whenever this method is accessed. // - This is done by updating the `sp` pointer whenever // the heap underlying the value stack is changed. unsafe { self.sp.read_as::(slot) } } /// Sets the [`Slot`] value to `value`. fn set_stack_slot(&mut self, slot: Slot, value: impl Into) { // Safety: - It is the responsibility of the `Executor` // implementation to keep the `sp` pointer valid // whenever this method is accessed. // - This is done by updating the `sp` pointer whenever // the heap underlying the value stack is changed. unsafe { self.sp.set(slot, value.into()) }; } /// Sets the [`Slot`] value to `value` of type `T`. fn set_stack_slot_as(&mut self, slot: Slot, value: T) where UntypedVal: WriteAs, { // Safety: - It is the responsibility of the `Executor` // implementation to keep the `sp` pointer valid // whenever this method is accessed. // - This is done by updating the `sp` pointer whenever // the heap underlying the value stack is changed. unsafe { self.sp.write_as::(slot, value) }; } /// Shifts the instruction pointer to the next instruction. #[inline(always)] fn next_instr(&mut self) { self.next_instr_at(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 [`Op::Branch`]. #[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<(), Error> { self.try_next_instr_at(1) } /// 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<(), Error> { self.next_instr_at(skip); Ok(()) } /// Returns the [`FrameSlots`] of the [`CallFrame`]. fn frame_stack_ptr_impl(value_stack: &mut ValueStack, frame: &CallFrame) -> FrameSlots { // Safety: We are using the frame's own base offset as input because it is // guaranteed by the Wasm validation and translation phase to be // valid for all register indices used by the associated function body. unsafe { value_stack.stack_ptr_at(frame.base_offset()) } } /// Initializes the [`Executor`] state for the [`CallFrame`]. /// /// # Note /// /// The initialization of the [`Executor`] allows for efficient execution. fn init_call_frame(&mut self, frame: &CallFrame) { Self::init_call_frame_impl(&mut self.stack.values, &mut self.sp, &mut self.ip, frame) } /// Initializes the [`Executor`] state for the [`CallFrame`]. /// /// # Note /// /// The initialization of the [`Executor`] allows for efficient execution. fn init_call_frame_impl( value_stack: &mut ValueStack, sp: &mut FrameSlots, ip: &mut InstructionPtr, frame: &CallFrame, ) { *sp = Self::frame_stack_ptr_impl(value_stack, frame); *ip = frame.instr_ptr(); } /// Executes a generic unary [`Op`]. #[inline(always)] fn execute_unary(&mut self, result: Slot, input: Slot, op: fn(P) -> R) where UntypedVal: ReadAs

+ WriteAs, { let value = self.get_stack_slot_as::

(input); self.set_stack_slot_as::(result, op(value)); self.next_instr(); } /// Executes a fallible generic unary [`Op`]. #[inline(always)] fn try_execute_unary( &mut self, result: Slot, input: Slot, op: fn(P) -> Result, ) -> Result<(), Error> where UntypedVal: ReadAs

+ WriteAs, { let value = self.get_stack_slot_as::

(input); self.set_stack_slot_as::(result, op(value)?); self.try_next_instr() } /// Executes a generic binary [`Op`]. #[inline(always)] fn execute_binary( &mut self, result: Slot, lhs: Slot, rhs: Slot, op: fn(Lhs, Rhs) -> Result, ) where UntypedVal: ReadAs + ReadAs + WriteAs, { let lhs = self.get_stack_slot_as::(lhs); let rhs = self.get_stack_slot_as::(rhs); self.set_stack_slot_as::(result, op(lhs, rhs)); self.next_instr(); } /// Executes a generic binary [`Op`]. #[inline(always)] fn execute_binary_imm16_rhs( &mut self, result: Slot, lhs: Slot, rhs: Const16, op: fn(Lhs, Rhs) -> T, ) where Rhs: From>, UntypedVal: ReadAs + ReadAs + WriteAs, { let lhs = self.get_stack_slot_as::(lhs); let rhs = Rhs::from(rhs); self.set_stack_slot_as::(result, op(lhs, rhs)); self.next_instr(); } /// Executes a generic binary [`Op`] with reversed operands. #[inline(always)] fn execute_binary_imm16_lhs( &mut self, result: Slot, lhs: Const16, rhs: Slot, op: fn(Lhs, Rhs) -> T, ) where Lhs: From>, UntypedVal: ReadAs + WriteAs, { let lhs = Lhs::from(lhs); let rhs = self.get_stack_slot_as::(rhs); self.set_stack_slot_as::(result, op(lhs, rhs)); self.next_instr(); } /// Executes a generic shift or rotate [`Op`]. #[inline(always)] fn execute_shift_by( &mut self, result: Slot, lhs: Slot, rhs: ShiftAmount, op: fn(Lhs, Rhs) -> T, ) where Rhs: From>, UntypedVal: ReadAs + ReadAs + WriteAs, { let lhs = self.get_stack_slot_as::(lhs); let rhs = Rhs::from(rhs); self.set_stack_slot_as::(result, op(lhs, rhs)); self.next_instr(); } /// Executes a fallible generic binary [`Op`]. #[inline(always)] fn try_execute_binary( &mut self, result: Slot, lhs: Slot, rhs: Slot, op: fn(Lhs, Rhs) -> Result, ) -> Result<(), Error> where UntypedVal: ReadAs + ReadAs + WriteAs, { let lhs = self.get_stack_slot_as::(lhs); let rhs = self.get_stack_slot_as::(rhs); self.set_stack_slot_as::(result, op(lhs, rhs)?); self.try_next_instr() } /// Executes a fallible generic binary [`Op`]. #[inline(always)] fn try_execute_divrem_imm16_rhs( &mut self, result: Slot, lhs: Slot, rhs: Const16, op: fn(Lhs, Rhs) -> Result, ) -> Result<(), Error> where Rhs: From>, UntypedVal: ReadAs + WriteAs, { let lhs = self.get_stack_slot_as::(lhs); let rhs = Rhs::from(rhs); self.set_stack_slot_as::(result, op(lhs, rhs)?); self.try_next_instr() } /// Executes a fallible generic binary [`Op`]. #[inline(always)] fn execute_divrem_imm16_rhs( &mut self, result: Slot, lhs: Slot, rhs: Const16, op: fn(Lhs, NonZeroT) -> T, ) where NonZeroT: From>, UntypedVal: ReadAs + WriteAs, { let lhs = self.get_stack_slot_as::(lhs); let rhs = ::from(rhs); self.set_stack_slot_as::(result, op(lhs, rhs)); self.next_instr() } /// Executes a fallible generic binary [`Op`] with reversed operands. #[inline(always)] fn try_execute_binary_imm16_lhs( &mut self, result: Slot, lhs: Const16, rhs: Slot, op: fn(Lhs, Rhs) -> Result, ) -> Result<(), Error> where Lhs: From>, UntypedVal: ReadAs + WriteAs, { let lhs = Lhs::from(lhs); let rhs = self.get_stack_slot_as::(rhs); self.set_stack_slot_as::(result, op(lhs, rhs)?); self.try_next_instr() } /// Returns the optional `memory` parameter for a `load_at` [`Op`]. /// /// # Note /// /// - Returns the default [`index::Memory`] if the parameter is missing. /// - Bumps `self.ip` if a [`Op::MemoryIndex`] parameter was found. #[inline(always)] fn fetch_optional_memory(&mut self, delta: usize) -> index::Memory { let mut addr: InstructionPtr = self.ip; addr.add(delta); match *addr.get() { Op::MemoryIndex { index } => { hint::cold(); self.ip.add(1); index } _ => index::Memory::from(0), } } /// Fetches the [`Slot`] and [`Offset64Hi`] parameters for a load or store [`Op`]. unsafe fn fetch_reg_and_offset_hi(&self) -> (Slot, Offset64Hi) { let mut addr: InstructionPtr = self.ip; addr.add(1); match addr.get().filter_register_and_offset_hi() { Ok(value) => value, Err(instr) => unsafe { unreachable_unchecked!("expected an `Op::SlotAndImm32` but found: {instr:?}") }, } } } impl Executor<'_> { /// Used for all [`Op`] words that are not meant for execution. /// /// # Note /// /// This includes [`Op`] variants such as [`Op::TableIndex`] /// that primarily carry parameters for actually executable [`Op`]. fn invalid_instruction_word(&mut self) -> Result<(), Error> { // Safety: Wasmi translation guarantees that branches are never taken to instruction parameters directly. unsafe { unreachable_unchecked!( "expected instruction but found instruction parameter: {:?}", *self.ip.get() ) } } /// Executes a Wasm `unreachable` instruction. fn execute_trap(&mut self, trap_code: TrapCode) -> Result<(), Error> { Err(Error::from(trap_code)) } /// Executes an [`Op::ConsumeFuel`]. fn execute_consume_fuel( &mut self, store: &mut StoreInner, block_fuel: BlockFuel, ) -> Result<(), Error> { // We do not have to check if fuel metering is enabled since // [`Op::ConsumeFuel`] are only generated if fuel metering // is enabled to begin with. store .fuel_mut() .consume_fuel_unchecked(block_fuel.to_u64())?; self.try_next_instr() } /// Executes an [`Op::RefFunc`]. fn execute_ref_func(&mut self, result: Slot, func_index: index::Func) { let func = self.get_func(func_index); let funcref = >::from(func); self.set_stack_slot(result, funcref); self.next_instr(); } } /// Extension method for [`UntypedVal`] required by the [`Executor`]. trait UntypedValueExt: Sized { /// Executes a logical `i{32,64}.and` instruction. fn and(x: Self, y: Self) -> bool; /// Executes a logical `i{32,64}.or` instruction. fn or(x: Self, y: Self) -> bool; /// Executes a fused `i{32,64}.and` + `i{32,64}.eqz` instruction. fn nand(x: Self, y: Self) -> bool { !Self::and(x, y) } /// Executes a fused `i{32,64}.or` + `i{32,64}.eqz` instruction. fn nor(x: Self, y: Self) -> bool { !Self::or(x, y) } } impl UntypedValueExt for i32 { fn and(x: Self, y: Self) -> bool { wasm::i32_bitand(x, y) != 0 } fn or(x: Self, y: Self) -> bool { wasm::i32_bitor(x, y) != 0 } } impl UntypedValueExt for i64 { fn and(x: Self, y: Self) -> bool { wasm::i64_bitand(x, y) != 0 } fn or(x: Self, y: Self) -> bool { wasm::i64_bitor(x, y) != 0 } } /// Extension method for [`UntypedVal`] required by the [`Executor`]. trait UntypedValueCmpExt: Sized { fn not_le(lhs: Self, rhs: Self) -> bool; fn not_lt(lhs: Self, rhs: Self) -> bool; } impl UntypedValueCmpExt for f32 { fn not_le(x: Self, y: Self) -> bool { !wasm::f32_le(x, y) } fn not_lt(x: Self, y: Self) -> bool { !wasm::f32_lt(x, y) } } impl UntypedValueCmpExt for f64 { fn not_le(x: Self, y: Self) -> bool { !wasm::f64_le(x, y) } fn not_lt(x: Self, y: Self) -> bool { !wasm::f64_lt(x, y) } } wasmi-1.1.0/src/engine/executor/mod.rs000064400000000000000000000347731046102023000160000ustar 00000000000000pub(crate) use self::stack::Stack; use self::{ instr_ptr::InstructionPtr, instrs::{dispatch_host_func, execute_instrs}, stack::CallFrame, }; use crate::{ engine::{ CallParams, CallResults, EngineInner, ResumableCallBase, ResumableCallHostTrap, ResumableCallOutOfFuel, }, func::HostFuncEntity, ir::{Slot, SlotSpan}, store::CallHooks, CallHook, Error, Func, FuncEntity, Store, StoreContextMut, }; use super::{code_map::CodeMap, ResumableError}; mod cache; mod instr_ptr; mod instrs; mod stack; impl EngineInner { /// Executes the given [`Func`] with the given `params` and returns the `results`. /// /// Uses the [`StoreContextMut`] for context information about the Wasm [`Store`]. /// /// # Errors /// /// If the Wasm execution traps or runs out of resources. pub fn execute_func( &self, ctx: StoreContextMut, func: &Func, params: impl CallParams, results: Results, ) -> Result<::Results, Error> where Results: CallResults, { let mut stack = self.stacks.lock().reuse_or_new(); let results = EngineExecutor::new(&self.code_map, &mut stack) .execute_root_func(ctx.store, func, params, results) .map_err(|error| match error.into_resumable() { Ok(error) => error.into_error(), Err(error) => error, }); self.stacks.lock().recycle(stack); results } /// Executes the given [`Func`] resumably with the given `params` and returns the `results`. /// /// Uses the [`StoreContextMut`] for context information about the Wasm [`Store`]. /// /// # Errors /// /// If the Wasm execution traps or runs out of resources. pub fn execute_func_resumable( &self, ctx: StoreContextMut, func: &Func, params: impl CallParams, results: Results, ) -> Result::Results>, Error> where Results: CallResults, { let store = ctx.store; let mut stack = self.stacks.lock().reuse_or_new(); let results = EngineExecutor::new(&self.code_map, &mut stack) .execute_root_func(store, func, params, results); match results { Ok(results) => { self.stacks.lock().recycle(stack); Ok(ResumableCallBase::Finished(results)) } Err(error) => match error.into_resumable() { Ok(ResumableError::HostTrap(error)) => { let host_func = *error.host_func(); let caller_results = *error.caller_results(); let host_error = error.into_error(); Ok(ResumableCallBase::HostTrap(ResumableCallHostTrap::new( store.engine().clone(), *func, host_func, host_error, caller_results, stack, ))) } Ok(ResumableError::OutOfFuel(error)) => { let required_fuel = error.required_fuel(); Ok(ResumableCallBase::OutOfFuel(ResumableCallOutOfFuel::new( store.engine().clone(), *func, stack, required_fuel, ))) } Err(error) => { self.stacks.lock().recycle(stack); Err(error) } }, } } /// Resumes the given [`Func`] with the given `params` and returns the `results`. /// /// Uses the [`StoreContextMut`] for context information about the Wasm [`Store`]. /// /// # Errors /// /// If the Wasm execution traps or runs out of resources. pub fn resume_func_host_trap( &self, ctx: StoreContextMut, mut invocation: ResumableCallHostTrap, params: impl CallParams, results: Results, ) -> Result::Results>, Error> where Results: CallResults, { let caller_results = invocation.caller_results(); let mut executor = EngineExecutor::new(&self.code_map, invocation.common.stack_mut()); let results = executor.resume_func_host_trap(ctx.store, params, caller_results, results); match results { Ok(results) => { self.stacks.lock().recycle(invocation.common.take_stack()); Ok(ResumableCallBase::Finished(results)) } Err(error) => match error.into_resumable() { Ok(ResumableError::HostTrap(error)) => { let host_func = *error.host_func(); let caller_results = *error.caller_results(); invocation.update(host_func, error.into_error(), caller_results); Ok(ResumableCallBase::HostTrap(invocation)) } Ok(ResumableError::OutOfFuel(error)) => { let required_fuel = error.required_fuel(); let invocation = invocation.update_to_out_of_fuel(required_fuel); Ok(ResumableCallBase::OutOfFuel(invocation)) } Err(error) => { self.stacks.lock().recycle(invocation.common.take_stack()); Err(error) } }, } } /// Resumes the given [`Func`] after running out of fuel and returns the `results`. /// /// Uses the [`StoreContextMut`] for context information about the Wasm [`Store`]. /// /// # Errors /// /// If the Wasm execution traps or runs out of resources. pub fn resume_func_out_of_fuel( &self, ctx: StoreContextMut, mut invocation: ResumableCallOutOfFuel, results: Results, ) -> Result::Results>, Error> where Results: CallResults, { let mut executor = EngineExecutor::new(&self.code_map, invocation.common.stack_mut()); let results = executor.resume_func_out_of_fuel(ctx.store, results); match results { Ok(results) => { self.stacks.lock().recycle(invocation.common.take_stack()); Ok(ResumableCallBase::Finished(results)) } Err(error) => match error.into_resumable() { Ok(ResumableError::HostTrap(error)) => { let host_func = *error.host_func(); let caller_results = *error.caller_results(); let invocation = invocation.update_to_host_trap( host_func, error.into_error(), caller_results, ); Ok(ResumableCallBase::HostTrap(invocation)) } Ok(ResumableError::OutOfFuel(error)) => { invocation.update(error.required_fuel()); Ok(ResumableCallBase::OutOfFuel(invocation)) } Err(error) => { self.stacks.lock().recycle(invocation.common.take_stack()); Err(error) } }, } } } /// The internal state of the Wasmi engine. #[derive(Debug)] pub struct EngineExecutor<'engine> { /// Shared and reusable generic engine resources. code_map: &'engine CodeMap, /// The value and call stacks. stack: &'engine mut Stack, } /// Convenience function that does nothing to its `&mut` parameter. #[inline] fn do_nothing(_: &mut T) {} impl<'engine> EngineExecutor<'engine> { /// Creates a new [`EngineExecutor`] for the given [`Stack`]. fn new(code_map: &'engine CodeMap, stack: &'engine mut Stack) -> Self { Self { code_map, stack } } /// Executes the given [`Func`] using the given `params`. /// /// Stores the execution result into `results` upon a successful execution. /// /// # Errors /// /// - If the given `params` do not match the expected parameters of `func`. /// - If the given `results` do not match the length of the expected results of `func`. /// - When encountering a Wasm or host trap during the execution of `func`. fn execute_root_func( &mut self, store: &mut Store, func: &Func, params: impl CallParams, results: Results, ) -> Result<::Results, Error> where Results: CallResults, { self.stack.reset(); match store.inner.resolve_func(func) { FuncEntity::Wasm(wasm_func) => { // We reserve space on the stack to write the results of the root function execution. let len_results = results.len_results(); self.stack.values.extend_by(len_results, do_nothing)?; let instance = *wasm_func.instance(); let engine_func = wasm_func.func_body(); let compiled_func = self .code_map .get(Some(store.inner.fuel_mut()), engine_func)?; let (mut uninit_params, offsets) = self .stack .values .alloc_call_frame(compiled_func, do_nothing)?; for value in params.call_params() { unsafe { uninit_params.init_next(value) }; } uninit_params.init_zeroes(); self.stack.calls.push( CallFrame::new( InstructionPtr::new(compiled_func.instrs().as_ptr()), offsets, SlotSpan::new(Slot::from(0)), ), Some(instance), )?; store.invoke_call_hook(CallHook::CallingWasm)?; self.execute_func(store)?; store.invoke_call_hook(CallHook::ReturningFromWasm)?; } FuncEntity::Host(host_func) => { // The host function signature is required for properly // adjusting, inspecting and manipulating the value stack. // In case the host function returns more values than it takes // we are required to extend the value stack. let len_params = host_func.len_params(); let len_results = host_func.len_results(); let max_inout = len_params.max(len_results); let uninit = self .stack .values .extend_by(usize::from(max_inout), do_nothing)?; for (uninit, param) in uninit.iter_mut().zip(params.call_params()) { uninit.write(param); } let host_func = *host_func; self.dispatch_host_func(store, host_func)?; } }; let results = self.write_results_back(results); Ok(results) } /// Resumes the execution of the given [`Func`] using `params` after a host function trapped. /// /// Stores the execution result into `results` upon a successful execution. /// /// # Errors /// /// - If the given `params` do not match the expected parameters of `func`. /// - If the given `results` do not match the length of the expected results of `func`. /// - When encountering a Wasm or host trap during the execution of `func`. fn resume_func_host_trap( &mut self, store: &mut Store, params: impl CallParams, caller_results: SlotSpan, results: Results, ) -> Result<::Results, Error> where Results: CallResults, { let caller = self .stack .calls .peek() .expect("must have caller call frame on stack upon function resumption"); let mut caller_sp = unsafe { self.stack.values.stack_ptr_at(caller.base_offset()) }; let call_params = params.call_params(); let len_params = call_params.len(); for (result, param) in caller_results.iter_sized(len_params).zip(call_params) { unsafe { caller_sp.set(result, param) }; } self.execute_func(store)?; let results = self.write_results_back(results); Ok(results) } /// Resumes the execution of the given [`Func`] using `params` after running out of fuel. /// /// Stores the execution result into `results` upon a successful execution. /// /// # Errors /// /// - If the given `results` do not match the length of the expected results of `func`. /// - When encountering a Wasm or host trap during the execution of `func`. fn resume_func_out_of_fuel( &mut self, store: &mut Store, results: Results, ) -> Result<::Results, Error> where Results: CallResults, { self.execute_func(store)?; let results = self.write_results_back(results); Ok(results) } /// Executes the top most Wasm function on the [`Stack`] until the [`Stack`] is empty. /// /// # Errors /// /// When encountering a Wasm or host trap during execution. #[inline(always)] fn execute_func(&mut self, store: &mut Store) -> Result<(), Error> { execute_instrs(store.prune(), self.stack, self.code_map) } /// Convenience forwarder to [`dispatch_host_func`]. #[inline(always)] fn dispatch_host_func( &mut self, store: &mut Store, host_func: HostFuncEntity, ) -> Result<(), Error> { dispatch_host_func( store.prune(), &mut self.stack.values, host_func, None, CallHooks::Ignore, )?; Ok(()) } /// Writes the results of the function execution back into the `results` buffer. /// /// # Note /// /// The value stack is empty after this operation. /// /// # Panics /// /// - If the `results` buffer length does not match the remaining amount of stack values. #[inline(always)] fn write_results_back(&mut self, results: Results) -> ::Results where Results: CallResults, { let len_results = results.len_results(); results.call_results(&self.stack.values.as_slice()[..len_results]) } } wasmi-1.1.0/src/engine/executor/stack/calls.rs000064400000000000000000000177061046102023000174210ustar 00000000000000use super::{err_stack_overflow, BaseValueStackOffset, FrameValueStackOffset}; use crate::{ collections::HeadVec, engine::executor::InstructionPtr, ir::SlotSpan, Instance, TrapCode, }; use alloc::vec::Vec; #[cfg(doc)] use crate::{ engine::executor::stack::ValueStack, engine::EngineFunc, ir::Op, ir::Slot, Global, Memory, Table, }; /// The stack of nested function calls. #[derive(Debug, Default)] pub struct CallStack { /// The stack of nested function call frames. frames: Vec, /// The [`Instance`] used at certain frame stack heights. instances: HeadVec, /// The maximum allowed recursion depth. /// /// # Note /// /// A [`TrapCode::StackOverflow`] is raised if the recursion limit is exceeded. recursion_limit: usize, } impl CallStack { /// Creates a new [`CallStack`] using the given recursion limit. pub fn new(recursion_limit: usize) -> Self { Self { frames: Vec::new(), instances: HeadVec::default(), recursion_limit, } } /// Clears the [`CallStack`] entirely. /// /// # Note /// /// The [`CallStack`] can sometimes be left in a non-empty state upon /// executing a function, for example when a trap is encountered. We /// reset the [`CallStack`] before executing the next function to /// provide a clean slate for all executions. #[inline(always)] pub fn reset(&mut self) { self.frames.clear(); self.instances.clear(); } /// Returns the number of [`CallFrame`]s on the [`CallStack`]. #[inline(always)] fn len(&self) -> usize { self.frames.len() } /// Returns `true` if the [`CallStack`] is empty. #[inline(always)] pub fn is_empty(&self) -> bool { self.len() == 0 } /// Returns the currently used [`Instance`]. #[inline(always)] pub fn instance(&self) -> Option<&Instance> { self.instances.last() } /// Returns the currently used [`Instance`]. /// /// # Panics /// /// If there is no currently used [`Instance`]. /// This happens if the [`CallStack`] is empty. #[inline(always)] #[track_caller] pub fn instance_expect(&self) -> &Instance { self.instance() .expect("the currently used instance must be present") } /// Pushes a [`CallFrame`] onto the [`CallStack`]. /// /// # Errors /// /// If the recursion limit has been reached. #[inline(always)] pub fn push( &mut self, mut call: CallFrame, instance: Option, ) -> Result<(), TrapCode> { if self.len() == self.recursion_limit { return Err(err_stack_overflow()); } if let Some(instance) = instance { call.changed_instance = self.push_instance(instance); } self.frames.push(call); Ok(()) } /// Pushes the `instance` onto the internal instances stack. /// /// Returns `true` if the [`Instance`] stack has been adjusted. #[inline(always)] fn push_instance(&mut self, instance: Instance) -> bool { if let Some(last) = self.instances.last() { if instance.eq(last) { return false; } } self.instances.push(instance); true } /// Pops the last [`CallFrame`] from the [`CallStack`] if any. /// /// Returns the popped [`Instance`] in case the popped [`CallFrame`] /// introduced a new [`Instance`] on the [`CallStack`]. #[inline(always)] pub fn pop(&mut self) -> Option<(CallFrame, Option)> { let frame = self.frames.pop()?; let instance = match frame.changed_instance { true => self.instances.pop(), false => None, }; Some((frame, instance)) } /// Peeks the last [`CallFrame`] of the [`CallStack`] if any. #[inline(always)] pub fn peek(&self) -> Option<&CallFrame> { self.frames.last() } /// Peeks the last [`CallFrame`] of the [`CallStack`] if any. #[inline(always)] pub fn peek_mut(&mut self) -> Option<&mut CallFrame> { self.frames.last_mut() } /// Peeks the two top-most [`CallFrame`] on the [`CallStack`] if any. /// /// # Note /// /// - The top-most [`CallFrame`] on the [`CallStack`] is referred to as the `callee`. /// - The second top-most [`CallFrame`] on the [`CallStack`] is referred to as the `caller`. /// /// So this function returns a pair of `(callee, caller?)`. pub fn peek_2(&self) -> Option<(&CallFrame, Option<&CallFrame>)> { let (callee, remaining) = self.frames.split_last()?; let caller = remaining.last(); Some((callee, caller)) } } /// Offsets for a [`CallFrame`] into the [`ValueStack`]. #[derive(Debug, Copy, Clone)] pub struct StackOffsets { /// Offset to the first mutable cell of a [`CallFrame`]. pub base: BaseValueStackOffset, /// Offset to the first cell of a [`CallFrame`]. pub frame: FrameValueStackOffset, } impl StackOffsets { /// Moves the [`StackOffsets`] values down by `delta`. /// /// # Note /// /// This is used for the implementation of tail calls. #[inline(always)] fn move_down(&mut self, delta: usize) { let base = usize::from(self.base); let frame = usize::from(self.frame); debug_assert!(delta <= base); debug_assert!(delta <= frame); self.base = BaseValueStackOffset::new(base - delta); self.frame = FrameValueStackOffset::new(frame - delta); } } /// A single frame of a called [`EngineFunc`]. #[derive(Debug, Copy, Clone)] pub struct CallFrame { /// The pointer to the [`Op`] that is executed next. instr_ptr: InstructionPtr, /// Offsets of the [`CallFrame`] into the [`ValueStack`]. offsets: StackOffsets, /// Span of registers were the caller expects them in its [`CallFrame`]. results: SlotSpan, /// Is `true` if this [`CallFrame`] changed the currently used [`Instance`]. /// /// - This flag is an optimization to reduce the amount of accesses on the /// instance stack of the [`CallStack`] for the common case where this is /// not needed. /// - This flag is private to the [`CallStack`] and shall not be observable /// from the outside. changed_instance: bool, } impl CallFrame { /// Creates a new [`CallFrame`]. pub fn new(instr_ptr: InstructionPtr, offsets: StackOffsets, results: SlotSpan) -> Self { Self { instr_ptr, offsets, results, changed_instance: false, } } /// Moves the [`ValueStack`] offsets of the [`CallFrame`] down by `delta`. /// /// # Note /// /// This is used for the implementation of tail calls. pub fn move_down(&mut self, delta: usize) { self.offsets.move_down(delta); } /// Updates the [`InstructionPtr`] of the [`CallFrame`]. /// /// This is required before dispatching a nested function call to update /// the instruction pointer of the caller so that it can continue at that /// position when the called function returns. pub fn update_instr_ptr(&mut self, new_instr_ptr: InstructionPtr) { self.instr_ptr = new_instr_ptr; } /// Returns the [`InstructionPtr`] of the [`CallFrame`]. pub fn instr_ptr(&self) -> InstructionPtr { self.instr_ptr } /// Returns the [`FrameValueStackOffset`] of the [`CallFrame`]. pub fn frame_offset(&self) -> FrameValueStackOffset { self.offsets.frame } /// Returns the [`BaseValueStackOffset`] of the [`CallFrame`]. pub fn base_offset(&self) -> BaseValueStackOffset { self.offsets.base } /// Returns the [`SlotSpan`] of the [`CallFrame`]. /// /// # Note /// /// The registers yielded by the returned [`SlotSpan`] /// refer to the [`CallFrame`] of the caller of this [`CallFrame`]. pub fn results(&self) -> SlotSpan { self.results } } wasmi-1.1.0/src/engine/executor/stack/mod.rs000064400000000000000000000054731046102023000171000ustar 00000000000000mod calls; mod values; pub use self::{ calls::{CallFrame, CallStack, StackOffsets}, values::{BaseValueStackOffset, FrameParams, FrameSlots, FrameValueStackOffset, ValueStack}, }; use crate::{engine::StackConfig, Instance, TrapCode}; /// Returns a [`TrapCode`] signalling a stack overflow. #[cold] fn err_stack_overflow() -> TrapCode { TrapCode::StackOverflow } /// Data structure that combines both value stack and call stack. #[derive(Debug, Default)] pub struct Stack { /// The call stack. pub calls: CallStack, /// The value stack. pub values: ValueStack, } impl Stack { /// Creates a new [`Stack`] given the [`Config`]. /// /// [`Config`]: [`crate::Config`] pub fn new(config: &StackConfig) -> Self { let calls = CallStack::new(config.max_recursion_depth()); let values = ValueStack::new(config.min_stack_height(), config.max_stack_height()); Self { calls, values } } /// Resets the [`Stack`] for clean reuse. pub fn reset(&mut self) { self.calls.reset(); self.values.reset(); } /// Create an empty [`Stack`]. /// /// # Note /// /// Empty stacks require no heap allocations and are cheap to construct. pub fn empty() -> Self { Self { values: ValueStack::empty(), calls: CallStack::default(), } } /// Returns the capacity of the [`Stack`]. pub fn capacity(&self) -> usize { self.values.capacity() } /// Merge the two top-most [`CallFrame`] with respect to a tail call. /// /// # Panics (Debug) /// /// - If the two top-most [`CallFrame`] do not have matching `results`. /// - If there are not at least two [`CallFrame`] on the [`CallStack`]. /// /// # Safety /// /// Any [`FrameSlots`] allocated within the range `from..to` on the [`ValueStack`] /// may be invalidated by this operation. It is the caller's responsibility to reinstantiate /// all [`FrameSlots`] affected by this. #[inline] #[must_use] pub unsafe fn merge_call_frames(&mut self, callee: &mut CallFrame) -> Option { let (caller, instance) = self.calls.pop().expect("caller call frame must exist"); debug_assert_eq!(callee.results(), caller.results()); debug_assert!(caller.base_offset() <= callee.base_offset()); // Safety: // // We only drain cells of the second top-most call frame on the value stack. // Therefore only value stack offsets of the top-most call frame on the // value stack are going to be invalidated which we ensure to adjust and // reinstantiate after this operation. let len_drained = self .values .drain(caller.frame_offset(), callee.frame_offset()); callee.move_down(len_drained); instance } } wasmi-1.1.0/src/engine/executor/stack/values.rs000064400000000000000000000341001046102023000176050ustar 00000000000000use super::{err_stack_overflow, StackOffsets}; use crate::{ core::{ReadAs, UntypedVal, WriteAs}, engine::code_map::CompiledFuncRef, ir::Slot, TrapCode, }; use alloc::vec::Vec; use core::{ fmt::{self, Debug}, mem::{self, MaybeUninit}, ops::Range, ptr, slice, }; #[cfg(doc)] use super::calls::CallFrame; #[cfg(doc)] use crate::engine::EngineFunc; pub struct ValueStack { /// The values on the [`ValueStack`]. values: Vec, /// Maximal possible `sp` value. max_len: usize, } impl ValueStack { /// Default value for initial value stack height in bytes. pub const DEFAULT_MIN_HEIGHT: usize = 1024; /// Default value for maximum value stack height in bytes. pub const DEFAULT_MAX_HEIGHT: usize = 1024 * Self::DEFAULT_MIN_HEIGHT; } impl Debug for ValueStack { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ValueStack") .field("max_len", &self.max_len) .field("entries", &&self.values[..]) .finish() } } #[cfg(test)] impl PartialEq for ValueStack { fn eq(&self, other: &Self) -> bool { self.values == other.values } } #[cfg(test)] impl Eq for ValueStack {} impl Default for ValueStack { fn default() -> Self { const REGISTER_SIZE: usize = mem::size_of::(); Self::new( Self::DEFAULT_MIN_HEIGHT / REGISTER_SIZE, Self::DEFAULT_MAX_HEIGHT / REGISTER_SIZE, ) } } impl ValueStack { /// Creates a new empty [`ValueStack`]. /// /// # Panics /// /// - If the `initial_len` is zero. /// - If the `initial_len` is greater than `maximum_len`. pub fn new(initial_len: usize, maximum_len: usize) -> Self { assert!( initial_len > 0, "cannot initialize the value stack with zero length", ); assert!( initial_len <= maximum_len, "initial value stack length is greater than maximum value stack length", ); Self { values: Vec::with_capacity(initial_len), max_len: maximum_len, } } /// Creates an empty [`ValueStack`] that does not allocate heap memory. /// /// # Note /// /// This is required for resumable functions in order to replace their /// proper stack with a cheap dummy one. pub fn empty() -> Self { Self { values: Vec::new(), max_len: 0, } } /// Resets the [`ValueStack`] for reuse. /// /// # Note /// /// The [`ValueStack`] can sometimes be left in a non-empty state upon /// executing a function, for example when a trap is encountered. We /// reset the [`ValueStack`] before executing the next function to /// provide a clean slate for all executions. pub fn reset(&mut self) { self.values.clear(); } /// Returns the root [`FrameSlots`] pointing to the first value on the [`ValueStack`]. pub fn root_stack_ptr(&mut self) -> FrameSlots { FrameSlots::new(self.values.as_mut_ptr()) } /// Returns the [`FrameSlots`] at the given `offset`. pub unsafe fn stack_ptr_at(&mut self, offset: impl Into) -> FrameSlots { let ptr = self.values.as_mut_ptr().add(offset.into().0); FrameSlots::new(ptr) } /// Returns the capacity of the [`ValueStack`]. pub fn capacity(&self) -> usize { debug_assert!(self.values.len() <= self.values.capacity()); self.values.capacity() } /// Reserves enough space for `additional` cells on the [`ValueStack`]. /// /// This may heap allocate in case the [`ValueStack`] ran out of preallocated memory. /// /// # Errors /// /// When trying to grow the [`ValueStack`] over its maximum size limit. #[inline(always)] pub fn extend_by( &mut self, additional: usize, on_resize: impl FnOnce(&mut Self), ) -> Result<&mut [MaybeUninit], TrapCode> { if additional >= self.max_len() - self.len() { return Err(err_stack_overflow()); } let prev_capacity = self.capacity(); self.values.reserve(additional); if prev_capacity != self.capacity() { on_resize(self); } let spare = self.values.spare_capacity_mut().as_mut_ptr(); unsafe { self.values.set_len(self.values.len() + additional) }; Ok(unsafe { slice::from_raw_parts_mut(spare, additional) }) } /// Returns the current length of the [`ValueStack`]. #[inline(always)] fn len(&self) -> usize { debug_assert!(self.values.len() <= self.max_len); self.values.len() } /// Returns the maximum length of the [`ValueStack`]. #[inline(always)] fn max_len(&self) -> usize { debug_assert!(self.values.len() <= self.max_len); self.max_len } /// Drop the last `amount` cells of the [`ValueStack`]. /// /// # Panics (Debug) /// /// If `amount` is greater than the [`ValueStack`] height. #[inline(always)] pub fn drop(&mut self, amount: usize) { assert!(self.len() >= amount); // Safety: we just asserted that the current length is large enough to not underflow. unsafe { self.values.set_len(self.len() - amount) }; } /// Drop the last `amount` cells of the [`ValueStack`] and returns a slice to them. /// /// # Panics (Debug) /// /// If `amount` is greater than the [`ValueStack`] height. #[inline(always)] pub fn drop_return(&mut self, amount: usize) -> &[UntypedVal] { let len = self.len(); let dropped = unsafe { self.values.get_unchecked(len - amount..) }.as_ptr(); self.drop(amount); unsafe { slice::from_raw_parts(dropped, amount) } } /// Shrink the [`ValueStack`] to the [`ValueStackOffset`]. /// /// # Panics (Debug) /// /// If `new_sp` is greater than the current [`ValueStack`] pointer. #[inline(always)] pub fn truncate(&mut self, new_len: impl Into) { let new_len = new_len.into().0; assert!(new_len <= self.len()); // Safety: we just asserted that the new length is valid. unsafe { self.values.set_len(new_len) }; } /// Allocates a new [`EngineFunc`] on the [`ValueStack`]. /// /// Returns the [`BaseValueStackOffset`] and [`FrameValueStackOffset`] of the allocated [`EngineFunc`]. /// /// # Note /// /// - All live [`FrameSlots`] might be invalidated and need to be reinstantiated. /// - The parameters of the allocated [`EngineFunc`] are set to zero /// and require proper initialization after this call. /// /// # Errors /// /// When trying to grow the [`ValueStack`] over its maximum size limit. pub fn alloc_call_frame( &mut self, func: CompiledFuncRef, on_resize: impl FnMut(&mut Self), ) -> Result<(FrameParams, StackOffsets), TrapCode> { let len_stack_slots = func.len_stack_slots(); let len_consts = func.consts().len(); let len = self.len(); let mut spare = self .extend_by(len_stack_slots as usize, on_resize)? .iter_mut(); (&mut spare) .zip(func.consts()) .for_each(|(uninit, const_value)| { uninit.write(*const_value); }); let params = FrameParams::new(spare.into_slice()); let frame = ValueStackOffset(len); let base = ValueStackOffset(len + len_consts); Ok(( params, StackOffsets { base: BaseValueStackOffset(base), frame: FrameValueStackOffset(frame), }, )) } /// Returns a shared slice over the values of the [`ValueStack`]. #[inline(always)] pub fn as_slice(&self) -> &[UntypedVal] { self.values.as_slice() } /// Returns an exclusive slice over the values of the [`ValueStack`]. #[inline(always)] pub fn as_slice_mut(&mut self) -> &mut [UntypedVal] { self.values.as_mut_slice() } /// Removes the slice `from..to` of [`UntypedVal`] cells from the [`ValueStack`]. /// /// Returns the number of drained [`ValueStack`] cells. /// /// # Safety /// /// - This invalidates all [`FrameSlots`] within the range `from..` and the caller has to /// make sure to properly reinstantiate all those pointers after this operation. /// - This also invalidates all [`FrameValueStackOffset`] and [`BaseValueStackOffset`] indices /// within the range `from..`. #[inline(always)] pub fn drain(&mut self, from: FrameValueStackOffset, to: FrameValueStackOffset) -> usize { debug_assert!(from <= to); let from = from.0 .0; let to = to.0 .0; debug_assert!(from <= self.len()); debug_assert!(to <= self.len()); let len_drained = to - from; self.values.drain(from..to); len_drained } } /// The offset of the [`FrameSlots`]. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct ValueStackOffset(usize); impl From for ValueStackOffset { fn from(offset: FrameValueStackOffset) -> Self { offset.0 } } impl From for ValueStackOffset { fn from(offset: BaseValueStackOffset) -> Self { offset.0 } } /// Returned when allocating a new [`CallFrame`] on the [`ValueStack`]. /// /// # Note /// /// This points to the first cell of the allocated [`CallFrame`]. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct FrameValueStackOffset(ValueStackOffset); impl FrameValueStackOffset { /// Creates a new [`FrameValueStackOffset`] at the `index`. pub(super) fn new(index: usize) -> Self { Self(ValueStackOffset(index)) } } impl From for usize { fn from(offset: FrameValueStackOffset) -> usize { offset.0 .0 } } impl From for FrameValueStackOffset { fn from(offset: ValueStackOffset) -> Self { Self(offset) } } /// Returned when allocating a new [`CallFrame`] on the [`ValueStack`]. /// /// # Note /// /// This points to the first mutable cell of the allocated [`CallFrame`]. /// The first mutable cell of a [`CallFrame`] is accessed by [`Slot(0)`]. /// /// [`Slot(0)`]: [`Slot`] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct BaseValueStackOffset(ValueStackOffset); impl BaseValueStackOffset { /// Creates a new [`BaseValueStackOffset`] at the `index`. pub(super) fn new(index: usize) -> Self { Self(ValueStackOffset(index)) } } impl From for usize { fn from(offset: BaseValueStackOffset) -> usize { offset.0 .0 } } /// Uninitialized parameters of a [`CallFrame`]. pub struct FrameParams { range: Range<*mut MaybeUninit>, } impl FrameParams { /// Creates a new [`FrameSlots`]. pub fn new(ptr: &mut [MaybeUninit]) -> Self { Self { range: ptr.as_mut_ptr_range(), } } /// Sets the value of the `register` to `value`.` /// /// # Safety /// /// It is the callers responsibility to provide a [`Slot`] that /// does not access the underlying [`ValueStack`] out of bounds. pub unsafe fn init_next(&mut self, value: UntypedVal) { self.range.start.write(MaybeUninit::new(value)); self.range.start = self.range.start.add(1); } /// Zero-initialize the remaining locals and parameters. pub fn init_zeroes(mut self) { debug_assert!( self.range.start <= self.range.end, "failed to zero-initialize `FrameParams`: start = {:?}, end = {:?}", self.range.start, self.range.end, ); while !core::ptr::eq(self.range.start, self.range.end) { // Safety: We do not write out-of-buffer due to the above condition. unsafe { self.init_next(UntypedVal::from(0_u64)) } } } } /// Accessor to the [`Slot`] values of a [`CallFrame`] on the [`CallStack`]. /// /// [`CallStack`]: [`super::CallStack`] pub struct FrameSlots { /// The underlying raw pointer to a [`CallFrame`] on the [`ValueStack`]. ptr: *mut UntypedVal, } impl Debug for FrameSlots { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self.ptr) } } impl FrameSlots { /// Creates a new [`FrameSlots`]. fn new(ptr: *mut UntypedVal) -> Self { Self { ptr } } /// Returns the [`UntypedVal`] at the given [`Slot`]. /// /// # Safety /// /// It is the callers responsibility to provide a [`Slot`] that /// does not access the underlying [`ValueStack`] out of bounds. pub unsafe fn get(&self, slot: Slot) -> UntypedVal { ptr::read(self.register_offset(slot)) } /// Returns the [`UntypedVal`] at the given [`Slot`]. /// /// # Safety /// /// It is the callers responsibility to provide a [`Slot`] that /// does not access the underlying [`ValueStack`] out of bounds. pub unsafe fn read_as(&self, slot: Slot) -> T where UntypedVal: ReadAs, { UntypedVal::read_as(&*self.register_offset(slot)) } /// Sets the value of the `register` to `value`.` /// /// # Safety /// /// It is the callers responsibility to provide a [`Slot`] that /// does not access the underlying [`ValueStack`] out of bounds. pub unsafe fn set(&mut self, slot: Slot, value: UntypedVal) { ptr::write(self.register_offset(slot), value) } /// Sets the value of the `register` to `value`.` /// /// # Safety /// /// It is the callers responsibility to provide a [`Slot`] that /// does not access the underlying [`ValueStack`] out of bounds. pub unsafe fn write_as(&mut self, slot: Slot, value: T) where UntypedVal: WriteAs, { let val: &mut UntypedVal = &mut *self.register_offset(slot); val.write_as(value); } /// Returns the underlying pointer offset by the [`Slot`] index. unsafe fn register_offset(&self, slot: Slot) -> *mut UntypedVal { unsafe { self.ptr.offset(isize::from(i16::from(slot))) } } } wasmi-1.1.0/src/engine/func_types.rs000064400000000000000000000104241046102023000155250ustar 00000000000000use super::{EngineIdx, Guarded}; use crate::{ collections::arena::{ArenaIndex, DedupArena, GuardedEntity}, FuncType, }; /// A raw index to a function signature entity. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DedupFuncTypeIdx(u32); impl ArenaIndex for DedupFuncTypeIdx { fn into_usize(self) -> usize { self.0 as _ } fn from_usize(value: usize) -> Self { let value = value.try_into().unwrap_or_else(|error| { panic!("index {value} is out of bounds as dedup func type index: {error}") }); Self(value) } } /// A deduplicated Wasm [`FuncType`]. /// /// # Note /// /// Advantages over a non-deduplicated [`FuncType`] are: /// /// - Comparison for equality is as fast as an integer value comparison. /// - With this we can speed up indirect calls in the engine. /// - Requires a lot less memory footprint to be stored somewhere compared /// to a full fledged [`FuncType`]. /// /// Disadvantages compared to non-deduplicated [`FuncType`] are: /// /// - Requires another indirection to acquire information such as parameter /// or result types of the underlying [`FuncType`]. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[repr(transparent)] pub struct DedupFuncType(GuardedEntity); impl DedupFuncType { /// Creates a new function signature reference. pub(super) fn from_inner(stored: GuardedEntity) -> Self { Self(stored) } /// Returns the underlying stored representation. pub(super) fn into_inner(self) -> GuardedEntity { self.0 } } /// A [`FuncType`] registry that efficiently deduplicate stored function types. /// /// Can also be used to later resolve deduplicated function types into their /// original [`FuncType`] for inspecting their parameter and result types. /// /// The big advantage of deduplicated [`FuncType`] entities is that we can use /// this for indirect calls to speed up the signature checks since comparing /// deduplicated [`FuncType`] instances is as fast as comparing integer values. /// Also with respect to Wasmi bytecode deduplicated [`FuncType`] entities /// require a lot less space to be stored. #[derive(Debug)] pub struct FuncTypeRegistry { /// A unique identifier for the associated engine. /// /// # Note /// /// This is used to guard against invalid entity indices. engine_idx: EngineIdx, /// Deduplicated function types. /// /// # Note /// /// The engine deduplicates function types to make the equality /// comparison very fast. This helps to speed up indirect calls. func_types: DedupArena, } impl FuncTypeRegistry { /// Creates a new [`FuncTypeRegistry`] using the given [`EngineIdx`]. pub(crate) fn new(engine_idx: EngineIdx) -> Self { Self { engine_idx, func_types: DedupArena::default(), } } /// Unpacks the entity and checks if it is owned by the engine. /// /// # Panics /// /// If the guarded entity is not owned by the engine. fn unwrap_index(&self, func_type: Guarded) -> Idx where Idx: ArenaIndex, { func_type.entity_index(self.engine_idx).unwrap_or_else(|| { panic!( "encountered foreign entity in func type registry: {}", self.engine_idx.into_usize() ) }) } /// Allocates a new function type to the engine. pub(crate) fn alloc_func_type(&mut self, func_type: FuncType) -> DedupFuncType { DedupFuncType::from_inner(Guarded::new( self.engine_idx, self.func_types.alloc(func_type), )) } /// Resolves a deduplicated function type into a [`FuncType`] entity. /// /// # Panics /// /// - If the deduplicated function type is not owned by the engine. /// - If the deduplicated function type cannot be resolved to its entity. pub(crate) fn resolve_func_type(&self, func_type: &DedupFuncType) -> &FuncType { let entity_index = self.unwrap_index(func_type.into_inner()); self.func_types .get(entity_index) .unwrap_or_else(|| panic!("failed to resolve stored function type: {entity_index:?}")) } } wasmi-1.1.0/src/engine/limits/engine.rs000064400000000000000000000221271046102023000161170ustar 00000000000000use core::{ error::Error, fmt::{self, Display}, }; /// An error that can occur upon parsing or compiling a Wasm module when [`EnforcedLimits`] are set. #[derive(Debug, Copy, Clone)] pub enum EnforcedLimitsError { /// When a Wasm module exceeds the global variable limit. TooManyGlobals { limit: u32 }, /// When a Wasm module exceeds the table limit. TooManyTables { limit: u32 }, /// When a Wasm module exceeds the function limit. TooManyFunctions { limit: u32 }, /// When a Wasm module exceeds the linear memory limit. TooManyMemories { limit: u32 }, /// When a Wasm module exceeds the element segment limit. TooManyElementSegments { limit: u32 }, /// When a Wasm module exceeds the data segment limit. TooManyDataSegments { limit: u32 }, /// When a Wasm module exceeds the function parameter limit. TooManyParameters { limit: usize }, /// When a Wasm module exceeds the function results limit. TooManyResults { limit: usize }, /// When a Wasm module exceeds the average bytes per function limit. MinAvgBytesPerFunction { limit: u32, avg: u32 }, } impl Error for EnforcedLimitsError {} impl Display for EnforcedLimitsError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::TooManyGlobals { limit } => write!( f, "the Wasm module exceeds the limit of {limit} global variables" ), Self::TooManyTables { limit } => { write!(f, "the Wasm module exceeds the limit of {limit} tables") } Self::TooManyFunctions { limit } => { write!(f, "the Wasm modules exceeds the limit of {limit} functions") } Self::TooManyMemories { limit } => { write!(f, "the Wasm module exceeds the limit of {limit} memories") } Self::TooManyElementSegments { limit } => write!( f, "the Wasm module exceeds the limit of {limit} active element segments" ), Self::TooManyDataSegments { limit } => write!( f, "the Wasm module exceeds the limit of {limit} active data segments", ), Self::TooManyParameters { limit } => { write!(f, "a function type exceeds the limit of {limit} parameters",) } Self::TooManyResults { limit } => { write!(f, "a function type exceeds the limit of {limit} results",) } Self::MinAvgBytesPerFunction { limit, avg } => write!( f, "the Wasm module failed to meet the minimum average bytes per function of {limit}: \ avg={avg}" ), } } } /// Stores customizable limits for the [`Engine`] when parsing or compiling Wasm modules. /// /// By default no limits are enforced. /// /// [`Engine`]: crate::Engine #[derive(Debug, Default, Copy, Clone)] pub struct EnforcedLimits { /// Number of global variables a single Wasm module can have at most. /// /// # Note /// /// - This is checked in [`Module::new`] or [`Module::new_unchecked`]. /// - `None` means the limit is not enforced. /// /// [`Module::new`]: crate::Module::new /// [`Module::new_unchecked`]: crate::Module::new_unchecked pub(crate) max_globals: Option, /// Number of functions a single Wasm module can have at most. /// /// # Note /// /// - This is checked in [`Module::new`] or [`Module::new_unchecked`]. /// - `None` means the limit is not enforced. /// /// [`Module::new`]: crate::Module::new /// [`Module::new_unchecked`]: crate::Module::new_unchecked pub(crate) max_functions: Option, /// Number of tables a single Wasm module can have at most. /// /// # Note /// /// - This is checked in [`Module::new`] or [`Module::new_unchecked`]. /// - This is only relevant if the Wasm `reference-types` proposal is enabled. /// - `None` means the limit is not enforced. /// /// [`Module::new`]: crate::Module::new /// [`Module::new_unchecked`]: crate::Module::new_unchecked pub(crate) max_tables: Option, /// Number of table element segments a single Wasm module can have at most. /// /// # Note /// /// - This is checked in [`Module::new`] or [`Module::new_unchecked`]. /// - This is only relevant if the Wasm `reference-types` proposal is enabled. /// - `None` means the limit is not enforced. /// /// [`Module::new`]: crate::Module::new /// [`Module::new_unchecked`]: crate::Module::new_unchecked pub(crate) max_element_segments: Option, /// Number of linear memories a single Wasm module can have. /// /// # Note /// /// - This is checked in [`Module::new`] or [`Module::new_unchecked`]. /// - This is only relevant if the Wasm `multi-memories` proposal is enabled /// which is not supported in Wasmi at the time of writing this comment. /// - `None` means the limit is not enforced. /// /// [`Module::new`]: crate::Module::new /// [`Module::new_unchecked`]: crate::Module::new_unchecked pub(crate) max_memories: Option, /// Number of linear memory data segments a single Wasm module can have at most. /// /// # Note /// /// - This is checked in [`Module::new`] or [`Module::new_unchecked`]. /// - This is only relevant if the Wasm `reference-types` proposal is enabled. /// - `None` means the limit is not enforced. /// /// [`Module::new`]: crate::Module::new /// [`Module::new_unchecked`]: crate::Module::new_unchecked pub(crate) max_data_segments: Option, /// Limits the number of parameter of all functions and control structures. /// /// # Note /// /// - This is checked in [`Module::new`] or [`Module::new_unchecked`]. /// - `None` means the limit is not enforced. /// /// [`Engine`]: crate::Engine /// [`Module::new`]: crate::Module::new /// [`Module::new_unchecked`]: crate::Module::new_unchecked pub(crate) max_params: Option, /// Limits the number of results of all functions and control structures. /// /// # Note /// /// - This is only relevant if the Wasm `multi-value` proposal is enabled. /// - This is checked in [`Module::new`] or [`Module::new_unchecked`]. /// - `None` means the limit is not enforced. /// /// [`Engine`]: crate::Engine /// [`Module::new`]: crate::Module::new /// [`Module::new_unchecked`]: crate::Module::new_unchecked pub(crate) max_results: Option, /// Minimum number of bytes a function must have on average. /// /// # Note /// /// - This is checked in [`Module::new`] or [`Module::new_unchecked`]. /// - This limitation might seem arbitrary but is important to defend against /// malicious inputs targeting lazy compilation. /// - `None` means the limit is not enforced. /// /// [`Module::new`]: crate::Module::new /// [`Module::new_unchecked`]: crate::Module::new_unchecked pub(crate) min_avg_bytes_per_function: Option, } /// The limit for average bytes per function limit and the threshold at which it is enforced. #[derive(Debug, Copy, Clone)] pub struct AvgBytesPerFunctionLimit { /// The number of Wasm module bytes at which the limit is actually enforced. /// /// This represents the total number of bytes of all Wasm function bodies in the Wasm module combined. /// /// # Note /// /// - A `req_funcs_bytes` of 0 always enforces the `min_avg_bytes_per_function` limit. /// - The `req_funcs_bytes` field exists to filter out small Wasm modules /// that cannot seriously be used to attack the Wasmi compilation. pub req_funcs_bytes: u32, /// The minimum number of bytes a function must have on average. pub min_avg_bytes_per_function: u32, } impl EnforcedLimits { /// A strict set of limits that makes use of Wasmi implementation details. /// /// This set of strict enforced rules can be used by Wasmi users in order /// to safeguard themselves against malicious actors trying to attack the Wasmi /// compilation procedures. pub fn strict() -> Self { Self { max_globals: Some(1000), max_functions: Some(10_000), max_tables: Some(100), max_element_segments: Some(1000), max_memories: Some(1), max_data_segments: Some(1000), max_params: Some(32), max_results: Some(32), min_avg_bytes_per_function: Some(AvgBytesPerFunctionLimit { // If all function bodies combined use a total of at least 1000 bytes // the average bytes per function body limit is enforced. req_funcs_bytes: 1000, // Compiled and optimized Wasm modules usually average out on 100-2500 // bytes per Wasm function. Thus the chosen limit is way below this threshold // and should not be exceeded for non-malicous Wasm modules. min_avg_bytes_per_function: 40, }), } } } wasmi-1.1.0/src/engine/limits/mod.rs000064400000000000000000000002171046102023000154250ustar 00000000000000mod engine; mod stack; #[cfg(test)] mod tests; pub use self::{ engine::{EnforcedLimits, EnforcedLimitsError}, stack::StackConfig, }; wasmi-1.1.0/src/engine/limits/stack.rs000064400000000000000000000071671046102023000157660ustar 00000000000000use core::{ error::Error, fmt::{self, Display}, }; /// Default value for maximum recursion depth. const DEFAULT_MAX_RECURSION_DEPTH: usize = 1000; /// Default value for minimum value stack height in bytes. const DEFAULT_MIN_STACK_HEIGHT: usize = 1_000; /// Default value for maximum value stack height in bytes. const DEFAULT_MAX_STACK_HEIGHT: usize = 1_000_000; /// The default maximum number of cached stacks for reuse. const DEFAULT_MAX_CACHED_STACKS: usize = 2; /// An error returned by some [`StackConfig`] methods. #[derive(Debug)] pub enum StackConfigError { /// The given minimum stack height exceeds the maximum stack height. MinStackHeightExceedsMax, } impl Error for StackConfigError {} impl Display for StackConfigError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { StackConfigError::MinStackHeightExceedsMax => { write!(f, "minimum value stack height exceeds maximum stack height") } } } } /// The Wasmi [`Engine`]'s stack configuration. /// /// [`Engine`]: crate::Engine #[derive(Debug, Copy, Clone)] pub struct StackConfig { /// The maximum recursion depth. max_recursion_depth: usize, /// The minimum (or initial) value stack height. min_stack_height: usize, /// The maximum value stack height. max_stack_height: usize, /// The maximum number of cached stacks kept for reuse. max_cached_stacks: usize, } impl Default for StackConfig { fn default() -> Self { Self { max_recursion_depth: DEFAULT_MAX_RECURSION_DEPTH, min_stack_height: DEFAULT_MIN_STACK_HEIGHT, max_stack_height: DEFAULT_MAX_STACK_HEIGHT, max_cached_stacks: DEFAULT_MAX_CACHED_STACKS, } } } impl StackConfig { /// Sets the new maximum recursion depth. pub fn set_max_recursion_depth(&mut self, value: usize) { self.max_recursion_depth = value; } /// Sets the new minimum (or initial) value stack height. /// /// # Errors /// /// If `value` is greater than the current maximum value stack heihgt. pub fn set_min_stack_height(&mut self, value: usize) -> Result<(), StackConfigError> { if value > self.max_stack_height { return Err(StackConfigError::MinStackHeightExceedsMax); } self.min_stack_height = value; Ok(()) } /// Sets the new maximum value stack height. /// /// # Errors /// /// If `value` is less than the current minimum (or initial) value stack heihgt. pub fn set_max_stack_height(&mut self, value: usize) -> Result<(), StackConfigError> { if value < self.min_stack_height { return Err(StackConfigError::MinStackHeightExceedsMax); } self.max_stack_height = value; Ok(()) } /// Sets the maximum number of stacks that the [`Engine`] keeps for reuse. /// /// [`Engine`]: crate::Engine pub fn set_max_cached_stacks(&mut self, value: usize) { self.max_cached_stacks = value; } /// Returns the maximum recursion depth. pub fn max_recursion_depth(&self) -> usize { self.max_recursion_depth } /// Returns the minimum (or initial) value stack height. pub fn min_stack_height(&self) -> usize { self.min_stack_height } /// Returns the maximum value stack height. pub fn max_stack_height(&self) -> usize { self.max_stack_height } /// Returns the maximum number of stacks that the [`Engine`] keeps for reuse. /// /// [`Engine`]: crate::Engine pub fn max_cached_stacks(&mut self) -> usize { self.max_cached_stacks } } wasmi-1.1.0/src/engine/limits/tests.rs000064400000000000000000000253601046102023000160160ustar 00000000000000use self::engine::AvgBytesPerFunctionLimit; use super::*; use crate::{error::ErrorKind, Config, Engine, Error, Module}; /// Parses and returns the Wasm module `wasm` with the given [`EnforcedLimits`] `limits`. fn parse_with(wasm: &str, limits: EnforcedLimits) -> Result { let mut config = Config::default(); config.enforced_limits(limits); let engine = Engine::new(&config); Module::new(&engine, wasm) } #[test] fn max_globals_ok() { let wasm = " (module (global i32 (i32.const 1)) (global i32 (i32.const 2)) ) "; parse_with( wasm, EnforcedLimits { max_globals: Some(2), ..EnforcedLimits::default() }, ) .unwrap(); } #[test] fn max_globals_err() { let wasm = " (module (global i32 (i32.const 1)) (global i32 (i32.const 2)) (global i32 (i32.const 3)) ) "; let limits = EnforcedLimits { max_globals: Some(2), ..EnforcedLimits::default() }; assert!(matches!( parse_with(wasm, limits).unwrap_err().kind(), ErrorKind::Limits(EnforcedLimitsError::TooManyGlobals { limit: 2 }), )) } #[test] fn max_functions_ok() { let wasm = " (module (func) (func) ) "; parse_with( wasm, EnforcedLimits { max_functions: Some(2), ..EnforcedLimits::default() }, ) .unwrap(); } #[test] fn max_functions_err() { let wasm = " (module (func) (func) (func) ) "; let limits = EnforcedLimits { max_functions: Some(2), ..EnforcedLimits::default() }; assert!(matches!( parse_with(wasm, limits).unwrap_err().kind(), ErrorKind::Limits(EnforcedLimitsError::TooManyFunctions { limit: 2 }), )) } #[test] fn max_tables_ok() { let wasm = " (module (table 0 funcref) (table 0 funcref) ) "; parse_with( wasm, EnforcedLimits { max_tables: Some(2), ..EnforcedLimits::default() }, ) .unwrap(); } #[test] fn max_tables_err() { let wasm = " (module (table 0 funcref) (table 0 funcref) (table 0 funcref) ) "; let limits = EnforcedLimits { max_tables: Some(2), ..EnforcedLimits::default() }; assert!(matches!( parse_with(wasm, limits).unwrap_err().kind(), ErrorKind::Limits(EnforcedLimitsError::TooManyTables { limit: 2 }), )) } #[test] fn max_memories_ok() { let wasm = " (module (memory 0) (memory 0) ) "; parse_with( wasm, EnforcedLimits { max_memories: Some(2), ..EnforcedLimits::default() }, ) .unwrap(); } #[test] fn max_memories_err() { let wasm = " (module (memory 0) (memory 0) (memory 0) ) "; let limits = EnforcedLimits { max_memories: Some(2), ..EnforcedLimits::default() }; assert!(matches!( parse_with(wasm, limits).unwrap_err().kind(), ErrorKind::Limits(EnforcedLimitsError::TooManyMemories { limit: 2 }), )) } #[test] fn max_element_segments_ok() { let wasm = " (module (table $t 0 funcref) (func $f) (elem (table $t) (i32.const 0) funcref (ref.func $f) (ref.null func)) (elem (table $t) (i32.const 1) funcref (ref.func $f) (ref.null func)) ) "; parse_with( wasm, EnforcedLimits { max_element_segments: Some(2), ..EnforcedLimits::default() }, ) .unwrap(); } #[test] fn max_element_segments_err() { let wasm = " (module (table $t 0 funcref) (func $f) (elem (table $t) (i32.const 0) funcref (ref.func $f) (ref.null func)) (elem (table $t) (i32.const 1) funcref (ref.func $f) (ref.null func)) (elem (table $t) (i32.const 2) funcref (ref.func $f) (ref.null func)) ) "; let limits = EnforcedLimits { max_element_segments: Some(2), ..EnforcedLimits::default() }; assert!(matches!( parse_with(wasm, limits).unwrap_err().kind(), ErrorKind::Limits(EnforcedLimitsError::TooManyElementSegments { limit: 2 }), )) } #[test] fn max_data_segments_ok() { let wasm = " (module (memory $m 0) (data (memory $m) (i32.const 0) \"abc\") (data (memory $m) (i32.const 1) \"abc\") ) "; parse_with( wasm, EnforcedLimits { max_data_segments: Some(2), ..EnforcedLimits::default() }, ) .unwrap(); } #[test] fn max_data_segments_err() { let wasm = " (module (memory $m 0) (data (memory $m) (i32.const 0) \"abc\") (data (memory $m) (i32.const 1) \"abc\") (data (memory $m) (i32.const 2) \"abc\") ) "; let limits = EnforcedLimits { max_data_segments: Some(2), ..EnforcedLimits::default() }; assert!(matches!( parse_with(wasm, limits).unwrap_err().kind(), ErrorKind::Limits(EnforcedLimitsError::TooManyDataSegments { limit: 2 }), )) } #[test] fn max_params_func_ok() { let wasm = " (module (func (param i32 i32)) ) "; parse_with( wasm, EnforcedLimits { max_params: Some(2), ..EnforcedLimits::default() }, ) .unwrap(); } #[test] fn max_params_func_err() { let wasm = " (module (func (param i32 i32 i32)) ) "; let limits = EnforcedLimits { max_params: Some(2), ..EnforcedLimits::default() }; assert!(matches!( parse_with(wasm, limits).unwrap_err().kind(), ErrorKind::Limits(EnforcedLimitsError::TooManyParameters { limit: 2 }), )) } #[test] fn max_params_control_ok() { let wasm = " (module (func (param i32) (local.get 0) (local.get 0) (block (param i32 i32) (drop) (drop) ) ) ) "; parse_with( wasm, EnforcedLimits { max_params: Some(2), ..EnforcedLimits::default() }, ) .unwrap(); } #[test] fn max_params_control_err() { let wasm = " (module (func (param i32) (local.get 0) (local.get 0) (block (param i32 i32 i32) (drop) (drop) (drop) ) ) ) "; let limits = EnforcedLimits { max_params: Some(2), ..EnforcedLimits::default() }; assert!(matches!( parse_with(wasm, limits).unwrap_err().kind(), ErrorKind::Limits(EnforcedLimitsError::TooManyParameters { limit: 2 }), )) } #[test] fn max_results_func_ok() { let wasm = " (module (func (result i32 i32) (i32.const 1) (i32.const 2) ) ) "; parse_with( wasm, EnforcedLimits { max_results: Some(2), ..EnforcedLimits::default() }, ) .unwrap(); } #[test] fn max_results_func_err() { let wasm = " (module (func (result i32 i32 i32) (i32.const 1) (i32.const 2) (i32.const 3) ) ) "; let limits = EnforcedLimits { max_results: Some(2), ..EnforcedLimits::default() }; assert!(matches!( parse_with(wasm, limits).unwrap_err().kind(), ErrorKind::Limits(EnforcedLimitsError::TooManyResults { limit: 2 }), )) } #[test] fn max_results_control_ok() { let wasm = " (module (func (block (result i32 i32) (i32.const 1) (i32.const 2) ) (drop) (drop) ) ) "; parse_with( wasm, EnforcedLimits { max_results: Some(2), ..EnforcedLimits::default() }, ) .unwrap(); } #[test] fn max_results_control_err() { let wasm = " (module (func (block (result i32 i32 i32) (i32.const 1) (i32.const 2) (i32.const 3) ) (drop) (drop) (drop) ) ) "; let limits = EnforcedLimits { max_results: Some(2), ..EnforcedLimits::default() }; assert!(matches!( parse_with(wasm, limits).unwrap_err().kind(), ErrorKind::Limits(EnforcedLimitsError::TooManyResults { limit: 2 }), )) } #[test] fn min_avg_code_bytes_ok() { let wasm = " (module (func (nop) (nop) (nop) ) (func (nop) (nop) (nop) ) ) "; parse_with( wasm, EnforcedLimits { min_avg_bytes_per_function: Some(AvgBytesPerFunctionLimit { req_funcs_bytes: 0, min_avg_bytes_per_function: 6, }), ..EnforcedLimits::default() }, ) .unwrap(); } #[test] fn min_avg_code_bytes_err() { let wasm = " (module (func (nop) (nop) ) (func (nop) (nop) ) ) "; let limits = EnforcedLimits { min_avg_bytes_per_function: Some(AvgBytesPerFunctionLimit { req_funcs_bytes: 0, min_avg_bytes_per_function: 6, }), ..EnforcedLimits::default() }; std::println!("{:?}", parse_with(wasm, limits).unwrap_err()); assert!(matches!( parse_with(wasm, limits).unwrap_err().kind(), ErrorKind::Limits(EnforcedLimitsError::MinAvgBytesPerFunction { limit: 6, avg: 5 }), )) } #[test] fn min_avg_code_bytes_ok_threshold() { let wasm = " (module (func (nop) (nop) ) (func (nop) (nop) ) ) "; let limits = EnforcedLimits { min_avg_bytes_per_function: Some(AvgBytesPerFunctionLimit { req_funcs_bytes: 12, min_avg_bytes_per_function: 6, }), ..EnforcedLimits::default() }; parse_with(wasm, limits).unwrap(); } wasmi-1.1.0/src/engine/mod.rs000064400000000000000000000633301046102023000141310ustar 00000000000000//! The Wasmi interpreter. mod block_type; mod code_map; mod config; mod executor; mod func_types; mod limits; mod resumable; mod traits; mod translator; mod utils; pub(crate) use self::{ block_type::BlockType, executor::Stack, func_types::DedupFuncType, translator::{ FuncTranslationDriver, FuncTranslator, FuncTranslatorAllocations, LazyFuncTranslator, ValidatingFuncTranslator, WasmTranslator, }, }; use self::{ code_map::{CodeMap, CompiledFuncEntity}, func_types::FuncTypeRegistry, resumable::ResumableCallBase, }; pub use self::{ code_map::{EngineFunc, EngineFuncSpan, EngineFuncSpanIter}, config::{CompilationMode, Config}, limits::{EnforcedLimits, EnforcedLimitsError, StackConfig}, resumable::{ ResumableCall, ResumableCallHostTrap, ResumableCallOutOfFuel, ResumableError, ResumableHostTrapError, ResumableOutOfFuelError, TypedResumableCall, TypedResumableCallHostTrap, TypedResumableCallOutOfFuel, }, traits::{CallParams, CallResults}, translator::TranslationError, }; use crate::{ collections::arena::{ArenaIndex, GuardedEntity}, func::FuncInOut, module::{FuncIdx, ModuleHeader}, Error, Func, FuncType, StoreContextMut, }; use alloc::{ sync::{Arc, Weak}, vec::Vec, }; use core::sync::atomic::{AtomicU32, Ordering}; use spin::{Mutex, RwLock}; use wasmparser::{FuncToValidate, FuncValidatorAllocations, ValidatorResources}; #[cfg(doc)] use crate::Store; /// A unique engine index. /// /// # Note /// /// Used to protect against invalid entity indices. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct EngineIdx(u32); impl ArenaIndex for EngineIdx { fn into_usize(self) -> usize { self.0 as _ } fn from_usize(value: usize) -> Self { let value = value.try_into().unwrap_or_else(|error| { panic!("index {value} is out of bounds as engine index: {error}") }); Self(value) } } impl EngineIdx { /// Returns a new unique [`EngineIdx`]. fn new() -> Self { /// A static store index counter. static CURRENT_STORE_IDX: AtomicU32 = AtomicU32::new(0); let next_idx = CURRENT_STORE_IDX.fetch_add(1, Ordering::AcqRel); Self(next_idx) } } /// An entity owned by the [`Engine`]. type Guarded = GuardedEntity; /// The Wasmi interpreter. /// /// # Note /// /// - The current Wasmi engine implements a bytecode interpreter. /// - This structure is intentionally cheap to copy. /// Most of its API has a `&self` receiver, so can be shared easily. #[derive(Debug, Clone)] pub struct Engine { inner: Arc, } /// A weak reference to an [`Engine`]. #[derive(Debug, Clone)] pub struct EngineWeak { inner: Weak, } impl EngineWeak { /// Upgrades the [`EngineWeak`] to an [`Engine`]. /// /// Returns `None` if strong references (the [`Engine`] itself) no longer exist. pub fn upgrade(&self) -> Option { let inner = self.inner.upgrade()?; Some(Engine { inner }) } } impl Default for Engine { fn default() -> Self { Self::new(&Config::default()) } } impl Engine { /// Creates a new [`Engine`] with default configuration. /// /// # Note /// /// Users should use [`Engine::default`] to construct a default [`Engine`]. pub fn new(config: &Config) -> Self { Self { inner: Arc::new(EngineInner::new(config)), } } /// Creates an [`EngineWeak`] from the given [`Engine`]. pub fn weak(&self) -> EngineWeak { EngineWeak { inner: Arc::downgrade(&self.inner), } } /// Returns a shared reference to the [`Config`] of the [`Engine`]. pub fn config(&self) -> &Config { self.inner.config() } /// Returns `true` if both [`Engine`] references `a` and `b` refer to the same [`Engine`]. pub fn same(a: &Engine, b: &Engine) -> bool { Arc::ptr_eq(&a.inner, &b.inner) } /// Allocates a new function type to the [`Engine`]. pub(super) fn alloc_func_type(&self, func_type: FuncType) -> DedupFuncType { self.inner.alloc_func_type(func_type) } /// Resolves a deduplicated function type into a [`FuncType`] entity. /// /// # Panics /// /// - If the deduplicated function type is not owned by the engine. /// - If the deduplicated function type cannot be resolved to its entity. pub(super) fn resolve_func_type(&self, func_type: &DedupFuncType, f: F) -> R where F: FnOnce(&FuncType) -> R, { self.inner.resolve_func_type(func_type, f) } /// Allocates `amount` new uninitialized [`EngineFunc`] to the [`CodeMap`]. /// /// Returns a range of [`EngineFunc`]s to allow accessing the allocated [`EngineFunc`]. pub(super) fn alloc_funcs(&self, amount: usize) -> EngineFuncSpan { self.inner.alloc_funcs(amount) } /// Translates the Wasm function using the [`Engine`]. /// /// - Uses the internal [`Config`] to drive the function translation as mandated. /// - Reuses translation and validation allocations to be more efficient when used for many translation units. /// /// # Parameters /// /// - `func_index`: The index of the translated function within its Wasm module. /// - `engine_func`: The index of the translated function in the [`Engine`]. /// - `offset`: The global offset of the Wasm function body within the Wasm binary. /// - `bytes`: The bytes that make up the Wasm encoded function body of the translated function. /// - `module`: The module header information of the Wasm module of the translated function. /// - `func_to_validate`: Optionally validates the translated function. /// /// # Errors /// /// - If function translation fails. /// - If function validation fails. pub(crate) fn translate_func( &self, func_index: FuncIdx, engine_func: EngineFunc, offset: usize, bytes: &[u8], module: ModuleHeader, func_to_validate: Option>, ) -> Result<(), Error> { self.inner.translate_func( func_index, engine_func, offset, bytes, module, func_to_validate, ) } /// Returns reusable [`FuncTranslatorAllocations`] from the [`Engine`]. pub(crate) fn get_translation_allocs(&self) -> FuncTranslatorAllocations { self.inner.get_translation_allocs() } /// Returns reusable [`FuncTranslatorAllocations`] and [`FuncValidatorAllocations`] from the [`Engine`]. pub(crate) fn get_allocs(&self) -> (FuncTranslatorAllocations, FuncValidatorAllocations) { self.inner.get_allocs() } /// Recycles the given [`FuncTranslatorAllocations`] in the [`Engine`]. pub(crate) fn recycle_translation_allocs(&self, allocs: FuncTranslatorAllocations) { self.inner.recycle_translation_allocs(allocs) } /// Recycles the given [`FuncTranslatorAllocations`] and [`FuncValidatorAllocations`] in the [`Engine`]. pub(crate) fn recycle_allocs( &self, translation: FuncTranslatorAllocations, validation: FuncValidatorAllocations, ) { self.inner.recycle_allocs(translation, validation) } /// Initializes the uninitialized [`EngineFunc`] for the [`Engine`]. /// /// # Note /// /// The initialized function will not be compiled after this call and instead /// be prepared to be compiled on the fly when it is called the first time. /// /// # Panics /// /// - If `func` is an invalid [`EngineFunc`] reference for this [`CodeMap`]. /// - If `func` refers to an already initialized [`EngineFunc`]. fn init_lazy_func( &self, func_idx: FuncIdx, func: EngineFunc, bytes: &[u8], module: &ModuleHeader, func_to_validate: Option>, ) { self.inner .init_lazy_func(func_idx, func, bytes, module, func_to_validate) } /// Executes the given [`Func`] with parameters `params`. /// /// Stores the execution result into `results` upon a successful execution. /// /// # Note /// /// - Assumes that the `params` and `results` are well typed. /// Type checks are done at the [`Func::call`] API or when creating /// a new [`TypedFunc`] instance via [`Func::typed`]. /// - The `params` out parameter is in a valid but unspecified state if this /// function returns with an error. /// /// # Errors /// /// - If `params` are overflowing or underflowing the expected amount of parameters. /// - If the given `results` do not match the length of the expected results of `func`. /// - When encountering a Wasm or host trap during the execution of `func`. /// /// [`TypedFunc`]: [`crate::TypedFunc`] #[inline] pub(crate) fn execute_func( &self, ctx: StoreContextMut, func: &Func, params: impl CallParams, results: Results, ) -> Result<::Results, Error> where Results: CallResults, { self.inner.execute_func(ctx, func, params, results) } /// Executes the given [`Func`] resumably with parameters `params` and returns. /// /// Stores the execution result into `results` upon a successful execution. /// If the execution encounters a host trap it will return a handle to the user /// that allows to resume the execution at that point. /// /// # Note /// /// - Assumes that the `params` and `results` are well typed. /// Type checks are done at the [`Func::call`] API or when creating /// a new [`TypedFunc`] instance via [`Func::typed`]. /// - The `params` out parameter is in a valid but unspecified state if this /// function returns with an error. /// /// # Errors /// /// - If `params` are overflowing or underflowing the expected amount of parameters. /// - If the given `results` do not match the length of the expected results of `func`. /// - When encountering a Wasm trap during the execution of `func`. /// - When `func` is a host function that traps. /// /// [`TypedFunc`]: [`crate::TypedFunc`] #[inline] pub(crate) fn execute_func_resumable( &self, ctx: StoreContextMut, func: &Func, params: impl CallParams, results: Results, ) -> Result::Results>, Error> where Results: CallResults, { self.inner .execute_func_resumable(ctx, func, params, results) } /// Resumes the given `invocation` after a host trap given the `params`. /// /// Stores the execution result into `results` upon a successful execution. /// If the execution encounters a host trap it will return a handle to the user /// that allows to resume the execution at that point. /// /// # Note /// /// - Assumes that the `params` and `results` are well typed. /// Type checks are done at the [`Func::call`] API or when creating /// a new [`TypedFunc`] instance via [`Func::typed`]. /// - The `params` out parameter is in a valid but unspecified state if this /// function returns with an error. /// /// # Errors /// /// - If `params` are overflowing or underflowing the expected amount of parameters. /// - If the given `results` do not match the length of the expected results of `func`. /// - When encountering a Wasm trap during the execution of `func`. /// - When `func` is a host function that traps. /// /// [`TypedFunc`]: [`crate::TypedFunc`] #[inline] pub(crate) fn resume_func_host_trap( &self, ctx: StoreContextMut, invocation: ResumableCallHostTrap, params: impl CallParams, results: Results, ) -> Result::Results>, Error> where Results: CallResults, { self.inner .resume_func_host_trap(ctx, invocation, params, results) } /// Resumes the given `invocation` after running out of fuel given the `params`. /// /// Stores the execution result into `results` upon a successful execution. /// If the execution encounters a host trap it will return a handle to the user /// that allows to resume the execution at that point. /// /// # Note /// /// - Assumes that the `params` and `results` are well typed. /// Type checks are done at the [`Func::call`] API or when creating /// a new [`TypedFunc`] instance via [`Func::typed`]. /// - The `params` out parameter is in a valid but unspecified state if this /// function returns with an error. /// /// # Errors /// /// - If `params` are overflowing or underflowing the expected amount of parameters. /// - If the given `results` do not match the length of the expected results of `func`. /// - When encountering a Wasm trap during the execution of `func`. /// - When `func` is a host function that traps. /// /// [`TypedFunc`]: [`crate::TypedFunc`] #[inline] pub(crate) fn resume_func_out_of_fuel( &self, ctx: StoreContextMut, invocation: ResumableCallOutOfFuel, results: Results, ) -> Result::Results>, Error> where Results: CallResults, { self.inner.resume_func_out_of_fuel(ctx, invocation, results) } /// Recycles the given [`Stack`] for reuse in the [`Engine`]. pub(crate) fn recycle_stack(&self, stack: Stack) { self.inner.recycle_stack(stack) } } /// The internal state of the Wasmi [`Engine`]. #[derive(Debug)] pub struct EngineInner { /// The [`Config`] of the engine. config: Config, /// Stores information about all compiled functions. code_map: CodeMap, /// Deduplicated function types. /// /// # Note /// /// The engine deduplicates function types to make the equality /// comparison very fast. This helps to speed up indirect calls. func_types: RwLock, /// Reusable allocation stacks. allocs: Mutex, /// Reusable engine stacks for Wasm execution. /// /// Concurrently executing Wasm executions each require their own stack to /// operate on. Therefore a Wasm engine is required to provide stacks and /// ideally recycles old ones since creation of a new stack is rather expensive. stacks: Mutex, } /// Stacks to hold and distribute reusable allocations. pub struct ReusableAllocationStack { /// The maximum height of each of the allocations stacks. max_height: usize, /// Allocations required by Wasm function translators. translation: Vec, /// Allocations required by Wasm function validators. validation: Vec, } impl Default for ReusableAllocationStack { fn default() -> Self { Self { max_height: 1, translation: Vec::new(), validation: Vec::new(), } } } impl core::fmt::Debug for ReusableAllocationStack { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { f.debug_struct("ReusableAllocationStack") .field("translation", &self.translation) // Note: FuncValidatorAllocations is missing Debug impl at the time of writing this commit. // We should derive Debug as soon as FuncValidatorAllocations has a Debug impl in future // wasmparser versions. .field("validation", &self.validation.len()) .finish() } } impl ReusableAllocationStack { /// Returns reusable [`FuncTranslatorAllocations`] from the [`Engine`]. pub fn get_translation_allocs(&mut self) -> FuncTranslatorAllocations { self.translation.pop().unwrap_or_default() } /// Returns reusable [`FuncValidatorAllocations`] from the [`Engine`]. pub fn get_validation_allocs(&mut self) -> FuncValidatorAllocations { self.validation.pop().unwrap_or_default() } /// Recycles the given [`FuncTranslatorAllocations`] in the [`Engine`]. pub fn recycle_translation_allocs(&mut self, recycled: FuncTranslatorAllocations) { debug_assert!(self.translation.len() <= self.max_height); if self.translation.len() >= self.max_height { return; } self.translation.push(recycled); } /// Recycles the given [`FuncValidatorAllocations`] in the [`Engine`]. pub fn recycle_validation_allocs(&mut self, recycled: FuncValidatorAllocations) { debug_assert!(self.validation.len() <= self.max_height); if self.validation.len() >= self.max_height { return; } self.validation.push(recycled); } } /// The engine's stacks for reuse. /// /// Required for efficient concurrent Wasm executions. #[derive(Debug)] pub struct EngineStacks { /// Stacks to be (re)used. stacks: Vec, /// The stack configuration. config: StackConfig, } impl EngineStacks { /// Creates new [`EngineStacks`] with the given [`StackConfig`]. pub fn new(config: &StackConfig) -> Self { Self { stacks: Vec::new(), config: *config, } } /// Reuse or create a new [`Stack`] if none was available. pub fn reuse_or_new(&mut self) -> Stack { match self.stacks.pop() { Some(stack) => stack, None => Stack::new(&self.config), } } /// Disose and recycle the `stack`. pub fn recycle(&mut self, stack: Stack) { if stack.capacity() > 0 && self.stacks.len() < self.config.max_cached_stacks() { self.stacks.push(stack); } } } impl EngineInner { /// Creates a new [`EngineInner`] with the given [`Config`]. fn new(config: &Config) -> Self { let engine_idx = EngineIdx::new(); Self { config: config.clone(), code_map: CodeMap::new(config), func_types: RwLock::new(FuncTypeRegistry::new(engine_idx)), allocs: Mutex::new(ReusableAllocationStack::default()), stacks: Mutex::new(EngineStacks::new(&config.stack)), } } /// Returns a shared reference to the [`Config`] of the [`EngineInner`]. fn config(&self) -> &Config { &self.config } /// Allocates a new function type to the [`EngineInner`]. fn alloc_func_type(&self, func_type: FuncType) -> DedupFuncType { self.func_types.write().alloc_func_type(func_type) } /// Resolves a deduplicated function type into a [`FuncType`] entity. /// /// # Panics /// /// - If the deduplicated function type is not owned by the engine. /// - If the deduplicated function type cannot be resolved to its entity. fn resolve_func_type(&self, func_type: &DedupFuncType, f: F) -> R where F: FnOnce(&FuncType) -> R, { f(self.func_types.read().resolve_func_type(func_type)) } /// Allocates `amount` new uninitialized [`EngineFunc`] to the [`CodeMap`]. /// /// Returns a range of [`EngineFunc`]s to allow accessing the allocated [`EngineFunc`]. fn alloc_funcs(&self, amount: usize) -> EngineFuncSpan { self.code_map.alloc_funcs(amount) } /// Translates the Wasm function using the [`Engine`]. /// /// For more information read [`Engine::translate_func`]. fn translate_func( &self, func_index: FuncIdx, engine_func: EngineFunc, offset: usize, bytes: &[u8], module: ModuleHeader, func_to_validate: Option>, ) -> Result<(), Error> { let features = self.config().wasm_features(); match (self.config.get_compilation_mode(), func_to_validate) { (CompilationMode::Eager, Some(func_to_validate)) => { let (translation_allocs, validation_allocs) = self.get_allocs(); let validator = func_to_validate.into_validator(validation_allocs); let translator = FuncTranslator::new(func_index, module, translation_allocs)?; let translator = ValidatingFuncTranslator::new(validator, translator)?; let allocs = FuncTranslationDriver::new(offset, bytes, translator)? .translate(|func_entity| self.init_func(engine_func, func_entity))?; self.recycle_allocs(allocs.translation, allocs.validation); } (CompilationMode::Eager, None) => { let allocs = self.get_translation_allocs(); let translator = FuncTranslator::new(func_index, module, allocs)?; let allocs = FuncTranslationDriver::new(offset, bytes, translator)? .translate(|func_entity| self.init_func(engine_func, func_entity))?; self.recycle_translation_allocs(allocs); } (CompilationMode::LazyTranslation, Some(func_to_validate)) => { let allocs = self.get_validation_allocs(); let translator = LazyFuncTranslator::new_unchecked(func_index, engine_func, module, features); let validator = func_to_validate.into_validator(allocs); let translator = ValidatingFuncTranslator::new(validator, translator)?; let allocs = FuncTranslationDriver::new(offset, bytes, translator)? .translate(|func_entity| self.init_func(engine_func, func_entity))?; self.recycle_validation_allocs(allocs.validation); } (CompilationMode::Lazy | CompilationMode::LazyTranslation, func_to_validate) => { let translator = match func_to_validate { Some(func_to_validate) => { LazyFuncTranslator::new(func_index, engine_func, module, func_to_validate) } None => { LazyFuncTranslator::new_unchecked(func_index, engine_func, module, features) } }; FuncTranslationDriver::new(offset, bytes, translator)? .translate(|func_entity| self.init_func(engine_func, func_entity))?; } } Ok(()) } /// Returns reusable [`FuncTranslatorAllocations`] from the [`Engine`]. fn get_translation_allocs(&self) -> FuncTranslatorAllocations { self.allocs.lock().get_translation_allocs() } /// Returns reusable [`FuncValidatorAllocations`] from the [`Engine`]. fn get_validation_allocs(&self) -> FuncValidatorAllocations { self.allocs.lock().get_validation_allocs() } /// Returns reusable [`FuncTranslatorAllocations`] and [`FuncValidatorAllocations`] from the [`Engine`]. /// /// # Note /// /// This method is a bit more efficient than calling both /// - [`EngineInner::get_translation_allocs`] /// - [`EngineInner::get_validation_allocs`] fn get_allocs(&self) -> (FuncTranslatorAllocations, FuncValidatorAllocations) { let mut allocs = self.allocs.lock(); let translation = allocs.get_translation_allocs(); let validation = allocs.get_validation_allocs(); (translation, validation) } /// Recycles the given [`FuncTranslatorAllocations`] in the [`Engine`]. fn recycle_translation_allocs(&self, allocs: FuncTranslatorAllocations) { self.allocs.lock().recycle_translation_allocs(allocs) } /// Recycles the given [`FuncValidatorAllocations`] in the [`Engine`]. fn recycle_validation_allocs(&self, allocs: FuncValidatorAllocations) { self.allocs.lock().recycle_validation_allocs(allocs) } /// Recycles the given [`FuncTranslatorAllocations`] and [`FuncValidatorAllocations`] in the [`Engine`]. /// /// # Note /// /// This method is a bit more efficient than calling both /// - [`EngineInner::recycle_translation_allocs`] /// - [`EngineInner::recycle_validation_allocs`] fn recycle_allocs( &self, translation: FuncTranslatorAllocations, validation: FuncValidatorAllocations, ) { let mut allocs = self.allocs.lock(); allocs.recycle_translation_allocs(translation); allocs.recycle_validation_allocs(validation); } /// Initializes the uninitialized [`EngineFunc`] for the [`EngineInner`]. /// /// # Note /// /// The initialized function will be compiled and ready to be executed after this call. /// /// # Panics /// /// - If `func` is an invalid [`EngineFunc`] reference for this [`CodeMap`]. /// - If `func` refers to an already initialized [`EngineFunc`]. fn init_func(&self, engine_func: EngineFunc, func_entity: CompiledFuncEntity) { self.code_map .init_func_as_compiled(engine_func, func_entity) } /// Initializes the uninitialized [`EngineFunc`] for the [`Engine`]. /// /// # Note /// /// The initialized function will not be compiled after this call and instead /// be prepared to be compiled on the fly when it is called the first time. /// /// # Panics /// /// - If `func` is an invalid [`EngineFunc`] reference for this [`CodeMap`]. /// - If `func` refers to an already initialized [`EngineFunc`]. fn init_lazy_func( &self, func_idx: FuncIdx, func: EngineFunc, bytes: &[u8], module: &ModuleHeader, func_to_validate: Option>, ) { self.code_map .init_func_as_uncompiled(func, func_idx, bytes, module, func_to_validate) } /// Recycles the given [`Stack`]. fn recycle_stack(&self, stack: Stack) { self.stacks.lock().recycle(stack) } } wasmi-1.1.0/src/engine/resumable.rs000064400000000000000000000505361046102023000153350ustar 00000000000000use super::Func; use crate::{ engine::Stack, func::{CallResultsTuple, FuncError}, ir::SlotSpan, AsContext, AsContextMut, Engine, Error, TrapCode, Val, WasmResults, }; use core::{fmt, marker::PhantomData, mem::replace, ops::Deref}; /// Returned by [`Engine`] methods for calling a function in a resumable way. /// /// # Note /// /// This is the base type for resumable call results and can be converted into /// either the dynamically typed [`ResumableCall`] or the statically typed /// [`TypedResumableCall`] that act as user facing API. Therefore this type /// must provide all the information necessary to be properly converted into /// either user facing types. #[derive(Debug)] pub(crate) enum ResumableCallBase { /// The resumable call has finished properly and returned a result. Finished(T), /// The resumable call encountered a host error and can be resumed. HostTrap(ResumableCallHostTrap), /// The resumable call ran out of fuel and can be resumed. OutOfFuel(ResumableCallOutOfFuel), } /// Any resumable error. #[derive(Debug)] pub enum ResumableError { HostTrap(ResumableHostTrapError), OutOfFuel(ResumableOutOfFuelError), } impl ResumableError { /// Consumes `self` to return the underlying [`Error`]. pub fn into_error(self) -> Error { match self { ResumableError::HostTrap(error) => error.into_error(), ResumableError::OutOfFuel(error) => error.into_error(), } } } /// Error returned from a called host function in a resumable state. #[derive(Debug)] pub struct ResumableHostTrapError { /// The error returned by the called host function. host_error: Error, /// The host function that returned the error. host_func: Func, /// The result registers of the caller of the host function. caller_results: SlotSpan, } impl core::error::Error for ResumableHostTrapError {} impl fmt::Display for ResumableHostTrapError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.host_error.fmt(f) } } impl ResumableHostTrapError { /// Creates a new [`ResumableHostTrapError`]. #[cold] pub(crate) fn new(host_error: Error, host_func: Func, caller_results: SlotSpan) -> Self { Self { host_error, host_func, caller_results, } } /// Consumes `self` to return the underlying [`Error`]. pub(crate) fn into_error(self) -> Error { self.host_error } /// Returns the [`Func`] of the [`ResumableHostTrapError`]. pub(crate) fn host_func(&self) -> &Func { &self.host_func } /// Returns the caller results [`SlotSpan`] of the [`ResumableHostTrapError`]. pub(crate) fn caller_results(&self) -> &SlotSpan { &self.caller_results } } /// Error returned from a called host function in a resumable state. #[derive(Debug)] pub struct ResumableOutOfFuelError { /// The minimum required amount of fuel to progress execution. required_fuel: u64, } impl core::error::Error for ResumableOutOfFuelError {} impl fmt::Display for ResumableOutOfFuelError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "ran out of fuel while calling a resumable function: required_fuel={}", self.required_fuel ) } } impl ResumableOutOfFuelError { /// Creates a new [`ResumableOutOfFuelError`]. #[cold] pub(crate) fn new(required_fuel: u64) -> Self { Self { required_fuel } } /// Consumes `self` to return the underlying [`Error`]. pub(crate) fn required_fuel(self) -> u64 { self.required_fuel } /// Consumes `self` to return the underlying [`Error`]. pub(crate) fn into_error(self) -> Error { Error::from(TrapCode::OutOfFuel) } } /// Returned by calling a [`Func`] in a resumable way. #[derive(Debug)] pub enum ResumableCall { /// The resumable call has finished properly and returned a result. Finished, /// The resumable call encountered a host error and can be resumed. HostTrap(ResumableCallHostTrap), /// The resumable call ran out of fuel but can be resumed. OutOfFuel(ResumableCallOutOfFuel), } impl ResumableCall { /// Creates a [`ResumableCall`] from the [`Engine`]'s base [`ResumableCallBase`]. pub(crate) fn new(call: ResumableCallBase<()>) -> Self { match call { ResumableCallBase::Finished(()) => Self::Finished, ResumableCallBase::HostTrap(invocation) => Self::HostTrap(invocation), ResumableCallBase::OutOfFuel(invocation) => Self::OutOfFuel(invocation), } } } /// Common state for resumable calls. #[derive(Debug)] pub struct ResumableCallCommon { /// The engine in use for the function invocation. /// /// # Note /// /// - This handle is required to resolve function types /// of both `func` and `host_func` fields as well as in /// the `Drop` impl to recycle the stack. engine: Engine, /// The underlying root function to be executed. /// /// # Note /// /// The results of this function must always match with the /// results given when resuming the call. func: Func, /// The value and call stack in use by the [`ResumableCallHostTrap`]. /// /// # Note /// /// - We need to keep the stack around since the user might want to /// resume the execution. /// - This stack is borrowed from the engine and needs to be given /// back to the engine when the [`ResumableCallHostTrap`] goes out /// of scope. stack: Stack, } impl ResumableCallCommon { /// Creates a new [`ResumableCallCommon`]. pub(super) fn new(engine: Engine, func: Func, stack: Stack) -> Self { Self { engine, func, stack, } } /// Replaces the internal stack with an empty one that has no heap allocations. pub(super) fn take_stack(&mut self) -> Stack { replace(&mut self.stack, Stack::empty()) } /// Returns an exclusive reference to the underlying [`Stack`]. pub(super) fn stack_mut(&mut self) -> &mut Stack { &mut self.stack } /// Prepares the `outputs` buffer for call resumption. /// /// # Errors /// /// If the number of items in `outputs` does not match the number of results of the resumed function. fn prepare_outputs( &self, ctx: impl AsContext, outputs: &mut [Val], ) -> Result<(), Error> { self.engine.resolve_func_type( self.func.ty_dedup(ctx.as_context()), |func_type| -> Result<(), Error> { func_type.prepare_outputs(outputs)?; Ok(()) }, ) } } impl Drop for ResumableCallCommon { fn drop(&mut self) { let stack = self.take_stack(); self.engine.recycle_stack(stack); } } // # Safety // // `ResumableCallCommon` is not `Sync` because of the following sequence of fields: // // - `ResumableCallCommon`'s `Stack` is not `Sync` // - `Stack`'s `CallStack` is not `Sync` // - `CallStack`'s `CallFrame` sequence is not `Sync` // - `CallFrame`'s `InstructionPtr` is not `Sync`: // Thi is because it is a raw pointer to an `Op` buffer owned by the [`Engine`]. // // Since `Engine` is owned by `ResumableCallCommon` it cannot be outlived. // Also the `Op` buffers that are pointed to by the `InstructionPtr` are immutable. // // Therefore `ResumableCallCommon` can safely be assumed to be `Sync`. unsafe impl Sync for ResumableCallCommon {} /// State required to resume a [`Func`] invocation after a host trap. #[derive(Debug)] pub struct ResumableCallHostTrap { /// Common state for resumable calls. pub(super) common: ResumableCallCommon, /// The host function that returned a host error. /// /// # Note /// /// - This is required to receive its result values that are /// needed to be fed back in manually by the user. This way we /// avoid heap memory allocations. /// - The results of this function must always match with the /// arguments given when resuming the call. host_func: Func, /// The host error that was returned by the `host_func` which /// caused the resumable function invocation to break. /// /// # Note /// /// This might be useful to users of this API to inspect the /// actual host error. This is therefore guaranteed to never /// be a Wasm trap. host_error: Error, /// The registers where to put provided host function results upon resumption. /// /// # Note /// /// This is only needed for the register-machine Wasmi engine backend. caller_results: SlotSpan, } impl ResumableCallHostTrap { /// Creates a new [`ResumableCallHostTrap`]. pub(super) fn new( engine: Engine, func: Func, host_func: Func, host_error: Error, caller_results: SlotSpan, stack: Stack, ) -> Self { Self { common: ResumableCallCommon::new(engine, func, stack), host_func, host_error, caller_results, } } /// Updates the [`ResumableCallHostTrap`] with the new `host_func`, `host_error` and `caller_results`. /// /// # Note /// /// This should only be called from the register-machine Wasmi engine backend. pub(super) fn update(&mut self, host_func: Func, host_error: Error, caller_results: SlotSpan) { self.host_func = host_func; self.host_error = host_error; self.caller_results = caller_results; } /// Updates the [`ResumableCallHostTrap`] to a [`ResumableCallOutOfFuel`]. pub(super) fn update_to_out_of_fuel(self, required_fuel: u64) -> ResumableCallOutOfFuel { ResumableCallOutOfFuel { common: self.common, required_fuel, } } /// Returns the host [`Func`] that returned the host error. /// /// # Note /// /// When using [`ResumableCallHostTrap::resume`] the `inputs` /// need to match the results of this host function so that /// the function invocation can properly resume. For that /// number and types of the values provided must match. pub fn host_func(&self) -> Func { self.host_func } /// Returns a shared reference to the encountered host error. /// /// # Note /// /// This is guaranteed to never be a Wasm trap. pub fn host_error(&self) -> &Error { &self.host_error } /// Consumes `self` and returns the encountered host error. /// /// # Note /// /// This is guaranteed to never be a Wasm trap. pub fn into_host_error(self) -> Error { self.host_error } /// Returns the caller results [`SlotSpan`]. /// /// # Note /// /// This is `Some` only for [`ResumableCallHostTrap`] originating from the register-machine Wasmi engine. pub(crate) fn caller_results(&self) -> SlotSpan { self.caller_results } /// Validates that the `inputs` types are valid for the host function resumption. /// /// # Errors /// /// If the `inputs` types do not match. fn validate_inputs( &self, ctx: impl AsContext, inputs: &[Val], ) -> Result<(), FuncError> { self.common .engine .resolve_func_type(self.host_func().ty_dedup(ctx.as_context()), |func_type| { func_type.match_results(inputs) }) } /// Resumes the call to the [`Func`] with the given inputs. /// /// The result is written back into the `outputs` buffer upon success. /// /// Returns a resumable handle to the function invocation upon /// encountering host errors with which it is possible to handle /// the error and continue the execution as if no error occurred. /// /// # Errors /// /// - If the function resumption returned a Wasm [`Error`]. /// - If the types or the number of values in `inputs` does not match /// the types and number of result values of the erroneous host function. /// - If the number of output values does not match the expected number of /// outputs required by the called function. pub fn resume( self, mut ctx: impl AsContextMut, inputs: &[Val], outputs: &mut [Val], ) -> Result { self.validate_inputs(ctx.as_context(), inputs)?; self.common.prepare_outputs(ctx.as_context(), outputs)?; self.common .engine .clone() .resume_func_host_trap(ctx.as_context_mut(), self, inputs, outputs) .map(ResumableCall::new) } } /// State required to resume a [`Func`] invocation after a host trap. #[derive(Debug)] pub struct ResumableCallOutOfFuel { /// Common state for resumable calls. pub(super) common: ResumableCallCommon, /// The minimum fuel required to progress execution. required_fuel: u64, } impl ResumableCallOutOfFuel { /// Creates a new [`ResumableCallOutOfFuel`]. pub(super) fn new(engine: Engine, func: Func, stack: Stack, required_fuel: u64) -> Self { Self { common: ResumableCallCommon::new(engine, func, stack), required_fuel, } } /// Updates the [`ResumableCallOutOfFuel`] with the new `required_fuel`. /// /// # Note /// /// This should only be called from the register-machine Wasmi engine backend. pub(super) fn update(&mut self, required_fuel: u64) { self.required_fuel = required_fuel; } /// Updates the [`ResumableCallHostTrap`] to a [`ResumableCallOutOfFuel`]. pub(super) fn update_to_host_trap( self, host_func: Func, host_error: Error, caller_results: SlotSpan, ) -> ResumableCallHostTrap { ResumableCallHostTrap { common: self.common, host_func, host_error, caller_results, } } /// Returns the minimum required fuel to progress execution. pub fn required_fuel(&self) -> u64 { self.required_fuel } /// Resumes the call to the [`Func`] with the given inputs. /// /// The result is written back into the `outputs` buffer upon success. /// Returns a resumable handle to the function invocation. /// /// # Errors /// /// - If the function resumption returned a Wasm [`Error`]. /// - If the types or the number of values in `inputs` does not match /// the types and number of result values of the erroneous host function. /// - If the number of output values does not match the expected number of /// outputs required by the called function. pub fn resume( self, mut ctx: impl AsContextMut, outputs: &mut [Val], ) -> Result { self.common.prepare_outputs(ctx.as_context(), outputs)?; self.common .engine .clone() .resume_func_out_of_fuel(ctx.as_context_mut(), self, outputs) .map(ResumableCall::new) } } /// Returned by calling a [`TypedFunc`] in a resumable way. /// /// [`TypedFunc`]: [`crate::TypedFunc`] #[derive(Debug)] pub enum TypedResumableCall { /// The resumable call has finished properly and returned a result. Finished(T), /// The resumable call encountered a host error and can be resumed. HostTrap(TypedResumableCallHostTrap), /// The resumable call ran out of fuel and can be resumed. OutOfFuel(TypedResumableCallOutOfFuel), } impl TypedResumableCall { /// Creates a [`TypedResumableCall`] from the [`Engine`]'s base [`ResumableCallBase`]. pub(crate) fn new(call: ResumableCallBase) -> Self { match call { ResumableCallBase::Finished(results) => Self::Finished(results), ResumableCallBase::HostTrap(invocation) => { Self::HostTrap(TypedResumableCallHostTrap::new(invocation)) } ResumableCallBase::OutOfFuel(invocation) => { Self::OutOfFuel(TypedResumableCallOutOfFuel::new(invocation)) } } } } /// State required to resume a [`TypedFunc`] invocation. /// /// [`TypedFunc`]: [`crate::TypedFunc`] pub struct TypedResumableCallHostTrap { invocation: ResumableCallHostTrap, /// The parameter and result typed encoded in Rust type system. results: PhantomData Results>, } impl TypedResumableCallHostTrap { /// Creates a [`TypedResumableCallHostTrap`] wrapper for the given [`ResumableCallHostTrap`]. pub(crate) fn new(invocation: ResumableCallHostTrap) -> Self { Self { invocation, results: PhantomData, } } /// Resumes the call to the [`TypedFunc`] with the given inputs. /// /// Returns a resumable handle to the function invocation upon /// encountering host errors with which it is possible to handle /// the error and continue the execution as if no error occurred. /// /// # Errors /// /// - If the function resumption returned a Wasm [`Error`]. /// - If the types or the number of values in `inputs` does not match /// the types and number of result values of the erroneous host function. /// /// [`TypedFunc`]: [`crate::TypedFunc`] pub fn resume( self, mut ctx: impl AsContextMut, inputs: &[Val], ) -> Result, Error> where Results: WasmResults, { self.invocation.validate_inputs(ctx.as_context(), inputs)?; self.common .engine .clone() .resume_func_host_trap( ctx.as_context_mut(), self.invocation, inputs, >::default(), ) .map(TypedResumableCall::new) } } impl Deref for TypedResumableCallHostTrap { type Target = ResumableCallHostTrap; fn deref(&self) -> &Self::Target { &self.invocation } } impl fmt::Debug for TypedResumableCallHostTrap { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("TypedResumableCallHostTrap") .field("invocation", &self.invocation) .field("results", &self.results) .finish() } } /// State required to resume a [`TypedFunc`] invocation after running out of fuel. /// /// [`TypedFunc`]: [`crate::TypedFunc`] pub struct TypedResumableCallOutOfFuel { invocation: ResumableCallOutOfFuel, /// The parameter and result typed encoded in Rust type system. results: PhantomData Results>, } impl TypedResumableCallOutOfFuel { /// Creates a [`TypedResumableCallHostTrap`] wrapper for the given [`ResumableCallOutOfFuel`]. pub(crate) fn new(invocation: ResumableCallOutOfFuel) -> Self { Self { invocation, results: PhantomData, } } /// Resumes the call to the [`TypedFunc`] with the given inputs. /// /// Returns a resumable handle to the function invocation upon /// encountering host errors with which it is possible to handle /// the error and continue the execution as if no error occurred. /// /// # Errors /// /// - If the function resumption returned a Wasm [`Error`]. /// - If the types or the number of values in `inputs` does not match /// the types and number of result values of the erroneous host function. /// /// [`TypedFunc`]: [`crate::TypedFunc`] pub fn resume( self, mut ctx: impl AsContextMut, ) -> Result, Error> where Results: WasmResults, { self.common .engine .clone() .resume_func_out_of_fuel( ctx.as_context_mut(), self.invocation, >::default(), ) .map(TypedResumableCall::new) } } impl Deref for TypedResumableCallOutOfFuel { type Target = ResumableCallOutOfFuel; fn deref(&self) -> &Self::Target { &self.invocation } } impl fmt::Debug for TypedResumableCallOutOfFuel { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("TypedResumableCallOutOfFuel") .field("invocation", &self.invocation) .field("results", &self.results) .finish() } } wasmi-1.1.0/src/engine/traits.rs000064400000000000000000000047331046102023000146620ustar 00000000000000use crate::{core::UntypedVal, value::WithType, Val}; use core::{iter, slice}; /// Types implementing this trait may be used as parameters for function execution. /// /// # Note /// /// - This is generically implemented by `&[Value]` and tuples of `T: WasmType` types. /// - Using this trait allows to customize the parameters entrypoint for efficient /// function execution via the [`Engine`]. /// /// [`Engine`]: [`crate::Engine`] pub trait CallParams { /// The iterator over the parameter values. type Params: ExactSizeIterator; /// Feeds the parameter values from the caller. fn call_params(self) -> Self::Params; } impl<'a> CallParams for &'a [Val] { type Params = CallParamsValueIter<'a>; #[inline] fn call_params(self) -> Self::Params { CallParamsValueIter { iter: self.iter().cloned(), } } } /// An iterator over the [`UntypedVal`] call parameters. #[derive(Debug)] pub struct CallParamsValueIter<'a> { iter: iter::Cloned>, } impl Iterator for CallParamsValueIter<'_> { type Item = UntypedVal; #[inline] fn next(&mut self) -> Option { self.iter.next().map(UntypedVal::from) } #[inline] fn size_hint(&self) -> (usize, Option) { self.iter.size_hint() } } impl ExactSizeIterator for CallParamsValueIter<'_> {} /// Types implementing this trait may be used as results for function execution. /// /// # Note /// /// - This is generically implemented by `&mut [Value]` and indirectly for /// tuples of `T: WasmType`. /// - Using this trait allows to customize the parameters entrypoint for efficient /// function execution via the [`Engine`]. /// /// [`Engine`]: [`crate::Engine`] pub trait CallResults { /// The type of the returned results value. type Results; /// Returns the number of expected results. fn len_results(&self) -> usize; /// Feeds the result values back to the caller. /// /// # Panics /// /// If the given `results` do not match the expected amount. fn call_results(self, results: &[UntypedVal]) -> Self::Results; } impl CallResults for &mut [Val] { type Results = (); fn len_results(&self) -> usize { self.len() } fn call_results(self, results: &[UntypedVal]) -> Self::Results { assert_eq!(self.len(), results.len()); self.iter_mut().zip(results).for_each(|(dst, src)| { *dst = src.with_type(dst.ty()); }) } } wasmi-1.1.0/src/engine/translator/comparator.rs000064400000000000000000001321451046102023000177130ustar 00000000000000use crate::{ core::UntypedVal, ir::{BranchOffset, BranchOffset16, Comparator, ComparatorAndOffset, Op, Slot}, Error, }; /// Types able to allocate function local constant values. /// /// # Note /// /// This allows to cheaply convert immediate values to [`Slot`]s. /// /// # Errors /// /// If the function local constant allocation from immediate value to [`Slot`] failed. pub trait AllocConst { /// Allocates a new function local constant value and returns its [`Slot`]. /// /// # Note /// /// Constant values allocated this way are deduplicated and return shared [`Slot`]. fn alloc_const>(&mut self, value: T) -> Result; } /// Extension trait to return [`Slot`] result of compare [`Op`]s. pub trait CompareResult { /// Returns the result [`Slot`] of the compare [`Op`]. /// /// Returns `None` if the [`Op`] is not a compare instruction. fn compare_result(&self) -> Option; /// Returns `true` if `self` is a compare [`Op`]. fn is_compare_instr(&self) -> bool { self.compare_result().is_some() } } impl CompareResult for Op { fn compare_result(&self) -> Option { let result = match *self { | Op::I32BitAnd { result, .. } | Op::I32BitAndImm16 { result, .. } | Op::I32BitOr { result, .. } | Op::I32BitOrImm16 { result, .. } | Op::I32BitXor { result, .. } | Op::I32BitXorImm16 { result, .. } | Op::I32And { result, .. } | Op::I32AndImm16 { result, .. } | Op::I32Or { result, .. } | Op::I32OrImm16 { result, .. } | Op::I32Nand { result, .. } | Op::I32NandImm16 { result, .. } | Op::I32Nor { result, .. } | Op::I32NorImm16 { result, .. } | Op::I32Eq { result, .. } | Op::I32EqImm16 { result, .. } | Op::I32Ne { result, .. } | Op::I32NeImm16 { result, .. } | Op::I32LtS { result, .. } | Op::I32LtSImm16Lhs { result, .. } | Op::I32LtSImm16Rhs { result, .. } | Op::I32LtU { result, .. } | Op::I32LtUImm16Lhs { result, .. } | Op::I32LtUImm16Rhs { result, .. } | Op::I32LeS { result, .. } | Op::I32LeSImm16Lhs { result, .. } | Op::I32LeSImm16Rhs { result, .. } | Op::I32LeU { result, .. } | Op::I32LeUImm16Lhs { result, .. } | Op::I32LeUImm16Rhs { result, .. } | Op::I64BitAnd { result, .. } | Op::I64BitAndImm16 { result, .. } | Op::I64BitOr { result, .. } | Op::I64BitOrImm16 { result, .. } | Op::I64BitXor { result, .. } | Op::I64BitXorImm16 { result, .. } | Op::I64And { result, .. } | Op::I64AndImm16 { result, .. } | Op::I64Or { result, .. } | Op::I64OrImm16 { result, .. } | Op::I64Nand { result, .. } | Op::I64NandImm16 { result, .. } | Op::I64Nor { result, .. } | Op::I64NorImm16 { result, .. } | Op::I64Eq { result, .. } | Op::I64EqImm16 { result, .. } | Op::I64Ne { result, .. } | Op::I64NeImm16 { result, .. } | Op::I64LtS { result, .. } | Op::I64LtSImm16Lhs { result, .. } | Op::I64LtSImm16Rhs { result, .. } | Op::I64LtU { result, .. } | Op::I64LtUImm16Lhs { result, .. } | Op::I64LtUImm16Rhs { result, .. } | Op::I64LeS { result, .. } | Op::I64LeSImm16Lhs { result, .. } | Op::I64LeSImm16Rhs { result, .. } | Op::I64LeU { result, .. } | Op::I64LeUImm16Lhs { result, .. } | Op::I64LeUImm16Rhs { result, .. } | Op::F32Eq { result, .. } | Op::F32Ne { result, .. } | Op::F32Lt { result, .. } | Op::F32Le { result, .. } | Op::F32NotLt { result, .. } | Op::F32NotLe { result, .. } | Op::F64Eq { result, .. } | Op::F64Ne { result, .. } | Op::F64Lt { result, .. } | Op::F64Le { result, .. } | Op::F64NotLt { result, .. } | Op::F64NotLe { result, .. } => result, _ => return None, }; Some(result) } } pub trait ReplaceCmpResult: Sized { /// Returns `self` `cmp` instruction with the `new_result`. /// /// Returns `None` if `self` is not a `cmp` instruction. fn replace_cmp_result(&self, new_result: Slot) -> Option; } impl ReplaceCmpResult for Op { fn replace_cmp_result(&self, new_result: Slot) -> Option { let mut copy = *self; match &mut copy { | Op::I32BitAnd { result, .. } | Op::I32BitAndImm16 { result, .. } | Op::I32BitOr { result, .. } | Op::I32BitOrImm16 { result, .. } | Op::I32BitXor { result, .. } | Op::I32BitXorImm16 { result, .. } | Op::I32And { result, .. } | Op::I32AndImm16 { result, .. } | Op::I32Or { result, .. } | Op::I32OrImm16 { result, .. } | Op::I32Nand { result, .. } | Op::I32NandImm16 { result, .. } | Op::I32Nor { result, .. } | Op::I32NorImm16 { result, .. } | Op::I32Eq { result, .. } | Op::I32EqImm16 { result, .. } | Op::I32Ne { result, .. } | Op::I32NeImm16 { result, .. } | Op::I32LtS { result, .. } | Op::I32LtSImm16Lhs { result, .. } | Op::I32LtSImm16Rhs { result, .. } | Op::I32LtU { result, .. } | Op::I32LtUImm16Lhs { result, .. } | Op::I32LtUImm16Rhs { result, .. } | Op::I32LeS { result, .. } | Op::I32LeSImm16Lhs { result, .. } | Op::I32LeSImm16Rhs { result, .. } | Op::I32LeU { result, .. } | Op::I32LeUImm16Lhs { result, .. } | Op::I32LeUImm16Rhs { result, .. } | Op::I64BitAnd { result, .. } | Op::I64BitAndImm16 { result, .. } | Op::I64BitOr { result, .. } | Op::I64BitOrImm16 { result, .. } | Op::I64BitXor { result, .. } | Op::I64BitXorImm16 { result, .. } | Op::I64And { result, .. } | Op::I64AndImm16 { result, .. } | Op::I64Or { result, .. } | Op::I64OrImm16 { result, .. } | Op::I64Nand { result, .. } | Op::I64NandImm16 { result, .. } | Op::I64Nor { result, .. } | Op::I64NorImm16 { result, .. } | Op::I64Eq { result, .. } | Op::I64EqImm16 { result, .. } | Op::I64Ne { result, .. } | Op::I64NeImm16 { result, .. } | Op::I64LtS { result, .. } | Op::I64LtSImm16Lhs { result, .. } | Op::I64LtSImm16Rhs { result, .. } | Op::I64LtU { result, .. } | Op::I64LtUImm16Lhs { result, .. } | Op::I64LtUImm16Rhs { result, .. } | Op::I64LeS { result, .. } | Op::I64LeSImm16Lhs { result, .. } | Op::I64LeSImm16Rhs { result, .. } | Op::I64LeU { result, .. } | Op::I64LeUImm16Lhs { result, .. } | Op::I64LeUImm16Rhs { result, .. } | Op::F32Eq { result, .. } | Op::F32Ne { result, .. } | Op::F32Lt { result, .. } | Op::F32Le { result, .. } | Op::F32NotLt { result, .. } | Op::F32NotLe { result, .. } | Op::F64Eq { result, .. } | Op::F64Ne { result, .. } | Op::F64Lt { result, .. } | Op::F64Le { result, .. } | Op::F64NotLt { result, .. } | Op::F64NotLe { result, .. } => *result = new_result, _ => return None, }; Some(copy) } } pub trait NegateCmpInstr: Sized { /// Negates the compare (`cmp`) [`Op`]. fn negate_cmp_instr(&self) -> Option; } impl NegateCmpInstr for Op { fn negate_cmp_instr(&self) -> Option { #[rustfmt::skip] let negated = match *self { // i32 Op::I32Eq { result, lhs, rhs } => Op::i32_ne(result, lhs, rhs), Op::I32Ne { result, lhs, rhs } => Op::i32_eq(result, lhs, rhs), Op::I32LeS { result, lhs, rhs } => Op::i32_lt_s(result, rhs, lhs), Op::I32LeU { result, lhs, rhs } => Op::i32_lt_u(result, rhs, lhs), Op::I32LtS { result, lhs, rhs } => Op::i32_le_s(result, rhs, lhs), Op::I32LtU { result, lhs, rhs } => Op::i32_le_u(result, rhs, lhs), Op::I32EqImm16 { result, lhs, rhs } => Op::i32_ne_imm16(result, lhs, rhs), Op::I32NeImm16 { result, lhs, rhs } => Op::i32_eq_imm16(result, lhs, rhs), Op::I32LeSImm16Rhs { result, lhs, rhs } => Op::i32_lt_s_imm16_lhs(result, rhs, lhs), Op::I32LeUImm16Rhs { result, lhs, rhs } => Op::i32_lt_u_imm16_lhs(result, rhs, lhs), Op::I32LtSImm16Rhs { result, lhs, rhs } => Op::i32_le_s_imm16_lhs(result, rhs, lhs), Op::I32LtUImm16Rhs { result, lhs, rhs } => Op::i32_le_u_imm16_lhs(result, rhs, lhs), Op::I32LeSImm16Lhs { result, lhs, rhs } => Op::i32_lt_s_imm16_rhs(result, rhs, lhs), Op::I32LeUImm16Lhs { result, lhs, rhs } => Op::i32_lt_u_imm16_rhs(result, rhs, lhs), Op::I32LtSImm16Lhs { result, lhs, rhs } => Op::i32_le_s_imm16_rhs(result, rhs, lhs), Op::I32LtUImm16Lhs { result, lhs, rhs } => Op::i32_le_u_imm16_rhs(result, rhs, lhs), // i32 (and, or, xor) Op::I32BitAnd { result, lhs, rhs } => Op::i32_nand(result, lhs, rhs), Op::I32BitOr { result, lhs, rhs } => Op::i32_nor(result, lhs, rhs), Op::I32BitXor { result, lhs, rhs } => Op::i32_eq(result, lhs, rhs), Op::I32BitAndImm16 { result, lhs, rhs } => Op::i32_nand_imm16(result, lhs, rhs), Op::I32BitOrImm16 { result, lhs, rhs } => Op::i32_nor_imm16(result, lhs, rhs), Op::I32BitXorImm16 { result, lhs, rhs } => Op::i32_eq_imm16(result, lhs, rhs), Op::I32And { result, lhs, rhs } => Op::i32_nand(result, lhs, rhs), Op::I32Or { result, lhs, rhs } => Op::i32_nor(result, lhs, rhs), Op::I32AndImm16 { result, lhs, rhs } => Op::i32_nand_imm16(result, lhs, rhs), Op::I32OrImm16 { result, lhs, rhs } => Op::i32_nor_imm16(result, lhs, rhs), Op::I32Nand { result, lhs, rhs } => Op::i32_and(result, lhs, rhs), Op::I32Nor { result, lhs, rhs } => Op::i32_or(result, lhs, rhs), Op::I32NandImm16 { result, lhs, rhs } => Op::i32_and_imm16(result, lhs, rhs), Op::I32NorImm16 { result, lhs, rhs } => Op::i32_or_imm16(result, lhs, rhs), // i64 Op::I64Eq { result, lhs, rhs } => Op::i64_ne(result, lhs, rhs), Op::I64Ne { result, lhs, rhs } => Op::i64_eq(result, lhs, rhs), Op::I64LeS { result, lhs, rhs } => Op::i64_lt_s(result, rhs, lhs), Op::I64LeU { result, lhs, rhs } => Op::i64_lt_u(result, rhs, lhs), Op::I64LtS { result, lhs, rhs } => Op::i64_le_s(result, rhs, lhs), Op::I64LtU { result, lhs, rhs } => Op::i64_le_u(result, rhs, lhs), Op::I64EqImm16 { result, lhs, rhs } => Op::i64_ne_imm16(result, lhs, rhs), Op::I64NeImm16 { result, lhs, rhs } => Op::i64_eq_imm16(result, lhs, rhs), Op::I64LeSImm16Rhs { result, lhs, rhs } => Op::i64_lt_s_imm16_lhs(result, rhs, lhs), Op::I64LeUImm16Rhs { result, lhs, rhs } => Op::i64_lt_u_imm16_lhs(result, rhs, lhs), Op::I64LtSImm16Rhs { result, lhs, rhs } => Op::i64_le_s_imm16_lhs(result, rhs, lhs), Op::I64LtUImm16Rhs { result, lhs, rhs } => Op::i64_le_u_imm16_lhs(result, rhs, lhs), Op::I64LeSImm16Lhs { result, lhs, rhs } => Op::i64_lt_s_imm16_rhs(result, rhs, lhs), Op::I64LeUImm16Lhs { result, lhs, rhs } => Op::i64_lt_u_imm16_rhs(result, rhs, lhs), Op::I64LtSImm16Lhs { result, lhs, rhs } => Op::i64_le_s_imm16_rhs(result, rhs, lhs), Op::I64LtUImm16Lhs { result, lhs, rhs } => Op::i64_le_u_imm16_rhs(result, rhs, lhs), // i64 (and, or, xor) Op::I64BitAnd { result, lhs, rhs } => Op::i64_nand(result, lhs, rhs), Op::I64BitOr { result, lhs, rhs } => Op::i64_nor(result, lhs, rhs), Op::I64BitXor { result, lhs, rhs } => Op::i64_eq(result, lhs, rhs), Op::I64BitAndImm16 { result, lhs, rhs } => Op::i64_nand_imm16(result, lhs, rhs), Op::I64BitOrImm16 { result, lhs, rhs } => Op::i64_nor_imm16(result, lhs, rhs), Op::I64BitXorImm16 { result, lhs, rhs } => Op::i64_eq_imm16(result, lhs, rhs), Op::I64And { result, lhs, rhs } => Op::i64_nand(result, lhs, rhs), Op::I64Or { result, lhs, rhs } => Op::i64_nor(result, lhs, rhs), Op::I64AndImm16 { result, lhs, rhs } => Op::i64_nand_imm16(result, lhs, rhs), Op::I64OrImm16 { result, lhs, rhs } => Op::i64_nor_imm16(result, lhs, rhs), Op::I64Nand { result, lhs, rhs } => Op::i64_and(result, lhs, rhs), Op::I64Nor { result, lhs, rhs } => Op::i64_or(result, lhs, rhs), Op::I64NandImm16 { result, lhs, rhs } => Op::i64_and_imm16(result, lhs, rhs), Op::I64NorImm16 { result, lhs, rhs } => Op::i64_or_imm16(result, lhs, rhs), // f32 Op::F32Eq { result, lhs, rhs } => Op::f32_ne(result, lhs, rhs), Op::F32Ne { result, lhs, rhs } => Op::f32_eq(result, lhs, rhs), Op::F32Le { result, lhs, rhs } => Op::f32_not_le(result, lhs, rhs), Op::F32Lt { result, lhs, rhs } => Op::f32_not_lt(result, lhs, rhs), Op::F32NotLe { result, lhs, rhs } => Op::f32_le(result, lhs, rhs), Op::F32NotLt { result, lhs, rhs } => Op::f32_lt(result, lhs, rhs), // f64 Op::F64Eq { result, lhs, rhs } => Op::f64_ne(result, lhs, rhs), Op::F64Ne { result, lhs, rhs } => Op::f64_eq(result, lhs, rhs), Op::F64Le { result, lhs, rhs } => Op::f64_not_le(result, lhs, rhs), Op::F64Lt { result, lhs, rhs } => Op::f64_not_lt(result, lhs, rhs), Op::F64NotLe { result, lhs, rhs } => Op::f64_le(result, lhs, rhs), Op::F64NotLt { result, lhs, rhs } => Op::f64_lt(result, lhs, rhs), _ => return None, }; Some(negated) } } pub trait LogicalizeCmpInstr: Sized { /// Logicalizes the compare (`cmp`) [`Op`]. /// /// This mainly turns bitwise [`Op`]s into logical ones. /// Logical instructions are simply unchanged. fn logicalize_cmp_instr(&self) -> Option; } impl LogicalizeCmpInstr for Op { fn logicalize_cmp_instr(&self) -> Option { #[rustfmt::skip] let logicalized = match *self { // Bitwise -> Logical: i32 Op::I32BitAnd { result, lhs, rhs } => Op::i32_and(result, lhs, rhs), Op::I32BitOr { result, lhs, rhs } => Op::i32_or(result, lhs, rhs), Op::I32BitXor { result, lhs, rhs } => Op::i32_ne(result, lhs, rhs), Op::I32BitAndImm16 { result, lhs, rhs } => Op::i32_and_imm16(result, lhs, rhs), Op::I32BitOrImm16 { result, lhs, rhs } => Op::i32_or_imm16(result, lhs, rhs), Op::I32BitXorImm16 { result, lhs, rhs } => Op::i32_ne_imm16(result, lhs, rhs), // Bitwise -> Logical: i64 Op::I64BitAnd { result, lhs, rhs } => Op::i64_and(result, lhs, rhs), Op::I64BitOr { result, lhs, rhs } => Op::i64_or(result, lhs, rhs), Op::I64BitXor { result, lhs, rhs } => Op::i64_ne(result, lhs, rhs), Op::I64BitAndImm16 { result, lhs, rhs } => Op::i64_and_imm16(result, lhs, rhs), Op::I64BitOrImm16 { result, lhs, rhs } => Op::i64_or_imm16(result, lhs, rhs), Op::I64BitXorImm16 { result, lhs, rhs } => Op::i64_ne_imm16(result, lhs, rhs), // Logical -> Logical Op::I32Eq { .. } | Op::I32Ne { .. } | Op::I32LeS { .. } | Op::I32LeU { .. } | Op::I32LtS { .. } | Op::I32LtU { .. } | Op::I32EqImm16 { .. } | Op::I32NeImm16 { .. } | Op::I32LeSImm16Rhs { .. } | Op::I32LeUImm16Rhs { .. } | Op::I32LtSImm16Rhs { .. } | Op::I32LtUImm16Rhs { .. } | Op::I32LeSImm16Lhs { .. } | Op::I32LeUImm16Lhs { .. } | Op::I32LtSImm16Lhs { .. } | Op::I32LtUImm16Lhs { .. } | Op::I32And { .. } | Op::I32Or { .. } | Op::I32AndImm16 { .. } | Op::I32OrImm16 { .. } | Op::I32Nand { .. } | Op::I32Nor { .. } | Op::I32NandImm16 { .. } | Op::I32NorImm16 { .. } | Op::I64Eq { .. } | Op::I64Ne { .. } | Op::I64LeS { .. } | Op::I64LeU { .. } | Op::I64LtS { .. } | Op::I64LtU { .. } | Op::I64EqImm16 { .. } | Op::I64NeImm16 { .. } | Op::I64LeSImm16Rhs { .. } | Op::I64LeUImm16Rhs { .. } | Op::I64LtSImm16Rhs { .. } | Op::I64LtUImm16Rhs { .. } | Op::I64LeSImm16Lhs { .. } | Op::I64LeUImm16Lhs { .. } | Op::I64LtSImm16Lhs { .. } | Op::I64LtUImm16Lhs { .. } | Op::I64And { .. } | Op::I64Or { .. } | Op::I64AndImm16 { .. } | Op::I64OrImm16 { .. } | Op::I64Nand { .. } | Op::I64Nor { .. } | Op::I64NandImm16 { .. } | Op::I64NorImm16 { .. } | Op::F32Eq { .. } | Op::F32Ne { .. } | Op::F32Lt { .. } | Op::F32Le { .. } | Op::F32NotLt { .. } | Op::F32NotLe { .. } | Op::F64Eq { .. } | Op::F64Ne { .. } | Op::F64Lt { .. } | Op::F64Le { .. } | Op::F64NotLt { .. } | Op::F64NotLe { .. } => *self, _ => return None, }; Some(logicalized) } } pub trait TryIntoCmpSelectInstr: Sized { fn try_into_cmp_select_instr( &self, get_result: impl FnOnce() -> Result, ) -> Result; } /// The outcome of `cmp`+`select` op-code fusion. pub enum CmpSelectFusion { /// The `cmp`+`select` fusion was applied and may require swapping operands. Applied { fused: Op, swap_operands: bool }, /// The `cmp`+`select` fusion was _not_ applied. Unapplied, } /// Returns `true` if a `cmp`+`select` fused instruction required to swap its operands. #[rustfmt::skip] fn cmp_select_swap_operands(instr: &Op) -> bool { matches!(instr, | Op::I32Ne { .. } | Op::I32NeImm16 { .. } | Op::I32LeSImm16Lhs { .. } | Op::I32LeUImm16Lhs { .. } | Op::I32LtSImm16Lhs { .. } | Op::I32LtUImm16Lhs { .. } | Op::I32BitXor { .. } | Op::I32BitXorImm16 { .. } | Op::I64BitXor { .. } | Op::I64BitXorImm16 { .. } | Op::I32Nand { .. } | Op::I32Nor { .. } | Op::I32NandImm16 { .. } | Op::I32NorImm16 { .. } | Op::I64Ne { .. } | Op::I64NeImm16 { .. } | Op::I64LeSImm16Lhs { .. } | Op::I64LeUImm16Lhs { .. } | Op::I64LtSImm16Lhs { .. } | Op::I64LtUImm16Lhs { .. } | Op::I64Nand { .. } | Op::I64Nor { .. } | Op::I64NandImm16 { .. } | Op::I64NorImm16 { .. } | Op::F32Ne { .. } | Op::F64Ne { .. } | Op::F32NotLt { .. } | Op::F32NotLe { .. } | Op::F64NotLt { .. } | Op::F64NotLe { .. } ) } impl TryIntoCmpSelectInstr for Op { fn try_into_cmp_select_instr( &self, get_result: impl FnOnce() -> Result, ) -> Result { if !self.is_compare_instr() { return Ok(CmpSelectFusion::Unapplied); } let swap_operands = cmp_select_swap_operands(self); let result = get_result()?; #[rustfmt::skip] let fused = match *self { // i32 Op::I32Eq { lhs, rhs, .. } => Op::select_i32_eq(result, lhs, rhs), Op::I32Ne { lhs, rhs, .. } => Op::select_i32_eq(result, lhs, rhs), Op::I32LeS { lhs, rhs, .. } => Op::select_i32_le_s(result, lhs, rhs), Op::I32LeU { lhs, rhs, .. } => Op::select_i32_le_u(result, lhs, rhs), Op::I32LtS { lhs, rhs, .. } => Op::select_i32_lt_s(result, lhs, rhs), Op::I32LtU { lhs, rhs, .. } => Op::select_i32_lt_u(result, lhs, rhs), Op::I32EqImm16 { lhs, rhs, .. } => Op::select_i32_eq_imm16(result, lhs, rhs), Op::I32NeImm16 { lhs, rhs, .. } => Op::select_i32_eq_imm16(result, lhs, rhs), Op::I32LeSImm16Lhs { lhs, rhs, .. } => Op::select_i32_lt_s_imm16_rhs(result, rhs, lhs), Op::I32LeUImm16Lhs { lhs, rhs, .. } => Op::select_i32_lt_u_imm16_rhs(result, rhs, lhs), Op::I32LtSImm16Lhs { lhs, rhs, .. } => Op::select_i32_le_s_imm16_rhs(result, rhs, lhs), Op::I32LtUImm16Lhs { lhs, rhs, .. } => Op::select_i32_le_u_imm16_rhs(result, rhs, lhs), Op::I32LeSImm16Rhs { lhs, rhs, .. } => Op::select_i32_le_s_imm16_rhs(result, lhs, rhs), Op::I32LeUImm16Rhs { lhs, rhs, .. } => Op::select_i32_le_u_imm16_rhs(result, lhs, rhs), Op::I32LtSImm16Rhs { lhs, rhs, .. } => Op::select_i32_lt_s_imm16_rhs(result, lhs, rhs), Op::I32LtUImm16Rhs { lhs, rhs, .. } => Op::select_i32_lt_u_imm16_rhs(result, lhs, rhs), // i32 (and, or, xor) Op::I32BitAnd { lhs, rhs, .. } => Op::select_i32_and(result, lhs, rhs), Op::I32BitOr { lhs, rhs, .. } => Op::select_i32_or(result, lhs, rhs), Op::I32BitXor { lhs, rhs, .. } => Op::select_i32_eq(result, lhs, rhs), Op::I32And { lhs, rhs, .. } => Op::select_i32_and(result, lhs, rhs), Op::I32Or { lhs, rhs, .. } => Op::select_i32_or(result, lhs, rhs), Op::I32Nand { lhs, rhs, .. } => Op::select_i32_and(result, lhs, rhs), Op::I32Nor { lhs, rhs, .. } => Op::select_i32_or(result, lhs, rhs), Op::I32BitAndImm16 { lhs, rhs, .. } => Op::select_i32_and_imm16(result, lhs, rhs), Op::I32BitOrImm16 { lhs, rhs, .. } => Op::select_i32_or_imm16(result, lhs, rhs), Op::I32BitXorImm16 { lhs, rhs, .. } => Op::select_i32_eq_imm16(result, lhs, rhs), Op::I32AndImm16 { lhs, rhs, .. } => Op::select_i32_and_imm16(result, lhs, rhs), Op::I32OrImm16 { lhs, rhs, .. } => Op::select_i32_or_imm16(result, lhs, rhs), Op::I32NandImm16 { lhs, rhs, .. } => Op::select_i32_and_imm16(result, lhs, rhs), Op::I32NorImm16 { lhs, rhs, .. } => Op::select_i32_or_imm16(result, lhs, rhs), // i64 Op::I64Eq { lhs, rhs, .. } => Op::select_i64_eq(result, lhs, rhs), Op::I64Ne { lhs, rhs, .. } => Op::select_i64_eq(result, lhs, rhs), Op::I64LeS { lhs, rhs, .. } => Op::select_i64_le_s(result, lhs, rhs), Op::I64LeU { lhs, rhs, .. } => Op::select_i64_le_u(result, lhs, rhs), Op::I64LtS { lhs, rhs, .. } => Op::select_i64_lt_s(result, lhs, rhs), Op::I64LtU { lhs, rhs, .. } => Op::select_i64_lt_u(result, lhs, rhs), Op::I64EqImm16 { lhs, rhs, .. } => Op::select_i64_eq_imm16(result, lhs, rhs), Op::I64NeImm16 { lhs, rhs, .. } => Op::select_i64_eq_imm16(result, lhs, rhs), Op::I64LeSImm16Lhs { lhs, rhs, .. } => Op::select_i64_lt_s_imm16_rhs(result, rhs, lhs), Op::I64LeUImm16Lhs { lhs, rhs, .. } => Op::select_i64_lt_u_imm16_rhs(result, rhs, lhs), Op::I64LtSImm16Lhs { lhs, rhs, .. } => Op::select_i64_le_s_imm16_rhs(result, rhs, lhs), Op::I64LtUImm16Lhs { lhs, rhs, .. } => Op::select_i64_le_u_imm16_rhs(result, rhs, lhs), Op::I64LeSImm16Rhs { lhs, rhs, .. } => Op::select_i64_le_s_imm16_rhs(result, lhs, rhs), Op::I64LeUImm16Rhs { lhs, rhs, .. } => Op::select_i64_le_u_imm16_rhs(result, lhs, rhs), Op::I64LtSImm16Rhs { lhs, rhs, .. } => Op::select_i64_lt_s_imm16_rhs(result, lhs, rhs), Op::I64LtUImm16Rhs { lhs, rhs, .. } => Op::select_i64_lt_u_imm16_rhs(result, lhs, rhs), // i64 (and, or, xor) Op::I64BitAnd { lhs, rhs, .. } => Op::select_i64_and(result, lhs, rhs), Op::I64BitOr { lhs, rhs, .. } => Op::select_i64_or(result, lhs, rhs), Op::I64BitXor { lhs, rhs, .. } => Op::select_i64_eq(result, lhs, rhs), Op::I64And { lhs, rhs, .. } => Op::select_i64_and(result, lhs, rhs), Op::I64Or { lhs, rhs, .. } => Op::select_i64_or(result, lhs, rhs), Op::I64Nand { lhs, rhs, .. } => Op::select_i64_and(result, lhs, rhs), Op::I64Nor { lhs, rhs, .. } => Op::select_i64_or(result, lhs, rhs), Op::I64BitAndImm16 { lhs, rhs, .. } => Op::select_i64_and_imm16(result, lhs, rhs), Op::I64BitOrImm16 { lhs, rhs, .. } => Op::select_i64_or_imm16(result, lhs, rhs), Op::I64BitXorImm16 { lhs, rhs, .. } => Op::select_i64_eq_imm16(result, lhs, rhs), Op::I64AndImm16 { lhs, rhs, .. } => Op::select_i64_and_imm16(result, lhs, rhs), Op::I64OrImm16 { lhs, rhs, .. } => Op::select_i64_or_imm16(result, lhs, rhs), Op::I64NandImm16 { lhs, rhs, .. } => Op::select_i64_and_imm16(result, lhs, rhs), Op::I64NorImm16 { lhs, rhs, .. } => Op::select_i64_or_imm16(result, lhs, rhs), // f32 Op::F32Eq { lhs, rhs, .. } => Op::select_f32_eq(result, lhs, rhs), Op::F32Ne { lhs, rhs, .. } => Op::select_f32_eq(result, lhs, rhs), Op::F32Lt { lhs, rhs, .. } => Op::select_f32_lt(result, lhs, rhs), Op::F32Le { lhs, rhs, .. } => Op::select_f32_le(result, lhs, rhs), Op::F32NotLt { lhs, rhs, .. } => Op::select_f32_lt(result, lhs, rhs), Op::F32NotLe { lhs, rhs, .. } => Op::select_f32_le(result, lhs, rhs), // f64 Op::F64Eq { lhs, rhs, .. } => Op::select_f64_eq(result, lhs, rhs), Op::F64Ne { lhs, rhs, .. } => Op::select_f64_eq(result, lhs, rhs), Op::F64Lt { lhs, rhs, .. } => Op::select_f64_lt(result, lhs, rhs), Op::F64Le { lhs, rhs, .. } => Op::select_f64_le(result, lhs, rhs), Op::F64NotLt { lhs, rhs, .. } => Op::select_f64_lt(result, lhs, rhs), Op::F64NotLe { lhs, rhs, .. } => Op::select_f64_le(result, lhs, rhs), _ => unreachable!("expected to successfully fuse cmp+select"), }; Ok(CmpSelectFusion::Applied { fused, swap_operands, }) } } pub trait TryIntoCmpBranchInstr: Sized { fn try_into_cmp_branch_instr( &self, offset: BranchOffset, stack: &mut impl AllocConst, ) -> Result, Error>; } impl TryIntoCmpBranchInstr for Op { fn try_into_cmp_branch_instr( &self, offset: BranchOffset, stack: &mut impl AllocConst, ) -> Result, Error> { let Ok(offset) = BranchOffset16::try_from(offset) else { return self.try_into_cmp_branch_fallback_instr(offset, stack); }; #[rustfmt::skip] let cmp_branch_instr = match *self { // i32 Op::I32Eq { lhs, rhs, .. } => Op::branch_i32_eq(lhs, rhs, offset), Op::I32Ne { lhs, rhs, .. } => Op::branch_i32_ne(lhs, rhs, offset), Op::I32LeS { lhs, rhs, .. } => Op::branch_i32_le_s(lhs, rhs, offset), Op::I32LeU { lhs, rhs, .. } => Op::branch_i32_le_u(lhs, rhs, offset), Op::I32LtS { lhs, rhs, .. } => Op::branch_i32_lt_s(lhs, rhs, offset), Op::I32LtU { lhs, rhs, .. } => Op::branch_i32_lt_u(lhs, rhs, offset), Op::I32EqImm16 { lhs, rhs, .. } => Op::branch_i32_eq_imm16(lhs, rhs, offset), Op::I32NeImm16 { lhs, rhs, .. } => Op::branch_i32_ne_imm16(lhs, rhs, offset), Op::I32LeSImm16Lhs { lhs, rhs, .. } => Op::branch_i32_le_s_imm16_lhs(lhs, rhs, offset), Op::I32LeUImm16Lhs { lhs, rhs, .. } => Op::branch_i32_le_u_imm16_lhs(lhs, rhs, offset), Op::I32LtSImm16Lhs { lhs, rhs, .. } => Op::branch_i32_lt_s_imm16_lhs(lhs, rhs, offset), Op::I32LtUImm16Lhs { lhs, rhs, .. } => Op::branch_i32_lt_u_imm16_lhs(lhs, rhs, offset), Op::I32LeSImm16Rhs { lhs, rhs, .. } => Op::branch_i32_le_s_imm16_rhs(lhs, rhs, offset), Op::I32LeUImm16Rhs { lhs, rhs, .. } => Op::branch_i32_le_u_imm16_rhs(lhs, rhs, offset), Op::I32LtSImm16Rhs { lhs, rhs, .. } => Op::branch_i32_lt_s_imm16_rhs(lhs, rhs, offset), Op::I32LtUImm16Rhs { lhs, rhs, .. } => Op::branch_i32_lt_u_imm16_rhs(lhs, rhs, offset), // i32 (and, or, xor) Op::I32BitAnd { lhs, rhs, .. } => Op::branch_i32_and(lhs, rhs, offset), Op::I32BitOr { lhs, rhs, .. } => Op::branch_i32_or(lhs, rhs, offset), Op::I32BitXor { lhs, rhs, .. } => Op::branch_i32_ne(lhs, rhs, offset), Op::I32And { lhs, rhs, .. } => Op::branch_i32_and(lhs, rhs, offset), Op::I32Or { lhs, rhs, .. } => Op::branch_i32_or(lhs, rhs, offset), Op::I32Nand { lhs, rhs, .. } => Op::branch_i32_nand(lhs, rhs, offset), Op::I32Nor { lhs, rhs, .. } => Op::branch_i32_nor(lhs, rhs, offset), Op::I32BitAndImm16 { lhs, rhs, .. } => Op::branch_i32_and_imm16(lhs, rhs, offset), Op::I32BitOrImm16 { lhs, rhs, .. } => Op::branch_i32_or_imm16(lhs, rhs, offset), Op::I32BitXorImm16 { lhs, rhs, .. } => Op::branch_i32_ne_imm16(lhs, rhs, offset), Op::I32AndImm16 { lhs, rhs, .. } => Op::branch_i32_and_imm16(lhs, rhs, offset), Op::I32OrImm16 { lhs, rhs, .. } => Op::branch_i32_or_imm16(lhs, rhs, offset), Op::I32NandImm16 { lhs, rhs, .. } => Op::branch_i32_nand_imm16(lhs, rhs, offset), Op::I32NorImm16 { lhs, rhs, .. } => Op::branch_i32_nor_imm16(lhs, rhs, offset), // i64 Op::I64Eq { lhs, rhs, .. } => Op::branch_i64_eq(lhs, rhs, offset), Op::I64Ne { lhs, rhs, .. } => Op::branch_i64_ne(lhs, rhs, offset), Op::I64LeS { lhs, rhs, .. } => Op::branch_i64_le_s(lhs, rhs, offset), Op::I64LeU { lhs, rhs, .. } => Op::branch_i64_le_u(lhs, rhs, offset), Op::I64LtS { lhs, rhs, .. } => Op::branch_i64_lt_s(lhs, rhs, offset), Op::I64LtU { lhs, rhs, .. } => Op::branch_i64_lt_u(lhs, rhs, offset), Op::I64EqImm16 { lhs, rhs, .. } => Op::branch_i64_eq_imm16(lhs, rhs, offset), Op::I64NeImm16 { lhs, rhs, .. } => Op::branch_i64_ne_imm16(lhs, rhs, offset), Op::I64LeSImm16Lhs { lhs, rhs, .. } => Op::branch_i64_le_s_imm16_lhs(lhs, rhs, offset), Op::I64LeUImm16Lhs { lhs, rhs, .. } => Op::branch_i64_le_u_imm16_lhs(lhs, rhs, offset), Op::I64LtSImm16Lhs { lhs, rhs, .. } => Op::branch_i64_lt_s_imm16_lhs(lhs, rhs, offset), Op::I64LtUImm16Lhs { lhs, rhs, .. } => Op::branch_i64_lt_u_imm16_lhs(lhs, rhs, offset), Op::I64LeSImm16Rhs { lhs, rhs, .. } => Op::branch_i64_le_s_imm16_rhs(lhs, rhs, offset), Op::I64LeUImm16Rhs { lhs, rhs, .. } => Op::branch_i64_le_u_imm16_rhs(lhs, rhs, offset), Op::I64LtSImm16Rhs { lhs, rhs, .. } => Op::branch_i64_lt_s_imm16_rhs(lhs, rhs, offset), Op::I64LtUImm16Rhs { lhs, rhs, .. } => Op::branch_i64_lt_u_imm16_rhs(lhs, rhs, offset), // i64 (and, or, xor) Op::I64BitAnd { lhs, rhs, .. } => Op::branch_i64_and(lhs, rhs, offset), Op::I64BitOr { lhs, rhs, .. } => Op::branch_i64_or(lhs, rhs, offset), Op::I64BitXor { lhs, rhs, .. } => Op::branch_i64_ne(lhs, rhs, offset), Op::I64And { lhs, rhs, .. } => Op::branch_i64_and(lhs, rhs, offset), Op::I64Or { lhs, rhs, .. } => Op::branch_i64_or(lhs, rhs, offset), Op::I64Nand { lhs, rhs, .. } => Op::branch_i64_nand(lhs, rhs, offset), Op::I64Nor { lhs, rhs, .. } => Op::branch_i64_nor(lhs, rhs, offset), Op::I64BitAndImm16 { lhs, rhs, .. } => Op::branch_i64_and_imm16(lhs, rhs, offset), Op::I64BitOrImm16 { lhs, rhs, .. } => Op::branch_i64_or_imm16(lhs, rhs, offset), Op::I64BitXorImm16 { lhs, rhs, .. } => Op::branch_i64_ne_imm16(lhs, rhs, offset), Op::I64AndImm16 { lhs, rhs, .. } => Op::branch_i64_and_imm16(lhs, rhs, offset), Op::I64OrImm16 { lhs, rhs, .. } => Op::branch_i64_or_imm16(lhs, rhs, offset), Op::I64NandImm16 { lhs, rhs, .. } => Op::branch_i64_nand_imm16(lhs, rhs, offset), Op::I64NorImm16 { lhs, rhs, .. } => Op::branch_i64_nor_imm16(lhs, rhs, offset), // f32 Op::F32Eq { lhs, rhs, .. } => Op::branch_f32_eq(lhs, rhs, offset), Op::F32Ne { lhs, rhs, .. } => Op::branch_f32_ne(lhs, rhs, offset), Op::F32Lt { lhs, rhs, .. } => Op::branch_f32_lt(lhs, rhs, offset), Op::F32Le { lhs, rhs, .. } => Op::branch_f32_le(lhs, rhs, offset), Op::F32NotLt { lhs, rhs, .. } => Op::branch_f32_not_lt(lhs, rhs, offset), Op::F32NotLe { lhs, rhs, .. } => Op::branch_f32_not_le(lhs, rhs, offset), // f64 Op::F64Eq { lhs, rhs, .. } => Op::branch_f64_eq(lhs, rhs, offset), Op::F64Ne { lhs, rhs, .. } => Op::branch_f64_ne(lhs, rhs, offset), Op::F64Lt { lhs, rhs, .. } => Op::branch_f64_lt(lhs, rhs, offset), Op::F64Le { lhs, rhs, .. } => Op::branch_f64_le(lhs, rhs, offset), Op::F64NotLt { lhs, rhs, .. } => Op::branch_f64_not_lt(lhs, rhs, offset), Op::F64NotLe { lhs, rhs, .. } => Op::branch_f64_not_le(lhs, rhs, offset), _ => return Ok(None), }; Ok(Some(cmp_branch_instr)) } } pub trait TryIntoCmpBranchFallbackInstr { fn try_into_cmp_branch_fallback_instr( &self, offset: BranchOffset, stack: &mut impl AllocConst, ) -> Result, Error>; } impl TryIntoCmpBranchFallbackInstr for Op { fn try_into_cmp_branch_fallback_instr( &self, offset: BranchOffset, stack: &mut impl AllocConst, ) -> Result, Error> { debug_assert!(BranchOffset16::try_from(offset).is_err()); let Some(comparator) = try_into_cmp_br_comparator(self) else { return Ok(None); }; #[rustfmt::skip] let (lhs, rhs) = match *self { | Op::BranchI32And { lhs, rhs, .. } | Op::BranchI32Or { lhs, rhs, .. } | Op::BranchI32Nand { lhs, rhs, .. } | Op::BranchI32Nor { lhs, rhs, .. } | Op::BranchI32Eq { lhs, rhs, .. } | Op::BranchI32Ne { lhs, rhs, .. } | Op::BranchI32LtS { lhs, rhs, .. } | Op::BranchI32LtU { lhs, rhs, .. } | Op::BranchI32LeS { lhs, rhs, .. } | Op::BranchI32LeU { lhs, rhs, .. } | Op::BranchI64And { lhs, rhs, .. } | Op::BranchI64Or { lhs, rhs, .. } | Op::BranchI64Nand { lhs, rhs, .. } | Op::BranchI64Nor { lhs, rhs, .. } | Op::BranchI64Eq { lhs, rhs, .. } | Op::BranchI64Ne { lhs, rhs, .. } | Op::BranchI64LtS { lhs, rhs, .. } | Op::BranchI64LtU { lhs, rhs, .. } | Op::BranchI64LeS { lhs, rhs, .. } | Op::BranchI64LeU { lhs, rhs, .. } | Op::BranchF32Eq { lhs, rhs, .. } | Op::BranchF32Ne { lhs, rhs, .. } | Op::BranchF32Lt { lhs, rhs, .. } | Op::BranchF32Le { lhs, rhs, .. } | Op::BranchF32NotLt { lhs, rhs, .. } | Op::BranchF32NotLe { lhs, rhs, .. } | Op::BranchF64Eq { lhs, rhs, .. } | Op::BranchF64Ne { lhs, rhs, .. } | Op::BranchF64Lt { lhs, rhs, .. } | Op::BranchF64Le { lhs, rhs, .. } | Op::BranchF64NotLt { lhs, rhs, .. } | Op::BranchF64NotLe { lhs, rhs, .. } => (lhs, rhs), | Op::BranchI32AndImm16 { lhs, rhs, .. } | Op::BranchI32OrImm16 { lhs, rhs, .. } | Op::BranchI32NandImm16 { lhs, rhs, .. } | Op::BranchI32NorImm16 { lhs, rhs, .. } | Op::BranchI32EqImm16 { lhs, rhs, .. } | Op::BranchI32NeImm16 { lhs, rhs, .. } | Op::BranchI32LtSImm16Rhs { lhs, rhs, .. } | Op::BranchI32LeSImm16Rhs { lhs, rhs, .. } => { let rhs = stack.alloc_const(i32::from(rhs))?; (lhs, rhs) } | Op::BranchI32LtSImm16Lhs { lhs, rhs, .. } | Op::BranchI32LeSImm16Lhs { lhs, rhs, .. } => { let lhs = stack.alloc_const(i32::from(lhs))?; (lhs, rhs) } | Op::BranchI32LtUImm16Rhs { lhs, rhs, .. } | Op::BranchI32LeUImm16Rhs { lhs, rhs, .. } => { let rhs = stack.alloc_const(u32::from(rhs))?; (lhs, rhs) } | Op::BranchI32LtUImm16Lhs { lhs, rhs, .. } | Op::BranchI32LeUImm16Lhs { lhs, rhs, .. } => { let lhs = stack.alloc_const(u32::from(lhs))?; (lhs, rhs) } | Op::BranchI64AndImm16 { lhs, rhs, .. } | Op::BranchI64OrImm16 { lhs, rhs, .. } | Op::BranchI64NandImm16 { lhs, rhs, .. } | Op::BranchI64NorImm16 { lhs, rhs, .. } | Op::BranchI64EqImm16 { lhs, rhs, .. } | Op::BranchI64NeImm16 { lhs, rhs, .. } | Op::BranchI64LtSImm16Rhs { lhs, rhs, .. } | Op::BranchI64LeSImm16Rhs { lhs, rhs, .. } => { let rhs = stack.alloc_const(i64::from(rhs))?; (lhs, rhs) } | Op::BranchI64LtSImm16Lhs { lhs, rhs, .. } | Op::BranchI64LeSImm16Lhs { lhs, rhs, .. } => { let lhs = stack.alloc_const(i64::from(lhs))?; (lhs, rhs) } | Op::BranchI64LtUImm16Rhs { lhs, rhs, .. } | Op::BranchI64LeUImm16Rhs { lhs, rhs, .. } => { let rhs = stack.alloc_const(u64::from(rhs))?; (lhs, rhs) } | Op::BranchI64LtUImm16Lhs { lhs, rhs, .. } | Op::BranchI64LeUImm16Lhs { lhs, rhs, .. } => { let lhs = stack.alloc_const(u64::from(lhs))?; (lhs, rhs) } _ => return Ok(None), }; let params = stack.alloc_const(ComparatorAndOffset::new(comparator, offset))?; Ok(Some(Op::branch_cmp_fallback(lhs, rhs, params))) } } fn try_into_cmp_br_comparator(instr: &Op) -> Option { #[rustfmt::skip] let comparator = match *instr { // i32 | Op::BranchI32Eq { .. } | Op::BranchI32EqImm16 { .. } => Comparator::I32Eq, | Op::BranchI32Ne { .. } | Op::BranchI32NeImm16 { .. } => Comparator::I32Ne, | Op::BranchI32LtS { .. } | Op::BranchI32LtSImm16Lhs { .. } | Op::BranchI32LtSImm16Rhs { .. } => Comparator::I32LtS, | Op::BranchI32LtU { .. } | Op::BranchI32LtUImm16Lhs { .. } | Op::BranchI32LtUImm16Rhs { .. } => Comparator::I32LtU, | Op::BranchI32LeS { .. } | Op::BranchI32LeSImm16Lhs { .. } | Op::BranchI32LeSImm16Rhs { .. } => Comparator::I32LeS, | Op::BranchI32LeU { .. } | Op::BranchI32LeUImm16Lhs { .. } | Op::BranchI32LeUImm16Rhs { .. } => Comparator::I32LeU, // i32 (and,or,xor) | Op::BranchI32And { .. } => Comparator::I32And, | Op::BranchI32Or { .. } => Comparator::I32Or, | Op::BranchI32Nand { .. } => Comparator::I32Nand, | Op::BranchI32Nor { .. } => Comparator::I32Nor, // i64 | Op::BranchI64Eq { .. } | Op::BranchI64EqImm16 { .. } => Comparator::I64Eq, | Op::BranchI64Ne { .. } | Op::BranchI64NeImm16 { .. } => Comparator::I64Ne, | Op::BranchI64LtS { .. } | Op::BranchI64LtSImm16Lhs { .. } | Op::BranchI64LtSImm16Rhs { .. } => Comparator::I64LtS, | Op::BranchI64LtU { .. } | Op::BranchI64LtUImm16Lhs { .. } | Op::BranchI64LtUImm16Rhs { .. } => Comparator::I64LtU, | Op::BranchI64LeS { .. } | Op::BranchI64LeSImm16Lhs { .. } | Op::BranchI64LeSImm16Rhs { .. } => Comparator::I64LeS, | Op::BranchI64LeU { .. } | Op::BranchI64LeUImm16Lhs { .. } | Op::BranchI64LeUImm16Rhs { .. } => Comparator::I64LeU, // f32 | Op::BranchF32Eq { .. } => Comparator::F32Eq, | Op::BranchF32Ne { .. } => Comparator::F32Ne, | Op::BranchF32Lt { .. } => Comparator::F32Lt, | Op::BranchF32Le { .. } => Comparator::F32Le, | Op::BranchF32NotLt { .. } => Comparator::F32NotLt, | Op::BranchF32NotLe { .. } => Comparator::F32NotLe, // f64 | Op::BranchF64Eq { .. } => Comparator::F64Eq, | Op::BranchF64Ne { .. } => Comparator::F64Ne, | Op::BranchF64Lt { .. } => Comparator::F64Lt, | Op::BranchF64Le { .. } => Comparator::F64Le, | Op::BranchF64NotLt { .. } => Comparator::F64NotLt, | Op::BranchF64NotLe { .. } => Comparator::F64NotLe, _ => return None, }; Some(comparator) } /// Extension trait to update the branch offset of an [`Op`]. pub trait UpdateBranchOffset { /// Updates the [`BranchOffset`] for the branch [`Op`]. /// /// # Panics /// /// If `self` is not a branch [`Op`]. fn update_branch_offset( &mut self, stack: &mut impl AllocConst, new_offset: BranchOffset, ) -> Result<(), Error>; } impl UpdateBranchOffset for Op { #[rustfmt::skip] fn update_branch_offset( &mut self, stack: &mut impl AllocConst, new_offset: BranchOffset, ) -> Result<(), Error> { match self { | Op::Branch { offset } | Op::BranchTableTarget { offset, .. } => { offset.init(new_offset); return Ok(()); } _ => {} }; let offset = match self { | Op::BranchI32And { offset, .. } | Op::BranchI32Or { offset, .. } | Op::BranchI32Nand { offset, .. } | Op::BranchI32Nor { offset, .. } | Op::BranchI32Eq { offset, .. } | Op::BranchI32Ne { offset, .. } | Op::BranchI32LtS { offset, .. } | Op::BranchI32LtU { offset, .. } | Op::BranchI32LeS { offset, .. } | Op::BranchI32LeU { offset, .. } | Op::BranchI64And { offset, .. } | Op::BranchI64Or { offset, .. } | Op::BranchI64Nand { offset, .. } | Op::BranchI64Nor { offset, .. } | Op::BranchI64Eq { offset, .. } | Op::BranchI64Ne { offset, .. } | Op::BranchI64LtS { offset, .. } | Op::BranchI64LtU { offset, .. } | Op::BranchI64LeS { offset, .. } | Op::BranchI64LeU { offset, .. } | Op::BranchF32Eq { offset, .. } | Op::BranchF32Ne { offset, .. } | Op::BranchF32Lt { offset, .. } | Op::BranchF32Le { offset, .. } | Op::BranchF32NotLt { offset, .. } | Op::BranchF32NotLe { offset, .. } | Op::BranchF64Eq { offset, .. } | Op::BranchF64Ne { offset, .. } | Op::BranchF64Lt { offset, .. } | Op::BranchF64Le { offset, .. } | Op::BranchF64NotLt { offset, .. } | Op::BranchF64NotLe { offset, .. } | Op::BranchI32AndImm16 { offset, .. } | Op::BranchI32OrImm16 { offset, .. } | Op::BranchI32NandImm16 { offset, .. } | Op::BranchI32NorImm16 { offset, .. } | Op::BranchI32EqImm16 { offset, .. } | Op::BranchI32NeImm16 { offset, .. } | Op::BranchI32LtSImm16Lhs { offset, .. } | Op::BranchI32LtSImm16Rhs { offset, .. } | Op::BranchI32LeSImm16Lhs { offset, .. } | Op::BranchI32LeSImm16Rhs { offset, .. } | Op::BranchI32LtUImm16Lhs { offset, .. } | Op::BranchI32LtUImm16Rhs { offset, .. } | Op::BranchI32LeUImm16Lhs { offset, .. } | Op::BranchI32LeUImm16Rhs { offset, .. } | Op::BranchI64AndImm16 { offset, .. } | Op::BranchI64OrImm16 { offset, .. } | Op::BranchI64NandImm16 { offset, .. } | Op::BranchI64NorImm16 { offset, .. } | Op::BranchI64EqImm16 { offset, .. } | Op::BranchI64NeImm16 { offset, .. } | Op::BranchI64LtSImm16Lhs { offset, .. } | Op::BranchI64LtSImm16Rhs { offset, .. } | Op::BranchI64LeSImm16Lhs { offset, .. } | Op::BranchI64LeSImm16Rhs { offset, .. } | Op::BranchI64LtUImm16Lhs { offset, .. } | Op::BranchI64LtUImm16Rhs { offset, .. } | Op::BranchI64LeUImm16Lhs { offset, .. } | Op::BranchI64LeUImm16Rhs { offset, .. } => offset, unexpected => { panic!("expected a Wasmi `cmp`+`branch` instruction but found: {unexpected:?}") } }; if offset.init(new_offset).is_err() { // Case: we need to convert `self` into its cmp+branch fallback instruction variant // since adjusting the 16-bit offset failed. let Some(fallback) = self.try_into_cmp_branch_fallback_instr(new_offset, stack)? else { unreachable!("failed to create cmp+branch fallback instruction for: {self:?}"); }; *self = fallback; } Ok(()) } } wasmi-1.1.0/src/engine/translator/driver.rs000064400000000000000000000060401046102023000170310ustar 00000000000000use crate::{ engine::{code_map::CompiledFuncEntity, WasmTranslator}, Error, }; use wasmparser::{BinaryReader, FunctionBody}; /// Translates Wasm bytecode into Wasmi bytecode for a single Wasm function. pub struct FuncTranslationDriver<'parser, T> { /// The function body that shall be translated. func_body: FunctionBody<'parser>, /// The bytes that make up the entirety of the function body. bytes: &'parser [u8], /// The underlying translator used for the translation (and validation) process. translator: T, } impl<'parser, T> FuncTranslationDriver<'parser, T> where T: WasmTranslator<'parser>, { /// Creates a new Wasm to Wasmi bytecode function translator. pub fn new( offset: impl Into>, bytes: &'parser [u8], translator: T, ) -> Result { let offset = offset.into().unwrap_or(0); let features = translator.features(); let reader = BinaryReader::new_features(bytes, offset, features); let func_body = FunctionBody::new(reader); Ok(Self { func_body, bytes, translator, }) } /// Starts translation of the Wasm stream into Wasmi bytecode. pub fn translate( mut self, finalize: impl FnOnce(CompiledFuncEntity), ) -> Result { if self.translator.setup(self.bytes)? { let allocations = self.translator.finish(finalize)?; return Ok(allocations); } self.translate_locals()?; let offset = self.translate_operators()?; let allocations = self.finish(offset, finalize)?; Ok(allocations) } /// Finishes construction of the function and returns its reusable allocations. fn finish( mut self, offset: usize, finalize: impl FnOnce(CompiledFuncEntity), ) -> Result { self.translator.update_pos(offset); self.translator.finish(finalize) } /// Translates local variables of the Wasm function. fn translate_locals(&mut self) -> Result<(), Error> { let mut reader = self.func_body.get_locals_reader()?; let len_locals = reader.get_count(); for _ in 0..len_locals { let offset = reader.original_position(); let (amount, value_type) = reader.read()?; self.translator.update_pos(offset); self.translator.translate_locals(amount, value_type)?; } self.translator.finish_translate_locals()?; Ok(()) } /// Translates the Wasm operators of the Wasm function. /// /// Returns the offset of the `End` Wasm operator. fn translate_operators(&mut self) -> Result { let mut reader = self.func_body.get_operators_reader()?; while !reader.eof() { let pos = reader.original_position(); self.translator.update_pos(pos); reader.visit_operator(&mut self.translator)??; } reader.finish()?; Ok(reader.original_position()) } } wasmi-1.1.0/src/engine/translator/error.rs000064400000000000000000000104401046102023000166660ustar 00000000000000use core::{ error::Error, fmt::{self, Display}, }; /// An error that may occur upon parsing, validating and translating Wasm. #[derive(Debug)] pub enum TranslationError { /// Encountered an unsupported Wasm block type. UnsupportedBlockType(wasmparser::BlockType), /// Encountered an unsupported Wasm value type. UnsupportedValueType(wasmparser::ValType), /// When using too many branch table targets. BranchTableTargetsOutOfBounds, /// Branching offset out of bounds. BranchOffsetOutOfBounds, /// Fuel required for a block is out of bounds. BlockFuelOutOfBounds, /// Tried to allocate more registers than possible. AllocatedTooManySlots, /// Tried to use an out of bounds register index. SlotOutOfBounds, /// Pushed too many values on the emulated value stack during translation. EmulatedValueStackOverflow, /// Tried to allocate too many or large provider slices. ProviderSliceOverflow, /// Tried to allocate too many function local constant values. TooManyFuncLocalConstValues, /// Tried to define a function with too many function results. TooManyFunctionResults, /// Tried to define a function with too many function parameters. TooManyFunctionParams, /// Tried to define a function with too many local variables. TooManyLocalVariables, /// The function failed to compiled lazily. LazyCompilationFailed, } impl TranslationError { /// Creates a new error indicating an unsupported Wasm block type. pub fn unsupported_block_type(block_type: wasmparser::BlockType) -> Self { Self::UnsupportedBlockType(block_type) } /// Creates a new error indicating an unsupported Wasm value type. pub fn unsupported_value_type(value_type: wasmparser::ValType) -> Self { Self::UnsupportedValueType(value_type) } } impl Error for TranslationError {} impl Display for TranslationError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::UnsupportedBlockType(error) => { write!(f, "encountered unsupported Wasm block type: {error:?}") } Self::UnsupportedValueType(error) => { write!(f, "encountered unsupported Wasm value type: {error:?}") } Self::BranchTableTargetsOutOfBounds => { write!( f, "branch table targets are out of bounds for wasmi bytecode" ) } Self::BranchOffsetOutOfBounds => { write!(f, "branching offset is out of bounds for wasmi bytecode") } Self::BlockFuelOutOfBounds => { write!( f, "fuel required to execute a block is out of bounds for wasmi bytecode" ) } Self::AllocatedTooManySlots => { write!( f, "translation requires more registers for a function than available" ) } Self::SlotOutOfBounds => { write!(f, "tried to access out of bounds register index") } Self::EmulatedValueStackOverflow => { write!(f, "function requires value stack with out of bounds depth") } Self::ProviderSliceOverflow => { write!(f, "tried to allocate too many or too large provider slices") } Self::TooManyFuncLocalConstValues => { write!( f, "tried to allocate too many function local constant values" ) } Self::TooManyFunctionResults => { write!(f, "encountered function with too many function results") } Self::TooManyFunctionParams => { write!(f, "encountered function with too many function parameters") } Self::TooManyLocalVariables => { write!(f, "encountered function with too many local variables") } Self::LazyCompilationFailed => { write!( f, "lazy function compilation encountered a Wasm validation or translation error" ) } } } } wasmi-1.1.0/src/engine/translator/func/instrs.rs000064400000000000000000000312341046102023000200160ustar 00000000000000use super::{Reset, ReusableAllocations}; use crate::{ core::FuelCostsProvider, engine::translator::{ comparator::{ CmpSelectFusion, CompareResult as _, TryIntoCmpSelectInstr as _, UpdateBranchOffset as _, }, func::{Operand, Stack, StackLayout, StackSpace}, relink_result::RelinkResult, utils::{BumpFuelConsumption as _, Instr, IsInstructionParameter as _}, }, ir::{BranchOffset, Op, Slot}, module::ModuleHeader, Engine, Error, ValType, }; use alloc::vec::{self, Vec}; /// Creates and encodes the list of [`Op`]s for a function. #[derive(Debug, Default)] pub struct InstrEncoder { /// The list of constructed instructions and their parameters. instrs: Vec, /// The fuel costs of instructions. /// /// This is `Some` if fuel metering is enabled, otherwise `None`. fuel_costs: Option, /// The last pushed non-parameter [`Op`]. last_instr: Option, } impl ReusableAllocations for InstrEncoder { type Allocations = InstrEncoderAllocations; fn into_allocations(self) -> Self::Allocations { Self::Allocations { instrs: self.instrs, } } } /// The reusable heap allocations of the [`InstrEncoder`]. #[derive(Debug, Default)] pub struct InstrEncoderAllocations { /// The list of constructed instructions and their parameters. instrs: Vec, } impl Reset for InstrEncoderAllocations { fn reset(&mut self) { self.instrs.clear(); } } impl InstrEncoder { /// Creates a new [`InstrEncoder`]. pub fn new(engine: &Engine, alloc: InstrEncoderAllocations) -> Self { let config = engine.config(); let fuel_costs = config .get_consume_fuel() .then(|| config.fuel_costs()) .cloned(); Self { instrs: alloc.instrs, fuel_costs, last_instr: None, } } /// Returns the next [`Instr`]. #[must_use] pub fn next_instr(&self) -> Instr { Instr::from_usize(self.instrs.len()) } /// Pushes an [`Op::ConsumeFuel`] instruction to `self`. /// /// # Note /// /// The pushes [`Op::ConsumeFuel`] is initialized with base fuel costs. pub fn push_consume_fuel_instr(&mut self) -> Result, Error> { let Some(fuel_costs) = &self.fuel_costs else { return Ok(None); }; let base_costs = fuel_costs.base(); let Ok(base_costs) = u32::try_from(base_costs) else { panic!("out of bounds base fuel costs: {base_costs}"); }; let instr = self.push_instr_impl(Op::consume_fuel(base_costs))?; Ok(Some(instr)) } /// Pushes a non-parameter [`Op`] to the [`InstrEncoder`]. /// /// Returns an [`Instr`] that refers to the pushed [`Op`]. pub fn push_instr( &mut self, instruction: Op, consume_fuel: Option, f: impl FnOnce(&FuelCostsProvider) -> u64, ) -> Result { self.bump_fuel_consumption(consume_fuel, f)?; self.push_instr_impl(instruction) } /// Pushes a non-parameter [`Op`] to the [`InstrEncoder`]. fn push_instr_impl(&mut self, instruction: Op) -> Result { debug_assert!( !instruction.is_instruction_parameter(), "parameter: {instruction:?}" ); let instr = self.next_instr(); self.instrs.push(instruction); self.last_instr = Some(instr); Ok(instr) } /// Replaces `instr` with `new_instr` in `self`. /// /// - Returns `Ok(true)` if replacement was successful. /// - Returns `Ok(false)` if replacement was unsuccessful. /// /// # Panics (Debug) /// /// If `instr` or `new_instr` are [`Op`] parameters. pub fn try_replace_instr(&mut self, instr: Instr, new_instr: Op) -> Result { debug_assert!( !new_instr.is_instruction_parameter(), "parameter: {new_instr:?}" ); let Some(last_instr) = self.last_instr else { return Ok(false); }; let replace = self.get_mut(instr); debug_assert!(!replace.is_instruction_parameter(), "parameter: {instr:?}"); if instr != last_instr { return Ok(false); } *replace = new_instr; Ok(true) } /// Tries to replace the result of the last instruction with `new_result` if possible. /// /// # Note /// /// - `old_result`: /// just required for additional safety to check if the last instruction /// really is the source of the `local.set` or `local.tee`. /// - `new_result`: /// the new result which shall replace the `old_result`. pub fn try_replace_result( &mut self, new_result: Slot, old_result: Slot, layout: &StackLayout, module: &ModuleHeader, ) -> Result { if !matches!(layout.stack_space(new_result), StackSpace::Local) { // Case: cannot replace result if `new_result` isn't a local. return Ok(false); } let Some(last_instr) = self.last_instr else { // Case: cannot replace result without last instruction. return Ok(false); }; if !self .get_mut(last_instr) .relink_result(module, new_result, old_result)? { // Case: it was impossible to relink the result of `last_instr. return Ok(false); } Ok(true) } /// Tries to fuse a compare instruction with a Wasm `select` instruction. /// /// # Returns /// /// - Returns `Some` if fusion was successful. /// - Returns `None` if fusion could not be applied. pub fn try_fuse_select( &mut self, ty: ValType, select_condition: Slot, layout: &StackLayout, stack: &mut Stack, ) -> Result, Error> { let Some(last_instr) = self.last_instr else { // If there is no last instruction there is no comparison instruction to negate. return Ok(None); }; let last_instruction = self.get(last_instr); let Some(last_result) = last_instruction.compare_result() else { // All negatable instructions have a single result register. return Ok(None); }; if matches!(layout.stack_space(last_result), StackSpace::Local) { // The instruction stores its result into a local variable which // is an observable side effect which we are not allowed to mutate. return Ok(None); } if last_result != select_condition { // The result of the last instruction and the select's `condition` // are not equal thus indicating that we cannot fuse the instructions. return Ok(None); } let CmpSelectFusion::Applied { fused, swap_operands, } = last_instruction.try_into_cmp_select_instr(|| { let select_result = stack.push_temp(ty, Some(last_instr))?; let select_result = layout.temp_to_reg(select_result)?; Ok(select_result) })? else { return Ok(None); }; let last_instr = self.get_mut(last_instr); *last_instr = fused; Ok(Some(swap_operands)) } /// Pushes an [`Op`] parameter to the [`InstrEncoder`]. /// /// The parameter is associated to the last pushed [`Op`]. pub fn push_param(&mut self, instruction: Op) { self.instrs.push(instruction); } /// Returns a shared reference to the [`Op`] associated to [`Instr`]. /// /// # Panics /// /// If `instr` is out of bounds for `self`. pub fn get(&self, instr: Instr) -> &Op { &self.instrs[instr.into_usize()] } /// Returns an exclusive reference to the [`Op`] associated to [`Instr`]. /// /// # Panics /// /// If `instr` is out of bounds for `self`. fn get_mut(&mut self, instr: Instr) -> &mut Op { &mut self.instrs[instr.into_usize()] } /// Resets the [`Instr`] last created via [`InstrEncoder::push_instr`]. /// /// # Note /// /// The `last_instr` info is used for op-code fusion of `local.set` /// `local.tee`, compare, conditional branch and `select` instructions. /// /// Whenever ending a control frame during Wasm translation `last_instr` /// needs to be reset to `None` to signal that no such optimization is /// valid across control flow boundaries. pub fn reset_last_instr(&mut self) { self.last_instr = None; } /// Updates the branch offset of `instr` to `offset`. /// /// # Errors /// /// If the branch offset could not be updated for `instr`. pub fn update_branch_offset( &mut self, instr: Instr, offset: BranchOffset, layout: &mut StackLayout, ) -> Result<(), Error> { self.get_mut(instr).update_branch_offset(layout, offset)?; Ok(()) } /// Bumps consumed fuel for [`Op::ConsumeFuel`] of `instr` by `delta`. /// /// # Errors /// /// If consumed fuel is out of bounds after this operation. pub fn bump_fuel_consumption( &mut self, consume_fuel: Option, f: impl FnOnce(&FuelCostsProvider) -> u64, ) -> Result<(), Error> { let (fuel_costs, consume_fuel) = match (&self.fuel_costs, consume_fuel) { (None, None) => return Ok(()), (Some(fuel_costs), Some(consume_fuel)) => (fuel_costs, consume_fuel), _ => { panic!( "fuel metering state mismatch: fuel_costs: {:?}, fuel_instr: {:?}", self.fuel_costs, consume_fuel, ); } }; let fuel_consumed = f(fuel_costs); self.get_mut(consume_fuel) .bump_fuel_consumption(fuel_consumed)?; Ok(()) } /// Encode the top-most `len` operands on the stack as register list. /// /// # Note /// /// This is used for the following n-ary instructions: /// /// - [`Op::ReturnMany`] /// - [`Op::CopyMany`] /// - [`Op::CallInternal`] /// - [`Op::CallImported`] /// - [`Op::CallIndirect`] /// - [`Op::ReturnCallInternal`] /// - [`Op::ReturnCallImported`] /// - [`Op::ReturnCallIndirect`] pub fn encode_register_list( &mut self, operands: &[Operand], layout: &mut StackLayout, ) -> Result<(), Error> { let mut remaining = operands; let mut operand_to_reg = |operand: &Operand| -> Result { layout.operand_to_reg(*operand) }; let instr = loop { match remaining { [] => return Ok(()), [v0] => { let v0 = operand_to_reg(v0)?; break Op::slot(v0); } [v0, v1] => { let v0 = operand_to_reg(v0)?; let v1 = operand_to_reg(v1)?; break Op::slot2_ext(v0, v1); } [v0, v1, v2] => { let v0 = operand_to_reg(v0)?; let v1 = operand_to_reg(v1)?; let v2 = operand_to_reg(v2)?; break Op::slot3_ext(v0, v1, v2); } [v0, v1, v2, rest @ ..] => { let v0 = operand_to_reg(v0)?; let v1 = operand_to_reg(v1)?; let v2 = operand_to_reg(v2)?; let instr = Op::slot_list_ext(v0, v1, v2); self.push_param(instr); remaining = rest; } }; }; self.push_param(instr); Ok(()) } /// Returns an iterator yielding all [`Op`]s of the [`InstrEncoder`]. /// /// # Note /// /// The [`InstrEncoder`] will be empty after this operation. pub fn drain(&mut self) -> InstrEncoderIter<'_> { InstrEncoderIter { iter: self.instrs.drain(..), } } /// Returns the last instruction of the [`InstrEncoder`] if any. pub fn last_instr(&self) -> Option { self.last_instr } } /// Iterator yielding all [`Op`]s of the [`InstrEncoder`]. #[derive(Debug)] pub struct InstrEncoderIter<'a> { /// The underlying iterator. iter: vec::Drain<'a, Op>, } impl<'a> Iterator for InstrEncoderIter<'a> { type Item = Op; fn next(&mut self) -> Option { self.iter.next() } fn size_hint(&self) -> (usize, Option) { self.iter.size_hint() } } impl ExactSizeIterator for InstrEncoderIter<'_> { fn len(&self) -> usize { self.iter.len() } } wasmi-1.1.0/src/engine/translator/func/layout/consts.rs000064400000000000000000000103411046102023000213160ustar 00000000000000use super::Reset; use crate::{core::UntypedVal, engine::TranslationError, ir::Slot, Error}; use alloc::{ collections::{btree_map, BTreeMap}, vec::Vec, }; use core::{iter::Rev, slice::Iter as SliceIter}; /// A pool of deduplicated function local constant values. /// /// - Those constant values are identified by their associated [`Slot`]. /// - All constant values are also deduplicated so that no duplicates /// are stored in a [`ConstRegistry`]. This also means that deciding if two /// [`Slot`] values refer to the equal constant values can be efficiently /// done by comparing the [`Slot`] indices without resolving to their /// underlying constant values. #[derive(Debug, Default)] pub struct ConstRegistry { /// Mapping from constant [`UntypedVal`] values to [`Slot`] indices. const2idx: BTreeMap, /// Mapping from [`Slot`] indices to constant [`UntypedVal`] values. idx2const: Vec, /// The [`Slot`] index for the next allocated function local constant value. next_idx: i16, } impl Reset for ConstRegistry { fn reset(&mut self) { self.const2idx.clear(); self.idx2const.clear(); self.next_idx = Self::first_index(); } } impl ConstRegistry { /// The maximum index for [`Slot`] referring to function local constant values. /// /// # Note /// /// The maximum index is also the one to be assigned to the first allocated /// function local constant value as indices are counting downwards. fn first_index() -> i16 { -1 } /// The mininmum index for [`Slot`] referring to function local constant values. /// /// # Note /// /// This index is not assignable to a function local constant value and acts /// as a bound to guard against overflowing the range of indices. fn last_index() -> i16 { i16::MIN } /// Allocates a new constant `value` on the [`ConstRegistry`] and returns its identifier. /// /// # Note /// /// If the constant `value` already exists in this [`ConstRegistry`] 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 [`ConstRegistry`]. pub fn alloc(&mut self, value: UntypedVal) -> Result { if self.next_idx == Self::last_index() { return Err(Error::from(TranslationError::TooManyFuncLocalConstValues)); } match self.const2idx.entry(value) { btree_map::Entry::Occupied(entry) => Ok(*entry.get()), btree_map::Entry::Vacant(entry) => { let register = Slot::from(self.next_idx); self.next_idx -= 1; entry.insert(register); self.idx2const.push(value); Ok(register) } } } /// Returns an iterator yielding all function local constant values of the [`ConstRegistry`]. /// /// # Note /// /// The function local constant values are yielded in their allocation order. pub fn iter(&self) -> ConstRegistryIter<'_> { ConstRegistryIter::new(self) } } /// Iterator yielding all allocated function local constant values. pub struct ConstRegistryIter<'a> { /// The underlying iterator. iter: Rev>, } impl<'a> ConstRegistryIter<'a> { /// Creates a new [`ConstRegistryIter`] from the given slice of [`UntypedVal`]. pub fn new(consts: &'a ConstRegistry) -> Self { // Note: we need to revert the iteration since we allocate new // function local constants in reverse order of their absolute // vector indices in the function call frame during execution. Self { iter: consts.idx2const.as_slice().iter().rev(), } } } impl Iterator for ConstRegistryIter<'_> { type Item = UntypedVal; fn next(&mut self) -> Option { self.iter.next().copied() } } impl DoubleEndedIterator for ConstRegistryIter<'_> { fn next_back(&mut self) -> Option { self.iter.next_back().copied() } } impl ExactSizeIterator for ConstRegistryIter<'_> { fn len(&self) -> usize { self.iter.len() } } wasmi-1.1.0/src/engine/translator/func/layout/mod.rs000064400000000000000000000103751046102023000205730ustar 00000000000000mod consts; use self::consts::{ConstRegistry, ConstRegistryIter}; use super::{LocalIdx, Operand, OperandIdx, Reset}; use crate::{ core::UntypedVal, engine::{translator::comparator::AllocConst, TranslationError}, ir::Slot, Error, }; #[cfg(doc)] use super::Stack; /// The layout of the [`Stack`]. #[derive(Debug, Default)] pub struct StackLayout { /// The number of locals registered to the function. len_locals: usize, /// All function local constants. consts: ConstRegistry, } impl Reset for StackLayout { fn reset(&mut self) { self.len_locals = 0; self.consts.reset(); } } impl StackLayout { /// Slot `amount` local variables of common type `ty`. /// /// # Errors /// /// If too many local variables are being registered. pub fn register_locals(&mut self, amount: usize) -> Result<(), Error> { self.len_locals += amount; Ok(()) } /// Returns the [`StackSpace`] of the [`Slot`]. /// /// Returns `None` if the [`Slot`] is unknown to the [`Stack`]. #[must_use] pub fn stack_space(&self, slot: Slot) -> StackSpace { let index = i16::from(slot); if index.is_negative() { return StackSpace::Const; } let index = index as u16; if usize::from(index) < self.len_locals { return StackSpace::Local; } StackSpace::Temp } /// Converts the `operand` into the associated [`Slot`]. /// /// # Note /// /// Forwards to one of /// /// - [`StackLayout::local_to_reg`] /// - [`StackLayout::temp_to_reg`] /// - [`StackLayout::const_to_reg`] /// /// # Errors /// /// If the forwarded method returned an error. pub fn operand_to_reg(&mut self, operand: Operand) -> Result { match operand { Operand::Local(operand) => self.local_to_reg(operand.local_index()), Operand::Temp(operand) => self.temp_to_reg(operand.operand_index()), Operand::Immediate(operand) => self.const_to_reg(operand.val()), } } /// Converts the local `index` into the associated [`Slot`]. /// /// # Errors /// /// If `index` cannot be converted into a [`Slot`]. #[inline] pub fn local_to_reg(&self, index: LocalIdx) -> Result { debug_assert!( (u32::from(index) as usize) < self.len_locals, "out of bounds local operand index: {index:?}" ); let Ok(index) = i16::try_from(u32::from(index)) else { return Err(Error::from(TranslationError::AllocatedTooManySlots)); }; Ok(Slot::from(index)) } /// Converts the operand `index` into the associated [`Slot`]. /// /// # Errors /// /// If `index` cannot be converted into a [`Slot`]. #[inline] pub fn temp_to_reg(&self, index: OperandIdx) -> Result { let index = usize::from(index); let Some(index) = index.checked_add(self.len_locals) else { return Err(Error::from(TranslationError::AllocatedTooManySlots)); }; let Ok(index) = i16::try_from(index) else { return Err(Error::from(TranslationError::AllocatedTooManySlots)); }; Ok(Slot::from(index)) } /// Allocates a function local constant `value`. /// /// # Errors /// /// If too many function local constants have been allocated already. #[inline] pub fn const_to_reg(&mut self, value: impl Into) -> Result { self.consts.alloc(value.into()) } /// Returns an iterator yielding all function local constants. /// /// # Note /// /// The function local constant are yielded in reverse order of allocation. pub fn consts(&self) -> ConstRegistryIter<'_> { self.consts.iter() } } impl AllocConst for StackLayout { fn alloc_const>(&mut self, value: T) -> Result { self.const_to_reg(value) } } /// The [`StackSpace`] of a [`Slot`]. #[derive(Debug, Copy, Clone)] pub enum StackSpace { /// Stack slot referring to a local variable. Local, /// Stack slot referring to a function local constant value. Const, /// Stack slot referring to a temporary stack operand. Temp, } wasmi-1.1.0/src/engine/translator/func/locals.rs000064400000000000000000000136461046102023000177600ustar 00000000000000use super::Reset; use crate::{engine::TranslationError, Error, ValType}; use alloc::vec::Vec; use core::{cmp, iter}; /// A local variable index. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct LocalIdx(u32); impl From for LocalIdx { fn from(index: u32) -> Self { Self(index) } } impl From for u32 { fn from(index: LocalIdx) -> Self { index.0 } } /// Stores definitions of locals. #[derive(Debug, Default, Clone)] pub struct LocalsRegistry { /// The types of the first defined local variables. tys_first: Vec, /// The types of the remaining defined local variables. tys_remaining: Vec, /// The number of registered locals. len_locals: usize, } impl Reset for LocalsRegistry { fn reset(&mut self) { self.tys_first.clear(); self.tys_remaining.clear(); self.len_locals = 0; } } impl LocalsRegistry { /// Returns the number of registered local variables in `self`. pub fn len(&self) -> usize { self.len_locals } /// The maximum number of local variables per function. const LOCAL_VARIABLES_MAX: usize = 30_000; /// The maximum number of local variables in the fast `tys_first` vector. const FIRST_TYS_MAX: usize = 100; /// Slots `amount` of locals of type `ty` for `self`. /// /// # Errors /// /// If too many locals are registered. pub fn register(&mut self, amount: usize, ty: ValType) -> Result<(), Error> { if amount == 0 { return Ok(()); } if self.len().saturating_add(amount) > Self::LOCAL_VARIABLES_MAX { return Err(Error::from(TranslationError::TooManyFunctionParams)); } let vacant_first = Self::FIRST_TYS_MAX.saturating_sub(self.tys_first.len()); let push_to_first = cmp::min(vacant_first, amount); self.tys_first.extend(iter::repeat_n(ty, push_to_first)); let remaining_amount = amount - push_to_first; let remaining_index = (self.len() + amount - 1) as u32; if remaining_amount > 0 { self.tys_remaining .push(LocalGroup::new(remaining_index, ty)); } self.len_locals += amount; Ok(()) } /// Converts `index` into a `usize` value. fn local_idx_to_index(index: LocalIdx) -> usize { let index = u32::from(index); let Ok(index) = usize::try_from(index) else { panic!("out of bounds `LocalIdx`: {index}") }; index } /// Returns the type of the local variable at `index` if any. /// /// # Panics /// /// If `index` is out of bounds and does not refer to a local in `self`. pub fn ty(&self, index: LocalIdx) -> ValType { let index_sz = Self::local_idx_to_index(index); match self.tys_first.get(index_sz) { Some(ty) => *ty, None => self .ty_slow(index) .unwrap_or_else(|| panic!("out of bounds local index: {index:?}")), } } /// Returns the type of the local variable at `index` if any. /// /// This is the slow-path for local variables that have been stored in the `remaining` buffer. #[cold] fn ty_slow(&self, index: LocalIdx) -> Option { if self.tys_remaining.is_empty() { return None; } match self .tys_remaining .binary_search_by_key(&index.0, LocalGroup::max_index) { Err(i) if i == self.tys_remaining.len() => None, Ok(i) | Err(i) => Some(self.tys_remaining[i].ty()), } } } /// A local group of one or more locals sharing a common type. #[derive(Debug, Copy, Clone)] struct LocalGroup { /// The local index of the first local in the group. max_index: u32, /// The shared type of the locals in the local group. ty: ValType, } impl LocalGroup { /// Creates a new [`LocalGroup`]. fn new(max_index: u32, ty: ValType) -> Self { Self { max_index, ty } } /// Returns the maximum index of the local variables in the [`LocalGroup`]. fn max_index(&self) -> u32 { self.max_index } /// Returns the [`ValType`] of the [`LocalGroup`]. fn ty(&self) -> ValType { self.ty } } #[cfg(test)] mod tests { use super::*; #[test] fn ty_works() { let mut locals = LocalsRegistry::default(); for locals_per_type in [1, 2, 10, 100] { locals.reset(); let tys = [ValType::I32, ValType::I64, ValType::F32, ValType::F64]; for ty in tys { locals.register(locals_per_type, ty).unwrap(); } assert_eq!(locals.len(), locals_per_type * tys.len()); for i in 0..locals.len() { assert_eq!(locals.ty(LocalIdx(i as u32)), tys[i / locals_per_type]); } } } #[test] fn locals_followed_by_groups() { let mut locals = LocalsRegistry::default(); let len_single = [1, 10, 100]; let len_groups = [1, 10, 100]; let locals_per_group = [10, 100]; for len_single in len_single { for len_groups in len_groups { for locals_per_group in locals_per_group { locals.reset(); let len_locals = len_single + (len_groups * locals_per_group); for _ in 0..len_single { locals.register(1, ValType::I32).unwrap(); } for _ in 0..len_groups { locals.register(locals_per_group, ValType::I64).unwrap(); } for i in 0..len_locals { let ty = match i < len_single { true => ValType::I32, false => ValType::I64, }; assert_eq!(locals.ty(LocalIdx(i as u32)), ty); } } } } } } wasmi-1.1.0/src/engine/translator/func/mod.rs000064400000000000000000003363511046102023000172630ustar 00000000000000#[macro_use] mod utils; mod instrs; mod layout; mod locals; mod op; #[cfg(feature = "simd")] mod simd; mod stack; mod visit; use self::{ instrs::{InstrEncoder, InstrEncoderAllocations}, layout::{StackLayout, StackSpace}, locals::{LocalIdx, LocalsRegistry}, stack::{ BlockControlFrame, ControlFrame, ControlFrameBase, ControlFrameKind, ElseControlFrame, ElseReachability, IfControlFrame, IfReachability, ImmediateOperand, LoopControlFrame, Operand, OperandIdx, Stack, StackAllocations, TempOperand, }, utils::{Input, Input16, Input32, Reset, ReusableAllocations}, }; use crate::{ core::{FuelCostsProvider, IndexType, Typed, TypedVal, UntypedVal}, engine::{ translator::{ comparator::{ CompareResult as _, LogicalizeCmpInstr, NegateCmpInstr, ReplaceCmpResult, TryIntoCmpBranchInstr as _, }, labels::{LabelRef, LabelRegistry}, utils::{Instr, WasmFloat, WasmInteger, Wrap}, WasmTranslator, }, BlockType, CompiledFuncEntity, TranslationError, }, ir::{ index, Address, Address32, AnyConst16, BoundedSlotSpan, BranchOffset, BranchOffset16, Comparator, ComparatorAndOffset, Const16, Const32, FixedSlotSpan, IntoShiftAmount, Offset16, Offset64, Offset64Lo, Op, Sign, Slot, SlotSpan, }, module::{FuncIdx, FuncTypeIdx, MemoryIdx, ModuleHeader, TableIdx, WasmiValueType}, Engine, Error, FuncType, TrapCode, ValType, }; use alloc::vec::Vec; use core::mem; use wasmparser::{MemArg, WasmFeatures}; /// Type concerned with translating from Wasm bytecode to Wasmi bytecode. #[derive(Debug)] pub struct FuncTranslator { /// The reference to the Wasm module function under construction. func: FuncIdx, /// The engine for which the function is compiled. /// /// # Note /// /// Technically this is not needed since the information is redundant given via /// the `module` field. However, this acts like a faster access since `module` /// only holds a weak reference to the engine. engine: Engine, /// The immutable Wasmi module resources. module: ModuleHeader, /// This represents the reachability of the currently translated code. /// /// - `true`: The currently translated code is reachable. /// - `false`: The currently translated code is unreachable and can be skipped. /// /// # Note /// /// Visiting the Wasm `Else` or `End` control flow operator resets /// reachability to `true` again. reachable: bool, /// Wasm value and control stack. stack: Stack, /// Types of local variables and function parameters. locals: LocalsRegistry, /// Wasm layout to map stack slots to Wasmi registers. layout: StackLayout, /// Slots and pins labels and tracks their users. labels: LabelRegistry, /// Constructs and encodes function instructions. instrs: InstrEncoder, /// Temporary buffer for operands. operands: Vec, /// Temporary buffer for immediate values. immediates: Vec, } /// Heap allocated data structured used by the [`FuncTranslator`]. #[derive(Debug, Default)] pub struct FuncTranslatorAllocations { /// Wasm value and control stack. stack: StackAllocations, /// Types of local variables and function parameters. locals: LocalsRegistry, /// Wasm layout to map stack slots to Wasmi registers. layout: StackLayout, /// Slots and pins labels and tracks their users. labels: LabelRegistry, /// Constructs and encodes function instructions. instrs: InstrEncoderAllocations, /// Temporary buffer for operands. operands: Vec, /// Temporary buffer for immediate values. immediates: Vec, } impl Reset for FuncTranslatorAllocations { fn reset(&mut self) { self.stack.reset(); self.locals.reset(); self.layout.reset(); self.labels.reset(); self.instrs.reset(); self.operands.clear(); self.immediates.clear(); } } impl WasmTranslator<'_> for FuncTranslator { type Allocations = FuncTranslatorAllocations; fn setup(&mut self, _bytes: &[u8]) -> Result { Ok(false) } fn features(&self) -> WasmFeatures { self.engine.config().wasm_features() } fn translate_locals( &mut self, amount: u32, value_type: wasmparser::ValType, ) -> Result<(), Error> { let ty = WasmiValueType::from(value_type).into_inner(); self.register_locals(amount, ty)?; Ok(()) } fn finish_translate_locals(&mut self) -> Result<(), Error> { Ok(()) } fn update_pos(&mut self, _pos: usize) {} fn finish( mut self, finalize: impl FnOnce(CompiledFuncEntity), ) -> Result { // Note: `update_branch_offsets` might change `frame_size` so we need to compute it prior. // // Context: // This only happens if the function has so many instructions that some conditional branch // operators need to be encoded as their fallbacks which requires to allocate more function // local constant values, thus increasing the size of the function frame. self.update_branch_offsets()?; let Some(frame_size) = self.frame_size() else { return Err(Error::from(TranslationError::AllocatedTooManySlots)); }; finalize(CompiledFuncEntity::new( frame_size, self.instrs.drain(), self.layout.consts(), )); Ok(self.into_allocations()) } } impl ReusableAllocations for FuncTranslator { type Allocations = FuncTranslatorAllocations; fn into_allocations(self) -> Self::Allocations { Self::Allocations { stack: self.stack.into_allocations(), locals: self.locals, layout: self.layout, labels: self.labels, instrs: self.instrs.into_allocations(), operands: self.operands, immediates: self.immediates, } } } impl FuncTranslator { /// Creates a new [`FuncTranslator`]. pub fn new( func: FuncIdx, module: ModuleHeader, alloc: FuncTranslatorAllocations, ) -> Result { let Some(engine) = module.engine().upgrade() else { panic!( "cannot compile function since engine does no longer exist: {:?}", module.engine() ) }; let FuncTranslatorAllocations { stack, locals, layout, labels, instrs, operands, immediates, } = alloc.into_reset(); let stack = Stack::new(&engine, stack); let instrs = InstrEncoder::new(&engine, instrs); let mut translator = Self { func, engine, module, reachable: true, stack, locals, layout, labels, instrs, operands, immediates, }; translator.init_func_body_block()?; translator.init_func_params()?; Ok(translator) } /// Initializes the function body enclosing control block. fn init_func_body_block(&mut self) -> Result<(), Error> { let func_ty = self.module.get_type_of_func(self.func); let block_ty = BlockType::func_type(func_ty); let end_label = self.labels.new_label(); let consume_fuel = self.instrs.push_consume_fuel_instr()?; self.stack .push_func_block(block_ty, end_label, consume_fuel)?; Ok(()) } /// Initializes the function's parameters. fn init_func_params(&mut self) -> Result<(), Error> { for ty in self.func_type().params() { self.register_locals(1, *ty)?; } Ok(()) } /// Slots an `amount` of local variables of type `ty`. fn register_locals(&mut self, amount: u32, ty: ValType) -> Result<(), Error> { let Ok(amount) = usize::try_from(amount) else { panic!( "failed to register {amount} local variables of type {ty:?}: out of bounds `usize`" ) }; self.locals.register(amount, ty)?; self.stack.register_locals(amount)?; self.layout.register_locals(amount)?; Ok(()) } /// Returns the frame size of the to-be-compiled function. /// /// Returns `None` if the frame size is out of bounds. fn frame_size(&self) -> Option { let frame_size = self .stack .max_height() .checked_add(self.locals.len())? .checked_add(self.layout.consts().len())?; u16::try_from(frame_size).ok() } /// 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<(), Error> { for (user, offset) in self.labels.resolved_users() { self.instrs .update_branch_offset(user, offset?, &mut self.layout)?; } Ok(()) } /// Returns the [`FuncType`] of the function that is currently translated. fn func_type(&self) -> FuncType { self.func_type_with(FuncType::clone) } /// Applies `f` to the [`FuncType`] of the function that is currently translated. fn func_type_with(&self, f: impl FnOnce(&FuncType) -> R) -> R { self.resolve_func_type_with(self.func, f) } /// Returns the [`FuncType`] of the function at `func_index`. fn resolve_func_type(&self, func_index: FuncIdx) -> FuncType { self.resolve_func_type_with(func_index, FuncType::clone) } /// Applies `f` to the [`FuncType`] of the function at `func_index`. fn resolve_func_type_with(&self, func_index: FuncIdx, f: impl FnOnce(&FuncType) -> R) -> R { let dedup_func_type = self.module.get_type_of_func(func_index); self.engine().resolve_func_type(dedup_func_type, f) } /// Resolves the [`FuncType`] at the given Wasm module `type_index`. fn resolve_type(&self, type_index: u32) -> FuncType { let func_type_idx = FuncTypeIdx::from(type_index); let dedup_func_type = self.module.get_func_type(func_type_idx); self.engine() .resolve_func_type(dedup_func_type, Clone::clone) } /// Returns the [`SlotSpan`] of a call instruction before manipulating the operand stack. fn call_regspan(&self, len_params: usize) -> Result { let height = self.stack.height(); let Some(start) = height.checked_sub(len_params) else { panic!("operand stack underflow while evaluating call `SlotSpan`"); }; let start = self.layout.temp_to_reg(OperandIdx::from(start))?; Ok(SlotSpan::new(start)) } /// Push `results` as [`TempOperand`] onto the [`Stack`] tagged to `instr`. /// /// Returns the [`SlotSpan`] identifying the pushed operands if any. fn push_results( &mut self, instr: Instr, results: &[ValType], ) -> Result, Error> { let (first, rest) = match results.split_first() { Some((first, rest)) => (first, rest), None => return Ok(None), }; let first = self.stack.push_temp(*first, Some(instr))?; for result in rest { self.stack.push_temp(*result, Some(instr))?; } let start = self.layout.temp_to_reg(first)?; Ok(Some(SlotSpan::new(start))) } /// Returns the [`Engine`] for which the function is compiled. fn engine(&self) -> &Engine { &self.engine } /// Copy the top-most `len` operands to [`Operand::Temp`] values. /// /// # Note /// /// - The top-most `len` operands on the [`Stack`] will be [`Operand::Temp`] upon completion. /// - Does nothing if an [`Operand`] is already an [`Operand::Temp`]. fn move_operands_to_temp( &mut self, len: usize, consume_fuel: Option, ) -> Result<(), Error> { for n in 0..len { let operand = self.stack.operand_to_temp(n); self.copy_operand_to_temp(operand, consume_fuel)?; } Ok(()) } /// Convert all branch params up to `depth` to [`Operand::Temp`]. /// /// # Note /// /// - The top-most `depth` operands on the [`Stack`] will be [`Operand::Temp`] upon completion. /// - Does nothing if an [`Operand`] is already an [`Operand::Temp`]. fn copy_branch_params( &mut self, target: &impl ControlFrameBase, consume_fuel_instr: Option, ) -> Result<(), Error> { let len_branch_params = target.len_branch_params(&self.engine); let Some(branch_results) = self.frame_results(target)? else { return Ok(()); }; self.encode_copies(branch_results, len_branch_params, consume_fuel_instr)?; Ok(()) } /// Pushes the temporary results of the control `frame` onto the [`Stack`]. /// /// # Note /// /// - Before pushing the results, the [`Stack`] is truncated to the `frame`'s height. /// - Not all control frames have temporary results, e.g. Wasm `loop`s, Wasm `if`s with /// a compile-time known branch or Wasm `block`s that are never branched to, do not /// require to call this function. fn push_frame_results(&mut self, frame: &impl ControlFrameBase) -> Result<(), Error> { let height = frame.height(); self.stack.trunc(height); frame .ty() .func_type_with(&self.engine, |func_ty| -> Result<(), Error> { for result in func_ty.results() { self.stack.push_temp(*result, None)?; } Ok(()) })?; Ok(()) } /// Encodes a copy instruction for the top-most `len_values` on the stack to `results`. /// /// # Note /// /// - This does _not_ pop values from the stack or manipulate the stack otherwise. /// - This might allocate new function local constant values if necessary. /// - This does _not_ encode a copy if the copy is a no-op. fn encode_copies( &mut self, results: SlotSpan, len_values: u16, consume_fuel_instr: Option, ) -> Result<(), Error> { match len_values { 0 => Ok(()), 1 => { let result = results.head(); let value = self.stack.peek(0); self.encode_copy(result, value, consume_fuel_instr)?; Ok(()) } 2 => { let (val0, val1) = self.stack.peek2(); self.encode_copy2(results, val0, val1, consume_fuel_instr)?; Ok(()) } _ => self.encode_copy_many(results, len_values, consume_fuel_instr), } } /// Encodes a single copy instruction. /// /// # Note /// /// This won't encode a copy if `result` and `value` yields a no-op copy. fn encode_copy( &mut self, result: Slot, value: Operand, consume_fuel_instr: Option, ) -> Result, Error> { let Some(copy_instr) = Self::make_copy_instr(result, value, &mut self.layout)? else { // Case: no-op copy instruction return Ok(None); }; if let Some(fused_copy) = self.try_merge_copies(copy_instr)? { // Case: successfully merged the copy instruction with its succeeding one. return Ok(Some(fused_copy)); } let instr = self.instrs .push_instr(copy_instr, consume_fuel_instr, FuelCostsProvider::base)?; Ok(Some(instr)) } /// Tries to merge `copy_instr` with the succeeding copy instruction if one exists. /// /// - Returns `None` if merging was not applicable. /// - Returns `Some(last_instr)` if merging was successful. fn try_merge_copies(&mut self, copy_instr: Op) -> Result, Error> { let Op::Copy { result, value } = copy_instr else { // Case: `copy_instr` is not fusable. return Ok(None); }; let Some(last_instr) = self.instrs.last_instr() else { // Case: no `last_instr` to fuse with. return Ok(None); }; let last_copy = *self.instrs.get(last_instr); let fused_copy = match last_copy { Op::Copy { .. } => Self::try_merge_copy_instr(last_copy, result, value), Op::Copy2 { .. } => Self::try_merge_copy2_instr(last_copy, result, value), Op::CopySpan { .. } => Self::try_merge_copy_span_instr(last_copy, result, value), _ => return Ok(None), }; if let Some(fused_copy) = fused_copy { let success = self.instrs.try_replace_instr(last_instr, fused_copy)?; debug_assert!(success); return Ok(Some(last_instr)); } Ok(None) } /// Tries to merge two [`Op::Copy`] instructions and returns the result. /// /// Returns `None` if merging was not possible. fn try_merge_copy_instr(last_copy: Op, result: Slot, value: Slot) -> Option { let Op::Copy { result: last_result, value: last_value, } = last_copy else { // Case: `last_copy` does not refer to a mergable copy instruction. return None; }; // Try to fuse to a `copy2` instruction. if value == last_result { // Case: cannot merge since the succeeding copy overwrites the result of `copy_instr` return None; } if result == last_result.next() { // Case: we can append `copy_instrs`. return Some(Op::copy2_ext(SlotSpan::new(last_result), last_value, value)); } if result == last_result.prev() { // Case: we can prepend `copy_instr`. return Some(Op::copy2_ext(SlotSpan::new(result), value, last_value)); } None } /// Tries to merge an [`Op::Copy2`] and an [`Op::Copy`] and returns the result. /// /// Returns `None` if merging was not possible. fn try_merge_copy2_instr(last_copy: Op, result: Slot, value: Slot) -> Option { let Op::Copy2 { results, values } = last_copy else { // Case: `last_copy` does not refer to a mergable copy instruction. return None; }; // Try to fuse to a `copy_span` instruction. let [last_result0, last_result1] = results.to_array(); let [last_value0, last_value1] = values; if last_value0.next() != last_value1 { // Case: last `copy2` instruction itself is not convertible to a `copy_span`. return None; } if result == last_result1.next() && value == last_value1.next() { // Case: we can append `copy_instr`. if value == last_result0 || value == last_result1 { // Case: cannot merge since `value` is overwritten by `last_copy`. return None; } let results = SlotSpan::new(last_result0); let values = SlotSpan::new(last_value0); let len = 3_u16; debug_assert!(!SlotSpan::has_overlapping_copies(results, values, len)); return Some(Op::copy_span(results, values, len)); } // Note: must not prepend copy when fusing since this may break the copy order // which breaks semantic equivalence. So only appending copies is allowed. None } /// Tries to merge an [`Op::CopySpan`] and an [`Op::Copy`] and returns the result. /// /// Returns `None` if merging was not possible. fn try_merge_copy_span_instr(last_copy: Op, result: Slot, value: Slot) -> Option { let Op::CopySpan { results, values, len, } = last_copy else { // Case: `last_copy` does not refer to a mergable copy instruction. return None; }; let last_result0 = results.head(); let last_value0 = values.head(); // Try to fuse to a larger `copy_span` instruction. if result == last_result0.next_n(len) && value == last_value0.next_n(len) { // Case: we can append `copy_instr`. let new_len = len + 1; if SlotSpan::has_overlapping_copies(results, values, new_len) { // Case: cannot merge since resulting `copy_span` has overlapping copies. return None; } return Some(Op::copy_span(results, values, new_len)); } // Note: must not prepend copy when fusing since this may break the copy order // which breaks semantic equivalence. So only appending copies is allowed. None } /// Returns the copy instruction to copy the given `operand` to `result`. /// /// Returns `None` if the resulting copy instruction is a no-op. fn make_copy_instr( result: Slot, value: Operand, layout: &mut StackLayout, ) -> Result, Error> { let instr = match value { Operand::Temp(value) => { let value = layout.temp_to_reg(value.operand_index())?; if result == value { // Case: no-op copy return Ok(None); } Op::copy(result, value) } Operand::Local(value) => { let value = layout.local_to_reg(value.local_index())?; if result == value { // Case: no-op copy return Ok(None); } Op::copy(result, value) } Operand::Immediate(value) => Self::make_copy_imm_instr(result, value.val(), layout)?, }; Ok(Some(instr)) } /// Returns the copy instruction to copy the given immediate `value` to `result`. fn make_copy_imm_instr( result: Slot, value: TypedVal, layout: &mut StackLayout, ) -> Result { let instr = match value.ty() { ValType::I32 => Op::copy_imm32(result, i32::from(value)), ValType::I64 => { let value = i64::from(value); match >::try_from(value) { Ok(value) => Op::copy_i64imm32(result, value), Err(_) => { let value = layout.const_to_reg(value)?; Op::copy(result, value) } } } ValType::F32 => Op::copy_imm32(result, f32::from(value)), ValType::F64 => { let value = f64::from(value); match >::try_from(value) { Ok(value) => Op::copy_f64imm32(result, value), Err(_) => { let value = layout.const_to_reg(value)?; Op::copy(result, value) } } } ValType::V128 | ValType::FuncRef | ValType::ExternRef => { let value = layout.const_to_reg(value)?; Op::copy(result, value) } }; Ok(instr) } /// Encode a copy instruction that copies 2 values. /// /// # Note /// /// This won't encode a copy if the resulting copy instruction is a no-op. fn encode_copy2( &mut self, results: SlotSpan, val0: Operand, val1: Operand, consume_fuel_instr: Option, ) -> Result<(), Error> { let val0 = self.layout.operand_to_reg(val0)?; let val1 = self.layout.operand_to_reg(val1)?; let result0 = results.head(); let result1 = result0.next(); if result0 == val0 && result1 == val1 { // Case: no-op copy instruction return Ok(()); } self.instrs.push_instr( Op::copy2_ext(results, val0, val1), consume_fuel_instr, FuelCostsProvider::base, )?; Ok(()) } /// Encode a copy instruction that copies a contiguous span of values. /// /// # Note /// /// This won't encode a copy if the resulting copy instruction is a no-op. fn encode_copy_span( &mut self, results: SlotSpan, values: SlotSpan, len: u16, consume_fuel_instr: Option, ) -> Result<(), Error> { if results == values { // Case: results and values are equal and therefore the copy is a no-op return Ok(()); } debug_assert!(!SlotSpan::has_overlapping_copies(results, values, len)); self.instrs.push_instr( Op::copy_span(results, values, len), consume_fuel_instr, |costs| costs.fuel_for_copying_values(u64::from(len)), )?; Ok(()) } /// Encode a copy instruction that copies many values. /// /// # Note /// /// - This won't encode a copy if the resulting copy instruction is a no-op. /// - Encodes either `copy`, `copy2`, `copy_span` or `copy_many` depending on the amount /// of noop copies between `results` and `values`. fn encode_copy_many( &mut self, results: SlotSpan, len: u16, consume_fuel_instr: Option, ) -> Result<(), Error> { self.peek_operands_into_buffer(usize::from(len)); let values = &self.operands[..]; let (results, values) = Self::copy_many_strip_noop_start(results, values, &self.layout)?; let values = Self::copy_many_strip_noop_end(results, values, &self.layout)?; debug_assert!(!Self::has_overlapping_copies( results, values, &self.layout )?); match values { [] => Ok(()), [val0] => { let result = results.head(); let value = *val0; self.encode_copy(result, value, consume_fuel_instr)?; Ok(()) } [val0, val1] => self.encode_copy2(results, *val0, *val1, consume_fuel_instr), [val0, val1, rest @ ..] => { debug_assert!(!rest.is_empty()); if let Some(values) = Self::try_form_regspan_of(values, &self.layout)? { return self.encode_copy_span(results, values, len, consume_fuel_instr); } let val0 = self.layout.operand_to_reg(*val0)?; let val1 = self.layout.operand_to_reg(*val1)?; self.instrs.push_instr( Op::copy_many_ext(results, val0, val1), consume_fuel_instr, |costs| costs.fuel_for_copying_values(u64::from(len)), )?; self.instrs.encode_register_list(rest, &mut self.layout)?; Ok(()) } } } /// Tries to strip noop copies from the start of the `copy_many`. /// /// Returns the stripped `results` [`SlotSpan`] and `values` slice of [`Operand`]s. fn copy_many_strip_noop_start<'a>( results: SlotSpan, values: &'a [Operand], layout: &StackLayout, ) -> Result<(SlotSpan, &'a [Operand]), Error> { let mut result = results.head(); let mut values = values; while let Some((value, rest)) = values.split_first() { let value = match value { Operand::Local(value) => layout.local_to_reg(value.local_index())?, Operand::Temp(value) => layout.temp_to_reg(value.operand_index())?, Operand::Immediate(_) => { // Immediate values will never yield no-op copies. break; } }; if result != value { // Can no longer strip no-op copies from the start. break; } result = result.next(); values = rest; } Ok((SlotSpan::new(result), values)) } /// Tries to strip noop copies from the end of the `copy_many`. /// /// Returns the stripped `values` slice of [`Operand`]s. fn copy_many_strip_noop_end<'a>( results: SlotSpan, values: &'a [Operand], layout: &StackLayout, ) -> Result<&'a [Operand], Error> { let Ok(len) = u16::try_from(values.len()) else { panic!("out of bounds `copy_many` values length: {}", values.len()) }; let mut result = results.head().next_n(len); let mut values = values; while let Some((value, rest)) = values.split_last() { let value = match value { Operand::Local(value) => layout.local_to_reg(value.local_index())?, Operand::Temp(value) => layout.temp_to_reg(value.operand_index())?, Operand::Immediate(_) => { // Immediate values will never yield no-op copies. break; } }; result = result.prev(); if result != value { // Can no longer strip no-op copies from the end. break; } values = rest; } Ok(values) } /// Returns `true` if there are overlapping copies with `results` and `values`. /// /// # Examples /// /// - `[ 0 <- 1, 1 <- 1, 2 <- 4 ]` has no overlapping copies. /// - `[ 0 <- 1, 1 <- 0 ]` has overlapping copies since register `0` /// is written to in the first copy but read from in the next. /// - `[ 3 <- 1, 4 <- 2, 5 <- 3 ]` has overlapping copies since register `3` /// is written to in the first copy but read from in the third. fn has_overlapping_copies( results: SlotSpan, values: &[Operand], layout: &StackLayout, ) -> Result { if values.is_empty() { // An empty set of copies can never have overlapping copies. return Ok(false); } let Ok(len) = u16::try_from(values.len()) else { panic!("operand span too large: len={}", values.len()); }; let result0 = results.head(); for (result, value) in results.iter(len).zip(values) { // Note: We only have to check the register case since constant value // copies can never overlap. let value = match value { Operand::Local(value) => layout.local_to_reg(value.local_index())?, Operand::Temp(value) => layout.temp_to_reg(value.operand_index())?, Operand::Immediate(_) => { // Immediates are allocated as function local constants // which can not collide with the result registers. continue; } }; if result0 <= value && value < result { // Case: `value` is in the range of `result0..result` which // means it has been overwritten by previous copies, // thus we detected a collission. return Ok(true); } } // No copy collissions have been found. Ok(false) } /// Returns the results [`SlotSpan`] of the `frame` if any. fn frame_results(&self, frame: &impl ControlFrameBase) -> Result, Error> { Self::frame_results_impl(frame, &self.engine, &self.layout) } /// Returns the results [`SlotSpan`] of the `frame` if any. fn frame_results_impl( frame: &impl ControlFrameBase, engine: &Engine, layout: &StackLayout, ) -> Result, Error> { if frame.len_branch_params(engine) == 0 { return Ok(None); } let height = frame.height(); let start = layout.temp_to_reg(OperandIdx::from(height))?; let span = SlotSpan::new(start); Ok(Some(span)) } /// Returns `true` if the [`ControlFrame`] at `depth` requires copying for its branch parameters. /// /// # Note /// /// Some instructions can be encoded in a more efficient way if no branch parameter copies are required. fn requires_branch_param_copies(&self, depth: usize) -> bool { let frame = self.stack.peek_control(depth); let len_branch_params = usize::from(frame.len_branch_params(&self.engine)); let frame_height = frame.height(); let height_matches = frame_height == (self.stack.height() - len_branch_params); let only_temps = (0..len_branch_params) .map(|depth| self.stack.peek(depth)) .all(|o| o.is_temp()); let can_avoid_copies = height_matches && only_temps; !can_avoid_copies } /// Pins the `label` to the next [`Instr`]. fn pin_label(&mut self, label: LabelRef) { self.labels .pin_label(label, self.instrs.next_instr()) .unwrap_or_else(|err| panic!("failed to pin label to next instruction: {err}")); } /// Convert the [`Operand`] at `depth` into an [`Operand::Temp`] by copying if necessary. /// /// # Note /// /// Does nothing if the [`Operand`] is already an [`Operand::Temp`]. fn copy_operand_to_temp( &mut self, operand: Operand, consume_fuel: Option, ) -> Result<(), Error> { let result = self.layout.temp_to_reg(operand.index())?; self.encode_copy(result, operand, consume_fuel)?; Ok(()) } /// Efficiently converts the `operand` to a [`Slot`] if it is an immediate. /// /// # Note /// /// - Preferrably, this encodes the immediate `operand` into a `copy` instruction /// with the immediate encoded inline. /// - If the immediate `operand` cannot be encoded as `copy` with inline immediate /// a function local constant [`Slot`] will be allocated and returned. /// - Returns the associated [`Slot`] if `operand` is an [`Operand::Temp`] or [`Operand::Local`]. fn immediate_to_reg(&mut self, operand: Operand) -> Result { match operand { Operand::Local(operand) => self.layout.local_to_reg(operand.local_index()), Operand::Temp(operand) => self.layout.temp_to_reg(operand.operand_index()), Operand::Immediate(operand) => { let value = operand.val(); let result = self.layout.temp_to_reg(operand.operand_index())?; match Self::make_copy_imm_instr(result, value, &mut self.layout)? { Op::Copy { value, .. } => { // Case: not possible to craft a `copy` instruction // with an inline immediate, so we can return // the allocated function local constant [`Slot`] // instead. Ok(value) } copy_instr => { let consume_fuel = self.stack.consume_fuel_instr(); self.instrs.push_instr( copy_instr, consume_fuel, FuelCostsProvider::base, )?; Ok(result) } } } } } /// Preserves all local operands on the stack. /// /// # Note /// /// This works by encoding copy instructions to `temp` register space. fn preserve_all_locals(&mut self) -> Result<(), Error> { let consume_fuel_instr = self.stack.consume_fuel_instr(); for local in self.stack.preserve_all_locals() { debug_assert!(matches!(local, Operand::Local(_))); let result = self.layout.temp_to_reg(local.index())?; let Some(copy_instr) = Self::make_copy_instr(result, local, &mut self.layout)? else { unreachable!("`result` and `local` refer to different stack spaces"); }; self.instrs .push_instr(copy_instr, consume_fuel_instr, FuelCostsProvider::base)?; } Ok(()) } /// Pushes the `instr` to the function with the associated `fuel_costs`. fn push_instr( &mut self, instr: Op, fuel_costs: impl FnOnce(&FuelCostsProvider) -> u64, ) -> Result { let consume_fuel = self.stack.consume_fuel_instr(); let instr = self.instrs.push_instr(instr, consume_fuel, fuel_costs)?; Ok(instr) } /// Pushes the `instr` to the function with the associated `fuel_costs`. fn push_instr_with_result( &mut self, result_ty: ValType, make_instr: impl FnOnce(Slot) -> Op, fuel_costs: impl FnOnce(&FuelCostsProvider) -> u64, ) -> Result<(), Error> { let consume_fuel_instr = self.stack.consume_fuel_instr(); let expected_iidx = self.instrs.next_instr(); let result = self .layout .temp_to_reg(self.stack.push_temp(result_ty, Some(expected_iidx))?)?; let actual_iidx = self.instrs .push_instr(make_instr(result), consume_fuel_instr, fuel_costs)?; assert_eq!(expected_iidx, actual_iidx); Ok(()) } /// Pushes a binary instruction with a result and associated fuel costs. fn push_binary_instr_with_result( &mut self, result_ty: ValType, lhs: Operand, rhs: Operand, make_instr: impl FnOnce(Slot, Slot, Slot) -> Op, fuel_costs: impl FnOnce(&FuelCostsProvider) -> u64, ) -> Result<(), Error> { debug_assert_eq!(lhs.ty(), rhs.ty()); let lhs = self.layout.operand_to_reg(lhs)?; let rhs = self.layout.operand_to_reg(rhs)?; self.push_instr_with_result(result_ty, |result| make_instr(result, lhs, rhs), fuel_costs) } /// Pushes an instruction parameter `param` to the list of instructions. fn push_param(&mut self, param: Op) -> Result<(), Error> { self.instrs.push_param(param); Ok(()) } /// Populate the `buffer` with the `table` targets including the `table` default target. /// /// Returns a shared slice to the `buffer` after it has been filled. /// /// # Note /// /// The `table` default target is pushed last to the `buffer`. fn copy_targets_from_br_table( table: &wasmparser::BrTable, buffer: &mut Vec, ) -> Result<(), Error> { let default_target = table.default(); buffer.clear(); for target in table.targets() { buffer.push(TypedVal::from(target?)); } buffer.push(TypedVal::from(default_target)); Ok(()) } /// Encodes a Wasm `br_table` that does not copy branching values. /// /// # Note /// /// Upon call the `immediates` buffer contains all `br_table` target values. fn encode_br_table_0(&mut self, table: wasmparser::BrTable, index: Slot) -> Result<(), Error> { debug_assert_eq!(self.immediates.len(), (table.len() + 1) as usize); self.push_instr( Op::branch_table_0(index, table.len() + 1), FuelCostsProvider::base, )?; // Encode the `br_table` targets: let targets = &self.immediates[..]; for target in targets { let Ok(depth) = usize::try_from(u32::from(*target)) else { panic!("out of bounds `br_table` target does not fit `usize`: {target:?}"); }; let mut frame = self.stack.peek_control_mut(depth).control_frame(); let offset = self .labels .try_resolve_label(frame.label(), self.instrs.next_instr())?; self.instrs.push_param(Op::branch(offset)); frame.branch_to(); } Ok(()) } /// Encodes a Wasm `br_table` that has to copy `len_values` branching values. /// /// # Note /// /// Upon call the `immediates` buffer contains all `br_table` target values. fn encode_br_table_n( &mut self, table: wasmparser::BrTable, index: Slot, len_values: u16, ) -> Result<(), Error> { debug_assert_eq!(self.immediates.len(), (table.len() + 1) as usize); let consume_fuel_instr = self.stack.consume_fuel_instr(); let values = self.try_form_regspan_or_move(usize::from(len_values), consume_fuel_instr)?; self.push_instr( Op::branch_table_span(index, table.len() + 1), FuelCostsProvider::base, )?; self.push_param(Op::slot_span(BoundedSlotSpan::new(values, len_values)))?; // Encode the `br_table` targets: let targets = &self.immediates[..]; for target in targets { let Ok(depth) = usize::try_from(u32::from(*target)) else { panic!("out of bounds `br_table` target does not fit `usize`: {target:?}"); }; let mut frame = self.stack.peek_control_mut(depth).control_frame(); let Some(results) = Self::frame_results_impl(&frame, &self.engine, &self.layout)? else { panic!("must have frame results since `br_table` requires to copy values"); }; let offset = self .labels .try_resolve_label(frame.label(), self.instrs.next_instr())?; self.instrs .push_param(Op::branch_table_target(results, offset)); frame.branch_to(); } Ok(()) } /// Encodes a generic return instruction. fn encode_return(&mut self, consume_fuel: Option) -> Result { let len_results = self.func_type_with(FuncType::len_results); let instr = match len_results { 0 => Op::Return, 1 => match self.stack.peek(0) { Operand::Local(operand) => { let value = self.layout.local_to_reg(operand.local_index())?; Op::return_reg(value) } Operand::Temp(operand) => { let value = self.layout.temp_to_reg(operand.operand_index())?; Op::return_reg(value) } Operand::Immediate(operand) => { let val = operand.val(); match operand.ty() { ValType::I32 => Op::return_imm32(i32::from(val)), ValType::I64 => match >::try_from(i64::from(val)) { Ok(value) => Op::return_i64imm32(value), Err(_) => { let value = self.layout.const_to_reg(val)?; Op::return_reg(value) } }, ValType::F32 => Op::return_imm32(f32::from(val)), ValType::F64 => match >::try_from(f64::from(val)) { Ok(value) => Op::return_f64imm32(value), Err(_) => { let value = self.layout.const_to_reg(val)?; Op::return_reg(value) } }, ValType::V128 | ValType::FuncRef | ValType::ExternRef => { let value = self.layout.const_to_reg(val)?; Op::return_reg(value) } } } }, 2 => { let (v0, v1) = self.stack.peek2(); let v0 = self.layout.operand_to_reg(v0)?; let v1 = self.layout.operand_to_reg(v1)?; Op::return_reg2_ext(v0, v1) } 3 => { let (v0, v1, v2) = self.stack.peek3(); let v0 = self.layout.operand_to_reg(v0)?; let v1 = self.layout.operand_to_reg(v1)?; let v2 = self.layout.operand_to_reg(v2)?; Op::return_reg3_ext(v0, v1, v2) } _ => return self.encode_return_many(len_results, consume_fuel), }; let instr = self .instrs .push_instr(instr, consume_fuel, FuelCostsProvider::base)?; Ok(instr) } /// Store the top-most [`Operand`]s on the [`Stack`] into the operands buffer. fn peek_operands_into_buffer(&mut self, len: usize) { self.operands.clear(); self.operands.extend(self.stack.peek_n(len)); } /// Encodes an [`Op::ReturnMany`] for `len` values. /// /// # Panics /// /// If `len` is not greater than or equal to 4. fn encode_return_many( &mut self, len: u16, consume_fuel_instr: Option, ) -> Result { self.peek_operands_into_buffer(usize::from(len)); if let Some(values) = Self::try_form_regspan_of(&self.operands, &self.layout)? { let values = BoundedSlotSpan::new(values, len); return self.instrs.push_instr( Op::return_span(values), consume_fuel_instr, FuelCostsProvider::base, ); } let [v0, v1, v2, rest @ ..] = &self.operands[..] else { unreachable!("encode_return_many (pre-condition): len >= 4") }; let v0 = self.layout.operand_to_reg(*v0)?; let v1 = self.layout.operand_to_reg(*v1)?; let v2 = self.layout.operand_to_reg(*v2)?; let return_instr = self.instrs.push_instr( Op::return_many_ext(v0, v1, v2), consume_fuel_instr, FuelCostsProvider::base, )?; self.instrs.encode_register_list(rest, &mut self.layout)?; Ok(return_instr) } /// Tries to form a [`SlotSpan`] from the top-most `n` operands on the [`Stack`]. /// /// Returns `None` if forming a [`SlotSpan`] was not possible. fn try_form_regspan(&self, len: usize) -> Result, Error> { Self::try_form_regspan_of(self.stack.peek_n(len), &self.layout) } /// Tries to form a [`SlotSpan`] from the `values` slice of [`Operand`]s. /// /// Returns `None` if forming a [`SlotSpan`] was not possible. fn try_form_regspan_of( values: impl IntoIterator, layout: &StackLayout, ) -> Result, Error> where T: AsRef, { let mut values = values.into_iter(); let Some(head) = values.next() else { return Ok(None); }; let mut head = match head.as_ref() { Operand::Local(start) => layout.local_to_reg(start.local_index())?, Operand::Temp(start) => layout.temp_to_reg(start.operand_index())?, Operand::Immediate(_) => return Ok(None), }; let start = head; for value in values { let cur = match value.as_ref() { Operand::Immediate(_) => return Ok(None), Operand::Local(value) => layout.local_to_reg(value.local_index())?, Operand::Temp(value) => layout.temp_to_reg(value.operand_index())?, }; if head != cur.prev() { return Ok(None); } head = cur; } Ok(Some(SlotSpan::new(start))) } /// Tries to form a [`SlotSpan`] from the top-most `len` operands on the [`Stack`] or copy to temporaries. /// /// Returns `None` if forming a [`SlotSpan`] was not possible. fn try_form_regspan_or_move( &mut self, len: usize, consume_fuel_instr: Option, ) -> Result { if let Some(span) = self.try_form_regspan(len)? { return Ok(span); } self.move_operands_to_temp(len, consume_fuel_instr)?; let Some(span) = self.try_form_regspan(len)? else { unreachable!("the top-most `len` operands are now temporaries thus `SlotSpan` forming should succeed") }; Ok(span) } /// Translates the end of a Wasm `block` control frame. fn translate_end_block(&mut self, frame: BlockControlFrame) -> Result<(), Error> { let consume_fuel_instr = frame.consume_fuel_instr(); if frame.is_branched_to() { if self.reachable { self.copy_branch_params(&frame, consume_fuel_instr)?; } self.push_frame_results(&frame)?; } if let Err(err) = self .labels .pin_label(frame.label(), self.instrs.next_instr()) { panic!("failed to pin label: {err}") } self.reachable |= frame.is_branched_to(); if self.reachable && self.stack.is_control_empty() { self.encode_return(consume_fuel_instr)?; } if frame.is_branched_to() { // No need to reset `last_instr` if there was no branch to the end of a Wasm `block`. self.instrs.reset_last_instr(); } Ok(()) } /// Translates the end of a Wasm `loop` control frame. fn translate_end_loop(&mut self, _frame: LoopControlFrame) -> Result<(), Error> { debug_assert!(!self.stack.is_control_empty()); // Nothing needs to be done since Wasm `loop` control frames always only have a single exit. // // Note: no need to reset `last_instr` since end of `loop` is not a control flow boundary. Ok(()) } /// Translates the end of a Wasm `if` control frame. fn translate_end_if(&mut self, frame: IfControlFrame) -> Result<(), Error> { debug_assert!(!self.stack.is_control_empty()); let is_end_of_then_reachable = self.reachable; let IfReachability::Both { else_label } = frame.reachability() else { let is_end_reachable = match frame.reachability() { IfReachability::OnlyThen => self.reachable, IfReachability::OnlyElse => true, IfReachability::Both { .. } => unreachable!(), }; return self.translate_end_if_or_else_only(frame, is_end_reachable); }; let len_results = frame.ty().len_results(self.engine()); let has_results = len_results >= 1; if is_end_of_then_reachable && has_results { let consume_fuel_instr = frame.consume_fuel_instr(); self.copy_branch_params(&frame, consume_fuel_instr)?; let end_offset = self .labels .try_resolve_label(frame.label(), self.instrs.next_instr()) .unwrap(); self.instrs.push_instr( Op::branch(end_offset), consume_fuel_instr, FuelCostsProvider::base, )?; } self.labels .try_pin_label(else_label, self.instrs.next_instr()); self.stack.push_else_operands(&frame)?; if has_results { // We haven't visited the `else` block and thus the `else` // providers are still on the auxiliary stack and need to // be popped. We use them to restore the stack to the state // when entering the `if` block so that we can properly copy // the `else` results to were they are expected. let consume_fuel_instr = self.instrs.push_consume_fuel_instr()?; self.copy_branch_params(&frame, consume_fuel_instr)?; } self.push_frame_results(&frame)?; self.labels .pin_label(frame.label(), self.instrs.next_instr()) .unwrap(); self.reachable = true; // Need to reset `last_instr` since end of `if` is a control flow boundary. self.instrs.reset_last_instr(); Ok(()) } /// Translates the end of a Wasm `else` control frame. fn translate_end_else(&mut self, frame: ElseControlFrame) -> Result<(), Error> { debug_assert!(!self.stack.is_control_empty()); match frame.reachability() { ElseReachability::OnlyThen { is_end_of_then_reachable, } => { return self.translate_end_if_or_else_only(frame, is_end_of_then_reachable); } ElseReachability::OnlyElse => { return self.translate_end_if_or_else_only(frame, self.reachable); } _ => {} }; let end_of_then_reachable = frame.is_end_of_then_reachable(); let end_of_else_reachable = self.reachable; let reachable = match (end_of_then_reachable, end_of_else_reachable) { (false, false) => frame.is_branched_to(), _ => true, }; if end_of_else_reachable { let consume_fuel_instr: Option = frame.consume_fuel_instr(); self.copy_branch_params(&frame, consume_fuel_instr)?; } self.push_frame_results(&frame)?; self.labels .pin_label(frame.label(), self.instrs.next_instr()) .unwrap(); self.reachable = reachable; // Need to reset `last_instr` since end of `else` is a control flow boundary. self.instrs.reset_last_instr(); Ok(()) } /// Translates the end of a Wasm `else` control frame where only one branch is known to be reachable. fn translate_end_if_or_else_only( &mut self, frame: impl ControlFrameBase, end_is_reachable: bool, ) -> Result<(), Error> { if frame.is_branched_to() { if end_is_reachable { let consume_fuel_instr = frame.consume_fuel_instr(); self.copy_branch_params(&frame, consume_fuel_instr)?; } self.push_frame_results(&frame)?; } self.labels .pin_label(frame.label(), self.instrs.next_instr()) .unwrap(); self.reachable = end_is_reachable || frame.is_branched_to(); if frame.is_branched_to() { // No need to reset `last_instr` if there was no branch to the // end of a Wasm `if` where only `then` or `else` is reachable. self.instrs.reset_last_instr(); } Ok(()) } /// Translates the end of an unreachable Wasm control frame. fn translate_end_unreachable(&mut self, _frame: ControlFrameKind) -> Result<(), Error> { debug_assert!(!self.stack.is_control_empty()); // We reset `last_instr` out of caution in case there is a control flow boundary. self.instrs.reset_last_instr(); Ok(()) } /// Translate the Wasm `local.set` and `local.tee` operations. /// /// # Note /// /// This applies op-code fusion that replaces the result of the previous instruction /// instead of encoding a copy instruction for the `local.set` or `local.tee` if possible. fn translate_local_set(&mut self, local_index: u32, push_result: bool) -> Result<(), Error> { bail_unreachable!(self); let input = self.stack.pop(); let input_ty = input.ty(); if let Operand::Local(input) = input { if u32::from(input.local_index()) == local_index { // Case: `(local.set $n (local.get $n))` is a no-op so we can ignore it. // // Note: This does not require any preservation since it won't change // the value of `local $n`. if push_result { // Need to push back input before we exit. self.stack.push_operand(input.into())?; } return Ok(()); } } let local_idx = LocalIdx::from(local_index); let consume_fuel_instr = self.stack.consume_fuel_instr(); for preserved in self.stack.preserve_locals(local_idx) { let result = self.layout.temp_to_reg(preserved)?; let value = self.layout.local_to_reg(local_idx)?; self.instrs.push_instr( Op::copy(result, value), consume_fuel_instr, FuelCostsProvider::base, )?; } if push_result { match input { Operand::Immediate(input) => { self.stack.push_immediate(input.val())?; } _ => { self.stack.push_local(local_idx, input_ty)?; } } } if self.try_replace_result(local_idx, input)? { // Case: it was possible to replace the result of the previous // instructions so no copy instruction is required. return Ok(()); } // At this point we need to encode a copy instruction. let result = self.layout.local_to_reg(local_idx)?; let outcome = self.encode_copy(result, input, consume_fuel_instr)?; debug_assert!( outcome.is_some(), "no-op copy cases have been filtered out already" ); Ok(()) } /// Tries to replace the result of the previous instruction with `new_result` if possible. /// /// Returns `Ok(true)` if replacement was successful and `Ok(false)` otherwise. fn try_replace_result( &mut self, new_result: LocalIdx, old_result: Operand, ) -> Result { let result = self.layout.local_to_reg(new_result)?; let old_result = match old_result { Operand::Immediate(_) => { // Case: cannot replace immediate value result. return Ok(false); } Operand::Local(_) => { // Case: cannot replace local with another local due to observable behavior. return Ok(false); } Operand::Temp(operand) => self.layout.temp_to_reg(operand.operand_index())?, }; self.instrs .try_replace_result(result, old_result, &self.layout, &self.module) } /// Encodes an unconditional Wasm `branch` instruction. fn encode_br(&mut self, label: LabelRef) -> Result { let instr = self.instrs.next_instr(); let offset = self.labels.try_resolve_label(label, instr)?; let br_instr = self.push_instr(Op::branch(offset), FuelCostsProvider::base)?; Ok(br_instr) } /// Encodes a `i32.eqz`+`br_if` or `if` conditional branch instruction. fn encode_br_eqz(&mut self, condition: Operand, label: LabelRef) -> Result<(), Error> { self.encode_br_if(condition, label, true) } /// Encodes a `br_if` conditional branch instruction. fn encode_br_nez(&mut self, condition: Operand, label: LabelRef) -> Result<(), Error> { self.encode_br_if(condition, label, false) } /// Encodes a generic `br_if` fused conditional branch instruction. fn encode_br_if( &mut self, condition: Operand, label: LabelRef, branch_eqz: bool, ) -> Result<(), Error> { if self.try_fuse_branch_cmp(condition, label, branch_eqz)? { return Ok(()); } let condition = match condition { Operand::Local(condition) => self.layout.local_to_reg(condition.local_index())?, Operand::Temp(condition) => self.layout.temp_to_reg(condition.operand_index())?, Operand::Immediate(condition) => { let condition = i32::from(condition.val()); let take_branch = match branch_eqz { true => condition == 0, false => condition != 0, }; match take_branch { true => { self.encode_br(label)?; self.reachable = false; return Ok(()); } false => return Ok(()), } } }; let instr = self.instrs.next_instr(); let offset = self.labels.try_resolve_label(label, instr)?; let instr = match BranchOffset16::try_from(offset) { Ok(offset) => match branch_eqz { true => Op::branch_i32_eq_imm16(condition, 0, offset), false => Op::branch_i32_ne_imm16(condition, 0, offset), }, Err(_) => { let zero = self.layout.const_to_reg(0_i32)?; let comparator = match branch_eqz { true => Comparator::I32Eq, false => Comparator::I32Ne, }; self.make_branch_cmp_fallback(comparator, condition, zero, offset)? } }; self.push_instr(instr, FuelCostsProvider::base)?; Ok(()) } /// Create an [`Op::BranchCmpFallback`]. fn make_branch_cmp_fallback( &mut self, cmp: Comparator, lhs: Slot, rhs: Slot, offset: BranchOffset, ) -> Result { let params = self .layout .const_to_reg(ComparatorAndOffset::new(cmp, offset))?; Ok(Op::branch_cmp_fallback(lhs, rhs, params)) } /// Try to fuse a cmp+branch [`Op`] with optional negation. fn try_fuse_branch_cmp( &mut self, condition: Operand, label: LabelRef, negate: bool, ) -> Result { let Some(last_instr) = self.instrs.last_instr() else { // Case: cannot fuse without a known last instruction return Ok(false); }; let Operand::Temp(condition) = condition else { // Case: cannot fuse non-temporary operands // - locals have observable behavior. // - immediates cannot be the result of a previous instruction. return Ok(false); }; let Some(origin) = condition.instr() else { // Case: cannot fuse temporary operands without origin instruction return Ok(false); }; if last_instr != origin { // Case: cannot fuse if last instruction does not match origin instruction return Ok(false); } debug_assert!(matches!(condition.ty(), ValType::I32 | ValType::I64)); let fused_instr = self.try_make_fused_branch_cmp_instr(origin, condition, label, negate)?; let Some(fused_instr) = fused_instr else { // Case: not possible to perform fusion with last instruction return Ok(false); }; assert!( self.instrs.try_replace_instr(origin, fused_instr)?, "op-code fusion must suceed at this point", ); Ok(true) } /// Try to return a fused cmp+branch [`Op`] from the given parameters. /// /// /// # Note /// /// - The `instr` parameter refers to the to-be-fused cmp instruction. /// - Returns `Ok(Some)` if cmp+branch fusion was successful. /// - Returns `Ok(None)`, otherwise. fn try_make_fused_branch_cmp_instr( &mut self, instr: Instr, condition: TempOperand, label: LabelRef, negate: bool, ) -> Result, Error> { let cmp_instr = *self.instrs.get(instr); let Some(result) = cmp_instr.compare_result() else { // Note: cannot fuse non-cmp instructions or cmp-instructions without result. return Ok(None); }; if matches!(self.layout.stack_space(result), StackSpace::Local) { // Note: cannot fuse cmp instructions with observable semantics. return Ok(None); } if result != self.layout.temp_to_reg(condition.operand_index())? { // Note: cannot fuse cmp instruction with a result that differs // from the condition operand. return Ok(None); } let cmp_instr = match negate { false => cmp_instr, true => match cmp_instr.negate_cmp_instr() { Some(negated) => negated, None => { // Note: cannot negate cmp instruction, thus not possible to fuse. return Ok(None); } }, }; let offset = self.labels.try_resolve_label(label, instr)?; let fused = cmp_instr .try_into_cmp_branch_instr(offset, &mut self.layout)? .expect("cmp+branch fusion must succeed"); Ok(Some(fused)) } /// Translates a unary Wasm instruction to Wasmi bytecode. fn translate_unary( &mut self, make_instr: fn(result: Slot, input: Slot) -> Op, consteval: fn(input: T) -> R, ) -> Result<(), Error> where T: From, R: Into + Typed, { bail_unreachable!(self); let input = self.stack.pop(); if let Operand::Immediate(input) = input { self.stack.push_immediate(consteval(input.val().into()))?; return Ok(()); } let input = self.layout.operand_to_reg(input)?; self.push_instr_with_result( ::TY, |result| make_instr(result, input), FuelCostsProvider::base, ) } /// Translates a unary Wasm instruction to Wasmi bytecode. fn translate_unary_fallible( &mut self, make_instr: fn(result: Slot, input: Slot) -> Op, consteval: fn(input: T) -> Result, ) -> Result<(), Error> where T: From, R: Into + Typed, { bail_unreachable!(self); let input = self.stack.pop(); if let Operand::Immediate(input) = input { let input = T::from(input.val()); match consteval(input) { Ok(result) => { self.stack.push_immediate(result)?; } Err(trap) => { self.translate_trap(trap)?; } } return Ok(()); } let input = self.layout.operand_to_reg(input)?; self.push_instr_with_result( ::TY, |result| make_instr(result, input), FuelCostsProvider::base, ) } /// Translate a generic Wasm reinterpret-like operation. /// /// # Note /// /// This Wasm operation is a no-op. Ideally we only have to change the types on the stack. fn translate_reinterpret(&mut self, consteval: fn(T) -> R) -> Result<(), Error> where T: From + Typed, R: Into + Typed, { bail_unreachable!(self); match self.stack.pop() { Operand::Local(input) => { debug_assert_eq!(input.ty(), ::TY); self.stack .push_local(input.local_index(), ::TY)?; } Operand::Temp(input) => { debug_assert_eq!(input.ty(), ::TY); self.stack.push_temp(::TY, None)?; } Operand::Immediate(input) => { let input: T = input.val().into(); self.stack.push_immediate(consteval(input))?; } } Ok(()) } /// Creates a new 16-bit encoded [`Input16`] from the given `value`. pub fn make_imm16(&mut self, value: T) -> Result, Error> where T: Into + Copy + TryInto>, { match value.try_into() { Ok(rhs) => Ok(Input::Immediate(rhs)), Err(_) => { let rhs = self.layout.const_to_reg(value)?; Ok(Input::Slot(rhs)) } } } /// Create a new generic [`Input`] from the given `operand`. fn make_input( &mut self, operand: Operand, f: impl FnOnce(&mut Self, TypedVal) -> Result, Error>, ) -> Result, Error> { let reg = match operand { Operand::Local(operand) => self.layout.local_to_reg(operand.local_index())?, Operand::Temp(operand) => self.layout.temp_to_reg(operand.operand_index())?, Operand::Immediate(operand) => return f(self, operand.val()), }; Ok(Input::Slot(reg)) } /// Converts the `provider` to a 16-bit index-type constant value. /// /// # Note /// /// - Turns immediates that cannot be 16-bit encoded into function local constants. /// - The behavior is different whether `memory64` is enabled or disabled. pub(super) fn make_index16( &mut self, operand: Operand, index_type: IndexType, ) -> Result, Error> { let value = match operand { Operand::Immediate(value) => value.val(), operand => { debug_assert_eq!(operand.ty(), index_type.ty()); let reg = self.layout.operand_to_reg(operand)?; return Ok(Input::Slot(reg)); } }; match index_type { IndexType::I64 => { if let Ok(value) = Const16::try_from(u64::from(value)) { return Ok(Input::Immediate(value)); } } IndexType::I32 => { if let Ok(value) = Const16::try_from(u32::from(value)) { return Ok(Input::Immediate(>::cast(value))); } } } let reg = self.layout.const_to_reg(value)?; Ok(Input::Slot(reg)) } /// Converts the `provider` to a 32-bit index-type constant value. /// /// # Note /// /// - Turns immediates that cannot be 32-bit encoded into function local constants. /// - The behavior is different whether `memory64` is enabled or disabled. pub(super) fn make_index32( &mut self, operand: Operand, index_type: IndexType, ) -> Result, Error> { let value = match operand { Operand::Immediate(value) => value.val(), operand => { debug_assert_eq!(operand.ty(), index_type.ty()); let reg = self.layout.operand_to_reg(operand)?; return Ok(Input::Slot(reg)); } }; match index_type { IndexType::I64 => { if let Ok(value) = Const32::try_from(u64::from(value)) { return Ok(Input::Immediate(value)); } } IndexType::I32 => { let value32 = Const32::from(u32::from(value)); return Ok(Input::Immediate(>::cast(value32))); } } let reg = self.layout.const_to_reg(value)?; Ok(Input::Slot(reg)) } /// Evaluates `consteval(lhs, rhs)` and pushed either its result or tranlates a `trap`. fn translate_binary_consteval_fallible( &mut self, lhs: ImmediateOperand, rhs: ImmediateOperand, consteval: impl FnOnce(T, T) -> Result, ) -> Result<(), Error> where T: From, R: Into, { let lhs: T = lhs.val().into(); let rhs: T = rhs.val().into(); match consteval(lhs, rhs) { Ok(value) => { self.stack.push_immediate(value)?; } Err(trap) => { self.translate_trap(trap)?; } } Ok(()) } /// Evaluates `consteval(lhs, rhs)` and pushed either its result or tranlates a `trap`. fn translate_binary_consteval( &mut self, lhs: ImmediateOperand, rhs: ImmediateOperand, consteval: fn(T, T) -> R, ) -> Result<(), Error> where T: From, R: Into, { self.translate_binary_consteval_fallible::(lhs, rhs, |lhs, rhs| { Ok(consteval(lhs, rhs)) }) } /// Convenience method to tell that there is no custom optimization. fn no_opt_ri(&mut self, _lhs: Operand, _rhs: T) -> Result { Ok(false) } /// Translates a commutative binary Wasm operator to Wasmi bytecode. fn translate_binary_commutative( &mut self, make_rr: fn(result: Slot, lhs: Slot, rhs: Slot) -> Op, make_ri: fn(result: Slot, lhs: Slot, rhs: Const16) -> Op, consteval: fn(T, T) -> R, opt_ri: fn(this: &mut Self, lhs: Operand, rhs: T) -> Result, ) -> Result<(), Error> where T: WasmInteger + TryInto>, R: Into + Typed, { bail_unreachable!(self); match self.stack.pop2() { (Operand::Immediate(lhs), Operand::Immediate(rhs)) => { self.translate_binary_consteval::(lhs, rhs, consteval) } (val, Operand::Immediate(imm)) | (Operand::Immediate(imm), val) => { let rhs = imm.val().into(); if opt_ri(self, val, rhs)? { return Ok(()); } let lhs = self.layout.operand_to_reg(val)?; let rhs16 = self.make_imm16(rhs)?; self.push_instr_with_result( ::TY, |result| match rhs16 { Input::Immediate(rhs) => make_ri(result, lhs, rhs), Input::Slot(rhs) => make_rr(result, lhs, rhs), }, FuelCostsProvider::base, ) } (lhs, rhs) => self.push_binary_instr_with_result( ::TY, lhs, rhs, make_rr, FuelCostsProvider::base, ), } } /// Translates integer division and remainder Wasm operators to Wasmi bytecode. fn translate_divrem( &mut self, make_instr: fn(result: Slot, lhs: Slot, rhs: Slot) -> Op, make_instr_imm16_rhs: fn( result: Slot, lhs: Slot, rhs: Const16<::NonZero>, ) -> Op, make_instr_imm16_lhs: fn(result: Slot, lhs: Const16, rhs: Slot) -> Op, consteval: fn(T, T) -> Result, ) -> Result<(), Error> where T: WasmInteger, { bail_unreachable!(self); match self.stack.pop2() { (Operand::Immediate(lhs), Operand::Immediate(rhs)) => { self.translate_binary_consteval_fallible::(lhs, rhs, consteval) } (lhs, Operand::Immediate(rhs)) => { let lhs = self.layout.operand_to_reg(lhs)?; let rhs = T::from(rhs.val()); let Some(non_zero_rhs) = ::non_zero(rhs) else { // Optimization: division by zero always traps return self.translate_trap(TrapCode::IntegerDivisionByZero); }; let rhs16 = self.make_imm16(non_zero_rhs)?; self.push_instr_with_result( ::TY, |result| match rhs16 { Input::Immediate(rhs) => make_instr_imm16_rhs(result, lhs, rhs), Input::Slot(rhs) => make_instr(result, lhs, rhs), }, FuelCostsProvider::base, ) } (Operand::Immediate(lhs), rhs) => { let lhs = T::from(lhs.val()); let lhs16 = self.make_imm16(lhs)?; let rhs = self.layout.operand_to_reg(rhs)?; self.push_instr_with_result( ::TY, |result| match lhs16 { Input::Immediate(lhs) => make_instr_imm16_lhs(result, lhs, rhs), Input::Slot(lhs) => make_instr(result, lhs, rhs), }, FuelCostsProvider::base, ) } (lhs, rhs) => self.push_binary_instr_with_result( ::TY, lhs, rhs, make_instr, FuelCostsProvider::base, ), } } /// Translates binary non-commutative Wasm operators to Wasmi bytecode. fn translate_binary( &mut self, make_instr: fn(result: Slot, lhs: Slot, rhs: Slot) -> Op, make_instr_imm16_rhs: fn(result: Slot, lhs: Slot, rhs: Const16) -> Op, make_instr_imm16_lhs: fn(result: Slot, lhs: Const16, rhs: Slot) -> Op, consteval: fn(T, T) -> R, ) -> Result<(), Error> where T: WasmInteger, R: Into + Typed, { bail_unreachable!(self); match self.stack.pop2() { (Operand::Immediate(lhs), Operand::Immediate(rhs)) => { self.translate_binary_consteval::(lhs, rhs, consteval) } (lhs, Operand::Immediate(rhs)) => { let lhs = self.layout.operand_to_reg(lhs)?; let rhs = T::from(rhs.val()); let rhs16 = self.make_imm16(rhs)?; self.push_instr_with_result( ::TY, |result| match rhs16 { Input::Immediate(rhs) => make_instr_imm16_rhs(result, lhs, rhs), Input::Slot(rhs) => make_instr(result, lhs, rhs), }, FuelCostsProvider::base, ) } (Operand::Immediate(lhs), rhs) => { let lhs = T::from(lhs.val()); let lhs16 = self.make_imm16(lhs)?; let rhs = self.layout.operand_to_reg(rhs)?; self.push_instr_with_result( ::TY, |result| match lhs16 { Input::Immediate(lhs) => make_instr_imm16_lhs(result, lhs, rhs), Input::Slot(lhs) => make_instr(result, lhs, rhs), }, FuelCostsProvider::base, ) } (lhs, rhs) => self.push_binary_instr_with_result( ::TY, lhs, rhs, make_instr, FuelCostsProvider::base, ), } } /// Translates Wasm `i{32,64}.sub` operators to Wasmi bytecode. fn translate_isub( &mut self, make_sub_rr: fn(result: Slot, lhs: Slot, rhs: Slot) -> Op, make_add_ri: fn(result: Slot, lhs: Slot, rhs: Const16) -> Op, make_sub_ir: fn(result: Slot, lhs: Const16, rhs: Slot) -> Op, consteval: fn(T, T) -> R, ) -> Result<(), Error> where T: WasmInteger, R: Into + Typed, { bail_unreachable!(self); match self.stack.pop2() { (Operand::Immediate(lhs), Operand::Immediate(rhs)) => { self.translate_binary_consteval::(lhs, rhs, consteval) } (lhs, Operand::Immediate(rhs)) => { let lhs = self.layout.operand_to_reg(lhs)?; let rhs = T::from(rhs.val()); let rhs16 = match rhs.wrapping_neg().try_into() { Ok(rhs) => Input::Immediate(rhs), Err(_) => { let rhs = self.layout.const_to_reg(rhs)?; Input::Slot(rhs) } }; self.push_instr_with_result( ::TY, |result| match rhs16 { Input::Immediate(rhs) => make_add_ri(result, lhs, rhs), Input::Slot(rhs) => make_sub_rr(result, lhs, rhs), }, FuelCostsProvider::base, ) } (Operand::Immediate(lhs), rhs) => { let lhs = T::from(lhs.val()); let lhs16 = self.make_imm16(lhs)?; let rhs = self.layout.operand_to_reg(rhs)?; self.push_instr_with_result( ::TY, |result| match lhs16 { Input::Immediate(lhs) => make_sub_ir(result, lhs, rhs), Input::Slot(lhs) => make_sub_rr(result, lhs, rhs), }, FuelCostsProvider::base, ) } (lhs, rhs) => self.push_binary_instr_with_result( ::TY, lhs, rhs, make_sub_rr, FuelCostsProvider::base, ), } } /// Translates Wasm shift and rotate operators to Wasmi bytecode. fn translate_shift( &mut self, make_instr: fn(result: Slot, lhs: Slot, rhs: Slot) -> Op, make_instr_imm16_rhs: fn( result: Slot, lhs: Slot, rhs: ::Output, ) -> Op, make_instr_imm16_lhs: fn(result: Slot, lhs: Const16, rhs: Slot) -> Op, consteval: fn(T, T) -> T, ) -> Result<(), Error> where T: WasmInteger + IntoShiftAmount>, Const16: From, { bail_unreachable!(self); match self.stack.pop2() { (Operand::Immediate(lhs), Operand::Immediate(rhs)) => { self.translate_binary_consteval::(lhs, rhs, consteval) } (lhs, Operand::Immediate(rhs)) => { let Some(rhs) = T::into_shift_amount(rhs.val().into()) else { // Optimization: Shifting or rotating by zero bits is a no-op. self.stack.push_operand(lhs)?; return Ok(()); }; let lhs = self.layout.operand_to_reg(lhs)?; self.push_instr_with_result( ::TY, |result| make_instr_imm16_rhs(result, lhs, rhs), FuelCostsProvider::base, ) } (Operand::Immediate(lhs), rhs) => { let lhs = T::from(lhs.val()); if lhs.is_zero() { // Optimization: Shifting or rotating a zero value is a no-op. self.stack.push_immediate(lhs)?; return Ok(()); } let lhs16 = self.make_imm16(lhs)?; let rhs = self.layout.operand_to_reg(rhs)?; self.push_instr_with_result( ::TY, |result| match lhs16 { Input::Immediate(lhs) => make_instr_imm16_lhs(result, lhs, rhs), Input::Slot(lhs) => make_instr(result, lhs, rhs), }, FuelCostsProvider::base, ) } (lhs, rhs) => self.push_binary_instr_with_result( ::TY, lhs, rhs, make_instr, FuelCostsProvider::base, ), } } /// Translate a binary float Wasm operation. fn translate_fbinary( &mut self, make_instr: fn(result: Slot, lhs: Slot, rhs: Slot) -> Op, consteval: fn(T, T) -> R, ) -> Result<(), Error> where T: WasmFloat, R: Into + Typed, { bail_unreachable!(self); let (lhs, rhs) = self.stack.pop2(); if let (Operand::Immediate(lhs), Operand::Immediate(rhs)) = (lhs, rhs) { return self.translate_binary_consteval::(lhs, rhs, consteval); } self.push_binary_instr_with_result( ::TY, lhs, rhs, make_instr, FuelCostsProvider::base, ) } /// Translate Wasmi `{f32,f64}.copysign` instructions. /// /// # Note /// /// - This applies some optimization that are valid for copysign instructions. /// - Applies constant evaluation if both operands are constant values. fn translate_fcopysign( &mut self, make_instr: fn(result: Slot, lhs: Slot, rhs: Slot) -> Op, make_instr_imm: fn(result: Slot, lhs: Slot, rhs: Sign) -> Op, consteval: fn(T, T) -> T, ) -> Result<(), Error> where T: WasmFloat, { bail_unreachable!(self); match self.stack.pop2() { (Operand::Immediate(lhs), Operand::Immediate(rhs)) => { self.translate_binary_consteval::(lhs, rhs, consteval) } (lhs, Operand::Immediate(rhs)) => { let lhs = self.layout.operand_to_reg(lhs)?; let sign = T::from(rhs.val()).sign(); self.push_instr_with_result( ::TY, |result| make_instr_imm(result, lhs, sign), FuelCostsProvider::base, ) } (lhs, rhs) => { if lhs.is_same(&rhs) { // Optimization: `copysign x x` is always just `x` self.stack.push_operand(lhs)?; return Ok(()); } self.push_binary_instr_with_result( ::TY, lhs, rhs, make_instr, FuelCostsProvider::base, ) } } } /// Translates a generic trap instruction. fn translate_trap(&mut self, trap: TrapCode) -> Result<(), Error> { self.push_instr(Op::trap(trap), FuelCostsProvider::base)?; self.reachable = false; Ok(()) } /// Translates a Wasm `select` or `select ` instruction. /// /// # Note /// /// - This applies constant propagation in case `condition` is a constant value. /// - If both `lhs` and `rhs` are equal registers or constant values `lhs` is forwarded. /// - Fuses compare instructions with the associated select instructions if possible. fn translate_select(&mut self, type_hint: Option) -> Result<(), Error> { bail_unreachable!(self); let (true_val, false_val, condition) = self.stack.pop3(); if let Some(type_hint) = type_hint { debug_assert_eq!(true_val.ty(), type_hint); debug_assert_eq!(false_val.ty(), type_hint); } let ty = true_val.ty(); if true_val.is_same(&false_val) { // Optimization: both `lhs` and `rhs` either are the same register or constant values and // thus `select` will always yield this same value irrespective of the condition. self.stack.push_operand(true_val)?; return Ok(()); } if let Operand::Immediate(condition) = condition { // Optimization: since condition is a constant value we can const-fold the `select` // instruction and simply push the selected value back to the provider stack. let condition = i32::from(condition.val()) != 0; let selected = match condition { true => true_val, false => false_val, }; if let Operand::Temp(selected) = selected { // Case: the selected operand is a temporary which needs to be copied // if it was the `false_val` since it changed its index. This is // not the case for the `true_val` since `true_val` is the first // value popped from the stack. if !condition { let selected = self.layout.temp_to_reg(selected.operand_index())?; self.push_instr_with_result( ty, |result| Op::copy(result, selected), FuelCostsProvider::base, )?; return Ok(()); } } self.stack.push_operand(selected)?; return Ok(()); } let condition = self.layout.operand_to_reg(condition)?; let mut true_val = self.immediate_to_reg(true_val)?; let mut false_val = self.immediate_to_reg(false_val)?; match self .instrs .try_fuse_select(ty, condition, &self.layout, &mut self.stack)? { Some(swap_operands) => { if swap_operands { mem::swap(&mut true_val, &mut false_val); } } None => { self.push_instr_with_result( ty, |result| Op::select_i32_eq_imm16(result, condition, 0_i16), FuelCostsProvider::base, )?; mem::swap(&mut true_val, &mut false_val); } }; self.push_param(Op::slot2_ext(true_val, false_val))?; Ok(()) } /// Create either [`Op::CallIndirectParams`] or [`Op::CallIndirectParamsImm16`] depending on the inputs. fn call_indirect_params(&mut self, index: Operand, table_index: u32) -> Result { let table_type = *self.module.get_type_of_table(TableIdx::from(table_index)); let index = self.make_index16(index, table_type.index_ty())?; let instr = match index { Input::Slot(index) => Op::call_indirect_params(index, table_index), Input::Immediate(index) => Op::call_indirect_params_imm16(index, table_index), }; Ok(instr) } /// Tries to fuse a Wasm `i32.eqz` (or `i32.eq` with 0 `rhs` value) instruction. /// /// Returns /// /// - `Ok(true)` if the intruction fusion was successful. /// - `Ok(false)` if instruction fusion could not be applied. /// - `Err(_)` if an error occurred. pub fn fuse_eqz(&mut self, lhs: Operand, rhs: T) -> Result { self.fuse_commutative_cmp_with(lhs, rhs, NegateCmpInstr::negate_cmp_instr) } /// Tries to fuse a Wasm `i32.ne` instruction with 0 `rhs` value. /// /// Returns /// /// - `Ok(true)` if the intruction fusion was successful. /// - `Ok(false)` if instruction fusion could not be applied. /// - `Err(_)` if an error occurred. pub fn fuse_nez(&mut self, lhs: Operand, rhs: T) -> Result { self.fuse_commutative_cmp_with(lhs, rhs, LogicalizeCmpInstr::logicalize_cmp_instr) } /// Tries to fuse a `i{32,64}`.{eq,ne}` instruction with `rhs` of zero. /// /// Generically applies `f` onto the fused last instruction. /// /// Returns /// /// - `Ok(true)` if the intruction fusion was successful. /// - `Ok(false)` if instruction fusion could not be applied. /// - `Err(_)` if an error occurred. pub fn fuse_commutative_cmp_with( &mut self, lhs: Operand, rhs: T, try_fuse: fn(cmp: &Op) -> Option, ) -> Result { if !rhs.is_zero() { // Case: cannot fuse with non-zero `rhs` return Ok(false); } let Some(last_instr) = self.instrs.last_instr() else { // Case: cannot fuse without registered last instruction return Ok(false); }; let Operand::Temp(lhs) = lhs else { // Case: cannot fuse non-temporary operands // - locals have observable behavior. // - immediates cannot be the result of a previous instruction. return Ok(false); }; let Some(origin) = lhs.instr() else { // Case: `lhs` has no origin instruciton, thus not possible to fuse. return Ok(false); }; if origin != last_instr { // Case: `lhs`'s origin instruction does not match the last instruction return Ok(false); } let lhs_reg = self.layout.temp_to_reg(lhs.operand_index())?; let last_instruction = self.instrs.get(last_instr); let Some(result) = last_instruction.compare_result() else { // Case: cannot fuse non-cmp instructions return Ok(false); }; if result != lhs_reg { // Case: the `cmp` instruction does not feed into the `eqz` and cannot be fused return Ok(false); } let Some(negated) = try_fuse(last_instruction) else { // Case: the `cmp` instruction cannot be negated return Ok(false); }; // Need to push back `lhs` but with its type adjusted to be `i32` // since that's the return type of `i{32,64}.{eqz,eq,ne}`. let result_idx = self.stack.push_temp(ValType::I32, lhs.instr())?; // Need to replace `cmp` instruction result register since it might // have been misaligned if `lhs` originally referred to the zero operand. let new_result = self.layout.temp_to_reg(result_idx)?; let Some(negated) = negated.replace_cmp_result(new_result) else { unreachable!("`negated` has been asserted as `cmp` instruction"); }; if !self.instrs.try_replace_instr(last_instr, negated)? { unreachable!("`negated` has been asserted to be `last_instr`"); } Ok(true) } /// Translates a Wasm `load` instruction to Wasmi bytecode. /// /// # Note /// /// This chooses the right encoding for the given `load` instruction. /// If `ptr+offset` is a constant value the address is pre-calculated. /// /// # Usage /// /// Used for translating the following Wasm operators to Wasmi bytecode: /// /// - `{i32, i64, f32, f64}.load` /// - `i32.{load8_s, load8_u, load16_s, load16_u}` /// - `i64.{load8_s, load8_u, load16_s, load16_u load32_s, load32_u}` fn translate_load( &mut self, memarg: MemArg, loaded_ty: ValType, make_instr: fn(result: Slot, offset_lo: Offset64Lo) -> Op, make_instr_offset16: fn(result: Slot, ptr: Slot, offset: Offset16) -> Op, make_instr_at: fn(result: Slot, address: Address32) -> Op, ) -> Result<(), Error> { bail_unreachable!(self); let (memory, offset) = Self::decode_memarg(memarg); let ptr = self.stack.pop(); let (ptr, offset) = match ptr { Operand::Immediate(ptr) => { let ptr = ptr.val(); let Some(address) = self.effective_address(memory, ptr, offset) else { return self.translate_trap(TrapCode::MemoryOutOfBounds); }; if let Ok(address) = Address32::try_from(address) { self.push_instr_with_result( loaded_ty, |result| make_instr_at(result, address), FuelCostsProvider::load, )?; if !memory.is_default() { self.push_param(Op::memory_index(memory))?; } return Ok(()); } // Case: we cannot use specialized encoding and thus have to fall back // to the general case where `ptr` is zero and `offset` stores the // `ptr+offset` address value. let zero_ptr = self.layout.const_to_reg(0_u64)?; (zero_ptr, u64::from(address)) } ptr => { let ptr = self.layout.operand_to_reg(ptr)?; (ptr, offset) } }; if memory.is_default() { if let Ok(offset) = Offset16::try_from(offset) { self.push_instr_with_result( loaded_ty, |result| make_instr_offset16(result, ptr, offset), FuelCostsProvider::load, )?; return Ok(()); } } let (offset_hi, offset_lo) = Offset64::split(offset); self.push_instr_with_result( loaded_ty, |result| make_instr(result, offset_lo), FuelCostsProvider::load, )?; self.push_param(Op::slot_and_offset_hi(ptr, offset_hi))?; if !memory.is_default() { self.push_param(Op::memory_index(memory))?; } Ok(()) } /// Translates Wasm integer `store` and `storeN` instructions to Wasmi bytecode. /// /// # Note /// /// This chooses the most efficient encoding for the given `store` instruction. /// If `ptr+offset` is a constant value the pointer address is pre-calculated. /// /// # Usage /// /// Used for translating the following Wasm operators to Wasmi bytecode: /// /// - `{i32, i64}.{store, store8, store16, store32}` fn translate_istore_wrap( &mut self, memarg: MemArg, ) -> Result<(), Error> where T::Value: Copy + Wrap + From, T::Param: TryFrom + Into, { bail_unreachable!(self); let (ptr, value) = self.stack.pop2(); self.encode_istore_wrap::(memarg, ptr, value) } /// Encodes Wasm integer `store` and `storeN` instructions as Wasmi bytecode. fn encode_istore_wrap( &mut self, memarg: MemArg, ptr: Operand, value: Operand, ) -> Result<(), Error> where T::Value: Copy + Wrap + From, T::Param: TryFrom + Into, { let (memory, offset) = Self::decode_memarg(memarg); let (ptr, offset) = match ptr { Operand::Immediate(ptr) => { let ptr = ptr.val(); let Some(address) = self.effective_address(memory, ptr, offset) else { return self.translate_trap(TrapCode::MemoryOutOfBounds); }; if let Ok(address) = Address32::try_from(address) { return self.encode_istore_wrap_at::(memory, address, value); } // Case: we cannot use specialized encoding and thus have to fall back // to the general case where `ptr` is zero and `offset` stores the // `ptr+offset` address value. let zero_ptr = self.layout.const_to_reg(0_u64)?; (zero_ptr, u64::from(address)) } ptr => { let ptr = self.layout.operand_to_reg(ptr)?; (ptr, offset) } }; if memory.is_default() { if let Some(_instr) = self.encode_istore_wrap_mem0::(ptr, offset, value)? { return Ok(()); } } let (offset_hi, offset_lo) = Offset64::split(offset); let (instr, param) = { match value { Operand::Immediate(value) => { let value = value.val(); match T::Param::try_from(T::Value::from(value).wrap()).ok() { Some(value) => ( T::store_imm(ptr, offset_lo), Op::imm16_and_offset_hi(value, offset_hi), ), None => ( T::store(ptr, offset_lo), Op::slot_and_offset_hi(self.layout.const_to_reg(value)?, offset_hi), ), } } value => { let value = self.layout.operand_to_reg(value)?; ( T::store(ptr, offset_lo), Op::slot_and_offset_hi(value, offset_hi), ) } } }; self.push_instr(instr, FuelCostsProvider::store)?; self.push_param(param)?; if !memory.is_default() { self.push_param(Op::memory_index(memory))?; } Ok(()) } /// Encodes a Wasm integer `store` and `storeN` instructions as Wasmi bytecode. /// /// # Note /// /// This is used in cases where the `ptr` is a known constant value. fn encode_istore_wrap_at( &mut self, memory: index::Memory, address: Address32, value: Operand, ) -> Result<(), Error> where T::Value: Copy + From + Wrap, T::Param: TryFrom, { match value { Operand::Immediate(value) => { let value = value.val(); let wrapped = T::Value::from(value).wrap(); if let Ok(value) = T::Param::try_from(wrapped) { self.push_instr(T::store_at_imm(value, address), FuelCostsProvider::store)?; } else { let value = self.layout.const_to_reg(value)?; self.push_instr(T::store_at(value, address), FuelCostsProvider::store)?; } } value => { let value = self.layout.operand_to_reg(value)?; self.push_instr(T::store_at(value, address), FuelCostsProvider::store)?; } } if !memory.is_default() { self.push_param(Op::memory_index(memory))?; } Ok(()) } /// Encodes a Wasm integer `store` and `storeN` instructions as Wasmi bytecode. /// /// # Note /// /// This optimizes for cases where the Wasm linear memory that is operated on is known /// to be the default memory. /// Returns `Some` in case the optimized instructions have been encoded. fn encode_istore_wrap_mem0( &mut self, ptr: Slot, offset: u64, value: Operand, ) -> Result, Error> where T::Value: Copy + From + Wrap, T::Param: TryFrom, { let Ok(offset16) = Offset16::try_from(offset) else { return Ok(None); }; let instr = match value { Operand::Immediate(value) => { let value = value.val(); let wrapped = T::Value::from(value).wrap(); match T::Param::try_from(wrapped) { Ok(value) => self.push_instr( T::store_offset16_imm(ptr, offset16, value), FuelCostsProvider::store, )?, Err(_) => { let value = self.layout.const_to_reg(value)?; self.push_instr( T::store_offset16(ptr, offset16, value), FuelCostsProvider::store, )? } } } value => { let value = self.layout.operand_to_reg(value)?; self.push_instr( T::store_offset16(ptr, offset16, value), FuelCostsProvider::store, )? } }; Ok(Some(instr)) } /// Translates a general Wasm `store` instruction to Wasmi bytecode. /// /// # Note /// /// This chooses the most efficient encoding for the given `store` instruction. /// If `ptr+offset` is a constant value the pointer address is pre-calculated. /// /// # Usage /// /// Used for translating the following Wasm operators to Wasmi bytecode: /// /// - `{f32, f64, v128}.store` fn translate_store( &mut self, memarg: MemArg, store: fn(ptr: Slot, offset_lo: Offset64Lo) -> Op, store_offset16: fn(ptr: Slot, offset: Offset16, value: Slot) -> Op, store_at: fn(value: Slot, address: Address32) -> Op, ) -> Result<(), Error> { bail_unreachable!(self); let (memory, offset) = Self::decode_memarg(memarg); let (ptr, value) = self.stack.pop2(); let (ptr, offset) = match ptr { Operand::Immediate(ptr) => { let Some(address) = self.effective_address(memory, ptr.val(), offset) else { return self.translate_trap(TrapCode::MemoryOutOfBounds); }; if let Ok(address) = Address32::try_from(address) { return self.encode_fstore_at(memory, address, value, store_at); } let zero_ptr = self.layout.const_to_reg(0_u64)?; (zero_ptr, u64::from(address)) } ptr => { let ptr = self.layout.operand_to_reg(ptr)?; (ptr, offset) } }; let (offset_hi, offset_lo) = Offset64::split(offset); let value = self.layout.operand_to_reg(value)?; if memory.is_default() { if let Ok(offset) = Offset16::try_from(offset) { self.push_instr(store_offset16(ptr, offset, value), FuelCostsProvider::store)?; return Ok(()); } } self.push_instr(store(ptr, offset_lo), FuelCostsProvider::store)?; self.push_param(Op::slot_and_offset_hi(value, offset_hi))?; if !memory.is_default() { self.push_param(Op::memory_index(memory))?; } Ok(()) } /// Encodes a Wasm `store` instruction with immediate address as Wasmi bytecode. /// /// # Note /// /// This is used in cases where the `ptr` is a known constant value. fn encode_fstore_at( &mut self, memory: index::Memory, address: Address32, value: Operand, make_instr_at: fn(value: Slot, address: Address32) -> Op, ) -> Result<(), Error> { let value = self.layout.operand_to_reg(value)?; self.push_instr(make_instr_at(value, address), FuelCostsProvider::store)?; if !memory.is_default() { self.push_param(Op::memory_index(memory))?; } Ok(()) } /// Returns the [`MemArg`] linear `memory` index and load/store `offset`. /// /// # Panics /// /// If the [`MemArg`] offset is not 32-bit. fn decode_memarg(memarg: MemArg) -> (index::Memory, u64) { let memory = index::Memory::from(memarg.memory); (memory, memarg.offset) } /// Returns the effective address `ptr+offset` if it is valid. fn effective_address(&self, mem: index::Memory, ptr: TypedVal, offset: u64) -> Option

{ let memory_type = *self .module .get_type_of_memory(MemoryIdx::from(u32::from(mem))); let ptr = match memory_type.is_64() { true => u64::from(ptr), false => u64::from(u32::from(ptr)), }; let Some(address) = ptr.checked_add(offset) else { // Case: address overflows any legal memory index. return None; }; if let Some(max) = memory_type.maximum() { // The memory's maximum size in bytes. let max_size = max << memory_type.page_size_log2(); if address > max_size { // Case: address overflows the memory's maximum size. return None; } } if !memory_type.is_64() && address >= 1 << 32 { // Case: address overflows the 32-bit memory index. return None; } let Ok(address) = Address::try_from(address) else { // Case: address is too big for the system to handle properly. return None; }; Some(address) } /// Translates a Wasm `i64.binop128` instruction from the `wide-arithmetic` proposal. fn translate_i64_binop128( &mut self, make_instr: fn(results: [Slot; 2], lhs_lo: Slot) -> Op, const_eval: fn(lhs_lo: i64, lhs_hi: i64, rhs_lo: i64, rhs_hi: i64) -> (i64, i64), ) -> Result<(), Error> { bail_unreachable!(self); let (rhs_lo, rhs_hi) = self.stack.pop2(); let (lhs_lo, lhs_hi) = self.stack.pop2(); if let ( Operand::Immediate(lhs_lo), Operand::Immediate(lhs_hi), Operand::Immediate(rhs_lo), Operand::Immediate(rhs_hi), ) = (lhs_lo, lhs_hi, rhs_lo, rhs_hi) { let (result_lo, result_hi) = const_eval( lhs_lo.val().into(), lhs_hi.val().into(), rhs_lo.val().into(), rhs_hi.val().into(), ); self.stack.push_immediate(result_lo)?; self.stack.push_immediate(result_hi)?; return Ok(()); } let rhs_lo = self.layout.operand_to_reg(rhs_lo)?; let rhs_hi = self.layout.operand_to_reg(rhs_hi)?; let lhs_lo = self.layout.operand_to_reg(lhs_lo)?; let lhs_hi = self.layout.operand_to_reg(lhs_hi)?; let result_lo = self.stack.push_temp(ValType::I64, None)?; let result_hi = self.stack.push_temp(ValType::I64, None)?; let result_lo = self.layout.temp_to_reg(result_lo)?; let result_hi = self.layout.temp_to_reg(result_hi)?; self.push_instr( make_instr([result_lo, result_hi], lhs_lo), FuelCostsProvider::base, )?; self.push_param(Op::slot3_ext(lhs_hi, rhs_lo, rhs_hi))?; Ok(()) } /// Translates a Wasm `i64.mul_wide_sx` instruction from the `wide-arithmetic` proposal. fn translate_i64_mul_wide_sx( &mut self, make_instr: fn(results: FixedSlotSpan<2>, lhs: Slot, rhs: Slot) -> Op, const_eval: fn(lhs: i64, rhs: i64) -> (i64, i64), signed: bool, ) -> Result<(), Error> { bail_unreachable!(self); let (lhs, rhs) = self.stack.pop2(); let (lhs, rhs) = match (lhs, rhs) { (Operand::Immediate(lhs), Operand::Immediate(rhs)) => { let (result_lo, result_hi) = const_eval(lhs.val().into(), rhs.val().into()); self.stack.push_immediate(result_lo)?; self.stack.push_immediate(result_hi)?; return Ok(()); } (lhs, Operand::Immediate(rhs)) => { let rhs = rhs.val(); if self.try_opt_i64_mul_wide_sx(lhs, rhs, signed)? { return Ok(()); } let lhs = self.layout.operand_to_reg(lhs)?; let rhs = self.layout.const_to_reg(rhs)?; (lhs, rhs) } (Operand::Immediate(lhs), rhs) => { let lhs = lhs.val(); if self.try_opt_i64_mul_wide_sx(rhs, lhs, signed)? { return Ok(()); } let lhs = self.layout.const_to_reg(lhs)?; let rhs = self.layout.operand_to_reg(rhs)?; (lhs, rhs) } (lhs, rhs) => { let lhs = self.layout.operand_to_reg(lhs)?; let rhs = self.layout.operand_to_reg(rhs)?; (lhs, rhs) } }; let result0 = self.stack.push_temp(ValType::I64, None)?; let _result1 = self.stack.push_temp(ValType::I64, None)?; let result0 = self.layout.temp_to_reg(result0)?; let Ok(results) = >::new(SlotSpan::new(result0)) else { return Err(Error::from(TranslationError::AllocatedTooManySlots)); }; self.push_instr(make_instr(results, lhs, rhs), FuelCostsProvider::base)?; Ok(()) } /// Try to optimize a `i64.mul_wide_sx` instruction with one [`Slot`] and one immediate input. /// /// - Returns `Ok(true)` if the optimiation was applied successfully. /// - Returns `Ok(false)` if no optimization was applied. fn try_opt_i64_mul_wide_sx( &mut self, lhs: Operand, rhs: TypedVal, signed: bool, ) -> Result { let rhs = i64::from(rhs); if rhs == 0 { // Case: `mul(x, 0)` or `mul(0, x)` always evaluates to 0. self.stack.push_immediate(0_i64)?; // lo-bits self.stack.push_immediate(0_i64)?; // hi-bits return Ok(true); } if rhs == 1 && !signed { // Case: `mul(x, 1)` or `mul(1, x)` always evaluates to just `x`. // This is only valid if `x` is not a singed (negative) value. let result = self.stack.push_operand(lhs)?; // lo-bits if matches!(lhs, Operand::Temp(_)) { // Case: `lhs` is temporary and thus might need a copy to its new result. let consume_fuel_instr = self.stack.consume_fuel_instr(); let result = self.layout.temp_to_reg(result)?; self.encode_copy(result, lhs, consume_fuel_instr)?; } self.stack.push_immediate(0_i64)?; // hi-bits return Ok(true); } Ok(false) } } wasmi-1.1.0/src/engine/translator/func/op.rs000064400000000000000000000123701046102023000171120ustar 00000000000000use crate::ir::{Address32, Offset16, Offset64Lo, Op, Slot}; /// Trait implemented by all Wasm operators that can be translated as wrapping store instructions. pub trait StoreWrapOperator { /// The type of the value to the stored. type Value; /// The type of the wrapped value. type Wrapped; /// The type of the value as (at most) 16-bit encoded instruction parameter. type Param; fn store(ptr: Slot, offset_lo: Offset64Lo) -> Op; fn store_imm(ptr: Slot, offset_lo: Offset64Lo) -> Op; fn store_offset16(ptr: Slot, offset: Offset16, value: Slot) -> Op; fn store_offset16_imm(ptr: Slot, offset: Offset16, value: Self::Param) -> Op; fn store_at(value: Slot, address: Address32) -> Op; fn store_at_imm(value: Self::Param, address: Address32) -> Op; } macro_rules! impl_store_wrap { ( $( impl StoreWrapOperator for $name:ident { type Value = $value_ty:ty; type Wrapped = $wrapped_ty:ty; type Param = $param_ty:ty; fn store = $store:expr; fn store_imm = $store_imm:expr; fn store_offset16 = $store_offset16:expr; fn store_offset16_imm = $store_offset16_imm:expr; fn store_at = $store_at:expr; fn store_at_imm = $store_at_imm:expr; } )* ) => { $( pub enum $name {} impl StoreWrapOperator for $name { type Value = $value_ty; type Wrapped = $wrapped_ty; type Param = $param_ty; fn store(ptr: Slot, offset_lo: Offset64Lo) -> Op { $store(ptr, offset_lo) } fn store_imm(ptr: Slot, offset_lo: Offset64Lo) -> Op { $store_imm(ptr, offset_lo) } fn store_offset16(ptr: Slot, offset: Offset16, value: Slot) -> Op { $store_offset16(ptr, offset, value) } fn store_offset16_imm(ptr: Slot, offset: Offset16, value: Self::Param) -> Op { $store_offset16_imm(ptr, offset, value) } fn store_at(value: Slot, address: Address32) -> Op { $store_at(value, address) } fn store_at_imm(value: Self::Param, address: Address32) -> Op { $store_at_imm(value, address) } } )* }; } impl_store_wrap! { impl StoreWrapOperator for I32Store { type Value = i32; type Wrapped = i32; type Param = i16; fn store = Op::store32; fn store_imm = Op::i32_store_imm16; fn store_offset16 = Op::store32_offset16; fn store_offset16_imm = Op::i32_store_offset16_imm16; fn store_at = Op::store32_at; fn store_at_imm = Op::i32_store_at_imm16; } impl StoreWrapOperator for I64Store { type Value = i64; type Wrapped = i64; type Param = i16; fn store = Op::store64; fn store_imm = Op::i64_store_imm16; fn store_offset16 = Op::store64_offset16; fn store_offset16_imm = Op::i64_store_offset16_imm16; fn store_at = Op::store64_at; fn store_at_imm = Op::i64_store_at_imm16; } impl StoreWrapOperator for I32Store8 { type Value = i32; type Wrapped = i8; type Param = i8; fn store = Op::i32_store8; fn store_imm = Op::i32_store8_imm; fn store_offset16 = Op::i32_store8_offset16; fn store_offset16_imm = Op::i32_store8_offset16_imm; fn store_at = Op::i32_store8_at; fn store_at_imm = Op::i32_store8_at_imm; } impl StoreWrapOperator for I32Store16 { type Value = i32; type Wrapped = i16; type Param = i16; fn store = Op::i32_store16; fn store_imm = Op::i32_store16_imm; fn store_offset16 = Op::i32_store16_offset16; fn store_offset16_imm = Op::i32_store16_offset16_imm; fn store_at = Op::i32_store16_at; fn store_at_imm = Op::i32_store16_at_imm; } impl StoreWrapOperator for I64Store8 { type Value = i64; type Wrapped = i8; type Param = i8; fn store = Op::i64_store8; fn store_imm = Op::i64_store8_imm; fn store_offset16 = Op::i64_store8_offset16; fn store_offset16_imm = Op::i64_store8_offset16_imm; fn store_at = Op::i64_store8_at; fn store_at_imm = Op::i64_store8_at_imm; } impl StoreWrapOperator for I64Store16 { type Value = i64; type Wrapped = i16; type Param = i16; fn store = Op::i64_store16; fn store_imm = Op::i64_store16_imm; fn store_offset16 = Op::i64_store16_offset16; fn store_offset16_imm = Op::i64_store16_offset16_imm; fn store_at = Op::i64_store16_at; fn store_at_imm = Op::i64_store16_at_imm; } impl StoreWrapOperator for I64Store32 { type Value = i64; type Wrapped = i32; type Param = i16; fn store = Op::i64_store32; fn store_imm = Op::i64_store32_imm16; fn store_offset16 = Op::i64_store32_offset16; fn store_offset16_imm = Op::i64_store32_offset16_imm16; fn store_at = Op::i64_store32_at; fn store_at_imm = Op::i64_store32_at_imm16; } } wasmi-1.1.0/src/engine/translator/func/simd/mod.rs000064400000000000000000000361271046102023000202150ustar 00000000000000use super::FuncTranslator; mod op; mod visit; use crate::{ core::{simd::IntoLaneIdx, FuelCostsProvider, Typed, TypedVal}, engine::translator::{ func::{utils::Input, Operand}, utils::{Instr, Wrap}, }, ir::{ index::{self, Memory}, Address32, IntoShiftAmount, Offset64, Offset64Lo, Offset8, Op, Slot, }, Error, TrapCode, ValType, V128, }; use wasmparser::MemArg; impl FuncTranslator { /// Generically translate any of the Wasm `simd` splat instructions. fn translate_simd_splat( &mut self, make_instr: fn(result: Slot, value: Slot) -> Op, const_eval: fn(Wrapped) -> V128, ) -> Result<(), Error> where T: From + Wrap, { bail_unreachable!(self); let value = self.stack.pop(); if let Operand::Immediate(value) = value { let value = T::from(value.val()).wrap(); let result = const_eval(value); self.stack.push_immediate(result)?; return Ok(()); }; let value = self.layout.operand_to_reg(value)?; self.push_instr_with_result( ValType::V128, |result| make_instr(result, value), FuelCostsProvider::simd, )?; Ok(()) } /// Generically translate any of the Wasm `simd` extract lane instructions. fn translate_extract_lane( &mut self, lane: u8, make_instr: fn(result: Slot, input: Slot, lane: T::LaneIdx) -> Op, const_eval: fn(input: V128, lane: T::LaneIdx) -> R, ) -> Result<(), Error> where R: Into + Typed, { bail_unreachable!(self); let Ok(lane) = ::try_from(lane) else { panic!("encountered out of bounds lane index: {lane}") }; let input = self.stack.pop(); if let Operand::Immediate(input) = input { let result = const_eval(input.val().into(), lane); self.stack.push_immediate(result)?; return Ok(()); }; let input = self.layout.operand_to_reg(input)?; self.push_instr_with_result( ::TY, |result| make_instr(result, input, lane), FuelCostsProvider::simd, )?; Ok(()) } /// Generically translate a Wasm SIMD replace lane instruction. #[allow(clippy::type_complexity)] fn translate_replace_lane(&mut self, lane: u8) -> Result<(), Error> where T::Item: IntoLaneIdx + From + Copy, T::Immediate: Copy, { bail_unreachable!(self); let Ok(lane) = <::LaneIdx>::try_from(lane) else { panic!("encountered out of bounds lane index: {lane}"); }; let (input, value) = self.stack.pop2(); if let (Operand::Immediate(x), Operand::Immediate(item)) = (input, value) { let result = T::const_eval(x.val().into(), lane, item.val().into()); self.stack.push_immediate(result)?; return Ok(()); } let input = self.layout.operand_to_reg(input)?; let value = self.make_input::(value, |this, value| { match T::value_to_imm(T::Item::from(value)) { Some(imm) => Ok(Input::Immediate(imm)), None => { let imm = this.layout.const_to_reg(value)?; Ok(Input::Slot(imm)) } } })?; let param = match value { Input::Slot(value) => Some(Op::slot(value)), Input::Immediate(value) => T::replace_lane_imm_param(value), }; self.push_instr_with_result( ::TY, |result| match value { Input::Slot(_) => T::replace_lane(result, input, lane), Input::Immediate(value) => T::replace_lane_imm(result, input, lane, value), }, FuelCostsProvider::simd, )?; if let Some(param) = param { self.push_param(param)?; } Ok(()) } /// Generically translate a Wasm unary instruction. fn translate_simd_unary( &mut self, make_instr: fn(result: Slot, input: Slot) -> Op, const_eval: fn(input: V128) -> T, ) -> Result<(), Error> where T: Into + Typed, { bail_unreachable!(self); let input = self.stack.pop(); if let Operand::Immediate(input) = input { // Case: the input is an immediate so we can const-eval the result. let result = const_eval(input.val().into()); self.stack.push_immediate(result)?; return Ok(()); }; let input = self.layout.operand_to_reg(input)?; self.push_instr_with_result( ::TY, |result| make_instr(result, input), FuelCostsProvider::simd, )?; Ok(()) } /// Generically translate a Wasm binary instruction. fn translate_simd_binary( &mut self, make_instr: fn(result: Slot, lhs: Slot, rhs: Slot) -> Op, const_eval: fn(lhs: V128, rhs: V128) -> V128, ) -> Result<(), Error> { bail_unreachable!(self); let (lhs, rhs) = self.stack.pop2(); if let (Operand::Immediate(lhs), Operand::Immediate(rhs)) = (lhs, rhs) { // Case: both inputs are immediates so we can const-eval the result. let result = const_eval(lhs.val().into(), rhs.val().into()); self.stack.push_immediate(result)?; return Ok(()); } let lhs = self.layout.operand_to_reg(lhs)?; let rhs = self.layout.operand_to_reg(rhs)?; self.push_instr_with_result( ValType::V128, |result| make_instr(result, lhs, rhs), FuelCostsProvider::simd, )?; Ok(()) } /// Generically translate a Wasm ternary instruction. fn translate_simd_ternary( &mut self, make_instr: fn(result: Slot, a: Slot, b: Slot) -> Op, const_eval: fn(lhas: V128, b: V128, c: V128) -> V128, ) -> Result<(), Error> { bail_unreachable!(self); let (a, b, c) = self.stack.pop3(); if let (Operand::Immediate(lhs), Operand::Immediate(rhs), Operand::Immediate(c)) = (a, b, c) { // Case: all inputs are immediates so we can const-eval the result. let result = const_eval(lhs.val().into(), rhs.val().into(), c.val().into()); self.stack.push_immediate(result)?; return Ok(()); } let lhs = self.layout.operand_to_reg(a)?; let rhs = self.layout.operand_to_reg(b)?; let selector = self.layout.operand_to_reg(c)?; self.push_instr_with_result( ValType::V128, |result| make_instr(result, lhs, rhs), FuelCostsProvider::simd, )?; self.push_param(Op::slot(selector))?; Ok(()) } /// Generically translate a Wasm SIMD shift instruction. fn translate_simd_shift( &mut self, make_instr: fn(result: Slot, lhs: Slot, rhs: Slot) -> Op, make_instr_imm: fn(result: Slot, lhs: Slot, rhs: ::Output) -> Op, const_eval: fn(lhs: V128, rhs: u32) -> V128, ) -> Result<(), Error> where T: IntoShiftAmount>, { bail_unreachable!(self); let (lhs, rhs) = self.stack.pop2(); if let (Operand::Immediate(lhs), Operand::Immediate(rhs)) = (lhs, rhs) { // Case: both inputs are immediates so we can const-eval the result. let result = const_eval(lhs.val().into(), rhs.val().into()); self.stack.push_immediate(result)?; return Ok(()); } if let Operand::Immediate(rhs) = rhs { let Some(rhs) = T::into_shift_amount(rhs.val().into()) else { // Case: the shift operation is a no-op self.stack.push_operand(lhs)?; return Ok(()); }; let lhs = self.layout.operand_to_reg(lhs)?; self.push_instr_with_result( ValType::V128, |result| make_instr_imm(result, lhs, rhs), FuelCostsProvider::simd, )?; return Ok(()); } let lhs = self.layout.operand_to_reg(lhs)?; let rhs = self.layout.operand_to_reg(rhs)?; self.push_instr_with_result( ValType::V128, |result| make_instr(result, lhs, rhs), FuelCostsProvider::simd, )?; Ok(()) } fn translate_v128_load_lane( &mut self, memarg: MemArg, lane: u8, make_instr: fn(result: Slot, offset_lo: Offset64Lo) -> Op, make_instr_at: fn(result: Slot, address: Address32) -> Op, ) -> Result<(), Error> { bail_unreachable!(self); let (memory, offset) = Self::decode_memarg(memarg); let Ok(lane) = ::try_from(lane) else { panic!("encountered out of bounds lane: {lane}"); }; let (ptr, x) = self.stack.pop2(); let x = self.layout.operand_to_reg(x)?; let (ptr, offset) = match ptr { Operand::Immediate(ptr) => { let Some(address) = self.effective_address(memory, ptr.val(), offset) else { return self.translate_trap(TrapCode::MemoryOutOfBounds); }; if let Ok(address) = Address32::try_from(address) { return self.translate_v128_load_lane_at::( memory, x, lane, address, make_instr_at, ); } let zero_ptr = self.layout.const_to_reg(0_u64)?; (zero_ptr, u64::from(address)) } ptr => { let ptr = self.layout.operand_to_reg(ptr)?; (ptr, offset) } }; let (offset_hi, offset_lo) = Offset64::split(offset); self.push_instr_with_result( ::TY, |result| make_instr(result, offset_lo), FuelCostsProvider::load, )?; self.push_param(Op::slot_and_offset_hi(ptr, offset_hi))?; self.push_param(Op::slot_and_lane(x, lane))?; if !memory.is_default() { self.push_param(Op::memory_index(memory))?; } Ok(()) } fn translate_v128_load_lane_at( &mut self, memory: Memory, x: Slot, lane: LaneType, address: Address32, make_instr_at: fn(result: Slot, address: Address32) -> Op, ) -> Result<(), Error> where LaneType: Into, { self.push_instr_with_result( ::TY, |result| make_instr_at(result, address), FuelCostsProvider::load, )?; self.push_param(Op::slot_and_lane(x, lane))?; if !memory.is_default() { self.push_param(Op::memory_index(memory))?; } Ok(()) } #[allow(clippy::type_complexity)] fn translate_v128_store_lane( &mut self, memarg: MemArg, lane: u8, make_instr: fn(ptr: Slot, offset_lo: Offset64Lo) -> Op, make_instr_offset8: fn(ptr: Slot, value: Slot, offset: Offset8, lane: T::LaneIdx) -> Op, make_instr_at: fn(value: Slot, address: Address32) -> Op, translate_imm: fn( &mut Self, memarg: MemArg, ptr: Operand, lane: T::LaneIdx, value: V128, ) -> Result<(), Error>, ) -> Result<(), Error> { bail_unreachable!(self); let Ok(lane) = ::try_from(lane) else { panic!("encountered out of bounds lane index: {lane}"); }; let (ptr, v128) = self.stack.pop2(); let v128 = match v128 { Operand::Immediate(v128) => { // Case: with `v128` being an immediate value we can extract its // lane value and translate as a more efficient non-SIMD operation. return translate_imm(self, memarg, ptr, lane, V128::from(v128.val())); } v128 => self.layout.operand_to_reg(v128)?, }; let (memory, offset) = Self::decode_memarg(memarg); let (ptr, offset) = match ptr { Operand::Immediate(ptr) => { let Some(address) = self.effective_address(memory, ptr.val(), offset) else { return self.translate_trap(TrapCode::MemoryOutOfBounds); }; if let Ok(address) = Address32::try_from(address) { return self.translate_v128_store_lane_at::( memory, address, v128, lane, make_instr_at, ); } // Case: we cannot use specialized encoding and thus have to fall back // to the general case where `ptr` is zero and `offset` stores the // `ptr+offset` address value. let zero_ptr = self.layout.const_to_reg(0_u64)?; (zero_ptr, u64::from(address)) } ptr => { let ptr = self.layout.operand_to_reg(ptr)?; (ptr, offset) } }; if let Ok(Some(_)) = self.translate_v128_store_lane_mem0(memory, ptr, offset, v128, lane, make_instr_offset8) { return Ok(()); } let (offset_hi, offset_lo) = Offset64::split(offset); let instr = make_instr(ptr, offset_lo); let param = Op::slot_and_offset_hi(v128, offset_hi); let param2 = Op::lane_and_memory_index(lane, memory); self.push_instr(instr, FuelCostsProvider::store)?; self.push_param(param)?; self.push_param(param2)?; Ok(()) } fn translate_v128_store_lane_at( &mut self, memory: index::Memory, address: Address32, value: Slot, lane: T::LaneIdx, make_instr_at: fn(value: Slot, address: Address32) -> Op, ) -> Result<(), Error> { self.push_instr(make_instr_at(value, address), FuelCostsProvider::store)?; self.push_param(Op::lane_and_memory_index(lane, memory))?; Ok(()) } fn translate_v128_store_lane_mem0( &mut self, memory: Memory, ptr: Slot, offset: u64, value: Slot, lane: LaneType, make_instr_offset8: fn(Slot, Slot, Offset8, LaneType) -> Op, ) -> Result, Error> { if !memory.is_default() { return Ok(None); } let Ok(offset8) = Offset8::try_from(offset) else { return Ok(None); }; let instr = self.push_instr( make_instr_offset8(ptr, value, offset8, lane), FuelCostsProvider::store, )?; Ok(Some(instr)) } } wasmi-1.1.0/src/engine/translator/func/simd/op.rs000064400000000000000000000120571046102023000200500ustar 00000000000000use super::IntoLaneIdx; use crate::{ core::{simd, Typed}, ir::{Const32, Op, Slot}, V128, }; pub trait SimdReplaceLane { type Item: Typed + IntoLaneIdx + Copy; type Immediate: Copy; fn const_eval( input: V128, lane: ::LaneIdx, value: Self::Item, ) -> V128; fn replace_lane(result: Slot, input: Slot, lane: ::LaneIdx) -> Op; fn replace_lane_imm( result: Slot, input: Slot, lane: ::LaneIdx, value: Self::Immediate, ) -> Op; fn replace_lane_imm_param(value: Self::Immediate) -> Option; fn value_to_imm(value: Self::Item) -> Option; } macro_rules! impl_replace_lane { ( $( impl SimdReplaceLane for $name:ident { type Item = $item_ty:ty; type Immediate = $imm_ty:ty; fn const_eval = $const_eval:expr; fn replace_lane = $replace_lane:expr; fn replace_lane_imm = $replace_lane_imm:expr; fn replace_lane_imm_param = $replace_lane_imm_param:expr; fn value_to_imm = $value_to_imm:expr; } )* ) => { $( #[doc = concat!("Wasm `", stringify!($name), "` operator.")] pub enum $name {} impl SimdReplaceLane for $name { type Item = $item_ty; type Immediate = $imm_ty; fn const_eval( input: V128, lane: ::LaneIdx, value: Self::Item, ) -> V128 { $const_eval(input, lane, value) } fn replace_lane( result: Slot, input: Slot, lane: ::LaneIdx, ) -> Op { $replace_lane(result, input, lane) } fn replace_lane_imm( result: Slot, input: Slot, lane: ::LaneIdx, value: Self::Immediate, ) -> Op { $replace_lane_imm(result, input, lane, value) } fn replace_lane_imm_param(value: Self::Immediate) -> Option { $replace_lane_imm_param(value) } fn value_to_imm(value: Self::Item) -> Option { $value_to_imm(value) } } )* }; } macro_rules! wrap { ($f:expr) => { |result, input, lane, _value| $f(result, input, lane) }; } impl_replace_lane! { impl SimdReplaceLane for I8x16ReplaceLane { type Item = i8; type Immediate = i8; fn const_eval = simd::i8x16_replace_lane; fn replace_lane = Op::i8x16_replace_lane; fn replace_lane_imm = Op::i8x16_replace_lane_imm; fn replace_lane_imm_param = |_| None; fn value_to_imm = Some; } impl SimdReplaceLane for I16x8ReplaceLane { type Item = i16; type Immediate = i16; fn const_eval = simd::i16x8_replace_lane; fn replace_lane = Op::i16x8_replace_lane; fn replace_lane_imm = wrap!(Op::i16x8_replace_lane_imm); fn replace_lane_imm_param = |value| Some(Op::const32(i32::from(value))); fn value_to_imm = Some; } impl SimdReplaceLane for I32x4ReplaceLane { type Item = i32; type Immediate = i32; fn const_eval = simd::i32x4_replace_lane; fn replace_lane = Op::i32x4_replace_lane; fn replace_lane_imm = wrap!(Op::i32x4_replace_lane_imm); fn replace_lane_imm_param = |value| Some(Op::const32(value)); fn value_to_imm = Some; } impl SimdReplaceLane for I64x2ReplaceLane { type Item = i64; type Immediate = Const32; fn const_eval = simd::i64x2_replace_lane; fn replace_lane = Op::i64x2_replace_lane; fn replace_lane_imm = wrap!(Op::i64x2_replace_lane_imm32); fn replace_lane_imm_param = |value| Some(Op::i64const32(value)); fn value_to_imm = |value| >::try_from(value).ok(); } impl SimdReplaceLane for F32x4ReplaceLane { type Item = f32; type Immediate = f32; fn const_eval = simd::f32x4_replace_lane; fn replace_lane = Op::f32x4_replace_lane; fn replace_lane_imm = wrap!(Op::f32x4_replace_lane_imm); fn replace_lane_imm_param = |value| Some(Op::const32(value)); fn value_to_imm = Some; } impl SimdReplaceLane for F64x2ReplaceLane { type Item = f64; type Immediate = Const32; fn const_eval = simd::f64x2_replace_lane; fn replace_lane = Op::f64x2_replace_lane; fn replace_lane_imm = wrap!(Op::f64x2_replace_lane_imm32); fn replace_lane_imm_param = |value| Some(Op::f64const32(value)); fn value_to_imm = |value| >::try_from(value).ok(); } } wasmi-1.1.0/src/engine/translator/func/simd/visit.rs000064400000000000000000001246551046102023000206000ustar 00000000000000use super::FuncTranslator; use crate::{ core::{ simd::{self, ImmLaneIdx32}, FuelCostsProvider, TypedVal, }, engine::translator::func::{op, simd::op as simd_op, Operand}, ir::{Op, Slot}, Error, ValType, V128, }; use core::array; use wasmparser::{MemArg, VisitSimdOperator}; impl FuncTranslator { /// Hacky utility method to convert an immediate value into an [`Operand`]. fn immediate_to_operand>(&mut self, value: T) -> Result { self.stack.push_immediate(value)?; Ok(self.stack.pop()) } } /// Used to swap operands of binary [`Op`] constructor. macro_rules! swap_ops { ($fn_name:path) => { |result: Slot, lhs, rhs| -> Op { $fn_name(result, rhs, lhs) } }; } impl VisitSimdOperator<'_> for FuncTranslator { fn visit_v128_load(&mut self, memarg: MemArg) -> Self::Output { self.translate_load( memarg, ValType::V128, Op::v128_load, Op::v128_load_offset16, Op::v128_load_at, ) } fn visit_v128_load8x8_s(&mut self, memarg: MemArg) -> Self::Output { self.translate_load( memarg, ValType::V128, Op::v128_load8x8_s, Op::v128_load8x8_s_offset16, Op::v128_load8x8_s_at, ) } fn visit_v128_load8x8_u(&mut self, memarg: MemArg) -> Self::Output { self.translate_load( memarg, ValType::V128, Op::v128_load8x8_u, Op::v128_load8x8_u_offset16, Op::v128_load8x8_u_at, ) } fn visit_v128_load16x4_s(&mut self, memarg: MemArg) -> Self::Output { self.translate_load( memarg, ValType::V128, Op::v128_load16x4_s, Op::v128_load16x4_s_offset16, Op::v128_load16x4_s_at, ) } fn visit_v128_load16x4_u(&mut self, memarg: MemArg) -> Self::Output { self.translate_load( memarg, ValType::V128, Op::v128_load16x4_u, Op::v128_load16x4_u_offset16, Op::v128_load16x4_u_at, ) } fn visit_v128_load32x2_s(&mut self, memarg: MemArg) -> Self::Output { self.translate_load( memarg, ValType::V128, Op::v128_load32x2_s, Op::v128_load32x2_s_offset16, Op::v128_load32x2_s_at, ) } fn visit_v128_load32x2_u(&mut self, memarg: MemArg) -> Self::Output { self.translate_load( memarg, ValType::V128, Op::v128_load32x2_u, Op::v128_load32x2_u_offset16, Op::v128_load32x2_u_at, ) } fn visit_v128_load8_splat(&mut self, memarg: MemArg) -> Self::Output { self.translate_load( memarg, ValType::V128, Op::v128_load8_splat, Op::v128_load8_splat_offset16, Op::v128_load8_splat_at, ) } fn visit_v128_load16_splat(&mut self, memarg: MemArg) -> Self::Output { self.translate_load( memarg, ValType::V128, Op::v128_load16_splat, Op::v128_load16_splat_offset16, Op::v128_load16_splat_at, ) } fn visit_v128_load32_splat(&mut self, memarg: MemArg) -> Self::Output { self.translate_load( memarg, ValType::V128, Op::v128_load32_splat, Op::v128_load32_splat_offset16, Op::v128_load32_splat_at, ) } fn visit_v128_load64_splat(&mut self, memarg: MemArg) -> Self::Output { self.translate_load( memarg, ValType::V128, Op::v128_load64_splat, Op::v128_load64_splat_offset16, Op::v128_load64_splat_at, ) } fn visit_v128_load32_zero(&mut self, memarg: MemArg) -> Self::Output { self.translate_load( memarg, ValType::V128, Op::v128_load32_zero, Op::v128_load32_zero_offset16, Op::v128_load32_zero_at, ) } fn visit_v128_load64_zero(&mut self, memarg: MemArg) -> Self::Output { self.translate_load( memarg, ValType::V128, Op::v128_load64_zero, Op::v128_load64_zero_offset16, Op::v128_load64_zero_at, ) } fn visit_v128_store(&mut self, memarg: MemArg) -> Self::Output { self.translate_store( memarg, Op::v128_store, Op::v128_store_offset16, Op::v128_store_at, ) } fn visit_v128_load8_lane(&mut self, memarg: MemArg, lane: u8) -> Self::Output { self.translate_v128_load_lane::( memarg, lane, Op::v128_load8_lane, Op::v128_load8_lane_at, ) } fn visit_v128_load16_lane(&mut self, memarg: MemArg, lane: u8) -> Self::Output { self.translate_v128_load_lane::( memarg, lane, Op::v128_load16_lane, Op::v128_load16_lane_at, ) } fn visit_v128_load32_lane(&mut self, memarg: MemArg, lane: u8) -> Self::Output { self.translate_v128_load_lane::( memarg, lane, Op::v128_load32_lane, Op::v128_load32_lane_at, ) } fn visit_v128_load64_lane(&mut self, memarg: MemArg, lane: u8) -> Self::Output { self.translate_v128_load_lane::( memarg, lane, Op::v128_load64_lane, Op::v128_load64_lane_at, ) } fn visit_v128_store8_lane(&mut self, memarg: MemArg, lane: u8) -> Self::Output { self.translate_v128_store_lane::( memarg, lane, Op::v128_store8_lane, Op::v128_store8_lane_offset8, Op::v128_store8_lane_at, |this, memarg, ptr, lane, v128| { let value = simd::i8x16_extract_lane_s(v128, lane); let value = this.immediate_to_operand(value)?; this.encode_istore_wrap::(memarg, ptr, value) }, ) } fn visit_v128_store16_lane(&mut self, memarg: MemArg, lane: u8) -> Self::Output { self.translate_v128_store_lane::( memarg, lane, Op::v128_store16_lane, Op::v128_store16_lane_offset8, Op::v128_store16_lane_at, |this, memarg, ptr, lane, v128| { let value = simd::i16x8_extract_lane_s(v128, lane); let value = this.immediate_to_operand(value)?; this.encode_istore_wrap::(memarg, ptr, value) }, ) } fn visit_v128_store32_lane(&mut self, memarg: MemArg, lane: u8) -> Self::Output { self.translate_v128_store_lane::( memarg, lane, Op::v128_store32_lane, Op::v128_store32_lane_offset8, Op::v128_store32_lane_at, |this, memarg, ptr, lane, v128| { let value = simd::i32x4_extract_lane(v128, lane); let value = this.immediate_to_operand(value)?; this.encode_istore_wrap::(memarg, ptr, value) }, ) } fn visit_v128_store64_lane(&mut self, memarg: MemArg, lane: u8) -> Self::Output { self.translate_v128_store_lane::( memarg, lane, Op::v128_store64_lane, Op::v128_store64_lane_offset8, Op::v128_store64_lane_at, |this, memarg, ptr, lane, v128| { let value = simd::i64x2_extract_lane(v128, lane); let value = this.immediate_to_operand(value)?; this.encode_istore_wrap::(memarg, ptr, value) }, ) } fn visit_v128_const(&mut self, value: wasmparser::V128) -> Self::Output { bail_unreachable!(self); let v128 = V128::from(value.i128() as u128); self.stack.push_immediate(v128)?; Ok(()) } fn visit_i8x16_shuffle(&mut self, lanes: [u8; 16]) -> Self::Output { bail_unreachable!(self); let selector: [ImmLaneIdx32; 16] = array::from_fn(|i| { let Ok(lane) = ImmLaneIdx32::try_from(lanes[i]) else { panic!("encountered out of bounds lane at index {i}: {}", lanes[i]) }; lane }); let (lhs, rhs) = self.stack.pop2(); if let (Operand::Immediate(lhs), Operand::Immediate(rhs)) = (lhs, rhs) { let result = simd::i8x16_shuffle(lhs.val().into(), rhs.val().into(), selector); self.stack.push_immediate(result)?; return Ok(()); } let lhs = self.layout.operand_to_reg(lhs)?; let rhs = self.layout.operand_to_reg(rhs)?; let selector = self .layout .const_to_reg(V128::from(u128::from_ne_bytes(lanes)))?; self.push_instr_with_result( ValType::V128, |result| Op::i8x16_shuffle(result, lhs, rhs), FuelCostsProvider::simd, )?; self.push_param(Op::slot(selector))?; Ok(()) } fn visit_i8x16_extract_lane_s(&mut self, lane: u8) -> Self::Output { self.translate_extract_lane::( lane, Op::i8x16_extract_lane_s, simd::i8x16_extract_lane_s, ) } fn visit_i8x16_extract_lane_u(&mut self, lane: u8) -> Self::Output { self.translate_extract_lane::( lane, Op::i8x16_extract_lane_u, simd::i8x16_extract_lane_u, ) } fn visit_i16x8_extract_lane_s(&mut self, lane: u8) -> Self::Output { self.translate_extract_lane::( lane, Op::i16x8_extract_lane_s, simd::i16x8_extract_lane_s, ) } fn visit_i16x8_extract_lane_u(&mut self, lane: u8) -> Self::Output { self.translate_extract_lane::( lane, Op::i16x8_extract_lane_u, simd::i16x8_extract_lane_u, ) } fn visit_i32x4_extract_lane(&mut self, lane: u8) -> Self::Output { self.translate_extract_lane::( lane, Op::i32x4_extract_lane, simd::i32x4_extract_lane, ) } fn visit_i64x2_extract_lane(&mut self, lane: u8) -> Self::Output { self.translate_extract_lane::( lane, Op::i64x2_extract_lane, simd::i64x2_extract_lane, ) } fn visit_f32x4_extract_lane(&mut self, lane: u8) -> Self::Output { self.translate_extract_lane::( lane, Op::f32x4_extract_lane, simd::f32x4_extract_lane, ) } fn visit_f64x2_extract_lane(&mut self, lane: u8) -> Self::Output { self.translate_extract_lane::( lane, Op::f64x2_extract_lane, simd::f64x2_extract_lane, ) } fn visit_i8x16_replace_lane(&mut self, lane: u8) -> Self::Output { self.translate_replace_lane::(lane) } fn visit_i16x8_replace_lane(&mut self, lane: u8) -> Self::Output { self.translate_replace_lane::(lane) } fn visit_i32x4_replace_lane(&mut self, lane: u8) -> Self::Output { self.translate_replace_lane::(lane) } fn visit_i64x2_replace_lane(&mut self, lane: u8) -> Self::Output { self.translate_replace_lane::(lane) } fn visit_f32x4_replace_lane(&mut self, lane: u8) -> Self::Output { self.translate_replace_lane::(lane) } fn visit_f64x2_replace_lane(&mut self, lane: u8) -> Self::Output { self.translate_replace_lane::(lane) } fn visit_i8x16_swizzle(&mut self) -> Self::Output { self.translate_simd_binary(Op::i8x16_swizzle, simd::i8x16_swizzle) } fn visit_i8x16_splat(&mut self) -> Self::Output { self.translate_simd_splat::(Op::i8x16_splat, simd::i8x16_splat) } fn visit_i16x8_splat(&mut self) -> Self::Output { self.translate_simd_splat::(Op::i16x8_splat, simd::i16x8_splat) } fn visit_i32x4_splat(&mut self) -> Self::Output { self.translate_simd_splat::(Op::i32x4_splat, simd::i32x4_splat) } fn visit_i64x2_splat(&mut self) -> Self::Output { self.translate_simd_splat::(Op::i64x2_splat, simd::i64x2_splat) } fn visit_f32x4_splat(&mut self) -> Self::Output { self.translate_simd_splat::(Op::f32x4_splat, simd::f32x4_splat) } fn visit_f64x2_splat(&mut self) -> Self::Output { self.translate_simd_splat::(Op::f64x2_splat, simd::f64x2_splat) } fn visit_i8x16_eq(&mut self) -> Self::Output { self.translate_simd_binary(Op::i8x16_eq, simd::i8x16_eq) } fn visit_i8x16_ne(&mut self) -> Self::Output { self.translate_simd_binary(Op::i8x16_ne, simd::i8x16_ne) } fn visit_i8x16_lt_s(&mut self) -> Self::Output { self.translate_simd_binary(Op::i8x16_lt_s, simd::i8x16_lt_s) } fn visit_i8x16_lt_u(&mut self) -> Self::Output { self.translate_simd_binary(Op::i8x16_lt_u, simd::i8x16_lt_u) } fn visit_i8x16_gt_s(&mut self) -> Self::Output { self.translate_simd_binary(swap_ops!(Op::i8x16_lt_s), simd::i8x16_gt_s) } fn visit_i8x16_gt_u(&mut self) -> Self::Output { self.translate_simd_binary(swap_ops!(Op::i8x16_lt_u), simd::i8x16_gt_u) } fn visit_i8x16_le_s(&mut self) -> Self::Output { self.translate_simd_binary(Op::i8x16_le_s, simd::i8x16_le_s) } fn visit_i8x16_le_u(&mut self) -> Self::Output { self.translate_simd_binary(Op::i8x16_le_u, simd::i8x16_le_u) } fn visit_i8x16_ge_s(&mut self) -> Self::Output { self.translate_simd_binary(swap_ops!(Op::i8x16_le_s), simd::i8x16_ge_s) } fn visit_i8x16_ge_u(&mut self) -> Self::Output { self.translate_simd_binary(swap_ops!(Op::i8x16_le_u), simd::i8x16_ge_u) } fn visit_i16x8_eq(&mut self) -> Self::Output { self.translate_simd_binary(Op::i16x8_eq, simd::i16x8_eq) } fn visit_i16x8_ne(&mut self) -> Self::Output { self.translate_simd_binary(Op::i16x8_ne, simd::i16x8_ne) } fn visit_i16x8_lt_s(&mut self) -> Self::Output { self.translate_simd_binary(Op::i16x8_lt_s, simd::i16x8_lt_s) } fn visit_i16x8_lt_u(&mut self) -> Self::Output { self.translate_simd_binary(Op::i16x8_lt_u, simd::i16x8_lt_u) } fn visit_i16x8_gt_s(&mut self) -> Self::Output { self.translate_simd_binary(swap_ops!(Op::i16x8_lt_s), simd::i16x8_gt_s) } fn visit_i16x8_gt_u(&mut self) -> Self::Output { self.translate_simd_binary(swap_ops!(Op::i16x8_lt_u), simd::i16x8_gt_u) } fn visit_i16x8_le_s(&mut self) -> Self::Output { self.translate_simd_binary(Op::i16x8_le_s, simd::i16x8_le_s) } fn visit_i16x8_le_u(&mut self) -> Self::Output { self.translate_simd_binary(Op::i16x8_le_u, simd::i16x8_le_u) } fn visit_i16x8_ge_s(&mut self) -> Self::Output { self.translate_simd_binary(swap_ops!(Op::i16x8_le_s), simd::i16x8_ge_s) } fn visit_i16x8_ge_u(&mut self) -> Self::Output { self.translate_simd_binary(swap_ops!(Op::i16x8_le_u), simd::i16x8_ge_u) } fn visit_i32x4_eq(&mut self) -> Self::Output { self.translate_simd_binary(Op::i32x4_eq, simd::i32x4_eq) } fn visit_i32x4_ne(&mut self) -> Self::Output { self.translate_simd_binary(Op::i32x4_ne, simd::i32x4_ne) } fn visit_i32x4_lt_s(&mut self) -> Self::Output { self.translate_simd_binary(Op::i32x4_lt_s, simd::i32x4_lt_s) } fn visit_i32x4_lt_u(&mut self) -> Self::Output { self.translate_simd_binary(Op::i32x4_lt_u, simd::i32x4_lt_u) } fn visit_i32x4_gt_s(&mut self) -> Self::Output { self.translate_simd_binary(swap_ops!(Op::i32x4_lt_s), simd::i32x4_gt_s) } fn visit_i32x4_gt_u(&mut self) -> Self::Output { self.translate_simd_binary(swap_ops!(Op::i32x4_lt_u), simd::i32x4_gt_u) } fn visit_i32x4_le_s(&mut self) -> Self::Output { self.translate_simd_binary(Op::i32x4_le_s, simd::i32x4_le_s) } fn visit_i32x4_le_u(&mut self) -> Self::Output { self.translate_simd_binary(Op::i32x4_le_u, simd::i32x4_le_u) } fn visit_i32x4_ge_s(&mut self) -> Self::Output { self.translate_simd_binary(swap_ops!(Op::i32x4_le_s), simd::i32x4_ge_s) } fn visit_i32x4_ge_u(&mut self) -> Self::Output { self.translate_simd_binary(swap_ops!(Op::i32x4_le_u), simd::i32x4_ge_u) } fn visit_i64x2_eq(&mut self) -> Self::Output { self.translate_simd_binary(Op::i64x2_eq, simd::i64x2_eq) } fn visit_i64x2_ne(&mut self) -> Self::Output { self.translate_simd_binary(Op::i64x2_ne, simd::i64x2_ne) } fn visit_i64x2_lt_s(&mut self) -> Self::Output { self.translate_simd_binary(Op::i64x2_lt_s, simd::i64x2_lt_s) } fn visit_i64x2_gt_s(&mut self) -> Self::Output { self.translate_simd_binary(swap_ops!(Op::i64x2_lt_s), simd::i64x2_gt_s) } fn visit_i64x2_le_s(&mut self) -> Self::Output { self.translate_simd_binary(Op::i64x2_le_s, simd::i64x2_le_s) } fn visit_i64x2_ge_s(&mut self) -> Self::Output { self.translate_simd_binary(swap_ops!(Op::i64x2_le_s), simd::i64x2_ge_s) } fn visit_f32x4_eq(&mut self) -> Self::Output { self.translate_simd_binary(Op::f32x4_eq, simd::f32x4_eq) } fn visit_f32x4_ne(&mut self) -> Self::Output { self.translate_simd_binary(Op::f32x4_ne, simd::f32x4_ne) } fn visit_f32x4_lt(&mut self) -> Self::Output { self.translate_simd_binary(Op::f32x4_lt, simd::f32x4_lt) } fn visit_f32x4_gt(&mut self) -> Self::Output { self.translate_simd_binary(swap_ops!(Op::f32x4_lt), simd::f32x4_gt) } fn visit_f32x4_le(&mut self) -> Self::Output { self.translate_simd_binary(Op::f32x4_le, simd::f32x4_le) } fn visit_f32x4_ge(&mut self) -> Self::Output { self.translate_simd_binary(swap_ops!(Op::f32x4_le), simd::f32x4_ge) } fn visit_f64x2_eq(&mut self) -> Self::Output { self.translate_simd_binary(Op::f64x2_eq, simd::f64x2_eq) } fn visit_f64x2_ne(&mut self) -> Self::Output { self.translate_simd_binary(Op::f64x2_ne, simd::f64x2_ne) } fn visit_f64x2_lt(&mut self) -> Self::Output { self.translate_simd_binary(Op::f64x2_lt, simd::f64x2_lt) } fn visit_f64x2_gt(&mut self) -> Self::Output { self.translate_simd_binary(swap_ops!(Op::f64x2_lt), simd::f64x2_gt) } fn visit_f64x2_le(&mut self) -> Self::Output { self.translate_simd_binary(Op::f64x2_le, simd::f64x2_le) } fn visit_f64x2_ge(&mut self) -> Self::Output { self.translate_simd_binary(swap_ops!(Op::f64x2_le), simd::f64x2_ge) } fn visit_v128_not(&mut self) -> Self::Output { self.translate_simd_unary(Op::v128_not, simd::v128_not) } fn visit_v128_and(&mut self) -> Self::Output { self.translate_simd_binary(Op::v128_and, simd::v128_and) } fn visit_v128_andnot(&mut self) -> Self::Output { self.translate_simd_binary(Op::v128_andnot, simd::v128_andnot) } fn visit_v128_or(&mut self) -> Self::Output { self.translate_simd_binary(Op::v128_or, simd::v128_or) } fn visit_v128_xor(&mut self) -> Self::Output { self.translate_simd_binary(Op::v128_xor, simd::v128_xor) } fn visit_v128_bitselect(&mut self) -> Self::Output { self.translate_simd_ternary(Op::v128_bitselect, simd::v128_bitselect) } fn visit_v128_any_true(&mut self) -> Self::Output { self.translate_simd_unary(Op::v128_any_true, simd::v128_any_true) } fn visit_i8x16_abs(&mut self) -> Self::Output { self.translate_simd_unary(Op::i8x16_abs, simd::i8x16_abs) } fn visit_i8x16_neg(&mut self) -> Self::Output { self.translate_simd_unary(Op::i8x16_neg, simd::i8x16_neg) } fn visit_i8x16_popcnt(&mut self) -> Self::Output { self.translate_simd_unary(Op::i8x16_popcnt, simd::i8x16_popcnt) } fn visit_i8x16_all_true(&mut self) -> Self::Output { self.translate_simd_unary(Op::i8x16_all_true, simd::i8x16_all_true) } fn visit_i8x16_bitmask(&mut self) -> Self::Output { self.translate_simd_unary(Op::i8x16_bitmask, simd::i8x16_bitmask) } fn visit_i8x16_narrow_i16x8_s(&mut self) -> Self::Output { self.translate_simd_binary(Op::i8x16_narrow_i16x8_s, simd::i8x16_narrow_i16x8_s) } fn visit_i8x16_narrow_i16x8_u(&mut self) -> Self::Output { self.translate_simd_binary(Op::i8x16_narrow_i16x8_u, simd::i8x16_narrow_i16x8_u) } fn visit_i8x16_shl(&mut self) -> Self::Output { self.translate_simd_shift::(Op::i8x16_shl, Op::i8x16_shl_by, simd::i8x16_shl) } fn visit_i8x16_shr_s(&mut self) -> Self::Output { self.translate_simd_shift::(Op::i8x16_shr_s, Op::i8x16_shr_s_by, simd::i8x16_shr_s) } fn visit_i8x16_shr_u(&mut self) -> Self::Output { self.translate_simd_shift::(Op::i8x16_shr_u, Op::i8x16_shr_u_by, simd::i8x16_shr_u) } fn visit_i8x16_add(&mut self) -> Self::Output { self.translate_simd_binary(Op::i8x16_add, simd::i8x16_add) } fn visit_i8x16_add_sat_s(&mut self) -> Self::Output { self.translate_simd_binary(Op::i8x16_add_sat_s, simd::i8x16_add_sat_s) } fn visit_i8x16_add_sat_u(&mut self) -> Self::Output { self.translate_simd_binary(Op::i8x16_add_sat_u, simd::i8x16_add_sat_u) } fn visit_i8x16_sub(&mut self) -> Self::Output { self.translate_simd_binary(Op::i8x16_sub, simd::i8x16_sub) } fn visit_i8x16_sub_sat_s(&mut self) -> Self::Output { self.translate_simd_binary(Op::i8x16_sub_sat_s, simd::i8x16_sub_sat_s) } fn visit_i8x16_sub_sat_u(&mut self) -> Self::Output { self.translate_simd_binary(Op::i8x16_sub_sat_u, simd::i8x16_sub_sat_u) } fn visit_i8x16_min_s(&mut self) -> Self::Output { self.translate_simd_binary(Op::i8x16_min_s, simd::i8x16_min_s) } fn visit_i8x16_min_u(&mut self) -> Self::Output { self.translate_simd_binary(Op::i8x16_min_u, simd::i8x16_min_u) } fn visit_i8x16_max_s(&mut self) -> Self::Output { self.translate_simd_binary(Op::i8x16_max_s, simd::i8x16_max_s) } fn visit_i8x16_max_u(&mut self) -> Self::Output { self.translate_simd_binary(Op::i8x16_max_u, simd::i8x16_max_u) } fn visit_i8x16_avgr_u(&mut self) -> Self::Output { self.translate_simd_binary(Op::i8x16_avgr_u, simd::i8x16_avgr_u) } fn visit_i16x8_extadd_pairwise_i8x16_s(&mut self) -> Self::Output { self.translate_simd_unary( Op::i16x8_extadd_pairwise_i8x16_s, simd::i16x8_extadd_pairwise_i8x16_s, ) } fn visit_i16x8_extadd_pairwise_i8x16_u(&mut self) -> Self::Output { self.translate_simd_unary( Op::i16x8_extadd_pairwise_i8x16_u, simd::i16x8_extadd_pairwise_i8x16_u, ) } fn visit_i16x8_abs(&mut self) -> Self::Output { self.translate_simd_unary(Op::i16x8_abs, simd::i16x8_abs) } fn visit_i16x8_neg(&mut self) -> Self::Output { self.translate_simd_unary(Op::i16x8_neg, simd::i16x8_neg) } fn visit_i16x8_q15mulr_sat_s(&mut self) -> Self::Output { self.translate_simd_binary(Op::i16x8_q15mulr_sat_s, simd::i16x8_q15mulr_sat_s) } fn visit_i16x8_all_true(&mut self) -> Self::Output { self.translate_simd_unary(Op::i16x8_all_true, simd::i16x8_all_true) } fn visit_i16x8_bitmask(&mut self) -> Self::Output { self.translate_simd_unary(Op::i16x8_bitmask, simd::i16x8_bitmask) } fn visit_i16x8_narrow_i32x4_s(&mut self) -> Self::Output { self.translate_simd_binary(Op::i16x8_narrow_i32x4_s, simd::i16x8_narrow_i32x4_s) } fn visit_i16x8_narrow_i32x4_u(&mut self) -> Self::Output { self.translate_simd_binary(Op::i16x8_narrow_i32x4_u, simd::i16x8_narrow_i32x4_u) } fn visit_i16x8_extend_low_i8x16_s(&mut self) -> Self::Output { self.translate_simd_unary(Op::i16x8_extend_low_i8x16_s, simd::i16x8_extend_low_i8x16_s) } fn visit_i16x8_extend_high_i8x16_s(&mut self) -> Self::Output { self.translate_simd_unary( Op::i16x8_extend_high_i8x16_s, simd::i16x8_extend_high_i8x16_s, ) } fn visit_i16x8_extend_low_i8x16_u(&mut self) -> Self::Output { self.translate_simd_unary(Op::i16x8_extend_low_i8x16_u, simd::i16x8_extend_low_i8x16_u) } fn visit_i16x8_extend_high_i8x16_u(&mut self) -> Self::Output { self.translate_simd_unary( Op::i16x8_extend_high_i8x16_u, simd::i16x8_extend_high_i8x16_u, ) } fn visit_i16x8_shl(&mut self) -> Self::Output { self.translate_simd_shift::(Op::i16x8_shl, Op::i16x8_shl_by, simd::i16x8_shl) } fn visit_i16x8_shr_s(&mut self) -> Self::Output { self.translate_simd_shift::(Op::i16x8_shr_s, Op::i16x8_shr_s_by, simd::i16x8_shr_s) } fn visit_i16x8_shr_u(&mut self) -> Self::Output { self.translate_simd_shift::(Op::i16x8_shr_u, Op::i16x8_shr_u_by, simd::i16x8_shr_u) } fn visit_i16x8_add(&mut self) -> Self::Output { self.translate_simd_binary(Op::i16x8_add, simd::i16x8_add) } fn visit_i16x8_add_sat_s(&mut self) -> Self::Output { self.translate_simd_binary(Op::i16x8_add_sat_s, simd::i16x8_add_sat_s) } fn visit_i16x8_add_sat_u(&mut self) -> Self::Output { self.translate_simd_binary(Op::i16x8_add_sat_u, simd::i16x8_add_sat_u) } fn visit_i16x8_sub(&mut self) -> Self::Output { self.translate_simd_binary(Op::i16x8_sub, simd::i16x8_sub) } fn visit_i16x8_sub_sat_s(&mut self) -> Self::Output { self.translate_simd_binary(Op::i16x8_sub_sat_s, simd::i16x8_sub_sat_s) } fn visit_i16x8_sub_sat_u(&mut self) -> Self::Output { self.translate_simd_binary(Op::i16x8_sub_sat_u, simd::i16x8_sub_sat_u) } fn visit_i16x8_mul(&mut self) -> Self::Output { self.translate_simd_binary(Op::i16x8_mul, simd::i16x8_mul) } fn visit_i16x8_min_s(&mut self) -> Self::Output { self.translate_simd_binary(Op::i16x8_min_s, simd::i16x8_min_s) } fn visit_i16x8_min_u(&mut self) -> Self::Output { self.translate_simd_binary(Op::i16x8_min_u, simd::i16x8_min_u) } fn visit_i16x8_max_s(&mut self) -> Self::Output { self.translate_simd_binary(Op::i16x8_max_s, simd::i16x8_max_s) } fn visit_i16x8_max_u(&mut self) -> Self::Output { self.translate_simd_binary(Op::i16x8_max_u, simd::i16x8_max_u) } fn visit_i16x8_avgr_u(&mut self) -> Self::Output { self.translate_simd_binary(Op::i16x8_avgr_u, simd::i16x8_avgr_u) } fn visit_i16x8_extmul_low_i8x16_s(&mut self) -> Self::Output { self.translate_simd_binary(Op::i16x8_extmul_low_i8x16_s, simd::i16x8_extmul_low_i8x16_s) } fn visit_i16x8_extmul_high_i8x16_s(&mut self) -> Self::Output { self.translate_simd_binary( Op::i16x8_extmul_high_i8x16_s, simd::i16x8_extmul_high_i8x16_s, ) } fn visit_i16x8_extmul_low_i8x16_u(&mut self) -> Self::Output { self.translate_simd_binary(Op::i16x8_extmul_low_i8x16_u, simd::i16x8_extmul_low_i8x16_u) } fn visit_i16x8_extmul_high_i8x16_u(&mut self) -> Self::Output { self.translate_simd_binary( Op::i16x8_extmul_high_i8x16_u, simd::i16x8_extmul_high_i8x16_u, ) } fn visit_i32x4_extadd_pairwise_i16x8_s(&mut self) -> Self::Output { self.translate_simd_unary( Op::i32x4_extadd_pairwise_i16x8_s, simd::i32x4_extadd_pairwise_i16x8_s, ) } fn visit_i32x4_extadd_pairwise_i16x8_u(&mut self) -> Self::Output { self.translate_simd_unary( Op::i32x4_extadd_pairwise_i16x8_u, simd::i32x4_extadd_pairwise_i16x8_u, ) } fn visit_i32x4_abs(&mut self) -> Self::Output { self.translate_simd_unary(Op::i32x4_abs, simd::i32x4_abs) } fn visit_i32x4_neg(&mut self) -> Self::Output { self.translate_simd_unary(Op::i32x4_neg, simd::i32x4_neg) } fn visit_i32x4_all_true(&mut self) -> Self::Output { self.translate_simd_unary(Op::i32x4_all_true, simd::i32x4_all_true) } fn visit_i32x4_bitmask(&mut self) -> Self::Output { self.translate_simd_unary(Op::i32x4_bitmask, simd::i32x4_bitmask) } fn visit_i32x4_extend_low_i16x8_s(&mut self) -> Self::Output { self.translate_simd_unary(Op::i32x4_extend_low_i16x8_s, simd::i32x4_extend_low_i16x8_s) } fn visit_i32x4_extend_high_i16x8_s(&mut self) -> Self::Output { self.translate_simd_unary( Op::i32x4_extend_high_i16x8_s, simd::i32x4_extend_high_i16x8_s, ) } fn visit_i32x4_extend_low_i16x8_u(&mut self) -> Self::Output { self.translate_simd_unary(Op::i32x4_extend_low_i16x8_u, simd::i32x4_extend_low_i16x8_u) } fn visit_i32x4_extend_high_i16x8_u(&mut self) -> Self::Output { self.translate_simd_unary( Op::i32x4_extend_high_i16x8_u, simd::i32x4_extend_high_i16x8_u, ) } fn visit_i32x4_shl(&mut self) -> Self::Output { self.translate_simd_shift::(Op::i32x4_shl, Op::i32x4_shl_by, simd::i32x4_shl) } fn visit_i32x4_shr_s(&mut self) -> Self::Output { self.translate_simd_shift::(Op::i32x4_shr_s, Op::i32x4_shr_s_by, simd::i32x4_shr_s) } fn visit_i32x4_shr_u(&mut self) -> Self::Output { self.translate_simd_shift::(Op::i32x4_shr_u, Op::i32x4_shr_u_by, simd::i32x4_shr_u) } fn visit_i32x4_add(&mut self) -> Self::Output { self.translate_simd_binary(Op::i32x4_add, simd::i32x4_add) } fn visit_i32x4_sub(&mut self) -> Self::Output { self.translate_simd_binary(Op::i32x4_sub, simd::i32x4_sub) } fn visit_i32x4_mul(&mut self) -> Self::Output { self.translate_simd_binary(Op::i32x4_mul, simd::i32x4_mul) } fn visit_i32x4_min_s(&mut self) -> Self::Output { self.translate_simd_binary(Op::i32x4_min_s, simd::i32x4_min_s) } fn visit_i32x4_min_u(&mut self) -> Self::Output { self.translate_simd_binary(Op::i32x4_min_u, simd::i32x4_min_u) } fn visit_i32x4_max_s(&mut self) -> Self::Output { self.translate_simd_binary(Op::i32x4_max_s, simd::i32x4_max_s) } fn visit_i32x4_max_u(&mut self) -> Self::Output { self.translate_simd_binary(Op::i32x4_max_u, simd::i32x4_max_u) } fn visit_i32x4_dot_i16x8_s(&mut self) -> Self::Output { self.translate_simd_binary(Op::i32x4_dot_i16x8_s, simd::i32x4_dot_i16x8_s) } fn visit_i32x4_extmul_low_i16x8_s(&mut self) -> Self::Output { self.translate_simd_binary(Op::i32x4_extmul_low_i16x8_s, simd::i32x4_extmul_low_i16x8_s) } fn visit_i32x4_extmul_high_i16x8_s(&mut self) -> Self::Output { self.translate_simd_binary( Op::i32x4_extmul_high_i16x8_s, simd::i32x4_extmul_high_i16x8_s, ) } fn visit_i32x4_extmul_low_i16x8_u(&mut self) -> Self::Output { self.translate_simd_binary(Op::i32x4_extmul_low_i16x8_u, simd::i32x4_extmul_low_i16x8_u) } fn visit_i32x4_extmul_high_i16x8_u(&mut self) -> Self::Output { self.translate_simd_binary( Op::i32x4_extmul_high_i16x8_u, simd::i32x4_extmul_high_i16x8_u, ) } fn visit_i64x2_abs(&mut self) -> Self::Output { self.translate_simd_unary(Op::i64x2_abs, simd::i64x2_abs) } fn visit_i64x2_neg(&mut self) -> Self::Output { self.translate_simd_unary(Op::i64x2_neg, simd::i64x2_neg) } fn visit_i64x2_all_true(&mut self) -> Self::Output { self.translate_simd_unary(Op::i64x2_all_true, simd::i64x2_all_true) } fn visit_i64x2_bitmask(&mut self) -> Self::Output { self.translate_simd_unary(Op::i64x2_bitmask, simd::i64x2_bitmask) } fn visit_i64x2_extend_low_i32x4_s(&mut self) -> Self::Output { self.translate_simd_unary(Op::i64x2_extend_low_i32x4_s, simd::i64x2_extend_low_i32x4_s) } fn visit_i64x2_extend_high_i32x4_s(&mut self) -> Self::Output { self.translate_simd_unary( Op::i64x2_extend_high_i32x4_s, simd::i64x2_extend_high_i32x4_s, ) } fn visit_i64x2_extend_low_i32x4_u(&mut self) -> Self::Output { self.translate_simd_unary(Op::i64x2_extend_low_i32x4_u, simd::i64x2_extend_low_i32x4_u) } fn visit_i64x2_extend_high_i32x4_u(&mut self) -> Self::Output { self.translate_simd_unary( Op::i64x2_extend_high_i32x4_u, simd::i64x2_extend_high_i32x4_u, ) } fn visit_i64x2_shl(&mut self) -> Self::Output { self.translate_simd_shift::(Op::i64x2_shl, Op::i64x2_shl_by, simd::i64x2_shl) } fn visit_i64x2_shr_s(&mut self) -> Self::Output { self.translate_simd_shift::(Op::i64x2_shr_s, Op::i64x2_shr_s_by, simd::i64x2_shr_s) } fn visit_i64x2_shr_u(&mut self) -> Self::Output { self.translate_simd_shift::(Op::i64x2_shr_u, Op::i64x2_shr_u_by, simd::i64x2_shr_u) } fn visit_i64x2_add(&mut self) -> Self::Output { self.translate_simd_binary(Op::i64x2_add, simd::i64x2_add) } fn visit_i64x2_sub(&mut self) -> Self::Output { self.translate_simd_binary(Op::i64x2_sub, simd::i64x2_sub) } fn visit_i64x2_mul(&mut self) -> Self::Output { self.translate_simd_binary(Op::i64x2_mul, simd::i64x2_mul) } fn visit_i64x2_extmul_low_i32x4_s(&mut self) -> Self::Output { self.translate_simd_binary(Op::i64x2_extmul_low_i32x4_s, simd::i64x2_extmul_low_i32x4_s) } fn visit_i64x2_extmul_high_i32x4_s(&mut self) -> Self::Output { self.translate_simd_binary( Op::i64x2_extmul_high_i32x4_s, simd::i64x2_extmul_high_i32x4_s, ) } fn visit_i64x2_extmul_low_i32x4_u(&mut self) -> Self::Output { self.translate_simd_binary(Op::i64x2_extmul_low_i32x4_u, simd::i64x2_extmul_low_i32x4_u) } fn visit_i64x2_extmul_high_i32x4_u(&mut self) -> Self::Output { self.translate_simd_binary( Op::i64x2_extmul_high_i32x4_u, simd::i64x2_extmul_high_i32x4_u, ) } fn visit_f32x4_ceil(&mut self) -> Self::Output { self.translate_simd_unary(Op::f32x4_ceil, simd::f32x4_ceil) } fn visit_f32x4_floor(&mut self) -> Self::Output { self.translate_simd_unary(Op::f32x4_floor, simd::f32x4_floor) } fn visit_f32x4_trunc(&mut self) -> Self::Output { self.translate_simd_unary(Op::f32x4_trunc, simd::f32x4_trunc) } fn visit_f32x4_nearest(&mut self) -> Self::Output { self.translate_simd_unary(Op::f32x4_nearest, simd::f32x4_nearest) } fn visit_f32x4_abs(&mut self) -> Self::Output { self.translate_simd_unary(Op::f32x4_abs, simd::f32x4_abs) } fn visit_f32x4_neg(&mut self) -> Self::Output { self.translate_simd_unary(Op::f32x4_neg, simd::f32x4_neg) } fn visit_f32x4_sqrt(&mut self) -> Self::Output { self.translate_simd_unary(Op::f32x4_sqrt, simd::f32x4_sqrt) } fn visit_f32x4_add(&mut self) -> Self::Output { self.translate_simd_binary(Op::f32x4_add, simd::f32x4_add) } fn visit_f32x4_sub(&mut self) -> Self::Output { self.translate_simd_binary(Op::f32x4_sub, simd::f32x4_sub) } fn visit_f32x4_mul(&mut self) -> Self::Output { self.translate_simd_binary(Op::f32x4_mul, simd::f32x4_mul) } fn visit_f32x4_div(&mut self) -> Self::Output { self.translate_simd_binary(Op::f32x4_div, simd::f32x4_div) } fn visit_f32x4_min(&mut self) -> Self::Output { self.translate_simd_binary(Op::f32x4_min, simd::f32x4_min) } fn visit_f32x4_max(&mut self) -> Self::Output { self.translate_simd_binary(Op::f32x4_max, simd::f32x4_max) } fn visit_f32x4_pmin(&mut self) -> Self::Output { self.translate_simd_binary(Op::f32x4_pmin, simd::f32x4_pmin) } fn visit_f32x4_pmax(&mut self) -> Self::Output { self.translate_simd_binary(Op::f32x4_pmax, simd::f32x4_pmax) } fn visit_f64x2_ceil(&mut self) -> Self::Output { self.translate_simd_unary(Op::f64x2_ceil, simd::f64x2_ceil) } fn visit_f64x2_floor(&mut self) -> Self::Output { self.translate_simd_unary(Op::f64x2_floor, simd::f64x2_floor) } fn visit_f64x2_trunc(&mut self) -> Self::Output { self.translate_simd_unary(Op::f64x2_trunc, simd::f64x2_trunc) } fn visit_f64x2_nearest(&mut self) -> Self::Output { self.translate_simd_unary(Op::f64x2_nearest, simd::f64x2_nearest) } fn visit_f64x2_abs(&mut self) -> Self::Output { self.translate_simd_unary(Op::f64x2_abs, simd::f64x2_abs) } fn visit_f64x2_neg(&mut self) -> Self::Output { self.translate_simd_unary(Op::f64x2_neg, simd::f64x2_neg) } fn visit_f64x2_sqrt(&mut self) -> Self::Output { self.translate_simd_unary(Op::f64x2_sqrt, simd::f64x2_sqrt) } fn visit_f64x2_add(&mut self) -> Self::Output { self.translate_simd_binary(Op::f64x2_add, simd::f64x2_add) } fn visit_f64x2_sub(&mut self) -> Self::Output { self.translate_simd_binary(Op::f64x2_sub, simd::f64x2_sub) } fn visit_f64x2_mul(&mut self) -> Self::Output { self.translate_simd_binary(Op::f64x2_mul, simd::f64x2_mul) } fn visit_f64x2_div(&mut self) -> Self::Output { self.translate_simd_binary(Op::f64x2_div, simd::f64x2_div) } fn visit_f64x2_min(&mut self) -> Self::Output { self.translate_simd_binary(Op::f64x2_min, simd::f64x2_min) } fn visit_f64x2_max(&mut self) -> Self::Output { self.translate_simd_binary(Op::f64x2_max, simd::f64x2_max) } fn visit_f64x2_pmin(&mut self) -> Self::Output { self.translate_simd_binary(Op::f64x2_pmin, simd::f64x2_pmin) } fn visit_f64x2_pmax(&mut self) -> Self::Output { self.translate_simd_binary(Op::f64x2_pmax, simd::f64x2_pmax) } fn visit_i32x4_trunc_sat_f32x4_s(&mut self) -> Self::Output { self.translate_simd_unary(Op::i32x4_trunc_sat_f32x4_s, simd::i32x4_trunc_sat_f32x4_s) } fn visit_i32x4_trunc_sat_f32x4_u(&mut self) -> Self::Output { self.translate_simd_unary(Op::i32x4_trunc_sat_f32x4_u, simd::i32x4_trunc_sat_f32x4_u) } fn visit_f32x4_convert_i32x4_s(&mut self) -> Self::Output { self.translate_simd_unary(Op::f32x4_convert_i32x4_s, simd::f32x4_convert_i32x4_s) } fn visit_f32x4_convert_i32x4_u(&mut self) -> Self::Output { self.translate_simd_unary(Op::f32x4_convert_i32x4_u, simd::f32x4_convert_i32x4_u) } fn visit_i32x4_trunc_sat_f64x2_s_zero(&mut self) -> Self::Output { self.translate_simd_unary( Op::i32x4_trunc_sat_f64x2_s_zero, simd::i32x4_trunc_sat_f64x2_s_zero, ) } fn visit_i32x4_trunc_sat_f64x2_u_zero(&mut self) -> Self::Output { self.translate_simd_unary( Op::i32x4_trunc_sat_f64x2_u_zero, simd::i32x4_trunc_sat_f64x2_u_zero, ) } fn visit_f64x2_convert_low_i32x4_s(&mut self) -> Self::Output { self.translate_simd_unary( Op::f64x2_convert_low_i32x4_s, simd::f64x2_convert_low_i32x4_s, ) } fn visit_f64x2_convert_low_i32x4_u(&mut self) -> Self::Output { self.translate_simd_unary( Op::f64x2_convert_low_i32x4_u, simd::f64x2_convert_low_i32x4_u, ) } fn visit_f32x4_demote_f64x2_zero(&mut self) -> Self::Output { self.translate_simd_unary(Op::f32x4_demote_f64x2_zero, simd::f32x4_demote_f64x2_zero) } fn visit_f64x2_promote_low_f32x4(&mut self) -> Self::Output { self.translate_simd_unary(Op::f64x2_promote_low_f32x4, simd::f64x2_promote_low_f32x4) } fn visit_i8x16_relaxed_swizzle(&mut self) -> Self::Output { self.visit_i8x16_swizzle() } fn visit_i32x4_relaxed_trunc_f32x4_s(&mut self) -> Self::Output { self.visit_i32x4_trunc_sat_f32x4_s() } fn visit_i32x4_relaxed_trunc_f32x4_u(&mut self) -> Self::Output { self.visit_i32x4_trunc_sat_f32x4_u() } fn visit_i32x4_relaxed_trunc_f64x2_s_zero(&mut self) -> Self::Output { self.visit_i32x4_trunc_sat_f64x2_s_zero() } fn visit_i32x4_relaxed_trunc_f64x2_u_zero(&mut self) -> Self::Output { self.visit_i32x4_trunc_sat_f64x2_u_zero() } fn visit_f32x4_relaxed_madd(&mut self) -> Self::Output { self.translate_simd_ternary(Op::f32x4_relaxed_madd, simd::f32x4_relaxed_madd) } fn visit_f32x4_relaxed_nmadd(&mut self) -> Self::Output { self.translate_simd_ternary(Op::f32x4_relaxed_nmadd, simd::f32x4_relaxed_nmadd) } fn visit_f64x2_relaxed_madd(&mut self) -> Self::Output { self.translate_simd_ternary(Op::f64x2_relaxed_madd, simd::f64x2_relaxed_madd) } fn visit_f64x2_relaxed_nmadd(&mut self) -> Self::Output { self.translate_simd_ternary(Op::f64x2_relaxed_nmadd, simd::f64x2_relaxed_nmadd) } fn visit_i8x16_relaxed_laneselect(&mut self) -> Self::Output { self.visit_v128_bitselect() } fn visit_i16x8_relaxed_laneselect(&mut self) -> Self::Output { self.visit_v128_bitselect() } fn visit_i32x4_relaxed_laneselect(&mut self) -> Self::Output { self.visit_v128_bitselect() } fn visit_i64x2_relaxed_laneselect(&mut self) -> Self::Output { self.visit_v128_bitselect() } fn visit_f32x4_relaxed_min(&mut self) -> Self::Output { self.visit_f32x4_min() } fn visit_f32x4_relaxed_max(&mut self) -> Self::Output { self.visit_f32x4_max() } fn visit_f64x2_relaxed_min(&mut self) -> Self::Output { self.visit_f64x2_min() } fn visit_f64x2_relaxed_max(&mut self) -> Self::Output { self.visit_f64x2_max() } fn visit_i16x8_relaxed_q15mulr_s(&mut self) -> Self::Output { self.visit_i16x8_q15mulr_sat_s() } fn visit_i16x8_relaxed_dot_i8x16_i7x16_s(&mut self) -> Self::Output { self.translate_simd_binary( Op::i16x8_relaxed_dot_i8x16_i7x16_s, simd::i16x8_relaxed_dot_i8x16_i7x16_s, ) } fn visit_i32x4_relaxed_dot_i8x16_i7x16_add_s(&mut self) -> Self::Output { self.translate_simd_ternary( Op::i32x4_relaxed_dot_i8x16_i7x16_add_s, simd::i32x4_relaxed_dot_i8x16_i7x16_add_s, ) } } wasmi-1.1.0/src/engine/translator/func/stack/control.rs000064400000000000000000000601601046102023000212610ustar 00000000000000use super::{Operand, Reset}; use crate::{ engine::{ translator::{labels::LabelRef, utils::Instr}, BlockType, }, Engine, }; use alloc::vec::{Drain, Vec}; #[cfg(doc)] use crate::ir::Op; /// The height of the operand stack upon entering a [`ControlFrame`]. #[derive(Debug, Copy, Clone)] pub struct StackHeight(u16); impl From for usize { fn from(height: StackHeight) -> Self { usize::from(height.0) } } impl From for StackHeight { fn from(height: usize) -> Self { let Ok(height) = u16::try_from(height) else { panic!("out of bounds stack height: {height}") }; Self(height) } } /// The Wasm control stack. #[derive(Debug, Default)] pub struct ControlStack { /// The stack of control frames. frames: Vec, /// The current top-most [`Op::ConsumeFuel`] on the stack. /// /// # Note /// /// This is meant as cache and optimization to quickly query the top-most /// fuel consumption instruction since this information is accessed commonly. /// /// [`Op`]: crate::ir::Op consume_fuel_instr: Option, /// Special operand stack to memorize operands for `else` control frames. else_operands: ElseOperands, /// This is `true` if an `if` with else providers was just popped from the stack. /// /// # Note /// /// This means that its associated `else` operands need to be taken care of by /// either pushing back an `else` control frame or by manually popping them off /// the control stack. orphaned_else_operands: bool, } /// Duplicated operands for Wasm `else` control frames. #[derive(Debug, Default)] pub struct ElseOperands { /// The end indices of each `else` operands. ends: Vec, /// All operands of all allocated `else` control frames. operands: Vec, } impl Reset for ElseOperands { fn reset(&mut self) { self.ends.clear(); self.operands.clear(); } } impl ElseOperands { /// Pushes operands for a new Wasm `else` control frame. pub fn push(&mut self, operands: impl IntoIterator) { self.operands.extend(operands); let end = self.operands.len(); self.ends.push(end); } /// Pops the top-most Wasm `else` operands from `self` and returns them. pub fn pop(&mut self) -> Option> { let end = self.ends.pop()?; let start = self.ends.last().copied().unwrap_or(0); Some(self.operands.drain(start..end)) } } impl Reset for ControlStack { fn reset(&mut self) { self.frames.clear(); self.else_operands.reset(); self.orphaned_else_operands = false; } } impl ControlStack { /// Returns the current [`Op::ConsumeFuel`] if fuel metering is enabled. /// /// Returns `None` otherwise. #[inline] pub fn consume_fuel_instr(&self) -> Option { debug_assert!(!self.is_empty()); self.consume_fuel_instr } /// Returns `true` if `self` is empty. pub fn is_empty(&self) -> bool { self.height() == 0 } /// Returns the height of the [`ControlStack`]. pub fn height(&self) -> usize { self.frames.len() } /// Pushes a new unreachable Wasm control frame onto the [`ControlStack`]. pub fn push_unreachable(&mut self, kind: ControlFrameKind) { debug_assert!(!self.orphaned_else_operands); self.frames.push(ControlFrame::from(kind)) } /// Pushes a new Wasm `block` onto the [`ControlStack`]. pub fn push_block( &mut self, ty: BlockType, height: usize, label: LabelRef, consume_fuel: Option, ) { debug_assert!(!self.orphaned_else_operands); self.frames.push(ControlFrame::from(BlockControlFrame { ty, height: StackHeight::from(height), is_branched_to: false, consume_fuel, label, })); self.consume_fuel_instr = consume_fuel; } /// Pushes a new Wasm `loop` onto the [`ControlStack`]. pub fn push_loop( &mut self, ty: BlockType, height: usize, label: LabelRef, consume_fuel: Option, ) { debug_assert!(!self.orphaned_else_operands); self.frames.push(ControlFrame::from(LoopControlFrame { ty, height: StackHeight::from(height), is_branched_to: false, consume_fuel, label, })); self.consume_fuel_instr = consume_fuel; } /// Pushes a new Wasm `if` onto the [`ControlStack`]. pub fn push_if( &mut self, ty: BlockType, height: usize, label: LabelRef, consume_fuel: Option, reachability: IfReachability, else_operands: impl IntoIterator, ) { debug_assert!(!self.orphaned_else_operands); self.frames.push(ControlFrame::from(IfControlFrame { ty, height: StackHeight::from(height), is_branched_to: false, consume_fuel, label, reachability, })); if matches!(reachability, IfReachability::Both { .. }) { self.else_operands.push(else_operands); } self.consume_fuel_instr = consume_fuel; } /// Pushes a new Wasm `else` onto the [`ControlStack`]. /// /// Returns iterator yielding the memorized `else` operands. pub fn push_else( &mut self, if_frame: IfControlFrame, consume_fuel: Option, is_end_of_then_reachable: bool, ) { debug_assert!(!self.orphaned_else_operands); let ty = if_frame.ty(); let height = if_frame.height(); let label = if_frame.label(); let is_branched_to = if_frame.is_branched_to(); let reachability = match if_frame.reachability { IfReachability::Both { .. } => ElseReachability::Both { is_end_of_then_reachable, }, IfReachability::OnlyThen => ElseReachability::OnlyThen { is_end_of_then_reachable, }, IfReachability::OnlyElse => ElseReachability::OnlyElse, }; self.frames.push(ControlFrame::from(ElseControlFrame { ty, height: StackHeight::from(height), is_branched_to, consume_fuel, label, reachability, })); self.consume_fuel_instr = consume_fuel; } /// Pops the top-most [`ControlFrame`] and returns it if any. pub fn pop(&mut self) -> Option { debug_assert!(!self.orphaned_else_operands); let frame = self.frames.pop()?; if !matches!(frame, ControlFrame::Block(_) | ControlFrame::Unreachable(_)) { // Need to replace the cached top-most `consume_fuel_instr`. self.consume_fuel_instr = self.get(0).consume_fuel_instr(); } self.orphaned_else_operands = match &frame { ControlFrame::If(frame) => { matches!(frame.reachability, IfReachability::Both { .. }) } _ => false, }; Some(frame) } /// Pops the top-most `else` operands from the control stack. /// /// # Panics (Debug) /// /// If the `else` operands are not in orphaned state. pub fn pop_else_operands(&mut self) -> Drain<'_, Operand> { debug_assert!(self.orphaned_else_operands); let Some(else_operands) = self.else_operands.pop() else { panic!("missing `else` operands") }; self.orphaned_else_operands = false; else_operands } /// Returns a shared reference to the [`ControlFrame`] at `depth` if any. pub fn get(&self, depth: usize) -> &ControlFrame { let height = self.height(); self.frames.iter().rev().nth(depth).unwrap_or_else(|| { panic!( "out of bounds control frame at depth (={depth}) for stack of height (={height})" ) }) } /// Returns an exclusive reference to the [`ControlFrame`] at `depth` if any. pub fn get_mut(&mut self, depth: usize) -> ControlFrameMut<'_> { let height = self.height(); self.frames .iter_mut() .rev() .nth(depth) .map(ControlFrameMut) .unwrap_or_else(|| { panic!( "out of bounds control frame at depth (={depth}) for stack of height (={height})" ) }) } } /// An exclusive reference to a [`ControlFrame`]. #[derive(Debug)] pub struct ControlFrameMut<'a>(&'a mut ControlFrame); impl<'a> ControlFrameBase for ControlFrameMut<'a> { fn ty(&self) -> BlockType { self.0.ty() } fn height(&self) -> usize { self.0.height() } fn label(&self) -> LabelRef { self.0.label() } fn is_branched_to(&self) -> bool { self.0.is_branched_to() } fn branch_to(&mut self) { self.0.branch_to() } fn len_branch_params(&self, engine: &Engine) -> u16 { self.0.len_branch_params(engine) } fn consume_fuel_instr(&self) -> Option { self.0.consume_fuel_instr() } } /// An acquired branch target. #[derive(Debug)] pub enum AcquiredTarget<'stack> { /// The branch targets the function enclosing `block` and therefore is a `return`. Return(ControlFrameMut<'stack>), /// The branch targets a regular [`ControlFrame`]. Branch(ControlFrameMut<'stack>), } impl<'stack> AcquiredTarget<'stack> { /// Returns an exclusive reference to the [`ControlFrame`] of the [`AcquiredTarget`]. pub fn control_frame(self) -> ControlFrameMut<'stack> { match self { Self::Return(frame) => frame, Self::Branch(frame) => frame, } } } impl ControlStack { /// Acquires the target [`ControlFrame`] at the given relative `depth`. pub fn acquire_target(&mut self, depth: usize) -> AcquiredTarget<'_> { let is_root = self.is_root(depth); let frame = self.get_mut(depth); if is_root { AcquiredTarget::Return(frame) } else { AcquiredTarget::Branch(frame) } } /// Returns `true` if `depth` points to the first control flow frame. fn is_root(&self, depth: usize) -> bool { if self.frames.is_empty() { return false; } depth == self.height() - 1 } } /// A Wasm control frame. #[derive(Debug)] pub enum ControlFrame { /// A Wasm `block` control frame. Block(BlockControlFrame), /// A Wasm `loop` control frame. Loop(LoopControlFrame), /// A Wasm `if` control frame. If(IfControlFrame), /// A Wasm `else` control frame. Else(ElseControlFrame), /// A generic unreachable control frame. Unreachable(ControlFrameKind), } 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: ElseControlFrame) -> Self { Self::Else(frame) } } impl From for ControlFrame { fn from(frame: ControlFrameKind) -> Self { Self::Unreachable(frame) } } /// Trait implemented by control frame types that share a common API. pub trait ControlFrameBase { /// Returns the [`BlockType`] of the [`BlockControlFrame`]. fn ty(&self) -> BlockType; /// Returns the height of the [`BlockControlFrame`]. fn height(&self) -> usize; /// Returns the branch label of `self`. fn label(&self) -> LabelRef; /// Returns `true` if there exists a branch to `self.` fn is_branched_to(&self) -> bool; /// Makes `self` aware that there is a branch to it. fn branch_to(&mut self); /// Returns the number of operands required for branching to `self`. fn len_branch_params(&self, engine: &Engine) -> u16; /// Returns a reference to the [`Op::ConsumeFuel`] of `self`. /// /// Returns `None` if fuel metering is disabled. fn consume_fuel_instr(&self) -> Option; } impl ControlFrameBase for ControlFrame { fn ty(&self) -> BlockType { match self { ControlFrame::Block(frame) => frame.ty(), ControlFrame::Loop(frame) => frame.ty(), ControlFrame::If(frame) => frame.ty(), ControlFrame::Else(frame) => frame.ty(), ControlFrame::Unreachable(_) => { panic!("invalid query for unreachable control frame: `ControlFrameBase::ty`") } } } fn height(&self) -> usize { match self { ControlFrame::Block(frame) => frame.height(), ControlFrame::Loop(frame) => frame.height(), ControlFrame::If(frame) => frame.height(), ControlFrame::Else(frame) => frame.height(), ControlFrame::Unreachable(_) => { panic!("invalid query for unreachable control frame: `ControlFrameBase::height`") } } } fn label(&self) -> LabelRef { match self { ControlFrame::Block(frame) => frame.label(), ControlFrame::Loop(frame) => frame.label(), ControlFrame::If(frame) => frame.label(), ControlFrame::Else(frame) => frame.label(), ControlFrame::Unreachable(_) => { panic!("invalid query for unreachable control frame: `ControlFrame::label`") } } } fn is_branched_to(&self) -> bool { match self { ControlFrame::Block(frame) => frame.is_branched_to(), ControlFrame::Loop(frame) => frame.is_branched_to(), ControlFrame::If(frame) => frame.is_branched_to(), ControlFrame::Else(frame) => frame.is_branched_to(), ControlFrame::Unreachable(_) => { panic!( "invalid query for unreachable control frame: `ControlFrame::is_branched_to`" ) } } } fn branch_to(&mut self) { match self { ControlFrame::Block(frame) => frame.branch_to(), ControlFrame::Loop(frame) => frame.branch_to(), ControlFrame::If(frame) => frame.branch_to(), ControlFrame::Else(frame) => frame.branch_to(), ControlFrame::Unreachable(_) => { panic!("invalid query for unreachable control frame: `ControlFrame::branch_to`") } } } fn len_branch_params(&self, engine: &Engine) -> u16 { match self { ControlFrame::Block(frame) => frame.len_branch_params(engine), ControlFrame::Loop(frame) => frame.len_branch_params(engine), ControlFrame::If(frame) => frame.len_branch_params(engine), ControlFrame::Else(frame) => frame.len_branch_params(engine), ControlFrame::Unreachable(_) => { panic!("invalid query for unreachable control frame: `ControlFrame::len_branch_params`") } } } 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::Else(frame) => frame.consume_fuel_instr(), ControlFrame::Unreachable(_) => { panic!("invalid query for unreachable control frame: `ControlFrame::consume_fuel_instr`") } } } } /// A Wasm `block` control frame. #[derive(Debug)] pub struct BlockControlFrame { /// The block type of the [`BlockControlFrame`]. ty: BlockType, /// The value stack height upon entering the [`BlockControlFrame`]. height: StackHeight, /// This is `true` if there is at least one branch to this [`BlockControlFrame`]. is_branched_to: bool, /// The [`BlockControlFrame`]'s [`Op::ConsumeFuel`] if fuel metering is enabled. /// /// # Note /// /// This is `Some` if fuel metering is enabled and `None` otherwise. consume_fuel: Option, /// The label used to branch to the [`BlockControlFrame`]. label: LabelRef, } impl ControlFrameBase for BlockControlFrame { fn ty(&self) -> BlockType { self.ty } fn height(&self) -> usize { self.height.into() } fn label(&self) -> LabelRef { self.label } fn is_branched_to(&self) -> bool { self.is_branched_to } fn branch_to(&mut self) { self.is_branched_to = true; } fn len_branch_params(&self, engine: &Engine) -> u16 { self.ty.len_results(engine) } fn consume_fuel_instr(&self) -> Option { self.consume_fuel } } /// A Wasm `loop` control frame. #[derive(Debug)] pub struct LoopControlFrame { /// The block type of the [`LoopControlFrame`]. ty: BlockType, /// The value stack height upon entering the [`LoopControlFrame`]. height: StackHeight, /// This is `true` if there is at least one branch to this [`LoopControlFrame`]. is_branched_to: bool, /// The [`LoopControlFrame`]'s [`Op::ConsumeFuel`] if fuel metering is enabled. /// /// # Note /// /// This is `Some` if fuel metering is enabled and `None` otherwise. consume_fuel: Option, /// The label used to branch to the [`LoopControlFrame`]. label: LabelRef, } impl ControlFrameBase for LoopControlFrame { fn ty(&self) -> BlockType { self.ty } fn height(&self) -> usize { self.height.into() } fn label(&self) -> LabelRef { self.label } fn is_branched_to(&self) -> bool { self.is_branched_to } fn branch_to(&mut self) { self.is_branched_to = true; } fn len_branch_params(&self, engine: &Engine) -> u16 { self.ty.len_params(engine) } fn consume_fuel_instr(&self) -> Option { self.consume_fuel } } /// A Wasm `if` control frame including its `then` part. #[derive(Debug)] pub struct IfControlFrame { /// The block type of the [`IfControlFrame`]. ty: BlockType, /// The value stack height upon entering the [`IfControlFrame`]. height: StackHeight, /// This is `true` if there is at least one branch to this [`IfControlFrame`]. is_branched_to: bool, /// The [`IfControlFrame`]'s [`Op::ConsumeFuel`] if fuel metering is enabled. /// /// # Note /// /// This is `Some` if fuel metering is enabled and `None` otherwise. consume_fuel: Option, /// The label used to branch to the [`IfControlFrame`]. label: LabelRef, /// The reachability of the `then` and `else` blocks. reachability: IfReachability, } impl IfControlFrame { /// Returns the [`IfReachability`] of the [`IfControlFrame`]. pub fn reachability(&self) -> IfReachability { self.reachability } /// Returns `true` if the `else` branch is reachable. /// /// # Note /// /// The `else` branch is unreachable if the `if` condition is a constant `true` value. pub fn is_else_reachable(&self) -> bool { match self.reachability { IfReachability::Both { .. } | IfReachability::OnlyElse => true, IfReachability::OnlyThen => false, } } } impl ControlFrameBase for IfControlFrame { fn ty(&self) -> BlockType { self.ty } fn height(&self) -> usize { self.height.into() } fn label(&self) -> LabelRef { self.label } fn is_branched_to(&self) -> bool { self.is_branched_to } fn branch_to(&mut self) { self.is_branched_to = true; } fn len_branch_params(&self, engine: &Engine) -> u16 { self.ty.len_results(engine) } fn consume_fuel_instr(&self) -> Option { self.consume_fuel } } /// The reachability of the `if` control flow frame. #[derive(Debug, Copy, Clone)] pub enum IfReachability { /// Both, `then` and `else` blocks of the `if` are reachable. /// /// # Note /// /// This variant does not mean that necessarily both `then` and `else` /// blocks do exist and are non-empty. The `then` block might still be /// empty and the `then` block might still be missing. Both { else_label: LabelRef }, /// Only the `then` block of the `if` is reachable. /// /// # Note /// /// This case happens only in case the `if` has a `true` constant condition. OnlyThen, /// Only the `else` block of the `if` is reachable. /// /// # Note /// /// This case happens only in case the `if` has a `false` constant condition. OnlyElse, } /// A Wasm `else` control frame part of Wasm `if`. #[derive(Debug)] pub struct ElseControlFrame { /// The block type of the [`ElseControlFrame`]. ty: BlockType, /// The value stack height upon entering the [`ElseControlFrame`]. height: StackHeight, /// This is `true` if there is at least one branch to this [`ElseControlFrame`]. is_branched_to: bool, /// The [`LoopControlFrame`]'s [`Op::ConsumeFuel`] if fuel metering is enabled. /// /// # Note /// /// This is `Some` if fuel metering is enabled and `None` otherwise. consume_fuel: Option, /// The label used to branch to the [`ElseControlFrame`]. label: LabelRef, /// The reachability of the `then` and `else` blocks. reachability: ElseReachability, } /// The reachability of the `else` control flow frame. #[derive(Debug, Copy, Clone)] pub enum ElseReachability { /// Both, `then` and `else` blocks of the `if` are reachable. /// /// # Note /// /// This variant does not mean that necessarily both `then` and `else` /// blocks do exist and are non-empty. The `then` block might still be /// empty and the `then` block might still be missing. Both { /// Is `true` if code is reachable when entering the `else` block. /// /// # Note /// /// This means that the end of the `then` block was reachable. is_end_of_then_reachable: bool, }, /// Only the `then` block of the `if` is reachable. /// /// # Note /// /// This case happens only in case the `if` has a `true` constant condition. OnlyThen { /// Is `true` if code is reachable when entering the `else` block. /// /// # Note /// /// This means that the end of the `then` block was reachable. is_end_of_then_reachable: bool, }, /// Only the `else` block of the `if` is reachable. /// /// # Note /// /// This case happens only in case the `if` has a `false` constant condition. OnlyElse, } impl ElseControlFrame { /// Returns the [`ElseReachability`] of the [`ElseReachability`]. pub fn reachability(&self) -> ElseReachability { self.reachability } /// Returns `true` if the end of the `then` branch is reachable. pub fn is_end_of_then_reachable(&self) -> bool { match self.reachability { ElseReachability::Both { is_end_of_then_reachable, } | ElseReachability::OnlyThen { is_end_of_then_reachable, } => is_end_of_then_reachable, ElseReachability::OnlyElse => false, } } } impl ControlFrameBase for ElseControlFrame { fn ty(&self) -> BlockType { self.ty } fn height(&self) -> usize { self.height.into() } fn label(&self) -> LabelRef { self.label } fn is_branched_to(&self) -> bool { self.is_branched_to } fn branch_to(&mut self) { self.is_branched_to = true; } fn len_branch_params(&self, engine: &Engine) -> u16 { self.ty.len_results(engine) } fn consume_fuel_instr(&self) -> Option { self.consume_fuel } } /// The kind of a Wasm control frame. #[derive(Debug, Copy, Clone)] pub enum ControlFrameKind { /// An Wasm `block` control frame. Block, /// An Wasm `loop` control frame. Loop, /// An Wasm `if` control frame. If, /// An Wasm `else` control frame. Else, } wasmi-1.1.0/src/engine/translator/func/stack/locals.rs000064400000000000000000000030671046102023000210610ustar 00000000000000use super::{OperandIdx, Reset}; use crate::{engine::translator::func::LocalIdx, Error}; use alloc::vec::Vec; use core::iter; /// Store the index of the first occurrence on the stack for every local variable. #[derive(Debug, Default)] pub struct LocalsHead { /// The index of the first occurrence of every local variable. first_operands: Vec>, } impl Reset for LocalsHead { fn reset(&mut self) { self.first_operands.clear(); } } impl LocalsHead { /// Slots `amount` of locals. /// /// # Errors /// /// If too many locals are registered. pub fn register(&mut self, amount: usize) -> Result<(), Error> { self.first_operands.extend(iter::repeat_n(None, amount)); Ok(()) } /// Converts `index` into a `usize` value. fn local_idx_to_index(index: LocalIdx) -> usize { let index = u32::from(index); let Ok(index) = usize::try_from(index) else { panic!("out of bounds `LocalIdx`: {index}") }; index } /// Replaces the first operand for this local on the stack and returns the old one. /// /// # Panics /// /// If `index` is out of bounds. pub fn replace_first( &mut self, index: LocalIdx, first_operand: Option, ) -> Option { let index = Self::local_idx_to_index(index); let cell = &mut self.first_operands[index]; match first_operand { Some(first_operand) => cell.replace(first_operand), None => cell.take(), } } } wasmi-1.1.0/src/engine/translator/func/stack/mod.rs000064400000000000000000000362451046102023000203670ustar 00000000000000mod control; mod locals; mod operand; mod operands; use self::{ control::ControlStack, locals::LocalsHead, operands::{OperandStack, StackOperand}, }; pub use self::{ control::{ AcquiredTarget, BlockControlFrame, ControlFrame, ControlFrameBase, ControlFrameKind, ElseControlFrame, ElseReachability, IfControlFrame, IfReachability, LoopControlFrame, }, operand::{ImmediateOperand, Operand, TempOperand}, operands::{OperandIdx, PreservedAllLocalsIter, PreservedLocalsIter}, }; use super::{Reset, ReusableAllocations}; use crate::{ core::TypedVal, engine::{ translator::{ func::{stack::operands::PeekedOperands, LocalIdx}, labels::LabelRef, utils::Instr, }, BlockType, }, Engine, Error, ValType, }; use alloc::vec::Vec; #[cfg(doc)] use crate::ir::Op; /// The Wasm value stack during translation from Wasm to Wasmi bytecode. #[derive(Debug)] pub struct Stack { /// The underlying [`Engine`]. engine: Engine, /// The Wasm value stack. operands: OperandStack, /// The Wasm control stack. controls: ControlStack, } /// Reusable heap allocations for the [`Stack`]. #[derive(Debug, Default)] pub struct StackAllocations { /// The Wasm value stack. operands: OperandStack, /// The Wasm control stack. controls: ControlStack, } impl Reset for StackAllocations { fn reset(&mut self) { self.operands.reset(); self.controls.reset(); } } impl ReusableAllocations for Stack { type Allocations = StackAllocations; fn into_allocations(self) -> StackAllocations { StackAllocations { operands: self.operands, controls: self.controls, } } } impl Stack { /// Creates a new empty [`Stack`] from the given `engine`. pub fn new(engine: &Engine, alloc: StackAllocations) -> Self { let StackAllocations { operands, controls } = alloc.into_reset(); Self { engine: engine.clone(), operands, controls, } } /// Slot `amount` local variables. /// /// # Errors /// /// If too many local variables are being registered. pub fn register_locals(&mut self, amount: usize) -> Result<(), Error> { self.operands.register_locals(amount) } /// Returns `true` if the control stack is empty. pub fn is_control_empty(&self) -> bool { self.controls.is_empty() } /// Returns the current height of the [`Stack`]. /// /// # Note /// /// The height is equal to the number of [`Operand`]s on the [`Stack`]. pub fn height(&self) -> usize { self.operands.height() } /// Returns the maximum height of the [`Stack`]. /// /// # Note /// /// The height is equal to the number of [`Operand`]s on the [`Stack`]. pub fn max_height(&self) -> usize { self.operands.max_height() } /// Truncates `self` to the target `height`. /// /// All operands above `height` are dropped. /// /// # Panic /// /// If `height` is greater than the current height of `self`. pub fn trunc(&mut self, height: usize) { debug_assert!(height <= self.height()); while self.height() > height { self.pop(); } } /// Returns `true` is fuel metering is enabled for the associated [`Engine`]. fn is_fuel_metering_enabled(&self) -> bool { self.engine.config().get_consume_fuel() } /// Pushes the function enclosing Wasm `block` onto the [`Stack`]. /// /// # Note /// /// - If `consume_fuel` is `None` fuel metering is expected to be disabled. /// - If `consume_fuel` is `Some` fuel metering is expected to be enabled. /// /// # Errors /// /// If the stack height exceeds the maximum height. pub fn push_func_block( &mut self, ty: BlockType, label: LabelRef, consume_fuel: Option, ) -> Result<(), Error> { debug_assert!(self.controls.is_empty()); debug_assert!(self.is_fuel_metering_enabled() == consume_fuel.is_some()); self.controls.push_block(ty, 0, label, consume_fuel); Ok(()) } /// Pushes a Wasm `block` onto the [`Stack`]. /// /// # Note /// /// This inherits the `consume_fuel` [`Instr`] from the parent [`ControlFrame`]. /// /// # Errors /// /// If the stack height exceeds the maximum height. pub fn push_block(&mut self, ty: BlockType, label: LabelRef) -> Result<(), Error> { debug_assert!(!self.controls.is_empty()); let len_params = usize::from(ty.len_params(&self.engine)); let block_height = self.height() - len_params; let consume_fuel = self.consume_fuel_instr(); self.controls .push_block(ty, block_height, label, consume_fuel); Ok(()) } /// Pushes a Wasm `loop` onto the [`Stack`]. /// /// # Panics (debug) /// /// - If `consume_fuel` is `None` and fuel metering is enabled. /// - If any of the Wasm `loop` operand parameters are _not_ [`Operand::Temp`]. /// /// # Errors /// /// If the stack height exceeds the maximum height. pub fn push_loop( &mut self, ty: BlockType, label: LabelRef, consume_fuel: Option, ) -> Result<(), Error> { debug_assert!(!self.controls.is_empty()); debug_assert!(self.is_fuel_metering_enabled() == consume_fuel.is_some()); let len_params = usize::from(ty.len_params(&self.engine)); let block_height = self.height() - len_params; debug_assert!(self .operands .peek(len_params) .all(|operand| operand.is_temp())); self.controls .push_loop(ty, block_height, label, consume_fuel); Ok(()) } /// Pushes a Wasm `if` onto the [`Stack`]. /// /// # Panics (debug) /// /// If `consume_fuel` is `None` and fuel metering is enabled. /// /// # Errors /// /// If the stack height exceeds the maximum height. pub fn push_if( &mut self, ty: BlockType, label: LabelRef, reachability: IfReachability, consume_fuel: Option, ) -> Result<(), Error> { debug_assert!(!self.controls.is_empty()); debug_assert!(self.is_fuel_metering_enabled() == consume_fuel.is_some()); let len_params = usize::from(ty.len_params(&self.engine)); let block_height = self.height() - len_params; let else_operands = self.operands.peek(len_params); debug_assert!(len_params == else_operands.len()); self.controls.push_if( ty, block_height, label, consume_fuel, reachability, else_operands, ); Ok(()) } /// Pushes a Wasm `else` onto the [`Stack`]. /// /// # Panics (debug) /// /// If `consume_fuel` is `None` and fuel metering is enabled. /// /// # Errors /// /// If the stack height exceeds the maximum height. pub fn push_else( &mut self, if_frame: IfControlFrame, is_end_of_then_reachable: bool, consume_fuel: Option, ) -> Result<(), Error> { debug_assert!(self.is_fuel_metering_enabled() == consume_fuel.is_some()); self.push_else_operands(&if_frame)?; self.controls .push_else(if_frame, consume_fuel, is_end_of_then_reachable); Ok(()) } /// Pushes an unreachable Wasm control onto the [`Stack`]. /// /// # Errors /// /// If the stack height exceeds the maximum height. pub fn push_unreachable(&mut self, kind: ControlFrameKind) -> Result<(), Error> { self.controls.push_unreachable(kind); Ok(()) } /// Pops the top-most control frame from the control stack and returns it. /// /// # Panics /// /// If the control stack is empty. pub fn pop_control(&mut self) -> ControlFrame { self.controls .pop() .unwrap_or_else(|| panic!("tried to pop control from empty control stack")) } /// Pushes the top-most `else` operands from the control stack onto the operand stack. /// /// # Panics (Debug) /// /// If the `else` operands are not in orphaned state. pub fn push_else_operands(&mut self, frame: &IfControlFrame) -> Result<(), Error> { match frame.reachability() { IfReachability::Both { .. } => {} IfReachability::OnlyThen | IfReachability::OnlyElse => return Ok(()), }; self.trunc(frame.height()); for else_operand in self.controls.pop_else_operands() { self.operands.push_operand(else_operand)?; } Ok(()) } /// Returns a shared reference to the [`ControlFrame`] at `depth`. /// /// # Panics /// /// If `depth` is out of bounds for `self`. pub fn peek_control(&self, depth: usize) -> &ControlFrame { self.controls.get(depth) } /// Returns an exclusive reference to the [`ControlFrame`] at `depth`. /// /// # Note /// /// This returns an [`AcquiredTarget`] to differentiate between the function /// body Wasm `block` and other control frames in order to know whether a branching /// target returns or branches. /// /// # Panics /// /// If `depth` is out of bounds for `self`. pub fn peek_control_mut(&mut self, depth: usize) -> AcquiredTarget<'_> { self.controls.acquire_target(depth) } /// Pushes the [`Operand`] back to the [`Stack`]. /// /// Returns the new [`OperandIdx`]. /// /// # Errors /// /// - If too many operands have been pushed onto the [`Stack`]. /// - If the local with `local_idx` does not exist. pub fn push_operand(&mut self, operand: Operand) -> Result { self.operands.push_operand(operand) } /// Pushes a local variable with index `local_idx` to the [`Stack`]. /// /// # Errors /// /// - If too many operands have been pushed onto the [`Stack`]. /// - If the local with `local_idx` does not exist. pub fn push_local(&mut self, local_index: LocalIdx, ty: ValType) -> Result { self.operands.push_local(local_index, ty) } /// Pushes a temporary with type `ty` on the [`Stack`]. /// /// # Errors /// /// If too many operands have been pushed onto the [`Stack`]. #[inline] pub fn push_temp(&mut self, ty: ValType, instr: Option) -> Result { self.operands.push_temp(ty, instr) } /// Pushes an immediate `value` on the [`Stack`]. /// /// # Errors /// /// If too many operands have been pushed onto the [`Stack`]. #[inline] pub fn push_immediate(&mut self, value: impl Into) -> Result { self.operands.push_immediate(value) } /// Peeks the [`Operand`] at `depth`. /// /// # Note /// /// A depth of 0 peeks the top-most [`Operand`] on `self`. /// /// # Panics /// /// If `depth` is out of bounds for `self`. #[inline] pub fn peek(&self, depth: usize) -> Operand { self.operands.get(depth) } /// Peeks the 2 top-most [`Operand`]s. /// /// # Panics /// /// If there aren't at least 2 [`Operand`]s on the [`Stack`]. #[inline] pub fn peek2(&self) -> (Operand, Operand) { let v0 = self.peek(1); let v1 = self.peek(0); (v0, v1) } /// Peeks the 3 top-most [`Operand`]s. /// /// # Panics /// /// If there aren't at least 2 [`Operand`]s on the [`Stack`]. pub fn peek3(&self) -> (Operand, Operand, Operand) { let v0 = self.peek(2); let v1 = self.peek(1); let v2 = self.peek(0); (v0, v1, v2) } /// Returns an iterator yielding the top-most `len` operands from the stack. /// /// Operands are yieleded in insertion order. pub fn peek_n(&self, len: usize) -> PeekedOperands<'_> { self.operands.peek(len) } /// Pops the top-most [`Operand`] from the [`Stack`]. /// /// # Panics /// /// If `self` is empty. #[inline] pub fn pop(&mut self) -> Operand { self.operands.pop() } /// Pops the two top-most [`Operand`] from the [`Stack`]. /// /// # Note /// /// The last returned [`Operand`] is the top-most one. /// /// # Panics /// /// If `self` does not contain enough operands to pop. #[inline] pub fn pop2(&mut self) -> (Operand, Operand) { let o2 = self.pop(); let o1 = self.pop(); (o1, o2) } /// Pops the two top-most [`Operand`] from the [`Stack`]. /// /// # Note /// /// The last returned [`Operand`] is the top-most one. /// /// # Panics /// /// If `self` does not contain enough operands to pop. pub fn pop3(&mut self) -> (Operand, Operand, Operand) { let o3 = self.pop(); let o2 = self.pop(); let o1 = self.pop(); (o1, o2, o3) } /// Pops `len` operands from the stack and store them into `buffer`. /// /// Operands stored into the buffer are placed in order. pub fn pop_n(&mut self, len: usize, buffer: &mut Vec) { buffer.clear(); for _ in 0..len { let operand = self.pop(); buffer.push(operand); } buffer.reverse(); } /// Preserve all locals on the [`Stack`] that refer to `local_index`. /// /// This is done by converting those locals to [`Operand::Temp`] and yielding them. /// /// # Note /// /// The users must fully consume all items yielded by the returned iterator in order /// for the local preservation to take full effect. /// /// # Panics /// /// If the local at `local_index` is out of bounds. #[must_use] pub fn preserve_locals(&mut self, local_index: LocalIdx) -> PreservedLocalsIter<'_> { self.operands.preserve_locals(local_index) } /// Preserve all locals on the [`OperandStack`]. /// /// This is done by converting those locals to [`StackOperand::Temp`] and yielding them. /// /// # Note /// /// The users must fully consume all items yielded by the returned iterator in order /// for the local preservation to take full effect. #[must_use] pub fn preserve_all_locals(&mut self) -> PreservedAllLocalsIter<'_> { self.operands.preserve_all_locals() } /// Converts and returns the [`Operand`] at `depth` into a [`Operand::Temp`]. /// /// # Note /// /// - Returns the [`Operand`] at `depth` before being converted to an [`Operand::Temp`]. /// - [`Operand::Temp`] will have their optional `instr` set to `None`. /// /// # Panics /// /// If `depth` is out of bounds for the [`Stack`] of operands. #[must_use] pub fn operand_to_temp(&mut self, depth: usize) -> Operand { self.operands.operand_to_temp(depth) } /// Returns the current [`Op::ConsumeFuel`] if fuel metering is enabled. /// /// Returns `None` otherwise. #[inline] pub fn consume_fuel_instr(&self) -> Option { self.controls.consume_fuel_instr() } } wasmi-1.1.0/src/engine/translator/func/stack/operand.rs000064400000000000000000000124161046102023000212320ustar 00000000000000use super::{LocalIdx, OperandIdx, StackOperand}; use crate::{core::TypedVal, engine::translator::utils::Instr, ValType}; #[cfg(doc)] use super::Stack; /// An operand on the [`Stack`]. #[derive(Debug, Copy, Clone)] pub enum Operand { /// A local variable operand. Local(LocalOperand), /// A temporary operand. Temp(TempOperand), /// An immediate value operand. Immediate(ImmediateOperand), } impl Operand { /// Creates a new [`Operand`] from the given [`StackOperand`] and its [`OperandIdx`]. pub(super) fn new(index: OperandIdx, operand: StackOperand) -> Self { match operand { StackOperand::Local { local_index, ty, .. } => Self::local(index, local_index, ty), StackOperand::Temp { ty, instr } => Self::temp(index, ty, instr), StackOperand::Immediate { val } => Self::immediate(index, val), } } /// Returns `true` if `self` and `other` evaluate to the same value. pub fn is_same(&self, other: &Self) -> bool { match (self, other) { (Operand::Local(lhs), Operand::Local(rhs)) => lhs.local_index() == rhs.local_index(), (Operand::Temp(lhs), Operand::Temp(rhs)) => lhs.operand_index() == rhs.operand_index(), (Operand::Immediate(lhs), Operand::Immediate(rhs)) => lhs.val() == rhs.val(), _ => false, } } /// Creates a local [`Operand`]. pub(super) fn local(operand_index: OperandIdx, local_index: LocalIdx, ty: ValType) -> Self { Self::Local(LocalOperand { operand_index, local_index, ty, }) } /// Creates a temporary [`Operand`]. pub(super) fn temp(operand_index: OperandIdx, ty: ValType, instr: Option) -> Self { Self::Temp(TempOperand { operand_index, ty, instr, }) } /// Creates an immediate [`Operand`]. pub(super) fn immediate(operand_index: OperandIdx, val: TypedVal) -> Self { Self::Immediate(ImmediateOperand { operand_index, val }) } /// Returns `true` if `self` is an [`Operand::Temp`]. pub fn is_temp(&self) -> bool { matches!(self, Self::Temp(_)) } /// Returns the [`OperandIdx`] of the [`Operand`]. pub fn index(&self) -> OperandIdx { match self { Operand::Local(operand) => operand.operand_index(), Operand::Temp(operand) => operand.operand_index(), Operand::Immediate(operand) => operand.operand_index(), } } /// Returns the type of the [`Operand`]. pub fn ty(&self) -> ValType { match self { Self::Local(operand) => operand.ty(), Self::Temp(operand) => operand.ty(), Self::Immediate(operand) => operand.ty(), } } } /// A local variable on the [`Stack`]. #[derive(Debug, Copy, Clone)] pub struct LocalOperand { /// The index of the operand. operand_index: OperandIdx, /// The index of the local variable. local_index: LocalIdx, /// The type of the local variable. ty: ValType, } impl From for Operand { fn from(operand: LocalOperand) -> Self { Self::Local(operand) } } impl LocalOperand { /// Returns the operand index of the [`LocalOperand`]. pub fn operand_index(&self) -> OperandIdx { self.operand_index } /// Returns the index of the [`LocalOperand`]. pub fn local_index(&self) -> LocalIdx { self.local_index } /// Returns the type of the [`LocalOperand`]. pub fn ty(&self) -> ValType { self.ty } } /// A temporary on the [`Stack`]. #[derive(Debug, Copy, Clone)] pub struct TempOperand { /// The index of the operand. operand_index: OperandIdx, /// The type of the temporary. ty: ValType, /// The instruction which created this [`TempOperand`] as its result if any. instr: Option, } impl From for Operand { fn from(operand: TempOperand) -> Self { Self::Temp(operand) } } impl TempOperand { /// Returns the operand index of the [`TempOperand`]. pub fn operand_index(&self) -> OperandIdx { self.operand_index } /// Returns the type of the [`TempOperand`]. pub fn ty(&self) -> ValType { self.ty } /// Returns the instruction which created this [`TempOperand`] as its result if any. pub fn instr(&self) -> Option { self.instr } } /// An immediate value on the [`Stack`]. #[derive(Debug, Copy, Clone)] pub struct ImmediateOperand { /// The index of the operand. operand_index: OperandIdx, /// The value and type of the immediate value. val: TypedVal, } impl From for Operand { fn from(operand: ImmediateOperand) -> Self { Self::Immediate(operand) } } impl ImmediateOperand { /// Returns the operand index of the [`ImmediateOperand`]. pub fn operand_index(&self) -> OperandIdx { self.operand_index } /// Returns the immediate value (and its type) of the [`ImmediateOperand`]. pub fn val(&self) -> TypedVal { self.val } /// Returns the type of the [`ImmediateOperand`]. pub fn ty(&self) -> ValType { self.val.ty() } } impl AsRef for Operand { fn as_ref(&self) -> &Operand { self } } wasmi-1.1.0/src/engine/translator/func/stack/operands.rs000064400000000000000000000413271046102023000214200ustar 00000000000000use super::{LocalIdx, LocalsHead, Operand, Reset}; use crate::{core::TypedVal, engine::translator::utils::Instr, Error, ValType}; use alloc::vec::Vec; use core::{num::NonZero, slice}; /// A [`StackOperand`] or [`Operand`] index on the [`OperandStack`]. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct OperandIdx(NonZero); impl From for usize { fn from(value: OperandIdx) -> Self { value.0.get().wrapping_sub(1) } } impl From for OperandIdx { fn from(value: usize) -> Self { let Some(operand_idx) = NonZero::new(value.wrapping_add(1)) else { panic!("out of bounds `OperandIdx`: {value}") }; Self(operand_idx) } } /// An [`Operand`] on the [`OperandStack`]. /// /// This is the internal version of [`Operand`] with information that shall remain /// hidden to the outside. #[derive(Debug, Copy, Clone)] pub enum StackOperand { /// A local variable. Local { /// The index of the local variable. local_index: LocalIdx, /// The type of the local operand. /// /// This does not have to be the type of the associated local but /// might be a type overwrite. This is useful for Wasm `reinterpret` /// operators with local operand inputs. ty: ValType, /// The previous [`StackOperand::Local`] on the [`OperandStack`]. prev_local: Option, /// The next [`StackOperand::Local`] on the [`OperandStack`]. next_local: Option, }, /// A temporary value on the [`OperandStack`]. Temp { /// The type of the temporary value. ty: ValType, /// The instruction which has this [`StackOperand`] as result if any. instr: Option, }, /// An immediate value on the [`OperandStack`]. Immediate { /// The value (and type) of the immediate value. val: TypedVal, }, } impl StackOperand { /// Returns the [`ValType`] of the [`StackOperand`]. pub fn ty(&self) -> ValType { match self { StackOperand::Temp { ty, .. } => *ty, StackOperand::Immediate { val } => val.ty(), StackOperand::Local { ty, .. } => *ty, } } } /// The Wasm operand (or value) stack. #[derive(Debug, Default)] pub struct OperandStack { /// The current set of operands on the [`OperandStack`]. operands: Vec, /// Stores the first occurrences of every local variable on the [`OperandStack`] if any. local_heads: LocalsHead, /// The maximum height of the [`OperandStack`]. max_height: usize, /// The current number of local operands on the `operands` stack. /// /// This field is required to optimize [`OperandStack::preserve_all_locals`]. len_locals: usize, } impl Reset for OperandStack { fn reset(&mut self) { self.operands.clear(); self.local_heads.reset(); self.max_height = 0; self.len_locals = 0; } } impl OperandStack { /// Slot `amount` local variables. /// /// # Errors /// /// If too many local variables are being registered. pub fn register_locals(&mut self, amount: usize) -> Result<(), Error> { self.local_heads.register(amount)?; Ok(()) } /// Returns the current height of `self` /// /// # Note /// /// The height is equal to the number of [`StackOperand`]s on `self`. pub fn height(&self) -> usize { self.operands.len() } /// Returns the maximum height of `self`. /// /// # Note /// /// The height is equal to the number of [`Operand`]s on `self`. pub fn max_height(&self) -> usize { self.max_height } /// Updates the maximum stack height if needed. fn update_max_stack_height(&mut self) { self.max_height = core::cmp::max(self.max_height, self.height()); } /// Returns the [`OperandIdx`] of the next pushed operand. fn next_index(&self) -> OperandIdx { OperandIdx::from(self.operands.len()) } /// Returns the [`OperandIdx`] of the operand at `depth`. fn depth_to_index(&self, depth: usize) -> OperandIdx { OperandIdx::from(self.height() - depth - 1) } /// Pushes the [`Operand`] back to the [`OperandStack`]. /// /// Returns the new [`OperandIdx`]. /// /// # Errors /// /// - If too many operands have been pushed onto the [`OperandStack`]. /// - If the local with `local_idx` does not exist. pub fn push_operand(&mut self, operand: Operand) -> Result { match operand { Operand::Local(operand) => self.push_local(operand.local_index(), operand.ty()), Operand::Temp(operand) => self.push_temp(operand.ty(), operand.instr()), Operand::Immediate(operand) => self.push_immediate(operand.val()), } } /// Pushes a local variable with index `local_idx` to the [`OperandStack`]. /// /// # Errors /// /// - If too many operands have been pushed onto the [`OperandStack`]. /// - If the local with `local_idx` does not exist. pub fn push_local(&mut self, local_index: LocalIdx, ty: ValType) -> Result { let operand_index = self.next_index(); let next_local = self .local_heads .replace_first(local_index, Some(operand_index)); if let Some(next_local) = next_local { self.update_prev_local(next_local, Some(operand_index)); } self.operands.push(StackOperand::Local { local_index, ty, prev_local: None, next_local, }); self.update_max_stack_height(); self.len_locals += 1; Ok(operand_index) } /// Pushes a temporary with type `ty` on the [`OperandStack`]. /// /// # Errors /// /// If too many operands have been pushed onto the [`OperandStack`]. #[inline] pub fn push_temp(&mut self, ty: ValType, instr: Option) -> Result { let idx = self.next_index(); self.operands.push(StackOperand::Temp { ty, instr }); self.update_max_stack_height(); Ok(idx) } /// Pushes an immediate `value` on the [`OperandStack`]. /// /// # Errors /// /// If too many operands have been pushed onto the [`OperandStack`]. #[inline] pub fn push_immediate(&mut self, value: impl Into) -> Result { let idx = self.next_index(); self.operands .push(StackOperand::Immediate { val: value.into() }); self.update_max_stack_height(); Ok(idx) } /// Returns an iterator that yields the last `n` [`Operand`]s. /// /// # Panics /// /// If `n` is out of bounds for `self`. pub fn peek(&self, n: usize) -> PeekedOperands<'_> { let len_operands = self.operands.len(); let first_index = len_operands - n; let Some(operands) = self.operands.get(first_index..) else { return PeekedOperands::empty(); }; PeekedOperands { index: first_index, operands: operands.iter(), } } /// Pops the top-most [`StackOperand`] from `self` if any. /// /// # Panics /// /// If `self` is empty. #[inline] pub fn pop(&mut self) -> Operand { let Some(operand) = self.operands.pop() else { panic!("tried to pop operand from empty stack"); }; let index = self.next_index(); self.try_unlink_local(operand); Operand::new(index, operand) } /// Returns the [`Operand`] at `depth`. /// /// # Panics /// /// If `depth` is out of bounds for `self`. #[inline] pub fn get(&self, depth: usize) -> Operand { let index = self.depth_to_index(depth); let operand = self.get_at(index); Operand::new(index, operand) } /// Returns the [`StackOperand`] at `index`. /// /// # Panics /// /// If `index` is out of bounds for `self`. #[inline] fn get_at(&self, index: OperandIdx) -> StackOperand { self.operands[usize::from(index)] } /// Converts and returns the [`Operand`] at `depth` into a [`Operand::Temp`]. /// /// # Note /// /// - Returns the [`Operand`] at `depth` before being converted to an [`Operand::Temp`]. /// - [`Operand::Temp`] will have their optional `instr` set to `None`. /// /// # Panics /// /// If `depth` is out of bounds for the [`OperandStack`] of operands. #[must_use] pub fn operand_to_temp(&mut self, depth: usize) -> Operand { let index = self.depth_to_index(depth); let operand = self.operand_to_temp_at(index); Operand::new(index, operand) } /// Converts and returns the [`StackOperand`] at `index` into a [`StackOperand::Temp`]. /// /// # Note /// /// - Returns the [`Operand`] at `index` before being converted to an [`Operand::Temp`]. /// - [`Operand::Temp`] will have their optional `instr` set to `None`. /// /// # Panics /// /// If `index` is out of bounds for `self`. #[must_use] fn operand_to_temp_at(&mut self, index: OperandIdx) -> StackOperand { let operand = self.get_at(index); let ty = operand.ty(); self.try_unlink_local(operand); self.operands[usize::from(index)] = StackOperand::Temp { ty, instr: None }; operand } /// Preserve all locals on the [`OperandStack`] that refer to `local_index`. /// /// This is done by converting those locals to [`StackOperand::Temp`] and yielding them. /// /// # Note /// /// The users must fully consume all items yielded by the returned iterator in order /// for the local preservation to take full effect. /// /// # Panics /// /// If the local at `local_index` is out of bounds. #[must_use] pub fn preserve_locals(&mut self, local_index: LocalIdx) -> PreservedLocalsIter<'_> { let index = self.local_heads.replace_first(local_index, None); PreservedLocalsIter { operands: self, index, } } /// Preserve all locals on the [`OperandStack`]. /// /// This is done by converting those locals to [`StackOperand::Temp`] and yielding them. /// /// # Note /// /// The users must fully consume all items yielded by the returned iterator in order /// for the local preservation to take full effect. #[must_use] pub fn preserve_all_locals(&mut self) -> PreservedAllLocalsIter<'_> { let index = self.operands.len(); PreservedAllLocalsIter { operands: self, index, } } /// Unlinks the [`StackOperand::Local`] `operand` at `index` from `self`. /// /// Does nothing if `operand` is not a [`StackOperand::Local`]. #[inline] fn try_unlink_local(&mut self, operand: StackOperand) { let StackOperand::Local { local_index, prev_local, next_local, .. } = operand else { return; }; self.unlink_local(local_index, prev_local, next_local); } /// Unlinks the [`StackOperand::Local`] `operand` identified by the parameters from `self`. fn unlink_local( &mut self, local_index: LocalIdx, prev_local: Option, next_local: Option, ) { if let Some(prev_local) = prev_local { self.update_next_local(prev_local, next_local); } else { self.local_heads.replace_first(local_index, next_local); } if let Some(next_local) = next_local { self.update_prev_local(next_local, prev_local); } self.len_locals -= 1; } /// Updates the `prev_local` of the [`StackOperand::Local`] at `local_index` to `prev_index`. /// /// # Panics /// /// - If `local_index` does not refer to a [`StackOperand::Local`]. /// - If `local_index` is out of bounds of the operand stack. fn update_prev_local(&mut self, local_index: OperandIdx, prev_index: Option) { match self.operands.get_mut(usize::from(local_index)) { Some(StackOperand::Local { prev_local, .. }) => { *prev_local = prev_index; } entry => panic!("expected `StackOperand::Local` but found: {entry:?}"), } } /// Updates the `next_local` of the [`StackOperand::Local`] at `local_index` to `prev_index`. /// /// # Panics /// /// - If `local_index` does not refer to a [`StackOperand::Local`]. /// - If `local_index` is out of bounds of the operand stack. fn update_next_local(&mut self, local_index: OperandIdx, next_index: Option) { match self.operands.get_mut(usize::from(local_index)) { Some(StackOperand::Local { next_local, .. }) => { *next_local = next_index; } entry => panic!("expected `StackOperand::Local` but found: {entry:?}"), } } } /// Iterator yielding preserved local indices while preserving them. /// /// # Note /// /// This intentionally iterates backwards from the last pushed stack operand to the first one. /// Together with the remaining number of local operands on the stack this achieves armortized /// constant O(1) preservation for all locals via [`OperandStack::preserve_all_locals`]. /// /// The reason for this is that a single call to [`OperandStack::preserve_all_locals`] has the /// effect that there are no more local operands on the stack. New locals are always pushed to the /// top of the stack. A single Wasm `local.get` operation (or similar) may only push a single local /// operand on the stack. This iterator yields once there are no more local operands and since /// it iterates from the back (top-most) operand it will find the newly inserted locals in /// armortized constant O(1) time. #[derive(Debug)] pub struct PreservedAllLocalsIter<'stack> { /// The underlying operand stack. operands: &'stack mut OperandStack, /// The current operand index of the next preserved local if any. index: usize, } impl PreservedAllLocalsIter<'_> { /// Returns `true` if there are remaining local operands on the stack. fn has_remaining_locals(&self) -> bool { self.operands.len_locals != 0 } /// Returns the index of the next local operand on the stack if any. /// /// Returns `None` if there are no more local operands on the stack. fn find_next_local(&mut self) -> Option { let mut index = self.index; loop { index -= 1; let opd = self.operands.operands.get(index)?; if let StackOperand::Local { .. } = opd { return Some(index); } } } } impl Iterator for PreservedAllLocalsIter<'_> { type Item = Operand; fn next(&mut self) -> Option { if !self.has_remaining_locals() { return None; } self.index = self.find_next_local()?; let index = OperandIdx::from(self.index); let operand = self.operands.operand_to_temp_at(index); debug_assert!(matches!(operand, StackOperand::Local { .. })); Some(Operand::new(index, operand)) } } /// Iterator yielding preserved local indices while preserving them. #[derive(Debug)] pub struct PreservedLocalsIter<'stack> { /// The underlying operand stack. operands: &'stack mut OperandStack, /// The current operand index of the next preserved local if any. index: Option, } impl Iterator for PreservedLocalsIter<'_> { type Item = OperandIdx; fn next(&mut self) -> Option { let index = self.index?; let operand = self.operands.operand_to_temp_at(index); self.index = match operand { StackOperand::Local { next_local, .. } => next_local, op => panic!("expected `StackOperand::Local` but found: {op:?}"), }; Some(index) } } /// Iterator yielding peeked stack operators. #[derive(Debug)] pub struct PeekedOperands<'stack> { /// The index of the next yielded operand. index: usize, /// The iterator of peeked stack operands. operands: slice::Iter<'stack, StackOperand>, } impl<'stack> PeekedOperands<'stack> { /// Creates a [`PeekedOperands`] iterator that yields no operands. pub fn empty() -> Self { Self { index: 0, operands: [].iter(), } } } impl Iterator for PeekedOperands<'_> { type Item = Operand; fn next(&mut self) -> Option { let operand = self.operands.next().copied()?; let index = OperandIdx::from(self.index); self.index += 1; Some(Operand::new(index, operand)) } } impl ExactSizeIterator for PeekedOperands<'_> { fn len(&self) -> usize { self.operands.len() } } wasmi-1.1.0/src/engine/translator/func/utils.rs000064400000000000000000000034011046102023000176270ustar 00000000000000use crate::ir::{Const16, Const32, Slot}; /// Bail out early in case the current code is unreachable. /// /// # Note /// /// - This should be prepended to most Wasm operator translation procedures. /// - If we are in unreachable code most Wasm translation is skipped. Only /// certain control flow operators such as `End` are going through the /// translation process. In particular the `End` operator may end unreachable /// code blocks. macro_rules! bail_unreachable { ($this:ident) => {{ if !$this.reachable { return ::core::result::Result::Ok(()); } }}; } /// Used to swap operands of binary [`Op`] constructor. /// /// [`Op`]: crate::ir::Op macro_rules! swap_ops { ($make_instr:path) => {{ |result: $crate::ir::Slot, lhs, rhs| -> $crate::ir::Op { $make_instr(result, rhs, lhs) } }}; } /// Implemented by types that can be reset for reuse. pub trait Reset: Sized { /// Resets `self` for reuse. fn reset(&mut self); /// Returns `self` in reset state. #[must_use] fn into_reset(self) -> Self { let mut this = self; this.reset(); this } } /// Types that have reusable heap allocations. pub trait ReusableAllocations { /// The type of the reusable heap allocations. type Allocations: Default + Reset; /// Returns the reusable heap allocations of `self`. fn into_allocations(self) -> Self::Allocations; } /// A 16-bit encoded input to Wasmi instruction. pub type Input16 = Input>; /// A 32-bit encoded input to Wasmi instruction. pub type Input32 = Input>; /// A concrete input to a Wasmi instruction. pub enum Input { /// A [`Slot`] operand. Slot(Slot), /// A 16-bit encoded immediate value operand. Immediate(T), } wasmi-1.1.0/src/engine/translator/func/visit.rs000064400000000000000000002054041046102023000176340ustar 00000000000000use super::{ControlFrame, ControlFrameKind, FuncTranslator, LocalIdx}; use crate::{ core::{wasm, FuelCostsProvider, IndexType, TypedVal}, engine::{ translator::func::{ op, stack::{AcquiredTarget, IfReachability}, ControlFrameBase, Input, Operand, }, BlockType, }, ir::{self, Const16, Op}, module::{self, FuncIdx, MemoryIdx, TableIdx, WasmiValueType}, Error, ExternRef, Func, FuncType, Mutability, Ref, TrapCode, ValType, F32, F64, }; use alloc::vec::Vec; use wasmparser::VisitOperator; macro_rules! impl_visit_operator { ( @mvp $($rest:tt)* ) => { impl_visit_operator!(@@skipped $($rest)*); }; ( @sign_extension $($rest:tt)* ) => { impl_visit_operator!(@@skipped $($rest)*); }; ( @saturating_float_to_int $($rest:tt)* ) => { impl_visit_operator!(@@skipped $($rest)*); }; ( @bulk_memory $($rest:tt)* ) => { impl_visit_operator!(@@skipped $($rest)*); }; ( @reference_types $($rest:tt)* ) => { impl_visit_operator!(@@skipped $($rest)*); }; ( @tail_call $($rest:tt)* ) => { impl_visit_operator!(@@skipped $($rest)*); }; ( @wide_arithmetic $($rest:tt)* ) => { impl_visit_operator!(@@skipped $($rest)*); }; ( @@skipped $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident $_ann:tt $($rest:tt)* ) => { // We skip Wasm operators that we already implement manually. impl_visit_operator!($($rest)*); }; ( @$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident $_ann:tt $($rest:tt)* ) => { // Wildcard match arm for all the other (yet) unsupported Wasm proposals. fn $visit(&mut self $($(, $arg: $argty)*)?) -> Self::Output { $( $( let _ = $arg; )* )? self.translate_unsupported_operator(stringify!($op)) } impl_visit_operator!($($rest)*); }; () => {}; } impl FuncTranslator { /// Called when translating an unsupported Wasm operator. /// /// # Note /// /// We panic instead of returning an error because unsupported Wasm /// errors should have been filtered out by the validation procedure /// already, therefore encountering an unsupported Wasm operator /// in the function translation procedure can be considered a bug. pub fn translate_unsupported_operator(&self, name: &str) -> Result<(), Error> { panic!("tried to translate an unsupported Wasm operator: {name}") } } impl<'a> VisitOperator<'a> for FuncTranslator { type Output = Result<(), Error>; #[cfg(feature = "simd")] fn simd_visitor( &mut self, ) -> Option<&mut dyn wasmparser::VisitSimdOperator<'a, Output = Self::Output>> { Some(self) } wasmparser::for_each_visit_operator!(impl_visit_operator); #[inline(never)] fn visit_unreachable(&mut self) -> Self::Output { bail_unreachable!(self); self.translate_trap(TrapCode::UnreachableCodeReached) } #[inline(never)] fn visit_nop(&mut self) -> Self::Output { Ok(()) } #[inline(never)] fn visit_block(&mut self, block_ty: wasmparser::BlockType) -> Self::Output { if !self.reachable { self.stack.push_unreachable(ControlFrameKind::Block)?; return Ok(()); } self.preserve_all_locals()?; let block_ty = BlockType::new(block_ty, &self.module); let end_label = self.labels.new_label(); self.stack.push_block(block_ty, end_label)?; Ok(()) } #[inline(never)] fn visit_loop(&mut self, block_ty: wasmparser::BlockType) -> Self::Output { if !self.reachable { self.stack.push_unreachable(ControlFrameKind::Loop)?; return Ok(()); } self.preserve_all_locals()?; let block_ty = BlockType::new(block_ty, &self.module); let len_params = block_ty.len_params(&self.engine); let continue_label = self.labels.new_label(); let consume_fuel = self.stack.consume_fuel_instr(); self.move_operands_to_temp(usize::from(len_params), consume_fuel)?; self.pin_label(continue_label); let consume_fuel = self.instrs.push_consume_fuel_instr()?; self.stack .push_loop(block_ty, continue_label, consume_fuel)?; // Need to reset `last_instr` because a loop header is a control flow boundary. self.instrs.reset_last_instr(); Ok(()) } #[inline(never)] fn visit_if(&mut self, block_ty: wasmparser::BlockType) -> Self::Output { if !self.reachable { self.stack.push_unreachable(ControlFrameKind::If)?; return Ok(()); } let end_label = self.labels.new_label(); let condition = self.stack.pop(); self.preserve_all_locals()?; let (reachability, consume_fuel_instr) = match condition { Operand::Immediate(operand) => { let condition = i32::from(operand.val()); let reachability = match condition { 0 => { self.reachable = false; IfReachability::OnlyElse } _ => IfReachability::OnlyThen, }; let consume_fuel_instr = self.stack.consume_fuel_instr(); (reachability, consume_fuel_instr) } _ => { let else_label = self.labels.new_label(); self.encode_br_eqz(condition, else_label)?; let reachability = IfReachability::Both { else_label }; let consume_fuel_instr = self.instrs.push_consume_fuel_instr()?; (reachability, consume_fuel_instr) } }; let block_ty = BlockType::new(block_ty, &self.module); self.stack .push_if(block_ty, end_label, reachability, consume_fuel_instr)?; Ok(()) } #[inline(never)] fn visit_else(&mut self) -> Self::Output { let mut frame = match self.stack.pop_control() { ControlFrame::If(frame) => frame, ControlFrame::Unreachable(ControlFrameKind::If) => { debug_assert!(!self.reachable); self.stack.push_unreachable(ControlFrameKind::Else)?; return Ok(()); } unexpected => panic!("expected `if` control frame but found: {unexpected:?}"), }; // After `then` block, before `else` block: // - Copy `if` branch parameters. // - Branch from end of `then` to end of `if`. let is_end_of_then_reachable = self.reachable; if let IfReachability::Both { else_label } = frame.reachability() { if is_end_of_then_reachable { let consume_fuel_instr = frame.consume_fuel_instr(); self.copy_branch_params(&frame, consume_fuel_instr)?; frame.branch_to(); self.encode_br(frame.label())?; } // Start of `else` block: self.labels .pin_label(else_label, self.instrs.next_instr()) .unwrap(); self.instrs.reset_last_instr(); } let consume_fuel_instr = self.instrs.push_consume_fuel_instr()?; self.reachable = frame.is_else_reachable(); self.stack .push_else(frame, is_end_of_then_reachable, consume_fuel_instr)?; Ok(()) } #[inline(never)] fn visit_end(&mut self) -> Self::Output { match self.stack.pop_control() { ControlFrame::Block(frame) => self.translate_end_block(frame), ControlFrame::Loop(frame) => self.translate_end_loop(frame), ControlFrame::If(frame) => self.translate_end_if(frame), ControlFrame::Else(frame) => self.translate_end_else(frame), ControlFrame::Unreachable(frame) => self.translate_end_unreachable(frame), } } #[inline(never)] fn visit_br(&mut self, depth: u32) -> Self::Output { bail_unreachable!(self); let Ok(depth) = usize::try_from(depth) else { panic!("out of bounds depth: {depth}") }; let consume_fuel_instr = self.stack.consume_fuel_instr(); match self.stack.peek_control_mut(depth) { AcquiredTarget::Return(_) => self.visit_return(), AcquiredTarget::Branch(mut frame) => { frame.branch_to(); let label = frame.label(); let len_params = frame.len_branch_params(&self.engine); let branch_results = Self::frame_results_impl(&frame, &self.engine, &self.layout)?; if let Some(branch_results) = branch_results { self.encode_copies(branch_results, len_params, consume_fuel_instr)?; } self.encode_br(label)?; self.reachable = false; Ok(()) } } } #[inline(never)] fn visit_br_if(&mut self, depth: u32) -> Self::Output { bail_unreachable!(self); let condition = self.stack.pop(); if let Operand::Immediate(condition) = condition { if i32::from(condition.val()) != 0 { // Case (true): always takes the branch self.visit_br(depth)?; } return Ok(()); } let Ok(depth) = usize::try_from(depth) else { panic!("out of bounds depth: {depth}") }; let mut frame = self.stack.peek_control_mut(depth).control_frame(); frame.branch_to(); let len_branch_params = frame.len_branch_params(&self.engine); let branch_results = Self::frame_results_impl(&frame, &self.engine, &self.layout)?; let label = frame.label(); if len_branch_params == 0 { // Case: no branch values are required to be copied self.encode_br_nez(condition, label)?; return Ok(()); } if !self.requires_branch_param_copies(depth) { // Case: no branch values are required to be copied self.encode_br_nez(condition, label)?; return Ok(()); } // Case: fallback to copy branch parameters conditionally let consume_fuel_instr = self.stack.consume_fuel_instr(); let skip_label = self.labels.new_label(); self.encode_br_eqz(condition, skip_label)?; if let Some(branch_results) = branch_results { self.encode_copies(branch_results, len_branch_params, consume_fuel_instr)?; } self.encode_br(label)?; self.labels .pin_label(skip_label, self.instrs.next_instr()) .unwrap(); Ok(()) } #[inline(never)] fn visit_br_table(&mut self, table: wasmparser::BrTable<'a>) -> Self::Output { bail_unreachable!(self); let index = self.stack.pop(); let default_target = table.default(); if table.is_empty() { // Case: the `br_table` only has a single target `t` which is equal to a `br t`. return self.visit_br(default_target); } if let Operand::Immediate(index) = index { // Case: the `br_table` index is a constant value, therefore always taking the same branch. // Note: `usize::MAX` is used to fallback to the default target. let chosen_index = usize::try_from(u32::from(index.val())).unwrap_or(usize::MAX); let chosen_target = table .targets() .nth(chosen_index) .transpose()? .unwrap_or(default_target); return self.visit_br(chosen_target); } Self::copy_targets_from_br_table(&table, &mut self.immediates)?; let targets = &self.immediates[..]; if targets .iter() .all(|&target| u32::from(target) == default_target) { // Case: all targets are the same and thus the `br_table` is equal to a `br`. return self.visit_br(default_target); } // Note: The Wasm spec mandates that all `br_table` targets manipulate the // Wasm value stack the same. This implies for Wasmi that all `br_table` // targets have the same branch parameter arity. let Ok(default_target) = usize::try_from(default_target) else { panic!("out of bounds `default_target` does not fit into `usize`: {default_target}"); }; let index = self.layout.operand_to_reg(index)?; let len_branch_params = self .stack .peek_control(default_target) .len_branch_params(&self.engine); match len_branch_params { 0 => self.encode_br_table_0(table, index)?, n => self.encode_br_table_n(table, index, n)?, }; self.reachable = false; Ok(()) } #[inline(never)] fn visit_return(&mut self) -> Self::Output { bail_unreachable!(self); let consume_fuel_instr = self.stack.consume_fuel_instr(); self.encode_return(consume_fuel_instr)?; let len_results = self.func_type_with(FuncType::len_results); for _ in 0..len_results { self.stack.pop(); } self.reachable = false; Ok(()) } #[inline(never)] fn visit_call(&mut self, function_index: u32) -> Self::Output { bail_unreachable!(self); let func_idx = FuncIdx::from(function_index); let func_type = self.resolve_func_type(func_idx); let len_params = usize::from(func_type.len_params()); let results = self.call_regspan(len_params)?; let instr = match self.module.get_engine_func(func_idx) { Some(engine_func) => { // Case: We are calling an internal function and can optimize // this case by using the special instruction for it. match len_params { 0 => Op::call_internal_0(results, engine_func), _ => Op::call_internal(results, engine_func), } } None => { // Case: We are calling an imported function and must use the // general calling operator for it. match len_params { 0 => Op::call_imported_0(results, function_index), _ => Op::call_imported(results, function_index), } } }; let call_instr = self.push_instr(instr, FuelCostsProvider::call)?; self.stack.pop_n(len_params, &mut self.operands); self.instrs .encode_register_list(&self.operands, &mut self.layout)?; if let Some(span) = self.push_results(call_instr, func_type.results())? { debug_assert_eq!(span, results); } Ok(()) } #[inline(never)] fn visit_call_indirect(&mut self, type_index: u32, table_index: u32) -> Self::Output { bail_unreachable!(self); let func_type = self.resolve_type(type_index); let index = self.stack.pop(); let indirect_params = self.call_indirect_params(index, table_index)?; let len_params = usize::from(func_type.len_params()); let results = self.call_regspan(len_params)?; let instr = match (len_params, indirect_params) { (0, Op::CallIndirectParams { .. }) => Op::call_indirect_0(results, type_index), (0, Op::CallIndirectParamsImm16 { .. }) => { Op::call_indirect_0_imm16(results, type_index) } (_, Op::CallIndirectParams { .. }) => Op::call_indirect(results, type_index), (_, Op::CallIndirectParamsImm16 { .. }) => Op::call_indirect_imm16(results, type_index), _ => unreachable!(), }; let call_instr = self.push_instr(instr, FuelCostsProvider::call)?; self.push_param(indirect_params)?; self.stack.pop_n(len_params, &mut self.operands); self.instrs .encode_register_list(&self.operands, &mut self.layout)?; if let Some(span) = self.push_results(call_instr, func_type.results())? { debug_assert_eq!(span, results); } Ok(()) } #[inline(never)] fn visit_drop(&mut self) -> Self::Output { bail_unreachable!(self); _ = self.stack.pop(); Ok(()) } #[inline(never)] fn visit_select(&mut self) -> Self::Output { self.translate_select(None) } #[inline(never)] fn visit_typed_select_multi(&mut self, _tys: Vec) -> Self::Output { unimplemented!() } #[inline(never)] fn visit_local_get(&mut self, local_index: u32) -> Self::Output { bail_unreachable!(self); let local_idx = LocalIdx::from(local_index); let ty = self.locals.ty(local_idx); self.stack.push_local(local_idx, ty)?; Ok(()) } #[inline(never)] fn visit_local_set(&mut self, local_index: u32) -> Self::Output { self.translate_local_set(local_index, false) } #[inline(never)] fn visit_local_tee(&mut self, local_index: u32) -> Self::Output { self.translate_local_set(local_index, true) } #[inline(never)] fn visit_global_get(&mut self, global_index: u32) -> Self::Output { bail_unreachable!(self); let global_idx = module::GlobalIdx::from(global_index); let (global_type, init_value) = self.module.get_global(global_idx); let content = global_type.content(); if let (Mutability::Const, Some(init_expr)) = (global_type.mutability(), init_value) { if let Some(value) = init_expr.eval_const() { // Case: access to immutable internally defined global variables // can be replaced with their constant initialization value. self.stack.push_immediate(TypedVal::new(content, value))?; return Ok(()); } if let Some(func_index) = init_expr.funcref() { // Case: forward to `ref.func x` translation. self.visit_ref_func(func_index.into_u32())?; return Ok(()); } } // Case: The `global.get` instruction accesses a mutable or imported // global variable and thus cannot be optimized away. let global_idx = ir::index::Global::from(global_index); self.push_instr_with_result( content, |result| Op::global_get(result, global_idx), FuelCostsProvider::instance, )?; Ok(()) } #[inline(never)] fn visit_global_set(&mut self, global_index: u32) -> Self::Output { bail_unreachable!(self); let global = ir::index::Global::from(global_index); let input = match self.stack.pop() { Operand::Immediate(input) => input.val(), input => { // Case: `global.set` with simple register input. let input = self.layout.operand_to_reg(input)?; self.push_instr(Op::global_set(input, global), FuelCostsProvider::instance)?; return Ok(()); } }; // Note: at this point we handle the different immediate `global.set` instructions. let (global_type, _init_value) = self .module .get_global(module::GlobalIdx::from(global_index)); debug_assert_eq!(global_type.content(), input.ty()); match global_type.content() { ValType::I32 => { if let Ok(value) = Const16::try_from(i32::from(input)) { // Case: `global.set` with 16-bit encoded `i32` value. self.push_instr( Op::global_set_i32imm16(value, global), FuelCostsProvider::instance, )?; return Ok(()); } } ValType::I64 => { if let Ok(value) = Const16::try_from(i64::from(input)) { // Case: `global.set` with 16-bit encoded `i64` value. self.push_instr( Op::global_set_i64imm16(value, global), FuelCostsProvider::instance, )?; return Ok(()); } } _ => {} }; // Note: at this point we have to allocate a function local constant. let cref = self.layout.const_to_reg(input)?; self.push_instr(Op::global_set(cref, global), FuelCostsProvider::instance)?; Ok(()) } #[inline(never)] fn visit_i32_load(&mut self, memarg: wasmparser::MemArg) -> Self::Output { self.translate_load( memarg, ValType::I32, Op::load32, Op::load32_offset16, Op::load32_at, ) } #[inline(never)] fn visit_i64_load(&mut self, memarg: wasmparser::MemArg) -> Self::Output { self.translate_load( memarg, ValType::I64, Op::load64, Op::load64_offset16, Op::load64_at, ) } #[inline(never)] fn visit_f32_load(&mut self, memarg: wasmparser::MemArg) -> Self::Output { self.translate_load( memarg, ValType::F32, Op::load32, Op::load32_offset16, Op::load32_at, ) } #[inline(never)] fn visit_f64_load(&mut self, memarg: wasmparser::MemArg) -> Self::Output { self.translate_load( memarg, ValType::F64, Op::load64, Op::load64_offset16, Op::load64_at, ) } #[inline(never)] fn visit_i32_load8_s(&mut self, memarg: wasmparser::MemArg) -> Self::Output { self.translate_load( memarg, ValType::I32, Op::i32_load8_s, Op::i32_load8_s_offset16, Op::i32_load8_s_at, ) } #[inline(never)] fn visit_i32_load8_u(&mut self, memarg: wasmparser::MemArg) -> Self::Output { self.translate_load( memarg, ValType::I32, Op::i32_load8_u, Op::i32_load8_u_offset16, Op::i32_load8_u_at, ) } #[inline(never)] fn visit_i32_load16_s(&mut self, memarg: wasmparser::MemArg) -> Self::Output { self.translate_load( memarg, ValType::I32, Op::i32_load16_s, Op::i32_load16_s_offset16, Op::i32_load16_s_at, ) } #[inline(never)] fn visit_i32_load16_u(&mut self, memarg: wasmparser::MemArg) -> Self::Output { self.translate_load( memarg, ValType::I32, Op::i32_load16_u, Op::i32_load16_u_offset16, Op::i32_load16_u_at, ) } #[inline(never)] fn visit_i64_load8_s(&mut self, memarg: wasmparser::MemArg) -> Self::Output { self.translate_load( memarg, ValType::I64, Op::i64_load8_s, Op::i64_load8_s_offset16, Op::i64_load8_s_at, ) } #[inline(never)] fn visit_i64_load8_u(&mut self, memarg: wasmparser::MemArg) -> Self::Output { self.translate_load( memarg, ValType::I64, Op::i64_load8_u, Op::i64_load8_u_offset16, Op::i64_load8_u_at, ) } #[inline(never)] fn visit_i64_load16_s(&mut self, memarg: wasmparser::MemArg) -> Self::Output { self.translate_load( memarg, ValType::I64, Op::i64_load16_s, Op::i64_load16_s_offset16, Op::i64_load16_s_at, ) } #[inline(never)] fn visit_i64_load16_u(&mut self, memarg: wasmparser::MemArg) -> Self::Output { self.translate_load( memarg, ValType::I64, Op::i64_load16_u, Op::i64_load16_u_offset16, Op::i64_load16_u_at, ) } #[inline(never)] fn visit_i64_load32_s(&mut self, memarg: wasmparser::MemArg) -> Self::Output { self.translate_load( memarg, ValType::I64, Op::i64_load32_s, Op::i64_load32_s_offset16, Op::i64_load32_s_at, ) } #[inline(never)] fn visit_i64_load32_u(&mut self, memarg: wasmparser::MemArg) -> Self::Output { self.translate_load( memarg, ValType::I64, Op::i64_load32_u, Op::i64_load32_u_offset16, Op::i64_load32_u_at, ) } #[inline(never)] fn visit_i32_store(&mut self, memarg: wasmparser::MemArg) -> Self::Output { self.translate_istore_wrap::(memarg) } #[inline(never)] fn visit_i64_store(&mut self, memarg: wasmparser::MemArg) -> Self::Output { self.translate_istore_wrap::(memarg) } #[inline(never)] fn visit_f32_store(&mut self, memarg: wasmparser::MemArg) -> Self::Output { self.translate_store(memarg, Op::store32, Op::store32_offset16, Op::store32_at) } #[inline(never)] fn visit_f64_store(&mut self, memarg: wasmparser::MemArg) -> Self::Output { self.translate_store(memarg, Op::store64, Op::store64_offset16, Op::store64_at) } #[inline(never)] fn visit_i32_store8(&mut self, memarg: wasmparser::MemArg) -> Self::Output { self.translate_istore_wrap::(memarg) } #[inline(never)] fn visit_i32_store16(&mut self, memarg: wasmparser::MemArg) -> Self::Output { self.translate_istore_wrap::(memarg) } #[inline(never)] fn visit_i64_store8(&mut self, memarg: wasmparser::MemArg) -> Self::Output { self.translate_istore_wrap::(memarg) } #[inline(never)] fn visit_i64_store16(&mut self, memarg: wasmparser::MemArg) -> Self::Output { self.translate_istore_wrap::(memarg) } #[inline(never)] fn visit_i64_store32(&mut self, memarg: wasmparser::MemArg) -> Self::Output { self.translate_istore_wrap::(memarg) } #[inline(never)] fn visit_memory_size(&mut self, mem: u32) -> Self::Output { bail_unreachable!(self); let index_ty = self .module .get_type_of_memory(MemoryIdx::from(mem)) .index_ty() .ty(); self.push_instr_with_result( index_ty, |result| Op::memory_size(result, mem), FuelCostsProvider::instance, )?; Ok(()) } #[inline(never)] fn visit_memory_grow(&mut self, mem: u32) -> Self::Output { bail_unreachable!(self); let index_ty = self .module .get_type_of_memory(MemoryIdx::from(mem)) .index_ty(); let delta = self.stack.pop(); if let Operand::Immediate(delta) = delta { let delta = delta.val(); let delta = match index_ty { IndexType::I32 => u64::from(u32::from(delta)), IndexType::I64 => u64::from(delta), }; if delta == 0 { // Case: growing by 0 pages. // // Since `memory.grow` returns the `memory.size` before the // operation a `memory.grow` with `delta` of 0 can be translated // as `memory.size` instruction instead. self.push_instr_with_result( index_ty.ty(), |result| Op::memory_size(result, mem), FuelCostsProvider::instance, )?; return Ok(()); } } // Case: fallback to generic `memory.grow` instruction let delta = self.immediate_to_reg(delta)?; self.push_instr_with_result( index_ty.ty(), |result| Op::memory_grow(result, delta), FuelCostsProvider::instance, )?; self.push_param(Op::memory_index(mem))?; Ok(()) } #[inline(never)] fn visit_i32_const(&mut self, value: i32) -> Self::Output { bail_unreachable!(self); self.stack.push_immediate(value)?; Ok(()) } #[inline(never)] fn visit_i64_const(&mut self, value: i64) -> Self::Output { bail_unreachable!(self); self.stack.push_immediate(value)?; Ok(()) } #[inline(never)] fn visit_f32_const(&mut self, value: wasmparser::Ieee32) -> Self::Output { bail_unreachable!(self); let value = F32::from_bits(value.bits()); self.stack.push_immediate(value)?; Ok(()) } #[inline(never)] fn visit_f64_const(&mut self, value: wasmparser::Ieee64) -> Self::Output { bail_unreachable!(self); let value = F64::from_bits(value.bits()); self.stack.push_immediate(value)?; Ok(()) } #[inline(never)] fn visit_i32_eqz(&mut self) -> Self::Output { bail_unreachable!(self); self.stack.push_immediate(0_i32)?; self.visit_i32_eq() } #[inline(never)] fn visit_i32_eq(&mut self) -> Self::Output { self.translate_binary_commutative::( Op::i32_eq, Op::i32_eq_imm16, wasm::i32_eq, FuncTranslator::fuse_eqz, ) } #[inline(never)] fn visit_i32_ne(&mut self) -> Self::Output { self.translate_binary_commutative::( Op::i32_ne, Op::i32_ne_imm16, wasm::i32_ne, FuncTranslator::fuse_nez, ) } #[inline(never)] fn visit_i32_lt_s(&mut self) -> Self::Output { self.translate_binary::( Op::i32_lt_s, Op::i32_lt_s_imm16_rhs, Op::i32_lt_s_imm16_lhs, wasm::i32_lt_s, ) } #[inline(never)] fn visit_i32_lt_u(&mut self) -> Self::Output { self.translate_binary::( Op::i32_lt_u, Op::i32_lt_u_imm16_rhs, Op::i32_lt_u_imm16_lhs, wasm::i32_lt_u, ) } #[inline(never)] fn visit_i32_gt_s(&mut self) -> Self::Output { self.translate_binary::( swap_ops!(Op::i32_lt_s), swap_ops!(Op::i32_lt_s_imm16_lhs), swap_ops!(Op::i32_lt_s_imm16_rhs), wasm::i32_gt_s, ) } #[inline(never)] fn visit_i32_gt_u(&mut self) -> Self::Output { self.translate_binary::( swap_ops!(Op::i32_lt_u), swap_ops!(Op::i32_lt_u_imm16_lhs), swap_ops!(Op::i32_lt_u_imm16_rhs), wasm::i32_gt_u, ) } #[inline(never)] fn visit_i32_le_s(&mut self) -> Self::Output { self.translate_binary::( Op::i32_le_s, Op::i32_le_s_imm16_rhs, Op::i32_le_s_imm16_lhs, wasm::i32_le_s, ) } #[inline(never)] fn visit_i32_le_u(&mut self) -> Self::Output { self.translate_binary::( Op::i32_le_u, Op::i32_le_u_imm16_rhs, Op::i32_le_u_imm16_lhs, wasm::i32_le_u, ) } #[inline(never)] fn visit_i32_ge_s(&mut self) -> Self::Output { self.translate_binary::( swap_ops!(Op::i32_le_s), swap_ops!(Op::i32_le_s_imm16_lhs), swap_ops!(Op::i32_le_s_imm16_rhs), wasm::i32_ge_s, ) } #[inline(never)] fn visit_i32_ge_u(&mut self) -> Self::Output { self.translate_binary::( swap_ops!(Op::i32_le_u), swap_ops!(Op::i32_le_u_imm16_lhs), swap_ops!(Op::i32_le_u_imm16_rhs), wasm::i32_ge_u, ) } #[inline(never)] fn visit_i64_eqz(&mut self) -> Self::Output { bail_unreachable!(self); self.stack.push_immediate(0_i64)?; self.visit_i64_eq() } #[inline(never)] fn visit_i64_eq(&mut self) -> Self::Output { self.translate_binary_commutative::( Op::i64_eq, Op::i64_eq_imm16, wasm::i64_eq, FuncTranslator::fuse_eqz, ) } #[inline(never)] fn visit_i64_ne(&mut self) -> Self::Output { self.translate_binary_commutative::( Op::i64_ne, Op::i64_ne_imm16, wasm::i64_ne, FuncTranslator::fuse_nez, ) } #[inline(never)] fn visit_i64_lt_s(&mut self) -> Self::Output { self.translate_binary::( Op::i64_lt_s, Op::i64_lt_s_imm16_rhs, Op::i64_lt_s_imm16_lhs, wasm::i64_lt_s, ) } #[inline(never)] fn visit_i64_lt_u(&mut self) -> Self::Output { self.translate_binary::( Op::i64_lt_u, Op::i64_lt_u_imm16_rhs, Op::i64_lt_u_imm16_lhs, wasm::i64_lt_u, ) } #[inline(never)] fn visit_i64_gt_s(&mut self) -> Self::Output { self.translate_binary::( swap_ops!(Op::i64_lt_s), swap_ops!(Op::i64_lt_s_imm16_lhs), swap_ops!(Op::i64_lt_s_imm16_rhs), wasm::i64_gt_s, ) } #[inline(never)] fn visit_i64_gt_u(&mut self) -> Self::Output { self.translate_binary::( swap_ops!(Op::i64_lt_u), swap_ops!(Op::i64_lt_u_imm16_lhs), swap_ops!(Op::i64_lt_u_imm16_rhs), wasm::i64_gt_u, ) } #[inline(never)] fn visit_i64_le_s(&mut self) -> Self::Output { self.translate_binary::( Op::i64_le_s, Op::i64_le_s_imm16_rhs, Op::i64_le_s_imm16_lhs, wasm::i64_le_s, ) } #[inline(never)] fn visit_i64_le_u(&mut self) -> Self::Output { self.translate_binary::( Op::i64_le_u, Op::i64_le_u_imm16_rhs, Op::i64_le_u_imm16_lhs, wasm::i64_le_u, ) } #[inline(never)] fn visit_i64_ge_s(&mut self) -> Self::Output { self.translate_binary::( swap_ops!(Op::i64_le_s), swap_ops!(Op::i64_le_s_imm16_lhs), swap_ops!(Op::i64_le_s_imm16_rhs), wasm::i64_ge_s, ) } #[inline(never)] fn visit_i64_ge_u(&mut self) -> Self::Output { self.translate_binary::( swap_ops!(Op::i64_le_u), swap_ops!(Op::i64_le_u_imm16_lhs), swap_ops!(Op::i64_le_u_imm16_rhs), wasm::i64_ge_u, ) } #[inline(never)] fn visit_f32_eq(&mut self) -> Self::Output { self.translate_fbinary(Op::f32_eq, wasm::f32_eq) } #[inline(never)] fn visit_f32_ne(&mut self) -> Self::Output { self.translate_fbinary(Op::f32_ne, wasm::f32_ne) } #[inline(never)] fn visit_f32_lt(&mut self) -> Self::Output { self.translate_fbinary(Op::f32_lt, wasm::f32_lt) } #[inline(never)] fn visit_f32_gt(&mut self) -> Self::Output { self.translate_fbinary(swap_ops!(Op::f32_lt), wasm::f32_gt) } #[inline(never)] fn visit_f32_le(&mut self) -> Self::Output { self.translate_fbinary(Op::f32_le, wasm::f32_le) } #[inline(never)] fn visit_f32_ge(&mut self) -> Self::Output { self.translate_fbinary(swap_ops!(Op::f32_le), wasm::f32_ge) } #[inline(never)] fn visit_f64_eq(&mut self) -> Self::Output { self.translate_fbinary(Op::f64_eq, wasm::f64_eq) } #[inline(never)] fn visit_f64_ne(&mut self) -> Self::Output { self.translate_fbinary(Op::f64_ne, wasm::f64_ne) } #[inline(never)] fn visit_f64_lt(&mut self) -> Self::Output { self.translate_fbinary(Op::f64_lt, wasm::f64_lt) } #[inline(never)] fn visit_f64_gt(&mut self) -> Self::Output { self.translate_fbinary(swap_ops!(Op::f64_lt), wasm::f64_gt) } #[inline(never)] fn visit_f64_le(&mut self) -> Self::Output { self.translate_fbinary(Op::f64_le, wasm::f64_le) } #[inline(never)] fn visit_f64_ge(&mut self) -> Self::Output { self.translate_fbinary(swap_ops!(Op::f64_le), wasm::f64_ge) } #[inline(never)] fn visit_i32_clz(&mut self) -> Self::Output { self.translate_unary::(Op::i32_clz, wasm::i32_clz) } #[inline(never)] fn visit_i32_ctz(&mut self) -> Self::Output { self.translate_unary::(Op::i32_ctz, wasm::i32_ctz) } #[inline(never)] fn visit_i32_popcnt(&mut self) -> Self::Output { self.translate_unary::(Op::i32_popcnt, wasm::i32_popcnt) } #[inline(never)] fn visit_i32_add(&mut self) -> Self::Output { self.translate_binary_commutative::( Op::i32_add, Op::i32_add_imm16, wasm::i32_add, FuncTranslator::no_opt_ri, ) } #[inline(never)] fn visit_i32_sub(&mut self) -> Self::Output { self.translate_isub( Op::i32_sub, Op::i32_add_imm16, Op::i32_sub_imm16_lhs, wasm::i32_sub, ) } #[inline(never)] fn visit_i32_mul(&mut self) -> Self::Output { self.translate_binary_commutative::( Op::i32_mul, Op::i32_mul_imm16, wasm::i32_mul, FuncTranslator::no_opt_ri, ) } #[inline(never)] fn visit_i32_div_s(&mut self) -> Self::Output { self.translate_divrem::( Op::i32_div_s, Op::i32_div_s_imm16_rhs, Op::i32_div_s_imm16_lhs, wasm::i32_div_s, ) } #[inline(never)] fn visit_i32_div_u(&mut self) -> Self::Output { self.translate_divrem::( Op::i32_div_u, Op::i32_div_u_imm16_rhs, Op::i32_div_u_imm16_lhs, wasm::i32_div_u, ) } #[inline(never)] fn visit_i32_rem_s(&mut self) -> Self::Output { self.translate_divrem::( Op::i32_rem_s, Op::i32_rem_s_imm16_rhs, Op::i32_rem_s_imm16_lhs, wasm::i32_rem_s, ) } #[inline(never)] fn visit_i32_rem_u(&mut self) -> Self::Output { self.translate_divrem::( Op::i32_rem_u, Op::i32_rem_u_imm16_rhs, Op::i32_rem_u_imm16_lhs, wasm::i32_rem_u, ) } #[inline(never)] fn visit_i32_and(&mut self) -> Self::Output { self.translate_binary_commutative::( Op::i32_bitand, Op::i32_bitand_imm16, wasm::i32_bitand, FuncTranslator::no_opt_ri, ) } #[inline(never)] fn visit_i32_or(&mut self) -> Self::Output { self.translate_binary_commutative::( Op::i32_bitor, Op::i32_bitor_imm16, wasm::i32_bitor, FuncTranslator::no_opt_ri, ) } #[inline(never)] fn visit_i32_xor(&mut self) -> Self::Output { self.translate_binary_commutative::( Op::i32_bitxor, Op::i32_bitxor_imm16, wasm::i32_bitxor, FuncTranslator::no_opt_ri, ) } #[inline(never)] fn visit_i32_shl(&mut self) -> Self::Output { self.translate_shift::( Op::i32_shl, Op::i32_shl_by, Op::i32_shl_imm16, wasm::i32_shl, ) } #[inline(never)] fn visit_i32_shr_s(&mut self) -> Self::Output { self.translate_shift::( Op::i32_shr_s, Op::i32_shr_s_by, Op::i32_shr_s_imm16, wasm::i32_shr_s, ) } #[inline(never)] fn visit_i32_shr_u(&mut self) -> Self::Output { self.translate_shift::( Op::i32_shr_u, Op::i32_shr_u_by, Op::i32_shr_u_imm16, wasm::i32_shr_u, ) } #[inline(never)] fn visit_i32_rotl(&mut self) -> Self::Output { self.translate_shift::( Op::i32_rotl, Op::i32_rotl_by, Op::i32_rotl_imm16, wasm::i32_rotl, ) } #[inline(never)] fn visit_i32_rotr(&mut self) -> Self::Output { self.translate_shift::( Op::i32_rotr, Op::i32_rotr_by, Op::i32_rotr_imm16, wasm::i32_rotr, ) } #[inline(never)] fn visit_i64_clz(&mut self) -> Self::Output { self.translate_unary::(Op::i64_clz, wasm::i64_clz) } #[inline(never)] fn visit_i64_ctz(&mut self) -> Self::Output { self.translate_unary::(Op::i64_ctz, wasm::i64_ctz) } #[inline(never)] fn visit_i64_popcnt(&mut self) -> Self::Output { self.translate_unary::(Op::i64_popcnt, wasm::i64_popcnt) } #[inline(never)] fn visit_i64_add(&mut self) -> Self::Output { self.translate_binary_commutative::( Op::i64_add, Op::i64_add_imm16, wasm::i64_add, FuncTranslator::no_opt_ri, ) } #[inline(never)] fn visit_i64_sub(&mut self) -> Self::Output { self.translate_isub( Op::i64_sub, Op::i64_add_imm16, Op::i64_sub_imm16_lhs, wasm::i64_sub, ) } #[inline(never)] fn visit_i64_mul(&mut self) -> Self::Output { self.translate_binary_commutative::( Op::i64_mul, Op::i64_mul_imm16, wasm::i64_mul, FuncTranslator::no_opt_ri, ) } #[inline(never)] fn visit_i64_div_s(&mut self) -> Self::Output { self.translate_divrem::( Op::i64_div_s, Op::i64_div_s_imm16_rhs, Op::i64_div_s_imm16_lhs, wasm::i64_div_s, ) } #[inline(never)] fn visit_i64_div_u(&mut self) -> Self::Output { self.translate_divrem::( Op::i64_div_u, Op::i64_div_u_imm16_rhs, Op::i64_div_u_imm16_lhs, wasm::i64_div_u, ) } #[inline(never)] fn visit_i64_rem_s(&mut self) -> Self::Output { self.translate_divrem::( Op::i64_rem_s, Op::i64_rem_s_imm16_rhs, Op::i64_rem_s_imm16_lhs, wasm::i64_rem_s, ) } #[inline(never)] fn visit_i64_rem_u(&mut self) -> Self::Output { self.translate_divrem::( Op::i64_rem_u, Op::i64_rem_u_imm16_rhs, Op::i64_rem_u_imm16_lhs, wasm::i64_rem_u, ) } #[inline(never)] fn visit_i64_and(&mut self) -> Self::Output { self.translate_binary_commutative::( Op::i64_bitand, Op::i64_bitand_imm16, wasm::i64_bitand, FuncTranslator::no_opt_ri, ) } #[inline(never)] fn visit_i64_or(&mut self) -> Self::Output { self.translate_binary_commutative::( Op::i64_bitor, Op::i64_bitor_imm16, wasm::i64_bitor, FuncTranslator::no_opt_ri, ) } #[inline(never)] fn visit_i64_xor(&mut self) -> Self::Output { self.translate_binary_commutative::( Op::i64_bitxor, Op::i64_bitxor_imm16, wasm::i64_bitxor, FuncTranslator::no_opt_ri, ) } #[inline(never)] fn visit_i64_shl(&mut self) -> Self::Output { self.translate_shift::( Op::i64_shl, Op::i64_shl_by, Op::i64_shl_imm16, wasm::i64_shl, ) } #[inline(never)] fn visit_i64_shr_s(&mut self) -> Self::Output { self.translate_shift::( Op::i64_shr_s, Op::i64_shr_s_by, Op::i64_shr_s_imm16, wasm::i64_shr_s, ) } #[inline(never)] fn visit_i64_shr_u(&mut self) -> Self::Output { self.translate_shift::( Op::i64_shr_u, Op::i64_shr_u_by, Op::i64_shr_u_imm16, wasm::i64_shr_u, ) } #[inline(never)] fn visit_i64_rotl(&mut self) -> Self::Output { self.translate_shift::( Op::i64_rotl, Op::i64_rotl_by, Op::i64_rotl_imm16, wasm::i64_rotl, ) } #[inline(never)] fn visit_i64_rotr(&mut self) -> Self::Output { self.translate_shift::( Op::i64_rotr, Op::i64_rotr_by, Op::i64_rotr_imm16, wasm::i64_rotr, ) } #[inline(never)] fn visit_f32_abs(&mut self) -> Self::Output { self.translate_unary(Op::f32_abs, wasm::f32_abs) } #[inline(never)] fn visit_f32_neg(&mut self) -> Self::Output { self.translate_unary(Op::f32_neg, wasm::f32_neg) } #[inline(never)] fn visit_f32_ceil(&mut self) -> Self::Output { self.translate_unary(Op::f32_ceil, wasm::f32_ceil) } #[inline(never)] fn visit_f32_floor(&mut self) -> Self::Output { self.translate_unary(Op::f32_floor, wasm::f32_floor) } #[inline(never)] fn visit_f32_trunc(&mut self) -> Self::Output { self.translate_unary(Op::f32_trunc, wasm::f32_trunc) } #[inline(never)] fn visit_f32_nearest(&mut self) -> Self::Output { self.translate_unary(Op::f32_nearest, wasm::f32_nearest) } #[inline(never)] fn visit_f32_sqrt(&mut self) -> Self::Output { self.translate_unary(Op::f32_sqrt, wasm::f32_sqrt) } #[inline(never)] fn visit_f32_add(&mut self) -> Self::Output { self.translate_fbinary(Op::f32_add, wasm::f32_add) } #[inline(never)] fn visit_f32_sub(&mut self) -> Self::Output { self.translate_fbinary(Op::f32_sub, wasm::f32_sub) } #[inline(never)] fn visit_f32_mul(&mut self) -> Self::Output { self.translate_fbinary(Op::f32_mul, wasm::f32_mul) } #[inline(never)] fn visit_f32_div(&mut self) -> Self::Output { self.translate_fbinary(Op::f32_div, wasm::f32_div) } #[inline(never)] fn visit_f32_min(&mut self) -> Self::Output { self.translate_fbinary(Op::f32_min, wasm::f32_min) } #[inline(never)] fn visit_f32_max(&mut self) -> Self::Output { self.translate_fbinary(Op::f32_max, wasm::f32_max) } #[inline(never)] fn visit_f32_copysign(&mut self) -> Self::Output { self.translate_fcopysign::(Op::f32_copysign, Op::f32_copysign_imm, wasm::f32_copysign) } #[inline(never)] fn visit_f64_abs(&mut self) -> Self::Output { self.translate_unary(Op::f64_abs, wasm::f64_abs) } #[inline(never)] fn visit_f64_neg(&mut self) -> Self::Output { self.translate_unary(Op::f64_neg, wasm::f64_neg) } #[inline(never)] fn visit_f64_ceil(&mut self) -> Self::Output { self.translate_unary(Op::f64_ceil, wasm::f64_ceil) } #[inline(never)] fn visit_f64_floor(&mut self) -> Self::Output { self.translate_unary(Op::f64_floor, wasm::f64_floor) } #[inline(never)] fn visit_f64_trunc(&mut self) -> Self::Output { self.translate_unary(Op::f64_trunc, wasm::f64_trunc) } #[inline(never)] fn visit_f64_nearest(&mut self) -> Self::Output { self.translate_unary(Op::f64_nearest, wasm::f64_nearest) } #[inline(never)] fn visit_f64_sqrt(&mut self) -> Self::Output { self.translate_unary(Op::f64_sqrt, wasm::f64_sqrt) } #[inline(never)] fn visit_f64_add(&mut self) -> Self::Output { self.translate_fbinary(Op::f64_add, wasm::f64_add) } #[inline(never)] fn visit_f64_sub(&mut self) -> Self::Output { self.translate_fbinary(Op::f64_sub, wasm::f64_sub) } #[inline(never)] fn visit_f64_mul(&mut self) -> Self::Output { self.translate_fbinary(Op::f64_mul, wasm::f64_mul) } #[inline(never)] fn visit_f64_div(&mut self) -> Self::Output { self.translate_fbinary(Op::f64_div, wasm::f64_div) } #[inline(never)] fn visit_f64_min(&mut self) -> Self::Output { self.translate_fbinary(Op::f64_min, wasm::f64_min) } #[inline(never)] fn visit_f64_max(&mut self) -> Self::Output { self.translate_fbinary(Op::f64_max, wasm::f64_max) } #[inline(never)] fn visit_f64_copysign(&mut self) -> Self::Output { self.translate_fcopysign::(Op::f64_copysign, Op::f64_copysign_imm, wasm::f64_copysign) } #[inline(never)] fn visit_i32_wrap_i64(&mut self) -> Self::Output { self.translate_unary(Op::i32_wrap_i64, wasm::i32_wrap_i64) } #[inline(never)] fn visit_i32_trunc_f32_s(&mut self) -> Self::Output { self.translate_unary_fallible(Op::i32_trunc_f32_s, wasm::i32_trunc_f32_s) } #[inline(never)] fn visit_i32_trunc_f32_u(&mut self) -> Self::Output { self.translate_unary_fallible(Op::i32_trunc_f32_u, wasm::i32_trunc_f32_u) } #[inline(never)] fn visit_i32_trunc_f64_s(&mut self) -> Self::Output { self.translate_unary_fallible(Op::i32_trunc_f64_s, wasm::i32_trunc_f64_s) } #[inline(never)] fn visit_i32_trunc_f64_u(&mut self) -> Self::Output { self.translate_unary_fallible(Op::i32_trunc_f64_u, wasm::i32_trunc_f64_u) } #[inline(never)] fn visit_i64_extend_i32_s(&mut self) -> Self::Output { self.translate_unary::(Op::i64_extend32_s, wasm::i64_extend_i32_s) } #[inline(never)] fn visit_i64_extend_i32_u(&mut self) -> Self::Output { self.translate_reinterpret(wasm::i64_extend_i32_u) } #[inline(never)] fn visit_i64_trunc_f32_s(&mut self) -> Self::Output { self.translate_unary_fallible(Op::i64_trunc_f32_s, wasm::i64_trunc_f32_s) } #[inline(never)] fn visit_i64_trunc_f32_u(&mut self) -> Self::Output { self.translate_unary_fallible(Op::i64_trunc_f32_u, wasm::i64_trunc_f32_u) } #[inline(never)] fn visit_i64_trunc_f64_s(&mut self) -> Self::Output { self.translate_unary_fallible(Op::i64_trunc_f64_s, wasm::i64_trunc_f64_s) } #[inline(never)] fn visit_i64_trunc_f64_u(&mut self) -> Self::Output { self.translate_unary_fallible(Op::i64_trunc_f64_u, wasm::i64_trunc_f64_u) } #[inline(never)] fn visit_f32_convert_i32_s(&mut self) -> Self::Output { self.translate_unary(Op::f32_convert_i32_s, wasm::f32_convert_i32_s) } #[inline(never)] fn visit_f32_convert_i32_u(&mut self) -> Self::Output { self.translate_unary(Op::f32_convert_i32_u, wasm::f32_convert_i32_u) } #[inline(never)] fn visit_f32_convert_i64_s(&mut self) -> Self::Output { self.translate_unary(Op::f32_convert_i64_s, wasm::f32_convert_i64_s) } #[inline(never)] fn visit_f32_convert_i64_u(&mut self) -> Self::Output { self.translate_unary(Op::f32_convert_i64_u, wasm::f32_convert_i64_u) } #[inline(never)] fn visit_f32_demote_f64(&mut self) -> Self::Output { self.translate_unary(Op::f32_demote_f64, wasm::f32_demote_f64) } #[inline(never)] fn visit_f64_convert_i32_s(&mut self) -> Self::Output { self.translate_unary(Op::f64_convert_i32_s, wasm::f64_convert_i32_s) } #[inline(never)] fn visit_f64_convert_i32_u(&mut self) -> Self::Output { self.translate_unary(Op::f64_convert_i32_u, wasm::f64_convert_i32_u) } #[inline(never)] fn visit_f64_convert_i64_s(&mut self) -> Self::Output { self.translate_unary(Op::f64_convert_i64_s, wasm::f64_convert_i64_s) } #[inline(never)] fn visit_f64_convert_i64_u(&mut self) -> Self::Output { self.translate_unary(Op::f64_convert_i64_u, wasm::f64_convert_i64_u) } #[inline(never)] fn visit_f64_promote_f32(&mut self) -> Self::Output { self.translate_unary(Op::f64_promote_f32, wasm::f64_promote_f32) } #[inline(never)] fn visit_i32_reinterpret_f32(&mut self) -> Self::Output { self.translate_reinterpret(wasm::i32_reinterpret_f32) } #[inline(never)] fn visit_i64_reinterpret_f64(&mut self) -> Self::Output { self.translate_reinterpret(wasm::i64_reinterpret_f64) } #[inline(never)] fn visit_f32_reinterpret_i32(&mut self) -> Self::Output { self.translate_reinterpret(wasm::f32_reinterpret_i32) } #[inline(never)] fn visit_f64_reinterpret_i64(&mut self) -> Self::Output { self.translate_reinterpret(wasm::f64_reinterpret_i64) } #[inline(never)] fn visit_i32_extend8_s(&mut self) -> Self::Output { self.translate_unary(Op::i32_extend8_s, wasm::i32_extend8_s) } #[inline(never)] fn visit_i32_extend16_s(&mut self) -> Self::Output { self.translate_unary(Op::i32_extend16_s, wasm::i32_extend16_s) } #[inline(never)] fn visit_i64_extend8_s(&mut self) -> Self::Output { self.translate_unary(Op::i64_extend8_s, wasm::i64_extend8_s) } #[inline(never)] fn visit_i64_extend16_s(&mut self) -> Self::Output { self.translate_unary(Op::i64_extend16_s, wasm::i64_extend16_s) } #[inline(never)] fn visit_i64_extend32_s(&mut self) -> Self::Output { self.translate_unary(Op::i64_extend32_s, wasm::i64_extend32_s) } #[inline(never)] fn visit_i32_trunc_sat_f32_s(&mut self) -> Self::Output { self.translate_unary(Op::i32_trunc_sat_f32_s, wasm::i32_trunc_sat_f32_s) } #[inline(never)] fn visit_i32_trunc_sat_f32_u(&mut self) -> Self::Output { self.translate_unary(Op::i32_trunc_sat_f32_u, wasm::i32_trunc_sat_f32_u) } #[inline(never)] fn visit_i32_trunc_sat_f64_s(&mut self) -> Self::Output { self.translate_unary(Op::i32_trunc_sat_f64_s, wasm::i32_trunc_sat_f64_s) } #[inline(never)] fn visit_i32_trunc_sat_f64_u(&mut self) -> Self::Output { self.translate_unary(Op::i32_trunc_sat_f64_u, wasm::i32_trunc_sat_f64_u) } #[inline(never)] fn visit_i64_trunc_sat_f32_s(&mut self) -> Self::Output { self.translate_unary(Op::i64_trunc_sat_f32_s, wasm::i64_trunc_sat_f32_s) } #[inline(never)] fn visit_i64_trunc_sat_f32_u(&mut self) -> Self::Output { self.translate_unary(Op::i64_trunc_sat_f32_u, wasm::i64_trunc_sat_f32_u) } #[inline(never)] fn visit_i64_trunc_sat_f64_s(&mut self) -> Self::Output { self.translate_unary(Op::i64_trunc_sat_f64_s, wasm::i64_trunc_sat_f64_s) } #[inline(never)] fn visit_i64_trunc_sat_f64_u(&mut self) -> Self::Output { self.translate_unary(Op::i64_trunc_sat_f64_u, wasm::i64_trunc_sat_f64_u) } #[inline(never)] fn visit_memory_init(&mut self, data_index: u32, mem: u32) -> Self::Output { bail_unreachable!(self); let (dst, src, len) = self.stack.pop3(); let dst = self.immediate_to_reg(dst)?; let src = self.immediate_to_reg(src)?; let len = self.immediate_to_reg(len)?; self.push_instr(Op::memory_init(dst, src, len), FuelCostsProvider::instance)?; self.push_param(Op::memory_index(mem))?; self.push_param(Op::data_index(data_index))?; Ok(()) } #[inline(never)] fn visit_data_drop(&mut self, data_index: u32) -> Self::Output { bail_unreachable!(self); self.push_instr(Op::data_drop(data_index), FuelCostsProvider::instance)?; Ok(()) } #[inline(never)] fn visit_memory_copy(&mut self, dst_mem: u32, src_mem: u32) -> Self::Output { bail_unreachable!(self); let (dst, src, len) = self.stack.pop3(); let dst = self.immediate_to_reg(dst)?; let src = self.immediate_to_reg(src)?; let len = self.immediate_to_reg(len)?; self.push_instr(Op::memory_copy(dst, src, len), FuelCostsProvider::instance)?; self.push_param(Op::memory_index(dst_mem))?; self.push_param(Op::memory_index(src_mem))?; Ok(()) } #[inline(never)] fn visit_memory_fill(&mut self, mem: u32) -> Self::Output { bail_unreachable!(self); let (dst, value, len) = self.stack.pop3(); let dst = self.immediate_to_reg(dst)?; let value = self.make_input(value, |_, value| { let byte = u32::from(value) as u8; Ok(Input::Immediate(byte)) })?; let len = self.immediate_to_reg(len)?; let instr: Op = match value { Input::Slot(value) => Op::memory_fill(dst, value, len), Input::Immediate(value) => Op::memory_fill_imm(dst, value, len), }; self.push_instr(instr, FuelCostsProvider::instance)?; self.push_param(Op::memory_index(mem))?; Ok(()) } #[inline(never)] fn visit_table_init(&mut self, elem_index: u32, table: u32) -> Self::Output { bail_unreachable!(self); let (dst, src, len) = self.stack.pop3(); let dst = self.immediate_to_reg(dst)?; let src = self.immediate_to_reg(src)?; let len = self.immediate_to_reg(len)?; self.push_instr(Op::table_init(dst, src, len), FuelCostsProvider::instance)?; self.push_param(Op::table_index(table))?; self.push_param(Op::elem_index(elem_index))?; Ok(()) } #[inline(never)] fn visit_elem_drop(&mut self, elem_index: u32) -> Self::Output { bail_unreachable!(self); self.push_instr(Op::elem_drop(elem_index), FuelCostsProvider::instance)?; Ok(()) } #[inline(never)] fn visit_table_copy(&mut self, dst_table: u32, src_table: u32) -> Self::Output { bail_unreachable!(self); let (dst, src, len) = self.stack.pop3(); let dst = self.immediate_to_reg(dst)?; let src = self.immediate_to_reg(src)?; let len = self.immediate_to_reg(len)?; self.push_instr(Op::table_copy(dst, src, len), FuelCostsProvider::instance)?; self.push_param(Op::table_index(dst_table))?; self.push_param(Op::table_index(src_table))?; Ok(()) } #[inline(never)] fn visit_typed_select(&mut self, ty: wasmparser::ValType) -> Self::Output { let type_hint = WasmiValueType::from(ty).into_inner(); self.translate_select(Some(type_hint)) } #[inline(never)] fn visit_ref_null(&mut self, ty: wasmparser::HeapType) -> Self::Output { bail_unreachable!(self); let type_hint = WasmiValueType::from(ty).into_inner(); let null = match type_hint { ValType::FuncRef => TypedVal::from(>::Null), ValType::ExternRef => TypedVal::from(>::Null), ty => panic!("expected a Wasm `reftype` but found: {ty:?}"), }; self.stack.push_immediate(null)?; Ok(()) } #[inline(never)] fn visit_ref_is_null(&mut self) -> Self::Output { bail_unreachable!(self); match self.stack.pop() { Operand::Local(input) => { // Note: `funcref` and `externref` both serialize to `UntypedValue` // as `u64` so we can use `i64.eqz` translation for `ref.is_null` // via reinterpretation of the value's type. self.stack.push_local(input.local_index(), ValType::I64)?; self.visit_i64_eqz() } Operand::Temp(input) => { // Note: `funcref` and `externref` both serialize to `UntypedValue` // as `u64` so we can use `i64.eqz` translation for `ref.is_null` // via reinterpretation of the value's type. self.stack.push_temp(ValType::I64, input.instr())?; self.visit_i64_eqz() } Operand::Immediate(input) => { let untyped = input.val().untyped(); let is_null = match input.ty() { ValType::FuncRef => >::from(untyped).is_null(), ValType::ExternRef => >::from(untyped).is_null(), invalid => panic!("`ref.is_null`: encountered invalid input type: {invalid:?}"), }; self.stack.push_immediate(i32::from(is_null))?; Ok(()) } } } #[inline(never)] fn visit_ref_func(&mut self, function_index: u32) -> Self::Output { bail_unreachable!(self); self.push_instr_with_result( ValType::FuncRef, |result| Op::ref_func(result, function_index), FuelCostsProvider::instance, )?; Ok(()) } #[inline(never)] fn visit_table_fill(&mut self, table: u32) -> Self::Output { bail_unreachable!(self); let (dst, value, len) = self.stack.pop3(); let dst = self.immediate_to_reg(dst)?; let value = self.immediate_to_reg(value)?; let len = self.immediate_to_reg(len)?; self.push_instr(Op::table_fill(dst, len, value), FuelCostsProvider::instance)?; self.push_param(Op::table_index(table))?; Ok(()) } #[inline(never)] fn visit_table_get(&mut self, table: u32) -> Self::Output { bail_unreachable!(self); let table_type = *self.module.get_type_of_table(TableIdx::from(table)); let index = self.stack.pop(); let item_ty = table_type.element(); let index_ty = table_type.index_ty(); let index = self.make_index32(index, index_ty)?; self.push_instr_with_result( item_ty, |result| match index { Input::Slot(index) => Op::table_get(result, index), Input::Immediate(index) => Op::table_get_imm(result, index), }, FuelCostsProvider::instance, )?; self.push_param(Op::table_index(table))?; Ok(()) } #[inline(never)] fn visit_table_set(&mut self, table: u32) -> Self::Output { bail_unreachable!(self); let table_type = *self.module.get_type_of_table(TableIdx::from(table)); let index_ty = table_type.index_ty(); let (index, value) = self.stack.pop2(); let index = self.make_index32(index, index_ty)?; let value = self.layout.operand_to_reg(value)?; let instr = match index { Input::Slot(index) => Op::table_set(index, value), Input::Immediate(index) => Op::table_set_at(value, index), }; self.push_instr(instr, FuelCostsProvider::instance)?; self.push_param(Op::table_index(table))?; Ok(()) } #[inline(never)] fn visit_table_grow(&mut self, table: u32) -> Self::Output { bail_unreachable!(self); let table_type = *self.module.get_type_of_table(TableIdx::from(table)); let index_ty = table_type.index_ty(); let (value, delta) = self.stack.pop2(); if let Operand::Immediate(delta) = delta { let delta = delta.val(); let delta = match index_ty { IndexType::I32 => u64::from(u32::from(delta)), IndexType::I64 => u64::from(delta), }; if delta == 0 { // Case: growing by 0 elements. // // Since `table.grow` returns the `table.size` before the // operation a `table.grow` with `delta` of 0 can be translated // as `table.size` instruction instead. self.push_instr_with_result( index_ty.ty(), |result| Op::table_size(result, table), FuelCostsProvider::instance, )?; return Ok(()); } } let value = self.immediate_to_reg(value)?; let delta = self.immediate_to_reg(delta)?; self.push_instr_with_result( index_ty.ty(), |result| Op::table_grow(result, delta, value), FuelCostsProvider::instance, )?; self.push_param(Op::table_index(table))?; Ok(()) } #[inline(never)] fn visit_table_size(&mut self, table: u32) -> Self::Output { bail_unreachable!(self); let table_type = *self.module.get_type_of_table(TableIdx::from(table)); let index_ty = table_type.index_ty(); self.push_instr_with_result( index_ty.ty(), |result| Op::table_size(result, table), FuelCostsProvider::instance, )?; Ok(()) } #[inline(never)] fn visit_return_call(&mut self, function_index: u32) -> Self::Output { bail_unreachable!(self); let func_idx = FuncIdx::from(function_index); let func_type = self.resolve_func_type(func_idx); let len_params = usize::from(func_type.len_params()); let instr = match self.module.get_engine_func(func_idx) { Some(engine_func) => { // Case: We are calling an internal function and can optimize // this case by using the special instruction for it. match len_params { 0 => Op::return_call_internal_0(engine_func), _ => Op::return_call_internal(engine_func), } } None => { // Case: We are calling an imported function and must use the // general calling operator for it. match len_params { 0 => Op::return_call_imported_0(function_index), _ => Op::return_call_imported(function_index), } } }; self.push_instr(instr, FuelCostsProvider::call)?; self.stack.pop_n(len_params, &mut self.operands); self.instrs .encode_register_list(&self.operands, &mut self.layout)?; self.reachable = false; Ok(()) } #[inline(never)] fn visit_return_call_indirect(&mut self, type_index: u32, table_index: u32) -> Self::Output { bail_unreachable!(self); let func_type = self.resolve_type(type_index); let index = self.stack.pop(); let indirect_params = self.call_indirect_params(index, table_index)?; let len_params = usize::from(func_type.len_params()); let instr = match (len_params, indirect_params) { (0, Op::CallIndirectParams { .. }) => Op::return_call_indirect_0(type_index), (0, Op::CallIndirectParamsImm16 { .. }) => Op::return_call_indirect_0_imm16(type_index), (_, Op::CallIndirectParams { .. }) => Op::return_call_indirect(type_index), (_, Op::CallIndirectParamsImm16 { .. }) => Op::return_call_indirect_imm16(type_index), _ => unreachable!(), }; self.push_instr(instr, FuelCostsProvider::call)?; self.push_param(indirect_params)?; self.stack.pop_n(len_params, &mut self.operands); self.instrs .encode_register_list(&self.operands, &mut self.layout)?; self.reachable = false; Ok(()) } #[inline(never)] fn visit_i64_add128(&mut self) -> Self::Output { self.translate_i64_binop128(Op::i64_add128, wasm::i64_add128) } #[inline(never)] fn visit_i64_sub128(&mut self) -> Self::Output { self.translate_i64_binop128(Op::i64_sub128, wasm::i64_sub128) } #[inline(never)] fn visit_i64_mul_wide_s(&mut self) -> Self::Output { self.translate_i64_mul_wide_sx(Op::i64_mul_wide_s, wasm::i64_mul_wide_s, true) } #[inline(never)] fn visit_i64_mul_wide_u(&mut self) -> Self::Output { self.translate_i64_mul_wide_sx(Op::i64_mul_wide_u, wasm::i64_mul_wide_u, false) } } wasmi-1.1.0/src/engine/translator/labels.rs000064400000000000000000000142111046102023000167770ustar 00000000000000use crate::{engine::translator::utils::Instr, ir::BranchOffset, Error}; 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] fn into_usize(self) -> usize { self.0 as usize } } /// The label registry. /// /// Allows to allocate new labels pin them and resolve pinned ones. #[derive(Debug, Default)] pub struct LabelRegistry { labels: Vec
, funcs: Vec, memories: Vec, globals: Vec, start_fn: Option, exports: Map, Extern>, data_segments: Vec, elem_segments: Vec, } impl InstanceEntityBuilder { /// Creates a new [`InstanceEntityBuilder`] optimized for the [`Module`]. pub fn new(module: &Module) -> Self { fn vec_with_capacity_exact(capacity: usize) -> Vec { let mut v = Vec::new(); v.reserve_exact(capacity); v } let mut len_funcs = module.len_funcs(); let mut len_globals = module.len_globals(); let mut len_tables = module.len_tables(); let mut len_memories = module.len_memories(); for import in module.imports() { match import.ty() { ExternType::Func(_) => { len_funcs += 1; } ExternType::Table(_) => { len_tables += 1; } ExternType::Memory(_) => { len_memories += 1; } ExternType::Global(_) => { len_globals += 1; } } } Self { func_types: module.func_types_cloned(), tables: vec_with_capacity_exact(len_tables), funcs: vec_with_capacity_exact(len_funcs), memories: vec_with_capacity_exact(len_memories), globals: vec_with_capacity_exact(len_globals), start_fn: None, exports: Map::default(), data_segments: Vec::new(), elem_segments: Vec::new(), } } /// Sets the start function of the built instance. /// /// # Panics /// /// If the start function has already been set. pub fn set_start(&mut self, start_fn: FuncIdx) { match &mut self.start_fn { Some(index) => panic!("already set start function {index:?}"), None => { self.start_fn = Some(start_fn); } } } /// Returns the start function index if any. pub fn get_start(&self) -> Option { self.start_fn } /// Returns the [`Memory`] at the `index`. /// /// # Panics /// /// If there is no [`Memory`] at the given `index. pub fn get_memory(&self, index: u32) -> Memory { self.memories .get(index as usize) .copied() .unwrap_or_else(|| panic!("missing `Memory` at index: {index}")) } /// Returns the [`Table`] at the `index`. /// /// # Panics /// /// If there is no [`Table`] at the given `index. pub fn get_table(&self, index: u32) -> Table { self.tables .get(index as usize) .copied() .unwrap_or_else(|| panic!("missing `Table` at index: {index}")) } /// Returns the [`Global`] at the `index`. /// /// # Panics /// /// If there is no [`Global`] at the given `index. pub fn get_global(&self, index: u32) -> Global { self.globals .get(index as usize) .copied() .unwrap_or_else(|| panic!("missing `Global` at index: {index}")) } /// Returns the function at the `index`. /// /// # Panics /// /// If there is no function at the given `index. pub fn get_func(&self, index: u32) -> Func { self.funcs .get(index as usize) .copied() .unwrap_or_else(|| panic!("missing `Func` at index: {index}")) } /// Pushes a new [`Memory`] to the [`InstanceEntity`] under construction. pub fn push_memory(&mut self, memory: Memory) { self.memories.push(memory); } /// Pushes a new [`Table`] to the [`InstanceEntity`] under construction. pub fn push_table(&mut self, table: Table) { self.tables.push(table); } /// Pushes a new [`Global`] to the [`InstanceEntity`] under construction. pub fn push_global(&mut self, global: Global) { self.globals.push(global); } /// Pushes a new [`Func`] to the [`InstanceEntity`] under construction. pub fn push_func(&mut self, func: Func) { self.funcs.push(func); } /// Pushes a new [`Extern`] under the given `name` to the [`InstanceEntity`] under construction. /// /// # Panics /// /// If the name has already been used by an already pushed [`Extern`]. pub fn push_export(&mut self, name: &str, new_value: Extern) { if let Some(old_value) = self.exports.get(name) { panic!( "tried to register {new_value:?} for name {name} \ but name is already used by {old_value:?}", ) } self.exports.insert(name.into(), new_value); } /// Pushes the [`DataSegment`] to the [`InstanceEntity`] under construction. pub fn push_data_segment(&mut self, segment: DataSegment) { self.data_segments.push(segment); } /// Pushes the [`ElementSegment`] to the [`InstanceEntity`] under construction. pub fn push_element_segment(&mut self, segment: ElementSegment) { self.elem_segments.push(segment); } /// Finishes constructing the [`InstanceEntity`]. pub fn finish(self) -> InstanceEntity { InstanceEntity { initialized: true, func_types: self.func_types, tables: self.tables.into(), funcs: self.funcs.into(), memories: self.memories.into(), globals: self.globals.into(), exports: self.exports, data_segments: self.data_segments.into(), elem_segments: self.elem_segments.into(), } } } wasmi-1.1.0/src/instance/exports.rs000064400000000000000000000167371046102023000154260ustar 00000000000000use crate::{ collections::map::Iter as MapIter, AsContext, Func, FuncType, Global, GlobalType, Memory, MemoryType, Table, TableType, }; use alloc::boxed::Box; use core::iter::FusedIterator; /// An external item to a WebAssembly module. /// /// This is returned from [`Instance::exports`](crate::Instance::exports) /// or [`Instance::get_export`](crate::Instance::get_export). #[derive(Debug, Copy, Clone)] pub enum Extern { /// A WebAssembly global which acts like a [`Cell`] of sorts, supporting `get` and `set` operations. /// /// [`Cell`]: https://doc.rust-lang.org/core/cell/struct.Cell.html Global(Global), /// A WebAssembly table which is an array of function references. Table(Table), /// A WebAssembly linear memory. Memory(Memory), /// A WebAssembly function which can be called. Func(Func), } impl From for Extern { fn from(global: Global) -> Self { Self::Global(global) } } impl From
for Extern { fn from(table: Table) -> Self { Self::Table(table) } } impl From for Extern { fn from(memory: Memory) -> Self { Self::Memory(memory) } } impl From for Extern { fn from(func: Func) -> Self { Self::Func(func) } } impl Extern { /// Returns the underlying global variable if `self` is a global variable. /// /// Returns `None` otherwise. pub fn into_global(self) -> Option { if let Self::Global(global) = self { return Some(global); } None } /// Returns the underlying table if `self` is a table. /// /// Returns `None` otherwise. pub fn into_table(self) -> Option
{ if let Self::Table(table) = self { return Some(table); } None } /// Returns the underlying linear memory if `self` is a linear memory. /// /// Returns `None` otherwise. pub fn into_memory(self) -> Option { if let Self::Memory(memory) = self { return Some(memory); } None } /// Returns the underlying function if `self` is a function. /// /// Returns `None` otherwise. pub fn into_func(self) -> Option { if let Self::Func(func) = self { return Some(func); } None } /// Returns the type associated with this [`Extern`]. /// /// # Panics /// /// If this item does not belong to the `store` provided. pub fn ty(&self, ctx: impl AsContext) -> ExternType { match self { Extern::Global(global) => global.ty(ctx).into(), Extern::Table(table) => table.ty(ctx).into(), Extern::Memory(memory) => memory.ty(ctx).into(), Extern::Func(func) => func.ty(ctx).into(), } } } /// The type of an [`Extern`] item. /// /// A list of all possible types which can be externally referenced from a WebAssembly module. #[derive(Debug, Clone)] pub enum ExternType { /// The type of an [`Extern::Global`]. Global(GlobalType), /// The type of an [`Extern::Table`]. Table(TableType), /// The type of an [`Extern::Memory`]. Memory(MemoryType), /// The type of an [`Extern::Func`]. Func(FuncType), } impl From for ExternType { fn from(global: GlobalType) -> Self { Self::Global(global) } } impl From for ExternType { fn from(table: TableType) -> Self { Self::Table(table) } } impl From for ExternType { fn from(memory: MemoryType) -> Self { Self::Memory(memory) } } impl From for ExternType { fn from(func: FuncType) -> Self { Self::Func(func) } } impl ExternType { /// Returns the underlying [`GlobalType`] or `None` if it is of a different type. pub fn global(&self) -> Option<&GlobalType> { match self { Self::Global(ty) => Some(ty), _ => None, } } /// Returns the underlying [`TableType`] or `None` if it is of a different type. pub fn table(&self) -> Option<&TableType> { match self { Self::Table(ty) => Some(ty), _ => None, } } /// Returns the underlying [`MemoryType`] or `None` if it is of a different type. pub fn memory(&self) -> Option<&MemoryType> { match self { Self::Memory(ty) => Some(ty), _ => None, } } /// Returns the underlying [`FuncType`] or `None` if it is of a different type. pub fn func(&self) -> Option<&FuncType> { match self { Self::Func(ty) => Some(ty), _ => None, } } } /// An exported WebAssembly value. /// /// This type is primarily accessed from the [`Instance::exports`](crate::Instance::exports) method /// and describes what names and items are exported from a Wasm [`Instance`](crate::Instance). #[derive(Debug, Clone)] pub struct Export<'instance> { /// The name of the exported item. name: &'instance str, /// The definition of the exported item. definition: Extern, } impl<'instance> Export<'instance> { /// Creates a new [`Export`] with the given `name` and `definition`. pub(crate) fn new(name: &'instance str, definition: Extern) -> Export<'instance> { Self { name, definition } } /// Returns the name by which this export is known. pub fn name(&self) -> &'instance str { self.name } /// Return the [`ExternType`] of this export. /// /// # Panics /// /// If `ctx` does not own this [`Export`]. pub fn ty(&self, ctx: impl AsContext) -> ExternType { self.definition.ty(ctx) } /// Consume this [`Export`] and return the underlying [`Extern`]. pub fn into_extern(self) -> Extern { self.definition } /// Returns the underlying [`Func`], if the [`Export`] is a function or `None` otherwise. pub fn into_func(self) -> Option { self.definition.into_func() } /// Returns the underlying [`Table`], if the [`Export`] is a table or `None` otherwise. pub fn into_table(self) -> Option
{ self.definition.into_table() } /// Returns the underlying [`Memory`], if the [`Export`] is a linear memory or `None` otherwise. pub fn into_memory(self) -> Option { self.definition.into_memory() } /// Returns the underlying [`Global`], if the [`Export`] is a global variable or `None` otherwise. pub fn into_global(self) -> Option { self.definition.into_global() } } /// An iterator over the [`Extern`] declarations of an [`Instance`](crate::Instance). #[derive(Debug)] pub struct ExportsIter<'instance> { iter: MapIter<'instance, Box, Extern>, } impl<'instance> ExportsIter<'instance> { /// Creates a new [`ExportsIter`]. pub(super) fn new(iter: MapIter<'instance, Box, Extern>) -> Self { Self { iter } } /// Prepares an item to match the expected iterator `Item` signature. #[allow(clippy::borrowed_box)] fn convert_item((name, export): (&'instance Box, &'instance Extern)) -> Export<'instance> { Export::new(name, *export) } } impl<'instance> Iterator for ExportsIter<'instance> { type Item = Export<'instance>; fn next(&mut self) -> Option { self.iter.next().map(Self::convert_item) } fn size_hint(&self) -> (usize, Option) { self.iter.size_hint() } } impl ExactSizeIterator for ExportsIter<'_> {} impl FusedIterator for ExportsIter<'_> {} wasmi-1.1.0/src/instance/mod.rs000064400000000000000000000245171046102023000144740ustar 00000000000000pub(crate) use self::builder::InstanceEntityBuilder; pub use self::exports::{Export, ExportsIter, Extern, ExternType}; use super::{ engine::DedupFuncType, AsContext, Func, Global, Memory, Module, StoreContext, Stored, Table, }; use crate::{ collections::{arena::ArenaIndex, Map}, func::FuncError, memory::DataSegment, AsContextMut, ElementSegment, Error, TypedFunc, WasmParams, WasmResults, }; use alloc::{boxed::Box, sync::Arc}; mod builder; mod exports; #[cfg(test)] mod tests; /// A raw index to a module instance entity. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct InstanceIdx(u32); impl ArenaIndex for InstanceIdx { fn into_usize(self) -> usize { self.0 as usize } fn from_usize(value: usize) -> Self { let value = value.try_into().unwrap_or_else(|error| { panic!("index {value} is out of bounds as instance index: {error}") }); Self(value) } } /// A module instance entity. #[derive(Debug)] pub struct InstanceEntity { initialized: bool, func_types: Arc<[DedupFuncType]>, tables: Box<[Table]>, funcs: Box<[Func]>, memories: Box<[Memory]>, globals: Box<[Global]>, exports: Map, Extern>, data_segments: Box<[DataSegment]>, elem_segments: Box<[ElementSegment]>, } impl InstanceEntity { /// Creates an uninitialized [`InstanceEntity`]. pub fn uninitialized() -> InstanceEntity { Self { initialized: false, func_types: Arc::new([]), tables: [].into(), funcs: [].into(), memories: [].into(), globals: [].into(), exports: Map::new(), data_segments: [].into(), elem_segments: [].into(), } } /// Creates a new [`InstanceEntityBuilder`]. pub fn build(module: &Module) -> InstanceEntityBuilder { InstanceEntityBuilder::new(module) } /// Returns `true` if the [`InstanceEntity`] has been fully initialized. pub fn is_initialized(&self) -> bool { self.initialized } /// Returns the linear memory at the `index` if any. pub fn get_memory(&self, index: u32) -> Option { self.memories.get(index as usize).copied() } /// Returns the table at the `index` if any. pub fn get_table(&self, index: u32) -> Option
{ self.tables.get(index as usize).copied() } /// Returns the global variable at the `index` if any. pub fn get_global(&self, index: u32) -> Option { self.globals.get(index as usize).copied() } /// Returns the function at the `index` if any. pub fn get_func(&self, index: u32) -> Option { self.funcs.get(index as usize).copied() } /// Returns the signature at the `index` if any. pub fn get_signature(&self, index: u32) -> Option<&DedupFuncType> { self.func_types.get(index as usize) } /// Returns the [`DataSegment`] at the `index` if any. pub fn get_data_segment(&self, index: u32) -> Option { self.data_segments.get(index as usize).copied() } /// Returns the [`ElementSegment`] at the `index` if any. pub fn get_element_segment(&self, index: u32) -> Option { self.elem_segments.get(index as usize).copied() } /// Returns the value exported to the given `name` if any. pub fn get_export(&self, name: &str) -> Option { self.exports.get(name).copied() } /// Returns an iterator over the exports of the [`Instance`]. /// /// The order of the yielded exports is not specified. pub fn exports(&self) -> ExportsIter<'_> { ExportsIter::new(self.exports.iter()) } } /// An instantiated WebAssembly [`Module`]. /// /// This type represents an instantiation of a [`Module`]. /// It primarily allows to access its [`exports`](Instance::exports) /// to call functions, get or set globals, read or write memory, etc. /// /// When interacting with any Wasm code you will want to create an /// [`Instance`] in order to execute anything. /// /// Instances are owned by a [`Store`](crate::Store). /// Create new instances using [`Linker::instantiate_and_start`](crate::Linker::instantiate_and_start). #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[repr(transparent)] pub struct Instance(Stored); impl Instance { /// Creates a new [`Instance`] from the pre-compiled [`Module`] and the list of `imports`. /// /// Uses the official [Wasm instantiation procedure] in order to resolve and type-check /// the provided `imports` and match them with the required imports of the [`Module`]. /// /// # Note /// /// - This function intentionally is rather low-level for [`Instance`] creation. /// Please use the [`Linker`](crate::Linker) type for a more high-level API for Wasm /// module instantiation with name-based resolution. /// - Wasm module instantiation implies running the Wasm `start` function which is _not_ /// to be confused with WASI's `_start` function. /// /// # Usage /// /// The `imports` are intended to correspond 1:1 with the required imports as returned by [`Module::imports`]. /// For each import type returned by [`Module::imports`], create an [`Extern`] which corresponds to that type. /// Collect the [`Extern`] values created this way into a list and pass them to this function. /// /// # Errors /// /// - If the number of provided imports does not match the number of imports required by the [`Module`]. /// - If the type of any provided [`Extern`] does not match the corresponding required [`ExternType`]. /// - If the `start` function, that is run at the end of the Wasm module instantiation, traps. /// - If Wasm module or instance related resource limits are exceeded. /// /// # Panics /// /// If any [`Extern`] does not originate from the provided `store`. /// /// [Wasm instantiation procedure]: https://webassembly.github.io/spec/core/exec/modules.html#exec-instantiation pub fn new( mut store: impl AsContextMut, module: &Module, imports: &[Extern], ) -> Result { let instance = Module::instantiate(module, &mut store, imports.iter().cloned())?; Ok(instance) } /// Creates a new stored instance reference. /// /// # Note /// /// This API is primarily used by the [`Store`] itself. /// /// [`Store`]: [`crate::Store`] pub(super) fn from_inner(stored: Stored) -> Self { Self(stored) } /// Returns the underlying stored representation. pub(super) fn as_inner(&self) -> &Stored { &self.0 } /// Returns the function at the `index` if any. /// /// # Panics /// /// Panics if `store` does not own this [`Instance`]. pub(crate) fn get_func_by_index(&self, store: impl AsContext, index: u32) -> Option { store .as_context() .store .inner .resolve_instance(self) .get_func(index) } /// Returns the value exported to the given `name` if any. /// /// # Panics /// /// Panics if `store` does not own this [`Instance`]. pub fn get_export(&self, store: impl AsContext, name: &str) -> Option { store .as_context() .store .inner .resolve_instance(self) .get_export(name) } /// Looks up an exported [`Func`] value by `name`. /// /// Returns `None` if there was no export named `name`, /// or if there was but it wasn’t a function. /// /// # Panics /// /// If `store` does not own this [`Instance`]. pub fn get_func(&self, store: impl AsContext, name: &str) -> Option { self.get_export(store, name)?.into_func() } /// Looks up an exported [`Func`] value by `name`. /// /// Returns `None` if there was no export named `name`, /// or if there was but it wasn’t a function. /// /// # Errors /// /// - If there is no export named `name`. /// - If there is no exported function named `name`. /// - If `Params` or `Results` do not match the exported function type. /// /// # Panics /// /// If `store` does not own this [`Instance`]. pub fn get_typed_func( &self, store: impl AsContext, name: &str, ) -> Result, Error> where Params: WasmParams, Results: WasmResults, { self.get_export(&store, name) .and_then(Extern::into_func) .ok_or_else(|| Error::from(FuncError::ExportedFuncNotFound))? .typed::(store) } /// Looks up an exported [`Global`] value by `name`. /// /// Returns `None` if there was no export named `name`, /// or if there was but it wasn’t a global variable. /// /// # Panics /// /// If `store` does not own this [`Instance`]. pub fn get_global(&self, store: impl AsContext, name: &str) -> Option { self.get_export(store, name)?.into_global() } /// Looks up an exported [`Table`] value by `name`. /// /// Returns `None` if there was no export named `name`, /// or if there was but it wasn’t a table. /// /// # Panics /// /// If `store` does not own this [`Instance`]. pub fn get_table(&self, store: impl AsContext, name: &str) -> Option
{ self.get_export(store, name)?.into_table() } /// Looks up an exported [`Memory`] value by `name`. /// /// Returns `None` if there was no export named `name`, /// or if there was but it wasn’t a memory. /// /// # Panics /// /// If `store` does not own this [`Instance`]. pub fn get_memory(&self, store: impl AsContext, name: &str) -> Option { self.get_export(store, name)?.into_memory() } /// Returns an iterator over the exports of the [`Instance`]. /// /// The order of the yielded exports is not specified. /// /// # Panics /// /// Panics if `store` does not own this [`Instance`]. pub fn exports<'ctx, T: 'ctx>( &self, store: impl Into>, ) -> ExportsIter<'ctx> { store.into().store.inner.resolve_instance(self).exports() } } wasmi-1.1.0/src/instance/tests.rs000064400000000000000000000142071046102023000150520ustar 00000000000000use super::*; use crate::{ error::ErrorKind, module::InstantiationError, Caller, Engine, ExternRef, Func, MemoryType, Mutability, Ref, Store, TableType, TrapCode, Val, ValType, }; #[test] fn instantiate_no_imports() { let wasm = r#" (module (func (export "f") (param i32 i32) (result i32) (i32.add (local.get 0) (local.get 1)) ) ) "#; let engine = Engine::default(); let module = Module::new(&engine, wasm).unwrap(); let mut store = Store::new(&engine, ()); let instance = Instance::new(&mut store, &module, &[]).unwrap(); assert!(instance.get_func(&store, "f").is_some()); } #[test] fn instantiate_with_start() { let wasm = r#" (module (func $f) (start $f) ) "#; let engine = Engine::default(); let module = Module::new(&engine, wasm).unwrap(); let mut store = Store::new(&engine, ()); let _instance = Instance::new(&mut store, &module, &[]).unwrap(); } #[test] fn instantiate_with_trapping_start() { let wasm = r#" (module (func $f (unreachable) ) (start $f) ) "#; let engine = Engine::default(); let module = Module::new(&engine, wasm).unwrap(); let mut store = Store::new(&engine, ()); let error = Instance::new(&mut store, &module, &[]).unwrap_err(); assert_eq!(error.as_trap_code(), Some(TrapCode::UnreachableCodeReached)); } #[test] fn instantiate_with_imports_and_start() { let wasm = r#" (module (import "env" "f" (func $f (param i32))) (import "env" "t" (table $t 0 funcref)) (import "env" "m" (memory $m 0)) (import "env" "g" (global $g (mut i32))) (elem declare func $f) (func $main (global.set $g (i32.const 1)) (i32.store8 $m (i32.const 0) (i32.const 1)) (table.set $t (i32.const 0) (ref.func $f)) (call $f (i32.const 1)) ) (start $main) ) "#; let engine = Engine::default(); let module = Module::new(&engine, wasm).unwrap(); let data: i32 = 0; let mut store = Store::new(&engine, data); let g = Global::new(&mut store, Val::I32(0), Mutability::Var); let m = Memory::new(&mut store, MemoryType::new(1, None)).unwrap(); let t = Table::new( &mut store, TableType::new(ValType::FuncRef, 1, None), Val::from(>::Null), ) .unwrap(); let f = Func::wrap(&mut store, |mut caller: Caller, a: i32| { let data = caller.data_mut(); *data = a; }); let externals = [ Extern::from(f), Extern::from(t), Extern::from(m), Extern::from(g), ] .map(Extern::from); let _instance = Instance::new(&mut store, &module, &externals).unwrap(); assert_eq!(g.get(&store).i32(), Some(1)); assert_eq!(m.data(&store)[0], 0x01_u8); assert!(!t.get(&store, 0).unwrap().funcref().unwrap().is_null()); assert_eq!(store.data(), &1); } #[test] fn instantiate_with_invalid_global_import() { let wasm = r#" (module (import "env" "g" (global $g (mut i32))) (func $main (global.set $g (i32.const 1)) ) ) "#; let engine = Engine::default(); let module = Module::new(&engine, wasm).unwrap(); let mut store = Store::new(&engine, ()); let g = Global::new(&mut store, Val::I64(0), Mutability::Var); let externals = [Extern::from(g)].map(Extern::from); let error = Instance::new(&mut store, &module, &externals).unwrap_err(); assert!(matches!( error.kind(), ErrorKind::Instantiation(InstantiationError::GlobalTypeMismatch { .. }) )); } #[test] fn instantiate_with_invalid_memory_import() { let wasm = r#" (module (import "env" "m" (memory $m 2)) (func (i32.store8 $m (i32.const 0) (i32.const 1)) ) ) "#; let engine = Engine::default(); let module = Module::new(&engine, wasm).unwrap(); let mut store = Store::new(&engine, ()); let m = Memory::new(&mut store, MemoryType::new(0, Some(1))).unwrap(); let externals = [Extern::from(m)].map(Extern::from); let error = Instance::new(&mut store, &module, &externals).unwrap_err(); assert!(matches!( error.kind(), ErrorKind::Instantiation(InstantiationError::MemoryTypeMismatch { .. }) )); } #[test] fn instantiate_with_invalid_table_import() { let wasm = r#" (module (import "env" "t" (table $t 0 funcref)) (elem declare func $f) (func $f (table.set $t (i32.const 0) (ref.func $f)) ) ) "#; let engine = Engine::default(); let module = Module::new(&engine, wasm).unwrap(); let mut store = Store::new(&engine, ()); let t = Table::new( &mut store, TableType::new(ValType::ExternRef, 1, None), Val::from(>::Null), ) .unwrap(); let externals = [Extern::from(t)].map(Extern::from); let error = Instance::new(&mut store, &module, &externals).unwrap_err(); assert!(matches!( error.kind(), ErrorKind::Instantiation(InstantiationError::TableTypeMismatch { .. }) )); } #[test] fn instantiate_with_invalid_func_import() { let wasm = r#" (module (import "env" "f" (func $f (param i32))) (elem declare func $f) (func (call $f (i32.const 1)) ) ) "#; let engine = Engine::default(); let module = Module::new(&engine, wasm).unwrap(); let data: i64 = 0; let mut store = Store::new(&engine, data); let f = Func::wrap(&mut store, |mut caller: Caller, a: i64| { let data = caller.data_mut(); *data = a; }); let externals = [Extern::from(f)].map(Extern::from); let error = Instance::new(&mut store, &module, &externals).unwrap_err(); assert!(matches!( error.kind(), ErrorKind::Instantiation(InstantiationError::FuncTypeMismatch { .. }) )); } wasmi-1.1.0/src/lib.rs000064400000000000000000000165521046102023000126570ustar 00000000000000//! The Wasmi virtual machine definitions. //! //! These closely mirror the WebAssembly specification definitions. //! The overall structure is heavily inspired by the `wasmtime` virtual //! machine architecture. //! //! # Example //! //! The following example shows a "Hello, World!"-like example of creating //! a Wasm module from some initial `.wat` contents, defining a simple host //! function and calling the exported Wasm function. //! //! The example was inspired by //! [Wasmtime's API example](https://docs.rs/wasmtime/0.39.1/wasmtime/). //! //! ``` //! use wasmi::*; //! //! // In this simple example we are going to compile the below Wasm source, //! // instantiate a Wasm module from it and call its exported "hello" function. //! fn main() -> Result<(), wasmi::Error> { //! let wasm = r#" //! (module //! (import "host" "hello" (func $host_hello (param i32))) //! (func (export "hello") //! (call $host_hello (i32.const 3)) //! ) //! ) //! "#; //! // First step is to create the Wasm execution engine with some config. //! // //! // In this example we are using the default configuration. //! let engine = Engine::default(); //! // Now we can compile the above Wasm module with the given Wasm source. //! let module = Module::new(&engine, wasm)?; //! //! // Wasm objects operate within the context of a Wasm `Store`. //! // //! // Each `Store` has a type parameter to store host specific data. //! // In this example the host state is a simple `u32` type with value `42`. //! type HostState = u32; //! let mut store = Store::new(&engine, 42); //! //! // A linker can be used to instantiate Wasm modules. //! // The job of a linker is to satisfy the Wasm module's imports. //! let mut linker = >::new(&engine); //! // We are required to define all imports before instantiating a Wasm module. //! linker.func_wrap("host", "hello", |caller: Caller<'_, HostState>, param: i32| { //! println!("Got {param} from WebAssembly and my host state is: {}", caller.data()); //! }); //! let instance = linker.instantiate_and_start(&mut store, &module)?; //! // Now we can finally query the exported "hello" function and call it. //! instance //! .get_typed_func::<(), ()>(&store, "hello")? //! .call(&mut store, ())?; //! Ok(()) //! } //! ``` //! //! # Crate Features //! //! | Feature | Crates | Description | //! |:-:|:--|:--| //! | `std` | `wasmi`
`wasmi_core`
`wasmi_ir`
`wasmi_collections` | Enables usage of Rust's standard library. This may have some performance advantages when enabled. Disabling this feature makes Wasmi compile on platforms that do not provide Rust's standard library such as many embedded platforms.

Enabled by default. | //! | `wat` | `wasmi` | Enables support to parse Wat encoded Wasm modules.

Enabled by default. | //! | `simd` | `wasmi`
`wasmi_core`
`wasmi_ir`
`wasmi_cli` | Enables support for the Wasm `simd` and `relaxed-simd` proposals. Note that this may introduce execution overhead and increased memory consumption for Wasm executions that do not need Wasm `simd` functionality.

Disabled by default. | //! | `hash-collections` | `wasmi`
`wasmi_collections` | Enables use of hash-map based collections in Wasmi internals. This might yield performance improvements in some use cases.

Disabled by default. | //! | `prefer-btree-collections` | `wasmi`
`wasmi_collections` | Enforces use of btree-map based collections in Wasmi internals. This may yield performance improvements and memory consumption decreases in some use cases. Also it enables Wasmi to run on platforms that have no random source.

Disabled by default. | //! | `extra-checks` | `wasmi` | Enables extra runtime checks in the Wasmi executor. Expected execution overhead is ~20%. Enable this if your focus is on safety. Disable this for maximum execution performance.

Disabled by default. | #![no_std] #![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::items_after_statements )] #![recursion_limit = "1000"] extern crate alloc; #[cfg(feature = "std")] extern crate std; #[macro_use] mod foreach_tuple; #[cfg(test)] pub mod tests; mod engine; mod error; mod func; mod global; mod instance; mod limits; mod linker; mod memory; mod module; mod reftype; mod store; mod table; mod value; /// Definitions from the `wasmi_core` crate. mod core { #[cfg(feature = "simd")] pub use wasmi_core::simd; pub use wasmi_core::{ hint, wasm, DecodeUntypedSlice, ElementSegment as CoreElementSegment, EncodeUntypedSlice, Fuel, FuelCostsProvider, FuncType as CoreFuncType, Global as CoreGlobal, IndexType, LimiterError, Memory as CoreMemory, MemoryType as CoreMemoryType, MemoryTypeBuilder as CoreMemoryTypeBuilder, ReadAs, ResourceLimiterRef, Table as CoreTable, TableType as CoreTableType, Typed, TypedVal, UntypedError, UntypedVal, WriteAs, }; } /// Definitions from the `wasmi_collections` crate. #[doc(inline)] use wasmi_collections as collections; /// Definitions from the `wasmi_collections` crate. #[doc(inline)] use wasmi_ir as ir; /// Defines some errors that may occur upon interaction with Wasmi. pub mod errors { pub use super::{ engine::EnforcedLimitsError, error::ErrorKind, func::FuncError, ir::Error as IrError, linker::LinkerError, module::{InstantiationError, ReadError}, }; pub use wasmi_core::{FuelError, GlobalError, HostError, MemoryError, TableError}; } pub use self::{ engine::{ CompilationMode, Config, EnforcedLimits, Engine, EngineWeak, ResumableCall, ResumableCallHostTrap, ResumableCallOutOfFuel, TypedResumableCall, TypedResumableCallHostTrap, TypedResumableCallOutOfFuel, }, error::Error, func::{ Caller, Func, FuncType, IntoFunc, TypedFunc, WasmParams, WasmResults, WasmRet, WasmTy, WasmTyList, }, global::Global, instance::{Export, ExportsIter, Extern, ExternType, Instance}, limits::{StoreLimits, StoreLimitsBuilder}, linker::Linker, memory::{Memory, MemoryType, MemoryTypeBuilder}, module::{ CustomSection, CustomSectionsIter, ExportType, ImportType, Module, ModuleExportsIter, ModuleImportsIter, Read, }, reftype::{ExternRef, Ref}, store::{AsContext, AsContextMut, CallHook, Store, StoreContext, StoreContextMut}, table::{Table, TableType}, value::Val, }; use self::{ func::{FuncEntity, FuncIdx}, global::GlobalIdx, instance::{InstanceEntity, InstanceEntityBuilder, InstanceIdx}, memory::{DataSegmentEntity, DataSegmentIdx, MemoryIdx}, store::Stored, table::{ElementSegment, ElementSegmentIdx, TableIdx}, }; pub use wasmi_core::{GlobalType, Mutability, ResourceLimiter, TrapCode, ValType, F32, F64, V128}; wasmi-1.1.0/src/limits.rs000064400000000000000000000132301046102023000134000ustar 00000000000000use crate::{core::LimiterError, ResourceLimiter}; /// Value returned by [`ResourceLimiter::instances`] default method pub const DEFAULT_INSTANCE_LIMIT: usize = 10000; /// Value returned by [`ResourceLimiter::tables`] default method pub const DEFAULT_TABLE_LIMIT: usize = 10000; /// Value returned by [`ResourceLimiter::memories`] default method pub const DEFAULT_MEMORY_LIMIT: usize = 10000; /// Used to build [`StoreLimits`]. pub struct StoreLimitsBuilder(StoreLimits); impl StoreLimitsBuilder { /// Creates a new [`StoreLimitsBuilder`]. /// /// See the documentation on each builder method for the default for each /// value. pub fn new() -> Self { Self(StoreLimits::default()) } /// The maximum number of bytes a linear memory can grow to. /// /// Growing a linear memory beyond this limit will fail. This limit is /// applied to each linear memory individually, so if a wasm module has /// multiple linear memories then they're all allowed to reach up to the /// `limit` specified. /// /// By default, linear memory will not be limited. pub fn memory_size(mut self, limit: usize) -> Self { self.0.memory_size = Some(limit); self } /// The maximum number of elements in a table. /// /// Growing a table beyond this limit will fail. This limit is applied to /// each table individually, so if a wasm module has multiple tables then /// they're all allowed to reach up to the `limit` specified. /// /// By default, table elements will not be limited. pub fn table_elements(mut self, limit: usize) -> Self { self.0.table_elements = Some(limit); self } /// The maximum number of instances that can be created for a [`Store`](crate::Store). /// /// Module instantiation will fail if this limit is exceeded. /// /// This value defaults to 10,000. pub fn instances(mut self, limit: usize) -> Self { self.0.instances = limit; self } /// The maximum number of tables that can be created for a [`Store`](crate::Store). /// /// Module instantiation will fail if this limit is exceeded. /// /// This value defaults to 10,000. pub fn tables(mut self, tables: usize) -> Self { self.0.tables = tables; self } /// The maximum number of linear memories that can be created for a [`Store`](crate::Store). /// /// Instantiation will fail with an error if this limit is exceeded. /// /// This value defaults to 10,000. pub fn memories(mut self, memories: usize) -> Self { self.0.memories = memories; self } /// Indicates that a trap should be raised whenever a growth operation /// would fail. /// /// This operation will force `memory.grow` and `table.grow` instructions /// to raise a trap on failure instead of returning -1. This is not /// necessarily spec-compliant, but it can be quite handy when debugging a /// module that fails to allocate memory and might behave oddly as a result. /// /// This value defaults to `false`. pub fn trap_on_grow_failure(mut self, trap: bool) -> Self { self.0.trap_on_grow_failure = trap; self } /// Consumes this builder and returns the [`StoreLimits`]. pub fn build(self) -> StoreLimits { self.0 } } impl Default for StoreLimitsBuilder { fn default() -> Self { Self::new() } } /// Provides limits for a [`Store`](crate::Store). /// /// This type is created with a [`StoreLimitsBuilder`] and is typically used in /// conjunction with [`Store::limiter`](crate::Store::limiter). /// /// This is a convenience type included to avoid needing to implement the /// [`ResourceLimiter`] trait if your use case fits in the static configuration /// that this [`StoreLimits`] provides. #[derive(Clone, Debug)] pub struct StoreLimits { memory_size: Option, table_elements: Option, instances: usize, tables: usize, memories: usize, trap_on_grow_failure: bool, } impl Default for StoreLimits { fn default() -> Self { Self { memory_size: None, table_elements: None, instances: DEFAULT_INSTANCE_LIMIT, tables: DEFAULT_TABLE_LIMIT, memories: DEFAULT_MEMORY_LIMIT, trap_on_grow_failure: false, } } } impl ResourceLimiter for StoreLimits { fn memory_growing( &mut self, _current: usize, desired: usize, maximum: Option, ) -> Result { let allow = match self.memory_size { Some(limit) if desired > limit => false, _ => match maximum { Some(max) if desired > max => false, Some(_) | None => true, }, }; if !allow && self.trap_on_grow_failure { return Err(LimiterError::ResourceLimiterDeniedAllocation); } Ok(allow) } fn table_growing( &mut self, _current: usize, desired: usize, maximum: Option, ) -> Result { let allow = match self.table_elements { Some(limit) if desired > limit => false, _ => match maximum { Some(max) if desired > max => false, Some(_) | None => true, }, }; if !allow && self.trap_on_grow_failure { return Err(LimiterError::ResourceLimiterDeniedAllocation); } Ok(allow) } fn instances(&self) -> usize { self.instances } fn tables(&self) -> usize { self.tables } fn memories(&self) -> usize { self.memories } } wasmi-1.1.0/src/linker.rs000064400000000000000000000620731046102023000133740ustar 00000000000000use crate::{ collections::{ string_interner::{InternHint, Sym as Symbol}, StringInterner, }, func::{FuncEntity, HostFuncEntity, HostFuncTrampolineEntity}, module::{ImportName, ImportType}, AsContext, AsContextMut, Caller, Engine, Error, Extern, ExternType, Func, FuncType, Instance, IntoFunc, Module, Val, }; use alloc::{ collections::{btree_map::Entry, BTreeMap}, vec::Vec, }; use core::fmt::{self, Debug, Display}; /// An error that may occur upon operating with [`Linker`] instances. #[derive(Debug)] pub enum LinkerError { /// Encountered duplicate definitions for the same name. DuplicateDefinition { /// The duplicate import name of the definition. import_name: ImportName, }, /// Encountered when no definition for an import is found. MissingDefinition { /// The name of the import for which no definition was found. name: ImportName, /// The type of the import for which no definition has been found. ty: ExternType, }, /// Encountered when a definition with invalid type is found. InvalidTypeDefinition { /// The name of the import for which no definition was found. name: ImportName, /// The expected import type. expected: ExternType, /// The found definition type. found: ExternType, }, } impl LinkerError { /// Creates a new [`LinkerError`] for when an imported definition was not found. fn missing_definition(import: &ImportType) -> Self { Self::MissingDefinition { name: import.import_name().clone(), ty: import.ty().clone(), } } /// Creates a new [`LinkerError`] for when an imported definition has an invalid type. fn invalid_type_definition(import: &ImportType, found: &ExternType) -> Self { Self::InvalidTypeDefinition { name: import.import_name().clone(), expected: import.ty().clone(), found: found.clone(), } } } impl core::error::Error for LinkerError {} impl Display for LinkerError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::DuplicateDefinition { import_name } => { write!( f, "encountered duplicate definition with name `{import_name}`", ) } Self::MissingDefinition { name, ty } => { write!( f, "cannot find definition for import {name} with type {ty:?}", ) } Self::InvalidTypeDefinition { name, expected, found, } => { write!(f, "found definition for import {name} with type {expected:?} but found type {found:?}") } } } } /// Wasm import keys. #[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq)] #[repr(transparent)] struct ImportKey { /// Merged module and name symbols. /// /// Merging allows for a faster `Ord` implementation. module_and_name: u64, } impl Debug for ImportKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ImportKey") .field("module", &self.module()) .field("name", &self.name()) .finish() } } impl ImportKey { /// Creates a new [`ImportKey`] from the given `module` and `name` symbols. #[inline] pub fn new(module: Symbol, name: Symbol) -> Self { let module_and_name = (u64::from(module.into_u32()) << 32) | u64::from(name.into_u32()); Self { module_and_name } } /// Returns the `module` [`Symbol`] of the [`ImportKey`]. #[inline] pub fn module(&self) -> Symbol { Symbol::from_u32((self.module_and_name >> 32) as u32) } /// Returns the `name` [`Symbol`] of the [`ImportKey`]. #[inline] pub fn name(&self) -> Symbol { Symbol::from_u32(self.module_and_name as u32) } } /// A [`Linker`] definition. #[derive(Debug)] enum Definition { /// An external item from an [`Instance`]. Extern(Extern), /// A [`Linker`] internal host function. HostFunc(HostFuncTrampolineEntity), } impl Clone for Definition { fn clone(&self) -> Self { match self { Self::Extern(definition) => Self::Extern(*definition), Self::HostFunc(host_func) => Self::HostFunc(host_func.clone()), } } } impl Definition { /// Returns the [`Extern`] item if this [`Definition`] is [`Definition::Extern`]. /// /// Otherwise returns `None`. fn as_extern(&self) -> Option<&Extern> { match self { Definition::Extern(item) => Some(item), Definition::HostFunc(_) => None, } } /// Returns the [`ExternType`] of the [`Definition`]. pub fn ty(&self, ctx: impl AsContext) -> ExternType { match self { Definition::Extern(item) => item.ty(ctx), Definition::HostFunc(host_func) => ExternType::Func(host_func.func_type().clone()), } } /// Returns the [`Func`] of the [`Definition`] if it is a function. /// /// Returns `None` otherwise. /// /// # Note /// /// - This allocates a new [`Func`] on the `ctx` if it is a [`Linker`] /// defined host function. /// - This unifies handling of [`Definition::Extern(Extern::Func)`] and /// [`Definition::HostFunc`]. /// /// [`Definition::Extern(Extern::Func)`]: crate::Extern::Func pub fn as_func(&self, mut ctx: impl AsContextMut) -> Option { match self { Definition::Extern(Extern::Func(func)) => Some(*func), Definition::HostFunc(host_func) => { let trampoline = ctx .as_context_mut() .store .alloc_trampoline(host_func.trampoline().clone()); let ty = host_func.func_type(); let entity = HostFuncEntity::new(ctx.as_context().engine(), ty, trampoline); let func = ctx .as_context_mut() .store .inner .alloc_func(FuncEntity::Host(entity)); Some(func) } _ => None, } } } /// A linker used to define module imports and instantiate module instances. #[derive(Debug)] pub struct Linker { /// The underlying [`Engine`] for the [`Linker`]. /// /// # Note /// /// Primarily required to define [`Linker`] owned host functions /// using [`Linker::func_wrap`] and [`Linker::func_new`]. engine: Engine, /// Inner linker implementation details. inner: LinkerInner, } impl Clone for Linker { fn clone(&self) -> Linker { Self { engine: self.engine.clone(), inner: self.inner.clone(), } } } impl Default for Linker { fn default() -> Self { Self::new(&Engine::default()) } } impl Linker { /// Creates a new [`Linker`]. pub fn new(engine: &Engine) -> Self { Self { engine: engine.clone(), inner: LinkerInner::default(), } } /// Returns the underlying [`Engine`] of the [`Linker`]. pub fn engine(&self) -> &Engine { &self.engine } /// Configures whether this [`Linker`] allows to shadow previous definitions with the same name. /// /// Disabled by default. pub fn allow_shadowing(&mut self, allow: bool) -> &mut Self { self.inner.allow_shadowing(allow); self } /// Define a new item in this [`Linker`]. /// /// # Errors /// /// If there already is a definition under the same name for this [`Linker`]. pub fn define( &mut self, module: &str, name: &str, item: impl Into, ) -> Result<&mut Self, LinkerError> { let key = self.inner.new_import_key(module, name); self.inner.insert(key, Definition::Extern(item.into()))?; Ok(self) } /// Creates a new named [`Func::new`]-style host [`Func`] for this [`Linker`]. /// /// For more information see [`Linker::func_wrap`]. /// /// # Errors /// /// If there already is a definition under the same name for this [`Linker`]. pub fn func_new( &mut self, module: &str, name: &str, ty: FuncType, func: impl Fn(Caller<'_, T>, &[Val], &mut [Val]) -> Result<(), Error> + Send + Sync + 'static, ) -> Result<&mut Self, LinkerError> { let func = HostFuncTrampolineEntity::new(ty, func); let key = self.inner.new_import_key(module, name); self.inner.insert(key, Definition::HostFunc(func))?; Ok(self) } /// Creates a new named [`Func::new`]-style host [`Func`] for this [`Linker`]. /// /// For information how to use this API see [`Func::wrap`]. /// /// This method creates a host function for this [`Linker`] under the given name. /// It is distinct in its ability to create a [`Store`] independent /// host function. Host functions defined this way can be used to instantiate /// instances in multiple different [`Store`] entities. /// /// The same applies to other [`Linker`] methods to define new [`Func`] instances /// such as [`Linker::func_new`]. /// /// In a concurrently running program, this means that these host functions /// could be called concurrently if different [`Store`] entities are executing on /// different threads. /// /// # Errors /// /// If there already is a definition under the same name for this [`Linker`]. /// /// [`Store`]: crate::Store pub fn func_wrap( &mut self, module: &str, name: &str, func: impl IntoFunc, ) -> Result<&mut Self, LinkerError> { let func = HostFuncTrampolineEntity::wrap(func); let key = self.inner.new_import_key(module, name); self.inner.insert(key, Definition::HostFunc(func))?; Ok(self) } /// Looks up a defined [`Extern`] by name in this [`Linker`]. /// /// - Returns `None` if this name was not previously defined in this [`Linker`]. /// - Returns `None` if the definition is a [`Linker`] defined host function. /// /// # Panics /// /// If the [`Engine`] of this [`Linker`] and the [`Engine`] of `context` are not the same. pub fn get( &self, context: impl AsContext, module: &str, name: &str, ) -> Option { match self.get_definition(context, module, name) { Some(Definition::Extern(item)) => Some(*item), _ => None, } } /// Looks up a [`Definition`] by name in this [`Linker`]. /// /// Returns `None` if this name was not previously defined in this [`Linker`]. /// /// # Panics /// /// If the [`Engine`] of this [`Linker`] and the [`Engine`] of `context` are not the same. fn get_definition( &self, context: impl AsContext, module: &str, name: &str, ) -> Option<&Definition> { assert!(Engine::same( context.as_context().store.engine(), self.engine() )); self.inner.get_definition(module, name) } /// Convenience wrapper to define an entire [`Instance`]` in this [`Linker`]. /// /// This is a convenience wrapper around [`Linker::define`] which defines all exports of /// the `instance` for `self`. The module name for each export is `module_name` and the /// field name for each export is the name in the `instance` itself. /// /// # Errors /// /// - If any item is re-defined in `self` (for example the same `module_name` was already defined). /// - If `instance` comes from a different [`Store`](crate::Store) than this [`Linker`] originally /// was created with. /// /// # Panics /// /// If the [`Engine`] of this [`Linker`] and the [`Engine`] of `store` are not the same. pub fn instance( &mut self, mut store: impl AsContextMut, module_name: &str, instance: Instance, ) -> Result<&mut Self, Error> { assert!(Engine::same( store.as_context().store.engine(), self.engine() )); let mut store = store.as_context_mut(); for export in instance.exports(&mut store) { let key = self.inner.new_import_key(module_name, export.name()); let def = Definition::Extern(export.into_extern()); self.inner.insert(key, def)?; } Ok(self) } /// Aliases one module's name as another. /// /// This method will alias all currently defined under `module` to also be /// defined under the name `as_module` too. /// /// # Errors /// /// Returns an error if any shadowing violations happen while defining new /// items. pub fn alias_module(&mut self, module: &str, as_module: &str) -> Result<(), Error> { self.inner.alias_module(module, as_module) } /// Instantiates the given [`Module`] using the definitions in the [`Linker`]. /// /// # Panics /// /// If the [`Engine`] of the [`Linker`] and `context` are not the same. /// /// # Errors /// /// - If the linker does not define imports of the instantiated [`Module`]. /// - If any imported item does not satisfy its type requirements. /// - If the `start` function traps. pub fn instantiate_and_start( &self, mut context: impl AsContextMut, module: &Module, ) -> Result { assert!(Engine::same(self.engine(), context.as_context().engine())); // TODO: possibly add further resource limitation here on number of externals. // Not clear that user can't import the same external lots of times to inflate this. let externals = module .imports() .map(|import| self.process_import(&mut context, import)) .collect::, Error>>()?; module.instantiate(context, externals) } /// Processes a single [`Module`] import. /// /// # Panics /// /// If the [`Engine`] of the [`Linker`] and `context` are not the same. /// /// # Errors /// /// If the imported item does not satisfy constraints set by the [`Module`]. fn process_import( &self, mut context: impl AsContextMut, import: ImportType, ) -> Result { assert!(Engine::same(self.engine(), context.as_context().engine())); let module_name = import.module(); let field_name = import.name(); let resolved = self .get_definition(context.as_context(), module_name, field_name) .ok_or_else(|| LinkerError::missing_definition(&import))?; let invalid_type = |context| LinkerError::invalid_type_definition(&import, &resolved.ty(context)); match import.ty() { ExternType::Func(_expected) => { let func = resolved .as_func(&mut context) .ok_or_else(|| invalid_type(context))?; Ok(Extern::Func(func)) } ExternType::Table(_expected) => { let table = resolved .as_extern() .copied() .and_then(Extern::into_table) .ok_or_else(|| invalid_type(context))?; Ok(Extern::Table(table)) } ExternType::Memory(_expected) => { let memory = resolved .as_extern() .copied() .and_then(Extern::into_memory) .ok_or_else(|| invalid_type(context))?; Ok(Extern::Memory(memory)) } ExternType::Global(_expected) => { let global = resolved .as_extern() .copied() .and_then(Extern::into_global) .ok_or_else(|| invalid_type(context))?; Ok(Extern::Global(global)) } } } } /// Internal [`Linker`] implementation. #[derive(Debug)] pub struct LinkerInner { /// Allows to efficiently store strings and deduplicate them.. strings: StringInterner, /// Stores the definitions given their names. /// /// # Dev. Note /// /// Benchmarks show that [`BTreeMap`] performs better than [`HashMap`] /// which is why we do not use [`wasmi_collections::Map`] here. /// /// [`HashMap`]: std::collections::HashMap definitions: BTreeMap>, /// True if this linker allows to shadow previous definitions. allow_shadowing: bool, } impl Default for LinkerInner { fn default() -> Self { Self { strings: StringInterner::default(), definitions: BTreeMap::default(), allow_shadowing: false, } } } impl Clone for LinkerInner { fn clone(&self) -> Self { Self { strings: self.strings.clone(), definitions: self.definitions.clone(), allow_shadowing: self.allow_shadowing, } } } impl LinkerInner { /// Configures whether this [`LinkerInner`] allows to shadow previous definitions with the same name. /// /// Disabled by default. pub fn allow_shadowing(&mut self, allow: bool) { self.allow_shadowing = allow; } /// Returns the import key for the module name and item name. fn new_import_key(&mut self, module: &str, name: &str) -> ImportKey { ImportKey::new( self.strings .get_or_intern_with_hint(module, InternHint::LikelyExists), self.strings .get_or_intern_with_hint(name, InternHint::LikelyNew), ) } /// Returns the import key for the module name and item name. fn get_import_key(&self, module: &str, name: &str) -> Option { Some(ImportKey::new( self.strings.get(module)?, self.strings.get(name)?, )) } /// Resolves the module and item name of the import key if any. fn resolve_import_key(&self, key: ImportKey) -> Option<(&str, &str)> { let module_name = self.strings.resolve(key.module())?; let item_name = self.strings.resolve(key.name())?; Some((module_name, item_name)) } /// Inserts the extern item under the import key. /// /// # Errors /// /// If there already is a definition for the import key for this [`Linker`]. fn insert(&mut self, key: ImportKey, item: Definition) -> Result<(), LinkerError> { match self.definitions.entry(key) { Entry::Occupied(mut entry) if self.allow_shadowing => { entry.insert(item); } Entry::Occupied(_) => { let (module_name, field_name) = self .resolve_import_key(key) .unwrap_or_else(|| panic!("encountered missing import names for key {key:?}")); let import_name = ImportName::new(module_name, field_name); return Err(LinkerError::DuplicateDefinition { import_name }); } Entry::Vacant(v) => { v.insert(item); } } Ok(()) } /// Aliases one module's name as another. /// /// Read more about this method in [`Linker::alias_module`]. pub fn alias_module(&mut self, module: &str, as_module: &str) -> Result<(), Error> { let module = self .strings .get_or_intern_with_hint(module, InternHint::LikelyExists); let as_module = self .strings .get_or_intern_with_hint(as_module, InternHint::LikelyNew); let items = self .definitions .iter() .filter(|(key, _def)| key.module() == module) .map(|(key, def)| (key.name(), def.clone())) .collect::>(); for (name, item) in items { self.insert(ImportKey::new(as_module, name), item)?; } Ok(()) } /// Looks up a [`Definition`] by name in this [`Linker`]. /// /// Returns `None` if this name was not previously defined in this [`Linker`]. /// /// # Panics /// /// If the [`Engine`] of this [`Linker`] and the [`Engine`] of `context` are not the same. fn get_definition(&self, module: &str, name: &str) -> Option<&Definition> { let key = self.get_import_key(module, name)?; self.definitions.get(&key) } } #[cfg(test)] mod tests { use super::*; use crate::{Store, ValType}; struct HostState { a: i32, b: i64, } #[test] fn linker_funcs_work() { let engine = Engine::default(); let mut linker = >::new(&engine); linker .func_new( "host", "get_a", FuncType::new([], [ValType::I32]), |ctx: Caller, _params: &[Val], results: &mut [Val]| { results[0] = Val::from(ctx.data().a); Ok(()) }, ) .unwrap(); linker .func_new( "host", "set_a", FuncType::new([ValType::I32], []), |mut ctx: Caller, params: &[Val], _results: &mut [Val]| { ctx.data_mut().a = params[0].i32().unwrap(); Ok(()) }, ) .unwrap(); linker .func_wrap("host", "get_b", |ctx: Caller| ctx.data().b) .unwrap(); linker .func_wrap("host", "set_b", |mut ctx: Caller, value: i64| { ctx.data_mut().b = value }) .unwrap(); let a_init = 42; let b_init = 77; let mut store = >::new( &engine, HostState { a: a_init, b: b_init, }, ); let wasm = r#" (module (import "host" "get_a" (func $host_get_a (result i32))) (import "host" "set_a" (func $host_set_a (param i32))) (import "host" "get_b" (func $host_get_b (result i64))) (import "host" "set_b" (func $host_set_b (param i64))) (func (export "wasm_get_a") (result i32) (call $host_get_a) ) (func (export "wasm_set_a") (param $param i32) (call $host_set_a (local.get $param)) ) (func (export "wasm_get_b") (result i64) (call $host_get_b) ) (func (export "wasm_set_b") (param $param i64) (call $host_set_b (local.get $param)) ) ) "#; let module = Module::new(&engine, wasm).unwrap(); let instance = linker.instantiate_and_start(&mut store, &module).unwrap(); let wasm_get_a = instance .get_typed_func::<(), i32>(&store, "wasm_get_a") .unwrap(); let wasm_set_a = instance .get_typed_func::(&store, "wasm_set_a") .unwrap(); let wasm_get_b = instance .get_typed_func::<(), i64>(&store, "wasm_get_b") .unwrap(); let wasm_set_b = instance .get_typed_func::(&store, "wasm_set_b") .unwrap(); assert_eq!(wasm_get_a.call(&mut store, ()).unwrap(), a_init); wasm_set_a.call(&mut store, 100).unwrap(); assert_eq!(wasm_get_a.call(&mut store, ()).unwrap(), 100); assert_eq!(wasm_get_b.call(&mut store, ()).unwrap(), b_init); wasm_set_b.call(&mut store, 200).unwrap(); assert_eq!(wasm_get_b.call(&mut store, ()).unwrap(), 200); } #[test] fn populate_via_imports() { use crate::{Engine, Func, Linker, Memory, MemoryType, Module, Store}; let wasm = r#" (module (import "host" "hello" (func $host_hello (param i32) (result i32))) (import "env" "memory" (memory $mem 0 4096)) (func (export "hello") (result i32) (call $host_hello (i32.const 3)) (i32.const 2) i32.add ) )"#; let engine = Engine::default(); let mut linker = >::new(&engine); let mut store = Store::new(&engine, ()); let memory = Memory::new(&mut store, MemoryType::new(1, Some(4096))).unwrap(); let module = Module::new(&engine, wasm).unwrap(); linker.define("env", "memory", memory).unwrap(); let func = Func::new( &mut store, FuncType::new([ValType::I32], [ValType::I32]), |_caller, _params, _results| todo!(), ); linker.define("host", "hello", func).unwrap(); linker.instantiate_and_start(&mut store, &module).unwrap(); } } wasmi-1.1.0/src/memory/data.rs000064400000000000000000000063311046102023000143240ustar 00000000000000use crate::{ collections::arena::ArenaIndex, module::{self, PassiveDataSegmentBytes}, store::Stored, AsContextMut, }; use core::convert::AsRef; /// A raw index to a data segment entity. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct DataSegmentIdx(u32); impl ArenaIndex for DataSegmentIdx { fn into_usize(self) -> usize { self.0 as usize } fn from_usize(value: usize) -> Self { let value = value.try_into().unwrap_or_else(|error| { panic!("index {value} is out of bounds as data segment index: {error}") }); Self(value) } } /// A Wasm data segment reference. #[derive(Debug, Copy, Clone)] #[repr(transparent)] pub struct DataSegment(Stored); impl DataSegment { /// Creates a new linear memory reference. pub fn from_inner(stored: Stored) -> Self { Self(stored) } /// Returns the underlying stored representation. pub fn as_inner(&self) -> &Stored { &self.0 } /// Allocates a new active [`DataSegment`] on the store. /// /// # Errors /// /// If more than [`u32::MAX`] much linear memory is allocated. pub fn new_active(mut ctx: impl AsContextMut) -> Self { ctx.as_context_mut() .store .inner .alloc_data_segment(DataSegmentEntity::active()) } /// Allocates a new passive [`DataSegment`] on the store. /// /// # Errors /// /// If more than [`u32::MAX`] much linear memory is allocated. pub fn new_passive(mut ctx: impl AsContextMut, bytes: PassiveDataSegmentBytes) -> Self { ctx.as_context_mut() .store .inner .alloc_data_segment(DataSegmentEntity::passive(bytes)) } } /// An instantiated [`DataSegmentEntity`]. /// /// # Note /// /// With the `bulk-memory` Wasm proposal it is possible to interact /// with data segments at runtime. Therefore Wasm instances now have /// a need to have an instantiated representation of data segments. #[derive(Debug)] pub struct DataSegmentEntity { /// The underlying bytes of the instance data segment. /// /// # Note /// /// These bytes are just readable after instantiation. /// Using Wasm `data.drop` simply replaces the instance /// with an empty one. bytes: Option, } impl DataSegmentEntity { /// Creates a new active [`DataSegmentEntity`]. pub fn active() -> Self { Self { bytes: None } } /// Creates a new passive [`DataSegmentEntity`] with its `bytes`. pub fn passive(bytes: PassiveDataSegmentBytes) -> Self { Self { bytes: Some(bytes) } } } impl From<&'_ module::DataSegment> for DataSegmentEntity { fn from(segment: &'_ module::DataSegment) -> Self { Self { bytes: segment.passive_data_segment_bytes(), } } } impl DataSegmentEntity { /// Returns the bytes of the [`DataSegmentEntity`]. pub fn bytes(&self) -> &[u8] { self.bytes .as_ref() .map(AsRef::as_ref) .unwrap_or_else(|| &[]) } /// Drops the bytes of the [`DataSegmentEntity`]. pub fn drop_bytes(&mut self) { self.bytes = None; } } wasmi-1.1.0/src/memory/mod.rs000064400000000000000000000162361046102023000141770ustar 00000000000000mod data; mod ty; pub use self::{ data::{DataSegment, DataSegmentEntity, DataSegmentIdx}, ty::{MemoryType, MemoryTypeBuilder}, }; use super::{AsContext, AsContextMut, StoreContext, StoreContextMut, Stored}; use crate::{collections::arena::ArenaIndex, core::CoreMemory, errors::MemoryError, Error}; /// A raw index to a linear memory entity. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct MemoryIdx(u32); impl ArenaIndex for MemoryIdx { fn into_usize(self) -> usize { self.0 as usize } fn from_usize(value: usize) -> Self { let value = value.try_into().unwrap_or_else(|error| { panic!("index {value} is out of bounds as memory index: {error}") }); Self(value) } } /// A Wasm linear memory reference. #[derive(Debug, Copy, Clone)] #[repr(transparent)] pub struct Memory(Stored); impl Memory { /// Creates a new linear memory reference. pub(super) fn from_inner(stored: Stored) -> Self { Self(stored) } /// Returns the underlying stored representation. pub(super) fn as_inner(&self) -> &Stored { &self.0 } /// Creates a new linear memory to the store. /// /// # Errors /// /// If more than [`u32::MAX`] much linear memory is allocated. pub fn new(mut ctx: impl AsContextMut, ty: MemoryType) -> Result { let (inner, mut resource_limiter) = ctx .as_context_mut() .store .store_inner_and_resource_limiter_ref(); let entity = CoreMemory::new(ty.core, &mut resource_limiter)?; let memory = inner.alloc_memory(entity); Ok(memory) } /// Creates a new linear memory to the store. /// /// # Errors /// /// If more than [`u32::MAX`] much linear memory is allocated. /// - If static buffer is invalid pub fn new_static( mut ctx: impl AsContextMut, ty: MemoryType, buf: &'static mut [u8], ) -> Result { let (inner, mut resource_limiter) = ctx .as_context_mut() .store .store_inner_and_resource_limiter_ref(); let entity = CoreMemory::new_static(ty.core, &mut resource_limiter, buf)?; let memory = inner.alloc_memory(entity); Ok(memory) } /// Returns the memory type of the linear memory. /// /// # Panics /// /// Panics if `ctx` does not own this [`Memory`]. pub fn ty(&self, ctx: impl AsContext) -> MemoryType { let core = ctx.as_context().store.inner.resolve_memory(self).ty(); MemoryType { core } } /// Returns the dynamic [`MemoryType`] of the [`Memory`]. /// /// # Note /// /// This respects the current size of the [`Memory`] as /// its minimum size and is useful for import subtyping checks. /// /// # Panics /// /// Panics if `ctx` does not own this [`Memory`]. pub(crate) fn dynamic_ty(&self, ctx: impl AsContext) -> MemoryType { let core = ctx .as_context() .store .inner .resolve_memory(self) .dynamic_ty(); MemoryType { core } } /// Returns the size, in WebAssembly pages, of this Wasm linear memory. /// /// # Panics /// /// Panics if `ctx` does not own this [`Memory`]. pub fn size(&self, ctx: impl AsContext) -> u64 { ctx.as_context().store.inner.resolve_memory(self).size() } /// Grows the linear memory by the given amount of new pages. /// /// Returns the amount of pages before the operation upon success. /// /// # Errors /// /// If the linear memory would grow beyond its maximum limit after /// the grow operation. /// /// # Panics /// /// Panics if `ctx` does not own this [`Memory`]. pub fn grow(&self, mut ctx: impl AsContextMut, additional: u64) -> Result { let (inner, mut limiter) = ctx .as_context_mut() .store .store_inner_and_resource_limiter_ref(); inner .resolve_memory_mut(self) .grow(additional, None, &mut limiter) .map_err(|_| MemoryError::OutOfBoundsGrowth) } /// Returns a shared slice to the bytes underlying the [`Memory`]. /// /// # Panics /// /// Panics if `ctx` does not own this [`Memory`]. pub fn data<'a, T: 'a>(&self, ctx: impl Into>) -> &'a [u8] { ctx.into().store.inner.resolve_memory(self).data() } /// Returns an exclusive slice to the bytes underlying the [`Memory`]. /// /// # Panics /// /// Panics if `ctx` does not own this [`Memory`]. pub fn data_mut<'a, T: 'a>(&self, ctx: impl Into>) -> &'a mut [u8] { ctx.into().store.inner.resolve_memory_mut(self).data_mut() } /// Returns an exclusive slice to the bytes underlying the [`Memory`], and an exclusive /// reference to the user provided state. /// /// # Panics /// /// Panics if `ctx` does not own this [`Memory`]. pub fn data_and_store_mut<'a, T: 'a>( &self, ctx: impl Into>, ) -> (&'a mut [u8], &'a mut T) { let (memory, store) = ctx.into().store.resolve_memory_and_state_mut(self); (memory.data_mut(), store) } /// Returns the base pointer, in the host’s address space, that the [`Memory`] is located at. /// /// # Panics /// /// Panics if `ctx` does not own this [`Memory`]. pub fn data_ptr(&self, ctx: impl AsContext) -> *mut u8 { ctx.as_context().store.inner.resolve_memory(self).data_ptr() } /// Returns the byte length of this [`Memory`]. /// /// The returned value will be a multiple of the wasm page size, 64k. /// /// # Panics /// /// Panics if `ctx` does not own this [`Memory`]. pub fn data_size(&self, ctx: impl AsContext) -> usize { ctx.as_context() .store .inner .resolve_memory(self) .data_size() } /// Reads `n` bytes from `memory[offset..offset+n]` into `buffer` /// where `n` is the length of `buffer`. /// /// # Errors /// /// If this operation accesses out of bounds linear memory. /// /// # Panics /// /// Panics if `ctx` does not own this [`Memory`]. pub fn read( &self, ctx: impl AsContext, offset: usize, buffer: &mut [u8], ) -> Result<(), MemoryError> { ctx.as_context() .store .inner .resolve_memory(self) .read(offset, buffer) } /// Writes `n` bytes to `memory[offset..offset+n]` from `buffer` /// where `n` if the length of `buffer`. /// /// # Errors /// /// If this operation accesses out of bounds linear memory. /// /// # Panics /// /// Panics if `ctx` does not own this [`Memory`]. pub fn write( &self, mut ctx: impl AsContextMut, offset: usize, buffer: &[u8], ) -> Result<(), MemoryError> { ctx.as_context_mut() .store .inner .resolve_memory_mut(self) .write(offset, buffer) } } wasmi-1.1.0/src/memory/ty.rs000064400000000000000000000120551046102023000140470ustar 00000000000000use crate::{ core::{CoreMemoryType, CoreMemoryTypeBuilder, IndexType}, errors::MemoryError, }; /// A Wasm memory descriptor. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct MemoryType { pub(crate) core: CoreMemoryType, } impl MemoryType { /// Creates a new memory type with minimum and optional maximum pages. /// /// # Panics /// /// - If the `minimum` pages exceeds the `maximum` pages. /// - If the `minimum` or `maximum` pages are out of bounds. pub fn new(minimum: u32, maximum: Option) -> Self { let mut b = Self::builder(); b.min(u64::from(minimum)); b.max(maximum.map(u64::from)); b.build().unwrap() } /// Creates a new 64-bit memory type with minimum and optional maximum pages. /// /// # Panics /// /// - If the `minimum` pages exceeds the `maximum` pages. /// - If the `minimum` or `maximum` pages are out of bounds. /// /// 64-bit memories are part of the [Wasm `memory64` proposal]. /// /// [Wasm `memory64` proposal]: https://github.com/WebAssembly/memory64 pub fn new64(minimum: u64, maximum: Option) -> Self { let mut b = Self::builder(); b.memory64(true); b.min(minimum); b.max(maximum); b.build().unwrap() } /// Returns a [`MemoryTypeBuilder`] to incrementally construct a [`MemoryType`]. pub fn builder() -> MemoryTypeBuilder { MemoryTypeBuilder::default() } /// Returns `true` if this is a 64-bit [`MemoryType`]. /// /// 64-bit memories are part of the Wasm `memory64` proposal. pub fn is_64(&self) -> bool { self.index_ty().is_64() } /// Returns the [`IndexType`] used by the [`MemoryType`]. pub(crate) fn index_ty(&self) -> IndexType { self.core.index_ty() } /// Returns the minimum pages of the memory type. pub fn minimum(self) -> u64 { self.core.minimum() } /// Returns the maximum pages of the memory type. /// /// Returns `None` if there is no limit set. pub fn maximum(self) -> Option { self.core.maximum() } /// Returns the page size of the [`MemoryType`] in log2(bytes). pub(crate) fn page_size_log2(self) -> u8 { self.core.page_size_log2() } /// Returns `true` if the [`MemoryType`] is a subtype of the `other` [`MemoryType`]. /// /// # Note /// /// This implements the [subtyping rules] according to the WebAssembly spec. /// /// [import subtyping]: /// https://webassembly.github.io/spec/core/valid/types.html#import-subtyping pub(crate) fn is_subtype_of(&self, other: &Self) -> bool { self.core.is_subtype_of(&other.core) } } /// A [`MemoryType`] builder. #[derive(Default)] pub struct MemoryTypeBuilder { core: CoreMemoryTypeBuilder, } impl MemoryTypeBuilder { /// Create a new builder for a [`MemoryType`]` with the default settings: /// /// - The minimum memory size is 0 pages. /// - The maximum memory size is unspecified. /// - The page size is 64KiB. pub fn new() -> Self { Self::default() } /// Set whether this is a 64-bit memory type or not. /// /// By default a memory is a 32-bit, a.k.a. `false`. /// /// 64-bit memories are part of the [Wasm `memory64` proposal]. /// /// [Wasm `memory64` proposal]: https://github.com/WebAssembly/memory64 pub fn memory64(&mut self, memory64: bool) -> &mut Self { self.core.memory64(memory64); self } /// Sets the minimum number of pages the built [`MemoryType`] supports. /// /// The default minimum is `0`. pub fn min(&mut self, minimum: u64) -> &mut Self { self.core.min(minimum); self } /// Sets the optional maximum number of pages the built [`MemoryType`] supports. /// /// A value of `None` means that there is no maximum number of pages. /// /// The default maximum is `None`. pub fn max(&mut self, maximum: Option) -> &mut Self { self.core.max(maximum); self } /// Sets the log2 page size in bytes, for the built [`MemoryType`]. /// /// The default value is 16, which results in the default Wasm page size of 64KiB (aka 2^16 or 65536). /// /// Currently, the only allowed values are 0 (page size of 1) or 16 (the default). /// Future Wasm proposal extensions might change this limitation. /// /// Non-default page sizes are part of the [`custom-page-sizes proposal`] /// for WebAssembly which is not fully standardized yet. /// /// [`custom-page-sizes proposal`]: https://github.com/WebAssembly/custom-page-sizes pub fn page_size_log2(&mut self, page_size_log2: u8) -> &mut Self { self.core.page_size_log2(page_size_log2); self } /// Finalize the construction of the [`MemoryType`]. /// /// # Errors /// /// If the chosen configuration for the constructed [`MemoryType`] is invalid. pub fn build(self) -> Result { let core = self.core.build()?; Ok(MemoryType { core }) } } wasmi-1.1.0/src/module/builder.rs000064400000000000000000000337151046102023000150240ustar 00000000000000use super::{ data::DataSegmentsBuilder, export::ExternIdx, import::FuncTypeIdx, ConstExpr, CustomSectionsBuilder, DataSegments, ElementSegment, ExternTypeIdx, FuncIdx, Global, Import, ImportName, Imported, Module, ModuleHeader, ModuleHeaderInner, ModuleImports, ModuleInner, }; use crate::{ collections::Map, engine::{DedupFuncType, EngineFuncSpan}, Engine, Error, FuncType, GlobalType, MemoryType, TableType, }; use alloc::{boxed::Box, sync::Arc, vec::Vec}; /// A builder for a WebAssembly [`Module`]. #[derive(Debug)] pub struct ModuleBuilder { engine: Engine, pub header: Option, pub func_types: Vec, pub imports: ModuleImportsBuilder, pub funcs: Vec, pub tables: Vec, pub memories: Vec, pub globals: Vec, pub globals_init: Vec, pub exports: Map, ExternIdx>, pub start: Option, pub engine_funcs: EngineFuncSpan, pub element_segments: Box<[ElementSegment]>, pub data_segments: DataSegmentsBuilder, pub custom_sections: CustomSectionsBuilder, } impl ModuleBuilder { /// Creates a new [`ModuleBuilder`] for the given [`Engine`]. pub fn new(engine: &Engine) -> Self { Self { engine: engine.clone(), header: None, func_types: Vec::new(), imports: ModuleImportsBuilder::default(), funcs: Vec::new(), tables: Vec::new(), memories: Vec::new(), globals: Vec::new(), globals_init: Vec::new(), exports: Map::new(), start: None, engine_funcs: EngineFuncSpan::default(), element_segments: Box::from([]), data_segments: DataSegments::build(), custom_sections: CustomSectionsBuilder::default(), } } /// Finishes construction of [`ModuleHeader`]. pub fn finish(mut self) -> Module { let header = self.header(); Module { inner: Arc::new(ModuleInner { engine: self.engine, data_segments: self.data_segments.finish(), custom_sections: self.custom_sections.finish(), header, }), } } pub fn header(&mut self) -> ModuleHeader { use core::mem::take; if let Some(header) = self.header.as_ref() { return header.clone(); } let func_types: Box<[DedupFuncType]> = take(&mut self.func_types).into(); let header = ModuleHeader { inner: Arc::new(ModuleHeaderInner { engine: self.engine.weak(), func_types: func_types.into(), imports: take(&mut self.imports).finish(), funcs: take(&mut self.funcs).into(), tables: take(&mut self.tables).into(), memories: take(&mut self.memories).into(), globals: take(&mut self.globals).into(), globals_init: take(&mut self.globals_init).into(), exports: take(&mut self.exports), start: self.start, engine_funcs: self.engine_funcs, element_segments: take(&mut self.element_segments), }), }; self.header = Some(header.clone()); header } /// Returns the number of imported functions. pub fn len_funcs_imports(&self) -> usize { if let Some(header) = self.header.as_ref() { return header.inner.imports.len_funcs(); } self.imports.funcs.len() } } /// The import names of the [`Module`] imports. #[derive(Debug, Default)] pub struct ModuleImportsBuilder { pub funcs: Vec, pub tables: Vec, pub memories: Vec, pub globals: Vec, } impl ModuleImportsBuilder { /// Finishes construction of [`ModuleImports`]. pub fn finish(self) -> ModuleImports { let len_funcs = self.funcs.len(); let len_globals = self.globals.len(); let len_memories = self.memories.len(); let len_tables = self.tables.len(); let funcs = self.funcs.into_iter().map(Imported::Func); let tables = self.tables.into_iter().map(Imported::Table); let memories = self.memories.into_iter().map(Imported::Memory); let globals = self.globals.into_iter().map(Imported::Global); let items = funcs .chain(tables) .chain(memories) .chain(globals) .collect::>(); ModuleImports { items, len_funcs, len_globals, len_memories, len_tables, } } } impl ModuleBuilder { /// Pushes the given function types to the [`Module`] under construction. /// /// # Errors /// /// If a function type fails to validate. /// /// # Panics /// /// If this function has already been called on the same [`ModuleBuilder`]. pub fn push_func_types(&mut self, func_types: T) -> Result<(), Error> where T: IntoIterator>, ::IntoIter: ExactSizeIterator, { assert!( self.func_types.is_empty(), "tried to initialize module function types twice" ); let func_types = func_types.into_iter(); // Note: we use `reserve_exact` instead of `reserve` because this // is the last extension of the vector during the build process // and optimizes conversion to boxed slice. self.func_types.reserve_exact(func_types.len()); for func_type in func_types { let func_type = func_type?; let dedup = self.engine.alloc_func_type(func_type); self.func_types.push(dedup) } Ok(()) } /// Pushes the given imports to the [`Module`] under construction. /// /// # Errors /// /// If an import fails to validate. /// /// # Panics /// /// If this function has already been called on the same [`ModuleBuilder`]. pub fn push_imports(&mut self, imports: T) -> Result<(), Error> where T: IntoIterator>, { for import in imports { let import = import?; let (name, kind) = import.into_name_and_type(); match kind { ExternTypeIdx::Func(func_type_idx) => { self.imports.funcs.push(name); let func_type = self.func_types[func_type_idx.into_u32() as usize]; self.funcs.push(func_type); } ExternTypeIdx::Table(table_type) => { self.imports.tables.push(name); self.tables.push(table_type); } ExternTypeIdx::Memory(memory_type) => { self.imports.memories.push(name); self.memories.push(memory_type); } ExternTypeIdx::Global(global_type) => { self.imports.globals.push(name); self.globals.push(global_type); } } } Ok(()) } /// Pushes the given function declarations to the [`Module`] under construction. /// /// # Errors /// /// If a function declaration fails to validate. /// /// # Panics /// /// If this function has already been called on the same [`ModuleBuilder`]. pub fn push_funcs(&mut self, funcs: T) -> Result<(), Error> where T: IntoIterator>, ::IntoIter: ExactSizeIterator, { assert_eq!( self.funcs.len(), self.imports.funcs.len(), "tried to initialize module function declarations twice" ); let funcs = funcs.into_iter(); // Note: we use `reserve_exact` instead of `reserve` because this // is the last extension of the vector during the build process // and optimizes conversion to boxed slice. self.funcs.reserve_exact(funcs.len()); self.engine_funcs = self.engine.alloc_funcs(funcs.len()); for func in funcs { let func_type_idx = func?; let func_type = self.func_types[func_type_idx.into_u32() as usize]; self.funcs.push(func_type); } Ok(()) } /// Pushes the given table types to the [`Module`] under construction. /// /// # Errors /// /// If a table declaration fails to validate. /// /// # Panics /// /// If this function has already been called on the same [`ModuleBuilder`]. pub fn push_tables(&mut self, tables: T) -> Result<(), Error> where T: IntoIterator>, ::IntoIter: ExactSizeIterator, { assert_eq!( self.tables.len(), self.imports.tables.len(), "tried to initialize module table declarations twice" ); let tables = tables.into_iter(); // Note: we use `reserve_exact` instead of `reserve` because this // is the last extension of the vector during the build process // and optimizes conversion to boxed slice. self.tables.reserve_exact(tables.len()); for table in tables { let table = table?; self.tables.push(table); } Ok(()) } /// Pushes the given linear memory types to the [`Module`] under construction. /// /// # Errors /// /// If a linear memory declaration fails to validate. /// /// # Panics /// /// If this function has already been called on the same [`ModuleBuilder`]. pub fn push_memories(&mut self, memories: T) -> Result<(), Error> where T: IntoIterator>, ::IntoIter: ExactSizeIterator, { assert_eq!( self.memories.len(), self.imports.memories.len(), "tried to initialize module linear memory declarations twice" ); let memories = memories.into_iter(); // Note: we use `reserve_exact` instead of `reserve` because this // is the last extension of the vector during the build process // and optimizes conversion to boxed slice. self.memories.reserve_exact(memories.len()); for memory in memories { let memory = memory?; self.memories.push(memory); } Ok(()) } /// Pushes the given global variables to the [`Module`] under construction. /// /// # Errors /// /// If a global variable declaration fails to validate. /// /// # Panics /// /// If this function has already been called on the same [`ModuleBuilder`]. pub fn push_globals(&mut self, globals: T) -> Result<(), Error> where T: IntoIterator>, ::IntoIter: ExactSizeIterator, { assert_eq!( self.globals.len(), self.imports.globals.len(), "tried to initialize module global variable declarations twice" ); let globals = globals.into_iter(); // Note: we use `reserve_exact` instead of `reserve` because this // is the last extension of the vector during the build process // and optimizes conversion to boxed slice. self.globals.reserve_exact(globals.len()); for global in globals { let global = global?; let (global_decl, global_init) = global.into_type_and_init(); self.globals.push(global_decl); self.globals_init.push(global_init); } Ok(()) } /// Pushes the given exports to the [`Module`] under construction. /// /// # Errors /// /// If an export declaration fails to validate. /// /// # Panics /// /// If this function has already been called on the same [`ModuleBuilder`]. pub fn push_exports(&mut self, exports: T) -> Result<(), Error> where T: IntoIterator, ExternIdx), Error>>, { assert!( self.exports.is_empty(), "tried to initialize module export declarations twice" ); self.exports = exports.into_iter().collect::, _>>()?; Ok(()) } /// Sets the start function of the [`Module`] to the given index. /// /// # Panics /// /// If this function has already been called on the same [`ModuleBuilder`]. pub fn set_start(&mut self, start: FuncIdx) { if let Some(old_start) = &self.start { panic!("encountered multiple start functions: {old_start:?}, {start:?}") } self.start = Some(start); } /// Pushes the given table elements to the [`Module`] under construction. /// /// # Errors /// /// If any of the table elements fail to validate. /// /// # Panics /// /// If this function has already been called on the same [`ModuleBuilder`]. pub fn push_element_segments(&mut self, elements: T) -> Result<(), Error> where T: IntoIterator>, ::IntoIter: ExactSizeIterator, { assert!( self.element_segments.is_empty(), "tried to initialize module export declarations twice" ); self.element_segments = elements.into_iter().collect::, _>>()?; Ok(()) } } impl ModuleBuilder { /// Reserve space for at least `additional` new data segments. pub fn reserve_data_segments(&mut self, additional: usize) { self.data_segments.reserve(additional); } /// Push another parsed data segment to the [`ModuleBuilder`]. pub fn push_data_segment(&mut self, data: wasmparser::Data) -> Result<(), Error> { self.data_segments.push_data_segment(data) } } wasmi-1.1.0/src/module/custom_section.rs000064400000000000000000000113241046102023000164240ustar 00000000000000use alloc::vec::Vec; use core::{slice, str}; /// Wasm custom sections. #[derive(Default, Debug)] pub struct CustomSections { inner: CustomSectionsInner, } impl CustomSections { /// Returns an iterator over the [`CustomSection`]s stored in `self`. #[inline] pub fn iter(&self) -> CustomSectionsIter<'_> { self.inner.iter() } } /// A builder for [`CustomSections`]. #[derive(Default, Debug)] pub struct CustomSectionsBuilder { inner: CustomSectionsInner, } impl CustomSectionsBuilder { /// Pushes a new custom section segment to the [`CustomSectionsBuilder`]. #[inline] pub fn push(&mut self, name: &str, data: &[u8]) { self.inner.push(name, data); } /// Finalize construction of the [`CustomSections`]. #[inline] pub fn finish(self) -> CustomSections { CustomSections { inner: self.inner } } } /// Internal representation of [`CustomSections`]. #[derive(Debug, Default)] pub struct CustomSectionsInner { /// The name and data lengths of each Wasm custom section. items: Vec, /// The combined name and data of all Wasm custom sections. names_and_data: Vec, } /// Internal representation of a Wasm [`CustomSection`]. #[derive(Debug, Copy, Clone)] pub struct CustomSectionInner { /// The length in bytes of the Wasm custom section name. len_name: usize, /// The length in bytes of the Wasm custom section data. len_data: usize, } impl CustomSectionsInner { /// Pushes a new custom section segment to the [`CustomSectionsBuilder`]. #[inline] pub fn push(&mut self, name: &str, data: &[u8]) { let name_bytes = name.as_bytes(); self.names_and_data.extend_from_slice(name_bytes); self.names_and_data.extend_from_slice(data); self.items.push(CustomSectionInner { len_name: name_bytes.len(), len_data: data.len(), }) } /// Returns an iterator over the [`CustomSection`]s stored in `self`. #[inline] pub fn iter(&self) -> CustomSectionsIter<'_> { CustomSectionsIter { items: self.items.iter(), names_and_data: &self.names_and_data[..], } } } /// A Wasm custom section. #[derive(Debug)] pub struct CustomSection<'a> { /// The name of the custom section. name: &'a str, /// The undecoded data of the custom section. data: &'a [u8], } impl<'a> CustomSection<'a> { /// Returns the name or identifier of the [`CustomSection`]. #[inline] pub fn name(&self) -> &'a str { self.name } /// Returns a shared reference to the data of the [`CustomSection`]. #[inline] pub fn data(&self) -> &'a [u8] { self.data } } /// An iterator over the custom sections of a Wasm module. #[derive(Debug)] pub struct CustomSectionsIter<'a> { items: slice::Iter<'a, CustomSectionInner>, names_and_data: &'a [u8], } impl<'a> Iterator for CustomSectionsIter<'a> { type Item = CustomSection<'a>; #[inline] fn size_hint(&self) -> (usize, Option) { self.items.size_hint() } #[inline] fn next(&mut self) -> Option { let item = self.items.next()?; let names_and_data = self.names_and_data; let (name, names_and_data) = names_and_data.split_at(item.len_name); let (data, names_and_data) = names_and_data.split_at(item.len_data); self.names_and_data = names_and_data; // Safety: We encoded this part of the data buffer from the bytes of a string previously. let name = unsafe { str::from_utf8_unchecked(name) }; Some(CustomSection { name, data }) } } #[cfg(test)] mod tests { use super::*; #[test] fn it_works() { let mut builder = CustomSectionsBuilder::default(); builder.push("A", b"first"); builder.push("B", b"second"); builder.push("C", b"third"); builder.push("", b"fourth"); // empty name builder.push("E", &[]); // empty data let custom_sections = builder.finish(); let mut iter = custom_sections.iter(); assert_eq!( iter.next().map(|s| (s.name(), s.data())), Some(("A", &b"first"[..])) ); assert_eq!( iter.next().map(|s| (s.name(), s.data())), Some(("B", &b"second"[..])) ); assert_eq!( iter.next().map(|s| (s.name(), s.data())), Some(("C", &b"third"[..])) ); assert_eq!( iter.next().map(|s| (s.name(), s.data())), Some(("", &b"fourth"[..])) ); assert_eq!( iter.next().map(|s| (s.name(), s.data())), Some(("E", &b""[..])) ); assert_eq!(iter.next().map(|s| (s.name(), s.data())), None); } } wasmi-1.1.0/src/module/data.rs000064400000000000000000000154221046102023000143020ustar 00000000000000use super::{ConstExpr, MemoryIdx}; use crate::Error; use alloc::{boxed::Box, sync::Arc, vec::Vec}; use core::slice; /// A Wasm [`Module`] data segment. /// /// [`Module`]: [`super::Module`] #[derive(Debug)] pub struct DataSegment { inner: DataSegmentInner, } /// The inner structure of a [`DataSegment`]. #[derive(Debug)] pub enum DataSegmentInner { /// An active data segment that is initialized upon Wasm module instantiation. Active(ActiveDataSegment), /// A passive data segment that can be used by some Wasm bulk instructions. Passive { /// The bytes of the passive data segment. bytes: PassiveDataSegmentBytes, }, } /// An active data segment that is initialized upon Wasm module instantiation. #[derive(Debug)] pub struct ActiveDataSegment { /// The linear memory that is to be initialized with this active segment. memory_index: MemoryIdx, /// The offset at which the data segment is initialized. offset: ConstExpr, /// Number of bytes of the active data segment. len: u32, } impl ActiveDataSegment { /// Returns the Wasm module memory index that is to be initialized. pub fn memory_index(&self) -> MemoryIdx { self.memory_index } /// Returns the offset expression of the [`ActiveDataSegment`]. pub fn offset(&self) -> &ConstExpr { &self.offset } /// Returns the number of bytes of the [`ActiveDataSegment`] as `usize`. pub fn len(&self) -> usize { self.len as usize } } /// The bytes of the passive data segment. #[derive(Debug, Clone)] pub struct PassiveDataSegmentBytes { bytes: Arc<[u8]>, } impl AsRef<[u8]> for PassiveDataSegmentBytes { fn as_ref(&self) -> &[u8] { &self.bytes[..] } } #[test] fn size_of_data_segment() { assert!(core::mem::size_of::() <= 32); assert!(core::mem::size_of::() <= 32); } impl DataSegment { /// Returns the bytes of the [`DataSegment`] if passive, otherwise returns `None`. pub fn passive_data_segment_bytes(&self) -> Option { match &self.inner { DataSegmentInner::Active { .. } => None, DataSegmentInner::Passive { bytes } => Some(bytes.clone()), } } } /// Stores all data segments and their associated data. #[derive(Debug)] pub struct DataSegments { /// All data segments. segments: Box<[DataSegment]>, /// All bytes from all active data segments. /// /// # Note /// /// We deliberately do not use `Box<[u8]>` here because it is not possible /// to properly pre-reserve space for the bytes and thus finishing construction /// of the [`DataSegments`] would highly likely reallocate and mass-copy /// which we prevent by simply using a `Vec` instead. bytes: Vec, } impl DataSegments { /// Creates a new [`DataSegmentsBuilder`]. pub fn build() -> DataSegmentsBuilder { DataSegmentsBuilder { segments: Vec::new(), bytes: Vec::new(), } } } /// Builds up a [`DataSegments`] instance. #[derive(Debug)] pub struct DataSegmentsBuilder { /// All active or passive data segments built-up so far. segments: Vec, /// The bytes of all active data segments. bytes: Vec, } impl DataSegmentsBuilder { /// Reserves space for at least `additional` new [`DataSegments`]. pub fn reserve(&mut self, count: usize) { assert!( self.segments.capacity() == 0, "must not reserve multiple times" ); self.segments.reserve(count); } /// Pushes another [`DataSegment`] to the [`DataSegmentsBuilder`]. /// /// # Panics /// /// If an active data segment has too many bytes. pub fn push_data_segment(&mut self, segment: wasmparser::Data) -> Result<(), Error> { match segment.kind { wasmparser::DataKind::Passive => { self.segments.push(DataSegment { inner: DataSegmentInner::Passive { bytes: PassiveDataSegmentBytes { bytes: segment.data.into(), }, }, }); } wasmparser::DataKind::Active { memory_index, offset_expr, } => { let memory_index = MemoryIdx::from(memory_index); let offset = ConstExpr::new(offset_expr); let len = u32::try_from(segment.data.len()).unwrap_or_else(|_x| { panic!("data segment has too many bytes: {}", segment.data.len()) }); self.bytes.extend_from_slice(segment.data); self.segments.push(DataSegment { inner: DataSegmentInner::Active(ActiveDataSegment { memory_index, offset, len, }), }); } } Ok(()) } pub fn finish(self) -> DataSegments { DataSegments { segments: self.segments.into(), bytes: self.bytes, } } } impl<'a> IntoIterator for &'a DataSegments { type Item = InitDataSegment<'a>; type IntoIter = InitDataSegmentIter<'a>; fn into_iter(self) -> Self::IntoIter { InitDataSegmentIter { segments: self.segments.iter(), bytes: &self.bytes[..], } } } /// Iterator over the [`DataSegment`]s and their associated bytes. #[derive(Debug)] pub struct InitDataSegmentIter<'a> { segments: slice::Iter<'a, DataSegment>, bytes: &'a [u8], } impl<'a> Iterator for InitDataSegmentIter<'a> { type Item = InitDataSegment<'a>; fn next(&mut self) -> Option { let segment = self.segments.next()?; match &segment.inner { DataSegmentInner::Active(segment) => { let (bytes, rest) = self.bytes.split_at(segment.len()); self.bytes = rest; Some(InitDataSegment::Active { memory_index: segment.memory_index(), offset: segment.offset(), bytes, }) } DataSegmentInner::Passive { bytes } => Some(InitDataSegment::Passive { bytes: bytes.clone(), }), } } } /// Iterated-over [`DataSegment`] when instantiating a [`Module`]. /// /// [`Module`]: crate::Module pub enum InitDataSegment<'a> { Active { /// The linear memory that is to be initialized with this active segment. memory_index: MemoryIdx, /// The offset at which the data segment is initialized. offset: &'a ConstExpr, /// The bytes of the active data segment. bytes: &'a [u8], }, Passive { bytes: PassiveDataSegmentBytes, }, } wasmi-1.1.0/src/module/element.rs000064400000000000000000000074361046102023000150300ustar 00000000000000use super::{ConstExpr, TableIdx}; use crate::ValType; use alloc::boxed::Box; /// A table element segment within a [`Module`]. /// /// [`Module`]: [`super::Module`] #[derive(Debug)] pub struct ElementSegment { /// The kind of the [`ElementSegment`]. kind: ElementSegmentKind, /// The type of elements of the [`ElementSegment`]. ty: ValType, /// The items of the [`ElementSegment`]. items: Box<[ConstExpr]>, } /// The kind of a Wasm [`ElementSegment`]. #[derive(Debug)] pub enum ElementSegmentKind { /// A passive [`ElementSegment`] from the `bulk-memory` Wasm proposal. Passive, /// An active [`ElementSegment`]. Active(ActiveElementSegment), /// A declared [`ElementSegment`] from the `reference-types` Wasm proposal. Declared, } /// An active Wasm element segment. #[derive(Debug)] pub struct ActiveElementSegment { /// The index of the Wasm table that is to be initialized. table_index: TableIdx, /// The offset where the Wasm table is to be initialized. offset: ConstExpr, } impl ActiveElementSegment { /// Returns the Wasm module table index that is to be initialized. pub fn table_index(&self) -> TableIdx { self.table_index } /// Returns the offset expression of the [`ActiveElementSegment`]. pub fn offset(&self) -> &ConstExpr { &self.offset } } impl From> for ElementSegmentKind { fn from(element_kind: wasmparser::ElementKind<'_>) -> Self { match element_kind { wasmparser::ElementKind::Active { table_index, offset_expr, } => { let table_index = TableIdx::from(table_index.unwrap_or(0)); let offset = ConstExpr::new(offset_expr); Self::Active(ActiveElementSegment { table_index, offset, }) } wasmparser::ElementKind::Passive => Self::Passive, wasmparser::ElementKind::Declared => Self::Declared, } } } impl From> for ElementSegment { fn from(element: wasmparser::Element<'_>) -> Self { let kind = ElementSegmentKind::from(element.kind); let (items, ty) = match element.items { wasmparser::ElementItems::Functions(items) => { let items = items .into_iter() .map(|item| { item.unwrap_or_else(|error| panic!("failed to parse element item: {error}")) }) .map(ConstExpr::new_funcref) .collect::>(); (items, ValType::FuncRef) } wasmparser::ElementItems::Expressions(ref_ty, items) => { let ty = match ref_ty { ty if ty.is_func_ref() => ValType::FuncRef, ty if ty.is_extern_ref() => ValType::ExternRef, _ => panic!("unsupported Wasm reference type"), }; let items = items .into_iter() .map(|item| { item.unwrap_or_else(|error| panic!("failed to parse element item: {error}")) }) .map(ConstExpr::new) .collect::>(); (items, ty) } }; Self { kind, ty, items } } } impl ElementSegment { /// Returns the offset expression of the [`ElementSegment`]. pub fn kind(&self) -> &ElementSegmentKind { &self.kind } /// Returns the [`ValType`] of the [`ElementSegment`]. pub fn ty(&self) -> ValType { self.ty } /// Returns the element items of the [`ElementSegment`]. pub fn items(&self) -> &[ConstExpr] { &self.items[..] } } wasmi-1.1.0/src/module/export.rs000064400000000000000000000101421046102023000147040ustar 00000000000000use super::GlobalIdx; use crate::{collections::map::Iter as MapIter, Error, ExternType, Module}; use alloc::boxed::Box; /// The index of a function declaration within a [`Module`]. /// /// [`Module`]: [`super::Module`] #[derive(Debug, Copy, Clone)] pub struct FuncIdx(u32); impl From for FuncIdx { fn from(index: u32) -> Self { Self(index) } } impl FuncIdx { /// Returns the [`FuncIdx`] as `u32`. pub fn into_u32(self) -> u32 { self.0 } } /// The index of a table declaration within a [`Module`]. /// /// [`Module`]: [`super::Module`] #[derive(Debug, Copy, Clone)] pub struct TableIdx(u32); impl From for TableIdx { fn from(index: u32) -> Self { Self(index) } } impl TableIdx { /// Returns the [`TableIdx`] as `u32`. pub fn into_u32(self) -> u32 { self.0 } } /// The index of a linear memory declaration within a [`Module`]. /// /// [`Module`]: [`super::Module`] #[derive(Debug, Copy, Clone)] pub struct MemoryIdx(u32); impl From for MemoryIdx { fn from(index: u32) -> Self { Self(index) } } impl MemoryIdx { /// Returns the [`MemoryIdx`] as `u32`. pub fn into_u32(self) -> u32 { self.0 } } /// An external item of an [`ExportType`] definition within a [`Module`]. /// /// [`Module`]: [`crate::Module`] #[derive(Debug, Copy, Clone)] pub enum ExternIdx { /// An exported function and its index within the [`Module`]. /// /// [`Module`]: [`super::Module`] Func(FuncIdx), /// An exported table and its index within the [`Module`]. /// /// [`Module`]: [`super::Module`] Table(TableIdx), /// An exported linear memory and its index within the [`Module`]. /// /// [`Module`]: [`super::Module`] Memory(MemoryIdx), /// An exported global variable and its index within the [`Module`]. /// /// [`Module`]: [`super::Module`] Global(GlobalIdx), } impl ExternIdx { /// Create a new [`ExternIdx`] from the given [`wasmparser::ExternalKind`] and `index`. /// /// # Errors /// /// If an unsupported external definition is encountered. pub fn new(kind: wasmparser::ExternalKind, index: u32) -> Result { match kind { wasmparser::ExternalKind::Func => Ok(ExternIdx::Func(FuncIdx(index))), wasmparser::ExternalKind::Table => Ok(ExternIdx::Table(TableIdx(index))), wasmparser::ExternalKind::Memory => Ok(ExternIdx::Memory(MemoryIdx(index))), wasmparser::ExternalKind::Global => Ok(ExternIdx::Global(GlobalIdx::from(index))), wasmparser::ExternalKind::Tag => { panic!("wasmi does not support the `exception-handling` Wasm proposal") } } } } /// An iterator over the exports of a [`Module`]. /// /// [`Module`]: [`super::Module`] #[derive(Debug)] pub struct ModuleExportsIter<'module> { exports: MapIter<'module, Box, ExternIdx>, module: &'module Module, } /// A descriptor for an exported WebAssembly value of a [`Module`]. /// /// This type is primarily accessed from the [`Module::exports`] method and describes /// what names are exported from a Wasm [`Module`] and the type of the item that is exported. #[derive(Debug)] pub struct ExportType<'module> { name: &'module str, ty: ExternType, } impl<'module> ExportType<'module> { /// Returns the name by which the export is known. pub fn name(&self) -> &'module str { self.name } /// Returns the type of the exported item. pub fn ty(&self) -> &ExternType { &self.ty } } impl<'module> ModuleExportsIter<'module> { /// Creates a new [`ModuleExportsIter`] from the given [`Module`]. pub(super) fn new(module: &'module Module) -> Self { Self { exports: module.module_header().exports.iter(), module, } } } impl<'module> Iterator for ModuleExportsIter<'module> { type Item = ExportType<'module>; fn next(&mut self) -> Option { self.exports.next().map(|(name, idx)| { let ty = self.module.get_extern_type(*idx); ExportType { name, ty } }) } } wasmi-1.1.0/src/module/global.rs000064400000000000000000000025471046102023000146350ustar 00000000000000use super::{utils::FromWasmparser as _, ConstExpr}; use crate::GlobalType; /// The index of a global variable within a [`Module`]. /// /// [`Module`]: [`super::Module`] #[derive(Debug, Copy, Clone)] pub struct GlobalIdx(u32); impl From for GlobalIdx { fn from(index: u32) -> Self { Self(index) } } impl GlobalIdx { /// Returns the [`GlobalIdx`] as `u32`. pub fn into_u32(self) -> u32 { self.0 } } /// A global variable definition within a [`Module`]. /// /// [`Module`]: [`super::Module`] #[derive(Debug)] pub struct Global { /// The type of the global variable. global_type: GlobalType, /// The initial value of the global variable. /// /// # Note /// /// This is represented by a so called initializer expression /// that is run at module instantiation time. init_expr: ConstExpr, } impl From> for Global { fn from(global: wasmparser::Global<'_>) -> Self { let global_type = GlobalType::from_wasmparser(global.ty); let init_expr = ConstExpr::new(global.init_expr); Self { global_type, init_expr, } } } impl Global { /// Splits the [`Global`] into its global type and its global initializer. pub fn into_type_and_init(self) -> (GlobalType, ConstExpr) { (self.global_type, self.init_expr) } } wasmi-1.1.0/src/module/import.rs000064400000000000000000000071101046102023000146760ustar 00000000000000use super::utils::FromWasmparser as _; use crate::{GlobalType, MemoryType, TableType}; use alloc::boxed::Box; use core::fmt::{self, Display}; use wasmparser::TypeRef; /// A [`Module`] import item. /// /// [`Module`]: [`super::Module`] #[derive(Debug)] pub struct Import { /// The name of the imported item. name: ImportName, /// The type of the imported item. kind: ExternTypeIdx, } /// The name or namespace of an imported item. #[derive(Debug, Clone)] pub struct ImportName { /// The name of the [`Module`] that defines the imported item. /// /// [`Module`]: [`super::Module`] module: Box, /// The name of the imported item within the [`Module`] namespace. /// /// [`Module`]: [`super::Module`] field: Box, } impl Display for ImportName { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let module_name = &*self.module; let field_name = &*self.field; write!(f, "{module_name}::{field_name}") } } impl ImportName { /// Creates a new [`Import`] item. pub fn new(module: &str, field: &str) -> Self { Self { module: module.into(), field: field.into(), } } /// Returns the name of the [`Module`] that defines the imported item. /// /// [`Module`]: [`super::Module`] pub fn module(&self) -> &str { &self.module } /// Returns the name of the imported item within the [`Module`] namespace. /// /// [`Module`]: [`super::Module`] pub fn name(&self) -> &str { &self.field } } impl From> for Import { fn from(import: wasmparser::Import) -> Self { let kind = match import.ty { TypeRef::Func(ty) => ExternTypeIdx::Func(ty.into()), TypeRef::Table(ty) => ExternTypeIdx::Table(TableType::from_wasmparser(ty)), TypeRef::Memory(ty) => ExternTypeIdx::Memory(MemoryType::from_wasmparser(ty)), TypeRef::Global(ty) => ExternTypeIdx::Global(GlobalType::from_wasmparser(ty)), TypeRef::Tag(tag) => panic!( "wasmi does not support the `exception-handling` Wasm proposal but found: {tag:?}" ), }; Self::new(import.module, import.name, kind) } } impl Import { /// Creates a new [`Import`] item. pub fn new(module: &str, field: &str, kind: ExternTypeIdx) -> Self { Self { name: ImportName::new(module, field), kind, } } /// Splits the [`Import`] into its raw parts. /// /// # Note /// /// This allows to reuse some allocations in certain cases. pub fn into_name_and_type(self) -> (ImportName, ExternTypeIdx) { (self.name, self.kind) } } /// The kind of a [`Module`] import. /// /// [`Module`]: [`super::Module`] #[derive(Debug)] pub enum ExternTypeIdx { /// An imported function. Func(FuncTypeIdx), /// An imported table. Table(TableType), /// An imported linear memory. Memory(MemoryType), /// An imported global variable. Global(GlobalType), } /// A [`FuncType`] index. /// /// # Note /// /// This generally refers to a [`FuncType`] within the same [`Module`] /// and is used by both function declarations and function imports. /// /// [`Module`]: [`super::Module`] /// [`FuncType`]: [`crate::FuncType`] #[derive(Debug, Copy, Clone)] pub struct FuncTypeIdx(u32); impl From for FuncTypeIdx { fn from(index: u32) -> Self { Self(index) } } impl FuncTypeIdx { /// Returns the inner `u32` index of the [`FuncTypeIdx`]. pub fn into_u32(self) -> u32 { self.0 } } wasmi-1.1.0/src/module/init_expr.rs000064400000000000000000000325561046102023000154010ustar 00000000000000//! Data structures to represents Wasm constant expressions. //! //! This has built-in support for the `extended-const` Wasm proposal. //! The design of the execution mechanic was inspired by the [`s1vm`] //! virtual machine architecture. //! //! [`s1vm`]: https://github.com/Neopallium/s1vm use super::FuncIdx; use crate::{ core::{wasm, UntypedVal}, ExternRef, Func, Ref, Val, F32, F64, }; use alloc::{boxed::Box, vec::Vec}; use core::{fmt, mem}; use wasmparser::AbstractHeapType; #[cfg(feature = "simd")] use crate::V128; /// Types that allow evaluation given an evaluation context. pub trait Eval { /// Evaluates `self` given an [`EvalContext`]. fn eval(&self, ctx: &dyn EvalContext) -> Option; } /// A [`ConstExpr`] evaluation context. /// /// Required for evaluating a [`ConstExpr`]. pub trait EvalContext { /// Returns the [`Val`] of the global value at `index` if any. fn get_global(&self, index: u32) -> Option; /// Returns the [`Ref`] of the [`Func`] at `index` if any. fn get_func(&self, index: u32) -> Option>; } /// An empty evaluation context. pub struct EmptyEvalContext; impl EvalContext for EmptyEvalContext { fn get_global(&self, _index: u32) -> Option { None } fn get_func(&self, _index: u32) -> Option> { None } } /// An input parameter to a [`ConstExpr`] operator. #[derive(Debug)] pub enum Op { /// A constant value. Const(ConstOp), /// The value of a global variable. Global(GlobalOp), /// A Wasm `ref.func index` value. FuncRef(FuncRefOp), /// An arbitrary expression. Expr(ExprOp), } /// A constant value operator. /// /// This may represent the following Wasm operators: /// /// - `i32.const` /// - `i64.const` /// - `f32.const` /// - `f64.const` /// - `ref.null` #[derive(Debug)] pub struct ConstOp { /// The underlying precomputed untyped value. value: UntypedVal, } impl Eval for ConstOp { fn eval(&self, _ctx: &dyn EvalContext) -> Option { Some(self.value) } } /// Represents a Wasm `global.get` operator. #[derive(Debug)] pub struct GlobalOp { /// The index of the global variable. global_index: u32, } impl Eval for GlobalOp { fn eval(&self, ctx: &dyn EvalContext) -> Option { ctx.get_global(self.global_index).map(UntypedVal::from) } } /// Represents a Wasm `func.ref` operator. #[derive(Debug)] pub struct FuncRefOp { /// The index of the function. function_index: u32, } impl Eval for FuncRefOp { fn eval(&self, ctx: &dyn EvalContext) -> Option { ctx.get_func(self.function_index).map(UntypedVal::from) } } /// A generic Wasm expression operator. /// /// This may represent one of the following Wasm operators: /// /// - `i32.add` /// - `i32.sub` /// - `i32.mul` /// - `i64.add` /// - `i64.sub` /// - `i64.mul` #[allow(clippy::type_complexity)] pub struct ExprOp { /// The underlying closure that implements the expression. expr: Box Option + Send + Sync>, } impl fmt::Debug for ExprOp { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ExprOp").finish() } } impl Eval for ExprOp { fn eval(&self, ctx: &dyn EvalContext) -> Option { (self.expr)(ctx) } } impl Op { /// Creates a new constant operator for the given `value`. pub fn constant(value: T) -> Self where T: Into, { Self::Const(ConstOp { value: value.into().into(), }) } /// Creates a new global operator with the given index. pub fn global(global_index: u32) -> Self { Self::Global(GlobalOp { global_index }) } /// Creates a new global operator with the given index. pub fn funcref(function_index: u32) -> Self { Self::FuncRef(FuncRefOp { function_index }) } /// Creates a new expression operator for the given `expr`. pub fn expr(expr: T) -> Self where T: Fn(&dyn EvalContext) -> Option + Send + Sync + 'static, { Self::Expr(ExprOp { expr: Box::new(expr), }) } } impl Eval for Op { fn eval(&self, ctx: &dyn EvalContext) -> Option { match self { Op::Const(op) => op.eval(ctx), Op::Global(op) => op.eval(ctx), Op::FuncRef(op) => op.eval(ctx), Op::Expr(op) => op.eval(ctx), } } } /// A Wasm constant expression. /// /// These are used to determine the offsets of memory data /// and table element segments as well as the initial value /// of global variables. #[derive(Debug)] pub struct ConstExpr { /// The root operator of the [`ConstExpr`]. op: Op, } impl Eval for ConstExpr { fn eval(&self, ctx: &dyn EvalContext) -> Option { self.op.eval(ctx) } } macro_rules! def_expr { ($lhs:ident, $rhs:ident, $expr:expr) => {{ Op::expr(move |ctx: &dyn EvalContext| -> Option { let lhs = $lhs.eval(ctx)?; let rhs = $rhs.eval(ctx)?; Some($expr(lhs.into(), rhs.into()).into()) }) }}; } /// Stack to translate [`ConstExpr`]. #[derive(Debug, Default)] pub struct ConstExprStack { /// The top-most [`Op`] on the stack. /// /// # Note /// This is an optimization so that the [`ConstExprStack`] does not /// require heap allocations for the common case where only a single /// stack slot is needed. top: Option, /// The remaining ops on the stack. ops: Vec, } impl ConstExprStack { /// Returns `true` if [`ConstExprStack`] is empty. pub fn is_empty(&self) -> bool { self.ops.is_empty() } /// Pushes an [`Op`] to the [`ConstExprStack`]. pub fn push(&mut self, op: Op) { let old_top = self.top.replace(op); if let Some(old_top) = old_top { self.ops.push(old_top); } } /// Pops the top-most [`Op`] from the [`ConstExprStack`] if any. pub fn pop(&mut self) -> Option { let new_top = self.ops.pop(); mem::replace(&mut self.top, new_top) } /// Pops the 2 top-most [`Op`]s from the [`ConstExprStack`] if any. pub fn pop2(&mut self) -> Option<(Op, Op)> { let rhs = self.pop()?; let lhs = self.pop()?; Some((lhs, rhs)) } } impl ConstExpr { /// Creates a new [`ConstExpr`] from the given Wasm [`ConstExpr`]. /// /// # Note /// /// The constructor assumes that Wasm validation already succeeded /// on the input Wasm [`ConstExpr`]. pub fn new(expr: wasmparser::ConstExpr<'_>) -> Self { /// Convenience function to create the various expression operators. fn expr_op(stack: &mut ConstExprStack, expr: fn(Lhs, Rhs) -> T) -> Op where Lhs: From + 'static, Rhs: From + 'static, T: 'static, UntypedVal: From, { let (lhs, rhs) = stack .pop2() .expect("must have 2 operators on the stack due to Wasm validation"); match (lhs, rhs) { (Op::Const(lhs), Op::Const(rhs)) => def_expr!(lhs, rhs, expr), (Op::Const(lhs), Op::Global(rhs)) => def_expr!(lhs, rhs, expr), (Op::Const(lhs), Op::FuncRef(rhs)) => def_expr!(lhs, rhs, expr), (Op::Const(lhs), Op::Expr(rhs)) => def_expr!(lhs, rhs, expr), (Op::Global(lhs), Op::Const(rhs)) => def_expr!(lhs, rhs, expr), (Op::Global(lhs), Op::Global(rhs)) => def_expr!(lhs, rhs, expr), (Op::Global(lhs), Op::FuncRef(rhs)) => def_expr!(lhs, rhs, expr), (Op::Global(lhs), Op::Expr(rhs)) => def_expr!(lhs, rhs, expr), (Op::FuncRef(lhs), Op::Const(rhs)) => def_expr!(lhs, rhs, expr), (Op::FuncRef(lhs), Op::Global(rhs)) => def_expr!(lhs, rhs, expr), (Op::FuncRef(lhs), Op::FuncRef(rhs)) => def_expr!(lhs, rhs, expr), (Op::FuncRef(lhs), Op::Expr(rhs)) => def_expr!(lhs, rhs, expr), (Op::Expr(lhs), Op::Const(rhs)) => def_expr!(lhs, rhs, expr), (Op::Expr(lhs), Op::Global(rhs)) => def_expr!(lhs, rhs, expr), (Op::Expr(lhs), Op::FuncRef(rhs)) => def_expr!(lhs, rhs, expr), (Op::Expr(lhs), Op::Expr(rhs)) => def_expr!(lhs, rhs, expr), } } let mut reader = expr.get_operators_reader(); let mut stack = ConstExprStack::default(); loop { let wasm_op = reader .read() .unwrap_or_else(|error| panic!("invalid const expression operator: {error}")); let op = match wasm_op { wasmparser::Operator::I32Const { value } => Op::constant(value), wasmparser::Operator::I64Const { value } => Op::constant(value), wasmparser::Operator::F32Const { value } => { Op::constant(F32::from_bits(value.bits())) } wasmparser::Operator::F64Const { value } => { Op::constant(F64::from_bits(value.bits())) } #[cfg(feature = "simd")] wasmparser::Operator::V128Const { value } => { Op::constant(V128::from(value.i128() as u128)) } wasmparser::Operator::GlobalGet { global_index } => Op::global(global_index), wasmparser::Operator::RefNull { hty } => { let value = match hty { wasmparser::HeapType::Abstract { shared: false, ty: AbstractHeapType::Func, } => Val::from(>::Null), wasmparser::HeapType::Abstract { shared: false, ty: AbstractHeapType::Extern, } => Val::from(>::Null), invalid => { panic!("invalid heap type for `ref.null`: {invalid:?}") } }; Op::constant(value) } wasmparser::Operator::RefFunc { function_index } => Op::funcref(function_index), wasmparser::Operator::I32Add => expr_op(&mut stack, wasm::i32_add), wasmparser::Operator::I32Sub => expr_op(&mut stack, wasm::i32_sub), wasmparser::Operator::I32Mul => expr_op(&mut stack, wasm::i32_mul), wasmparser::Operator::I64Add => expr_op(&mut stack, wasm::i64_add), wasmparser::Operator::I64Sub => expr_op(&mut stack, wasm::i64_sub), wasmparser::Operator::I64Mul => expr_op(&mut stack, wasm::i64_mul), wasmparser::Operator::End => break, op => panic!("unexpected Wasm const expression operator: {op:?}"), }; stack.push(op); } reader .finish() .expect("Wasm validation requires const expressions to have an `end`"); let op = stack .pop() .expect("must contain the root const expression at this point"); debug_assert!(stack.is_empty()); Self { op } } /// Create a new `ref.func x` [`ConstExpr`]. /// /// # Note /// /// Required for setting up table elements. pub fn new_funcref(function_index: u32) -> Self { Self { op: Op::FuncRef(FuncRefOp { function_index }), } } /// Returns `Some(index)` if the [`ConstExpr`] is a `funcref(index)`. /// /// Otherwise returns `None`. pub fn funcref(&self) -> Option { if let Op::FuncRef(op) = &self.op { return Some(FuncIdx::from(op.function_index)); } None } /// Evaluates the [`ConstExpr`] in a constant evaluation context. /// /// # Note /// /// This is useful for evaluations during Wasm translation to /// perform optimizations on the translated bytecode. pub fn eval_const(&self) -> Option { self.eval(&EmptyEvalContext) } /// Evaluates the [`ConstExpr`] given a context for globals and functions. /// /// Returns `None` if a non-const expression operand is encountered /// or the provided globals and functions context returns `None`. /// /// # Note /// /// This is useful for evaluation of [`ConstExpr`] during bytecode execution. pub fn eval_with_context(&self, global_get: G, func_get: F) -> Option where G: Fn(u32) -> Val, F: Fn(u32) -> Ref, { /// Context that wraps closures representing partial evaluation contexts. struct WrappedEvalContext { /// Wrapped context for global variables. global_get: G, /// Wrapped context for functions. func_get: F, } impl EvalContext for WrappedEvalContext where G: Fn(u32) -> Val, F: Fn(u32) -> Ref, { fn get_global(&self, index: u32) -> Option { Some((self.global_get)(index)) } fn get_func(&self, index: u32) -> Option> { Some((self.func_get)(index)) } } self.eval(&WrappedEvalContext:: { global_get, func_get, }) } } wasmi-1.1.0/src/module/instantiate/error.rs000064400000000000000000000122221046102023000170400ustar 00000000000000use crate::{ errors::{MemoryError, TableError}, Extern, ExternType, FuncType, GlobalType, MemoryType, Table, TableType, }; use core::{ error::Error, fmt::{self, Display}, }; /// An error that may occur upon instantiation of a Wasm module. #[derive(Debug)] pub enum InstantiationError { /// Encountered when trying to instantiate a Wasm module with /// a non-matching number of external imports. InvalidNumberOfImports { /// The number of imports required by the Wasm module definition. required: usize, /// The number of imports given by the faulty Wasm module instantiation. given: usize, }, /// Caused when a given external value does not match the /// type of the required import for module instantiation. ImportsExternalsMismatch { /// The expected external value for the module import. expected: ExternType, /// The actually found external value for the module import. actual: Extern, }, /// Returned when a global has a mismatching type. GlobalTypeMismatch { /// The expected global type of the global import. expected: GlobalType, /// The actual global type of the global import. actual: GlobalType, }, /// Returned when a function has a mismatching type. FuncTypeMismatch { /// The expected function type of the function import. expected: FuncType, /// The actual function type of the function import. actual: FuncType, }, /// Returned when a table has a mismatching type. TableTypeMismatch { /// The expected table type of the table import. expected: TableType, /// The actual table type of the table import. actual: TableType, }, /// Returned when a linear memory has a mismatching type. MemoryTypeMismatch { /// The expected memory type of the memory import. expected: MemoryType, /// The actual memory type of the memory import. actual: MemoryType, }, /// Caused when an element segment does not fit into the specified table instance. ElementSegmentDoesNotFit { /// The table of the element segment. table: Table, /// The offset to store the `amount` of elements into the table. table_index: u64, /// The amount of elements with which the table is initialized at the `offset`. len: u32, }, /// Caused when the `start` function was unexpectedly found in the instantiated module. UnexpectedStartFn { /// The index of the found `start` function. index: u32, }, /// When trying to instantiate more instances than supported by Wasmi. TooManyInstances, /// When trying to instantiate more tables than supported by Wasmi. TooManyTables, /// When trying to instantiate more linear memories than supported by Wasmi. TooManyMemories, /// Encountered when failing to instantiate a linear memory. FailedToInstantiateMemory(MemoryError), /// Encountered when failing to instantiate a table. FailedToInstantiateTable(TableError), } impl Error for InstantiationError {} impl Display for InstantiationError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::InvalidNumberOfImports { required, given } => write!( f, "invalid number of imports: required = {required}, given = {given}", ), Self::ImportsExternalsMismatch { expected, actual } => write!( f, "expected {expected:?} external for import but found {actual:?}", ), Self::GlobalTypeMismatch { expected, actual } => write!(f, "imported global type mismatch. expected {expected:?} but found {actual:?}"), Self::FuncTypeMismatch { expected, actual } => write!(f, "imported function type mismatch. expected {expected:?} but found {actual:?}"), Self::TableTypeMismatch { expected, actual } => write!(f, "imported table type mismatch. expected {expected:?} but found {actual:?}"), Self::MemoryTypeMismatch { expected, actual } => write!(f, "imported memory type mismatch. expected {expected:?} but found {actual:?}"), Self::ElementSegmentDoesNotFit { table, table_index: offset, len: amount, } => write!( f, "out of bounds table access: {table:?} does not fit {amount} elements starting from offset {offset}", ), Self::UnexpectedStartFn { index } => { write!(f, "found an unexpected start function with index {index}") } Self::TooManyInstances => write!(f, "tried to instantiate too many instances"), Self::TooManyTables => write!(f, "tried to instantiate too many tables"), Self::TooManyMemories => write!(f, "tried to instantiate too many linear memories"), Self::FailedToInstantiateMemory(error) => write!(f, "failed to instantiate memory: {error}"), Self::FailedToInstantiateTable(error) => write!(f, "failed to instantiate table: {error}"), } } } wasmi-1.1.0/src/module/instantiate/mod.rs000064400000000000000000000402371046102023000164750ustar 00000000000000mod error; #[cfg(test)] mod tests; pub use self::error::InstantiationError; use super::{element::ElementSegmentKind, export, ConstExpr, InitDataSegment, Module}; use crate::{ core::UntypedVal, error::ErrorKind, errors::MemoryError, func::WasmFuncEntity, memory::DataSegment, module::FuncIdx, value::WithType, AsContext, AsContextMut, ElementSegment, Error, Extern, ExternType, Func, Global, Instance, InstanceEntity, InstanceEntityBuilder, Memory, Ref, Table, Val, }; impl Module { /// Instantiates a new [`Instance`] from the given compiled [`Module`]. /// /// Uses the given `context` to store the instance data to. /// The given `externals` are joined with the imports in the same order in which they occurred. /// /// # Note /// /// This is a very low-level API. For a more high-level API users should use the /// corresponding instantiation methods provided by the [`Linker`]. /// /// # Errors /// /// If the given `externals` do not satisfy the required imports, e.g. if an externally /// provided [`Func`] has a different function signature than required by the module import. /// /// [`Linker`]: struct.Linker.html /// [`Func`]: [`crate::Func`] pub(crate) fn instantiate( &self, mut context: impl AsContextMut, externals: I, ) -> Result where I: IntoIterator, { let mut context = context.as_context_mut().store; if !context.can_create_more_instances(1) { return Err(Error::from(InstantiationError::TooManyInstances)); } let handle = context.as_context_mut().store.inner.alloc_instance(); let mut builder = InstanceEntity::build(self); self.extract_imports(&context, &mut builder, externals)?; self.extract_functions(&mut context, &mut builder, handle); self.extract_tables(&mut context, &mut builder)?; self.extract_memories(&mut context, &mut builder)?; self.extract_globals(&mut context, &mut builder); self.extract_exports(&mut builder); self.extract_start_fn(&mut builder); self.initialize_table_elements(&mut context, &mut builder)?; self.initialize_memory_data(&mut context, &mut builder)?; Self::start(&mut context, builder, handle)?; Ok(handle) } /// Executes the [`Instance`]'s `start` function if any and finalizes instantiation. fn start( mut store: impl AsContextMut, builder: InstanceEntityBuilder, instance: Instance, ) -> Result<(), Error> { let opt_start_index = builder.get_start().map(FuncIdx::into_u32); store .as_context_mut() .store .inner .initialize_instance(instance, builder.finish()); if let Some(start_index) = opt_start_index { let start_func = instance .get_func_by_index(store.as_context_mut(), start_index) .unwrap_or_else(|| { panic!("encountered invalid start function after validation: {start_index}") }); start_func.call(store.as_context_mut(), &[], &mut [])? } Ok(()) } /// Extract the Wasm imports from the module and zips them with the given external values. /// /// This also stores imported references into the [`Instance`] under construction. /// /// # Errors /// /// - If too few or too many external values are given for the required module imports. /// - If the zipped import and given external have mismatching types, e.g. on index `i` /// the module requires a function import but on index `i` the externals provide a global /// variable external value. /// - If the externally provided [`Table`], [`Memory`], [`Func`] or [`Global`] has a type /// mismatch with the expected module import type. /// /// [`Func`]: [`crate::Func`] fn extract_imports( &self, store: impl AsContext, builder: &mut InstanceEntityBuilder, externals: I, ) -> Result<(), InstantiationError> where I: IntoIterator, { let imports = self.imports(); let externals = externals.into_iter(); if imports.len() != externals.len() { return Err(InstantiationError::InvalidNumberOfImports { required: imports.len(), given: externals.len(), }); } for (import, external) in imports.zip(externals) { match (import.ty(), external) { (ExternType::Func(expected_signature), Extern::Func(func)) => { let actual_signature = func.ty(&store); if &actual_signature != expected_signature { return Err(InstantiationError::FuncTypeMismatch { actual: actual_signature, expected: expected_signature.clone(), }); } builder.push_func(func); } (ExternType::Table(required), Extern::Table(table)) => { let imported = table.dynamic_ty(&store); if !imported.is_subtype_of(required) { return Err(InstantiationError::TableTypeMismatch { expected: *required, actual: imported, }); } builder.push_table(table); } (ExternType::Memory(required), Extern::Memory(memory)) => { let imported = memory.dynamic_ty(&store); if !imported.is_subtype_of(required) { return Err(InstantiationError::MemoryTypeMismatch { expected: *required, actual: imported, }); } builder.push_memory(memory); } (ExternType::Global(required), Extern::Global(global)) => { let imported = global.ty(&store); let required = *required; if imported != required { return Err(InstantiationError::GlobalTypeMismatch { expected: required, actual: imported, }); } builder.push_global(global); } (expected_import, actual_extern_val) => { return Err(InstantiationError::ImportsExternalsMismatch { expected: expected_import.clone(), actual: actual_extern_val, }); } } } Ok(()) } /// Extracts the Wasm functions from the module and stores them into the [`Store`]. /// /// This also stores [`Func`] references into the [`Instance`] under construction. /// /// [`Store`]: struct.Store.html /// [`Func`]: [`crate::Func`] fn extract_functions( &self, mut context: impl AsContextMut, builder: &mut InstanceEntityBuilder, handle: Instance, ) { for (func_type, func_body) in self.internal_funcs() { let wasm_func = WasmFuncEntity::new(func_type, func_body, handle); let func = context .as_context_mut() .store .inner .alloc_func(wasm_func.into()); builder.push_func(func); } } /// Extracts the Wasm tables from the module and stores them into the [`Store`]. /// /// This also stores [`Table`] references into the [`Instance`] under construction. /// /// [`Store`]: struct.Store.html fn extract_tables( &self, mut context: impl AsContextMut, builder: &mut InstanceEntityBuilder, ) -> Result<(), InstantiationError> { let ctx = context.as_context_mut().store; if !ctx.can_create_more_tables(self.len_tables()) { return Err(InstantiationError::TooManyTables); } for table_type in self.internal_tables().copied() { let init = Val::default(table_type.element()); let table = Table::new(context.as_context_mut(), table_type, init).map_err(|error| { let error = match error.kind() { ErrorKind::Table(error) => *error, error => panic!("unexpected error: {error}"), }; InstantiationError::FailedToInstantiateTable(error) })?; builder.push_table(table); } Ok(()) } /// Extracts the Wasm linear memories from the module and stores them into the [`Store`]. /// /// This also stores [`Memory`] references into the [`Instance`] under construction. /// /// [`Store`]: struct.Store.html fn extract_memories( &self, mut context: impl AsContextMut, builder: &mut InstanceEntityBuilder, ) -> Result<(), InstantiationError> { let ctx = context.as_context_mut().store; if !ctx.can_create_more_memories(self.len_memories()) { return Err(InstantiationError::TooManyMemories); } for memory_type in self.internal_memories().copied() { let memory = Memory::new(context.as_context_mut(), memory_type).map_err(|error| { let error = match error.kind() { ErrorKind::Memory(error) => *error, error => panic!("unexpected error: {error}"), }; InstantiationError::FailedToInstantiateMemory(error) })?; builder.push_memory(memory); } Ok(()) } /// Extracts the Wasm global variables from the module and stores them into the [`Store`]. /// /// This also stores [`Global`] references into the [`Instance`] under construction. /// /// [`Store`]: struct.Store.html fn extract_globals(&self, mut context: impl AsContextMut, builder: &mut InstanceEntityBuilder) { for (global_type, global_init) in self.internal_globals() { let value_type = global_type.content(); let init_value = Self::eval_init_expr(context.as_context_mut(), builder, global_init); let mutability = global_type.mutability(); let global = Global::new( context.as_context_mut(), init_value.with_type(value_type), mutability, ); builder.push_global(global); } } /// Evaluates the given initializer expression using the partially constructed [`Instance`]. fn eval_init_expr( context: impl AsContext, builder: &InstanceEntityBuilder, init_expr: &ConstExpr, ) -> UntypedVal { init_expr .eval_with_context( |global_index| builder.get_global(global_index).get(&context), |func_index| >::from(builder.get_func(func_index)), ) .expect("must evaluate to proper value") } /// Extracts the Wasm exports from the module and registers them into the [`Instance`]. fn extract_exports(&self, builder: &mut InstanceEntityBuilder) { for (field, idx) in &self.module_header().exports { let external = match idx { export::ExternIdx::Func(func_index) => { let func_index = func_index.into_u32(); let func = builder.get_func(func_index); Extern::Func(func) } export::ExternIdx::Table(table_index) => { let table_index = table_index.into_u32(); let table = builder.get_table(table_index); Extern::Table(table) } export::ExternIdx::Memory(memory_index) => { let memory_index = memory_index.into_u32(); let memory = builder.get_memory(memory_index); Extern::Memory(memory) } export::ExternIdx::Global(global_index) => { let global_index = global_index.into_u32(); let global = builder.get_global(global_index); Extern::Global(global) } }; builder.push_export(field, external); } } /// Extracts the optional start function for the build instance. fn extract_start_fn(&self, builder: &mut InstanceEntityBuilder) { if let Some(start_fn) = self.module_header().start { builder.set_start(start_fn) } } /// Initializes the [`Instance`] tables with the Wasm element segments of the [`Module`]. fn initialize_table_elements( &self, mut context: impl AsContextMut, builder: &mut InstanceEntityBuilder, ) -> Result<(), Error> { for segment in &self.module_header().element_segments[..] { let get_global = |index| builder.get_global(index); let get_func = |index| builder.get_func(index); let element = ElementSegment::new(context.as_context_mut(), segment, get_func, get_global); if let ElementSegmentKind::Active(active) = segment.kind() { let dst_index = u64::from(Self::eval_init_expr( context.as_context(), builder, active.offset(), )); let table = builder.get_table(active.table_index().into_u32()); // Note: This checks not only that the elements in the element segments properly // fit into the table at the given offset but also that the element segment // consists of at least 1 element member. let len_table = table.size(&context); let len_items = element.size(&context); dst_index .checked_add(u64::from(len_items)) .filter(|&max_index| max_index <= len_table) .ok_or(InstantiationError::ElementSegmentDoesNotFit { table, table_index: dst_index, len: len_items, })?; let (table, elem) = context .as_context_mut() .store .inner .resolve_table_and_element_mut(&table, &element); table.init(elem.as_ref(), dst_index, 0, len_items, None)?; // Now drop the active element segment as commanded by the Wasm spec. elem.drop_items(); } builder.push_element_segment(element); } Ok(()) } /// Initializes the [`Instance`] linear memories with the Wasm data segments of the [`Module`]. fn initialize_memory_data( &self, mut context: impl AsContextMut, builder: &mut InstanceEntityBuilder, ) -> Result<(), Error> { for segment in &self.inner.data_segments { let segment = match segment { InitDataSegment::Active { memory_index, offset, bytes, } => { let memory = builder.get_memory(memory_index.into_u32()); let offset = Self::eval_init_expr(context.as_context(), builder, offset); let offset = match usize::try_from(u64::from(offset)) { Ok(offset) => offset, Err(_) => return Err(Error::from(MemoryError::OutOfBoundsAccess)), }; memory.write(context.as_context_mut(), offset, bytes)?; DataSegment::new_active(context.as_context_mut()) } InitDataSegment::Passive { bytes } => { DataSegment::new_passive(context.as_context_mut(), bytes) } }; builder.push_data_segment(segment); } Ok(()) } } wasmi-1.1.0/src/module/instantiate/tests.rs000064400000000000000000000077731046102023000170700ustar 00000000000000//! Regression tests for GitHub issue: //! https://github.com/paritytech/wasmi/issues/587 //! //! The problem was that Wasm memories (and tables) were defined twice for a //! Wasmi instance for every imported Wasm memory (or table). Since Wasmi //! does not support the `multi-memory` Wasm proposal this resulted Wasm //! instances with more than 1 memory (or table) if the Wasm module imported //! those entities. use crate::{ instance::InstanceEntity, Engine, Error, Instance, Linker, Memory, MemoryType, Module, Store, Table, TableType, Val, ValType, }; fn try_instantiate_from_wat(wasm: &str) -> Result<(Store<()>, Instance), Error> { let engine = Engine::default(); let module = Module::new(&engine, wasm)?; let mut store = Store::new(&engine, ()); let mut linker = >::new(&engine); // Define one memory that can be used by the tests as import. let memory_type = MemoryType::new(4, None); let memory = Memory::new(&mut store, memory_type)?; linker.define("env", "memory", memory)?; // Define one table that can be used by the tests as import. let table_type = TableType::new(ValType::FuncRef, 4, None); let init = Val::default(table_type.element()); let table = Table::new(&mut store, table_type, init)?; linker.define("env", "table", table)?; let instance = linker.instantiate_and_start(&mut store, &module)?; Ok((store, instance)) } fn instantiate_from_wat(wat: &str) -> (Store<()>, Instance) { try_instantiate_from_wat(wat).unwrap() } fn resolve_instance<'a>(store: &'a Store<()>, instance: &Instance) -> &'a InstanceEntity { store.inner.resolve_instance(instance) } fn assert_no_duplicates(store: &Store<()>, instance: Instance) { assert!(resolve_instance(store, &instance).get_memory(1).is_none()); assert!(resolve_instance(store, &instance).get_table(1).is_none()); } #[test] fn test_import_memory_and_table() { let wat = r#" (module (import "env" "memory" (memory 4)) (import "env" "table" (table 4 funcref)) )"#; let (store, instance) = instantiate_from_wat(wat); assert!(resolve_instance(&store, &instance).get_memory(0).is_some()); assert!(resolve_instance(&store, &instance).get_table(0).is_some()); assert_no_duplicates(&store, instance); } #[test] fn test_import_memory() { let wat = r#" (module (import "env" "memory" (memory 4)) )"#; let (store, instance) = instantiate_from_wat(wat); assert!(resolve_instance(&store, &instance).get_memory(0).is_some()); assert!(resolve_instance(&store, &instance).get_table(0).is_none()); assert_no_duplicates(&store, instance); } #[test] fn test_import_table() { let wat = r#" (module (import "env" "table" (table 4 funcref)) )"#; let (store, instance) = instantiate_from_wat(wat); assert!(resolve_instance(&store, &instance).get_memory(0).is_none()); assert!(resolve_instance(&store, &instance).get_table(0).is_some()); assert_no_duplicates(&store, instance); } #[test] fn test_no_memory_no_table() { let wat = "(module)"; let (store, instance) = instantiate_from_wat(wat); assert!(resolve_instance(&store, &instance).get_memory(0).is_none()); assert!(resolve_instance(&store, &instance).get_table(0).is_none()); assert_no_duplicates(&store, instance); } #[test] fn test_internal_memory() { let wat = "(module (memory 1 10) )"; let (store, instance) = instantiate_from_wat(wat); assert!(resolve_instance(&store, &instance).get_memory(0).is_some()); assert!(resolve_instance(&store, &instance).get_table(0).is_none()); assert_no_duplicates(&store, instance); } #[test] fn test_internal_table() { let wat = "(module (table 4 funcref) )"; let (store, instance) = instantiate_from_wat(wat); assert!(resolve_instance(&store, &instance).get_memory(0).is_none()); assert!(resolve_instance(&store, &instance).get_table(0).is_some()); assert_no_duplicates(&store, instance); } wasmi-1.1.0/src/module/mod.rs000064400000000000000000000502661046102023000141550ustar 00000000000000mod builder; mod custom_section; mod data; mod element; mod export; mod global; mod import; mod init_expr; mod instantiate; mod parser; mod read; pub(crate) mod utils; use self::{ builder::ModuleBuilder, custom_section::{CustomSections, CustomSectionsBuilder}, export::ExternIdx, global::Global, import::{ExternTypeIdx, Import}, parser::ModuleParser, }; pub use self::{ custom_section::{CustomSection, CustomSectionsIter}, export::{ExportType, FuncIdx, MemoryIdx, ModuleExportsIter, TableIdx}, global::GlobalIdx, import::{FuncTypeIdx, ImportName}, instantiate::InstantiationError, read::{Read, ReadError}, }; pub(crate) use self::{ data::{DataSegment, DataSegments, InitDataSegment, PassiveDataSegmentBytes}, element::{ElementSegment, ElementSegmentKind}, init_expr::ConstExpr, utils::WasmiValueType, }; use crate::{ collections::Map, engine::{DedupFuncType, EngineFunc, EngineFuncSpan, EngineFuncSpanIter, EngineWeak}, Engine, Error, ExternType, FuncType, GlobalType, MemoryType, TableType, }; use alloc::{boxed::Box, sync::Arc}; use core::{iter, slice::Iter as SliceIter}; use wasmparser::{FuncValidatorAllocations, Parser, ValidPayload, Validator}; /// A parsed and validated WebAssembly module. #[derive(Debug, Clone)] pub struct Module { inner: Arc, } /// The internal data of a [`Module`]. #[derive(Debug)] struct ModuleInner { engine: Engine, header: ModuleHeader, data_segments: DataSegments, custom_sections: CustomSections, } /// A parsed and validated WebAssembly module header. #[derive(Debug, Clone)] pub struct ModuleHeader { inner: Arc, } #[derive(Debug)] struct ModuleHeaderInner { engine: EngineWeak, func_types: Arc<[DedupFuncType]>, imports: ModuleImports, funcs: Box<[DedupFuncType]>, tables: Box<[TableType]>, memories: Box<[MemoryType]>, globals: Box<[GlobalType]>, globals_init: Box<[ConstExpr]>, exports: Map, ExternIdx>, start: Option, engine_funcs: EngineFuncSpan, element_segments: Box<[ElementSegment]>, } impl ModuleHeader { /// Returns the [`Engine`] of the [`ModuleHeader`]. pub fn engine(&self) -> &EngineWeak { &self.inner.engine } /// Returns the [`FuncType`] at the given index. pub fn get_func_type(&self, func_type_idx: FuncTypeIdx) -> &DedupFuncType { &self.inner.func_types[func_type_idx.into_u32() as usize] } /// Returns the [`FuncType`] of the indexed function. pub fn get_type_of_func(&self, func_idx: FuncIdx) -> &DedupFuncType { &self.inner.funcs[func_idx.into_u32() as usize] } /// Returns the [`GlobalType`] of the indexed global variable. pub fn get_type_of_global(&self, global_idx: GlobalIdx) -> &GlobalType { &self.inner.globals[global_idx.into_u32() as usize] } /// Returns the [`MemoryType`] of the indexed Wasm memory. pub fn get_type_of_memory(&self, memory_idx: MemoryIdx) -> &MemoryType { &self.inner.memories[memory_idx.into_u32() as usize] } /// Returns the [`TableType`] of the indexed Wasm table. pub fn get_type_of_table(&self, table_idx: TableIdx) -> &TableType { &self.inner.tables[table_idx.into_u32() as usize] } /// Returns the [`EngineFunc`] for the given [`FuncIdx`]. /// /// Returns `None` if [`FuncIdx`] refers to an imported function. pub fn get_engine_func(&self, func_idx: FuncIdx) -> Option { let index = func_idx.into_u32(); let len_imported = self.inner.imports.len_funcs() as u32; let index = index.checked_sub(len_imported)?; // Note: It is a bug if this index access is out of bounds // therefore we panic here instead of using `get`. Some(self.inner.engine_funcs.get_or_panic(index)) } /// Returns the [`FuncIdx`] for the given [`EngineFunc`]. pub fn get_func_index(&self, func: EngineFunc) -> Option { let position = self.inner.engine_funcs.position(func)?; let len_imports = self.inner.imports.len_funcs as u32; Some(FuncIdx::from(position + len_imports)) } /// Returns the global variable type and optional initial value. pub fn get_global(&self, global_idx: GlobalIdx) -> (&GlobalType, Option<&ConstExpr>) { let index = global_idx.into_u32() as usize; let len_imports = self.inner.imports.len_globals(); let global_type = self.get_type_of_global(global_idx); if index < len_imports { // The index refers to an imported global without init value. (global_type, None) } else { // The index refers to an internal global with init value. let init_expr = &self.inner.globals_init[index - len_imports]; (global_type, Some(init_expr)) } } } /// The index of the default Wasm linear memory. pub(crate) const DEFAULT_MEMORY_INDEX: u32 = 0; /// An imported item declaration in the [`Module`]. #[derive(Debug)] pub enum Imported { /// The name of an imported [`Func`]. /// /// [`Func`]: [`crate::Func`] Func(ImportName), /// The name of an imported [`Table`]. /// /// [`Table`]: [`crate::Table`] Table(ImportName), /// The name of an imported [`Memory`]. /// /// [`Memory`]: [`crate::Memory`] Memory(ImportName), /// The name of an imported [`Global`]. Global(ImportName), } /// The import names of the [`Module`] imports. #[derive(Debug)] pub struct ModuleImports { /// All names and types of all imported items. items: Box<[Imported]>, /// The amount of imported [`Func`]. /// /// [`Func`]: [`crate::Func`] len_funcs: usize, /// The amount of imported [`Global`]. len_globals: usize, /// The amount of imported [`Memory`]. /// /// [`Memory`]: [`crate::Memory`] len_memories: usize, /// The amount of imported [`Table`]. /// /// [`Table`]: [`crate::Table`] len_tables: usize, } impl ModuleImports { /// Returns the number of imported global variables. pub fn len_globals(&self) -> usize { self.len_globals } /// Returns the number of imported functions. pub fn len_funcs(&self) -> usize { self.len_funcs } } impl Module { /// Creates a new Wasm [`Module`] from the given Wasm bytecode buffer. /// /// # Note /// /// - This parses, validates and translates the buffered Wasm bytecode. /// - The `wasm` may be encoded as WebAssembly binary (`.wasm`) or as /// WebAssembly text format (`.wat`). /// /// # Errors /// /// - If the Wasm bytecode is malformed or fails to validate. /// - If the Wasm bytecode violates restrictions /// set in the [`Config`] used by the `engine`. /// - If Wasmi cannot translate the Wasm bytecode. /// /// [`Config`]: crate::Config pub fn new(engine: &Engine, wasm: impl AsRef<[u8]>) -> Result { let wasm = wasm.as_ref(); #[cfg(feature = "wat")] let wasm = &wat::parse_bytes(wasm)?[..]; ModuleParser::new(engine).parse_buffered(wasm) } /// Creates a new Wasm [`Module`] from the given Wasm bytecode buffer. /// /// # Note /// /// This parses and translates the buffered Wasm bytecode. /// /// # Safety /// /// - This does _not_ validate the Wasm bytecode. /// - It is the caller's responsibility that the Wasm bytecode is valid. /// - It is the caller's responsibility that the Wasm bytecode adheres /// to the restrictions set by the used [`Config`] of the `engine`. /// - Violating the above rules is undefined behavior. /// /// # Errors /// /// - If the Wasm bytecode is malformed or contains invalid sections. /// - If the Wasm bytecode fails to be compiled by Wasmi. /// /// [`Config`]: crate::Config pub unsafe fn new_unchecked(engine: &Engine, wasm: &[u8]) -> Result { let parser = ModuleParser::new(engine); unsafe { parser.parse_buffered_unchecked(wasm) } } /// Returns the [`Engine`] used during creation of the [`Module`]. pub fn engine(&self) -> &Engine { &self.inner.engine } /// Returns a shared reference to the [`ModuleHeaderInner`]. fn module_header(&self) -> &ModuleHeaderInner { &self.inner.header.inner } /// Validates `wasm` as a WebAssembly binary given the configuration (via [`Config`]) in `engine`. /// /// This function performs Wasm validation of the binary input WebAssembly module and /// returns either `Ok` or `Err` depending on the results of the validation. /// The [`Config`] of the `engine` is used for Wasm validation which indicates which WebAssembly /// features are valid and invalid for the validation. /// /// # Note /// /// - The input `wasm` must be in binary form, the text format is not accepted by this function. /// - This will only validate the `wasm` but not try to translate it. Therefore `Module::new` /// might still fail if translation of the Wasm binary input fails to translate via the Wasmi /// [`Engine`]. /// - Validation automatically happens as part of [`Module::new`]. /// /// # Errors /// /// If Wasm validation for `wasm` fails for the given [`Config`] provided via `engine`. /// /// [`Config`]: crate::Config pub fn validate(engine: &Engine, wasm: &[u8]) -> Result<(), Error> { let mut validator = Validator::new_with_features(engine.config().wasm_features()); for payload in Parser::new(0).parse_all(wasm) { let payload = payload?; if let ValidPayload::Func(func_to_validate, func_body) = validator.payload(&payload)? { func_to_validate .into_validator(FuncValidatorAllocations::default()) .validate(&func_body)?; } } Ok(()) } /// Returns the number of non-imported functions of the [`Module`]. pub(crate) fn len_funcs(&self) -> usize { self.module_header().funcs.len() } /// Returns the number of non-imported tables of the [`Module`]. pub(crate) fn len_tables(&self) -> usize { self.module_header().tables.len() } /// Returns the number of non-imported linear memories of the [`Module`]. pub(crate) fn len_memories(&self) -> usize { self.module_header().memories.len() } /// Returns the number of non-imported global variables of the [`Module`]. pub(crate) fn len_globals(&self) -> usize { self.module_header().globals.len() } /// Returns a slice to the function types of the [`Module`]. /// /// # Note /// /// The slice is stored in a `Arc` so that this operation is very cheap. pub(crate) fn func_types_cloned(&self) -> Arc<[DedupFuncType]> { self.module_header().func_types.clone() } /// Returns an iterator over the imports of the [`Module`]. pub fn imports(&self) -> ModuleImportsIter<'_> { let header = self.module_header(); let len_imported_funcs = header.imports.len_funcs; let len_imported_globals = header.imports.len_globals; ModuleImportsIter { engine: self.engine(), names: header.imports.items.iter(), funcs: header.funcs[..len_imported_funcs].iter(), tables: header.tables.iter(), memories: header.memories.iter(), globals: header.globals[..len_imported_globals].iter(), } } /// Returns an iterator over the internally defined [`Func`]. /// /// [`Func`]: [`crate::Func`] pub(crate) fn internal_funcs(&self) -> InternalFuncsIter<'_> { let header = self.module_header(); let len_imported = header.imports.len_funcs; // We skip the first `len_imported` elements in `funcs` // since they refer to imported and not internally defined // functions. let funcs = &header.funcs[len_imported..]; let engine_funcs = header.engine_funcs.iter(); assert_eq!(funcs.len(), engine_funcs.len()); InternalFuncsIter { iter: funcs.iter().zip(engine_funcs), } } /// Returns an iterator over the [`MemoryType`] of internal linear memories. fn internal_memories(&self) -> SliceIter<'_, MemoryType> { let header = self.module_header(); let len_imported = header.imports.len_memories; // We skip the first `len_imported` elements in `memories` // since they refer to imported and not internally defined // linear memories. let memories = &header.memories[len_imported..]; memories.iter() } /// Returns an iterator over the [`TableType`] of internal tables. fn internal_tables(&self) -> SliceIter<'_, TableType> { let header = self.module_header(); let len_imported = header.imports.len_tables; // We skip the first `len_imported` elements in `tables` // since they refer to imported and not internally defined // tables. let tables = &header.tables[len_imported..]; tables.iter() } /// Returns an iterator over the internally defined [`Global`]. fn internal_globals(&self) -> InternalGlobalsIter<'_> { let header = self.module_header(); let len_imported = header.imports.len_globals; // We skip the first `len_imported` elements in `globals` // since they refer to imported and not internally defined // global variables. let globals = header.globals[len_imported..].iter(); let global_inits = header.globals_init.iter(); InternalGlobalsIter { iter: globals.zip(global_inits), } } /// Returns an iterator over the exports of the [`Module`]. pub fn exports(&self) -> ModuleExportsIter<'_> { ModuleExportsIter::new(self) } /// Looks up an export in this [`Module`] by its `name`. /// /// Returns `None` if no export with the name was found. /// /// # Note /// /// This function will return the type of an export with the given `name`. pub fn get_export(&self, name: &str) -> Option { let idx = self.module_header().exports.get(name).copied()?; let ty = self.get_extern_type(idx); Some(ty) } /// Returns the [`ExternType`] for a given [`ExternIdx`]. /// /// # Note /// /// This function assumes that the given [`ExternType`] is valid. fn get_extern_type(&self, idx: ExternIdx) -> ExternType { let header = self.module_header(); match idx { ExternIdx::Func(index) => { let dedup = &header.funcs[index.into_u32() as usize]; let func_type = self.engine().resolve_func_type(dedup, Clone::clone); ExternType::Func(func_type) } ExternIdx::Table(index) => { let table_type = header.tables[index.into_u32() as usize]; ExternType::Table(table_type) } ExternIdx::Memory(index) => { let memory_type = header.memories[index.into_u32() as usize]; ExternType::Memory(memory_type) } ExternIdx::Global(index) => { let global_type = header.globals[index.into_u32() as usize]; ExternType::Global(global_type) } } } /// Returns an iterator yielding the custom sections of the Wasm [`Module`]. /// /// # Note /// /// The returned iterator will yield no items if [`Config::ignore_custom_sections`] /// is set to `true` even if the original Wasm module contains custom sections. /// /// /// [`Config::ignore_custom_sections`]: crate::Config::ignore_custom_sections #[inline] pub fn custom_sections(&self) -> CustomSectionsIter<'_> { self.inner.custom_sections.iter() } } /// An iterator over the imports of a [`Module`]. #[derive(Debug)] pub struct ModuleImportsIter<'a> { engine: &'a Engine, names: SliceIter<'a, Imported>, funcs: SliceIter<'a, DedupFuncType>, tables: SliceIter<'a, TableType>, memories: SliceIter<'a, MemoryType>, globals: SliceIter<'a, GlobalType>, } impl<'a> Iterator for ModuleImportsIter<'a> { type Item = ImportType<'a>; fn next(&mut self) -> Option { let import = match self.names.next()? { Imported::Func(name) => { let func_type = self.funcs.next().unwrap_or_else(|| { panic!("unexpected missing imported function for {name:?}") }); let func_type = self.engine.resolve_func_type(func_type, FuncType::clone); ImportType::new(name, func_type) } Imported::Table(name) => { let table_type = self.tables.next().unwrap_or_else(|| { panic!("unexpected missing imported table for {name:?}") }); ImportType::new(name, *table_type) } Imported::Memory(name) => { let memory_type = self.memories.next().unwrap_or_else(|| { panic!("unexpected missing imported linear memory for {name:?}") }); ImportType::new(name, *memory_type) } Imported::Global(name) => { let global_type = self.globals.next().unwrap_or_else(|| { panic!("unexpected missing imported global variable for {name:?}") }); ImportType::new(name, *global_type) } }; Some(import) } fn size_hint(&self) -> (usize, Option) { self.names.size_hint() } } impl ExactSizeIterator for ModuleImportsIter<'_> { fn len(&self) -> usize { ExactSizeIterator::len(&self.names) } } /// A descriptor for an imported value into a Wasm [`Module`]. /// /// This type is primarily accessed from the [`Module::imports`] method. /// Each [`ImportType`] describes an import into the Wasm module with the `module/name` /// that it is imported from as well as the type of item that is being imported. #[derive(Debug)] pub struct ImportType<'module> { /// The name of the imported item. name: &'module ImportName, /// The external item type. ty: ExternType, } impl<'module> ImportType<'module> { /// Creates a new [`ImportType`]. pub(crate) fn new(name: &'module ImportName, ty: T) -> Self where T: Into, { Self { name, ty: ty.into(), } } /// Returns the import name. pub(crate) fn import_name(&self) -> &ImportName { self.name } /// Returns the module import name. pub fn module(&self) -> &'module str { self.name.module() } /// Returns the field import name. pub fn name(&self) -> &'module str { self.name.name() } /// Returns the import item type. pub fn ty(&self) -> &ExternType { &self.ty } } /// An iterator over the internally defined functions of a [`Module`]. #[derive(Debug)] pub struct InternalFuncsIter<'a> { iter: iter::Zip, EngineFuncSpanIter>, } impl Iterator for InternalFuncsIter<'_> { type Item = (DedupFuncType, EngineFunc); fn next(&mut self) -> Option { self.iter .next() .map(|(func_type, engine_func)| (*func_type, engine_func)) } fn size_hint(&self) -> (usize, Option) { self.iter.size_hint() } } impl ExactSizeIterator for InternalFuncsIter<'_> { fn len(&self) -> usize { ExactSizeIterator::len(&self.iter) } } /// An iterator over the internally defined functions of a [`Module`]. #[derive(Debug)] pub struct InternalGlobalsIter<'a> { iter: iter::Zip, SliceIter<'a, ConstExpr>>, } impl<'a> Iterator for InternalGlobalsIter<'a> { type Item = (&'a GlobalType, &'a ConstExpr); fn next(&mut self) -> Option { self.iter.next() } fn size_hint(&self) -> (usize, Option) { self.iter.size_hint() } } impl ExactSizeIterator for InternalGlobalsIter<'_> { fn len(&self) -> usize { ExactSizeIterator::len(&self.iter) } } wasmi-1.1.0/src/module/parser/buffered.rs000064400000000000000000000126061046102023000164500ustar 00000000000000use super::{ModuleBuilder, ModuleParser}; use crate::{Error, Module}; use wasmparser::{Chunk, Payload, Validator}; impl ModuleParser { /// Starts parsing and validating the Wasm bytecode stream. /// /// Returns the compiled and validated Wasm [`Module`] upon success. /// /// # Errors /// /// If the Wasm bytecode stream fails to validate. pub fn parse_buffered(mut self, buffer: &[u8]) -> Result { let features = self.engine.config().wasm_features(); self.validator = Some(Validator::new_with_features(features)); // SAFETY: we just pre-populated the Wasm module parser with a validator // thus calling this method is safe. unsafe { self.parse_buffered_impl(buffer) } } /// Starts parsing and validating the Wasm bytecode stream. /// /// Returns the compiled and validated Wasm [`Module`] upon success. /// /// # Safety /// /// The caller is responsible to make sure that the provided /// `stream` yields valid WebAssembly bytecode. /// /// # Errors /// /// If the Wasm bytecode stream fails to validate. pub unsafe fn parse_buffered_unchecked(self, buffer: &[u8]) -> Result { unsafe { self.parse_buffered_impl(buffer) } } /// Starts parsing and validating the Wasm bytecode stream. /// /// Returns the compiled and validated Wasm [`Module`] upon success. /// /// # Safety /// /// The caller is responsible to either /// /// 1) Populate the [`ModuleParser`] with a [`Validator`] prior to calling this method, OR; /// 2) Make sure that the provided `stream` yields valid WebAssembly bytecode. /// /// Otherwise this method has undefined behavior. /// /// # Errors /// /// If the Wasm bytecode stream fails to validate. unsafe fn parse_buffered_impl(mut self, mut buffer: &[u8]) -> Result { let mut builder = ModuleBuilder::new(&self.engine); self.parse_module(&mut buffer, &mut builder)?; Ok(builder.finish()) } /// Fetch next Wasm module payload and adust the `buffer`. /// /// # Errors /// /// If the parsed Wasm is malformed. fn next_payload<'a>(&mut self, buffer: &mut &'a [u8]) -> Result<(usize, Payload<'a>), Error> { match self.parser.parse(&buffer[..], true)? { Chunk::Parsed { consumed, payload } => Ok((consumed, payload)), Chunk::NeedMoreData(_hint) => { // This is not possible since `eof` is always true. unreachable!() } } } /// Consumes the parts of the buffer that have been processed. fn consume_buffer<'a>(consumed: usize, buffer: &mut &'a [u8]) -> &'a [u8] { let (consumed, remaining) = buffer.split_at(consumed); *buffer = remaining; consumed } /// Parse the Wasm module header. /// /// - The Wasm module header is the set of all sections that appear before /// the Wasm code section. /// - We separate parsing of the Wasm module header since the information of /// the Wasm module header is required for translating the Wasm code section. /// /// # Errors /// /// If the Wasm bytecode stream fails to parse or validate. fn parse_module( &mut self, buffer: &mut &[u8], module: &mut ModuleBuilder, ) -> Result<(), Error> { loop { let (consumed, payload) = self.next_payload(buffer)?; match payload { Payload::Version { num, encoding, range, } => self.process_version(num, encoding, range), Payload::TypeSection(section) => self.process_types(section, module), Payload::ImportSection(section) => self.process_imports(section, module), Payload::FunctionSection(section) => self.process_functions(section, module), Payload::TableSection(section) => self.process_tables(section, module), Payload::MemorySection(section) => self.process_memories(section, module), Payload::GlobalSection(section) => self.process_globals(section, module), Payload::ExportSection(section) => self.process_exports(section, module), Payload::StartSection { func, range } => self.process_start(func, range, module), Payload::ElementSection(section) => self.process_element(section, module), Payload::DataCountSection { count, range } => self.process_data_count(count, range), Payload::CodeSectionStart { count, range, size } => { self.process_code_start(count, range, size) } Payload::CodeSectionEntry(func_body) => { let bytes = func_body.as_bytes(); self.process_code_entry(func_body, bytes, module) } Payload::DataSection(section) => self.process_data(section, module), Payload::End(offset) => { self.process_end(offset)?; break; } Payload::CustomSection(reader) => self.process_custom_section(reader, module), unexpected => self.process_invalid_payload(unexpected), }?; Self::consume_buffer(consumed, buffer); } Ok(()) } } wasmi-1.1.0/src/module/parser/mod.rs000064400000000000000000000404461046102023000154500ustar 00000000000000use super::{ export::ExternIdx, global::Global, import::{FuncTypeIdx, Import}, utils::FromWasmparser as _, ElementSegment, FuncIdx, ModuleBuilder, }; use crate::{ engine::{EnforcedLimitsError, EngineFunc}, Engine, Error, FuncType, MemoryType, TableType, }; use alloc::boxed::Box; use core::ops::Range; use wasmparser::{ CustomSectionReader, DataSectionReader, ElementSectionReader, Encoding, ExportSectionReader, FunctionBody, FunctionSectionReader, GlobalSectionReader, ImportSectionReader, MemorySectionReader, Parser as WasmParser, Payload, TableSectionReader, TypeSectionReader, Validator, }; #[cfg(doc)] use crate::Module; mod buffered; /// Context used to construct a WebAssembly module from a stream of bytes. pub struct ModuleParser { /// The engine used for translation. engine: Engine, /// The Wasm validator used throughout stream parsing. validator: Option, /// The underlying Wasm parser. parser: WasmParser, /// The number of compiled or processed functions. engine_funcs: u32, } impl ModuleParser { /// Creates a new [`ModuleParser`] for the given [`Engine`]. pub fn new(engine: &Engine) -> Self { let mut parser = WasmParser::new(0); parser.set_features(engine.config().wasm_features()); Self { engine: engine.clone(), validator: None, parser, engine_funcs: 0, } } /// Processes the end of the Wasm binary. fn process_end(&mut self, offset: usize) -> Result<(), Error> { if let Some(validator) = &mut self.validator { // This only checks if the number of code section entries and data segments match // their expected numbers thus we must avoid this check in header-only mode because // otherwise we will receive errors for unmatched data section entries. validator.end(offset)?; } Ok(()) } /// Validates the Wasm version section. fn process_version( &mut self, num: u16, encoding: Encoding, range: Range, ) -> Result<(), Error> { if let Some(validator) = &mut self.validator { validator.version(num, encoding, &range)?; } Ok(()) } /// Processes the Wasm type section. /// /// # Note /// /// This extracts all function types into the [`Module`] under construction. /// /// # Errors /// /// If an unsupported function type is encountered. fn process_types( &mut self, section: TypeSectionReader, module: &mut ModuleBuilder, ) -> Result<(), Error> { if let Some(validator) = &mut self.validator { validator.type_section(§ion)?; } let limits = self.engine.config().get_enforced_limits(); let func_types = section.into_iter().map(|result| { let ty = result?.into_types().next().unwrap(); let func_ty = ty.unwrap_func(); if let Some(limit) = limits.max_params { if func_ty.params().len() > limit { return Err(Error::from(EnforcedLimitsError::TooManyParameters { limit, })); } } if let Some(limit) = limits.max_results { if func_ty.results().len() > limit { return Err(Error::from(EnforcedLimitsError::TooManyResults { limit })); } } Ok(FuncType::from_wasmparser(func_ty)) }); module.push_func_types(func_types)?; Ok(()) } /// Processes the Wasm import section. /// /// # Note /// /// This extracts all imports into the [`Module`] under construction. /// /// # Errors /// /// - If an import fails to validate. /// - If an unsupported import declaration is encountered. fn process_imports( &mut self, section: ImportSectionReader, module: &mut ModuleBuilder, ) -> Result<(), Error> { if let Some(validator) = &mut self.validator { validator.import_section(§ion)?; } let imports = section .into_iter() .map(|import| import.map(Import::from).map_err(Error::from)); module.push_imports(imports)?; Ok(()) } /// Process module function declarations. /// /// # Note /// /// This extracts all function declarations into the [`Module`] under construction. /// /// # Errors /// /// If a function declaration fails to validate. fn process_functions( &mut self, section: FunctionSectionReader, module: &mut ModuleBuilder, ) -> Result<(), Error> { if let Some(limit) = self.engine.config().get_enforced_limits().max_functions { if section.count() > limit { return Err(Error::from(EnforcedLimitsError::TooManyFunctions { limit })); } } if let Some(validator) = &mut self.validator { validator.function_section(§ion)?; } let funcs = section .into_iter() .map(|func| func.map(FuncTypeIdx::from).map_err(Error::from)); module.push_funcs(funcs)?; Ok(()) } /// Process module table declarations. /// /// # Note /// /// This extracts all table declarations into the [`Module`] under construction. /// /// # Errors /// /// If a table declaration fails to validate. fn process_tables( &mut self, section: TableSectionReader, module: &mut ModuleBuilder, ) -> Result<(), Error> { if let Some(limit) = self.engine.config().get_enforced_limits().max_tables { if section.count() > limit { return Err(Error::from(EnforcedLimitsError::TooManyTables { limit })); } } if let Some(validator) = &mut self.validator { validator.table_section(§ion)?; } let tables = section.into_iter().map(|table| match table { Ok(table) => { assert!(matches!(table.init, wasmparser::TableInit::RefNull)); Ok(TableType::from_wasmparser(table.ty)) } Err(err) => Err(err.into()), }); module.push_tables(tables)?; Ok(()) } /// Process module linear memory declarations. /// /// # Note /// /// This extracts all linear memory declarations into the [`Module`] under construction. /// /// # Errors /// /// If a linear memory declaration fails to validate. fn process_memories( &mut self, section: MemorySectionReader, module: &mut ModuleBuilder, ) -> Result<(), Error> { if let Some(limit) = self.engine.config().get_enforced_limits().max_memories { if section.count() > limit { return Err(Error::from(EnforcedLimitsError::TooManyMemories { limit })); } } if let Some(validator) = &mut self.validator { validator.memory_section(§ion)?; } let memories = section .into_iter() .map(|memory| memory.map(MemoryType::from_wasmparser).map_err(Error::from)); module.push_memories(memories)?; Ok(()) } /// Process module global variable declarations. /// /// # Note /// /// This extracts all global variable declarations into the [`Module`] under construction. /// /// # Errors /// /// If a global variable declaration fails to validate. fn process_globals( &mut self, section: GlobalSectionReader, module: &mut ModuleBuilder, ) -> Result<(), Error> { if let Some(limit) = self.engine.config().get_enforced_limits().max_globals { if section.count() > limit { return Err(Error::from(EnforcedLimitsError::TooManyGlobals { limit })); } } if let Some(validator) = &mut self.validator { validator.global_section(§ion)?; } let globals = section .into_iter() .map(|global| global.map(Global::from).map_err(Error::from)); module.push_globals(globals)?; Ok(()) } /// Process module export declarations. /// /// # Note /// /// This extracts all export declarations into the [`Module`] under construction. /// /// # Errors /// /// If an export declaration fails to validate. fn process_exports( &mut self, section: ExportSectionReader, module: &mut ModuleBuilder, ) -> Result<(), Error> { if let Some(validator) = &mut self.validator { validator.export_section(§ion)?; } let exports = section.into_iter().map(|export| { let export = export?; let field: Box = export.name.into(); let idx = ExternIdx::new(export.kind, export.index)?; Ok((field, idx)) }); module.push_exports(exports)?; Ok(()) } /// Process module start section. /// /// # Note /// /// This sets the start function for the [`Module`] under construction. /// /// # Errors /// /// If the start function declaration fails to validate. fn process_start( &mut self, func: u32, range: Range, module: &mut ModuleBuilder, ) -> Result<(), Error> { if let Some(validator) = &mut self.validator { validator.start_section(func, &range)?; } module.set_start(FuncIdx::from(func)); Ok(()) } /// Process module table element segments. /// /// # Note /// /// This extracts all table element segments into the [`Module`] under construction. /// /// # Errors /// /// If any of the table element segments fail to validate. fn process_element( &mut self, section: ElementSectionReader, module: &mut ModuleBuilder, ) -> Result<(), Error> { if let Some(limit) = self .engine .config() .get_enforced_limits() .max_element_segments { if section.count() > limit { return Err(Error::from(EnforcedLimitsError::TooManyElementSegments { limit, })); } } if let Some(validator) = &mut self.validator { validator.element_section(§ion)?; } let segments = section .into_iter() .map(|segment| segment.map(ElementSegment::from).map_err(Error::from)); module.push_element_segments(segments)?; Ok(()) } /// Process module data count section. /// /// # Note /// /// This is part of the bulk memory operations Wasm proposal and not yet supported /// by Wasmi. fn process_data_count(&mut self, count: u32, range: Range) -> Result<(), Error> { if let Some(limit) = self.engine.config().get_enforced_limits().max_data_segments { if count > limit { return Err(Error::from(EnforcedLimitsError::TooManyDataSegments { limit, })); } } if let Some(validator) = &mut self.validator { validator.data_count_section(count, &range)?; } Ok(()) } /// Process module linear memory data segments. /// /// # Note /// /// This extracts all table elements into the [`Module`] under construction. /// /// # Errors /// /// If any of the table elements fail to validate. fn process_data( &mut self, section: DataSectionReader, builder: &mut ModuleBuilder, ) -> Result<(), Error> { if let Some(limit) = self.engine.config().get_enforced_limits().max_data_segments { if section.count() > limit { return Err(Error::from(EnforcedLimitsError::TooManyDataSegments { limit, })); } } if let Some(validator) = &mut self.validator { // Note: data section does not belong to the Wasm module header. // // Also benchmarks show that validation of the data section can be very costly. validator.data_section(§ion)?; } builder.reserve_data_segments(section.count() as usize); for segment in section { builder.push_data_segment(segment?)?; } Ok(()) } /// Process module code section start. /// /// # Note /// /// This currently does not do a lot but it might become important in the /// future if we add parallel translation of function bodies to prepare for /// the translation. /// /// # Errors /// /// If the code start section fails to validate. fn process_code_start( &mut self, count: u32, range: Range, size: u32, ) -> Result<(), Error> { let enforced_limits = self.engine.config().get_enforced_limits(); if let Some(limit) = enforced_limits.max_functions { if count > limit { return Err(Error::from(EnforcedLimitsError::TooManyFunctions { limit })); } } if let Some(limit) = enforced_limits.min_avg_bytes_per_function { if size >= limit.req_funcs_bytes { let limit = limit.min_avg_bytes_per_function; let avg = size / count; if avg < limit { return Err(Error::from(EnforcedLimitsError::MinAvgBytesPerFunction { limit, avg, })); } } } if let Some(validator) = &mut self.validator { validator.code_section_start(&range)?; } Ok(()) } /// Returns the next `FuncIdx` for processing of its function body. fn next_func(&mut self, module: &ModuleBuilder) -> (FuncIdx, EngineFunc) { let index = self.engine_funcs; let engine_func = module.engine_funcs.get_or_panic(index); self.engine_funcs += 1; // We have to adjust the initial func reference to the first // internal function before we process any of the internal functions. let len_func_imports = u32::try_from(module.len_funcs_imports()) .unwrap_or_else(|_| panic!("too many imported functions")); let func_idx = FuncIdx::from(index + len_func_imports); (func_idx, engine_func) } /// Process a single module code section entry. /// /// # Note /// /// This contains the local variables and Wasm instructions of /// a single function body. /// This procedure is translating the Wasm bytecode into Wasmi bytecode. /// /// # Errors /// /// If the function body fails to validate. fn process_code_entry( &mut self, func_body: FunctionBody, bytes: &[u8], module: &mut ModuleBuilder, ) -> Result<(), Error> { let (func, engine_func) = self.next_func(module); let offset = func_body.get_binary_reader().original_position(); let func_to_validate = match &mut self.validator { Some(validator) => Some(validator.code_section_entry(&func_body)?), None => None, }; let header = module.header(); self.engine .translate_func(func, engine_func, offset, bytes, header, func_to_validate)?; Ok(()) } /// Process a single Wasm custom section. fn process_custom_section( &mut self, reader: CustomSectionReader, module: &mut ModuleBuilder, ) -> Result<(), Error> { if self.engine.config().get_ignore_custom_sections() { return Ok(()); } module.custom_sections.push(reader.name(), reader.data()); Ok(()) } /// Process an unexpected, unsupported or malformed Wasm module section payload. fn process_invalid_payload(&mut self, payload: Payload<'_>) -> Result<(), Error> { if let Some(validator) = &mut self.validator { if let Err(error) = validator.payload(&payload) { return Err(Error::from(error)); } } panic!("encountered unsupported, unexpected or malformed Wasm payload: {payload:?}") } } wasmi-1.1.0/src/module/read.rs000064400000000000000000000042101046102023000142750ustar 00000000000000use core::{ error::Error, fmt::{self, Display}, }; #[cfg(feature = "std")] use std::io; /// Errors returned by [`Read::read`]. #[derive(Debug, PartialEq, Eq)] pub enum ReadError { /// The source has reached the end of the stream. EndOfStream, /// An unknown error occurred. UnknownError, } impl Error for ReadError {} impl Display for ReadError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ReadError::EndOfStream => write!(f, "encountered unexpected end of stream"), ReadError::UnknownError => write!(f, "encountered unknown error"), } } } /// Types implementing this trait act as byte streams. /// /// # Note /// /// Provides a subset of the interface provided by Rust's [`std::io::Read`][std_io_read] trait. /// /// [`Module::new`]: [`crate::Module::new`] /// [std_io_read]: https://doc.rust-lang.org/std/io/trait.Read.html pub trait Read { /// Pull some bytes from this source into the specified buffer, returning how many bytes were read. /// /// # Note /// /// Provides the same guarantees to the caller as [`std::io::Read::read`][io_read_read]. /// /// [io_read_read]: https://doc.rust-lang.org/std/io/trait.Read.html#tymethod.read /// /// # Errors /// /// - If `self` stream is already at its end. /// - For any unknown error returned by the generic [`Read`] implementer. fn read(&mut self, buf: &mut [u8]) -> Result; } #[cfg(feature = "std")] impl Read for T where T: io::Read, { fn read(&mut self, buffer: &mut [u8]) -> Result { ::read(self, buffer).map_err(|error| match error.kind() { io::ErrorKind::UnexpectedEof => ReadError::EndOfStream, _ => ReadError::UnknownError, }) } } #[cfg(not(feature = "std"))] impl Read for &[u8] { fn read(&mut self, buffer: &mut [u8]) -> Result { let len_copy = self.len().min(buffer.len()); let (read, rest) = self.split_at(len_copy); buffer[..len_copy].copy_from_slice(read); *self = rest; Ok(len_copy) } } wasmi-1.1.0/src/module/utils.rs000064400000000000000000000136751046102023000145410ustar 00000000000000use crate::{FuncType, GlobalType, MemoryType, Mutability, TableType, ValType}; use wasmparser::AbstractHeapType; /// Types that can be created from `wasmparser` definitions. pub(crate) trait FromWasmparser { /// Create `Self` from the `wasmparser` definition. /// /// # Panics /// /// If creation of `Self` is not possible. fn from_wasmparser(value: T) -> Self; } impl FromWasmparser for TableType { /// Creates a new [`TableType`] from the given `wasmparser` primitive. /// /// # Dev. Note /// /// We do not use the `From` trait here so that this conversion /// routine does not become part of the public API of [`TableType`]. fn from_wasmparser(table_type: wasmparser::TableType) -> Self { let element = WasmiValueType::from(table_type.element_type).into_inner(); let minimum: u64 = table_type.initial; let maximum: Option = table_type.maximum; match table_type.table64 { true => Self::new64(element, minimum, maximum), false => { let Ok(minimum) = u32::try_from(minimum) else { panic!("invalid 32-bit table.minimum: {minimum}") }; let Ok(maximum) = maximum.map(u32::try_from).transpose() else { panic!("invalid 32-bit table.maximum: {maximum:?}") }; Self::new(element, minimum, maximum) } } } } impl FromWasmparser for MemoryType { /// Creates a new [`MemoryType`] from the given `wasmparser` primitive. /// /// # Dev. Note /// /// We do not use the `From` trait here so that this conversion /// routine does not become part of the public API of [`MemoryType`]. fn from_wasmparser(memory_type: wasmparser::MemoryType) -> Self { assert!( !memory_type.shared, "wasmi does not support the `threads` Wasm proposal" ); let mut b = Self::builder(); b.min(memory_type.initial); b.max(memory_type.maximum); b.memory64(memory_type.memory64); if let Some(page_size_log2) = memory_type.page_size_log2 { let Ok(page_size_log2) = u8::try_from(page_size_log2) else { panic!("page size (in log2) must be a valid `u8` if any"); }; b.page_size_log2(page_size_log2); } b.build() .unwrap_or_else(|err| panic!("received invalid `MemoryType` from `wasmparser`: {err}")) } } impl FromWasmparser for GlobalType { /// Creates a new [`GlobalType`] from the given `wasmparser` primitive. /// /// # Dev. Note /// /// We do not use the `From` trait here so that this conversion /// routine does not become part of the public API of [`GlobalType`]. fn from_wasmparser(global_type: wasmparser::GlobalType) -> Self { let value_type = WasmiValueType::from(global_type.content_type).into_inner(); let mutability = match global_type.mutable { true => Mutability::Var, false => Mutability::Const, }; Self::new(value_type, mutability) } } impl FromWasmparser<&wasmparser::FuncType> for FuncType { /// Creates a new [`FuncType`] from the given `wasmparser` primitive. /// /// # Dev. Note /// /// We do not use the `From` trait here so that this conversion /// routine does not become part of the public API of [`FuncType`]. fn from_wasmparser(func_type: &wasmparser::FuncType) -> Self { /// Returns the [`ValType`] from the given [`wasmparser::Type`]. /// /// # Panics /// /// If the [`wasmparser::Type`] is not supported by Wasmi. fn extract_value_type(value_type: &wasmparser::ValType) -> ValType { WasmiValueType::from(*value_type).into_inner() } let params = func_type.params().iter().map(extract_value_type); let results = func_type.results().iter().map(extract_value_type); Self::new(params, results) } } /// A Wasmi [`ValType`]. /// /// # Note /// /// This new-type wrapper exists so that we can implement the `From` trait. pub struct WasmiValueType { inner: ValType, } impl WasmiValueType { /// Returns the inner [`ValType`]. pub fn into_inner(self) -> ValType { self.inner } } impl From for WasmiValueType { fn from(value: ValType) -> Self { Self { inner: value } } } impl From for WasmiValueType { fn from(heap_type: wasmparser::HeapType) -> Self { match heap_type { wasmparser::HeapType::Abstract { shared: false, ty: AbstractHeapType::Func, } => Self::from(ValType::FuncRef), wasmparser::HeapType::Abstract { shared: false, ty: AbstractHeapType::Extern, } => Self::from(ValType::ExternRef), unsupported => panic!("encountered unsupported heap type: {unsupported:?}"), } } } impl From for WasmiValueType { fn from(ref_type: wasmparser::RefType) -> Self { match ref_type { wasmparser::RefType::FUNCREF => Self::from(ValType::FuncRef), wasmparser::RefType::EXTERNREF => Self::from(ValType::ExternRef), unsupported => panic!("encountered unsupported reference type: {unsupported:?}"), } } } impl From for WasmiValueType { fn from(value_type: wasmparser::ValType) -> Self { match value_type { wasmparser::ValType::I32 => Self::from(ValType::I32), wasmparser::ValType::I64 => Self::from(ValType::I64), wasmparser::ValType::F32 => Self::from(ValType::F32), wasmparser::ValType::F64 => Self::from(ValType::F64), wasmparser::ValType::V128 => Self::from(ValType::V128), wasmparser::ValType::Ref(ref_type) => WasmiValueType::from(ref_type), } } } wasmi-1.1.0/src/reftype.rs000064400000000000000000000176511046102023000135700ustar 00000000000000use crate::{ collections::arena::ArenaIndex, core::{ReadAs, UntypedVal, WriteAs}, store::Stored, AsContextMut, Func, StoreContext, }; use alloc::boxed::Box; use core::{any::Any, mem, num::NonZeroU32}; /// A nullable reference type. #[derive(Debug, Default, Copy, Clone)] pub enum Ref { /// The [`Ref`] is a non-`null` value. Val(T), /// The [`Ref`] is `null`. #[default] Null, } impl Ref { /// Returns `true` is `self` is null. pub fn is_null(&self) -> bool { matches!(self, Self::Null) } /// Returns `Some` if `self` is a non-`null` value. /// /// Otherwise returns `None`. pub fn val(&self) -> Option<&T> { match self { Ref::Val(val) => Some(val), Ref::Null => None, } } /// Converts from `&Ref` to `Ref<&T>`. pub fn as_ref(&self) -> Ref<&T> { match self { Ref::Val(val) => Ref::Val(val), Ref::Null => Ref::Null, } } } impl From for Ref { fn from(value: T) -> Self { Self::Val(value) } } /// A raw index to an external entity. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct ExternRefIdx(NonZeroU32); impl ArenaIndex for ExternRefIdx { fn into_usize(self) -> usize { self.0.get().wrapping_sub(1) as usize } fn from_usize(index: usize) -> Self { index .try_into() .ok() .map(|index: u32| index.wrapping_add(1)) .and_then(NonZeroU32::new) .map(Self) .unwrap_or_else(|| panic!("out of bounds extern object index {index}")) } } /// An externally defined object. #[derive(Debug)] pub struct ExternRefEntity { inner: Box, } impl ExternRefEntity { /// Creates a new instance of `ExternRef` wrapping the given value. pub fn new(object: T) -> Self where T: 'static + Any + Send + Sync, { Self { inner: Box::new(object), } } /// Returns a shared reference to the external object. pub fn data(&self) -> &dyn Any { &*self.inner } } /// Represents an opaque reference to any data within WebAssembly. #[derive(Debug, Copy, Clone)] #[repr(transparent)] pub struct ExternRef(Stored); impl ExternRef { /// Creates a new [`ExternRef`] reference from its raw representation. pub(crate) fn from_inner(stored: Stored) -> Self { Self(stored) } /// Returns the raw representation of the [`ExternRef`]. pub(crate) fn as_inner(&self) -> &Stored { &self.0 } /// Creates a new instance of `ExternRef` wrapping the given value. pub fn new(mut ctx: impl AsContextMut, object: T) -> Self where T: 'static + Any + Send + Sync, { ctx.as_context_mut() .store .inner .alloc_extern_object(ExternRefEntity::new(object)) } /// Returns a shared reference to the underlying data for this [`ExternRef`]. /// /// # Panics /// /// Panics if `ctx` does not own this [`ExternRef`]. pub fn data<'a, T: 'a>(&self, ctx: impl Into>) -> &'a dyn Any { ctx.into().store.inner.resolve_externref(self).data() } } #[test] fn externref_sizeof() { // These assertions are important in order to convert `FuncRef` // from and to 64-bit `UntypedValue` instances. // // The following equation must be true: // size_of(ExternRef) == size_of(ExternObject) == size_of(UntypedValue) use core::mem::size_of; assert_eq!(size_of::(), size_of::()); assert_eq!(size_of::(), size_of::()); } #[test] fn externref_null_to_zero() { assert_eq!( UntypedVal::from(>::Null), UntypedVal::from(0) ); assert!(>::from(UntypedVal::from(0)).is_null()); } #[test] fn funcref_sizeof() { // These assertions are important in order to convert `FuncRef` // from and to 64-bit `UntypedValue` instances. // // The following equation must be true: // size_of(Func) == size_of(UntypedValue) == size_of(FuncRef) use crate::Func; use core::mem::size_of; assert_eq!(size_of::(), size_of::()); assert_eq!(size_of::(), size_of::>()); } #[test] fn funcref_null_to_zero() { use crate::Func; assert_eq!(UntypedVal::from(>::Null), UntypedVal::from(0)); assert!(>::from(UntypedVal::from(0)).is_null()); } macro_rules! impl_conversions { ( $( $reftype:ty ),* $(,)? ) => { $( impl ReadAs<$reftype> for UntypedVal { fn read_as(&self) -> $reftype { let bits = u64::from(*self); unsafe { mem::transmute::(bits) } } } impl ReadAs> for UntypedVal { fn read_as(&self) -> Ref<$reftype> { let bits = u64::from(*self); if bits == 0 { return >::Null; } >::Val(>::read_as(self)) } } impl WriteAs<$reftype> for UntypedVal { fn write_as(&mut self, value: $reftype) { let bits = unsafe { mem::transmute::<$reftype, u64>(value) }; self.write_as(bits) } } impl WriteAs> for UntypedVal { fn write_as(&mut self, value: Ref<$reftype>) { match value { Ref::Null => self.write_as(0_u64), Ref::Val(value) => self.write_as(value), } } } impl From for Ref<$reftype> { fn from(untyped: UntypedVal) -> Self { if u64::from(untyped) == 0 { return >::Null; } // Safety: This operation is safe since there are no invalid // bit patterns for [`ExternRef`] instances. Therefore // this operation cannot produce invalid [`ExternRef`] // instances even though the input [`UntypedVal`] // was modified arbitrarily. unsafe { mem::transmute::(untyped.into()) } } } impl From<$reftype> for UntypedVal { fn from(reftype: $reftype) -> Self { // Safety: This operation is safe since there are no invalid // bit patterns for [`UntypedVal`] instances. Therefore // this operation cannot produce invalid [`UntypedVal`] // instances even if it was possible to arbitrarily modify // the input `$reftype` instance. let bits = unsafe { mem::transmute::<$reftype, u64>(reftype) }; UntypedVal::from(bits) } } impl From> for UntypedVal { fn from(reftype: Ref<$reftype>) -> Self { match reftype { Ref::Val(reftype) => UntypedVal::from(reftype), Ref::Null => UntypedVal::from(0_u64), } } } )* }; } impl_conversions! { ExternRef, Func, } #[cfg(test)] mod tests { use super::*; use crate::{Engine, Store}; #[test] fn it_works() { let engine = Engine::default(); let mut store = >::new(&engine, ()); let value = 42_i32; let obj = ExternRef::new::(&mut store, value); assert_eq!(obj.data(&store).downcast_ref::(), Some(&value),); } } wasmi-1.1.0/src/store/context.rs000064400000000000000000000116701046102023000147250ustar 00000000000000use crate::{Engine, Error, Store}; /// A trait used to get shared access to a [`Store`] in Wasmi. pub trait AsContext { /// The user state associated with the [`Store`], aka the `T` in `Store`. type Data; /// Returns the store context that this type provides access to. fn as_context(&self) -> StoreContext<'_, Self::Data>; } /// A trait used to get exclusive access to a [`Store`] in Wasmi. pub trait AsContextMut: AsContext { /// Returns the store context that this type provides access to. fn as_context_mut(&mut self) -> StoreContextMut<'_, Self::Data>; } /// A temporary handle to a [`&Store`][`Store`]. /// /// This type is suitable for [`AsContext`] trait bounds on methods if desired. /// For more information, see [`Store`]. #[derive(Debug, Copy, Clone)] #[repr(transparent)] pub struct StoreContext<'a, T> { pub(crate) store: &'a Store, } impl StoreContext<'_, T> { /// Returns the underlying [`Engine`] this store is connected to. pub fn engine(&self) -> &Engine { self.store.engine() } /// Access the underlying data owned by this store. /// /// Same as [`Store::data`]. pub fn data(&self) -> &T { self.store.data() } /// Returns the remaining fuel of the [`Store`] if fuel metering is enabled. /// /// For more information see [`Store::get_fuel`](crate::Store::get_fuel). /// /// # Errors /// /// If fuel metering is disabled. pub fn get_fuel(&self) -> Result { self.store.get_fuel() } } impl<'a, T: AsContext> From<&'a T> for StoreContext<'a, T::Data> { #[inline] fn from(ctx: &'a T) -> Self { ctx.as_context() } } impl<'a, T: AsContext> From<&'a mut T> for StoreContext<'a, T::Data> { #[inline] fn from(ctx: &'a mut T) -> Self { T::as_context(ctx) } } impl<'a, T: AsContextMut> From<&'a mut T> for StoreContextMut<'a, T::Data> { #[inline] fn from(ctx: &'a mut T) -> Self { ctx.as_context_mut() } } /// A temporary handle to a [`&mut Store`][`Store`]. /// /// This type is suitable for [`AsContextMut`] or [`AsContext`] trait bounds on methods if desired. /// For more information, see [`Store`]. #[derive(Debug)] #[repr(transparent)] pub struct StoreContextMut<'a, T> { pub(crate) store: &'a mut Store, } impl StoreContextMut<'_, T> { /// Returns the underlying [`Engine`] this store is connected to. pub fn engine(&self) -> &Engine { self.store.engine() } /// Access the underlying data owned by this store. /// /// Same as [`Store::data`]. pub fn data(&self) -> &T { self.store.data() } /// Access the underlying data owned by this store. /// /// Same as [`Store::data_mut`]. pub fn data_mut(&mut self) -> &mut T { self.store.data_mut() } /// Returns the remaining fuel of the [`Store`] if fuel metering is enabled. /// /// For more information see [`Store::get_fuel`](crate::Store::get_fuel). /// /// # Errors /// /// If fuel metering is disabled. pub fn get_fuel(&self) -> Result { self.store.get_fuel() } /// Sets the remaining fuel of the [`Store`] to `value` if fuel metering is enabled. /// /// For more information see [`Store::get_fuel`](crate::Store::set_fuel). /// /// # Errors /// /// If fuel metering is disabled. pub fn set_fuel(&mut self, fuel: u64) -> Result<(), Error> { self.store.set_fuel(fuel) } } impl AsContext for &'_ T where T: AsContext, { type Data = T::Data; #[inline] fn as_context(&self) -> StoreContext<'_, T::Data> { T::as_context(*self) } } impl AsContext for &'_ mut T where T: AsContext, { type Data = T::Data; #[inline] fn as_context(&self) -> StoreContext<'_, T::Data> { T::as_context(*self) } } impl AsContextMut for &'_ mut T where T: AsContextMut, { #[inline] fn as_context_mut(&mut self) -> StoreContextMut<'_, T::Data> { T::as_context_mut(*self) } } impl AsContext for StoreContext<'_, T> { type Data = T; #[inline] fn as_context(&self) -> StoreContext<'_, Self::Data> { StoreContext { store: self.store } } } impl AsContext for StoreContextMut<'_, T> { type Data = T; #[inline] fn as_context(&self) -> StoreContext<'_, Self::Data> { StoreContext { store: self.store } } } impl AsContextMut for StoreContextMut<'_, T> { #[inline] fn as_context_mut(&mut self) -> StoreContextMut<'_, Self::Data> { StoreContextMut { store: &mut *self.store, } } } impl AsContext for Store { type Data = T; #[inline] fn as_context(&self) -> StoreContext<'_, Self::Data> { StoreContext { store: self } } } impl AsContextMut for Store { #[inline] fn as_context_mut(&mut self) -> StoreContextMut<'_, Self::Data> { StoreContextMut { store: self } } } wasmi-1.1.0/src/store/error.rs000064400000000000000000000071061046102023000143710ustar 00000000000000use core::{any::type_name, fmt}; #[cfg(doc)] use super::{PrunedStore, Store, StoreInner}; /// An error occurred on [`Store`] or [`StoreInner`] methods. #[derive(Debug, Copy, Clone)] pub enum StoreError { /// An error representing an internal error, a.k.a. a bug or invalid behavior within Wasmi. Internal(InternalStoreError), /// An external error forwarded by the [`Store`] or [`StoreInner`]. External(E), } impl fmt::Display for StoreError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { StoreError::Internal(error) => fmt::Display::fmt(error, f), StoreError::External(error) => fmt::Display::fmt(error, f), } } } /// A [`Store`] or [`StoreInner`] internal error, a.k.a. bug or invalid behavior within Wasmi. #[derive(Debug, Copy, Clone)] pub struct InternalStoreError { kind: InternalStoreErrorKind, } impl InternalStoreError { /// Creates a new [`InternalStoreError`]. fn new(kind: InternalStoreErrorKind) -> Self { Self { kind } } /// An error indicating that a [`Store`] resource could not be found. #[cold] #[inline] pub fn not_found() -> Self { Self::new(InternalStoreErrorKind::EntityNotFound) } /// An error indicating that a [`Store`] resource does not originate from the store. #[cold] #[inline] pub fn store_mismatch() -> Self { Self::new(InternalStoreErrorKind::StoreMismatch) } /// An error indicating that restoring a [`PrunedStore`] to a [`Store`] mismatched `T`. #[cold] #[inline] pub fn restore_type_mismatch() -> Self { Self::new(InternalStoreErrorKind::RestoreTypeMismatch( RestoreTypeMismatchError::new::(), )) } } impl fmt::Display for InternalStoreError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let message = match &self.kind { InternalStoreErrorKind::RestoreTypeMismatch(error) => { return fmt::Display::fmt(error, f) } InternalStoreErrorKind::StoreMismatch => "store owner mismatch", InternalStoreErrorKind::EntityNotFound => "entity not found", }; write!(f, "failed to resolve entity: {message}") } } #[derive(Debug, Copy, Clone)] enum InternalStoreErrorKind { /// An error when restoring a [`PrunedStore`] with an incorrect `T` for [`Store`]. RestoreTypeMismatch(RestoreTypeMismatchError), /// An error indicating that a store resource does not originate from the given store. StoreMismatch, /// An error indicating that a store resource was not found. EntityNotFound, } impl StoreError { /// Create a new [`StoreError`] from the external `error`. pub fn external(error: E) -> Self { Self::External(error) } } impl From for StoreError { fn from(error: InternalStoreError) -> Self { Self::Internal(error) } } /// Error occurred when restoring a [`PrunedStore`] to a [`Store`] with an mismatching `T`. #[derive(Debug, Copy, Clone)] struct RestoreTypeMismatchError { type_name: fn() -> &'static str, } impl RestoreTypeMismatchError { /// Create a new [`RestoreTypeMismatchError`]. pub fn new() -> Self { Self { type_name: type_name::, } } } impl fmt::Display for RestoreTypeMismatchError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "failed to unprune store due to type mismatch: {}", (self.type_name)() ) } } wasmi-1.1.0/src/store/inner.rs000064400000000000000000000672111046102023000143560ustar 00000000000000use crate::{ collections::arena::{Arena, ArenaIndex, GuardedEntity}, core::{CoreElementSegment, CoreGlobal, CoreMemory, CoreTable, Fuel}, engine::DedupFuncType, memory::DataSegment, reftype::{ExternRef, ExternRefEntity, ExternRefIdx}, store::error::InternalStoreError, DataSegmentEntity, DataSegmentIdx, ElementSegment, ElementSegmentIdx, Engine, Error, Func, FuncEntity, FuncIdx, FuncType, Global, GlobalIdx, Instance, InstanceEntity, InstanceIdx, Memory, MemoryIdx, Table, TableIdx, }; use core::{ fmt::Debug, sync::atomic::{AtomicU32, Ordering}, }; /// A unique store index. /// /// # Note /// /// Used to protect against invalid entity indices. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct StoreIdx(u32); impl ArenaIndex for StoreIdx { fn into_usize(self) -> usize { self.0 as usize } fn from_usize(value: usize) -> Self { let value = value.try_into().unwrap_or_else(|error| { panic!("index {value} is out of bounds as store index: {error}") }); Self(value) } } impl StoreIdx { /// Returns a new unique [`StoreIdx`]. fn new() -> Self { /// A static store index counter. static CURRENT_STORE_IDX: AtomicU32 = AtomicU32::new(0); let next_idx = CURRENT_STORE_IDX.fetch_add(1, Ordering::AcqRel); Self(next_idx) } } /// A stored entity. pub type Stored = GuardedEntity; /// The inner store that owns all data not associated to the host state. #[derive(Debug)] pub struct StoreInner { /// The unique store index. /// /// Used to protect against invalid entity indices. store_idx: StoreIdx, /// Stored Wasm or host functions. funcs: Arena, /// Stored linear memories. memories: Arena, /// Stored tables. tables: Arena, /// Stored global variables. globals: Arena, /// Stored module instances. instances: Arena, /// Stored data segments. datas: Arena, /// Stored data segments. elems: Arena, /// Stored external objects for [`ExternRef`] types. /// /// [`ExternRef`]: [`crate::ExternRef`] extern_objects: Arena, /// The [`Engine`] in use by the [`StoreInner`]. /// /// Amongst others the [`Engine`] stores the Wasm function definitions. engine: Engine, /// The fuel of the [`StoreInner`]. pub(super) fuel: Fuel, } impl StoreInner { /// Creates a new [`StoreInner`] for the given [`Engine`]. pub fn new(engine: &Engine) -> Self { let config = engine.config(); let fuel_enabled = config.get_consume_fuel(); let fuel_costs = config.fuel_costs().clone(); let fuel = Fuel::new(fuel_enabled, fuel_costs); StoreInner { engine: engine.clone(), store_idx: StoreIdx::new(), funcs: Arena::new(), memories: Arena::new(), tables: Arena::new(), globals: Arena::new(), instances: Arena::new(), datas: Arena::new(), elems: Arena::new(), extern_objects: Arena::new(), fuel, } } /// Returns the [`Engine`] that this store is associated with. pub fn engine(&self) -> &Engine { &self.engine } /// Returns an exclusive reference to the [`Fuel`] counters. pub fn fuel_mut(&mut self) -> &mut Fuel { &mut self.fuel } /// Returns the remaining fuel of the [`StoreInner`] if fuel metering is enabled. /// /// # Note /// /// Enable fuel metering via [`Config::consume_fuel`](crate::Config::consume_fuel). /// /// # Errors /// /// If fuel metering is disabled. pub fn get_fuel(&self) -> Result { self.fuel.get_fuel().map_err(Error::from) } /// Sets the remaining fuel of the [`StoreInner`] to `value` if fuel metering is enabled. /// /// # Note /// /// Enable fuel metering via [`Config::consume_fuel`](crate::Config::consume_fuel). /// /// # Errors /// /// If fuel metering is disabled. pub fn set_fuel(&mut self, fuel: u64) -> Result<(), Error> { self.fuel.set_fuel(fuel).map_err(Error::from) } /// Returns the number of instances allocated to the [`StoreInner`]. pub fn len_instances(&self) -> usize { self.instances.len() } /// Returns the number of tables allocated to the [`StoreInner`]. pub fn len_tables(&self) -> usize { self.tables.len() } /// Returns the number of memories allocated to the [`StoreInner`]. pub fn len_memories(&self) -> usize { self.memories.len() } /// Wraps an entity `Idx` (index type) as a [`Stored`] type. /// /// # Note /// /// [`Stored`] associates an `Idx` type with the internal store index. /// This way wrapped indices cannot be misused with incorrect [`StoreInner`] instances. pub(super) fn wrap_stored(&self, entity_idx: Idx) -> Stored { Stored::new(self.store_idx, entity_idx) } /// Unwraps the given [`Stored`] reference and returns the `Idx`. /// /// # Errors /// /// If the [`Stored`] does not originate from this [`StoreInner`]. pub(super) fn unwrap_stored(&self, stored: &Stored) -> Result where Idx: ArenaIndex + Debug, { match stored.entity_index(self.store_idx) { Some(index) => Ok(index), None => Err(InternalStoreError::store_mismatch()), } } /// Allocates a new [`CoreGlobal`] and returns a [`Global`] reference to it. pub fn alloc_global(&mut self, global: CoreGlobal) -> Global { let global = self.globals.alloc(global); Global::from_inner(self.wrap_stored(global)) } /// Allocates a new [`CoreTable`] and returns a [`Table`] reference to it. pub fn alloc_table(&mut self, table: CoreTable) -> Table { let table = self.tables.alloc(table); Table::from_inner(self.wrap_stored(table)) } /// Allocates a new [`CoreMemory`] and returns a [`Memory`] reference to it. pub fn alloc_memory(&mut self, memory: CoreMemory) -> Memory { let memory = self.memories.alloc(memory); Memory::from_inner(self.wrap_stored(memory)) } /// Allocates a new [`DataSegmentEntity`] and returns a [`DataSegment`] reference to it. pub fn alloc_data_segment(&mut self, segment: DataSegmentEntity) -> DataSegment { let segment = self.datas.alloc(segment); DataSegment::from_inner(self.wrap_stored(segment)) } /// Allocates a new [`CoreElementSegment`] and returns a [`ElementSegment`] reference to it. pub fn alloc_element_segment(&mut self, segment: CoreElementSegment) -> ElementSegment { let segment = self.elems.alloc(segment); ElementSegment::from_inner(self.wrap_stored(segment)) } /// Allocates a new [`ExternRefEntity`] and returns a [`ExternRef`] reference to it. pub fn alloc_extern_object(&mut self, object: ExternRefEntity) -> ExternRef { let object = self.extern_objects.alloc(object); ExternRef::from_inner(self.wrap_stored(object)) } /// Allocates a new uninitialized [`InstanceEntity`] and returns an [`Instance`] reference to it. /// /// # Note /// /// - This will create an uninitialized dummy [`InstanceEntity`] as a place holder /// for the returned [`Instance`]. Using this uninitialized [`Instance`] will result /// in a runtime panic. /// - The returned [`Instance`] must later be initialized via the [`StoreInner::initialize_instance`] /// method. Afterwards the [`Instance`] may be used. pub fn alloc_instance(&mut self) -> Instance { let instance = self.instances.alloc(InstanceEntity::uninitialized()); Instance::from_inner(self.wrap_stored(instance)) } /// Initializes the [`Instance`] using the given [`InstanceEntity`]. /// /// # Note /// /// After this operation the [`Instance`] is initialized and can be used. /// /// # Panics /// /// - If the [`Instance`] does not belong to the [`StoreInner`]. /// - If the [`Instance`] is unknown to the [`StoreInner`]. /// - If the [`Instance`] has already been initialized. /// - If the given [`InstanceEntity`] is itself not initialized, yet. pub fn initialize_instance(&mut self, instance: Instance, init: InstanceEntity) { assert!( init.is_initialized(), "encountered an uninitialized new instance entity: {init:?}", ); let idx = match self.unwrap_stored(instance.as_inner()) { Ok(idx) => idx, Err(error) => panic!("failed to unwrap stored entity: {error}"), }; let uninit = self .instances .get_mut(idx) .unwrap_or_else(|| panic!("missing entity for the given instance: {instance:?}")); assert!( !uninit.is_initialized(), "encountered an already initialized instance: {uninit:?}", ); *uninit = init; } /// Returns a shared reference to the entity indexed by the given `idx`. /// /// # Errors /// /// - If the indexed entity does not originate from this [`StoreInner`]. /// - If the entity index cannot be resolved to its entity. fn resolve<'a, Idx, Entity>( &self, idx: &Stored, entities: &'a Arena, ) -> Result<&'a Entity, InternalStoreError> where Idx: ArenaIndex + Debug, { let idx = self.unwrap_stored(idx)?; match entities.get(idx) { Some(entity) => Ok(entity), None => Err(InternalStoreError::not_found()), } } /// Returns an exclusive reference to the entity indexed by the given `idx`. /// /// # Note /// /// Due to borrow checking issues this method takes an already unwrapped /// `Idx` unlike the [`StoreInner::resolve`] method. /// /// # Errors /// /// If the entity index cannot be resolved to its entity. fn resolve_mut( idx: Idx, entities: &mut Arena, ) -> Result<&mut Entity, InternalStoreError> where Idx: ArenaIndex + Debug, { match entities.get_mut(idx) { Some(entity) => Ok(entity), None => Err(InternalStoreError::not_found()), } } /// Returns the [`FuncType`] associated to the given [`DedupFuncType`]. /// /// # Panics /// /// - If the [`DedupFuncType`] does not originate from this [`StoreInner`]. /// - If the [`DedupFuncType`] cannot be resolved to its entity. pub fn resolve_func_type(&self, func_type: &DedupFuncType) -> FuncType { self.resolve_func_type_with(func_type, FuncType::clone) } /// Calls `f` on the [`FuncType`] associated to the given [`DedupFuncType`] and returns the result. /// /// # Panics /// /// - If the [`DedupFuncType`] does not originate from this [`StoreInner`]. /// - If the [`DedupFuncType`] cannot be resolved to its entity. pub fn resolve_func_type_with( &self, func_type: &DedupFuncType, f: impl FnOnce(&FuncType) -> R, ) -> R { self.engine.resolve_func_type(func_type, f) } /// Returns a shared reference to the [`CoreGlobal`] associated to the given [`Global`]. /// /// # Errors /// /// - If the [`Global`] does not originate from this [`StoreInner`]. /// - If the [`Global`] cannot be resolved to its entity. pub fn try_resolve_global(&self, global: &Global) -> Result<&CoreGlobal, InternalStoreError> { self.resolve(global.as_inner(), &self.globals) } /// Returns an exclusive reference to the [`CoreGlobal`] associated to the given [`Global`]. /// /// # Errors /// /// - If the [`Global`] does not originate from this [`StoreInner`]. /// - If the [`Global`] cannot be resolved to its entity. pub fn try_resolve_global_mut( &mut self, global: &Global, ) -> Result<&mut CoreGlobal, InternalStoreError> { let idx = self.unwrap_stored(global.as_inner())?; Self::resolve_mut(idx, &mut self.globals) } /// Returns a shared reference to the [`CoreTable`] associated to the given [`Table`]. /// /// # Errors /// /// - If the [`Table`] does not originate from this [`StoreInner`]. /// - If the [`Table`] cannot be resolved to its entity. pub fn try_resolve_table(&self, table: &Table) -> Result<&CoreTable, InternalStoreError> { self.resolve(table.as_inner(), &self.tables) } /// Returns an exclusive reference to the [`CoreTable`] associated to the given [`Table`]. /// /// # Errors /// /// - If the [`Table`] does not originate from this [`StoreInner`]. /// - If the [`Table`] cannot be resolved to its entity. pub fn try_resolve_table_mut( &mut self, table: &Table, ) -> Result<&mut CoreTable, InternalStoreError> { let idx = self.unwrap_stored(table.as_inner())?; Self::resolve_mut(idx, &mut self.tables) } /// Returns an exclusive reference to the [`CoreTable`] and [`CoreElementSegment`] associated to `table` and `elem`. /// /// # Errors /// /// - If the [`Table`] does not originate from this [`StoreInner`]. /// - If the [`Table`] cannot be resolved to its entity. /// - If the [`ElementSegment`] does not originate from this [`StoreInner`]. /// - If the [`ElementSegment`] cannot be resolved to its entity. pub fn try_resolve_table_and_element_mut( &mut self, table: &Table, elem: &ElementSegment, ) -> Result<(&mut CoreTable, &mut CoreElementSegment), InternalStoreError> { let table_idx = self.unwrap_stored(table.as_inner())?; let elem_idx = self.unwrap_stored(elem.as_inner())?; let table = Self::resolve_mut(table_idx, &mut self.tables)?; let elem = Self::resolve_mut(elem_idx, &mut self.elems)?; Ok((table, elem)) } /// Returns both /// /// - an exclusive reference to the [`CoreTable`] associated to the given [`Table`] /// - an exclusive reference to the [`Fuel`] of the [`StoreInner`]. /// /// # Errors /// /// - If the [`Table`] does not originate from this [`StoreInner`]. /// - If the [`Table`] cannot be resolved to its entity. pub fn try_resolve_table_and_fuel_mut( &mut self, table: &Table, ) -> Result<(&mut CoreTable, &mut Fuel), InternalStoreError> { let idx = self.unwrap_stored(table.as_inner())?; let table = Self::resolve_mut(idx, &mut self.tables)?; let fuel = &mut self.fuel; Ok((table, fuel)) } /// Returns an exclusive reference to the [`CoreTable`] associated to the given [`Table`]. /// /// # Errors /// /// - If the [`Table`] does not originate from this [`StoreInner`]. /// - If the [`Table`] cannot be resolved to its entity. pub fn try_resolve_table_pair_and_fuel( &mut self, fst: &Table, snd: &Table, ) -> Result<(&mut CoreTable, &mut CoreTable, &mut Fuel), InternalStoreError> { let fst = self.unwrap_stored(fst.as_inner())?; let snd = self.unwrap_stored(snd.as_inner())?; let (fst, snd) = self.tables.get_pair_mut(fst, snd).unwrap_or_else(|| { panic!("failed to resolve stored pair of entities: {fst:?} and {snd:?}") }); let fuel = &mut self.fuel; Ok((fst, snd, fuel)) } /// Returns the following data: /// /// - A shared reference to the [`InstanceEntity`] associated to the given [`Instance`]. /// - An exclusive reference to the [`CoreTable`] associated to the given [`Table`]. /// - A shared reference to the [`CoreElementSegment`] associated to the given [`ElementSegment`]. /// - An exclusive reference to the [`Fuel`] of the [`StoreInner`]. /// /// # Note /// /// This method exists to properly handle use cases where /// otherwise the Rust borrow-checker would not accept. /// /// # Errors /// /// - If the [`Instance`] does not originate from this [`StoreInner`]. /// - If the [`Instance`] cannot be resolved to its entity. /// - If the [`Table`] does not originate from this [`StoreInner`]. /// - If the [`Table`] cannot be resolved to its entity. /// - If the [`ElementSegment`] does not originate from this [`StoreInner`]. /// - If the [`ElementSegment`] cannot be resolved to its entity. pub fn try_resolve_table_init_params( &mut self, table: &Table, segment: &ElementSegment, ) -> Result<(&mut CoreTable, &CoreElementSegment, &mut Fuel), InternalStoreError> { let mem_idx = self.unwrap_stored(table.as_inner())?; let elem_idx = segment.as_inner(); let elem = self.resolve(elem_idx, &self.elems)?; let mem = Self::resolve_mut(mem_idx, &mut self.tables)?; let fuel = &mut self.fuel; Ok((mem, elem, fuel)) } /// Returns a shared reference to the [`CoreElementSegment`] associated to the given [`ElementSegment`]. /// /// # Errors /// /// - If the [`ElementSegment`] does not originate from this [`StoreInner`]. /// - If the [`ElementSegment`] cannot be resolved to its entity. pub fn try_resolve_element( &self, segment: &ElementSegment, ) -> Result<&CoreElementSegment, InternalStoreError> { self.resolve(segment.as_inner(), &self.elems) } /// Returns an exclusive reference to the [`CoreElementSegment`] associated to the given [`ElementSegment`]. /// /// # Errors /// /// - If the [`ElementSegment`] does not originate from this [`StoreInner`]. /// - If the [`ElementSegment`] cannot be resolved to its entity. pub fn try_resolve_element_mut( &mut self, segment: &ElementSegment, ) -> Result<&mut CoreElementSegment, InternalStoreError> { let idx = self.unwrap_stored(segment.as_inner())?; Self::resolve_mut(idx, &mut self.elems) } /// Returns a shared reference to the [`CoreMemory`] associated to the given [`Memory`]. /// /// # Errors /// /// - If the [`Memory`] does not originate from this [`StoreInner`]. /// - If the [`Memory`] cannot be resolved to its entity. pub fn try_resolve_memory<'a>( &'a self, memory: &Memory, ) -> Result<&'a CoreMemory, InternalStoreError> { self.resolve(memory.as_inner(), &self.memories) } /// Returns an exclusive reference to the [`CoreMemory`] associated to the given [`Memory`]. /// /// # Errors /// /// - If the [`Memory`] does not originate from this [`StoreInner`]. /// - If the [`Memory`] cannot be resolved to its entity. pub fn try_resolve_memory_mut<'a>( &'a mut self, memory: &Memory, ) -> Result<&'a mut CoreMemory, InternalStoreError> { let idx = self.unwrap_stored(memory.as_inner())?; Self::resolve_mut(idx, &mut self.memories) } /// Returns an exclusive reference to the [`CoreMemory`] associated to the given [`Memory`]. /// /// # Errors /// /// - If the [`Memory`] does not originate from this [`StoreInner`]. /// - If the [`Memory`] cannot be resolved to its entity. pub fn try_resolve_memory_and_fuel_mut( &mut self, memory: &Memory, ) -> Result<(&mut CoreMemory, &mut Fuel), InternalStoreError> { let idx = self.unwrap_stored(memory.as_inner())?; let memory = Self::resolve_mut(idx, &mut self.memories)?; let fuel = &mut self.fuel; Ok((memory, fuel)) } /// Returns the following data: /// /// - An exclusive reference to the [`CoreMemory`] associated to the given [`Memory`]. /// - A shared reference to the [`DataSegmentEntity`] associated to the given [`DataSegment`]. /// - An exclusive reference to the [`Fuel`] of the [`StoreInner`]. /// /// # Note /// /// This method exists to properly handle use cases where /// otherwise the Rust borrow-checker would not accept. /// /// # Errors /// /// - If the [`Memory`] does not originate from this [`StoreInner`]. /// - If the [`Memory`] cannot be resolved to its entity. /// - If the [`DataSegment`] does not originate from this [`StoreInner`]. /// - If the [`DataSegment`] cannot be resolved to its entity. pub fn try_resolve_memory_init_params( &mut self, memory: &Memory, segment: &DataSegment, ) -> Result<(&mut CoreMemory, &DataSegmentEntity, &mut Fuel), InternalStoreError> { let mem_idx = self.unwrap_stored(memory.as_inner())?; let data_idx = segment.as_inner(); let data = self.resolve(data_idx, &self.datas)?; let mem = Self::resolve_mut(mem_idx, &mut self.memories)?; let fuel = &mut self.fuel; Ok((mem, data, fuel)) } /// Returns an exclusive pair of references to the [`CoreMemory`] associated to the given [`Memory`]s. /// /// # Errors /// /// - If the [`Memory`] does not originate from this [`StoreInner`]. /// - If the [`Memory`] cannot be resolved to its entity. pub fn try_resolve_memory_pair_and_fuel( &mut self, fst: &Memory, snd: &Memory, ) -> Result<(&mut CoreMemory, &mut CoreMemory, &mut Fuel), InternalStoreError> { let fst = self.unwrap_stored(fst.as_inner())?; let snd = self.unwrap_stored(snd.as_inner())?; let (fst, snd) = self.memories.get_pair_mut(fst, snd).unwrap_or_else(|| { panic!("failed to resolve stored pair of entities: {fst:?} and {snd:?}") }); let fuel = &mut self.fuel; Ok((fst, snd, fuel)) } /// Returns an exclusive reference to the [`DataSegmentEntity`] associated to the given [`DataSegment`]. /// /// # Errors /// /// - If the [`DataSegment`] does not originate from this [`StoreInner`]. /// - If the [`DataSegment`] cannot be resolved to its entity. pub fn try_resolve_data_mut( &mut self, segment: &DataSegment, ) -> Result<&mut DataSegmentEntity, InternalStoreError> { let idx = self.unwrap_stored(segment.as_inner())?; Self::resolve_mut(idx, &mut self.datas) } /// Returns a shared reference to the [`InstanceEntity`] associated to the given [`Instance`]. /// /// # Errors /// /// - If the [`Instance`] does not originate from this [`StoreInner`]. /// - If the [`Instance`] cannot be resolved to its entity. pub fn try_resolve_instance( &self, instance: &Instance, ) -> Result<&InstanceEntity, InternalStoreError> { self.resolve(instance.as_inner(), &self.instances) } /// Returns a shared reference to the [`ExternRefEntity`] associated to the given [`ExternRef`]. /// /// # Errors /// /// - If the [`ExternRef`] does not originate from this [`StoreInner`]. /// - If the [`ExternRef`] cannot be resolved to its entity. pub fn try_resolve_externref( &self, object: &ExternRef, ) -> Result<&ExternRefEntity, InternalStoreError> { self.resolve(object.as_inner(), &self.extern_objects) } /// Allocates a new Wasm or host [`FuncEntity`] and returns a [`Func`] reference to it. pub fn alloc_func(&mut self, func: FuncEntity) -> Func { let idx = self.funcs.alloc(func); Func::from_inner(self.wrap_stored(idx)) } /// Returns a shared reference to the associated entity of the Wasm or host function. /// /// # Errors /// /// - If the [`Func`] does not originate from this [`StoreInner`]. /// - If the [`Func`] cannot be resolved to its entity. pub fn try_resolve_func(&self, func: &Func) -> Result<&FuncEntity, InternalStoreError> { self.resolve(func.as_inner(), &self.funcs) } } macro_rules! define_panicking_getters { ( $( pub fn $getter:ident($receiver:ty, $( $param_name:ident: $param_ty:ty ),* $(,)? ) -> $ret_ty:ty = $try_getter:expr );* $(;)? ) => { $( #[doc = ::core::concat!( "Resolves `", ::core::stringify!($ret_ty), "` via [`", ::core::stringify!($try_getter), "`] panicking upon error." )] pub fn $getter(self: $receiver, $( $param_name: $param_ty ),*) -> $ret_ty { match $try_getter(self, $($param_name),*) { ::core::result::Result::Ok(value) => value, ::core::result::Result::Err(error) => ::core::panic!( ::core::concat!( "failed to resolve stored", $( " ", ::core::stringify!($param_name), )* ": {}" ), error, ) } } )* }; } impl StoreInner { define_panicking_getters! { pub fn resolve_global(&Self, global: &Global) -> &CoreGlobal = Self::try_resolve_global; pub fn resolve_global_mut(&mut Self, global: &Global) -> &mut CoreGlobal = Self::try_resolve_global_mut; pub fn resolve_memory(&Self, memory: &Memory) -> &CoreMemory = Self::try_resolve_memory; pub fn resolve_memory_mut(&mut Self, memory: &Memory) -> &mut CoreMemory = Self::try_resolve_memory_mut; pub fn resolve_table(&Self, table: &Table) -> &CoreTable = Self::try_resolve_table; pub fn resolve_table_mut(&mut Self, table: &Table) -> &mut CoreTable = Self::try_resolve_table_mut; pub fn resolve_element(&Self, elem: &ElementSegment) -> &CoreElementSegment = Self::try_resolve_element; pub fn resolve_element_mut(&mut Self, elem: &ElementSegment) -> &mut CoreElementSegment = Self::try_resolve_element_mut; pub fn resolve_func(&Self, func: &Func) -> &FuncEntity = Self::try_resolve_func; pub fn resolve_data_mut(&mut Self, data: &DataSegment) -> &mut DataSegmentEntity = Self::try_resolve_data_mut; pub fn resolve_instance(&Self, instance: &Instance) -> &InstanceEntity = Self::try_resolve_instance; pub fn resolve_externref(&Self, data: &ExternRef) -> &ExternRefEntity = Self::try_resolve_externref; pub fn resolve_table_and_element_mut( &mut Self, table: &Table, elem: &ElementSegment, ) -> (&mut CoreTable, &mut CoreElementSegment) = Self::try_resolve_table_and_element_mut; pub fn resolve_table_and_fuel_mut( &mut Self, table: &Table, ) -> (&mut CoreTable, &mut Fuel) = Self::try_resolve_table_and_fuel_mut; pub fn resolve_table_pair_and_fuel( &mut Self, fst: &Table, snd: &Table, ) -> (&mut CoreTable, &mut CoreTable, &mut Fuel) = Self::try_resolve_table_pair_and_fuel; pub fn resolve_table_init_params( &mut Self, table: &Table, elem: &ElementSegment, ) -> (&mut CoreTable, &CoreElementSegment, &mut Fuel) = Self::try_resolve_table_init_params; pub fn resolve_memory_and_fuel_mut( &mut Self, memory: &Memory, ) -> (&mut CoreMemory, &mut Fuel) = Self::try_resolve_memory_and_fuel_mut; pub fn resolve_memory_init_params( &mut Self, memory: &Memory, segment: &DataSegment, ) -> (&mut CoreMemory, &DataSegmentEntity, &mut Fuel) = Self::try_resolve_memory_init_params; pub fn resolve_memory_pair_and_fuel( &mut Self, fst: &Memory, snd: &Memory, ) -> (&mut CoreMemory, &mut CoreMemory, &mut Fuel) = Self::try_resolve_memory_pair_and_fuel; } } wasmi-1.1.0/src/store/mod.rs000064400000000000000000000316641046102023000140250ustar 00000000000000mod context; mod error; mod inner; mod pruned; mod typeid; use self::pruned::PrunedStoreVTable; pub use self::{ context::{AsContext, AsContextMut, StoreContext, StoreContextMut}, error::{InternalStoreError, StoreError}, inner::{StoreInner, Stored}, pruned::PrunedStore, }; use crate::{ collections::arena::Arena, core::{CoreMemory, ResourceLimiterRef}, func::{FuncInOut, HostFuncEntity, Trampoline, TrampolineEntity, TrampolineIdx}, Engine, Error, Instance, Memory, ResourceLimiter, }; use alloc::boxed::Box; use core::{ any::{type_name, TypeId}, fmt::{self, Debug}, }; /// The store that owns all data associated to Wasm modules. #[derive(Debug)] pub struct Store { /// All data that is not associated to `T`. /// /// # Note /// /// This is re-exported to the rest of the crate since /// it is used directly by the engine's executor. pub(crate) inner: StoreInner, /// The inner parts of the [`Store`] that are generic over a host provided `T`. typed: TypedStoreInner, /// The [`TypeId`] of the `T` of the `store`. /// /// This is used in [`PrunedStore::restore`] to check if the /// restored `T` matches the original `T` of the `store`. id: TypeId, /// Used to restore a [`PrunedStore`] to a [`Store`]. restore_pruned: PrunedStoreVTable, } impl Default for Store where T: Default, { fn default() -> Self { let engine = Engine::default(); Self::new(&engine, T::default()) } } impl Store { /// Creates a new store. pub fn new(engine: &Engine, data: T) -> Self { Self { inner: StoreInner::new(engine), typed: TypedStoreInner::new(data), id: typeid::of::(), restore_pruned: PrunedStoreVTable::new::(), } } } impl Store { /// Returns the [`Engine`] that this store is associated with. pub fn engine(&self) -> &Engine { self.inner.engine() } /// Returns a shared reference to the user provided data owned by this [`Store`]. pub fn data(&self) -> &T { &self.typed.data } /// Returns an exclusive reference to the user provided data owned by this [`Store`]. pub fn data_mut(&mut self) -> &mut T { &mut self.typed.data } /// Consumes `self` and returns its user provided data. pub fn into_data(self) -> T { *self.typed.data } /// Installs a function into the [`Store`] that will be called with the user /// data type `T` to retrieve a [`ResourceLimiter`] any time a limited, /// growable resource such as a linear memory or table is grown. pub fn limiter( &mut self, limiter: impl (FnMut(&mut T) -> &mut dyn ResourceLimiter) + Send + Sync + 'static, ) { self.typed.limiter = Some(ResourceLimiterQuery(Box::new(limiter))) } /// Calls the given [`HostFuncEntity`] with the `params` and `results` on `instance`. /// /// # Errors /// /// If the called host function returned an error. fn call_host_func( &mut self, func: &HostFuncEntity, instance: Option<&Instance>, params_results: FuncInOut, ) -> Result<(), StoreError> { let trampoline = self.resolve_trampoline(func.trampoline())?.clone(); trampoline .call(self, instance, params_results) .map_err(StoreError::external)?; Ok(()) } /// Returns `true` if it is possible to create `additional` more instances in the [`Store`]. pub(crate) fn can_create_more_instances(&mut self, additional: usize) -> bool { let (inner, mut limiter) = self.store_inner_and_resource_limiter_ref(); if let Some(limiter) = limiter.as_resource_limiter() { if inner.len_instances().saturating_add(additional) > limiter.instances() { return false; } } true } /// Returns `true` if it is possible to create `additional` more linear memories in the [`Store`]. pub(crate) fn can_create_more_memories(&mut self, additional: usize) -> bool { let (inner, mut limiter) = self.store_inner_and_resource_limiter_ref(); if let Some(limiter) = limiter.as_resource_limiter() { if inner.len_memories().saturating_add(additional) > limiter.memories() { return false; } } true } /// Returns `true` if it is possible to create `additional` more tables in the [`Store`]. pub(crate) fn can_create_more_tables(&mut self, additional: usize) -> bool { let (inner, mut limiter) = self.store_inner_and_resource_limiter_ref(); if let Some(limiter) = limiter.as_resource_limiter() { if inner.len_tables().saturating_add(additional) > limiter.tables() { return false; } } true } /// Returns a pair of [`StoreInner`] and [`ResourceLimiterRef`]. /// /// # Note /// /// This methods mostly exists to satisfy certain use cases that otherwise would conflict with the borrow checker. pub(crate) fn store_inner_and_resource_limiter_ref( &mut self, ) -> (&mut StoreInner, ResourceLimiterRef<'_>) { let resource_limiter = match &mut self.typed.limiter { Some(query) => { let limiter = query.0(&mut self.typed.data); ResourceLimiterRef::from(limiter) } None => ResourceLimiterRef::default(), }; (&mut self.inner, resource_limiter) } /// Returns the remaining fuel of the [`Store`] if fuel metering is enabled. /// /// # Note /// /// Enable fuel metering via [`Config::consume_fuel`](crate::Config::consume_fuel). /// /// # Errors /// /// If fuel metering is disabled. pub fn get_fuel(&self) -> Result { self.inner.get_fuel() } /// Sets the remaining fuel of the [`Store`] to `value` if fuel metering is enabled. /// /// # Note /// /// Enable fuel metering via [`Config::consume_fuel`](crate::Config::consume_fuel). /// /// # Errors /// /// If fuel metering is disabled. pub fn set_fuel(&mut self, fuel: u64) -> Result<(), Error> { self.inner.set_fuel(fuel) } /// Allocates a new [`TrampolineEntity`] and returns a [`Trampoline`] reference to it. pub(super) fn alloc_trampoline(&mut self, func: TrampolineEntity) -> Trampoline { let idx = self.typed.trampolines.alloc(func); Trampoline::from_inner(self.inner.wrap_stored(idx)) } /// Returns an exclusive reference to the [`CoreMemory`] associated to the given [`Memory`] /// and an exclusive reference to the user provided host state. /// /// # Note /// /// This method exists to properly handle use cases where /// otherwise the Rust borrow-checker would not accept. /// /// # Panics /// /// - If the [`Memory`] does not originate from this [`Store`]. /// - If the [`Memory`] cannot be resolved to its entity. pub(super) fn resolve_memory_and_state_mut( &mut self, memory: &Memory, ) -> (&mut CoreMemory, &mut T) { (self.inner.resolve_memory_mut(memory), &mut self.typed.data) } /// Returns a shared reference to the associated entity of the host function trampoline. /// /// # Panics /// /// - If the [`Trampoline`] does not originate from this [`Store`]. /// - If the [`Trampoline`] cannot be resolved to its entity. fn resolve_trampoline( &self, func: &Trampoline, ) -> Result<&TrampolineEntity, InternalStoreError> { let entity_index = self.inner.unwrap_stored(func.as_inner())?; let Some(trampoline) = self.typed.trampolines.get(entity_index) else { return Err(InternalStoreError::not_found()); }; Ok(trampoline) } /// Sets a callback function that is executed whenever a WebAssembly /// function is called from the host or a host function is called from /// WebAssembly, or these functions return. /// /// The function is passed a `&mut T` to the underlying store, and a /// [`CallHook`]. [`CallHook`] can be used to find out what kind of function /// is being called or returned from. /// /// The callback can either return `Ok(())` or an `Err` with an /// [`Error`]. If an error is returned, it is returned to the host /// caller. If there are nested calls, only the most recent host caller /// receives the error and it is not propagated further automatically. The /// hook may be invoked again as new functions are called and returned from. pub fn call_hook( &mut self, hook: impl FnMut(&mut T, CallHook) -> Result<(), Error> + Send + Sync + 'static, ) { self.typed.call_hook = Some(CallHookWrapper(Box::new(hook))); } /// Executes the callback set by [`Store::call_hook`] if any has been set. /// /// # Note /// /// - Returns the value returned by the call hook. /// - Returns `Ok(())` if no call hook exists. #[inline] pub(crate) fn invoke_call_hook(&mut self, call_type: CallHook) -> Result<(), Error> { match self.typed.call_hook.as_mut() { None => Ok(()), Some(call_hook) => { Self::invoke_call_hook_impl(&mut self.typed.data, call_type, call_hook) } } } /// Utility function to invoke the [`Store::call_hook`] that is asserted to /// be available in this case. /// /// This is kept as a separate `#[cold]` function to help the compiler speed /// up the code path without any call hooks. #[cold] fn invoke_call_hook_impl( data: &mut T, call_type: CallHook, call_hook: &mut CallHookWrapper, ) -> Result<(), Error> { call_hook.0(data, call_type) } } /// The inner parts of the [`Store`] which are generic over a host provided `T`. #[derive(Debug)] pub struct TypedStoreInner { /// Stored host function trampolines. trampolines: Arena>, /// User provided hook to retrieve a [`ResourceLimiter`]. limiter: Option>, /// User provided callback called when a host calls a WebAssembly function /// or a WebAssembly function calls a host function, or these functions /// return. call_hook: Option>, /// User provided host data owned by the [`Store`]. data: Box, } impl TypedStoreInner { /// Creates a new [`TypedStoreInner`] from the given data of type `T`. fn new(data: T) -> Self { Self { trampolines: Arena::new(), data: Box::new(data), limiter: None, call_hook: None, } } } /// A wrapper around a boxed `dyn FnMut(&mut T)` returning a `&mut dyn` /// [`ResourceLimiter`]; in other words a function that one can call to retrieve /// a [`ResourceLimiter`] from the [`Store`] object's user data type `T`. /// /// This wrapper exists both to make types a little easier to read and to /// provide a `Debug` impl so that `#[derive(Debug)]` works on structs that /// contain it. struct ResourceLimiterQuery(Box &mut dyn ResourceLimiter) + Send + Sync>); impl Debug for ResourceLimiterQuery { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "ResourceLimiterQuery<{}>(...)", type_name::()) } } /// A wrapper used to store hooks added with [`Store::call_hook`], containing a /// boxed `FnMut(&mut T, CallHook) -> Result<(), Error>`. /// /// This wrapper exists to provide a `Debug` impl so that `#[derive(Debug)]` /// works for [`Store`]. #[allow(clippy::type_complexity)] struct CallHookWrapper(Box Result<(), Error> + Send + Sync>); impl Debug for CallHookWrapper { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "CallHook<{}>", type_name::()) } } /// Argument to the callback set by [`Store::call_hook`] to indicate why the /// callback was invoked. #[derive(Debug)] pub enum CallHook { /// Indicates that a WebAssembly function is being called from the host. CallingWasm, /// Indicates that a WebAssembly function called from the host is returning. ReturningFromWasm, /// Indicates that a host function is being called from a WebAssembly function. CallingHost, /// Indicates that a host function called from a WebAssembly function is returning. ReturningFromHost, } /// The call hook behavior when calling a host function. #[derive(Debug, Copy, Clone)] pub enum CallHooks { /// Invoke the host call hooks. Call, /// Ignore the host call hooks. Ignore, } #[test] fn test_store_is_send_sync() { const _: () = { #[allow(clippy::extra_unused_type_parameters)] fn assert_send() {} #[allow(clippy::extra_unused_type_parameters)] fn assert_sync() {} let _ = assert_send::>; let _ = assert_sync::>; }; } wasmi-1.1.0/src/store/pruned.rs000064400000000000000000000234131046102023000145340ustar 00000000000000use super::{typeid, CallHooks, FuncInOut, HostFuncEntity, StoreInner}; use crate::{ core::{hint, UntypedVal}, errors::{MemoryError, TableError}, store::error::{InternalStoreError, StoreError}, CallHook, Error, Instance, Memory, Store, Table, }; use core::{ fmt::{self, Debug}, mem, }; #[cfg(test)] use crate::Engine; /// A wrapper used to restore a [`PrunedStore`]. /// /// This wrapper exists to provide a `Debug` impl so that `#[derive(Debug)]` /// works for [`Store`]. #[allow(clippy::type_complexity)] #[derive(Copy, Clone)] pub struct PrunedStoreVTable { /// Calls the given [`HostFuncEntity`] with the `params` and `results` on `instance`. /// /// # Errors /// /// If the called host function returned an error. call_host_func: fn( store: &mut PrunedStore, func: &HostFuncEntity, instance: Option<&Instance>, params_results: FuncInOut, call_hooks: CallHooks, ) -> Result<(), StoreError>, /// Grows `memory` by `delta` pages. grow_memory: fn(&mut PrunedStore, memory: &Memory, delta: u64) -> Result>, /// Grows `table` by `delta` items filling with `init`. grow_table: fn( &mut PrunedStore, table: &Table, delta: u64, init: UntypedVal, ) -> Result>, } impl PrunedStoreVTable { pub fn new() -> Self { Self { call_host_func: |pruned: &mut PrunedStore, func: &HostFuncEntity, instance: Option<&Instance>, params_results: FuncInOut, call_hooks: CallHooks| -> Result<(), StoreError> { let store: &mut Store = pruned.restore()?; if matches!(call_hooks, CallHooks::Call) { store .invoke_call_hook(CallHook::CallingHost) .map_err(StoreError::external)?; } store.call_host_func(func, instance, params_results)?; if matches!(call_hooks, CallHooks::Call) { store .invoke_call_hook(CallHook::ReturningFromHost) .map_err(StoreError::external)?; } Ok(()) }, grow_memory: |pruned: &mut PrunedStore, memory: &Memory, delta: u64| -> Result> { let store: &mut Store = pruned.restore()?; let (store, mut resource_limiter) = store.store_inner_and_resource_limiter_ref(); let (memory, fuel) = store.try_resolve_memory_and_fuel_mut(memory)?; memory .grow(delta, Some(fuel), &mut resource_limiter) .map_err(StoreError::external) }, grow_table: |pruned: &mut PrunedStore, table: &Table, delta: u64, init: UntypedVal| -> Result> { let store: &mut Store = pruned.restore()?; let (store, mut resource_limiter) = store.store_inner_and_resource_limiter_ref(); let (table, fuel) = store.try_resolve_table_and_fuel_mut(table)?; table .grow_untyped(delta, init, Some(fuel), &mut resource_limiter) .map_err(StoreError::external) }, } } } impl PrunedStoreVTable { #[inline] fn call_host_func( &self, pruned: &mut PrunedStore, func: &HostFuncEntity, instance: Option<&Instance>, params_results: FuncInOut, call_hooks: CallHooks, ) -> Result<(), StoreError> { (self.call_host_func)(pruned, func, instance, params_results, call_hooks) } #[inline] fn grow_memory( &self, pruned: &mut PrunedStore, memory: &Memory, delta: u64, ) -> Result> { (self.grow_memory)(pruned, memory, delta) } #[inline] fn grow_table( &self, pruned: &mut PrunedStore, table: &Table, delta: u64, init: UntypedVal, ) -> Result> { (self.grow_table)(pruned, table, delta, init) } } impl Debug for PrunedStoreVTable { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "RestorePrunedWrapper") } } /// A [`Store`] with a pruned `T`. #[derive(Debug)] #[repr(transparent)] pub struct PrunedStore { /// The underlying [`Store`] with pruned type signature. pruned: Store, } /// Placeholder type of `T` for a pruned `Store`. #[derive(Debug)] pub struct Pruned; impl<'a, T> From<&'a mut Store> for &'a mut PrunedStore { #[inline] fn from(store: &'a mut Store) -> Self { // Safety: the generic `Store` has its `T` pruned here. // // - This is safe because we are operating on a `&mut Store` thus it is just // a reference and since `Store` and `Store` have the same size and API. // - We make sure in `PrunedStore` to never access the typed parts of the original // `Store` and check in the restoration process the type-ID of the target `T`. // - `Store` has the same size and alignment for all `T`. unsafe { mem::transmute::<&'a mut Store, &'a mut PrunedStore>(store) } } } impl Store { /// Prune the [`Store`] from `T` returning a [`PrunedStore`]. #[inline] pub(crate) fn prune(&mut self) -> &mut PrunedStore { self.into() } } impl PrunedStore { /// Returns a shared reference to the underlying [`StoreInner`]. #[inline] pub fn inner(&self) -> &StoreInner { &self.pruned.inner } /// Returns an exclusive reference to the underlying [`StoreInner`]. #[inline] pub fn inner_mut(&mut self) -> &mut StoreInner { &mut self.pruned.inner } /// Calls a host `func` at `instance` with `params_results` buffer. /// /// # Errors /// /// If the host function returns an error. #[inline] pub fn call_host_func( &mut self, func: &HostFuncEntity, instance: Option<&Instance>, params_results: FuncInOut, call_hooks: CallHooks, ) -> Result<(), StoreError> { self.pruned.restore_pruned.clone().call_host_func( self, func, instance, params_results, call_hooks, ) } /// Grows the [`Memory`] by `delta` pages and returns the result. #[inline] pub fn grow_memory( &mut self, memory: &Memory, delta: u64, ) -> Result> { self.pruned .restore_pruned .clone() .grow_memory(self, memory, delta) } /// Grows the [`Table`] by `delta` items and returns the result. #[inline] pub fn grow_table( &mut self, table: &Table, delta: u64, init: UntypedVal, ) -> Result> { self.pruned .restore_pruned .clone() .grow_table(self, table, delta, init) } /// Restores `self` to a proper [`Store`] if possible. /// /// # Errors /// /// If the `T` of the resulting [`Store`] does not match the given `T`. #[inline] fn restore(&mut self) -> Result<&mut Store, InternalStoreError> { if hint::unlikely(typeid::of::() != self.pruned.id) { return Err(InternalStoreError::restore_type_mismatch::()); } let store = { // Safety: we restore the original `Store` from the pruned `Store`. // // This is safe because we have already checked above that the `TypedId` of `T` // matches the `id` of the original `Store` and thus the `T`'s are identical. // // Furthermore, we are only operating on `&mut` pointers and not values. // Finally, `Store` has the same size and alignment for all `T`. unsafe { mem::transmute::<&mut PrunedStore, &mut Store>(self) } }; Ok(store) } } #[test] fn pruning_works() { let engine = Engine::default(); let mut store = Store::new(&engine, ()); let pruned = store.prune(); assert!(pruned.restore::<()>().is_ok()); } #[test] fn pruning_errors() { let engine = Engine::default(); let mut store = Store::new(&engine, ()); let pruned = store.prune(); assert!(pruned.restore::().is_err()); } #[test] fn pruned_store_deref() { let mut config = crate::Config::default(); config.consume_fuel(true); let engine = Engine::new(&config); let mut store = Store::new(&engine, ()); let fuel_amount = 100; store.set_fuel(fuel_amount).unwrap(); let pruned = store.prune(); assert_eq!( PrunedStore::inner(pruned).fuel.get_fuel().unwrap(), fuel_amount ); PrunedStore::inner_mut(pruned) .fuel .set_fuel(fuel_amount * 2) .unwrap(); assert_eq!( PrunedStore::inner(pruned).fuel.get_fuel().unwrap(), fuel_amount * 2 ); } #[test] fn equal_size() { use super::TypedStoreInner; type SmallType = (); type BigType = [i64; 16]; // Note: `TypedStore` must be of the same size for all `T` so that // `PrunedStore` works and is a safe abstraction. use core::mem::size_of; assert_eq!(size_of::>(), size_of::>(),); assert_eq!( size_of::>(), size_of::>(), ); } wasmi-1.1.0/src/store/typeid.rs000064400000000000000000000015531046102023000145360ustar 00000000000000use core::{any::TypeId, marker::PhantomData, mem}; /// Returns the [`TypeId`] of `T`. /// /// # Note /// /// - `T` does _not_ need to be `'static` for this to work. /// - This uses a trick copied from the [`typeid` crate](https://docs.rs/typeid) by [dtolnay](https://github.com/dtolnay). #[must_use] #[inline(always)] pub fn of() -> TypeId where T: ?Sized, { trait NonStaticAny { fn get_type_id(&self) -> TypeId where Self: 'static; } impl NonStaticAny for PhantomData { #[inline(always)] fn get_type_id(&self) -> TypeId where Self: 'static, { TypeId::of::() } } let phantom_data = PhantomData::; NonStaticAny::get_type_id(unsafe { mem::transmute::<&dyn NonStaticAny, &(dyn NonStaticAny + 'static)>(&phantom_data) }) } wasmi-1.1.0/src/table/element.rs000064400000000000000000000050331046102023000146210ustar 00000000000000use crate::{ collections::arena::ArenaIndex, core::{CoreElementSegment, UntypedVal}, module, store::Stored, AsContext, AsContextMut, Func, Global, }; use alloc::boxed::Box; /// A raw index to a element segment entity. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct ElementSegmentIdx(u32); impl ArenaIndex for ElementSegmentIdx { fn into_usize(self) -> usize { self.0 as usize } fn from_usize(value: usize) -> Self { let value = value.try_into().unwrap_or_else(|error| { panic!("index {value} is out of bounds as element segment index: {error}") }); Self(value) } } /// A Wasm data segment reference. #[derive(Debug, Copy, Clone)] #[repr(transparent)] pub struct ElementSegment(Stored); impl ElementSegment { /// Creates a new linear memory reference. pub fn from_inner(stored: Stored) -> Self { Self(stored) } /// Returns the underlying stored representation. pub fn as_inner(&self) -> &Stored { &self.0 } /// Allocates a new [`ElementSegment`] on the store. /// /// # Errors /// /// If more than [`u32::MAX`] much linear memory is allocated. pub fn new( mut ctx: impl AsContextMut, elem: &module::ElementSegment, get_func: impl Fn(u32) -> Func, get_global: impl Fn(u32) -> Global, ) -> Self { let get_func = |index| get_func(index).into(); let get_global = |index| get_global(index).get(&ctx); let items: Box<[UntypedVal]> = match elem.kind() { module::ElementSegmentKind::Passive | module::ElementSegmentKind::Active(_) => { elem .items() .iter() .map(|const_expr| { const_expr.eval_with_context(get_global, get_func).unwrap_or_else(|| { panic!("unexpected failed initialization of constant expression: {const_expr:?}") }) }).collect() } module::ElementSegmentKind::Declared => Box::from([]), }; let entity = CoreElementSegment::new(elem.ty(), items); ctx.as_context_mut() .store .inner .alloc_element_segment(entity) } /// Returns the number of items in the [`ElementSegment`]. pub fn size(&self, ctx: impl AsContext) -> u32 { ctx.as_context().store.inner.resolve_element(self).size() } } wasmi-1.1.0/src/table/mod.rs000064400000000000000000000161231046102023000137510ustar 00000000000000pub use self::{ element::{ElementSegment, ElementSegmentIdx}, ty::TableType, }; use super::{AsContext, AsContextMut, Stored}; use crate::{collections::arena::ArenaIndex, core::CoreTable, errors::TableError, Error, Val}; mod element; mod ty; /// A raw index to a table entity. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct TableIdx(u32); impl ArenaIndex for TableIdx { fn into_usize(self) -> usize { self.0 as usize } fn from_usize(value: usize) -> Self { let value = value.try_into().unwrap_or_else(|error| { panic!("index {value} is out of bounds as table index: {error}") }); Self(value) } } /// A Wasm table reference. #[derive(Debug, Copy, Clone)] #[repr(transparent)] pub struct Table(Stored); impl Table { /// Creates a new table reference. pub(super) fn from_inner(stored: Stored) -> Self { Self(stored) } /// Returns the underlying stored representation. pub(super) fn as_inner(&self) -> &Stored { &self.0 } /// Creates a new table to the store. /// /// # Errors /// /// If `init` does not match the [`TableType`] element type. pub fn new(mut ctx: impl AsContextMut, ty: TableType, init: Val) -> Result { let (inner, mut resource_limiter) = ctx .as_context_mut() .store .store_inner_and_resource_limiter_ref(); let entity = CoreTable::new(ty.core, init.into(), &mut resource_limiter)?; let table = inner.alloc_table(entity); Ok(table) } /// Returns the type and limits of the table. /// /// # Panics /// /// Panics if `ctx` does not own this [`Table`]. pub fn ty(&self, ctx: impl AsContext) -> TableType { let core = ctx.as_context().store.inner.resolve_table(self).ty(); TableType { core } } /// Returns the dynamic [`TableType`] of the [`Table`]. /// /// # Note /// /// This respects the current size of the [`Table`] as /// its minimum size and is useful for import subtyping checks. /// /// # Panics /// /// Panics if `ctx` does not own this [`Table`]. pub(crate) fn dynamic_ty(&self, ctx: impl AsContext) -> TableType { let core = ctx .as_context() .store .inner .resolve_table(self) .dynamic_ty(); TableType { core } } /// Returns the current size of the [`Table`]. /// /// # Panics /// /// If `ctx` does not own this [`Table`]. pub fn size(&self, ctx: impl AsContext) -> u64 { ctx.as_context().store.inner.resolve_table(self).size() } /// Grows the table by the given amount of elements. /// /// Returns the old size of the [`Table`] upon success. /// /// # Note /// /// The newly added elements are initialized to the `init` [`Val`]. /// /// # Errors /// /// - If the table is grown beyond its maximum limits. /// - If `value` does not match the [`Table`] element type. /// /// # Panics /// /// Panics if `ctx` does not own this [`Table`]. pub fn grow( &self, mut ctx: impl AsContextMut, delta: u64, init: Val, ) -> Result { let (inner, mut limiter) = ctx .as_context_mut() .store .store_inner_and_resource_limiter_ref(); let table = inner.resolve_table_mut(self); table.grow(delta, init.into(), None, &mut limiter) } /// Returns the [`Table`] element value at `index`. /// /// Returns `None` if `index` is out of bounds. /// /// # Panics /// /// Panics if `ctx` does not own this [`Table`]. pub fn get(&self, ctx: impl AsContext, index: u64) -> Option { ctx.as_context() .store .inner .resolve_table(self) .get(index) .map(Val::from) } /// Sets the [`Val`] of this [`Table`] at `index`. /// /// # Errors /// /// - If `index` is out of bounds. /// - If `value` does not match the [`Table`] element type. /// /// # Panics /// /// Panics if `ctx` does not own this [`Table`]. pub fn set( &self, mut ctx: impl AsContextMut, index: u64, value: Val, ) -> Result<(), TableError> { ctx.as_context_mut() .store .inner .resolve_table_mut(self) .set(index, value.into()) } /// Returns `true` if `lhs` and `rhs` [`Table`] refer to the same entity. /// /// # Note /// /// We do not implement `Eq` and `PartialEq` and /// intentionally keep this API hidden from users. #[inline] pub(crate) fn eq(lhs: &Self, rhs: &Self) -> bool { lhs.as_inner() == rhs.as_inner() } /// Copy `len` elements from `src_table[src_index..]` into /// `dst_table[dst_index..]`. /// /// # Errors /// /// Returns an error if the range is out of bounds of either the source or /// destination tables. /// /// # Panics /// /// Panics if `store` does not own either `dst_table` or `src_table`. pub fn copy( mut store: impl AsContextMut, dst_table: &Table, dst_index: u64, src_table: &Table, src_index: u64, len: u64, ) -> Result<(), TableError> { if Self::eq(dst_table, src_table) { // The `dst_table` and `src_table` are the same table // therefore we have to copy within the same table. let table = store .as_context_mut() .store .inner .resolve_table_mut(dst_table); table .copy_within(dst_index, src_index, len, None) .map_err(|_| TableError::CopyOutOfBounds) } else { // The `dst_table` and `src_table` are different entities // therefore we have to copy from one table to the other. let (dst_table, src_table, _fuel) = store .as_context_mut() .store .inner .resolve_table_pair_and_fuel(dst_table, src_table); CoreTable::copy(dst_table, dst_index, src_table, src_index, len, None) .map_err(|_| TableError::CopyOutOfBounds) } } /// Fill `table[dst..(dst + len)]` with the given value. /// /// # Errors /// /// - If `val` has a type mismatch with the element type of the [`Table`]. /// - If the region to be filled is out of bounds for the [`Table`]. /// - If `val` originates from a different [`Store`] than the [`Table`]. /// /// # Panics /// /// If `ctx` does not own `dst_table` or `src_table`. /// /// [`Store`]: [`crate::Store`] pub fn fill( &self, mut ctx: impl AsContextMut, dst: u64, val: Val, len: u64, ) -> Result<(), TableError> { ctx.as_context_mut() .store .inner .resolve_table_mut(self) .fill(dst, val.into(), len, None) } } wasmi-1.1.0/src/table/ty.rs000064400000000000000000000043531046102023000136300ustar 00000000000000use crate::{ core::{CoreTableType, IndexType}, ValType, }; /// A Wasm table descriptor. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct TableType { pub(crate) core: CoreTableType, } impl TableType { /// Creates a new [`TableType`]. /// /// # Panics /// /// If `min` is greater than `max`. pub fn new(element: ValType, min: u32, max: Option) -> Self { let core = CoreTableType::new(element, min, max); Self { core } } /// Creates a new [`TableType`] with a 64-bit index type. /// /// # Note /// /// 64-bit tables are part of the [Wasm `memory64` proposal]. /// /// [Wasm `memory64` proposal]: https://github.com/WebAssembly/memory64 /// /// # Panics /// /// If `min` is greater than `max`. pub fn new64(element: ValType, min: u64, max: Option) -> Self { let core = CoreTableType::new64(element, min, max); Self { core } } /// Returns `true` if this is a 64-bit [`TableType`]. /// /// 64-bit memories are part of the Wasm `memory64` proposal. pub fn is_64(&self) -> bool { self.core.is_64() } /// Returns the [`IndexType`] used by the [`TableType`]. pub(crate) fn index_ty(&self) -> IndexType { self.core.index_ty() } /// Returns the [`ValType`] of elements stored in the table. pub fn element(&self) -> ValType { self.core.element() } /// Returns minimum number of elements the table with this type must have. pub fn minimum(&self) -> u64 { self.core.minimum() } /// The optional maximum number of elements a table with this type can have. /// /// If this returns `None` then tables with this type are not limited in size. pub fn maximum(&self) -> Option { self.core.maximum() } /// Returns `true` if the [`TableType`] is a subtype of the `other` [`TableType`]. /// /// # Note /// /// This implements the [subtyping rules] according to the WebAssembly spec. /// /// [import subtyping]: /// https://webassembly.github.io/spec/core/valid/types.html#import-subtyping pub(crate) fn is_subtype_of(&self, other: &Self) -> bool { self.core.is_subtype_of(&other.core) } } wasmi-1.1.0/src/tests.rs000064400000000000000000000102021046102023000132350ustar 00000000000000use crate::{Config, Engine, Error, Linker, Module, Store, TrapCode, WasmParams, WasmResults}; use core::{fmt::Debug, mem}; /// Wasmi execution test runner. pub struct ExecutionTest { /// The underlying engine that is tested. engine: Engine, /// The store data which is used for the calls. data: T, /// The compiled Wasm module that is used for the calls. module: Option, } impl Default for ExecutionTest<()> { fn default() -> Self { Self { engine: Engine::default(), data: (), module: None, } } } impl ExecutionTest where T: Default + PartialEq + Eq + 'static, { /// Creates a new [`ExecutionTest`] with default initialized data. pub fn new() -> Self { Self { engine: Engine::default(), data: T::default(), module: None, } } /// Creates a new [`ExecutionTest`] with the given `config` and default initialized data. pub fn with_config(config: Config) -> Self { let engine = Engine::new(&config); let data = ::default(); Self { engine, data, module: None, } } /// Sets the Wasm source that this [`ExecutionTest`] uses for its calls. pub fn wasm(&mut self, wasm: &str) -> &mut Self { assert!(self.module.is_none(), "already provided Wasm input"); let module = Module::new(&self.engine, wasm).unwrap(); self.module = Some(module); self } /// Calls the function named `func_name` with `args` and returns the result. /// /// # Panics /// /// If no Wasm source was set before. /// /// # Errors /// /// - If instantiation fails. /// - If starting the instance fails. /// - If there is no function named `func_name`. /// - If the call fails or traps. pub fn call(&mut self, func_name: &str, args: Args) -> Result where Args: WasmParams, Results: WasmResults, { let Some(module) = &self.module else { panic!("need Wasm before calling") }; let data = mem::take(&mut self.data); let mut store = >::new(&self.engine, data); let linker = Linker::new(&self.engine); let results = linker .instantiate_and_start(&mut store, module)? .get_typed_func::(&store, func_name)? .call(&mut store, args)?; _ = mem::replace(&mut self.data, store.into_data()); Ok(results) } /// Sets the store data to `data`. pub fn data(&mut self, data: T) -> &mut Self { self.data = data; self } /// Asserts that the store data is as `expected`. pub fn assert_data(&mut self, expected: impl AsRef) -> &mut Self { assert!(&self.data == expected.as_ref()); self } } /// Convenience trait to assert that some call results are as expected. pub trait AssertResults { /// The expected call result type. type Val; /// Panics if the `Result::Ok` value is not `expected`. fn assert_results(&self, expected: Self::Val); } impl AssertResults for Result where T: PartialEq + Debug, Self: Debug, { type Val = T; fn assert_results(&self, expected: T) { let results = match self { Ok(results) => results, Err(_) => panic!("must have Ok value but found: {self:?}"), }; assert_eq!(results, &expected) } } /// Convenience trait to assert that some call traps are as expected. pub trait AssertTrap { /// Panics if the `Result::Err` value is not `expected`. fn assert_trap(&self, expected: TrapCode); } impl AssertTrap for Result where Self: Debug, { fn assert_trap(&self, expected: TrapCode) { let error = match self { Ok(_) => panic!("must have Err value but found: {self:?}"), Err(error) => error, }; let Some(trap) = error.as_trap_code() else { panic!("must have trap code Err but found: {error:?}") }; assert_eq!(trap, expected) } } wasmi-1.1.0/src/value.rs000064400000000000000000000161351046102023000132220ustar 00000000000000use crate::{ core::{TypedVal, UntypedVal}, ExternRef, Func, Ref, ValType, F32, F64, V128, }; /// Untyped instances that allow to be typed. pub trait WithType { /// The typed output type. type Output; /// Converts `self` to [`Self::Output`] using `ty`. fn with_type(self, ty: ValType) -> Self::Output; } impl WithType for UntypedVal { type Output = Val; fn with_type(self, ty: ValType) -> Self::Output { match ty { ValType::I32 => Val::I32(self.into()), ValType::I64 => Val::I64(self.into()), ValType::F32 => Val::F32(self.into()), ValType::F64 => Val::F64(self.into()), #[cfg(feature = "simd")] ValType::V128 => Val::V128(self.into()), ValType::FuncRef => Val::FuncRef(self.into()), ValType::ExternRef => Val::ExternRef(self.into()), #[cfg(not(feature = "simd"))] unsupported => unimplemented!("encountered unsupported `ValType`: {unsupported:?}"), } } } impl From for UntypedVal { fn from(value: Val) -> Self { match value { Val::I32(value) => value.into(), Val::I64(value) => value.into(), Val::F32(value) => value.into(), Val::F64(value) => value.into(), #[cfg(feature = "simd")] Val::V128(value) => value.into(), Val::FuncRef(value) => value.into(), Val::ExternRef(value) => value.into(), #[cfg(not(feature = "simd"))] unsupported => unimplemented!("encountered unsupported `Val`: {unsupported:?}"), } } } /// Runtime representation of a Wasm value. /// /// Wasm code manipulate values of the four basic value types: /// integers and floating-point (IEEE 754-2008) data of 32 or 64 bit width each, respectively. /// /// There is no distinction between signed and unsigned integer types. Instead, integers are /// interpreted by respective operations as either unsigned or signed in two’s complement representation. #[derive(Clone, Debug)] pub enum Val { /// Value of 32-bit signed or unsigned integer. I32(i32), /// Value of 64-bit signed or unsigned integer. I64(i64), /// Value of 32-bit IEEE 754-2008 floating point number. F32(F32), /// Value of 64-bit IEEE 754-2008 floating point number. F64(F64), /// 128-bit Wasm `simd` proposal vector. V128(V128), /// A nullable [`Func`] reference. FuncRef(Ref), /// A nullable [`ExternRef`] reference. ExternRef(Ref), } impl Val { /// Creates new default value of given type. #[inline] pub fn default(value_type: ValType) -> Self { match value_type { ValType::I32 => Self::I32(0), ValType::I64 => Self::I64(0), ValType::F32 => Self::F32(0f32.into()), ValType::F64 => Self::F64(0f64.into()), ValType::V128 => Self::V128(V128::from(0_u128)), ValType::FuncRef => Self::from(>::Null), ValType::ExternRef => Self::from(>::Null), } } /// Get variable type for this value. #[inline] pub fn ty(&self) -> ValType { match *self { Self::I32(_) => ValType::I32, Self::I64(_) => ValType::I64, Self::F32(_) => ValType::F32, Self::F64(_) => ValType::F64, Self::V128(_) => ValType::V128, Self::FuncRef(_) => ValType::FuncRef, Self::ExternRef(_) => ValType::ExternRef, } } /// Returns the underlying `i32` if the type matches otherwise returns `None`. pub fn i32(&self) -> Option { match self { Self::I32(value) => Some(*value), _ => None, } } /// Returns the underlying `i64` if the type matches otherwise returns `None`. pub fn i64(&self) -> Option { match self { Self::I64(value) => Some(*value), _ => None, } } /// Returns the underlying `f32` if the type matches otherwise returns `None`. pub fn f32(&self) -> Option { match self { Self::F32(value) => Some(*value), _ => None, } } /// Returns the underlying `f64` if the type matches otherwise returns `None`. pub fn f64(&self) -> Option { match self { Self::F64(value) => Some(*value), _ => None, } } /// Returns the underlying `funcref` if the type matches otherwise returns `None`. pub fn funcref(&self) -> Option> { match self { Self::FuncRef(value) => Some(value.as_ref()), _ => None, } } /// Returns the underlying `externref` if the type matches otherwise returns `None`. pub fn externref(&self) -> Option> { match self { Self::ExternRef(value) => Some(value.as_ref()), _ => None, } } } impl From for Val { #[inline] fn from(val: i32) -> Self { Self::I32(val) } } impl From for Val { #[inline] fn from(val: i64) -> Self { Self::I64(val) } } impl From for Val { #[inline] fn from(val: f32) -> Self { Self::F32(F32::from_float(val)) } } impl From for Val { #[inline] fn from(val: f64) -> Self { Self::F64(F64::from_float(val)) } } impl From for Val { #[inline] fn from(val: F32) -> Self { Self::F32(val) } } impl From for Val { #[inline] fn from(val: F64) -> Self { Self::F64(val) } } impl From for Val { #[inline] fn from(func: Func) -> Self { Self::FuncRef(Ref::Val(func)) } } impl From for Val { #[inline] fn from(externref: ExternRef) -> Self { Self::ExternRef(Ref::Val(externref)) } } impl From> for Val { #[inline] fn from(funcref: Ref) -> Self { Self::FuncRef(funcref) } } impl From> for Val { #[inline] fn from(externref: Ref) -> Self { Self::ExternRef(externref) } } impl From for Val { #[inline] fn from(value: V128) -> Self { Self::V128(value) } } impl From for Val { fn from(value: TypedVal) -> Self { let untyped = value.untyped(); match value.ty() { ValType::I32 => Self::I32(untyped.into()), ValType::I64 => Self::I64(untyped.into()), ValType::F32 => Self::F32(untyped.into()), ValType::F64 => Self::F64(untyped.into()), ValType::V128 => { #[cfg(feature = "simd")] { Self::V128(untyped.into()) } #[cfg(not(feature = "simd"))] { panic!("`simd` crate feature is disabled") } } ValType::FuncRef => Self::FuncRef(untyped.into()), ValType::ExternRef => Self::ExternRef(untyped.into()), } } } impl From for TypedVal { fn from(value: Val) -> Self { Self::new(value.ty(), value.into()) } } wasmi-1.1.0/tests/integration/call_hook.rs000064400000000000000000000201431046102023000167310ustar 00000000000000//! Tests to check if `Store::call_hook` works as intended. use wasmi::{ AsContext, AsContextMut, CallHook, Caller, Error, Extern, Func, Linker, Module, Store, TrapCode, }; /// Number of times different callback events have fired. #[derive(Default)] struct CallHookTestState { calling_wasm: u32, returning_from_wasm: u32, calling_host: u32, returning_from_host: u32, erroneous_callback_invocation: bool, } fn test_setup() -> (Store, Linker) { let store = Store::default(); let linker = >::new(store.engine()); (store, linker) } /// Prepares the test WAT and executes it. The wat defines two functions, /// `wasm_fn_a` and `wasm_fn_b` and two imports, `host_fn_a` and `host_fn_b`. /// `wasm_fn_a` calls `host_fn_a`, and `wasm_fn_b` calls `host_fn_b`. /// None of the functions accept any arguments or return any value. fn execute_wasm_fn_a( mut store: &mut Store, linker: &mut Linker, ) -> Result<(), Error> { let wasm = r#" (module (import "env" "host_fn_a" (func $host_fn_a)) (import "env" "host_fn_b" (func $host_fn_b)) (func (export "wasm_fn_a") (call $host_fn_a) ) (func (export "wasm_fn_b") (call $host_fn_b) ) ) "#; let module = Module::new(store.engine(), wasm).unwrap(); let instance = linker.instantiate_and_start(&mut store, &module).unwrap(); let wasm_fn = instance .get_export(store.as_context(), "wasm_fn_a") .and_then(Extern::into_func) .unwrap() .typed::<(), ()>(&store) .unwrap(); wasm_fn.call(store.as_context_mut(), ()) } #[test] fn call_hooks_get_called() { let (mut store, mut linker) = test_setup(); store.call_hook( |data: &mut CallHookTestState, hook_type: CallHook| -> Result<(), Error> { match hook_type { CallHook::CallingWasm => data.calling_wasm += 1, CallHook::ReturningFromWasm => data.returning_from_wasm += 1, CallHook::CallingHost => data.calling_host += 1, CallHook::ReturningFromHost => data.returning_from_host += 1, }; Ok(()) }, ); let host_fn_a = Func::wrap(&mut store, |mut caller: Caller| { // Call wasm_fn_a // Call host_fn_a assert_eq!(caller.data().calling_wasm, 1); assert_eq!(caller.data().returning_from_wasm, 0); assert_eq!(caller.data().calling_host, 1); assert_eq!(caller.data().returning_from_host, 0); caller .get_export("wasm_fn_b") .and_then(Extern::into_func) .unwrap() .typed::<(), ()>(&caller) .unwrap() .call(&mut caller, ()) .unwrap(); // Call wasm_fn_a // Call host_fn_a // Call wasm_fn_b // Call host_fn_b // Return host_fn_b // Return wasm_fn_b assert_eq!(caller.data().calling_wasm, 2); assert_eq!(caller.data().returning_from_wasm, 1); assert_eq!(caller.data().calling_host, 2); assert_eq!(caller.data().returning_from_host, 1); }); linker.define("env", "host_fn_a", host_fn_a).unwrap(); let host_fn_b = Func::wrap(&mut store, |caller: Caller| { // Call wasm_fn_a // Call host_fn_a // Call wasm_fn_b // Call host_fn_b assert_eq!(caller.data().calling_wasm, 2); assert_eq!(caller.data().returning_from_wasm, 0); assert_eq!(caller.data().calling_host, 2); assert_eq!(caller.data().returning_from_host, 0); }); linker.define("env", "host_fn_b", host_fn_b).unwrap(); execute_wasm_fn_a(&mut store, &mut linker).unwrap(); assert_eq!(store.data().calling_wasm, 2); assert_eq!(store.data().returning_from_wasm, 2); assert_eq!(store.data().calling_host, 2); assert_eq!(store.data().returning_from_host, 2); } /// Utility function to generate a callback that fails after is has been called /// `n` times. #[allow(clippy::type_complexity)] fn generate_error_after_n_calls + Clone + Send + Sync + 'static>( limit: u32, error: E, ) -> Box Result<(), Error> + Send + Sync> { Box::new(move |data, hook_type| -> Result<(), Error> { if (data.calling_wasm + data.returning_from_wasm + data.calling_host + data.returning_from_host) >= limit { return Err(error.clone().into()); } match hook_type { CallHook::CallingWasm => data.calling_wasm += 1, CallHook::ReturningFromWasm => data.returning_from_wasm += 1, CallHook::CallingHost => data.calling_host += 1, CallHook::ReturningFromHost => data.returning_from_host += 1, }; Ok(()) }) } #[test] fn call_hook_prevents_wasm_execution() { let (mut store, mut linker) = test_setup(); store.call_hook(generate_error_after_n_calls( 0, wasmi_core::TrapCode::BadConversionToInteger, )); let should_not_run = Func::wrap(&mut store, |mut caller: Caller| { caller.data_mut().erroneous_callback_invocation = true; }); linker.define("env", "host_fn_a", should_not_run).unwrap(); linker.define("env", "host_fn_b", should_not_run).unwrap(); let result = execute_wasm_fn_a(&mut store, &mut linker).map_err(|err| { err.as_trap_code() .expect("The returned error is not a trap code") }); assert!( !store.data().erroneous_callback_invocation, "A callback that should have been prevented was executed." ); assert_eq!(result, Err(TrapCode::BadConversionToInteger)); } #[test] fn call_hook_prevents_host_execution() { let (mut store, mut linker) = test_setup(); store.call_hook(generate_error_after_n_calls(1, TrapCode::BadSignature)); let should_not_run = Func::wrap(&mut store, |mut caller: Caller| { caller.data_mut().erroneous_callback_invocation = true; }); linker.define("env", "host_fn_a", should_not_run).unwrap(); linker.define("env", "host_fn_b", should_not_run).unwrap(); let result = execute_wasm_fn_a(&mut store, &mut linker).map_err(|err| { err.as_trap_code() .expect("The returned error is not a trap code") }); assert!( !store.data().erroneous_callback_invocation, "A callback that should have been prevented was executed." ); assert_eq!(result, Err(TrapCode::BadSignature)); } #[test] fn call_hook_prevents_nested_wasm_execution() { let (mut store, mut linker) = test_setup(); store.call_hook(generate_error_after_n_calls( 2, TrapCode::GrowthOperationLimited, )); let host_fn_a = Func::wrap(&mut store, |mut caller: Caller| { let result = caller .get_export("wasm_fn_b") .and_then(Extern::into_func) .unwrap() .typed::<(), ()>(&caller) .unwrap() .call(&mut caller, ()) .map_err(|err| { err.as_trap_code() .expect("The returned error is not a trap code") }); assert_eq!(result, Err(TrapCode::GrowthOperationLimited)); }); let should_not_run = Func::wrap(&mut store, |mut caller: Caller| { caller.data_mut().erroneous_callback_invocation = true; }); linker.define("env", "host_fn_a", host_fn_a).unwrap(); linker.define("env", "host_fn_b", should_not_run).unwrap(); // wasm_fn_a should also return a `TrapCode` from `CallHook::ReturningFromHost` hook. let result = execute_wasm_fn_a(&mut store, &mut linker).map_err(|err| { err.as_trap_code() .expect("The returned error is not a trap code") }); assert!( !store.data().erroneous_callback_invocation, "A callback that should have been prevented was executed." ); assert_eq!(result, Err(TrapCode::GrowthOperationLimited)); } wasmi-1.1.0/tests/integration/call_host_via_engine.rs000064400000000000000000000121751046102023000211400ustar 00000000000000//! This submodule tests the unusual use case of calling host functions through the engine from the host side. use wasmi::{Caller, Config, Engine, Error, Func, Linker, Module, Store}; /// Setup a new `Store` for testing with initial value of 5. fn setup_store() -> Store { let config = Config::default(); let engine = Engine::new(&config); Store::new(&engine, 5_i32) } #[test] fn host_call_from_host_params_0_results_0() { let mut store = setup_store(); let err_if_zero = Func::wrap(&mut store, |caller: Caller| { if *caller.data() == 0 { return Err(Error::new("test trap")); } Ok(()) }); let err_if_zero = err_if_zero.typed::<(), ()>(&mut store).unwrap(); assert!(err_if_zero.call(&mut store, ()).is_ok()); *store.data_mut() = 0; assert!(err_if_zero.call(&mut store, ()).is_err()); } #[test] fn host_call_from_host_params_0_results_1() { let mut store = setup_store(); let data_plus_42 = Func::wrap(&mut store, |caller: Caller| caller.data() + 42_i32); let data_plus_42 = data_plus_42.typed::<(), i32>(&mut store).unwrap(); assert_eq!(data_plus_42.call(&mut store, ()).unwrap(), 47_i32); *store.data_mut() = 10; assert_eq!(data_plus_42.call(&mut store, ()).unwrap(), 52_i32); } #[test] fn host_call_from_host_params_2_results_1() { let mut store = setup_store(); let sum_with_data = Func::wrap(&mut store, |caller: Caller, a: i32, b: i32| { caller.data() + a + b }); let sum_with_data = sum_with_data.typed::<(i32, i32), i32>(&mut store).unwrap(); assert_eq!(sum_with_data.call(&mut store, (1, 2)).unwrap(), 8_i32); *store.data_mut() = 10; assert_eq!(sum_with_data.call(&mut store, (10, 15)).unwrap(), 35_i32); } #[test] fn host_call_from_host_params_0_results_2() { let mut store = setup_store(); let get_data = Func::wrap(&mut store, |caller: Caller| { let data = *caller.data(); (data + data, data * data) }); let get_data = get_data.typed::<(), (i32, i32)>(&mut store).unwrap(); assert_eq!(get_data.call(&mut store, ()).unwrap(), (10, 25)); *store.data_mut() = 10; assert_eq!(get_data.call(&mut store, ()).unwrap(), (20, 100)); } #[test] fn host_call_from_host_params_4_results_4() { let mut store = setup_store(); // Function f(D, a,b,c,d) that outputs (d+D, c+D, b+D, a+D) let reverse_and_add = Func::wrap( &mut store, |caller: Caller, a: i32, b: i32, c: i32, d: i32| { let offset = caller.data(); (d + offset, c + offset, b + offset, a + offset) }, ); let reverse_and_add = reverse_and_add .typed::<(i32, i32, i32, i32), (i32, i32, i32, i32)>(&mut store) .unwrap(); assert_eq!( reverse_and_add.call(&mut store, (1, 2, 3, 4)).unwrap(), (9, 8, 7, 6) ); *store.data_mut() = 10; assert_eq!( reverse_and_add.call(&mut store, (40, 30, 20, 10)).unwrap(), (20, 30, 40, 50) ); } #[test] fn host_tail_calls_0() { let wasm = r#" (module (import "host" "sum_with_data" (func $sum_with_data (param i32) (result i32))) (func (export "test") (param i32) (result i32) (local.get 0) (return_call $sum_with_data) ) ) "#; let engine = Engine::default(); let module = Module::new(&engine, wasm).unwrap(); let mut store = Store::new(&engine, 5_i32); let mut linker = Linker::new(&engine); linker .func_wrap("host", "sum_with_data", |caller: Caller, a: i32| { caller.data() + a }) .unwrap(); let instance = linker.instantiate_and_start(&mut store, &module).unwrap(); let test = instance .get_typed_func::(&mut store, "test") .unwrap(); assert_eq!(*store.data(), 5); assert_eq!(test.call(&mut store, 1).unwrap(), 1 + 5); *store.data_mut() = 10; assert_eq!(*store.data(), 10); assert_eq!(test.call(&mut store, 5).unwrap(), 5 + 10); } #[test] fn host_tail_calls_1() { let wasm = r#" (module (import "host" "sum_with_data" (func $sum_with_data (param i32) (result i32))) (func $f (param i32) (result i32) (local.get 0) (return_call $sum_with_data) ) (func (export "test") (param i32) (result i32) (local.get 0) (call $f) ) ) "#; let engine = Engine::default(); let module = Module::new(&engine, wasm).unwrap(); let mut store = Store::new(&engine, 5_i32); let mut linker = Linker::new(&engine); linker .func_wrap("host", "sum_with_data", |caller: Caller, a: i32| { caller.data() + a }) .unwrap(); let instance = linker.instantiate_and_start(&mut store, &module).unwrap(); let test = instance .get_typed_func::(&mut store, "test") .unwrap(); assert_eq!(*store.data(), 5); assert_eq!(test.call(&mut store, 1).unwrap(), 1 + 5); *store.data_mut() = 10; assert_eq!(*store.data(), 10); assert_eq!(test.call(&mut store, 5).unwrap(), 5 + 10); } wasmi-1.1.0/tests/integration/fuel_consumption.rs000064400000000000000000000050441046102023000203720ustar 00000000000000//! Tests to check if wasmi's fuel metering works as intended. use wasmi::{Config, Engine, Error, Func, Linker, Module, Store}; /// Setup [`Engine`] and [`Store`] for fuel metering. fn test_setup() -> (Store<()>, Linker<()>) { let mut config = Config::default(); config.consume_fuel(true); config.compilation_mode(wasmi::CompilationMode::Eager); let engine = Engine::new(&config); let store = Store::new(&engine, ()); let linker = Linker::new(&engine); (store, linker) } /// Compiles the `wasm` encoded bytes into a [`Module`]. /// /// # Panics /// /// If an error occurred upon module compilation, validation or translation. fn create_module(store: &Store<()>, bytes: &[u8]) -> Module { Module::new(store.engine(), bytes).unwrap() } /// Setup [`Store`] and [`Instance`] for fuel metering. fn default_test_setup(wasm: &[u8]) -> (Store<()>, Func) { let (mut store, linker) = test_setup(); let module = create_module(&store, wasm); let instance = linker.instantiate_and_start(&mut store, &module).unwrap(); let func = instance.get_func(&store, "test").unwrap(); (store, func) } /// Asserts that the call was successful. /// /// # Note /// /// We just check if the call succeeded, not if the results are correct. /// That is to be determined by another kind of test. fn assert_success(call_result: Result) { assert!(call_result.is_ok()); assert_eq!(call_result.unwrap(), -1); } /// The test module exporting a function as `"test"`. /// /// # Note /// /// The module's `memory` has one pages minimum and one page maximum /// and thus cannot grow. Therefore the `memory.grow` operation in /// the `test` function will fail and only consume a small amount of /// fuel in lazy mode but will still consume a large amount in eager /// mode. fn test_module() -> &'static str { r#" (module (memory 1 1) (func (export "test") (result i32) (memory.grow (i32.const 1)) ) )"# } fn check_fuel_consumption(given_fuel: u64, consumed_fuel: u64) { assert!(given_fuel >= consumed_fuel); let wasm = test_module(); let (mut store, func) = default_test_setup(wasm.as_bytes()); let func = func.typed::<(), i32>(&store).unwrap(); // Now add enough fuel, so execution should succeed. store.set_fuel(given_fuel).unwrap(); // this is just enough fuel for a successful `memory.grow` assert_success(func.call(&mut store, ())); assert_eq!(given_fuel - store.get_fuel().unwrap(), consumed_fuel); } #[test] fn fuel_consumption_01() { check_fuel_consumption(4, 4); } wasmi-1.1.0/tests/integration/fuel_metering.rs000064400000000000000000000062061046102023000176270ustar 00000000000000//! Tests to check if wasmi's fuel metering works as intended. use std::fmt::Debug; use wasmi::{CompilationMode, Config, Engine, Error, Func, Linker, Module, Store, TrapCode}; /// Setup [`Engine`] and [`Store`] for fuel metering. fn test_setup(mode: CompilationMode) -> (Store<()>, Linker<()>) { let mut config = Config::default(); config.consume_fuel(true); config.compilation_mode(mode); let engine = Engine::new(&config); let store = Store::new(&engine, ()); let linker = Linker::new(&engine); (store, linker) } /// Compiles the `wasm` encoded bytes into a [`Module`]. /// /// # Panics /// /// If an error occurred upon module compilation, validation or translation. fn create_module(store: &Store<()>, bytes: &[u8]) -> Module { Module::new(store.engine(), bytes).unwrap() } /// Setup [`Store`] and [`Instance`] for fuel metering. fn default_test_setup(mode: CompilationMode, wasm: &[u8]) -> (Store<()>, Func) { let (mut store, linker) = test_setup(mode); let module = create_module(&store, wasm); let instance = linker.instantiate_and_start(&mut store, &module).unwrap(); let func = instance.get_func(&store, "test").unwrap(); (store, func) } /// Asserts that the call was successful. /// /// # Note /// /// We just check if the call succeeded, not if the results are correct. /// That is to be determined by another kind of test. #[track_caller] fn assert_success(call_result: Result) where T: Debug, { if let Err(error) = call_result { panic!("expected `Ok` but got: {error}") } } /// Asserts that the call trapped with [`TrapCode::OutOfFuel`]. #[track_caller] fn assert_out_of_fuel(call_result: Result) where T: Debug, { let Err(error) = call_result else { panic!("expected `out of fuel` error but got `Ok`") }; assert_eq!( error.as_trap_code(), Some(TrapCode::OutOfFuel), "expected `out of fuel` but got: {error}" ); } const WASM_INPUT: &str = r#" (module (func (export "test") (param $a i32) (param $b i32) (result i32) (i32.add (local.get $a) (local.get $b) ) ) ) "#; fn run_test(mode: CompilationMode, final_fuel: u64) { let (mut store, func) = default_test_setup(mode, WASM_INPUT.as_bytes()); let func = func.typed::<(i32, i32), i32>(&store).unwrap(); // No fuel -> no success. assert_out_of_fuel(func.call(&mut store, (1, 2))); assert_eq!(store.get_fuel().ok(), Some(0)); // Now set too little fuel for a start, so still no success. store.set_fuel(1).unwrap(); assert_out_of_fuel(func.call(&mut store, (1, 2))); assert_eq!(store.get_fuel().ok(), Some(1)); // Now add enough fuel, so execution should succeed. store.set_fuel(100).unwrap(); assert_success(func.call(&mut store, (1, 2))); assert_eq!(store.get_fuel().ok(), Some(final_fuel)); } #[test] fn metered_i32_add_eager() { run_test(CompilationMode::Eager, 97) } #[test] fn metered_i32_add_lazy_translation() { run_test(CompilationMode::LazyTranslation, 48) } #[test] fn metered_i32_add_lazy() { run_test(CompilationMode::Lazy, 34) } wasmi-1.1.0/tests/integration/func.rs000064400000000000000000000375571046102023000157520ustar 00000000000000//! Tests for the `Func` type in Wasmi. use assert_matches::assert_matches; use core::slice; use wasmi::{ errors::{ErrorKind, FuncError}, Engine, Func, FuncType, Store, Val, }; use wasmi_core::{ValType, F32, F64}; fn test_setup() -> Store<()> { let engine = Engine::default(); Store::new(&engine, ()) } /// Asserts that `lhs` and `rhs` tuples are equal. /// /// We need to define the following macro for the comparison of tuples with /// 16 elements since `PartialEq` and `Debug` do not seem to be implemented /// for tuples of that size. macro_rules! assert_eq_tuple { ( $lhs:ident, $rhs:ident; $($n:tt),* $(,)? ) => { $( assert_eq!($lhs.$n, $rhs.$n); )* } } // Returns a Wasm store and two binary addition [`Func`] instances. fn setup_add2() -> (Store<()>, Func, Func) { let mut store = test_setup(); let add2 = Func::wrap(&mut store, |lhs: i32, rhs: i32| lhs + rhs); let add2_dyn = Func::new( &mut store, FuncType::new([ValType::I32, ValType::I32], [ValType::I32]), |_caller, inputs: &[Val], results: &mut [Val]| { assert_eq!(inputs.len(), 2); assert_eq!(results.len(), 1); let lhs = &inputs[0].i32().unwrap(); let rhs = &inputs[1].i32().unwrap(); results[0] = (lhs + rhs).into(); Ok(()) }, ); (store, add2, add2_dyn) } #[test] fn dynamic_add2_works() { let (mut store, add2, add2_dyn) = setup_add2(); for a in 0..10 { for b in 0..10 { let params = [Val::I32(a), Val::I32(b)]; let expected = a + b; let mut result = Val::I32(0); // Call to Func with statically typed closure. add2.call(&mut store, ¶ms, slice::from_mut(&mut result)) .unwrap(); // Reset result before execution. result = Val::I32(0); // Call to Func with dynamically typed closure. add2_dyn .call(&mut store, ¶ms, slice::from_mut(&mut result)) .unwrap(); assert_eq!(result.i32(), Some(expected)); } } } #[test] fn static_add2_works() { let (mut store, add2, add2_dyn) = setup_add2(); let add2 = add2.typed::<(i32, i32), i32>(&mut store).unwrap(); let add2_dyn = add2_dyn.typed::<(i32, i32), i32>(&mut store).unwrap(); for a in 0..10 { for b in 0..10 { let expected = a + b; assert_eq!(add2.call(&mut store, (a, b)).unwrap(), expected); assert_eq!(add2_dyn.call(&mut store, (a, b)).unwrap(), expected); } } } // Returns a Wasm store and two three-way addition [`Func`] instances. fn setup_add3() -> (Store<()>, Func, Func) { let mut store = test_setup(); let add3 = Func::wrap(&mut store, |v0: i32, v1: i32, v2: i32| v0 + v1 + v2); let add3_dyn = Func::new( &mut store, FuncType::new([ValType::I32, ValType::I32, ValType::I32], [ValType::I32]), |_caller, inputs: &[Val], results: &mut [Val]| { assert_eq!(inputs.len(), 3); assert_eq!(results.len(), 1); let a = &inputs[0].i32().unwrap(); let b = &inputs[1].i32().unwrap(); let c = &inputs[2].i32().unwrap(); results[0] = (a + b + c).into(); Ok(()) }, ); (store, add3, add3_dyn) } #[test] fn dynamic_add3_works() { let (mut store, add3, add3_dyn) = setup_add3(); for a in 0..5 { for b in 0..5 { for c in 0..5 { let params = [Val::I32(a), Val::I32(b), Val::I32(c)]; let expected = a + b + c; let mut result = Val::I32(0); // Call to Func with statically typed closure. add3.call(&mut store, ¶ms, slice::from_mut(&mut result)) .unwrap(); assert_eq!(result.i32(), Some(expected)); // Reset result before execution. result = Val::I32(0); // Call to Func with dynamically typed closure. add3_dyn .call(&mut store, ¶ms, slice::from_mut(&mut result)) .unwrap(); assert_eq!(result.i32(), Some(expected)); } } } } #[test] fn static_add3_works() { let (mut store, add3, add3_dyn) = setup_add3(); let add3 = add3.typed::<(i32, i32, i32), i32>(&mut store).unwrap(); let add3_dyn = add3_dyn.typed::<(i32, i32, i32), i32>(&mut store).unwrap(); for a in 0..5 { for b in 0..5 { for c in 0..5 { let expected = a + b + c; assert_eq!(add3.call(&mut store, (a, b, c)).unwrap(), expected); assert_eq!(add3_dyn.call(&mut store, (a, b, c)).unwrap(), expected); } } } } // Returns a `Store` and two Wasm host functions that duplicate their inputs. fn setup_duplicate() -> (Store<()>, Func, Func) { let mut store = test_setup(); let duplicate = Func::wrap(&mut store, |value: i32| (value, value)); let duplicate_dyn = Func::new( &mut store, FuncType::new([ValType::I32], [ValType::I32, ValType::I32]), |_caller, inputs: &[Val], results: &mut [Val]| { assert_eq!(inputs.len(), 1); assert_eq!(results.len(), 2); let input = inputs[0].i32().unwrap(); results[0] = input.into(); results[1] = input.into(); Ok(()) }, ); (store, duplicate, duplicate_dyn) } #[test] fn dynamic_duplicate_works() { let (mut store, duplicate, duplicate_dyn) = setup_duplicate(); for input in 0..10 { let params = [Val::I32(input)]; let expected = [Val::I32(input), Val::I32(input)]; let mut results = [Val::I32(0), Val::I32(0)]; // Call to Func with statically typed closure. duplicate.call(&mut store, ¶ms, &mut results).unwrap(); assert_eq!(results[0].i32(), expected[0].i32()); assert_eq!(results[1].i32(), expected[1].i32()); // Reset result before execution. results = [Val::I32(0), Val::I32(0)]; // Call to Func with dynamically typed closure. duplicate_dyn .call(&mut store, ¶ms, &mut results) .unwrap(); assert_eq!(results[0].i32(), expected[0].i32()); assert_eq!(results[1].i32(), expected[1].i32()); } } #[test] fn static_duplicate_works() { let (mut store, duplicate, duplicate_dyn) = setup_duplicate(); let duplicate = duplicate.typed::(&mut store).unwrap(); let duplicate_dyn = duplicate_dyn.typed::(&mut store).unwrap(); for input in 0..10 { assert_eq!(duplicate.call(&mut store, input).unwrap(), (input, input)); assert_eq!( duplicate_dyn.call(&mut store, input).unwrap(), (input, input) ); } } fn setup_many_params() -> (Store<()>, Func) { let mut store = test_setup(); // Function taking 16 arguments (maximum) and doing nothing. let func = Func::wrap( &mut store, |_0: i32, _1: i32, _2: i32, _3: i32, _4: i32, _5: i32, _6: i32, _7: i32, _8: i32, _9: i32, _10: i32, _11: i32, _12: i32, _13: i32, _14: i32, _15: i32| (), ); (store, func) } type I32x16 = ( i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, ); /// Returns a `(i32, ...)` tuple with 16 elements that have ascending values. /// /// This is required as input or output of many of the following tests. fn ascending_tuple() -> I32x16 { (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15) } #[test] fn dynamic_many_params_works() { let (mut store, func) = setup_many_params(); func.call( &mut store, &[ Val::I32(0), Val::I32(1), Val::I32(2), Val::I32(3), Val::I32(4), Val::I32(5), Val::I32(6), Val::I32(7), Val::I32(8), Val::I32(9), Val::I32(10), Val::I32(11), Val::I32(12), Val::I32(13), Val::I32(14), Val::I32(15), ], &mut [], ) .unwrap(); } #[test] fn static_many_params_works() { let (mut store, func) = setup_many_params(); let typed_func = func.typed::(&mut store).unwrap(); let inputs = ascending_tuple(); let result = typed_func.call(&mut store, inputs); assert_matches!(result, Ok(())); } fn setup_many_results() -> (Store<()>, Func) { let mut store = test_setup(); // Function taking 16 arguments (maximum) and doing nothing. let func = Func::wrap(&mut store, ascending_tuple); (store, func) } #[test] fn dynamic_many_results_works() { let (mut store, func) = setup_many_results(); let mut results = [0; 16].map(Val::I32); func.call(&mut store, &[], &mut results).unwrap(); let mut i = 0; let expected = [0; 16].map(|_| { let value = Val::I32(i as _); i += 1; value }); assert_eq!( results.map(|result| result.i32().unwrap()), expected.map(|expected| expected.i32().unwrap()) ) } #[test] fn static_many_results_works() { let (mut store, func) = setup_many_results(); let typed_func = func.typed::<(), I32x16>(&mut store).unwrap(); let result = typed_func.call(&mut store, ()).unwrap(); let expected = ascending_tuple(); assert_eq_tuple!(result, expected; 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); } fn setup_many_params_many_results() -> (Store<()>, Func) { let mut store = test_setup(); // Function taking 16 arguments (maximum) and doing nothing. let func = Func::wrap( &mut store, |v0: i32, v1: i32, v2: i32, v3: i32, v4: i32, v5: i32, v6: i32, v7: i32, v8: i32, v9: i32, v10: i32, v11: i32, v12: i32, v13: i32, v14: i32, v15: i32| { ( v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, ) }, ); (store, func) } #[test] fn dynamic_many_params_many_results_works() { let (mut store, func) = setup_many_params_many_results(); let mut results = [0; 16].map(Val::I32); let inputs = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15].map(Val::I32); func.call(&mut store, &inputs, &mut results).unwrap(); assert_eq!( results.map(|result| result.i32().unwrap()), inputs.map(|input| input.i32().unwrap()), ) } #[test] fn static_many_params_many_results_works() { let (mut store, func) = setup_many_params_many_results(); let typed_func = func.typed::(&mut store).unwrap(); let inputs = ascending_tuple(); let result = typed_func.call(&mut store, inputs).unwrap(); assert_eq_tuple!(result, inputs; 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); } #[test] fn dynamic_many_types_works() { let mut store = test_setup(); // Function taking no arguments and returning 16 results as tuple (maximum). let func = Func::wrap( &mut store, |v0: i32, v1: u32, v2: i64, v3: u64, v4: F32, v5: F64| (v0, v1, v2, v3, v4, v5), ); let mut results = [0; 6].map(Val::I32); let inputs = [ Val::I32(0), Val::I32(1), Val::I64(2), Val::I64(3), Val::F32(4.0.into()), Val::F64(5.0.into()), ]; func.call(&mut store, &inputs, &mut results).unwrap(); assert_eq!(results[0].i32(), Some(0)); assert_eq!(results[1].i32(), Some(1)); assert_eq!(results[2].i64(), Some(2)); assert_eq!(results[3].i64(), Some(3)); assert_eq!(results[4].f32(), Some(4.0.into())); assert_eq!(results[5].f64(), Some(5.0.into())); } #[test] fn static_many_types_works() { let mut store = test_setup(); // Function taking no arguments and returning 16 results as tuple (maximum). let func = Func::wrap( &mut store, |v0: i32, v1: u32, v2: i64, v3: u64, v4: F32, v5: F64| (v0, v1, v2, v3, v4, v5), ); let typed_func = func .typed::<(i32, u32, i64, u64, F32, F64), (i32, u32, i64, u64, F32, F64)>(&mut store) .unwrap(); let inputs = (0, 1, 2, 3, 4.0.into(), 5.0.into()); let result = typed_func.call(&mut store, inputs).unwrap(); assert_eq!(result, inputs); } #[test] fn dynamic_type_check_works() { let mut store = test_setup(); let identity = Func::wrap(&mut store, |value: i32| value); let mut result = Val::I32(0); // Case: Too few inputs given to function. assert_matches!( identity .call(&mut store, &[], core::slice::from_mut(&mut result)) .unwrap_err() .kind(), ErrorKind::Func(FuncError::MismatchingParameterLen) ); // Case: Too many inputs given to function. assert_matches!( identity .call( &mut store, &[Val::I32(0), Val::I32(1)], core::slice::from_mut(&mut result) ) .unwrap_err() .kind(), ErrorKind::Func(FuncError::MismatchingParameterLen) ); // Case: Too few outputs given to function. assert_matches!( identity .call(&mut store, &[Val::I32(0)], &mut [],) .unwrap_err() .kind(), ErrorKind::Func(FuncError::MismatchingResultLen) ); // Case: Too many outputs given to function. assert_matches!( identity .call(&mut store, &[Val::I32(0)], &mut [Val::I32(0), Val::I32(1)],) .unwrap_err() .kind(), ErrorKind::Func(FuncError::MismatchingResultLen) ); // Case: Mismatching type given as input to function. for input in &[Val::I64(0), Val::F32(0.0.into()), Val::F64(0.0.into())] { assert_matches!( identity .call( &mut store, core::slice::from_ref(input), core::slice::from_mut(&mut result) ) .unwrap_err() .kind(), ErrorKind::Func(FuncError::MismatchingParameterType) ); } // Case: Allow for incorrect result type. // // The result type will be overwritten anyways. assert_matches!( identity.call(&mut store, &[Val::I32(0)], &mut [Val::I64(0)]), Ok(_) ); } #[test] fn static_type_check_works() { let mut store = test_setup(); let identity = Func::wrap(&mut store, |value: i32| value); // Case: Too few inputs given to function. assert_matches!( identity.typed::<(), i32>(&mut store).unwrap_err().kind(), ErrorKind::Func(FuncError::MismatchingParameterLen) ); // Case: Too many inputs given to function. assert_matches!( identity .typed::<(i32, i32), i32>(&mut store) .unwrap_err() .kind(), ErrorKind::Func(FuncError::MismatchingParameterLen) ); // Case: Too few results given to function. assert_matches!( identity.typed::(&mut store).unwrap_err().kind(), ErrorKind::Func(FuncError::MismatchingResultLen) ); // Case: Too many results given to function. assert_matches!( identity .typed::(&mut store) .unwrap_err() .kind(), ErrorKind::Func(FuncError::MismatchingResultLen) ); // Case: Mismatching type given as input to function. assert_matches!( identity.typed::(&mut store).unwrap_err().kind(), ErrorKind::Func(FuncError::MismatchingParameterType) ); // Case: Mismatching type given as output of function. assert_matches!( identity.typed::(&mut store).unwrap_err().kind(), ErrorKind::Func(FuncError::MismatchingResultType) ); } wasmi-1.1.0/tests/integration/host_call_compilation.rs000064400000000000000000000023411046102023000213440ustar 00000000000000//! This tests that a host function called from Wasm can compile Wasm modules and does not deadlock. use wasmi::{AsContextMut, Caller, Engine, Linker, Module, Store}; fn compile_module(engine: &Engine) -> wasmi::Module { let wasm = r#" (module (import "env" "compile" (func $compile)) (func (export "run") (call $compile) ) ) "#; Module::new(engine, wasm).unwrap() } #[test] fn test_compile_in_host_call() { let engine = Engine::default(); let mut store = >::new(&engine, ()); let module = compile_module(store.engine()); let mut linker = >::new(&engine); linker .func_wrap( "env", "compile", |mut caller: Caller<()>| -> Result<(), wasmi::Error> { let store = caller.as_context_mut(); let engine = store.engine(); let _module = compile_module(engine); Ok(()) }, ) .unwrap(); let instance = linker.instantiate_and_start(&mut store, &module).unwrap(); instance .get_typed_func::<(), ()>(&mut store, "run") .unwrap() .call(&mut store, ()) .unwrap(); } wasmi-1.1.0/tests/integration/host_call_instantiation.rs000064400000000000000000000043051046102023000217140ustar 00000000000000//! This tests that a host function called from Wasm can instantiate Wasm modules and does not deadlock. use std::{fmt, sync::Arc}; use wasmi::{AsContextMut, Caller, Engine, Linker, Module, Store}; #[derive(Debug)] pub enum Data { Uninit, Init { linker: Arc>, module: Arc, }, } #[derive(Debug, Copy, Clone)] pub enum Error { Uninit, InstantiationFailed, } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Error::Uninit => write!(f, "error: uninit"), Error::InstantiationFailed => write!(f, "error: instantiation failed"), } } } impl core::error::Error for Error {} impl wasmi::errors::HostError for Error {} #[test] fn test_instantiate_in_host_call() { let engine = Engine::default(); let mut store = >::new(&engine, Data::Uninit); let wasm = r#" (module (import "env" "instantiate" (func $instantiate)) (func (export "run") (call $instantiate) ) ) "#; let module = Module::new(&engine, wasm).unwrap(); let mut linker = >::new(&engine); linker .func_wrap( "env", "instantiate", |mut caller: Caller| -> Result<(), wasmi::Error> { let mut store = caller.as_context_mut(); let Data::Init { linker, module } = store.data() else { return Err(wasmi::Error::host(Error::Uninit)); }; let linker = linker.clone(); let module = module.clone(); let _instance = linker .instantiate_and_start(&mut store, &module) .map_err(|_| wasmi::Error::host(Error::InstantiationFailed))?; Ok(()) }, ) .unwrap(); let instance = linker.instantiate_and_start(&mut store, &module).unwrap(); let run = instance .get_typed_func::<(), ()>(&mut store, "run") .unwrap(); *store.data_mut() = Data::Init { linker: Arc::new(linker), module: Arc::new(module), }; run.call(&mut store, ()).unwrap(); } wasmi-1.1.0/tests/integration/host_calls_wasm.rs000064400000000000000000000033211046102023000201570ustar 00000000000000//! Test to assert that host functions that call back into //! Wasm works correctly. use wasmi::{Caller, Engine, Extern, Func, Linker, Module, Store}; fn test_setup() -> (Store<()>, Linker<()>) { let engine = Engine::default(); let store = Store::new(&engine, ()); let linker = >::new(&engine); (store, linker) } #[test] fn host_calls_wasm() { let (mut store, mut linker) = test_setup(); let host_fn = Func::wrap(&mut store, |mut caller: Caller<()>, input: i32| -> i32 { let wasm_fn = caller .get_export("square") .and_then(Extern::into_func) .unwrap() .typed::(&caller) .unwrap(); wasm_fn.call(&mut caller, input + input).unwrap() }); linker.define("env", "host_fn", host_fn).unwrap(); let wasm = r#" (module (import "env" "host_fn" (func $host_fn (param i32) (result i32))) (func (export "wasm_fn") (param i32) (result i32) (call $host_fn (local.get 0)) ) (func (export "square") (param i32) (result i32) (i32.mul (local.get 0) (local.get 0) ) ) ) "#; let module = Module::new(store.engine(), wasm).unwrap(); let instance = linker.instantiate_and_start(&mut store, &module).unwrap(); let wasm_fn = instance .get_export(&store, "wasm_fn") .and_then(Extern::into_func) .unwrap() .typed::(&store) .unwrap(); let input = 5; let expected = (input + input) * (input + input); let result = wasm_fn.call(&mut store, input).unwrap(); assert_eq!(result, expected); } wasmi-1.1.0/tests/integration/instantitation.rs000064400000000000000000000007061046102023000200510ustar 00000000000000use wasmi::{Engine, Instance, Module, Store}; #[test] fn instantiate_out_of_memory() { let wasm = r#" (module (memory (;0;) i64 1 1) (func (export "")) (data (i64.const -1095216660480) "\ff") ) "#; let engine = Engine::default(); let module = Module::new(&engine, wasm).unwrap(); let mut store = Store::new(&engine, ()); Instance::new(&mut store, &module, &[]).unwrap_err(); } wasmi-1.1.0/tests/integration/mod.rs000064400000000000000000000003471046102023000155610ustar 00000000000000mod call_hook; mod call_host_via_engine; mod fuel_consumption; mod fuel_metering; mod func; mod host_call_compilation; mod host_call_instantiation; mod host_calls_wasm; mod instantitation; mod resource_limiter; mod resumable_call; wasmi-1.1.0/tests/integration/resource_limiter.rs000064400000000000000000000173731046102023000203650ustar 00000000000000//! Tests to check if wasmi's ResourceLimiter works as intended. use wasmi::{ Config, Engine, Error, Linker, Module, Store, StoreLimits, StoreLimitsBuilder, TrapCode, TypedFunc, }; /// Setup [`Engine`] and [`Store`] for resource limiting. fn test_setup(limits: StoreLimits) -> (Store, Linker) { let config = Config::default(); let engine = Engine::new(&config); let mut store = Store::new(&engine, limits); store.limiter(|limits| limits); let linker = Linker::new(&engine); (store, linker) } /// Compiles the `wasm` encoded bytes into a [`Module`]. /// /// # Panics /// /// If an error occurred upon module compilation, validation or translation. fn create_module(store: &Store, bytes: &[u8]) -> Result { Module::new(store.engine(), bytes) } struct Test { store: Store, memory_grow: TypedFunc, memory_size: TypedFunc<(), i32>, table_grow: TypedFunc, table_size: TypedFunc<(), i32>, } impl Test { fn new(mem_pages: i32, table_elts: i32, limits: StoreLimits) -> Result { let wasm = format!( r#" (module (memory {mem_pages}) (table {table_elts} funcref) (func (export "memory_grow") (param $pages i32) (result i32) (memory.grow (local.get $pages))) (func (export "memory_size") (result i32) (memory.size)) (func (export "table_grow") (param $elts i32) (result i32) (table.grow (ref.func 0) (local.get $elts))) (func (export "table_size") (result i32) (table.size)) ) "# ); let (mut store, linker) = test_setup(limits); let module = create_module(&store, wasm.as_bytes())?; let instance = linker.instantiate_and_start(&mut store, &module)?; let memory_grow = instance.get_func(&store, "memory_grow").unwrap(); let memory_size = instance.get_func(&store, "memory_size").unwrap(); let table_grow = instance.get_func(&store, "table_grow").unwrap(); let table_size = instance.get_func(&store, "table_size").unwrap(); let memory_grow = memory_grow.typed::(&store)?; let memory_size = memory_size.typed::<(), i32>(&store)?; let table_grow = table_grow.typed::(&store)?; let table_size = table_size.typed::<(), i32>(&store)?; Ok(Self { store, memory_grow, memory_size, table_grow, table_size, }) } } #[test] fn test_big_memory_fails_to_instantiate() { let loose_limits = StoreLimitsBuilder::new().memory_size(3 * (1 << 16)).build(); let tight_limits = StoreLimitsBuilder::new().memory_size(2 * (1 << 16)).build(); assert!(Test::new(3, 0, loose_limits).is_ok()); assert!(Test::new(3, 0, tight_limits).is_err()); } #[test] fn test_big_table_fails_to_instantiate() { let loose_limits = StoreLimitsBuilder::new().table_elements(100).build(); let tight_limits = StoreLimitsBuilder::new().table_elements(99).build(); assert!(Test::new(0, 100, loose_limits).is_ok()); assert!(Test::new(0, 100, tight_limits).is_err()); } #[test] fn test_memory_count_limit() { let limits = StoreLimitsBuilder::new().memories(0).build(); assert!(Test::new(0, 0, limits).is_err()); } #[test] fn test_instance_count_limit() { let limits = StoreLimitsBuilder::new().instances(0).build(); assert!(Test::new(0, 0, limits).is_err()); } #[test] fn test_tables_count_limit() { let limits = StoreLimitsBuilder::new().tables(0).build(); assert!(Test::new(0, 0, limits).is_err()); } #[test] fn test_memory_does_not_grow_on_limited_growth() -> Result<(), Error> { let limits = StoreLimitsBuilder::new().memory_size(3 * (1 << 16)).build(); let mut test = Test::new(2, 0, limits)?; // By default the policy of a memory.grow failure is just for the instruction // to return -1 and not-grow the underlying memory. We also have the option to // trap on failure, which is exercised by the next test below. // Check memory size is what we expect. assert_eq!(test.memory_size.call(&mut test.store, ())?, 2); // First memory.grow doesn't hit the limit, so succeeds, returns previous size. assert_eq!(test.memory_grow.call(&mut test.store, 1)?, 2); // Check memory size is what we expect. assert_eq!(test.memory_size.call(&mut test.store, ())?, 3); // Second call goes past the limit, so fails to grow the memory, but returns Ok(-1) assert_eq!(test.memory_grow.call(&mut test.store, 1)?, -1); // Check memory size is what we expect. assert_eq!(test.memory_size.call(&mut test.store, ())?, 3); Ok(()) } #[test] fn test_memory_traps_on_limited_growth() -> Result<(), Error> { let limits = StoreLimitsBuilder::new() .memory_size(3 * (1 << 16)) .trap_on_grow_failure(true) .build(); let mut test = Test::new(2, 0, limits)?; // Check memory size is what we expect. assert_eq!(test.memory_size.call(&mut test.store, ())?, 2); // First memory.grow doesn't hit the limit, so succeeds, returns previous size. assert_eq!(test.memory_grow.call(&mut test.store, 1)?, 2); // Check memory size is what we expect. assert_eq!(test.memory_size.call(&mut test.store, ())?, 3); // Second call goes past the limit, so fails to grow the memory, and we've configured it to trap. assert!(matches!( test.memory_grow .call(&mut test.store, 1) .unwrap_err() .as_trap_code(), Some(TrapCode::GrowthOperationLimited) )); // Check memory size is what we expect. assert_eq!(test.memory_size.call(&mut test.store, ())?, 0x3); Ok(()) } #[test] fn test_table_does_not_grow_on_limited_growth() -> Result<(), Error> { let limits = StoreLimitsBuilder::new().table_elements(100).build(); let mut test = Test::new(0, 99, limits)?; // By default the policy of a table.grow failure is just for the instruction // to return -1 and not-grow the underlying table. We also have the option to // trap on failure, which is exercised by the next test below. // Check table size is what we expect. assert_eq!(test.table_size.call(&mut test.store, ())?, 99); // First table.grow doesn't hit the limit, so succeeds, returns previous size. assert_eq!(test.table_grow.call(&mut test.store, 1)?, 99); // Check table size is what we expect. assert_eq!(test.table_size.call(&mut test.store, ())?, 100); // Second call goes past the limit, so fails to grow the table, but returns Ok(-1) assert_eq!(test.table_grow.call(&mut test.store, 1)?, -1); // Check table size is what we expect. assert_eq!(test.table_size.call(&mut test.store, ())?, 100); Ok(()) } #[test] fn test_table_traps_on_limited_growth() -> Result<(), Error> { let limits = StoreLimitsBuilder::new() .table_elements(100) .trap_on_grow_failure(true) .build(); let mut test = Test::new(0, 99, limits)?; // Check table size is what we expect. assert_eq!(test.table_size.call(&mut test.store, ())?, 99); // First table.grow doesn't hit the limit, so succeeds, returns previous size. assert_eq!(test.table_grow.call(&mut test.store, 1)?, 99); // Check table size is what we expect. assert_eq!(test.table_size.call(&mut test.store, ())?, 100); // Second call goes past the limit, so fails to grow the table, and we've configured it to trap. assert!(matches!( test.table_grow .call(&mut test.store, 1) .unwrap_err() .as_trap_code(), Some(TrapCode::GrowthOperationLimited) )); // Check table size is what we expect. assert_eq!(test.table_size.call(&mut test.store, ())?, 100); Ok(()) } wasmi-1.1.0/tests/integration/resumable_call.rs000064400000000000000000000306611046102023000177560ustar 00000000000000//! Test to assert that resumable call feature works as intended. use core::slice; use wasmi::{ errors::ErrorKind, AsContext, AsContextMut, Caller, Config, Engine, Error, Extern, Func, Linker, Module, ResumableCall, ResumableCallHostTrap, Store, TrapCode, TypedFunc, TypedResumableCall, TypedResumableCallHostTrap, Val, ValType, }; fn test_setup(remaining: u32) -> (Store, Linker) { let mut config = Config::default(); config.wasm_tail_call(true); let engine = Engine::new(&config); let store = Store::new( &engine, TestData { _remaining: remaining, }, ); let linker = >::new(&engine); (store, linker) } #[derive(Debug)] pub struct TestData { /// How many host calls must be made before returning a non error value. _remaining: u32, } fn resumable_call_smoldot_common(wasm: &str) -> (Store, TypedFunc<(), i32>) { let (mut store, mut linker) = test_setup(0); // The important part about this test is that this // host function has more results than parameters. linker .func_wrap( "env", "host_fn", |mut _caller: Caller<'_, TestData>| -> Result { Err(Error::i32_exit(100)) }, ) .unwrap(); // The Wasm defines a single function that calls the // host function, returns 10 if the output is 0 and // returns 20 otherwise. let module = Module::new(store.engine(), wasm).unwrap(); let instance = linker.instantiate_and_start(&mut store, &module).unwrap(); let wasm_fn = instance.get_typed_func::<(), i32>(&store, "test").unwrap(); (store, wasm_fn) } pub trait ResumableExt { type Results; fn assert_finished_with(self, check_results: impl FnOnce(Self::Results) -> bool); fn unwrap_resumable_host_trap(self) -> TypedResumableCallHostTrap; } impl ResumableExt for Result, Error> { type Results = Results; fn assert_finished_with(self, check_results: impl FnOnce(Results) -> bool) { match self.unwrap() { TypedResumableCall::Finished(results) => { assert!(check_results(results)); } TypedResumableCall::HostTrap(_) => { panic!("expected `TypedResumableCall::Finished` but found: `TypedResumableCall::HostTrap`") } TypedResumableCall::OutOfFuel(_) => { panic!("expected `TypedResumableCall::Finished` but found: `TypedResumableCall::OutOfFuel`") } } } fn unwrap_resumable_host_trap(self) -> TypedResumableCallHostTrap { match self.unwrap() { TypedResumableCall::HostTrap(invocation) => invocation, TypedResumableCall::OutOfFuel(_) => { panic!("expected resumable host trap but found: `TypedResumableCall::OutOfFuel`") } TypedResumableCall::Finished(_) => { panic!("expected resumable host trap but found: `TypedResumableCall::Finished`") } } } } #[test] fn resumable_call_smoldot_01() { let (mut store, wasm_fn) = resumable_call_smoldot_common( r#" (module (import "env" "host_fn" (func $host_fn (result i32))) (func (export "test") (result i32) (call $host_fn) ) ) "#, ); let invocation = wasm_fn .call_resumable(&mut store, ()) .unwrap_resumable_host_trap(); invocation .resume(&mut store, &[Val::I32(42)]) .assert_finished_with(|result| result == 42); } #[test] fn resumable_call_smoldot_tail_01() { let (mut store, wasm_fn) = resumable_call_smoldot_common( r#" (module (import "env" "host_fn" (func $host_fn (result i32))) (func (export "test") (result i32) (return_call $host_fn) ) ) "#, ); assert_eq!( wasm_fn .call_resumable(&mut store, ()) .unwrap_err() .i32_exit_status(), Some(100), ); } #[test] fn resumable_call_smoldot_tail_02() { let (mut store, wasm_fn) = resumable_call_smoldot_common( r#" (module (import "env" "host_fn" (func $host (result i32))) (func $wasm (result i32) (return_call $host) ) (func (export "test") (result i32) (call $wasm) ) ) "#, ); let invocation = wasm_fn .call_resumable(&mut store, ()) .unwrap_resumable_host_trap(); invocation .resume(&mut store, &[Val::I32(42)]) .assert_finished_with(|result| result == 42); } #[test] fn resumable_call_smoldot_02() { let (mut store, wasm_fn) = resumable_call_smoldot_common( r#" (module (import "env" "host_fn" (func $host_fn (result i32))) (func (export "test") (result i32) (if (result i32) (i32.ne (call $host_fn) (i32.const 0)) (then (i32.const 11) ;; EXPECTED ) (else (i32.const 22) ;; FAILURE ) ) ) ) "#, ); let invocation = wasm_fn .call_resumable(&mut store, ()) .unwrap_resumable_host_trap(); invocation .resume(&mut store, &[Val::I32(42)]) .assert_finished_with(|result| result == 11); } #[test] fn resumable_call_host() { let (mut store, _linker) = test_setup(0); let host_fn = Func::wrap(&mut store, || -> Result<(), Error> { Err(Error::i32_exit(100)) }); // Even though the called host function traps we expect a normal error // since the host function is the root function of the call and therefore // it would not make sense to resume it. let error = host_fn .call_resumable(&mut store, &[], &mut []) .unwrap_err(); match error.i32_exit_status() { Some(100) => {} _ => panic!("expected Wasm trap"), } // The same test for `TypedFunc`: let trap = host_fn .typed::<(), ()>(&store) .unwrap() .call_resumable(&mut store, ()) .unwrap_err(); assert_eq!(trap.i32_exit_status(), Some(100)); } #[test] fn resumable_call() { let (mut store, mut linker) = test_setup(0); let host_fn = Func::wrap(&mut store, |input: i32| -> Result { match input { 1 => Err(Error::i32_exit(10)), 2 => Err(Error::i32_exit(20)), n => Ok(n + 1), } }); linker.define("env", "host_fn", host_fn).unwrap(); let wasm = r#" (module (import "env" "host_fn" (func $host_fn (param i32) (result i32))) (func (export "wasm_fn") (param $wasm_trap i32) (result i32) (local $i i32) (local.set $i (i32.const 0)) (local.set $i (call $host_fn (local.get $i))) ;; Ok (local.set $i (call $host_fn (local.get $i))) ;; Trap::i32_exit(1) (local.set $i (call $host_fn (local.get $i))) ;; Trap::i32_exit(2) (local.set $i (call $host_fn (local.get $i))) ;; Ok (if (i32.eq (local.get $wasm_trap) (i32.const 1)) (then unreachable) ;; trap in Wasm if $wasm_trap == 1 ) (local.get $i) ;; return i == 4 ) ) "#; let module = Module::new(store.engine(), wasm).unwrap(); let instance = linker.instantiate_and_start(&mut store, &module).unwrap(); let wasm_fn = instance .get_export(&store, "wasm_fn") .and_then(Extern::into_func) .unwrap(); run_test(wasm_fn, &mut store, false); run_test(wasm_fn, &mut store, true); run_test_typed(wasm_fn, &mut store, false); run_test_typed(wasm_fn, &mut store, true); } trait AssertResumable { type Results; type Invocation; fn assert_resumable( self, store: &Store, exit_status: i32, host_results: &[ValType], ) -> Self::Invocation; fn unwrap_finished(self) -> Self::Results; } impl AssertResumable for Result { type Results = (); type Invocation = ResumableCallHostTrap; fn assert_resumable( self, store: &Store, exit_status: i32, host_results: &[ValType], ) -> Self::Invocation { match self.unwrap() { ResumableCall::HostTrap(invocation) => { assert_eq!(invocation.host_error().i32_exit_status(), Some(exit_status)); assert_eq!(invocation.host_func().ty(store).results(), host_results,); invocation } ResumableCall::OutOfFuel(_) => panic!("expected `HostTrap` but found: `OutOfFuel`"), ResumableCall::Finished => panic!("expected `HostTrap` but found: `Finished`"), } } fn unwrap_finished(self) -> Self::Results { match self.unwrap() { ResumableCall::Finished => (), ResumableCall::HostTrap(_) => panic!("expected `Finished` but found: `HostTrap`"), ResumableCall::OutOfFuel(_) => panic!("expected `Finished` but found: `OutOfFuel`"), } } } fn run_test(wasm_fn: Func, store: &mut Store, wasm_trap: bool) { let mut results = Val::I32(0); let invocation = wasm_fn .call_resumable( store.as_context_mut(), &[Val::I32(wasm_trap as i32)], slice::from_mut(&mut results), ) .assert_resumable(store, 10, &[ValType::I32]); let invocation = invocation .resume( store.as_context_mut(), &[Val::I32(2)], slice::from_mut(&mut results), ) .assert_resumable(store, 20, &[ValType::I32]); let call = invocation.resume(store, &[Val::I32(3)], slice::from_mut(&mut results)); if wasm_trap { match call.unwrap_err().kind() { ErrorKind::TrapCode(trap) => { assert!(matches!(trap, TrapCode::UnreachableCodeReached,)); } _ => panic!("expected Wasm trap"), } } else { call.unwrap_finished(); assert_eq!(results.i32(), Some(4)); } } impl AssertResumable for Result, Error> { type Results = Results; type Invocation = TypedResumableCallHostTrap; fn assert_resumable( self, store: &Store, exit_status: i32, host_results: &[ValType], ) -> Self::Invocation { match self.unwrap() { TypedResumableCall::HostTrap(invocation) => { assert_eq!(invocation.host_error().i32_exit_status(), Some(exit_status)); assert_eq!(invocation.host_func().ty(store).results(), host_results,); invocation } TypedResumableCall::OutOfFuel(_) => { panic!("expected `HostTrap` but found: `OutOfFuel`") } TypedResumableCall::Finished(_) => panic!("expected `HostTrap` but found: `Finished`"), } } fn unwrap_finished(self) -> Self::Results { match self.unwrap() { TypedResumableCall::Finished(results) => results, TypedResumableCall::HostTrap(_) => panic!("expected `Finished` but found: `HostTrap`"), TypedResumableCall::OutOfFuel(_) => { panic!("expected `Finished` but found: `OutOfFuel`") } } } } fn run_test_typed(wasm_fn: Func, store: &mut Store, wasm_trap: bool) { let invocation = wasm_fn .typed::(store.as_context()) .unwrap() .call_resumable(store.as_context_mut(), wasm_trap as i32) .assert_resumable(store, 10, &[ValType::I32]); let invocation = invocation .resume(store.as_context_mut(), &[Val::I32(2)]) .assert_resumable(store, 20, &[ValType::I32]); let call = invocation.resume(store, &[Val::I32(3)]); if wasm_trap { match call.unwrap_err().kind() { ErrorKind::TrapCode(trap) => { assert!(matches!(trap, TrapCode::UnreachableCodeReached,)); } _ => panic!("expected Wasm trap"), } } else { assert_eq!(call.unwrap_finished(), 4); } } wasmi-1.1.0/tests/mod.rs000064400000000000000000000000631046102023000132310ustar 00000000000000//! Integration tests for Wasmi. mod integration;