diff --git a/Cargo.lock b/Cargo.lock index 19a54eb1a..1330b7340 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,7 +16,16 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a49806b9dadc843c61e7c97e72490ad7f7220ae249012fbda9ad0609457c0543" dependencies = [ - "gimli", + "gimli 0.21.0", +] + +[[package]] +name = "aho-corasick" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" +dependencies = [ + "memchr", ] [[package]] @@ -57,6 +66,12 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "anyhow" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f" + [[package]] name = "approx" version = "0.3.2" @@ -149,6 +164,28 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" + +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + +[[package]] +name = "bincode" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30d3a39baa26f9651f17b375061f3233dde33424a8b72b0dbe93a68a0bc896d" +dependencies = [ + "byteorder", + "serde", +] + [[package]] name = "bit_field" version = "0.9.0" @@ -177,6 +214,17 @@ dependencies = [ "radium", ] +[[package]] +name = "blake2b_simd" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + [[package]] name = "blake3" version = "0.2.3" @@ -198,6 +246,27 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array 0.12.3", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + [[package]] name = "bs58" version = "0.3.1" @@ -228,6 +297,12 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0a5e3906bcbf133e33c1d4d95afc664ad37fbdb9f6568d8043e7ea8c27d93d3" +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + [[package]] name = "byteorder" version = "1.3.4" @@ -277,6 +352,9 @@ name = "cc" version = "1.0.54" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311" +dependencies = [ + "jobserver", +] [[package]] name = "cfg-if" @@ -407,6 +485,97 @@ dependencies = [ "objc", ] +[[package]] +name = "cranelift-bforest" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f94ed5f53da27b3ed5e0c01be27c2395c06984101079ecd7b89166913288c9f" +dependencies = [ + "cranelift-entity", +] + +[[package]] +name = "cranelift-codegen" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d27daa12747771d248d0452fc8b7c9c05bec7dda30a9f75382cfce9c90a79f0" +dependencies = [ + "byteorder", + "cranelift-bforest", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-entity", + "gimli 0.20.0", + "log", + "serde", + "smallvec", + "target-lexicon", + "thiserror", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3acd0ced2877343c597bf0d19b0200c21848444446b200bf60caffaaef6db5e" +dependencies = [ + "cranelift-codegen-shared", + "cranelift-entity", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9160c4b2521fded37386bfde52b59a226ebab4a7e9a02e4efbd565833b12259d" + +[[package]] +name = "cranelift-entity" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35e78c238d0a1231378e87d3123515d7cabf61bd58525b8ecd3d82ce00754b59" +dependencies = [ + "serde", +] + +[[package]] +name = "cranelift-frontend" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1eefdd65b67aa8fd2f43c1f3a94c006b99957d0cb9b8aac5c8cda49c86549f46" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-native" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f5ee2e8b1f538d337f6d97aef80824f6e477d580cb9122630432482b7abfb4a" +dependencies = [ + "cranelift-codegen", + "raw-cpuid", + "target-lexicon", +] + +[[package]] +name = "cranelift-wasm" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634972998dfca80bb4321fd2ea3f46bb7d36502f1033bb3cb4781fc44b65bb9f" +dependencies = [ + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", + "log", + "serde", + "thiserror", + "wasmparser", +] + [[package]] name = "criterion" version = "0.3.2" @@ -541,6 +710,27 @@ dependencies = [ "generic-array 0.12.3", ] +[[package]] +name = "directories" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "551a778172a450d7fc12e629ca3b0428d00f6afa9a43da1b630d54604e97371c" +dependencies = [ + "cfg-if", + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" +dependencies = [ + "libc", + "redox_users", + "winapi 0.3.8", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -568,6 +758,77 @@ version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "errno" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b480f641ccf0faf324e20c1d3e53d81b7484c698b42ea677f6907ae4db195371" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi 0.3.8", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067" +dependencies = [ + "gcc", + "libc", +] + +[[package]] +name = "faerie" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfef65b0e94693295c5d2fe2506f0ee6f43465342d4b5331659936aee8b16084" +dependencies = [ + "goblin", + "indexmap", + "log", + "scroll", + "string-interner", + "target-lexicon", + "thiserror", +] + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "file-per-thread-logger" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b3937f028664bd0e13df401ba49a4567ccda587420365823242977f06609ed1" +dependencies = [ + "env_logger", + "log", +] + [[package]] name = "fnv" version = "1.0.6" @@ -715,6 +976,12 @@ dependencies = [ "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "gcc" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" + [[package]] name = "generic-array" version = "0.12.3" @@ -744,6 +1011,20 @@ dependencies = [ "wasi", ] +[[package]] +name = "gimli" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dd6190aad0f05ddbbf3245c54ed14ca4aa6dd32f22312b70d8f168c3e3e633" +dependencies = [ + "arrayvec", + "byteorder", + "fallible-iterator", + "indexmap", + "smallvec", + "stable_deref_trait", +] + [[package]] name = "gimli" version = "0.21.0" @@ -788,6 +1069,12 @@ dependencies = [ "takeable-option", ] +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + [[package]] name = "gloo-timers" version = "0.2.1" @@ -873,6 +1160,17 @@ dependencies = [ "gl_generator 0.13.1", ] +[[package]] +name = "goblin" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3081214398d39e4bd7f2c1975f0488ed04614ffdd976c6fc7a0708278552c0da" +dependencies = [ + "log", + "plain", + "scroll", +] + [[package]] name = "hashbrown" version = "0.7.2" @@ -900,6 +1198,24 @@ dependencies = [ "libc", ] +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + +[[package]] +name = "indexmap" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c398b2b113b55809ceb9ee3e753fcbac793f1956663f3c36549c1346015c2afe" +dependencies = [ + "autocfg", +] + [[package]] name = "instant" version = "0.1.4" @@ -930,6 +1246,15 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" +[[package]] +name = "jobserver" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.40" @@ -1058,6 +1383,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -1147,6 +1481,12 @@ dependencies = [ "ws2_32-sys", ] +[[package]] +name = "more-asserts" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238" + [[package]] name = "multiboot2" version = "0.8.2" @@ -1253,6 +1593,12 @@ version = "11.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94af325bc33c7f60191be4e2c984d48aaa21e2854f473b85398344b60c9b6358" +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + [[package]] name = "ordered-float" version = "1.0.2" @@ -1380,6 +1726,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "plotters" version = "0.2.15" @@ -1463,6 +1815,12 @@ dependencies = [ "unicode-xid 0.2.0", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "0.6.13" @@ -1538,6 +1896,17 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "raw-cpuid" +version = "7.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4a349ca83373cfa5d6dbb66fd76e58b2cca08da71a5f6400de0a0a6a9bceeaf" +dependencies = [ + "bitflags", + "cc", + "rustc_version", +] + [[package]] name = "raw-window-handle" version = "0.3.3" @@ -1586,6 +1955,17 @@ version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +[[package]] +name = "redox_users" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431" +dependencies = [ + "getrandom", + "redox_syscall", + "rust-argon2", +] + [[package]] name = "redshirt-cli-kernel" version = "0.1.0" @@ -1636,6 +2016,7 @@ dependencies = [ "spinning_top", "wasi", "wasmi", + "wasmtime", ] [[package]] @@ -1892,7 +2273,10 @@ version = "1.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" dependencies = [ + "aho-corasick", + "memchr", "regex-syntax", + "thread_local", ] [[package]] @@ -1910,12 +2294,36 @@ version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" +[[package]] +name = "region" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877e54ea2adcd70d80e9179344c97f93ef0dffd6b03e1f4529e6e83ab2fa9ae0" +dependencies = [ + "bitflags", + "libc", + "mach", + "winapi 0.3.8", +] + [[package]] name = "rlibc" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc874b127765f014d792f16763a81245ab80500e2ad921ed4ee9e82481ee08fe" +[[package]] +name = "rust-argon2" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017" +dependencies = [ + "base64 0.11.0", + "blake2b_simd", + "constant_time_eq", + "crossbeam-utils", +] + [[package]] name = "rustc-demangle" version = "0.1.16" @@ -1978,6 +2386,26 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "scroll" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb2332cb595d33f7edd5700f4cbf94892e680c7f0ae56adab58a35190b66cb1" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e367622f934864ffa1c704ba2b82280aab856e3d8213c84c5720257eb34b15b9" +dependencies = [ + "proc-macro2 1.0.18", + "quote 1.0.6", + "syn", +] + [[package]] name = "semver" version = "0.9.0" @@ -2005,6 +2433,9 @@ name = "serde" version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9124df5b40cbd380080b2cc6ab894c040a3070d995f5c9dc77e18c34a8ae37d" +dependencies = [ + "serde_derive", +] [[package]] name = "serde_derive" @@ -2028,6 +2459,18 @@ dependencies = [ "serde", ] +[[package]] +name = "sha2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" +dependencies = [ + "block-buffer", + "digest", + "fake-simd", + "opaque-debug", +] + [[package]] name = "shared_library" version = "0.1.9" @@ -2119,6 +2562,12 @@ dependencies = [ "lock_api", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "stb_truetype" version = "0.3.1" @@ -2128,6 +2577,15 @@ dependencies = [ "byteorder", ] +[[package]] +name = "string-interner" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd710eadff449a1531351b0e43eb81ea404336fa2f56c777427ab0e32a4cf183" +dependencies = [ + "serde", +] + [[package]] name = "strsim" version = "0.8.0" @@ -2192,6 +2650,21 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36ae8932fcfea38b7d3883ae2ab357b0d57a02caaa18ebb4f5ece08beaec4aa0" +[[package]] +name = "target-lexicon" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab0e7238dcc7b40a7be719a25365910f6807bd864f4cce6b2e6b873658e2b19d" + +[[package]] +name = "termcolor" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" +dependencies = [ + "winapi-util", +] + [[package]] name = "textwrap" version = "0.11.0" @@ -2201,6 +2674,35 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "thiserror" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" +dependencies = [ + "proc-macro2 1.0.18", + "quote 1.0.6", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +dependencies = [ + "lazy_static", +] + [[package]] name = "tinytemplate" version = "1.1.0" @@ -2382,6 +2884,140 @@ dependencies = [ "parity-wasm", ] +[[package]] +name = "wasmparser" +version = "0.51.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeb1956b19469d1c5e63e459d29e7b5aa0f558d9f16fcef09736f8a265e6c10a" + +[[package]] +name = "wasmtime" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fd1764fe8123e4f550636cc5c809ea95447454cb2b76f931c01863f247dc1f" +dependencies = [ + "anyhow", + "backtrace", + "cfg-if", + "lazy_static", + "libc", + "region", + "rustc-demangle", + "target-lexicon", + "wasmparser", + "wasmtime-environ", + "wasmtime-jit", + "wasmtime-profiling", + "wasmtime-runtime", + "winapi 0.3.8", +] + +[[package]] +name = "wasmtime-debug" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1d7e50a1b7fb6d7147f22328808a8af21fa116f3bab6a7672f942a76c5bea3" +dependencies = [ + "anyhow", + "faerie", + "gimli 0.20.0", + "more-asserts", + "target-lexicon", + "thiserror", + "wasmparser", + "wasmtime-environ", +] + +[[package]] +name = "wasmtime-environ" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07b8400483d6b0312b8c5871c69989438e38c1a0d9fa0dd96637ee84bee06560" +dependencies = [ + "anyhow", + "base64 0.12.3", + "bincode", + "cranelift-codegen", + "cranelift-entity", + "cranelift-wasm", + "directories", + "errno", + "file-per-thread-logger", + "indexmap", + "libc", + "log", + "more-asserts", + "rayon", + "serde", + "sha2", + "thiserror", + "toml", + "wasmparser", + "winapi 0.3.8", + "zstd", +] + +[[package]] +name = "wasmtime-jit" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d63d98adf1755e6f1c396e349c4b01ae7d25809691f699767368e1a3383e55f3" +dependencies = [ + "anyhow", + "cfg-if", + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", + "cranelift-native", + "cranelift-wasm", + "log", + "more-asserts", + "region", + "target-lexicon", + "thiserror", + "wasmparser", + "wasmtime-debug", + "wasmtime-environ", + "wasmtime-profiling", + "wasmtime-runtime", + "winapi 0.3.8", +] + +[[package]] +name = "wasmtime-profiling" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6467761fd74dd582102f1da5b29a3a8ba93b0b4c4962948a85a15cdb58c415b3" +dependencies = [ + "anyhow", + "cfg-if", + "lazy_static", + "libc", + "serde", + "target-lexicon", + "wasmtime-environ", + "wasmtime-runtime", +] + +[[package]] +name = "wasmtime-runtime" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daa636f3a7542f72aef81cb1f81257095abf2637898e675628cbfa3f79c8b3f5" +dependencies = [ + "backtrace", + "cc", + "cfg-if", + "indexmap", + "libc", + "memoffset", + "more-asserts", + "region", + "thiserror", + "wasmtime-environ", + "winapi 0.3.8", +] + [[package]] name = "wast" version = "18.0.0" @@ -2604,3 +3240,34 @@ name = "xml-rs" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a" + +[[package]] +name = "zstd" +version = "0.5.3+zstd.1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01b32eaf771efa709e8308605bbf9319bf485dc1503179ec0469b611937c0cd8" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "2.0.5+zstd.1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfb642e0d27f64729a639c52db457e0ae906e7bc6f5fe8f5c453230400f1055" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "1.4.17+zstd.1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89249644df056b522696b1bb9e7c18c87e8ffa3e2f0dc3b0155875d6498f01b" +dependencies = [ + "cc", + "glob", + "itertools", + "libc", +] diff --git a/core/Cargo.toml b/core/Cargo.toml index 67ad7c88a..9addd9ca9 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -42,6 +42,9 @@ wasmi = { git = "https://github.com/tomaka/wasmi", branch = "no-std", default-fe criterion = "0.3" futures = { version = "0.3.1", default-features = false, features = ["executor"] } +[target.'cfg(target_arch = "x86_64")'.dependencies] +wasmtime = { version = "0.13.0", default-features = false } + [[bench]] name = "keccak" harness = false diff --git a/core/src/lib.rs b/core/src/lib.rs index 242f2b67f..58941bb6c 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -110,12 +110,15 @@ //! handler. //! +#![feature(asm, global_asm, naked_functions)] +#![feature(new_uninit)] // TODO: no; definitely can be circumvented too #![warn(missing_docs)] //#![deny(unsafe_code)] // TODO: 🤷 #![allow(dead_code)] // TODO: temporary during development // The crate uses the stdlib for testing purposes. -#![cfg_attr(not(test), no_std)] +// TODO: restore no_std +//#![cfg_attr(not(test), no_std)] extern crate alloc; diff --git a/core/src/module.rs b/core/src/module.rs index 4adeeb5f2..035095f03 100644 --- a/core/src/module.rs +++ b/core/src/module.rs @@ -20,7 +20,10 @@ use core::fmt; /// This is the equivalent of an [ELF](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format) /// or a [PE](https://en.wikipedia.org/wiki/Portable_Executable). pub struct Module { + #[cfg(not(target_arch = "x86_64"))] inner: wasmi::Module, + #[cfg(target_arch = "x86_64")] + bytes: Vec, hash: ModuleHash, } @@ -39,17 +42,30 @@ pub struct FromBase58Error {} impl Module { /// Parses a module from WASM bytes. pub fn from_bytes(buffer: impl AsRef<[u8]>) -> Result { - let inner = wasmi::Module::from_buffer(buffer.as_ref()).map_err(|_| FromBytesError {})?; + let buffer = buffer.as_ref(); let hash = ModuleHash::from_bytes(buffer); - Ok(Module { inner, hash }) + Ok(Module { + #[cfg(not(target_arch = "x86_64"))] + inner: wasmi::Module::from_buffer(buffer.as_ref()).map_err(|_| FromBytesError {})?, + #[cfg(target_arch = "x86_64")] + bytes: buffer.to_owned(), + hash, + }) } /// Returns a reference to the internal module. + #[cfg(not(target_arch = "x86_64"))] pub(crate) fn as_ref(&self) -> &wasmi::Module { &self.inner } + /// Returns the Wasm binary. + #[cfg(target_arch = "x86_64")] + pub(crate) fn as_ref(&self) -> &[u8] { + &self.bytes + } + /// Returns the hash of that module. /// /// This gives the same result as calling `ModuleHash::from_bytes` on the original input. diff --git a/core/src/primitives.rs b/core/src/primitives.rs index 3722e6a8d..df4047de5 100644 --- a/core/src/primitives.rs +++ b/core/src/primitives.rs @@ -97,6 +97,19 @@ impl<'a> From<&'a wasmi::Signature> for Signature { } } +impl<'a> From<&'a wasmtime::FuncType> for Signature { + fn from(sig: &'a wasmtime::FuncType) -> Signature { + // TODO: we only support one return type at the moment; what even is multiple + // return types? + assert!(sig.results().len() <= 1); + + Signature::new( + sig.params().iter().cloned().map(ValueType::from), + sig.results().get(0).cloned().map(ValueType::from), + ) + } +} + impl From for Signature { fn from(sig: wasmi::Signature) -> Signature { Signature::from(&sig) @@ -198,6 +211,26 @@ impl From for wasmi::RuntimeValue { } } +impl From for wasmtime::Val { + fn from(val: WasmValue) -> Self { + match val { + WasmValue::I32(v) => wasmtime::Val::I32(v), + WasmValue::I64(v) => wasmtime::Val::I64(v), + _ => unimplemented!(), + } + } +} + +impl From for WasmValue { + fn from(val: wasmtime::Val) -> Self { + match val { + wasmtime::Val::I32(v) => WasmValue::I32(v), + wasmtime::Val::I64(v) => WasmValue::I64(v), + _ => unimplemented!(), + } + } +} + impl From for ValueType { fn from(val: wasmi::ValueType) -> Self { match val { @@ -208,3 +241,15 @@ impl From for ValueType { } } } + +impl From for ValueType { + fn from(val: wasmtime::ValType) -> Self { + match val { + wasmtime::ValType::I32 => ValueType::I32, + wasmtime::ValType::I64 => ValueType::I64, + wasmtime::ValType::F32 => ValueType::F32, + wasmtime::ValType::F64 => ValueType::F64, + _ => unimplemented!(), // TODO: + } + } +} diff --git a/core/src/scheduler/vm.rs b/core/src/scheduler/vm.rs index e5acdf280..2dc6f9a3f 100644 --- a/core/src/scheduler/vm.rs +++ b/core/src/scheduler/vm.rs @@ -15,18 +15,18 @@ use crate::{module::Module, primitives::Signature, ValueType, WasmValue}; -use alloc::{ - borrow::{Cow, ToOwned as _}, - boxed::Box, - format, - vec::Vec, -}; -use core::{ - cell::RefCell, - convert::{TryFrom as _, TryInto}, - fmt, -}; -use smallvec::SmallVec; +use alloc::vec::Vec; +use core::fmt; + +#[cfg(target_arch = "x86_64")] +mod jit; +#[cfg(target_arch = "x86_64")] +use jit::{Jit as ImpStateMachine, Thread as ImpThread}; + +#[cfg(not(target_arch = "x86_64"))] +mod interpreter; +#[cfg(not(target_arch = "x86_64"))] +use interpreter::{Interpreter as ImpStateMachine, Thread as ImpThread}; /// WASMI state machine dedicated to a process. /// @@ -75,55 +75,10 @@ use smallvec::SmallVec; /// The [`ProcessStateMachine`] is single-threaded. In other words, the VM can only ever run one /// thread simultaneously. This might change in the future. /// -pub struct ProcessStateMachine { - /// Original module, with resolved imports. - module: wasmi::ModuleRef, - - /// Memory of the module instantiation. - /// - /// Right now we only support one unique `Memory` object per process. This is it. - /// Contains `None` if the process doesn't export any memory object, which means it doesn't use - /// any memory. - memory: Option, - - /// Table of the indirect function calls. - /// - /// In WASM, function pointers are in reality indices in a table called - /// `__indirect_function_table`. This is this table, if it exists. - indirect_table: Option, - - /// List of threads that this process is running. - threads: SmallVec<[ThreadState; 4]>, - - /// If true, the state machine is in a poisoned state and cannot run any code anymore. - is_poisoned: bool, -} - -/// State of a single thread within the VM. -struct ThreadState { - /// Execution context of this thread. This notably holds the program counter, state of the - /// stack, and so on. - /// - /// This field is an `Option` because we need to be able to temporarily extract it. It must - /// always be `Some`. - execution: Option>, - - /// If false, then one must call `execution.start_execution()` instead of `resume_execution()`. - /// This is a particularity of the WASM interpreter that we don't want to expose in our API. - interrupted: bool, - - /// Opaque user data associated with the thread. - user_data: T, -} +pub struct ProcessStateMachine(ImpStateMachine); /// Access to a thread within the virtual machine. -pub struct Thread<'a, T> { - /// Reference to the parent object. - vm: &'a mut ProcessStateMachine, - - // Index within [`ProcessStateMachine::threads`] of the thread we are referencing. - index: usize, -} +pub struct Thread<'a, T>(ImpThread<'a, T>); /// Outcome of the [`run`](Thread::run) function. #[derive(Debug)] @@ -239,150 +194,18 @@ impl ProcessStateMachine { pub fn new( module: &Module, main_thread_user_data: T, - mut symbols: impl FnMut(&str, &str, &Signature) -> Result, + symbols: impl FnMut(&str, &str, &Signature) -> Result, ) -> Result { - struct ImportResolve<'a> { - func: RefCell<&'a mut dyn FnMut(&str, &str, &Signature) -> Result>, - memory: RefCell<&'a mut Option>, - } - - impl<'a> wasmi::ImportResolver for ImportResolve<'a> { - fn resolve_func( - &self, - module_name: &str, - field_name: &str, - signature: &wasmi::Signature, - ) -> Result { - let closure = &mut **self.func.borrow_mut(); - let index = match closure(module_name, field_name, &From::from(signature)) { - Ok(i) => i, - Err(_) => { - return Err(wasmi::Error::Instantiation(format!( - "Couldn't resolve `{}`:`{}`", - module_name, field_name - ))) - } - }; - - Ok(wasmi::FuncInstance::alloc_host(signature.clone(), index)) - } - - fn resolve_global( - &self, - _module_name: &str, - _field_name: &str, - _global_type: &wasmi::GlobalDescriptor, - ) -> Result { - Err(wasmi::Error::Instantiation( - "Importing globals is not supported yet".to_owned(), - )) - } - - fn resolve_memory( - &self, - _module_name: &str, - _field_name: &str, - memory_type: &wasmi::MemoryDescriptor, - ) -> Result { - let mut mem = self.memory.borrow_mut(); - if mem.is_some() { - return Err(wasmi::Error::Instantiation( - "Only one memory object is supported yet".to_owned(), - )); - } - - let new_mem = wasmi::MemoryInstance::alloc( - wasmi::memory_units::Pages(usize::try_from(memory_type.initial()).unwrap()), - memory_type - .maximum() - .map(|p| wasmi::memory_units::Pages(usize::try_from(p).unwrap())), - ) - .unwrap(); - **mem = Some(new_mem.clone()); - Ok(new_mem) - } - - fn resolve_table( - &self, - _module_name: &str, - _field_name: &str, - _table_type: &wasmi::TableDescriptor, - ) -> Result { - Err(wasmi::Error::Instantiation( - "Importing tables is not supported yet".to_owned(), - )) - } - } - - let (not_started, imported_memory) = { - let mut imported_memory = None; - let resolve = ImportResolve { - func: RefCell::new(&mut symbols), - memory: RefCell::new(&mut imported_memory), - }; - let not_started = wasmi::ModuleInstance::new(module.as_ref(), &resolve) - .map_err(NewErr::Interpreter)?; - (not_started, imported_memory) - }; - - // TODO: WASM has a special "start" instruction that can be used to designate a function - // that must be executed before the module is considered initialized. It is unclear whether - // this is intended to be a function that for example initializes global variables, or if - // this is an equivalent of "_start". In practice, Rust never seems to generate such as - // "start" instruction, so for now we ignore it. The code below panics if there is such - // a "start" item, so we will fortunately not blindly run into troubles. - let module = not_started.assert_no_start(); - - let memory = if let Some(imported_mem) = imported_memory { - if module - .export_by_name("memory") - .map_or(false, |m| m.as_memory().is_some()) - { - return Err(NewErr::MultipleMemoriesNotSupported); - } - Some(imported_mem) - } else if let Some(mem) = module.export_by_name("memory") { - if let Some(mem) = mem.as_memory() { - Some(mem.clone()) - } else { - return Err(NewErr::MemoryIsntMemory); - } - } else { - None - }; - - let indirect_table = if let Some(tbl) = module.export_by_name("__indirect_function_table") { - if let Some(tbl) = tbl.as_table() { - Some(tbl.clone()) - } else { - return Err(NewErr::IndirectTableIsntTable); - } - } else { - None - }; - - let mut state_machine = ProcessStateMachine { + Ok(ProcessStateMachine(ImpStateMachine::new( module, - memory, - indirect_table, - is_poisoned: false, - threads: SmallVec::new(), - }; - - // Try to start executing `_start`. - match state_machine.start_thread_by_name("_start", &[][..], main_thread_user_data) { - Ok(_) => {} - Err((StartErr::FunctionNotFound, _)) => return Err(NewErr::StartNotFound), - Err((StartErr::Poisoned, _)) => unreachable!(), - Err((StartErr::NotAFunction, _)) => return Err(NewErr::StartIsntAFunction), - }; - - Ok(state_machine) + main_thread_user_data, + symbols, + )?)) } /// Returns true if the state machine is in a poisoned state and cannot run anymore. pub fn is_poisoned(&self) -> bool { - self.is_poisoned + self.0.is_poisoned() } /// Starts executing a function. Immediately pauses the execution and puts it in an @@ -399,79 +222,16 @@ impl ProcessStateMachine { params: impl IntoIterator, user_data: T, ) -> Result, StartErr> { - if self.is_poisoned { - return Err(StartErr::Poisoned); - } - - // Find the function within the process. - let function = self - .indirect_table - .as_ref() - .and_then(|t| t.get(function_id).ok()) - .and_then(|f| f) - .ok_or(StartErr::FunctionNotFound)?; - - let params = params - .into_iter() - .map(wasmi::RuntimeValue::from) - .collect::>(); - - let execution = match wasmi::FuncInstance::invoke_resumable(&function, params) { - Ok(e) => e, - Err(err) => unreachable!("{:?}", err), - }; - - self.threads.push(ThreadState { - execution: Some(execution), - interrupted: false, + Ok(Thread(self.0.start_thread_by_id( + function_id, + params, user_data, - }); - - let thread_id = self.threads.len() - 1; - Ok(Thread { - vm: self, - index: thread_id, - }) - } - - /// Same as [`start_thread_by_id`](ProcessStateMachine::start_thread_by_id), but executes a - /// symbol by name. - fn start_thread_by_name( - &mut self, - symbol_name: &str, - params: impl Into>, - user_data: T, - ) -> Result, (StartErr, T)> { - if self.is_poisoned { - return Err((StartErr::Poisoned, user_data)); - } - - match self.module.export_by_name(symbol_name) { - Some(wasmi::ExternVal::Func(f)) => { - let execution = match wasmi::FuncInstance::invoke_resumable(&f, params) { - Ok(e) => e, - Err(err) => unreachable!("{:?}", err), - }; - self.threads.push(ThreadState { - execution: Some(execution), - interrupted: false, - user_data, - }); - } - None => return Err((StartErr::FunctionNotFound, user_data)), - _ => return Err((StartErr::NotAFunction, user_data)), - } - - let thread_id = self.threads.len() - 1; - Ok(Thread { - vm: self, - index: thread_id, - }) + )?)) } /// Returns the number of threads that are running. pub fn num_threads(&self) -> usize { - self.threads.len() + self.0.num_threads() } /// Returns the thread with the given index. The index is between `0` and @@ -484,69 +244,41 @@ impl ProcessStateMachine { /// Returns `None` if the index is superior or equal to what /// [`num_threads`](ProcessStateMachine::num_threads) would return. pub fn thread(&mut self, index: usize) -> Option> { - if index < self.threads.len() { - Some(Thread { vm: self, index }) - } else { - None - } + Some(Thread(self.0.thread(index)?)) } /// Consumes this VM and returns all the remaining threads' user datas. pub fn into_user_datas(self) -> impl ExactSizeIterator { - self.threads.into_iter().map(|thread| thread.user_data) + self.0.into_user_datas() } /// Copies the given memory range into a `Vec`. /// /// Returns an error if the range is invalid or out of range. pub fn read_memory(&self, offset: u32, size: u32) -> Result, ()> { - let mem = match self.memory.as_ref() { - Some(m) => m, - None => unreachable!(), - }; - - mem.get(offset, size.try_into().map_err(|_| ())?) - .map_err(|_| ()) + self.0.read_memory(offset, size) } /// Write the data at the given memory location. /// /// Returns an error if the range is invalid or out of range. pub fn write_memory(&mut self, offset: u32, value: &[u8]) -> Result<(), ()> { - let mem = match self.memory.as_ref() { - Some(m) => m, - None => unreachable!(), - }; - - mem.set(offset, value).map_err(|_| ()) + self.0.write_memory(offset, value) } } -// The fields related to `wasmi` do not implement `Send` because they use `std::rc::Rc`. `Rc` -// does not implement `Send` because incrementing/decrementing the reference counter from -// multiple threads simultaneously would be racy. It is however perfectly sound to move all the -// instances of `Rc`s at once between threads, which is what we're doing here. -// -// This importantly means that we should never return a `Rc` (even by reference) across the API -// boundary. -// TODO: really annoying to have to use unsafe code -unsafe impl Send for ProcessStateMachine {} - -impl fmt::Debug for ProcessStateMachine -where - T: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_list().entries(self.threads.iter()).finish() +impl From> for ProcessStateMachine { + fn from(t: ImpStateMachine) -> Self { + Self(t) } } -impl fmt::Debug for ThreadState +impl fmt::Debug for ProcessStateMachine where T: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_tuple("Thread").field(&self.user_data).finish() + fmt::Debug::fmt(&self.0, f) } } @@ -557,103 +289,8 @@ impl<'a, T> Thread<'a, T> { /// a value of `None`. /// If, however, you call this function after a previous call to [`run`](Thread::run) that was /// interrupted by an external function call, then you must pass back the outcome of that call. - pub fn run(mut self, value: Option) -> Result, RunErr> { - struct DummyExternals; - impl wasmi::Externals for DummyExternals { - fn invoke_index( - &mut self, - index: usize, - args: wasmi::RuntimeArgs, - ) -> Result, wasmi::Trap> { - Err(wasmi::TrapKind::Host(Box::new(Interrupt { - index, - args: args.as_ref().to_vec(), - })) - .into()) - } - } - - #[derive(Debug)] - struct Interrupt { - index: usize, - args: Vec, - } - impl fmt::Display for Interrupt { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Interrupt") - } - } - impl wasmi::HostError for Interrupt {} - - if self.vm.is_poisoned { - return Err(RunErr::Poisoned); - } - - let thread_state = &mut self.vm.threads[self.index]; - - let mut execution = match thread_state.execution.take() { - Some(e) => e, - None => unreachable!(), - }; - let result = if thread_state.interrupted { - let expected_ty = execution.resumable_value_type().map(ValueType::from); - let obtained_ty = value.as_ref().map(|v| v.ty()); - if expected_ty != obtained_ty { - return Err(RunErr::BadValueTy { - expected: expected_ty, - obtained: obtained_ty, - }); - } - execution.resume_execution(value.map(From::from), &mut DummyExternals) - } else { - if value.is_some() { - return Err(RunErr::BadValueTy { - expected: None, - obtained: value.as_ref().map(|v| v.ty()), - }); - } - thread_state.interrupted = true; - execution.start_execution(&mut DummyExternals) - }; - - match result { - Ok(return_value) => { - let user_data = self.vm.threads.remove(self.index).user_data; - // If this is the "main" function, the state machine is now poisoned. - if self.index == 0 { - self.vm.is_poisoned = true; - } - Ok(ExecOutcome::ThreadFinished { - thread_index: self.index, - return_value: return_value.map(From::from), - user_data, - }) - } - Err(wasmi::ResumableError::AlreadyStarted) => unreachable!(), - Err(wasmi::ResumableError::NotResumable) => unreachable!(), - Err(wasmi::ResumableError::Trap(ref trap)) if trap.kind().is_host() => { - let interrupt: &Interrupt = match trap.kind() { - wasmi::TrapKind::Host(err) => match err.downcast_ref() { - Some(e) => e, - None => unreachable!(), - }, - _ => unreachable!(), - }; - thread_state.execution = Some(execution); - Ok(ExecOutcome::Interrupted { - thread: self, - id: interrupt.index, - params: interrupt.args.iter().map(|v| From::from(*v)).collect(), - }) - } - Err(wasmi::ResumableError::Trap(trap)) => { - self.vm.is_poisoned = true; - Ok(ExecOutcome::Errored { - thread: self, - error: trap, - }) - } - } + pub fn run(self, value: Option) -> Result, RunErr> { + self.0.run(value) } /// Returns the index of the thread, so that you can retreive the thread later by calling @@ -661,17 +298,23 @@ impl<'a, T> Thread<'a, T> { /// /// Keep in mind that when a thread finishes, all the indices above its index shift by one. pub fn index(&self) -> usize { - self.index + self.0.index() } /// Returns the user data associated to that thread. pub fn user_data(&mut self) -> &mut T { - &mut self.vm.threads[self.index].user_data + self.0.user_data() } /// Turns this thread into the user data associated to it. pub fn into_user_data(self) -> &'a mut T { - &mut self.vm.threads[self.index].user_data + self.0.into_user_data() + } +} + +impl<'a, T> From> for Thread<'a, T> { + fn from(t: ImpThread<'a, T>) -> Self { + Self(t) } } @@ -680,7 +323,7 @@ where T: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(&self.vm.threads[self.index], f) + fmt::Debug::fmt(&self.0, f) } } diff --git a/core/src/scheduler/vm/interpreter.rs b/core/src/scheduler/vm/interpreter.rs new file mode 100644 index 000000000..3e73f0676 --- /dev/null +++ b/core/src/scheduler/vm/interpreter.rs @@ -0,0 +1,540 @@ +// Copyright (C) 2019-2020 Pierre Krieger +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use super::{ExecOutcome, NewErr, RunErr, StartErr}; +use crate::module::Module; + +use alloc::{ + borrow::{Cow, ToOwned as _}, + boxed::Box, + format, + vec::Vec, +}; +use core::{cell::RefCell, convert::TryInto, fmt}; +use smallvec::SmallVec; + +pub struct Interpreter { + /// Original module, with resolved imports. + module: wasmi::ModuleRef, + + /// Memory of the module instantiation. + /// + /// Right now we only support one unique `Memory` object per process. This is it. + /// Contains `None` if the process doesn't export any memory object, which means it doesn't use + /// any memory. + memory: Option, + + /// Table of the indirect function calls. + /// + /// In WASM, function pointers are in reality indices in a table called + /// `__indirect_function_table`. This is this table, if it exists. + indirect_table: Option, + + /// List of threads that this process is running. + threads: SmallVec<[ThreadState; 4]>, + + /// If true, the state machine is in a poisoned state and cannot run any code anymore. + is_poisoned: bool, +} + +/// State of a single thread within the VM. +struct ThreadState { + /// Execution context of this thread. This notably holds the program counter, state of the + /// stack, and so on. + /// + /// This field is an `Option` because we need to be able to temporarily extract it. It must + /// always be `Some`. + execution: Option>, + + /// If false, then one must call `execution.start_execution()` instead of `resume_execution()`. + /// This is a particularity of the WASM interpreter that we don't want to expose in our API. + interrupted: bool, + + /// Opaque user data associated with the thread. + user_data: T, +} + +/// Access to a thread within the virtual machine. +pub struct Thread<'a, T> { + /// Reference to the parent object. + vm: &'a mut Interpreter, + + // Index within [`Interpreter::threads`] of the thread we are referencing. + index: usize, +} + +impl Interpreter { + /// Creates a new process state machine from the given module. + /// + /// The closure is called for each import that the module has. It must assign a number to each + /// import, or return an error if the import can't be resolved. When the VM calls one of these + /// functions, this number will be returned back in order for the user to know how to handle + /// the call. + /// + /// A single main thread (whose user data is passed by parameter) is automatically created and + /// is paused at the start of the "_start" function of the module. + pub fn new( + module: &Module, + main_thread_user_data: T, + mut symbols: impl FnMut(&str, &str, &wasmi::Signature) -> Result, + ) -> Result { + struct ImportResolve<'a> { + func: RefCell<&'a mut dyn FnMut(&str, &str, &Signature) -> Result>, + memory: RefCell<&'a mut Option>, + } + + impl<'a> wasmi::ImportResolver for ImportResolve<'a> { + fn resolve_func( + &self, + module_name: &str, + field_name: &str, + signature: &wasmi::Signature, + ) -> Result { + let closure = &mut **self.func.borrow_mut(); + let index = match closure(module_name, field_name, &From::from(signature)) { + Ok(i) => i, + Err(_) => { + return Err(wasmi::Error::Instantiation(format!( + "Couldn't resolve `{}`:`{}`", + module_name, field_name + ))) + } + }; + + Ok(wasmi::FuncInstance::alloc_host(signature.clone(), index)) + } + + fn resolve_global( + &self, + _module_name: &str, + _field_name: &str, + _global_type: &wasmi::GlobalDescriptor, + ) -> Result { + Err(wasmi::Error::Instantiation( + "Importing globals is not supported yet".to_owned(), + )) + } + + fn resolve_memory( + &self, + _module_name: &str, + _field_name: &str, + memory_type: &wasmi::MemoryDescriptor, + ) -> Result { + let mut mem = self.memory.borrow_mut(); + if mem.is_some() { + return Err(wasmi::Error::Instantiation( + "Only one memory object is supported yet".to_owned(), + )); + } + + let new_mem = wasmi::MemoryInstance::alloc( + wasmi::memory_units::Pages(usize::try_from(memory_type.initial()).unwrap()), + memory_type + .maximum() + .map(|p| wasmi::memory_units::Pages(usize::try_from(p).unwrap())), + ) + .unwrap(); + **mem = Some(new_mem.clone()); + Ok(new_mem) + } + + fn resolve_table( + &self, + _module_name: &str, + _field_name: &str, + _table_type: &wasmi::TableDescriptor, + ) -> Result { + Err(wasmi::Error::Instantiation( + "Importing tables is not supported yet".to_owned(), + )) + } + } + + let (not_started, imported_memory) = { + let mut imported_memory = None; + let resolve = ImportResolve { + func: RefCell::new(&mut symbols), + memory: RefCell::new(&mut imported_memory), + }; + let not_started = wasmi::ModuleInstance::new(module.as_ref(), &resolve) + .map_err(NewErr::Interpreter)?; + (not_started, imported_memory) + }; + + // TODO: WASM has a special "start" instruction that can be used to designate a function + // that must be executed before the module is considered initialized. It is unclear whether + // this is intended to be a function that for example initializes global variables, or if + // this is an equivalent of "_start". In practice, Rust never seems to generate such as + // "start" instruction, so for now we ignore it. The code below panics if there is such + // a "start" item, so we will fortunately not blindly run into troubles. + let module = not_started.assert_no_start(); + + let memory = if let Some(imported_mem) = imported_memory { + if module + .export_by_name("memory") + .map_or(false, |m| m.as_memory().is_some()) + { + return Err(NewErr::MultipleMemoriesNotSupported); + } + Some(imported_mem) + } else if let Some(mem) = module.export_by_name("memory") { + if let Some(mem) = mem.as_memory() { + Some(mem.clone()) + } else { + return Err(NewErr::MemoryIsntMemory); + } + } else { + None + }; + + let indirect_table = if let Some(tbl) = module.export_by_name("__indirect_function_table") { + if let Some(tbl) = tbl.as_table() { + Some(tbl.clone()) + } else { + return Err(NewErr::IndirectTableIsntTable); + } + } else { + None + }; + + let mut state_machine = Interpreter { + module, + memory, + indirect_table, + is_poisoned: false, + threads: SmallVec::new(), + }; + + // Try to start executing `_start` or `main`. + // TODO: executing `main` is a hack right now in order to support wasm32-unknown-unknown which doesn't have + // a `_start` function + match state_machine.start_thread_by_name("_start", &[][..], main_thread_user_data) { + Ok(_) => {} + Err((StartErr::FunctionNotFound, user_data)) => { + static ARGC_ARGV: [wasmi::RuntimeValue; 2] = + [wasmi::RuntimeValue::I32(0), wasmi::RuntimeValue::I32(0)]; + match state_machine.start_thread_by_name("main", &ARGC_ARGV[..], user_data) { + Ok(_) => {} + Err((StartErr::FunctionNotFound, _)) => return Err(NewErr::StartNotFound), + Err((StartErr::Poisoned, _)) => unreachable!(), + Err((StartErr::NotAFunction, _)) => return Err(NewErr::StartIsntAFunction), + } + } + Err((StartErr::Poisoned, _)) => unreachable!(), + Err((StartErr::NotAFunction, _)) => return Err(NewErr::StartIsntAFunction), + }; + + Ok(state_machine) + } + + /// Returns true if the state machine is in a poisoned state and cannot run anymore. + pub fn is_poisoned(&self) -> bool { + self.is_poisoned + } + + /// Starts executing a function. Immediately pauses the execution and puts it in an + /// interrupted state. + /// + /// You should call [`run`](Thread::run) afterwards with a value of `None`. + /// + /// > **Note**: The "function ID" is the index of the function in the WASM module. WASM + /// > doesn't have function pointers. Instead, all the functions are part of a single + /// > global array of functions. + pub fn start_thread_by_id( + &mut self, + function_id: u32, + params: impl Into>, + user_data: T, + ) -> Result, StartErr> { + if self.is_poisoned { + return Err(StartErr::Poisoned); + } + + // Find the function within the process. + let function = self + .indirect_table + .as_ref() + .and_then(|t| t.get(function_id).ok()) + .and_then(|f| f) + .ok_or(StartErr::FunctionNotFound)?; + + let execution = match wasmi::FuncInstance::invoke_resumable(&function, params) { + Ok(e) => e, + Err(err) => unreachable!("{:?}", err), + }; + + self.threads.push(ThreadState { + execution: Some(execution), + interrupted: false, + user_data, + }); + + let thread_id = self.threads.len() - 1; + Ok(Thread { + vm: self, + index: thread_id, + }) + } + + /// Same as [`start_thread_by_id`](Interpreter::start_thread_by_id), but executes a + /// symbol by name. + fn start_thread_by_name( + &mut self, + symbol_name: &str, + params: impl Into>, + user_data: T, + ) -> Result, (StartErr, T)> { + if self.is_poisoned { + return Err((StartErr::Poisoned, user_data)); + } + + match self.module.export_by_name(symbol_name) { + Some(wasmi::ExternVal::Func(f)) => { + let execution = match wasmi::FuncInstance::invoke_resumable(&f, params) { + Ok(e) => e, + Err(err) => unreachable!("{:?}", err), + }; + self.threads.push(ThreadState { + execution: Some(execution), + interrupted: false, + user_data, + }); + } + None => return Err((StartErr::FunctionNotFound, user_data)), + _ => return Err((StartErr::NotAFunction, user_data)), + } + + let thread_id = self.threads.len() - 1; + Ok(Thread { + vm: self, + index: thread_id, + }) + } + + /// Returns the number of threads that are running. + pub fn num_threads(&self) -> usize { + self.threads.len() + } + + /// Returns the thread with the given index. The index is between `0` and + /// [`num_threads`](Interpreter::num_threads). + /// + /// The main thread is always index `0`, unless it has terminated. + /// + /// Keep in mind that when a thread finishes, all the indices above its index shift by one. + /// + /// Returns `None` if the index is superior or equal to what + /// [`num_threads`](Interpreter::num_threads) would return. + pub fn thread(&mut self, index: usize) -> Option> { + if index < self.threads.len() { + Some(Thread { vm: self, index }) + } else { + None + } + } + + /// Consumes this VM and returns all the remaining threads' user datas. + pub fn into_user_datas(self) -> impl ExactSizeIterator { + self.threads.into_iter().map(|thread| thread.user_data) + } + + /// Copies the given memory range into a `Vec`. + /// + /// Returns an error if the range is invalid or out of range. + pub fn read_memory(&self, offset: u32, size: u32) -> Result, ()> { + let mem = match self.memory.as_ref() { + Some(m) => m, + None => unreachable!(), + }; + + mem.get(offset, size.try_into().map_err(|_| ())?) + .map_err(|_| ()) + } + + /// Write the data at the given memory location. + /// + /// Returns an error if the range is invalid or out of range. + pub fn write_memory(&mut self, offset: u32, value: &[u8]) -> Result<(), ()> { + let mem = match self.memory.as_ref() { + Some(m) => m, + None => unreachable!(), + }; + + mem.set(offset, value).map_err(|_| ()) + } +} + +// The fields related to `wasmi` do not implement `Send` because they use `std::rc::Rc`. `Rc` +// does not implement `Send` because incrementing/decrementing the reference counter from +// multiple threads simultaneously would be racy. It is however perfectly sound to move all the +// instances of `Rc`s at once between threads, which is what we're doing here. +// +// This importantly means that we should never return a `Rc` (even by reference) across the API +// boundary. +// TODO: really annoying to have to use unsafe code +unsafe impl Send for Interpreter {} + +impl fmt::Debug for Interpreter +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_list().entries(self.threads.iter()).finish() + } +} + +impl fmt::Debug for ThreadState +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("Thread").field(&self.user_data).finish() + } +} + +impl<'a, T> Thread<'a, T> { + /// Starts or continues execution of this thread. + /// + /// If this is the first call you call [`run`](Thread::run) for this thread, then you must pass + /// a value of `None`. + /// If, however, you call this function after a previous call to [`run`](Thread::run) that was + /// interrupted by an external function call, then you must pass back the outcome of that call. + pub fn run(mut self, value: Option) -> Result, RunErr> { + struct DummyExternals; + impl wasmi::Externals for DummyExternals { + fn invoke_index( + &mut self, + index: usize, + args: wasmi::RuntimeArgs, + ) -> Result, wasmi::Trap> { + Err(wasmi::TrapKind::Host(Box::new(Interrupt { + index, + args: args.as_ref().to_vec(), + })) + .into()) + } + } + + #[derive(Debug)] + struct Interrupt { + index: usize, + args: Vec, + } + impl fmt::Display for Interrupt { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Interrupt") + } + } + impl wasmi::HostError for Interrupt {} + + if self.vm.is_poisoned { + return Err(RunErr::Poisoned); + } + + let thread_state = &mut self.vm.threads[self.index]; + + let mut execution = match thread_state.execution.take() { + Some(e) => e, + None => unreachable!(), + }; + let result = if thread_state.interrupted { + let expected_ty = execution.resumable_value_type(); + let obtained_ty = value.as_ref().map(|v| v.value_type()); + if expected_ty != obtained_ty { + return Err(RunErr::BadValueTy { + expected: expected_ty, + obtained: obtained_ty, + }); + } + execution.resume_execution(value, &mut DummyExternals) + } else { + if value.is_some() { + return Err(RunErr::BadValueTy { + expected: None, + obtained: value.as_ref().map(|v| v.value_type()), + }); + } + thread_state.interrupted = true; + execution.start_execution(&mut DummyExternals) + }; + + match result { + Ok(return_value) => { + let user_data = self.vm.threads.remove(self.index).user_data; + // If this is the "main" function, the state machine is now poisoned. + if self.index == 0 { + self.vm.is_poisoned = true; + } + Ok(ExecOutcome::ThreadFinished { + thread_index: self.index, + return_value, + user_data, + }) + } + Err(wasmi::ResumableError::AlreadyStarted) => unreachable!(), + Err(wasmi::ResumableError::NotResumable) => unreachable!(), + Err(wasmi::ResumableError::Trap(ref trap)) if trap.kind().is_host() => { + let interrupt: &Interrupt = match trap.kind() { + wasmi::TrapKind::Host(err) => match err.downcast_ref() { + Some(e) => e, + None => unreachable!(), + }, + _ => unreachable!(), + }; + thread_state.execution = Some(execution); + Ok(ExecOutcome::Interrupted { + thread: From::from(self), + id: interrupt.index, + params: interrupt.args.clone(), + }) + } + Err(wasmi::ResumableError::Trap(trap)) => { + self.vm.is_poisoned = true; + Ok(ExecOutcome::Errored { + thread: From::from(self), + error: trap, + }) + } + } + } + + /// Returns the index of the thread, so that you can retreive the thread later by calling + /// [`Interpreter::thread`]. + /// + /// Keep in mind that when a thread finishes, all the indices above its index shift by one. + pub fn index(&self) -> usize { + self.index + } + + /// Returns the user data associated to that thread. + pub fn user_data(&mut self) -> &mut T { + &mut self.vm.threads[self.index].user_data + } + + /// Turns this thread into the user data associated to it. + pub fn into_user_data(self) -> &'a mut T { + &mut self.vm.threads[self.index].user_data + } +} + +impl<'a, T> fmt::Debug for Thread<'a, T> +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&self.vm.threads[self.index], f) + } +} diff --git a/core/src/scheduler/vm/jit.rs b/core/src/scheduler/vm/jit.rs new file mode 100644 index 000000000..c18f275e7 --- /dev/null +++ b/core/src/scheduler/vm/jit.rs @@ -0,0 +1,362 @@ +// Copyright (C) 2019-2020 Pierre Krieger +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use super::{ExecOutcome, NewErr, RunErr, StartErr}; +use crate::{module::Module, primitives::Signature, WasmValue}; + +use alloc::{boxed::Box, rc::Rc, vec::Vec}; +use core::{cell::RefCell, convert::TryFrom as _, fmt}; + +mod coroutine; + +/// Wasm VM that uses JITted compilation. +pub struct Jit { + /// Coroutine that contains the Wasm execution stack. + main_thread: coroutine::Coroutine< + Box Result, wasmtime::Trap>>, + Interrupt, + Option, + >, + + /// Reference to the memory, in case we need to access it. + /// `None` if the module doesn't export its memory. + memory: Option, + + /// Reference to the table of indirect functions, in case we need to access it. + /// `None` if the module doesn't export such table. + indirect_table: Option, + + /// We only support one thread. That's its user data. Contains `None` if the main thread + /// has terminated. + thread_user_data: Option, +} + +/// Type yielded by a thread's coroutine. +enum Interrupt { + /// Reports how well the initialization went. Must only be sent once, at initialization. + Init(Result<(), NewErr>), + /// Execution of the Wasm code has been interrupted by a call. + Interrupt { + /// Index of the function, to put in [`ExecOutcome::Interrupted::id`]. + function_index: usize, + /// Parameters of the function. + parameters: Vec, + }, +} + +/// Access to a thread within the virtual machine. +pub struct Thread<'a, T> { + /// Reference to the parent object. + vm: &'a mut Jit, +} + +impl Jit { + /// Creates a new process state machine from the given module. + /// + /// The closure is called for each import that the module has. It must assign a number to each + /// import, or return an error if the import can't be resolved. When the VM calls one of these + /// functions, this number will be returned back in order for the user to know how to handle + /// the call. + /// + /// A single main thread (whose user data is passed by parameter) is automatically created and + /// is paused at the start of the "_start" function of the module. + pub fn new( + module: &Module, + main_thread_user_data: T, + mut symbols: impl FnMut(&str, &str, &Signature) -> Result, + ) -> Result { + let engine = wasmtime::Engine::new(&Default::default()); + let store = wasmtime::Store::new(&engine); + let module = wasmtime::Module::from_binary(&store, module.as_ref()).unwrap(); + + let builder = coroutine::CoroutineBuilder::new(); + + // Building the list of symbols that the Wasm VM is able to use. + let imports = { + let mut imports = Vec::with_capacity(module.imports().len()); + for import in module.imports() { + match import.ty() { + wasmtime::ExternType::Func(f) => { + // TODO: don't panic if not found + let function_index = + symbols(import.module(), import.name(), &From::from(f)).unwrap(); + let interrupter = builder.interrupter(); + imports.push(wasmtime::Extern::Func(wasmtime::Func::new( + &store, + f.clone(), + move |_, params, ret_val| { + // This closure is executed whenever the Wasm VM calls an external function. + let returned = interrupter.interrupt(Interrupt::Interrupt { + function_index, + parameters: params.iter().cloned().map(From::from).collect(), + }); + if let Some(returned) = returned { + assert_eq!(ret_val.len(), 1); + ret_val[0] = From::from(returned); + } else { + assert!(ret_val.is_empty()); + } + Ok(()) + }, + ))); + } + wasmtime::ExternType::Global(_) => unimplemented!(), + wasmtime::ExternType::Table(_) => unimplemented!(), + wasmtime::ExternType::Memory(_) => unimplemented!(), + }; + } + imports + }; + + // These objects will be filled by the memory and indirect table during the first call to + // the coroutine below. + let memory = Rc::new(RefCell::new(None)); + let indirect_table = Rc::new(RefCell::new(None)); + + // We now build the coroutine of the main thread. + // + // After building the coroutine, we will execute it one time. During this initial + // execution, the instance is initialized and all the symbols exported. The coroutine + // must then yield an `Interrupted::Init` reporting if everything is ok. + let mut main_thread = { + let memory = memory.clone(); + let indirect_table = indirect_table.clone(); + + let interrupter = builder.interrupter(); + builder.build(Box::new(move || { + // TODO: don't unwrap + let instance = wasmtime::Instance::new(&module, &imports).unwrap(); + + if let Some(mem) = instance.get_export("memory") { + if let Some(mem) = mem.memory() { + *memory.borrow_mut() = Some(mem.clone()); + } else { + let err = NewErr::MemoryIsntMemory; + interrupter.interrupt(Interrupt::Init(Err(err))); + return Ok(None); + } + } + + if let Some(tbl) = instance.get_export("__indirect_function_table") { + if let Some(tbl) = tbl.table() { + *indirect_table.borrow_mut() = Some(tbl.clone()); + } else { + let err = NewErr::IndirectTableIsntTable; + interrupter.interrupt(Interrupt::Init(Err(err))); + return Ok(None); + } + } + + // Try to start executing `_start`. + let start_function = if let Some(f) = instance.get_export("_start") { + if let Some(f) = f.func() { + f.clone() + } else { + let err = NewErr::StartIsntAFunction; + interrupter.interrupt(Interrupt::Init(Err(err))); + return Ok(None); + } + } else { + let err = NewErr::StartNotFound; + interrupter.interrupt(Interrupt::Init(Err(err))); + return Ok(None); + }; + + // Report back that everything went ok. + let reinjected: Option = interrupter.interrupt(Interrupt::Init(Ok(()))); + assert!(reinjected.is_none()); // First call to run must always be with `None`. + + // Now running the `start` function of the Wasm code. + // This will interrupt the coroutine every time we reach an external function. + let result = start_function.call(&[])?; + + // Execution resumes here when the Wasm code has gracefully finished. + assert!(result.len() == 0 || result.len() == 1); // TODO: I don't know what multiple results means + if result.is_empty() { + Ok(None) + } else { + Ok(Some(result[0].clone())) // TODO: don't clone? + } + }) as Box<_>) + }; + + // Execute the coroutine once, as described above. + // The first yield must always be an `Interrupt::Init`. + match main_thread.run(None) { + coroutine::RunOut::Interrupted(Interrupt::Init(Err(err))) => return Err(err), + coroutine::RunOut::Interrupted(Interrupt::Init(Ok(()))) => {} + _ => unreachable!(), + } + + // The first execution has filled these objects. + let memory = memory.borrow_mut().clone(); + let indirect_table = indirect_table.borrow_mut().clone(); + + Ok(Jit { + memory, + indirect_table, + main_thread, + thread_user_data: Some(main_thread_user_data), + }) + } + + /// Returns true if the state machine is in a poisoned state and cannot run anymore. + pub fn is_poisoned(&self) -> bool { + self.main_thread.is_finished() + } + + pub fn start_thread_by_id( + &mut self, + _: u32, + _: impl IntoIterator, + _: T, + ) -> Result, StartErr> { + unimplemented!() + } + + /// Returns the number of threads that are running. + pub fn num_threads(&self) -> usize { + 1 + } + + pub fn thread(&mut self, index: usize) -> Option> { + if index == 0 && !self.is_poisoned() { + Some(Thread { vm: self }) + } else { + None + } + } + + pub fn into_user_datas(self) -> impl ExactSizeIterator { + self.thread_user_data.into_iter() + } + + /// Copies the given memory range into a `Vec`. + /// + /// Returns an error if the range is invalid or out of range. + pub fn read_memory(&self, offset: u32, size: u32) -> Result, ()> { + let mem = self.memory.as_ref().ok_or(())?; + let start = usize::try_from(offset).map_err(|_| ())?; + let end = start + .checked_add(usize::try_from(size).map_err(|_| ())?) + .ok_or(())?; + + // Soundness: the documentation of wasmtime precisely explains what is safe or not. + // Basically, we are safe as long as we are sure that we don't potentially grow the + // buffer (which would invalidate the buffer pointer). + unsafe { Ok(mem.data_unchecked()[start..end].to_vec()) } + } + + /// Write the data at the given memory location. + /// + /// Returns an error if the range is invalid or out of range. + pub fn write_memory(&mut self, offset: u32, value: &[u8]) -> Result<(), ()> { + let mem = self.memory.as_ref().ok_or(())?; + let start = usize::try_from(offset).map_err(|_| ())?; + let end = start.checked_add(value.len()).ok_or(())?; + + // Soundness: the documentation of wasmtime precisely explains what is safe or not. + // Basically, we are safe as long as we are sure that we don't potentially grow the + // buffer (which would invalidate the buffer pointer). + unsafe { + mem.data_unchecked_mut()[start..end].copy_from_slice(value); + } + + Ok(()) + } +} + +// TODO: explain how this is sound +unsafe impl Send for Jit {} + +impl fmt::Debug for Jit +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("Jit").finish() + } +} + +impl<'a, T> Thread<'a, T> { + /// Starts or continues execution of this thread. + /// + /// If this is the first call you call [`run`](Thread::run) for this thread, then you must pass + /// a value of `None`. + /// If, however, you call this function after a previous call to [`run`](Thread::run) that was + /// interrupted by an external function call, then you must pass back the outcome of that call. + pub fn run(self, value: Option) -> Result, RunErr> { + if self.vm.main_thread.is_finished() { + return Err(RunErr::Poisoned); + } + + // TODO: check value type + + // Resume the coroutine execution. + match self.vm.main_thread.run(Some(value.map(From::from))) { + coroutine::RunOut::Finished(Err(err)) => { + Ok(ExecOutcome::Errored { + thread: From::from(self), + error: unimplemented!(), // TODO: err, + }) + } + coroutine::RunOut::Finished(Ok(val)) => Ok(ExecOutcome::ThreadFinished { + thread_index: 0, + return_value: val.map(From::from), + user_data: self.vm.thread_user_data.take().unwrap(), + }), + coroutine::RunOut::Interrupted(Interrupt::Interrupt { + function_index, + parameters, + }) => Ok(ExecOutcome::Interrupted { + thread: From::from(self), + id: function_index, + params: parameters, + }), + + // `Init` must only be produced at initialization. + coroutine::RunOut::Interrupted(Interrupt::Init(_)) => unreachable!(), + } + } + + /// Returns the index of the thread, so that you can retreive the thread later by calling + /// [`Jit::thread`]. + /// + /// Keep in mind that when a thread finishes, all the indices above its index shift by one. + pub fn index(&self) -> usize { + 0 + } + + /// Returns the user data associated to that thread. + pub fn user_data(&mut self) -> &mut T { + self.vm.thread_user_data.as_mut().unwrap() + } + + /// Turns this thread into the user data associated to it. + pub fn into_user_data(self) -> &'a mut T { + self.vm.thread_user_data.as_mut().unwrap() + } +} + +impl<'a, T> fmt::Debug for Thread<'a, T> +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("Thread") + .field(&self.vm.thread_user_data) + .finish() + } +} diff --git a/core/src/scheduler/vm/jit/coroutine.rs b/core/src/scheduler/vm/jit/coroutine.rs new file mode 100644 index 000000000..eddd513e1 --- /dev/null +++ b/core/src/scheduler/vm/jit/coroutine.rs @@ -0,0 +1,367 @@ +// Copyright (C) 2019-2020 Pierre Krieger +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use alloc::rc::Rc; +use core::{cell::Cell, marker::PhantomData, pin::Pin}; + +// Documentation about the x86_64 ABI here: https://github.com/hjl-tools/x86-psABI/wiki/X86-psABI + +// TODO: make it works on 32bits +// TODO: the UnwindSafe trait should be enforced but isn't available in core +// TODO: require Send trait for the closure? it's a bit tricky, need to look into details +// TODO: will leak heap-allocated stuff if Dropped before it's finished + +/// Prototype for a [`Coroutine`]. +pub struct CoroutineBuilder { + stack_size: usize, + state: Rc>, +} + +/// Runnable coroutine. +pub struct Coroutine { + /// We use a `u128` because the stack must be 16-bytes-aligned. + stack: Pin>, + /// State shared between the coroutine and the interrupters. + state: Rc>, + /// True if the coroutine has been run at least once. + has_run_once: bool, + /// True if the coroutine has finished. Trying to resume running will panic. + has_finished: bool, + marker: PhantomData, +} + +/// Object whose intent is to be stored in the closure and that is capable of interrupting +/// execution. +pub struct Interrupter { + state: Rc>, +} + +struct Shared { + /// Value to put in `rsp` in order to resume the coroutine. + /// + /// Must point somewhere within [`Coroutine::stack`]. Will contain null before initialization. + /// + /// Must point to a memory location that contains the values of the following registers in + /// order: r15, r14, r13, r12, rbp, rbx, rsi, rip + /// + /// In order to resume the coroutine, set `rsp` to this value then pop all the registers. + coroutine_stack_pointer: Cell, + + /// Stack pointer of the caller. Only valid if we are within the coroutine. + caller_stack_pointer: Cell, + + /// Where to write the return value. + potential_return_value_ptr: Cell, + /// Storage where to write the value yielded to outside the coroutine before jumping out, + /// or left to `None` if the coroutine has terminated. + interrupt_val: Cell>, + + /// Storage where to write the value yielded back *to* the coroutine before resuming + /// execution. + resume_value: Cell>, +} + +impl Default for CoroutineBuilder { + fn default() -> Self { + Self::new() + } +} + +impl CoroutineBuilder { + /// Starts a builder. + pub fn new() -> Self { + CoroutineBuilder { + // TODO: no stack protection :( + stack_size: 4 * 1024 * 1024, + state: Rc::new(Shared { + coroutine_stack_pointer: Cell::new(0), + caller_stack_pointer: Cell::new(0), + potential_return_value_ptr: Cell::new(0), + interrupt_val: Cell::new(None), + resume_value: Cell::new(None), + }), + } + } + + /// Creates a new [`Interrupter`] to store within the closure1. + // TODO: it's super unsafe to use an interrupter with a different closure than the one passed + // to build + pub fn interrupter(&self) -> Interrupter { + Interrupter { + state: self.state.clone(), + } + } + + /// Builds the coroutine. + pub fn build TRet>( + self, + to_exec: TExec, + ) -> Coroutine { + let to_actually_exec = Box::into_raw({ + let state = self.state.clone(); + // TODO: panic safety handling here + let closure = Box::new(move |caller_stack_pointer| { + state.caller_stack_pointer.set(caller_stack_pointer); + let ret_value = to_exec(); + let ptr = + unsafe { &mut *(state.potential_return_value_ptr.get() as *mut Option) }; + assert!(ptr.is_none()); + *ptr = Some(ret_value); + unsafe { + coroutine_switch_stack(state.caller_stack_pointer.get()); + core::hint::unreachable_unchecked() + } + }) as Box; + Box::new(closure) as Box> + }); + + let mut stack = Pin::new(unsafe { + let stack = Box::new_uninit_slice(1 + (self.stack_size.checked_sub(1).unwrap() / 16)); + stack.assume_init() + }); + + unsafe { + // Starting the stack of top, we virtually push a fake return address, plus our 8 saved + // registers. + // The fake return address is important is order to guarantee the proper alignment of + // the stack. + let stack_top = (stack.as_mut_ptr() as *mut u64) + .add(self.stack_size / 8) + .sub(9); + + stack_top.add(6).write(to_actually_exec as usize as u64); // RSI + let rip = start_call as extern "C" fn(usize, usize) as usize as u64; + stack_top.add(7).write(rip); + stack_top.add(8).write(0); + + self.state.coroutine_stack_pointer.set(stack_top as usize); + } + + Coroutine { + stack, + state: self.state.clone(), + has_run_once: false, + has_finished: false, + marker: PhantomData, + } + } +} + +/// Return value of [`Coroutine::run`]. +pub enum RunOut { + /// The coroutine has finished. Contains the return value of the closure. + Finished(TRet), + /// The coroutine has called [`Interrupter::interrupt`] + Interrupted(TInt), +} + +impl TRet, TRet, TInt, TRes> Coroutine { + /// Returns true if running the closure has produced a [`RunOut::Finished`] earlier. + pub fn is_finished(&self) -> bool { + self.has_finished + } + + /// Runs the coroutine until it finishes or is interrupted. + /// + /// `resume` must be `None` the first time a coroutine is run, then must be `Some` with the + /// value to reinject back as the return value of [`Interrupter::interrupt`]. + /// + /// # Panic + /// + /// Panics if [`RunOut::Finished`] has been returned earlier. + /// Panics if `None` is passed and it is not the first time the coroutine is being run. + /// Panics if `Some` is passed and it is the first time the coroutine is being run. + /// + pub fn run(&mut self, resume: Option) -> RunOut { + assert!(!self.has_finished); + + if !self.has_run_once { + assert!(resume.is_none()); + self.has_run_once = true; + } else { + assert!(resume.is_some()); + } + + // Store the resume value for the coroutine to pick up. + debug_assert!(self.state.resume_value.take().is_none()); + self.state.resume_value.set(resume); + + // We allocate some space where the coroutine is allowed to put its return value, and put + // a pointer to this space in `self.state`. + let mut potential_return_value = None::; + self.state + .potential_return_value_ptr + .set(&mut potential_return_value as *mut _ as usize); + + // Doing a jump to the coroutine, which will then jump back here once it interrupts or + // finishes. + let new_stack_ptr = + unsafe { coroutine_switch_stack(self.state.coroutine_stack_pointer.get()) }; + self.state.coroutine_stack_pointer.set(new_stack_ptr); + debug_assert!(self.state.resume_value.take().is_none()); + + // We determine whether the function has ended or is simply interrupted based on the + // content of `self.state`. + if let Some(interrupted) = self.state.interrupt_val.take() { + debug_assert!(potential_return_value.take().is_none()); + RunOut::Interrupted(interrupted) + } else { + self.has_finished = true; + RunOut::Finished(potential_return_value.take().unwrap()) + } + } +} + +impl Interrupter { + /// Interrupts the current execution flow and jumps back to the [`Coroutine::run`] function, + /// which will then return a [`RunOut::Interrupted`] containing the value passed as parameter. + pub fn interrupt(&self, val: TInt) -> TRes { + debug_assert!(self.state.interrupt_val.take().is_none()); + self.state.interrupt_val.set(Some(val)); + + let new_caller_ptr = + unsafe { coroutine_switch_stack(self.state.caller_stack_pointer.get()) }; + self.state.caller_stack_pointer.set(new_caller_ptr); + + self.state.resume_value.take().unwrap() + } +} + +impl Clone for Interrupter { + fn clone(&self) -> Self { + Interrupter { + state: self.state.clone(), + } + } +} + +/// Function whose role is to bootstrap the coroutine. +/// +/// `caller_stack_pointer` is the value produced by [`coroutine_switch_stack`], and [`to_exec`] +/// is a pointer to the closure to execute. The closure must never return. +// TODO: turn `Box` into `Box !>` when `!` is stable +extern "C" fn start_call(caller_stack_pointer: usize, to_exec: usize) { + unsafe { + let to_exec: Box> = + Box::from_raw(to_exec as *mut Box); + (*to_exec)(caller_stack_pointer); + core::hint::unreachable_unchecked() + } +} + +extern "C" { + /// Pushes the current processor's state on the stack, then sets the stack pointer to the + /// given value and pops the processor's state back. The "processor's state" includes the + /// %rip register, which means that we're essentially returning somewhere else than where this + /// function has been called. + /// + /// Before returning, this function sets the `%rax` and `%rdi` registers to the stack pointer + /// that contains the state of the caller. This is shown here as a return value, but it also + /// means that the poped `%rip` can be the entry point of a function that will thus accept + /// as first parameter the value of this stack pointer. + /// + /// The state of the caller can later be restored by calling this function again with the + /// value that it produced. + /// + /// Contrary to pre-emptive multitasking systems, we don't need to save the entire state. We + /// only need to save the registers that the caller expects to not change, as defined by the + /// ABI. + fn coroutine_switch_stack(stack: usize) -> usize; +} +// TODO: we would like to use a naked function in order to have mangled function name and possibly +// avoid collisions, but if we do so the compiler still inserts a `mov %rdi, ...` at the top. +global_asm! {r#" +.global coroutine_switch_stack +coroutine_switch_stack: + push %rsi + push %rbx + push %rbp + push %r12 + push %r13 + push %r14 + push %r15 + mov %rsp, %rax + mov %rdi, %rsp + mov %rax, %rdi + pop %r15 + pop %r14 + pop %r13 + pop %r12 + pop %rbp + pop %rbx + pop %rsi + ret +"#} + +#[cfg(test)] +mod tests { + use super::{CoroutineBuilder, RunOut}; + + #[test] + fn basic_works() { + let mut coroutine = CoroutineBuilder::<(), ()>::new().build(|| 12); + match coroutine.run(None) { + RunOut::Finished(12) => {} + _ => panic!(), + } + } + + #[test] + fn basic_interrupter() { + let builder = CoroutineBuilder::new(); + let interrupter = builder.interrupter(); + let mut coroutine = builder.build(|| { + let val = interrupter.interrupt(format!("hello world {}", 53)); + val + 12 + }); + + match coroutine.run(None) { + RunOut::Interrupted(val) => assert_eq!(val, "hello world 53"), + _ => panic!(), + } + + match coroutine.run(Some(5)) { + RunOut::Finished(17) => {} + _ => panic!(), + } + } + + #[test] + fn many_interruptions() { + let builder = CoroutineBuilder::new(); + let interrupter = builder.interrupter(); + let mut coroutine = builder.build(|| { + let mut val = 0; + for _ in 0..1000 { + val = interrupter.interrupt(format!("hello! {}", val)); + } + val + }); + + let mut val = None; + loop { + match coroutine.run(val) { + RunOut::Interrupted(v) => { + assert_eq!(v, format!("hello! {}", val.unwrap_or(0))); + val = Some(val.unwrap_or(0) + 1); + } + RunOut::Finished(v) => { + assert_eq!(v, val.unwrap()); + break; + } + } + } + } +}