diff --git a/CHANGELOG.md b/CHANGELOG.md index a7e2d4f7891..d1be9801613 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - [#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. diff --git a/Cargo.lock b/Cargo.lock index 87cb795cf5c..bc33f9a4713 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", ] @@ -20,9 +20,18 @@ dependencies = [ [[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" @@ -32,10 +41,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", ] @@ -70,12 +80,12 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "blake3" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "100179f909a27ed067ce4fa6db58f7fa0f67070d7a04d38f040886174b85ef6f" +checksum = "46080006c1505f12f64dd2a09264b343381ed3190fa02c8005d5d662ac571c63" dependencies = [ "arrayref", - "arrayvec", + "arrayvec 0.5.1", "cc", "cfg-if", "constant_time_eq", @@ -85,9 +95,9 @@ dependencies = [ [[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", @@ -127,11 +137,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", ] @@ -230,7 +240,7 @@ dependencies = [ "cranelift-codegen-shared", "cranelift-entity", "log", - "smallvec 1.1.0", + "smallvec 1.2.0", "target-lexicon", "thiserror", ] @@ -332,10 +342,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", ] @@ -362,9 +373,9 @@ dependencies = [ [[package]] name = "csv" -version = "1.1.1" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37519ccdfd73a75821cac9319d4fce15a81b9fcf75f951df5b9988aa3a0af87d" +checksum = "00affe7f6ab566df61b4be3ce8cf16bc2576bca0963ceb0955e45d514bf9a279" dependencies = [ "bstr", "csv-core", @@ -375,9 +386,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", ] @@ -389,7 +400,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]] @@ -411,9 +422,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]] @@ -434,9 +445,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", ] @@ -521,9 +532,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]] @@ -560,9 +571,9 @@ 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", ] @@ -609,9 +620,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", @@ -620,13 +631,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]] @@ -640,9 +651,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" @@ -654,6 +665,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" @@ -675,9 +699,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", ] @@ -705,12 +729,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" @@ -758,6 +779,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" @@ -771,39 +809,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", @@ -811,9 +849,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" @@ -882,7 +920,7 @@ dependencies = [ "cloudabi", "libc", "redox_syscall", - "smallvec 1.1.0", + "smallvec 1.2.0", "winapi", ] @@ -910,27 +948,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", ] @@ -945,9 +983,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", ] @@ -967,7 +1005,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]] @@ -991,9 +1029,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", @@ -1185,9 +1223,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", @@ -1206,9 +1244,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" @@ -1230,13 +1268,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]] @@ -1247,9 +1285,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", ] @@ -1354,16 +1392,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", @@ -1381,9 +1419,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" @@ -1391,6 +1429,12 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" +[[package]] +name = "static_assertions" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3" + [[package]] name = "strsim" version = "0.8.0" @@ -1399,9 +1443,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", @@ -1410,15 +1454,15 @@ 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]] @@ -1440,24 +1484,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]] @@ -1474,7 +1518,7 @@ checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" dependencies = [ "cfg-if", "libc", - "rand 0.7.2", + "rand 0.7.3", "redox_syscall", "remove_dir_all", "winapi", @@ -1491,22 +1535,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]] @@ -1531,9 +1575,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", @@ -1541,9 +1585,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", ] @@ -1573,9 +1617,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]] @@ -1608,6 +1652,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" @@ -1639,9 +1689,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", @@ -1717,7 +1767,7 @@ checksum = "6d2e13201ef9ef527ad30a6bf1b08e3e024a40cf2731f393d80375dc88506207" dependencies = [ "cranelift-codegen", "log", - "smallvec 1.1.0", + "smallvec 1.2.0", "target-lexicon", ] @@ -1769,6 +1819,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" @@ -2000,9 +2057,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", ] @@ -2015,9 +2072,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", diff --git a/Cargo.toml b/Cargo.toml index 8b74cd54c35..25dc8d3220b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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", diff --git a/Makefile b/Makefile index 21ca562fdab..700ae82e4d2 100644 --- a/Makefile +++ b/Makefile @@ -307,7 +307,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 --document-private-items --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 diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b70cda976a9..9e42676cf96 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -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 diff --git a/lib/interface-types/Cargo.toml b/lib/interface-types/Cargo.toml new file mode 100644 index 00000000000..a3d2474796e --- /dev/null +++ b/lib/interface-types/Cargo.toml @@ -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 "] +repository = "https://github.com/wasmerio/wasmer" +edition = "2018" + +[dependencies] +nom = "5.1" \ No newline at end of file diff --git a/lib/interface-types/src/ast.rs b/lib/interface-types/src/ast.rs new file mode 100644 index 00000000000..d8b82f588e7 --- /dev/null +++ b/lib/interface-types/src/ast.rs @@ -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, + + /// The function output types. + pub output_types: Vec, +} + +/// 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, + + /// The function output types. + pub output_types: Vec, +} + +/// 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, +} + +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) -> 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 { + &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, + + /// The function output types. + output_types: Vec, + + /// The instructions of the adapter. + instructions: Vec>, + }, + + /// An adapter for an exported function. + Export { + /// The function name. + name: &'input str, + + /// The function input types. + input_types: Vec, + + /// The function output types. + output_types: Vec, + + /// The instructions of the adapter. + instructions: Vec>, + }, + + /// An adapter for a helper function. + HelperFunction { + /// The helper name. + name: &'input str, + + /// The helper input types. + input_types: Vec, + + /// The helper output types. + output_types: Vec, + + /// The instructions of the adapter. + instructions: Vec>, + }, +} + +/// 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>, + + /// All the types. + pub types: Vec>, + + /// All the imported functions. + pub imports: Vec>, + + /// All the adapters. + pub adapters: Vec>, + + /// All the forwarded functions. + pub forwards: Vec>, +} diff --git a/lib/interface-types/src/decoders/binary.rs b/lib/interface-types/src/decoders/binary.rs new file mode 100644 index 00000000000..270fc13a725 --- /dev/null +++ b/lib/interface-types/src/decoders/binary.rs @@ -0,0 +1,983 @@ +//! 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 for InterfaceType { + type Error = &'static str; + + fn try_from(code: u64) -> Result { + 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 for AdapterKind { + type Error = &'static str; + + fn try_from(code: u8) -> Result { + 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 dbg!(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, 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, 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, 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, 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, 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, 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 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> { + 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, + }, + )) +} + +#[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::(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::(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!(parse::<()>(input), output); + } +} diff --git a/lib/interface-types/src/decoders/mod.rs b/lib/interface-types/src/decoders/mod.rs new file mode 100644 index 00000000000..0ac9d881dd2 --- /dev/null +++ b/lib/interface-types/src/decoders/mod.rs @@ -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; diff --git a/lib/interface-types/src/encoders/mod.rs b/lib/interface-types/src/encoders/mod.rs new file mode 100644 index 00000000000..ae1611e892f --- /dev/null +++ b/lib/interface-types/src/encoders/mod.rs @@ -0,0 +1,5 @@ +//! 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 wat; diff --git a/lib/interface-types/src/encoders/wat.rs b/lib/interface-types/src/encoders/wat.rs new file mode 100644 index 00000000000..08813a27136 --- /dev/null +++ b/lib/interface-types/src/encoders/wat.rs @@ -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 = 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 = 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 = 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 = 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 = 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); + } +} diff --git a/lib/interface-types/src/interpreter/instruction.rs b/lib/interface-types/src/interpreter/instruction.rs new file mode 100644 index 00000000000..5364ac55a45 --- /dev/null +++ b/lib/interface-types/src/interpreter/instruction.rs @@ -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), +} diff --git a/lib/interface-types/src/interpreter/instructions/argument_get.rs b/lib/interface-types/src/interpreter/instructions/argument_get.rs new file mode 100644 index 00000000000..0a679091008 --- /dev/null +++ b/lib/interface-types/src/interpreter/instructions/argument_get.rs @@ -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." + ); +} diff --git a/lib/interface-types/src/interpreter/instructions/call.rs b/lib/interface-types/src/interpreter/instructions/call.rs new file mode 100644 index 00000000000..5fe896bb9da --- /dev/null +++ b/lib/interface-types/src/interpreter/instructions/call.rs @@ -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::>(); + + 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: [], + ); +} diff --git a/lib/interface-types/src/interpreter/instructions/call_export.rs b/lib/interface-types/src/interpreter/instructions/call_export.rs new file mode 100644 index 00000000000..9afe984174c --- /dev/null +++ b/lib/interface-types/src/interpreter/instructions/call_export.rs @@ -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::>(); + + 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: [], + ); +} diff --git a/lib/interface-types/src/interpreter/instructions/mod.rs b/lib/interface-types/src/interpreter/instructions/mod.rs new file mode 100644 index 00000000000..d84d2b47e82 --- /dev/null +++ b/lib/interface-types/src/interpreter/instructions/mod.rs @@ -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, + pub(crate) outputs: Vec, + pub(crate) function: fn(arguments: &[InterfaceValue]) -> Result, ()>, + } + + 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, ()> { + (self.function)(arguments) + } + } + + pub(crate) struct LocalImport { + pub(crate) inputs: Vec, + pub(crate) outputs: Vec, + pub(crate) function: fn(arguments: &[InterfaceValue]) -> Result, ()>, + } + + 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, ()> { + (self.function)(arguments) + } + } + + #[derive(Default, Clone)] + pub(crate) struct MemoryView(Rc>>); + + impl wasm::structures::MemoryView for MemoryView {} + + impl Deref for MemoryView { + type Target = [Cell]; + + 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>) -> Self { + Self { + view: MemoryView(Rc::new(data)), + } + } + } + + impl wasm::structures::Memory for Memory { + fn view(&self) -> MemoryView { + self.view.clone() + } + } + + #[derive(Default)] + pub(crate) struct Instance { + pub(crate) exports: HashMap, + pub(crate) locals_or_imports: HashMap, + 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 for Instance { + fn export(&self, export_name: &str) -> Option<&Export> { + self.exports.get(export_name) + } + + fn local_or_import( + &mut self, + index: I, + ) -> Option<&LocalImport> { + self.locals_or_imports.get(&index.index()) + } + + fn memory(&self, _index: usize) -> Option<&Memory> { + Some(&self.memory) + } + } +} diff --git a/lib/interface-types/src/interpreter/instructions/read_utf8.rs b/lib/interface-types/src/interpreter/instructions/read_utf8.rs new file mode 100644 index 00000000000..a06bc56309f --- /dev/null +++ b/lib/interface-types/src/interpreter/instructions/read_utf8.rs @@ -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 = (&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::>>()), + ..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)."#, + ); +} diff --git a/lib/interface-types/src/interpreter/instructions/write_utf8.rs b/lib/interface-types/src/interpreter/instructions/write_utf8.rs new file mode 100644 index 00000000000..a1e21509f14 --- /dev/null +++ b/lib/interface-types/src/interpreter/instructions/write_utf8.rs @@ -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])."#, + ); +} diff --git a/lib/interface-types/src/interpreter/mod.rs b/lib/interface-types/src/interpreter/mod.rs new file mode 100644 index 00000000000..792c91e8973 --- /dev/null +++ b/lib/interface-types/src/interpreter/mod.rs @@ -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 + 'instance, + MemoryView: wasm::structures::MemoryView, + Instance: wasm::structures::Instance + '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, + + /// 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 = Box< + dyn Fn(&mut Runtime) -> 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 = (&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 +where + Export: wasm::structures::Export, + LocalImport: wasm::structures::LocalImport, + Memory: wasm::structures::Memory, + MemoryView: wasm::structures::MemoryView, + Instance: wasm::structures::Instance, +{ + executable_instructions: + Vec>, +} + +impl + Interpreter +where + Export: wasm::structures::Export, + LocalImport: wasm::structures::LocalImport, + Memory: wasm::structures::Memory, + MemoryView: wasm::structures::MemoryView, + Instance: wasm::structures::Instance, +{ + fn iter( + &self, + ) -> impl Iterator< + Item = &ExecutableInstruction, + > + '_ { + 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, 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` into an `Interpreter`. +impl<'binary_input, Instance, Export, LocalImport, Memory, MemoryView> + TryFrom<&Vec>> + for Interpreter +where + Export: wasm::structures::Export, + LocalImport: wasm::structures::LocalImport, + Memory: wasm::structures::Memory, + MemoryView: wasm::structures::MemoryView, + Instance: wasm::structures::Instance, +{ + type Error = String; + + fn try_from(instructions: &Vec) -> Result { + 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); + } +} diff --git a/lib/interface-types/src/interpreter/stack.rs b/lib/interface-types/src/interpreter/stack.rs new file mode 100644 index 00000000000..7877da5739e --- /dev/null +++ b/lib/interface-types/src/interpreter/stack.rs @@ -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; + + /// 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>; +} + +/// A stack implementation of the `Stackable` trait, based on a vector. +#[derive(Debug, Default)] +pub struct Stack +where + T: Default + Clone, +{ + /// Inner structure holding the items. + inner: Vec, +} + +impl Stack +where + T: Default + Clone, +{ + /// Creates a new empty stack. + pub fn new() -> Self { + Self { + ..Default::default() + } + } +} + +impl Stackable for Stack +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.inner.pop() + } + + fn pop(&mut self, n: usize) -> Option> { + if self.inner.len() < n { + None + } else { + let items = self + .inner + .drain(self.inner.len() - n..) + .rev() + .collect::>(); + + 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); + } +} diff --git a/lib/interface-types/src/interpreter/wasm/mod.rs b/lib/interface-types/src/interpreter/wasm/mod.rs new file mode 100644 index 00000000000..1edccc59268 --- /dev/null +++ b/lib/interface-types/src/interpreter/wasm/mod.rs @@ -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; diff --git a/lib/interface-types/src/interpreter/wasm/structures.rs b/lib/interface-types/src/interpreter/wasm/structures.rs new file mode 100644 index 00000000000..dbeab832fde --- /dev/null +++ b/lib/interface-types/src/interpreter/wasm/structures.rs @@ -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, ()>; +} + +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, ()>; +} + +pub trait MemoryView: Deref]> {} + +pub trait Memory +where + View: MemoryView, +{ + fn view(&self) -> View; +} + +pub trait Instance +where + E: Export, + LI: LocalImport, + M: Memory, + MV: MemoryView, +{ + fn export(&self, export_name: &str) -> Option<&E>; + fn local_or_import(&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, ()> { + 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, ()> { + Err(()) + } +} + +pub(crate) struct EmptyMemoryView; + +impl MemoryView for EmptyMemoryView {} + +impl Deref for EmptyMemoryView { + type Target = [Cell]; + + fn deref(&self) -> &Self::Target { + &[] + } +} + +impl Memory for () { + fn view(&self) -> EmptyMemoryView { + EmptyMemoryView + } +} + +impl Instance for () +where + E: Export, + LI: LocalImport, + M: Memory, + MV: MemoryView, +{ + fn export(&self, _export_name: &str) -> Option<&E> { + None + } + + fn memory(&self, _: usize) -> Option<&M> { + None + } + + fn local_or_import(&mut self, _index: I) -> Option<&LI> { + None + } +} diff --git a/lib/interface-types/src/interpreter/wasm/values.rs b/lib/interface-types/src/interpreter/wasm/values.rs new file mode 100644 index 00000000000..f50763fc086 --- /dev/null +++ b/lib/interface-types/src/interpreter/wasm/values.rs @@ -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 { + 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); diff --git a/lib/interface-types/src/lib.rs b/lib/interface-types/src/lib.rs new file mode 100644 index 00000000000..f4deaba974c --- /dev/null +++ b/lib/interface-types/src/lib.rs @@ -0,0 +1,55 @@ +//! 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; + +pub use decoders::binary::parse as parse_binary; diff --git a/lib/interface-types/src/macros.rs b/lib/interface-types/src/macros.rs new file mode 100644 index 00000000000..735cecd3cd1 --- /dev/null +++ b/lib/interface-types/src/macros.rs @@ -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( + $($argument_name: $argument_type),* + ) -> ExecutableInstruction + where + Export: wasm::structures::Export, + LocalImport: wasm::structures::LocalImport, + Memory: wasm::structures::Memory, + MemoryView: wasm::structures::MemoryView, + Instance: wasm::structures::Instance, + { + 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 = + (&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 = + (&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)); + } + }; +} diff --git a/lib/runtime-core-tests/tests/exception_handling.rs b/lib/runtime-core-tests/tests/exception_handling.rs new file mode 100644 index 00000000000..bf7a21a0492 --- /dev/null +++ b/lib/runtime-core-tests/tests/exception_handling.rs @@ -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()); + } +} diff --git a/lib/singlepass-backend/src/codegen_x64.rs b/lib/singlepass-backend/src/codegen_x64.rs index 702e0d4cbc3..4c15a688c26 100644 --- a/lib/singlepass-backend/src/codegen_x64.rs +++ b/lib/singlepass-backend/src/codegen_x64.rs @@ -6316,7 +6316,7 @@ impl FunctionCodeGenerator for X64FunctionCode { 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, diff --git a/lib/spectests/spectests/wasmer.wast b/lib/spectests/spectests/wasmer.wast new file mode 100644 index 00000000000..cf3841f9bfc --- /dev/null +++ b/lib/spectests/spectests/wasmer.wast @@ -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)) \ No newline at end of file diff --git a/lib/win-exception-handler/exception_handling/exception_handling.c b/lib/win-exception-handler/exception_handling/exception_handling.c index c3ecbab1d74..0dc133e2848 100644 --- a/lib/win-exception-handler/exception_handling/exception_handling.c +++ b/lib/win-exception-handler/exception_handling/exception_handling.c @@ -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; -} \ No newline at end of file +}