diff --git a/Cargo.lock b/Cargo.lock index 871469522f..5339ea7b14 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -237,9 +237,9 @@ checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" [[package]] name = "cap-fs-ext" -version = "0.24.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16812cf8cc096ebfddba83ad6cc768635f4d53eb6bd060f7b80866556493874a" +checksum = "de96a353a1b625fae721c0274552533a75d4961e2f313d5a873076c964e9982e" dependencies = [ "cap-primitives", "cap-std", @@ -249,9 +249,9 @@ dependencies = [ [[package]] name = "cap-primitives" -version = "0.24.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef65294d0067dd0167a84e5d50aaa52d999ab4fc7cfa59ff2ad3906afb033b36" +checksum = "7217718088981caa36e35a01307daeeaad2f6e6e188bf4149d35029dad1c08a2" dependencies = [ "ambient-authority", "errno", @@ -268,9 +268,9 @@ dependencies = [ [[package]] name = "cap-rand" -version = "0.24.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a124986fcbe880abe9ca6aad4189de44b37dbcf6ac0079f8dd9f94b379484b94" +checksum = "575e96a49058d34b2d75caa6ef677d35569add0fcb16cf7866d1a47a35649a87" dependencies = [ "ambient-authority", "rand 0.8.5", @@ -278,9 +278,9 @@ dependencies = [ [[package]] name = "cap-std" -version = "0.24.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3430d1b5ba78fa381eb227525578d2b85d77b42ff49be85d1e72a94f305e603c" +checksum = "5d684df5773e4af5c343c466f47151db7e7a4366daab609b4a6bb7a75aecf732" dependencies = [ "cap-primitives", "io-extras", @@ -291,20 +291,21 @@ dependencies = [ [[package]] name = "cap-tempfile" -version = "0.24.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1857f2acd81cd0dafd729ec386032900863bc0c542188df48f761b6fab5bb41" +checksum = "1def2a81a97ba5f361944b55a96ab0ccf0b3b64bd829c12f20f4bf709ce2ab03" dependencies = [ "cap-std", "rand 0.8.5", - "uuid", + "rustix", + "uuid 1.0.0", ] [[package]] name = "cap-time-ext" -version = "0.24.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ee4390904645f384bd4e03125a4ed4d1167e9f195931c41323bacc8a2328ce" +checksum = "4ed053e759cc9bb1c2cbdb53d029c76c56820787a65619579b9a7147eaaf307b" dependencies = [ "cap-primitives", "once_cell", @@ -403,9 +404,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.1.12" +version = "3.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c167e37342afc5f33fd87bbc870cedd020d2a6dffa05d45ccd9241fbdd146db" +checksum = "85a35a599b11c089a7f49105658d089b8f2cf0882993c17daf6de15285c2c35d" dependencies = [ "atty", "bitflags", @@ -433,9 +434,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "189ddd3b5d32a70b35e7686054371742a937b0d99128e76dde6340210e966669" +checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213" dependencies = [ "os_str_bytes", ] @@ -678,7 +679,7 @@ dependencies = [ name = "cranelift-serde" version = "0.84.0" dependencies = [ - "clap 3.1.12", + "clap 3.1.15", "cranelift-codegen", "cranelift-reader", "serde_json", @@ -691,7 +692,7 @@ dependencies = [ "anyhow", "capstone", "cfg-if", - "clap 3.1.12", + "clap 3.1.15", "cranelift", "cranelift-codegen", "cranelift-entity", @@ -1457,7 +1458,7 @@ dependencies = [ name = "islec" version = "0.1.0" dependencies = [ - "clap 3.1.12", + "clap 3.1.15", "cranelift-isle", "env_logger 0.9.0", "miette", @@ -1540,9 +1541,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.124" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a41fed9d98f27ab1c6d161da622a4fa35e8a54a8adc24bbf3ddd0ef70b0e50" +checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" [[package]] name = "libfuzzer-sys" @@ -1584,7 +1585,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e514e2cb8a9624701346ea3e694c1766d76778e343e537d873c1c366e79a7" dependencies = [ "libc", - "uuid", + "uuid 0.8.2", "winapi", ] @@ -1600,9 +1601,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", ] @@ -1624,9 +1625,9 @@ checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" [[package]] name = "memchr" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memfd" @@ -1770,9 +1771,9 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg 1.1.0", "num-traits", @@ -1803,9 +1804,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg 1.1.0", "libm", @@ -2500,15 +2501,15 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "semver" -version = "1.0.7" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d65bd28f48be7196d222d95b9243287f48d27aca604e08497513019ff0502cc4" +checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd" [[package]] name = "serde" -version = "1.0.136" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" dependencies = [ "serde_derive", ] @@ -2525,9 +2526,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.136" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" dependencies = [ "proc-macro2", "quote", @@ -2536,9 +2537,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.79" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +checksum = "f972498cf015f7c0746cac89ebe1d6ef10c293b94175a243a2d9442c163d9944" dependencies = [ "itoa 1.0.1", "ryu", @@ -2710,9 +2711,9 @@ checksum = "7c68d531d83ec6c531150584c42a4290911964d5f0d79132b193b67252a23b71" [[package]] name = "syn" -version = "1.0.91" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d" +checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52" dependencies = [ "proc-macro2", "quote", @@ -2833,18 +2834,18 @@ checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" dependencies = [ "proc-macro2", "quote", @@ -2872,9 +2873,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.18.0" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f48b6d60512a392e34dbf7fd456249fd2de3c83669ab642e021903f4015185b" +checksum = "dce653fb475565de9f6fb0614b28bca8df2c430c0cf84bcd9c843f15de5414cc" dependencies = [ "bytes", "libc", @@ -2990,9 +2991,9 @@ checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" [[package]] name = "unicode-xid" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" [[package]] name = "universal-hash" @@ -3009,6 +3010,12 @@ name = "uuid" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" + +[[package]] +name = "uuid" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfcd319456c4d6ea10087ed423473267e1a071f3bc0aa89f80d60997843c6f0" dependencies = [ "getrandom 0.2.6", ] @@ -3348,8 +3355,10 @@ dependencies = [ "anyhow", "cap-std", "shuffling-allocator", + "target-lexicon", "wasi-cap-std-sync", "wasmtime", + "wasmtime-cli-flags", "wasmtime-wasi", "wasmtime-wasi-crypto", "wasmtime-wasi-nn", @@ -3408,10 +3417,9 @@ version = "0.37.0" dependencies = [ "anyhow", "async-trait", - "clap 3.1.12", + "clap 3.1.15", "criterion", "env_logger 0.9.0", - "file-per-thread-logger", "filecheck", "humantime 2.1.0", "lazy_static", @@ -3421,7 +3429,6 @@ dependencies = [ "more-asserts", "num_cpus", "once_cell", - "pretty_env_logger", "rayon", "rustix", "target-lexicon", @@ -3432,6 +3439,7 @@ dependencies = [ "wasmparser", "wasmtime", "wasmtime-cache", + "wasmtime-cli-flags", "wasmtime-cranelift", "wasmtime-environ", "wasmtime-runtime", @@ -3444,6 +3452,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "wasmtime-cli-flags" +version = "0.37.0" +dependencies = [ + "anyhow", + "clap 3.1.15", + "file-per-thread-logger", + "pretty_env_logger", + "rayon", + "wasmtime", +] + [[package]] name = "wasmtime-cranelift" version = "0.37.0" diff --git a/Cargo.toml b/Cargo.toml index 3ae82b10bd..42d94a1c95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ doc = false [dependencies] wasmtime = { path = "crates/wasmtime", version = "0.37.0", default-features = false, features = ['cache', 'cranelift'] } wasmtime-cache = { path = "crates/cache", version = "=0.37.0" } +wasmtime-cli-flags = { path = "crates/cli-flags", version = "=0.37.0" } wasmtime-cranelift = { path = "crates/cranelift", version = "=0.37.0" } wasmtime-environ = { path = "crates/environ", version = "=0.37.0" } wasmtime-wast = { path = "crates/wast", version = "=0.37.0" } @@ -32,10 +33,7 @@ wasmtime-wasi-nn = { path = "crates/wasi-nn", version = "0.37.0", optional = tru clap = { version = "3.1.12", features = ["color", "suggestions", "derive"] } anyhow = "1.0.19" target-lexicon = { version = "0.12.0", default-features = false } -pretty_env_logger = "0.4.0" -file-per-thread-logger = "0.1.1" libc = "0.2.60" -rayon = "1.5.0" humantime = "2.0.0" wasmparser = "0.84.0" lazy_static = "1.4.0" @@ -63,6 +61,7 @@ memchr = "2.4" async-trait = "0.1" wat = "1.0.42" once_cell = "1.9.0" +rayon = "1.5.0" [build-dependencies] anyhow = "1.0.19" @@ -79,6 +78,7 @@ members = [ "cranelift/serde", "crates/bench-api", "crates/c-api", + "crates/cli-flags", "crates/misc/run-examples", "examples/fib-debug/wasm", "examples/wasi/wasm", @@ -105,11 +105,11 @@ jitdump = ["wasmtime/jitdump"] vtune = ["wasmtime/vtune"] wasi-crypto = ["wasmtime-wasi-crypto"] wasi-nn = ["wasmtime-wasi-nn"] -memory-init-cow = ["wasmtime/memory-init-cow"] -pooling-allocator = ["wasmtime/pooling-allocator"] +memory-init-cow = ["wasmtime/memory-init-cow", "wasmtime-cli-flags/memory-init-cow"] +pooling-allocator = ["wasmtime/pooling-allocator", "wasmtime-cli-flags/pooling-allocator"] all-arch = ["wasmtime/all-arch"] posix-signals-on-macos = ["wasmtime/posix-signals-on-macos"] -wasm-backtrace = ["wasmtime/wasm-backtrace"] +wasm-backtrace = ["wasmtime/wasm-backtrace", "wasmtime-cli-flags/wasm-backtrace"] # Stub feature that does nothing, for Cargo-features compatibility: the new # backend is the default now. diff --git a/crates/bench-api/Cargo.toml b/crates/bench-api/Cargo.toml index 7393615612..bc02c3d285 100644 --- a/crates/bench-api/Cargo.toml +++ b/crates/bench-api/Cargo.toml @@ -17,7 +17,9 @@ crate-type = ["rlib", "cdylib"] [dependencies] anyhow = "1.0" shuffling-allocator = { version = "1.1.1", optional = true } +target-lexicon = "0.12" wasmtime = { path = "../wasmtime", default-features = true } +wasmtime-cli-flags = { path = "../cli-flags", default-features = true } wasmtime-wasi = { path = "../wasi" } wasmtime-wasi-crypto = { path = "../wasi-crypto", optional = true } wasmtime-wasi-nn = { path = "../wasi-nn", optional = true } diff --git a/crates/bench-api/src/lib.rs b/crates/bench-api/src/lib.rs index 6a351737e5..0806f43bb6 100644 --- a/crates/bench-api/src/lib.rs +++ b/crates/bench-api/src/lib.rs @@ -90,6 +90,8 @@ //! execution_timer: ptr::null_mut(), //! execution_start, //! execution_end, +//! execution_flags_ptr: ptr::null(), +//! execution_flags_len: 0, //! }; //! //! let mut bench_api = ptr::null_mut(); @@ -138,7 +140,9 @@ use anyhow::{anyhow, Context, Result}; use std::os::raw::{c_int, c_void}; use std::slice; use std::{env, path::PathBuf}; +use target_lexicon::Triple; use wasmtime::{Config, Engine, Instance, Linker, Module, Store}; +use wasmtime_cli_flags::CommonOptions; use wasmtime_wasi::{sync::WasiCtxBuilder, WasiCtx}; pub type ExitCode = c_int; @@ -190,6 +194,11 @@ pub struct WasmBenchConfig { pub execution_timer: *mut u8, pub execution_start: extern "C" fn(*mut u8), pub execution_end: extern "C" fn(*mut u8), + + /// The (optional) flags to use when running Wasmtime. These correspond to + /// the flags used when running Wasmtime from the command line. + pub execution_flags_ptr: *const u8, + pub execution_flags_len: usize, } impl WasmBenchConfig { @@ -228,6 +237,22 @@ impl WasmBenchConfig { std::str::from_utf8(stdin_path).context("given stdin path is not valid UTF-8")?; Ok(Some(stdin_path.into())) } + + fn execution_flags(&self) -> Result> { + if self.execution_flags_ptr.is_null() { + return Ok(None); + } + + let execution_flags = unsafe { + std::slice::from_raw_parts(self.execution_flags_ptr, self.execution_flags_len) + }; + let execution_flags = std::str::from_utf8(execution_flags) + .context("given execution flags string is not valid UTF-8")?; + + let options = CommonOptions::parse_from_str(execution_flags)?; + let config = options.config(Some(&Triple::host().to_string()))?; + Ok(Some(config)) + } } /// Exposes a C-compatible way of creating the engine from the bytes of a single @@ -256,8 +281,10 @@ pub extern "C" fn wasm_bench_create( let stdout_path = config.stdout_path()?; let stderr_path = config.stderr_path()?; let stdin_path = config.stdin_path()?; + let engine_config = config.execution_flags()?; let state = Box::new(BenchState::new( + engine_config, config.compilation_timer, config.compilation_start, config.compilation_end, @@ -393,6 +420,7 @@ struct HostState { impl BenchState { fn new( + engine_config: Option, compilation_timer: *mut u8, compilation_start: extern "C" fn(*mut u8), compilation_end: extern "C" fn(*mut u8), @@ -405,9 +433,7 @@ impl BenchState { make_wasi_cx: impl FnMut() -> Result + 'static, ) -> Result { // NB: do not configure a code cache. - let mut config = Config::new(); - config.wasm_simd(true); - let engine = Engine::new(&config)?; + let engine = Engine::new(&engine_config.unwrap_or(Config::new()))?; let mut linker = Linker::::new(&engine); // Define the benchmarking start/end functions. diff --git a/crates/cli-flags/Cargo.toml b/crates/cli-flags/Cargo.toml new file mode 100644 index 0000000000..df0f173b6c --- /dev/null +++ b/crates/cli-flags/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "wasmtime-cli-flags" +version = "0.37.0" +authors = ["The Wasmtime Project Developers"] +description = "Exposes common CLI flags used for running Wasmtime" +license = "Apache-2.0 WITH LLVM-exception" +repository = "https://github.com/bytecodealliance/wasmtime" +documentation = "https://docs.rs/wasmtime-cache/" +edition = "2021" + +[dependencies] +anyhow = "1.0.19" +clap = { version = "3.1.12", features = ["color", "suggestions", "derive"] } +file-per-thread-logger = "0.1.1" +pretty_env_logger = "0.4.0" +rayon = "1.5.0" +wasmtime = { path = "../wasmtime", version = "0.37.0", default-features = false } + +[features] +default = [ + "wasmtime/cache", + "wasmtime/cranelift", + "wasmtime/jitdump", + "wasmtime/vtune", +] +pooling-allocator = [] +memory-init-cow = [] +wasm-backtrace = [] diff --git a/crates/cli-flags/LICENSE b/crates/cli-flags/LICENSE new file mode 100644 index 0000000000..f9d81955f4 --- /dev/null +++ b/crates/cli-flags/LICENSE @@ -0,0 +1,220 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +--- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + diff --git a/crates/cli-flags/src/lib.rs b/crates/cli-flags/src/lib.rs new file mode 100644 index 0000000000..bdbe13f163 --- /dev/null +++ b/crates/cli-flags/src/lib.rs @@ -0,0 +1,723 @@ +//! Contains the common Wasmtime command line interface (CLI) flags. + +#![deny(trivial_numeric_casts, unused_extern_crates, unstable_features)] +#![warn(unused_import_braces)] +#![cfg_attr(feature = "clippy", plugin(clippy(conf_file = "../../clippy.toml")))] +#![cfg_attr(feature = "cargo-clippy", allow(clippy::new_without_default))] +#![cfg_attr( + feature = "cargo-clippy", + warn( + clippy::float_arithmetic, + clippy::mut_mut, + clippy::nonminimal_bool, + clippy::map_unwrap_or, + clippy::unicode_not_nfc, + clippy::use_self + ) +)] + +use anyhow::{bail, Context, Result}; +use clap::Parser; +use std::collections::HashMap; +use std::path::PathBuf; +use wasmtime::{Config, ProfilingStrategy}; +#[cfg(feature = "pooling-allocator")] +use wasmtime::{InstanceLimits, PoolingAllocationStrategy}; + +pub const SUPPORTED_WASM_FEATURES: &[(&str, &str)] = &[ + ("all", "enables all supported WebAssembly features"), + ( + "bulk-memory", + "enables support for bulk memory instructions", + ), + ( + "multi-memory", + "enables support for the multi-memory proposal", + ), + ("multi-value", "enables support for multi-value functions"), + ("reference-types", "enables support for reference types"), + ("simd", "enables support for proposed SIMD instructions"), + ("threads", "enables support for WebAssembly threads"), + ("memory64", "enables support for 64-bit memories"), +]; + +pub const SUPPORTED_WASI_MODULES: &[(&str, &str)] = &[ + ( + "default", + "enables all stable WASI modules (no experimental modules)", + ), + ( + "wasi-common", + "enables support for the WASI common APIs, see https://github.com/WebAssembly/WASI", + ), + ( + "experimental-wasi-nn", + "enables support for the WASI neural network API (experimental), see https://github.com/WebAssembly/wasi-nn", + ), + ( + "experimental-wasi-crypto", + "enables support for the WASI cryptography APIs (experimental), see https://github.com/WebAssembly/wasi-crypto", + ), +]; + +fn pick_profiling_strategy(jitdump: bool, vtune: bool) -> Result { + Ok(match (jitdump, vtune) { + (true, false) => ProfilingStrategy::JitDump, + (false, true) => ProfilingStrategy::VTune, + (true, true) => { + println!("Can't enable --jitdump and --vtune at the same time. Profiling not enabled."); + ProfilingStrategy::None + } + _ => ProfilingStrategy::None, + }) +} + +fn init_file_per_thread_logger(prefix: &'static str) { + file_per_thread_logger::initialize(prefix); + + // Extending behavior of default spawner: + // https://docs.rs/rayon/1.1.0/rayon/struct.ThreadPoolBuilder.html#method.spawn_handler + // Source code says DefaultSpawner is implementation detail and + // shouldn't be used directly. + rayon::ThreadPoolBuilder::new() + .spawn_handler(move |thread| { + let mut b = std::thread::Builder::new(); + if let Some(name) = thread.name() { + b = b.name(name.to_owned()); + } + if let Some(stack_size) = thread.stack_size() { + b = b.stack_size(stack_size); + } + b.spawn(move || { + file_per_thread_logger::initialize(prefix); + thread.run() + })?; + Ok(()) + }) + .build_global() + .unwrap(); +} + +/// Common options for commands that translate WebAssembly modules +#[derive(Parser)] +#[cfg_attr(test, derive(Debug, PartialEq))] +pub struct CommonOptions { + /// Use specified configuration file + #[clap(long, parse(from_os_str), value_name = "CONFIG_PATH")] + pub config: Option, + + /// Disable logging. + #[clap(long, conflicts_with = "log-to-files")] + pub disable_logging: bool, + + /// Log to per-thread log files instead of stderr. + #[clap(long)] + pub log_to_files: bool, + + /// Generate debug information + #[clap(short = 'g')] + pub debug_info: bool, + + /// Disable cache system + #[clap(long)] + pub disable_cache: bool, + + /// Enables or disables WebAssembly features + #[clap(long, value_name = "FEATURE,FEATURE,...", parse(try_from_str = parse_wasm_features))] + pub wasm_features: Option, + + /// Enables or disables WASI modules + #[clap(long, value_name = "MODULE,MODULE,...", parse(try_from_str = parse_wasi_modules))] + pub wasi_modules: Option, + + /// Generate jitdump file (supported on --features=profiling build) + #[clap(long, conflicts_with = "vtune")] + pub jitdump: bool, + + /// Generate vtune (supported on --features=vtune build) + #[clap(long, conflicts_with = "jitdump")] + pub vtune: bool, + + /// Run optimization passes on translated functions, on by default + #[clap(short = 'O', long)] + pub optimize: bool, + + /// Optimization level for generated functions + /// Supported levels: 0 (none), 1, 2 (most), or s (size); default is "most" + #[clap( + long, + value_name = "LEVEL", + parse(try_from_str = parse_opt_level), + verbatim_doc_comment, + )] + pub opt_level: Option, + + /// Set a Cranelift setting to a given value. + /// Use `wasmtime settings` to list Cranelift settings for a target. + #[clap(long = "cranelift-set", value_name = "NAME=VALUE", number_of_values = 1, verbatim_doc_comment, parse(try_from_str = parse_cranelift_flag))] + pub cranelift_set: Vec<(String, String)>, + + /// Enable a Cranelift boolean setting or preset. + /// Use `wasmtime settings` to list Cranelift settings for a target. + #[clap( + long, + value_name = "SETTING", + number_of_values = 1, + verbatim_doc_comment + )] + pub cranelift_enable: Vec, + + /// Maximum size in bytes of wasm memory before it becomes dynamically + /// relocatable instead of up-front-reserved. + #[clap(long, value_name = "MAXIMUM")] + pub static_memory_maximum_size: Option, + + /// Force using a "static" style for all wasm memories. + #[clap(long)] + pub static_memory_forced: bool, + + /// Byte size of the guard region after static memories are allocated. + #[clap(long, value_name = "SIZE")] + pub static_memory_guard_size: Option, + + /// Byte size of the guard region after dynamic memories are allocated. + #[clap(long, value_name = "SIZE")] + pub dynamic_memory_guard_size: Option, + + /// Enable Cranelift's internal debug verifier (expensive) + #[clap(long)] + pub enable_cranelift_debug_verifier: bool, + + /// Enable Cranelift's internal NaN canonicalization + #[clap(long)] + pub enable_cranelift_nan_canonicalization: bool, + + /// Enable execution fuel with N units fuel, where execution will trap after + /// running out of fuel. + /// + /// Most WebAssembly instructions consume 1 unit of fuel. Some instructions, + /// such as `nop`, `drop`, `block`, and `loop`, consume 0 units, as any + /// execution cost associated with them involves other instructions which do + /// consume fuel. + #[clap(long, value_name = "N")] + pub fuel: Option, + + /// Executing wasm code will yield when a global epoch counter + /// changes, allowing for async operation without blocking the + /// executor. + #[clap(long)] + pub epoch_interruption: bool, + + /// Disables the on-by-default address map from native code to wasm code. + #[clap(long)] + pub disable_address_map: bool, + + /// Disables the default of attempting to initialize linear memory via a + /// copy-on-write mapping. + #[cfg(feature = "memory-init-cow")] + #[clap(long)] + pub disable_memory_init_cow: bool, + + /// Enables the pooling allocator, in place of the on-demand + /// allocator. + #[cfg(feature = "pooling-allocator")] + #[clap(long)] + pub pooling_allocator: bool, +} + +impl CommonOptions { + pub fn parse_from_str(s: &str) -> Result { + let parts = s.split(" "); + let options = + Self::try_parse_from(parts).context("unable to parse options from passed flags")?; + Ok(options) + } + + pub fn init_logging(&self) { + if self.disable_logging { + return; + } + if self.log_to_files { + let prefix = "wasmtime.dbg."; + init_file_per_thread_logger(prefix); + } else { + pretty_env_logger::init(); + } + } + + pub fn config(&self, target: Option<&str>) -> Result { + let mut config = Config::new(); + + // Set the target before setting any cranelift options, since the + // target will reset any target-specific options. + if let Some(target) = target { + config.target(target)?; + } + + config + .cranelift_debug_verifier(self.enable_cranelift_debug_verifier) + .debug_info(self.debug_info) + .cranelift_opt_level(self.opt_level()) + .profiler(pick_profiling_strategy(self.jitdump, self.vtune)?)? + .cranelift_nan_canonicalization(self.enable_cranelift_nan_canonicalization); + + self.enable_wasm_features(&mut config); + + for name in &self.cranelift_enable { + unsafe { + config.cranelift_flag_enable(name)?; + } + } + + for (name, value) in &self.cranelift_set { + unsafe { + config.cranelift_flag_set(name, value)?; + } + } + + if !self.disable_cache { + match &self.config { + Some(path) => { + config.cache_config_load(path)?; + } + None => { + config.cache_config_load_default()?; + } + } + } + + if let Some(max) = self.static_memory_maximum_size { + config.static_memory_maximum_size(max); + } + + config.static_memory_forced(self.static_memory_forced); + + if let Some(size) = self.static_memory_guard_size { + config.static_memory_guard_size(size); + } + + if let Some(size) = self.dynamic_memory_guard_size { + config.dynamic_memory_guard_size(size); + } + + // If fuel has been configured, set the `consume fuel` flag on the config. + if self.fuel.is_some() { + config.consume_fuel(true); + } + + config.epoch_interruption(self.epoch_interruption); + config.generate_address_map(!self.disable_address_map); + #[cfg(feature = "memory-init-cow")] + config.memory_init_cow(!self.disable_memory_init_cow); + + #[cfg(feature = "pooling-allocator")] + { + if self.pooling_allocator { + let instance_limits = InstanceLimits::default(); + config.allocation_strategy(wasmtime::InstanceAllocationStrategy::Pooling { + strategy: PoolingAllocationStrategy::NextAvailable, + instance_limits, + }); + } + } + + Ok(config) + } + + pub fn enable_wasm_features(&self, config: &mut Config) { + let WasmFeatures { + simd, + bulk_memory, + reference_types, + multi_value, + threads, + multi_memory, + memory64, + } = self.wasm_features.unwrap_or_default(); + + if let Some(enable) = simd { + config.wasm_simd(enable); + } + if let Some(enable) = bulk_memory { + config.wasm_bulk_memory(enable); + } + if let Some(enable) = reference_types { + #[cfg(feature = "wasm-backtrace")] + config.wasm_reference_types(enable); + drop(enable); // suppress unused warnings + } + if let Some(enable) = multi_value { + config.wasm_multi_value(enable); + } + if let Some(enable) = threads { + config.wasm_threads(enable); + } + if let Some(enable) = multi_memory { + config.wasm_multi_memory(enable); + } + if let Some(enable) = memory64 { + config.wasm_memory64(enable); + } + } + + pub fn opt_level(&self) -> wasmtime::OptLevel { + match (self.optimize, self.opt_level.clone()) { + (true, _) => wasmtime::OptLevel::Speed, + (false, other) => other.unwrap_or(wasmtime::OptLevel::Speed), + } + } +} + +fn parse_opt_level(opt_level: &str) -> Result { + match opt_level { + "s" => Ok(wasmtime::OptLevel::SpeedAndSize), + "0" => Ok(wasmtime::OptLevel::None), + "1" => Ok(wasmtime::OptLevel::Speed), + "2" => Ok(wasmtime::OptLevel::Speed), + other => bail!( + "unknown optimization level `{}`, only 0,1,2,s accepted", + other + ), + } +} + +#[derive(Default, Clone, Copy)] +#[cfg_attr(test, derive(Debug, PartialEq))] +pub struct WasmFeatures { + pub reference_types: Option, + pub multi_value: Option, + pub bulk_memory: Option, + pub simd: Option, + pub threads: Option, + pub multi_memory: Option, + pub memory64: Option, +} + +fn parse_wasm_features(features: &str) -> Result { + let features = features.trim(); + + let mut all = None; + let mut values: HashMap<_, _> = SUPPORTED_WASM_FEATURES + .iter() + .map(|(name, _)| (name.to_string(), None)) + .collect(); + + if features == "all" { + all = Some(true); + } else if features == "-all" { + all = Some(false); + } else { + for feature in features.split(',') { + let feature = feature.trim(); + + if feature.is_empty() { + continue; + } + + let (feature, value) = if feature.starts_with('-') { + (&feature[1..], false) + } else { + (feature, true) + }; + + if feature == "all" { + bail!("'all' cannot be specified with other WebAssembly features"); + } + + match values.get_mut(feature) { + Some(v) => *v = Some(value), + None => bail!("unsupported WebAssembly feature '{}'", feature), + } + } + } + + Ok(WasmFeatures { + reference_types: all.or(values["reference-types"]), + multi_value: all.or(values["multi-value"]), + bulk_memory: all.or(values["bulk-memory"]), + simd: all.or(values["simd"]), + threads: all.or(values["threads"]), + multi_memory: all.or(values["multi-memory"]), + memory64: all.or(values["memory64"]), + }) +} + +fn parse_wasi_modules(modules: &str) -> Result { + let modules = modules.trim(); + match modules { + "default" => Ok(WasiModules::default()), + "-default" => Ok(WasiModules::none()), + _ => { + // Starting from the default set of WASI modules, enable or disable a list of + // comma-separated modules. + let mut wasi_modules = WasiModules::default(); + let mut set = |module: &str, enable: bool| match module { + "" => Ok(()), + "wasi-common" => Ok(wasi_modules.wasi_common = enable), + "experimental-wasi-nn" => Ok(wasi_modules.wasi_nn = enable), + "experimental-wasi-crypto" => Ok(wasi_modules.wasi_crypto = enable), + "default" => bail!("'default' cannot be specified with other WASI modules"), + _ => bail!("unsupported WASI module '{}'", module), + }; + + for module in modules.split(',') { + let module = module.trim(); + let (module, value) = if module.starts_with('-') { + (&module[1..], false) + } else { + (module, true) + }; + set(module, value)?; + } + + Ok(wasi_modules) + } + } +} + +/// Select which WASI modules are available at runtime for use by Wasm programs. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct WasiModules { + /// Enable the wasi-common implementation; eventually this should be split into its separate + /// parts once the implementation allows for it (e.g. wasi-fs, wasi-clocks, etc.). + pub wasi_common: bool, + + /// Enable the experimental wasi-nn implementation. + pub wasi_nn: bool, + + /// Enable the experimental wasi-crypto implementation. + pub wasi_crypto: bool, +} + +impl Default for WasiModules { + fn default() -> Self { + Self { + wasi_common: true, + wasi_nn: false, + wasi_crypto: false, + } + } +} + +impl WasiModules { + /// Enable no modules. + pub fn none() -> Self { + Self { + wasi_common: false, + wasi_nn: false, + wasi_crypto: false, + } + } +} + +fn parse_cranelift_flag(name_and_value: &str) -> Result<(String, String)> { + let mut split = name_and_value.splitn(2, '='); + let name = if let Some(name) = split.next() { + name.to_string() + } else { + bail!("missing name in cranelift flag"); + }; + let value = if let Some(value) = split.next() { + value.to_string() + } else { + bail!("missing value in cranelift flag"); + }; + Ok((name, value)) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_all_features() -> Result<()> { + let options = CommonOptions::try_parse_from(vec!["foo", "--wasm-features=all"])?; + + let WasmFeatures { + reference_types, + multi_value, + bulk_memory, + simd, + threads, + multi_memory, + memory64, + } = options.wasm_features.unwrap(); + + assert_eq!(reference_types, Some(true)); + assert_eq!(multi_value, Some(true)); + assert_eq!(bulk_memory, Some(true)); + assert_eq!(simd, Some(true)); + assert_eq!(threads, Some(true)); + assert_eq!(multi_memory, Some(true)); + assert_eq!(memory64, Some(true)); + + Ok(()) + } + + #[test] + fn test_no_features() -> Result<()> { + let options = CommonOptions::try_parse_from(vec!["foo", "--wasm-features=-all"])?; + + let WasmFeatures { + reference_types, + multi_value, + bulk_memory, + simd, + threads, + multi_memory, + memory64, + } = options.wasm_features.unwrap(); + + assert_eq!(reference_types, Some(false)); + assert_eq!(multi_value, Some(false)); + assert_eq!(bulk_memory, Some(false)); + assert_eq!(simd, Some(false)); + assert_eq!(threads, Some(false)); + assert_eq!(multi_memory, Some(false)); + assert_eq!(memory64, Some(false)); + + Ok(()) + } + + #[test] + fn test_multiple_features() -> Result<()> { + let options = CommonOptions::try_parse_from(vec![ + "foo", + "--wasm-features=-reference-types,simd,multi-memory,memory64", + ])?; + + let WasmFeatures { + reference_types, + multi_value, + bulk_memory, + simd, + threads, + multi_memory, + memory64, + } = options.wasm_features.unwrap(); + + assert_eq!(reference_types, Some(false)); + assert_eq!(multi_value, None); + assert_eq!(bulk_memory, None); + assert_eq!(simd, Some(true)); + assert_eq!(threads, None); + assert_eq!(multi_memory, Some(true)); + assert_eq!(memory64, Some(true)); + + Ok(()) + } + + macro_rules! feature_test { + ($test_name:ident, $name:ident, $flag:literal) => { + #[test] + fn $test_name() -> Result<()> { + let options = + CommonOptions::try_parse_from(vec!["foo", concat!("--wasm-features=", $flag)])?; + + let WasmFeatures { $name, .. } = options.wasm_features.unwrap(); + + assert_eq!($name, Some(true)); + + let options = CommonOptions::try_parse_from(vec![ + "foo", + concat!("--wasm-features=-", $flag), + ])?; + + let WasmFeatures { $name, .. } = options.wasm_features.unwrap(); + + assert_eq!($name, Some(false)); + + Ok(()) + } + }; + } + + feature_test!( + test_reference_types_feature, + reference_types, + "reference-types" + ); + feature_test!(test_multi_value_feature, multi_value, "multi-value"); + feature_test!(test_bulk_memory_feature, bulk_memory, "bulk-memory"); + feature_test!(test_simd_feature, simd, "simd"); + feature_test!(test_threads_feature, threads, "threads"); + feature_test!(test_multi_memory_feature, multi_memory, "multi-memory"); + feature_test!(test_memory64_feature, memory64, "memory64"); + + #[test] + fn test_default_modules() { + let options = CommonOptions::try_parse_from(vec!["foo", "--wasi-modules=default"]).unwrap(); + assert_eq!( + options.wasi_modules.unwrap(), + WasiModules { + wasi_common: true, + wasi_nn: false, + wasi_crypto: false + } + ); + } + + #[test] + fn test_empty_modules() { + let options = CommonOptions::try_parse_from(vec!["foo", "--wasi-modules="]).unwrap(); + assert_eq!( + options.wasi_modules.unwrap(), + WasiModules { + wasi_common: true, + wasi_nn: false, + wasi_crypto: false + } + ); + } + + #[test] + fn test_some_modules() { + let options = CommonOptions::try_parse_from(vec![ + "foo", + "--wasi-modules=experimental-wasi-nn,-wasi-common", + ]) + .unwrap(); + assert_eq!( + options.wasi_modules.unwrap(), + WasiModules { + wasi_common: false, + wasi_nn: true, + wasi_crypto: false + } + ); + } + + #[test] + fn test_no_modules() { + let options = + CommonOptions::try_parse_from(vec!["foo", "--wasi-modules=-default"]).unwrap(); + assert_eq!( + options.wasi_modules.unwrap(), + WasiModules { + wasi_common: false, + wasi_nn: false, + wasi_crypto: false + } + ); + } + + #[test] + fn test_parse_from_str() { + fn use_func(flags: &str) -> CommonOptions { + CommonOptions::parse_from_str(flags).unwrap() + } + fn use_clap_parser(flags: &[&str]) -> CommonOptions { + CommonOptions::try_parse_from(flags).unwrap() + } + + assert_eq!(use_func(""), use_clap_parser(&[])); + assert_eq!( + use_func("foo --wasm-features=threads"), + use_clap_parser(&["foo", "--wasm-features=threads"]) + ); + assert_eq!( + use_func("foo --cranelift-set enable_simd=true"), + use_clap_parser(&["foo", "--cranelift-set", "enable_simd=true"]) + ); + } +} diff --git a/deny.toml b/deny.toml index 036c996d0c..1f926f6ab3 100644 --- a/deny.toml +++ b/deny.toml @@ -40,4 +40,9 @@ skip-tree = [ # This is somewhat unmaintained at this point and seems to pull in an old # version of `env_logger`, so ignore it. { name = "pretty_env_logger", depth = 20 }, + + # This crate depends on an old version of the `uuid` crate: `wasmtime-cli -> + # listenfd -> uuid`. Once `listenfd` upgrades to `uuid` v1.0.0, this can be + # removed. + { name = "listenfd", depth = 20 }, ] diff --git a/scripts/publish.rs b/scripts/publish.rs index 9e7c20d3ac..3637fc95b4 100644 --- a/scripts/publish.rs +++ b/scripts/publish.rs @@ -53,11 +53,12 @@ const CRATES_TO_PUBLISH: &[&str] = &[ "wasi-common", "wasi-cap-std-sync", "wasi-tokio", - // other mic wasmtime crates + // other misc wasmtime crates "wasmtime-wasi", "wasmtime-wasi-nn", "wasmtime-wasi-crypto", "wasmtime-wast", + "wasmtime-cli-flags", "wasmtime-cli", ]; @@ -76,6 +77,7 @@ const PUBLIC_CRATES: &[&str] = &[ "wasmtime-wasi", "wasmtime-wasi-nn", "wasmtime-wasi-crypto", + "wasmtime-cli-flags", "wasmtime-cli", // all cranelift crates are considered "public" in that they can't // have breaking API changes in patch releases @@ -452,9 +454,7 @@ fn verify(crates: &[Crate]) { .arg("--manifest-path") .arg(&krate.manifest) .env("CARGO_TARGET_DIR", "./target"); - if krate.name == "witx" - || krate.name.contains("wasi-nn") - { + if krate.name == "witx" || krate.name.contains("wasi-nn") { cmd.arg("--no-verify"); } let status = cmd.status().unwrap(); diff --git a/src/commands/compile.rs b/src/commands/compile.rs index 9851ca4cfe..08c3b06fb1 100644 --- a/src/commands/compile.rs +++ b/src/commands/compile.rs @@ -1,12 +1,12 @@ //! The module that implements the `wasmtime compile` command. -use crate::CommonOptions; use anyhow::{bail, Context, Result}; use clap::Parser; use std::fs; use std::path::PathBuf; use target_lexicon::Triple; use wasmtime::Engine; +use wasmtime_cli_flags::CommonOptions; lazy_static::lazy_static! { static ref AFTER_HELP: String = { diff --git a/src/commands/run.rs b/src/commands/run.rs index 2f62b2b1cb..05f466d9b4 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -1,6 +1,5 @@ //! The module that implements the `wasmtime run` command. -use crate::{CommonOptions, WasiModules}; use anyhow::{anyhow, bail, Context as _, Result}; use clap::Parser; use std::fs::File; @@ -13,6 +12,7 @@ use std::{ process, }; use wasmtime::{Engine, Func, Linker, Module, Store, Trap, Val, ValType}; +use wasmtime_cli_flags::{CommonOptions, WasiModules}; use wasmtime_wasi::sync::{ambient_authority, Dir, TcpListener, WasiCtxBuilder}; #[cfg(feature = "wasi-nn")] diff --git a/src/commands/wast.rs b/src/commands/wast.rs index b847fcc0ee..a7305ecd6c 100644 --- a/src/commands/wast.rs +++ b/src/commands/wast.rs @@ -1,10 +1,10 @@ //! The module that implements the `wasmtime wast` command. -use crate::CommonOptions; use anyhow::{Context as _, Result}; use clap::Parser; use std::path::PathBuf; use wasmtime::{Engine, Store}; +use wasmtime_cli_flags::CommonOptions; use wasmtime_wast::WastContext; lazy_static::lazy_static! { diff --git a/src/lib.rs b/src/lib.rs index eb08fcb170..39c3c360b4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,41 +23,7 @@ ) )] -const SUPPORTED_WASM_FEATURES: &[(&str, &str)] = &[ - ("all", "enables all supported WebAssembly features"), - ( - "bulk-memory", - "enables support for bulk memory instructions", - ), - ( - "multi-memory", - "enables support for the multi-memory proposal", - ), - ("multi-value", "enables support for multi-value functions"), - ("reference-types", "enables support for reference types"), - ("simd", "enables support for proposed SIMD instructions"), - ("threads", "enables support for WebAssembly threads"), - ("memory64", "enables support for 64-bit memories"), -]; - -const SUPPORTED_WASI_MODULES: &[(&str, &str)] = &[ - ( - "default", - "enables all stable WASI modules (no experimental modules)", - ), - ( - "wasi-common", - "enables support for the WASI common APIs, see https://github.com/WebAssembly/WASI", - ), - ( - "experimental-wasi-nn", - "enables support for the WASI neural network API (experimental), see https://github.com/WebAssembly/wasi-nn", - ), - ( - "experimental-wasi-crypto", - "enables support for the WASI cryptography APIs (experimental), see https://github.com/WebAssembly/wasi-crypto", - ), -]; +use wasmtime_cli_flags::{SUPPORTED_WASI_MODULES, SUPPORTED_WASM_FEATURES}; lazy_static::lazy_static! { static ref FLAG_EXPLANATIONS: String = { @@ -90,644 +56,3 @@ lazy_static::lazy_static! { } pub mod commands; - -use anyhow::{bail, Result}; -use clap::Parser; -use std::collections::HashMap; -use std::path::PathBuf; -use wasmtime::{Config, ProfilingStrategy}; -#[cfg(feature = "pooling-allocator")] -use wasmtime::{InstanceLimits, PoolingAllocationStrategy}; - -fn pick_profiling_strategy(jitdump: bool, vtune: bool) -> Result { - Ok(match (jitdump, vtune) { - (true, false) => ProfilingStrategy::JitDump, - (false, true) => ProfilingStrategy::VTune, - (true, true) => { - println!("Can't enable --jitdump and --vtune at the same time. Profiling not enabled."); - ProfilingStrategy::None - } - _ => ProfilingStrategy::None, - }) -} - -fn init_file_per_thread_logger(prefix: &'static str) { - file_per_thread_logger::initialize(prefix); - - // Extending behavior of default spawner: - // https://docs.rs/rayon/1.1.0/rayon/struct.ThreadPoolBuilder.html#method.spawn_handler - // Source code says DefaultSpawner is implementation detail and - // shouldn't be used directly. - rayon::ThreadPoolBuilder::new() - .spawn_handler(move |thread| { - let mut b = std::thread::Builder::new(); - if let Some(name) = thread.name() { - b = b.name(name.to_owned()); - } - if let Some(stack_size) = thread.stack_size() { - b = b.stack_size(stack_size); - } - b.spawn(move || { - file_per_thread_logger::initialize(prefix); - thread.run() - })?; - Ok(()) - }) - .build_global() - .unwrap(); -} - -/// Common options for commands that translate WebAssembly modules -#[derive(Parser)] -struct CommonOptions { - /// Use specified configuration file - #[clap(long, parse(from_os_str), value_name = "CONFIG_PATH")] - config: Option, - - /// Disable logging. - #[clap(long, conflicts_with = "log-to-files")] - disable_logging: bool, - - /// Log to per-thread log files instead of stderr. - #[clap(long)] - log_to_files: bool, - - /// Generate debug information - #[clap(short = 'g')] - debug_info: bool, - - /// Disable cache system - #[clap(long)] - disable_cache: bool, - - /// Enables or disables WebAssembly features - #[clap(long, value_name = "FEATURE,FEATURE,...", parse(try_from_str = parse_wasm_features))] - wasm_features: Option, - - /// Enables or disables WASI modules - #[clap(long, value_name = "MODULE,MODULE,...", parse(try_from_str = parse_wasi_modules))] - wasi_modules: Option, - - /// Generate jitdump file (supported on --features=profiling build) - #[clap(long, conflicts_with = "vtune")] - jitdump: bool, - - /// Generate vtune (supported on --features=vtune build) - #[clap(long, conflicts_with = "jitdump")] - vtune: bool, - - /// Run optimization passes on translated functions, on by default - #[clap(short = 'O', long)] - optimize: bool, - - /// Optimization level for generated functions - /// Supported levels: 0 (none), 1, 2 (most), or s (size); default is "most" - #[clap( - long, - value_name = "LEVEL", - parse(try_from_str = parse_opt_level), - verbatim_doc_comment, - )] - opt_level: Option, - - /// Set a Cranelift setting to a given value. - /// Use `wasmtime settings` to list Cranelift settings for a target. - #[clap(long = "cranelift-set", value_name = "NAME=VALUE", number_of_values = 1, verbatim_doc_comment, parse(try_from_str = parse_cranelift_flag))] - cranelift_set: Vec<(String, String)>, - - /// Enable a Cranelift boolean setting or preset. - /// Use `wasmtime settings` to list Cranelift settings for a target. - #[clap( - long, - value_name = "SETTING", - number_of_values = 1, - verbatim_doc_comment - )] - cranelift_enable: Vec, - - /// Maximum size in bytes of wasm memory before it becomes dynamically - /// relocatable instead of up-front-reserved. - #[clap(long, value_name = "MAXIMUM")] - static_memory_maximum_size: Option, - - /// Force using a "static" style for all wasm memories. - #[clap(long)] - static_memory_forced: bool, - - /// Byte size of the guard region after static memories are allocated. - #[clap(long, value_name = "SIZE")] - static_memory_guard_size: Option, - - /// Byte size of the guard region after dynamic memories are allocated. - #[clap(long, value_name = "SIZE")] - dynamic_memory_guard_size: Option, - - /// Enable Cranelift's internal debug verifier (expensive) - #[clap(long)] - enable_cranelift_debug_verifier: bool, - - /// Enable Cranelift's internal NaN canonicalization - #[clap(long)] - enable_cranelift_nan_canonicalization: bool, - - /// Enable execution fuel with N units fuel, where execution will trap after - /// running out of fuel. - /// - /// Most WebAssembly instructions consume 1 unit of fuel. Some instructions, - /// such as `nop`, `drop`, `block`, and `loop`, consume 0 units, as any - /// execution cost associated with them involves other instructions which do - /// consume fuel. - #[clap(long, value_name = "N")] - fuel: Option, - - /// Executing wasm code will yield when a global epoch counter - /// changes, allowing for async operation without blocking the - /// executor. - #[clap(long)] - epoch_interruption: bool, - - /// Disables the on-by-default address map from native code to wasm code. - #[clap(long)] - disable_address_map: bool, - - /// Disables the default of attempting to initialize linear memory via a - /// copy-on-write mapping. - #[cfg(feature = "memory-init-cow")] - #[clap(long)] - disable_memory_init_cow: bool, - - /// Enables the pooling allocator, in place of the on-demand - /// allocator. - #[cfg(feature = "pooling-allocator")] - #[clap(long)] - pooling_allocator: bool, -} - -impl CommonOptions { - fn init_logging(&self) { - if self.disable_logging { - return; - } - if self.log_to_files { - let prefix = "wasmtime.dbg."; - init_file_per_thread_logger(prefix); - } else { - pretty_env_logger::init(); - } - } - - fn config(&self, target: Option<&str>) -> Result { - let mut config = Config::new(); - - // Set the target before setting any cranelift options, since the - // target will reset any target-specific options. - if let Some(target) = target { - config.target(target)?; - } - - config - .cranelift_debug_verifier(self.enable_cranelift_debug_verifier) - .debug_info(self.debug_info) - .cranelift_opt_level(self.opt_level()) - .profiler(pick_profiling_strategy(self.jitdump, self.vtune)?)? - .cranelift_nan_canonicalization(self.enable_cranelift_nan_canonicalization); - - self.enable_wasm_features(&mut config); - - for name in &self.cranelift_enable { - unsafe { - config.cranelift_flag_enable(name)?; - } - } - - for (name, value) in &self.cranelift_set { - unsafe { - config.cranelift_flag_set(name, value)?; - } - } - - if !self.disable_cache { - match &self.config { - Some(path) => { - config.cache_config_load(path)?; - } - None => { - config.cache_config_load_default()?; - } - } - } - - if let Some(max) = self.static_memory_maximum_size { - config.static_memory_maximum_size(max); - } - - config.static_memory_forced(self.static_memory_forced); - - if let Some(size) = self.static_memory_guard_size { - config.static_memory_guard_size(size); - } - - if let Some(size) = self.dynamic_memory_guard_size { - config.dynamic_memory_guard_size(size); - } - - // If fuel has been configured, set the `consume fuel` flag on the config. - if self.fuel.is_some() { - config.consume_fuel(true); - } - - config.epoch_interruption(self.epoch_interruption); - config.generate_address_map(!self.disable_address_map); - #[cfg(feature = "memory-init-cow")] - config.memory_init_cow(!self.disable_memory_init_cow); - - #[cfg(feature = "pooling-allocator")] - { - if self.pooling_allocator { - let instance_limits = InstanceLimits::default(); - config.allocation_strategy(wasmtime::InstanceAllocationStrategy::Pooling { - strategy: PoolingAllocationStrategy::NextAvailable, - instance_limits, - }); - } - } - - Ok(config) - } - - fn enable_wasm_features(&self, config: &mut Config) { - let WasmFeatures { - simd, - bulk_memory, - reference_types, - multi_value, - threads, - multi_memory, - memory64, - } = self.wasm_features.unwrap_or_default(); - - if let Some(enable) = simd { - config.wasm_simd(enable); - } - if let Some(enable) = bulk_memory { - config.wasm_bulk_memory(enable); - } - if let Some(enable) = reference_types { - #[cfg(feature = "wasm-backtrace")] - config.wasm_reference_types(enable); - drop(enable); // suppress unused warnings - } - if let Some(enable) = multi_value { - config.wasm_multi_value(enable); - } - if let Some(enable) = threads { - config.wasm_threads(enable); - } - if let Some(enable) = multi_memory { - config.wasm_multi_memory(enable); - } - if let Some(enable) = memory64 { - config.wasm_memory64(enable); - } - } - - fn opt_level(&self) -> wasmtime::OptLevel { - match (self.optimize, self.opt_level.clone()) { - (true, _) => wasmtime::OptLevel::Speed, - (false, other) => other.unwrap_or(wasmtime::OptLevel::Speed), - } - } -} - -fn parse_opt_level(opt_level: &str) -> Result { - match opt_level { - "s" => Ok(wasmtime::OptLevel::SpeedAndSize), - "0" => Ok(wasmtime::OptLevel::None), - "1" => Ok(wasmtime::OptLevel::Speed), - "2" => Ok(wasmtime::OptLevel::Speed), - other => bail!( - "unknown optimization level `{}`, only 0,1,2,s accepted", - other - ), - } -} - -#[derive(Default, Clone, Copy)] -struct WasmFeatures { - reference_types: Option, - multi_value: Option, - bulk_memory: Option, - simd: Option, - threads: Option, - multi_memory: Option, - memory64: Option, -} - -fn parse_wasm_features(features: &str) -> Result { - let features = features.trim(); - - let mut all = None; - let mut values: HashMap<_, _> = SUPPORTED_WASM_FEATURES - .iter() - .map(|(name, _)| (name.to_string(), None)) - .collect(); - - if features == "all" { - all = Some(true); - } else if features == "-all" { - all = Some(false); - } else { - for feature in features.split(',') { - let feature = feature.trim(); - - if feature.is_empty() { - continue; - } - - let (feature, value) = if feature.starts_with('-') { - (&feature[1..], false) - } else { - (feature, true) - }; - - if feature == "all" { - bail!("'all' cannot be specified with other WebAssembly features"); - } - - match values.get_mut(feature) { - Some(v) => *v = Some(value), - None => bail!("unsupported WebAssembly feature '{}'", feature), - } - } - } - - Ok(WasmFeatures { - reference_types: all.or(values["reference-types"]), - multi_value: all.or(values["multi-value"]), - bulk_memory: all.or(values["bulk-memory"]), - simd: all.or(values["simd"]), - threads: all.or(values["threads"]), - multi_memory: all.or(values["multi-memory"]), - memory64: all.or(values["memory64"]), - }) -} - -fn parse_wasi_modules(modules: &str) -> Result { - let modules = modules.trim(); - match modules { - "default" => Ok(WasiModules::default()), - "-default" => Ok(WasiModules::none()), - _ => { - // Starting from the default set of WASI modules, enable or disable a list of - // comma-separated modules. - let mut wasi_modules = WasiModules::default(); - let mut set = |module: &str, enable: bool| match module { - "" => Ok(()), - "wasi-common" => Ok(wasi_modules.wasi_common = enable), - "experimental-wasi-nn" => Ok(wasi_modules.wasi_nn = enable), - "experimental-wasi-crypto" => Ok(wasi_modules.wasi_crypto = enable), - "default" => bail!("'default' cannot be specified with other WASI modules"), - _ => bail!("unsupported WASI module '{}'", module), - }; - - for module in modules.split(',') { - let module = module.trim(); - let (module, value) = if module.starts_with('-') { - (&module[1..], false) - } else { - (module, true) - }; - set(module, value)?; - } - - Ok(wasi_modules) - } - } -} - -/// Select which WASI modules are available at runtime for use by Wasm programs. -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct WasiModules { - /// Enable the wasi-common implementation; eventually this should be split into its separate - /// parts once the implementation allows for it (e.g. wasi-fs, wasi-clocks, etc.). - pub wasi_common: bool, - - /// Enable the experimental wasi-nn implementation. - pub wasi_nn: bool, - - /// Enable the experimental wasi-crypto implementation. - pub wasi_crypto: bool, -} - -impl Default for WasiModules { - fn default() -> Self { - Self { - wasi_common: true, - wasi_nn: false, - wasi_crypto: false, - } - } -} - -impl WasiModules { - /// Enable no modules. - pub fn none() -> Self { - Self { - wasi_common: false, - wasi_nn: false, - wasi_crypto: false, - } - } -} - -fn parse_cranelift_flag(name_and_value: &str) -> Result<(String, String)> { - let mut split = name_and_value.splitn(2, '='); - let name = if let Some(name) = split.next() { - name.to_string() - } else { - bail!("missing name in cranelift flag"); - }; - let value = if let Some(value) = split.next() { - value.to_string() - } else { - bail!("missing value in cranelift flag"); - }; - Ok((name, value)) -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_all_features() -> Result<()> { - let options = CommonOptions::try_parse_from(vec!["foo", "--wasm-features=all"])?; - - let WasmFeatures { - reference_types, - multi_value, - bulk_memory, - simd, - threads, - multi_memory, - memory64, - } = options.wasm_features.unwrap(); - - assert_eq!(reference_types, Some(true)); - assert_eq!(multi_value, Some(true)); - assert_eq!(bulk_memory, Some(true)); - assert_eq!(simd, Some(true)); - assert_eq!(threads, Some(true)); - assert_eq!(multi_memory, Some(true)); - assert_eq!(memory64, Some(true)); - - Ok(()) - } - - #[test] - fn test_no_features() -> Result<()> { - let options = CommonOptions::try_parse_from(vec!["foo", "--wasm-features=-all"])?; - - let WasmFeatures { - reference_types, - multi_value, - bulk_memory, - simd, - threads, - multi_memory, - memory64, - } = options.wasm_features.unwrap(); - - assert_eq!(reference_types, Some(false)); - assert_eq!(multi_value, Some(false)); - assert_eq!(bulk_memory, Some(false)); - assert_eq!(simd, Some(false)); - assert_eq!(threads, Some(false)); - assert_eq!(multi_memory, Some(false)); - assert_eq!(memory64, Some(false)); - - Ok(()) - } - - #[test] - fn test_multiple_features() -> Result<()> { - let options = CommonOptions::try_parse_from(vec![ - "foo", - "--wasm-features=-reference-types,simd,multi-memory,memory64", - ])?; - - let WasmFeatures { - reference_types, - multi_value, - bulk_memory, - simd, - threads, - multi_memory, - memory64, - } = options.wasm_features.unwrap(); - - assert_eq!(reference_types, Some(false)); - assert_eq!(multi_value, None); - assert_eq!(bulk_memory, None); - assert_eq!(simd, Some(true)); - assert_eq!(threads, None); - assert_eq!(multi_memory, Some(true)); - assert_eq!(memory64, Some(true)); - - Ok(()) - } - - macro_rules! feature_test { - ($test_name:ident, $name:ident, $flag:literal) => { - #[test] - fn $test_name() -> Result<()> { - let options = - CommonOptions::try_parse_from(vec!["foo", concat!("--wasm-features=", $flag)])?; - - let WasmFeatures { $name, .. } = options.wasm_features.unwrap(); - - assert_eq!($name, Some(true)); - - let options = CommonOptions::try_parse_from(vec![ - "foo", - concat!("--wasm-features=-", $flag), - ])?; - - let WasmFeatures { $name, .. } = options.wasm_features.unwrap(); - - assert_eq!($name, Some(false)); - - Ok(()) - } - }; - } - - feature_test!( - test_reference_types_feature, - reference_types, - "reference-types" - ); - feature_test!(test_multi_value_feature, multi_value, "multi-value"); - feature_test!(test_bulk_memory_feature, bulk_memory, "bulk-memory"); - feature_test!(test_simd_feature, simd, "simd"); - feature_test!(test_threads_feature, threads, "threads"); - feature_test!(test_multi_memory_feature, multi_memory, "multi-memory"); - feature_test!(test_memory64_feature, memory64, "memory64"); - - #[test] - fn test_default_modules() { - let options = CommonOptions::try_parse_from(vec!["foo", "--wasi-modules=default"]).unwrap(); - assert_eq!( - options.wasi_modules.unwrap(), - WasiModules { - wasi_common: true, - wasi_nn: false, - wasi_crypto: false - } - ); - } - - #[test] - fn test_empty_modules() { - let options = CommonOptions::try_parse_from(vec!["foo", "--wasi-modules="]).unwrap(); - assert_eq!( - options.wasi_modules.unwrap(), - WasiModules { - wasi_common: true, - wasi_nn: false, - wasi_crypto: false - } - ); - } - - #[test] - fn test_some_modules() { - let options = CommonOptions::try_parse_from(vec![ - "foo", - "--wasi-modules=experimental-wasi-nn,-wasi-common", - ]) - .unwrap(); - assert_eq!( - options.wasi_modules.unwrap(), - WasiModules { - wasi_common: false, - wasi_nn: true, - wasi_crypto: false - } - ); - } - - #[test] - fn test_no_modules() { - let options = - CommonOptions::try_parse_from(vec!["foo", "--wasi-modules=-default"]).unwrap(); - assert_eq!( - options.wasi_modules.unwrap(), - WasiModules { - wasi_common: false, - wasi_nn: false, - wasi_crypto: false - } - ); - } -}