Merge branch 'master' into feature/debug-prototype2

This commit is contained in:
Mark McCaskey 2020-02-19 16:13:35 -08:00 committed by GitHub
commit 64cc59179a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
95 changed files with 6423 additions and 790 deletions

View File

@ -2,6 +2,13 @@
## **[Unreleased]**
- [#1216](https://github.com/wasmerio/wasmer/pull/1216) `wasmer-interface-types` receives a binary encoder.
- [#1228](https://github.com/wasmerio/wasmer/pull/1228) Singlepass cleanup: Resolve several FIXMEs and remove protect_unix.
- [#1218](https://github.com/wasmerio/wasmer/pull/1218) Enable Cranelift verifier in debug mode. Fix bug with table indices being the wrong type.
- [#787](https://github.com/wasmerio/wasmer/pull/787) New crate `wasmer-interface-types` to implement WebAssembly Interface Types.
- [#1213](https://github.com/wasmerio/wasmer/pull/1213) Fixed WASI `fdstat` to detect `isatty` properly.
- [#1192](https://github.com/wasmerio/wasmer/pull/1192) Use `ExceptionCode` for error representation.
- [#1191](https://github.com/wasmerio/wasmer/pull/1191) Fix singlepass miscompilation on `Operator::CallIndirect`.
- [#1180](https://github.com/wasmerio/wasmer/pull/1180) Fix compilation for target `x86_64-unknown-linux-musl`.
- [#1170](https://github.com/wasmerio/wasmer/pull/1170) Improve the WasiFs builder API with convenience methods for overriding stdin, stdout, and stderr as well as a new sub-builder for controlling the permissions and properties of preopened directories. Also breaks that implementations of `WasiFile` must be `Send` -- please file an issue if this change causes you any issues.
- [#1161](https://github.com/wasmerio/wasmer/pull/1161) Require imported functions to be `Send`. This is a breaking change that fixes a soundness issue in the API.

342
Cargo.lock generated
View File

@ -2,9 +2,9 @@
# It is not intended for manual editing.
[[package]]
name = "aho-corasick"
version = "0.7.6"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d"
checksum = "743ad5a418686aad3b87fd14c43badd828cf26e214a00f92a384291cf22e1811"
dependencies = [
"memchr",
]
@ -26,9 +26,18 @@ checksum = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c"
[[package]]
name = "arrayref"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee"
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
[[package]]
name = "arrayvec"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9"
dependencies = [
"nodrop",
]
[[package]]
name = "arrayvec"
@ -38,10 +47,11 @@ checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
[[package]]
name = "atty"
version = "0.2.13"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
@ -76,22 +86,24 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "blake3"
version = "0.1.3"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41b598ce988ade113c05bc970a8c9102b59dfac0b318289971cba3379471cae4"
checksum = "46080006c1505f12f64dd2a09264b343381ed3190fa02c8005d5d662ac571c63"
dependencies = [
"arrayref",
"arrayvec",
"arrayvec 0.5.1",
"cc",
"cfg-if",
"constant_time_eq",
"crypto-mac",
"digest",
]
[[package]]
name = "bstr"
version = "0.2.8"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d6c2c5b58ab920a4f5aeaaca34b4488074e8cc7596af94e6f8c6ff247c60245"
checksum = "502ae1441a0a5adb8fbd38a5955a6416b9493e92b465de5e4a9bde6a539c2c48"
dependencies = [
"lazy_static",
"memchr",
@ -101,9 +113,9 @@ dependencies = [
[[package]]
name = "byteorder"
version = "1.3.2"
version = "1.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
[[package]]
name = "c2-chacha"
@ -131,11 +143,11 @@ checksum = "9daec6140ab4dcd38c3dd57e580b59a621172a526ac79f1527af760a55afeafd"
dependencies = [
"clap",
"log",
"proc-macro2 1.0.6",
"proc-macro2 1.0.8",
"quote 1.0.2",
"serde",
"serde_json",
"syn 1.0.11",
"syn 1.0.14",
"tempfile",
"toml",
]
@ -234,7 +246,7 @@ dependencies = [
"cranelift-codegen-shared",
"cranelift-entity",
"log",
"smallvec 1.1.0",
"smallvec 1.2.0",
"target-lexicon",
"thiserror",
]
@ -336,10 +348,11 @@ dependencies = [
[[package]]
name = "crossbeam-queue"
version = "0.2.0"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfd6515864a82d2f877b42813d4553292c6659498c9a2aa31bab5a15243c2700"
checksum = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
@ -355,10 +368,20 @@ dependencies = [
]
[[package]]
name = "csv"
version = "1.1.1"
name = "crypto-mac"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37519ccdfd73a75821cac9319d4fce15a81b9fcf75f951df5b9988aa3a0af87d"
checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5"
dependencies = [
"generic-array",
"subtle",
]
[[package]]
name = "csv"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00affe7f6ab566df61b4be3ce8cf16bc2576bca0963ceb0955e45d514bf9a279"
dependencies = [
"bstr",
"csv-core",
@ -369,9 +392,9 @@ dependencies = [
[[package]]
name = "csv-core"
version = "0.1.6"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b5cadb6b25c77aeff80ba701712494213f4a8418fcda2ee11b6560c3ad0bf4c"
checksum = "076bbef4255ffbc67b0358f2c92b5635928260c4cd28f3a65fa4b07c7a4f546d"
dependencies = [
"memchr",
]
@ -383,7 +406,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd8ce37ad4184ab2ce004c33bf6379185d3b1c95801cab51026bd271bf68eedc"
dependencies = [
"quote 1.0.2",
"syn 1.0.11",
"syn 1.0.14",
]
[[package]]
@ -405,9 +428,9 @@ dependencies = [
"byteorder",
"lazy_static",
"owning_ref",
"proc-macro2 1.0.6",
"proc-macro2 1.0.8",
"quote 1.0.2",
"syn 1.0.11",
"syn 1.0.14",
]
[[package]]
@ -428,9 +451,9 @@ checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
[[package]]
name = "erased-serde"
version = "0.3.9"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3beee4bc16478a1b26f2e80ad819a52d24745e292f521a63c16eea5f74b7eb60"
checksum = "cd7d80305c9bd8cd78e3c753eb9fb110f83621e5211f1a3afffcc812b104daf9"
dependencies = [
"serde",
]
@ -537,9 +560,9 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a36606a68532b5640dc86bb1f33c64b45c4682aad4c50f3937b317ea387f3d6"
dependencies = [
"proc-macro2 1.0.6",
"proc-macro2 1.0.8",
"quote 1.0.2",
"syn 1.0.11",
"syn 1.0.14",
]
[[package]]
@ -601,24 +624,24 @@ dependencies = [
[[package]]
name = "hermit-abi"
version = "0.1.5"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f629dc602392d3ec14bfc8a09b5e644d7ffd725102b48b81e59f90f2633621d7"
checksum = "eff2656d88f158ce120947499e971d743c05dbcbed62e5bd2f38f1698bbc3772"
dependencies = [
"libc",
]
[[package]]
name = "hex"
version = "0.4.0"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "023b39be39e3a2da62a94feb433e91e8bcd37676fbc8bea371daf52b7a769a3e"
checksum = "76cdda6bf525062a0c9e8f14ee2b37935c86b8efb6c8b69b3c83dfb518a914af"
[[package]]
name = "indexmap"
version = "1.3.1"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b54058f0a6ff80b6803da8faf8997cde53872b38f4023728f6830b06cd3c0dc"
checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292"
dependencies = [
"autocfg 1.0.0",
"serde",
@ -650,9 +673,9 @@ dependencies = [
[[package]]
name = "inventory"
version = "0.1.4"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4cece20baea71d9f3435e7bbe9adf4765f091c5fe404975f844006964a71299"
checksum = "2bf98296081bd2cb540acc09ef9c97f22b7e487841520350293605db1b2c7a27"
dependencies = [
"ctor",
"ghost",
@ -661,13 +684,13 @@ dependencies = [
[[package]]
name = "inventory-impl"
version = "0.1.4"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2869bf972e998977b1cb87e60df70341d48e48dca0823f534feb91ea44adaf9"
checksum = "0a8e30575afe28eea36a9a39136b70b2fb6b0dd0a212a5bd1f30a498395c0274"
dependencies = [
"proc-macro2 1.0.6",
"proc-macro2 1.0.8",
"quote 1.0.2",
"syn 1.0.11",
"syn 1.0.14",
]
[[package]]
@ -681,9 +704,9 @@ dependencies = [
[[package]]
name = "itoa"
version = "0.4.4"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e"
[[package]]
name = "kernel-net"
@ -695,6 +718,19 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lexical-core"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304bccb228c4b020f3a4835d247df0a02a7c4686098d4167762cfbbe4c5cb14"
dependencies = [
"arrayvec 0.4.12",
"cfg-if",
"rustc_version",
"ryu",
"static_assertions",
]
[[package]]
name = "libc"
version = "0.2.66"
@ -716,9 +752,9 @@ dependencies = [
[[package]]
name = "lock_api"
version = "0.3.2"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e57b3997725d2b60dbec1297f6c2e2957cc383db1cebd6be812163f969c7d586"
checksum = "79b2de95ecb4691949fea4716ca53cdbcfccb2c612e19644a8bad05edcf9f47b"
dependencies = [
"scopeguard",
]
@ -746,12 +782,9 @@ checksum = "7e6bcd6433cff03a4bfc3d9834d504467db1f1cf6d0ea765d37d330249ed629d"
[[package]]
name = "memchr"
version = "2.2.1"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
dependencies = [
"libc",
]
checksum = "53445de381a1f436797497c61d851644d0e8e88e6140f22872ad33a704933978"
[[package]]
name = "memmap"
@ -805,6 +838,23 @@ dependencies = [
"void",
]
[[package]]
name = "nodrop"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
[[package]]
name = "nom"
version = "5.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c433f4d505fe6ce7ff78523d2fa13a0b9f2690e181fc26168bcbe5ccc5d14e07"
dependencies = [
"lexical-core",
"memchr",
"version_check",
]
[[package]]
name = "num"
version = "0.1.42"
@ -818,39 +868,39 @@ dependencies = [
[[package]]
name = "num-integer"
version = "0.1.41"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09"
checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba"
dependencies = [
"autocfg 0.1.7",
"autocfg 1.0.0",
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.39"
version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76bd5272412d173d6bf9afdf98db8612bbabc9a7a830b7bfc9c188911716132e"
checksum = "dfb0800a0291891dd9f4fe7bd9c19384f98f7fbe0cd0f39a2c6b88b9868bbc00"
dependencies = [
"autocfg 0.1.7",
"autocfg 1.0.0",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.10"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4"
checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096"
dependencies = [
"autocfg 0.1.7",
"autocfg 1.0.0",
]
[[package]]
name = "num_cpus"
version = "1.11.1"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76dac5ed2a876980778b8b85f75a71b6cbf0db0b1232ee12f826bccb00d09d72"
checksum = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6"
dependencies = [
"hermit-abi",
"libc",
@ -858,9 +908,9 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.2.0"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "891f486f630e5c5a4916c7e16c4b24a53e78c860b646e9f8e005e4f16847bfed"
checksum = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b"
[[package]]
name = "orbclient"
@ -929,7 +979,7 @@ dependencies = [
"cloudabi",
"libc",
"redox_syscall",
"smallvec 1.1.0",
"smallvec 1.2.0",
"winapi",
]
@ -957,27 +1007,27 @@ checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
[[package]]
name = "proc-macro-error"
version = "0.4.4"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53c98547ceaea14eeb26fcadf51dc70d01a2479a7839170eae133721105e4428"
checksum = "875077759af22fa20b610ad4471d8155b321c89c3f2785526c9839b099be4e0a"
dependencies = [
"proc-macro-error-attr",
"proc-macro2 1.0.6",
"proc-macro2 1.0.8",
"quote 1.0.2",
"rustversion",
"syn 1.0.11",
"syn 1.0.14",
]
[[package]]
name = "proc-macro-error-attr"
version = "0.4.3"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2bf5d493cf5d3e296beccfd61794e445e830dfc8070a9c248ad3ee071392c6c"
checksum = "c5717d9fa2664351a01ed73ba5ef6df09c01a521cb42cb65a061432a826f3c7a"
dependencies = [
"proc-macro2 1.0.6",
"proc-macro2 1.0.8",
"quote 1.0.2",
"rustversion",
"syn 1.0.11",
"syn 1.0.14",
"syn-mid",
]
@ -992,9 +1042,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.6"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27"
checksum = "3acb317c6ff86a4e579dfa00fc5e6cca91ecbb4e7eb2df0468805b674eb88548"
dependencies = [
"unicode-xid 0.2.0",
]
@ -1014,7 +1064,7 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
dependencies = [
"proc-macro2 1.0.6",
"proc-macro2 1.0.8",
]
[[package]]
@ -1038,9 +1088,9 @@ dependencies = [
[[package]]
name = "rand"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom",
"libc",
@ -1232,9 +1282,9 @@ checksum = "d813022b2e00774a48eaf43caaa3c20b45f040ba8cbf398e2e8911a06668dbe6"
[[package]]
name = "regex"
version = "1.3.3"
version = "1.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5508c1941e4e7cb19965abef075d35a9a8b5cdf0846f30b4050e9b55dc55e87"
checksum = "322cf97724bea3ee221b78fe25ac9c46114ebb51747ad5babd51a2fc6a8235a8"
dependencies = [
"aho-corasick",
"memchr",
@ -1253,9 +1303,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.6.12"
version = "0.6.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716"
checksum = "b28dfe3fe9badec5dbf0a79a9cccad2cfc2ab5484bdb3e44cbd1ae8b3ba2be06"
[[package]]
name = "remove_dir_all"
@ -1277,13 +1327,13 @@ dependencies = [
[[package]]
name = "rustversion"
version = "1.0.1"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a0538bd897e17257b0128d2fd95c2ed6df939374073a36166051a79e2eb7986"
checksum = "b3bba175698996010c4f6dce5e7f173b6eb781fce25d2cfc45e27091ce0b79f6"
dependencies = [
"proc-macro2 1.0.6",
"proc-macro2 1.0.8",
"quote 1.0.2",
"syn 1.0.11",
"syn 1.0.14",
]
[[package]]
@ -1294,9 +1344,9 @@ checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
[[package]]
name = "same-file"
version = "1.0.5"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "585e8ddcedc187886a30fa705c47985c3fa88d06624095856b36ca0b82ff4421"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
@ -1421,16 +1471,16 @@ version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64"
dependencies = [
"proc-macro2 1.0.6",
"proc-macro2 1.0.8",
"quote 1.0.2",
"syn 1.0.11",
"syn 1.0.14",
]
[[package]]
name = "serde_json"
version = "1.0.44"
version = "1.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c575e0cc52bdd09b47f330f646cf59afc586e9c4e3ccd6fc1f625b8ea1dad7"
checksum = "9371ade75d4c2d6cb154141b9752cf3781ec9c05e0e5cf35060e1e70ee7b9c25"
dependencies = [
"itoa",
"ryu",
@ -1448,9 +1498,9 @@ dependencies = [
[[package]]
name = "smallvec"
version = "1.1.0"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44e59e0c9fa00817912ae6e4e6e3c4fe04455e75699d06eedc7d85917ed8e8f4"
checksum = "5c2fb2ec9bcd216a5b0d0ccf31ab17b5ed1d627960edff65bbe95d3ce221cefc"
[[package]]
name = "stable_deref_trait"
@ -1459,13 +1509,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8"
[[package]]
name = "string-interner"
version = "0.7.1"
name = "static_assertions"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd710eadff449a1531351b0e43eb81ea404336fa2f56c777427ab0e32a4cf183"
dependencies = [
"serde",
]
checksum = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3"
[[package]]
name = "strsim"
@ -1475,9 +1522,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "structopt"
version = "0.3.8"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df136b42d76b1fbea72e2ab3057343977b04b4a2e00836c3c7c0673829572713"
checksum = "a1bcbed7d48956fcbb5d80c6b95aedb553513de0a1b451ea92679d999c010e98"
dependencies = [
"clap",
"lazy_static",
@ -1486,17 +1533,23 @@ dependencies = [
[[package]]
name = "structopt-derive"
version = "0.4.1"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd50a87d2f7b8958055f3e73a963d78feaccca3836767a9069844e34b5b03c0a"
checksum = "095064aa1f5b94d14e635d0a5684cf140c43ae40a0fd990708d38f5d669e5f64"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2 1.0.6",
"proc-macro2 1.0.8",
"quote 1.0.2",
"syn 1.0.11",
"syn 1.0.14",
]
[[package]]
name = "subtle"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
[[package]]
name = "syn"
version = "0.15.44"
@ -1510,24 +1563,24 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.11"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff0acdb207ae2fe6d5976617f887eb1e35a2ba52c13c7234c790960cdad9238"
checksum = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f5"
dependencies = [
"proc-macro2 1.0.6",
"proc-macro2 1.0.8",
"quote 1.0.2",
"unicode-xid 0.2.0",
]
[[package]]
name = "syn-mid"
version = "0.4.0"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fd3937748a7eccff61ba5b90af1a20dbf610858923a9192ea0ecb0cb77db1d0"
checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a"
dependencies = [
"proc-macro2 1.0.6",
"proc-macro2 1.0.8",
"quote 1.0.2",
"syn 1.0.11",
"syn 1.0.14",
]
[[package]]
@ -1544,7 +1597,7 @@ checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
dependencies = [
"cfg-if",
"libc",
"rand 0.7.2",
"rand 0.7.3",
"redox_syscall",
"remove_dir_all",
"winapi",
@ -1561,22 +1614,22 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.9"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f357d1814b33bc2dc221243f8424104bfe72dbe911d5b71b3816a2dff1c977e"
checksum = "205684fd018ca14432b12cce6ea3d46763311a571c3d294e71ba3f01adcf1aad"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.9"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb2e25d25307eb8436894f727aba8f65d07adf02e5b35a13cebed48bd282bfef"
checksum = "57e4d2e50ca050ed44fb58309bdce3efa79948f84f9993ad1978de5eebdce5a7"
dependencies = [
"proc-macro2 1.0.6",
"proc-macro2 1.0.8",
"quote 1.0.2",
"syn 1.0.11",
"syn 1.0.14",
]
[[package]]
@ -1601,9 +1654,9 @@ dependencies = [
[[package]]
name = "tinytemplate"
version = "1.0.2"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4574b75faccaacddb9b284faecdf0b544b80b6b294f3d062d325c5726a209c20"
checksum = "57a3c6667d3e65eb1bc3aed6fd14011c6cbc3a0665218ab7f5daf040b9ec371a"
dependencies = [
"serde",
"serde_json",
@ -1611,9 +1664,9 @@ dependencies = [
[[package]]
name = "toml"
version = "0.5.5"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01d1404644c8b12b16bfcffa4322403a91a451584daaaa7c28d3152e6cbc98cf"
checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a"
dependencies = [
"serde",
]
@ -1643,9 +1696,9 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b63fd4799e4d0ec5cf0b055ebb8e2c3a657bbf76a84f6edc77ca60780e000204"
dependencies = [
"proc-macro2 1.0.6",
"proc-macro2 1.0.8",
"quote 1.0.2",
"syn 1.0.11",
"syn 1.0.14",
]
[[package]]
@ -1678,6 +1731,12 @@ version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
[[package]]
name = "version_check"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
[[package]]
name = "void"
version = "1.0.2"
@ -1709,9 +1768,9 @@ dependencies = [
[[package]]
name = "walkdir"
version = "2.2.9"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9658c94fa8b940eab2250bd5a457f9c48b748420d71293b165c8cdbe2f55f71e"
checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d"
dependencies = [
"same-file",
"winapi",
@ -1804,7 +1863,7 @@ checksum = "6d2e13201ef9ef527ad30a6bf1b08e3e024a40cf2731f393d80375dc88506207"
dependencies = [
"cranelift-codegen",
"log",
"smallvec 1.1.0",
"smallvec 1.2.0",
"target-lexicon",
]
@ -1856,6 +1915,13 @@ dependencies = [
"wasmer-singlepass-backend",
]
[[package]]
name = "wasmer-interface-types"
version = "0.13.1"
dependencies = [
"nom",
]
[[package]]
name = "wasmer-kernel-loader"
version = "0.1.0"
@ -2067,15 +2133,9 @@ dependencies = [
[[package]]
name = "wasmparser"
version = "0.39.3"
version = "0.45.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c702914acda5feeeffbc29e4d953e5b9ce79d8b98da4dbf18a77086e116c5470"
[[package]]
name = "wasmparser"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd242c848b25027c3e29fb2bd34c8648755b33fef66a22f65b942d06562d3020"
checksum = "8b4eab1d9971d0803729cba3617b56eb04fcb4bd25361cb63880ed41a42f20d5"
[[package]]
name = "winapi"
@ -2095,9 +2155,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.2"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9"
checksum = "4ccfbf554c6ad11084fb7517daca16cfdcaccbdadba4fc336f032a8b12c2ad80"
dependencies = [
"winapi",
]
@ -2110,9 +2170,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "x11-dl"
version = "2.18.4"
version = "2.18.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be65e1342a3baae65439cd03306778831a3d133b0d20243a7fb83fd5cf403c58"
checksum = "2bf981e3a5b3301209754218f962052d4d9ee97e478f4d26d4a6eced34c1fef8"
dependencies = [
"lazy_static",
"libc",

View File

@ -64,6 +64,7 @@ members = [
"lib/wasi-tests",
"lib/emscripten-tests",
"lib/middleware-common-tests",
"lib/interface-types",
"examples/parallel",
"examples/plugin-for-example",
"examples/parallel-guest",

View File

@ -1,4 +1,4 @@
FROM circleci/rust:1.39.0-stretch as wasmer-build-env
FROM circleci/rust:1.40.0-stretch as wasmer-build-env
RUN sudo apt-get update && \
sudo apt-get install -y --no-install-recommends \
cmake \

View File

@ -201,7 +201,7 @@ check-bench: check-bench-singlepass check-bench-llvm
# TODO: We wanted `--workspace --exclude wasmer-runtime`, but can't due
# to https://github.com/rust-lang/cargo/issues/6745 .
NOT_RUNTIME_CRATES = -p wasmer-clif-backend -p wasmer-singlepass-backend -p wasmer-middleware-common -p wasmer-runtime-core -p wasmer-emscripten -p wasmer-llvm-backend -p wasmer-wasi -p wasmer-kernel-loader -p wasmer-dev-utils -p wasmer-wasi-tests -p wasmer-middleware-common-tests -p wasmer-emscripten-tests
NOT_RUNTIME_CRATES = -p wasmer-clif-backend -p wasmer-singlepass-backend -p wasmer-middleware-common -p wasmer-runtime-core -p wasmer-emscripten -p wasmer-llvm-backend -p wasmer-wasi -p wasmer-kernel-loader -p wasmer-dev-utils -p wasmer-wasi-tests -p wasmer-middleware-common-tests -p wasmer-emscripten-tests -p wasmer-interface-types
RUNTIME_CHECK = cargo check --manifest-path lib/runtime/Cargo.toml --no-default-features
check: check-bench
cargo check $(NOT_RUNTIME_CRATES)
@ -308,7 +308,7 @@ dep-graph:
cargo deps --optional-deps --filter wasmer-wasi wasmer-wasi-tests wasmer-kernel-loader wasmer-dev-utils wasmer-llvm-backend wasmer-emscripten wasmer-emscripten-tests wasmer-runtime-core wasmer-runtime wasmer-middleware-common wasmer-middleware-common-tests wasmer-singlepass-backend wasmer-clif-backend wasmer --manifest-path Cargo.toml | dot -Tpng > wasmer_depgraph.png
docs:
cargo doc --features=backend-singlepass,backend-cranelift,backend-llvm,docs,wasi,managed --all --no-deps
cargo doc --features=backend-singlepass,backend-cranelift,backend-llvm,docs,wasi,managed --workspace --document-private-items --no-deps
cd lib/runtime-c-api/ && doxygen doxyfile && cd ..
mkdir -p api-docs
mkdir -p api-docs/c

View File

@ -174,7 +174,7 @@ nginx and Lua do not work on Windows - you can track the progress on [this issue
## Building
[![Rustc Version 1.39+](https://img.shields.io/badge/rustc-1.39+-red.svg?style=flat-square)](https://blog.rust-lang.org/2019/11/07/Rust-1.39.0.html)
[![Rustc Version 1.40+](https://img.shields.io/badge/rustc-1.40+-red.svg?style=flat-square)](https://blog.rust-lang.org/2019/12/19/Rust-1.40.0.html)
Wasmer is built with [Cargo](https://crates.io/), the Rust package manager.

View File

@ -21,7 +21,7 @@ jobs:
- script: cargo fmt --all -- --check
displayName: Lint
variables:
rust_toolchain: '1.39.0'
rust_toolchain: '1.40.0'
- job: clippy_lint
pool:
@ -57,7 +57,7 @@ jobs:
CARGO_HTTP_CHECK_REVOKE: false
windows:
imageName: "vs2017-win2016"
rust_toolchain: '1.39.0'
rust_toolchain: '1.40.0'
pool:
vmImage: $(imageName)
condition: in(variables['Build.SourceBranch'], 'refs/heads/master', 'refs/heads/staging', 'refs/heads/trying')
@ -118,7 +118,7 @@ jobs:
MACOSX_DEPLOYMENT_TARGET: 10.10
windows:
imageName: "vs2017-win2016"
rust_toolchain: '1.39.0'
rust_toolchain: '1.40.0'
# RUSTFLAGS: -Ctarget-feature=+crt-static
pool:
vmImage: $(imageName)
@ -188,7 +188,7 @@ jobs:
MACOSX_DEPLOYMENT_TARGET: 10.10
windows:
imageName: "vs2017-win2016"
rust_toolchain: '1.39.0'
rust_toolchain: '1.40.0'
# RUSTFLAGS: -Ctarget-feature=+crt-static
pool:
vmImage: $(imageName)
@ -235,7 +235,7 @@ jobs:
pool:
vmImage: "ubuntu-18.04"
variables:
rust_toolchain: nightly-2019-08-15
rust_toolchain: nightly-2019-12-19
condition: in(variables['Build.SourceBranch'], 'refs/heads/master', 'refs/heads/staging', 'refs/heads/trying')
steps:
- checkout: self

View File

@ -1 +0,0 @@
<meta http-equiv="refresh" content="0; url=rust/wasmer_runtime/index.html">

View File

@ -145,7 +145,7 @@ wasmer_link() {
printf "$cyan> Adding to bash profile...$reset\n"
WASMER_PROFILE="$(wasmer_detect_profile)"
LOAD_STR="\n# Wasmer\nexport WASMER_DIR=\"$INSTALL_DIRECTORY\"\n[ -s \"\$WASMER_DIR/wasmer.sh\" ] && source \"\$WASMER_DIR/wasmer.sh\"\n"
SOURCE_STR="# Wasmer config\nexport WASMER_DIR=\"$INSTALL_DIRECTORY\"\nexport WASMER_CACHE_DIR=\"\$WASMER_DIR/cache\"\nexport PATH=\"\$WASMER_DIR/bin:\$WASMER_DIR/globals/wapm_packages/.bin:\$PATH\"\n"
SOURCE_STR="# Wasmer config\nexport WASMER_DIR=\"$INSTALL_DIRECTORY\"\nexport WASMER_CACHE_DIR=\"\$WASMER_DIR/cache\"\nexport PATH=\"\$WASMER_DIR/bin:\$PATH:\$WASMER_DIR/globals/wapm_packages/.bin\"\n"
# We create the wasmer.sh file
printf "$SOURCE_STR" > "$INSTALL_DIRECTORY/wasmer.sh"

View File

@ -24,6 +24,7 @@ Wasmer intends to support different integrations:
- [WASI](./wasi): run WebAssembly files with the [WASI ABI](https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webassembly-system-interface/).
- [Emscripten](./emscripten): run Emscripten-generated WebAssembly files, such as [Lua](../examples/lua.wasm) or [nginx](../examples/nginx/nginx.wasm).
- **Your own ABI**: Do you want to create your own ABI? Here's a [repo showcasing how](https://github.com/wasmerio/wasmer-rust-customabi-example)!
- Go ABI: _we will work on this soon! Want to give us a hand? ✋_
- Blazor: _research period, see [tracking issue](https://github.com/wasmerio/wasmer/issues/97)_

View File

@ -42,7 +42,9 @@ fn get_isa() -> Box<dyn isa::TargetIsa> {
builder.set("opt_level", "speed_and_size").unwrap();
builder.set("jump_tables_enabled", "false").unwrap();
if cfg!(not(test)) && cfg!(not(debug)) {
if cfg!(test) || cfg!(debug_assertions) {
builder.set("enable_verifier", "true").unwrap();
} else {
builder.set("enable_verifier", "false").unwrap();
}

View File

@ -77,6 +77,9 @@ pub extern "C" fn nearbyintf64(x: f64) -> f64 {
}
// FIXME: Is there a replacement on AArch64?
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
#[cfg(all(
any(target_os = "freebsd", target_os = "linux"),
target_arch = "aarch64"
))]
#[no_mangle]
pub extern "C" fn __rust_probestack() {}

View File

@ -11,7 +11,7 @@ use crate::{
use byteorder::{ByteOrder, LittleEndian};
use cranelift_codegen::{
binemit::{Stackmap, StackmapSink},
ir, isa, Context, ValueLabelsRanges,
ir, isa,CodegenError, Context, ValueLabelsRanges,
};
use rayon::prelude::*;
use std::{
@ -141,7 +141,12 @@ impl FuncResolverBuilder {
&mut local_trap_sink,
&mut stackmap_sink,
)
.map_err(|e| CompileError::InternalError { msg: e.to_string() })?;
.map_err(|e| match e {
CodegenError::Verifier(v) => CompileError::InternalError {
msg: format!("Verifier error: {}", v),
},
_ => CompileError::InternalError { msg: e.to_string() },
})?;
let debug_entry = if generate_debug_info {
let func = &ctx.func;

View File

@ -8,7 +8,7 @@ use std::{any::Any, cell::Cell, ptr::NonNull, sync::Arc};
use wasmer_runtime_core::{
backend::RunnableModule,
module::ModuleInfo,
typed_func::{Trampoline, Wasm, WasmTrapInfo},
typed_func::{Trampoline, Wasm},
types::{LocalFuncIndex, SigIndex},
vm,
};
@ -29,10 +29,7 @@ thread_local! {
pub static TRAP_EARLY_DATA: Cell<Option<Box<dyn Any + Send>>> = Cell::new(None);
}
pub enum CallProtError {
Trap(WasmTrapInfo),
Error(Box<dyn Any + Send>),
}
pub struct CallProtError(pub Box<dyn Any + Send>);
pub struct Caller {
handler_data: HandlerData,
@ -66,8 +63,7 @@ impl RunnableModule for Caller {
func: NonNull<vm::Func>,
args: *const u64,
rets: *mut u64,
trap_info: *mut WasmTrapInfo,
user_error: *mut Option<Box<dyn Any + Send>>,
error_out: *mut Option<Box<dyn Any + Send>>,
invoke_env: Option<NonNull<c_void>>,
) -> bool {
let handler_data = &*invoke_env.unwrap().cast().as_ptr();
@ -84,10 +80,7 @@ impl RunnableModule for Caller {
match res {
Err(err) => {
match err {
CallProtError::Trap(info) => *trap_info = info,
CallProtError::Error(data) => *user_error = Some(data),
}
*error_out = Some(err.0);
false
}
Ok(()) => true,

View File

@ -18,7 +18,7 @@ use nix::sys::signal::{
use std::cell::{Cell, UnsafeCell};
use std::ptr;
use std::sync::Once;
use wasmer_runtime_core::typed_func::WasmTrapInfo;
use wasmer_runtime_core::backend::ExceptionCode;
extern "C" fn signal_trap_handler(
signum: ::nix::libc::c_int,
@ -79,7 +79,7 @@ pub fn call_protected<T>(
*jmp_buf = prev_jmp_buf;
if let Some(data) = super::TRAP_EARLY_DATA.with(|cell| cell.replace(None)) {
Err(CallProtError::Error(data))
Err(CallProtError(data))
} else {
let (faulting_addr, inst_ptr) = CAUGHT_ADDRESSES.with(|cell| cell.get());
@ -88,21 +88,31 @@ pub fn call_protected<T>(
srcloc: _,
}) = handler_data.lookup(inst_ptr)
{
Err(CallProtError::Trap(match Signal::from_c_int(signum) {
Err(CallProtError(Box::new(match Signal::from_c_int(signum) {
Ok(SIGILL) => match trapcode {
TrapCode::BadSignature => WasmTrapInfo::IncorrectCallIndirectSignature,
TrapCode::IndirectCallToNull => WasmTrapInfo::CallIndirectOOB,
TrapCode::HeapOutOfBounds => WasmTrapInfo::MemoryOutOfBounds,
TrapCode::TableOutOfBounds => WasmTrapInfo::CallIndirectOOB,
_ => WasmTrapInfo::Unknown,
TrapCode::StackOverflow => ExceptionCode::MemoryOutOfBounds,
TrapCode::HeapOutOfBounds => ExceptionCode::MemoryOutOfBounds,
TrapCode::TableOutOfBounds => ExceptionCode::CallIndirectOOB,
TrapCode::OutOfBounds => ExceptionCode::MemoryOutOfBounds,
TrapCode::IndirectCallToNull => ExceptionCode::CallIndirectOOB,
TrapCode::BadSignature => ExceptionCode::IncorrectCallIndirectSignature,
TrapCode::IntegerOverflow => ExceptionCode::IllegalArithmetic,
TrapCode::IntegerDivisionByZero => ExceptionCode::IllegalArithmetic,
TrapCode::BadConversionToInteger => ExceptionCode::IllegalArithmetic,
TrapCode::UnreachableCodeReached => ExceptionCode::Unreachable,
_ => {
return Err(CallProtError(Box::new(
"unknown clif trap code".to_string(),
)))
}
},
Ok(SIGSEGV) | Ok(SIGBUS) => WasmTrapInfo::MemoryOutOfBounds,
Ok(SIGFPE) => WasmTrapInfo::IllegalArithmetic,
Ok(SIGSEGV) | Ok(SIGBUS) => ExceptionCode::MemoryOutOfBounds,
Ok(SIGFPE) => ExceptionCode::IllegalArithmetic,
_ => unimplemented!(
"WasmTrapInfo::Unknown signal:{:?}",
"ExceptionCode::Unknown signal:{:?}",
Signal::from_c_int(signum)
),
}))
})))
} else {
let signal = match Signal::from_c_int(signum) {
Ok(SIGFPE) => "floating-point exception",
@ -114,7 +124,7 @@ pub fn call_protected<T>(
};
// When the trap-handler is fully implemented, this will return more information.
let s = format!("unknown trap at {:p} - {}", faulting_addr, signal);
Err(CallProtError::Error(Box::new(s)))
Err(CallProtError(Box::new(s)))
}
}
} else {
@ -141,6 +151,84 @@ pub unsafe fn do_unwind(signum: i32, siginfo: *const c_void, ucontext: *const c_
longjmp(jmp_buf as *mut ::nix::libc::c_void, signum)
}
#[cfg(all(target_os = "freebsd", target_arch = "aarch64"))]
unsafe fn get_faulting_addr_and_ip(
_siginfo: *const c_void,
_ucontext: *const c_void,
) -> (*const c_void, *const c_void) {
(::std::ptr::null(), ::std::ptr::null())
}
#[cfg(all(target_os = "freebsd", target_arch = "x86_64"))]
unsafe fn get_faulting_addr_and_ip(
siginfo: *const c_void,
ucontext: *const c_void,
) -> (*const c_void, *const c_void) {
#[repr(C)]
pub struct ucontext_t {
uc_sigmask: libc::sigset_t,
uc_mcontext: mcontext_t,
uc_link: *mut ucontext_t,
uc_stack: libc::stack_t,
uc_flags: i32,
__spare__: [i32; 4],
}
#[repr(C)]
pub struct mcontext_t {
mc_onstack: u64,
mc_rdi: u64,
mc_rsi: u64,
mc_rdx: u64,
mc_rcx: u64,
mc_r8: u64,
mc_r9: u64,
mc_rax: u64,
mc_rbx: u64,
mc_rbp: u64,
mc_r10: u64,
mc_r11: u64,
mc_r12: u64,
mc_r13: u64,
mc_r14: u64,
mc_r15: u64,
mc_trapno: u32,
mc_fs: u16,
mc_gs: u16,
mc_addr: u64,
mc_flags: u32,
mc_es: u16,
mc_ds: u16,
mc_err: u64,
mc_rip: u64,
mc_cs: u64,
mc_rflags: u64,
mc_rsp: u64,
mc_ss: u64,
mc_len: i64,
mc_fpformat: i64,
mc_ownedfp: i64,
mc_fpstate: [i64; 64], // mc_fpstate[0] is a pointer to savefpu
mc_fsbase: u64,
mc_gsbase: u64,
mc_xfpustate: u64,
mc_xfpustate_len: u64,
mc_spare: [i64; 4],
}
let siginfo = siginfo as *const siginfo_t;
let si_addr = (*siginfo).si_addr;
let ucontext = ucontext as *const ucontext_t;
let rip = (*ucontext).uc_mcontext.mc_rip;
(si_addr, rip as _)
}
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
unsafe fn get_faulting_addr_and_ip(
_siginfo: *const c_void,
@ -239,6 +327,8 @@ unsafe fn get_faulting_addr_and_ip(
}
#[cfg(not(any(
all(target_os = "freebsd", target_arch = "aarch64"),
all(target_os = "freebsd", target_arch = "x86_64"),
all(target_os = "macos", target_arch = "x86_64"),
all(target_os = "linux", target_arch = "x86_64"),
all(target_os = "linux", target_arch = "aarch64"),

View File

@ -8,7 +8,8 @@ use std::{
ptr::{self, NonNull},
};
use wasmer_runtime_core::{
typed_func::{Trampoline, WasmTrapInfo},
backend::ExceptionCode,
typed_func::Trampoline,
vm::{Ctx, Func},
};
use wasmer_win_exception_handler::CallProtectedData;
@ -62,22 +63,26 @@ pub fn call_protected(
srcloc: _,
}) = handler_data.lookup(instruction_pointer as _)
{
Err(CallProtError::Trap(match code as DWORD {
EXCEPTION_ACCESS_VIOLATION => WasmTrapInfo::MemoryOutOfBounds,
Err(CallProtError(Box::new(match code as DWORD {
EXCEPTION_ACCESS_VIOLATION => ExceptionCode::MemoryOutOfBounds,
EXCEPTION_ILLEGAL_INSTRUCTION => match trapcode {
TrapCode::BadSignature => WasmTrapInfo::IncorrectCallIndirectSignature,
TrapCode::IndirectCallToNull => WasmTrapInfo::CallIndirectOOB,
TrapCode::HeapOutOfBounds => WasmTrapInfo::MemoryOutOfBounds,
TrapCode::TableOutOfBounds => WasmTrapInfo::CallIndirectOOB,
TrapCode::UnreachableCodeReached => WasmTrapInfo::Unreachable,
_ => WasmTrapInfo::Unknown,
TrapCode::BadSignature => ExceptionCode::IncorrectCallIndirectSignature,
TrapCode::IndirectCallToNull => ExceptionCode::CallIndirectOOB,
TrapCode::HeapOutOfBounds => ExceptionCode::MemoryOutOfBounds,
TrapCode::TableOutOfBounds => ExceptionCode::CallIndirectOOB,
TrapCode::UnreachableCodeReached => ExceptionCode::Unreachable,
_ => return Err(CallProtError(Box::new("unknown trap code".to_string()))),
},
EXCEPTION_STACK_OVERFLOW => WasmTrapInfo::Unknown,
EXCEPTION_STACK_OVERFLOW => ExceptionCode::MemoryOutOfBounds,
EXCEPTION_INT_DIVIDE_BY_ZERO | EXCEPTION_INT_OVERFLOW => {
WasmTrapInfo::IllegalArithmetic
ExceptionCode::IllegalArithmetic
}
_ => WasmTrapInfo::Unknown,
}))
_ => {
return Err(CallProtError(Box::new(
"unknown exception code".to_string(),
)))
}
})))
} else {
let signal = match code as DWORD {
EXCEPTION_FLT_DENORMAL_OPERAND
@ -110,7 +115,7 @@ pub fn call_protected(
exception_address, code, signal,
);
Err(CallProtError::Error(Box::new(s)))
Err(CallProtError(Box::new(s)))
}
}

View File

@ -130,7 +130,19 @@ extern "C" {
pub fn lstat64(path: *const libc::c_char, buf: *mut c_void) -> c_int;
}
#[cfg(not(target_os = "macos"))]
// Linking to functions that are not provided by rust libc
#[cfg(target_os = "freebsd")]
#[link(name = "c")]
extern "C" {
pub fn wait4(pid: pid_t, status: *mut c_int, options: c_int, rusage: *mut rusage) -> pid_t;
pub fn fdatasync(fd: c_int) -> c_int;
pub fn ftruncate(fd: c_int, length: i64) -> c_int;
pub fn lstat(path: *const libc::c_char, buf: *mut stat) -> c_int;
}
#[cfg(target_os = "freebsd")]
use libc::madvise;
#[cfg(not(any(target_os = "freebsd", target_os = "macos")))]
use libc::{fallocate, fdatasync, ftruncate64, lstat, madvise, wait4};
// Another conditional constant for name resolution: Macos et iOS use
@ -255,10 +267,14 @@ pub fn ___syscall194(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_in
debug!("emscripten::___syscall194 (ftruncate64) {}", _which);
let _fd: c_int = varargs.get(ctx);
let _length: i64 = varargs.get(ctx);
#[cfg(not(target_os = "macos"))]
#[cfg(not(any(target_os = "freebsd", target_os = "macos")))]
unsafe {
ftruncate64(_fd, _length)
}
#[cfg(target_os = "freebsd")]
unsafe {
ftruncate(_fd, _length)
}
#[cfg(target_os = "macos")]
unimplemented!("emscripten::___syscall194 (ftruncate64) {}", _which)
}
@ -621,7 +637,7 @@ pub fn ___syscall102(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_in
let mut host_address: sockaddr = sockaddr {
sa_family: Default::default(),
sa_data: Default::default(),
#[cfg(target_os = "macos")]
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
sa_len: Default::default(),
};
let fd = unsafe { accept(socket, &mut host_address, address_len_addr) };
@ -655,7 +671,7 @@ pub fn ___syscall102(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_in
let mut sock_addr_host: sockaddr = sockaddr {
sa_family: Default::default(),
sa_data: Default::default(),
#[cfg(target_os = "macos")]
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
sa_len: Default::default(),
};
let ret = unsafe {
@ -1056,8 +1072,17 @@ pub fn ___syscall220(ctx: &mut Ctx, _which: i32, mut varargs: VarArgs) -> i32 {
break;
}
#[allow(clippy::cast_ptr_alignment)]
#[cfg(not(target_os = "freebsd"))]
unsafe {
*(dirp.add(pos) as *mut u32) = (*dirent).d_ino as u32;
}
#[allow(clippy::cast_ptr_alignment)]
#[cfg(target_os = "freebsd")]
unsafe {
*(dirp.add(pos) as *mut u32) = (*dirent).d_fileno as u32;
}
#[allow(clippy::cast_ptr_alignment)]
unsafe {
*(dirp.add(pos + 4) as *mut u32) = pos as u32;
*(dirp.add(pos + 8) as *mut u16) = offset as u16;
*(dirp.add(pos + 10) as *mut u8) = (*dirent).d_type;
@ -1106,11 +1131,11 @@ pub fn ___syscall324(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_in
let _mode: c_int = varargs.get(ctx);
let _offset: off_t = varargs.get(ctx);
let _len: off_t = varargs.get(ctx);
#[cfg(not(target_os = "macos"))]
#[cfg(not(any(target_os = "freebsd", target_os = "macos")))]
unsafe {
fallocate(_fd, _mode, _offset, _len)
}
#[cfg(target_os = "macos")]
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
{
unimplemented!("emscripten::___syscall324 (fallocate) {}", _which)
}

View File

@ -31,6 +31,11 @@ use wasmer_runtime_core::vm::Ctx;
#[cfg(target_os = "linux")]
use libc::{CLOCK_MONOTONIC, CLOCK_MONOTONIC_COARSE, CLOCK_REALTIME};
#[cfg(target_os = "freebsd")]
use libc::{CLOCK_MONOTONIC, CLOCK_REALTIME};
#[cfg(target_os = "freebsd")]
const CLOCK_MONOTONIC_COARSE: clockid_t = 6;
#[cfg(target_os = "macos")]
use libc::CLOCK_REALTIME;
#[cfg(target_os = "macos")]

View File

@ -0,0 +1,11 @@
[package]
name = "wasmer-interface-types"
version = "0.13.1"
description = "WebAssembly Interface Types library for Wasmer"
license = "MIT"
authors = ["The Wasmer Engineering Team <engineering@wasmer.io>"]
repository = "https://github.com/wasmerio/wasmer"
edition = "2018"
[dependencies]
nom = "5.1"

View File

@ -0,0 +1,32 @@
<p align="center">
<a href="https://wasmer.io" target="_blank" rel="noopener noreferrer">
<img width="300" src="https://raw.githubusercontent.com/wasmerio/wasmer/master/logo.png" alt="Wasmer logo">
</a>
</p>
<p align="center">
<a href="https://dev.azure.com/wasmerio/wasmer/_build/latest?definitionId=3&branchName=master">
<img src="https://img.shields.io/azure-devops/build/wasmerio/wasmer/3.svg?style=flat-square" alt="Build Status">
</a>
<a href="https://github.com/wasmerio/wasmer/blob/master/LICENSE">
<img src="https://img.shields.io/github/license/wasmerio/wasmer.svg?style=flat-square" alt="License">
</a>
<a href="https://spectrum.chat/wasmer">
<img src="https://withspectrum.github.io/badge/badge.svg" alt="Join the Wasmer Community">
</a>
<a href="https://crates.io/crates/wasmer-interface-types">
<img src="https://img.shields.io/crates/d/wasmer-interface-types.svg?style=flat-square" alt="Number of downloads from crates.io">
</a>
<a href="https://docs.rs/wasmer-interface-types">
<img src="https://docs.rs/wasmer-interface-types/badge.svg" alt="Read our API documentation">
</a>
</p>
# Wasmer Interface Types
Wasmer is a standalone JIT WebAssembly runtime, aiming to be fully
compatible with WASI, Emscripten, Rust and Go. [Learn
more](https://github.com/wasmerio/wasmer).
This crate is an implementation of [the living WebAssembly Interface
Types standard](https://github.com/WebAssembly/interface-types).

View File

@ -0,0 +1,209 @@
//! Represents the WIT language as a tree. This is the central
//! representation of the language.
use crate::interpreter::Instruction;
use std::str;
/// Represents the types supported by WIT.
#[derive(PartialEq, Clone, Debug)]
pub enum InterfaceType {
/// An integer.
Int,
/// A float.
Float,
/// Opaque reference.
Any,
/// A string.
String,
/// A sequence.
Seq,
/// A 32-bits integer.
I32,
/// A 64-bits integer.
I64,
/// A 32-bits float.
F32,
/// A 64-bits float.
F64,
/// An `any` reference.
AnyRef,
}
/// Represents the kind of adapter.
#[derive(PartialEq, Debug)]
pub(crate) enum AdapterKind {
/// An adapter defined for an imported function of a WebAssembly instance.
Import,
/// An adapter defined for an exported function of a WebAssembly instance.
Export,
/// A helper function.
HelperFunction,
}
/// Represents an exported function signature.
#[derive(PartialEq, Debug)]
pub struct Export<'input> {
/// The function name.
pub name: &'input str,
/// The function input types.
pub input_types: Vec<InterfaceType>,
/// The function output types.
pub output_types: Vec<InterfaceType>,
}
/// Represents an imported function signature.
#[derive(PartialEq, Debug)]
pub struct Import<'input> {
/// The function namespace.
pub namespace: &'input str,
/// The function name.
pub name: &'input str,
/// The function input types.
pub input_types: Vec<InterfaceType>,
/// The function output types.
pub output_types: Vec<InterfaceType>,
}
/// Represents a structural type.
#[derive(PartialEq, Debug)]
pub struct Type<'input> {
/// The type name.
pub name: &'input str,
/// The field names.
field_names: Vec<&'input str>,
/// The field types.
field_types: Vec<InterfaceType>,
}
impl<'input> Type<'input> {
/// Creates a new `Type`.
///
/// The constructor panics if there is the length of `names` is
/// different than the length of `types`.
pub fn new(type_name: &'input str, names: Vec<&'input str>, types: Vec<InterfaceType>) -> Self {
assert_eq!(
names.len(),
types.len(),
"There must be the same number of field names than field types."
);
Self {
name: type_name,
field_names: names,
field_types: types,
}
}
/// Adds a new field to the type.
pub fn add_field(&mut self, name: &'input str, ty: InterfaceType) {
self.field_names.push(name);
self.field_types.push(ty);
}
/// Returns the field names.
pub fn field_names(&self) -> &Vec<&'input str> {
&self.field_names
}
/// Returns the field types.
pub fn field_types(&self) -> &Vec<InterfaceType> {
&self.field_types
}
}
/// Represents an adapter.
#[derive(PartialEq, Debug)]
pub enum Adapter<'input> {
/// An adapter for an imported function.
Import {
/// The function namespace.
namespace: &'input str,
/// The function name.
name: &'input str,
/// The function input types.
input_types: Vec<InterfaceType>,
/// The function output types.
output_types: Vec<InterfaceType>,
/// The instructions of the adapter.
instructions: Vec<Instruction<'input>>,
},
/// An adapter for an exported function.
Export {
/// The function name.
name: &'input str,
/// The function input types.
input_types: Vec<InterfaceType>,
/// The function output types.
output_types: Vec<InterfaceType>,
/// The instructions of the adapter.
instructions: Vec<Instruction<'input>>,
},
/// An adapter for a helper function.
HelperFunction {
/// The helper name.
name: &'input str,
/// The helper input types.
input_types: Vec<InterfaceType>,
/// The helper output types.
output_types: Vec<InterfaceType>,
/// The instructions of the adapter.
instructions: Vec<Instruction<'input>>,
},
}
/// Represented a forwarded export.
#[derive(PartialEq, Debug)]
pub struct Forward<'input> {
/// The forwarded export name.
pub name: &'input str,
}
/// Represents a set of interfaces, i.e. it entirely describes a WIT
/// definition.
#[derive(PartialEq, Debug)]
pub struct Interfaces<'input> {
/// All the exported functions.
pub exports: Vec<Export<'input>>,
/// All the types.
pub types: Vec<Type<'input>>,
/// All the imported functions.
pub imports: Vec<Import<'input>>,
/// All the adapters.
pub adapters: Vec<Adapter<'input>>,
/// All the forwarded functions.
pub forwards: Vec<Forward<'input>>,
}

View File

@ -0,0 +1,990 @@
//! Parse the WIT binary representation into an AST.
use crate::{ast::*, interpreter::Instruction};
use nom::{
error::{make_error, ErrorKind, ParseError},
Err, IResult,
};
use std::{convert::TryFrom, str};
/// Parse an `InterfaceType`.
impl TryFrom<u64> for InterfaceType {
type Error = &'static str;
fn try_from(code: u64) -> Result<Self, Self::Error> {
Ok(match code {
0x7fff => Self::Int,
0x7ffe => Self::Float,
0x7ffd => Self::Any,
0x7ffc => Self::String,
0x7ffb => Self::Seq,
0x7f => Self::I32,
0x7e => Self::I64,
0x7d => Self::F32,
0x7c => Self::F64,
0x6f => Self::AnyRef,
_ => return Err("Unknown interface type code."),
})
}
}
/// Parse an adapter kind.
impl TryFrom<u8> for AdapterKind {
type Error = &'static str;
fn try_from(code: u8) -> Result<Self, Self::Error> {
Ok(match code {
0x0 => Self::Import,
0x1 => Self::Export,
0x2 => Self::HelperFunction,
_ => return Err("Unknown adapter kind code."),
})
}
}
/// Parse a byte.
fn byte<'input, E: ParseError<&'input [u8]>>(input: &'input [u8]) -> IResult<&'input [u8], u8, E> {
if input.is_empty() {
return Err(Err::Error(make_error(input, ErrorKind::Eof)));
}
Ok((&input[1..], input[0]))
}
/// Parse an unsigned Little Endian Based (LEB) with value no larger
/// than a 64-bits number. Read
/// [LEB128](https://en.wikipedia.org/wiki/LEB128) to learn more, or
/// the Variable Length Data Section from the [DWARF 4
/// standard](http://dwarfstd.org/doc/DWARF4.pdf).
fn uleb<'input, E: ParseError<&'input [u8]>>(input: &'input [u8]) -> IResult<&'input [u8], u64, E> {
if input.is_empty() {
return Err(Err::Error(make_error(input, ErrorKind::Eof)));
}
let (output, bytes) = match input.iter().position(|&byte| byte & 0x80 == 0) {
Some(length) if length <= 8 => (&input[length + 1..], &input[..=length]),
Some(_) => return Err(Err::Error(make_error(input, ErrorKind::TooLarge))),
None => return Err(Err::Error(make_error(input, ErrorKind::Eof))),
};
Ok((
output,
bytes
.iter()
.rev()
.fold(0, |acc, byte| (acc << 7) | u64::from(byte & 0x7f)),
))
}
/// Parse a UTF-8 string.
fn string<'input, E: ParseError<&'input [u8]>>(
input: &'input [u8],
) -> IResult<&'input [u8], &'input str, E> {
if input.is_empty() {
return Err(Err::Error(make_error(input, ErrorKind::Eof)));
}
let length = input[0] as usize;
let input = &input[1..];
if input.len() < length {
return Err(Err::Error(make_error(input, ErrorKind::Eof)));
}
Ok((
&input[length..],
str::from_utf8(&input[..length])
.map_err(|_| Err::Error(make_error(input, ErrorKind::ParseTo)))?,
))
}
/// Parse a list, with a item parser.
#[allow(clippy::type_complexity)]
fn list<'input, I, E: ParseError<&'input [u8]>>(
input: &'input [u8],
item_parser: fn(&'input [u8]) -> IResult<&'input [u8], I, E>,
) -> IResult<&'input [u8], Vec<I>, E> {
if input.is_empty() {
return Err(Err::Error(make_error(input, ErrorKind::Eof)));
}
let length = input[0] as usize;
let mut input = &input[1..];
if input.len() < length {
return Err(Err::Error(make_error(input, ErrorKind::Eof)));
}
let mut items = Vec::with_capacity(length as usize);
for _ in 0..length {
consume!((input, item) = item_parser(input)?);
items.push(item);
}
Ok((input, items))
}
/// Parse a type.
fn ty<'input, E: ParseError<&'input [u8]>>(
input: &'input [u8],
) -> IResult<&'input [u8], InterfaceType, E> {
if input.is_empty() {
return Err(Err::Error(make_error(input, ErrorKind::Eof)));
}
let (output, ty) = uleb(input)?;
match InterfaceType::try_from(ty) {
Ok(ty) => Ok((output, ty)),
Err(_) => Err(Err::Error(make_error(input, ErrorKind::ParseTo))),
}
}
/// Parse an instruction with its arguments.
fn instruction<'input, E: ParseError<&'input [u8]>>(
input: &'input [u8],
) -> IResult<&'input [u8], Instruction, E> {
let (mut input, opcode) = byte(input)?;
Ok(match opcode {
0x00 => {
consume!((input, argument_0) = uleb(input)?);
(input, Instruction::ArgumentGet { index: argument_0 })
}
0x01 => {
consume!((input, argument_0) = uleb(input)?);
(
input,
Instruction::Call {
function_index: argument_0 as usize,
},
)
}
0x02 => {
consume!((input, argument_0) = string(input)?);
(
input,
Instruction::CallExport {
export_name: argument_0,
},
)
}
0x03 => (input, Instruction::ReadUtf8),
0x04 => {
consume!((input, argument_0) = string(input)?);
(
input,
Instruction::WriteUtf8 {
allocator_name: argument_0,
},
)
}
0x05 => {
consume!((input, argument_0) = ty(input)?);
(input, Instruction::AsWasm(argument_0))
}
0x06 => {
consume!((input, argument_0) = ty(input)?);
(input, Instruction::AsInterface(argument_0))
}
0x07 => (input, Instruction::TableRefAdd),
0x08 => (input, Instruction::TableRefGet),
0x09 => {
consume!((input, argument_0) = uleb(input)?);
(input, Instruction::CallMethod(argument_0))
}
0x0a => {
consume!((input, argument_0) = ty(input)?);
(input, Instruction::MakeRecord(argument_0))
}
0x0c => {
consume!((input, argument_0) = ty(input)?);
consume!((input, argument_1) = uleb(input)?);
(input, Instruction::GetField(argument_0, argument_1))
}
0x0d => {
consume!((input, argument_0) = ty(input)?);
consume!((input, argument_1) = uleb(input)?);
(input, Instruction::Const(argument_0, argument_1))
}
0x0e => {
consume!((input, argument_0) = uleb(input)?);
(input, Instruction::FoldSeq(argument_0))
}
0x0f => {
consume!((input, argument_0) = ty(input)?);
(input, Instruction::Add(argument_0))
}
0x10 => {
consume!((input, argument_0) = ty(input)?);
consume!((input, argument_1) = string(input)?);
(input, Instruction::MemToSeq(argument_0, argument_1))
}
0x11 => {
consume!((input, argument_0) = ty(input)?);
consume!((input, argument_1) = string(input)?);
(input, Instruction::Load(argument_0, argument_1))
}
0x12 => {
consume!((input, argument_0) = ty(input)?);
(input, Instruction::SeqNew(argument_0))
}
0x13 => (input, Instruction::ListPush),
0x14 => {
consume!((input, argument_0) = uleb(input)?);
consume!((input, argument_1) = uleb(input)?);
(input, Instruction::RepeatUntil(argument_0, argument_1))
}
_ => return Err(Err::Error(make_error(input, ErrorKind::ParseTo))),
})
}
/// Parse a list of exports.
fn exports<'input, E: ParseError<&'input [u8]>>(
mut input: &'input [u8],
) -> IResult<&'input [u8], Vec<Export>, E> {
consume!((input, number_of_exports) = uleb(input)?);
let mut exports = Vec::with_capacity(number_of_exports as usize);
for _ in 0..number_of_exports {
consume!((input, export_name) = string(input)?);
consume!((input, export_input_types) = list(input, ty)?);
consume!((input, export_output_types) = list(input, ty)?);
exports.push(Export {
name: export_name,
input_types: export_input_types,
output_types: export_output_types,
});
}
Ok((input, exports))
}
/// Parse a list of types.
fn types<'input, E: ParseError<&'input [u8]>>(
mut input: &'input [u8],
) -> IResult<&'input [u8], Vec<Type>, E> {
consume!((input, number_of_types) = uleb(input)?);
let mut types = Vec::with_capacity(number_of_types as usize);
for _ in 0..number_of_types {
consume!((input, type_name) = string(input)?);
consume!((input, type_fields) = list(input, string)?);
consume!((input, type_types) = list(input, ty)?);
types.push(Type::new(type_name, type_fields, type_types));
}
Ok((input, types))
}
/// Parse a list of imports.
fn imports<'input, E: ParseError<&'input [u8]>>(
mut input: &'input [u8],
) -> IResult<&'input [u8], Vec<Import>, E> {
consume!((input, number_of_imports) = uleb(input)?);
let mut imports = Vec::with_capacity(number_of_imports as usize);
for _ in 0..number_of_imports {
consume!((input, import_namespace) = string(input)?);
consume!((input, import_name) = string(input)?);
consume!((input, import_input_types) = list(input, ty)?);
consume!((input, import_output_types) = list(input, ty)?);
imports.push(Import {
namespace: import_namespace,
name: import_name,
input_types: import_input_types,
output_types: import_output_types,
});
}
Ok((input, imports))
}
/// Parse a list of adapters.
fn adapters<'input, E: ParseError<&'input [u8]>>(
mut input: &'input [u8],
) -> IResult<&'input [u8], Vec<Adapter>, E> {
consume!((input, number_of_adapters) = uleb(input)?);
let mut adapters = Vec::with_capacity(number_of_adapters as usize);
for _ in 0..number_of_adapters {
consume!((input, adapter_kind) = byte(input)?);
let adapter_kind = AdapterKind::try_from(adapter_kind)
.map_err(|_| Err::Error(make_error(input, ErrorKind::ParseTo)))?;
match adapter_kind {
AdapterKind::Import => {
consume!((input, adapter_namespace) = string(input)?);
consume!((input, adapter_name) = string(input)?);
consume!((input, adapter_input_types) = list(input, ty)?);
consume!((input, adapter_output_types) = list(input, ty)?);
consume!((input, adapter_instructions) = list(input, instruction)?);
adapters.push(Adapter::Import {
namespace: adapter_namespace,
name: adapter_name,
input_types: adapter_input_types,
output_types: adapter_output_types,
instructions: adapter_instructions,
});
}
AdapterKind::Export => {
consume!((input, adapter_name) = string(input)?);
consume!((input, adapter_input_types) = list(input, ty)?);
consume!((input, adapter_output_types) = list(input, ty)?);
consume!((input, adapter_instructions) = list(input, instruction)?);
adapters.push(Adapter::Export {
name: adapter_name,
input_types: adapter_input_types,
output_types: adapter_output_types,
instructions: adapter_instructions,
});
}
AdapterKind::HelperFunction => {
consume!((input, adapter_name) = string(input)?);
consume!((input, adapter_input_types) = list(input, ty)?);
consume!((input, adapter_output_types) = list(input, ty)?);
consume!((input, adapter_instructions) = list(input, instruction)?);
adapters.push(Adapter::HelperFunction {
name: adapter_name,
input_types: adapter_input_types,
output_types: adapter_output_types,
instructions: adapter_instructions,
});
}
}
}
Ok((input, adapters))
}
/// Parse a list of forwarded exports.
fn forwards<'input, E: ParseError<&'input [u8]>>(
mut input: &'input [u8],
) -> IResult<&'input [u8], Vec<Forward>, E> {
consume!((input, number_of_forwards) = uleb(input)?);
let mut forwards = Vec::with_capacity(number_of_forwards as usize);
for _ in 0..number_of_forwards {
consume!((input, forward_name) = string(input)?);
forwards.push(Forward { name: forward_name });
}
Ok((input, forwards))
}
/// Parse complete interfaces.
fn interfaces<'input, E: ParseError<&'input [u8]>>(
bytes: &'input [u8],
) -> IResult<&'input [u8], Interfaces, E> {
let mut input = bytes;
consume!((input, exports) = exports(input)?);
consume!((input, types) = types(input)?);
consume!((input, imports) = imports(input)?);
consume!((input, adapters) = adapters(input)?);
consume!((input, forwards) = forwards(input)?);
Ok((
input,
Interfaces {
exports,
types,
imports,
adapters,
forwards,
},
))
}
/// Parse a sequence of bytes, expecting it to be a valid WIT binary
/// representation, into an `ast::Interfaces`.
///
/// # Example
///
/// ```rust
/// use wasmer_interface_types::{
/// ast::*,
/// decoders::binary::parse,
/// interpreter::Instruction,
/// };
///
/// # fn main() {
/// let input = &[
/// 0x01, // 1 export
/// 0x02, // string of 2 bytes
/// 0x61, 0x62, // "a", "b"
/// 0x01, // list of 1 item
/// 0x7f, // I32
/// 0x01, // list of 1 item
/// 0x7f, // I32
/// 0x01, // 1 type
/// 0x02, // string of 2 bytes
/// 0x61, 0x62, // "a", "b"
/// 0x02, // list of 2 items
/// 0x02, // string of 2 bytes
/// 0x63, 0x64, // "c", "d"
/// 0x01, // string of 1 byte
/// 0x65, // "e"
/// 0x02, // list of 2 items
/// 0x7f, // I32
/// 0x7f, // I32
/// 0x01, // 1 import
/// 0x01, // string of 1 byte
/// 0x61, // "a"
/// 0x01, // string of 1 byte
/// 0x62, // "b"
/// 0x01, // list of 1 item
/// 0x7f, // I32
/// 0x01, // list of 1 item
/// 0x7e, // I64
/// 0x01, // 1 adapter
/// 0x00, // adapter kind: import
/// 0x01, // string of 1 byte
/// 0x61, // "a"
/// 0x01, // string of 1 byte
/// 0x62, // "b"
/// 0x01, // list of 1 item
/// 0x7f, // I32
/// 0x01, // list of 1 item
/// 0x7f, // I32
/// 0x01, // list of 1 item
/// 0x00, 0x01, // ArgumentGet { index: 1 }
/// 0x01, // 1 adapter
/// 0x01, // string of 1 byte
/// 0x61, // "a"
/// ];
/// let output = Ok((
/// &[] as &[u8],
/// Interfaces {
/// exports: vec![Export {
/// name: "ab",
/// input_types: vec![InterfaceType::I32],
/// output_types: vec![InterfaceType::I32],
/// }],
/// types: vec![Type::new(
/// "ab",
/// vec!["cd", "e"],
/// vec![InterfaceType::I32, InterfaceType::I32],
/// )],
/// imports: vec![Import {
/// namespace: "a",
/// name: "b",
/// input_types: vec![InterfaceType::I32],
/// output_types: vec![InterfaceType::I64],
/// }],
/// adapters: vec![Adapter::Import {
/// namespace: "a",
/// name: "b",
/// input_types: vec![InterfaceType::I32],
/// output_types: vec![InterfaceType::I32],
/// instructions: vec![Instruction::ArgumentGet { index: 1 }],
/// }],
/// forwards: vec![Forward { name: "a" }],
/// },
/// ));
///
/// assert_eq!(parse::<()>(input), output);
/// # }
/// ```
pub fn parse<'input, E: ParseError<&'input [u8]>>(
bytes: &'input [u8],
) -> IResult<&'input [u8], Interfaces, E> {
interfaces(bytes)
}
#[cfg(test)]
mod tests {
use super::*;
use nom::{error, Err};
#[test]
fn test_byte() {
let input = &[0x01, 0x02, 0x03];
let output = Ok((&[0x02, 0x03][..], 0x01u8));
assert_eq!(byte::<()>(input), output);
}
#[test]
fn test_uleb_1_byte() {
let input = &[0x01, 0x02, 0x03];
let output = Ok((&[0x02, 0x03][..], 0x01u64));
assert_eq!(uleb::<()>(input), output);
}
#[test]
fn test_uleb_3_bytes() {
let input = &[0xfc, 0xff, 0x01, 0x02];
let output = Ok((&[0x02][..], 0x7ffcu64));
assert_eq!(uleb::<()>(input), output);
}
// Examples from Figure 22 of [DWARF 4
// standard](http://dwarfstd.org/doc/DWARF4.pdf).
#[test]
fn test_uleb_from_dwarf_standard() {
macro_rules! assert_uleb {
($to_parse:expr => $expected_result:expr) => {
assert_eq!(uleb::<()>($to_parse), Ok((&[][..], $expected_result)));
};
}
assert_uleb!(&[2u8] => 2u64);
assert_uleb!(&[127u8] => 127u64);
assert_uleb!(&[0x80, 1u8] => 128u64);
assert_uleb!(&[1u8 | 0x80, 1] => 129u64);
assert_uleb!(&[2u8 | 0x80, 1] => 130u64);
assert_uleb!(&[57u8 | 0x80, 100] => 12857u64);
}
#[test]
fn test_uleb_eof() {
let input = &[0x80];
assert_eq!(
uleb::<(&[u8], error::ErrorKind)>(input),
Err(Err::Error((&input[..], error::ErrorKind::Eof))),
);
}
#[test]
fn test_uleb_overflow() {
let input = &[
0x01 | 0x80,
0x02 | 0x80,
0x03 | 0x80,
0x04 | 0x80,
0x05 | 0x80,
0x06 | 0x80,
0x07 | 0x80,
0x08 | 0x80,
0x09 | 0x80,
0x0a,
];
assert_eq!(
uleb::<(&[u8], error::ErrorKind)>(input),
Err(Err::Error((&input[..], error::ErrorKind::TooLarge))),
);
}
#[test]
fn test_string() {
let input = &[
0x03, // string of 3 bytes
0x61, // "a"
0x62, // "b"
0x63, // "c"
0x64, 0x65,
];
let output = Ok((&[0x64, 0x65][..], "abc"));
assert_eq!(string::<()>(input), output);
}
#[test]
fn test_list() {
let input = &[
0x02, // list of 2 items
0x01, // string of 1 byte
0x61, // "a"
0x02, // string of 2 bytes
0x62, // "b"
0x63, // "c"
0x07,
];
let output = Ok((&[0x07][..], vec!["a", "bc"]));
assert_eq!(list::<&str, ()>(input, string), output);
}
#[test]
fn test_ty() {
let input = &[
0x0a, // list of 10 items
0xff, 0xff, 0x01, // Int
0xfe, 0xff, 0x01, // Float
0xfd, 0xff, 0x01, // Any
0xfc, 0xff, 0x01, // String
0xfb, 0xff, 0x01, // Seq
0x7f, // I32
0x7e, // I64
0x7d, // F32
0x7c, // F64
0x6f, // AnyRef
0x01,
];
let output = Ok((
&[0x01][..],
vec![
InterfaceType::Int,
InterfaceType::Float,
InterfaceType::Any,
InterfaceType::String,
InterfaceType::Seq,
InterfaceType::I32,
InterfaceType::I64,
InterfaceType::F32,
InterfaceType::F64,
InterfaceType::AnyRef,
],
));
assert_eq!(list::<InterfaceType, ()>(input, ty), output);
}
#[test]
fn test_instructions() {
let input = &[
0x14, // list of 20 items
0x00, 0x01, // ArgumentGet { index: 1 }
0x01, 0x01, // Call { function_index: 1 }
0x02, 0x03, 0x61, 0x62, 0x63, // CallExport { export_name: "abc" }
0x03, // ReadUtf8
0x04, 0x03, 0x61, 0x62, 0x63, // WriteUtf8 { allocator_name: "abc" }
0x05, 0xff, 0xff, 0x01, // AsWasm(Int)
0x06, 0x7e, // AsInterface(I64)
0x07, // TableRefAdd
0x08, // TableRefGet
0x09, 0x01, // CallMethod(1)
0x0a, 0x7f, // MakeRecord(I32)
0x0c, 0xff, 0xff, 0x01, 0x02, // GetField(Int, 2)
0x0d, 0x7f, 0x01, // Const(I32, 1)
0x0e, 0x01, // FoldSeq(1)
0x0f, 0x7f, // Add(I32)
0x10, 0x7f, 0x03, 0x61, 0x62, 0x63, // MemToSeq(I32, "abc")
0x11, 0x7f, 0x03, 0x61, 0x62, 0x63, // Load(I32, "abc")
0x12, 0x7f, // SeqNew(I32)
0x13, // ListPush
0x14, 0x01, 0x02, // RepeatUntil(1, 2)
0x0a,
];
let output = Ok((
&[0x0a][..],
vec![
Instruction::ArgumentGet { index: 1 },
Instruction::Call { function_index: 1 },
Instruction::CallExport { export_name: "abc" },
Instruction::ReadUtf8,
Instruction::WriteUtf8 {
allocator_name: "abc",
},
Instruction::AsWasm(InterfaceType::Int),
Instruction::AsInterface(InterfaceType::I64),
Instruction::TableRefAdd,
Instruction::TableRefGet,
Instruction::CallMethod(1),
Instruction::MakeRecord(InterfaceType::I32),
Instruction::GetField(InterfaceType::Int, 2),
Instruction::Const(InterfaceType::I32, 1),
Instruction::FoldSeq(1),
Instruction::Add(InterfaceType::I32),
Instruction::MemToSeq(InterfaceType::I32, "abc"),
Instruction::Load(InterfaceType::I32, "abc"),
Instruction::SeqNew(InterfaceType::I32),
Instruction::ListPush,
Instruction::RepeatUntil(1, 2),
],
));
assert_eq!(list::<Instruction, ()>(input, instruction), output);
}
#[test]
fn test_exports() {
let input = &[
0x02, // 2 exports
0x02, // string of 2 bytes
0x61, 0x62, // "a", "b"
0x01, // list of 1 item
0x7f, // I32
0x01, // list of 1 item
0x7f, // I32
0x02, // string of 2 bytes
0x63, 0x64, // "c", "d"
0x00, // list of 0 item
0x00, // list of 0 item
];
let output = Ok((
&[] as &[u8],
vec![
Export {
name: "ab",
input_types: vec![InterfaceType::I32],
output_types: vec![InterfaceType::I32],
},
Export {
name: "cd",
input_types: vec![],
output_types: vec![],
},
],
));
assert_eq!(exports::<()>(input), output);
}
#[test]
fn test_types() {
let input = &[
0x01, // 1 type
0x02, // string of 2 bytes
0x61, 0x62, // "a", "b"
0x02, // list of 2 items
0x02, // string of 2 bytes
0x63, 0x64, // "c", "d"
0x01, // string of 1 byte
0x65, // "e"
0x02, // list of 2 items
0x7f, // I32
0x7f, // I32
];
let output = Ok((
&[] as &[u8],
vec![Type::new(
"ab",
vec!["cd", "e"],
vec![InterfaceType::I32, InterfaceType::I32],
)],
));
assert_eq!(types::<()>(input), output);
}
#[test]
fn test_imports() {
let input = &[
0x02, // 2 imports
0x01, // string of 1 byte
0x61, // "a"
0x01, // string of 1 byte
0x62, // "b"
0x01, // list of 1 item
0x7f, // I32
0x01, // list of 1 item
0x7e, // I64
0x01, // string of 1 byte
0x63, // "c"
0x01, // string of 1 byte
0x64, // "d"
0x01, // list of 1 item
0x7f, // I32
0x01, // list of 1 item
0x7e, // I64
];
let output = Ok((
&[] as &[u8],
vec![
Import {
namespace: "a",
name: "b",
input_types: vec![InterfaceType::I32],
output_types: vec![InterfaceType::I64],
},
Import {
namespace: "c",
name: "d",
input_types: vec![InterfaceType::I32],
output_types: vec![InterfaceType::I64],
},
],
));
assert_eq!(imports::<()>(input), output);
}
#[test]
fn test_adapters() {
let input = &[
0x03, // 3 adapters
0x00, // adapter kind: import
0x01, // string of 1 byte
0x61, // "a"
0x01, // string of 1 byte
0x62, // "b"
0x01, // list of 1 item
0x7f, // I32
0x01, // list of 1 item
0x7f, // I32
0x01, // list of 1 item
0x00, 0x01, // ArgumentGet { index: 1 }
0x01, // adapter kind: export
0x01, // string of 1 byte
0x63, // "c"
0x01, // list of 1 item
0x7f, // I32
0x01, // list of 1 item
0x7f, // I32
0x01, // list of 1 item
0x00, 0x01, // ArgumentGet { index: 1 }
0x02, // adapter kind: helper function
0x01, // string of 1 byte
0x64, // "d"
0x01, // list of 1 item
0x7f, // I32
0x01, // list of 1 item
0x7f, // I32
0x01, // list of 1 item
0x00, 0x01, // ArgumentGet { index: 1 }
];
let output = Ok((
&[] as &[u8],
vec![
Adapter::Import {
namespace: "a",
name: "b",
input_types: vec![InterfaceType::I32],
output_types: vec![InterfaceType::I32],
instructions: vec![Instruction::ArgumentGet { index: 1 }],
},
Adapter::Export {
name: "c",
input_types: vec![InterfaceType::I32],
output_types: vec![InterfaceType::I32],
instructions: vec![Instruction::ArgumentGet { index: 1 }],
},
Adapter::HelperFunction {
name: "d",
input_types: vec![InterfaceType::I32],
output_types: vec![InterfaceType::I32],
instructions: vec![Instruction::ArgumentGet { index: 1 }],
},
],
));
assert_eq!(adapters::<()>(input), output);
}
#[test]
fn test_forwards() {
let input = &[
0x02, // 2 adapters
0x01, // string of 1 byte
0x61, // "a"
0x02, // string of 2 bytes
0x62, 0x63, // "b", "c"
];
let output = Ok((
&[] as &[u8],
vec![Forward { name: "a" }, Forward { name: "bc" }],
));
assert_eq!(forwards::<()>(input), output);
}
#[test]
fn test_parse() {
let input = &[
0x01, // 1 export
0x02, // string of 2 bytes
0x61, 0x62, // "a", "b"
0x01, // list of 1 item
0x7f, // I32
0x01, // list of 1 item
0x7f, // I32
0x01, // 1 type
0x02, // string of 2 bytes
0x61, 0x62, // "a", "b"
0x02, // list of 2 items
0x02, // string of 2 bytes
0x63, 0x64, // "c", "d"
0x01, // string of 1 byte
0x65, // "e"
0x02, // list of 2 items
0x7f, // I32
0x7f, // I32
0x01, // 1 import
0x01, // string of 1 byte
0x61, // "a"
0x01, // string of 1 byte
0x62, // "b"
0x01, // list of 1 item
0x7f, // I32
0x01, // list of 1 item
0x7e, // I64
0x01, // 1 adapter
0x00, // adapter kind: import
0x01, // string of 1 byte
0x61, // "a"
0x01, // string of 1 byte
0x62, // "b"
0x01, // list of 1 item
0x7f, // I32
0x01, // list of 1 item
0x7f, // I32
0x01, // list of 1 item
0x00, 0x01, // ArgumentGet { index: 1 }
0x01, // 1 adapter
0x01, // string of 1 byte
0x61, // "a"
];
let output = Ok((
&[] as &[u8],
Interfaces {
exports: vec![Export {
name: "ab",
input_types: vec![InterfaceType::I32],
output_types: vec![InterfaceType::I32],
}],
types: vec![Type::new(
"ab",
vec!["cd", "e"],
vec![InterfaceType::I32, InterfaceType::I32],
)],
imports: vec![Import {
namespace: "a",
name: "b",
input_types: vec![InterfaceType::I32],
output_types: vec![InterfaceType::I64],
}],
adapters: vec![Adapter::Import {
namespace: "a",
name: "b",
input_types: vec![InterfaceType::I32],
output_types: vec![InterfaceType::I32],
instructions: vec![Instruction::ArgumentGet { index: 1 }],
}],
forwards: vec![Forward { name: "a" }],
},
));
assert_eq!(interfaces::<()>(input), output);
}
}

View File

@ -0,0 +1,4 @@
//! Reads the AST from a particular data representation; for instance,
//! `decoders::binary` reads the AST from a binary.
pub mod binary;

View File

@ -0,0 +1,749 @@
//! Writes the AST into bytes representing WIT with its binary format.
use crate::{
ast::{Adapter, AdapterKind, Export, Forward, Import, InterfaceType, Interfaces, Type},
interpreter::Instruction,
};
use std::io::{self, Write};
/// A trait for converting a value to bytes.
pub trait ToBytes<W>
where
W: Write,
{
/// Converts the given value into `&[u8]` in the given `writer`.
fn to_bytes(&self, writer: &mut W) -> io::Result<()>;
}
/// Encode a `u8` into a byte (well, it's already a byte!).
impl<W> ToBytes<W> for u8
where
W: Write,
{
fn to_bytes(&self, writer: &mut W) -> io::Result<()> {
writer.write_all(&[*self])
}
}
/// Encode a `u64` into bytes with a LEB128 representation.
///
/// Decoder is `decoders::binary::uleb`.
impl<W> ToBytes<W> for u64
where
W: Write,
{
fn to_bytes(&self, writer: &mut W) -> io::Result<()> {
let mut value = *self;
// Code adapted from the Rust' `serialize` library.
loop {
if value < 0x80 {
writer.write_all(&[value as u8])?;
break;
}
writer.write_all(&[((value & 0x7f) | 0x80) as u8])?;
value >>= 7;
}
Ok(())
}
}
/// Encode a `str` into bytes.
///
/// Decoder is `decoders::binary::string`.
impl<W> ToBytes<W> for &str
where
W: Write,
{
fn to_bytes(&self, writer: &mut W) -> io::Result<()> {
// Size first.
writer.write_all(&[self.len() as u8])?;
// Then the string.
writer.write_all(self.as_bytes())?;
Ok(())
}
}
/// Encode a vector into bytes.
///
/// Decoder is `decoders::binary::list`.
impl<W, I> ToBytes<W> for Vec<I>
where
W: Write,
I: ToBytes<W>,
{
fn to_bytes(&self, writer: &mut W) -> io::Result<()> {
// Size first.
(self.len() as u64).to_bytes(writer)?;
// Then the items.
for item in self {
item.to_bytes(writer)?;
}
Ok(())
}
}
/// Encode an `InterfaceType` into bytes.
impl<W> ToBytes<W> for InterfaceType
where
W: Write,
{
fn to_bytes(&self, writer: &mut W) -> io::Result<()> {
match self {
InterfaceType::Int => 0x7fff_u64.to_bytes(writer),
InterfaceType::Float => 0x7ffe_u64.to_bytes(writer),
InterfaceType::Any => 0x7ffd_u64.to_bytes(writer),
InterfaceType::String => 0x7ffc_u64.to_bytes(writer),
InterfaceType::Seq => 0x7ffb_u64.to_bytes(writer),
InterfaceType::I32 => 0x7f_u64.to_bytes(writer),
InterfaceType::I64 => 0x7e_u64.to_bytes(writer),
InterfaceType::F32 => 0x7d_u64.to_bytes(writer),
InterfaceType::F64 => 0x7c_u64.to_bytes(writer),
InterfaceType::AnyRef => 0x6f_u64.to_bytes(writer),
}
}
}
/// Encode an `AdapterKind` into bytes.
impl<W> ToBytes<W> for AdapterKind
where
W: Write,
{
fn to_bytes(&self, writer: &mut W) -> io::Result<()> {
match self {
AdapterKind::Import => 0x00_u8.to_bytes(writer),
AdapterKind::Export => 0x01_u8.to_bytes(writer),
AdapterKind::HelperFunction => 0x02_u8.to_bytes(writer),
}
}
}
/// Encode an `Export` into bytes.
///
/// Decoder is in `decoders::binary::exports`.
impl<W> ToBytes<W> for Export<'_>
where
W: Write,
{
fn to_bytes(&self, writer: &mut W) -> io::Result<()> {
self.name.to_bytes(writer)?;
self.input_types.to_bytes(writer)?;
self.output_types.to_bytes(writer)?;
Ok(())
}
}
/// Encode a `Type` into bytes.
///
/// Decoder is in `decoders::binary::types`.
impl<W> ToBytes<W> for Type<'_>
where
W: Write,
{
fn to_bytes(&self, writer: &mut W) -> io::Result<()> {
self.name.to_bytes(writer)?;
self.field_names().to_bytes(writer)?;
self.field_types().to_bytes(writer)?;
Ok(())
}
}
/// Encode an `Import` into bytes.
///
/// Decoder is in `decoders::binary::imports`.
impl<W> ToBytes<W> for Import<'_>
where
W: Write,
{
fn to_bytes(&self, writer: &mut W) -> io::Result<()> {
self.namespace.to_bytes(writer)?;
self.name.to_bytes(writer)?;
self.input_types.to_bytes(writer)?;
self.output_types.to_bytes(writer)?;
Ok(())
}
}
/// Encode an `Adapter` into bytes.
///
/// Decoder is in `decoders::binary::imports`.
impl<W> ToBytes<W> for Adapter<'_>
where
W: Write,
{
fn to_bytes(&self, writer: &mut W) -> io::Result<()> {
match self {
Adapter::Import {
namespace,
name,
input_types,
output_types,
instructions,
} => {
AdapterKind::Import.to_bytes(writer)?;
namespace.to_bytes(writer)?;
name.to_bytes(writer)?;
input_types.to_bytes(writer)?;
output_types.to_bytes(writer)?;
instructions.to_bytes(writer)?;
}
Adapter::Export {
name,
input_types,
output_types,
instructions,
} => {
AdapterKind::Export.to_bytes(writer)?;
name.to_bytes(writer)?;
input_types.to_bytes(writer)?;
output_types.to_bytes(writer)?;
instructions.to_bytes(writer)?;
}
Adapter::HelperFunction {
name,
input_types,
output_types,
instructions,
} => {
AdapterKind::HelperFunction.to_bytes(writer)?;
name.to_bytes(writer)?;
input_types.to_bytes(writer)?;
output_types.to_bytes(writer)?;
instructions.to_bytes(writer)?;
}
}
Ok(())
}
}
/// Encode an `Forward` into bytes.
///
/// Decoder is `decoders::binary::forwards`.
impl<W> ToBytes<W> for Forward<'_>
where
W: Write,
{
fn to_bytes(&self, writer: &mut W) -> io::Result<()> {
self.name.to_bytes(writer)
}
}
/// Encode an `Interfaces` into bytes.
///
/// Decoder is `decoders::binary::parse`.
impl<W> ToBytes<W> for Interfaces<'_>
where
W: Write,
{
fn to_bytes(&self, writer: &mut W) -> io::Result<()> {
self.exports.to_bytes(writer)?;
self.types.to_bytes(writer)?;
self.imports.to_bytes(writer)?;
self.adapters.to_bytes(writer)?;
self.forwards.to_bytes(writer)?;
Ok(())
}
}
/// Encode an `Instruction` into bytes.
///
/// Decoder is `decoders::binary::instruction`.
impl<W> ToBytes<W> for Instruction<'_>
where
W: Write,
{
fn to_bytes(&self, writer: &mut W) -> io::Result<()> {
match self {
Instruction::ArgumentGet { index } => {
0x00_u8.to_bytes(writer)?;
index.to_bytes(writer)?;
}
Instruction::Call { function_index } => {
0x01_u8.to_bytes(writer)?;
(*function_index as u64).to_bytes(writer)?;
}
Instruction::CallExport { export_name } => {
0x02_u8.to_bytes(writer)?;
export_name.to_bytes(writer)?;
}
Instruction::ReadUtf8 => 0x03_u8.to_bytes(writer)?,
Instruction::WriteUtf8 { allocator_name } => {
0x04_u8.to_bytes(writer)?;
allocator_name.to_bytes(writer)?;
}
Instruction::AsWasm(interface_type) => {
0x05_u8.to_bytes(writer)?;
interface_type.to_bytes(writer)?;
}
Instruction::AsInterface(interface_type) => {
0x06_u8.to_bytes(writer)?;
interface_type.to_bytes(writer)?;
}
Instruction::TableRefAdd => 0x07_u8.to_bytes(writer)?,
Instruction::TableRefGet => 0x08_u8.to_bytes(writer)?,
Instruction::CallMethod(function_index) => {
0x09_u8.to_bytes(writer)?;
function_index.to_bytes(writer)?;
}
Instruction::MakeRecord(interface_type) => {
0x0a_u8.to_bytes(writer)?;
interface_type.to_bytes(writer)?;
}
Instruction::GetField(interface_type, field_index) => {
0x0c_u8.to_bytes(writer)?;
interface_type.to_bytes(writer)?;
field_index.to_bytes(writer)?;
}
Instruction::Const(interface_type, index) => {
0x0d_u8.to_bytes(writer)?;
interface_type.to_bytes(writer)?;
index.to_bytes(writer)?;
}
Instruction::FoldSeq(index) => {
0x0e_u8.to_bytes(writer)?;
index.to_bytes(writer)?;
}
Instruction::Add(interface_type) => {
0x0f_u8.to_bytes(writer)?;
interface_type.to_bytes(writer)?;
}
Instruction::MemToSeq(interface_type, string) => {
0x10_u8.to_bytes(writer)?;
interface_type.to_bytes(writer)?;
string.to_bytes(writer)?;
}
Instruction::Load(interface_type, string) => {
0x11_u8.to_bytes(writer)?;
interface_type.to_bytes(writer)?;
string.to_bytes(writer)?;
}
Instruction::SeqNew(interface_type) => {
0x12_u8.to_bytes(writer)?;
interface_type.to_bytes(writer)?;
}
Instruction::ListPush => 0x13_u8.to_bytes(writer)?,
Instruction::RepeatUntil(index1, index2) => {
0x14_u8.to_bytes(writer)?;
index1.to_bytes(writer)?;
index2.to_bytes(writer)?;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! assert_to_bytes {
($expr:expr, $expected_output:expr) => {{
let mut output = vec![];
$expr.to_bytes(&mut output).expect(concat!(
"Unable to encode the expression `",
stringify!($expr),
"` to bytes."
));
assert_eq!(output.as_slice(), &$expected_output[..]);
}};
}
#[test]
fn test_u8() {
assert_to_bytes!(0x01_u8, &[0x01]);
}
#[test]
fn test_uleb_1_byte() {
assert_to_bytes!(0x01_u64, &[0x01]);
}
#[test]
fn test_uleb_3_bytes() {
assert_to_bytes!(0x7ffc_u64, &[0xfc, 0xff, 0x01]);
}
// Examples from Figure 22 of [DWARF 4
// standard](http://dwarfstd.org/doc/DWARF4.pdf).
#[test]
fn test_uleb_from_dward_standard() {
assert_to_bytes!(2u64, &[2u8]);
assert_to_bytes!(127u64, &[127u8]);
assert_to_bytes!(128u64, &[0x80, 1u8]);
assert_to_bytes!(129u64, &[1u8 | 0x80, 1]);
assert_to_bytes!(130u64, &[2u8 | 0x80, 1]);
assert_to_bytes!(12857u64, &[57u8 | 0x80, 100]);
}
#[test]
fn test_empty_str() {
assert_to_bytes!("", &[0x00]);
}
#[test]
fn test_str() {
assert_to_bytes!("abc", &[0x03, 0x61, 0x62, 0x63]);
}
#[test]
fn test_empty_vec() {
assert_to_bytes!(Vec::<u8>::new(), &[0x00]);
}
#[test]
fn test_vec() {
assert_to_bytes!(
vec!["a", "b", "c"],
&[
0x03, // list of 3 items
0x01, // string of 1 byte
0x61, // "a"
0x01, // string of 1 byte
0x62, // "b"
0x01, // string of 1 byte
0x63, // "c"
]
);
}
#[test]
fn test_interface_type() {
assert_to_bytes!(InterfaceType::Int, &[0xff, 0xff, 0x01]);
assert_to_bytes!(InterfaceType::Float, &[0xfe, 0xff, 0x01]);
assert_to_bytes!(InterfaceType::Any, &[0xfd, 0xff, 0x01]);
assert_to_bytes!(InterfaceType::String, &[0xfc, 0xff, 0x01]);
assert_to_bytes!(InterfaceType::Seq, &[0xfb, 0xff, 0x01]);
assert_to_bytes!(InterfaceType::I32, &[0x7f]);
assert_to_bytes!(InterfaceType::I64, &[0x7e]);
assert_to_bytes!(InterfaceType::F32, &[0x7d]);
assert_to_bytes!(InterfaceType::F64, &[0x7c]);
assert_to_bytes!(InterfaceType::AnyRef, &[0x6f]);
}
#[test]
fn test_adapter_kind() {
assert_to_bytes!(AdapterKind::Import, &[0x00]);
assert_to_bytes!(AdapterKind::Export, &[0x01]);
assert_to_bytes!(AdapterKind::HelperFunction, &[0x02]);
}
#[test]
fn test_export() {
assert_to_bytes!(
Export {
name: "abc",
input_types: vec![InterfaceType::I32, InterfaceType::I64],
output_types: vec![InterfaceType::I32]
},
&[
0x03, // string of length 3
0x61, // "a"
0x62, // "b"
0x63, // "c"
0x02, // list of 2 items
0x7f, // I32
0x7e, // I64
0x01, // list of 1 items
0x7f, // I32
]
);
}
#[test]
fn test_type() {
assert_to_bytes!(
Type::new(
"a",
vec!["b", "c"],
vec![InterfaceType::I32, InterfaceType::I64],
),
&[
0x01, // string of length 1
0x61, // "a"
0x02, // list of 2 items
0x01, // string of length 1
0x62, // "b"
0x01, // string of length 1
0x63, // "c"
0x02, // list of 2 items
0x7f, // I32
0x7e, // I64
]
);
}
#[test]
fn test_import() {
assert_to_bytes!(
Import {
namespace: "a",
name: "b",
input_types: vec![InterfaceType::I32, InterfaceType::I64],
output_types: vec![InterfaceType::I32],
},
&[
0x01, // string of length 1
0x61, // "a"
0x01, // string of length 1
0x62, // "b"
0x02, // list of 2 items
0x7f, // I32
0x7e, // I64
0x01, // list of 1 items
0x7f, // I32
]
);
}
#[test]
fn test_adapter_import() {
assert_to_bytes!(
Adapter::Import {
namespace: "a",
name: "b",
input_types: vec![InterfaceType::I32, InterfaceType::I64],
output_types: vec![InterfaceType::I32],
instructions: vec![Instruction::ArgumentGet { index: 1 }],
},
&[
0x00, // AdapterKind::Import
0x01, // string of length 1
0x61, // "a"
0x01, // string of length 1
0x62, // "b"
0x02, // list of 2 items
0x7f, // I32
0x7e, // I64
0x01, // list of 1 items
0x7f, // I32
0x01, // list of 1 item
0x00, 0x01, // ArgumentGet { index: 1 }
]
);
}
#[test]
fn test_adapter_export() {
assert_to_bytes!(
Adapter::Export {
name: "a",
input_types: vec![InterfaceType::I32, InterfaceType::I64],
output_types: vec![InterfaceType::I32],
instructions: vec![Instruction::ArgumentGet { index: 1 }],
},
&[
0x01, // AdapterKind::Export
0x01, // string of length 1
0x61, // "a"
0x02, // list of 2 items
0x7f, // I32
0x7e, // I64
0x01, // list of 1 items
0x7f, // I32
0x01, // list of 1 item
0x00, 0x01, // ArgumentGet { index: 1 }
]
);
}
#[test]
fn test_adapter_helper_function() {
assert_to_bytes!(
Adapter::HelperFunction {
name: "a",
input_types: vec![InterfaceType::I32, InterfaceType::I64],
output_types: vec![InterfaceType::I32],
instructions: vec![Instruction::ArgumentGet { index: 1 }],
},
&[
0x02, // AdapterKind::HelperFunction
0x01, // string of length 1
0x61, // "a"
0x02, // list of 2 items
0x7f, // I32
0x7e, // I64
0x01, // list of 1 items
0x7f, // I32
0x01, // list of 1 item
0x00, 0x01, // ArgumentGet { index: 1 }
]
);
}
#[test]
fn test_forward() {
assert_to_bytes!(
Forward { name: "ab" },
&[
0x02, // string of length 2
0x61, // "a"
0x62, // "b"
]
);
}
#[test]
fn test_interfaces() {
assert_to_bytes!(
Interfaces {
exports: vec![Export {
name: "ab",
input_types: vec![InterfaceType::I32],
output_types: vec![InterfaceType::I32],
}],
types: vec![Type::new(
"ab",
vec!["cd", "e"],
vec![InterfaceType::I32, InterfaceType::I32],
)],
imports: vec![Import {
namespace: "a",
name: "b",
input_types: vec![InterfaceType::I32],
output_types: vec![InterfaceType::I64],
}],
adapters: vec![Adapter::Import {
namespace: "a",
name: "b",
input_types: vec![InterfaceType::I32],
output_types: vec![InterfaceType::I32],
instructions: vec![Instruction::ArgumentGet { index: 1 }],
}],
forwards: vec![Forward { name: "a" }],
},
&[
0x01, // 1 export
0x02, // string of 2 bytes
0x61, 0x62, // "a", "b"
0x01, // list of 1 item
0x7f, // I32
0x01, // list of 1 item
0x7f, // I32
0x01, // 1 type
0x02, // string of 2 bytes
0x61, 0x62, // "a", "b"
0x02, // list of 2 items
0x02, // string of 2 bytes
0x63, 0x64, // "c", "d"
0x01, // string of 1 byte
0x65, // "e"
0x02, // list of 2 items
0x7f, // I32
0x7f, // I32
0x01, // 1 import
0x01, // string of 1 byte
0x61, // "a"
0x01, // string of 1 byte
0x62, // "b"
0x01, // list of 1 item
0x7f, // I32
0x01, // list of 1 item
0x7e, // I64
0x01, // 1 adapter
0x00, // adapter kind: import
0x01, // string of 1 byte
0x61, // "a"
0x01, // string of 1 byte
0x62, // "b"
0x01, // list of 1 item
0x7f, // I32
0x01, // list of 1 item
0x7f, // I32
0x01, // list of 1 item
0x00, 0x01, // ArgumentGet { index: 1 }
0x01, // 1 adapter
0x01, // string of 1 byte
0x61, // "a"
]
);
}
#[test]
fn test_instructions() {
assert_to_bytes!(
vec![
Instruction::ArgumentGet { index: 1 },
Instruction::Call { function_index: 1 },
Instruction::CallExport { export_name: "abc" },
Instruction::ReadUtf8,
Instruction::WriteUtf8 {
allocator_name: "abc",
},
Instruction::AsWasm(InterfaceType::Int),
Instruction::AsInterface(InterfaceType::I64),
Instruction::TableRefAdd,
Instruction::TableRefGet,
Instruction::CallMethod(1),
Instruction::MakeRecord(InterfaceType::I32),
Instruction::GetField(InterfaceType::Int, 2),
Instruction::Const(InterfaceType::I32, 1),
Instruction::FoldSeq(1),
Instruction::Add(InterfaceType::I32),
Instruction::MemToSeq(InterfaceType::I32, "abc"),
Instruction::Load(InterfaceType::I32, "abc"),
Instruction::SeqNew(InterfaceType::I32),
Instruction::ListPush,
Instruction::RepeatUntil(1, 2),
],
&[
0x14, // list of 20 items
0x00, 0x01, // ArgumentGet { index: 1 }
0x01, 0x01, // Call { function_index: 1 }
0x02, 0x03, 0x61, 0x62, 0x63, // CallExport { export_name: "abc" }
0x03, // ReadUtf8
0x04, 0x03, 0x61, 0x62, 0x63, // WriteUtf8 { allocator_name: "abc" }
0x05, 0xff, 0xff, 0x01, // AsWasm(Int)
0x06, 0x7e, // AsInterface(I64)
0x07, // TableRefAdd
0x08, // TableRefGet
0x09, 0x01, // CallMethod(1)
0x0a, 0x7f, // MakeRecord(I32)
0x0c, 0xff, 0xff, 0x01, 0x02, // GetField(Int, 2)
0x0d, 0x7f, 0x01, // Const(I32, 1)
0x0e, 0x01, // FoldSeq(1)
0x0f, 0x7f, // Add(I32)
0x10, 0x7f, 0x03, 0x61, 0x62, 0x63, // MemToSeq(I32, "abc")
0x11, 0x7f, 0x03, 0x61, 0x62, 0x63, // Load(I32, "abc")
0x12, 0x7f, // SeqNew(I32)
0x13, // ListPush
0x14, 0x01, 0x02, // RepeatUntil(1, 2)
]
);
}
}

View File

@ -0,0 +1,6 @@
//! Writes the AST into a particular format; for instance,
//! `encoders::wat` writes the AST into a string representing WIT with
//! its textual format.
pub mod binary;
pub mod wat;

View File

@ -0,0 +1,713 @@
//! Writes the AST into a string representing WIT with its textual format.
//!
//! # Example
//!
//! ```rust
//! use wasmer_interface_types::{
//! ast::*,
//! encoders::wat::*,
//! interpreter::Instruction,
//! };
//!
//! # fn main() {
//! let input: String = (&Interfaces {
//! exports: vec![
//! Export {
//! name: "foo",
//! input_types: vec![InterfaceType::I32],
//! output_types: vec![],
//! },
//! Export {
//! name: "bar",
//! input_types: vec![],
//! output_types: vec![],
//! },
//! ],
//! types: vec![],
//! imports: vec![
//! Import {
//! namespace: "ns",
//! name: "foo",
//! input_types: vec![],
//! output_types: vec![InterfaceType::I32],
//! },
//! Import {
//! namespace: "ns",
//! name: "bar",
//! input_types: vec![],
//! output_types: vec![],
//! },
//! ],
//! adapters: vec![
//! Adapter::Import {
//! namespace: "ns",
//! name: "foo",
//! input_types: vec![InterfaceType::I32],
//! output_types: vec![],
//! instructions: vec![Instruction::ArgumentGet { index: 42 }],
//! },
//! Adapter::Export {
//! name: "bar",
//! input_types: vec![],
//! output_types: vec![],
//! instructions: vec![Instruction::ArgumentGet { index: 42 }],
//! },
//! ],
//! forwards: vec![Forward { name: "main" }],
//! })
//! .to_string();
//! let output = r#";; Interfaces
//!
//! ;; Interface, Export foo
//! (@interface export "foo"
//! (param i32))
//!
//! ;; Interface, Export bar
//! (@interface export "bar")
//!
//! ;; Interface, Import ns.foo
//! (@interface func $ns_foo (import "ns" "foo")
//! (result i32))
//!
//! ;; Interface, Import ns.bar
//! (@interface func $ns_bar (import "ns" "bar"))
//!
//! ;; Interface, Adapter ns.foo
//! (@interface adapt (import "ns" "foo")
//! (param i32)
//! arg.get 42)
//!
//! ;; Interface, Adapter bar
//! (@interface adapt (export "bar")
//! arg.get 42)
//!
//! ;; Interface, Forward main
//! (@interface forward (export "main"))"#;
//!
//! assert_eq!(input, output);
//! # }
//! ```
use crate::{
ast::{Adapter, Export, Forward, Import, InterfaceType, Interfaces, Type},
interpreter::Instruction,
};
use std::string::ToString;
/// Encode an `InterfaceType` into a string.
impl ToString for &InterfaceType {
fn to_string(&self) -> String {
match self {
InterfaceType::Int => "Int".into(),
InterfaceType::Float => "Float".into(),
InterfaceType::Any => "Any".into(),
InterfaceType::String => "String".into(),
InterfaceType::Seq => "Seq".into(),
InterfaceType::I32 => "i32".into(),
InterfaceType::I64 => "i64".into(),
InterfaceType::F32 => "f32".into(),
InterfaceType::F64 => "f64".into(),
InterfaceType::AnyRef => "anyref".into(),
}
}
}
/// Encode an `Instruction` into a string.
impl<'input> ToString for &Instruction<'input> {
fn to_string(&self) -> String {
match self {
Instruction::ArgumentGet { index } => format!("arg.get {}", index),
Instruction::Call { function_index } => format!("call {}", function_index),
Instruction::CallExport { export_name } => format!(r#"call-export "{}""#, export_name),
Instruction::ReadUtf8 => "read-utf8".into(),
Instruction::WriteUtf8 { allocator_name } => {
format!(r#"write-utf8 "{}""#, allocator_name)
}
Instruction::AsWasm(interface_type) => {
format!("as-wasm {}", interface_type.to_string())
}
Instruction::AsInterface(interface_type) => {
format!("as-interface {}", interface_type.to_string())
}
Instruction::TableRefAdd => "table-ref-add".into(),
Instruction::TableRefGet => "table-ref-get".into(),
Instruction::CallMethod(index) => format!("call-method {}", index),
Instruction::MakeRecord(interface_type) => {
format!("make-record {}", interface_type.to_string())
}
Instruction::GetField(interface_type, field_index) => {
format!("get-field {} {}", interface_type.to_string(), field_index)
}
Instruction::Const(interface_type, value) => {
format!("const {} {}", interface_type.to_string(), value)
}
Instruction::FoldSeq(import_index) => format!("fold-seq {}", import_index),
Instruction::Add(interface_type) => format!("add {}", interface_type.to_string()),
Instruction::MemToSeq(interface_type, memory) => {
format!(r#"mem-to-seq {} "{}""#, interface_type.to_string(), memory)
}
Instruction::Load(interface_type, memory) => {
format!(r#"load {} "{}""#, interface_type.to_string(), memory)
}
Instruction::SeqNew(interface_type) => {
format!("seq.new {}", interface_type.to_string())
}
Instruction::ListPush => "list.push".into(),
Instruction::RepeatUntil(condition_index, step_index) => {
format!("repeat-until {} {}", condition_index, step_index)
}
}
}
}
/// Encode a list of `InterfaceType` representing inputs into a
/// string.
fn input_types_to_param(input_types: &[InterfaceType]) -> String {
if input_types.is_empty() {
"".into()
} else {
format!(
"\n (param{})",
input_types
.iter()
.fold(String::new(), |mut accumulator, interface_type| {
accumulator.push(' ');
accumulator.push_str(&interface_type.to_string());
accumulator
})
)
}
}
/// Encode a list of `InterfaceType` representing outputs into a
/// string.
fn output_types_to_result(output_types: &[InterfaceType]) -> String {
if output_types.is_empty() {
"".into()
} else {
format!(
"\n (result{})",
output_types
.iter()
.fold(String::new(), |mut accumulator, interface_type| {
accumulator.push(' ');
accumulator.push_str(&interface_type.to_string());
accumulator
})
)
}
}
/// Encode an `Export` into a string.
impl<'input> ToString for &Export<'input> {
fn to_string(&self) -> String {
format!(
r#"(@interface export "{name}"{inputs}{outputs})"#,
name = self.name,
inputs = input_types_to_param(&self.input_types),
outputs = output_types_to_result(&self.output_types),
)
}
}
/// Encode a `Type` into a string.
impl<'input> ToString for &Type<'input> {
fn to_string(&self) -> String {
todo!("To be implemented.")
}
}
/// Encode an `Import` into a string.
impl<'input> ToString for &Import<'input> {
fn to_string(&self) -> String {
format!(
r#"(@interface func ${namespace}_{name} (import "{namespace}" "{name}"){inputs}{outputs})"#,
namespace = self.namespace,
name = self.name,
inputs = input_types_to_param(&self.input_types),
outputs = output_types_to_result(&self.output_types),
)
}
}
/// Encode an `Adapter` into a string.
impl<'input> ToString for &Adapter<'input> {
fn to_string(&self) -> String {
match self {
Adapter::Import {
namespace,
name,
input_types,
output_types,
instructions,
} => format!(
r#"(@interface adapt (import "{namespace}" "{name}"){inputs}{outputs}{instructions})"#,
namespace = namespace,
name = name,
inputs = input_types_to_param(&input_types),
outputs = output_types_to_result(&output_types),
instructions =
instructions
.iter()
.fold(String::new(), |mut accumulator, instruction| {
accumulator.push_str("\n ");
accumulator.push_str(&instruction.to_string());
accumulator
}),
),
Adapter::Export {
name,
input_types,
output_types,
instructions,
} => format!(
r#"(@interface adapt (export "{name}"){inputs}{outputs}{instructions})"#,
name = name,
inputs = input_types_to_param(&input_types),
outputs = output_types_to_result(&output_types),
instructions =
instructions
.iter()
.fold(String::new(), |mut accumulator, instruction| {
accumulator.push_str("\n ");
accumulator.push_str(&instruction.to_string());
accumulator
}),
),
_ => todo!("To be implemented."),
}
}
}
/// Encode a `Forward` into a string.
impl<'input> ToString for &Forward<'input> {
fn to_string(&self) -> String {
format!(
r#"(@interface forward (export "{name}"))"#,
name = self.name,
)
}
}
/// Encode an `Interfaces` into a string.
impl<'input> ToString for &Interfaces<'input> {
fn to_string(&self) -> String {
let mut output = String::from(";; Interfaces");
let exports = self
.exports
.iter()
.fold(String::new(), |mut accumulator, export| {
accumulator.push_str(&format!("\n\n;; Interface, Export {}\n", export.name));
accumulator.push_str(&export.to_string());
accumulator
});
let types = self
.types
.iter()
.fold(String::new(), |mut accumulator, ty| {
accumulator.push_str(&format!("\n\n;; Interface, Ty {}\n", ty.name));
accumulator.push_str(&ty.to_string());
accumulator
});
let imports = self
.imports
.iter()
.fold(String::new(), |mut accumulator, import| {
accumulator.push_str(&format!(
"\n\n;; Interface, Import {}.{}\n",
import.namespace, import.name
));
accumulator.push_str(&import.to_string());
accumulator
});
let adapters = self
.adapters
.iter()
.fold(String::new(), |mut accumulator, adapter| {
match adapter {
Adapter::Import {
namespace, name, ..
} => accumulator.push_str(&format!(
"\n\n;; Interface, Adapter {}.{}\n",
namespace, name
)),
Adapter::Export { name, .. } => {
accumulator.push_str(&format!("\n\n;; Interface, Adapter {}\n", name))
}
_ => todo!("To be implemented."),
}
accumulator.push_str(&adapter.to_string());
accumulator
});
let forwards = self
.forwards
.iter()
.fold(String::new(), |mut accumulator, forward| {
accumulator.push_str(&format!("\n\n;; Interface, Forward {}\n", forward.name));
accumulator.push_str(&forward.to_string());
accumulator
});
output.push_str(&exports);
output.push_str(&types);
output.push_str(&imports);
output.push_str(&adapters);
output.push_str(&forwards);
output
}
}
#[cfg(test)]
mod tests {
use crate::{ast::*, interpreter::Instruction};
#[test]
fn test_interface_types() {
let inputs: Vec<String> = vec![
(&InterfaceType::Int).to_string(),
(&InterfaceType::Float).to_string(),
(&InterfaceType::Any).to_string(),
(&InterfaceType::String).to_string(),
(&InterfaceType::Seq).to_string(),
(&InterfaceType::I32).to_string(),
(&InterfaceType::I64).to_string(),
(&InterfaceType::F32).to_string(),
(&InterfaceType::F64).to_string(),
(&InterfaceType::AnyRef).to_string(),
];
let outputs = vec![
"Int", "Float", "Any", "String", "Seq", "i32", "i64", "f32", "f64", "anyref",
];
assert_eq!(inputs, outputs);
}
#[test]
fn test_instructions() {
let inputs: Vec<String> = vec![
(&Instruction::ArgumentGet { index: 7 }).to_string(),
(&Instruction::Call { function_index: 7 }).to_string(),
(&Instruction::CallExport { export_name: "foo" }).to_string(),
(&Instruction::ReadUtf8).to_string(),
(&Instruction::WriteUtf8 {
allocator_name: "foo",
})
.to_string(),
(&Instruction::AsWasm(InterfaceType::Int)).to_string(),
(&Instruction::AsInterface(InterfaceType::AnyRef)).to_string(),
(&Instruction::TableRefAdd).to_string(),
(&Instruction::TableRefGet).to_string(),
(&Instruction::CallMethod(7)).to_string(),
(&Instruction::MakeRecord(InterfaceType::Int)).to_string(),
(&Instruction::GetField(InterfaceType::Int, 7)).to_string(),
(&Instruction::Const(InterfaceType::I32, 7)).to_string(),
(&Instruction::FoldSeq(7)).to_string(),
(&Instruction::Add(InterfaceType::Int)).to_string(),
(&Instruction::MemToSeq(InterfaceType::Int, "foo")).to_string(),
(&Instruction::Load(InterfaceType::Int, "foo")).to_string(),
(&Instruction::SeqNew(InterfaceType::Int)).to_string(),
(&Instruction::ListPush).to_string(),
(&Instruction::RepeatUntil(1, 2)).to_string(),
];
let outputs = vec![
"arg.get 7",
"call 7",
r#"call-export "foo""#,
"read-utf8",
r#"write-utf8 "foo""#,
"as-wasm Int",
"as-interface anyref",
"table-ref-add",
"table-ref-get",
"call-method 7",
"make-record Int",
"get-field Int 7",
"const i32 7",
"fold-seq 7",
"add Int",
r#"mem-to-seq Int "foo""#,
r#"load Int "foo""#,
"seq.new Int",
"list.push",
"repeat-until 1 2",
];
assert_eq!(inputs, outputs);
}
#[test]
fn test_exports() {
let inputs: Vec<String> = vec![
(&Export {
name: "foo",
input_types: vec![InterfaceType::I32, InterfaceType::F32],
output_types: vec![InterfaceType::I32],
})
.to_string(),
(&Export {
name: "foo",
input_types: vec![InterfaceType::I32],
output_types: vec![],
})
.to_string(),
(&Export {
name: "foo",
input_types: vec![],
output_types: vec![InterfaceType::I32],
})
.to_string(),
(&Export {
name: "foo",
input_types: vec![],
output_types: vec![],
})
.to_string(),
];
let outputs = vec![
r#"(@interface export "foo"
(param i32 f32)
(result i32))"#,
r#"(@interface export "foo"
(param i32))"#,
r#"(@interface export "foo"
(result i32))"#,
r#"(@interface export "foo")"#,
];
assert_eq!(inputs, outputs);
}
#[test]
fn test_imports() {
let inputs: Vec<String> = vec![
(&Import {
namespace: "ns",
name: "foo",
input_types: vec![InterfaceType::Int, InterfaceType::String],
output_types: vec![InterfaceType::String],
})
.to_string(),
(&Import {
namespace: "ns",
name: "foo",
input_types: vec![InterfaceType::String],
output_types: vec![],
})
.to_string(),
(&Import {
namespace: "ns",
name: "foo",
input_types: vec![],
output_types: vec![InterfaceType::String],
})
.to_string(),
(&Import {
namespace: "ns",
name: "foo",
input_types: vec![],
output_types: vec![],
})
.to_string(),
];
let outputs = vec![
r#"(@interface func $ns_foo (import "ns" "foo")
(param Int String)
(result String))"#,
r#"(@interface func $ns_foo (import "ns" "foo")
(param String))"#,
r#"(@interface func $ns_foo (import "ns" "foo")
(result String))"#,
r#"(@interface func $ns_foo (import "ns" "foo"))"#,
];
assert_eq!(inputs, outputs);
}
#[test]
fn test_adapters() {
let inputs: Vec<String> = vec![
(&Adapter::Import {
namespace: "ns",
name: "foo",
input_types: vec![InterfaceType::I32, InterfaceType::F32],
output_types: vec![InterfaceType::I32],
instructions: vec![
Instruction::ArgumentGet { index: 0 },
Instruction::WriteUtf8 {
allocator_name: "hello",
},
Instruction::CallExport { export_name: "f" },
],
})
.to_string(),
(&Adapter::Import {
namespace: "ns",
name: "foo",
input_types: vec![InterfaceType::I32],
output_types: vec![],
instructions: vec![Instruction::CallExport { export_name: "f" }],
})
.to_string(),
(&Adapter::Import {
namespace: "ns",
name: "foo",
input_types: vec![],
output_types: vec![InterfaceType::I32],
instructions: vec![Instruction::CallExport { export_name: "f" }],
})
.to_string(),
(&Adapter::Export {
name: "foo",
input_types: vec![InterfaceType::I32, InterfaceType::F32],
output_types: vec![InterfaceType::I32],
instructions: vec![
Instruction::ArgumentGet { index: 0 },
Instruction::WriteUtf8 {
allocator_name: "hello",
},
Instruction::CallExport { export_name: "f" },
],
})
.to_string(),
(&Adapter::Export {
name: "foo",
input_types: vec![InterfaceType::I32],
output_types: vec![],
instructions: vec![Instruction::CallExport { export_name: "f" }],
})
.to_string(),
(&Adapter::Export {
name: "foo",
input_types: vec![],
output_types: vec![InterfaceType::I32],
instructions: vec![Instruction::CallExport { export_name: "f" }],
})
.to_string(),
];
let outputs = vec![
r#"(@interface adapt (import "ns" "foo")
(param i32 f32)
(result i32)
arg.get 0
write-utf8 "hello"
call-export "f")"#,
r#"(@interface adapt (import "ns" "foo")
(param i32)
call-export "f")"#,
r#"(@interface adapt (import "ns" "foo")
(result i32)
call-export "f")"#,
r#"(@interface adapt (export "foo")
(param i32 f32)
(result i32)
arg.get 0
write-utf8 "hello"
call-export "f")"#,
r#"(@interface adapt (export "foo")
(param i32)
call-export "f")"#,
r#"(@interface adapt (export "foo")
(result i32)
call-export "f")"#,
];
assert_eq!(inputs, outputs);
}
#[test]
fn test_forward() {
let input: String = (&Forward { name: "main" }).to_string();
let output = r#"(@interface forward (export "main"))"#;
assert_eq!(input, output);
}
#[test]
fn test_interfaces() {
let input: String = (&Interfaces {
exports: vec![
Export {
name: "foo",
input_types: vec![InterfaceType::I32],
output_types: vec![],
},
Export {
name: "bar",
input_types: vec![],
output_types: vec![],
},
],
types: vec![],
imports: vec![
Import {
namespace: "ns",
name: "foo",
input_types: vec![],
output_types: vec![InterfaceType::I32],
},
Import {
namespace: "ns",
name: "bar",
input_types: vec![],
output_types: vec![],
},
],
adapters: vec![
Adapter::Import {
namespace: "ns",
name: "foo",
input_types: vec![InterfaceType::I32],
output_types: vec![],
instructions: vec![Instruction::ArgumentGet { index: 42 }],
},
Adapter::Export {
name: "bar",
input_types: vec![],
output_types: vec![],
instructions: vec![Instruction::ArgumentGet { index: 42 }],
},
],
forwards: vec![Forward { name: "main" }],
})
.to_string();
let output = r#";; Interfaces
;; Interface, Export foo
(@interface export "foo"
(param i32))
;; Interface, Export bar
(@interface export "bar")
;; Interface, Import ns.foo
(@interface func $ns_foo (import "ns" "foo")
(result i32))
;; Interface, Import ns.bar
(@interface func $ns_bar (import "ns" "bar"))
;; Interface, Adapter ns.foo
(@interface adapt (import "ns" "foo")
(param i32)
arg.get 42)
;; Interface, Adapter bar
(@interface adapt (export "bar")
arg.get 42)
;; Interface, Forward main
(@interface forward (export "main"))"#;
assert_eq!(input, output);
}
}

View File

@ -0,0 +1,77 @@
use crate::ast::InterfaceType;
/// Represents all the possible WIT instructions.
#[derive(PartialEq, Debug)]
pub enum Instruction<'input> {
/// The `arg.get` instruction.
ArgumentGet {
/// The argument index.
index: u64,
},
/// The `call` instruction.
Call {
/// The function index.
function_index: usize,
},
/// The `call-export` instruction.
CallExport {
/// The exported function name.
export_name: &'input str,
},
/// The `read-utf8` instruction.
ReadUtf8,
/// The `write-utf8` instruction.
WriteUtf8 {
/// The allocator function name.
allocator_name: &'input str,
},
/// The `as-wasm` instruction.
AsWasm(InterfaceType),
/// The `as-interface` instruction.
AsInterface(InterfaceType),
/// The `table-ref-add` instruction.
TableRefAdd,
/// The `table-ref-get` instruction.
TableRefGet,
/// The `call-method` instruction.
CallMethod(u64),
/// The `make-record` instruction.
MakeRecord(InterfaceType),
/// The `get-field` instruction.
GetField(InterfaceType, u64),
/// The `const` instruction.
Const(InterfaceType, u64),
/// The `fold-seq` instruction.
FoldSeq(u64),
/// The `add` instruction.
Add(InterfaceType),
/// The `mem-to-seq` instruction.
MemToSeq(InterfaceType, &'input str),
/// The `load` instruction.
Load(InterfaceType, &'input str),
/// The `seq.new` instruction.
SeqNew(InterfaceType),
/// The `list.push` instruction.
ListPush,
/// The `repeat-until` instruction.
RepeatUntil(u64, u64),
}

View File

@ -0,0 +1,54 @@
executable_instruction!(
argument_get(index: u64, instruction_name: String) -> _ {
move |runtime| -> _ {
let invocation_inputs = runtime.invocation_inputs;
if index >= (invocation_inputs.len() as u64) {
return Err(format!(
"`{}` cannot access argument #{} because it doesn't exist.",
instruction_name, index
));
}
runtime.stack.push(invocation_inputs[index as usize].clone());
Ok(())
}
}
);
#[cfg(test)]
mod tests {
test_executable_instruction!(
test_argument_get =
instructions: [Instruction::ArgumentGet { index: 0 }],
invocation_inputs: [InterfaceValue::I32(42)],
instance: Instance::new(),
stack: [InterfaceValue::I32(42)],
);
test_executable_instruction!(
test_argument_get__twice =
instructions: [
Instruction::ArgumentGet { index: 0 },
Instruction::ArgumentGet { index: 1 },
],
invocation_inputs: [
InterfaceValue::I32(7),
InterfaceValue::I32(42),
],
instance: Instance::new(),
stack: [
InterfaceValue::I32(7),
InterfaceValue::I32(42),
],
);
test_executable_instruction!(
test_argument_get__invalid_index =
instructions: [Instruction::ArgumentGet { index: 1 }],
invocation_inputs: [InterfaceValue::I32(42)],
instance: Instance::new(),
error: "`arg.get 1` cannot access argument #1 because it doesn't exist."
);
}

View File

@ -0,0 +1,187 @@
use crate::interpreter::wasm::{
structures::{FunctionIndex, TypedIndex},
values::InterfaceType,
};
executable_instruction!(
call(function_index: usize, instruction_name: String) -> _ {
move |runtime| -> _ {
let instance = &mut runtime.wasm_instance;
let index = FunctionIndex::new(function_index);
match instance.local_or_import(index) {
Some(local_or_import) => {
let inputs_cardinality = local_or_import.inputs_cardinality();
match runtime.stack.pop(inputs_cardinality) {
Some(inputs) => {
let input_types = inputs
.iter()
.map(Into::into)
.collect::<Vec<InterfaceType>>();
if input_types != local_or_import.inputs() {
return Err(format!(
"`{}` cannot call the local or imported function `{}` because the value types on the stack mismatch the function signature (expects {:?}).",
instruction_name,
function_index,
local_or_import.inputs(),
))
}
match local_or_import.call(&inputs) {
Ok(outputs) => {
for output in outputs.iter() {
runtime.stack.push(output.clone());
}
Ok(())
}
Err(_) => Err(format!(
"`{}` failed when calling the local or imported function `{}`.",
instruction_name,
function_index
))
}
}
None => Err(format!(
"`{}` cannot call the local or imported function `{}` because there is not enough data on the stack for the arguments (needs {}).",
instruction_name,
function_index,
inputs_cardinality,
))
}
}
None => Err(format!(
"`{}` cannot call the local or imported function `{}` because it doesn't exist.",
instruction_name,
function_index,
))
}
}
}
);
#[cfg(test)]
mod tests {
test_executable_instruction!(
test_call =
instructions: [
Instruction::ArgumentGet { index: 1 },
Instruction::ArgumentGet { index: 0 },
Instruction::Call { function_index: 42 },
],
invocation_inputs: [
InterfaceValue::I32(3),
InterfaceValue::I32(4),
],
instance: Instance::new(),
stack: [InterfaceValue::I32(12)],
);
test_executable_instruction!(
test_call__invalid_local_import_index =
instructions: [
Instruction::Call { function_index: 42 },
],
invocation_inputs: [
InterfaceValue::I32(3),
InterfaceValue::I32(4),
],
instance: Default::default(),
error: r#"`call 42` cannot call the local or imported function `42` because it doesn't exist."#,
);
test_executable_instruction!(
test_call__stack_is_too_small =
instructions: [
Instruction::ArgumentGet { index: 0 },
Instruction::Call { function_index: 42 },
// ^^ `42` expects 2 values on the stack, only one is present
],
invocation_inputs: [
InterfaceValue::I32(3),
InterfaceValue::I32(4),
],
instance: Instance::new(),
error: r#"`call 42` cannot call the local or imported function `42` because there is not enough data on the stack for the arguments (needs 2)."#,
);
test_executable_instruction!(
test_call__invalid_types_in_the_stack =
instructions: [
Instruction::ArgumentGet { index: 1 },
Instruction::ArgumentGet { index: 0 },
Instruction::Call { function_index: 42 },
],
invocation_inputs: [
InterfaceValue::I32(3),
InterfaceValue::I64(4),
// ^^^ mismatch with `42` signature
],
instance: Instance::new(),
error: r#"`call 42` cannot call the local or imported function `42` because the value types on the stack mismatch the function signature (expects [I32, I32])."#,
);
test_executable_instruction!(
test_call__failure_when_calling =
instructions: [
Instruction::ArgumentGet { index: 1 },
Instruction::ArgumentGet { index: 0 },
Instruction::Call { function_index: 42 },
],
invocation_inputs: [
InterfaceValue::I32(3),
InterfaceValue::I32(4),
],
instance: Instance {
locals_or_imports: {
let mut hashmap = HashMap::new();
hashmap.insert(
42,
LocalImport {
inputs: vec![InterfaceType::I32, InterfaceType::I32],
outputs: vec![InterfaceType::I32],
function: |_| Err(()),
// ^^^^^^^ function fails
},
);
hashmap
},
..Default::default()
},
error: r#"`call 42` failed when calling the local or imported function `42`."#,
);
test_executable_instruction!(
test_call__void =
instructions: [
Instruction::ArgumentGet { index: 1 },
Instruction::ArgumentGet { index: 0 },
Instruction::Call { function_index: 42 },
],
invocation_inputs: [
InterfaceValue::I32(3),
InterfaceValue::I32(4),
],
instance: Instance {
locals_or_imports: {
let mut hashmap = HashMap::new();
hashmap.insert(
42,
LocalImport {
inputs: vec![InterfaceType::I32, InterfaceType::I32],
outputs: vec![InterfaceType::I32],
function: |_| Ok(vec![]),
// ^^^^^^^^^^ void
},
);
hashmap
},
..Default::default()
},
stack: [],
);
}

View File

@ -0,0 +1,177 @@
use crate::interpreter::wasm::values::InterfaceType;
executable_instruction!(
call_export(export_name: String, instruction_name: String) -> _ {
move |runtime| -> _ {
let instance = &mut runtime.wasm_instance;
match instance.export(&export_name) {
Some(export) => {
let inputs_cardinality = export.inputs_cardinality();
match runtime.stack.pop(inputs_cardinality) {
Some(inputs) => {
let input_types = inputs
.iter()
.map(Into::into)
.collect::<Vec<InterfaceType>>();
if input_types != export.inputs() {
return Err(format!(
"`{}` cannot call the exported function `{}` because the value types on the stack mismatch the function signature (expects {:?}).",
instruction_name,
export_name,
export.inputs(),
))
}
match export.call(&inputs) {
Ok(outputs) => {
for output in outputs.iter() {
runtime.stack.push(output.clone());
}
Ok(())
}
Err(_) => Err(format!(
"`{}` failed when calling the exported function `{}`.",
instruction_name,
export_name
))
}
}
None => Err(format!(
"`{}` cannot call the exported function `{}` because there is not enough data on the stack for the arguments (needs {}).",
instruction_name,
export_name,
inputs_cardinality,
))
}
}
None => Err(format!(
"`{}` cannot call the exported function `{}` because it doesn't exist.",
instruction_name,
export_name,
))
}
}
}
);
#[cfg(test)]
mod tests {
test_executable_instruction!(
test_call_export =
instructions: [
Instruction::ArgumentGet { index: 1 },
Instruction::ArgumentGet { index: 0 },
Instruction::CallExport { export_name: "sum" },
],
invocation_inputs: [
InterfaceValue::I32(3),
InterfaceValue::I32(4),
],
instance: Instance::new(),
stack: [InterfaceValue::I32(7)],
);
test_executable_instruction!(
test_call_export__invalid_export_name =
instructions: [Instruction::CallExport { export_name: "bar" }],
invocation_inputs: [],
instance: Instance::new(),
error: r#"`call-export "bar"` cannot call the exported function `bar` because it doesn't exist."#,
);
test_executable_instruction!(
test_call_export__stack_is_too_small =
instructions: [
Instruction::ArgumentGet { index: 0 },
Instruction::CallExport { export_name: "sum" },
],
invocation_inputs: [
InterfaceValue::I32(3),
InterfaceValue::I32(4),
],
instance: Instance::new(),
error: r#"`call-export "sum"` cannot call the exported function `sum` because there is not enough data on the stack for the arguments (needs 2)."#,
);
test_executable_instruction!(
test_call_export__invalid_types_in_the_stack =
instructions: [
Instruction::ArgumentGet { index: 1 },
Instruction::ArgumentGet { index: 0 },
Instruction::CallExport { export_name: "sum" },
],
invocation_inputs: [
InterfaceValue::I32(3),
InterfaceValue::I64(4),
// ^^^ mismatch with `sum` signature
],
instance: Instance::new(),
error: r#"`call-export "sum"` cannot call the exported function `sum` because the value types on the stack mismatch the function signature (expects [I32, I32])."#,
);
test_executable_instruction!(
test_call_export__failure_when_calling =
instructions: [
Instruction::ArgumentGet { index: 1 },
Instruction::ArgumentGet { index: 0 },
Instruction::CallExport { export_name: "sum" },
],
invocation_inputs: [
InterfaceValue::I32(3),
InterfaceValue::I32(4),
],
instance: Instance {
exports: {
let mut hashmap = HashMap::new();
hashmap.insert(
"sum".into(),
Export {
inputs: vec![InterfaceType::I32, InterfaceType::I32],
outputs: vec![InterfaceType::I32],
function: |_| Err(()),
// ^^^^^^^ function fails
},
);
hashmap
},
..Default::default()
},
error: r#"`call-export "sum"` failed when calling the exported function `sum`."#,
);
test_executable_instruction!(
test_call_export__void =
instructions: [
Instruction::ArgumentGet { index: 1 },
Instruction::ArgumentGet { index: 0 },
Instruction::CallExport { export_name: "sum" },
],
invocation_inputs: [
InterfaceValue::I32(3),
InterfaceValue::I32(4),
],
instance: Instance {
exports: {
let mut hashmap = HashMap::new();
hashmap.insert(
"sum".into(),
Export {
inputs: vec![InterfaceType::I32, InterfaceType::I32],
outputs: vec![InterfaceType::I32],
function: |_| Ok(vec![]),
// ^^^^^^^^^^ void function
},
);
hashmap
},
..Default::default()
},
stack: [],
);
}

View File

@ -0,0 +1,188 @@
mod argument_get;
mod call;
mod call_export;
mod read_utf8;
mod write_utf8;
pub(crate) use argument_get::argument_get;
pub(crate) use call::call;
pub(crate) use call_export::call_export;
pub(crate) use read_utf8::read_utf8;
pub(crate) use write_utf8::write_utf8;
#[cfg(test)]
pub(crate) mod tests {
use crate::interpreter::wasm::{
self,
values::{InterfaceType, InterfaceValue},
};
use std::{cell::Cell, collections::HashMap, convert::TryInto, ops::Deref, rc::Rc};
pub(crate) struct Export {
pub(crate) inputs: Vec<InterfaceType>,
pub(crate) outputs: Vec<InterfaceType>,
pub(crate) function: fn(arguments: &[InterfaceValue]) -> Result<Vec<InterfaceValue>, ()>,
}
impl wasm::structures::Export for Export {
fn inputs_cardinality(&self) -> usize {
self.inputs.len() as usize
}
fn outputs_cardinality(&self) -> usize {
self.outputs.len()
}
fn inputs(&self) -> &[InterfaceType] {
&self.inputs
}
fn outputs(&self) -> &[InterfaceType] {
&self.outputs
}
fn call(&self, arguments: &[InterfaceValue]) -> Result<Vec<InterfaceValue>, ()> {
(self.function)(arguments)
}
}
pub(crate) struct LocalImport {
pub(crate) inputs: Vec<InterfaceType>,
pub(crate) outputs: Vec<InterfaceType>,
pub(crate) function: fn(arguments: &[InterfaceValue]) -> Result<Vec<InterfaceValue>, ()>,
}
impl wasm::structures::LocalImport for LocalImport {
fn inputs_cardinality(&self) -> usize {
self.inputs.len()
}
fn outputs_cardinality(&self) -> usize {
self.outputs.len()
}
fn inputs(&self) -> &[InterfaceType] {
&self.inputs
}
fn outputs(&self) -> &[InterfaceType] {
&self.outputs
}
fn call(&self, arguments: &[InterfaceValue]) -> Result<Vec<InterfaceValue>, ()> {
(self.function)(arguments)
}
}
#[derive(Default, Clone)]
pub(crate) struct MemoryView(Rc<Vec<Cell<u8>>>);
impl wasm::structures::MemoryView for MemoryView {}
impl Deref for MemoryView {
type Target = [Cell<u8>];
fn deref(&self) -> &Self::Target {
self.0.as_slice()
}
}
#[derive(Default)]
pub(crate) struct Memory {
pub(crate) view: MemoryView,
}
impl Memory {
pub(crate) fn new(data: Vec<Cell<u8>>) -> Self {
Self {
view: MemoryView(Rc::new(data)),
}
}
}
impl wasm::structures::Memory<MemoryView> for Memory {
fn view(&self) -> MemoryView {
self.view.clone()
}
}
#[derive(Default)]
pub(crate) struct Instance {
pub(crate) exports: HashMap<String, Export>,
pub(crate) locals_or_imports: HashMap<usize, LocalImport>,
pub(crate) memory: Memory,
}
impl Instance {
pub(crate) fn new() -> Self {
Self {
exports: {
let mut hashmap = HashMap::new();
hashmap.insert(
"sum".into(),
Export {
inputs: vec![InterfaceType::I32, InterfaceType::I32],
outputs: vec![InterfaceType::I32],
function: |arguments: &[InterfaceValue]| {
let a: i32 = (&arguments[0]).try_into().unwrap();
let b: i32 = (&arguments[1]).try_into().unwrap();
Ok(vec![InterfaceValue::I32(a + b)])
},
},
);
hashmap.insert(
"alloc".into(),
Export {
inputs: vec![InterfaceType::I32],
outputs: vec![InterfaceType::I32],
function: |arguments: &[InterfaceValue]| {
let _size: i32 = (&arguments[0]).try_into().unwrap();
Ok(vec![InterfaceValue::I32(0)])
},
},
);
hashmap
},
locals_or_imports: {
let mut hashmap = HashMap::new();
hashmap.insert(
42,
LocalImport {
inputs: vec![InterfaceType::I32, InterfaceType::I32],
outputs: vec![InterfaceType::I32],
function: |arguments: &[InterfaceValue]| {
let a: i32 = (&arguments[0]).try_into().unwrap();
let b: i32 = (&arguments[1]).try_into().unwrap();
Ok(vec![InterfaceValue::I32(a * b)])
},
},
);
hashmap
},
memory: Memory::new(vec![Cell::new(0); 128]),
}
}
}
impl wasm::structures::Instance<Export, LocalImport, Memory, MemoryView> for Instance {
fn export(&self, export_name: &str) -> Option<&Export> {
self.exports.get(export_name)
}
fn local_or_import<I: wasm::structures::TypedIndex + wasm::structures::LocalImportIndex>(
&mut self,
index: I,
) -> Option<&LocalImport> {
self.locals_or_imports.get(&index.index())
}
fn memory(&self, _index: usize) -> Option<&Memory> {
Some(&self.memory)
}
}
}

View File

@ -0,0 +1,131 @@
use crate::interpreter::wasm::values::InterfaceValue;
use std::{cell::Cell, convert::TryFrom};
executable_instruction!(
read_utf8(instruction_name: String) -> _ {
move |runtime| -> _ {
match runtime.stack.pop(2) {
Some(inputs) => match runtime.wasm_instance.memory(0) {
Some(memory) => {
let length = i32::try_from(&inputs[0])? as usize;
let pointer = i32::try_from(&inputs[1])? as usize;
let memory_view = memory.view();
if memory_view.len() < pointer + length {
return Err(format!(
"`{}` failed because it has to read out of the memory bounds (index {} > memory length {}).",
instruction_name,
pointer + length,
memory_view.len()
));
}
let data: Vec<u8> = (&memory_view[pointer..pointer + length])
.iter()
.map(Cell::get)
.collect();
match String::from_utf8(data) {
Ok(string) => {
runtime.stack.push(InterfaceValue::String(string));
Ok(())
}
Err(utf8_error) => Err(format!(
"`{}` failed because the read string isn't UTF-8 valid ({}).",
instruction_name,
utf8_error,
))
}
}
None => Err(format!(
"`{}` failed because there is no memory to read.",
instruction_name
))
}
None => Err(format!(
"`{}` failed because there is not enough data on the stack (needs 2).",
instruction_name,
))
}
}
}
);
#[cfg(test)]
mod tests {
test_executable_instruction!(
test_read_utf8 =
instructions: [
Instruction::ArgumentGet { index: 1 },
Instruction::ArgumentGet { index: 0 },
Instruction::ReadUtf8,
],
invocation_inputs: [
InterfaceValue::I32(13),
// ^^^^^^^ length
InterfaceValue::I32(0),
// ^^^^^^ pointer
],
instance: Instance {
memory: Memory::new("Hello, World!".as_bytes().iter().map(|u| Cell::new(*u)).collect()),
..Default::default()
},
stack: [InterfaceValue::String("Hello, World!".into())],
);
test_executable_instruction!(
test_read_utf8__read_out_of_memory =
instructions: [
Instruction::ArgumentGet { index: 1 },
Instruction::ArgumentGet { index: 0 },
Instruction::ReadUtf8,
],
invocation_inputs: [
InterfaceValue::I32(13),
// ^^^^^^^ length is too long
InterfaceValue::I32(0),
// ^^^^^^ pointer
],
instance: Instance {
memory: Memory::new("Hello!".as_bytes().iter().map(|u| Cell::new(*u)).collect()),
..Default::default()
},
error: r#"`read-utf8` failed because it has to read out of the memory bounds (index 13 > memory length 6)."#,
);
test_executable_instruction!(
test_read_utf8__invalid_encoding =
instructions: [
Instruction::ArgumentGet { index: 1 },
Instruction::ArgumentGet { index: 0 },
Instruction::ReadUtf8,
],
invocation_inputs: [
InterfaceValue::I32(4),
// ^^^^^^ length is too long
InterfaceValue::I32(0),
// ^^^^^^ pointer
],
instance: Instance {
memory: Memory::new(vec![0, 159, 146, 150].iter().map(|b| Cell::new(*b)).collect::<Vec<Cell<u8>>>()),
..Default::default()
},
error: r#"`read-utf8` failed because the read string isn't UTF-8 valid (invalid utf-8 sequence of 1 bytes from index 1)."#,
);
test_executable_instruction!(
test_read_utf8__stack_is_too_small =
instructions: [
Instruction::ArgumentGet { index: 0 },
Instruction::ReadUtf8,
// ^^^^^^^^ `read-utf8` expects 2 values on the stack, only one is present.
],
invocation_inputs: [
InterfaceValue::I32(13),
InterfaceValue::I32(0),
],
instance: Instance::new(),
error: r#"`read-utf8` failed because there is not enough data on the stack (needs 2)."#,
);
}

View File

@ -0,0 +1,169 @@
use crate::interpreter::wasm::values::{InterfaceType, InterfaceValue};
use std::convert::TryInto;
executable_instruction!(
write_utf8(allocator_name: String, instruction_name: String) -> _ {
move |runtime| -> _ {
let instance = &mut runtime.wasm_instance;
match instance.export(&allocator_name) {
Some(allocator) => {
if allocator.inputs() != [InterfaceType::I32] ||
allocator.outputs() != [InterfaceType::I32] {
return Err(format!(
"`{}` failed because the allocator `{}` has an invalid signature (expects [I32] -> [I32]).",
instruction_name,
allocator_name,
))
}
match instance.memory(0) {
Some(memory) => match runtime.stack.pop1() {
Some(string) => {
let memory_view = memory.view();
let string: String = (&string).try_into()?;
let string_bytes = string.as_bytes();
let string_length = (string_bytes.len() as i32)
.try_into()
.map_err(|error| format!("{}", error))?;
match allocator.call(&[InterfaceValue::I32(string_length)]) {
Ok(outputs) => {
let string_pointer: i32 = (&outputs[0]).try_into()?;
for (nth, byte) in string_bytes.iter().enumerate() {
memory_view[string_pointer as usize + nth].set(*byte);
}
runtime.stack.push(InterfaceValue::I32(string_pointer));
runtime.stack.push(InterfaceValue::I32(string_length));
Ok(())
}
Err(_) => Err(format!(
"`{}` failed when calling the allocator `{}`.",
instruction_name,
allocator_name,
))
}
}
None => Err(format!(
"`{}` cannot call the allocator `{}` because there is not enough data on the stack for the arguments (needs {}).",
instruction_name,
allocator_name,
1
))
}
None => Err(format!(
"`{}` failed because there is no memory to write into.",
instruction_name
))
}
}
None => Err(format!(
"`{}` failed because the exported function `{}` (the allocator) doesn't exist.",
instruction_name,
allocator_name
))
}
}
}
);
#[cfg(test)]
mod tests {
test_executable_instruction!(
test_write_utf8 =
instructions: [
Instruction::ArgumentGet { index: 0 },
Instruction::WriteUtf8 { allocator_name: "alloc" },
],
invocation_inputs: [InterfaceValue::String("Hello, World!".into())],
instance: Instance::new(),
stack: [
InterfaceValue::I32(0),
// ^^^^^^ pointer
InterfaceValue::I32(13),
// ^^^^^^^ length
]
);
test_executable_instruction!(
test_write_utf8__roundtrip_with_read_utf8 =
instructions: [
Instruction::ArgumentGet { index: 0 },
Instruction::WriteUtf8 { allocator_name: "alloc" },
Instruction::ReadUtf8,
],
invocation_inputs: [InterfaceValue::String("Hello, World!".into())],
instance: Instance::new(),
stack: [InterfaceValue::String("Hello, World!".into())],
);
test_executable_instruction!(
test_write_utf8__allocator_does_not_exist =
instructions: [Instruction::WriteUtf8 { allocator_name: "alloc" }],
invocation_inputs: [],
instance: Instance { ..Default::default() },
error: r#"`write-utf8 "alloc"` failed because the exported function `alloc` (the allocator) doesn't exist."#,
);
test_executable_instruction!(
test_write_utf8__stack_is_too_small =
instructions: [
Instruction::WriteUtf8 { allocator_name: "alloc" }
// ^^^^^ `alloc` expects 1 value on the stack, none is present
],
invocation_inputs: [InterfaceValue::String("Hello, World!".into())],
instance: Instance::new(),
error: r#"`write-utf8 "alloc"` cannot call the allocator `alloc` because there is not enough data on the stack for the arguments (needs 1)."#,
);
test_executable_instruction!(
test_write_utf8__failure_when_calling_the_allocator =
instructions: [
Instruction::ArgumentGet { index: 0 },
Instruction::WriteUtf8 { allocator_name: "alloc-fail" }
],
invocation_inputs: [InterfaceValue::String("Hello, World!".into())],
instance: {
let mut instance = Instance::new();
instance.exports.insert(
"alloc-fail".into(),
Export {
inputs: vec![InterfaceType::I32],
outputs: vec![InterfaceType::I32],
function: |_| Err(()),
// ^^^^^^^ function fails
},
);
instance
},
error: r#"`write-utf8 "alloc-fail"` failed when calling the allocator `alloc-fail`."#,
);
test_executable_instruction!(
test_write_utf8__invalid_allocator_signature =
instructions: [
Instruction::ArgumentGet { index: 0 },
Instruction::WriteUtf8 { allocator_name: "alloc-fail" }
],
invocation_inputs: [InterfaceValue::String("Hello, World!".into())],
instance: {
let mut instance = Instance::new();
instance.exports.insert(
"alloc-fail".into(),
Export {
inputs: vec![InterfaceType::I32, InterfaceType::I32],
outputs: vec![],
function: |_| Err(()),
},
);
instance
},
error: r#"`write-utf8 "alloc-fail"` failed because the allocator `alloc-fail` has an invalid signature (expects [I32] -> [I32])."#,
);
}

View File

@ -0,0 +1,241 @@
//! A stack-based interpreter to execute instructions of WIT adapters.
mod instruction;
mod instructions;
pub mod stack;
pub mod wasm;
pub use instruction::Instruction;
use stack::Stack;
use std::{convert::TryFrom, marker::PhantomData};
use wasm::values::InterfaceValue;
/// Represents the `Runtime`, which is used by an adapter to execute
/// its instructions.
pub(crate) struct Runtime<'invocation, 'instance, Instance, Export, LocalImport, Memory, MemoryView>
where
Export: wasm::structures::Export + 'instance,
LocalImport: wasm::structures::LocalImport + 'instance,
Memory: wasm::structures::Memory<MemoryView> + 'instance,
MemoryView: wasm::structures::MemoryView,
Instance: wasm::structures::Instance<Export, LocalImport, Memory, MemoryView> + 'instance,
{
/// The invocation inputs are all the arguments received by an
/// adapter.
invocation_inputs: &'invocation [InterfaceValue],
/// Each runtime (so adapter) has its own stack instance.
stack: Stack<InterfaceValue>,
/// The WebAssembly module instance. It is used by adapter's
/// instructions.
wasm_instance: &'instance mut Instance,
/// Phantom data.
_phantom: PhantomData<(Export, LocalImport, Memory, MemoryView)>,
}
/// Type alias for an executable instruction. It's an implementation
/// details, but an instruction is a boxed closure instance.
pub(crate) type ExecutableInstruction<Instance, Export, LocalImport, Memory, MemoryView> = Box<
dyn Fn(&mut Runtime<Instance, Export, LocalImport, Memory, MemoryView>) -> Result<(), String>,
>;
/// An interpreter is the central piece of this crate. It is a set of
/// executable instructions. Each instruction takes the runtime as
/// argument. The runtime holds the invocation inputs, the stack, and
/// the WebAssembly instance.
///
/// When the interpreter executes the instructions, each of them can
/// query the WebAssembly instance, operates on the stack, or reads
/// the invocation inputs. At the end of the execution, the stack
/// supposedly contains a result. Since an interpreter is used by a
/// WIT adapter to execute its instructions, the result on the stack
/// is the result of the adapter.
///
/// # Example
///
/// ```rust,ignore
/// use std::{cell::Cell, collections::HashMap, convert::TryInto};
/// use wasmer_interface_types::interpreter::{
/// instructions::tests::{Export, Instance, LocalImport, Memory, MemoryView},
/// // ^^^^^^^^^^^^ This is private and for testing purposes only.
/// // It is basically a fake WebAssembly runtime.
/// stack::Stackable,
/// wasm::values::{InterfaceType, InterfaceValue},
/// Instruction, Interpreter,
/// };
///
/// # fn main() {
/// // 1. Creates an interpreter from a set of instructions. They will
/// // be transformed into executable instructions.
/// let interpreter: Interpreter<Instance, Export, LocalImport, Memory, MemoryView> = (&vec![
/// Instruction::ArgumentGet { index: 1 },
/// Instruction::ArgumentGet { index: 0 },
/// Instruction::CallExport { export_name: "sum" },
/// ])
/// .try_into()
/// .unwrap();
///
/// // 2. Defines the arguments of the adapter.
/// let invocation_inputs = vec![InterfaceValue::I32(3), InterfaceValue::I32(4)];
///
/// // 3. Creates a WebAssembly instance.
/// let mut instance = Instance {
/// // 3.1. Defines one exported function: `fn sum(a: i32, b: i32) -> i32 { a + b }`.
/// exports: {
/// let mut hashmap = HashMap::new();
/// hashmap.insert(
/// "sum".into(),
/// Export {
/// // Defines the argument types of the function.
/// inputs: vec![InterfaceType::I32, InterfaceType::I32],
///
/// // Defines the result types.
/// outputs: vec![InterfaceType::I32],
///
/// // Defines the function implementation.
/// function: |arguments: &[InterfaceValue]| {
/// let a: i32 = (&arguments[0]).try_into().unwrap();
/// let b: i32 = (&arguments[1]).try_into().unwrap();
///
/// Ok(vec![InterfaceValue::I32(a + b)])
/// },
/// },
/// );
/// },
/// ..Default::default()
/// };
///
/// // 4. Executes the instructions.
/// let run = interpreter.run(&invocation_inputs, &mut instance);
///
/// assert!(run.is_ok());
///
/// let stack = run.unwrap();
///
/// // 5. Read the stack to get the result.
/// assert_eq!(stack.as_slice(), &[InterfaceValue::I32(7)]);
/// # }
/// ```
pub struct Interpreter<Instance, Export, LocalImport, Memory, MemoryView>
where
Export: wasm::structures::Export,
LocalImport: wasm::structures::LocalImport,
Memory: wasm::structures::Memory<MemoryView>,
MemoryView: wasm::structures::MemoryView,
Instance: wasm::structures::Instance<Export, LocalImport, Memory, MemoryView>,
{
executable_instructions:
Vec<ExecutableInstruction<Instance, Export, LocalImport, Memory, MemoryView>>,
}
impl<Instance, Export, LocalImport, Memory, MemoryView>
Interpreter<Instance, Export, LocalImport, Memory, MemoryView>
where
Export: wasm::structures::Export,
LocalImport: wasm::structures::LocalImport,
Memory: wasm::structures::Memory<MemoryView>,
MemoryView: wasm::structures::MemoryView,
Instance: wasm::structures::Instance<Export, LocalImport, Memory, MemoryView>,
{
fn iter(
&self,
) -> impl Iterator<
Item = &ExecutableInstruction<Instance, Export, LocalImport, Memory, MemoryView>,
> + '_ {
self.executable_instructions.iter()
}
/// Runs the interpreter, such as:
/// 1. Create a fresh stack,
/// 2. Create a fresh stack,
/// 3. Execute the instructions one after the other, and
/// returns the stack.
pub fn run(
&self,
invocation_inputs: &[InterfaceValue],
wasm_instance: &mut Instance,
) -> Result<Stack<InterfaceValue>, String> {
let mut runtime = Runtime {
invocation_inputs,
stack: Stack::new(),
wasm_instance,
_phantom: PhantomData,
};
for executable_instruction in self.iter() {
match executable_instruction(&mut runtime) {
Ok(_) => continue,
Err(message) => return Err(message),
}
}
Ok(runtime.stack)
}
}
/// Transforms a `Vec<Instruction>` into an `Interpreter`.
impl<'binary_input, Instance, Export, LocalImport, Memory, MemoryView>
TryFrom<&Vec<Instruction<'binary_input>>>
for Interpreter<Instance, Export, LocalImport, Memory, MemoryView>
where
Export: wasm::structures::Export,
LocalImport: wasm::structures::LocalImport,
Memory: wasm::structures::Memory<MemoryView>,
MemoryView: wasm::structures::MemoryView,
Instance: wasm::structures::Instance<Export, LocalImport, Memory, MemoryView>,
{
type Error = String;
fn try_from(instructions: &Vec<Instruction>) -> Result<Self, Self::Error> {
let executable_instructions = instructions
.iter()
.map(|instruction| {
let instruction_name = instruction.to_string();
match instruction {
Instruction::ArgumentGet { index } => {
instructions::argument_get(*index, instruction_name)
}
Instruction::Call { function_index } => {
instructions::call(*function_index, instruction_name)
}
Instruction::CallExport { export_name } => {
instructions::call_export((*export_name).to_owned(), instruction_name)
}
Instruction::ReadUtf8 => instructions::read_utf8(instruction_name),
Instruction::WriteUtf8 { allocator_name } => {
instructions::write_utf8((*allocator_name).to_owned(), instruction_name)
}
_ => unimplemented!(),
}
})
.collect();
Ok(Interpreter {
executable_instructions,
})
}
}
#[cfg(test)]
mod tests {
use super::{wasm::structures::EmptyMemoryView, Instruction, Interpreter};
use std::convert::TryInto;
#[test]
fn test_interpreter_from_instructions() {
let instructions = vec![
Instruction::ArgumentGet { index: 0 },
Instruction::ArgumentGet { index: 0 },
Instruction::CallExport { export_name: "foo" },
Instruction::ReadUtf8,
Instruction::Call { function_index: 7 },
];
let interpreter: Interpreter<(), (), (), (), EmptyMemoryView> =
(&instructions).try_into().unwrap();
assert_eq!(interpreter.executable_instructions.len(), 5);
}
}

View File

@ -0,0 +1,130 @@
//! A very light and generic stack implementation, exposing only the
//! operations required by the interpreter.
/// The `Stackable` trait represents a small basic set of operations
/// required by the interpreter.
pub trait Stackable {
/// The kind of item the stack holds.
type Item;
/// Checks whether the stack is empty.
fn is_empty(&self) -> bool;
/// Extracts a slice containing the entire stack.
fn as_slice(&self) -> &[Self::Item];
/// Appends one item to the end of the stack.
fn push(&mut self, item: Self::Item);
/// Removes the last item of the stack and returns it, `None` if
/// the stack is empty.
fn pop1(&mut self) -> Option<Self::Item>;
/// Removes `n` elements from the end of the stack, `None` if the
/// stack doesn't contain enough elements.
/// Returned items are ordered by FIFO: the last element comes
/// first in the list.
fn pop(&mut self, n: usize) -> Option<Vec<Self::Item>>;
}
/// A stack implementation of the `Stackable` trait, based on a vector.
#[derive(Debug, Default)]
pub struct Stack<T>
where
T: Default + Clone,
{
/// Inner structure holding the items.
inner: Vec<T>,
}
impl<T> Stack<T>
where
T: Default + Clone,
{
/// Creates a new empty stack.
pub fn new() -> Self {
Self {
..Default::default()
}
}
}
impl<T> Stackable for Stack<T>
where
T: Default + Clone,
{
type Item = T;
fn is_empty(&self) -> bool {
self.inner.is_empty()
}
fn as_slice(&self) -> &[Self::Item] {
self.inner.as_slice()
}
fn push(&mut self, item: Self::Item) {
self.inner.push(item);
}
fn pop1(&mut self) -> Option<Self::Item> {
self.inner.pop()
}
fn pop(&mut self, n: usize) -> Option<Vec<Self::Item>> {
if self.inner.len() < n {
None
} else {
let items = self
.inner
.drain(self.inner.len() - n..)
.rev()
.collect::<Vec<Self::Item>>();
assert!(items.len() == n);
Some(items)
}
}
}
#[cfg(test)]
mod tests {
use super::{Stack, Stackable};
#[test]
fn test_is_empty() {
let mut stack = Stack::new();
assert_eq!(stack.is_empty(), true);
stack.push(1);
assert_eq!(stack.is_empty(), false);
}
#[test]
fn test_push_pop1() {
let mut stack = Stack::new();
stack.push(1);
assert_eq!(stack.pop1(), Some(1));
assert_eq!(stack.is_empty(), true);
}
#[test]
fn test_pop() {
let mut stack = Stack::new();
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
stack.push(5);
stack.push(6);
assert_eq!(stack.pop(1), Some(vec![6]));
assert_eq!(stack.pop(2), Some(vec![5, 4]));
assert_eq!(stack.pop(4), None); // not enough items
assert_eq!(stack.pop(3), Some(vec![3, 2, 1]));
assert_eq!(stack.pop1(), None);
assert_eq!(stack.is_empty(), true);
}
}

View File

@ -0,0 +1,6 @@
//! An hypothetic WebAssembly runtime, represented as a set of enums,
//! types, and traits —basically this is the part a runtime should
//! take a look to use the `wasmer-interface-types` crate—.
pub mod structures;
pub mod values;

View File

@ -0,0 +1,159 @@
#![allow(missing_docs)]
use super::values::{InterfaceType, InterfaceValue};
use std::{cell::Cell, ops::Deref};
pub trait TypedIndex: Copy + Clone {
fn new(index: usize) -> Self;
fn index(&self) -> usize;
}
macro_rules! typed_index {
($type:ident) => {
#[derive(Copy, Clone)]
pub struct $type(usize);
impl TypedIndex for $type {
fn new(index: usize) -> Self {
Self(index)
}
fn index(&self) -> usize {
self.0
}
}
};
}
typed_index!(FunctionIndex);
typed_index!(LocalFunctionIndex);
typed_index!(ImportFunctionIndex);
pub trait LocalImportIndex {
type Local: TypedIndex;
type Import: TypedIndex;
}
impl LocalImportIndex for FunctionIndex {
type Local = LocalFunctionIndex;
type Import = ImportFunctionIndex;
}
pub trait Export {
fn inputs_cardinality(&self) -> usize;
fn outputs_cardinality(&self) -> usize;
fn inputs(&self) -> &[InterfaceType];
fn outputs(&self) -> &[InterfaceType];
fn call(&self, arguments: &[InterfaceValue]) -> Result<Vec<InterfaceValue>, ()>;
}
pub trait LocalImport {
fn inputs_cardinality(&self) -> usize;
fn outputs_cardinality(&self) -> usize;
fn inputs(&self) -> &[InterfaceType];
fn outputs(&self) -> &[InterfaceType];
fn call(&self, arguments: &[InterfaceValue]) -> Result<Vec<InterfaceValue>, ()>;
}
pub trait MemoryView: Deref<Target = [Cell<u8>]> {}
pub trait Memory<View>
where
View: MemoryView,
{
fn view(&self) -> View;
}
pub trait Instance<E, LI, M, MV>
where
E: Export,
LI: LocalImport,
M: Memory<MV>,
MV: MemoryView,
{
fn export(&self, export_name: &str) -> Option<&E>;
fn local_or_import<I: TypedIndex + LocalImportIndex>(&mut self, index: I) -> Option<&LI>;
fn memory(&self, index: usize) -> Option<&M>;
}
impl Export for () {
fn inputs_cardinality(&self) -> usize {
0
}
fn outputs_cardinality(&self) -> usize {
0
}
fn inputs(&self) -> &[InterfaceType] {
&[]
}
fn outputs(&self) -> &[InterfaceType] {
&[]
}
fn call(&self, _arguments: &[InterfaceValue]) -> Result<Vec<InterfaceValue>, ()> {
Err(())
}
}
impl LocalImport for () {
fn inputs_cardinality(&self) -> usize {
0
}
fn outputs_cardinality(&self) -> usize {
0
}
fn inputs(&self) -> &[InterfaceType] {
&[]
}
fn outputs(&self) -> &[InterfaceType] {
&[]
}
fn call(&self, _arguments: &[InterfaceValue]) -> Result<Vec<InterfaceValue>, ()> {
Err(())
}
}
pub(crate) struct EmptyMemoryView;
impl MemoryView for EmptyMemoryView {}
impl Deref for EmptyMemoryView {
type Target = [Cell<u8>];
fn deref(&self) -> &Self::Target {
&[]
}
}
impl Memory<EmptyMemoryView> for () {
fn view(&self) -> EmptyMemoryView {
EmptyMemoryView
}
}
impl<E, LI, M, MV> Instance<E, LI, M, MV> for ()
where
E: Export,
LI: LocalImport,
M: Memory<MV>,
MV: MemoryView,
{
fn export(&self, _export_name: &str) -> Option<&E> {
None
}
fn memory(&self, _: usize) -> Option<&M> {
None
}
fn local_or_import<I: TypedIndex + LocalImportIndex>(&mut self, _index: I) -> Option<&LI> {
None
}
}

View File

@ -0,0 +1,67 @@
#![allow(missing_docs)]
use std::convert::TryFrom;
pub use crate::ast::InterfaceType;
#[derive(Debug, Clone, PartialEq)]
pub enum InterfaceValue {
Int(isize),
Float(f64),
Any(isize),
String(String),
// Seq(…),
I32(i32),
I64(i64),
F32(f32),
F64(f64),
// AnyRef(…),
}
impl From<&InterfaceValue> for InterfaceType {
fn from(value: &InterfaceValue) -> Self {
match value {
InterfaceValue::Int(_) => Self::Int,
InterfaceValue::Float(_) => Self::Float,
InterfaceValue::Any(_) => Self::Any,
InterfaceValue::String(_) => Self::String,
InterfaceValue::I32(_) => Self::I32,
InterfaceValue::I64(_) => Self::I64,
InterfaceValue::F32(_) => Self::F32,
InterfaceValue::F64(_) => Self::F64,
}
}
}
impl Default for InterfaceValue {
fn default() -> Self {
Self::I32(0)
}
}
macro_rules! from_x_for_interface_value {
($native_type:ty, $value_variant:ident) => {
impl From<$native_type> for InterfaceValue {
fn from(n: $native_type) -> Self {
Self::$value_variant(n)
}
}
impl TryFrom<&InterfaceValue> for $native_type {
type Error = &'static str;
fn try_from(w: &InterfaceValue) -> Result<Self, Self::Error> {
match w {
InterfaceValue::$value_variant(n) => Ok(n.clone()),
_ => Err("Invalid cast."),
}
}
}
};
}
from_x_for_interface_value!(String, String);
from_x_for_interface_value!(i32, I32);
from_x_for_interface_value!(i64, I64);
from_x_for_interface_value!(f32, F32);
from_x_for_interface_value!(f64, F64);

View File

@ -0,0 +1,53 @@
//! This crate contains an implementation of [WebAssembly Interface
//! Types][wit] (abbreviated WIT). It is composed of 4 parts:
//!
//! 1. AST: To represent the WIT language as a tree (which is not
//! really abstract). This is the central representation of the
//! language.
//! 2. Decoders: To read the AST from a particular data representation;
//! for instance, `decoders::binary` reads the AST from a binary.
//! 3. Encoders: To write the AST into a particular format; for
//! instance, `encoders::wat` writes the AST into a string
//! representing WIT with its textual format.
//! 4. Interpreter: WIT defines a concept called Adapters. An adapter
//! contains a set of instructions. So, in more details, this
//! module contains:
//! * A very light and generic stack implementation, exposing only
//! the operations required by the interpreter,
//! * A stack-based interpreter, defined by:
//! * A compiler that transforms a set of instructions into a
//! set of executable instructions,
//! * A stack,
//! * A runtime that holds the “invocation inputs” (arguments
//! of the interpreter), the stack, and the WebAssembly
//! instance (which holds the exports, the imports, the
//! memories, the tables etc.),
//! * An hypothetic WebAssembly runtime, represented as a set of
//! enums, types, and traits —basically this is the part a
//! runtime should take a look to use the
//! `wasmer-interface-types` crate—.
//!
//!
//! [wit]: https://github.com/WebAssembly/interface-types
#![deny(
dead_code,
missing_docs,
nonstandard_style,
unreachable_patterns,
unused_imports,
unused_mut,
unused_unsafe,
unused_variables
)]
#![forbid(unsafe_code)]
#![doc(html_favicon_url = "https://wasmer.io/static/icons/favicon.ico")]
#![doc(html_logo_url = "https://github.com/wasmerio.png")]
pub mod ast;
#[macro_use]
mod macros;
pub mod decoders;
pub mod encoders;
pub mod interpreter;

View File

@ -0,0 +1,124 @@
/// This macro runs a parser, extracts the next input and the parser
/// output, and positions the next input on `$input`.
macro_rules! consume {
(($input:ident, $parser_output:ident) = $parser_expression:expr) => {
let (next_input, $parser_output) = $parser_expression;
$input = next_input;
};
}
/// This macro creates an executable instruction for the interpreter.
///
/// # Example
///
/// The following example creates a `foo` executable instruction,
/// which takes 2 arguments (`x` and `y`), and does something
/// mysterious by using the `interpreter::Runtime` API.
///
/// ```rust,ignore
/// executable_instruction!(
/// foo(x: u64, y: u64, instruction_name: String) -> _ {
/// // ^ output type is purposely blank
/// // ^^^^^^^^^^^^^^^^ the instruction name, for debugging purposes
/// // ^ the `y` argument
/// // ^ the `x` argument
///
/// // an executable instruction is a closure that takes a `Runtime` instance
/// move |runtime| -> _ {
/// // Do something.
///
/// Ok(())
/// }
/// );
/// ```
///
/// Check the existing executable instruction to get more examples.
macro_rules! executable_instruction {
($name:ident ( $($argument_name:ident: $argument_type:ty),* ) -> _ $implementation:block ) => {
use crate::interpreter::{ExecutableInstruction, wasm, stack::Stackable};
pub(crate) fn $name<Instance, Export, LocalImport, Memory, MemoryView>(
$($argument_name: $argument_type),*
) -> ExecutableInstruction<Instance, Export, LocalImport, Memory, MemoryView>
where
Export: wasm::structures::Export,
LocalImport: wasm::structures::LocalImport,
Memory: wasm::structures::Memory<MemoryView>,
MemoryView: wasm::structures::MemoryView,
Instance: wasm::structures::Instance<Export, LocalImport, Memory, MemoryView>,
{
Box::new($implementation)
}
};
}
#[cfg(test)]
macro_rules! test_executable_instruction {
(
$test_name:ident =
instructions: [ $($instructions:expr),* $(,)* ],
invocation_inputs: [ $($invocation_inputs:expr),* $(,)* ],
instance: $instance:expr,
stack: [ $($stack:expr),* $(,)* ]
$(,)*
) => {
#[test]
#[allow(non_snake_case, unused)]
fn $test_name() {
use crate::interpreter::{
instructions::tests::{Export, Instance, LocalImport, Memory, MemoryView},
stack::Stackable,
wasm::values::{InterfaceType, InterfaceValue},
Instruction, Interpreter,
};
use std::{cell::Cell, collections::HashMap, convert::TryInto};
let interpreter: Interpreter<Instance, Export, LocalImport, Memory, MemoryView> =
(&vec![$($instructions),*]).try_into().unwrap();
let invocation_inputs = vec![$($invocation_inputs),*];
let mut instance = $instance;
let run = interpreter.run(&invocation_inputs, &mut instance);
assert!(run.is_ok());
let stack = run.unwrap();
assert_eq!(stack.as_slice(), &[$($stack),*]);
}
};
(
$test_name:ident =
instructions: [ $($instructions:expr),* $(,)* ],
invocation_inputs: [ $($invocation_inputs:expr),* $(,)* ],
instance: $instance:expr,
error: $error:expr
$(,)*
) => {
#[test]
#[allow(non_snake_case, unused)]
fn $test_name() {
use crate::interpreter::{
instructions::tests::{Export, Instance, LocalImport, Memory, MemoryView},
stack::Stackable,
wasm::values::{InterfaceType, InterfaceValue},
Instruction, Interpreter,
};
use std::{cell::Cell, collections::HashMap, convert::TryInto};
let interpreter: Interpreter<Instance, Export, LocalImport, Memory, MemoryView> =
(&vec![$($instructions),*]).try_into().unwrap();
let invocation_inputs = vec![$($invocation_inputs),*];
let mut instance = $instance;
let run = interpreter.run(&invocation_inputs, &mut instance);
assert!(run.is_err());
let error = run.unwrap_err();
assert_eq!(error, String::from($error));
}
};
}

View File

@ -0,0 +1,46 @@
use wasmer_interface_types::{
ast::*, decoders::binary::parse, encoders::binary::ToBytes, interpreter::Instruction,
};
/// Tests an AST to binary, then binary to AST roundtrip.
#[test]
fn test_binary_encoding_decoding_roundtrip() {
let original_ast = Interfaces {
exports: vec![Export {
name: "ab",
input_types: vec![InterfaceType::I32],
output_types: vec![InterfaceType::I32],
}],
types: vec![Type::new(
"ab",
vec!["cd", "e"],
vec![InterfaceType::I32, InterfaceType::I32],
)],
imports: vec![Import {
namespace: "a",
name: "b",
input_types: vec![InterfaceType::I32],
output_types: vec![InterfaceType::I64],
}],
adapters: vec![Adapter::Import {
namespace: "a",
name: "b",
input_types: vec![InterfaceType::I32],
output_types: vec![InterfaceType::I32],
instructions: vec![Instruction::ArgumentGet { index: 1 }],
}],
forwards: vec![Forward { name: "a" }],
};
let mut binary = vec![];
original_ast
.to_bytes(&mut binary)
.expect("Failed to encode the AST.");
let (remainder, ast) = parse::<()>(binary.as_slice()).expect("Failed to decode the AST.");
assert!(remainder.is_empty());
assert_eq!(original_ast, ast);
}

View File

@ -58,6 +58,7 @@ lazy_static! {
"llvm-config".into(),
format!("llvm-config-{}", CRATE_VERSION.major),
format!("llvm-config-{}.{}", CRATE_VERSION.major, CRATE_VERSION.minor),
format!("llvm-config{}{}", CRATE_VERSION.major, CRATE_VERSION.minor),
]
};

View File

@ -59,6 +59,7 @@ enum WasmTrapType {
MemoryOutOfBounds = 2,
CallIndirectOOB = 3,
IllegalArithmetic = 4,
MisalignedAtomicAccess = 5,
Unknown,
};
@ -285,7 +286,7 @@ void module_delete(WasmModule *module) { delete module; }
unsafe_unwind(new BreakpointException(callback));
}
bool invoke_trampoline(trampoline_t trampoline, void *ctx, void *func,
bool cxx_invoke_trampoline(trampoline_t trampoline, void *ctx, void *func,
void *params, void *results, WasmTrapType *trap_out,
box_any_t *user_error, void *invoke_env) noexcept {
try {

View File

@ -24,13 +24,13 @@ use std::{
use wasmer_runtime_core::{
backend::{
sys::{Memory, Protect},
CacheGen, RunnableModule,
CacheGen, ExceptionCode, RunnableModule,
},
cache::Error as CacheError,
module::ModuleInfo,
state::ModuleStateMap,
structures::TypedIndex,
typed_func::{Trampoline, Wasm, WasmTrapInfo},
typed_func::{Trampoline, Wasm},
types::{LocalFuncIndex, SigIndex},
vm, vmcalls,
};
@ -59,18 +59,55 @@ extern "C" {
fn throw_any(data: *mut dyn Any) -> !;
#[allow(improper_ctypes)]
fn invoke_trampoline(
fn cxx_invoke_trampoline(
trampoline: Trampoline,
vmctx_ptr: *mut vm::Ctx,
func_ptr: NonNull<vm::Func>,
params: *const u64,
results: *mut u64,
trap_out: *mut WasmTrapInfo,
user_error: *mut Option<Box<dyn Any + Send>>,
trap_out: *mut i32,
error_out: *mut Option<Box<dyn Any + Send>>,
invoke_env: Option<NonNull<c_void>>,
) -> bool;
}
/// `invoke_trampoline` is a wrapper around `cxx_invoke_trampoline`, for fixing up the obsoleted
/// `trap_out` in the C++ part.
unsafe extern "C" fn invoke_trampoline(
trampoline: Trampoline,
vmctx_ptr: *mut vm::Ctx,
func_ptr: NonNull<vm::Func>,
params: *const u64,
results: *mut u64,
error_out: *mut Option<Box<dyn Any + Send>>,
invoke_env: Option<NonNull<c_void>>,
) -> bool {
let mut trap_out: i32 = -1;
let ret = cxx_invoke_trampoline(
trampoline,
vmctx_ptr,
func_ptr,
params,
results,
&mut trap_out,
error_out,
invoke_env,
);
// Translate trap code if an error occurred.
if !ret && (*error_out).is_none() && trap_out != -1 {
*error_out = Some(Box::new(match trap_out {
0 => ExceptionCode::Unreachable,
1 => ExceptionCode::IncorrectCallIndirectSignature,
2 => ExceptionCode::MemoryOutOfBounds,
3 => ExceptionCode::CallIndirectOOB,
4 => ExceptionCode::IllegalArithmetic,
5 => ExceptionCode::MisalignedAtomicAccess,
_ => return ret,
}));
}
ret
}
static SIGNAL_HANDLER_INSTALLED: Once = Once::new();
fn get_callbacks() -> Callbacks {
@ -206,7 +243,10 @@ impl LLVMBackend {
let buffer = Arc::new(Buffer::LlvmMemory(memory_buffer));
#[cfg(all(any(target_os = "linux", target_os = "macos"), target_arch = "x86_64"))]
#[cfg(all(
any(target_os = "freebsd", target_os = "linux", target_os = "macos"),
target_arch = "x86_64"
))]
{
use super::stackmap::{self, StkMapRecord, StkSizeRecord};
use std::collections::BTreeMap;

View File

@ -1,3 +1,9 @@
//! Code for dealing with [LLVM][llvm-intrinsics] and VM intrinsics.
//!
//! VM intrinsics are used to interact with the host VM.
//!
//! [llvm-intrinsics]: https://llvm.org/docs/LangRef.html#intrinsic-functions
use inkwell::{
attributes::{Attribute, AttributeLoc},
builder::Builder,
@ -34,6 +40,7 @@ fn type_to_llvm_ptr<'ctx>(intrinsics: &Intrinsics<'ctx>, ty: Type) -> PointerTyp
}
}
/// Struct containing LLVM and VM intrinsics.
pub struct Intrinsics<'ctx> {
pub ctlz_i32: FunctionValue<'ctx>,
pub ctlz_i64: FunctionValue<'ctx>,
@ -151,6 +158,7 @@ pub struct Intrinsics<'ctx> {
}
impl<'ctx> Intrinsics<'ctx> {
/// Create an [`Intrinsics`] for the given [`Context`].
pub fn declare(module: &Module<'ctx>, context: &'ctx Context) -> Self {
let void_ty = context.void_type();
let i1_ty = context.bool_type();

View File

@ -53,7 +53,10 @@ pub enum StackmapEntryKind {
}
impl StackmapEntry {
#[cfg(all(any(target_os = "linux", target_os = "macos"), target_arch = "x86_64"))]
#[cfg(all(
any(target_os = "freebsd", target_os = "linux", target_os = "macos"),
target_arch = "x86_64"
))]
pub fn populate_msm(
&self,
module_info: &ModuleInfo,

View File

@ -165,12 +165,10 @@ mod tests {
}
let err = result.unwrap_err();
match err {
RuntimeError::Error { data } => {
assert!(data.downcast_ref::<ExecutionLimitExceededError>().is_some());
}
_ => unreachable!(),
}
assert!(err
.0
.downcast_ref::<ExecutionLimitExceededError>()
.is_some());
// verify it used the correct number of points
assert_eq!(get_points_used(&instance), 109); // Used points will be slightly more than `limit` because of the way we do gas checking.

View File

@ -15,7 +15,7 @@ auto-generated documentation.
Since the Wasmer runtime is written in Rust, the C and C++ API are
designed to work hand-in-hand with its shared library. The C and C++
header files, namely [`wasmer.h`] and [`wasmer.hh`] are documented
header files, namely [`wasmer.h`] and `wasmer.hh` are documented
here. Their source code can be found in the source tree of this
crate. They are automatically generated, and always up-to-date in this
repository. The C and C++ header files along with the runtime shared
@ -23,7 +23,6 @@ libraries (`.so`, `.dylib`, `.dll`) can also be downloaded in the
Wasmer [release page].
[`wasmer.h`]: ./wasmer_8h.html
[`wasmer.hh`]: ./wasmer_8hh.html
[release page]: https://github.com/wasmerio/wasmer/releases
Here is a simple example to use the C API:

View File

@ -118,7 +118,6 @@ WARN_LOGFILE =
#---------------------------------------------------------------------------
INPUT = doc/index.md
INPUT += wasmer.h
INPUT += wasmer.hh
INPUT_ENCODING = UTF-8
FILE_PATTERNS = *.h \
*.hh

View File

@ -1,11 +1,12 @@
//! Read runtime errors.
use libc::{c_char, c_int};
use std::cell::RefCell;
use std::error::Error;
use std::fmt::{self, Display, Formatter};
use std::ptr;
use std::slice;
use std::{
cell::RefCell,
error::Error,
fmt::{self, Display, Formatter},
ptr, slice,
};
thread_local! {
static LAST_ERROR: RefCell<Option<Box<dyn Error>>> = RefCell::new(None);

View File

@ -107,7 +107,7 @@ pub mod table;
pub mod trampoline;
pub mod value;
/// The `wasmer_result_t` struct is a type that represents either a
/// The `wasmer_result_t` enum is a type that represents either a
/// success, or a failure.
#[allow(non_camel_case_types)]
#[repr(C)]
@ -119,15 +119,26 @@ pub enum wasmer_result_t {
WASMER_ERROR = 2,
}
/// The `wasmer_limits_t` struct is a type that describes a memory
/// options. See the `wasmer_memory_t` struct or the
/// `wasmer_memory_new()` function to get more information.
#[repr(C)]
pub struct wasmer_limits_t {
/// The minimum number of allowed pages.
pub min: u32,
/// The maximum number of allowed pages.
pub max: wasmer_limit_option_t,
}
/// The `wasmer_limit_option_t` struct represents an optional limit
/// for `wasmer_limits_t`.
#[repr(C)]
pub struct wasmer_limit_option_t {
/// Whether the limit is set.
pub has_some: bool,
/// The limit value.
pub some: u32,
}

View File

@ -1,26 +1,66 @@
//! Create, read, write, grow, destroy memory of an instance.
use crate::{error::update_last_error, error::CApiError, wasmer_limits_t, wasmer_result_t};
use std::cell::Cell;
use crate::{
error::{update_last_error, CApiError},
wasmer_limits_t, wasmer_result_t,
};
use std::{cell::Cell, ptr};
use wasmer_runtime::Memory;
use wasmer_runtime_core::{
types::MemoryDescriptor,
units::{Bytes, Pages},
};
/// Opaque pointer to a `wasmer_runtime::Memory` value in Rust.
///
/// A `wasmer_runtime::Memory` represents a WebAssembly memory. It is
/// possible to create one with `wasmer_memory_new()` and pass it as
/// imports of an instance, or to read it from exports of an instance
/// with `wasmer_export_to_memory()`.
#[repr(C)]
#[derive(Clone)]
pub struct wasmer_memory_t;
/// Creates a new Memory for the given descriptor and initializes the given
/// pointer to pointer to a pointer to the new memory.
/// Creates a new empty WebAssembly memory for the given descriptor.
///
/// The caller owns the object and should call `wasmer_memory_destroy` to free it.
/// The result is stored in the first argument `memory` if successful,
/// i.e. when the function returns
/// `wasmer_result_t::WASMER_OK`. Otherwise,
/// `wasmer_result_t::WASMER_ERROR` is returned, and
/// `wasmer_last_error_length()` with `wasmer_last_error_message()`
/// must be used to read the error message.
///
/// Returns `wasmer_result_t::WASMER_OK` upon success.
/// The caller owns the memory and is responsible to free it with
/// `wasmer_memory_destroy()`.
///
/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length`
/// and `wasmer_last_error_message` to get an error message.
/// Example:
///
/// ```c
/// // 1. The memory object.
/// wasmer_memory_t *memory = NULL;
///
/// // 2. The memory descriptor.
/// wasmer_limits_t memory_descriptor = {
/// .min = 10,
/// .max = {
/// .has_some = true,
/// .some = 15,
/// },
/// };
///
/// // 3. Initialize the memory.
/// wasmer_result_t result = wasmer_memory_new(&memory, memory_descriptor);
///
/// if (result != WASMER_OK) {
/// int error_length = wasmer_last_error_length();
/// char *error = malloc(error_length);
/// wasmer_last_error_message(error, error_length);
/// // Do something with `error`…
/// }
///
/// // 4. Free the memory!
/// wasmer_memory_destroy(memory);
/// ```
#[no_mangle]
pub unsafe extern "C" fn wasmer_memory_new(
memory: *mut *mut wasmer_memory_t,
@ -53,53 +93,139 @@ pub unsafe extern "C" fn wasmer_memory_new(
wasmer_result_t::WASMER_OK
}
/// Grows a Memory by the given number of pages.
/// Grows a memory by the given number of pages (of 65Kb each).
///
/// Returns `wasmer_result_t::WASMER_OK` upon success.
/// The functions return `wasmer_result_t::WASMER_OK` upon success,
/// `wasmer_result_t::WASMER_ERROR` otherwise. Use
/// `wasmer_last_error_length()` with `wasmer_last_error_message()` to
/// read the error message.
///
/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length`
/// and `wasmer_last_error_message` to get an error message.
/// Example:
///
/// ```c
/// wasmer_result_t result = wasmer_memory_grow(memory, 10);
///
/// if (result != WASMER_OK) {
/// // …
/// }
/// ```
#[allow(clippy::cast_ptr_alignment)]
#[no_mangle]
pub extern "C" fn wasmer_memory_grow(memory: *mut wasmer_memory_t, delta: u32) -> wasmer_result_t {
if memory.is_null() {
update_last_error(CApiError {
msg: "`memory` is NULL.".to_string(),
});
return wasmer_result_t::WASMER_ERROR;
}
let memory = unsafe { &*(memory as *mut Memory) };
let delta_result = memory.grow(Pages(delta));
match delta_result {
Ok(_) => wasmer_result_t::WASMER_OK,
Err(grow_error) => {
update_last_error(grow_error);
wasmer_result_t::WASMER_ERROR
}
}
}
/// Returns the current length in pages of the given memory
/// Reads the current length (in pages) of the given memory.
///
/// The function returns zero if `memory` is a null pointer.
///
/// Example:
///
/// ```c
/// uint32_t memory_length = wasmer_memory_length(memory);
///
/// printf("Memory pages length: %d\n", memory_length);
/// ```
#[allow(clippy::cast_ptr_alignment)]
#[no_mangle]
pub extern "C" fn wasmer_memory_length(memory: *const wasmer_memory_t) -> u32 {
if memory.is_null() {
return 0;
}
let memory = unsafe { &*(memory as *const Memory) };
let Pages(len) = memory.size();
len
let Pages(length) = memory.size();
length
}
/// Gets the start pointer to the bytes within a Memory
/// Gets a pointer to the beginning of the contiguous memory data
/// bytes.
///
/// The function returns `NULL` if `memory` is a null pointer.
///
/// Note that when the memory grows, it can be reallocated, and thus
/// the returned pointer can be invalidated.
///
/// Example:
///
/// ```c
/// uint8_t *memory_data = wasmer_memory_data(memory);
/// char *str = (char*) malloc(sizeof(char) * 7);
///
/// for (uint32_t nth = 0; nth < 7; ++nth) {
/// str[nth] = (char) memory_data[nth];
/// }
/// ```
#[allow(clippy::cast_ptr_alignment)]
#[no_mangle]
pub extern "C" fn wasmer_memory_data(mem: *const wasmer_memory_t) -> *mut u8 {
let memory = unsafe { &*(mem as *const Memory) };
pub extern "C" fn wasmer_memory_data(memory: *const wasmer_memory_t) -> *mut u8 {
if memory.is_null() {
return ptr::null_mut();
}
let memory = unsafe { &*(memory as *const Memory) };
memory.view::<u8>()[..].as_ptr() as *mut Cell<u8> as *mut u8
}
/// Gets the size in bytes of a Memory
/// Gets the size in bytes of the memory data.
///
/// This function returns 0 if `memory` is a null pointer.
///
/// Example:
///
/// ```c
/// uint32_t memory_data_length = wasmer_memory_data_length(memory);
/// ```
#[allow(clippy::cast_ptr_alignment)]
#[no_mangle]
pub extern "C" fn wasmer_memory_data_length(mem: *mut wasmer_memory_t) -> u32 {
let memory = mem as *mut Memory;
let Bytes(len) = unsafe { (*memory).size().bytes() };
len as u32
pub extern "C" fn wasmer_memory_data_length(memory: *mut wasmer_memory_t) -> u32 {
if memory.is_null() {
return 0;
}
let memory = unsafe { &*(memory as *const Memory) };
let Bytes(length) = memory.size().bytes();
length as u32
}
/// Frees memory for the given Memory
/// Frees memory for the given `wasmer_memory_t`.
///
/// Check the `wasmer_memory_new()` function to get a complete
/// example.
///
/// If `memory` is a null pointer, this function does nothing.
///
/// Example:
///
/// ```c
/// // Get a memory.
/// wasmer_memory_t *memory = NULL;
/// wasmer_result_t result = wasmer_memory_new(&memory, memory_descriptor);
///
/// // Destroy the memory.
/// wasmer_memory_destroy(memory);
/// ```
#[allow(clippy::cast_ptr_alignment)]
#[no_mangle]
pub extern "C" fn wasmer_memory_destroy(memory: *mut wasmer_memory_t) {

View File

@ -3,16 +3,32 @@
use wasmer_runtime::Value;
use wasmer_runtime_core::types::Type;
/// Represents all possibles WebAssembly value types.
///
/// See `wasmer_value_t` to get a complete example.
#[allow(non_camel_case_types)]
#[repr(u32)]
#[derive(Clone)]
pub enum wasmer_value_tag {
/// Represents the `i32` WebAssembly type.
WASM_I32,
/// Represents the `i64` WebAssembly type.
WASM_I64,
/// Represents the `f32` WebAssembly type.
WASM_F32,
/// Represents the `f64` WebAssembly type.
WASM_F64,
}
/// Represents a WebAssembly value.
///
/// This is a [Rust union][rust-union], which is equivalent to the C
/// union. See `wasmer_value_t` to get a complete example.
///
/// [rust-union]: https://doc.rust-lang.org/reference/items/unions.html
#[repr(C)]
#[derive(Clone, Copy)]
#[allow(non_snake_case)]
@ -23,10 +39,33 @@ pub union wasmer_value {
pub F64: f64,
}
/// Represents a WebAssembly type and value pair,
/// i.e. `wasmer_value_tag` and `wasmer_value`. Since the latter is an
/// union, it's the safe way to read or write a WebAssembly value in
/// C.
///
/// Example:
///
/// ```c
/// // Create a WebAssembly value.
/// wasmer_value_t wasm_value = {
/// .tag = WASM_I32,
/// .value.I32 = 42,
/// };
///
/// // Read a WebAssembly value.
/// if (wasm_value.tag == WASM_I32) {
/// int32_t x = wasm_value.value.I32;
/// // …
/// }
/// ```
#[repr(C)]
#[derive(Clone)]
pub struct wasmer_value_t {
/// The value type.
pub tag: wasmer_value_tag,
/// The value.
pub value: wasmer_value,
}

View File

@ -7,12 +7,13 @@
int main()
{
wasmer_memory_t *memory = NULL;
wasmer_limits_t descriptor;
descriptor.min = 10;
wasmer_limit_option_t max;
max.has_some = true;
max.some = 15;
descriptor.max = max;
wasmer_limits_t descriptor = {
.min = 10,
.max = {
.has_some = true,
.some = 15,
},
};
wasmer_result_t memory_result = wasmer_memory_new(&memory, descriptor);
printf("Memory result: %d\n", memory_result);
assert(memory_result == WASMER_OK);

View File

@ -86,7 +86,7 @@ enum wasmer_import_export_kind {
typedef uint32_t wasmer_import_export_kind;
/**
* The `wasmer_result_t` struct is a type that represents either a
* The `wasmer_result_t` enum is a type that represents either a
* success, or a failure.
*/
typedef enum {
@ -100,10 +100,27 @@ typedef enum {
WASMER_ERROR = 2,
} wasmer_result_t;
/**
* Represents all possibles WebAssembly value types.
*
* See `wasmer_value_t` to get a complete example.
*/
enum wasmer_value_tag {
/**
* Represents the `i32` WebAssembly type.
*/
WASM_I32,
/**
* Represents the `i64` WebAssembly type.
*/
WASM_I64,
/**
* Represents the `f32` WebAssembly type.
*/
WASM_F32,
/**
* Represents the `f64` WebAssembly type.
*/
WASM_F64,
};
typedef uint32_t wasmer_value_tag;
@ -162,6 +179,14 @@ typedef struct {
} wasmer_export_func_t;
/**
* Represents a WebAssembly value.
*
* This is a [Rust union][rust-union], which is equivalent to the C
* union. See `wasmer_value_t` to get a complete example.
*
* [rust-union]: https://doc.rust-lang.org/reference/items/unions.html
*/
typedef union {
int32_t I32;
int64_t I64;
@ -169,8 +194,36 @@ typedef union {
double F64;
} wasmer_value;
/**
* Represents a WebAssembly type and value pair,
* i.e. `wasmer_value_tag` and `wasmer_value`. Since the latter is an
* union, it's the safe way to read or write a WebAssembly value in
* C.
*
* Example:
*
* ```c
* // Create a WebAssembly value.
* wasmer_value_t wasm_value = {
* .tag = WASM_I32,
* .value.I32 = 42,
* };
*
* // Read a WebAssembly value.
* if (wasm_value.tag == WASM_I32) {
* int32_t x = wasm_value.value.I32;
* // …
* }
* ```
*/
typedef struct {
/**
* The value type.
*/
wasmer_value_tag tag;
/**
* The value.
*/
wasmer_value value;
} wasmer_value_t;
@ -181,6 +234,14 @@ typedef struct {
} wasmer_export_t;
/**
* Opaque pointer to a `wasmer_runtime::Memory` value in Rust.
*
* A `wasmer_runtime::Memory` represents a WebAssembly memory. It is
* possible to create one with `wasmer_memory_new()` and pass it as
* imports of an instance, or to read it from exports of an instance
* with `wasmer_export_to_memory()`.
*/
typedef struct {
} wasmer_memory_t;
@ -273,13 +334,34 @@ typedef struct {
} wasmer_instance_context_t;
/**
* The `wasmer_limit_option_t` struct repreesents an optional limit
* for `wasmer_limits_t`.
*/
typedef struct {
/**
* Whether the limit is set.
*/
bool has_some;
/**
* The limit value.
*/
uint32_t some;
} wasmer_limit_option_t;
/**
* The `wasmer_limits_t` struct is a type that describes a memory
* options. See the `wasmer_memory_t` struct or the
* `wasmer_memory_new()` function to get more information.
*/
typedef struct {
/**
* The minimum number of allowed pages.
*/
uint32_t min;
/**
* The maximum number of allowed pages.
*/
wasmer_limit_option_t max;
} wasmer_limits_t;
@ -1057,45 +1139,137 @@ int wasmer_last_error_length(void);
int wasmer_last_error_message(char *buffer, int length);
/**
* Gets the start pointer to the bytes within a Memory
* Gets a pointer to the beginning of the contiguous memory data
* bytes.
*
* The function returns `NULL` if `memory` is a null pointer.
*
* Note that when the memory grows, it can be reallocated, and thus
* the returned pointer can be invalidated.
*
* Example:
*
* ```c
* uint8_t *memory_data = wasmer_memory_data(memory);
* char *str = (char*) malloc(sizeof(char) * 7);
*
* for (uint32_t nth = 0; nth < 7; ++nth) {
* str[nth] = (char) memory_data[nth];
* }
* ```
*/
uint8_t *wasmer_memory_data(const wasmer_memory_t *mem);
uint8_t *wasmer_memory_data(const wasmer_memory_t *memory);
/**
* Gets the size in bytes of a Memory
* Gets the size in bytes of the memory data.
*
* This function returns 0 if `memory` is a null pointer.
*
* Example:
*
* ```c
* uint32_t memory_data_length = wasmer_memory_data_length(memory);
* ```
*/
uint32_t wasmer_memory_data_length(wasmer_memory_t *mem);
uint32_t wasmer_memory_data_length(wasmer_memory_t *memory);
/**
* Frees memory for the given Memory
* Frees memory for the given `wasmer_memory_t`.
*
* Check the `wasmer_memory_new()` function to get a complete
* example.
*
* If `memory` is a null pointer, this function does nothing.
*
* Example:
*
* ```c
* // Get a memory.
* wasmer_memory_t *memory = NULL;
* wasmer_result_t result = wasmer_memory_new(&memory, memory_descriptor);
*
* // Destroy the memory.
* wasmer_memory_destroy(memory);
* ```
*/
void wasmer_memory_destroy(wasmer_memory_t *memory);
/**
* Grows a Memory by the given number of pages.
* Grows a memory by the given number of pages (of 65Kb each).
*
* Returns `wasmer_result_t::WASMER_OK` upon success.
* The functions return `wasmer_result_t::WASMER_OK` upon success,
* `wasmer_result_t::WASMER_ERROR` otherwise. Use
* `wasmer_last_error_length()` with `wasmer_last_error_message()` to
* read the error message.
*
* Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length`
* and `wasmer_last_error_message` to get an error message.
* Example:
*
* ```c
* wasmer_result_t result = wasmer_memory_grow(memory, 10);
*
* if (result != WASMER_OK) {
* // …
* }
* ```
*/
wasmer_result_t wasmer_memory_grow(wasmer_memory_t *memory, uint32_t delta);
/**
* Returns the current length in pages of the given memory
* Reads the current length (in pages) of the given memory.
*
* The function returns zero if `memory` is a null pointer.
*
* Example:
*
* ```c
* uint32_t memory_length = wasmer_memory_length(memory);
*
* printf("Memory pages length: %d\n", memory_length);
* ```
*/
uint32_t wasmer_memory_length(const wasmer_memory_t *memory);
/**
* Creates a new Memory for the given descriptor and initializes the given
* pointer to pointer to a pointer to the new memory.
* Creates a new empty WebAssembly memory for the given descriptor.
*
* The caller owns the object and should call `wasmer_memory_destroy` to free it.
* The result is stored in the first argument `memory` if successful,
* i.e. when the function returns
* `wasmer_result_t::WASMER_OK`. Otherwise,
* `wasmer_result_t::WASMER_ERROR` is returned, and
* `wasmer_last_error_length()` with `wasmer_last_error_message()`
* must be used to read the error message.
*
* Returns `wasmer_result_t::WASMER_OK` upon success.
* The caller owns the memory and is responsible to free it with
* `wasmer_memory_destroy()`.
*
* Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length`
* and `wasmer_last_error_message` to get an error message.
* Example:
*
* ```c
* // 1. The memory object.
* wasmer_memory_t *memory = NULL;
*
* // 2. The memory descriptor.
* wasmer_limits_t memory_descriptor = {
* .min = 10,
* .max = {
* .has_some = true,
* .some = 15,
* },
* };
*
* // 3. Initialize the memory.
* wasmer_result_t result = wasmer_memory_new(&memory, memory_descriptor);
*
* if (result != WASMER_OK) {
* int error_length = wasmer_last_error_length();
* char *error = malloc(error_length);
* wasmer_last_error_message(error, error_length);
* // Do something with `error`…
* }
*
* // 4. Free the memory!
* wasmer_memory_destroy(memory);
* ```
*/
wasmer_result_t wasmer_memory_new(wasmer_memory_t **memory, wasmer_limits_t limits);

View File

@ -65,7 +65,7 @@ enum class wasmer_import_export_kind : uint32_t {
WASM_TABLE = 3,
};
/// The `wasmer_result_t` struct is a type that represents either a
/// The `wasmer_result_t` enum is a type that represents either a
/// success, or a failure.
enum class wasmer_result_t {
/// Represents a success.
@ -74,10 +74,17 @@ enum class wasmer_result_t {
WASMER_ERROR = 2,
};
/// Represents all possibles WebAssembly value types.
///
/// See `wasmer_value_t` to get a complete example.
enum class wasmer_value_tag : uint32_t {
/// Represents the `i32` WebAssembly type.
WASM_I32,
/// Represents the `i64` WebAssembly type.
WASM_I64,
/// Represents the `f32` WebAssembly type.
WASM_F32,
/// Represents the `f64` WebAssembly type.
WASM_F64,
};
@ -125,6 +132,12 @@ struct wasmer_export_func_t {
};
/// Represents a WebAssembly value.
///
/// This is a [Rust union][rust-union], which is equivalent to the C
/// union. See `wasmer_value_t` to get a complete example.
///
/// [rust-union]: https://doc.rust-lang.org/reference/items/unions.html
union wasmer_value {
int32_t I32;
int64_t I64;
@ -132,8 +145,30 @@ union wasmer_value {
double F64;
};
/// Represents a WebAssembly type and value pair,
/// i.e. `wasmer_value_tag` and `wasmer_value`. Since the latter is an
/// union, it's the safe way to read or write a WebAssembly value in
/// C.
///
/// Example:
///
/// ```c
/// // Create a WebAssembly value.
/// wasmer_value_t wasm_value = {
/// .tag = WASM_I32,
/// .value.I32 = 42,
/// };
///
/// // Read a WebAssembly value.
/// if (wasm_value.tag == WASM_I32) {
/// int32_t x = wasm_value.value.I32;
/// // …
/// }
/// ```
struct wasmer_value_t {
/// The value type.
wasmer_value_tag tag;
/// The value.
wasmer_value value;
};
@ -142,6 +177,12 @@ struct wasmer_export_t {
};
/// Opaque pointer to a `wasmer_runtime::Memory` value in Rust.
///
/// A `wasmer_runtime::Memory` represents a WebAssembly memory. It is
/// possible to create one with `wasmer_memory_new()` and pass it as
/// imports of an instance, or to read it from exports of an instance
/// with `wasmer_export_to_memory()`.
struct wasmer_memory_t {
};
@ -228,13 +269,22 @@ struct wasmer_instance_context_t {
};
/// The `wasmer_limit_option_t` struct repreesents an optional limit
/// for `wasmer_limits_t`.
struct wasmer_limit_option_t {
/// Whether the limit is set.
bool has_some;
/// The limit value.
uint32_t some;
};
/// The `wasmer_limits_t` struct is a type that describes a memory
/// options. See the `wasmer_memory_t` struct or the
/// `wasmer_memory_new()` function to get more information.
struct wasmer_limits_t {
/// The minimum number of allowed pages.
uint32_t min;
/// The maximum number of allowed pages.
wasmer_limit_option_t max;
};
@ -885,35 +935,127 @@ int wasmer_last_error_length();
/// ```
int wasmer_last_error_message(char *buffer, int length);
/// Gets the start pointer to the bytes within a Memory
uint8_t *wasmer_memory_data(const wasmer_memory_t *mem);
/// Gets a pointer to the beginning of the contiguous memory data
/// bytes.
///
/// The function returns `NULL` if `memory` is a null pointer.
///
/// Note that when the memory grows, it can be reallocated, and thus
/// the returned pointer can be invalidated.
///
/// Example:
///
/// ```c
/// uint8_t *memory_data = wasmer_memory_data(memory);
/// char *str = (char*) malloc(sizeof(char) * 7);
///
/// for (uint32_t nth = 0; nth < 7; ++nth) {
/// str[nth] = (char) memory_data[nth];
/// }
/// ```
uint8_t *wasmer_memory_data(const wasmer_memory_t *memory);
/// Gets the size in bytes of a Memory
uint32_t wasmer_memory_data_length(wasmer_memory_t *mem);
/// Gets the size in bytes of the memory data.
///
/// This function returns 0 if `memory` is a null pointer.
///
/// Example:
///
/// ```c
/// uint32_t memory_data_length = wasmer_memory_data_length(memory);
/// ```
uint32_t wasmer_memory_data_length(wasmer_memory_t *memory);
/// Frees memory for the given Memory
/// Frees memory for the given `wasmer_memory_t`.
///
/// Check the `wasmer_memory_new()` function to get a complete
/// example.
///
/// If `memory` is a null pointer, this function does nothing.
///
/// Example:
///
/// ```c
/// // Get a memory.
/// wasmer_memory_t *memory = NULL;
/// wasmer_result_t result = wasmer_memory_new(&memory, memory_descriptor);
///
/// // Destroy the memory.
/// wasmer_memory_destroy(memory);
/// ```
void wasmer_memory_destroy(wasmer_memory_t *memory);
/// Grows a Memory by the given number of pages.
/// Grows a memory by the given number of pages (of 65Kb each).
///
/// Returns `wasmer_result_t::WASMER_OK` upon success.
/// The functions return `wasmer_result_t::WASMER_OK` upon success,
/// `wasmer_result_t::WASMER_ERROR` otherwise. Use
/// `wasmer_last_error_length()` with `wasmer_last_error_message()` to
/// read the error message.
///
/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length`
/// and `wasmer_last_error_message` to get an error message.
/// Example:
///
/// ```c
/// wasmer_result_t result = wasmer_memory_grow(memory, 10);
///
/// if (result != WASMER_OK) {
/// // …
/// }
/// ```
wasmer_result_t wasmer_memory_grow(wasmer_memory_t *memory, uint32_t delta);
/// Returns the current length in pages of the given memory
/// Reads the current length (in pages) of the given memory.
///
/// The function returns zero if `memory` is a null pointer.
///
/// Example:
///
/// ```c
/// uint32_t memory_length = wasmer_memory_length(memory);
///
/// printf("Memory pages length: %d\n", memory_length);
/// ```
uint32_t wasmer_memory_length(const wasmer_memory_t *memory);
/// Creates a new Memory for the given descriptor and initializes the given
/// pointer to pointer to a pointer to the new memory.
/// Creates a new empty WebAssembly memory for the given descriptor.
///
/// The caller owns the object and should call `wasmer_memory_destroy` to free it.
/// The result is stored in the first argument `memory` if successful,
/// i.e. when the function returns
/// `wasmer_result_t::WASMER_OK`. Otherwise,
/// `wasmer_result_t::WASMER_ERROR` is returned, and
/// `wasmer_last_error_length()` with `wasmer_last_error_message()`
/// must be used to read the error message.
///
/// Returns `wasmer_result_t::WASMER_OK` upon success.
/// The caller owns the memory and is responsible to free it with
/// `wasmer_memory_destroy()`.
///
/// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length`
/// and `wasmer_last_error_message` to get an error message.
/// Example:
///
/// ```c
/// // 1. The memory object.
/// wasmer_memory_t *memory = NULL;
///
/// // 2. The memory descriptor.
/// wasmer_limits_t memory_descriptor = {
/// .min = 10,
/// .max = {
/// .has_some = true,
/// .some = 15,
/// },
/// };
///
/// // 3. Initialize the memory.
/// wasmer_result_t result = wasmer_memory_new(&memory, memory_descriptor);
///
/// if (result != WASMER_OK) {
/// int error_length = wasmer_last_error_length();
/// char *error = malloc(error_length);
/// wasmer_last_error_message(error, error_length);
/// // Do something with `error`…
/// }
///
/// // 4. Free the memory!
/// wasmer_memory_destroy(memory);
/// ```
wasmer_result_t wasmer_memory_new(wasmer_memory_t **memory, wasmer_limits_t limits);
/// Deserialize the given serialized module.

View File

@ -0,0 +1,21 @@
use wasmer_runtime_core::{compile_with, imports};
use wasmer_runtime_core_tests::{get_compiler, wat2wasm};
#[test]
fn exception_handling_works() {
const MODULE: &str = r#"
(module
(func (export "throw_trap")
unreachable
))
"#;
let wasm_binary = wat2wasm(MODULE.as_bytes()).expect("WAST not valid or malformed");
let module = compile_with(&wasm_binary, &get_compiler()).unwrap();
let imports = imports! {};
for _ in 0..2 {
let instance = module.instantiate(&imports).unwrap();
assert!(instance.call("throw_trap", &[]).is_err());
}
}

View File

@ -16,12 +16,7 @@ macro_rules! call_and_assert {
expected_value,
concat!("Expected right when calling `", stringify!($function), "`.")
),
(
Err(RuntimeError::Error { data }),
Err(RuntimeError::Error {
data: expected_data,
}),
) => {
(Err(RuntimeError(data)), Err(RuntimeError(expected_data))) => {
if let (Some(data), Some(expected_data)) = (
data.downcast_ref::<&str>(),
expected_data.downcast_ref::<&str>(),
@ -260,35 +255,25 @@ test!(
test!(
test_fn_trap,
function_fn_trap,
Err(RuntimeError::Error {
data: Box::new(format!("foo {}", 2))
})
Err(RuntimeError(Box::new(format!("foo {}", 2))))
);
test!(
test_closure_trap,
function_closure_trap,
Err(RuntimeError::Error {
data: Box::new(format!("bar {}", 2))
})
Err(RuntimeError(Box::new(format!("bar {}", 2))))
);
test!(
test_fn_trap_with_vmctx,
function_fn_trap_with_vmctx,
Err(RuntimeError::Error {
data: Box::new(format!("baz {}", 2 + SHIFT))
})
Err(RuntimeError(Box::new(format!("baz {}", 2 + SHIFT))))
);
test!(
test_closure_trap_with_vmctx,
function_closure_trap_with_vmctx,
Err(RuntimeError::Error {
data: Box::new(format!("qux {}", 2 + SHIFT))
})
Err(RuntimeError(Box::new(format!("qux {}", 2 + SHIFT))))
);
test!(
test_closure_trap_with_vmctx_and_env,
function_closure_trap_with_vmctx_and_env,
Err(RuntimeError::Error {
data: Box::new(format!("! {}", 2 + shift + SHIFT))
})
Err(RuntimeError(Box::new(format!("! {}", 2 + shift + SHIFT))))
);

View File

@ -32,6 +32,11 @@ fn main() {
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
match (target_os.as_str(), target_arch.as_str()) {
("freebsd", "x86_64") => {
cc::Build::new()
.file("image-loading-freebsd-x86-64.s")
.compile("image-loading");
}
("linux", "x86_64") => {
cc::Build::new()
.file("image-loading-linux-x86-64.s")

View File

@ -0,0 +1,127 @@
# NOTE: Keep this consistent with `fault.rs`.
.globl run_on_alternative_stack
run_on_alternative_stack:
# (stack_end, stack_begin)
# We need to ensure 16-byte alignment here.
pushq %r15
pushq %r14
pushq %r13
pushq %r12
pushq %rbx
pushq %rbp
movq %rsp, -16(%rdi)
leaq run_on_alternative_stack.returning(%rip), %rax
movq %rax, -24(%rdi)
movq %rsi, %rsp
movq (%rsp), %xmm0
add $8, %rsp
movq (%rsp), %xmm1
add $8, %rsp
movq (%rsp), %xmm2
add $8, %rsp
movq (%rsp), %xmm3
add $8, %rsp
movq (%rsp), %xmm4
add $8, %rsp
movq (%rsp), %xmm5
add $8, %rsp
movq (%rsp), %xmm6
add $8, %rsp
movq (%rsp), %xmm7
add $8, %rsp
movq (%rsp), %xmm8
add $8, %rsp
movq (%rsp), %xmm9
add $8, %rsp
movq (%rsp), %xmm10
add $8, %rsp
movq (%rsp), %xmm11
add $8, %rsp
movq (%rsp), %xmm12
add $8, %rsp
movq (%rsp), %xmm13
add $8, %rsp
movq (%rsp), %xmm14
add $8, %rsp
movq (%rsp), %xmm15
add $8, %rsp
popq %rbp
popq %rax
popq %rbx
popq %rcx
popq %rdx
popq %rdi
popq %rsi
popq %r8
popq %r9
popq %r10
popq %r11
popq %r12
popq %r13
popq %r14
popq %r15
retq
run_on_alternative_stack.returning:
movq (%rsp), %rsp
popq %rbp
popq %rbx
popq %r12
popq %r13
popq %r14
popq %r15
retq
# For switching into a backend without information about where registers are preserved.
.globl register_preservation_trampoline
register_preservation_trampoline:
subq $8, %rsp
pushq %rax
pushq %rcx
pushq %rdx
pushq %rdi
pushq %rsi
pushq %r8
pushq %r9
pushq %r10
callq get_boundary_register_preservation@PLT
# Keep this consistent with BoundaryRegisterPreservation
movq %r15, 0(%rax)
movq %r14, 8(%rax)
movq %r13, 16(%rax)
movq %r12, 24(%rax)
movq %rbx, 32(%rax)
popq %r10
popq %r9
popq %r8
popq %rsi
popq %rdi
popq %rdx
popq %rcx
popq %rax
addq $8, %rsp
jmpq *%rax

View File

@ -13,6 +13,7 @@ use crate::{
module::ModuleInfo,
sys::Memory,
};
use std::fmt;
use std::{any::Any, ptr::NonNull};
use std::collections::HashMap;
@ -109,9 +110,28 @@ impl BackendCompilerConfig {
pub struct CompilerConfig {
/// Symbol information generated from emscripten; used for more detailed debug messages
pub symbol_map: Option<HashMap<u32, String>>,
/// How to make the decision whether to emit bounds checks for memory accesses.
pub memory_bound_check_mode: MemoryBoundCheckMode,
/// Whether to generate explicit native stack checks against `stack_lower_bound` in `InternalCtx`.
///
/// Usually it's adequate to use hardware memory protection mechanisms such as `mprotect` on Unix to
/// prevent stack overflow. But for low-level environments, e.g. the kernel, faults are generally
/// not expected and relying on hardware memory protection would add too much complexity.
pub enforce_stack_check: bool,
/// Whether to enable state tracking. Necessary for managed mode.
pub track_state: bool,
/// Whether to enable full preemption checkpoint generation.
///
/// This inserts checkpoints at critical locations such as loop backedges and function calls,
/// allowing preemptive unwinding/task switching.
///
/// When enabled there can be a small amount of runtime performance overhead.
pub full_preemption: bool,
pub features: Features,
// Target info. Presently only supported by LLVM.
@ -150,13 +170,36 @@ impl ExceptionTable {
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
pub enum ExceptionCode {
/// An `unreachable` opcode was executed.
Unreachable,
Unreachable = 0,
/// Call indirect incorrect signature trap.
IncorrectCallIndirectSignature = 1,
/// Memory out of bounds trap.
MemoryOutOfBounds = 2,
/// Call indirect out of bounds trap.
CallIndirectOOB = 3,
/// An arithmetic exception, e.g. divided by zero.
Arithmetic,
IllegalArithmetic = 4,
/// Misaligned atomic access trap.
MisalignedAtomicAccess = 5,
}
/// Memory access exception, e.g. misaligned/out-of-bound read/write.
Memory,
impl fmt::Display for ExceptionCode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
match self {
ExceptionCode::Unreachable => "unreachable",
ExceptionCode::IncorrectCallIndirectSignature => {
"incorrect `call_indirect` signature"
}
ExceptionCode::MemoryOutOfBounds => "memory out-of-bounds access",
ExceptionCode::CallIndirectOOB => "`call_indirect` out-of-bounds",
ExceptionCode::IllegalArithmetic => "illegal arithmetic operation",
ExceptionCode::MisalignedAtomicAccess => "misaligned atomic access",
}
)
}
}
pub trait Compiler {

View File

@ -179,18 +179,7 @@ impl std::error::Error for LinkError {}
/// The main way to do this is `Instance.call`.
///
/// Comparing two `RuntimeError`s always evaluates to false.
pub enum RuntimeError {
/// Trap.
Trap {
/// Trap message.
msg: Box<str>,
},
/// Error.
Error {
/// Error data.
data: Box<dyn Any + Send>,
},
}
pub struct RuntimeError(pub Box<dyn Any + Send>);
impl PartialEq for RuntimeError {
fn eq(&self, _other: &RuntimeError) -> bool {
@ -200,21 +189,15 @@ impl PartialEq for RuntimeError {
impl std::fmt::Display for RuntimeError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
RuntimeError::Trap { ref msg } => {
write!(f, "WebAssembly trap occurred during runtime: {}", msg)
}
RuntimeError::Error { data } => {
if let Some(s) = data.downcast_ref::<String>() {
write!(f, "\"{}\"", s)
} else if let Some(s) = data.downcast_ref::<&str>() {
write!(f, "\"{}\"", s)
} else if let Some(exc_code) = data.downcast_ref::<ExceptionCode>() {
write!(f, "Caught exception of type \"{:?}\".", exc_code)
} else {
write!(f, "unknown error")
}
}
let data = &*self.0;
if let Some(s) = data.downcast_ref::<String>() {
write!(f, "\"{}\"", s)
} else if let Some(s) = data.downcast_ref::<&str>() {
write!(f, "\"{}\"", s)
} else if let Some(exc_code) = data.downcast_ref::<ExceptionCode>() {
write!(f, "Caught exception of type \"{:?}\".", exc_code)
} else {
write!(f, "unknown error")
}
}
}

View File

@ -500,6 +500,249 @@ impl FaultInfo {
}
}
#[cfg(all(target_os = "freebsd", target_arch = "aarch64"))]
/// Get fault info from siginfo and ucontext.
pub unsafe fn get_fault_info(siginfo: *const c_void, ucontext: *mut c_void) -> FaultInfo {
#[repr(C)]
pub struct ucontext_t {
uc_sigmask: libc::sigset_t,
uc_mcontext: mcontext_t,
uc_link: *mut ucontext_t,
uc_stack: libc::stack_t,
uc_flags: i32,
__spare__: [i32; 4],
}
#[repr(C)]
pub struct gpregs {
gp_x: [u64; 30],
gp_lr: u64,
gp_sp: u64,
gp_elr: u64,
gp_spsr: u64,
gp_pad: i32,
};
#[repr(C)]
pub struct fpregs {
fp_q: [u128; 32],
fp_sr: u32,
fp_cr: u32,
fp_flags: i32,
fp_pad: i32,
};
#[repr(C)]
pub struct mcontext_t {
mc_gpregs: gpregs,
mc_fpregs: fpregs,
mc_flags: i32,
mc_pad: i32,
mc_spare: [u64; 8],
}
let siginfo = siginfo as *const siginfo_t;
let si_addr = (*siginfo).si_addr;
let ucontext = ucontext as *mut ucontext_t;
let gregs = &(*ucontext).uc_mcontext.mc_gpregs;
let mut known_registers: [Option<u64>; 32] = [None; 32];
known_registers[X64Register::GPR(GPR::R15).to_index().0] = Some(gregs.gp_x[15] as _);
known_registers[X64Register::GPR(GPR::R14).to_index().0] = Some(gregs.gp_x[14] as _);
known_registers[X64Register::GPR(GPR::R13).to_index().0] = Some(gregs.gp_x[13] as _);
known_registers[X64Register::GPR(GPR::R12).to_index().0] = Some(gregs.gp_x[12] as _);
known_registers[X64Register::GPR(GPR::R11).to_index().0] = Some(gregs.gp_x[11] as _);
known_registers[X64Register::GPR(GPR::R10).to_index().0] = Some(gregs.gp_x[10] as _);
known_registers[X64Register::GPR(GPR::R9).to_index().0] = Some(gregs.gp_x[9] as _);
known_registers[X64Register::GPR(GPR::R8).to_index().0] = Some(gregs.gp_x[8] as _);
known_registers[X64Register::GPR(GPR::RSI).to_index().0] = Some(gregs.gp_x[6] as _);
known_registers[X64Register::GPR(GPR::RDI).to_index().0] = Some(gregs.gp_x[7] as _);
known_registers[X64Register::GPR(GPR::RDX).to_index().0] = Some(gregs.gp_x[2] as _);
known_registers[X64Register::GPR(GPR::RCX).to_index().0] = Some(gregs.gp_x[1] as _);
known_registers[X64Register::GPR(GPR::RBX).to_index().0] = Some(gregs.gp_x[3] as _);
known_registers[X64Register::GPR(GPR::RAX).to_index().0] = Some(gregs.gp_x[0] as _);
known_registers[X64Register::GPR(GPR::RBP).to_index().0] = Some(gregs.gp_x[5] as _);
known_registers[X64Register::GPR(GPR::RSP).to_index().0] = Some(gregs.gp_x[28] as _);
FaultInfo {
faulting_addr: si_addr as usize as _,
ip: std::mem::transmute::<&mut u64, &'static Cell<usize>>(
&mut (*ucontext).uc_mcontext.mc_gpregs.gp_elr,
),
known_registers,
}
}
#[cfg(all(target_os = "freebsd", target_arch = "x86_64"))]
/// Get fault info from siginfo and ucontext.
pub unsafe fn get_fault_info(siginfo: *const c_void, ucontext: *mut c_void) -> FaultInfo {
use crate::state::x64::XMM;
#[repr(C)]
pub struct ucontext_t {
uc_sigmask: libc::sigset_t,
uc_mcontext: mcontext_t,
uc_link: *mut ucontext_t,
uc_stack: libc::stack_t,
uc_flags: i32,
__spare__: [i32; 4],
}
#[repr(C)]
pub struct mcontext_t {
mc_onstack: u64,
mc_rdi: u64,
mc_rsi: u64,
mc_rdx: u64,
mc_rcx: u64,
mc_r8: u64,
mc_r9: u64,
mc_rax: u64,
mc_rbx: u64,
mc_rbp: u64,
mc_r10: u64,
mc_r11: u64,
mc_r12: u64,
mc_r13: u64,
mc_r14: u64,
mc_r15: u64,
mc_trapno: u32,
mc_fs: u16,
mc_gs: u16,
mc_addr: u64,
mc_flags: u32,
mc_es: u16,
mc_ds: u16,
mc_err: u64,
mc_rip: u64,
mc_cs: u64,
mc_rflags: u64,
mc_rsp: u64,
mc_ss: u64,
mc_len: i64,
mc_fpformat: i64,
mc_ownedfp: i64,
mc_savefpu: *const savefpu,
mc_fpstate: [i64; 63], // mc_fpstate[0] is a pointer to savefpu
mc_fsbase: u64,
mc_gsbase: u64,
mc_xfpustate: u64,
mc_xfpustate_len: u64,
mc_spare: [i64; 4],
}
#[repr(C)]
pub struct xmmacc {
element: [u32; 4],
}
#[repr(C)]
pub struct __envxmm64 {
en_cw: u16,
en_sw: u16,
en_tw: u8,
en_zero: u8,
en_opcode: u16,
en_rip: u64,
en_rdp: u64,
en_mxcsr: u32,
en_mxcsr_mask: u32,
}
#[repr(C)]
pub struct fpacc87 {
fp_bytes: [u8; 10],
}
#[repr(C)]
pub struct sv_fp {
fp_acc: fpacc87,
fp_pad: [u8; 6],
}
#[repr(C, align(16))]
pub struct savefpu {
sv_env: __envxmm64,
sv_fp_t: [sv_fp; 8],
sv_xmm: [xmmacc; 16],
sv_pad: [u8; 96],
}
let siginfo = siginfo as *const siginfo_t;
let si_addr = (*siginfo).si_addr;
let ucontext = ucontext as *mut ucontext_t;
let gregs = &mut (*ucontext).uc_mcontext;
fn read_xmm(reg: &xmmacc) -> u64 {
(reg.element[0] as u64) | ((reg.element[1] as u64) << 32)
}
let mut known_registers: [Option<u64>; 32] = [None; 32];
known_registers[X64Register::GPR(GPR::R15).to_index().0] = Some(gregs.mc_r15);
known_registers[X64Register::GPR(GPR::R14).to_index().0] = Some(gregs.mc_r14);
known_registers[X64Register::GPR(GPR::R13).to_index().0] = Some(gregs.mc_r13);
known_registers[X64Register::GPR(GPR::R12).to_index().0] = Some(gregs.mc_r12);
known_registers[X64Register::GPR(GPR::R11).to_index().0] = Some(gregs.mc_r11);
known_registers[X64Register::GPR(GPR::R10).to_index().0] = Some(gregs.mc_r10);
known_registers[X64Register::GPR(GPR::R9).to_index().0] = Some(gregs.mc_r9);
known_registers[X64Register::GPR(GPR::R8).to_index().0] = Some(gregs.mc_r8);
known_registers[X64Register::GPR(GPR::RSI).to_index().0] = Some(gregs.mc_rsi);
known_registers[X64Register::GPR(GPR::RDI).to_index().0] = Some(gregs.mc_rdi);
known_registers[X64Register::GPR(GPR::RDX).to_index().0] = Some(gregs.mc_rdx);
known_registers[X64Register::GPR(GPR::RCX).to_index().0] = Some(gregs.mc_rcx);
known_registers[X64Register::GPR(GPR::RBX).to_index().0] = Some(gregs.mc_rbx);
known_registers[X64Register::GPR(GPR::RAX).to_index().0] = Some(gregs.mc_rax);
known_registers[X64Register::GPR(GPR::RBP).to_index().0] = Some(gregs.mc_rbp);
known_registers[X64Register::GPR(GPR::RSP).to_index().0] = Some(gregs.mc_rsp);
// https://lists.freebsd.org/pipermail/freebsd-arch/2011-December/012077.html
// https://people.freebsd.org/~kib/misc/defer_sig.c
const _MC_HASFPXSTATE: u32 = 0x4;
if (gregs.mc_flags & _MC_HASFPXSTATE) == 0 {
// XXX mc_fpstate[0] is actually a pointer to a struct savefpu
let fpregs = &*(*ucontext).uc_mcontext.mc_savefpu;
known_registers[X64Register::XMM(XMM::XMM0).to_index().0] =
Some(read_xmm(&fpregs.sv_xmm[0]));
known_registers[X64Register::XMM(XMM::XMM1).to_index().0] =
Some(read_xmm(&fpregs.sv_xmm[1]));
known_registers[X64Register::XMM(XMM::XMM2).to_index().0] =
Some(read_xmm(&fpregs.sv_xmm[2]));
known_registers[X64Register::XMM(XMM::XMM3).to_index().0] =
Some(read_xmm(&fpregs.sv_xmm[3]));
known_registers[X64Register::XMM(XMM::XMM4).to_index().0] =
Some(read_xmm(&fpregs.sv_xmm[4]));
known_registers[X64Register::XMM(XMM::XMM5).to_index().0] =
Some(read_xmm(&fpregs.sv_xmm[5]));
known_registers[X64Register::XMM(XMM::XMM6).to_index().0] =
Some(read_xmm(&fpregs.sv_xmm[6]));
known_registers[X64Register::XMM(XMM::XMM7).to_index().0] =
Some(read_xmm(&fpregs.sv_xmm[7]));
known_registers[X64Register::XMM(XMM::XMM8).to_index().0] =
Some(read_xmm(&fpregs.sv_xmm[8]));
known_registers[X64Register::XMM(XMM::XMM9).to_index().0] =
Some(read_xmm(&fpregs.sv_xmm[9]));
known_registers[X64Register::XMM(XMM::XMM10).to_index().0] =
Some(read_xmm(&fpregs.sv_xmm[10]));
known_registers[X64Register::XMM(XMM::XMM11).to_index().0] =
Some(read_xmm(&fpregs.sv_xmm[11]));
known_registers[X64Register::XMM(XMM::XMM12).to_index().0] =
Some(read_xmm(&fpregs.sv_xmm[12]));
known_registers[X64Register::XMM(XMM::XMM13).to_index().0] =
Some(read_xmm(&fpregs.sv_xmm[13]));
known_registers[X64Register::XMM(XMM::XMM14).to_index().0] =
Some(read_xmm(&fpregs.sv_xmm[14]));
known_registers[X64Register::XMM(XMM::XMM15).to_index().0] =
Some(read_xmm(&fpregs.sv_xmm[15]));
}
FaultInfo {
faulting_addr: si_addr,
ip: std::mem::transmute::<&mut u64, &'static Cell<usize>>(
&mut (*ucontext).uc_mcontext.mc_rip,
),
known_registers,
}
}
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
/// Get fault info from siginfo and ucontext.
pub unsafe fn get_fault_info(siginfo: *const c_void, ucontext: *mut c_void) -> FaultInfo {

View File

@ -1,5 +1,5 @@
//! The global module contains the implementation data structures and helper functions used to
//! manipulate and access a wasm globals.
//! The global module contains data structures and helper functions used to
//! manipulate and access Wasm globals.
use crate::{
export::Export,
import::IsExport,
@ -11,7 +11,7 @@ use std::{
sync::{Arc, Mutex},
};
/// Container with a descriptor and a reference to a global value.
/// A handle to a Wasm Global
pub struct Global {
desc: GlobalDescriptor,
storage: Arc<Mutex<vm::LocalGlobal>>,

View File

@ -13,7 +13,7 @@ use crate::{
sig_registry::SigRegistry,
structures::TypedIndex,
table::Table,
typed_func::{Func, Wasm, WasmTrapInfo, WasmTypeList},
typed_func::{Func, Wasm, WasmTypeList},
types::{FuncIndex, FuncSig, GlobalIndex, LocalOrImport, MemoryIndex, TableIndex, Type, Value},
vm::{self, InternalField},
};
@ -674,8 +674,7 @@ pub(crate) fn call_func_with_index_inner(
} = wasm;
let run_wasm = |result_space: *mut u64| unsafe {
let mut trap_info = WasmTrapInfo::Unknown;
let mut user_error = None;
let mut error_out = None;
let success = invoke(
trampoline,
@ -683,21 +682,16 @@ pub(crate) fn call_func_with_index_inner(
func_ptr,
raw_args.as_ptr(),
result_space,
&mut trap_info,
&mut user_error,
&mut error_out,
invoke_env,
);
if success {
Ok(())
} else {
if let Some(data) = user_error {
Err(RuntimeError::Error { data })
} else {
Err(RuntimeError::Trap {
msg: trap_info.to_string().into(),
})
}
Err(error_out
.map(RuntimeError)
.unwrap_or_else(|| RuntimeError(Box::new("invoke(): Unknown error".to_string()))))
}
};

View File

@ -1,15 +1,15 @@
//! Wasmer Runtime Core Library
//!
//! The runtime core library provides common data structures which are shared by compiler backends
//! to implement a Web Assembly runtime.
//! This crate provides common data structures which are shared by compiler backends
//! to implement a WebAssembly runtime.
//!
//! The runtime core also provides an API for users who use wasmer as an embedded wasm runtime which
//! This crate also provides an API for users who use wasmer as an embedded wasm runtime which
//! allows operations like compiling, instantiating, providing imports, access exports, memories,
//! and tables for example.
//!
//! The runtime core library is recommended to be used by only power users who wish to customize the
//! wasmer runtime. Most wasmer users should prefer the API which is re-exported by the wasmer
//! runtime library which provides common defaults and a friendly API.
//! Most wasmer users should prefer the API which is re-exported by the `wasmer-runtime`
//! library by default. This crate provides additional APIs which may be useful to users
//! that wish to customize the wasmer runtime.
//!
#![deny(

View File

@ -88,7 +88,7 @@ impl Instance for LocalInstance {
}
}
let offset = self.offsets[id];
let addr: *const u8 = unsafe { self.code.as_ptr().offset(offset as isize) };
let addr: *const u8 = unsafe { self.code.as_ptr().add(offset) };
use std::mem::transmute;
Ok(unsafe {
match args_u64.len() {

View File

@ -109,7 +109,7 @@ impl Memory {
}
/// Return a "view" of the currently accessible memory. By
/// default, the view is unsyncronized, using regular memory
/// default, the view is unsynchronized, using regular memory
/// accesses. You can force a memory view to use atomic accesses
/// by calling the [`atomically`] method.
///

View File

@ -82,7 +82,9 @@ impl<'a, T: Atomic> MemoryView<'a, T> {
impl<'a, T> Deref for MemoryView<'a, T, NonAtomically> {
type Target = [Cell<T>];
fn deref(&self) -> &[Cell<T>] {
unsafe { slice::from_raw_parts(self.ptr as *const Cell<T>, self.length) }
let mut_slice: &mut [T] = unsafe { slice::from_raw_parts_mut(self.ptr, self.length) };
let cell_slice: &Cell<[T]> = Cell::from_mut(mut_slice);
cell_slice.as_slice_of_cells()
}
}

View File

@ -11,52 +11,12 @@ use std::{
any::Any,
convert::Infallible,
ffi::c_void,
fmt,
marker::PhantomData,
mem, panic,
ptr::{self, NonNull},
sync::Arc,
};
/// Wasm trap info.
#[repr(C)]
pub enum WasmTrapInfo {
/// Unreachable trap.
Unreachable = 0,
/// Call indirect incorrect signature trap.
IncorrectCallIndirectSignature = 1,
/// Memory out of bounds trap.
MemoryOutOfBounds = 2,
/// Call indirect out of bounds trap.
CallIndirectOOB = 3,
/// Illegal arithmetic trap.
IllegalArithmetic = 4,
/// Misaligned atomic access trap.
MisalignedAtomicAccess = 5,
/// Unknown trap.
Unknown,
}
impl fmt::Display for WasmTrapInfo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
match self {
WasmTrapInfo::Unreachable => "unreachable",
WasmTrapInfo::IncorrectCallIndirectSignature => {
"incorrect `call_indirect` signature"
}
WasmTrapInfo::MemoryOutOfBounds => "memory out-of-bounds access",
WasmTrapInfo::CallIndirectOOB => "`call_indirect` out-of-bounds",
WasmTrapInfo::IllegalArithmetic => "illegal arithmetic operation",
WasmTrapInfo::MisalignedAtomicAccess => "misaligned atomic access",
WasmTrapInfo::Unknown => "unknown",
}
)
}
}
/// This is just an empty trait to constrict that types that
/// can be put into the third/fourth (depending if you include lifetimes)
/// of the `Func` struct.
@ -77,8 +37,7 @@ pub type Invoke = unsafe extern "C" fn(
func: NonNull<vm::Func>,
args: *const u64,
rets: *mut u64,
trap_info: *mut WasmTrapInfo,
user_error: *mut Option<Box<dyn Any + Send>>,
error_out: *mut Option<Box<dyn Any + Send>>,
extra: Option<NonNull<c_void>>,
) -> bool;
@ -92,6 +51,8 @@ pub struct Wasm {
pub(crate) invoke_env: Option<NonNull<c_void>>,
}
impl Kind for Wasm {}
impl Wasm {
/// Create new `Wasm` from given parts.
pub unsafe fn from_raw_parts(
@ -111,7 +72,6 @@ impl Wasm {
/// by the host.
pub struct Host(());
impl Kind for Wasm {}
impl Kind for Host {}
/// Represents a list of WebAssembly values.
@ -140,7 +100,7 @@ pub trait WasmTypeList {
/// This method is used to distribute the values onto a function,
/// e.g. `(1, 2).call(func, …)`. This form is unlikely to be used
/// directly in the code, see the `Func:call` implementation.
/// directly in the code, see the `Func::call` implementation.
unsafe fn call<Rets>(
self,
f: NonNull<vm::Func>,
@ -151,14 +111,15 @@ pub trait WasmTypeList {
Rets: WasmTypeList;
}
/// Empty trait to specify the kind of `ExternalFunction`: With or
/// Empty trait to specify the kind of `HostFunction`: With or
/// without a `vm::Ctx` argument. See the `ExplicitVmCtx` and the
/// `ImplicitVmCtx` structures.
///
/// This type is never aimed to be used by a user. It is used by the
/// This trait is never aimed to be used by a user. It is used by the
/// trait system to automatically generate an appropriate `wrap`
/// function.
pub trait ExternalFunctionKind {}
#[doc(hidden)]
pub trait HostFunctionKind {}
/// This empty structure indicates that an external function must
/// contain an explicit `vm::Ctx` argument (at first position).
@ -168,8 +129,11 @@ pub trait ExternalFunctionKind {}
/// x + 1
/// }
/// ```
#[doc(hidden)]
pub struct ExplicitVmCtx {}
impl HostFunctionKind for ExplicitVmCtx {}
/// This empty structure indicates that an external function has no
/// `vm::Ctx` argument (at first position). Its signature is:
///
@ -180,18 +144,17 @@ pub struct ExplicitVmCtx {}
/// ```
pub struct ImplicitVmCtx {}
impl ExternalFunctionKind for ExplicitVmCtx {}
impl ExternalFunctionKind for ImplicitVmCtx {}
impl HostFunctionKind for ImplicitVmCtx {}
/// Represents a function that can be converted to a `vm::Func`
/// (function pointer) that can be called within WebAssembly.
pub trait ExternalFunction<Kind, Args, Rets>
pub trait HostFunction<Kind, Args, Rets>
where
Kind: ExternalFunctionKind,
Kind: HostFunctionKind,
Args: WasmTypeList,
Rets: WasmTypeList,
{
/// Conver to function pointer.
/// Convert to function pointer.
fn to_raw(self) -> (NonNull<vm::Func>, Option<NonNull<vm::FuncEnv>>);
}
@ -268,8 +231,8 @@ where
/// Creates a new `Func`.
pub fn new<F, Kind>(func: F) -> Func<'a, Args, Rets, Host>
where
Kind: ExternalFunctionKind,
F: ExternalFunction<Kind, Args, Rets>,
Kind: HostFunctionKind,
F: HostFunction<Kind, Args, Rets>,
{
let (func, func_env) = func.to_raw();
@ -351,6 +314,7 @@ macro_rules! impl_traits {
where
$( $x: WasmExternType ),*;
#[allow(unused_parens)]
impl< $( $x ),* > WasmTypeList for ( $( $x ),* )
where
$( $x: WasmExternType ),*
@ -401,8 +365,7 @@ macro_rules! impl_traits {
let ( $( $x ),* ) = self;
let args = [ $( $x.to_native().to_binary()),* ];
let mut rets = Rets::empty_ret_array();
let mut trap = WasmTrapInfo::Unknown;
let mut user_error = None;
let mut error_out = None;
if (wasm.invoke)(
wasm.trampoline,
@ -410,22 +373,20 @@ macro_rules! impl_traits {
f,
args.as_ptr(),
rets.as_mut().as_mut_ptr(),
&mut trap,
&mut user_error,
&mut error_out,
wasm.invoke_env
) {
Ok(Rets::from_ret_array(rets))
} else {
if let Some(data) = user_error {
Err(RuntimeError::Error { data })
} else {
Err(RuntimeError::Trap { msg: trap.to_string().into() })
}
Err(error_out.map(RuntimeError).unwrap_or_else(|| {
RuntimeError(Box::new("invoke(): Unknown error".to_string()))
}))
}
}
}
impl< $( $x, )* Rets, Trap, FN > ExternalFunction<ExplicitVmCtx, ( $( $x ),* ), Rets> for FN
#[allow(unused_parens)]
impl< $( $x, )* Rets, Trap, FN > HostFunction<ExplicitVmCtx, ( $( $x ),* ), Rets> for FN
where
$( $x: WasmExternType, )*
Rets: WasmTypeList,
@ -540,7 +501,8 @@ macro_rules! impl_traits {
}
}
impl< $( $x, )* Rets, Trap, FN > ExternalFunction<ImplicitVmCtx, ( $( $x ),* ), Rets> for FN
#[allow(unused_parens)]
impl< $( $x, )* Rets, Trap, FN > HostFunction<ImplicitVmCtx, ( $( $x ),* ), Rets> for FN
where
$( $x: WasmExternType, )*
Rets: WasmTypeList,
@ -652,13 +614,14 @@ macro_rules! impl_traits {
}
}
#[allow(unused_parens)]
impl<'a $( , $x )*, Rets> Func<'a, ( $( $x ),* ), Rets, Wasm>
where
$( $x: WasmExternType, )*
Rets: WasmTypeList,
{
/// Call the typed func and return results.
#[allow(non_snake_case)]
#[allow(non_snake_case, clippy::too_many_arguments)]
pub fn call(&self, $( $x: $x, )* ) -> Result<Rets, RuntimeError> {
#[allow(unused_parens)]
unsafe {

View File

@ -2,7 +2,7 @@
//! convert to other represenations.
use crate::{memory::MemoryType, module::ModuleInfo, structures::TypedIndex, units::Pages};
use std::borrow::Cow;
use std::{borrow::Cow, convert::TryFrom};
/// Represents a WebAssembly type.
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
@ -67,35 +67,32 @@ impl Value {
}
}
impl From<i32> for Value {
fn from(i: i32) -> Self {
Value::I32(i)
}
macro_rules! value_conversions {
($native_type:ty, $value_variant:ident) => {
impl From<$native_type> for Value {
fn from(n: $native_type) -> Self {
Self::$value_variant(n)
}
}
impl TryFrom<&Value> for $native_type {
type Error = &'static str;
fn try_from(value: &Value) -> Result<Self, Self::Error> {
match value {
Value::$value_variant(value) => Ok(*value),
_ => Err("Invalid cast."),
}
}
}
};
}
impl From<i64> for Value {
fn from(i: i64) -> Self {
Value::I64(i)
}
}
impl From<f32> for Value {
fn from(f: f32) -> Self {
Value::F32(f)
}
}
impl From<f64> for Value {
fn from(f: f64) -> Self {
Value::F64(f)
}
}
impl From<u128> for Value {
fn from(v: u128) -> Self {
Value::V128(v)
}
}
value_conversions!(i32, I32);
value_conversions!(i64, I64);
value_conversions!(f32, F32);
value_conversions!(f64, F64);
value_conversions!(u128, V128);
/// Represents a native wasm type.
pub unsafe trait NativeWasmType: Copy + Into<Value>
@ -104,44 +101,57 @@ where
{
/// Type for this `NativeWasmType`.
const TYPE: Type;
/// Convert from u64 bites to self.
fn from_binary(bits: u64) -> Self;
/// Convert self to u64 binary representation.
fn to_binary(self) -> u64;
}
unsafe impl NativeWasmType for i32 {
const TYPE: Type = Type::I32;
fn from_binary(bits: u64) -> Self {
bits as _
}
fn to_binary(self) -> u64 {
self as _
}
}
unsafe impl NativeWasmType for i64 {
const TYPE: Type = Type::I64;
fn from_binary(bits: u64) -> Self {
bits as _
}
fn to_binary(self) -> u64 {
self as _
}
}
unsafe impl NativeWasmType for f32 {
const TYPE: Type = Type::F32;
fn from_binary(bits: u64) -> Self {
f32::from_bits(bits as u32)
}
fn to_binary(self) -> u64 {
self.to_bits() as _
}
}
unsafe impl NativeWasmType for f64 {
const TYPE: Type = Type::F64;
fn from_binary(bits: u64) -> Self {
f64::from_bits(bits)
}
fn to_binary(self) -> u64 {
self.to_bits()
}
@ -154,103 +164,41 @@ where
{
/// Native wasm type for this `WasmExternType`.
type Native: NativeWasmType;
/// Convert from given `Native` type to self.
fn from_native(native: Self::Native) -> Self;
/// Convert self to `Native` type.
fn to_native(self) -> Self::Native;
}
unsafe impl WasmExternType for i8 {
type Native = i32;
fn from_native(native: Self::Native) -> Self {
native as _
}
fn to_native(self) -> Self::Native {
self as _
}
}
unsafe impl WasmExternType for u8 {
type Native = i32;
fn from_native(native: Self::Native) -> Self {
native as _
}
fn to_native(self) -> Self::Native {
self as _
}
}
unsafe impl WasmExternType for i16 {
type Native = i32;
fn from_native(native: Self::Native) -> Self {
native as _
}
fn to_native(self) -> Self::Native {
self as _
}
}
unsafe impl WasmExternType for u16 {
type Native = i32;
fn from_native(native: Self::Native) -> Self {
native as _
}
fn to_native(self) -> Self::Native {
self as _
}
}
unsafe impl WasmExternType for i32 {
type Native = i32;
fn from_native(native: Self::Native) -> Self {
native
}
fn to_native(self) -> Self::Native {
self
}
}
unsafe impl WasmExternType for u32 {
type Native = i32;
fn from_native(native: Self::Native) -> Self {
native as _
}
fn to_native(self) -> Self::Native {
self as _
}
}
unsafe impl WasmExternType for i64 {
type Native = i64;
fn from_native(native: Self::Native) -> Self {
native
}
fn to_native(self) -> Self::Native {
self
}
}
unsafe impl WasmExternType for u64 {
type Native = i64;
fn from_native(native: Self::Native) -> Self {
native as _
}
fn to_native(self) -> Self::Native {
self as _
}
}
unsafe impl WasmExternType for f32 {
type Native = f32;
fn from_native(native: Self::Native) -> Self {
native
}
fn to_native(self) -> Self::Native {
self
}
}
unsafe impl WasmExternType for f64 {
type Native = f64;
fn from_native(native: Self::Native) -> Self {
native
}
fn to_native(self) -> Self::Native {
self
}
macro_rules! wasm_extern_type {
($type:ty => $native_type:ty) => {
unsafe impl WasmExternType for $type {
type Native = $native_type;
fn from_native(native: Self::Native) -> Self {
native as _
}
fn to_native(self) -> Self::Native {
self as _
}
}
};
}
wasm_extern_type!(i8 => i32);
wasm_extern_type!(u8 => i32);
wasm_extern_type!(i16 => i32);
wasm_extern_type!(u16 => i32);
wasm_extern_type!(i32 => i32);
wasm_extern_type!(u32 => i32);
wasm_extern_type!(i64 => i64);
wasm_extern_type!(u64 => i64);
wasm_extern_type!(f32 => f32);
wasm_extern_type!(f64 => f64);
// pub trait IntegerAtomic
// where
// Self: Sized

View File

@ -70,8 +70,8 @@ fn main() -> Result<(), error::Error> {
println!("result: {:?}", result);
if let Err(RuntimeError::Error { data }) = result {
if let Ok(exit_code) = data.downcast::<ExitCode>() {
if let Err(e) = result {
if let Ok(exit_code) = e.0.downcast::<ExitCode>() {
println!("exit code: {:?}", exit_code);
}
}

View File

@ -78,7 +78,7 @@
//!
//! # Additional Notes:
//!
//! The `wasmer-runtime` is build to support compiler multiple backends.
//! `wasmer-runtime` is built to support multiple compiler backends.
//! Currently, we support the Singlepass, [Cranelift], and LLVM compilers
//! with the [`wasmer-singlepass-backend`], [`wasmer-clif-backend`], and
//! wasmer-llvm-backend crates, respectively.
@ -145,7 +145,7 @@ pub mod units {
}
pub mod types {
//! Various types.
//! Types used in the Wasm runtime and conversion functions.
pub use wasmer_runtime_core::types::*;
}

View File

@ -40,10 +40,10 @@ fn error_propagation() {
let result = foo.call();
if let Err(RuntimeError::Error { data }) = result {
let exit_code = data.downcast::<ExitCode>().unwrap();
if let Err(RuntimeError(e)) = result {
let exit_code = e.downcast::<ExitCode>().unwrap();
assert_eq!(exit_code.code, 42);
} else {
panic!("didn't return RuntimeError::Error")
panic!("didn't return RuntimeError")
}
}

View File

@ -3,7 +3,6 @@
use crate::emitter_x64::*;
use crate::machine::*;
use crate::protect_unix;
#[cfg(target_arch = "aarch64")]
use dynasmrt::aarch64::Assembler;
#[cfg(target_arch = "x86_64")]
@ -28,7 +27,7 @@ use wasmer_runtime_core::{
},
cache::{Artifact, Error as CacheError},
codegen::*,
fault::raw::register_preservation_trampoline,
fault::{self, raw::register_preservation_trampoline},
loader::CodeMemory,
memory::MemoryType,
module::{ModuleInfo, ModuleInner},
@ -37,7 +36,7 @@ use wasmer_runtime_core::{
ModuleStateMap, OffsetInfo, SuspendOffset, WasmAbstractValue,
},
structures::{Map, TypedIndex},
typed_func::{Trampoline, Wasm, WasmTrapInfo},
typed_func::{Trampoline, Wasm},
types::{
FuncIndex, FuncSig, GlobalIndex, LocalFuncIndex, LocalOrImport, MemoryIndex, SigIndex,
TableIndex, Type,
@ -369,14 +368,18 @@ impl RunnableModule for X64ExecutionContext {
}
fn get_trampoline(&self, _: &ModuleInfo, sig_index: SigIndex) -> Option<Wasm> {
// Correctly unwinding from `catch_unsafe_unwind` on hardware exceptions depends
// on the signal handlers being installed. Here we call `ensure_sighandler` "statically"
// outside `invoke()`.
fault::ensure_sighandler();
unsafe extern "C" fn invoke(
_trampoline: Trampoline,
ctx: *mut vm::Ctx,
func: NonNull<vm::Func>,
args: *const u64,
rets: *mut u64,
trap_info: *mut WasmTrapInfo,
user_error: *mut Option<Box<dyn Any + Send>>,
error_out: *mut Option<Box<dyn Any + Send>>,
num_params_plus_one: Option<NonNull<c_void>>,
) -> bool {
let rm: &Box<dyn RunnableModule> = &(&*(*ctx).module).runnable_module;
@ -384,8 +387,9 @@ impl RunnableModule for X64ExecutionContext {
let args =
slice::from_raw_parts(args, num_params_plus_one.unwrap().as_ptr() as usize - 1);
let ret = match protect_unix::call_protected(
let ret = match fault::catch_unsafe_unwind(
|| {
// Puts the arguments onto the stack and calls Wasm entry.
#[cfg(target_arch = "x86_64")]
{
let args_reverse: SmallVec<[u64; 8]> = args.iter().cloned().rev().collect();
@ -396,6 +400,9 @@ impl RunnableModule for X64ExecutionContext {
func.as_ptr(),
)
}
// FIXME: Currently we are doing a hack here to convert between native aarch64 and
// "emulated" x86 ABIs. Ideally, this should be done using handwritten assembly.
#[cfg(target_arch = "aarch64")]
{
struct CallCtx<'a> {
@ -520,10 +527,7 @@ impl RunnableModule for X64ExecutionContext {
true
}
Err(err) => {
match err {
protect_unix::CallProtError::Trap(info) => *trap_info = info,
protect_unix::CallProtError::Error(data) => *user_error = Some(data),
}
*error_out = Some(err);
false
}
};
@ -549,8 +553,7 @@ impl RunnableModule for X64ExecutionContext {
}
unsafe fn do_early_trap(&self, data: Box<dyn Any + Send>) -> ! {
protect_unix::TRAP_EARLY_DATA.with(|x| x.set(Some(data)));
protect_unix::trigger_trap();
fault::begin_unsafe_unwind(data);
}
fn get_code(&self) -> Option<&[u8]> {
@ -639,6 +642,7 @@ struct CodegenConfig {
memory_bound_check_mode: MemoryBoundCheckMode,
enforce_stack_check: bool,
track_state: bool,
full_preemption: bool,
}
impl ModuleCodeGenerator<X64FunctionCode, X64ExecutionContext, CodegenError>
@ -921,6 +925,7 @@ impl ModuleCodeGenerator<X64FunctionCode, X64ExecutionContext, CodegenError>
memory_bound_check_mode: config.memory_bound_check_mode,
enforce_stack_check: config.enforce_stack_check,
track_state: config.track_state,
full_preemption: config.full_preemption,
}));
Ok(())
}
@ -1021,14 +1026,14 @@ impl X64FunctionCode {
Self::mark_trappable(a, m, fsm, control_stack);
etable
.offset_to_code
.insert(a.get_offset().0, ExceptionCode::Arithmetic);
.insert(a.get_offset().0, ExceptionCode::IllegalArithmetic);
op(a, sz, Location::GPR(GPR::RCX));
}
_ => {
Self::mark_trappable(a, m, fsm, control_stack);
etable
.offset_to_code
.insert(a.get_offset().0, ExceptionCode::Arithmetic);
.insert(a.get_offset().0, ExceptionCode::IllegalArithmetic);
op(a, sz, loc);
}
}
@ -1701,14 +1706,11 @@ impl X64FunctionCode {
Location::GPR(GPR::RSP),
);
// FIXME: Possible dynasm bug. This is a workaround.
// Using RSP as the source/destination operand of a `mov` instruction produces invalid code.
a.emit_mov(Size::S64, Location::GPR(GPR::RSP), Location::GPR(GPR::RCX));
for (i, r) in used_xmms.iter().enumerate() {
a.emit_mov(
Size::S64,
Location::XMM(*r),
Location::Memory(GPR::RCX, (i * 8) as i32),
Location::Memory(GPR::RSP, (i * 8) as i32),
);
}
for r in used_xmms.iter().rev() {
@ -1786,37 +1788,26 @@ impl X64FunctionCode {
}
}
match *param {
// Dynasm bug: RSP in memory operand does not work
Location::Imm64(_) | Location::XMM(_) => {
Location::Imm64(_) => {
// Dummy value slot to be filled with `mov`.
a.emit_push(Size::S64, Location::GPR(GPR::RAX));
// Use R10 as the temporary register here, since it is callee-saved and not
// used by the callback `cb`.
a.emit_mov(Size::S64, *param, Location::GPR(GPR::R10));
a.emit_mov(
Size::S64,
Location::GPR(GPR::RAX),
Location::XMM(XMM::XMM0),
);
a.emit_mov(
Size::S64,
Location::GPR(GPR::RCX),
Location::XMM(XMM::XMM1),
);
a.emit_sub(Size::S64, Location::Imm32(8), Location::GPR(GPR::RSP));
a.emit_mov(Size::S64, Location::GPR(GPR::RSP), Location::GPR(GPR::RCX));
a.emit_mov(Size::S64, *param, Location::GPR(GPR::RAX));
a.emit_mov(
Size::S64,
Location::GPR(GPR::RAX),
Location::Memory(GPR::RCX, 0),
);
a.emit_mov(
Size::S64,
Location::XMM(XMM::XMM0),
Location::GPR(GPR::RAX),
);
a.emit_mov(
Size::S64,
Location::XMM(XMM::XMM1),
Location::GPR(GPR::RCX),
Location::GPR(GPR::R10),
Location::Memory(GPR::RSP, 0),
);
}
Location::XMM(_) => {
// Dummy value slot to be filled with `mov`.
a.emit_push(Size::S64, Location::GPR(GPR::RAX));
// XMM registers can be directly stored to memory.
a.emit_mov(Size::S64, *param, Location::Memory(GPR::RSP, 0));
}
_ => a.emit_push(Size::S64, *param),
}
}
@ -1888,12 +1879,10 @@ impl X64FunctionCode {
// Restore XMMs.
if used_xmms.len() > 0 {
// FIXME: Possible dynasm bug. This is a workaround.
a.emit_mov(Size::S64, Location::GPR(GPR::RSP), Location::GPR(GPR::RDX));
for (i, r) in used_xmms.iter().enumerate() {
a.emit_mov(
Size::S64,
Location::Memory(GPR::RDX, (i * 8) as i32),
Location::Memory(GPR::RSP, (i * 8) as i32),
Location::XMM(*r),
);
}
@ -2014,9 +2003,12 @@ impl X64FunctionCode {
a.emit_add(Size::S64, Location::GPR(tmp_base), Location::GPR(tmp_addr));
a.emit_cmp(Size::S64, Location::GPR(tmp_bound), Location::GPR(tmp_addr));
Self::mark_range_with_exception_code(a, etable, ExceptionCode::Memory, |a| {
a.emit_conditional_trap(Condition::Above)
});
Self::mark_range_with_exception_code(
a,
etable,
ExceptionCode::MemoryOutOfBounds,
|a| a.emit_conditional_trap(Condition::Above),
);
m.release_temp_gpr(tmp_bound);
}
@ -2056,13 +2048,16 @@ impl X64FunctionCode {
Location::Imm32(align - 1),
Location::GPR(tmp_aligncheck),
);
Self::mark_range_with_exception_code(a, etable, ExceptionCode::Memory, |a| {
a.emit_conditional_trap(Condition::NotEqual)
});
Self::mark_range_with_exception_code(
a,
etable,
ExceptionCode::MemoryOutOfBounds,
|a| a.emit_conditional_trap(Condition::NotEqual),
);
m.release_temp_gpr(tmp_aligncheck);
}
Self::mark_range_with_exception_code(a, etable, ExceptionCode::Memory, |a| {
Self::mark_range_with_exception_code(a, etable, ExceptionCode::MemoryOutOfBounds, |a| {
cb(a, m, tmp_addr)
})?;
@ -2116,6 +2111,10 @@ impl X64FunctionCode {
true,
value_size,
|a, m, addr| {
// Memory moves with size < 32b do not zero upper bits.
if memory_sz < Size::S32 {
a.emit_xor(Size::S32, Location::GPR(compare), Location::GPR(compare));
}
a.emit_mov(memory_sz, Location::Memory(addr, 0), Location::GPR(compare));
a.emit_mov(stack_sz, Location::GPR(compare), ret);
cb(a, m, compare, value);
@ -2193,7 +2192,7 @@ impl X64FunctionCode {
a.emit_label(trap);
etable
.offset_to_code
.insert(a.get_offset().0, ExceptionCode::Arithmetic);
.insert(a.get_offset().0, ExceptionCode::IllegalArithmetic);
a.emit_ud2();
a.emit_label(end);
}
@ -2321,7 +2320,7 @@ impl X64FunctionCode {
a.emit_label(trap);
etable
.offset_to_code
.insert(a.get_offset().0, ExceptionCode::Arithmetic);
.insert(a.get_offset().0, ExceptionCode::IllegalArithmetic);
a.emit_ud2();
a.emit_label(end);
}
@ -2449,7 +2448,7 @@ impl FunctionCodeGenerator<CodegenError> for X64FunctionCode {
Self::mark_range_with_exception_code(
a,
self.exception_table.as_mut().unwrap(),
ExceptionCode::Memory,
ExceptionCode::MemoryOutOfBounds,
|a| a.emit_conditional_trap(Condition::Below),
);
}
@ -2491,28 +2490,31 @@ impl FunctionCodeGenerator<CodegenError> for X64FunctionCode {
// Check interrupt signal without branching
let activate_offset = a.get_offset().0;
a.emit_mov(
Size::S64,
Location::Memory(
Machine::get_vmctx_reg(),
vm::Ctx::offset_interrupt_signal_mem() as i32,
),
Location::GPR(GPR::RAX),
);
self.fsm.loop_offsets.insert(
a.get_offset().0,
OffsetInfo {
end_offset: a.get_offset().0 + 1,
activate_offset,
diff_id: state_diff_id,
},
);
self.fsm.wasm_function_header_target_offset = Some(SuspendOffset::Loop(a.get_offset().0));
a.emit_mov(
Size::S64,
Location::Memory(GPR::RAX, 0),
Location::GPR(GPR::RAX),
);
if self.config.full_preemption {
a.emit_mov(
Size::S64,
Location::Memory(
Machine::get_vmctx_reg(),
vm::Ctx::offset_interrupt_signal_mem() as i32,
),
Location::GPR(GPR::RAX),
);
self.fsm.loop_offsets.insert(
a.get_offset().0,
OffsetInfo {
end_offset: a.get_offset().0 + 1,
activate_offset,
diff_id: state_diff_id,
},
);
self.fsm.wasm_function_header_target_offset =
Some(SuspendOffset::Loop(a.get_offset().0));
a.emit_mov(
Size::S64,
Location::Memory(GPR::RAX, 0),
Location::GPR(GPR::RAX),
);
}
if self.machine.state.wasm_inst_offset != usize::MAX {
return Err(CodegenError {
@ -6320,10 +6322,10 @@ impl FunctionCodeGenerator<CodegenError> for X64FunctionCode {
Self::mark_range_with_exception_code(
a,
self.exception_table.as_mut().unwrap(),
ExceptionCode::Memory,
ExceptionCode::CallIndirectOOB,
|a| a.emit_conditional_trap(Condition::BelowEqual),
);
a.emit_mov(Size::S64, func_index, Location::GPR(table_count));
a.emit_mov(Size::S32, func_index, Location::GPR(table_count));
a.emit_imul_imm32_gpr64(vm::Anyfunc::size() as u32, table_count);
a.emit_add(
Size::S64,
@ -6351,7 +6353,7 @@ impl FunctionCodeGenerator<CodegenError> for X64FunctionCode {
Self::mark_range_with_exception_code(
a,
self.exception_table.as_mut().unwrap(),
ExceptionCode::Memory,
ExceptionCode::IncorrectCallIndirectSignature,
|a| a.emit_conditional_trap(Condition::NotEqual),
);
@ -6575,31 +6577,33 @@ impl FunctionCodeGenerator<CodegenError> for X64FunctionCode {
a.emit_label(label);
// Check interrupt signal without branching
a.emit_mov(
Size::S64,
Location::Memory(
Machine::get_vmctx_reg(),
vm::Ctx::offset_interrupt_signal_mem() as i32,
),
Location::GPR(GPR::RAX),
);
self.fsm.loop_offsets.insert(
a.get_offset().0,
OffsetInfo {
end_offset: a.get_offset().0 + 1,
activate_offset,
diff_id: state_diff_id,
},
);
self.fsm.wasm_offset_to_target_offset.insert(
self.machine.state.wasm_inst_offset,
SuspendOffset::Loop(a.get_offset().0),
);
a.emit_mov(
Size::S64,
Location::Memory(GPR::RAX, 0),
Location::GPR(GPR::RAX),
);
if self.config.full_preemption {
a.emit_mov(
Size::S64,
Location::Memory(
Machine::get_vmctx_reg(),
vm::Ctx::offset_interrupt_signal_mem() as i32,
),
Location::GPR(GPR::RAX),
);
self.fsm.loop_offsets.insert(
a.get_offset().0,
OffsetInfo {
end_offset: a.get_offset().0 + 1,
activate_offset,
diff_id: state_diff_id,
},
);
self.fsm.wasm_offset_to_target_offset.insert(
self.machine.state.wasm_inst_offset,
SuspendOffset::Loop(a.get_offset().0),
);
a.emit_mov(
Size::S64,
Location::Memory(GPR::RAX, 0),
Location::GPR(GPR::RAX),
);
}
}
Operator::Nop => {}
Operator::MemorySize { reserved } => {

View File

@ -12,11 +12,13 @@
#![doc(html_logo_url = "https://avatars3.githubusercontent.com/u/44205449?s=200&v=4")]
#[cfg(not(any(
all(target_os = "freebsd", target_arch = "x86_64"),
all(target_os = "freebsd", target_arch = "aarch64"),
all(target_os = "macos", target_arch = "x86_64"),
all(target_os = "linux", target_arch = "x86_64"),
all(target_os = "linux", target_arch = "aarch64"),
)))]
compile_error!("This crate doesn't yet support compiling on operating systems other than linux and macos and architectures other than x86_64");
compile_error!("This crate doesn't yet support compiling on operating systems other than FreeBSD, linux and macos and architectures other than x86_64");
extern crate dynasmrt;
@ -38,7 +40,6 @@ extern crate smallvec;
mod codegen_x64;
mod emitter_x64;
mod machine;
pub mod protect_unix;
#[cfg(target_arch = "aarch64")]
mod translator_aarch64;

View File

@ -1,53 +0,0 @@
//! Installing signal handlers allows us to handle traps and out-of-bounds memory
//! accesses that occur when runniing WebAssembly.
//!
//! This code is inspired by: https://github.com/pepyakin/wasmtime/commit/625a2b6c0815b21996e111da51b9664feb174622
//!
//! When a WebAssembly module triggers any traps, we perform recovery here.
//!
//! This module uses TLS (thread-local storage) to track recovery information. Since the four signals we're handling
//! are very special, the async signal unsafety of Rust's TLS implementation generally does not affect the correctness here
//! unless you have memory unsafety elsewhere in your code.
//!
use std::any::Any;
use std::cell::Cell;
use wasmer_runtime_core::codegen::BreakpointMap;
use wasmer_runtime_core::fault::{begin_unsafe_unwind, catch_unsafe_unwind, ensure_sighandler};
use wasmer_runtime_core::typed_func::WasmTrapInfo;
thread_local! {
pub static TRAP_EARLY_DATA: Cell<Option<Box<dyn Any + Send>>> = Cell::new(None);
}
pub unsafe fn trigger_trap() -> ! {
begin_unsafe_unwind(Box::new(()));
}
pub enum CallProtError {
Trap(WasmTrapInfo),
Error(Box<dyn Any + Send>),
}
pub fn call_protected<T>(
f: impl FnOnce() -> T,
breakpoints: Option<BreakpointMap>,
) -> Result<T, CallProtError> {
ensure_sighandler();
unsafe {
let ret = catch_unsafe_unwind(|| f(), breakpoints);
match ret {
Ok(x) => Ok(x),
Err(e) => {
if let Some(data) = TRAP_EARLY_DATA.with(|cell| cell.replace(None)) {
Err(CallProtError::Error(data))
} else {
Err(CallProtError::Error(e))
}
}
}
}
}
pub unsafe fn throw(payload: Box<dyn Any + Send>) -> ! {
begin_unsafe_unwind(payload);
}

33
lib/spectests/spectests/wasmer.wast vendored Normal file
View File

@ -0,0 +1,33 @@
;; Wasmer-specific tests.
(module
;; Auxiliary definitions
(type $out-i32 (func (result i32)))
(func $const-i32 (type $out-i32) (i32.const 0x132))
(table funcref
(elem
$const-i32
)
)
;; https://github.com/wasmerio/wasmer/pull/1191
(func (export "call-indirect-from-spilled-stack") (result i32)
(i64.add (i64.const 0) (i64.const 0))
(i64.add (i64.const 0) (i64.const 0))
(i64.add (i64.const 0) (i64.const 0))
(i64.add (i64.const 0) (i64.const 0))
(i64.add (i64.const 0) (i64.const 0))
(i64.add (i64.const 0) (i64.const 0))
(i64.add (i64.const 0) (i64.const 0))
(i64.add (i64.const 0) (i64.const 0))
(i64.add (i64.const 0) (i64.const 0))
(i64.add (i64.const 0x100000000) (i64.const 0))
(i32.wrap_i64)
(call_indirect (type $out-i32))
(return)
)
)
(assert_return (invoke "call-indirect-from-spilled-stack") (i32.const 0x132))

View File

@ -3,7 +3,7 @@ mod tests {
use wabt::wat2wasm;
use wasmer_runtime::{
error::{CallError, RuntimeError},
ImportObject,
ExceptionCode, ImportObject,
};
// The semantics of stack overflow are documented at:
@ -29,9 +29,9 @@ mod tests {
match result {
Err(err) => match err {
CallError::Runtime(RuntimeError::Trap { msg }) => {
assert!(!msg.contains("segmentation violation"));
assert!(!msg.contains("bus error"));
CallError::Runtime(RuntimeError(e)) => {
e.downcast::<ExceptionCode>()
.expect("expecting exception code");
}
_ => unimplemented!(),
},

View File

@ -674,54 +674,41 @@ mod tests {
let call_result = maybe_call_result.unwrap();
use wasmer_runtime::error::{CallError, RuntimeError};
match call_result {
Err(e) => {
match e {
CallError::Resolve(_) => {
Err(e) => match e {
CallError::Resolve(_) => {
test_report.add_failure(
SpecFailure {
file: filename.to_string(),
line,
kind: format!("{}", "AssertTrap"),
message: format!("expected trap, got {:?}", e),
},
&test_key,
excludes,
line,
);
}
CallError::Runtime(RuntimeError(e)) => {
use wasmer_runtime::ExceptionCode;
if let Some(_) = e.downcast_ref::<ExceptionCode>() {
test_report.count_passed();
} else {
test_report.add_failure(
SpecFailure {
file: filename.to_string(),
line,
kind: format!("{}", "AssertTrap"),
message: format!("expected trap, got {:?}", e),
message: format!(
"expected trap, got RuntimeError"
),
},
&test_key,
excludes,
line,
);
}
CallError::Runtime(r) => {
match r {
RuntimeError::Trap { .. } => {
// TODO assert message?
test_report.count_passed()
}
RuntimeError::Error { ref data } => {
use wasmer_runtime::ExceptionCode;
if let Some(_) =
data.downcast_ref::<ExceptionCode>()
{
test_report.count_passed();
} else {
test_report.add_failure(
SpecFailure {
file: filename.to_string(),
line,
kind: format!("{}", "AssertTrap"),
message: format!(
"expected trap, got Runtime:Error {:?}",
r
),
},
&test_key,
excludes,
line,
);
}
}
}
}
}
}
},
Ok(values) => {
test_report.add_failure(
SpecFailure {

View File

@ -0,0 +1,15 @@
// !!! THIS IS A GENERATED FILE !!!
// ANY MANUAL EDITS MAY BE OVERWRITTEN AT ANY TIME
// Files autogenerated with cargo build (build/wasitests.rs).
#[test]
fn test_isatty() {
assert_wasi_output!(
"../../wasitests/isatty.wasm",
"isatty",
vec![],
vec![],
vec![],
"../../wasitests/isatty.out"
);
}

View File

@ -18,6 +18,7 @@ mod file_metadata;
mod fs_sandbox_test;
mod fseek;
mod hello;
mod isatty;
mod mapdir;
mod path_link;
mod path_rename;

View File

@ -0,0 +1,3 @@
stdin: 1
stdout: 1
stderr: 1

View File

@ -0,0 +1,20 @@
// We don't have access to libc, so we just use isatty
// as an external function
// use libc::isatty;
extern "C" {
pub fn isatty(fd: i32) -> i32;
}
fn main() {
#[cfg(target = "wasi")] {
println!("stdin: {}", unsafe { isatty(0) });
println!("stdout: {}", unsafe { isatty(1) });
println!("stderr: {}", unsafe { isatty(2) });
}
#[cfg(not(target = "wasi"))] {
println!("stdin: 1");
println!("stdout: 1");
println!("stderr: 1");
}
}

Binary file not shown.

View File

@ -5,7 +5,8 @@
unused_mut,
unused_variables,
unused_unsafe,
unreachable_patterns
unreachable_patterns,
clippy::missing_safety_doc
)]
#![doc(html_favicon_url = "https://wasmer.io/static/icons/favicon.ico")]
#![doc(html_logo_url = "https://avatars3.githubusercontent.com/u/44205449?s=200&v=4")]
@ -43,7 +44,7 @@ pub use self::utils::{get_wasi_version, is_wasi_module, WasiVersion};
use wasmer_runtime_core::{func, import::ImportObject, imports};
/// This is returned in the Box<dyn Any> RuntimeError::Error variant.
/// This is returned in `RuntimeError`.
/// Use `downcast` or `downcast_ref` to retrieve the `ExitCode`.
pub struct ExitCode {
pub code: syscalls::types::__wasi_exitcode_t,

View File

@ -1,3 +1,7 @@
//! Macros to simplify some common WASI-specific tasks.
/// Like the `try!` macro or `?` syntax: returns the value if the computation
/// succeeded or returns the error value.
macro_rules! wasi_try {
($expr:expr) => {{
let res: Result<_, crate::syscalls::types::__wasi_errno_t> = $expr;
@ -18,6 +22,8 @@ macro_rules! wasi_try {
}};
}
/// Reads a string from Wasm memory and returns the invalid argument error
/// code if it fails.
macro_rules! get_input_str {
($memory:expr, $data:expr, $len:expr) => {{
wasi_try!($data.get_utf8_string($memory, $len), __WASI_EINVAL)

View File

@ -36,7 +36,7 @@ use wasmer_runtime_core::vm::Ctx;
/// the fd value of the virtual root
pub const VIRTUAL_ROOT_FD: __wasi_fd_t = 3;
/// all the rights enabled
pub const ALL_RIGHTS: __wasi_rights_t = 0x1FFFFFFF;
pub const ALL_RIGHTS: __wasi_rights_t = 0x1FFF_FFFF;
const STDIN_DEFAULT_RIGHTS: __wasi_rights_t = __WASI_RIGHT_FD_DATASYNC
| __WASI_RIGHT_FD_READ
| __WASI_RIGHT_FD_SYNC
@ -52,7 +52,10 @@ const STDOUT_DEFAULT_RIGHTS: __wasi_rights_t = __WASI_RIGHT_FD_DATASYNC
const STDERR_DEFAULT_RIGHTS: __wasi_rights_t = STDOUT_DEFAULT_RIGHTS;
/// Get WasiState from a Ctx
/// This function is unsafe because it must be called on a WASI Ctx
///
/// # Safety
/// - This function must be called on a `Ctx` that was created with `WasiState`
/// in the data field
pub unsafe fn get_wasi_state(ctx: &mut Ctx) -> &mut WasiState {
&mut *(ctx.data as *mut WasiState)
}
@ -186,7 +189,7 @@ impl WasiFs {
for dir in preopened_dirs {
debug!("Attempting to preopen {}", &dir.to_string_lossy());
// TODO: think about this
let default_rights = 0x1FFFFFFF; // all rights
let default_rights = ALL_RIGHTS;
let cur_dir_metadata = dir.metadata().map_err(|e| {
format!(
"Could not get metadata for file {:?}: {}",
@ -236,7 +239,7 @@ impl WasiFs {
for (alias, real_dir) in mapped_dirs {
debug!("Attempting to open {:?} at {}", real_dir, alias);
// TODO: think about this
let default_rights = 0x1FFFFFFF; // all rights
let default_rights = ALL_RIGHTS;
let cur_dir_metadata = real_dir.metadata().map_err(|e| {
format!(
"Could not get metadata for file {:?}: {}",
@ -428,7 +431,7 @@ impl WasiFs {
// create virtual root
let root_inode = {
let all_rights = 0x1FFFFFFF;
let all_rights = ALL_RIGHTS;
// TODO: make this a list of positive rigths instead of negative ones
// root gets all right for now
let root_rights = all_rights
@ -525,10 +528,15 @@ impl WasiFs {
next
}
/// like create dir all, but it also opens it
/// This function is like create dir all, but it also opens it.
/// Function is unsafe because it may break invariants and hasn't been tested.
/// This is an experimental function and may be removed
// dead code because this is an API for external use
///
/// # Safety
/// - Virtual directories created with this function must not conflict with
/// the standard operation of the WASI filesystem. This is vague and
/// unlikely in pratice. Join the discussion at https://github.com/wasmerio/wasmer/issues/1219
/// for what the newer, safer WASI FS APIs should look like.
#[allow(dead_code)]
pub unsafe fn open_dir_all(
&mut self,
@ -1045,12 +1053,28 @@ impl WasiFs {
pub fn fdstat(&self, fd: __wasi_fd_t) -> Result<__wasi_fdstat_t, __wasi_errno_t> {
match fd {
__WASI_STDOUT_FILENO => {
__WASI_STDIN_FILENO => {
return Ok(__wasi_fdstat_t {
fs_filetype: __WASI_FILETYPE_CHARACTER_DEVICE,
fs_flags: 0,
fs_rights_base: ALL_RIGHTS,
fs_rights_inheriting: ALL_RIGHTS,
fs_rights_base: STDIN_DEFAULT_RIGHTS,
fs_rights_inheriting: 0,
})
}
__WASI_STDOUT_FILENO => {
return Ok(__wasi_fdstat_t {
fs_filetype: __WASI_FILETYPE_CHARACTER_DEVICE,
fs_flags: __WASI_FDFLAG_APPEND,
fs_rights_base: STDOUT_DEFAULT_RIGHTS,
fs_rights_inheriting: 0,
})
}
__WASI_STDERR_FILENO => {
return Ok(__wasi_fdstat_t {
fs_filetype: __WASI_FILETYPE_CHARACTER_DEVICE,
fs_flags: __WASI_FDFLAG_APPEND,
fs_rights_base: STDERR_DEFAULT_RIGHTS,
fs_rights_inheriting: 0,
})
}
_ => (),
@ -1145,7 +1169,7 @@ impl WasiFs {
stat.st_ino = self.get_next_inode_index();
Ok(self.inodes.insert(InodeVal {
stat: stat,
stat,
is_preopened,
name,
kind,
@ -1194,10 +1218,14 @@ impl WasiFs {
Ok(idx)
}
/// This function is unsafe because it's the caller's responsibility to ensure that
/// all refences to the given inode have been removed from the filesystem
/// Low level function to remove an inode, that is it deletes the WASI FS's
/// knowledge of a file.
///
/// returns the inode if it existed and was removed
/// This function returns the inode if it existed and was removed.
///
/// # Safety
/// - The caller must ensure that all references to the specified inode have
/// been removed from the filesystem.
pub unsafe fn remove_inode(&mut self, inode: Inode) -> Option<InodeVal> {
self.inodes.remove(inode)
}

View File

@ -434,7 +434,7 @@ impl<'de> Deserialize<'de> for HostFile {
}
}
const FIELDS: &'static [&'static str] = &["host_path", "flags"];
const FIELDS: &[&str] = &["host_path", "flags"];
deserializer.deserialize_struct("HostFile", FIELDS, HostFileVisitor)
}
}

View File

@ -1,6 +1,6 @@
#![allow(unused, clippy::too_many_arguments)]
pub mod types;
#[cfg(any(target_os = "linux", target_os = "macos"))]
#[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "macos"))]
pub mod unix;
#[cfg(any(target_os = "windows"))]
pub mod windows;
@ -23,7 +23,7 @@ use std::convert::{Infallible, TryInto};
use std::io::{self, Read, Seek, Write};
use wasmer_runtime_core::{memory::Memory, vm::Ctx};
#[cfg(any(target_os = "linux", target_os = "macos"))]
#[cfg(any(target_os = "freebsd", target_os = "linux", target_os = "macos"))]
pub use unix::*;
#[cfg(any(target_os = "windows"))]
@ -1221,7 +1221,14 @@ pub fn fd_write(
iovs_len: u32,
nwritten: WasmPtr<u32>,
) -> __wasi_errno_t {
debug!("wasi::fd_write: fd={}", fd);
// If we are writing to stdout or stderr
// we skip debug to not pollute the stdout/err
// and do debugging happily after :)
if fd != __WASI_STDOUT_FILENO && fd != __WASI_STDERR_FILENO {
debug!("wasi::fd_write: fd={}", fd);
} else {
trace!("wasi::fd_write: fd={}", fd);
}
let (memory, state) = get_memory_and_wasi_state(ctx, 0);
let iovs_arr_cell = wasi_try!(iovs.deref(memory, 0, iovs_len));
let nwritten_cell = wasi_try!(nwritten.deref(memory));

View File

@ -1,4 +1,4 @@
#![allow(non_camel_case_types)]
#![allow(non_camel_case_types, clippy::identity_op)]
use crate::ptr::{Array, WasmPtr};
use byteorder::{ReadBytesExt, WriteBytesExt, LE};

View File

@ -30,10 +30,10 @@ pub enum WasiVersion {
}
/// Namespace for the `Snapshot0` version.
const SNAPSHOT0_NAMESPACE: &'static str = "wasi_unstable";
const SNAPSHOT0_NAMESPACE: &str = "wasi_unstable";
/// Namespace for the `Snapshot1` version.
const SNAPSHOT1_NAMESPACE: &'static str = "wasi_snapshot_preview1";
const SNAPSHOT1_NAMESPACE: &str = "wasi_snapshot_preview1";
/// Detect the version of WASI being used based on the import
/// namespaces.

View File

@ -59,6 +59,7 @@ uint8_t callProtected(trampoline_t trampoline,
// install exception handler
if (exceptionHandlerInstalled == FALSE) {
exceptionHandlerInstalled = TRUE;
alreadyHandlingException = FALSE;
handle = AddVectoredExceptionHandler(CALL_FIRST, exceptionHandler);
}
@ -86,4 +87,4 @@ uint8_t callProtected(trampoline_t trampoline,
removeExceptionHandler();
return FALSE;
}
}

View File

@ -486,7 +486,6 @@ fn execute_wasi(
#[cfg(not(feature = "managed"))]
{
use wasmer_runtime::error::RuntimeError;
let result;
#[cfg(unix)]
@ -525,13 +524,8 @@ fn execute_wasi(
}
if let Err(ref err) = result {
match err {
RuntimeError::Trap { msg } => return Err(format!("wasm trap occured: {}", msg)),
RuntimeError::Error { data } => {
if let Some(error_code) = data.downcast_ref::<wasmer_wasi::ExitCode>() {
std::process::exit(error_code.code as i32)
}
}
if let Some(error_code) = err.0.downcast_ref::<wasmer_wasi::ExitCode>() {
std::process::exit(error_code.code as i32)
}
return Err(format!("error: {:?}", err));
}
@ -711,6 +705,10 @@ fn execute_wasm(options: &Run) -> Result<(), String> {
symbol_map: em_symbol_map.clone(),
memory_bound_check_mode: MemoryBoundCheckMode::Disable,
enforce_stack_check: true,
// Kernel loader does not support explicit preemption checkpoints.
full_preemption: false,
track_state,
features: options.features.into_backend_features(),
backend_specific_config,
@ -725,6 +723,11 @@ fn execute_wasm(options: &Run) -> Result<(), String> {
CompilerConfig {
symbol_map: em_symbol_map.clone(),
track_state,
// Enable full preemption if state tracking is enabled.
// Preemption only makes sense with state information.
full_preemption: track_state,
features: options.features.into_backend_features(),
backend_specific_config,
generate_debug_info: options.generate_debug_info,
@ -822,7 +825,7 @@ fn execute_wasm(options: &Run) -> Result<(), String> {
LoaderName::Kernel => Box::new(
instance
.load(::wasmer_kernel_loader::KernelLoader)
.map_err(|e| format!("Can't use the local loader: {:?}", e))?,
.map_err(|e| format!("Can't use the kernel loader: {:?}", e))?,
),
};
println!("{:?}", ins.call(index, &args));