From 64b2df2d04563366bb15ebb89872362ce9c79390 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Fri, 1 Oct 2021 16:27:31 +0100 Subject: [PATCH 1/8] Initial preview of Wasmer support Note that the API for the generated bindings is still evolving and may be subjected to breaking changes. --- Cargo.lock | 1191 ++++++++++-- Cargo.toml | 1 + crates/gen-wasmer/Cargo.toml | 25 + crates/gen-wasmer/build.rs | 4 + crates/gen-wasmer/src/lib.rs | 2319 ++++++++++++++++++++++++ crates/gen-wasmer/tests/codegen.rs | 111 ++ crates/gen-wasmer/tests/runtime.rs | 23 + crates/test-helpers/Cargo.toml | 1 + crates/test-helpers/src/lib.rs | 73 + crates/wasmer-impl/Cargo.toml | 21 + crates/wasmer-impl/src/lib.rs | 173 ++ crates/wasmer/Cargo.toml | 27 + crates/wasmer/src/error.rs | 40 + crates/wasmer/src/exports.rs | 90 + crates/wasmer/src/imports.rs | 413 +++++ crates/wasmer/src/le.rs | 194 ++ crates/wasmer/src/lib.rs | 232 +++ crates/wasmer/src/region.rs | 312 ++++ crates/wasmer/src/slab.rs | 72 + crates/wasmer/src/table.rs | 144 ++ crates/wit-bindgen-demo/Cargo.toml | 1 + crates/wit-bindgen-demo/demo.wit | 4 + crates/wit-bindgen-demo/index.html | 17 + crates/wit-bindgen-demo/main.ts | 24 + crates/wit-bindgen-demo/src/lib.rs | 18 + src/bin/wit-bindgen.rs | 7 + tests/runtime/buffers/host-wasmer.rs | 195 ++ tests/runtime/flavorful/host-wasmer.rs | 148 ++ tests/runtime/handles/host-wasmer.rs | 150 ++ tests/runtime/invalid/host-wasmer.rs | 68 + tests/runtime/lists/host-wasmer.rs | 156 ++ tests/runtime/numbers/host-wasmer.rs | 127 ++ tests/runtime/records/host-wasmer.rs | 94 + tests/runtime/smoke/host-wasmer.rs | 39 + tests/runtime/variants/host-wasmer.rs | 114 ++ 35 files changed, 6436 insertions(+), 192 deletions(-) create mode 100644 crates/gen-wasmer/Cargo.toml create mode 100644 crates/gen-wasmer/build.rs create mode 100644 crates/gen-wasmer/src/lib.rs create mode 100644 crates/gen-wasmer/tests/codegen.rs create mode 100644 crates/gen-wasmer/tests/runtime.rs create mode 100644 crates/wasmer-impl/Cargo.toml create mode 100644 crates/wasmer-impl/src/lib.rs create mode 100644 crates/wasmer/Cargo.toml create mode 100644 crates/wasmer/src/error.rs create mode 100644 crates/wasmer/src/exports.rs create mode 100644 crates/wasmer/src/imports.rs create mode 100644 crates/wasmer/src/le.rs create mode 100644 crates/wasmer/src/lib.rs create mode 100644 crates/wasmer/src/region.rs create mode 100644 crates/wasmer/src/slab.rs create mode 100644 crates/wasmer/src/table.rs create mode 100644 tests/runtime/buffers/host-wasmer.rs create mode 100644 tests/runtime/flavorful/host-wasmer.rs create mode 100644 tests/runtime/handles/host-wasmer.rs create mode 100644 tests/runtime/invalid/host-wasmer.rs create mode 100644 tests/runtime/lists/host-wasmer.rs create mode 100644 tests/runtime/numbers/host-wasmer.rs create mode 100644 tests/runtime/records/host-wasmer.rs create mode 100644 tests/runtime/smoke/host-wasmer.rs create mode 100644 tests/runtime/variants/host-wasmer.rs diff --git a/Cargo.lock b/Cargo.lock index abcd97e55..5846332b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,15 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "addr2line" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61f2b7f93d2c7d2b08263acaa4a363b3e276806c68af6134c44f523bf1aacd" -dependencies = [ - "gimli 0.25.0", -] - [[package]] name = "addr2line" version = "0.17.0" @@ -26,6 +17,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "aho-corasick" version = "0.7.18" @@ -41,15 +43,6 @@ version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec8ad6edb4840b78c5c3d88de606b22252d552b55f3a4699fbb10fc070ec3049" -[[package]] -name = "ansi_term" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -dependencies = [ - "winapi", -] - [[package]] name = "ansi_term" version = "0.12.1" @@ -61,15 +54,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.44" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1" +checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203" [[package]] name = "async-trait" -version = "0.1.51" +version = "0.1.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e" +checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" dependencies = [ "proc-macro2", "quote", @@ -95,19 +88,25 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "backtrace" -version = "0.3.61" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a905d892734eea339e896738c14b9afce22b5318f64b951e70bf3844419b01" +checksum = "321629d8ba6513061f26707241fa9bc89524ff1cd7a915a97ef0c62c666ce1b6" dependencies = [ - "addr2line 0.16.0", + "addr2line", "cc", - "cfg-if", + "cfg-if 1.0.0", "libc", "miniz_oxide", - "object 0.26.2", + "object", "rustc-demangle", ] +[[package]] +name = "base-x" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" + [[package]] name = "base64" version = "0.13.0" @@ -140,13 +139,46 @@ dependencies = [ [[package]] name = "bstr" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" dependencies = [ "memchr", ] +[[package]] +name = "bumpalo" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" + +[[package]] +name = "bytecheck" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "314889ea31cda264cb7c3d6e6e5c9415a987ecb0e72c17c00d36fbb881d34abe" +dependencies = [ + "bytecheck_derive", + "ptr_meta", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a2b3b92c135dae665a6f760205b89187638e83bed17ef3e44e83c712cf30600" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + [[package]] name = "cap-fs-ext" version = "0.21.1" @@ -156,7 +188,7 @@ dependencies = [ "cap-primitives", "cap-std", "io-lifetimes", - "rustc_version", + "rustc_version 0.4.0", "winapi", ] @@ -173,7 +205,7 @@ dependencies = [ "io-lifetimes", "ipnet", "maybe-owned", - "rustc_version", + "rustc_version 0.4.0", "rustix", "winapi", "winapi-util", @@ -200,7 +232,7 @@ dependencies = [ "io-extras", "io-lifetimes", "ipnet", - "rustc_version", + "rustc_version 0.4.0", "rustix", ] @@ -218,13 +250,19 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.70" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" +checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" dependencies = [ "jobserver", ] +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cfg-if" version = "1.0.0" @@ -233,26 +271,32 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "2.33.3" +version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ - "ansi_term 0.11.0", + "ansi_term", "atty", "bitflags", - "strsim", + "strsim 0.8.0", "textwrap", "unicode-width", "vec_map", ] +[[package]] +name = "const_fn" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f92cfa0fd5690b3cf8c1ef2cabbd9b7ef22fa53cf5e1f92b05103f6d5d1cf6e7" + [[package]] name = "cpp_demangle" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea47428dc9d2237f3c6bc134472edfd63ebba0af932e783506dcfd66f10d18a" +checksum = "eeaa953eaad386a53111e47172c2fedba671e5684c8dd601a5f474f4f118710f" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -264,13 +308,39 @@ dependencies = [ "libc", ] +[[package]] +name = "cranelift-bforest" +version = "0.76.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e6bea67967505247f54fa2c85cf4f6e0e31c4e5692c9b70e4ae58e339067333" +dependencies = [ + "cranelift-entity 0.76.0", +] + [[package]] name = "cranelift-bforest" version = "0.79.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0fb5e025141af5b9cbfff4351dc393596d017725f126c954bf472ce78dbba6b" dependencies = [ - "cranelift-entity", + "cranelift-entity 0.79.0", +] + +[[package]] +name = "cranelift-codegen" +version = "0.76.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48194035d2752bdd5bdae429e3ab88676e95f52a2b1355a5d4e809f9e39b1d74" +dependencies = [ + "cranelift-bforest 0.76.0", + "cranelift-codegen-meta 0.76.0", + "cranelift-codegen-shared 0.76.0", + "cranelift-entity 0.76.0", + "gimli 0.25.0", + "log", + "regalloc 0.0.31", + "smallvec", + "target-lexicon", ] [[package]] @@ -279,33 +349,55 @@ version = "0.79.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a278c67cc48d0e8ff2275fb6fc31527def4be8f3d81640ecc8cd005a3aa45ded" dependencies = [ - "cranelift-bforest", - "cranelift-codegen-meta", - "cranelift-codegen-shared", - "cranelift-entity", + "cranelift-bforest 0.79.0", + "cranelift-codegen-meta 0.79.0", + "cranelift-codegen-shared 0.79.0", + "cranelift-entity 0.79.0", "gimli 0.26.1", "log", - "regalloc", + "regalloc 0.0.33", "sha2", "smallvec", "target-lexicon", ] +[[package]] +name = "cranelift-codegen-meta" +version = "0.76.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976efb22fcab4f2cd6bd4e9913764616a54d895c1a23530128d04e03633c555f" +dependencies = [ + "cranelift-codegen-shared 0.76.0", + "cranelift-entity 0.76.0", +] + [[package]] name = "cranelift-codegen-meta" version = "0.79.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28274c1916c931c5603d94c5479d2ddacaaa574d298814ac1c99964ce92cbe85" dependencies = [ - "cranelift-codegen-shared", + "cranelift-codegen-shared 0.79.0", ] +[[package]] +name = "cranelift-codegen-shared" +version = "0.76.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dabb5fe66e04d4652e434195b45ae65b5c8172d520247b8f66d8df42b2b45dc" + [[package]] name = "cranelift-codegen-shared" version = "0.79.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5411cf49ab440b749d4da5129dfc45caf6e5fb7b2742b1fe1a421964fda2ee88" +[[package]] +name = "cranelift-entity" +version = "0.76.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3329733e4d4b8e91c809efcaa4faee80bf66f20164e3dd16d707346bd3494799" + [[package]] name = "cranelift-entity" version = "0.79.0" @@ -315,13 +407,25 @@ dependencies = [ "serde", ] +[[package]] +name = "cranelift-frontend" +version = "0.76.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "279afcc0d3e651b773f94837c3d581177b348c8d69e928104b2e9fccb226f921" +dependencies = [ + "cranelift-codegen 0.76.0", + "log", + "smallvec", + "target-lexicon", +] + [[package]] name = "cranelift-frontend" version = "0.79.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "544605d400710bd9c89924050b30c2e0222a387a5a8b5f04da9a9fdcbd8656a5" dependencies = [ - "cranelift-codegen", + "cranelift-codegen 0.79.0", "log", "smallvec", "target-lexicon", @@ -333,7 +437,7 @@ version = "0.79.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f8839befb64f7a39cb855241ae2c8eb74cea27c97fff2a007075fdb8a7f7d4" dependencies = [ - "cranelift-codegen", + "cranelift-codegen 0.79.0", "libc", "target-lexicon", ] @@ -344,9 +448,9 @@ version = "0.79.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80c9e14062c6a1cd2367dd30ea8945976639d5fe2062da8bdd40ada9ce3cb82e" dependencies = [ - "cranelift-codegen", - "cranelift-entity", - "cranelift-frontend", + "cranelift-codegen 0.79.0", + "cranelift-entity 0.79.0", + "cranelift-frontend 0.79.0", "itertools", "log", "smallvec", @@ -356,11 +460,11 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +checksum = "738c290dfaea84fc1ca15ad9c168d083b05a714e1efddd8edaab678dc28d2836" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -369,7 +473,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crossbeam-utils", ] @@ -379,7 +483,7 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crossbeam-epoch", "crossbeam-utils", ] @@ -390,7 +494,7 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crossbeam-utils", "lazy_static", "memoffset", @@ -403,7 +507,7 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "lazy_static", ] @@ -417,6 +521,41 @@ dependencies = [ "syn", ] +[[package]] +name = "darling" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d720b8683f8dd83c65155f0530560cba68cd2bf395f6513a483caee57ff7f4" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a340f241d2ceed1deb47ae36c4144b2707ec7dd0b649f894cb39bb595986324" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c41b3b7352feb3211a0d743dc5700a4e3b60f51bd2b368892d1e0f9a95f44b" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "diff" version = "0.1.12" @@ -438,7 +577,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "dirs-sys-next", ] @@ -448,7 +587,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "dirs-sys-next", ] @@ -463,12 +602,39 @@ dependencies = [ "winapi", ] +[[package]] +name = "discard" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" + [[package]] name = "either" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "enumset" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6216d2c19a6fb5f29d1ada1dc7bc4367a8cbf0fa4af5cf12e07b5bbdde6b5b2c" +dependencies = [ + "enumset_derive", +] + +[[package]] +name = "enumset_derive" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6451128aa6655d880755345d085494cf7561a6bee7c8dc821e5d77e6d267ecd4" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "env_logger" version = "0.7.1" @@ -508,11 +674,11 @@ dependencies = [ [[package]] name = "errno-dragonfly" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" dependencies = [ - "gcc", + "cc", "libc", ] @@ -538,7 +704,7 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "redox_syscall", "winapi", @@ -569,18 +735,16 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.17" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d" +checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7" [[package]] name = "futures-macro" -version = "0.3.17" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18e4a4b95cea4b4ccbcf1c5675ca7c4ee4e9e75eb79944d07defde18068f79bb" +checksum = "6dbd947adfffb0efc70599b3ddcf7b5597bb5fa9e245eb99f62b3a5f7bb8bd3c" dependencies = [ - "autocfg", - "proc-macro-hack", "proc-macro2", "quote", "syn", @@ -588,32 +752,32 @@ dependencies = [ [[package]] name = "futures-task" -version = "0.3.17" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99" +checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72" [[package]] name = "futures-util" -version = "0.3.17" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481" +checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164" dependencies = [ - "autocfg", "futures-core", "futures-macro", "futures-task", "pin-project-lite", "pin-utils", - "proc-macro-hack", - "proc-macro-nested", "slab", ] [[package]] -name = "gcc" -version = "0.3.55" +name = "generational-arena" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" +checksum = "8e1d3b771574f62d0548cee0ad9057857e9fc25d7a3335f140c84f6acd0bf601" +dependencies = [ + "cfg-if 0.1.10", +] [[package]] name = "generic-array" @@ -631,7 +795,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "wasi", ] @@ -641,6 +805,11 @@ name = "gimli" version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" +dependencies = [ + "fallible-iterator", + "indexmap", + "stable_deref_trait", +] [[package]] name = "gimli" @@ -671,6 +840,9 @@ name = "hashbrown" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] [[package]] name = "heck" @@ -711,6 +883,12 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "ignore" version = "0.4.18" @@ -747,7 +925,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a1d9a66d8b0312e3601a04a2dcf8f0ddd873319560ddeabe2110fa1e5af781a" dependencies = [ "io-lifetimes", - "rustc_version", + "rustc_version 0.4.0", "winapi", ] @@ -757,7 +935,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "278e90d6f8a6c76a8334b336e306efa3c5f2b604048cbfd486d6f49878e3af14" dependencies = [ - "rustc_version", + "rustc_version 0.4.0", "winapi", ] @@ -769,9 +947,9 @@ checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" [[package]] name = "itertools" -version = "0.10.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" dependencies = [ "either", ] @@ -782,6 +960,12 @@ version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" +[[package]] +name = "itoa" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" + [[package]] name = "jobserver" version = "0.1.24" @@ -791,6 +975,15 @@ dependencies = [ "libc", ] +[[package]] +name = "js-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -799,15 +992,25 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "leb128" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3576a87f2ba00f6f106fdfcd16db1d698d648a26ad8e0573cad8537c3c362d2a" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.102" +version = "0.2.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" + +[[package]] +name = "libloading" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2a5ac8f984bfcf3a823267e5fde638acc3325f6496633a5da6bb6eb2171e103" +checksum = "afe203d669ec979b7128619bae5a63b7b42e9203c1b29146079ee05e2f604b52" +dependencies = [ + "cfg-if 1.0.0", + "winapi", +] [[package]] name = "linux-raw-sys" @@ -821,7 +1024,28 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", +] + +[[package]] +name = "loupe" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6a72dfa44fe15b5e76b94307eeb2ff995a8c5b283b55008940c02e0c5b634d" +dependencies = [ + "indexmap", + "loupe-derive", + "rustversion", +] + +[[package]] +name = "loupe-derive" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fbfc88337168279f2e9ae06e157cfed4efd3316e14dc96ed074d4f2e6c5952" +dependencies = [ + "quote", + "syn", ] [[package]] @@ -845,11 +1069,20 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +[[package]] +name = "memmap2" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4647a11b578fead29cdbb34d4adef8dd3dc35b876c9c6d5240d83f205abfe96e" +dependencies = [ + "libc", +] + [[package]] name = "memoffset" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ "autocfg", ] @@ -866,9 +1099,9 @@ dependencies = [ [[package]] name = "more-asserts" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238" +checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" [[package]] name = "num_cpus" @@ -880,15 +1113,6 @@ dependencies = [ "libc", ] -[[package]] -name = "object" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39f37e50073ccad23b6d09bcb5b263f4e76d3bb6038e4a3c08e52162ffa8abc2" -dependencies = [ - "memchr", -] - [[package]] name = "object" version = "0.27.1" @@ -902,9 +1126,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" +checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" [[package]] name = "opaque-debug" @@ -923,9 +1147,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58" +checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" [[package]] name = "petgraph" @@ -951,9 +1175,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "ppv-lite86" -version = "0.2.10" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" [[package]] name = "pretty_assertions" @@ -961,7 +1185,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cab0e7c02cf376875e9335e0ba1da535775beb5450d21e1dffca068818ed98b" dependencies = [ - "ansi_term 0.12.1", + "ansi_term", "ctor", "diff", "output_vt100", @@ -997,17 +1221,11 @@ version = "0.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" -[[package]] -name = "proc-macro-nested" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" - [[package]] name = "proc-macro2" -version = "1.0.29" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" +checksum = "2f84e92c0f7c9d58328b85a78557813e4bd845130db68d7184635344399423b1" dependencies = [ "unicode-xid", ] @@ -1021,6 +1239,26 @@ dependencies = [ "cc", ] +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pulldown-cmark" version = "0.8.0" @@ -1040,9 +1278,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" dependencies = [ "proc-macro2", ] @@ -1131,6 +1369,17 @@ dependencies = [ "redox_syscall", ] +[[package]] +name = "regalloc" +version = "0.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "571f7f397d61c4755285cd37853fe8e03271c243424a907415909379659381c5" +dependencies = [ + "log", + "rustc-hash", + "smallvec", +] + [[package]] name = "regalloc" version = "0.0.33" @@ -1172,24 +1421,88 @@ dependencies = [ ] [[package]] -name = "rustc-demangle" -version = "0.1.21" +name = "region" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +checksum = "76e189c2369884dce920945e2ddf79b3dff49e071a167dd1817fa9c4c00d512e" +dependencies = [ + "bitflags", + "libc", + "mach", + "winapi", +] [[package]] -name = "rustc-hash" -version = "1.1.0" +name = "remove_dir_all" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] [[package]] -name = "rustc_version" -version = "0.4.0" +name = "rend" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79af64b4b6362ffba04eef3a4e10829718a4896dac19daa741851c86781edf95" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "rkyv" +version = "0.7.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "631f7d2a2854abb66724f492ce5256e79685a673dc210ac022194cedd5c914d3" +dependencies = [ + "bytecheck", + "hashbrown", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c067e650861a749720952aed722fb344449bc95de33e6456d426f5c7d44f71c0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver", + "semver 1.0.4", ] [[package]] @@ -1201,19 +1514,25 @@ dependencies = [ "bitflags", "errno", "io-lifetimes", - "itoa", + "itoa 0.4.8", "libc", "linux-raw-sys", "once_cell", - "rustc_version", + "rustc_version 0.4.0", "winapi", ] +[[package]] +name = "rustversion" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" + [[package]] name = "ryu" -version = "1.0.5" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" [[package]] name = "same-file" @@ -1230,26 +1549,56 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + [[package]] name = "semver" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + [[package]] name = "serde" -version = "1.0.130" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +checksum = "8b9875c23cf305cd1fd7eb77234cbb705f21ea6a72c637a5c6db5fe4b8e7f008" dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" -version = "1.0.130" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +checksum = "ecc0db5cb2556c0e558887d9bbdcf6ac4471e83ff66cf696e5419024d1606276" dependencies = [ "proc-macro2", "quote", @@ -1258,15 +1607,21 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.68" +version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8" +checksum = "bcbd0344bc6533bc7ec56df11d42fb70f1b912351c0825ccb7211b59d8af7cf5" dependencies = [ - "itoa", + "itoa 1.0.1", "ryu", "serde", ] +[[package]] +name = "sha1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" + [[package]] name = "sha2" version = "0.9.8" @@ -1274,7 +1629,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" dependencies = [ "block-buffer", - "cfg-if", + "cfg-if 1.0.0", "cpufeatures", "digest", "opaque-debug", @@ -1291,15 +1646,15 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590" +checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" [[package]] name = "smallvec" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" +checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" [[package]] name = "stable_deref_trait" @@ -1307,17 +1662,81 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "standback" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" +dependencies = [ + "version_check", +] + +[[package]] +name = "stdweb" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" +dependencies = [ + "discard", + "rustc_version 0.2.3", + "stdweb-derive", + "stdweb-internal-macros", + "stdweb-internal-runtime", + "wasm-bindgen", +] + +[[package]] +name = "stdweb-derive" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "serde_derive", + "syn", +] + +[[package]] +name = "stdweb-internal-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" +dependencies = [ + "base-x", + "proc-macro2", + "quote", + "serde", + "serde_derive", + "serde_json", + "sha1", + "syn", +] + +[[package]] +name = "stdweb-internal-runtime" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" + [[package]] name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "structopt" -version = "0.3.23" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf9d950ef167e25e0bdb073cf1d68e9ad2795ac826f2f3f59647817cf23c0bfa" +checksum = "40b9788f4202aa75c240ecc9c15c65185e6a39ccdeb0fd5d008b98825464c87c" dependencies = [ "clap", "lazy_static", @@ -1326,9 +1745,9 @@ dependencies = [ [[package]] name = "structopt-derive" -version = "0.4.16" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134d838a2c9943ac3125cf6df165eda53493451b719f3255b2a26b85f772d0ba" +checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" dependencies = [ "heck", "proc-macro-error", @@ -1339,9 +1758,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.76" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f107db402c2c2055242dbf4d2af0e69197202e9faacbef9571bbe47f5a1b84" +checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" dependencies = [ "proc-macro2", "quote", @@ -1359,7 +1778,7 @@ dependencies = [ "cap-fs-ext", "cap-std", "io-lifetimes", - "rustc_version", + "rustc_version 0.4.0", "rustix", "winapi", "winx", @@ -1371,6 +1790,20 @@ version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9bffcddbc2458fa3e6058414599e3c838a022abae82e5c67b4f7f80298d5bff" +[[package]] +name = "tempfile" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "rand", + "redox_syscall", + "remove_dir_all", + "winapi", +] + [[package]] name = "termcolor" version = "1.1.2" @@ -1395,6 +1828,7 @@ dependencies = [ "wit-bindgen-gen-js", "wit-bindgen-gen-rust-wasm", "wit-bindgen-gen-spidermonkey", + "wit-bindgen-gen-wasmer", "wit-bindgen-gen-wasmtime", "wit-bindgen-gen-wasmtime-py", "wit-parser", @@ -1430,18 +1864,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.29" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "602eca064b2d83369e2b2f34b09c70b605402801927c65c11071ac911d299b88" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.29" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad553cc2c78e8de258400763a647e80e6d1b31ee237275d756f6836d204494c" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ "proc-macro2", "quote", @@ -1457,6 +1891,44 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242" +dependencies = [ + "const_fn", + "libc", + "standback", + "stdweb", + "time-macros", + "version_check", + "winapi", +] + +[[package]] +name = "time-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" +dependencies = [ + "proc-macro-hack", + "time-macros-impl", +] + +[[package]] +name = "time-macros-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "standback", + "syn", +] + [[package]] name = "toml" version = "0.5.8" @@ -1468,11 +1940,11 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84f96e095c0c82419687c20ddf5cb3eadb61f4e1405923c9dc8e53a1adacbda8" +checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "log", "pin-project-lite", "tracing-attributes", @@ -1481,9 +1953,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.16" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98863d0dd09fa59a1b79c6750ad80dbda6b75f4e71c437a6a1a8cb91a8bcbd77" +checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e" dependencies = [ "proc-macro2", "quote", @@ -1492,9 +1964,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46125608c26121c81b0c6d693eab5a420e416da7e43c426d2e8f7df8da8a3acf" +checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" dependencies = [ "lazy_static", ] @@ -1600,6 +2072,60 @@ dependencies = [ "winapi", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" + [[package]] name = "wasm-encoder" version = "0.4.1" @@ -1618,6 +2144,236 @@ dependencies = [ "leb128", ] +[[package]] +name = "wasmer" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03ea93a6ba209613d82b8fe128ec39be4297b0f6d9571ee0db963939ff02c25e" +dependencies = [ + "cfg-if 1.0.0", + "indexmap", + "js-sys", + "loupe", + "more-asserts", + "target-lexicon", + "thiserror", + "wasm-bindgen", + "wasmer-compiler", + "wasmer-compiler-cranelift", + "wasmer-derive", + "wasmer-engine", + "wasmer-engine-dylib", + "wasmer-engine-universal", + "wasmer-types", + "wasmer-vm", + "wat", + "winapi", +] + +[[package]] +name = "wasmer-compiler" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0f7a9201a79b68fe6427afa7835828b23647ef75f8a7aa212ec112f1625eeb1" +dependencies = [ + "enumset", + "loupe", + "rkyv", + "serde", + "serde_bytes", + "smallvec", + "target-lexicon", + "thiserror", + "wasmer-types", + "wasmer-vm", + "wasmparser 0.78.2", +] + +[[package]] +name = "wasmer-compiler-cranelift" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d9e195af82b7c339fa946fcd13792a3ceb65264c5631e737cc8d4941b50dcd" +dependencies = [ + "cranelift-codegen 0.76.0", + "cranelift-entity 0.76.0", + "cranelift-frontend 0.76.0", + "gimli 0.25.0", + "loupe", + "more-asserts", + "rayon", + "smallvec", + "target-lexicon", + "tracing", + "wasmer-compiler", + "wasmer-types", + "wasmer-vm", +] + +[[package]] +name = "wasmer-derive" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63990dd633cb4a8c45d2f58429aa9500385734050d0c3e434a97cd87dfecf9cc" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "wasmer-engine" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae9202a77333cfad9a32d33862dda7c1a981c3f17139f3da44a447df6b56ae4d" +dependencies = [ + "backtrace", + "enumset", + "lazy_static", + "loupe", + "memmap2", + "more-asserts", + "rustc-demangle", + "serde", + "serde_bytes", + "target-lexicon", + "thiserror", + "wasmer-compiler", + "wasmer-types", + "wasmer-vm", +] + +[[package]] +name = "wasmer-engine-dylib" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d633a81aa4278720ef476f9800efafccc4616d55f6e4fb079f6f268bd2df0a5c" +dependencies = [ + "cfg-if 1.0.0", + "enumset", + "leb128", + "libloading", + "loupe", + "rkyv", + "serde", + "tempfile", + "tracing", + "wasmer-compiler", + "wasmer-engine", + "wasmer-object", + "wasmer-types", + "wasmer-vm", + "which", +] + +[[package]] +name = "wasmer-engine-universal" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d70c28b4a5c300b91f55dbefa947751485899bf3de6cfaf3b702d14833ddb7" +dependencies = [ + "cfg-if 1.0.0", + "enumset", + "leb128", + "loupe", + "region 3.0.0", + "rkyv", + "wasmer-compiler", + "wasmer-engine", + "wasmer-types", + "wasmer-vm", + "winapi", +] + +[[package]] +name = "wasmer-object" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a94c41ae3e6df06eec59bf781043119b85d50da3e9886c2c4bf5d2e64d3532d8" +dependencies = [ + "object", + "thiserror", + "wasmer-compiler", + "wasmer-types", +] + +[[package]] +name = "wasmer-types" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "191ca11a0b1635690bbdfa1d8b677c0717a307b57064de4c8d7b579ce960fd57" +dependencies = [ + "indexmap", + "loupe", + "rkyv", + "serde", + "thiserror", +] + +[[package]] +name = "wasmer-vfs" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b58e68b9d8552144081e215a2c9fca820616952ea383b0ac1d2c68ae1ad4f719" +dependencies = [ + "libc", + "thiserror", + "tracing", +] + +[[package]] +name = "wasmer-vm" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721f7570037d25e5215f74e44af6d644a8cee10cc3df7825d03ff4179a8f6004" +dependencies = [ + "backtrace", + "cc", + "cfg-if 1.0.0", + "indexmap", + "libc", + "loupe", + "memoffset", + "more-asserts", + "region 3.0.0", + "rkyv", + "serde", + "thiserror", + "wasmer-types", + "winapi", +] + +[[package]] +name = "wasmer-wasi" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ceb5671a42650a4dcd4f3414b386cc4d3fdc695112c3963c026feca6eb21b1" +dependencies = [ + "cfg-if 1.0.0", + "generational-arena", + "getrandom", + "libc", + "thiserror", + "tracing", + "wasm-bindgen", + "wasmer", + "wasmer-vfs", + "wasmer-wasi-types", + "winapi", +] + +[[package]] +name = "wasmer-wasi-types" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d2ece0f5f508764b4ddee08a7731ab33de38e4fe2295b6c5a0dfeeb0c6fbdd7" +dependencies = [ + "byteorder", + "time", + "wasmer-types", +] + [[package]] name = "wasmlink" version = "0.1.0" @@ -1655,9 +2411,9 @@ checksum = "52144d4c78e5cf8b055ceab8e5fa22814ce4315d6002ad32cfd914f37c12fd65" [[package]] name = "wasmparser" -version = "0.80.1" +version = "0.80.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be92b6dcaa5af4b2a176b29be3bf1402fab9e69d313141185099c7d1684f2dca" +checksum = "449167e2832691a1bff24cde28d2804e90e09586a448c8e76984792c44334a6b" [[package]] name = "wasmparser" @@ -1667,12 +2423,12 @@ checksum = "98930446519f63d00a836efdc22f67766ceae8dbcc1571379f2bcabc6b2b9abc" [[package]] name = "wasmprinter" -version = "0.2.29" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f62f18810edc34db799bcb3d555835b2eecc9b6b221f8ee74fdb5aae0bffa176" +checksum = "a00ad4a51ba74183137c776ab37dea50b9f71db7454d7b654c2ba69ac5d9b223" dependencies = [ "anyhow", - "wasmparser 0.80.1", + "wasmparser 0.81.0", ] [[package]] @@ -1685,17 +2441,17 @@ dependencies = [ "async-trait", "backtrace", "bincode", - "cfg-if", + "cfg-if 1.0.0", "cpp_demangle", "indexmap", "lazy_static", "libc", "log", - "object 0.27.1", + "object", "paste", "psm", "rayon", - "region", + "region 2.2.0", "rustc-demangle", "serde", "target-lexicon", @@ -1737,15 +2493,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30d079ceda53d15a5d29e8f4f8d3fcf9a9bb589c05e29b49ea10d129b5ff8e09" dependencies = [ "anyhow", - "cranelift-codegen", - "cranelift-entity", - "cranelift-frontend", + "cranelift-codegen 0.79.0", + "cranelift-entity 0.79.0", + "cranelift-frontend 0.79.0", "cranelift-native", "cranelift-wasm", "gimli 0.26.1", "log", "more-asserts", - "object 0.27.1", + "object", "target-lexicon", "thiserror", "wasmparser 0.81.0", @@ -1759,12 +2515,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c39e4ba1fb154cca6a0f2350acc83248e22defb0cc647ae78879fe246a49dd61" dependencies = [ "anyhow", - "cranelift-entity", + "cranelift-entity 0.79.0", "gimli 0.26.1", "indexmap", "log", "more-asserts", - "object 0.27.1", + "object", "serde", "target-lexicon", "thiserror", @@ -1789,13 +2545,13 @@ version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dd538de9501eb0f2c4c7b3d8acc7f918276ca28591a67d4ebe0672ebd558b65" dependencies = [ - "addr2line 0.17.0", + "addr2line", "anyhow", "bincode", - "cfg-if", + "cfg-if 1.0.0", "gimli 0.26.1", - "object 0.27.1", - "region", + "object", + "region 2.2.0", "rustix", "serde", "target-lexicon", @@ -1814,7 +2570,7 @@ dependencies = [ "anyhow", "backtrace", "cc", - "cfg-if", + "cfg-if 1.0.0", "indexmap", "lazy_static", "libc", @@ -1823,7 +2579,7 @@ dependencies = [ "memoffset", "more-asserts", "rand", - "region", + "region 2.2.0", "rustix", "thiserror", "wasmtime-environ", @@ -1837,7 +2593,7 @@ version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115bfe5c6eb6aba7e4eaa931ce225871c40280fb2cfb4ce4d3ab98d082e52fc4" dependencies = [ - "cranelift-entity", + "cranelift-entity 0.79.0", "serde", "thiserror", "wasmparser 0.81.0", @@ -1876,9 +2632,9 @@ dependencies = [ [[package]] name = "wast" -version = "38.0.0" +version = "38.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ebc29df4629f497e0893aacd40f13a4a56b85ef6eb4ab6d603f07244f1a7bf2" +checksum = "ae0d7b256bef26c898fa7344a2d627e8499f5a749432ce0a05eae1a64ff0c271" dependencies = [ "leb128", ] @@ -1889,7 +2645,18 @@ version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adcfaeb27e2578d2c6271a45609f4a055e6d7ba3a12eff35b1fd5ba147bdf046" dependencies = [ - "wast 38.0.0", + "wast 38.0.1", +] + +[[package]] +name = "which" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea187a8ef279bc014ec368c27a920da2024d2a711109bfbe3440585d5cf27ad9" +dependencies = [ + "either", + "lazy_static", + "libc", ] [[package]] @@ -1967,9 +2734,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "winx" -version = "0.29.1" +version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ecd175b4077107a91bb6bbb34aa9a691d8b45314791776f78b63a1cb8a08928" +checksum = "afba0891d41a50943c32fcea61e124b9dd5755275054b0a3e1e1eba26e671137" dependencies = [ "bitflags", "io-lifetimes", @@ -1988,6 +2755,7 @@ dependencies = [ "wit-bindgen-gen-markdown", "wit-bindgen-gen-rust-wasm", "wit-bindgen-gen-spidermonkey", + "wit-bindgen-gen-wasmer", "wit-bindgen-gen-wasmtime", "wit-bindgen-gen-wasmtime-py", ] @@ -2003,6 +2771,7 @@ dependencies = [ "wit-bindgen-gen-markdown", "wit-bindgen-gen-rust-wasm", "wit-bindgen-gen-spidermonkey", + "wit-bindgen-gen-wasmer", "wit-bindgen-gen-wasmtime", "wit-bindgen-gen-wasmtime-py", "wit-bindgen-rust", @@ -2074,10 +2843,25 @@ dependencies = [ "structopt", "test-helpers", "wasm-encoder 0.8.0", - "wasmparser 0.80.1", + "wasmparser 0.80.2", "wit-bindgen-gen-core", ] +[[package]] +name = "wit-bindgen-gen-wasmer" +version = "0.1.0" +dependencies = [ + "anyhow", + "heck", + "structopt", + "test-helpers", + "wasmer", + "wasmer-wasi", + "wit-bindgen-gen-core", + "wit-bindgen-gen-rust", + "wit-bindgen-wasmer", +] + [[package]] name = "wit-bindgen-gen-wasmtime" version = "0.1.0" @@ -2122,6 +2906,29 @@ dependencies = [ "wit-bindgen-gen-rust-wasm", ] +[[package]] +name = "wit-bindgen-wasmer" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "bitflags", + "thiserror", + "tracing", + "wasmer", + "wit-bindgen-wasmer-impl", +] + +[[package]] +name = "wit-bindgen-wasmer-impl" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "syn", + "wit-bindgen-gen-core", + "wit-bindgen-gen-wasmer", +] + [[package]] name = "wit-bindgen-wasmtime" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index a4cf4e7be..68f91c8ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ wit-bindgen-gen-js = { path = 'crates/gen-js', features = ['structopt'] } wit-bindgen-gen-c = { path = 'crates/gen-c', features = ['structopt'] } wit-bindgen-gen-markdown = { path = 'crates/gen-markdown', features = ['structopt'] } wit-bindgen-gen-spidermonkey = { path = 'crates/gen-spidermonkey', features = ['structopt'] } +wit-bindgen-gen-wasmer = { path = 'crates/gen-wasmer', features = ['structopt'] } # Compiling `spidermonkey.wasm` takes way too long without this. [profile.dev.package.cranelift-codegen] diff --git a/crates/gen-wasmer/Cargo.toml b/crates/gen-wasmer/Cargo.toml new file mode 100644 index 000000000..1c6cab71a --- /dev/null +++ b/crates/gen-wasmer/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "wit-bindgen-gen-wasmer" +version = "0.1.0" +authors = ["Alex Crichton "] +edition = "2018" + +[lib] +test = false +doctest = false + +[dependencies] +wit-bindgen-gen-core = { path = '../gen-core', version = '0.1.0' } +wit-bindgen-gen-rust = { path = '../gen-rust', version = '0.1.0' } +heck = "0.3" +structopt = { version = "0.3", default-features = false, optional = true } + +[dev-dependencies] +anyhow = "1.0" +test-helpers = { path = '../test-helpers', features = ['wit-bindgen-gen-wasmer'] } +wasmer = "2.1" +wasmer-wasi = "2.1" +wit-bindgen-wasmer = { path = '../wasmer', features = ['tracing'] } + +[features] +witx-compat = ['wit-bindgen-gen-core/witx-compat'] diff --git a/crates/gen-wasmer/build.rs b/crates/gen-wasmer/build.rs new file mode 100644 index 000000000..d7c7fc240 --- /dev/null +++ b/crates/gen-wasmer/build.rs @@ -0,0 +1,4 @@ +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + // this build script is currently only here so OUT_DIR is set for testing. +} diff --git a/crates/gen-wasmer/src/lib.rs b/crates/gen-wasmer/src/lib.rs new file mode 100644 index 000000000..cd6d400a5 --- /dev/null +++ b/crates/gen-wasmer/src/lib.rs @@ -0,0 +1,2319 @@ +use heck::*; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; +use std::io::{Read, Write}; +use std::mem; +use std::process::{Command, Stdio}; +use std::str::FromStr; +use wit_bindgen_gen_core::wit_parser::abi::{ + Abi, AbiVariant, Bindgen, Instruction, LiftLower, WasmType, WitxInstruction, +}; +use wit_bindgen_gen_core::{wit_parser::*, Direction, Files, Generator, Source, TypeInfo, Types}; +use wit_bindgen_gen_rust::{ + int_repr, to_rust_ident, wasm_type, FnSig, RustFunctionGenerator, RustGenerator, TypeMode, +}; + +#[derive(Default)] +pub struct Wasmer { + src: Source, + opts: Opts, + needs_memory: bool, + needs_functions: BTreeMap, + needs_char_from_i32: bool, + needs_invalid_variant: bool, + needs_validate_flags: bool, + needs_raw_mem: bool, + needs_bad_int: bool, + needs_copy_slice: bool, + needs_buffer_glue: bool, + needs_le: bool, + needs_custom_error_to_trap: bool, + needs_custom_error_to_types: BTreeSet, + all_needed_handles: BTreeSet, + exported_resources: BTreeSet, + types: Types, + guest_imports: HashMap>, + guest_exports: HashMap, + in_import: bool, + in_trait: bool, + trait_name: String, + has_preview1_dtor: bool, + sizes: SizeAlign, +} + +enum NeededFunction { + Realloc, + Free, +} + +struct Import { + is_async: bool, + name: String, + trait_signature: String, + closure: String, +} + +#[derive(Default)] +struct Exports { + fields: BTreeMap, + funcs: Vec, +} + +#[derive(Default, Debug, Clone)] +#[cfg_attr(feature = "structopt", derive(structopt::StructOpt))] +pub struct Opts { + /// Whether or not `rustfmt` is executed to format generated code. + #[cfg_attr(feature = "structopt", structopt(long))] + pub rustfmt: bool, + + /// Whether or not to emit `tracing` macro calls on function entry/exit. + #[cfg_attr(feature = "structopt", structopt(long))] + pub tracing: bool, + + /// Indicates which functions should be `async`: `all`, `none`, or a + /// comma-separated list. + #[cfg_attr( + feature = "structopt", + structopt(long = "async", default_value = "none") + )] + pub async_: Async, + + /// A flag to indicate that all trait methods in imports should return a + /// custom trait-defined error. Applicable for import bindings. + #[cfg_attr(feature = "structopt", structopt(long))] + pub custom_error: bool, +} + +#[derive(Debug, Clone)] +pub enum Async { + None, + All, + Only(HashSet), +} + +impl Async { + fn includes(&self, name: &str) -> bool { + match self { + Async::None => false, + Async::All => true, + Async::Only(list) => list.contains(name), + } + } + + fn is_none(&self) -> bool { + match self { + Async::None => true, + _ => false, + } + } +} + +impl Default for Async { + fn default() -> Async { + Async::None + } +} + +impl FromStr for Async { + type Err = String; + fn from_str(s: &str) -> Result { + Ok(if s == "all" { + Async::All + } else if s == "none" { + Async::None + } else { + Async::Only(s.split(',').map(|s| s.trim().to_string()).collect()) + }) + } +} + +impl Opts { + pub fn build(self) -> Wasmer { + let mut r = Wasmer::new(); + r.opts = self; + r + } +} + +enum FunctionRet { + /// The function return is normal and needs to extra handling. + Normal, + /// The function return was wrapped in a `Result` in Rust. The `Ok` variant + /// is the actual value that will be lowered, and the `Err`, if present, + /// means that a trap has occurred. + CustomToTrap, + /// The function returns a `Result` in both wasm and in Rust, but the + /// Rust error type is a custom error and must be converted to `err`. The + /// `ok` variant payload is provided here too. + CustomToError { ok: Option, err: String }, +} + +impl Wasmer { + pub fn new() -> Wasmer { + Wasmer::default() + } + + fn abi_variant(dir: Direction) -> AbiVariant { + // This generator uses a reversed mapping! In the Wasmer host-side + // bindings, we don't use any extra adapter layer between guest wasm + // modules and the host. When the guest imports functions using the + // `GuestImport` ABI, the host directly implements the `GuestImport` + // ABI, even though the host is *exporting* functions. Similarly, when + // the guest exports functions using the `GuestExport` ABI, the host + // directly imports them with the `GuestExport` ABI, even though the + // host is *importing* functions. + match dir { + Direction::Import => AbiVariant::GuestExport, + Direction::Export => AbiVariant::GuestImport, + } + } + + fn print_intrinsics(&mut self) { + if self.needs_raw_mem { + self.push_str("use wit_bindgen_wasmer::rt::RawMem;\n"); + } + if self.needs_char_from_i32 { + self.push_str("use wit_bindgen_wasmer::rt::char_from_i32;\n"); + } + if self.needs_invalid_variant { + self.push_str("use wit_bindgen_wasmer::rt::invalid_variant;\n"); + } + if self.needs_bad_int { + self.push_str("use core::convert::TryFrom;\n"); + self.push_str("use wit_bindgen_wasmer::rt::bad_int;\n"); + } + if self.needs_validate_flags { + self.push_str("use wit_bindgen_wasmer::rt::validate_flags;\n"); + } + if self.needs_le { + self.push_str("use wit_bindgen_wasmer::Le;\n"); + } + if self.needs_copy_slice { + self.push_str("use wit_bindgen_wasmer::rt::copy_slice;\n"); + } + } + + /// Classifies the return value of a function to see if it needs handling + /// with respect to the `custom_error` configuration option. + fn classify_fn_ret(&mut self, iface: &Interface, f: &Function) -> FunctionRet { + if !self.opts.custom_error { + return FunctionRet::Normal; + } + + if f.results.len() != 1 { + self.needs_custom_error_to_trap = true; + return FunctionRet::CustomToTrap; + } + if let Type::Id(id) = &f.results[0].1 { + if let TypeDefKind::Variant(v) = &iface.types[*id].kind { + if let Some((ok, Some(err))) = v.as_expected() { + if let Type::Id(err) = err { + if let Some(name) = &iface.types[*err].name { + self.needs_custom_error_to_types.insert(name.clone()); + return FunctionRet::CustomToError { + ok: ok.cloned(), + err: name.to_string(), + }; + } + } + } + } + } + + self.needs_custom_error_to_trap = true; + FunctionRet::CustomToTrap + } +} + +impl RustGenerator for Wasmer { + fn default_param_mode(&self) -> TypeMode { + if self.in_import { + // The default here is that only leaf values can be borrowed because + // otherwise lists and such need to be copied into our own memory. + TypeMode::LeafBorrowed("'a") + } else { + // When we're calling wasm exports, however, there's no need to take + // any ownership of anything from the host so everything is borrowed + // in the parameter position. + TypeMode::AllBorrowed("'a") + } + } + + fn handle_projection(&self) -> Option<(&'static str, String)> { + if self.in_import { + if self.in_trait { + Some(("Self", self.trait_name.clone())) + } else { + Some(("T", self.trait_name.clone())) + } + } else { + None + } + } + + fn handle_wrapper(&self) -> Option<&'static str> { + None + } + + fn push_str(&mut self, s: &str) { + self.src.push_str(s); + } + + fn info(&self, ty: TypeId) -> TypeInfo { + self.types.get(ty) + } + + fn types_mut(&mut self) -> &mut Types { + &mut self.types + } + + fn print_usize(&mut self) { + self.src.push_str("u32"); + } + + fn print_pointer(&mut self, _iface: &Interface, _const_: bool, _ty: &Type) { + self.push_str("u32"); + } + + fn print_borrowed_slice( + &mut self, + iface: &Interface, + mutbl: bool, + ty: &Type, + lifetime: &'static str, + ) { + if self.sizes.align(ty) > 1 && self.in_import { + // If we're generating bindings for an import we ideally want to + // hand out raw pointers into memory. We can't guarantee anything + // about alignment in memory, though, so if the alignment + // requirement is bigger than one then we have to use slices where + // the type has a `Le<...>` wrapper. + // + // For exports we're generating functions that take values from + // Rust, so we can assume alignment and use raw slices. For types + // with an align of 1, then raw pointers are fine since Rust will + // have the same alignment requirement. + self.needs_le = true; + self.push_str("&"); + if lifetime != "'_" { + self.push_str(lifetime); + self.push_str(" "); + } + if mutbl { + self.push_str(" mut "); + } + self.push_str("[Le<"); + self.print_ty(iface, ty, TypeMode::AllBorrowed(lifetime)); + self.push_str(">]"); + } else { + self.print_rust_slice(iface, mutbl, ty, lifetime); + } + } + + fn print_borrowed_str(&mut self, lifetime: &'static str) { + self.push_str("&"); + if lifetime != "'_" { + self.push_str(lifetime); + self.push_str(" "); + } + self.push_str(" str"); + } + + fn print_lib_buffer( + &mut self, + iface: &Interface, + push: bool, + ty: &Type, + mode: TypeMode, + lt: &'static str, + ) { + if self.in_import { + if let TypeMode::AllBorrowed(_) = mode { + self.push_str("&"); + if lt != "'_" { + self.push_str(lt); + } + self.push_str(" mut "); + } + self.push_str(&format!( + "wit_bindgen_wasmer::exports::{}Buffer<{}, ", + if push { "Push" } else { "Pull" }, + lt, + )); + self.print_ty(iface, ty, if push { TypeMode::Owned } else { mode }); + self.push_str(">"); + } else { + if push { + // Push buffers, where wasm pushes, are a `Vec` which is pushed onto + self.push_str("&"); + if lt != "'_" { + self.push_str(lt); + } + self.push_str(" mut Vec<"); + self.print_ty(iface, ty, if push { TypeMode::Owned } else { mode }); + self.push_str(">"); + } else { + // Pull buffers, which wasm pulls from, are modeled as iterators + // in Rust. + self.push_str("&"); + if lt != "'_" { + self.push_str(lt); + } + self.push_str(" mut (dyn ExactSizeIterator"); + if lt != "'_" { + self.push_str(" + "); + self.push_str(lt); + } + self.push_str(")"); + } + } + } +} + +impl Generator for Wasmer { + fn preprocess_one(&mut self, iface: &Interface, dir: Direction) { + let variant = Self::abi_variant(dir); + self.types.analyze(iface); + self.in_import = variant == AbiVariant::GuestImport; + self.trait_name = iface.name.to_camel_case(); + self.src + .push_str(&format!("pub mod {} {{\n", iface.name.to_snake_case())); + self.src + .push_str("#[allow(unused_imports)]\nuse wit_bindgen_wasmer::{anyhow, wasmer};\n"); + self.sizes.fill(variant, iface); + } + + fn type_record( + &mut self, + iface: &Interface, + id: TypeId, + name: &str, + record: &Record, + docs: &Docs, + ) { + if record.is_flags() { + self.src + .push_str("wit_bindgen_wasmer::bitflags::bitflags! {\n"); + self.rustdoc(docs); + self.src + .push_str(&format!("pub struct {}: ", name.to_camel_case())); + let repr = iface + .flags_repr(record) + .expect("unsupported number of flags"); + self.int_repr(repr); + self.src.push_str(" {\n"); + for (i, field) in record.fields.iter().enumerate() { + self.rustdoc(&field.docs); + self.src.push_str(&format!( + "const {} = 1 << {};\n", + field.name.to_shouty_snake_case(), + i, + )); + } + self.src.push_str("}\n"); + self.src.push_str("}\n\n"); + + self.src.push_str("impl core::fmt::Display for "); + self.src.push_str(&name.to_camel_case()); + self.src.push_str( + "{\nfn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n", + ); + + self.src.push_str("f.write_str(\""); + self.src.push_str(&name.to_camel_case()); + self.src.push_str("(\")?;\n"); + self.src.push_str("core::fmt::Debug::fmt(self, f)?;\n"); + self.src.push_str("f.write_str(\" (0x\")?;\n"); + self.src + .push_str("core::fmt::LowerHex::fmt(&self.bits, f)?;\n"); + self.src.push_str("f.write_str(\"))\")?;\n"); + self.src.push_str("Ok(())"); + + self.src.push_str("}\n"); + self.src.push_str("}\n\n"); + return; + } + + self.print_typedef_record(iface, id, record, docs); + + // If this record might be used as a slice type in various places then + // we synthesize an `Endian` implementation for it so `&[Le]` + // is usable. + if self.modes_of(iface, id).len() > 0 + && record.fields.iter().all(|f| iface.all_bits_valid(&f.ty)) + && !record.is_tuple() + { + self.src.push_str("impl wit_bindgen_wasmer::Endian for "); + self.src.push_str(&name.to_camel_case()); + self.src.push_str(" {\n"); + + self.src.push_str("fn into_le(self) -> Self {\n"); + self.src.push_str("Self {\n"); + for field in record.fields.iter() { + self.src.push_str(&field.name.to_snake_case()); + self.src.push_str(": self."); + self.src.push_str(&field.name.to_snake_case()); + self.src.push_str(".into_le(),\n"); + } + self.src.push_str("}\n"); + self.src.push_str("}\n"); + + self.src.push_str("fn from_le(self) -> Self {\n"); + self.src.push_str("Self {\n"); + for field in record.fields.iter() { + self.src.push_str(&field.name.to_snake_case()); + self.src.push_str(": self."); + self.src.push_str(&field.name.to_snake_case()); + self.src.push_str(".from_le(),\n"); + } + self.src.push_str("}\n"); + self.src.push_str("}\n"); + + self.src.push_str("}\n"); + + // Also add an `AllBytesValid` valid impl since this structure's + // byte representations are valid (guarded by the `all_bits_valid` + // predicate). + self.src + .push_str("unsafe impl wit_bindgen_wasmer::AllBytesValid for "); + self.src.push_str(&name.to_camel_case()); + self.src.push_str(" {}\n"); + } + } + + fn type_variant( + &mut self, + iface: &Interface, + id: TypeId, + name: &str, + variant: &Variant, + docs: &Docs, + ) { + self.print_typedef_variant(iface, id, name, variant, docs); + } + + fn type_resource(&mut self, iface: &Interface, ty: ResourceId) { + let name = &iface.resources[ty].name; + self.all_needed_handles.insert(name.to_string()); + + // If we're binding imports then all handles are associated types so + // there's nothing that we need to do about that. + if self.in_import { + return; + } + + self.exported_resources.insert(ty); + + // ... otherwise for exports we generate a newtype wrapper around an + // `i32` to manage the resultt. + let tyname = name.to_camel_case(); + self.rustdoc(&iface.resources[ty].docs); + self.src.push_str("#[derive(Debug)]\n"); + self.src.push_str(&format!( + "pub struct {}(wit_bindgen_wasmer::rt::ResourceIndex);\n", + tyname + )); + } + + fn type_alias(&mut self, iface: &Interface, id: TypeId, _name: &str, ty: &Type, docs: &Docs) { + self.print_typedef_alias(iface, id, ty, docs); + } + + fn type_list(&mut self, iface: &Interface, id: TypeId, _name: &str, ty: &Type, docs: &Docs) { + self.print_type_list(iface, id, ty, docs); + } + + fn type_pointer( + &mut self, + iface: &Interface, + _id: TypeId, + name: &str, + const_: bool, + ty: &Type, + docs: &Docs, + ) { + self.rustdoc(docs); + let mutbl = if const_ { "const" } else { "mut" }; + self.src + .push_str(&format!("pub type {} = *{} ", name.to_camel_case(), mutbl,)); + self.print_ty(iface, ty, TypeMode::Owned); + self.src.push_str(";\n"); + } + + fn type_builtin(&mut self, iface: &Interface, _id: TypeId, name: &str, ty: &Type, docs: &Docs) { + self.rustdoc(docs); + self.src + .push_str(&format!("pub type {}", name.to_camel_case())); + self.src.push_str(" = "); + self.print_ty(iface, ty, TypeMode::Owned); + self.src.push_str(";\n"); + } + + fn type_push_buffer( + &mut self, + iface: &Interface, + id: TypeId, + _name: &str, + ty: &Type, + docs: &Docs, + ) { + self.print_typedef_buffer(iface, id, true, ty, docs); + } + + fn type_pull_buffer( + &mut self, + iface: &Interface, + id: TypeId, + _name: &str, + ty: &Type, + docs: &Docs, + ) { + self.print_typedef_buffer(iface, id, false, ty, docs); + } + + // fn const_(&mut self, name: &Id, ty: &Id, val: u64, docs: &str) { + // self.rustdoc(docs); + // self.src.push_str(&format!( + // "pub const {}_{}: {} = {};\n", + // ty.to_shouty_snake_case(), + // name.to_shouty_snake_case(), + // ty.to_camel_case(), + // val + // )); + // } + + // As with `abi_variant` above, we're generating host-side bindings here + // so a user "export" uses the "guest import" ABI variant on the inside of + // this `Generator` implementation. + fn export(&mut self, iface: &Interface, func: &Function) { + assert!(!func.is_async, "async not supported yet"); + let prev = mem::take(&mut self.src); + + let is_dtor = self.types.is_preview1_dtor_func(func); + self.has_preview1_dtor = self.has_preview1_dtor || is_dtor; + + // Generate the closure that's passed to a `Linker`, the final piece of + // codegen here. + let sig = iface.wasm_signature(AbiVariant::GuestImport, func); + let params = (0..sig.params.len()) + .map(|i| format!("arg{}", i)) + .collect::>(); + let mut f = FunctionBindgen::new(self, is_dtor, params); + f.func_takes_all_memory = func.abi == Abi::Preview1 + && func + .params + .iter() + .any(|(_, t)| iface.has_preview1_pointer(t)); + iface.call( + AbiVariant::GuestImport, + LiftLower::LiftArgsLowerResults, + func, + &mut f, + ); + let FunctionBindgen { + src, + cleanup, + needs_borrow_checker, + needs_memory, + needs_buffer_transaction, + needs_functions, + closures, + async_intrinsic_called, + func_takes_all_memory, + .. + } = f; + assert!(cleanup.is_none()); + assert!(!needs_buffer_transaction); + + // Generate the signature this function will have in the final trait + let mut self_arg = "&mut self".to_string(); + if func_takes_all_memory { + self_arg.push_str(", mem: wit_bindgen_wasmer::RawMemory"); + } + self.in_trait = true; + + let mut fnsig = FnSig::default(); + fnsig.private = true; + fnsig.self_arg = Some(self_arg); + self.print_docs_and_params( + iface, + func, + if is_dtor { + TypeMode::Owned + } else { + TypeMode::LeafBorrowed("'_") + }, + &fnsig, + ); + // The Rust return type may differ from the wasm return type based on + // the `custom_error` configuration of this code generator. + match self.classify_fn_ret(iface, func) { + FunctionRet::Normal => { + if func.results.len() > 0 { + self.push_str(" -> "); + self.print_results(iface, func); + } + } + FunctionRet::CustomToTrap => { + self.push_str(" -> Result<"); + self.print_results(iface, func); + self.push_str(", Self::Error>"); + } + FunctionRet::CustomToError { ok, .. } => { + self.push_str(" -> Result<"); + match ok { + Some(ty) => self.print_ty(iface, &ty, TypeMode::Owned), + None => self.push_str("()"), + } + self.push_str(", Self::Error>"); + } + } + self.in_trait = false; + let trait_signature = mem::take(&mut self.src).into(); + + // Generate the closure that's passed to a `Linker`, the final piece of + // codegen here. + let result_ty = match &sig.results[..] { + &[] => format!("()"), + &[ty] => format!("{}", wasm_type(ty)), + tys => format!( + "({})", + tys.iter() + .map(|&ty| wasm_type(ty)) + .collect::>() + .join(", ") + ), + }; + self.src + .push_str("move |env: &std::sync::Arc>>"); + for (i, param) in sig.params.iter().enumerate() { + let arg = format!("arg{}", i); + self.src.push_str(","); + self.src.push_str(&arg); + self.src.push_str(":"); + self.wasm_type(*param); + } + self.src.push_str(&format!( + "| -> Result<{}, wasmer::RuntimeError> {{\n", + result_ty + )); + + // If an intrinsic was called asynchronously, which happens if anything + // in the module could be asynchronous, then we must wrap this host + // import with an async block. Otherwise if the function is itself + // explicitly async then we must also wrap it in an async block. + // + // If none of that happens, then this is fine to be sync because + // everything is sync. + let is_async = if async_intrinsic_called || self.opts.async_.includes(&func.name) { + self.src.push_str("Box::new(async move {\n"); + true + } else { + false + }; + + self.src.push_str("let env = &mut *env.lock().unwrap();\n"); + + if self.opts.tracing { + self.src.push_str(&format!( + " + let span = wit_bindgen_wasmer::tracing::span!( + wit_bindgen_wasmer::tracing::Level::TRACE, + \"wit-bindgen abi\", + module = \"{}\", + function = \"{}\", + ); + let _enter = span.enter(); + ", + iface.name, func.name, + )); + } + self.src.push_str(&closures); + + for name in needs_functions.keys() { + self.src.push_str(&format!( + "let func_{0} = env.func_{0}.get_ref().unwrap();\n", + name + )); + } + self.needs_functions.extend(needs_functions); + self.needs_memory |= needs_memory || needs_borrow_checker; + + if needs_borrow_checker { + // TODO: This isn't actually sound and should be replaced with use + // of WasmPtr/WasmCell. + self.src.push_str( + "let mut _bc = unsafe { wit_bindgen_wasmer::BorrowChecker::new(env.memory.get_ref().unwrap().data_unchecked_mut()) };\n", + ); + } + + self.src.push_str("let host = &mut env.data;\n"); + + if self.all_needed_handles.len() > 0 { + self.src.push_str("let _tables = &mut env.tables;\n"); + } + + self.src.push_str(&String::from(src)); + + if is_async { + self.src.push_str("})\n"); + } + self.src.push_str("}"); + let closure = mem::replace(&mut self.src, prev).into(); + + self.guest_imports + .entry(iface.name.to_string()) + .or_insert(Vec::new()) + .push(Import { + is_async, + name: func.name.to_string(), + closure, + trait_signature, + }); + } + + // As with `abi_variant` above, we're generating host-side bindings here + // so a user "import" uses the "export" ABI variant on the inside of + // this `Generator` implementation. + fn import(&mut self, iface: &Interface, func: &Function) { + assert!(!func.is_async, "async not supported yet"); + let prev = mem::take(&mut self.src); + + // If anything is asynchronous on exports then everything must be + // asynchronous, we can't intermix async and sync calls because + // it's unknown whether the wasm module will make an async host call. + let is_async = !self.opts.async_.is_none(); + let mut sig = FnSig::default(); + sig.async_ = is_async; + sig.self_arg = Some("&self".to_string()); + self.print_docs_and_params(iface, func, TypeMode::AllBorrowed("'_"), &sig); + self.push_str("-> Result<"); + self.print_results(iface, func); + self.push_str(", wasmer::RuntimeError> {\n"); + + let is_dtor = self.types.is_preview1_dtor_func(func); + if is_dtor { + assert_eq!(func.results.len(), 0, "destructors cannot have results"); + } + let params = func + .params + .iter() + .map(|(name, _)| to_rust_ident(name).to_string()) + .collect(); + let mut f = FunctionBindgen::new(self, is_dtor, params); + iface.call( + AbiVariant::GuestExport, + LiftLower::LowerArgsLiftResults, + func, + &mut f, + ); + let FunctionBindgen { + needs_memory, + src, + needs_borrow_checker, + needs_buffer_transaction, + closures, + needs_functions, + .. + } = f; + + let exports = self + .guest_exports + .entry(iface.name.to_string()) + .or_insert_with(Exports::default); + + for (name, func) in needs_functions { + self.src + .push_str(&format!("let func_{0} = &self.func_{0};\n", name)); + let get = format!( + "instance.exports.get_native_function::<{}>(\"{}\")?", + func.cvt(), + name + ); + exports + .fields + .insert(format!("func_{}", name), (func.ty(), get)); + } + + self.src.push_str(&closures); + + assert!(!needs_borrow_checker); + if needs_memory { + self.src.push_str("let memory = &self.memory;\n"); + exports.fields.insert( + "memory".to_string(), + ( + "wasmer::Memory".to_string(), + "instance.exports.get_memory(\"memory\")?.clone()".to_string(), + ), + ); + } + + if needs_buffer_transaction { + self.needs_buffer_glue = true; + self.src + .push_str("let mut buffer_transaction = self.buffer_glue.transaction();\n"); + } + + self.src.push_str(&String::from(src)); + self.src.push_str("}\n"); + let func_body = mem::replace(&mut self.src, prev); + if !is_dtor { + exports.funcs.push(func_body.into()); + } + + // Create the code snippet which will define the type of this field in + // the struct that we're exporting and additionally extracts the + // function from an instantiated instance. + let sig = iface.wasm_signature(AbiVariant::GuestExport, func); + let mut cvt = String::new(); + if sig.params.len() == 1 { + cvt.push_str(wasm_type(sig.params[0])); + } else { + cvt.push_str("("); + for param in sig.params.iter() { + cvt.push_str(wasm_type(*param)); + cvt.push_str(","); + } + cvt.push_str(")"); + } + cvt.push_str(", "); + if sig.results.len() == 1 { + cvt.push_str(wasm_type(sig.results[0])); + } else { + cvt.push_str("("); + for result in sig.results.iter() { + cvt.push_str(wasm_type(*result)); + cvt.push_str(","); + } + cvt.push_str(")"); + } + exports.fields.insert( + format!("func_{}", to_rust_ident(&func.name)), + ( + format!("wasmer::NativeFunc<{}>", cvt), + format!( + "instance.exports.get_native_function::<{}>(\"{}\")?", + cvt, func.name, + ), + ), + ); + } + + fn finish_one(&mut self, iface: &Interface, files: &mut Files) { + for (module, funcs) in sorted_iter(&self.guest_imports) { + let module_camel = module.to_camel_case(); + let is_async = !self.opts.async_.is_none(); + if is_async { + self.src.push_str("#[wit_bindgen_wasmer::async_trait]\n"); + } + self.src.push_str("pub trait "); + self.src.push_str(&module_camel); + self.src.push_str(": Sized + wasmer::WasmerEnv + 'static"); + if is_async { + self.src.push_str(" + Send"); + } + self.src.push_str("{\n"); + if self.all_needed_handles.len() > 0 { + for handle in self.all_needed_handles.iter() { + self.src.push_str("type "); + self.src.push_str(&handle.to_camel_case()); + self.src.push_str(": std::fmt::Debug"); + if is_async { + self.src.push_str(" + Send + Sync"); + } + self.src.push_str(";\n"); + } + } + if self.opts.custom_error { + self.src.push_str("type Error;\n"); + if self.needs_custom_error_to_trap { + self.src.push_str( + "fn error_to_trap(&mut self, err: Self::Error) -> wasmer::RuntimeError;\n", + ); + } + for ty in self.needs_custom_error_to_types.iter() { + self.src.push_str(&format!( + "fn error_to_{}(&mut self, err: Self::Error) -> Result<{}, wasmer::RuntimeError>;\n", + ty.to_snake_case(), + ty.to_camel_case(), + )); + } + } + for f in funcs { + self.src.push_str(&f.trait_signature); + self.src.push_str(";\n\n"); + } + for handle in self.all_needed_handles.iter() { + self.src.push_str(&format!( + "fn drop_{}(&mut self, state: Self::{}) {{ + drop(state); + }}\n", + handle.to_snake_case(), + handle.to_camel_case(), + )); + } + self.src.push_str("}\n"); + + if self.all_needed_handles.len() > 0 { + self.src.push_str("\npub struct "); + self.src.push_str(&module_camel); + self.src.push_str("Tables {\n"); + for handle in self.all_needed_handles.iter() { + self.src.push_str("pub(crate) "); + self.src.push_str(&handle.to_snake_case()); + self.src.push_str("_table: wit_bindgen_wasmer::Table,\n"); + } + self.src.push_str("}\n"); + self.src.push_str("impl Default for "); + self.src.push_str(&module_camel); + self.src.push_str("Tables {\n"); + self.src.push_str("fn default() -> Self { Self {"); + for handle in self.all_needed_handles.iter() { + self.src.push_str(&handle.to_snake_case()); + self.src.push_str("_table: Default::default(),"); + } + self.src.push_str("}}}"); + self.src.push_str("impl Clone for "); + self.src.push_str(&module_camel); + self.src.push_str("Tables {\n"); + self.src.push_str("fn clone(&self) -> Self {\n"); + self.src.push_str("Self::default()\n"); + self.src.push_str("}}\n"); + } + } + + for (module, funcs) in mem::take(&mut self.guest_imports) { + let module_camel = module.to_camel_case(); + self.push_str("\npub fn add_to_imports(store: &wasmer::Store, imports: &mut wasmer::ImportObject, data: T)\n"); + self.push_str("where T: "); + self.push_str(&module_camel); + self.push_str("\n{\n"); + + self.push_str("#[derive(Clone)]"); + self.push_str("struct EnvWrapper {\n"); + self.push_str("data: T,\n"); + if self.all_needed_handles.len() > 0 { + self.push_str("tables: "); + self.push_str(&module_camel); + self.push_str("Tables,\n"); + } + if self.needs_memory { + self.push_str("memory: wasmer::LazyInit,\n"); + } + for (name, func) in &self.needs_functions { + self.src.push_str(&format!( + "func_{name}: wasmer::LazyInit>,\n", + name = name, + cvt = func.cvt(), + )); + } + self.push_str("}\n"); + self.push_str("unsafe impl Send for EnvWrapper {}\n"); + self.push_str("unsafe impl Sync for EnvWrapper {}\n"); + self.push_str("impl wasmer::WasmerEnv for EnvWrapper {\n"); + self.push_str("fn init_with_instance(&mut self, instance: &wasmer::Instance) -> Result<(), wasmer::HostEnvInitError>{\n"); + self.push_str("self.data.init_with_instance(instance)?;"); + if self.needs_memory { + self.push_str("self.memory.initialize(instance.exports.get_with_generics_weak(\"memory\")?);\n"); + } + for name in self.needs_functions.keys() { + self.src.push_str(&format!( + "self.func_{name}.initialize(instance.exports.get_with_generics_weak(\"{name}\")?);\n", + name = name + )); + } + self.push_str("Ok(())"); + self.push_str("}\n"); + self.push_str("}\n"); + + self.push_str("let env = std::sync::Arc::new(std::sync::Mutex::new(EnvWrapper {\n"); + self.push_str("data,\n"); + if self.all_needed_handles.len() > 0 { + self.push_str("tables: "); + self.push_str(&module_camel); + self.push_str("Tables::default(),\n"); + } + if self.needs_memory { + self.push_str("memory: wasmer::LazyInit::new(),\n"); + } + for name in self.needs_functions.keys() { + self.src + .push_str(&format!("func_{}: wasmer::LazyInit::new(),\n", name,)); + } + self.push_str("}));\n"); + + self.push_str("let mut exports = wasmer::Exports::new();\n"); + for f in funcs { + if f.is_async { + unimplemented!(); + } + self.push_str(&format!( + "exports.insert(\"{}\", wasmer::Function::new_native_with_env(store, env.clone(), {}));\n", + f.name, f.closure, + )); + } + self.push_str(&format!("imports.register(\"{}\", exports);\n", module)); + + if !self.has_preview1_dtor && !self.all_needed_handles.is_empty() { + self.push_str("let mut canonical_abi = imports.get_namespace_exports(\"canonical_abi\").unwrap_or_else(wasmer::Exports::new);\n"); + for handle in self.all_needed_handles.iter() { + self.src.push_str(&format!( + "canonical_abi.insert( + \"resource_drop_{name}\", + wasmer::Function::new_native_with_env(store, env.clone(), + move |env: &std::sync::Arc>>, handle: u32| -> Result<(), wasmer::RuntimeError> {{ + let env = &mut *env.lock().unwrap(); + let handle = env + .tables + .{snake}_table + .remove(handle) + .map_err(|e| {{ + wasmer::RuntimeError::new(format!(\"failed to remove handle: {{}}\", e)) + }})?; + env.data.drop_{snake}(handle); + Ok(()) + }} + ) + );\n", + name = handle, + snake = handle.to_snake_case(), + )); + } + self.push_str("imports.register(\"canonical_abi\", canonical_abi);\n"); + } + self.push_str("}\n"); + } + + for (module, exports) in sorted_iter(&mem::take(&mut self.guest_exports)) { + let name = module.to_camel_case(); + + // Generate a struct that is the "state" of this exported module + // which is held internally. + self.push_str( + " + /// Auxiliary data associated with the wasm exports. + ", + ); + self.push_str("#[derive(Default)]\n"); + self.push_str("pub struct "); + self.push_str(&name); + self.push_str("Data {\n"); + for r in self.exported_resources.iter() { + self.src.push_str(&format!( + " + index_slab{idx}: wit_bindgen_wasmer::rt::IndexSlab, + resource_slab{idx}: wit_bindgen_wasmer::rt::ResourceSlab, + dtor{idx}: wasmer::LazyInit>, + ", + idx = r.index(), + )); + } + self.push_str("}\n"); + self.push_str("impl wasmer::WasmerEnv for "); + self.push_str(&name); + self.push_str("Data {\n"); + self.push_str("fn init_with_instance(&mut self, instance: &wasmer::Instance) -> Result<(), wasmer::HostEnvInitError>{\n"); + self.push_str("let _ = instance;\n"); + for r in self.exported_resources.iter() { + self.src.push_str(&format!( + "self.dtor{idx}.initialize(instance.exports.get_with_generics_weak(\"canonical_abi_drop_{name}\")?);\n", + idx = r.index(), + name = iface.resources[*r].name, + )); + } + self.push_str("Ok(())"); + self.push_str("}\n"); + self.push_str("}\n"); + self.src.push_str("impl Clone for "); + self.src.push_str(&name); + self.src.push_str("Data {\n"); + self.src.push_str("fn clone(&self) -> Self {\n"); + self.src.push_str("Self::default()\n"); + self.src.push_str("}}\n"); + + self.push_str("pub struct "); + self.push_str(&name); + self.push_str(" {\n"); + self.push_str(&format!( + "state: std::sync::Arc>,\n", + name + )); + for (name, (ty, _)) in exports.fields.iter() { + self.push_str(name); + self.push_str(": "); + self.push_str(ty); + self.push_str(",\n"); + } + // if self.needs_buffer_glue { + // self.push_str("buffer_glue: wit_bindgen_wasmer::imports::BufferGlue,"); + // } + self.push_str("}\n"); + self.push_str(&format!("impl {} {{\n", name)); + + if self.exported_resources.len() == 0 { + self.push_str("#[allow(unused_variables)]\n"); + } + self.push_str(&format!( + " + /// Adds any intrinsics, if necessary for this exported wasm + /// functionality to the `ImportObject` provided. + /// + /// This function returns the `{0}Data` which needs to be + /// passed through to `{0}::new`. + fn add_to_imports( + store: &wasmer::Store, + imports: &mut wasmer::ImportObject, + ) -> std::sync::Arc> {{ + ", + name, + )); + self.push_str( + "let state = std::sync::Arc::new(std::sync::Mutex::new(Default::default()));\n", + ); + if !self.all_needed_handles.is_empty() { + self.push_str("let mut canonical_abi = imports.get_namespace_exports(\"canonical_abi\").unwrap_or_else(wasmer::Exports::new);\n"); + for r in self.exported_resources.iter() { + if !self.opts.async_.is_none() { + unimplemented!(); + } + self.src.push_str(&format!( + " + canonical_abi.insert( + \"resource_drop_{resource}\", + wasmer::Function::new_native_with_env(store, state.clone(), + move |env: &std::sync::Arc>, idx: u32| -> Result<(), wasmer::RuntimeError> {{ + let state = &mut *env.lock().unwrap(); + let resource_idx = state.index_slab{idx}.remove(idx)?; + let wasm = match state.resource_slab{idx}.drop(resource_idx) {{ + Some(wasm) => wasm, + None => return Ok(()), + }}; + let dtor = state.dtor{idx}.get_ref().unwrap(); + dtor.call(wasm)?; + Ok(()) + }}, + ) + ); + canonical_abi.insert( + \"resource_clone_{resource}\", + wasmer::Function::new_native_with_env(store, state.clone(), + move |env: &std::sync::Arc>, idx: u32| -> Result {{ + let state = &mut *env.lock().unwrap(); + let resource_idx = state.index_slab{idx}.get(idx)?; + state.resource_slab{idx}.clone(resource_idx)?; + Ok(state.index_slab{idx}.insert(resource_idx)) + }}, + ) + ); + canonical_abi.insert( + \"resource_get_{resource}\", + wasmer::Function::new_native_with_env(store, state.clone(), + move |env: &std::sync::Arc>, idx: u32| -> Result {{ + let state = &mut *env.lock().unwrap(); + let resource_idx = state.index_slab{idx}.get(idx)?; + Ok(state.resource_slab{idx}.get(resource_idx)) + }}, + ) + ); + canonical_abi.insert( + \"resource_new_{resource}\", + wasmer::Function::new_native_with_env(store, state.clone(), + move |env: &std::sync::Arc>, val: i32| -> Result {{ + let state = &mut *env.lock().unwrap(); + let resource_idx = state.resource_slab{idx}.insert(val); + Ok(state.index_slab{idx}.insert(resource_idx)) + }}, + ) + ); + ", + name = name, + resource = iface.resources[*r].name, + idx = r.index(), + )); + } + self.push_str("imports.register(\"canonical_abi\", canonical_abi);\n"); + } + // if self.needs_buffer_glue { + // self.push_str( + // " + // use wit_bindgen_wasmer::rt::get_memory; + + // let buffer_glue = wit_bindgen_wasmer::imports::BufferGlue::default(); + // let g = buffer_glue.clone(); + // linker.func( + // \"wit_canonical_buffer_abi\", + // \"in_len\", + // move |handle: u32| g.in_len(handle), + // )?; + // let g = buffer_glue.clone(); + // linker.func( + // \"wit_canonical_buffer_abi\", + // \"in_read\", + // move |caller: wasmtime::Caller<'_>, handle: u32, len: u32, offset: u32| { + // let memory = get_memory(&mut caller, \"memory\")?; + // g.in_read(handle, &memory, offset, len) + // }, + // )?; + // let g = buffer_glue.clone(); + // linker.func( + // \"wit_canonical_buffer_abi\", + // \"out_len\", + // move |handle: u32| g.out_len(handle), + // )?; + // let g = buffer_glue.clone(); + // linker.func( + // \"wit_canonical_buffer_abi\", + // \"out_write\", + // move |caller: wasmtime::Caller<'_>, handle: u32, len: u32, offset: u32| { + // let memory = get_memory(&mut caller, \"memory\")?; + // g.out_write(handle, &memory, offset, len) + // }, + // )?; + // ", + // ); + // } + self.push_str("state\n"); + self.push_str("}\n"); + + if !self.opts.async_.is_none() { + unimplemented!(); + } + self.push_str(&format!( + " + /// Instantiates the provided `module` using the specified + /// parameters, wrapping up the result in a structure that + /// translates between wasm and the host. + /// + /// The `imports` provided will have intrinsics added to it + /// automatically, so it's not necessary to call + /// `add_to_imports` beforehand. This function will + /// instantiate the `module` otherwise using `imports`, and + /// both an instance of this structure and the underlying + /// `wasmer::Instance` will be returned. + pub fn instantiate( + store: &wasmer::Store, + module: &wasmer::Module, + imports: &mut wasmer::ImportObject, + ) -> anyhow::Result<(Self, wasmer::Instance)> {{ + let state = Self::add_to_imports(store, imports); + let instance = wasmer::Instance::new(module, &*imports)?; + Ok((Self::new(&instance, state)?, instance)) + }} + ", + )); + + self.push_str(&format!( + " + /// Low-level creation wrapper for wrapping up the exports + /// of the `instance` provided in this structure of wasm + /// exports. + /// + /// This function will extract exports from the `instance` + /// and wrap them all up in the returned structure which can + /// be used to interact with the wasm module. + pub fn new( + instance: &wasmer::Instance, + state: std::sync::Arc>, + ) -> Result {{ + ", + name, + )); + //assert!(!self.needs_get_func); + for (name, (_, get)) in exports.fields.iter() { + self.push_str("let "); + self.push_str(&name); + self.push_str("= "); + self.push_str(&get); + self.push_str(";\n"); + } + self.push_str("Ok("); + self.push_str(&name); + self.push_str("{\n"); + for (name, _) in exports.fields.iter() { + self.push_str(name); + self.push_str(",\n"); + } + self.push_str("state,\n"); + self.push_str("\n})\n"); + self.push_str("}\n"); + + for func in exports.funcs.iter() { + self.push_str(func); + } + + for r in self.exported_resources.iter() { + if !self.opts.async_.is_none() { + unimplemented!(); + } + self.src.push_str(&format!( + " + /// Drops the host-owned handle to the resource + /// specified. + /// + /// Note that this may execute the WebAssembly-defined + /// destructor for this type. This also may not run + /// the destructor if there are still other references + /// to this type. + pub fn drop_{name_snake}( + &self, + val: {name_camel}, + ) -> Result<(), wasmer::RuntimeError> {{ + let data = &mut *self.state.lock().unwrap(); + let wasm = match data.resource_slab{idx}.drop(val.0) {{ + Some(val) => val, + None => return Ok(()), + }}; + data.dtor{idx}.get_ref().unwrap().call(wasm)?; + Ok(()) + }} + ", + name_snake = iface.resources[*r].name.to_snake_case(), + name_camel = iface.resources[*r].name.to_camel_case(), + idx = r.index(), + )); + } + + self.push_str("}\n"); + } + self.print_intrinsics(); + + // Close the opening `mod`. + self.push_str("}\n"); + + let mut src = mem::take(&mut self.src); + if self.opts.rustfmt { + let mut child = Command::new("rustfmt") + .arg("--edition=2018") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .expect("failed to spawn `rustfmt`"); + child + .stdin + .take() + .unwrap() + .write_all(src.as_bytes()) + .unwrap(); + src.as_mut_string().truncate(0); + child + .stdout + .take() + .unwrap() + .read_to_string(src.as_mut_string()) + .unwrap(); + let status = child.wait().unwrap(); + assert!(status.success()); + } + + files.push("bindings.rs", src.as_bytes()); + } +} + +struct FunctionBindgen<'a> { + gen: &'a mut Wasmer, + + // Number used to assign unique names to temporary variables. + tmp: usize, + + // Destination where source code is pushed onto for this function + src: Source, + + // Whether or not this function is a preview1 dtor + is_dtor: bool, + + // The named parameters that are available to this function + params: Vec, + + // Management of block scopes used by `Bindgen`. + block_storage: Vec, + blocks: Vec, + + // Whether or not the code generator is after the invocation of wasm or the + // host, used for knowing where to acquire memory from. + after_call: bool, + // Whether or not the `caller_memory` variable has been defined and is + // available for use. + caller_memory_available: bool, + // Whether or not a helper function was called in an async fashion. If so + // and this is an import, then the import must be defined asynchronously as + // well. + async_intrinsic_called: bool, + // Code that must be executed before a return, generated during instruction + // lowering. + cleanup: Option, + // Only present for preview1 ABIs where some arguments might be a `pointer` + // type. + func_takes_all_memory: bool, + + // Rust clousures for buffers that must be placed at the front of the + // function. + closures: Source, + + // Various intrinsic properties this function's codegen required, must be + // satisfied in the function header if any are set. + needs_buffer_transaction: bool, + needs_borrow_checker: bool, + needs_memory: bool, + needs_functions: HashMap, +} + +impl FunctionBindgen<'_> { + fn new(gen: &mut Wasmer, is_dtor: bool, params: Vec) -> FunctionBindgen<'_> { + FunctionBindgen { + gen, + block_storage: Vec::new(), + blocks: Vec::new(), + src: Source::default(), + after_call: false, + caller_memory_available: false, + async_intrinsic_called: false, + tmp: 0, + cleanup: None, + func_takes_all_memory: false, + closures: Source::default(), + needs_buffer_transaction: false, + needs_borrow_checker: false, + needs_memory: false, + needs_functions: HashMap::new(), + is_dtor, + params, + } + } + + fn memory_src(&mut self) -> String { + if self.gen.in_import { + if !self.after_call { + // Before calls we use `_bc` which is a borrow checker used for + // getting long-lasting borrows into memory. + self.needs_borrow_checker = true; + return format!("_bc"); + } + + if !self.caller_memory_available { + self.needs_memory = true; + self.caller_memory_available = true; + self.push_str("let caller_memory = unsafe { env.memory.get_ref().unwrap().data_unchecked_mut() };\n"); + } + format!("caller_memory") + } else { + self.needs_memory = true; + format!("unsafe {{ memory.data_unchecked_mut() }}") + } + } + + fn call_intrinsic(&mut self, name: &str, args: String) { + if !self.gen.opts.async_.is_none() { + self.async_intrinsic_called = true; + unimplemented!(); + }; + self.push_str(&format!("func_{}.call({})?;\n", name, args)); + self.caller_memory_available = false; // invalidated by call + } + + fn type_string(&mut self, iface: &Interface, ty: &Type, mode: TypeMode) -> String { + let start = self.gen.src.len(); + self.gen.print_ty(iface, ty, mode); + let ty = self.gen.src[start..].to_string(); + self.gen.src.as_mut_string().truncate(start); + ty + } + + fn load(&mut self, offset: i32, ty: &str, operands: &[String]) -> String { + let mem = self.memory_src(); + self.gen.needs_raw_mem = true; + let tmp = self.tmp(); + self.push_str(&format!( + "let load{} = {}.load::<{}>({} + {})?;\n", + tmp, mem, ty, operands[0], offset + )); + format!("load{}", tmp) + } + + fn store(&mut self, offset: i32, method: &str, extra: &str, operands: &[String]) { + let mem = self.memory_src(); + self.gen.needs_raw_mem = true; + self.push_str(&format!( + "{}.store({} + {}, wit_bindgen_wasmer::rt::{}({}){})?;\n", + mem, operands[1], offset, method, operands[0], extra + )); + } +} + +impl RustFunctionGenerator for FunctionBindgen<'_> { + fn push_str(&mut self, s: &str) { + self.src.push_str(s); + } + + fn tmp(&mut self) -> usize { + let ret = self.tmp; + self.tmp += 1; + ret + } + + fn rust_gen(&self) -> &dyn RustGenerator { + self.gen + } + + fn lift_lower(&self) -> LiftLower { + if self.gen.in_import { + LiftLower::LiftArgsLowerResults + } else { + LiftLower::LowerArgsLiftResults + } + } +} + +impl Bindgen for FunctionBindgen<'_> { + type Operand = String; + + fn sizes(&self) -> &SizeAlign { + &self.gen.sizes + } + + fn push_block(&mut self) { + let prev = mem::take(&mut self.src); + self.block_storage.push(prev); + } + + fn finish_block(&mut self, operands: &mut Vec) { + let to_restore = self.block_storage.pop().unwrap(); + let src = mem::replace(&mut self.src, to_restore); + let expr = match operands.len() { + 0 => "()".to_string(), + 1 => operands[0].clone(), + _ => format!("({})", operands.join(", ")), + }; + if src.is_empty() { + self.blocks.push(expr); + } else if operands.is_empty() { + self.blocks.push(format!("{{\n{}}}", &src[..])); + } else { + self.blocks.push(format!("{{\n{}{}\n}}", &src[..], expr)); + } + self.caller_memory_available = false; + } + + fn allocate_typed_space(&mut self, _iface: &Interface, _ty: TypeId) -> String { + unimplemented!() + } + + fn i64_return_pointer_area(&mut self, _amt: usize) -> String { + unimplemented!() + } + + fn is_list_canonical(&self, iface: &Interface, ty: &Type) -> bool { + iface.all_bits_valid(ty) + } + + fn emit( + &mut self, + iface: &Interface, + inst: &Instruction<'_>, + operands: &mut Vec, + results: &mut Vec, + ) { + let mut top_as = |cvt: &str| { + let mut s = operands.pop().unwrap(); + s.push_str(" as "); + s.push_str(cvt); + results.push(s); + }; + + let mut try_from = |cvt: &str, operands: &[String], results: &mut Vec| { + self.gen.needs_bad_int = true; + let result = format!("{}::try_from({}).map_err(bad_int)?", cvt, operands[0]); + results.push(result); + }; + + match inst { + Instruction::GetArg { nth } => results.push(self.params[*nth].clone()), + Instruction::I32Const { val } => results.push(format!("{}i32", val)), + Instruction::ConstZero { tys } => { + for ty in tys.iter() { + match ty { + WasmType::I32 => results.push("0i32".to_string()), + WasmType::I64 => results.push("0i64".to_string()), + WasmType::F32 => results.push("0.0f32".to_string()), + WasmType::F64 => results.push("0.0f64".to_string()), + } + } + } + + Instruction::I64FromU64 | Instruction::I64FromS64 => { + let s = operands.pop().unwrap(); + results.push(format!("wit_bindgen_wasmer::rt::as_i64({})", s)); + } + Instruction::I32FromUsize + | Instruction::I32FromChar + | Instruction::I32FromU8 + | Instruction::I32FromS8 + | Instruction::I32FromChar8 + | Instruction::I32FromU16 + | Instruction::I32FromS16 + | Instruction::I32FromU32 + | Instruction::I32FromS32 => { + let s = operands.pop().unwrap(); + results.push(format!("wit_bindgen_wasmer::rt::as_i32({})", s)); + } + + Instruction::F32FromIf32 + | Instruction::F64FromIf64 + | Instruction::If32FromF32 + | Instruction::If64FromF64 + | Instruction::S32FromI32 + | Instruction::S64FromI64 => { + results.push(operands.pop().unwrap()); + } + + // Downcasts from `i32` into smaller integers are checked to ensure + // that they fit within the valid range. While not strictly + // necessary since we could chop bits off this should be more + // forward-compatible with any future changes. + Instruction::S8FromI32 => try_from("i8", operands, results), + Instruction::Char8FromI32 | Instruction::U8FromI32 => try_from("u8", operands, results), + Instruction::S16FromI32 => try_from("i16", operands, results), + Instruction::U16FromI32 => try_from("u16", operands, results), + + // Casts of the same bit width simply use `as` since we're just + // reinterpreting the bits already there. + Instruction::U32FromI32 | Instruction::UsizeFromI32 => top_as("u32"), + Instruction::U64FromI64 => top_as("u64"), + + Instruction::CharFromI32 => { + self.gen.needs_char_from_i32 = true; + results.push(format!("char_from_i32({})?", operands[0])); + } + + Instruction::Bitcasts { casts } => { + wit_bindgen_gen_rust::bitcast(casts, operands, results) + } + + Instruction::I32FromOwnedHandle { ty } => { + let name = &iface.resources[*ty].name; + results.push(format!( + "_tables.{}_table.insert({}) as i32", + name.to_snake_case(), + operands[0] + )); + } + Instruction::HandleBorrowedFromI32 { ty } => { + let name = &iface.resources[*ty].name; + if self.is_dtor { + results.push(format!( + "_tables.{}_table.remove(({}) as u32).map_err(|e| {{ + wasmer::RuntimeError::new(format!(\"failed to remove handle: {{}}\", e)) + }})?", + name.to_snake_case(), + operands[0] + )); + } else { + results.push(format!( + "_tables.{}_table.get(({}) as u32).ok_or_else(|| {{ + wasmer::RuntimeError::new(\"invalid handle index\") + }})?", + name.to_snake_case(), + operands[0] + )); + } + } + Instruction::I32FromBorrowedHandle { ty } => { + let tmp = self.tmp(); + self.push_str(&format!( + " + let obj{tmp} = {op}; + let handle{tmp} = {{ + let state = &mut *self.state.lock().unwrap(); + state.resource_slab{idx}.clone(obj{tmp}.0)?; + state.index_slab{idx}.insert(obj{tmp}.0) + }}; + ", + tmp = tmp, + idx = ty.index(), + op = operands[0], + )); + + results.push(format!("handle{} as i32", tmp,)); + } + Instruction::HandleOwnedFromI32 { ty } => { + let tmp = self.tmp(); + self.push_str(&format!( + "let handle{} = self.state.lock().unwrap().index_slab{}.remove({} as u32)?;\n", + tmp, + ty.index(), + operands[0], + )); + + let name = iface.resources[*ty].name.to_camel_case(); + results.push(format!("{}(handle{})", name, tmp)); + } + + Instruction::RecordLower { ty, record, .. } => { + self.record_lower(iface, *ty, record, &operands[0], results); + } + Instruction::RecordLift { ty, record, .. } => { + self.record_lift(iface, *ty, record, operands, results); + } + + Instruction::FlagsLower { record, .. } => { + let tmp = self.tmp(); + self.push_str(&format!("let flags{} = {};\n", tmp, operands[0])); + for i in 0..record.num_i32s() { + results.push(format!("(flags{}.bits >> {}) as i32", tmp, i * 32)); + } + } + Instruction::FlagsLower64 { .. } => { + results.push(format!("({}).bits as i64", operands[0])); + } + Instruction::FlagsLift { record, name, .. } + | Instruction::FlagsLift64 { record, name, .. } => { + self.gen.needs_validate_flags = true; + let repr = iface + .flags_repr(record) + .expect("unsupported number of flags"); + let mut flags = String::from("0"); + for (i, op) in operands.iter().enumerate() { + flags.push_str(&format!("| (i64::from({}) << {})", op, i * 32)); + } + results.push(format!( + "validate_flags( + {}, + {name}::all().bits() as i64, + \"{name}\", + |b| {name} {{ bits: b as {ty} }} + )?", + flags, + name = name.to_camel_case(), + ty = int_repr(repr), + )); + } + + Instruction::VariantPayloadName => results.push("e".to_string()), + Instruction::BufferPayloadName => results.push("e".to_string()), + + Instruction::VariantLower { + variant, + results: result_types, + ty, + .. + } => { + let blocks = self + .blocks + .drain(self.blocks.len() - variant.cases.len()..) + .collect::>(); + self.variant_lower( + iface, + *ty, + variant, + result_types.len(), + &operands[0], + results, + blocks, + ); + } + + Instruction::VariantLift { variant, name, ty } => { + let blocks = self + .blocks + .drain(self.blocks.len() - variant.cases.len()..) + .collect::>(); + let mut result = format!("match "); + result.push_str(&operands[0]); + result.push_str(" {\n"); + for (i, (case, block)) in variant.cases.iter().zip(blocks).enumerate() { + result.push_str(&i.to_string()); + result.push_str(" => "); + self.variant_lift_case(iface, *ty, variant, case, &block, &mut result); + result.push_str(",\n"); + } + let variant_name = name.map(|s| s.to_camel_case()); + let variant_name = variant_name.as_deref().unwrap_or_else(|| { + if variant.is_bool() { + "bool" + } else if variant.as_expected().is_some() { + "Result" + } else if variant.as_option().is_some() { + "Option" + } else { + unimplemented!() + } + }); + result.push_str("_ => return Err(invalid_variant(\""); + result.push_str(&variant_name); + result.push_str("\")),\n"); + result.push_str("}"); + results.push(result); + self.gen.needs_invalid_variant = true; + } + + Instruction::ListCanonLower { element, realloc } => { + // Lowering only happens when we're passing lists into wasm, + // which forces us to always allocate, so this should always be + // `Some`. + // + // Note that the size of a list of `char` is 1 because it's + // encoded as utf-8, otherwise it's just normal contiguous array + // elements. + let realloc = realloc.unwrap(); + self.needs_functions + .insert(realloc.to_string(), NeededFunction::Realloc); + let (size, align) = match element { + Type::Char => (1, 1), + _ => (self.gen.sizes.size(element), self.gen.sizes.align(element)), + }; + + // Store the operand into a temporary... + let tmp = self.tmp(); + let val = format!("vec{}", tmp); + self.push_str(&format!("let {} = {};\n", val, operands[0])); + + // ... and then realloc space for the result in the guest module + let ptr = format!("ptr{}", tmp); + self.push_str(&format!("let {} = ", ptr)); + self.call_intrinsic( + realloc, + format!("0, 0, {}, ({}.len() as i32) * {}", align, val, size), + ); + + // ... and then copy over the result. + let mem = self.memory_src(); + self.push_str(&format!( + "{}.store_many({}, {}.as_ref())?;\n", + mem, ptr, val + )); + self.gen.needs_raw_mem = true; + self.needs_memory = true; + results.push(ptr); + results.push(format!("{}.len() as i32", val)); + } + + Instruction::ListCanonLift { element, free, .. } => match free { + Some(free) => { + self.needs_memory = true; + self.gen.needs_copy_slice = true; + self.needs_functions + .insert(free.to_string(), NeededFunction::Free); + let (stringify, align) = match element { + Type::Char => (true, 1), + _ => (false, self.gen.sizes.align(element)), + }; + let tmp = self.tmp(); + self.push_str(&format!("let ptr{} = {};\n", tmp, operands[0])); + self.push_str(&format!("let len{} = {};\n", tmp, operands[1])); + let result = format!( + " + copy_slice( + memory, + func_{}, + ptr{tmp}, len{tmp}, {} + )? + ", + free, + align, + tmp = tmp + ); + if stringify { + results.push(format!( + "String::from_utf8({}) + .map_err(|_| wasmer::RuntimeError::new(\"invalid utf-8\"))?", + result + )); + } else { + results.push(result); + } + } + None => { + self.needs_borrow_checker = true; + let method = match element { + Type::Char => "slice_str", + _ => "slice", + }; + let tmp = self.tmp(); + self.push_str(&format!("let ptr{} = {};\n", tmp, operands[0])); + self.push_str(&format!("let len{} = {};\n", tmp, operands[1])); + let slice = format!("_bc.{}(ptr{1}, len{1})?", method, tmp); + results.push(slice); + } + }, + + Instruction::ListLower { element, realloc } => { + let realloc = realloc.unwrap(); + let body = self.blocks.pop().unwrap(); + let tmp = self.tmp(); + let vec = format!("vec{}", tmp); + let result = format!("result{}", tmp); + let len = format!("len{}", tmp); + self.needs_functions + .insert(realloc.to_string(), NeededFunction::Realloc); + let size = self.gen.sizes.size(element); + let align = self.gen.sizes.align(element); + + // first store our vec-to-lower in a temporary since we'll + // reference it multiple times. + self.push_str(&format!("let {} = {};\n", vec, operands[0])); + self.push_str(&format!("let {} = {}.len() as i32;\n", len, vec)); + + // ... then realloc space for the result in the guest module + self.push_str(&format!("let {} = ", result)); + self.call_intrinsic(realloc, format!("0, 0, {}, {} * {}", align, len, size)); + + // ... then consume the vector and use the block to lower the + // result. + self.push_str(&format!( + "for (i, e) in {}.into_iter().enumerate() {{\n", + vec + )); + self.push_str(&format!("let base = {} + (i as i32) * {};\n", result, size)); + self.push_str(&body); + self.push_str("}"); + + results.push(result); + results.push(len); + } + + Instruction::ListLift { element, free, .. } => { + let body = self.blocks.pop().unwrap(); + let tmp = self.tmp(); + let size = self.gen.sizes.size(element); + let align = self.gen.sizes.align(element); + let len = format!("len{}", tmp); + self.push_str(&format!("let {} = {};\n", len, operands[1])); + let base = format!("base{}", tmp); + self.push_str(&format!("let {} = {};\n", base, operands[0])); + let result = format!("result{}", tmp); + self.push_str(&format!( + "let mut {} = Vec::with_capacity({} as usize);\n", + result, len, + )); + + self.push_str("for i in 0.."); + self.push_str(&len); + self.push_str(" {\n"); + self.push_str("let base = "); + self.push_str(&base); + self.push_str(" + i *"); + self.push_str(&size.to_string()); + self.push_str(";\n"); + self.push_str(&result); + self.push_str(".push("); + self.push_str(&body); + self.push_str(");\n"); + self.push_str("}\n"); + results.push(result); + + if let Some(free) = free { + self.call_intrinsic(free, format!("{}, {} * {}, {}", base, len, size, align)); + self.needs_functions + .insert(free.to_string(), NeededFunction::Free); + } + } + + Instruction::IterElem { .. } => results.push("e".to_string()), + + Instruction::IterBasePointer => results.push("base".to_string()), + + // Never used due to the call modes that this binding generator + // uses + Instruction::BufferLowerPtrLen { .. } => unreachable!(), + Instruction::BufferLiftHandle { .. } => unimplemented!(), + + Instruction::BufferLiftPtrLen { push, ty } => { + let block = self.blocks.pop().unwrap(); + self.needs_borrow_checker = true; + let tmp = self.tmp(); + self.push_str(&format!("let _ = {};\n", operands[0])); + self.push_str(&format!("let ptr{} = {};\n", tmp, operands[1])); + self.push_str(&format!("let len{} = {};\n", tmp, operands[2])); + if iface.all_bits_valid(ty) { + let method = if *push { "slice_mut" } else { "slice" }; + results.push(format!("_bc.{}(ptr{1}, len{1})?", method, tmp)); + } else { + let size = self.gen.sizes.size(ty); + let closure = format!("closure{}", tmp); + self.closures.push_str(&format!("let {} = ", closure)); + if *push { + self.closures.push_str("|_bc: &mut [u8], e:"); + let ty = self.type_string(iface, ty, TypeMode::Owned); + self.closures.push_str(&ty); + self.closures.push_str("| {let base = 0;\n"); + self.closures.push_str(&block); + self.closures.push_str("; Ok(()) };\n"); + results.push(format!( + "wit_bindgen_wasmer::exports::PushBuffer::new( + &mut _bc, ptr{}, len{}, {}, &{})?", + tmp, tmp, size, closure + )); + } else { + self.closures.push_str("|_bc: &[u8]| { let base = 0;Ok("); + self.closures.push_str(&block); + self.closures.push_str(") };\n"); + results.push(format!( + "wit_bindgen_wasmer::exports::PullBuffer::new( + &mut _bc, ptr{}, len{}, {}, &{})?", + tmp, tmp, size, closure + )); + } + } + } + + Instruction::BufferLowerHandle { push, ty } => { + let block = self.blocks.pop().unwrap(); + let size = self.gen.sizes.size(ty); + let tmp = self.tmp(); + let handle = format!("handle{}", tmp); + let closure = format!("closure{}", tmp); + self.needs_buffer_transaction = true; + if iface.all_bits_valid(ty) { + let method = if *push { "push_out_raw" } else { "push_in_raw" }; + self.push_str(&format!( + "let {} = unsafe {{ buffer_transaction.{}({}) }};\n", + handle, method, operands[0], + )); + } else if *push { + self.closures.push_str(&format!( + "let {} = |memory: &wasmer::Memory, base: i32| {{ + Ok(({}, {})) + }};\n", + closure, block, size, + )); + self.push_str(&format!( + "let {} = unsafe {{ buffer_transaction.push_out({}, &{}) }};\n", + handle, operands[0], closure, + )); + } else { + let ty = self.type_string(iface, ty, TypeMode::AllBorrowed("'_")); + self.closures.push_str(&format!( + "let {} = |memory: &wasmer::Memory, base: i32, e: {}| {{ + {}; + Ok({}) + }};\n", + closure, ty, block, size, + )); + self.push_str(&format!( + "let {} = unsafe {{ buffer_transaction.push_in({}, &{}) }};\n", + handle, operands[0], closure, + )); + } + results.push(format!("{}", handle)); + } + + Instruction::CallWasm { + module: _, + name, + sig, + } => { + if sig.results.len() > 0 { + let tmp = self.tmp(); + if sig.results.len() == 1 { + self.push_str("let "); + let arg = format!("result{}", tmp); + self.push_str(&arg); + results.push(arg); + self.push_str(" = "); + } else { + self.push_str("let ("); + for i in 0..sig.results.len() { + let arg = format!("result{}_{}", tmp, i); + self.push_str(&arg); + self.push_str(","); + results.push(arg); + } + self.push_str(") = "); + } + } + self.push_str("self.func_"); + self.push_str(&to_rust_ident(name)); + if self.gen.opts.async_.includes(name) { + self.push_str(".call_async("); + } else { + self.push_str(".call("); + } + for operand in operands { + self.push_str(operand); + self.push_str(", "); + } + self.push_str(")"); + if self.gen.opts.async_.includes(name) { + self.push_str(".await"); + } + self.push_str("?;\n"); + self.after_call = true; + self.caller_memory_available = false; // invalidated by call + } + + Instruction::CallWasmAsyncImport { .. } => unimplemented!(), + Instruction::CallWasmAsyncExport { .. } => unimplemented!(), + + Instruction::CallInterface { module: _, func } => { + for (i, operand) in operands.iter().enumerate() { + self.push_str(&format!("let param{} = {};\n", i, operand)); + } + if self.gen.opts.tracing && func.params.len() > 0 { + self.push_str("wit_bindgen_wasmer::tracing::event!(\n"); + self.push_str("wit_bindgen_wasmer::tracing::Level::TRACE,\n"); + for (i, (name, _ty)) in func.params.iter().enumerate() { + self.push_str(&format!( + "{} = wit_bindgen_wasmer::tracing::field::debug(¶m{}),\n", + to_rust_ident(name), + i + )); + } + self.push_str(");\n"); + } + + if self.func_takes_all_memory { + let mem = self.memory_src(); + self.push_str("let raw_memory = wit_bindgen_wasmer::RawMemory { slice: "); + self.push_str(&mem); + self.push_str(".raw() };\n"); + } + + let mut call = format!("host.{}(", func.name.to_snake_case()); + if self.func_takes_all_memory { + call.push_str("raw_memory, "); + } + for i in 0..operands.len() { + call.push_str(&format!("param{}, ", i)); + } + call.push_str(")"); + if self.gen.opts.async_.includes(&func.name) { + call.push_str(".await"); + } + + self.let_results(func.results.len(), results); + match self.gen.classify_fn_ret(iface, func) { + FunctionRet::Normal => self.push_str(&call), + // Unwrap the result, translating errors to unconditional + // traps + FunctionRet::CustomToTrap => { + self.push_str("match "); + self.push_str(&call); + self.push_str("{\n"); + self.push_str("Ok(val) => val,\n"); + self.push_str("Err(e) => return Err(host.error_to_trap(e)),\n"); + self.push_str("}"); + } + // Keep the `Result` as a `Result`, but convert the error + // to either the expected destination value or a trap, + // propagating a trap outwards. + FunctionRet::CustomToError { err, .. } => { + self.push_str("match "); + self.push_str(&call); + self.push_str("{\n"); + self.push_str("Ok(val) => Ok(val),\n"); + self.push_str(&format!("Err(e) => Err(host.error_to_{}(e)?),\n", err)); + self.push_str("}"); + } + } + self.push_str(";\n"); + self.after_call = true; + if self.gen.opts.tracing && func.results.len() > 0 { + self.push_str("wit_bindgen_wasmer::tracing::event!(\n"); + self.push_str("wit_bindgen_wasmer::tracing::Level::TRACE,\n"); + for name in results.iter() { + self.push_str(&format!( + "{} = wit_bindgen_wasmer::tracing::field::debug(&{0}),\n", + name, + )); + } + self.push_str(");\n"); + } + } + + Instruction::Return { amt, .. } => { + let result = match amt { + 0 => format!("Ok(())\n"), + 1 => format!("Ok({})\n", operands[0]), + _ => format!("Ok(({}))\n", operands.join(", ")), + }; + match self.cleanup.take() { + Some(cleanup) => { + self.push_str("let ret = "); + self.push_str(&result); + self.push_str(";\n"); + self.push_str(&cleanup); + self.push_str("ret"); + } + None => self.push_str(&result), + } + } + + Instruction::ReturnAsyncExport { .. } => unimplemented!(), + Instruction::ReturnAsyncImport { .. } => unimplemented!(), + + Instruction::I32Load { offset } => results.push(self.load(*offset, "i32", operands)), + Instruction::I32Load8U { offset } => { + results.push(format!("i32::from({})", self.load(*offset, "u8", operands))); + } + Instruction::I32Load8S { offset } => { + results.push(format!("i32::from({})", self.load(*offset, "i8", operands))); + } + Instruction::I32Load16U { offset } => { + results.push(format!( + "i32::from({})", + self.load(*offset, "u16", operands) + )); + } + Instruction::I32Load16S { offset } => { + results.push(format!( + "i32::from({})", + self.load(*offset, "i16", operands) + )); + } + Instruction::I64Load { offset } => results.push(self.load(*offset, "i64", operands)), + Instruction::F32Load { offset } => results.push(self.load(*offset, "f32", operands)), + Instruction::F64Load { offset } => results.push(self.load(*offset, "f64", operands)), + + Instruction::I32Store { offset } => self.store(*offset, "as_i32", "", operands), + Instruction::I64Store { offset } => self.store(*offset, "as_i64", "", operands), + Instruction::F32Store { offset } => self.store(*offset, "as_f32", "", operands), + Instruction::F64Store { offset } => self.store(*offset, "as_f64", "", operands), + Instruction::I32Store8 { offset } => self.store(*offset, "as_i32", " as u8", operands), + Instruction::I32Store16 { offset } => { + self.store(*offset, "as_i32", " as u16", operands) + } + + Instruction::Witx { instr } => match instr { + WitxInstruction::PointerFromI32 { .. } + | WitxInstruction::ConstPointerFromI32 { .. } => top_as("u32"), + i => unimplemented!("{:?}", i), + }, + } + } +} + +impl NeededFunction { + fn cvt(&self) -> &'static str { + match self { + NeededFunction::Realloc => "(i32, i32, i32, i32), i32", + NeededFunction::Free => "(i32, i32, i32), ()", + } + } + + fn ty(&self) -> String { + format!("wasmer::NativeFunc<{}>", self.cvt()) + } +} + +fn sorted_iter(map: &HashMap) -> impl Iterator { + let mut list = map.into_iter().collect::>(); + list.sort_by_key(|p| p.0); + list.into_iter() +} diff --git a/crates/gen-wasmer/tests/codegen.rs b/crates/gen-wasmer/tests/codegen.rs new file mode 100644 index 000000000..afc6da4cb --- /dev/null +++ b/crates/gen-wasmer/tests/codegen.rs @@ -0,0 +1,111 @@ +#![allow(dead_code, type_alias_bounds)] + +fn main() { + println!("compiled successfully!") +} + +#[rustfmt::skip] +mod imports { + test_helpers::codegen_wasmer_export!( + "*.wit" + "*.witx" + + // TODO: implement async support + "!async_functions.wit" + + // If you want to exclude a specific test you can include it here with + // gitignore glob syntax: + // + // "!wasm.wit" + // "!host.wit" + // + // + // Similarly you can also just remove the `*.wit` glob and list tests + // individually if you're debugging. + ); +} + +mod exports { + test_helpers::codegen_wasmer_import!( + "*.wit" + + // TODO: implement async support + "!async_functions.wit" + + // TODO: these use push/pull buffer which isn't implemented in the test + // generator just yet + "!wasi_next.wit" + "!host.wit" + ); +} + +/* +mod async_tests { + mod not_async { + wit_bindgen_wasmer::export!({ + src["x"]: "foo: function()", + async: ["bar"], + }); + + struct Me; + + impl x::X for Me { + fn foo(&mut self) {} + } + } + mod one_async { + wit_bindgen_wasmer::export!({ + src["x"]: " + foo: function() -> list + bar: function() + ", + async: ["bar"], + }); + + struct Me; + + #[wit_bindgen_wasmer::async_trait] + impl x::X for Me { + fn foo(&mut self) -> Vec { + Vec::new() + } + + async fn bar(&mut self) {} + } + } + mod one_async_export { + wit_bindgen_wasmer::import!({ + src["x"]: " + foo: function(x: list) + bar: function() + ", + async: ["bar"], + }); + } + mod resource_with_none_async { + wit_bindgen_wasmer::export!({ + src["x"]: " + resource y { + z: function() -> string + } + ", + async: [], + }); + } +} +*/ + +mod custom_errors { + wit_bindgen_wasmer::export!({ + src["x"]: " + foo: function() + bar: function() -> expected<_, u32> + enum errno { + bad1, + bad2, + } + baz: function() -> expected + ", + custom_error: true, + }); +} diff --git a/crates/gen-wasmer/tests/runtime.rs b/crates/gen-wasmer/tests/runtime.rs new file mode 100644 index 000000000..ef490d7c5 --- /dev/null +++ b/crates/gen-wasmer/tests/runtime.rs @@ -0,0 +1,23 @@ +use anyhow::Result; +use wasmer::{ImportObject, Instance, Module, Store}; +use wasmer_wasi::WasiState; + +test_helpers::runtime_tests_wasmer!(); + +fn instantiate( + wasm: &str, + add_imports: impl FnOnce(&Store, &mut ImportObject), + mk_exports: impl FnOnce(&Store, &Module, &mut ImportObject) -> Result<(T, Instance)>, +) -> Result { + let store = Store::default(); + let module = Module::from_file(&store, wasm)?; + + let mut wasi_env = WasiState::new("test").finalize()?; + let mut import_object = wasi_env + .import_object(&module) + .unwrap_or(ImportObject::new()); + add_imports(&store, &mut import_object); + + let (exports, _instance) = mk_exports(&store, &module, &mut import_object)?; + Ok(exports) +} diff --git a/crates/test-helpers/Cargo.toml b/crates/test-helpers/Cargo.toml index d7e1d5b4a..3bc4b418a 100644 --- a/crates/test-helpers/Cargo.toml +++ b/crates/test-helpers/Cargo.toml @@ -23,6 +23,7 @@ wit-bindgen-gen-wasmtime-py = { path = '../gen-wasmtime-py', optional = true } wit-bindgen-gen-js = { path = '../gen-js', optional = true } wit-bindgen-gen-c = { path = '../gen-c', optional = true } wit-bindgen-gen-spidermonkey = { path = '../gen-spidermonkey', optional = true } +wit-bindgen-gen-wasmer = { path = '../gen-wasmer', optional = true } wit-parser = { path = '../parser', features = ['witx-compat'] } filetime = "0.2" diff --git a/crates/test-helpers/src/lib.rs b/crates/test-helpers/src/lib.rs index cd029d170..eb917d2db 100644 --- a/crates/test-helpers/src/lib.rs +++ b/crates/test-helpers/src/lib.rs @@ -353,6 +353,46 @@ pub fn codegen_spidermonkey_export(input: TokenStream) -> TokenStream { }) } +#[proc_macro] +#[cfg(feature = "wit-bindgen-gen-wasmer")] +pub fn codegen_wasmer_import(input: TokenStream) -> TokenStream { + gen_rust( + input, + Direction::Import, + &[ + ( + "import", + || wit_bindgen_gen_wasmer::Opts::default().build(), + |_| quote::quote!(), + ), + ( + "import-tracing-and-custom-error", + || { + let mut opts = wit_bindgen_gen_wasmer::Opts::default(); + opts.tracing = true; + opts.custom_error = true; + opts.build() + }, + |_| quote::quote!(), + ), + ], + ) +} + +#[proc_macro] +#[cfg(feature = "wit-bindgen-gen-wasmer")] +pub fn codegen_wasmer_export(input: TokenStream) -> TokenStream { + gen_rust( + input, + Direction::Export, + &[( + "export", + || wit_bindgen_gen_wasmer::Opts::default().build(), + |_| quote::quote!(), + )], + ) +} + fn generate_tests( input: TokenStream, dir: &str, @@ -576,3 +616,36 @@ pub fn runtime_tests_wasmtime(_input: TokenStream) -> TokenStream { (quote::quote!(#(#tests)*)).into() } + +#[proc_macro] +#[cfg(feature = "wit-bindgen-gen-wasmer")] +pub fn runtime_tests_wasmer(_input: TokenStream) -> TokenStream { + let mut tests = Vec::new(); + let cwd = std::env::current_dir().unwrap(); + for entry in std::fs::read_dir(cwd.join("tests/runtime")).unwrap() { + let entry = entry.unwrap().path(); + if !entry.join("host-wasmer.rs").exists() { + continue; + } + let name_str = entry.file_name().unwrap().to_str().unwrap(); + for (lang, name, wasm) in WASMS { + if *name != name_str { + continue; + } + let name = quote::format_ident!("{}_{}", name_str, lang); + let host_file = entry.join("host-wasmer.rs").to_str().unwrap().to_string(); + tests.push(quote::quote! { + mod #name { + include!(#host_file); + + #[test] + fn test() -> anyhow::Result<()> { + run(#wasm) + } + } + }); + } + } + + (quote::quote!(#(#tests)*)).into() +} diff --git a/crates/wasmer-impl/Cargo.toml b/crates/wasmer-impl/Cargo.toml new file mode 100644 index 000000000..991d92abd --- /dev/null +++ b/crates/wasmer-impl/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "wit-bindgen-wasmer-impl" +version = "0.1.0" +authors = ["Alex Crichton "] +edition = "2018" + +[lib] +proc-macro = true +doctest = false +test = false + +[dependencies] +proc-macro2 = "1.0" +syn = "1.0" +wit-bindgen-gen-core = { path = "../gen-core", version = "0.1" } +wit-bindgen-gen-wasmer = { path = "../gen-wasmer", version = "0.1" } + +[features] +witx-compat = ['wit-bindgen-gen-wasmer/witx-compat'] +tracing = [] +async = [] diff --git a/crates/wasmer-impl/src/lib.rs b/crates/wasmer-impl/src/lib.rs new file mode 100644 index 000000000..866e8b77c --- /dev/null +++ b/crates/wasmer-impl/src/lib.rs @@ -0,0 +1,173 @@ +use proc_macro::TokenStream; +use syn::parse::{Error, Parse, ParseStream, Result}; +use syn::punctuated::Punctuated; +use syn::{token, Token}; +use wit_bindgen_gen_core::{wit_parser::Interface, Direction, Files, Generator}; +use wit_bindgen_gen_wasmer::Async; + +/// Generate code to support consuming the given interfaces, importaing them +/// from wasm modules. +#[proc_macro] +pub fn import(input: TokenStream) -> TokenStream { + run(input, Direction::Import) +} + +/// Generate code to support implementing the given interfaces and exporting +/// them to wasm modules. +#[proc_macro] +pub fn export(input: TokenStream) -> TokenStream { + run(input, Direction::Export) +} + +fn run(input: TokenStream, dir: Direction) -> TokenStream { + let input = syn::parse_macro_input!(input as Opts); + let mut gen = input.opts.build(); + let mut files = Files::default(); + let (imports, exports) = match dir { + Direction::Import => (input.interfaces, vec![]), + Direction::Export => (vec![], input.interfaces), + }; + gen.generate_all(&imports, &exports, &mut files); + + let (_, contents) = files.iter().next().unwrap(); + + let contents = std::str::from_utf8(contents).unwrap(); + let mut contents = contents.parse::().unwrap(); + + // Include a dummy `include_str!` for any files we read so rustc knows that + // we depend on the contents of those files. + let cwd = std::env::current_dir().unwrap(); + for file in input.files.iter() { + contents.extend( + format!( + "const _: &str = include_str!(r#\"{}\"#);\n", + cwd.join(file).display() + ) + .parse::() + .unwrap(), + ); + } + + return contents; +} + +struct Opts { + opts: wit_bindgen_gen_wasmer::Opts, + interfaces: Vec, + files: Vec, +} + +mod kw { + syn::custom_keyword!(src); + syn::custom_keyword!(paths); + syn::custom_keyword!(custom_error); +} + +impl Parse for Opts { + fn parse(input: ParseStream<'_>) -> Result { + let call_site = proc_macro2::Span::call_site(); + let mut opts = wit_bindgen_gen_wasmer::Opts::default(); + let mut files = Vec::new(); + opts.tracing = cfg!(feature = "tracing"); + + let interfaces = if input.peek(token::Brace) { + let content; + syn::braced!(content in input); + let mut interfaces = Vec::new(); + let fields = Punctuated::::parse_terminated(&content)?; + for field in fields.into_pairs() { + match field.into_value() { + ConfigField::Interfaces(v) => interfaces = v, + ConfigField::Async(v) => opts.async_ = v, + ConfigField::CustomError(v) => opts.custom_error = v, + } + } + if interfaces.is_empty() { + return Err(Error::new( + call_site, + "must either specify `src` or `paths` keys", + )); + } + interfaces + } else { + while !input.is_empty() { + let s = input.parse::()?; + files.push(s.value()); + } + let mut interfaces = Vec::new(); + for path in files.iter() { + let iface = Interface::parse_file(path).map_err(|e| Error::new(call_site, e))?; + interfaces.push(iface); + } + interfaces + }; + Ok(Opts { + opts, + interfaces, + files, + }) + } +} + +enum ConfigField { + Interfaces(Vec), + Async(wit_bindgen_gen_wasmer::Async), + CustomError(bool), +} + +impl Parse for ConfigField { + fn parse(input: ParseStream<'_>) -> Result { + let l = input.lookahead1(); + if l.peek(kw::src) { + input.parse::()?; + let name; + syn::bracketed!(name in input); + let name = name.parse::()?; + input.parse::()?; + let s = input.parse::()?; + let interface = + Interface::parse(&name.value(), &s.value()).map_err(|e| Error::new(s.span(), e))?; + Ok(ConfigField::Interfaces(vec![interface])) + } else if l.peek(kw::paths) { + input.parse::()?; + input.parse::()?; + let paths; + let bracket = syn::bracketed!(paths in input); + let paths = Punctuated::::parse_terminated(&paths)?; + let values = paths.iter().map(|s| s.value()).collect::>(); + let mut interfaces = Vec::new(); + for value in &values { + let interface = + Interface::parse_file(value).map_err(|e| Error::new(bracket.span, e))?; + interfaces.push(interface); + } + Ok(ConfigField::Interfaces(interfaces)) + } else if l.peek(token::Async) { + if !cfg!(feature = "async") { + return Err( + input.error("async support not enabled in the `wit-bindgen-wasmer` crate") + ); + } + input.parse::()?; + input.parse::()?; + let val = if input.parse::>()?.is_some() { + Async::All + } else { + let names; + syn::bracketed!(names in input); + let paths = Punctuated::::parse_terminated(&names)?; + let values = paths.iter().map(|s| s.value()).collect(); + Async::Only(values) + }; + Ok(ConfigField::Async(val)) + } else if l.peek(kw::custom_error) { + input.parse::()?; + input.parse::()?; + Ok(ConfigField::CustomError( + input.parse::()?.value, + )) + } else { + Err(l.error()) + } + } +} diff --git a/crates/wasmer/Cargo.toml b/crates/wasmer/Cargo.toml new file mode 100644 index 000000000..584531a38 --- /dev/null +++ b/crates/wasmer/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "wit-bindgen-wasmer" +version = "0.1.0" +authors = ["Alex Crichton "] +edition = "2018" + +[dependencies] +anyhow = "1.0" +bitflags = "1.2" +thiserror = "1.0" +wasmer = "2.1" +wit-bindgen-wasmer-impl = { path = "../wasmer-impl", version = "0.1" } +tracing-lib = { version = "0.1.26", optional = true, package = 'tracing' } +async-trait = { version = "0.1.50", optional = true } + +[features] +# Enables generated code to emit events via the `tracing` crate whenever wasm is +# entered and when native functions are called. Note that tracin is currently +# only done for imported functions. +tracing = ['tracing-lib', 'wit-bindgen-wasmer-impl/tracing'] + +# Enables async support for generated code, although when enabled this still +# needs to be configured through the macro invocation. +async = ['async-trait', 'wit-bindgen-wasmer-impl/async'] + +# Enables the ability to parse the old s-expression-based `*.wit` format. +witx-compat = ['wit-bindgen-wasmer-impl/witx-compat'] diff --git a/crates/wasmer/src/error.rs b/crates/wasmer/src/error.rs new file mode 100644 index 000000000..5cfab6667 --- /dev/null +++ b/crates/wasmer/src/error.rs @@ -0,0 +1,40 @@ +use crate::Region; +use thiserror::Error; + +#[derive(Debug, Error, PartialEq, Eq)] +pub enum GuestError { + #[error("Invalid flag value {0}")] + InvalidFlagValue(&'static str), + #[error("Invalid enum value {0}")] + InvalidEnumValue(&'static str), + #[error("Pointer overflow")] + PtrOverflow, + #[error("Pointer out of bounds: {0:?}")] + PtrOutOfBounds(Region), + #[error("Pointer not aligned to {1}: {0:?}")] + PtrNotAligned(Region, u32), + #[error("Pointer already borrowed: {0:?}")] + PtrBorrowed(Region), + #[error("Borrow checker out of handles")] + BorrowCheckerOutOfHandles, + #[error("Slice length mismatch")] + SliceLengthsDiffer, + #[error("In func {funcname}:{location}:")] + InFunc { + funcname: &'static str, + location: &'static str, + #[source] + err: Box, + }, + #[error("In data {typename}.{field}:")] + InDataField { + typename: String, + field: String, + #[source] + err: Box, + }, + #[error("Invalid UTF-8 encountered: {0:?}")] + InvalidUtf8(#[from] ::std::str::Utf8Error), + #[error("Int conversion error: {0:?}")] + TryFromIntError(#[from] ::std::num::TryFromIntError), +} diff --git a/crates/wasmer/src/exports.rs b/crates/wasmer/src/exports.rs new file mode 100644 index 000000000..fa05d992e --- /dev/null +++ b/crates/wasmer/src/exports.rs @@ -0,0 +1,90 @@ +use crate::BorrowChecker; +use std::fmt; +use std::mem; +use wasmer::RuntimeError; + +pub struct PullBuffer<'a, T> { + mem: &'a [u8], + size: usize, + deserialize: &'a (dyn Fn(&'a [u8]) -> Result + Send + Sync + 'a), +} + +impl<'a, T> PullBuffer<'a, T> { + pub fn new( + mem: &mut BorrowChecker<'a>, + offset: i32, + len: i32, + size: i32, + deserialize: &'a (dyn Fn(&'a [u8]) -> Result + Send + Sync + 'a), + ) -> Result, RuntimeError> { + Ok(PullBuffer { + mem: mem.slice(offset, len.saturating_mul(size))?, + size: size as usize, + deserialize, + }) + } + + pub fn len(&self) -> usize { + self.mem.len() / self.size + } + + pub fn iter(&self) -> impl Iterator> + 'a { + let deserialize = self.deserialize; + self.mem.chunks(self.size).map(deserialize) + } +} + +impl fmt::Debug for PullBuffer<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("PullBuffer") + .field("len", &self.len()) + .finish() + } +} + +pub struct PushBuffer<'a, T> { + mem: &'a mut [u8], + size: usize, + serialize: &'a (dyn Fn(&mut [u8], T) -> Result<(), RuntimeError> + Send + Sync + 'a), +} + +impl<'a, T> PushBuffer<'a, T> { + pub fn new( + mem: &mut BorrowChecker<'a>, + offset: i32, + len: i32, + size: i32, + serialize: &'a (dyn Fn(&mut [u8], T) -> Result<(), RuntimeError> + Send + Sync + 'a), + ) -> Result, RuntimeError> { + let mem = mem.slice_mut(offset, (len as u32).saturating_mul(size as u32) as i32)?; + Ok(PushBuffer { + mem, + size: size as usize, + serialize, + }) + } + + pub fn capacity(&self) -> usize { + self.mem.len() / self.size + } + + pub fn write(&mut self, iter: impl IntoIterator) -> Result<(), RuntimeError> { + for item in iter { + if self.mem.len() == 0 { + return Err(RuntimeError::new("too many results in `PushBuffer::write`")); + } + let (chunk, rest) = mem::take(&mut self.mem).split_at_mut(self.size); + self.mem = rest; + (self.serialize)(chunk, item)?; + } + Ok(()) + } +} + +impl fmt::Debug for PushBuffer<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("PushBuffer") + .field("capacity", &self.capacity()) + .finish() + } +} diff --git a/crates/wasmer/src/imports.rs b/crates/wasmer/src/imports.rs new file mode 100644 index 000000000..5c5c941cb --- /dev/null +++ b/crates/wasmer/src/imports.rs @@ -0,0 +1,413 @@ +use crate::slab::Slab; +use std::cell::RefCell; +use std::convert::TryFrom; +use std::mem; +use std::rc::Rc; +use wasmer::{Memory, RuntimeError}; + +#[derive(Default, Clone)] +pub struct BufferGlue { + inner: Rc>, +} + +#[derive(Default)] +struct Inner { + in_buffers: Slab>, + out_buffers: Slab>, +} + +struct Buffer { + len: u32, + kind: T, +} + +enum Input { + Bytes(*const u8, usize), + General { + shim: unsafe fn([usize; 2], *const u8, &Memory, i32, u32, &mut u32) -> Result<(), RuntimeError>, + iterator: [usize; 2], + serialize: *const u8, + }, +} + +enum Output { + Bytes(*mut u8, usize), + General { + shim: unsafe fn(*mut u8, *const u8, &Memory, i32, u32) -> Result<(), RuntimeError>, + dst: *mut u8, + deserialize: *const u8, + }, +} + +impl BufferGlue { + pub fn transaction(&self) -> BufferTransaction<'_> { + BufferTransaction { + handles: Vec::new(), + glue: self, + } + } + + pub fn in_len(&self, handle: u32) -> Result { + let mut inner = self.inner.borrow_mut(); + let b = inner + .in_buffers + .get_mut(handle) + .ok_or_else(|| RuntimeError::new("invalid in-buffer handle"))?; + Ok(b.len) + } + + /// Implementation of the canonical abi "in_read" function + pub fn in_read( + &self, + handle: u32, + memory: &wasmer::Memory, + base: u32, + len: u32, + ) -> Result<(), RuntimeError> { + let mut inner = self.inner.borrow_mut(); + let b = inner + .in_buffers + .get_mut(handle) + .ok_or_else(|| RuntimeError::new("invalid in-buffer handle"))?; + if len > b.len { + return Err(RuntimeError::new( + "more items requested from in-buffer than are available", + )); + } + unsafe { + match &mut b.kind { + Input::Bytes(ptr, elem_size) => { + let write_size = (len as usize) * *elem_size; + let dest = memory + .data_unchecked_mut() + .get_mut(base as usize..base as usize + write_size) + .ok_or_else(|| { + RuntimeError::new("out-of-bounds write while reading in-buffer") + })?; + dest.copy_from_slice(std::slice::from_raw_parts(*ptr, write_size)); + *ptr = (*ptr).add(write_size); + b.len -= len; + Ok(()) + } + &mut Input::General { + shim, + iterator, + serialize, + } => { + drop(inner); + let mut processed = 0; + let res = shim( + iterator, + serialize, + memory, + base as i32, + len, + &mut processed, + ); + self.inner + .borrow_mut() + .in_buffers + .get_mut(handle) + .expect("should still be there") + .len -= processed; + res + } + } + } + } + + pub fn out_len(&self, handle: u32) -> Result { + let mut inner = self.inner.borrow_mut(); + let b = inner + .out_buffers + .get_mut(handle) + .ok_or_else(|| RuntimeError::new("out in-buffer handle"))?; + Ok(b.len) + } + + /// Implementation of the canonical abi "out_write" function + pub fn out_write( + &self, + handle: u32, + memory: &wasmer::Memory, + base: u32, + len: u32, + ) -> Result<(), RuntimeError> { + let mut inner = self.inner.borrow_mut(); + let b = inner + .out_buffers + .get_mut(handle) + .ok_or_else(|| RuntimeError::new("invalid out-buffer handle"))?; + if len > b.len { + return Err(RuntimeError::new( + "more items written to out-buffer than are available", + )); + } + unsafe { + match &mut b.kind { + Output::Bytes(ptr, elem_size) => { + let read_size = (len as usize) * *elem_size; + let src = memory + .data_unchecked() + .get(base as usize..base as usize + read_size) + .ok_or_else(|| { + RuntimeError::new("out-of-bounds read while writing to out-buffer") + })?; + std::slice::from_raw_parts_mut(*ptr, read_size).copy_from_slice(src); + *ptr = (*ptr).add(read_size); + b.len -= len; + Ok(()) + } + &mut Output::General { + shim, + dst, + deserialize, + } => { + shim(dst, deserialize, memory, base as i32, len)?; + b.len -= len; + Ok(()) + } + } + } + } +} + +pub struct BufferTransaction<'a> { + glue: &'a BufferGlue, + handles: Vec<(bool, u32)>, +} + +impl<'call> BufferTransaction<'call> { + pub unsafe fn push_in_raw<'a, T>(&mut self, buffer: &'a [T]) -> i32 + where + 'a: 'call, + { + let mut inner = self.glue.inner.borrow_mut(); + let handle = inner.in_buffers.insert(Buffer { + len: u32::try_from(buffer.len()).unwrap(), + kind: Input::Bytes(buffer.as_ptr() as *const u8, mem::size_of::()), + }); + self.handles.push((false, handle)); + return handle as i32; + } + + pub unsafe fn push_in<'a, T, F>( + &mut self, + iter: &'a mut (dyn ExactSizeIterator + 'a), + write: &'a F, + ) -> i32 + where + F: Fn(&Memory, i32, T) -> Result + 'a, + 'a: 'call, + { + let mut inner = self.glue.inner.borrow_mut(); + let handle = inner.in_buffers.insert(Buffer { + len: u32::try_from(iter.len()).unwrap(), + kind: Input::General { + shim: shim::, + iterator: mem::transmute(iter), + serialize: write as *const F as *const u8, + }, + }); + self.handles.push((false, handle)); + return handle as i32; + + unsafe fn shim( + iter: [usize; 2], + serialize: *const u8, + memory: &Memory, + mut offset: i32, + len: u32, + processed: &mut u32, + ) -> Result<(), RuntimeError> + where + F: Fn(&Memory, i32, T) -> Result, + { + let iter = mem::transmute::<_, &mut dyn ExactSizeIterator>(iter); + let write = &*(serialize as *const F); + for _ in 0..len { + let item = iter.next().unwrap(); + offset += write(memory, offset, item)?; + *processed += 1; + } + Ok(()) + } + } + + pub unsafe fn push_out_raw<'a, T>(&mut self, buffer: &'a mut [T]) -> i32 + where + 'a: 'call, + { + let mut inner = self.glue.inner.borrow_mut(); + let handle = inner.out_buffers.insert(Buffer { + len: u32::try_from(buffer.len()).unwrap(), + kind: Output::Bytes(buffer.as_mut_ptr() as *mut u8, mem::size_of::()), + }); + self.handles.push((true, handle)); + return handle as i32; + } + + pub unsafe fn push_out<'a, T, F>(&mut self, dst: &'a mut Vec, read: &'a F) -> i32 + where + F: Fn(&Memory, i32) -> Result<(T, i32), RuntimeError> + 'a, + 'a: 'call, + { + let mut inner = self.glue.inner.borrow_mut(); + let handle = inner.out_buffers.insert(Buffer { + len: u32::try_from(dst.capacity() - dst.len()).unwrap(), + kind: Output::General { + shim: shim::, + dst: dst as *mut Vec as *mut u8, + deserialize: read as *const F as *const u8, + }, + }); + self.handles.push((true, handle)); + return handle as i32; + + unsafe fn shim( + dst: *mut u8, + deserialize: *const u8, + memory: &Memory, + mut offset: i32, + len: u32, + ) -> Result<(), RuntimeError> + where + F: Fn(&Memory, i32) -> Result<(T, i32), RuntimeError>, + { + let dst = &mut *(dst as *mut Vec); + let read = &*(deserialize as *const F); + for _ in 0..len { + let (item, size) = read(memory, offset)?; + dst.push(item); + offset += size; + } + Ok(()) + } + } +} + +impl Drop for BufferTransaction<'_> { + fn drop(&mut self) { + let mut inner = self.glue.inner.borrow_mut(); + for (out, handle) in self.handles.iter() { + if *out { + inner.out_buffers.remove(*handle); + } else { + inner.in_buffers.remove(*handle); + } + } + } +} + +///// Implementation of `(in-buffer T)`. +///// +///// Holds a region of memory to store into as well as an iterator of items to +///// serialize when calling an API. +//pub struct InBuffer<'a, T> { +// storage: &'a mut [u8], +// items: &'a mut dyn Iterator, +//} + +//impl<'a, T: 'a> InBuffer<'a, T> { +// /// Creates a new buffer where `items` are serialized into `storage` when +// /// this buffer is passed to a function call. +// /// +// /// # Panics +// /// +// /// `storage` must be large enough to store all the `items` +// /// provided. This will panic otherwise when passed to a callee. +// pub fn new(storage: &'a mut [u8], items: &'a mut dyn Iterator) -> InBuffer<'a, T> { +// InBuffer { storage, items } +// } + +// /// Called from adapters with implementation of how to serialize. +// #[doc(hidden)] +// pub fn serialize(&mut self, mut write: F) -> Result<&[u8], RuntimeError> +// where +// F: FnMut(&mut [u8], T) -> Result<(), RuntimeError>, +// { +// let mut len = 0; +// for (i, item) in self.items.enumerate() { +// let storage = &mut self.storage[N * i..][..N]; +// write(storage, item)?; +// len += 1; +// } +// Ok(&self.storage[..len * N]) +// } +//} + +//impl fmt::Debug for InBuffer<'_, T> { +// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +// f.debug_struct("InBuffer") +// .field("bytes", &self.storage.len()) +// .finish() +// } +//} + +///// Implementation of `(out-buffer T)` +//pub struct OutBuffer<'a, T: 'a> { +// storage: &'a mut [u8], +// deserialize: fn(&[u8]) -> T, +// element_size: usize, +//} + +//impl<'a, T: 'a> OutBuffer<'a, T> { +// /// Creates a new buffer with `storage` as where to store raw byte given to +// /// the host from wasm. +// /// +// /// The `storage` should be appropriately sized to hold the desired number +// /// of items to receive. +// pub fn new(storage: &'a mut [u8]) -> OutBuffer<'a, T> { +// OutBuffer { +// storage, +// deserialize: |_| loop {}, +// element_size: usize::max_value(), +// } +// } + +// #[doc(hidden)] +// pub fn storage( +// &mut self, +// _: usize, +// _: impl Fn(&[u8]) -> Result + Clone + 'static, +// ) -> &mut [u8] { +// &mut *self.storage +// } + +// ///// Called from adapters with implementation of how to deserialize. +// //#[doc(hidden)] +// //pub fn ptr_len(&mut self, deserialize: fn(i32) -> T) -> (i32, i32) { +// // self.element_size = N; +// // self.deserialize = deserialize; +// // ( +// // self.storage.as_ptr() as i32, +// // (self.storage.len() / N) as i32, +// // ) +// //} + +// ///// Consumes this output buffer, returning an iterator of the deserialized +// ///// version of all items that callee wrote. +// ///// +// ///// This is `unsafe` because the `amt` here is not known to be valid, and +// ///// deserializing arbitrary bytes is not safe. The callee should always +// ///// indicate how many items were written into this output buffer by some +// ///// other means. +// //pub unsafe fn into_iter(self, amt: usize) -> impl Iterator + 'a +// //where +// // T: 'a, +// //{ +// // let size = self.element_size; +// // (0..amt) +// // .map(move |i| i * size) +// // .map(move |i| (self.deserialize)(self.storage[i..][..size].as_mut_ptr() as i32)) +// // // TODO: data is leaked if this iterator isn't run in full +// //} +//} + +//impl fmt::Debug for OutBuffer<'_, T> { +// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +// f.debug_struct("OutBuffer") +// .field("bytes", &self.storage.len()) +// .finish() +// } +//} diff --git a/crates/wasmer/src/le.rs b/crates/wasmer/src/le.rs new file mode 100644 index 000000000..dd74b37c9 --- /dev/null +++ b/crates/wasmer/src/le.rs @@ -0,0 +1,194 @@ +use crate::AllBytesValid; +use std::cmp::Ordering; +use std::fmt; +use std::mem; +use std::slice; + +/// Helper type representing a 1-byte-aligned little-endian value in memory. +/// +/// This type is used in slice types for Wasmer host bindings. Guest types are +/// not guaranteed to be either aligned or in the native endianness. This type +/// wraps these types and provides explicit getters/setters to interact with the +/// underlying value in a safe host-agnostic manner. +#[repr(packed)] +pub struct Le(T); + +impl Le +where + T: Endian, +{ + /// Creates a new `Le` value where the internals are stored in a way + /// that's safe to copy into wasm linear memory. + pub fn new(t: T) -> Le { + Le(t.into_le()) + } + + /// Reads the value stored in this `Le`. + /// + /// This will perform a correct read even if the underlying memory is + /// unaligned, and it will also convert to the host's endianness for the + /// right representation of `T`. + pub fn get(&self) -> T { + self.0.from_le() + } + + /// Writes the `val` to this slot. + /// + /// This will work correctly even if the underlying memory is unaligned and + /// it will also automatically convert the `val` provided to an endianness + /// appropriate for WebAssembly (little-endian). + pub fn set(&mut self, val: T) { + self.0 = val.into_le(); + } + + pub(crate) fn from_slice(bytes: &[u8]) -> &[Le] { + // SAFETY: The invariants we uphold here are: + // + // * the lifetime of the input is the same as the output, so we're only + // dealing with valid memory. + // * the alignment of the input is the same as the output (1) + // * the input isn't being truncated and we're consuming all of it (it + // must be a multiple of the size of `Le`) + // * all byte-patterns for `Le` are valid. This is guaranteed by the + // `AllBytesValid` supertrait of `Endian`. + unsafe { + assert_eq!(mem::align_of::>(), 1); + assert!(bytes.len() % mem::size_of::>() == 0); + fn all_bytes_valid() {} + all_bytes_valid::>(); + + slice::from_raw_parts( + bytes.as_ptr().cast::>(), + bytes.len() / mem::size_of::>(), + ) + } + } + + pub(crate) fn from_slice_mut(bytes: &mut [u8]) -> &mut [Le] { + // SAFETY: see `from_slice` above + // + // Note that both the input and the output are `mut`, helping to + // maintain the guarantee of uniqueness. + unsafe { + assert_eq!(mem::align_of::>(), 1); + assert!(bytes.len() % mem::size_of::>() == 0); + slice::from_raw_parts_mut( + bytes.as_mut_ptr().cast::>(), + bytes.len() / mem::size_of::>(), + ) + } + } +} + +impl Clone for Le { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Le {} + +impl PartialEq for Le { + fn eq(&self, other: &Le) -> bool { + self.get() == other.get() + } +} + +impl PartialEq for Le { + fn eq(&self, other: &T) -> bool { + self.get() == *other + } +} + +impl Eq for Le {} + +impl PartialOrd for Le { + fn partial_cmp(&self, other: &Le) -> Option { + self.get().partial_cmp(&other.get()) + } +} + +impl Ord for Le { + fn cmp(&self, other: &Le) -> Ordering { + self.get().cmp(&other.get()) + } +} + +impl fmt::Debug for Le { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.get().fmt(f) + } +} + +impl From for Le { + fn from(t: T) -> Le { + Le::new(t) + } +} + +unsafe impl AllBytesValid for Le {} + +/// Trait used for the implementation of the `Le` type. +pub trait Endian: AllBytesValid + Copy + Sized { + /// Converts this value and any aggregate fields (if any) into little-endian + /// byte order + fn into_le(self) -> Self; + /// Converts this value and any aggregate fields (if any) from + /// little-endian byte order + fn from_le(self) -> Self; +} + +macro_rules! primitives { + ($($t:ident)*) => ($( + impl Endian for $t { + #[inline] + fn into_le(self) -> Self { + Self::from_ne_bytes(self.to_le_bytes()) + } + + #[inline] + fn from_le(self) -> Self { + Self::from_le_bytes(self.to_ne_bytes()) + } + } + )*) +} + +primitives! { + u8 i8 + u16 i16 + u32 i32 + u64 i64 + f32 f64 +} + +macro_rules! tuples { + ($(($($t:ident)*))*) => ($( + #[allow(non_snake_case)] + impl <$($t:Endian,)*> Endian for ($($t,)*) { + fn into_le(self) -> Self { + let ($($t,)*) = self; + ($($t.into_le(),)*) + } + + fn from_le(self) -> Self { + let ($($t,)*) = self; + ($($t.from_le(),)*) + } + } + )*) +} + +tuples! { + () + (T1) + (T1 T2) + (T1 T2 T3) + (T1 T2 T3 T4) + (T1 T2 T3 T4 T5) + (T1 T2 T3 T4 T5 T6) + (T1 T2 T3 T4 T5 T6 T7) + (T1 T2 T3 T4 T5 T6 T7 T8) + (T1 T2 T3 T4 T5 T6 T7 T8 T9) + (T1 T2 T3 T4 T5 T6 T7 T8 T9 T10) +} diff --git a/crates/wasmer/src/lib.rs b/crates/wasmer/src/lib.rs new file mode 100644 index 000000000..8349606a9 --- /dev/null +++ b/crates/wasmer/src/lib.rs @@ -0,0 +1,232 @@ +pub use wit_bindgen_wasmer_impl::{export, import}; + +#[cfg(feature = "async")] +pub use async_trait::async_trait; +#[cfg(feature = "tracing-lib")] +pub use tracing_lib as tracing; +#[doc(hidden)] +pub use {anyhow, bitflags, wasmer}; + +mod error; +pub mod exports; +pub mod imports; +mod le; +mod region; +mod slab; +mod table; + +pub use error::GuestError; +pub use le::{Endian, Le}; +pub use region::{AllBytesValid, BorrowChecker, Region}; +pub use table::*; + +pub struct RawMemory { + pub slice: *mut [u8], +} + +// This type is threadsafe despite its internal pointer because it allows no +// safe access to the internal pointer. Consumers must uphold Send/Sync +// guarantees themselves. +unsafe impl Send for RawMemory {} +unsafe impl Sync for RawMemory {} + +#[doc(hidden)] +pub mod rt { + use crate::slab::Slab; + use crate::{Endian, Le}; + use std::mem; + use wasmer::*; + + pub trait RawMem { + fn store(&mut self, offset: i32, val: T) -> Result<(), RuntimeError>; + fn store_many(&mut self, offset: i32, vals: &[T]) -> Result<(), RuntimeError>; + fn load(&self, offset: i32) -> Result; + } + + impl RawMem for [u8] { + fn store(&mut self, offset: i32, val: T) -> Result<(), RuntimeError> { + let mem = self + .get_mut(offset as usize..) + .and_then(|m| m.get_mut(..mem::size_of::())) + .ok_or_else(|| RuntimeError::new("out of bounds write"))?; + Le::from_slice_mut(mem)[0].set(val); + Ok(()) + } + + fn store_many(&mut self, offset: i32, val: &[T]) -> Result<(), RuntimeError> { + let mem = self + .get_mut(offset as usize..) + .and_then(|m| { + let len = mem::size_of::().checked_mul(val.len())?; + m.get_mut(..len) + }) + .ok_or_else(|| RuntimeError::new("out of bounds write"))?; + for (slot, val) in Le::from_slice_mut(mem).iter_mut().zip(val) { + slot.set(*val); + } + Ok(()) + } + + fn load(&self, offset: i32) -> Result { + let mem = self + .get(offset as usize..) + .and_then(|m| m.get(..mem::size_of::>())) + .ok_or_else(|| RuntimeError::new("out of bounds read"))?; + Ok(Le::from_slice(mem)[0].get()) + } + } + + pub fn char_from_i32(val: i32) -> Result { + core::char::from_u32(val as u32) + .ok_or_else(|| RuntimeError::new("char value out of valid range")) + } + + pub fn invalid_variant(name: &str) -> RuntimeError { + let msg = format!("invalid discriminant for `{}`", name); + RuntimeError::new(msg) + } + + pub fn validate_flags( + bits: i64, + all: i64, + name: &str, + mk: impl FnOnce(i64) -> U, + ) -> Result { + if bits & !all != 0 { + let msg = format!("invalid flags specified for `{}`", name); + Err(RuntimeError::new(msg)) + } else { + Ok(mk(bits)) + } + } + + pub fn bad_int(_: std::num::TryFromIntError) -> RuntimeError { + let msg = "out-of-bounds integer conversion"; + RuntimeError::new(msg) + } + + pub fn copy_slice( + memory: &Memory, + free: &NativeFunc<(i32, i32, i32), ()>, + base: i32, + len: i32, + align: i32, + ) -> Result, RuntimeError> { + let size = (len as u32) + .checked_mul(mem::size_of::() as u32) + .ok_or_else(|| RuntimeError::new("array too large to fit in wasm memory"))?; + let slice = unsafe { + memory + .data_unchecked() + .get(base as usize..) + .and_then(|s| s.get(..size as usize)) + .ok_or_else(|| RuntimeError::new("out of bounds read"))? + }; + let result = Le::from_slice(slice).iter().map(|s| s.get()).collect(); + free.call(base, size as i32, align)?; + Ok(result) + } + + macro_rules! as_traits { + ($(($name:ident $tr:ident $ty:ident ($($tys:ident)*)))*) => ($( + pub fn $name(t: T) -> $ty { + t.$name() + } + + pub trait $tr { + fn $name(self) -> $ty; + } + + impl<'a, T: Copy + $tr> $tr for &'a T { + fn $name(self) -> $ty { + (*self).$name() + } + } + + $( + impl $tr for $tys { + #[inline] + fn $name(self) -> $ty { + self as $ty + } + } + )* + )*) + } + + as_traits! { + (as_i32 AsI32 i32 (char i8 u8 i16 u16 i32 u32)) + (as_i64 AsI64 i64 (i64 u64)) + (as_f32 AsF32 f32 (f32)) + (as_f64 AsF64 f64 (f64)) + } + + #[derive(Default, Debug)] + pub struct IndexSlab { + slab: Slab, + } + + impl IndexSlab { + pub fn insert(&mut self, resource: ResourceIndex) -> u32 { + self.slab.insert(resource) + } + + pub fn get(&self, slab_idx: u32) -> Result { + match self.slab.get(slab_idx) { + Some(idx) => Ok(*idx), + None => Err(RuntimeError::new("invalid index specified for handle")), + } + } + + pub fn remove(&mut self, slab_idx: u32) -> Result { + match self.slab.remove(slab_idx) { + Some(idx) => Ok(idx), + None => Err(RuntimeError::new("invalid index specified for handle")), + } + } + } + + #[derive(Default, Debug)] + pub struct ResourceSlab { + slab: Slab, + } + + #[derive(Debug)] + struct Resource { + wasm: i32, + refcnt: u32, + } + + #[derive(Debug, Copy, Clone)] + pub struct ResourceIndex(u32); + + impl ResourceSlab { + pub fn insert(&mut self, wasm: i32) -> ResourceIndex { + ResourceIndex(self.slab.insert(Resource { wasm, refcnt: 1 })) + } + + pub fn get(&self, idx: ResourceIndex) -> i32 { + self.slab.get(idx.0).unwrap().wasm + } + + pub fn clone(&mut self, idx: ResourceIndex) -> Result<(), RuntimeError> { + let resource = self.slab.get_mut(idx.0).unwrap(); + resource.refcnt = match resource.refcnt.checked_add(1) { + Some(cnt) => cnt, + None => return Err(RuntimeError::new("resource index count overflow")), + }; + Ok(()) + } + + pub fn drop(&mut self, idx: ResourceIndex) -> Option { + let resource = self.slab.get_mut(idx.0).unwrap(); + assert!(resource.refcnt > 0); + resource.refcnt -= 1; + if resource.refcnt != 0 { + return None; + } + let resource = self.slab.remove(idx.0).unwrap(); + Some(resource.wasm) + } + } +} diff --git a/crates/wasmer/src/region.rs b/crates/wasmer/src/region.rs new file mode 100644 index 000000000..650d073b9 --- /dev/null +++ b/crates/wasmer/src/region.rs @@ -0,0 +1,312 @@ +use crate::rt::RawMem; +use crate::{Endian, GuestError, Le}; +use std::collections::HashSet; +use std::convert::TryInto; +use std::marker; +use std::mem; +use wasmer::RuntimeError; + +// This is a pretty naive way to account for borrows. This datastructure +// could be made a lot more efficient with some effort. +pub struct BorrowChecker<'a> { + /// Maps from handle to region borrowed. A HashMap is probably not ideal + /// for this but it works. It would be more efficient if we could + /// check `is_borrowed` without an O(n) iteration, by organizing borrows + /// by an ordering of Region. + shared_borrows: HashSet, + mut_borrows: HashSet, + _marker: marker::PhantomData<&'a mut [u8]>, + ptr: *mut u8, + len: usize, +} + +// These are not automatically implemented with our storage of `*mut u8`, so we +// need to manually declare that this type is threadsafe. +unsafe impl Send for BorrowChecker<'_> {} +unsafe impl Sync for BorrowChecker<'_> {} + +fn to_error(err: impl std::fmt::Display) -> RuntimeError { + RuntimeError::new(err.to_string()) +} + +impl<'a> BorrowChecker<'a> { + pub fn new(data: &'a mut [u8]) -> BorrowChecker<'a> { + BorrowChecker { + ptr: data.as_mut_ptr(), + len: data.len(), + shared_borrows: Default::default(), + mut_borrows: Default::default(), + _marker: marker::PhantomData, + } + } + + pub fn slice(&mut self, ptr: i32, len: i32) -> Result<&'a [T], RuntimeError> { + let (ret, r) = self.get_slice(ptr, len)?; + // SAFETY: We're promoting the valid lifetime of `ret` from a temporary + // borrow on `self` to `'a` on this `BorrowChecker`. At the same time + // we're recording that this is a persistent shared borrow (until this + // borrow checker is deleted), which disallows future mutable borrows + // of the same data. + let ret = unsafe { &*(ret as *const [T]) }; + self.shared_borrows.insert(r); + Ok(ret) + } + + pub fn slice_mut(&mut self, ptr: i32, len: i32) -> Result<&'a mut [T], RuntimeError> { + let (ret, r) = self.get_slice_mut(ptr, len)?; + // SAFETY: see `slice` for how we're extending the lifetime by + // recording the borrow here. Note that the `mut_borrows` list is + // checked on both shared and mutable borrows in the future since a + // mutable borrow can't alias with anything. + let ret = unsafe { &mut *(ret as *mut [T]) }; + self.mut_borrows.insert(r); + Ok(ret) + } + + fn get_slice(&self, ptr: i32, len: i32) -> Result<(&[T], Region), RuntimeError> { + let r = self.region::(ptr, len)?; + if self.is_mut_borrowed(r) { + Err(to_error(GuestError::PtrBorrowed(r))) + } else { + Ok(( + // SAFETY: invariants to uphold: + // + // * The lifetime of the input is valid for the lifetime of the + // output. In this case we're threading through the lifetime + // of `&self` to the output. + // * The actual output is valid, which is guaranteed with the + // `AllBytesValid` bound. + // * We uphold Rust's borrowing guarantees, namely that this + // borrow we're returning isn't overlapping with any mutable + // borrows. + // * The region `r` we're returning accurately describes the + // slice we're returning in wasm linear memory. + unsafe { + std::slice::from_raw_parts( + self.ptr.add(r.start as usize) as *const T, + len as usize, + ) + }, + r, + )) + } + } + + fn get_slice_mut(&mut self, ptr: i32, len: i32) -> Result<(&mut [T], Region), RuntimeError> { + let r = self.region::(ptr, len)?; + if self.is_mut_borrowed(r) || self.is_shared_borrowed(r) { + Err(to_error(GuestError::PtrBorrowed(r))) + } else { + Ok(( + // SAFETY: same as `get_slice`, except for that we're threading + // through `&mut` properties as well. + unsafe { + std::slice::from_raw_parts_mut( + self.ptr.add(r.start as usize) as *mut T, + len as usize, + ) + }, + r, + )) + } + } + + fn region(&self, ptr: i32, len: i32) -> Result { + assert_eq!(std::mem::align_of::(), 1); + let r = Region { + start: ptr as u32, + len: (len as u32) + .checked_mul(mem::size_of::() as u32) + .ok_or_else(|| to_error(GuestError::PtrOverflow))?, + }; + self.validate_contains(&r)?; + Ok(r) + } + + pub fn slice_str(&mut self, ptr: i32, len: i32) -> Result<&'a str, RuntimeError> { + let bytes = self.slice(ptr, len)?; + std::str::from_utf8(bytes).map_err(to_error) + } + + fn validate_contains(&self, region: &Region) -> Result<(), RuntimeError> { + let end = region + .start + .checked_add(region.len) + .ok_or_else(|| to_error(GuestError::PtrOverflow))? as usize; + if end <= self.len { + Ok(()) + } else { + Err(to_error(GuestError::PtrOutOfBounds(*region))) + } + } + + fn is_shared_borrowed(&self, r: Region) -> bool { + self.shared_borrows.iter().any(|b| b.overlaps(r)) + } + + fn is_mut_borrowed(&self, r: Region) -> bool { + self.mut_borrows.iter().any(|b| b.overlaps(r)) + } + + pub fn raw(&self) -> *mut [u8] { + std::ptr::slice_from_raw_parts_mut(self.ptr, self.len) + } +} + +impl RawMem for BorrowChecker<'_> { + fn store(&mut self, offset: i32, val: T) -> Result<(), RuntimeError> { + let (slice, _) = self.get_slice_mut::>(offset, 1)?; + slice[0].set(val); + Ok(()) + } + + fn store_many(&mut self, offset: i32, val: &[T]) -> Result<(), RuntimeError> { + let (slice, _) = self.get_slice_mut::>( + offset, + val.len() + .try_into() + .map_err(|_| to_error(GuestError::PtrOverflow))?, + )?; + for (slot, val) in slice.iter_mut().zip(val) { + slot.set(*val); + } + Ok(()) + } + + fn load(&self, offset: i32) -> Result { + let (slice, _) = self.get_slice::>(offset, 1)?; + Ok(slice[0].get()) + } +} + +/// Unsafe trait representing types where every byte pattern is valid for their +/// representation. +/// +/// This is the set of types which wasmer can have a raw pointer to for +/// values which reside in wasm linear memory. +pub unsafe trait AllBytesValid {} + +unsafe impl AllBytesValid for u8 {} +unsafe impl AllBytesValid for u16 {} +unsafe impl AllBytesValid for u32 {} +unsafe impl AllBytesValid for u64 {} +unsafe impl AllBytesValid for i8 {} +unsafe impl AllBytesValid for i16 {} +unsafe impl AllBytesValid for i32 {} +unsafe impl AllBytesValid for i64 {} +unsafe impl AllBytesValid for f32 {} +unsafe impl AllBytesValid for f64 {} + +macro_rules! tuples { + ($(($($t:ident)*))*) => ($( + unsafe impl <$($t:AllBytesValid,)*> AllBytesValid for ($($t,)*) {} + )*) +} + +tuples! { + () + (T1) + (T1 T2) + (T1 T2 T3) + (T1 T2 T3 T4) + (T1 T2 T3 T4 T5) + (T1 T2 T3 T4 T5 T6) + (T1 T2 T3 T4 T5 T6 T7) + (T1 T2 T3 T4 T5 T6 T7 T8) + (T1 T2 T3 T4 T5 T6 T7 T8 T9) + (T1 T2 T3 T4 T5 T6 T7 T8 T9 T10) +} + +/// Represents a contiguous region in memory. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct Region { + pub start: u32, + pub len: u32, +} + +impl Region { + /// Checks if this `Region` overlaps with `rhs` `Region`. + fn overlaps(&self, rhs: Region) -> bool { + // Zero-length regions can never overlap! + if self.len == 0 || rhs.len == 0 { + return false; + } + + let self_start = self.start as u64; + let self_end = self_start + (self.len - 1) as u64; + + let rhs_start = rhs.start as u64; + let rhs_end = rhs_start + (rhs.len - 1) as u64; + + if self_start <= rhs_start { + self_end >= rhs_start + } else { + rhs_end >= self_start + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn nonoverlapping() { + let mut bytes = [0; 100]; + let mut bc = BorrowChecker::new(&mut bytes); + bc.slice::(0, 10).unwrap(); + bc.slice::(10, 10).unwrap(); + + let mut bc = BorrowChecker::new(&mut bytes); + bc.slice::(10, 10).unwrap(); + bc.slice::(0, 10).unwrap(); + + let mut bc = BorrowChecker::new(&mut bytes); + bc.slice_mut::(0, 10).unwrap(); + bc.slice_mut::(10, 10).unwrap(); + + let mut bc = BorrowChecker::new(&mut bytes); + bc.slice_mut::(10, 10).unwrap(); + bc.slice_mut::(0, 10).unwrap(); + } + + #[test] + fn overlapping() { + let mut bytes = [0; 100]; + let mut bc = BorrowChecker::new(&mut bytes); + bc.slice::(0, 10).unwrap(); + bc.slice_mut::(9, 10).unwrap_err(); + bc.slice::(9, 10).unwrap(); + + let mut bc = BorrowChecker::new(&mut bytes); + bc.slice::(0, 10).unwrap(); + bc.slice_mut::(2, 5).unwrap_err(); + bc.slice::(2, 5).unwrap(); + + let mut bc = BorrowChecker::new(&mut bytes); + bc.slice::(9, 10).unwrap(); + bc.slice_mut::(0, 10).unwrap_err(); + bc.slice::(0, 10).unwrap(); + + let mut bc = BorrowChecker::new(&mut bytes); + bc.slice::(2, 5).unwrap(); + bc.slice_mut::(0, 10).unwrap_err(); + bc.slice::(0, 10).unwrap(); + + let mut bc = BorrowChecker::new(&mut bytes); + bc.slice::(2, 5).unwrap(); + bc.slice::(10, 5).unwrap(); + bc.slice::(15, 5).unwrap(); + bc.slice_mut::(0, 10).unwrap_err(); + bc.slice::(0, 10).unwrap(); + } + + #[test] + fn zero_length() { + let mut bytes = [0; 100]; + let mut bc = BorrowChecker::new(&mut bytes); + bc.slice_mut::(0, 0).unwrap(); + bc.slice_mut::(0, 0).unwrap(); + bc.slice::(0, 1).unwrap(); + } +} diff --git a/crates/wasmer/src/slab.rs b/crates/wasmer/src/slab.rs new file mode 100644 index 000000000..29db0c9ba --- /dev/null +++ b/crates/wasmer/src/slab.rs @@ -0,0 +1,72 @@ +use std::fmt; +use std::mem; + +pub struct Slab { + storage: Vec>, + next: usize, +} + +enum Entry { + Full(T), + Empty { next: usize }, +} + +impl Slab { + pub fn insert(&mut self, item: T) -> u32 { + if self.next == self.storage.len() { + self.storage.push(Entry::Empty { + next: self.next + 1, + }); + } + let ret = self.next as u32; + let entry = Entry::Full(item); + self.next = match mem::replace(&mut self.storage[self.next], entry) { + Entry::Empty { next } => next, + _ => unreachable!(), + }; + return ret; + } + + pub fn get(&self, idx: u32) -> Option<&T> { + match self.storage.get(idx as usize)? { + Entry::Full(b) => Some(b), + Entry::Empty { .. } => None, + } + } + + pub fn get_mut(&mut self, idx: u32) -> Option<&mut T> { + match self.storage.get_mut(idx as usize)? { + Entry::Full(b) => Some(b), + Entry::Empty { .. } => None, + } + } + + pub fn remove(&mut self, idx: u32) -> Option { + let slot = self.storage.get_mut(idx as usize)?; + match mem::replace(slot, Entry::Empty { next: self.next }) { + Entry::Full(b) => { + self.next = idx as usize; + Some(b) + } + Entry::Empty { next } => { + *slot = Entry::Empty { next }; + None + } + } + } +} + +impl Default for Slab { + fn default() -> Slab { + Slab { + storage: Vec::new(), + next: 0, + } + } +} + +impl fmt::Debug for Slab { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Slab").finish() + } +} diff --git a/crates/wasmer/src/table.rs b/crates/wasmer/src/table.rs new file mode 100644 index 000000000..c3f6fa2c4 --- /dev/null +++ b/crates/wasmer/src/table.rs @@ -0,0 +1,144 @@ +use std::convert::TryFrom; +use std::fmt; +use std::mem; + +pub struct Table { + elems: Vec>, + next: usize, +} + +#[derive(Debug)] +pub enum RemoveError { + NotAllocated, +} + +enum Slot { + Empty { next_empty: usize }, + Full { item: Box }, +} + +impl Table { + /// Creates a new empty table + pub fn new() -> Table { + Table { + elems: Vec::new(), + next: 0, + } + } + + /// Inserts an item into this table, returning the index that it was + /// inserted at. + pub fn insert(&mut self, item: T) -> u32 { + if self.next == self.elems.len() { + let next_empty = self.next + 1; + self.elems.push(Slot::Empty { next_empty }); + } + let index = self.next; + let ret = u32::try_from(index).unwrap(); + self.next = match &self.elems[index] { + Slot::Empty { next_empty } => *next_empty, + Slot::Full { .. } => unreachable!(), + }; + self.elems[index] = Slot::Full { + item: Box::new(item), + }; + return ret; + } + + /// Borrows an item from this table. + /// + /// Returns `None` if the index is not allocated at this time. Otherwise + /// returns `Some` with a borrow of the item from this table. + pub fn get(&self, item: u32) -> Option<&T> { + let index = usize::try_from(item).unwrap(); + match self.elems.get(index)? { + Slot::Empty { .. } => None, + Slot::Full { item } => Some(item), + } + } + + /// Removes an item from this table. + /// + /// On success it returns back the original item. + pub fn remove(&mut self, item: u32) -> Result { + let index = usize::try_from(item).unwrap(); + let new_empty = Slot::Empty { + next_empty: self.next, + }; + let slot = self.elems.get_mut(index).ok_or(RemoveError::NotAllocated)?; + + // Assume that `item` is valid, and if it is, we can return quickly + match mem::replace(slot, new_empty) { + Slot::Full { item } => { + self.next = index; + Ok(*item) + } + + // Oops `item` wasn't valid, put it back where we found it and then + // figure out why it was invalid + Slot::Empty { next_empty } => { + *slot = Slot::Empty { next_empty }; + Err(RemoveError::NotAllocated) + } + } + } +} + +impl Default for Table { + fn default() -> Table { + Table::new() + } +} + +impl fmt::Debug for Table { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Table") + .field("capacity", &self.elems.capacity()) + .finish() + } +} + +impl fmt::Display for RemoveError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + RemoveError::NotAllocated => f.write_str("invalid handle index"), + } + } +} + +impl std::error::Error for RemoveError {} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn simple() { + let mut table = Table::new(); + assert_eq!(table.insert(0), 0); + assert_eq!(table.insert(100), 1); + assert_eq!(table.insert(200), 2); + + assert_eq!(*table.get(0).unwrap(), 0); + assert_eq!(*table.get(1).unwrap(), 100); + assert_eq!(*table.get(2).unwrap(), 200); + assert!(table.get(100).is_none()); + + assert!(table.remove(0).is_ok()); + assert!(table.get(0).is_none()); + assert_eq!(table.insert(1), 0); + assert!(table.get(0).is_some()); + + table.get(1).unwrap(); + assert!(table.remove(1).is_ok()); + assert!(table.remove(1).is_err()); + + assert!(table.remove(2).is_ok()); + assert!(table.remove(0).is_ok()); + + assert_eq!(table.insert(100), 0); + assert_eq!(table.insert(100), 2); + assert_eq!(table.insert(100), 1); + assert_eq!(table.insert(100), 3); + } +} diff --git a/crates/wit-bindgen-demo/Cargo.toml b/crates/wit-bindgen-demo/Cargo.toml index 08c6fd3e6..cd88bcd6d 100644 --- a/crates/wit-bindgen-demo/Cargo.toml +++ b/crates/wit-bindgen-demo/Cargo.toml @@ -19,5 +19,6 @@ wit-bindgen-gen-js = { path = '../gen-js' } wit-bindgen-gen-c = { path = '../gen-c' } wit-bindgen-gen-markdown = { path = '../gen-markdown' } wit-bindgen-gen-spidermonkey = { path = '../gen-spidermonkey' } +wit-bindgen-gen-wasmer = { path = '../gen-wasmer' } wit-bindgen-rust = { path = '../rust-wasm' } wasmprinter = "0.2.29" diff --git a/crates/wit-bindgen-demo/demo.wit b/crates/wit-bindgen-demo/demo.wit index 4b8869b36..a5191eefa 100644 --- a/crates/wit-bindgen-demo/demo.wit +++ b/crates/wit-bindgen-demo/demo.wit @@ -14,6 +14,7 @@ enum lang { c, markdown, spidermonkey, + wasmer, } @@ -26,4 +27,7 @@ resource config { set_wasmtime_tracing: function(unchecked: bool) set_wasmtime_async: function(val: wasmtime_async) set_wasmtime_custom_error: function(custom: bool) + set_wasmer_tracing: function(unchecked: bool) + set_wasmer_async: function(val: wasmtime_async) + set_wasmer_custom_error: function(custom: bool) } diff --git a/crates/wit-bindgen-demo/index.html b/crates/wit-bindgen-demo/index.html index f30d318c7..3c9e994c5 100644 --- a/crates/wit-bindgen-demo/index.html +++ b/crates/wit-bindgen-demo/index.html @@ -56,6 +56,7 @@

Generated bindings

+ · @@ -105,6 +106,22 @@

Generated bindings

+
+ · + + + + + · + + + + + · + + + +
diff --git a/crates/wit-bindgen-demo/main.ts b/crates/wit-bindgen-demo/main.ts index 052bd8ada..379c168c4 100644 --- a/crates/wit-bindgen-demo/main.ts +++ b/crates/wit-bindgen-demo/main.ts @@ -10,6 +10,9 @@ class Editor { wasmtimeTracing: HTMLInputElement; wasmtimeAsync: HTMLInputElement; wasmtimeCustomError: HTMLInputElement; + wasmerTracing: HTMLInputElement; + wasmerAsync: HTMLInputElement; + wasmerCustomError: HTMLInputElement; generatedFiles: Record; demo: Demo; config: Config | null; @@ -27,6 +30,9 @@ class Editor { this.wasmtimeTracing = document.getElementById('wasmtime-tracing') as HTMLInputElement; this.wasmtimeAsync = document.getElementById('wasmtime-async') as HTMLInputElement; this.wasmtimeCustomError = document.getElementById('wasmtime-custom-error') as HTMLInputElement; + this.wasmerTracing = document.getElementById('wasmer-tracing') as HTMLInputElement; + this.wasmerAsync = document.getElementById('wasmer-async') as HTMLInputElement; + this.wasmerCustomError = document.getElementById('wasmer-custom-error') as HTMLInputElement; this.outputHtml = document.getElementById('html-output') as HTMLDivElement; this.inputEditor = ace.edit("input"); @@ -89,6 +95,23 @@ class Editor { this.config.setWasmtimeCustomError(this.wasmtimeCustomError.checked); this.render(); }); + this.wasmerTracing.addEventListener('change', () => { + this.config.setWasmerTracing(this.wasmerTracing.checked); + this.render(); + }); + this.wasmerAsync.addEventListener('change', () => { + let async_; + if (this.wasmerAsync.checked) + async_ = { tag: 'all' }; + else + async_ = { tag: 'none' }; + this.config.setWasmerAsync(async_); + this.render(); + }); + this.wasmerCustomError.addEventListener('change', () => { + this.config.setWasmerCustomError(this.wasmerCustomError.checked); + this.render(); + }); this.files.addEventListener('change', () => this.updateSelectedFile()); } @@ -112,6 +135,7 @@ class Editor { case "c": lang = Lang.C; break; case "markdown": lang = Lang.Markdown; break; case "spidermonkey": lang = Lang.Spidermonkey; break; + case "wasmer": lang = Lang.Wasmer; break; default: return; } const result = this.config.render(lang, wit, is_import); diff --git a/crates/wit-bindgen-demo/src/lib.rs b/crates/wit-bindgen-demo/src/lib.rs index 2e55451e8..2b77f0cff 100644 --- a/crates/wit-bindgen-demo/src/lib.rs +++ b/crates/wit-bindgen-demo/src/lib.rs @@ -20,6 +20,7 @@ pub struct Config { wasmtime_py: RefCell, markdown: RefCell, spidermonkey: RefCell, + wasmer: RefCell, } impl demo::Config for Config { @@ -56,6 +57,7 @@ impl demo::Config for Config { let script = "throw new Error('unimplemented');"; Box::new(opts.clone().build(script)) } + demo::Lang::Wasmer => Box::new(self.wasmer.borrow().clone().build()), }; let iface = Interface::parse("input", &wit).map_err(|e| format!("{:?}", e))?; let mut files = Default::default(); @@ -98,4 +100,20 @@ impl demo::Config for Config { demo::WasmtimeAsync::Only(list) => Async::Only(list.into_iter().collect()), }; } + fn set_wasmer_tracing(&self, tracing: bool) { + self.wasmer.borrow_mut().tracing = tracing; + } + fn set_wasmer_custom_error(&self, custom_error: bool) { + browser::log("custom error"); + self.wasmer.borrow_mut().custom_error = custom_error; + } + fn set_wasmer_async(&self, async_: demo::WasmtimeAsync) { + use wit_bindgen_gen_wasmer::Async; + + self.wasmer.borrow_mut().async_ = match async_ { + demo::WasmtimeAsync::All => Async::All, + demo::WasmtimeAsync::None => Async::None, + demo::WasmtimeAsync::Only(list) => Async::Only(list.into_iter().collect()), + }; + } } diff --git a/src/bin/wit-bindgen.rs b/src/bin/wit-bindgen.rs index 836ea066c..fcb08f32f 100644 --- a/src/bin/wit-bindgen.rs +++ b/src/bin/wit-bindgen.rs @@ -55,6 +55,12 @@ enum Command { #[structopt(flatten)] common: Common, }, + Wasmer { + #[structopt(flatten)] + opts: wit_bindgen_gen_wasmer::Opts, + #[structopt(flatten)] + common: Common, + }, } #[derive(Debug, StructOpt)] @@ -88,6 +94,7 @@ fn main() -> Result<()> { .with_context(|| format!("failed to read {}", opts.js.display()))?; (Box::new(opts.build(js_source)), common) } + Command::Wasmer { opts, common } => (Box::new(opts.build()), common), }; let imports = common diff --git a/tests/runtime/buffers/host-wasmer.rs b/tests/runtime/buffers/host-wasmer.rs new file mode 100644 index 000000000..f31da8308 --- /dev/null +++ b/tests/runtime/buffers/host-wasmer.rs @@ -0,0 +1,195 @@ +wit_bindgen_wasmer::export!("./tests/runtime/buffers/imports.wit"); + +use anyhow::Result; +use imports::*; +use wasmer::WasmerEnv; +use wit_bindgen_wasmer::exports::{PullBuffer, PushBuffer}; +use wit_bindgen_wasmer::Le; + +#[derive(WasmerEnv, Clone)] +pub struct MyImports; + +impl Imports for MyImports { + fn buffer_u8(&mut self, in_: &[u8], out: &mut [u8]) -> u32 { + assert_eq!(in_, [0]); + assert_eq!(out.len(), 10); + out[0] = 1; + out[1] = 2; + out[2] = 3; + 3 + } + + fn buffer_u32(&mut self, in_: &[Le], out: &mut [Le]) -> u32 { + assert_eq!(in_.len(), 1); + assert_eq!(in_[0].get(), 0); + assert_eq!(out.len(), 10); + out[0].set(1); + out[1].set(2); + out[2].set(3); + 3 + } + + fn buffer_bool(&mut self, in_: PullBuffer<'_, bool>, mut out: PushBuffer<'_, bool>) -> u32 { + assert!(in_.len() <= out.capacity()); + let len = in_.len(); + for item in in_.iter() { + let item = item.unwrap(); + out.write(Some(!item)).unwrap(); + } + len as u32 + } + + // fn buffer_string( + // &mut self, + // in_: PullBuffer<'_, GuestPtr<'_, str>>, + // mut out: PushBuffer<'_, String>, + // ) -> u32 { + // assert!(in_.len() < out.capacity()); + // let len = in_.len(); + // for item in in_.iter().unwrap() { + // let item = item.unwrap(); + // let s = item.borrow().unwrap(); + // out.write(Some(s.to_uppercase())).unwrap(); + // } + // len as u32 + // } + + // fn buffer_list_bool( + // &mut self, + // in_: PullBuffer<'_, Vec>, + // mut out: PushBuffer<'_, Vec>, + // ) -> u32 { + // assert!(in_.len() < out.capacity()); + // let len = in_.len(); + // for item in in_.iter().unwrap() { + // let item = item.unwrap(); + // out.write(Some(item.into_iter().map(|b| !b).collect())) + // .unwrap(); + // } + // len as u32 + // } + + // fn buffer_buffer_bool(&mut self, in_: PullBuffer<'_, PullBuffer<'_, bool>>) { + // assert_eq!(in_.len(), 1); + // let buf = in_.iter().unwrap().next().unwrap().unwrap(); + // assert_eq!(buf.len(), 5); + // assert_eq!( + // buf.iter() + // .unwrap() + // .collect::, _>>() + // .unwrap(), + // [true, false, true, true, false] + // ); + // } + + fn buffer_mutable1(&mut self, a: Vec>) { + assert_eq!(a.len(), 1); + assert_eq!(a[0].len(), 5); + assert_eq!( + a[0].iter().collect::, _>>().unwrap(), + [true, false, true, true, false] + ); + } + + fn buffer_mutable2(&mut self, mut a: Vec<&mut [u8]>) -> u32 { + assert_eq!(a.len(), 1); + assert!(a[0].len() > 4); + a[0][..4].copy_from_slice(&[1, 2, 3, 4]); + return 4; + } + + fn buffer_mutable3(&mut self, mut a: Vec>) -> u32 { + assert_eq!(a.len(), 1); + assert!(a[0].capacity() > 3); + a[0].write([false, true, false].iter().copied()).unwrap(); + return 3; + } + + fn buffer_in_record(&mut self, _: BufferInRecord<'_>) {} + fn buffer_typedef( + &mut self, + _: ParamInBufferU8<'_>, + _: ParamOutBufferU8<'_>, + _: ParamInBufferBool<'_>, + _: ParamOutBufferBool<'_>, + ) { + } +} + +wit_bindgen_wasmer::import!("./tests/runtime/buffers/exports.wit"); + +fn run(wasm: &str) -> Result<()> { + use exports::*; + + let exports = crate::instantiate( + wasm, + |store, import_object| imports::add_to_imports(store, import_object, MyImports), + |store, module, import_object| exports::Exports::instantiate(store, module, import_object), + )?; + + exports.test_imports()?; + // fn buffers(wasm: &Wasm) -> Result<()> { + // let mut out = [0; 10]; + // let n = wasm.buffer_u8(&[0u8], &mut out)? as usize; + // assert_eq!(n, 3); + // assert_eq!(&out[..n], [1, 2, 3]); + // assert!(out[n..].iter().all(|x| *x == 0)); + + // let mut out = [0; 10]; + // let n = wasm.buffer_u32(&[0], &mut out)? as usize; + // assert_eq!(n, 3); + // assert_eq!(&out[..n], [1, 2, 3]); + // assert!(out[n..].iter().all(|x| *x == 0)); + + // assert_eq!(wasm.buffer_bool(&mut iter::empty(), &mut Vec::new())?, 0); + // assert_eq!(wasm.buffer_string(&mut iter::empty(), &mut Vec::new())?, 0); + // assert_eq!( + // wasm.buffer_list_bool(&mut iter::empty(), &mut Vec::new())?, + // 0 + // ); + + // let mut bools = [true, false, true].iter().copied(); + // let mut out = Vec::with_capacity(4); + // let n = wasm.buffer_bool(&mut bools, &mut out)?; + // assert_eq!(n, 3); + // assert_eq!(out, [false, true, false]); + + // let mut strings = ["foo", "bar", "baz"].iter().copied(); + // let mut out = Vec::with_capacity(3); + // let n = wasm.buffer_string(&mut strings, &mut out)?; + // assert_eq!(n, 3); + // assert_eq!(out, ["FOO", "BAR", "BAZ"]); + + // let a = &[true, false, true][..]; + // let b = &[false, false][..]; + // let list = [a, b]; + // let mut lists = list.iter().copied(); + // let mut out = Vec::with_capacity(4); + // let n = wasm.buffer_list_bool(&mut lists, &mut out)?; + // assert_eq!(n, 2); + // assert_eq!(out, [vec![false, true, false], vec![true, true]]); + + // let a = [true, false, true, true, false]; + // // let mut bools = a.iter().copied(); + // // let mut list = [&mut bools as &mut dyn ExactSizeIterator]; + // // let mut buffers = list.iter_mut().map(|b| &mut **b); + // // wasm.buffer_buffer_bool(&mut buffers)?; + + // let mut bools = a.iter().copied(); + // wasm.buffer_mutable1(&mut [&mut bools])?; + + // let mut dst = [0; 10]; + // let n = wasm.buffer_mutable2(&mut [&mut dst])? as usize; + // assert_eq!(n, 4); + // assert_eq!(&dst[..n], [1, 2, 3, 4]); + + // let mut out = Vec::with_capacity(10); + // let n = wasm.buffer_mutable3(&mut [&mut out])?; + // assert_eq!(n, 3); + // assert_eq!(out, [false, true, false]); + + // Ok(()) + // } + + Ok(()) +} diff --git a/tests/runtime/flavorful/host-wasmer.rs b/tests/runtime/flavorful/host-wasmer.rs new file mode 100644 index 000000000..8bbbc9515 --- /dev/null +++ b/tests/runtime/flavorful/host-wasmer.rs @@ -0,0 +1,148 @@ +use anyhow::Result; +use wasmer::WasmerEnv; + +wit_bindgen_wasmer::export!("./tests/runtime/flavorful/imports.wit"); + +use imports::*; + +#[derive(WasmerEnv, Clone)] +pub struct MyImports; + +impl Imports for MyImports { + fn list_in_record1(&mut self, ty: ListInRecord1<'_>) { + assert_eq!(ty.a, "list_in_record1"); + } + + fn list_in_record2(&mut self) -> ListInRecord2 { + ListInRecord2 { + a: "list_in_record2".to_string(), + } + } + + fn list_in_record3(&mut self, a: ListInRecord3Param<'_>) -> ListInRecord3Result { + assert_eq!(a.a, "list_in_record3 input"); + ListInRecord3Result { + a: "list_in_record3 output".to_string(), + } + } + + fn list_in_record4(&mut self, a: ListInAliasParam<'_>) -> ListInAliasResult { + assert_eq!(a.a, "input4"); + ListInRecord4Result { + a: "result4".to_string(), + } + } + + fn list_in_variant1( + &mut self, + a: ListInVariant11<'_>, + b: ListInVariant12<'_>, + c: ListInVariant13<'_>, + ) { + assert_eq!(a.unwrap(), "foo"); + assert_eq!(b.unwrap_err(), "bar"); + match c { + ListInVariant13::V0(s) => assert_eq!(s, "baz"), + ListInVariant13::V1(_) => panic!(), + } + } + + fn list_in_variant2(&mut self) -> Option { + Some("list_in_variant2".to_string()) + } + + fn list_in_variant3(&mut self, a: ListInVariant3Param<'_>) -> Option { + assert_eq!(a.unwrap(), "input3"); + Some("output3".to_string()) + } + + fn errno_result(&mut self) -> Result<(), MyErrno> { + MyErrno::A.to_string(); + format!("{:?}", MyErrno::A); + fn assert_error() {} + assert_error::(); + Err(MyErrno::B) + } + + fn list_typedefs( + &mut self, + a: ListTypedef<'_>, + b: ListTypedef3Param<'_>, + ) -> (ListTypedef2, ListTypedef3Result) { + assert_eq!(a, "typedef1"); + assert_eq!(b.len(), 1); + assert_eq!(b[0], "typedef2"); + (b"typedef3".to_vec(), vec!["typedef4".to_string()]) + } + + fn list_of_variants( + &mut self, + bools: Vec, + results: Vec>, + enums: Vec, + ) -> (Vec, Vec>, Vec) { + assert_eq!(bools, [true, false]); + assert_eq!(results, [Ok(()), Err(())]); + assert_eq!(enums, [MyErrno::Success, MyErrno::A]); + ( + vec![false, true], + vec![Err(()), Ok(())], + vec![MyErrno::A, MyErrno::B], + ) + } +} + +wit_bindgen_wasmer::import!("./tests/runtime/flavorful/exports.wit"); + +fn run(wasm: &str) -> Result<()> { + use exports::*; + + let exports = crate::instantiate( + wasm, + |store, import_object| imports::add_to_imports(store, import_object, MyImports), + |store, module, import_object| exports::Exports::instantiate(store, module, import_object), + )?; + + exports.test_imports()?; + + exports.list_in_record1(ListInRecord1 { + a: "list_in_record1", + })?; + assert_eq!(exports.list_in_record2()?.a, "list_in_record2"); + + assert_eq!( + exports + .list_in_record3(ListInRecord3Param { + a: "list_in_record3 input" + })? + .a, + "list_in_record3 output" + ); + + assert_eq!( + exports.list_in_record4(ListInAliasParam { a: "input4" })?.a, + "result4" + ); + + exports.list_in_variant1(Some("foo"), Err("bar"), ListInVariant13::V0("baz"))?; + assert_eq!( + exports.list_in_variant2()?, + Some("list_in_variant2".to_string()) + ); + assert_eq!( + exports.list_in_variant3(Some("input3"))?, + Some("output3".to_string()) + ); + + assert!(exports.errno_result()?.is_err()); + MyErrno::A.to_string(); + format!("{:?}", MyErrno::A); + fn assert_error() {} + assert_error::(); + + let (a, b) = exports.list_typedefs("typedef1", &["typedef2"])?; + assert_eq!(a, b"typedef3"); + assert_eq!(b.len(), 1); + assert_eq!(b[0], "typedef4"); + Ok(()) +} diff --git a/tests/runtime/handles/host-wasmer.rs b/tests/runtime/handles/host-wasmer.rs new file mode 100644 index 000000000..037ad4849 --- /dev/null +++ b/tests/runtime/handles/host-wasmer.rs @@ -0,0 +1,150 @@ +wit_bindgen_wasmer::export!("./tests/runtime/handles/imports.wit"); + +use anyhow::Result; +use imports::*; +use std::cell::RefCell; +use wasmer::WasmerEnv; + +#[derive(Default, WasmerEnv, Clone)] +pub struct MyImports { + host_state2_closed: bool, +} + +#[derive(Debug)] +pub struct SuchState(u32); + +#[derive(Default, Debug)] +pub struct Markdown { + buf: RefCell, +} + +impl Imports for MyImports { + type HostState = SuchState; + type HostState2 = (); + type Markdown2 = Markdown; + type OddName = (); + + fn host_state_create(&mut self) -> SuchState { + SuchState(100) + } + + fn host_state_get(&mut self, state: &SuchState) -> u32 { + state.0 + } + + fn host_state2_create(&mut self) {} + + fn host_state2_saw_close(&mut self) -> bool { + self.host_state2_closed + } + + fn drop_host_state2(&mut self, _state: ()) { + self.host_state2_closed = true; + } + + fn two_host_states(&mut self, _a: &SuchState, _b: &()) -> (SuchState, ()) { + (SuchState(2), ()) + } + + fn host_state2_param_record(&mut self, _a: HostStateParamRecord<'_, Self>) {} + fn host_state2_param_tuple(&mut self, _a: (&'_ (),)) {} + fn host_state2_param_option(&mut self, _a: Option<&'_ ()>) {} + fn host_state2_param_result(&mut self, _a: Result<&'_ (), u32>) {} + fn host_state2_param_variant(&mut self, _a: HostStateParamVariant<'_, Self>) {} + fn host_state2_param_list(&mut self, _a: Vec<&()>) {} + + fn host_state2_result_record(&mut self) -> HostStateResultRecord { + HostStateResultRecord { a: () } + } + fn host_state2_result_tuple(&mut self) -> ((),) { + ((),) + } + fn host_state2_result_option(&mut self) -> Option<()> { + Some(()) + } + fn host_state2_result_result(&mut self) -> Result<(), u32> { + Ok(()) + } + fn host_state2_result_variant(&mut self) -> HostStateResultVariant { + HostStateResultVariant::V0(()) + } + fn host_state2_result_list(&mut self) -> Vec<()> { + vec![(), ()] + } + + fn markdown2_create(&mut self) -> Markdown { + Markdown::default() + } + + fn markdown2_append(&mut self, md: &Markdown, buf: &str) { + md.buf.borrow_mut().push_str(buf); + } + + fn markdown2_render(&mut self, md: &Markdown) -> String { + md.buf.borrow().replace("red", "green") + } + + fn odd_name_create(&mut self) {} + fn odd_name_frob_the_odd(&mut self, _: &()) {} +} + +wit_bindgen_wasmer::import!("./tests/runtime/handles/exports.wit"); + +fn run(wasm: &str) -> Result<()> { + use exports::*; + + let exports = crate::instantiate( + wasm, + |store, import_object| imports::add_to_imports(store, import_object, MyImports::default()), + |store, module, import_object| exports::Exports::instantiate(store, module, import_object), + )?; + + exports.test_imports()?; + + let s: WasmState = exports.wasm_state_create()?; + assert_eq!(exports.wasm_state_get_val(&s)?, 100); + exports.drop_wasm_state(s)?; + + assert_eq!(exports.wasm_state2_saw_close()?, false); + let s: WasmState2 = exports.wasm_state2_create()?; + assert_eq!(exports.wasm_state2_saw_close()?, false); + exports.drop_wasm_state2(s)?; + assert_eq!(exports.wasm_state2_saw_close()?, true); + + let a = exports.wasm_state_create()?; + let b = exports.wasm_state2_create()?; + let (s1, s2) = exports.two_wasm_states(&a, &b)?; + exports.drop_wasm_state(a)?; + exports.drop_wasm_state(s1)?; + exports.drop_wasm_state2(b)?; + + exports.wasm_state2_param_record(WasmStateParamRecord { a: &s2 })?; + exports.wasm_state2_param_tuple((&s2,))?; + exports.wasm_state2_param_option(Some(&s2))?; + exports.wasm_state2_param_option(None)?; + exports.wasm_state2_param_result(Ok(&s2))?; + exports.wasm_state2_param_result(Err(2))?; + exports.wasm_state2_param_variant(WasmStateParamVariant::V0(&s2))?; + exports.wasm_state2_param_variant(WasmStateParamVariant::V1(2))?; + exports.wasm_state2_param_list(&[])?; + exports.wasm_state2_param_list(&[&s2])?; + exports.wasm_state2_param_list(&[&s2, &s2])?; + exports.drop_wasm_state2(s2)?; + + let s = exports.wasm_state2_result_record()?.a; + exports.drop_wasm_state2(s)?; + let s = exports.wasm_state2_result_tuple()?.0; + exports.drop_wasm_state2(s)?; + let s = exports.wasm_state2_result_option()?.unwrap(); + exports.drop_wasm_state2(s)?; + let s = exports.wasm_state2_result_result()?.unwrap(); + match exports.wasm_state2_result_variant()? { + WasmStateResultVariant::V0(s) => exports.drop_wasm_state2(s)?, + WasmStateResultVariant::V1(_) => panic!(), + } + exports.drop_wasm_state2(s)?; + for s in exports.wasm_state2_result_list()? { + exports.drop_wasm_state2(s)?; + } + Ok(()) +} diff --git a/tests/runtime/invalid/host-wasmer.rs b/tests/runtime/invalid/host-wasmer.rs new file mode 100644 index 000000000..4e070748b --- /dev/null +++ b/tests/runtime/invalid/host-wasmer.rs @@ -0,0 +1,68 @@ +wit_bindgen_wasmer::export!("./tests/runtime/invalid/imports.wit"); + +use anyhow::Result; +use imports::*; +use wasmer::{RuntimeError, WasmerEnv}; + +#[derive(WasmerEnv, Clone)] +pub struct MyImports; + +impl Imports for MyImports { + type HostState = (); + + fn roundtrip_u8(&mut self, _: u8) -> u8 { + unreachable!() + } + fn roundtrip_s8(&mut self, _: i8) -> i8 { + unreachable!() + } + fn roundtrip_u16(&mut self, _: u16) -> u16 { + unreachable!() + } + fn roundtrip_s16(&mut self, _: i16) -> i16 { + unreachable!() + } + fn roundtrip_char(&mut self, _: char) -> char { + unreachable!() + } + fn roundtrip_bool(&mut self, _: bool) -> bool { + unreachable!() + } + fn roundtrip_enum(&mut self, _: imports::E) -> imports::E { + unreachable!() + } + fn get_internal(&mut self, _: &()) -> u32 { + unreachable!() + } +} + +wit_bindgen_wasmer::import!("./tests/runtime/invalid/exports.wit"); + +fn run(wasm: &str) -> Result<()> { + use exports::*; + + let exports = crate::instantiate( + wasm, + |store, import_object| imports::add_to_imports(store, import_object, MyImports), + |store, module, import_object| exports::Exports::instantiate(store, module, import_object), + )?; + + assert_err(exports.invalid_bool(), "invalid discriminant for `bool`")?; + assert_err(exports.invalid_u8(), "out-of-bounds integer conversion")?; + assert_err(exports.invalid_s8(), "out-of-bounds integer conversion")?; + assert_err(exports.invalid_u16(), "out-of-bounds integer conversion")?; + assert_err(exports.invalid_s16(), "out-of-bounds integer conversion")?; + assert_err(exports.invalid_char(), "char value out of valid range")?; + assert_err(exports.invalid_enum(), "invalid discriminant for `E`")?; + assert_err(exports.invalid_handle(), "invalid handle index")?; + assert_err(exports.invalid_handle_close(), "invalid handle index")?; + return Ok(()); + + fn assert_err(result: Result<(), RuntimeError>, err: &str) -> Result<()> { + match result { + Ok(()) => anyhow::bail!("export didn't trap"), + Err(e) if e.to_string().contains(err) => Ok(()), + Err(e) => Err(e.into()), + } + } +} diff --git a/tests/runtime/lists/host-wasmer.rs b/tests/runtime/lists/host-wasmer.rs new file mode 100644 index 000000000..1771b7371 --- /dev/null +++ b/tests/runtime/lists/host-wasmer.rs @@ -0,0 +1,156 @@ +use anyhow::Result; +use wasmer::WasmerEnv; + +wit_bindgen_wasmer::export!("./tests/runtime/lists/imports.wit"); + +use imports::*; +use wit_bindgen_wasmer::Le; + +#[derive(WasmerEnv, Clone)] +pub struct MyImports; + +impl Imports for MyImports { + fn list_param(&mut self, list: &[u8]) { + assert_eq!(list, [1, 2, 3, 4]); + } + + fn list_param2(&mut self, ptr: &str) { + assert_eq!(ptr, "foo"); + } + + fn list_param3(&mut self, ptr: Vec<&str>) { + assert_eq!(ptr.len(), 3); + assert_eq!(ptr[0], "foo"); + assert_eq!(ptr[1], "bar"); + assert_eq!(ptr[2], "baz"); + } + + fn list_param4(&mut self, ptr: Vec>) { + assert_eq!(ptr.len(), 2); + assert_eq!(ptr[0][0], "foo"); + assert_eq!(ptr[0][1], "bar"); + assert_eq!(ptr[1][0], "baz"); + } + + fn list_result(&mut self) -> Vec { + vec![1, 2, 3, 4, 5] + } + + fn list_result2(&mut self) -> String { + "hello!".to_string() + } + + fn list_result3(&mut self) -> Vec { + vec!["hello,".to_string(), "world!".to_string()] + } + + fn string_roundtrip(&mut self, s: &str) -> String { + s.to_string() + } + + fn list_minmax8(&mut self, u: &[u8], s: &[i8]) -> (Vec, Vec) { + assert_eq!(u, [u8::MIN, u8::MAX]); + assert_eq!(s, [i8::MIN, i8::MAX]); + (u.to_vec(), s.to_vec()) + } + + fn list_minmax16(&mut self, u: &[Le], s: &[Le]) -> (Vec, Vec) { + assert_eq!(u, [u16::MIN, u16::MAX]); + assert_eq!(s, [i16::MIN, i16::MAX]); + ( + u.iter().map(|e| e.get()).collect(), + s.iter().map(|e| e.get()).collect(), + ) + } + + fn list_minmax32(&mut self, u: &[Le], s: &[Le]) -> (Vec, Vec) { + assert_eq!(u, [u32::MIN, u32::MAX]); + assert_eq!(s, [i32::MIN, i32::MAX]); + ( + u.iter().map(|e| e.get()).collect(), + s.iter().map(|e| e.get()).collect(), + ) + } + + fn list_minmax64(&mut self, u: &[Le], s: &[Le]) -> (Vec, Vec) { + assert_eq!(u, [u64::MIN, u64::MAX]); + assert_eq!(s, [i64::MIN, i64::MAX]); + ( + u.iter().map(|e| e.get()).collect(), + s.iter().map(|e| e.get()).collect(), + ) + } + + fn list_minmax_float(&mut self, u: &[Le], s: &[Le]) -> (Vec, Vec) { + assert_eq!(u, [f32::MIN, f32::MAX, f32::NEG_INFINITY, f32::INFINITY]); + assert_eq!(s, [f64::MIN, f64::MAX, f64::NEG_INFINITY, f64::INFINITY]); + ( + u.iter().map(|e| e.get()).collect(), + s.iter().map(|e| e.get()).collect(), + ) + } + + fn unaligned_roundtrip1( + &mut self, + u16s: &[Le], + u32s: &[Le], + u64s: &[Le], + flag32s: Vec, + flag64s: Vec, + ) { + assert_eq!(u16s, [1]); + assert_eq!(u32s, [2]); + assert_eq!(u64s, [3]); + assert_eq!(flag32s, [Flag32::B8]); + assert_eq!(flag64s, [Flag64::B9]); + } + + fn unaligned_roundtrip2( + &mut self, + records: &[Le], + f32s: &[Le], + f64s: &[Le], + strings: Vec<&str>, + lists: Vec<&[u8]>, + ) { + assert_eq!(records.len(), 1); + assert_eq!(records[0].get().a, 10); + assert_eq!(records[0].get().b, 11); + assert_eq!(f32s, [100.0]); + assert_eq!(f64s, [101.0]); + assert_eq!(strings, ["foo"]); + assert_eq!(lists, [&[102][..]]); + } +} + +wit_bindgen_wasmer::import!("./tests/runtime/lists/exports.wit"); + +fn run(wasm: &str) -> Result<()> { + use exports::*; + + let exports = crate::instantiate( + wasm, + |store, import_object| imports::add_to_imports(store, import_object, MyImports), + |store, module, import_object| exports::Exports::instantiate(store, module, import_object), + )?; + + let bytes = exports.allocated_bytes()?; + exports.test_imports()?; + exports.list_param( &[1, 2, 3, 4])?; + exports.list_param2( "foo")?; + exports.list_param3( &["foo", "bar", "baz"])?; + exports.list_param4( &[&["foo", "bar"], &["baz"]])?; + assert_eq!(exports.list_result()?, [1, 2, 3, 4, 5]); + assert_eq!(exports.list_result2()?, "hello!"); + assert_eq!(exports.list_result3()?, ["hello,", "world!"]); + assert_eq!(exports.string_roundtrip( "x")?, "x"); + assert_eq!(exports.string_roundtrip( "")?, ""); + assert_eq!( + exports.string_roundtrip( "hello ⚑ world")?, + "hello ⚑ world" + ); + // Ensure that we properly called `free` everywhere in all the glue that we + // needed to. + assert_eq!(bytes, exports.allocated_bytes()?); + Ok(()) +} diff --git a/tests/runtime/numbers/host-wasmer.rs b/tests/runtime/numbers/host-wasmer.rs new file mode 100644 index 000000000..70366d071 --- /dev/null +++ b/tests/runtime/numbers/host-wasmer.rs @@ -0,0 +1,127 @@ +use anyhow::Result; +use wasmer::WasmerEnv; + +wit_bindgen_wasmer::export!("./tests/runtime/numbers/imports.wit"); + +#[derive(Default, WasmerEnv, Clone)] +pub struct MyImports { + scalar: u32, +} + +impl imports::Imports for MyImports { + fn roundtrip_u8(&mut self, val: u8) -> u8 { + val + } + + fn roundtrip_s8(&mut self, val: i8) -> i8 { + val + } + + fn roundtrip_u16(&mut self, val: u16) -> u16 { + val + } + + fn roundtrip_s16(&mut self, val: i16) -> i16 { + val + } + + fn roundtrip_u32(&mut self, val: u32) -> u32 { + val + } + + fn roundtrip_s32(&mut self, val: i32) -> i32 { + val + } + + fn roundtrip_u64(&mut self, val: u64) -> u64 { + val + } + + fn roundtrip_s64(&mut self, val: i64) -> i64 { + val + } + + fn roundtrip_f32(&mut self, val: f32) -> f32 { + val + } + + fn roundtrip_f64(&mut self, val: f64) -> f64 { + val + } + + fn roundtrip_char(&mut self, val: char) -> char { + val + } + + fn set_scalar(&mut self, val: u32) { + self.scalar = val; + } + + fn get_scalar(&mut self) -> u32 { + self.scalar + } +} + +wit_bindgen_wasmer::import!("./tests/runtime/numbers/exports.wit"); + +fn run(wasm: &str) -> Result<()> { + let exports = crate::instantiate( + wasm, + |store, import_object| imports::add_to_imports(store, import_object, MyImports::default()), + |store, module, import_object| exports::Exports::instantiate(store, module, import_object), + )?; + + exports.test_imports()?; + assert_eq!(exports.roundtrip_u8(1)?, 1); + assert_eq!(exports.roundtrip_u8(u8::min_value())?, u8::min_value()); + assert_eq!(exports.roundtrip_u8(u8::max_value())?, u8::max_value()); + + assert_eq!(exports.roundtrip_s8(1)?, 1); + assert_eq!(exports.roundtrip_s8(i8::min_value())?, i8::min_value()); + assert_eq!(exports.roundtrip_s8(i8::max_value())?, i8::max_value()); + + assert_eq!(exports.roundtrip_u16(1)?, 1); + assert_eq!(exports.roundtrip_u16(u16::min_value())?, u16::min_value()); + assert_eq!(exports.roundtrip_u16(u16::max_value())?, u16::max_value()); + + assert_eq!(exports.roundtrip_s16(1)?, 1); + assert_eq!(exports.roundtrip_s16(i16::min_value())?, i16::min_value()); + assert_eq!(exports.roundtrip_s16(i16::max_value())?, i16::max_value()); + + assert_eq!(exports.roundtrip_u32(1)?, 1); + assert_eq!(exports.roundtrip_u32(u32::min_value())?, u32::min_value()); + assert_eq!(exports.roundtrip_u32(u32::max_value())?, u32::max_value()); + + assert_eq!(exports.roundtrip_s32(1)?, 1); + assert_eq!(exports.roundtrip_s32(i32::min_value())?, i32::min_value()); + assert_eq!(exports.roundtrip_s32(i32::max_value())?, i32::max_value()); + + assert_eq!(exports.roundtrip_u64(1)?, 1); + assert_eq!(exports.roundtrip_u64(u64::min_value())?, u64::min_value()); + assert_eq!(exports.roundtrip_u64(u64::max_value())?, u64::max_value()); + + assert_eq!(exports.roundtrip_s64(1)?, 1); + assert_eq!(exports.roundtrip_s64(i64::min_value())?, i64::min_value()); + assert_eq!(exports.roundtrip_s64(i64::max_value())?, i64::max_value()); + + assert_eq!(exports.roundtrip_f32(1.0)?, 1.0); + assert_eq!(exports.roundtrip_f32(f32::INFINITY)?, f32::INFINITY); + assert_eq!(exports.roundtrip_f32(f32::NEG_INFINITY)?, f32::NEG_INFINITY); + assert!(exports.roundtrip_f32(f32::NAN)?.is_nan()); + + assert_eq!(exports.roundtrip_f64(1.0)?, 1.0); + assert_eq!(exports.roundtrip_f64(f64::INFINITY)?, f64::INFINITY); + assert_eq!(exports.roundtrip_f64(f64::NEG_INFINITY)?, f64::NEG_INFINITY); + assert!(exports.roundtrip_f64(f64::NAN)?.is_nan()); + + assert_eq!(exports.roundtrip_char('a')?, 'a'); + assert_eq!(exports.roundtrip_char(' ')?, ' '); + assert_eq!(exports.roundtrip_char('🚩')?, '🚩'); + + exports.set_scalar(2)?; + assert_eq!(exports.get_scalar()?, 2); + exports.set_scalar(4)?; + assert_eq!(exports.get_scalar()?, 4); + + Ok(()) +} diff --git a/tests/runtime/records/host-wasmer.rs b/tests/runtime/records/host-wasmer.rs new file mode 100644 index 000000000..8778ac887 --- /dev/null +++ b/tests/runtime/records/host-wasmer.rs @@ -0,0 +1,94 @@ +use anyhow::Result; +use wasmer::WasmerEnv; + +wit_bindgen_wasmer::export!("./tests/runtime/records/imports.wit"); + +use imports::*; + +#[derive(WasmerEnv, Clone)] +pub struct MyImports; + +impl Imports for MyImports { + fn multiple_results(&mut self) -> (u8, u16) { + (4, 5) + } + + fn swap_tuple(&mut self, a: (u8, u32)) -> (u32, u8) { + (a.1, a.0) + } + + fn roundtrip_flags1(&mut self, a: F1) -> F1 { + drop(a.to_string()); + drop(format!("{:?}", a)); + drop(a & F1::all()); + a + } + + fn roundtrip_flags2(&mut self, a: F2) -> F2 { + a + } + + fn roundtrip_flags3( + &mut self, + a: Flag8, + b: Flag16, + c: Flag32, + d: Flag64, + ) -> (Flag8, Flag16, Flag32, Flag64) { + (a, b, c, d) + } + + fn roundtrip_record1(&mut self, a: R1) -> R1 { + drop(format!("{:?}", a)); + a + } + + fn tuple0(&mut self, _: ()) {} + + fn tuple1(&mut self, a: (u8,)) -> (u8,) { + (a.0,) + } +} + +wit_bindgen_wasmer::import!("./tests/runtime/records/exports.wit"); + +fn run(wasm: &str) -> Result<()> { + use exports::*; + + let exports = crate::instantiate( + wasm, + |store, import_object| imports::add_to_imports(store, import_object, MyImports), + |store, module, import_object| exports::Exports::instantiate(store, module, import_object), + )?; + + exports.test_imports()?; + assert_eq!(exports.multiple_results()?, (100, 200)); + assert_eq!(exports.swap_tuple((1u8, 2u32))?, (2u32, 1u8)); + assert_eq!(exports.roundtrip_flags1(F1::A)?, F1::A); + assert_eq!(exports.roundtrip_flags1(F1::empty())?, F1::empty()); + assert_eq!(exports.roundtrip_flags1(F1::B)?, F1::B); + assert_eq!(exports.roundtrip_flags1(F1::A | F1::B)?, F1::A | F1::B); + + assert_eq!(exports.roundtrip_flags2(F2::C)?, F2::C); + assert_eq!(exports.roundtrip_flags2(F2::empty())?, F2::empty()); + assert_eq!(exports.roundtrip_flags2(F2::D)?, F2::D); + assert_eq!(exports.roundtrip_flags2(F2::C | F2::E)?, F2::C | F2::E); + + let r = exports.roundtrip_record1(R1 { + a: 8, + b: F1::empty(), + })?; + assert_eq!(r.a, 8); + assert_eq!(r.b, F1::empty()); + + let r = exports.roundtrip_record1(R1 { + a: 0, + b: F1::A | F1::B, + })?; + assert_eq!(r.a, 0); + assert_eq!(r.b, F1::A | F1::B); + + assert_eq!(exports.tuple0(())?, ()); + assert_eq!(exports.tuple1((1,))?, (1,)); + Ok(()) +} diff --git a/tests/runtime/smoke/host-wasmer.rs b/tests/runtime/smoke/host-wasmer.rs new file mode 100644 index 000000000..45f854ff4 --- /dev/null +++ b/tests/runtime/smoke/host-wasmer.rs @@ -0,0 +1,39 @@ +use anyhow::Result; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; +use wasmer::WasmerEnv; + +wit_bindgen_wasmer::export!("./tests/runtime/smoke/imports.wit"); + +#[derive(WasmerEnv, Clone)] +pub struct MyImports { + hit: Arc, +} + +impl imports::Imports for MyImports { + fn thunk(&mut self) { + self.hit.store(true, Ordering::Relaxed); + println!("in the host"); + } +} + +wit_bindgen_wasmer::import!("./tests/runtime/smoke/exports.wit"); + +fn run(wasm: &str) -> Result<()> { + let hit = Arc::new(AtomicBool::new(false)); + let exports = crate::instantiate( + wasm, + |store, import_object| { + imports::add_to_imports(store, import_object, MyImports { hit: hit.clone() }) + }, + |store, module, import_object| exports::Exports::instantiate(store, module, import_object), + )?; + + exports.thunk()?; + + assert!(hit.load(Ordering::Relaxed)); + + Ok(()) +} diff --git a/tests/runtime/variants/host-wasmer.rs b/tests/runtime/variants/host-wasmer.rs new file mode 100644 index 000000000..642df692a --- /dev/null +++ b/tests/runtime/variants/host-wasmer.rs @@ -0,0 +1,114 @@ +use anyhow::Result; +use wasmer::WasmerEnv; + +wit_bindgen_wasmer::export!("./tests/runtime/variants/imports.wit"); + +use imports::*; + +#[derive(WasmerEnv, Clone)] +pub struct MyImports; + +impl Imports for MyImports { + fn roundtrip_option(&mut self, a: Option) -> Option { + a.map(|x| x as u8) + } + + fn roundtrip_result(&mut self, a: Result) -> Result { + match a { + Ok(a) => Ok(a.into()), + Err(b) => Err(b as u8), + } + } + + fn roundtrip_enum(&mut self, a: E1) -> E1 { + assert_eq!(a, a); + a + } + + fn invert_bool(&mut self, a: bool) -> bool { + !a + } + + fn variant_casts(&mut self, a: Casts) -> Casts { + a + } + + fn variant_zeros(&mut self, a: Zeros) -> Zeros { + a + } + + fn variant_typedefs(&mut self, _: Option, _: bool, _: Result) {} + + fn variant_enums( + &mut self, + a: bool, + b: Result<(), ()>, + c: MyErrno, + ) -> (bool, Result<(), ()>, MyErrno) { + assert_eq!(a, true); + assert_eq!(b, Ok(())); + assert_eq!(c, MyErrno::Success); + (false, Err(()), MyErrno::A) + } +} + +wit_bindgen_wasmer::import!("./tests/runtime/variants/exports.wit"); + +fn run(wasm: &str) -> Result<()> { + use exports::*; + + let exports = crate::instantiate( + wasm, + |store, import_object| imports::add_to_imports(store, import_object, MyImports), + |store, module, import_object| exports::Exports::instantiate(store, module, import_object), + )?; + + exports.test_imports()?; + + assert_eq!(exports.roundtrip_option(Some(1.0))?, Some(1)); + assert_eq!(exports.roundtrip_option(None)?, None); + assert_eq!(exports.roundtrip_option(Some(2.0))?, Some(2)); + assert_eq!(exports.roundtrip_result(Ok(2))?, Ok(2.0)); + assert_eq!(exports.roundtrip_result(Ok(4))?, Ok(4.0)); + assert_eq!(exports.roundtrip_result(Err(5.3))?, Err(5)); + + assert_eq!(exports.roundtrip_enum(E1::A)?, E1::A); + assert_eq!(exports.roundtrip_enum(E1::B)?, E1::B); + + assert_eq!(exports.invert_bool(true)?, false); + assert_eq!(exports.invert_bool(false)?, true); + + let (a1, a2, a3, a4, a5, a6) = + exports.variant_casts((C1::A(1), C2::A(2), C3::A(3), C4::A(4), C5::A(5), C6::A(6.0)))?; + assert!(matches!(a1, C1::A(1))); + assert!(matches!(a2, C2::A(2))); + assert!(matches!(a3, C3::A(3))); + assert!(matches!(a4, C4::A(4))); + assert!(matches!(a5, C5::A(5))); + assert!(matches!(a6, C6::A(b) if b == 6.0)); + + let (a1, a2, a3, a4, a5, a6) = exports.variant_casts(( + C1::B(1), + C2::B(2.0), + C3::B(3.0), + C4::B(4.0), + C5::B(5.0), + C6::B(6.0), + ))?; + assert!(matches!(a1, C1::B(1))); + assert!(matches!(a2, C2::B(b) if b == 2.0)); + assert!(matches!(a3, C3::B(b) if b == 3.0)); + assert!(matches!(a4, C4::B(b) if b == 4.0)); + assert!(matches!(a5, C5::B(b) if b == 5.0)); + assert!(matches!(a6, C6::B(b) if b == 6.0)); + + let (a1, a2, a3, a4) = exports.variant_zeros((Z1::A(1), Z2::A(2), Z3::A(3.0), Z4::A(4.0)))?; + assert!(matches!(a1, Z1::A(1))); + assert!(matches!(a2, Z2::A(2))); + assert!(matches!(a3, Z3::A(b) if b == 3.0)); + assert!(matches!(a4, Z4::A(b) if b == 4.0)); + + exports.variant_typedefs(None, false, Err(()))?; + + Ok(()) +} From fb58bae7578296d56dc3c77e255b084e516ab169 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Sat, 18 Dec 2021 01:53:48 +0100 Subject: [PATCH 2/8] Add wasmer-python bindings generator --- Cargo.lock | 13 + Cargo.toml | 1 + crates/gen-wasmer-py/Cargo.toml | 20 + crates/gen-wasmer-py/build.rs | 4 + crates/gen-wasmer-py/mypy.ini | 13 + crates/gen-wasmer-py/src/lib.rs | 2271 ++++++++++++++++++++++++ crates/gen-wasmer-py/tests/codegen.rs | 51 + crates/gen-wasmer-py/tests/runtime.rs | 75 + crates/test-helpers/Cargo.toml | 1 + crates/test-helpers/src/lib.rs | 54 + crates/wit-bindgen-demo/Cargo.toml | 1 + crates/wit-bindgen-demo/demo.wit | 1 + crates/wit-bindgen-demo/index.html | 3 + crates/wit-bindgen-demo/main.ts | 1 + crates/wit-bindgen-demo/src/lib.rs | 2 + src/bin/wit-bindgen.rs | 7 + tests/runtime/buffers/host-wasmer.py | 95 + tests/runtime/flavorful/host-wasmer.py | 90 + tests/runtime/handles/host-wasmer.py | 184 ++ tests/runtime/invalid/host-wasmer.py | 78 + tests/runtime/lists/host-wasmer.py | 114 ++ tests/runtime/numbers/host-wasmer.py | 111 ++ tests/runtime/records/host-wasmer.py | 76 + tests/runtime/smoke/host-wasmer.py | 32 + tests/runtime/variants/host-wasmer.py | 118 ++ 25 files changed, 3416 insertions(+) create mode 100644 crates/gen-wasmer-py/Cargo.toml create mode 100644 crates/gen-wasmer-py/build.rs create mode 100644 crates/gen-wasmer-py/mypy.ini create mode 100644 crates/gen-wasmer-py/src/lib.rs create mode 100644 crates/gen-wasmer-py/tests/codegen.rs create mode 100644 crates/gen-wasmer-py/tests/runtime.rs create mode 100644 tests/runtime/buffers/host-wasmer.py create mode 100644 tests/runtime/flavorful/host-wasmer.py create mode 100644 tests/runtime/handles/host-wasmer.py create mode 100644 tests/runtime/invalid/host-wasmer.py create mode 100644 tests/runtime/lists/host-wasmer.py create mode 100644 tests/runtime/numbers/host-wasmer.py create mode 100644 tests/runtime/records/host-wasmer.py create mode 100644 tests/runtime/smoke/host-wasmer.py create mode 100644 tests/runtime/variants/host-wasmer.py diff --git a/Cargo.lock b/Cargo.lock index 5846332b7..1d1348624 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1829,6 +1829,7 @@ dependencies = [ "wit-bindgen-gen-rust-wasm", "wit-bindgen-gen-spidermonkey", "wit-bindgen-gen-wasmer", + "wit-bindgen-gen-wasmer-py", "wit-bindgen-gen-wasmtime", "wit-bindgen-gen-wasmtime-py", "wit-parser", @@ -2756,6 +2757,7 @@ dependencies = [ "wit-bindgen-gen-rust-wasm", "wit-bindgen-gen-spidermonkey", "wit-bindgen-gen-wasmer", + "wit-bindgen-gen-wasmer-py", "wit-bindgen-gen-wasmtime", "wit-bindgen-gen-wasmtime-py", ] @@ -2772,6 +2774,7 @@ dependencies = [ "wit-bindgen-gen-rust-wasm", "wit-bindgen-gen-spidermonkey", "wit-bindgen-gen-wasmer", + "wit-bindgen-gen-wasmer-py", "wit-bindgen-gen-wasmtime", "wit-bindgen-gen-wasmtime-py", "wit-bindgen-rust", @@ -2862,6 +2865,16 @@ dependencies = [ "wit-bindgen-wasmer", ] +[[package]] +name = "wit-bindgen-gen-wasmer-py" +version = "0.1.0" +dependencies = [ + "heck", + "structopt", + "test-helpers", + "wit-bindgen-gen-core", +] + [[package]] name = "wit-bindgen-gen-wasmtime" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 68f91c8ab..f827aaa09 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ wit-bindgen-gen-c = { path = 'crates/gen-c', features = ['structopt'] } wit-bindgen-gen-markdown = { path = 'crates/gen-markdown', features = ['structopt'] } wit-bindgen-gen-spidermonkey = { path = 'crates/gen-spidermonkey', features = ['structopt'] } wit-bindgen-gen-wasmer = { path = 'crates/gen-wasmer', features = ['structopt'] } +wit-bindgen-gen-wasmer-py = { path = 'crates/gen-wasmer-py', features = ['structopt'] } # Compiling `spidermonkey.wasm` takes way too long without this. [profile.dev.package.cranelift-codegen] diff --git a/crates/gen-wasmer-py/Cargo.toml b/crates/gen-wasmer-py/Cargo.toml new file mode 100644 index 000000000..955b2a600 --- /dev/null +++ b/crates/gen-wasmer-py/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "wit-bindgen-gen-wasmer-py" +version = "0.1.0" +authors = ["Alex Crichton "] +edition = "2018" + +[lib] +doctest = false +test = false + +[dependencies] +wit-bindgen-gen-core = { path = '../gen-core', version = '0.1.0' } +heck = "0.3" +structopt = { version = "0.3", default-features = false, optional = true } + +[dev-dependencies] +test-helpers = { path = '../test-helpers', features = ['wit-bindgen-gen-wasmer-py'] } + +[features] +witx-compat = ['wit-bindgen-gen-core/witx-compat'] diff --git a/crates/gen-wasmer-py/build.rs b/crates/gen-wasmer-py/build.rs new file mode 100644 index 000000000..d7c7fc240 --- /dev/null +++ b/crates/gen-wasmer-py/build.rs @@ -0,0 +1,4 @@ +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + // this build script is currently only here so OUT_DIR is set for testing. +} diff --git a/crates/gen-wasmer-py/mypy.ini b/crates/gen-wasmer-py/mypy.ini new file mode 100644 index 000000000..0d47989e0 --- /dev/null +++ b/crates/gen-wasmer-py/mypy.ini @@ -0,0 +1,13 @@ +[mypy] + +disallow_any_unimported = False + +disallow_untyped_calls = True +disallow_untyped_defs = True +disallow_incomplete_defs = True +check_untyped_defs = True +disallow_untyped_decorators = True +strict_optional = True + +warn_return_any = True +warn_unused_configs = True diff --git a/crates/gen-wasmer-py/src/lib.rs b/crates/gen-wasmer-py/src/lib.rs new file mode 100644 index 000000000..314582b92 --- /dev/null +++ b/crates/gen-wasmer-py/src/lib.rs @@ -0,0 +1,2271 @@ +use heck::*; +use std::collections::{BTreeMap, BTreeSet, HashMap}; +use std::mem; +use wit_bindgen_gen_core::wit_parser::abi::{ + AbiVariant, Bindgen, Bitcast, Instruction, LiftLower, WasmType, WitxInstruction, +}; +use wit_bindgen_gen_core::{wit_parser::*, Direction, Files, Generator, Ns}; + +#[derive(Default)] +pub struct WasmtimePy { + src: Source, + in_import: bool, + opts: Opts, + guest_imports: HashMap, + guest_exports: HashMap, + sizes: SizeAlign, + needs_clamp: bool, + needs_store: bool, + needs_load: bool, + needs_validate_guest_char: bool, + needs_expected: bool, + needs_i32_to_f32: bool, + needs_f32_to_i32: bool, + needs_i64_to_f64: bool, + needs_f64_to_i64: bool, + needs_decode_utf8: bool, + needs_encode_utf8: bool, + needs_list_canon_lift: bool, + needs_list_canon_lower: bool, + needs_push_buffer: bool, + needs_pull_buffer: bool, + needs_t_typevar: bool, + pyimports: BTreeMap>>, +} + +#[derive(Default)] +struct Imports { + freestanding_funcs: Vec, + resource_funcs: BTreeMap>, +} + +struct Import { + name: String, + src: Source, + wasm_ty: String, + pysig: String, +} + +#[derive(Default)] +struct Exports { + freestanding_funcs: Vec, + resource_funcs: BTreeMap>, + fields: BTreeMap, +} + +#[derive(Default, Debug, Clone)] +#[cfg_attr(feature = "structopt", derive(structopt::StructOpt))] +pub struct Opts { + #[cfg_attr(feature = "structopt", structopt(long = "no-typescript"))] + pub no_typescript: bool, +} + +impl Opts { + pub fn build(self) -> WasmtimePy { + let mut r = WasmtimePy::new(); + r.opts = self; + r + } +} + +impl WasmtimePy { + pub fn new() -> WasmtimePy { + WasmtimePy::default() + } + + fn abi_variant(dir: Direction) -> AbiVariant { + // This generator uses a reversed mapping! In the Wasmer-py host-side + // bindings, we don't use any extra adapter layer between guest wasm + // modules and the host. When the guest imports functions using the + // `GuestImport` ABI, the host directly implements the `GuestImport` + // ABI, even though the host is *exporting* functions. Similarly, when + // the guest exports functions using the `GuestExport` ABI, the host + // directly imports them with the `GuestExport` ABI, even though the + // host is *importing* functions. + match dir { + Direction::Import => AbiVariant::GuestExport, + Direction::Export => AbiVariant::GuestImport, + } + } + + fn indent(&mut self) { + self.src.indent(2); + } + + fn deindent(&mut self) { + self.src.deindent(2); + } + + fn print_intrinsics(&mut self, iface: &Interface) { + if self.needs_clamp { + self.src.push_str( + " + def _clamp(i: int, min: int, max: int) -> int: + if i < min or i > max: + raise OverflowError(f'must be between {min} and {max}') + return i + ", + ); + } + if self.needs_store { + self.pyimport("typing", "Callable"); + // TODO: this uses native endianness + self.src.push_str( + " + def _store(make_view: Callable[[], Any], mem: wasmer.Memory, base: int, offset: int, val: Any) -> None: + ptr = (base & 0xffffffff) + offset + view = make_view() + if ptr + view.bytes_per_element > mem.data_size: + raise IndexError('out-of-bounds store') + view_ptr = ptr // view.bytes_per_element + view[view_ptr] = val + ", + ); + } + if self.needs_load { + self.pyimport("typing", "Callable"); + // TODO: this uses native endianness + self.src.push_str( + " + def _load(make_view: Callable[[], Any], mem: wasmer.Memory, base: int, offset: int) -> Any: + ptr = (base & 0xffffffff) + offset + view = make_view() + if ptr + view.bytes_per_element > mem.data_size: + raise IndexError('out-of-bounds load') + view_ptr = ptr // view.bytes_per_element + return view[view_ptr] + ", + ); + } + if self.needs_validate_guest_char { + self.src.push_str( + " + def _validate_guest_char(i: int) -> str: + if i > 0x10ffff or (i >= 0xd800 and i <= 0xdfff): + raise TypeError('not a valid char'); + return chr(i) + ", + ); + } + if self.needs_expected { + self.pyimport("dataclasses", "dataclass"); + self.pyimport("typing", "TypeVar"); + self.pyimport("typing", "Generic"); + self.pyimport("typing", "Union"); + self.needs_t_typevar = true; + self.src.push_str( + " + @dataclass + class Ok(Generic[T]): + value: T + E = TypeVar('E') + @dataclass + class Err(Generic[E]): + value: E + + Expected = Union[Ok[T], Err[E]] + ", + ); + } + if self.needs_i32_to_f32 || self.needs_f32_to_i32 { + self.pyimport("ctypes", None); + self.src + .push_str("_i32_to_f32_i32 = ctypes.pointer(ctypes.c_int32(0))\n"); + self.src.push_str( + "_i32_to_f32_f32 = ctypes.cast(_i32_to_f32_i32, ctypes.POINTER(ctypes.c_float))\n", + ); + if self.needs_i32_to_f32 { + self.src.push_str( + " + def _i32_to_f32(i: int) -> float: + _i32_to_f32_i32[0] = i # type: ignore + return _i32_to_f32_f32[0] # type: ignore + ", + ); + } + if self.needs_f32_to_i32 { + self.src.push_str( + " + def _f32_to_i32(i: float) -> int: + _i32_to_f32_f32[0] = i # type: ignore + return _i32_to_f32_i32[0] # type: ignore + ", + ); + } + } + if self.needs_i64_to_f64 || self.needs_f64_to_i64 { + self.pyimport("ctypes", None); + self.src + .push_str("_i64_to_f64_i64 = ctypes.pointer(ctypes.c_int64(0))\n"); + self.src.push_str( + "_i64_to_f64_f64 = ctypes.cast(_i64_to_f64_i64, ctypes.POINTER(ctypes.c_double))\n", + ); + if self.needs_i64_to_f64 { + self.src.push_str( + " + def _i64_to_f64(i: int) -> float: + _i64_to_f64_i64[0] = i # type: ignore + return _i64_to_f64_f64[0] # type: ignore + ", + ); + } + if self.needs_f64_to_i64 { + self.src.push_str( + " + def _f64_to_i64(i: float) -> int: + _i64_to_f64_f64[0] = i # type: ignore + return _i64_to_f64_i64[0] # type: ignore + ", + ); + } + } + if self.needs_decode_utf8 { + self.src.push_str( + " + def _decode_utf8(mem: wasmer.Memory, ptr: int, len: int) -> str: + print('ptr = ' + str(ptr)) + print('len = ' + str(len)) + ptr = ptr & 0xffffffff + len = len & 0xffffffff + if ptr + len > mem.data_size: + raise IndexError('string out of bounds') + view = mem.uint8_view() + bytes = bytearray(view[ptr:ptr+len]) + x = bytes.decode('utf8') + print('decode_utf8: ' + x) + print('bytes: ' + str(bytes)) + return x + ", + ); + } + if self.needs_encode_utf8 { + self.pyimport("typing", "Tuple"); + self.src.push_str( + " + def _encode_utf8(val: str, realloc: wasmer.Function, mem: wasmer.Memory) -> Tuple[int, int]: + bytes = val.encode('utf8') + print('encode_utf8: ' + val) + print('bytes: ' + str(bytes)) + ptr = realloc(0, 0, 1, len(bytes)) + assert(isinstance(ptr, int)) + ptr = ptr & 0xffffffff + if ptr + len(bytes) > mem.data_size: + raise IndexError('string out of bounds') + view = mem.uint8_view() + view[ptr:ptr+len(bytes)] = bytes + return (ptr, len(bytes)) + ", + ); + } + if self.needs_list_canon_lift { + self.pyimport("typing", "List"); + self.pyimport("typing", "Callable"); + // TODO: this is doing a native-endian read, not a little-endian + // read + self.src.push_str( + " + def _list_canon_lift(ptr: int, len: int, size: int, make_view: Callable[[], Any], mem: wasmer.Memory) -> Any: + ptr = ptr & 0xffffffff + len = len & 0xffffffff + if ptr + len * size > mem.data_size: + raise IndexError('list out of bounds') + view = make_view() + assert(size == view.bytes_per_element) + view_ptr = ptr // view.bytes_per_element + return view[view_ptr:view_ptr + len] + ", + ); + } + if self.needs_list_canon_lower { + self.pyimport("typing", "List"); + self.pyimport("typing", "Tuple"); + self.pyimport("typing", "Callable"); + // TODO: this is doing a native-endian write, not a little-endian + // write + self.src.push_str( + " + def _list_canon_lower(list: Any, make_view: Callable[[], Any], size: int, align: int, realloc: wasmer.Function, mem: wasmer.Memory) -> Tuple[int, int]: + total_size = size * len(list) + ptr = realloc(0, 0, align, total_size) + assert(isinstance(ptr, int)) + ptr = ptr & 0xffffffff + if ptr + total_size > mem.data_size: + raise IndexError('list realloc return of bounds') + view = make_view() + assert(size == view.bytes_per_element) + view_ptr = ptr // view.bytes_per_element + view[view_ptr:view_ptr + len(list)] = list + return (ptr, len(list)) + ", + ); + } + + if iface.resources.len() > 0 { + self.pyimport("typing", "TypeVar"); + self.pyimport("typing", "Generic"); + self.pyimport("typing", "List"); + self.pyimport("typing", "Optional"); + self.pyimport("dataclasses", "dataclass"); + self.needs_t_typevar = true; + self.src.push_str( + " + @dataclass + class SlabEntry(Generic[T]): + next: int + val: Optional[T] + + class Slab(Generic[T]): + head: int + list: List[SlabEntry[T]] + + def __init__(self) -> None: + self.list = [] + self.head = 0 + + def insert(self, val: T) -> int: + if self.head >= len(self.list): + self.list.append(SlabEntry(next = len(self.list) + 1, val = None)) + ret = self.head + slot = self.list[ret] + self.head = slot.next + slot.next = -1 + slot.val = val + return ret + + def get(self, idx: int) -> T: + if idx >= len(self.list): + raise IndexError('handle index not valid') + slot = self.list[idx] + if slot.next == -1: + assert(slot.val is not None) + return slot.val + raise IndexError('handle index not valid') + + def remove(self, idx: int) -> T: + ret = self.get(idx) + slot = self.list[idx] + slot.val = None + slot.next = self.head + self.head = idx + return ret + ", + ); + } + if self.needs_push_buffer { + self.pyimport("typing", "TypeVar"); + self.pyimport("typing", "Generic"); + self.pyimport("typing", "Callable"); + self.needs_t_typevar = true; + self.src.push_str( + " + class PushBuffer(Generic[T]): + def __init__(self, ptr: int, len: int, size: int, write: Callable) -> None: + self.ptr = ptr + self.len = len + self.size = size + self.write = write + + def __len__(self) -> int: + return self.len + + def push(self, val: T) -> bool: + if self.len == 0: + return False; + self.len -= 1; + self.write(val, self.ptr); + self.ptr += self.size; + return True + ", + ) + } + if self.needs_pull_buffer { + self.pyimport("typing", "TypeVar"); + self.pyimport("typing", "Generic"); + self.pyimport("typing", "Callable"); + self.pyimport("typing", "Optional"); + self.needs_t_typevar = true; + self.src.push_str( + " + class PullBuffer(Generic[T]): + def __init__(self, ptr: int, len: int, size: int, read: Callable) -> None: + self.len = len + self.ptr = ptr + self.size = size + self.read = read + + def __len__(self) -> int: + return self.len + + def pull(self) -> Optional[T]: + if self.len == 0: + return None + self.len -= 1 + ret: T = self.read(self.ptr) + self.ptr += self.size + return ret + ", + ) + } + } + + fn type_string(&mut self, iface: &Interface, ty: &Type) -> String { + let prev = mem::take(&mut self.src); + self.print_ty(iface, ty); + mem::replace(&mut self.src, prev).into() + } + + fn print_ty(&mut self, iface: &Interface, ty: &Type) { + match ty { + Type::U8 + | Type::S8 + | Type::U16 + | Type::S16 + | Type::U32 + | Type::S32 + | Type::U64 + | Type::S64 + | Type::Usize + | Type::CChar => self.src.push_str("int"), + Type::F32 | Type::F64 => self.src.push_str("float"), + Type::Char => self.src.push_str("str"), + Type::Handle(id) => { + // In general we want to use quotes around this to support + // forward-references (such as a method on a resource returning + // that resource), but that would otherwise generate type alias + // annotations that look like `Foo = 'HandleType'` which isn't + // creating a type alias but rather a string definition. Hack + // around that here to detect the type alias scenario and don't + // surround the type with quotes, otherwise surround with + // single-quotes. + let suffix = if self.src.ends_with(" = ") { + "" + } else { + self.src.push_str("'"); + "'" + }; + self.src + .push_str(&iface.resources[*id].name.to_camel_case()); + self.src.push_str(suffix); + } + Type::Id(id) => { + let ty = &iface.types[*id]; + if let Some(name) = &ty.name { + self.src.push_str(&name.to_camel_case()); + return; + } + match &ty.kind { + TypeDefKind::Type(t) => self.print_ty(iface, t), + TypeDefKind::Record(r) if r.is_tuple() => { + self.print_tuple(iface, r.fields.iter().map(|f| &f.ty)) + } + TypeDefKind::Record(_) => unreachable!(), + TypeDefKind::Variant(v) => { + if v.is_bool() { + self.src.push_str("bool"); + } else if let Some(t) = v.as_option() { + self.pyimport("typing", "Optional"); + self.src.push_str("Optional["); + self.print_ty(iface, t); + self.src.push_str("]"); + } else if let Some((ok, err)) = v.as_expected() { + self.needs_expected = true; + self.src.push_str("Expected["); + match ok { + Some(t) => self.print_ty(iface, t), + None => self.src.push_str("None"), + } + self.src.push_str(", "); + match err { + Some(t) => self.print_ty(iface, t), + None => self.src.push_str("None"), + } + self.src.push_str("]"); + } else { + unreachable!() + } + } + TypeDefKind::List(t) => self.print_list(iface, t), + TypeDefKind::Pointer(_) | TypeDefKind::ConstPointer(_) => { + self.src.push_str("int") + } + TypeDefKind::PushBuffer(t) => self.print_buffer(iface, true, t), + TypeDefKind::PullBuffer(t) => self.print_buffer(iface, false, t), + } + } + } + } + + fn print_tuple<'a>(&mut self, iface: &Interface, types: impl IntoIterator) { + let types = types.into_iter().collect::>(); + if types.is_empty() { + return self.src.push_str("None"); + } + self.pyimport("typing", "Tuple"); + self.src.push_str("Tuple["); + for (i, t) in types.iter().enumerate() { + if i > 0 { + self.src.push_str(", "); + } + self.print_ty(iface, t); + } + self.src.push_str("]"); + } + + fn print_buffer(&mut self, iface: &Interface, push: bool, ty: &Type) { + if push { + self.needs_push_buffer = true; + self.src.push_str("PushBuffer"); + } else { + self.needs_pull_buffer = true; + self.src.push_str("PullBuffer"); + } + self.src.push_str("["); + self.print_ty(iface, ty); + self.src.push_str("]"); + } + + fn print_list(&mut self, iface: &Interface, element: &Type) { + match element { + Type::Char => self.src.push_str("str"), + Type::U8 => self.src.push_str("bytes"), + t => { + self.pyimport("typing", "List"); + self.src.push_str("List["); + self.print_ty(iface, t); + self.src.push_str("]"); + } + } + } + + fn pyimport<'a>(&mut self, module: &str, name: impl Into>) { + let name = name.into(); + let list = self + .pyimports + .entry(module.to_string()) + .or_insert(match name { + Some(_) => Some(BTreeSet::new()), + None => None, + }); + match name { + Some(name) => { + assert!(list.is_some()); + list.as_mut().unwrap().insert(name.to_string()); + } + None => assert!(list.is_none()), + } + } + + fn array_ty(&self, iface: &Interface, ty: &Type) -> Option<&'static str> { + match ty { + Type::U8 | Type::CChar => Some("uint8"), + Type::S8 => Some("int8"), + Type::U16 => Some("uint16"), + Type::S16 => Some("int16"), + Type::U32 | Type::Usize => Some("uint32"), + Type::S32 => Some("int32"), + Type::U64 => Some("uint64"), + Type::S64 => Some("int64"), + Type::F32 => Some("float32"), + Type::F64 => Some("float64"), + Type::Char => None, + Type::Handle(_) => None, + Type::Id(id) => match &iface.types[*id].kind { + TypeDefKind::Type(t) => self.array_ty(iface, t), + _ => None, + }, + } + } + + fn docs(&mut self, docs: &Docs) { + let docs = match &docs.contents { + Some(docs) => docs, + None => return, + }; + for line in docs.lines() { + self.src.push_str(&format!("# {}\n", line)); + } + } + + fn print_sig(&mut self, iface: &Interface, func: &Function) -> Vec { + if !self.in_import { + if let FunctionKind::Static { .. } = func.kind { + self.src.push_str("@classmethod\n"); + } + } + self.src.push_str("def "); + match &func.kind { + FunctionKind::Method { .. } => self.src.push_str(&func.item_name().to_snake_case()), + FunctionKind::Static { .. } if !self.in_import => { + self.src.push_str(&func.item_name().to_snake_case()) + } + _ => self.src.push_str(&func.name.to_snake_case()), + } + if self.in_import { + self.src.push_str("(self"); + } else if let FunctionKind::Static { .. } = func.kind { + self.src.push_str("(cls, obj: '"); + self.src.push_str(&iface.name.to_camel_case()); + self.src.push_str("'"); + } else { + self.src.push_str("(self"); + } + let mut params = Vec::new(); + for (i, (param, ty)) in func.params.iter().enumerate() { + if i == 0 { + if let FunctionKind::Method { .. } = func.kind { + params.push("self".to_string()); + continue; + } + } + self.src.push_str(", "); + self.src.push_str(¶m.to_snake_case()); + params.push(param.to_snake_case()); + self.src.push_str(": "); + self.print_ty(iface, ty); + } + self.src.push_str(") -> "); + match func.results.len() { + 0 => self.src.push_str("None"), + 1 => self.print_ty(iface, &func.results[0].1), + _ => self.print_tuple(iface, func.results.iter().map(|p| &p.1)), + } + params + } +} + +impl Generator for WasmtimePy { + fn preprocess_one(&mut self, iface: &Interface, dir: Direction) { + let variant = Self::abi_variant(dir); + self.sizes.fill(variant, iface); + self.in_import = variant == AbiVariant::GuestImport; + } + + fn type_record( + &mut self, + iface: &Interface, + _id: TypeId, + name: &str, + record: &Record, + docs: &Docs, + ) { + self.docs(docs); + if record.is_tuple() { + self.src.push_str(&format!("{} = ", name.to_camel_case())); + self.print_tuple(iface, record.fields.iter().map(|f| &f.ty)); + } else if record.is_flags() { + self.pyimport("enum", "Flag"); + self.pyimport("enum", "auto"); + self.src + .push_str(&format!("class {}(Flag):\n", name.to_camel_case())); + self.indent(); + for field in record.fields.iter() { + self.docs(&field.docs); + self.src + .push_str(&format!("{} = auto()\n", field.name.to_shouty_snake_case())); + } + if record.fields.is_empty() { + self.src.push_str("pass\n"); + } + self.deindent(); + } else { + self.pyimport("dataclasses", "dataclass"); + self.src + .push_str(&format!("@dataclass\nclass {}:\n", name.to_camel_case())); + self.indent(); + for field in record.fields.iter() { + self.docs(&field.docs); + self.src + .push_str(&format!("{}: ", field.name.to_snake_case())); + self.print_ty(iface, &field.ty); + self.src.push_str("\n"); + } + if record.fields.is_empty() { + self.src.push_str("pass\n"); + } + self.deindent(); + } + self.src.push_str("\n"); + } + + fn type_variant( + &mut self, + iface: &Interface, + _id: TypeId, + name: &str, + variant: &Variant, + docs: &Docs, + ) { + self.docs(docs); + if variant.is_bool() { + self.src + .push_str(&format!("{} = bool\n", name.to_camel_case())); + } else if variant.is_enum() { + self.pyimport("enum", "Enum"); + self.src + .push_str(&format!("class {}(Enum):\n", name.to_camel_case())); + self.indent(); + for (i, case) in variant.cases.iter().enumerate() { + self.docs(&case.docs); + + // TODO this handling of digits should be more general and + // shouldn't be here just to fix the one case in wasi where an + // enum variant is "2big" and doesn't generate valid Python. We + // should probably apply this to all generated Python + // identifiers. + let mut name = case.name.to_shouty_snake_case(); + if name.chars().next().unwrap().is_digit(10) { + name = format!("_{}", name); + } + self.src.push_str(&format!("{} = {}\n", name, i)); + } + self.deindent(); + } else if let Some(t) = variant.as_option() { + self.pyimport("typing", "Optional"); + self.src + .push_str(&format!("{} = Optional[", name.to_camel_case())); + self.print_ty(iface, t); + self.src.push_str("]\n"); + } else if let Some((ok, err)) = variant.as_expected() { + self.needs_expected = true; + self.src + .push_str(&format!("{} = Expected[", name.to_camel_case())); + match ok { + Some(t) => self.print_ty(iface, t), + None => self.src.push_str("None"), + } + self.src.push_str(", "); + match err { + Some(t) => self.print_ty(iface, t), + None => self.src.push_str("None"), + } + self.src.push_str("]\n"); + } else { + self.pyimport("dataclasses", "dataclass"); + let mut cases = Vec::new(); + for case in variant.cases.iter() { + self.docs(&case.docs); + self.src.push_str("@dataclass\n"); + let name = format!("{}{}", name.to_camel_case(), case.name.to_camel_case()); + self.src.push_str(&format!("class {}:\n", name)); + self.indent(); + match &case.ty { + Some(ty) => { + self.src.push_str("value: "); + self.print_ty(iface, ty); + self.src.push_str("\n"); + } + None => self.src.push_str("pass\n"), + } + self.deindent(); + self.src.push_str("\n"); + cases.push(name); + } + + self.pyimport("typing", "Union"); + self.src.push_str(&format!( + "{} = Union[{}]\n", + name.to_camel_case(), + cases.join(", "), + )); + } + self.src.push_str("\n"); + } + + fn type_resource(&mut self, _iface: &Interface, _ty: ResourceId) { + // if !self.in_import { + // self.exported_resources.insert(ty); + // } + } + + fn type_alias(&mut self, iface: &Interface, _id: TypeId, name: &str, ty: &Type, docs: &Docs) { + self.docs(docs); + self.src.push_str(&format!("{} = ", name.to_camel_case())); + self.print_ty(iface, ty); + self.src.push_str("\n"); + } + + fn type_list(&mut self, iface: &Interface, _id: TypeId, name: &str, ty: &Type, docs: &Docs) { + self.docs(docs); + self.src.push_str(&format!("{} = ", name.to_camel_case())); + self.print_list(iface, ty); + self.src.push_str("\n"); + } + + fn type_pointer( + &mut self, + _iface: &Interface, + _id: TypeId, + _name: &str, + _const_: bool, + _ty: &Type, + _docs: &Docs, + ) { + // drop((iface, _id, name, const_, ty, docs)); + } + + fn type_builtin(&mut self, iface: &Interface, id: TypeId, name: &str, ty: &Type, docs: &Docs) { + self.type_alias(iface, id, name, ty, docs); + } + + fn type_push_buffer( + &mut self, + iface: &Interface, + _id: TypeId, + name: &str, + ty: &Type, + docs: &Docs, + ) { + self.docs(docs); + self.src.push_str(&format!("{} = ", name.to_camel_case())); + self.print_buffer(iface, true, ty); + self.src.push_str("\n"); + } + + fn type_pull_buffer( + &mut self, + iface: &Interface, + _id: TypeId, + name: &str, + ty: &Type, + docs: &Docs, + ) { + self.docs(docs); + self.src.push_str(&format!("{} = ", name.to_camel_case())); + self.print_buffer(iface, false, ty); + self.src.push_str("\n"); + } + + // As with `abi_variant` above, we're generating host-side bindings here + // so a user "export" uses the "guest import" ABI variant on the inside of + // this `Generator` implementation. + fn export(&mut self, iface: &Interface, func: &Function) { + assert!(!func.is_async, "async not supported yet"); + let prev = mem::take(&mut self.src); + + self.print_sig(iface, func); + let pysig = mem::take(&mut self.src).into(); + + let sig = iface.wasm_signature(AbiVariant::GuestImport, func); + self.src + .push_str(&format!("def {}(", func.name.to_snake_case(),)); + let mut params = Vec::new(); + for (i, param) in sig.params.iter().enumerate() { + if i != 0 { + self.src.push_str(", "); + } + let name = format!("arg{}", i); + self.src.push_str(&name); + self.src.push_str(": "); + self.src.push_str(wasm_ty_typing(*param)); + params.push(name); + } + self.src.push_str(") -> "); + match sig.results.len() { + 0 => self.src.push_str("None"), + 1 => self.src.push_str(wasm_ty_typing(sig.results[0])), + _ => unimplemented!(), + } + self.src.push_str(":\n"); + self.indent(); + + let mut f = FunctionBindgen::new(self, params); + iface.call( + AbiVariant::GuestImport, + LiftLower::LiftArgsLowerResults, + func, + &mut f, + ); + + let FunctionBindgen { + src, + needs_memory, + needs_realloc, + needs_free, + mut locals, + .. + } = f; + + if needs_memory { + // TODO: hardcoding "memory" + self.src.push_str("m = get_export(\"memory\")\n"); + self.src.push_str("assert(isinstance(m, wasmer.Memory))\n"); + self.pyimport("typing", "cast"); + self.src.push_str("memory = cast(wasmer.Memory, m)\n"); + locals.insert("memory").unwrap(); + } + + if let Some(name) = needs_realloc { + self.src + .push_str(&format!("realloc = get_export(\"{}\")\n", name)); + self.src + .push_str("assert(isinstance(realloc, wasmer.Function))\n"); + locals.insert("realloc").unwrap(); + } + + if let Some(name) = needs_free { + self.src.push_str(&format!("free = get_export(\"{}\")\n", name)); + self.src + .push_str("assert(isinstance(free, wasmer.Function))\n"); + locals.insert("free").unwrap(); + } + self.src.push_str(&src); + self.deindent(); + + let src = mem::replace(&mut self.src, prev); + let mut wasm_ty = String::from("wasmer.FunctionType(["); + wasm_ty.push_str( + &sig.params + .iter() + .map(|t| wasm_ty_ctor(*t)) + .collect::>() + .join(", "), + ); + wasm_ty.push_str("], ["); + wasm_ty.push_str( + &sig.results + .iter() + .map(|t| wasm_ty_ctor(*t)) + .collect::>() + .join(", "), + ); + wasm_ty.push_str("])"); + let import = Import { + name: func.name.clone(), + src, + wasm_ty, + pysig, + }; + let imports = self + .guest_imports + .entry(iface.name.to_string()) + .or_insert(Imports::default()); + let dst = match &func.kind { + FunctionKind::Freestanding | FunctionKind::Static { .. } => { + &mut imports.freestanding_funcs + } + FunctionKind::Method { resource, .. } => imports + .resource_funcs + .entry(*resource) + .or_insert(Vec::new()), + }; + dst.push(import); + } + + // As with `abi_variant` above, we're generating host-side bindings here + // so a user "import" uses the "export" ABI variant on the inside of + // this `Generator` implementation. + fn import(&mut self, iface: &Interface, func: &Function) { + assert!(!func.is_async, "async not supported yet"); + let prev = mem::take(&mut self.src); + + let params = self.print_sig(iface, func); + self.src.push_str(":\n"); + self.indent(); + + let src_object = match &func.kind { + FunctionKind::Freestanding => "self".to_string(), + FunctionKind::Static { .. } => "obj".to_string(), + FunctionKind::Method { .. } => "self._obj".to_string(), + }; + let mut f = FunctionBindgen::new(self, params); + f.src_object = src_object; + iface.call( + AbiVariant::GuestExport, + LiftLower::LowerArgsLiftResults, + func, + &mut f, + ); + + let FunctionBindgen { + src, + needs_memory, + needs_realloc, + needs_free, + src_object, + .. + } = f; + if needs_memory { + // TODO: hardcoding "memory" + self.src + .push_str(&format!("memory = {}._memory;\n", src_object)); + } + + if let Some(name) = &needs_realloc { + self.src.push_str(&format!( + "realloc = {}._{}\n", + src_object, + name.to_snake_case(), + )); + } + + if let Some(name) = &needs_free { + self.src.push_str(&format!( + "free = {}._{}\n", + src_object, + name.to_snake_case(), + )); + } + self.src.push_str(&src); + self.deindent(); + + let exports = self + .guest_exports + .entry(iface.name.to_string()) + .or_insert_with(Exports::default); + if needs_memory { + exports.fields.insert("memory".to_string(), "wasmer.Memory"); + } + if let Some(name) = &needs_realloc { + exports.fields.insert(name.clone(), "wasmer.Function"); + } + if let Some(name) = &needs_free { + exports.fields.insert(name.clone(), "wasmer.Function"); + } + exports.fields.insert(func.name.clone(), "wasmer.Function"); + + let func_body = mem::replace(&mut self.src, prev); + let dst = match &func.kind { + FunctionKind::Freestanding => &mut exports.freestanding_funcs, + FunctionKind::Static { resource, .. } | FunctionKind::Method { resource, .. } => { + exports + .resource_funcs + .entry(*resource) + .or_insert(Vec::new()) + } + }; + dst.push(func_body); + } + + fn finish_one(&mut self, iface: &Interface, files: &mut Files) { + self.pyimport("typing", "Any"); + self.pyimport("abc", "abstractmethod"); + self.pyimport("typing", "Callable"); + + let types = mem::take(&mut self.src); + self.print_intrinsics(iface); + let intrinsics = mem::take(&mut self.src); + + for (k, v) in self.pyimports.iter() { + match v { + Some(list) => { + let list = list.iter().cloned().collect::>().join(", "); + self.src.push_str(&format!("from {} import {}\n", k, list)); + } + None => { + self.src.push_str(&format!("import {}\n", k)); + } + } + } + self.src.push_str("import wasmer # type: ignore\n"); + self.src.push_str( + " + try: + from typing import Protocol + except ImportError: + class Protocol: # type: ignore + pass + ", + ); + self.src.push_str("\n"); + + if self.needs_t_typevar { + self.src.push_str("T = TypeVar('T')\n"); + } + + self.src.push_str(&intrinsics); + for (id, r) in iface.resources.iter() { + let name = r.name.to_camel_case(); + if self.in_import { + self.src.push_str(&format!("class {}(Protocol):\n", name)); + self.src.indent(2); + self.src.push_str("def drop(self) -> None:\n"); + self.src.indent(2); + self.src.push_str("pass\n"); + self.src.deindent(2); + + for (_, funcs) in self.guest_imports.iter() { + if let Some(funcs) = funcs.resource_funcs.get(&id) { + for func in funcs { + self.src.push_str("@abstractmethod\n"); + self.src.push_str(&func.pysig); + self.src.push_str(":\n"); + self.src.indent(2); + self.src.push_str("raise NotImplementedError\n"); + self.src.deindent(2); + } + } + } + self.src.deindent(2); + } else { + self.src.push_str(&format!("class {}:\n", name)); + self.src.indent(2); + self.src.push_str(&format!( + " + _wasm_val: int + _refcnt: int + _obj: '{iface}' + _destroyed: bool + + def __init__(self, val: int, obj: '{iface}') -> None: + self._wasm_val = val + self._refcnt = 1 + self._obj = obj + self._destroyed = False + + def clone(self) -> '{name}': + self._refcnt += 1 + return self + + def drop(self) -> None: + self._refcnt -= 1; + if self._refcnt != 0: + return + assert(not self._destroyed) + self._destroyed = True + self._obj._canonical_abi_drop_{drop}(self._wasm_val) + + def __del__(self) -> None: + if not self._destroyed: + raise RuntimeError('wasm object not dropped') + ", + name = name, + iface = iface.name.to_camel_case(), + drop = name.to_snake_case(), + )); + + for (_, exports) in self.guest_exports.iter() { + if let Some(funcs) = exports.resource_funcs.get(&id) { + for func in funcs { + self.src.push_str(func); + } + } + } + + self.src.deindent(2); + } + } + self.src.push_str(&types); + + for (module, funcs) in mem::take(&mut self.guest_imports) { + self.src + .push_str(&format!("class {}(Protocol):\n", module.to_camel_case())); + self.indent(); + for func in funcs.freestanding_funcs.iter() { + self.src.push_str("@abstractmethod\n"); + self.src.push_str(&func.pysig); + self.src.push_str(":\n"); + self.indent(); + self.src.push_str("raise NotImplementedError\n"); + self.deindent(); + } + self.deindent(); + self.src.push_str("\n"); + + self.src.push_str(&format!( + "def add_{}_to_imports(store: wasmer.Store, imports: dict[str, dict[str, Any]], host: {}, get_export: Callable[[str], Any]) -> None:\n", + module.to_snake_case(), + module.to_camel_case(), + )); + self.indent(); + + for (id, r) in iface.resources.iter() { + self.src.push_str(&format!( + "_resources{}: Slab[{}] = Slab()\n", + id.index(), + r.name.to_camel_case() + )); + } + + for func in funcs + .freestanding_funcs + .iter() + .chain(funcs.resource_funcs.values().flat_map(|v| v)) + { + self.src.push_str(&format!("ty = {}\n", func.wasm_ty)); + self.src.push_str(&func.src); + self.src.push_str(&format!( + "imports.setdefault('{}', {{}})['{}'] = wasmer.Function(store, {}, ty)\n", + iface.name, + func.name, + func.name.to_snake_case(), + )); + } + + for (id, resource) in iface.resources.iter() { + let snake = resource.name.to_snake_case(); + + self.src.push_str(&format!( + "def resource_drop_{}(i: int) -> None:\n _resources{}.remove(i).drop()\n", + snake, + id.index(), + )); + self.src + .push_str("ty = wasmer.FunctionType([wasmer.Type.I32], [])\n"); + self.src.push_str(&format!( + "imports.setdefault('canonical_abi', {{}})['resource_drop_{}'] = \ + wasmer.Function(store, resource_drop_{}, ty)\n", + resource.name, snake, + )); + } + self.deindent(); + } + + // This is exculsively here to get mypy to not complain about empty + // modules, this probably won't really get triggered much in practice + if !self.in_import && self.guest_exports.is_empty() { + self.src + .push_str(&format!("class {}:\n", iface.name.to_camel_case())); + self.indent(); + if iface.resources.len() == 0 { + self.src.push_str("pass\n"); + } else { + for (_, r) in iface.resources.iter() { + self.src.push_str(&format!( + "_canonical_abi_drop_{}: wasmer.Function\n", + r.name.to_snake_case(), + )); + } + } + self.deindent(); + } + + for (module, exports) in mem::take(&mut self.guest_exports) { + let module = module.to_camel_case(); + self.src.push_str(&format!("class {}:\n", module)); + self.indent(); + + self.src.push_str("instance: wasmer.Instance\n"); + for (name, ty) in exports.fields.iter() { + self.src + .push_str(&format!("_{}: {}\n", name.to_snake_case(), ty)); + } + for (id, r) in iface.resources.iter() { + self.src.push_str(&format!( + "_resource{}_slab: Slab[{}]\n", + id.index(), + r.name.to_camel_case(), + )); + self.src.push_str(&format!( + "_canonical_abi_drop_{}: wasmer.Function\n", + r.name.to_snake_case(), + )); + } + + self.src.push_str("def __init__(self, store: wasmer.Store, imports: dict[str, dict[str, Any]], module: wasmer.Module):\n"); + self.indent(); + for (id, r) in iface.resources.iter() { + self.src.push_str(&format!( + " + ty1 = wasmer.FunctionType([wasmer.Type.I32], []) + ty2 = wasmer.FunctionType([wasmer.Type.I32], [wasmer.Type.I32]) + def drop_{snake}(idx: int) -> None: + self._resource{idx}_slab.remove(idx).drop(); + imports.setdefault('canonical_abi', {{}})['resource_drop_{name}'] = wasmer.Function(store, drop_{snake}, ty1) + + def clone_{snake}(idx: int) -> int: + obj = self._resource{idx}_slab.get(idx) + return self._resource{idx}_slab.insert(obj.clone()) + imports.setdefault('canonical_abi', {{}})['resource_clone_{name}'] = wasmer.Function(store, clone_{snake}, ty2) + + def get_{snake}(idx: int) -> int: + obj = self._resource{idx}_slab.get(idx) + return obj._wasm_val + imports.setdefault('canonical_abi', {{}})['resource_get_{name}'] = wasmer.Function(store, get_{snake}, ty2) + + def new_{snake}(val: int) -> int: + return self._resource{idx}_slab.insert({camel}(val, self)) + imports.setdefault('canonical_abi', {{}})['resource_new_{name}'] = wasmer.Function(store, new_{snake}, ty2) + ", + name = r.name, + camel = r.name.to_camel_case(), + snake = r.name.to_snake_case(), + idx = id.index(), + )); + } + self.src + .push_str("self.instance = wasmer.Instance(module, imports)\n"); + for (name, ty) in exports.fields.iter() { + self.src.push_str(&format!( + " + {snake} = self.instance.exports.__getattribute__('{name}') + assert(isinstance({snake}, {ty})) + self._{snake} = {snake} + ", + name = name, + snake = name.to_snake_case(), + ty = ty, + )); + } + for (id, r) in iface.resources.iter() { + self.src.push_str(&format!( + " + self._resource{idx}_slab = Slab() + canon_drop_{snake} = self.instance.exports.__getattribute__('canonical_abi_drop_{name}') + assert(isinstance(canon_drop_{snake}, wasmer.Function)) + self._canonical_abi_drop_{snake} = canon_drop_{snake} + ", + idx = id.index(), + name = r.name, + snake = r.name.to_snake_case(), + )); + } + self.deindent(); + + for func in exports.freestanding_funcs.iter() { + self.src.push_str(&func); + } + + self.deindent(); + } + + files.push("bindings.py", self.src.as_bytes()); + } +} + +struct FunctionBindgen<'a> { + gen: &'a mut WasmtimePy, + locals: Ns, + src: Source, + block_storage: Vec, + blocks: Vec<(String, Vec)>, + needs_memory: bool, + needs_realloc: Option, + needs_free: Option, + params: Vec, + payloads: Vec, + src_object: String, +} + +impl FunctionBindgen<'_> { + fn new(gen: &mut WasmtimePy, params: Vec) -> FunctionBindgen<'_> { + let mut locals = Ns::default(); + locals.insert("len").unwrap(); // python built-in + locals.insert("base").unwrap(); // may be used as loop var + locals.insert("i").unwrap(); // may be used as loop var + for param in params.iter() { + locals.insert(param).unwrap(); + } + FunctionBindgen { + gen, + locals, + src: Source::default(), + block_storage: Vec::new(), + blocks: Vec::new(), + needs_memory: false, + needs_realloc: None, + needs_free: None, + params, + payloads: Vec::new(), + src_object: "self".to_string(), + } + } + + fn clamp(&mut self, results: &mut Vec, operands: &[String], min: T, max: T) + where + T: std::fmt::Display, + { + self.gen.needs_clamp = true; + results.push(format!("_clamp({}, {}, {})", operands[0], min, max)); + } + + fn load(&mut self, ty: &str, offset: i32, operands: &[String], results: &mut Vec) { + self.needs_memory = true; + self.gen.needs_load = true; + let tmp = self.locals.tmp("load"); + self.src.push_str(&format!( + "{} = _load(memory.{}_view, memory, {}, {})\n", + tmp, ty, operands[0], offset, + )); + results.push(tmp); + } + + fn store(&mut self, ty: &str, offset: i32, operands: &[String]) { + self.needs_memory = true; + self.gen.needs_store = true; + self.src.push_str(&format!( + "_store(memory.{}_view, memory, {}, {}, {})\n", + ty, operands[1], offset, operands[0] + )); + } +} + +impl Bindgen for FunctionBindgen<'_> { + type Operand = String; + + fn sizes(&self) -> &SizeAlign { + &self.gen.sizes + } + + fn push_block(&mut self) { + let prev = mem::take(&mut self.src); + self.block_storage.push(prev); + } + + fn finish_block(&mut self, operands: &mut Vec) { + let to_restore = self.block_storage.pop().unwrap(); + let src = mem::replace(&mut self.src, to_restore); + self.blocks.push((src.into(), mem::take(operands))); + } + + fn allocate_typed_space(&mut self, _iface: &Interface, _ty: TypeId) -> String { + unimplemented!() + } + + fn i64_return_pointer_area(&mut self, _amt: usize) -> String { + unimplemented!() + } + + fn is_list_canonical(&self, iface: &Interface, ty: &Type) -> bool { + self.gen.array_ty(iface, ty).is_some() + } + + fn emit( + &mut self, + iface: &Interface, + inst: &Instruction<'_>, + operands: &mut Vec, + results: &mut Vec, + ) { + match inst { + Instruction::GetArg { nth } => results.push(self.params[*nth].clone()), + Instruction::I32Const { val } => results.push(val.to_string()), + Instruction::ConstZero { tys } => { + for t in tys.iter() { + match t { + WasmType::I32 | WasmType::I64 => results.push("0".to_string()), + WasmType::F32 | WasmType::F64 => results.push("0.0".to_string()), + } + } + } + + // The representation of i32 in Python is a number, so 8/16-bit + // values get further clamped to ensure that the upper bits aren't + // set when we pass the value, ensuring that only the right number + // of bits are transferred. + Instruction::U8FromI32 => self.clamp(results, operands, u8::MIN, u8::MAX), + Instruction::S8FromI32 => self.clamp(results, operands, i8::MIN, i8::MAX), + Instruction::U16FromI32 => self.clamp(results, operands, u16::MIN, u16::MAX), + Instruction::S16FromI32 => self.clamp(results, operands, i16::MIN, i16::MAX), + // Ensure the bits of the number are treated as unsigned. + Instruction::U32FromI32 | Instruction::UsizeFromI32 => { + results.push(format!("{} & 0xffffffff", operands[0])); + } + // All bigints coming from wasm are treated as signed, so convert + // it to ensure it's treated as unsigned. + Instruction::U64FromI64 => { + results.push(format!("{} & 0xffffffffffffffff", operands[0])); + } + // Nothing to do signed->signed where the representations are the + // same. + Instruction::S32FromI32 | Instruction::S64FromI64 => { + results.push(operands.pop().unwrap()) + } + + // All values coming from the host and going to wasm need to have + // their ranges validated, since the host could give us any value. + Instruction::I32FromU8 => self.clamp(results, operands, u8::MIN, u8::MAX), + Instruction::I32FromS8 => self.clamp(results, operands, i8::MIN, i8::MAX), + Instruction::I32FromU16 => self.clamp(results, operands, u16::MIN, u16::MAX), + Instruction::I32FromS16 => self.clamp(results, operands, i16::MIN, i16::MAX), + // TODO: need to do something to get this to be represented as signed? + Instruction::I32FromU32 | Instruction::I32FromUsize => { + self.clamp(results, operands, u32::MIN, u32::MAX); + } + Instruction::I32FromS32 => self.clamp(results, operands, i32::MIN, i32::MAX), + // TODO: need to do something to get this to be represented as signed? + Instruction::I64FromU64 => self.clamp(results, operands, u64::MIN, u64::MAX), + Instruction::I64FromS64 => self.clamp(results, operands, i64::MIN, i64::MAX), + + // Python uses `float` for f32/f64, so everything is equivalent + // here. + Instruction::If32FromF32 + | Instruction::If64FromF64 + | Instruction::F32FromIf32 + | Instruction::F64FromIf64 => results.push(operands.pop().unwrap()), + + // Validate that i32 values coming from wasm are indeed valid code + // points. + Instruction::CharFromI32 => { + self.gen.needs_validate_guest_char = true; + results.push(format!("_validate_guest_char({})", operands[0])); + } + + Instruction::I32FromChar => { + results.push(format!("ord({})", operands[0])); + } + + Instruction::Bitcasts { casts } => { + for (cast, op) in casts.iter().zip(operands) { + match cast { + Bitcast::I32ToF32 => { + self.gen.needs_i32_to_f32 = true; + results.push(format!("_i32_to_f32({})", op)); + } + Bitcast::F32ToI32 => { + self.gen.needs_f32_to_i32 = true; + results.push(format!("_f32_to_i32({})", op)); + } + Bitcast::F32ToF64 | Bitcast::F64ToF32 => results.push(op.clone()), + Bitcast::I64ToF64 => { + self.gen.needs_i64_to_f64 = true; + results.push(format!("_i64_to_f64({})", op)); + } + Bitcast::F64ToI64 => { + self.gen.needs_f64_to_i64 = true; + results.push(format!("_f64_to_i64({})", op)); + } + Bitcast::I64ToF32 => { + self.gen.needs_i32_to_f32 = true; + results.push(format!("_i32_to_f32(({}) & 0xffffffff)", op)); + } + Bitcast::F32ToI64 => { + self.gen.needs_f32_to_i32 = true; + results.push(format!("_f32_to_i32({})", op)); + } + Bitcast::I32ToI64 | Bitcast::I64ToI32 | Bitcast::None => { + results.push(op.clone()) + } + } + } + } + + // These instructions are used with handles when we're implementing + // imports. This means we interact with the `resources` slabs to + // translate the wasm-provided index into a Python value. + Instruction::I32FromOwnedHandle { ty } => { + results.push(format!("_resources{}.insert({})", ty.index(), operands[0])); + } + Instruction::HandleBorrowedFromI32 { ty } => { + results.push(format!("_resources{}.get({})", ty.index(), operands[0])); + } + + // These instructions are used for handles to objects owned in wasm. + // This means that they're interacting with a wrapper class defined + // in Python. + Instruction::I32FromBorrowedHandle { ty } => { + let obj = self.locals.tmp("obj"); + self.src.push_str(&format!("{} = {}\n", obj, operands[0])); + + results.push(format!( + "{}._resource{}_slab.insert({}.clone())", + self.src_object, + ty.index(), + obj, + )); + } + Instruction::HandleOwnedFromI32 { ty } => { + results.push(format!( + "{}._resource{}_slab.remove({})", + self.src_object, + ty.index(), + operands[0], + )); + } + Instruction::RecordLower { record, .. } => { + if record.fields.is_empty() { + return; + } + if record.is_tuple() { + self.src.push_str("("); + for _ in 0..record.fields.len() { + let name = self.locals.tmp("tuplei"); + self.src.push_str(&name); + self.src.push_str(","); + results.push(name); + } + self.src.push_str(") = "); + self.src.push_str(&operands[0]); + self.src.push_str("\n"); + } else { + let tmp = self.locals.tmp("record"); + self.src.push_str(&format!("{} = {}\n", tmp, operands[0])); + for field in record.fields.iter() { + let name = self.locals.tmp("field"); + self.src.push_str(&format!( + "{} = {}.{}\n", + name, + tmp, + field.name.to_snake_case(), + )); + results.push(name); + } + } + } + + Instruction::RecordLift { record, name, .. } => { + if record.is_tuple() { + if operands.is_empty() { + results.push("None".to_string()); + } else { + results.push(format!("({},)", operands.join(", "))); + } + } else { + let name = name.unwrap(); + results.push(format!("{}({})", name.to_camel_case(), operands.join(", "))); + } + } + Instruction::FlagsLift { name, .. } | Instruction::FlagsLift64 { name, .. } => { + results.push(format!("{}({})", name.to_camel_case(), operands[0])); + } + Instruction::FlagsLower { .. } | Instruction::FlagsLower64 { .. } => { + results.push(format!("({}).value", operands[0])); + } + + Instruction::VariantPayloadName => { + let name = self.locals.tmp("payload"); + results.push(name.clone()); + self.payloads.push(name); + } + Instruction::BufferPayloadName => results.push("e".to_string()), + Instruction::VariantLower { + variant, + results: result_types, + name, + .. + } => { + let blocks = self + .blocks + .drain(self.blocks.len() - variant.cases.len()..) + .collect::>(); + let payloads = self + .payloads + .drain(self.payloads.len() - variant.cases.len()..) + .collect::>(); + + if result_types.len() == 1 && variant.is_enum() { + if variant.is_bool() { + return results.push(format!("int({})", operands[0])); + } + if name.is_some() { + return results.push(format!("({}).value", operands[0])); + } + } + + for _ in 0..result_types.len() { + results.push(self.locals.tmp("variant")); + } + + let mut needs_else = true; + for (i, ((case, (block, block_results)), payload)) in + variant.cases.iter().zip(blocks).zip(payloads).enumerate() + { + if i == 0 { + self.src.push_str("if "); + } else if i == 1 && variant.as_option().is_some() { + needs_else = false; + self.src.push_str("else:\n"); + } else { + self.src.push_str("elif "); + } + + if variant.is_bool() { + self.src + .push_str(&format!("{} == bool({}):\n", operands[0], i)); + } else if variant.as_option().is_some() { + if i == 0 { + self.src.push_str(&format!("{} is None:\n", operands[0])); + } + } else if variant.is_enum() && variant.as_expected().is_none() { + self.src + .push_str(&format!("{}.value == {}:\n", operands[0], i)); + } else { + self.src.push_str(&format!( + "isinstance({}, {}{}):\n", + operands[0], + if variant.as_expected().is_some() { + String::new() + } else { + name.unwrap().to_camel_case() + }, + case.name.to_camel_case() + )); + } + self.src.indent(2); + + if case.ty.is_some() { + if variant.as_option().is_some() { + self.src + .push_str(&format!("{} = {}\n", payload, operands[0])); + } else { + self.src + .push_str(&format!("{} = {}.value\n", payload, operands[0])); + } + } + + self.src.push_str(&block); + + for (i, result) in block_results.iter().enumerate() { + self.src.push_str(&format!("{} = {}\n", results[i], result)); + } + self.src.deindent(2); + } + if needs_else { + let variant_name = name.map(|s| s.to_camel_case()); + let variant_name = variant_name.as_deref().unwrap_or_else(|| { + if variant.is_bool() { + "bool" + } else if variant.as_expected().is_some() { + "expected" + } else if variant.as_option().is_some() { + "option" + } else { + unimplemented!() + } + }); + self.src.push_str("else:\n"); + self.src.indent(2); + self.src.push_str(&format!( + "raise TypeError(\"invalid variant specified for {}\")\n", + variant_name + )); + self.src.deindent(2); + } + } + + Instruction::VariantLift { + variant, name, ty, .. + } => { + let blocks = self + .blocks + .drain(self.blocks.len() - variant.cases.len()..) + .collect::>(); + + if let Some(name) = name { + if variant.is_enum() { + results.push(format!("{}({})", name.to_camel_case(), operands[0])); + return; + } + } + + let result = self.locals.tmp("variant"); + self.src.push_str(&format!( + "{}: {}\n", + result, + self.gen.type_string(iface, &Type::Id(*ty)), + )); + for (i, (case, (block, block_results))) in + variant.cases.iter().zip(blocks).enumerate() + { + if i == 0 { + self.src.push_str("if "); + } else { + self.src.push_str("elif "); + } + self.src.push_str(&format!("{} == {}:\n", operands[0], i)); + self.src.indent(2); + self.src.push_str(&block); + + if variant.is_bool() { + assert!(block_results.is_empty()); + self.src + .push_str(&format!("{} = {}\n", result, case.name.to_camel_case(),)); + } else if variant.as_option().is_some() { + if case.ty.is_none() { + assert!(block_results.is_empty()); + self.src.push_str(&format!("{} = None\n", result)); + } else { + assert!(block_results.len() == 1); + self.src + .push_str(&format!("{} = {}\n", result, block_results[0])); + } + } else { + self.src.push_str(&format!( + "{} = {}{}(", + result, + if variant.as_expected().is_some() { + String::new() + } else { + name.unwrap().to_camel_case() + }, + case.name.to_camel_case() + )); + if case.ty.is_some() { + assert!(block_results.len() == 1); + self.src.push_str(&block_results[0]); + } else { + assert!(block_results.is_empty()); + if variant.as_expected().is_some() { + self.src.push_str("None"); + } + } + self.src.push_str(")\n"); + } + self.src.deindent(2); + } + self.src.push_str("else:\n"); + self.src.indent(2); + let variant_name = name.map(|s| s.to_camel_case()); + let variant_name = variant_name.as_deref().unwrap_or_else(|| { + if variant.is_bool() { + "bool" + } else if variant.as_expected().is_some() { + "expected" + } else if variant.as_option().is_some() { + "option" + } else { + unimplemented!() + } + }); + self.src.push_str(&format!( + "raise TypeError(\"invalid variant discriminant for {}\")\n", + variant_name + )); + self.src.deindent(2); + results.push(result); + } + + Instruction::ListCanonLower { element, realloc } => { + // Lowering only happens when we're passing lists into wasm, + // which forces us to always allocate, so this should always be + // `Some`. + let realloc = realloc.unwrap(); + self.needs_memory = true; + self.needs_realloc = Some(realloc.to_string()); + + let ptr = self.locals.tmp("ptr"); + let len = self.locals.tmp("len"); + match element { + Type::Char => { + self.gen.needs_encode_utf8 = true; + self.src.push_str(&format!( + "{}, {} = _encode_utf8({}, realloc, memory)\n", + ptr, len, operands[0], + )); + } + _ => { + let array_ty = self.gen.array_ty(iface, element).unwrap(); + self.gen.needs_list_canon_lower = true; + let size = self.gen.sizes.size(element); + let align = self.gen.sizes.align(element); + self.src.push_str(&format!( + "{}, {} = _list_canon_lower({}, memory.{}_view, {}, {}, realloc, memory)\n", + ptr, len, operands[0], array_ty, size, align, + )); + } + }; + results.push(ptr); + results.push(len); + } + Instruction::ListCanonLift { element, free, .. } => { + self.needs_memory = true; + let ptr = self.locals.tmp("ptr"); + let len = self.locals.tmp("len"); + self.src.push_str(&format!("{} = {}\n", ptr, operands[0])); + self.src.push_str(&format!("{} = {}\n", len, operands[1])); + let (result, align) = match element { + Type::Char => { + self.gen.needs_decode_utf8 = true; + (format!("_decode_utf8(memory, {}, {})", ptr, len), 1) + } + _ => { + let array_ty = self.gen.array_ty(iface, element).unwrap(); + self.gen.needs_list_canon_lift = true; + let mut lift = format!( + "_list_canon_lift({}, {}, {}, memory.{}_view, memory)", + ptr, + len, + self.gen.sizes.size(element), + array_ty, + ); + let pyty = match element { + Type::U8 => { + lift = format!("bytes({})", lift); + "bytes".to_string() + }, + _ => { + self.gen.pyimport("typing", "List"); + format!("List[{}]", self.gen.type_string(iface, element)) + } + }; + self.gen.pyimport("typing", "cast"); + ( + format!("cast({}, {})", pyty, lift), + self.gen.sizes.align(element), + ) + } + }; + match free { + Some(free) => { + self.needs_free = Some(free.to_string()); + let list = self.locals.tmp("list"); + self.src.push_str(&format!("{} = {}\n", list, result)); + self.src + .push_str(&format!("free({}, {}, {})\n", ptr, len, align)); + results.push(list); + } + None => results.push(result), + } + } + + Instruction::ListLower { element, realloc } => { + let base = self.payloads.pop().unwrap(); + let e = self.payloads.pop().unwrap(); + let realloc = realloc.unwrap(); + let (body, body_results) = self.blocks.pop().unwrap(); + assert!(body_results.is_empty()); + let vec = self.locals.tmp("vec"); + let result = self.locals.tmp("result"); + let len = self.locals.tmp("len"); + self.needs_realloc = Some(realloc.to_string()); + let size = self.gen.sizes.size(element); + let align = self.gen.sizes.align(element); + + // first store our vec-to-lower in a temporary since we'll + // reference it multiple times. + self.src.push_str(&format!("{} = {}\n", vec, operands[0])); + self.src.push_str(&format!("{} = len({})\n", len, vec)); + + // ... then realloc space for the result in the guest module + self.src.push_str(&format!( + "{} = realloc(0, 0, {}, {} * {})\n", + result, align, len, size, + )); + self.src + .push_str(&format!("assert(isinstance({}, int))\n", result)); + + // ... then consume the vector and use the block to lower the + // result. + let i = self.locals.tmp("i"); + self.src + .push_str(&format!("for {} in range(0, {}):\n", i, len)); + self.src.indent(2); + self.src.push_str(&format!("{} = {}[{}]\n", e, vec, i)); + self.src + .push_str(&format!("{} = {} + {} * {}\n", base, result, i, size)); + self.src.push_str(&body); + self.src.deindent(2); + + results.push(result); + results.push(len); + } + + Instruction::ListLift { element, free, .. } => { + let (body, body_results) = self.blocks.pop().unwrap(); + let base = self.payloads.pop().unwrap(); + let size = self.gen.sizes.size(element); + let align = self.gen.sizes.align(element); + let ptr = self.locals.tmp("ptr"); + let len = self.locals.tmp("len"); + self.src.push_str(&format!("{} = {}\n", ptr, operands[0])); + self.src.push_str(&format!("{} = {}\n", len, operands[1])); + let result = self.locals.tmp("result"); + let ty = self.gen.type_string(iface, element); + self.src + .push_str(&format!("{}: List[{}] = []\n", result, ty)); + + let i = self.locals.tmp("i"); + self.src + .push_str(&format!("for {} in range(0, {}):\n", i, len)); + self.src.indent(2); + self.src + .push_str(&format!("{} = {} + {} * {}\n", base, ptr, i, size)); + self.src.push_str(&body); + assert_eq!(body_results.len(), 1); + self.src + .push_str(&format!("{}.append({})\n", result, body_results[0])); + self.src.deindent(2); + + if let Some(free) = free { + self.needs_free = Some(free.to_string()); + self.src + .push_str(&format!("free({}, {} * {}, {})\n", ptr, len, size, align,)); + } + results.push(result); + } + + Instruction::IterElem { .. } => { + let name = self.locals.tmp("e"); + results.push(name.clone()); + self.payloads.push(name); + } + Instruction::IterBasePointer => { + let name = self.locals.tmp("base"); + results.push(name.clone()); + self.payloads.push(name); + } + + Instruction::BufferLiftPtrLen { push, ty } => { + let (block, block_results) = self.blocks.pop().unwrap(); + let base = self.payloads.pop().unwrap(); + self.needs_memory = true; + let ptr = self.locals.tmp("ptr"); + let len = self.locals.tmp("len"); + self.src.push_str(&format!("{} = {}\n", ptr, operands[1])); + self.src.push_str(&format!("{} = {}\n", len, operands[2])); + let size = self.gen.sizes.size(ty); + if *push { + self.gen.needs_push_buffer = true; + assert!(block_results.is_empty()); + let write = self.locals.tmp("write_val"); + self.src + .push_str(&format!("def {}(e, {}): # type: ignore\n", write, base)); + self.src.indent(2); + self.src.push_str(&block); + self.src.deindent(2); + results.push(format!("PushBuffer({}, {}, {}, {})", ptr, len, size, write)); + } else { + self.gen.needs_pull_buffer = true; + assert_eq!(block_results.len(), 1); + let read = self.locals.tmp("read_val"); + self.src + .push_str(&format!("def {}({}): # type: ignore\n", read, base)); + self.src.indent(2); + self.src.push_str(&block); + self.src.push_str(&format!("return {}\n", block_results[0])); + self.src.deindent(2); + results.push(format!("PullBuffer({}, {}, {}, {})", ptr, len, size, read)); + } + } + + // Instruction::BufferLowerHandle { push, ty } => { + // let block = self.blocks.pop().unwrap(); + // let size = self.sizes.size(ty); + // let tmp = self.tmp(); + // let handle = format!("handle{}", tmp); + // let closure = format!("closure{}", tmp); + // self.needs_buffer_transaction = true; + // if iface.all_bits_valid(ty) { + // let method = if *push { "push_out_raw" } else { "push_in_raw" }; + // self.push_str(&format!( + // "let {} = unsafe {{ buffer_transaction.{}({}) }};\n", + // handle, method, operands[0], + // )); + // } else if *push { + // self.closures.push_str(&format!( + // "let {} = |memory: &wasmtime::Memory, base: i32| {{ + // Ok(({}, {})) + // }};\n", + // closure, block, size, + // )); + // self.push_str(&format!( + // "let {} = unsafe {{ buffer_transaction.push_out({}, &{}) }};\n", + // handle, operands[0], closure, + // )); + // } else { + // let start = self.src.len(); + // self.print_ty(iface, ty, TypeMode::AllBorrowed("'_")); + // let ty = self.src[start..].to_string(); + // self.src.truncate(start); + // self.closures.push_str(&format!( + // "let {} = |memory: &wasmtime::Memory, base: i32, e: {}| {{ + // {}; + // Ok({}) + // }};\n", + // closure, ty, block, size, + // )); + // self.push_str(&format!( + // "let {} = unsafe {{ buffer_transaction.push_in({}, &{}) }};\n", + // handle, operands[0], closure, + // )); + // } + // results.push(format!("{}", handle)); + // } + Instruction::CallWasm { + module: _, + name, + sig, + } => { + if sig.results.len() > 0 { + for i in 0..sig.results.len() { + if i > 0 { + self.src.push_str(", "); + } + let ret = self.locals.tmp("ret"); + self.src.push_str(&ret); + results.push(ret); + } + self.src.push_str(" = "); + } + self.src.push_str(&self.src_object); + self.src.push_str("._"); + self.src.push_str(&name.to_snake_case()); + self.src.push_str("("); + self.src.push_str(&operands.join(", ")); + self.src.push_str(")\n"); + for (ty, name) in sig.results.iter().zip(results.iter()) { + let ty = match ty { + WasmType::I32 | WasmType::I64 => "int", + WasmType::F32 | WasmType::F64 => "float", + }; + self.src + .push_str(&format!("assert(isinstance({}, {}))\n", name, ty)); + } + } + Instruction::CallInterface { module: _, func } => { + for i in 0..func.results.len() { + if i > 0 { + self.src.push_str(", "); + } + let result = self.locals.tmp("ret"); + self.src.push_str(&result); + results.push(result); + } + if func.results.len() > 0 { + self.src.push_str(" = "); + } + match &func.kind { + FunctionKind::Freestanding | FunctionKind::Static { .. } => { + self.src.push_str(&format!( + "host.{}({})", + func.name.to_snake_case(), + operands.join(", "), + )); + } + FunctionKind::Method { name, .. } => { + self.src.push_str(&format!( + "{}.{}({})", + operands[0], + name.to_snake_case(), + operands[1..].join(", "), + )); + } + } + self.src.push_str("\n"); + } + + Instruction::Return { amt, .. } => match amt { + 0 => {} + 1 => self.src.push_str(&format!("return {}\n", operands[0])), + _ => { + self.src + .push_str(&format!("return ({})\n", operands.join(", "))); + } + }, + + Instruction::I32Load { offset } => self.load("int32", *offset, operands, results), + Instruction::I64Load { offset } => self.load("int64", *offset, operands, results), + Instruction::F32Load { offset } => self.load("float32", *offset, operands, results), + Instruction::F64Load { offset } => self.load("float64", *offset, operands, results), + Instruction::I32Load8U { offset } => self.load("uint8", *offset, operands, results), + Instruction::I32Load8S { offset } => self.load("int8", *offset, operands, results), + Instruction::I32Load16U { offset } => self.load("uint16", *offset, operands, results), + Instruction::I32Load16S { offset } => self.load("int16", *offset, operands, results), + Instruction::I32Store { offset } => self.store("uint32", *offset, operands), + Instruction::I64Store { offset } => self.store("uint64", *offset, operands), + Instruction::F32Store { offset } => self.store("float32", *offset, operands), + Instruction::F64Store { offset } => self.store("float64", *offset, operands), + Instruction::I32Store8 { offset } => self.store("uint8", *offset, operands), + Instruction::I32Store16 { offset } => self.store("uint16", *offset, operands), + + Instruction::Witx { instr } => match instr { + WitxInstruction::PointerFromI32 { .. } => results.push(operands[0].clone()), + i => unimplemented!("{:?}", i), + }, + i => unimplemented!("{:?}", i), + } + } +} + +#[derive(Default)] +pub struct Source { + s: String, + indent: usize, +} + +impl Source { + pub fn push_str(&mut self, src: &str) { + let lines = src.lines().collect::>(); + let mut trim = None; + for (i, line) in lines.iter().enumerate() { + self.s.push_str(if lines.len() == 1 { + line + } else { + let trim = match trim { + Some(n) => n, + None => { + let val = line.len() - line.trim_start().len(); + if !line.is_empty() { + trim = Some(val); + } + val + } + }; + line.get(trim..).unwrap_or("") + }); + if i != lines.len() - 1 || src.ends_with("\n") { + self.newline(); + } + } + } + + pub fn indent(&mut self, amt: usize) { + self.indent += amt; + for _ in 0..amt { + self.s.push_str(" "); + } + } + + pub fn deindent(&mut self, amt: usize) { + self.indent -= amt; + for _ in 0..amt { + assert!(self.s.ends_with(" ")); + self.s.pop(); + self.s.pop(); + } + } + + fn newline(&mut self) { + self.s.push_str("\n"); + for _ in 0..self.indent { + self.s.push_str(" "); + } + } +} + +impl std::ops::Deref for Source { + type Target = str; + fn deref(&self) -> &str { + &self.s + } +} + +impl From for String { + fn from(s: Source) -> String { + s.s + } +} + +fn wasm_ty_ctor(ty: WasmType) -> &'static str { + match ty { + WasmType::I32 => "wasmer.Type.I32", + WasmType::I64 => "wasmer.Type.I64", + WasmType::F32 => "wasmer.Type.F32", + WasmType::F64 => "wasmer.Type.F64", + } +} + +fn wasm_ty_typing(ty: WasmType) -> &'static str { + match ty { + WasmType::I32 => "int", + WasmType::I64 => "int", + WasmType::F32 => "float", + WasmType::F64 => "float", + } +} + +#[cfg(test)] +mod tests { + use super::Source; + + #[test] + fn simple_append() { + let mut s = Source::default(); + s.push_str("x"); + assert_eq!(s.s, "x"); + s.push_str("y"); + assert_eq!(s.s, "xy"); + s.push_str("z "); + assert_eq!(s.s, "xyz "); + s.push_str(" a "); + assert_eq!(s.s, "xyz a "); + s.push_str("\na"); + assert_eq!(s.s, "xyz a \na"); + } + + #[test] + fn trim_ws() { + let mut s = Source::default(); + s.push_str("def foo():\n return 1\n"); + assert_eq!(s.s, "def foo():\n return 1\n"); + } +} diff --git a/crates/gen-wasmer-py/tests/codegen.rs b/crates/gen-wasmer-py/tests/codegen.rs new file mode 100644 index 000000000..cfd791a6c --- /dev/null +++ b/crates/gen-wasmer-py/tests/codegen.rs @@ -0,0 +1,51 @@ +use std::path::Path; +use std::process::Command; + +mod exports { + test_helpers::codegen_py_export!( + "*.wit" + + // TODO: implement async support + "!async_functions.wit" + ); +} + +mod imports { + test_helpers::codegen_py_import!( + "*.wit" + + // TODO: implement async support + "!async_functions.wit" + + // This uses buffers, which we don't support in imports just yet + // TODO: should support this + "!wasi_next.wit" + "!host.wit" + ); +} + +fn verify(dir: &str, _name: &str) { + let output = Command::new("mypy") + .arg(Path::new(dir).join("bindings.py")) + .arg("--config-file") + .arg("mypy.ini") + .output() + .expect("failed to run `mypy`; do you have it installed?"); + if output.status.success() { + return; + } + panic!( + "mypy failed + +status: {status} + +stdout --- +{stdout} + +stderr --- +{stderr}", + status = output.status, + stdout = String::from_utf8_lossy(&output.stdout).replace("\n", "\n\t"), + stderr = String::from_utf8_lossy(&output.stderr).replace("\n", "\n\t"), + ); +} diff --git a/crates/gen-wasmer-py/tests/runtime.rs b/crates/gen-wasmer-py/tests/runtime.rs new file mode 100644 index 000000000..7855d6a4f --- /dev/null +++ b/crates/gen-wasmer-py/tests/runtime.rs @@ -0,0 +1,75 @@ +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; +use wit_bindgen_gen_core::Generator; + +test_helpers::runtime_tests_wasmer_py!("py"); + +fn execute(name: &str, wasm: &Path, py: &Path, imports: &Path, exports: &Path) { + let out_dir = PathBuf::from(env!("OUT_DIR")); + let dir = out_dir.join(name); + drop(fs::remove_dir_all(&dir)); + fs::create_dir_all(&dir).unwrap(); + fs::create_dir_all(&dir.join("imports")).unwrap(); + fs::create_dir_all(&dir.join("exports")).unwrap(); + + println!("OUT_DIR = {:?}", dir); + println!("Generating bindings..."); + // We call `generate_all` with exports from the imports.wit file, and + // imports from the exports.wit wit file. It's reversed because we're + // implementing the host side of these APIs. + let iface = wit_bindgen_gen_core::wit_parser::Interface::parse_file(imports).unwrap(); + let mut files = Default::default(); + wit_bindgen_gen_wasmer_py::Opts::default() + .build() + .generate_all(&[], &[iface], &mut files); + for (file, contents) in files.iter() { + fs::write(dir.join("imports").join(file), contents).unwrap(); + } + fs::write(dir.join("imports").join("__init__.py"), "").unwrap(); + + let iface = wit_bindgen_gen_core::wit_parser::Interface::parse_file(exports).unwrap(); + let mut files = Default::default(); + wit_bindgen_gen_wasmer_py::Opts::default() + .build() + .generate_all(&[iface], &[], &mut files); + for (file, contents) in files.iter() { + fs::write(dir.join("exports").join(file), contents).unwrap(); + } + fs::write(dir.join("exports").join("__init__.py"), "").unwrap(); + + println!("Running mypy..."); + exec( + Command::new("mypy") + .env("MYPYPATH", &dir) + .arg(py) + .arg("--cache-dir") + .arg(out_dir.join("mypycache").join(name)), + ); + + exec( + Command::new("python3") + .env("PYTHONPATH", &dir) + .arg(py) + .arg(wasm), + ); +} + +fn exec(cmd: &mut Command) { + println!("{:?}", cmd); + let output = cmd.output().unwrap(); + if output.status.success() { + return; + } + println!("status: {}", output.status); + println!( + "stdout ---\n {}", + String::from_utf8_lossy(&output.stdout).replace("\n", "\n ") + ); + println!( + "stderr ---\n {}", + String::from_utf8_lossy(&output.stderr).replace("\n", "\n ") + ); + panic!("no success"); +} diff --git a/crates/test-helpers/Cargo.toml b/crates/test-helpers/Cargo.toml index 3bc4b418a..f0a9c2b35 100644 --- a/crates/test-helpers/Cargo.toml +++ b/crates/test-helpers/Cargo.toml @@ -24,6 +24,7 @@ wit-bindgen-gen-js = { path = '../gen-js', optional = true } wit-bindgen-gen-c = { path = '../gen-c', optional = true } wit-bindgen-gen-spidermonkey = { path = '../gen-spidermonkey', optional = true } wit-bindgen-gen-wasmer = { path = '../gen-wasmer', optional = true } +wit-bindgen-gen-wasmer-py = { path = '../gen-wasmer-py', optional = true } wit-parser = { path = '../parser', features = ['witx-compat'] } filetime = "0.2" diff --git a/crates/test-helpers/src/lib.rs b/crates/test-helpers/src/lib.rs index eb917d2db..36dd46115 100644 --- a/crates/test-helpers/src/lib.rs +++ b/crates/test-helpers/src/lib.rs @@ -393,6 +393,22 @@ pub fn codegen_wasmer_export(input: TokenStream) -> TokenStream { ) } +#[proc_macro] +#[cfg(feature = "wit-bindgen-gen-wasmer-py")] +pub fn codegen_py_export(input: TokenStream) -> TokenStream { + gen_verify(input, Direction::Export, "export", || { + wit_bindgen_gen_wasmer_py::Opts::default().build() + }) +} + +#[proc_macro] +#[cfg(feature = "wit-bindgen-gen-wasmer-py")] +pub fn codegen_py_import(input: TokenStream) -> TokenStream { + gen_verify(input, Direction::Import, "import", || { + wit_bindgen_gen_wasmer_py::Opts::default().build() + }) +} + fn generate_tests( input: TokenStream, dir: &str, @@ -584,6 +600,44 @@ pub fn runtime_tests(input: TokenStream) -> TokenStream { (quote::quote!(#(#tests)*)).into() } +#[proc_macro] +#[cfg(feature = "wit-bindgen-gen-wasmer-py")] +pub fn runtime_tests_wasmer_py(_input: TokenStream) -> TokenStream { + let mut tests = Vec::new(); + let cwd = std::env::current_dir().unwrap(); + for entry in std::fs::read_dir(cwd.join("tests/runtime")).unwrap() { + let entry = entry.unwrap().path(); + if !entry.join("host-wasmer.py").exists() { + continue; + } + let name_str = entry.file_name().unwrap().to_str().unwrap(); + for (lang, name, wasm) in WASMS { + if *name != name_str { + continue; + } + let name_str = format!("{}_{}", name_str, lang); + let name = quote::format_ident!("{}", name_str); + let host_file = entry.join("host-wasmer.py").to_str().unwrap().to_string(); + let import_wit = entry.join("imports.wit").to_str().unwrap().to_string(); + let export_wit = entry.join("exports.wit").to_str().unwrap().to_string(); + tests.push(quote::quote! { + #[test] + fn #name() { + crate::execute( + #name_str, + #wasm.as_ref(), + #host_file.as_ref(), + #import_wit.as_ref(), + #export_wit.as_ref(), + ) + } + }); + } + } + + (quote::quote!(#(#tests)*)).into() +} + #[proc_macro] #[cfg(feature = "wit-bindgen-gen-wasmtime")] pub fn runtime_tests_wasmtime(_input: TokenStream) -> TokenStream { diff --git a/crates/wit-bindgen-demo/Cargo.toml b/crates/wit-bindgen-demo/Cargo.toml index cd88bcd6d..e89a32a00 100644 --- a/crates/wit-bindgen-demo/Cargo.toml +++ b/crates/wit-bindgen-demo/Cargo.toml @@ -20,5 +20,6 @@ wit-bindgen-gen-c = { path = '../gen-c' } wit-bindgen-gen-markdown = { path = '../gen-markdown' } wit-bindgen-gen-spidermonkey = { path = '../gen-spidermonkey' } wit-bindgen-gen-wasmer = { path = '../gen-wasmer' } +wit-bindgen-gen-wasmer-py = { path = '../gen-wasmer-py' } wit-bindgen-rust = { path = '../rust-wasm' } wasmprinter = "0.2.29" diff --git a/crates/wit-bindgen-demo/demo.wit b/crates/wit-bindgen-demo/demo.wit index a5191eefa..e2792ec22 100644 --- a/crates/wit-bindgen-demo/demo.wit +++ b/crates/wit-bindgen-demo/demo.wit @@ -15,6 +15,7 @@ enum lang { markdown, spidermonkey, wasmer, + wasmer_py, } diff --git a/crates/wit-bindgen-demo/index.html b/crates/wit-bindgen-demo/index.html index 3c9e994c5..91f992a08 100644 --- a/crates/wit-bindgen-demo/index.html +++ b/crates/wit-bindgen-demo/index.html @@ -57,6 +57,7 @@

Generated bindings

+ · @@ -122,6 +123,8 @@

Generated bindings

+
+
diff --git a/crates/wit-bindgen-demo/main.ts b/crates/wit-bindgen-demo/main.ts index 379c168c4..cdb720255 100644 --- a/crates/wit-bindgen-demo/main.ts +++ b/crates/wit-bindgen-demo/main.ts @@ -136,6 +136,7 @@ class Editor { case "markdown": lang = Lang.Markdown; break; case "spidermonkey": lang = Lang.Spidermonkey; break; case "wasmer": lang = Lang.Wasmer; break; + case "wasmer-py": lang = Lang.WasmerPy; break; default: return; } const result = this.config.render(lang, wit, is_import); diff --git a/crates/wit-bindgen-demo/src/lib.rs b/crates/wit-bindgen-demo/src/lib.rs index 2b77f0cff..f1827ecae 100644 --- a/crates/wit-bindgen-demo/src/lib.rs +++ b/crates/wit-bindgen-demo/src/lib.rs @@ -21,6 +21,7 @@ pub struct Config { markdown: RefCell, spidermonkey: RefCell, wasmer: RefCell, + wasmer_py: RefCell, } impl demo::Config for Config { @@ -58,6 +59,7 @@ impl demo::Config for Config { Box::new(opts.clone().build(script)) } demo::Lang::Wasmer => Box::new(self.wasmer.borrow().clone().build()), + demo::Lang::WasmerPy => Box::new(self.wasmer_py.borrow().clone().build()), }; let iface = Interface::parse("input", &wit).map_err(|e| format!("{:?}", e))?; let mut files = Default::default(); diff --git a/src/bin/wit-bindgen.rs b/src/bin/wit-bindgen.rs index fcb08f32f..cc347613f 100644 --- a/src/bin/wit-bindgen.rs +++ b/src/bin/wit-bindgen.rs @@ -61,6 +61,12 @@ enum Command { #[structopt(flatten)] common: Common, }, + WasmerPy { + #[structopt(flatten)] + opts: wit_bindgen_gen_wasmer_py::Opts, + #[structopt(flatten)] + common: Common, + }, } #[derive(Debug, StructOpt)] @@ -95,6 +101,7 @@ fn main() -> Result<()> { (Box::new(opts.build(js_source)), common) } Command::Wasmer { opts, common } => (Box::new(opts.build()), common), + Command::WasmerPy { opts, common } => (Box::new(opts.build()), common), }; let imports = common diff --git a/tests/runtime/buffers/host-wasmer.py b/tests/runtime/buffers/host-wasmer.py new file mode 100644 index 000000000..405de22bf --- /dev/null +++ b/tests/runtime/buffers/host-wasmer.py @@ -0,0 +1,95 @@ +from exports.bindings import Exports +from imports.bindings import add_imports_to_imports, Imports +from typing import Tuple, List, Any +import exports.bindings as e +import imports.bindings as i +import sys +import wasmer # type: ignore + +class MyImports: + def buffer_u8(self, a: i.PullBuffer[int], b: i.PushBuffer[int]) -> int: + assert(len(a) == 1) + assert(len(b) == 10) + assert(a.pull() == 0) + assert(a.pull() == None) + b.push(1) + b.push(2) + b.push(3) + return 3 + + def buffer_u32(self, a: i.PullBuffer[int], b: i.PushBuffer[int]) -> int: + assert(len(a) == 1) + assert(len(b) == 10) + assert(a.pull() == 0) + assert(a.pull() == None) + b.push(1) + b.push(2) + b.push(3) + return 3 + + def buffer_bool(self, a: i.PullBuffer[bool], b: i.PushBuffer[bool]) -> int: + assert(len(a) <= len(b)) + n = 0 + while True: + val = a.pull() + if val is None: + break + b.push(not val) + n += 1 + return n + + def buffer_mutable1(self, x: List[i.PullBuffer[bool]]) -> None: + assert(len(x) == 1) + assert(len(x[0]) == 5) + assert(x[0].pull() == True) + assert(x[0].pull() == False) + assert(x[0].pull() == True) + assert(x[0].pull() == True) + assert(x[0].pull() == False) + assert(x[0].pull() == None) + + def buffer_mutable2(self, a: List[i.PushBuffer[int]]) -> int: + assert(len(a) == 1) + assert(len(a[0]) > 4) + a[0].push(1) + a[0].push(2) + a[0].push(3) + a[0].push(4) + return 4 + + def buffer_mutable3(self, a: List[i.PushBuffer[bool]]) -> int: + assert(len(a) == 1) + assert(len(a[0]) > 3) + a[0].push(False) + a[0].push(True) + a[0].push(False) + return 3 + + def buffer_in_record(self, a: i.BufferInRecord) -> None: + pass + + def buffer_typedef(self, a: i.ParamInBufferU8, b: i.ParamOutBufferU8, c: i.ParamInBufferBool, d: i.ParamOutBufferBool) -> None: + pass + +def run(wasm_file: str) -> None: + store = wasmer.Store() + module = wasmer.Module(store, open(wasm_file, 'rb').read()) + wasi_version = wasmer.wasi.get_version(module, strict=False) + if wasi_version is None: + import_object = {} + else: + wasi_env = wasmer.wasi.StateBuilder('test').finalize() + import_object = wasi_env.generate_imports(store, wasi_version) + + wasm: Exports + def get_export(name: str) -> Any: + return wasm.instance.exports.__getattribute__(name) + + imports = MyImports() + add_imports_to_imports(store, import_object, imports, get_export) + wasm = Exports(store, import_object, module) + + wasm.test_imports() + +if __name__ == '__main__': + run(sys.argv[1]) diff --git a/tests/runtime/flavorful/host-wasmer.py b/tests/runtime/flavorful/host-wasmer.py new file mode 100644 index 000000000..21d11076f --- /dev/null +++ b/tests/runtime/flavorful/host-wasmer.py @@ -0,0 +1,90 @@ +from exports.bindings import Exports +from imports.bindings import add_imports_to_imports, Imports +from typing import Tuple, List, Any +import exports.bindings as e +import imports.bindings as i +import sys +import wasmer # type: ignore + +class MyImports: + def list_in_record1(self, a: i.ListInRecord1) -> None: + pass + + def list_in_record2(self) -> i.ListInRecord2: + return i.ListInRecord2('list_in_record2') + + def list_in_record3(self, a: i.ListInRecord3) -> i.ListInRecord3: + assert(a.a == 'list_in_record3 input') + return i.ListInRecord3('list_in_record3 output') + + def list_in_record4(self, a: i.ListInAlias) -> i.ListInAlias: + assert(a.a == 'input4') + return i.ListInRecord4('result4') + + def list_in_variant1(self, a: i.ListInVariant11, b: i.ListInVariant12, c: i.ListInVariant13) -> None: + assert(a == 'foo') + assert(b == i.Err('bar')) + assert(c == i.ListInVariant130('baz')) + + def list_in_variant2(self) -> i.ListInVariant2: + return 'list_in_variant2' + + def list_in_variant3(self, a: i.ListInVariant3) -> i.ListInVariant3: + assert(a == 'input3') + return 'output3' + + def errno_result(self) -> i.Expected[None, i.MyErrno]: + return i.Err(i.MyErrno.B) + + def list_typedefs(self, a: i.ListTypedef, c: i.ListTypedef3) -> Tuple[i.ListTypedef2, i.ListTypedef3]: + assert(a == 'typedef1') + assert(c == ['typedef2']) + return (b'typedef3', ['typedef4']) + + def list_of_variants(self, a: List[bool], b: List[i.Expected[None, None]], c: List[i.MyErrno]) -> Tuple[List[bool], List[i.Expected[None, None]], List[i.MyErrno]]: + assert(a == [True, False]) + assert(b == [i.Ok(None), i.Err(None)]) + assert(c == [i.MyErrno.SUCCESS, i.MyErrno.A]) + return ( + [False, True], + [i.Err(None), i.Ok(None)], + [i.MyErrno.A, i.MyErrno.B], + ) + +def run(wasm_file: str) -> None: + store = wasmer.Store() + module = wasmer.Module(store, open(wasm_file, 'rb').read()) + wasi_version = wasmer.wasi.get_version(module, strict=False) + if wasi_version is None: + import_object = {} + else: + wasi_env = wasmer.wasi.StateBuilder('test').finalize() + import_object = wasi_env.generate_imports(store, wasi_version) + + wasm: Exports + def get_export(name: str) -> Any: + return wasm.instance.exports.__getattribute__(name) + + imports = MyImports() + add_imports_to_imports(store, import_object, imports, get_export) + wasm = Exports(store, import_object, module) + + wasm.test_imports() + wasm.list_in_record1(e.ListInRecord1("list_in_record1")) + assert(wasm.list_in_record2() == e.ListInRecord2(a="list_in_record2")) + + assert(wasm.list_in_record3(e.ListInRecord3("list_in_record3 input")).a == "list_in_record3 output") + assert(wasm.list_in_record4(e.ListInRecord4("input4")).a == "result4") + + wasm.list_in_variant1("foo", e.Err("bar"), e.ListInVariant130('baz')) + assert(wasm.list_in_variant2() == "list_in_variant2") + assert(wasm.list_in_variant3("input3") == "output3") + + assert(isinstance(wasm.errno_result(), e.Err)) + + r1, r2 = wasm.list_typedefs("typedef1", ["typedef2"]) + assert(r1 == b'typedef3') + assert(r2 == ['typedef4']) + +if __name__ == '__main__': + run(sys.argv[1]) diff --git a/tests/runtime/handles/host-wasmer.py b/tests/runtime/handles/host-wasmer.py new file mode 100644 index 000000000..077ed7004 --- /dev/null +++ b/tests/runtime/handles/host-wasmer.py @@ -0,0 +1,184 @@ +from dataclasses import dataclass +from exports.bindings import Exports +from imports.bindings import add_imports_to_imports, Imports +from typing import Tuple, List, Any +import exports.bindings as e +import imports.bindings as i +import sys +import wasmer # type: ignore + +@dataclass +class HostState(i.HostState): + val: int + + def __init__(self, val: int) -> None: + self.val = val + + +HOST_STATE2_CLOSED = False + + +@dataclass +class HostState2(i.HostState2): + val: int + + def __init__(self, val: int) -> None: + self.val = val + + def drop(self) -> None: + global HOST_STATE2_CLOSED + HOST_STATE2_CLOSED = True + + +@dataclass +class Markdown(i.Markdown2): + buf: str = '' + + def append(self, data: str) -> None: + self.buf += data + + def render(self) -> str: + return self.buf.replace('red', 'green') + + +class OddName(i.OddName): + def frob_the_odd(self) -> None: + pass + + +class MyImports: + def host_state_create(self) -> i.HostState: + return HostState(100) + + def host_state_get(self, a: i.HostState) -> int: + assert(isinstance(a, HostState)) + return a.val + + def host_state2_create(self) -> i.HostState2: + return HostState2(101) + + def host_state2_saw_close(self) -> bool: + return HOST_STATE2_CLOSED + + def two_host_states(self, a: i.HostState, b: i.HostState2) -> Tuple[i.HostState, i.HostState2]: + return (b, a) + + def host_state2_param_record(self, a: i.HostStateParamRecord) -> None: + pass + + def host_state2_param_tuple(self, a: i.HostStateParamTuple) -> None: + pass + + def host_state2_param_option(self, a: i.HostStateParamOption) -> None: + pass + + def host_state2_param_result(self, a: i.HostStateParamResult) -> None: + pass + + def host_state2_param_variant(self, a: i.HostStateParamVariant) -> None: + pass + + def host_state2_param_list(self, a: List[i.HostState2]) -> None: + pass + + def host_state2_result_record(self) -> i.HostStateResultRecord: + return i.HostStateResultRecord(HostState(2)) + + def host_state2_result_tuple(self) -> i.HostStateResultTuple: + return (HostState(2),) + + def host_state2_result_option(self) -> i.HostStateResultOption: + return HostState(2) + + def host_state2_result_result(self) -> i.HostStateResultResult: + return i.Ok(HostState2(2)) + + def host_state2_result_variant(self) -> i.HostStateResultVariant: + return i.HostStateResultVariant0(HostState2(2)) + + def host_state2_result_list(self) -> List[i.HostState2]: + return [HostState2(2), HostState2(5)] + + def markdown2_create(self) -> i.Markdown2: + return Markdown() + + def odd_name_create(self) -> i.OddName: + return OddName() + +def run(wasm_file: str) -> None: + store = wasmer.Store() + module = wasmer.Module(store, open(wasm_file, 'rb').read()) + wasi_version = wasmer.wasi.get_version(module, strict=False) + if wasi_version is None: + import_object = {} + else: + wasi_env = wasmer.wasi.StateBuilder('test').finalize() + import_object = wasi_env.generate_imports(store, wasi_version) + + wasm: Exports + def get_export(name: str) -> Any: + return wasm.instance.exports.__getattribute__(name) + + imports = MyImports() + add_imports_to_imports(store, import_object, imports, get_export) + wasm = Exports(store, import_object, module) + + wasm.test_imports() + + # Param/result of a handle works in a simple fashion + s: e.WasmState = wasm.wasm_state_create() + assert(wasm.wasm_state_get_val(s) == 100) + + # Deterministic destruction is possible + assert(wasm.wasm_state2_saw_close() == False) + s2: e.WasmState2 = wasm.wasm_state2_create() + assert(wasm.wasm_state2_saw_close() == False) + s2.drop() + assert(wasm.wasm_state2_saw_close() == True) + + arg1 = wasm.wasm_state_create() + arg2 = wasm.wasm_state2_create() + c, d = wasm.two_wasm_states(arg1, arg2) + arg1.drop() + arg2.drop() + + wasm.wasm_state2_param_record(e.WasmStateParamRecord(d)) + wasm.wasm_state2_param_tuple((d,)) + wasm.wasm_state2_param_option(d) + wasm.wasm_state2_param_option(None) + wasm.wasm_state2_param_result(e.Ok(d)) + wasm.wasm_state2_param_result(e.Err(2)) + wasm.wasm_state2_param_variant(e.WasmStateParamVariant0(d)) + wasm.wasm_state2_param_variant(e.WasmStateParamVariant1(2)) + wasm.wasm_state2_param_list([]) + wasm.wasm_state2_param_list([d]) + wasm.wasm_state2_param_list([d, d]) + + c.drop() + d.drop() + + wasm.wasm_state2_result_record().a.drop() + wasm.wasm_state2_result_tuple()[0].drop() + opt = wasm.wasm_state2_result_option() + assert(opt is not None) + opt.drop() + result = wasm.wasm_state2_result_result() + assert(isinstance(result, e.Ok)) + result.value.drop() + variant = wasm.wasm_state2_result_variant() + print(variant) + assert(isinstance(variant, e.WasmStateResultVariant0)) + variant.value.drop() + for val in wasm.wasm_state2_result_list(): + val.drop() + + s.drop() + + md = e.Markdown.create(wasm) + if md: + md.append("red is the best color") + assert(md.render() == "green is the best color") + md.drop() + +if __name__ == '__main__': + run(sys.argv[1]) diff --git a/tests/runtime/invalid/host-wasmer.py b/tests/runtime/invalid/host-wasmer.py new file mode 100644 index 000000000..a0d816948 --- /dev/null +++ b/tests/runtime/invalid/host-wasmer.py @@ -0,0 +1,78 @@ +from exports.bindings import Exports +from imports.bindings import add_imports_to_imports, Imports +from typing import Callable, Any +import imports.bindings as i +import sys +import wasmer # type: ignore + +class MyImports(Imports): + def roundtrip_u8(self, x: int) -> int: + raise Exception('unreachable') + + def roundtrip_s8(self, x: int) -> int: + raise Exception('unreachable') + + def roundtrip_u16(self, x: int) -> int: + raise Exception('unreachable') + + def roundtrip_s16(self, x: int) -> int: + raise Exception('unreachable') + + def roundtrip_bool(self, x: bool) -> bool: + raise Exception('unreachable') + + def roundtrip_char(self, x: str) -> str: + raise Exception('unreachable') + + def roundtrip_enum(self, x: i.E) -> i.E: + raise Exception('unreachable') + + def get_internal(self, x: i.HostState) -> int: + raise Exception('unreachable') + +def run(wasm_file: str) -> None: + store = wasmer.Store() + module = wasmer.Module(store, open(wasm_file, 'rb').read()) + wasi_version = wasmer.wasi.get_version(module, strict=False) + if wasi_version is None: + import_object = {} + else: + wasi_env = wasmer.wasi.StateBuilder('test').finalize() + import_object = wasi_env.generate_imports(store, wasi_version) + + wasm: Exports + def get_export(name: str) -> Any: + return wasm.instance.exports.__getattribute__(name) + + imports = MyImports() + add_imports_to_imports(store, import_object, imports, get_export) + wasm = Exports(store, import_object, module) + + def assert_throws(f: Callable, msg: str) -> None: + try: + f() + raise RuntimeError('expected exception') + except TypeError as e: + actual = str(e) + except OverflowError as e: + actual = str(e) + except ValueError as e: + actual = str(e) + except IndexError as e: + actual = str(e) + if not msg in actual: + print(actual) + assert(msg in actual) + + assert_throws(lambda: wasm.invalid_bool(), 'invalid variant discriminant for bool') + assert_throws(lambda: wasm.invalid_u8(), 'must be between') + assert_throws(lambda: wasm.invalid_s8(), 'must be between') + assert_throws(lambda: wasm.invalid_u16(), 'must be between') + assert_throws(lambda: wasm.invalid_s16(), 'must be between') + assert_throws(lambda: wasm.invalid_char(), 'not a valid char') + assert_throws(lambda: wasm.invalid_enum(), 'not a valid E') + assert_throws(lambda: wasm.invalid_handle(), 'handle index not valid') + assert_throws(lambda: wasm.invalid_handle_close(), 'handle index not valid') + +if __name__ == '__main__': + run(sys.argv[1]) diff --git a/tests/runtime/lists/host-wasmer.py b/tests/runtime/lists/host-wasmer.py new file mode 100644 index 000000000..92a16d992 --- /dev/null +++ b/tests/runtime/lists/host-wasmer.py @@ -0,0 +1,114 @@ +from exports.bindings import Exports +from imports.bindings import add_imports_to_imports, Imports +from typing import Tuple, List, Any +import exports.bindings as e +import imports.bindings as i +import sys +import wasmer # type: ignore + +class MyImports: + def list_param(self, a: bytes) -> None: + assert(a == b'\x01\x02\x03\x04') + + def list_param2(self, a: str) -> None: + assert(a == 'foo') + + def list_param3(self, a: List[str]) -> None: + assert(a == ['foo', 'bar', 'baz']) + + def list_param4(self, a: List[List[str]]) -> None: + assert(a == [['foo', 'bar'], ['baz']]) + + def list_result(self) -> bytes: + return b'\x01\x02\x03\x04\x05' + + def list_result2(self) -> str: + return 'hello!' + + def list_result3(self) -> List[str]: + return ['hello,', 'world!'] + + def string_roundtrip(self, a: str) -> str: + return a + + def unaligned_roundtrip1(self, a: List[int], b: List[int], c: List[int], d: List[i.Flag32], e: List[i.Flag64]) -> None: + # Wasmer's python API doesn't support unaligned memory views + pass + #assert(a == [1]) + #assert(b == [2]) + #assert(c == [3]) + #assert(d == [i.Flag32.B8]) + #assert(e == [i.Flag64.B9]) + + def unaligned_roundtrip2(self, a: List[i.UnalignedRecord], b: List[float], c: List[float], d: List[str], e: List[bytes]) -> None: + # Wasmer's python API doesn't support unaligned memory views + pass + #assert(a == [i.UnalignedRecord(a=10, b=11)]) + #assert(b == [100.0]) + #assert(c == [101.0]) + #assert(d == ['foo']) + #assert(e == [b'\x66']) + + def list_minmax8(self, a: bytes, b: List[int]) -> Tuple[bytes, List[int]]: + assert(a == b'\x00\xff') + assert(b == [-(1 << (8 - 1)), (1 << (8 - 1)) - 1]) + return (a, b) + + def list_minmax16(self, a: List[int], b: List[int]) -> Tuple[List[int], List[int]]: + assert(a == [0, (1 << 16) - 1]) + assert(b == [-(1 << (16 - 1)), (1 << (16 - 1)) - 1]) + return (a, b) + + def list_minmax32(self, a: List[int], b: List[int]) -> Tuple[List[int], List[int]]: + assert(a == [0, (1 << 32) - 1]) + assert(b == [-(1 << (32 - 1)), (1 << (32 - 1)) - 1]) + return (a, b) + + def list_minmax64(self, a: List[int], b: List[int]) -> Tuple[List[int], List[int]]: + assert(a == [0, (1 << 64) - 1]) + assert(b == [-(1 << (64 - 1)), (1 << (64 - 1)) - 1]) + return (a, b) + + def list_minmax_float(self, a: List[float], b: List[float]) -> Tuple[List[float], List[float]]: + assert(a == [-3.4028234663852886e+38, 3.4028234663852886e+38, -float('inf'), float('inf')]) + assert(b == [-sys.float_info.max, sys.float_info.max, -float('inf'), float('inf')]) + return (a, b) + +def run(wasm_file: str) -> None: + store = wasmer.Store() + module = wasmer.Module(store, open(wasm_file, 'rb').read()) + wasi_version = wasmer.wasi.get_version(module, strict=False) + if wasi_version is None: + import_object = {} + else: + wasi_env = wasmer.wasi.StateBuilder('test').finalize() + import_object = wasi_env.generate_imports(store, wasi_version) + + wasm: Exports + def get_export(name: str) -> Any: + return wasm.instance.exports.__getattribute__(name) + + imports = MyImports() + add_imports_to_imports(store, import_object, imports, get_export) + wasm = Exports(store, import_object, module) + + allocated_bytes = wasm.allocated_bytes() + wasm.test_imports() + wasm.list_param(b'\x01\x02\x03\x04') + wasm.list_param2("foo") + wasm.list_param3(["foo", "bar", "baz"]) + wasm.list_param4([["foo", "bar"], ["baz"]]) + assert(wasm.list_result() == b'\x01\x02\x03\x04\x05') + assert(wasm.list_result2() == "hello!") + assert(wasm.list_result3() == ["hello,", "world!"]) + + assert(wasm.string_roundtrip("x") == "x") + assert(wasm.string_roundtrip("") == "") + assert(wasm.string_roundtrip("hello ⚑ world") == "hello ⚑ world") + + # Ensure that we properly called `free` everywhere in all the glue that we + # needed to. + assert(allocated_bytes == wasm.allocated_bytes()) + +if __name__ == '__main__': + run(sys.argv[1]) diff --git a/tests/runtime/numbers/host-wasmer.py b/tests/runtime/numbers/host-wasmer.py new file mode 100644 index 000000000..2be393cda --- /dev/null +++ b/tests/runtime/numbers/host-wasmer.py @@ -0,0 +1,111 @@ +from exports.bindings import Exports +from imports.bindings import add_imports_to_imports, Imports +from typing import Any +import math; +import sys +import wasmer # type: ignore + +class MyImports: + def roundtrip_u8(self, a: int) -> int: + return a + + def roundtrip_s8(self, a: int) -> int: + return a + + def roundtrip_u16(self, a: int) -> int: + return a + + def roundtrip_s16(self, a: int) -> int: + return a + + def roundtrip_u32(self, a: int) -> int: + return a + + def roundtrip_s32(self, a: int) -> int: + return a + + def roundtrip_u64(self, a: int) -> int: + return a + + def roundtrip_s64(self, a: int) -> int: + return a + + def roundtrip_f32(self, a: float) -> float: + return a + + def roundtrip_f64(self, a: float) -> float: + return a + + def roundtrip_char(self, a: str) -> str: + return a + + def set_scalar(self, a: int) -> None: + self.scalar = a + + def get_scalar(self) -> int: + return self.scalar + + +def run(wasm_file: str) -> None: + store = wasmer.Store() + module = wasmer.Module(store, open(wasm_file, 'rb').read()) + wasi_version = wasmer.wasi.get_version(module, strict=False) + if wasi_version is None: + import_object = {} + else: + wasi_env = wasmer.wasi.StateBuilder('test').finalize() + import_object = wasi_env.generate_imports(store, wasi_version) + + wasm: Exports + def get_export(name: str) -> Any: + return wasm.instance.exports.__getattribute__(name) + + imports = MyImports() + add_imports_to_imports(store, import_object, imports, get_export) + wasm = Exports(store, import_object, module) + + wasm.test_imports() + assert(wasm.roundtrip_u8(1) == 1) + assert(wasm.roundtrip_u8((1 << 8) - 1) == (1 << 8) - 1) + assert(wasm.roundtrip_u16(1) == 1) + assert(wasm.roundtrip_u16((1 << 16) - 1) == (1 << 16) - 1) + assert(wasm.roundtrip_u32(1) == 1) + assert(wasm.roundtrip_u32((1 << 32) - 1) == (1 << 32) - 1) + assert(wasm.roundtrip_u64(1) == 1) + assert(wasm.roundtrip_u64((1 << 64) - 1) == (1 << 64) - 1) + + assert(wasm.roundtrip_s8(1) == 1) + assert(wasm.roundtrip_s8((1 << (8 - 1) - 1)) == (1 << (8 - 1) - 1)) + assert(wasm.roundtrip_s8(-(1 << (8 - 1))) == -(1 << (8 - 1))) + assert(wasm.roundtrip_s16(1) == 1) + assert(wasm.roundtrip_s16((1 << (16 - 1) - 1)) == (1 << (16 - 1) - 1)) + assert(wasm.roundtrip_s16(-(1 << (16 - 1))) == -(1 << (16 - 1))) + assert(wasm.roundtrip_s32(1) == 1) + assert(wasm.roundtrip_s32((1 << (32 - 1) - 1)) == (1 << (32 - 1) - 1)) + assert(wasm.roundtrip_s32(-(1 << (32 - 1))) == -(1 << (32 - 1))) + assert(wasm.roundtrip_s64(1) == 1) + assert(wasm.roundtrip_s64((1 << (64 - 1) - 1)) == (1 << (64 - 1) - 1)) + assert(wasm.roundtrip_s64(-(1 << (64 - 1))) == -(1 << (64 - 1))) + + inf = float('inf') + assert(wasm.roundtrip_f32(1.0) == 1.0) + assert(wasm.roundtrip_f32(inf) == inf) + assert(wasm.roundtrip_f32(-inf) == -inf) + assert(math.isnan(wasm.roundtrip_f32(float('nan')))) + + assert(wasm.roundtrip_f64(1.0) == 1.0) + assert(wasm.roundtrip_f64(inf) == inf) + assert(wasm.roundtrip_f64(-inf) == -inf) + assert(math.isnan(wasm.roundtrip_f64(float('nan')))) + + assert(wasm.roundtrip_char('a') == 'a') + assert(wasm.roundtrip_char(' ') == ' ') + assert(wasm.roundtrip_char('🚩') == '🚩') + + wasm.set_scalar(2) + assert(wasm.get_scalar() == 2) + wasm.set_scalar(4) + assert(wasm.get_scalar() == 4) + +if __name__ == '__main__': + run(sys.argv[1]) diff --git a/tests/runtime/records/host-wasmer.py b/tests/runtime/records/host-wasmer.py new file mode 100644 index 000000000..a6fbf20f3 --- /dev/null +++ b/tests/runtime/records/host-wasmer.py @@ -0,0 +1,76 @@ +from exports.bindings import Exports +from imports.bindings import add_imports_to_imports, Imports +from typing import Tuple, Any +import exports.bindings as e +import imports.bindings as i +import sys +import wasmer # type: ignore + +class MyImports: + def multiple_results(self) -> Tuple[int, int]: + return (4, 5) + + def swap_tuple(self, a: Tuple[int, int]) -> Tuple[int, int]: + return (a[1], a[0]) + + def roundtrip_flags1(self, a: i.F1) -> i.F1: + return a + + def roundtrip_flags2(self, a: i.F2) -> i.F2: + return a + + def roundtrip_flags3(self, a: i.Flag8, b: i.Flag16, c: i.Flag32, d: i.Flag64) -> Tuple[i.Flag8, i.Flag16, i.Flag32, i.Flag64]: + return (a, b, c, d) + + def roundtrip_record1(self, a: i.R1) -> i.R1: + return a + + def tuple0(self, a: None) -> None: + pass + + def tuple1(self, a: Tuple[int]) -> Tuple[int]: + return (a[0],) + +def run(wasm_file: str) -> None: + store = wasmer.Store() + module = wasmer.Module(store, open(wasm_file, 'rb').read()) + wasi_version = wasmer.wasi.get_version(module, strict=False) + if wasi_version is None: + import_object = {} + else: + wasi_env = wasmer.wasi.StateBuilder('test').finalize() + import_object = wasi_env.generate_imports(store, wasi_version) + + wasm: Exports + def get_export(name: str) -> Any: + return wasm.instance.exports.__getattribute__(name) + + imports = MyImports() + add_imports_to_imports(store, import_object, imports, get_export) + wasm = Exports(store, import_object, module) + + wasm.test_imports() + assert(wasm.multiple_results() == (100, 200)) + assert(wasm.swap_tuple((1, 2)) == (2, 1)) + assert(wasm.roundtrip_flags1(e.F1.A) == e.F1.A) + assert(wasm.roundtrip_flags1(e.F1(0)) == e.F1(0)) + assert(wasm.roundtrip_flags1(e.F1.A | e.F1.B) == (e.F1.A | e.F1.B)) + + assert(wasm.roundtrip_flags2(e.F2.C) == e.F2.C) + assert(wasm.roundtrip_flags2(e.F2(0)) == e.F2(0)) + assert(wasm.roundtrip_flags2(e.F2.D) == e.F2.D) + assert(wasm.roundtrip_flags2(e.F2.C | e.F2.E) == (e.F2.C | e.F2.E)) + + r = wasm.roundtrip_record1(e.R1(8, e.F1(0))) + assert(r.a == 8) + assert(r.b == e.F1(0)) + + r = wasm.roundtrip_record1(e.R1(a=0, b=e.F1.A | e.F1.B)) + assert(r.a == 0) + assert(r.b == (e.F1.A | e.F1.B)) + + wasm.tuple0(None) + assert(wasm.tuple1((1,)) == (1,)) + +if __name__ == '__main__': + run(sys.argv[1]) diff --git a/tests/runtime/smoke/host-wasmer.py b/tests/runtime/smoke/host-wasmer.py new file mode 100644 index 000000000..b64da7aa0 --- /dev/null +++ b/tests/runtime/smoke/host-wasmer.py @@ -0,0 +1,32 @@ +from exports.bindings import Exports +from imports.bindings import add_imports_to_imports, Imports +from typing import Any +import sys +import wasmer # type: ignore + +class MyImports: + def thunk(self) -> None: + self.hit = True + +def run(wasm_file: str) -> None: + store = wasmer.Store() + module = wasmer.Module(store, open(wasm_file, 'rb').read()) + wasi_version = wasmer.wasi.get_version(module, strict=False) + if wasi_version is None: + import_object = {} + else: + wasi_env = wasmer.wasi.StateBuilder('test').finalize() + import_object = wasi_env.generate_imports(store, wasi_version) + + wasm: Exports + def get_export(name: str) -> Any: + return wasm.instance.exports.__getattribute__(name) + + imports = MyImports() + add_imports_to_imports(store, import_object, imports, get_export) + wasm = Exports(store, import_object, module) + wasm.thunk() + assert(imports.hit) + +if __name__ == '__main__': + run(sys.argv[1]) diff --git a/tests/runtime/variants/host-wasmer.py b/tests/runtime/variants/host-wasmer.py new file mode 100644 index 000000000..1d8ca8e85 --- /dev/null +++ b/tests/runtime/variants/host-wasmer.py @@ -0,0 +1,118 @@ +from exports.bindings import Exports +from imports.bindings import add_imports_to_imports, Imports +from typing import Optional, Tuple, Any +import exports.bindings as e +import imports.bindings as i +import sys +import wasmer # type: ignore + +class MyImports: + def roundtrip_option(self, a: Optional[float]) -> Optional[int]: + if a: + return int(a) + return None + + def roundtrip_result(self, a: i.Expected[int, float]) -> i.Expected[float, int]: + if isinstance(a, i.Ok): + return i.Ok(float(a.value)) + return i.Err(int(a.value)) + + def roundtrip_enum(self, a: i.E1) -> i.E1: + return a + + def invert_bool(self, a: bool) -> bool: + return not a + + def variant_casts(self, a: i.Casts) -> i.Casts: + return a + + def variant_zeros(self, a: i.Zeros) -> i.Zeros: + return a + + def variant_typedefs(self, a: i.OptionTypedef, b: i.BoolTypedef, c: i.ResultTypedef) -> None: + pass + + def variant_enums(self, a: bool, b: i.Expected[None, None], c: i.MyErrno) -> Tuple[bool, i.Expected[None, None], i.MyErrno]: + assert(a) + assert(isinstance(b, i.Ok)) + assert(c == i.MyErrno.SUCCESS) + return (False, i.Err(None), i.MyErrno.A) + +def run(wasm_file: str) -> None: + store = wasmer.Store() + module = wasmer.Module(store, open(wasm_file, 'rb').read()) + wasi_version = wasmer.wasi.get_version(module, strict=False) + if wasi_version is None: + import_object = {} + else: + wasi_env = wasmer.wasi.StateBuilder('test').finalize() + import_object = wasi_env.generate_imports(store, wasi_version) + + wasm: Exports + def get_export(name: str) -> Any: + return wasm.instance.exports.__getattribute__(name) + + imports = MyImports() + add_imports_to_imports(store, import_object, imports, get_export) + wasm = Exports(store, import_object, module) + + wasm.test_imports() + + assert(wasm.roundtrip_option(1.) == 1) + assert(wasm.roundtrip_option(None) == None) + assert(wasm.roundtrip_option(2.) == 2) + assert(wasm.roundtrip_result(e.Ok(2)) == e.Ok(2)) + assert(wasm.roundtrip_result(e.Ok(4)) == e.Ok(4)) + assert(wasm.roundtrip_result(e.Err(5)) == e.Err(5)) + + assert(wasm.roundtrip_enum(e.E1.A) == e.E1.A) + assert(wasm.roundtrip_enum(e.E1.B) == e.E1.B) + + assert(wasm.invert_bool(True) == False) + assert(wasm.invert_bool(False) == True) + + a1, a2, a3, a4, a5, a6 = wasm.variant_casts(( + e.C1A(1), + e.C2A(2), + e.C3A(3), + e.C4A(4), + e.C5A(5), + e.C6A(6.), + )) + assert(a1 == e.C1A(1)) + assert(a2 == e.C2A(2)) + assert(a3 == e.C3A(3)) + assert(a4 == e.C4A(4)) + assert(a5 == e.C5A(5)) + assert(a6 == e.C6A(6)) + + b1, b2, b3, b4, b5, b6 = wasm.variant_casts(( + e.C1B(1), + e.C2B(2), + e.C3B(3), + e.C4B(4), + e.C5B(5), + e.C6B(6.), + )) + assert(b1 == e.C1B(1)) + assert(b2 == e.C2B(2)) + assert(b3 == e.C3B(3)) + assert(b4 == e.C4B(4)) + assert(b5 == e.C5B(5)) + assert(b6 == e.C6B(6)) + + z1, z2, z3, z4 = wasm.variant_zeros(( + e.Z1A(1), + e.Z2A(2), + e.Z3A(3.), + e.Z4A(4.), + )) + assert(z1 == e.Z1A(1)) + assert(z2 == e.Z2A(2)) + assert(z3 == e.Z3A(3)) + assert(z4 == e.Z4A(4)) + + wasm.variant_typedefs(None, False, e.Err(None)) + +if __name__ == '__main__': + run(sys.argv[1]) From 1ebc43db0d489d3ea1a05de9451f40680bcbcb55 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 15 Mar 2022 17:46:44 -0700 Subject: [PATCH 3/8] Fix formatting --- crates/gen-wasmer-py/src/lib.rs | 5 +++-- crates/gen-wasmer/tests/codegen.rs | 2 +- crates/wasmer/src/imports.rs | 9 ++++++++- crates/wasmer/src/region.rs | 12 ++++++++++-- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/crates/gen-wasmer-py/src/lib.rs b/crates/gen-wasmer-py/src/lib.rs index 314582b92..d307ad3b3 100644 --- a/crates/gen-wasmer-py/src/lib.rs +++ b/crates/gen-wasmer-py/src/lib.rs @@ -903,7 +903,8 @@ impl Generator for WasmtimePy { } if let Some(name) = needs_free { - self.src.push_str(&format!("free = get_export(\"{}\")\n", name)); + self.src + .push_str(&format!("free = get_export(\"{}\")\n", name)); self.src .push_str("assert(isinstance(free, wasmer.Function))\n"); locals.insert("free").unwrap(); @@ -1871,7 +1872,7 @@ impl Bindgen for FunctionBindgen<'_> { Type::U8 => { lift = format!("bytes({})", lift); "bytes".to_string() - }, + } _ => { self.gen.pyimport("typing", "List"); format!("List[{}]", self.gen.type_string(iface, element)) diff --git a/crates/gen-wasmer/tests/codegen.rs b/crates/gen-wasmer/tests/codegen.rs index afc6da4cb..b25aff1a9 100644 --- a/crates/gen-wasmer/tests/codegen.rs +++ b/crates/gen-wasmer/tests/codegen.rs @@ -28,7 +28,7 @@ mod imports { mod exports { test_helpers::codegen_wasmer_import!( "*.wit" - + // TODO: implement async support "!async_functions.wit" diff --git a/crates/wasmer/src/imports.rs b/crates/wasmer/src/imports.rs index 5c5c941cb..7180258a8 100644 --- a/crates/wasmer/src/imports.rs +++ b/crates/wasmer/src/imports.rs @@ -24,7 +24,14 @@ struct Buffer { enum Input { Bytes(*const u8, usize), General { - shim: unsafe fn([usize; 2], *const u8, &Memory, i32, u32, &mut u32) -> Result<(), RuntimeError>, + shim: unsafe fn( + [usize; 2], + *const u8, + &Memory, + i32, + u32, + &mut u32, + ) -> Result<(), RuntimeError>, iterator: [usize; 2], serialize: *const u8, }, diff --git a/crates/wasmer/src/region.rs b/crates/wasmer/src/region.rs index 650d073b9..e14cd879d 100644 --- a/crates/wasmer/src/region.rs +++ b/crates/wasmer/src/region.rs @@ -52,7 +52,11 @@ impl<'a> BorrowChecker<'a> { Ok(ret) } - pub fn slice_mut(&mut self, ptr: i32, len: i32) -> Result<&'a mut [T], RuntimeError> { + pub fn slice_mut( + &mut self, + ptr: i32, + len: i32, + ) -> Result<&'a mut [T], RuntimeError> { let (ret, r) = self.get_slice_mut(ptr, len)?; // SAFETY: see `slice` for how we're extending the lifetime by // recording the borrow here. Note that the `mut_borrows` list is @@ -63,7 +67,11 @@ impl<'a> BorrowChecker<'a> { Ok(ret) } - fn get_slice(&self, ptr: i32, len: i32) -> Result<(&[T], Region), RuntimeError> { + fn get_slice( + &self, + ptr: i32, + len: i32, + ) -> Result<(&[T], Region), RuntimeError> { let r = self.region::(ptr, len)?; if self.is_mut_borrowed(r) { Err(to_error(GuestError::PtrBorrowed(r))) From aec3a77f3cba783d691511478a1206832cf702ff Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 15 Mar 2022 17:47:22 -0700 Subject: [PATCH 4/8] Fixed wit demo --- crates/wit-bindgen-demo/demo.wit | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/wit-bindgen-demo/demo.wit b/crates/wit-bindgen-demo/demo.wit index f35259ca4..e1a139ff4 100644 --- a/crates/wit-bindgen-demo/demo.wit +++ b/crates/wit-bindgen-demo/demo.wit @@ -15,7 +15,7 @@ enum lang { markdown, spidermonkey, wasmer, - wasmer_py, + wasmer-py, } @@ -29,6 +29,6 @@ resource config { set-wasmtime-async: function(val: wasmtime-async) set-wasmtime-custom-error: function(custom: bool) set-wasmer-tracing: function(unchecked: bool) - set-wasmer-async: function(val: wasmtime_async) + set-wasmer-async: function(val: wasmtime-async) set-wasmer-custom-error: function(custom: bool) } From e2e1a184a7bb506824c90cd5cc59ad8c4f3f0ef8 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 15 Mar 2022 20:21:03 -0700 Subject: [PATCH 5/8] Small fixes in tests --- crates/gen-wasmer-py/tests/codegen.rs | 10 +++++----- crates/gen-wasmer/tests/codegen.rs | 6 +++--- crates/gen-wasmtime-py/tests/codegen.rs | 4 ++-- crates/test-helpers/src/lib.rs | 8 ++++---- crates/test-modules/src/lib.rs | 3 ++- tests/runtime/flavorful/host-wasmer.py | 6 +++--- tests/runtime/flavorful/host-wasmer.rs | 12 ++++++------ tests/runtime/lists/host-wasmer.rs | 4 ++++ 8 files changed, 29 insertions(+), 24 deletions(-) diff --git a/crates/gen-wasmer-py/tests/codegen.rs b/crates/gen-wasmer-py/tests/codegen.rs index cfd791a6c..c11c4c0ad 100644 --- a/crates/gen-wasmer-py/tests/codegen.rs +++ b/crates/gen-wasmer-py/tests/codegen.rs @@ -2,24 +2,24 @@ use std::path::Path; use std::process::Command; mod exports { - test_helpers::codegen_py_export!( + test_helpers::codegen_wasmer_py_export!( "*.wit" // TODO: implement async support - "!async_functions.wit" + "!async-functions.wit" ); } mod imports { - test_helpers::codegen_py_import!( + test_helpers::codegen_wasmer_py_import!( "*.wit" // TODO: implement async support - "!async_functions.wit" + "!async-functions.wit" // This uses buffers, which we don't support in imports just yet // TODO: should support this - "!wasi_next.wit" + "!wasi-next.wit" "!host.wit" ); } diff --git a/crates/gen-wasmer/tests/codegen.rs b/crates/gen-wasmer/tests/codegen.rs index b25aff1a9..242416016 100644 --- a/crates/gen-wasmer/tests/codegen.rs +++ b/crates/gen-wasmer/tests/codegen.rs @@ -11,7 +11,7 @@ mod imports { "*.witx" // TODO: implement async support - "!async_functions.wit" + "!async-functions.wit" // If you want to exclude a specific test you can include it here with // gitignore glob syntax: @@ -30,11 +30,11 @@ mod exports { "*.wit" // TODO: implement async support - "!async_functions.wit" + "!async-functions.wit" // TODO: these use push/pull buffer which isn't implemented in the test // generator just yet - "!wasi_next.wit" + "!wasi-next.wit" "!host.wit" ); } diff --git a/crates/gen-wasmtime-py/tests/codegen.rs b/crates/gen-wasmtime-py/tests/codegen.rs index 285eb4ad9..a9bbdc166 100644 --- a/crates/gen-wasmtime-py/tests/codegen.rs +++ b/crates/gen-wasmtime-py/tests/codegen.rs @@ -2,7 +2,7 @@ use std::path::Path; use std::process::Command; mod exports { - test_helpers::codegen_py_export!( + test_helpers::codegen_wasmtime_py_export!( "*.wit" // TODO: implement async support @@ -11,7 +11,7 @@ mod exports { } mod imports { - test_helpers::codegen_py_import!( + test_helpers::codegen_wasmtime_py_import!( "*.wit" // TODO: implement async support diff --git a/crates/test-helpers/src/lib.rs b/crates/test-helpers/src/lib.rs index 00b9f9c91..c142c1a3c 100644 --- a/crates/test-helpers/src/lib.rs +++ b/crates/test-helpers/src/lib.rs @@ -319,7 +319,7 @@ pub fn codegen_c_export(input: TokenStream) -> TokenStream { #[proc_macro] #[cfg(feature = "wit-bindgen-gen-wasmtime-py")] -pub fn codegen_py_export(input: TokenStream) -> TokenStream { +pub fn codegen_wasmtime_py_export(input: TokenStream) -> TokenStream { gen_verify(input, Direction::Export, "export", || { wit_bindgen_gen_wasmtime_py::Opts::default().build() }) @@ -327,7 +327,7 @@ pub fn codegen_py_export(input: TokenStream) -> TokenStream { #[proc_macro] #[cfg(feature = "wit-bindgen-gen-wasmtime-py")] -pub fn codegen_py_import(input: TokenStream) -> TokenStream { +pub fn codegen_wasmtime_py_import(input: TokenStream) -> TokenStream { gen_verify(input, Direction::Import, "import", || { wit_bindgen_gen_wasmtime_py::Opts::default().build() }) @@ -395,7 +395,7 @@ pub fn codegen_wasmer_export(input: TokenStream) -> TokenStream { #[proc_macro] #[cfg(feature = "wit-bindgen-gen-wasmer-py")] -pub fn codegen_py_export(input: TokenStream) -> TokenStream { +pub fn codegen_wasmer_py_export(input: TokenStream) -> TokenStream { gen_verify(input, Direction::Export, "export", || { wit_bindgen_gen_wasmer_py::Opts::default().build() }) @@ -403,7 +403,7 @@ pub fn codegen_py_export(input: TokenStream) -> TokenStream { #[proc_macro] #[cfg(feature = "wit-bindgen-gen-wasmer-py")] -pub fn codegen_py_import(input: TokenStream) -> TokenStream { +pub fn codegen_wasmer_py_import(input: TokenStream) -> TokenStream { gen_verify(input, Direction::Import, "import", || { wit_bindgen_gen_wasmer_py::Opts::default().build() }) diff --git a/crates/test-modules/src/lib.rs b/crates/test-modules/src/lib.rs index 31340a591..11294a23a 100644 --- a/crates/test-modules/src/lib.rs +++ b/crates/test-modules/src/lib.rs @@ -119,7 +119,8 @@ mod tests { fn resources_with_invalid_handle() -> Result<()> { let e = run(&link("resources-invalid-main", &["resources"])?).expect_err("should trap"); - assert!(e.to_string().contains("invalid_handle_trap")); + let str_e = e.to_string(); + assert!(str_e.contains("invalid_handle_trap") || str_e.contains("unreachable")); Ok(()) } diff --git a/tests/runtime/flavorful/host-wasmer.py b/tests/runtime/flavorful/host-wasmer.py index 21d11076f..67e1f8518 100644 --- a/tests/runtime/flavorful/host-wasmer.py +++ b/tests/runtime/flavorful/host-wasmer.py @@ -21,10 +21,10 @@ def list_in_record4(self, a: i.ListInAlias) -> i.ListInAlias: assert(a.a == 'input4') return i.ListInRecord4('result4') - def list_in_variant1(self, a: i.ListInVariant11, b: i.ListInVariant12, c: i.ListInVariant13) -> None: + def list_in_variant1(self, a: i.ListInVariant1V1, b: i.ListInVariant1V2, c: i.ListInVariant1V3) -> None: assert(a == 'foo') assert(b == i.Err('bar')) - assert(c == i.ListInVariant130('baz')) + assert(c == i.ListInVariant1V30('baz')) def list_in_variant2(self) -> i.ListInVariant2: return 'list_in_variant2' @@ -76,7 +76,7 @@ def get_export(name: str) -> Any: assert(wasm.list_in_record3(e.ListInRecord3("list_in_record3 input")).a == "list_in_record3 output") assert(wasm.list_in_record4(e.ListInRecord4("input4")).a == "result4") - wasm.list_in_variant1("foo", e.Err("bar"), e.ListInVariant130('baz')) + wasm.list_in_variant1("foo", e.Err("bar"), e.ListInVariant1V30('baz')) assert(wasm.list_in_variant2() == "list_in_variant2") assert(wasm.list_in_variant3("input3") == "output3") diff --git a/tests/runtime/flavorful/host-wasmer.rs b/tests/runtime/flavorful/host-wasmer.rs index 8bbbc9515..d41c47a6f 100644 --- a/tests/runtime/flavorful/host-wasmer.rs +++ b/tests/runtime/flavorful/host-wasmer.rs @@ -35,15 +35,15 @@ impl Imports for MyImports { fn list_in_variant1( &mut self, - a: ListInVariant11<'_>, - b: ListInVariant12<'_>, - c: ListInVariant13<'_>, + a: ListInVariant1V1<'_>, + b: ListInVariant1V2<'_>, + c: ListInVariant1V3<'_>, ) { assert_eq!(a.unwrap(), "foo"); assert_eq!(b.unwrap_err(), "bar"); match c { - ListInVariant13::V0(s) => assert_eq!(s, "baz"), - ListInVariant13::V1(_) => panic!(), + ListInVariant1V3::V0(s) => assert_eq!(s, "baz"), + ListInVariant1V3::V1(_) => panic!(), } } @@ -124,7 +124,7 @@ fn run(wasm: &str) -> Result<()> { "result4" ); - exports.list_in_variant1(Some("foo"), Err("bar"), ListInVariant13::V0("baz"))?; + exports.list_in_variant1(Some("foo"), Err("bar"), ListInVariant1V3::V0("baz"))?; assert_eq!( exports.list_in_variant2()?, Some("list_in_variant2".to_string()) diff --git a/tests/runtime/lists/host-wasmer.rs b/tests/runtime/lists/host-wasmer.rs index 1771b7371..25f0fa944 100644 --- a/tests/runtime/lists/host-wasmer.rs +++ b/tests/runtime/lists/host-wasmer.rs @@ -44,6 +44,10 @@ impl Imports for MyImports { vec!["hello,".to_string(), "world!".to_string()] } + fn list_roundtrip(&mut self, list: &[u8]) -> Vec { + list.to_vec() + } + fn string_roundtrip(&mut self, s: &str) -> String { s.to_string() } From 86a78fc4654911d42f8c508029fa1336f96cc961 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 15 Mar 2022 20:51:10 -0700 Subject: [PATCH 6/8] Fix Python wasmer dependency --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2b4827176..2667157db 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -55,7 +55,7 @@ jobs: - uses: actions/setup-python@v1 with: python-version: 3.9 - - run: pip install mypy wasmtime + - run: pip install mypy wasmtime wasmer wasmer-compiler-cranelift - if: matrix.mode == 'release' name: Test release build run: cargo test --workspace --release From 7837e9f9eecef6c3076dba052f3ef975b1a21e7c Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 15 Mar 2022 21:25:37 -0700 Subject: [PATCH 7/8] Fix python tests --- tests/runtime/lists/host-wasmer.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/runtime/lists/host-wasmer.py b/tests/runtime/lists/host-wasmer.py index 92a16d992..2c52abf04 100644 --- a/tests/runtime/lists/host-wasmer.py +++ b/tests/runtime/lists/host-wasmer.py @@ -28,6 +28,9 @@ def list_result2(self) -> str: def list_result3(self) -> List[str]: return ['hello,', 'world!'] + def list_roundtrip(self, a: bytes) -> bytes: + return a + def string_roundtrip(self, a: str) -> str: return a From 56a74882b5441bb79699b9c3bfd0aa708dbb081b Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Tue, 15 Mar 2022 22:10:26 -0700 Subject: [PATCH 8/8] Removed unused prints to fix tests --- crates/gen-wasmer-py/src/lib.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crates/gen-wasmer-py/src/lib.rs b/crates/gen-wasmer-py/src/lib.rs index d307ad3b3..6b4e7a22f 100644 --- a/crates/gen-wasmer-py/src/lib.rs +++ b/crates/gen-wasmer-py/src/lib.rs @@ -223,8 +223,6 @@ impl WasmtimePy { self.src.push_str( " def _decode_utf8(mem: wasmer.Memory, ptr: int, len: int) -> str: - print('ptr = ' + str(ptr)) - print('len = ' + str(len)) ptr = ptr & 0xffffffff len = len & 0xffffffff if ptr + len > mem.data_size: @@ -232,8 +230,6 @@ impl WasmtimePy { view = mem.uint8_view() bytes = bytearray(view[ptr:ptr+len]) x = bytes.decode('utf8') - print('decode_utf8: ' + x) - print('bytes: ' + str(bytes)) return x ", ); @@ -244,8 +240,6 @@ impl WasmtimePy { " def _encode_utf8(val: str, realloc: wasmer.Function, mem: wasmer.Memory) -> Tuple[int, int]: bytes = val.encode('utf8') - print('encode_utf8: ' + val) - print('bytes: ' + str(bytes)) ptr = realloc(0, 0, 1, len(bytes)) assert(isinstance(ptr, int)) ptr = ptr & 0xffffffff