From 92e0b6b9e8231bedfac0687da84b9d5683b29339 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Thu, 29 Apr 2021 13:03:28 -0700 Subject: [PATCH] wasi-nn: turn it on by default (#2859) * wasi-nn: turn it on by default This change makes the wasi-nn Cargo feature a default feature. Previously, a wasi-nn user would have to build a separate Wasmtime binary (e.g. `cargo build --features wasi-nn ...`) to use wasi-nn and the resulting binary would require OpenVINO shared libraries to be present in the environment in order to run (otherwise it would fail immediately with linking errors). With recent changes to the `openvino` crate, the wasi-nn implementation can defer the loading of the OpenVINO shared libraries until runtime (i.e., when the user Wasm program calls `wasi_ephemeral_nn::load`) and display a user-level error if anything goes wrong (e.g., the OpenVINO libraries are not present on the system). This runtime-linking addition allows the wasi-nn feature to be turned on by default and shipped with upcoming releases of Wasmtime. This change should be transparent for users who do not use wasi-nn: the `openvino` crate is small and the newly-available wasi-nn imports only affect programs in which they are used. For those interested in reviewing the runtime linking approach added to the `openvino` crate, see https://github.com/intel/openvino-rs/pull/19. * wasi-nn spec path: don't use canonicalize * Allow dependencies using the ISC license The ISC license should be [just as permissive](https://choosealicense.com/licenses/isc) as MIT, e.g., with no additional limitations. * Add a `--wasi-modules` flag This flag controls which WASI modules are made available to the Wasm program. This initial commit enables `wasi-common` by default (equivalent to `--wasi-modules=all`) and allows `wasi-nn` and `wasi-crypto` to be added in either individually (e.g., `--wasi-modules=wasi-nn`) or as a group (e.g., `--wasi-modules=all-experimental`). * wasi-crypto: fix unused dependency Co-authored-by: Pat Hickey --- Cargo.lock | 53 ++---- Cargo.toml | 2 +- ci/run-wasi-crypto-example.sh | 2 +- ci/run-wasi-nn-example.sh | 2 +- .../src/wiggle_interfaces/error.rs | 2 +- crates/wasi-nn/Cargo.toml | 2 +- crates/wasi-nn/build.rs | 6 +- crates/wasi-nn/src/ctx.rs | 12 +- crates/wasi-nn/src/impl.rs | 20 ++- crates/wasi-nn/src/witx.rs | 3 +- deny.toml | 1 + src/commands/compile.rs | 2 +- src/commands/run.rs | 60 +++++-- src/commands/wasm2obj.rs | 2 +- src/commands/wast.rs | 2 +- src/lib.rs | 164 +++++++++++++++++- 16 files changed, 255 insertions(+), 80 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 37c27174dd7d..05b1a92bd2d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -189,30 +189,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bindgen" -version = "0.55.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b13ce559e6433d360c26305643803cb52cfbabbc2b9c47ce04a58493dfb443" -dependencies = [ - "bitflags", - "cexpr", - "cfg-if 0.1.10", - "clang-sys", - "clap", - "env_logger 0.7.1", - "lazy_static", - "lazycell", - "log", - "peeking_take_while", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "which", -] - [[package]] name = "bindgen" version = "0.57.0" @@ -1730,22 +1706,30 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openvino" -version = "0.1.8" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43eeb44285b7ce8e2012b92bec32968622e1dad452e812e6edea9e001e5e9410" +checksum = "0cb74b3d8c653f7a9928bda494d329e6363ea0b428d3a3e5805b45ebb74ace76" dependencies = [ "openvino-sys", "thiserror", ] +[[package]] +name = "openvino-finder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "426587a131841eb1e1111b0fea96cbd4fd0fd5d7b6526fb9c41400587d1c525c" + [[package]] name = "openvino-sys" -version = "0.1.8" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb64bef270a1ff665b0b2e28ebfa213e6205a007ce88223d020730225d6008f" +checksum = "83d5e5d5e913f4e9aa42b2a7ae9c8719aedb4bc0eb443bf92f07d9ee9a05e7b1" dependencies = [ - "bindgen 0.55.1", "cmake", + "lazy_static", + "libloading", + "openvino-finder", ] [[package]] @@ -2984,7 +2968,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ada4f4ae167325015f52cc65f9fb6c251b868d8fb3b6dd0ce2d60e497c4870a" dependencies = [ - "bindgen 0.57.0", + "bindgen", "cc", "cfg-if 0.1.10", ] @@ -3604,15 +3588,6 @@ dependencies = [ "wast 35.0.2", ] -[[package]] -name = "which" -version = "3.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724" -dependencies = [ - "libc", -] - [[package]] name = "wiggle" version = "0.26.0" diff --git a/Cargo.toml b/Cargo.toml index ffff6d865e7b..e23bddb5e3ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,7 +84,7 @@ members = [ ] [features] -default = ["jitdump", "wasmtime/wat", "wasmtime/parallel-compilation"] +default = ["jitdump", "wasmtime/wat", "wasmtime/parallel-compilation", "wasi-nn"] lightbeam = ["wasmtime/lightbeam"] jitdump = ["wasmtime/jitdump"] vtune = ["wasmtime/vtune"] diff --git a/ci/run-wasi-crypto-example.sh b/ci/run-wasi-crypto-example.sh index d2582c71b1d3..b027e4834002 100755 --- a/ci/run-wasi-crypto-example.sh +++ b/ci/run-wasi-crypto-example.sh @@ -7,4 +7,4 @@ pushd "$RUST_BINDINGS" cargo build --release --target=wasm32-wasi popd -cargo run --features wasi-crypto -- run "$RUST_BINDINGS/target/wasm32-wasi/release/wasi-crypto-guest.wasm" +cargo run --features wasi-crypto -- run "$RUST_BINDINGS/target/wasm32-wasi/release/wasi-crypto-guest.wasm" --wasi-modules=experimental-wasi-crypto diff --git a/ci/run-wasi-nn-example.sh b/ci/run-wasi-nn-example.sh index 49c472c95eee..97b55362c6c7 100755 --- a/ci/run-wasi-nn-example.sh +++ b/ci/run-wasi-nn-example.sh @@ -37,7 +37,7 @@ cp target/wasm32-wasi/release/wasi-nn-example.wasm $TMP_DIR popd # Run the example in Wasmtime (note that the example uses `fixture` as the expected location of the model/tensor files). -OPENVINO_INSTALL_DIR=/opt/intel/openvino cargo run --features wasi-nn -- run --mapdir fixture::$TMP_DIR $TMP_DIR/wasi-nn-example.wasm +cargo run -- run --mapdir fixture::$TMP_DIR $TMP_DIR/wasi-nn-example.wasm --wasi-modules=experimental-wasi-nn # Clean up the temporary directory only if it was not specified (users may want to keep the directory around). if [[ $REMOVE_TMP_DIR -eq 1 ]]; then diff --git a/crates/wasi-crypto/src/wiggle_interfaces/error.rs b/crates/wasi-crypto/src/wiggle_interfaces/error.rs index fd13a8733169..e54a104b030b 100644 --- a/crates/wasi-crypto/src/wiggle_interfaces/error.rs +++ b/crates/wasi-crypto/src/wiggle_interfaces/error.rs @@ -1,4 +1,4 @@ -use super::{guest_types, WasiCryptoCtx}; +use super::guest_types; use std::num::TryFromIntError; use wasi_crypto::CryptoError; diff --git a/crates/wasi-nn/Cargo.toml b/crates/wasi-nn/Cargo.toml index 1ce73aeed708..a5de00ed3a2c 100644 --- a/crates/wasi-nn/Cargo.toml +++ b/crates/wasi-nn/Cargo.toml @@ -22,7 +22,7 @@ wasmtime-wasi = { path = "../wasi", version = "0.26.0" } wiggle = { path = "../wiggle", version = "0.26.0" } # These dependencies are necessary for the wasi-nn implementation: -openvino = "0.1.5" +openvino = { version = "0.3.1", features = ["runtime-linking"] } thiserror = "1.0" [build-dependencies] diff --git a/crates/wasi-nn/build.rs b/crates/wasi-nn/build.rs index 189b9513a6e0..535d0e0f80a9 100644 --- a/crates/wasi-nn/build.rs +++ b/crates/wasi-nn/build.rs @@ -1,11 +1,9 @@ //! This build script: //! - has the configuration necessary for the wiggle and witx macros. - -use std::path::PathBuf; - fn main() { // This is necessary for Wiggle/Witx macros. - let wasi_root = PathBuf::from("./spec").canonicalize().unwrap(); + let cwd = std::env::current_dir().unwrap(); + let wasi_root = cwd.join("spec"); println!("cargo:rustc-env=WASI_ROOT={}", wasi_root.display()); // Also automatically rebuild if the Witx files change diff --git a/crates/wasi-nn/src/ctx.rs b/crates/wasi-nn/src/ctx.rs index f180f113fb37..dbe93c72241c 100644 --- a/crates/wasi-nn/src/ctx.rs +++ b/crates/wasi-nn/src/ctx.rs @@ -2,7 +2,7 @@ //! wasi-nn API. use crate::r#impl::UsageError; use crate::witx::types::{Graph, GraphExecutionContext}; -use openvino::InferenceError; +use openvino::{InferenceError, SetupError}; use std::cell::RefCell; use std::collections::HashMap; use std::hash::Hash; @@ -14,8 +14,10 @@ use wiggle::GuestError; pub enum WasiNnError { #[error("guest error")] GuestError(#[from] GuestError), - #[error("openvino error")] - OpenvinoError(#[from] InferenceError), + #[error("openvino inference error")] + OpenvinoInferenceError(#[from] InferenceError), + #[error("openvino setup error")] + OpenvinoSetupError(#[from] SetupError), #[error("usage error")] UsageError(#[from] UsageError), } @@ -74,7 +76,7 @@ impl ExecutionContext { /// Capture the state necessary for calling into `openvino`. pub struct Ctx { - pub(crate) core: openvino::Core, + pub(crate) core: Option, pub(crate) graphs: Table, pub(crate) executions: Table, } @@ -83,7 +85,7 @@ impl Ctx { /// Make a new `WasiNnCtx` with the default settings. pub fn new() -> WasiNnResult { Ok(Self { - core: openvino::Core::new(None)?, + core: Option::default(), graphs: Table::default(), executions: Table::default(), }) diff --git a/crates/wasi-nn/src/impl.rs b/crates/wasi-nn/src/impl.rs index e18caafdc2d6..47fd323fdb41 100644 --- a/crates/wasi-nn/src/impl.rs +++ b/crates/wasi-nn/src/impl.rs @@ -12,6 +12,8 @@ use wiggle::GuestPtr; #[derive(Debug, Error)] pub enum UsageError { + #[error("Invalid context; has the load function been called?")] + InvalidContext, #[error("Only OpenVINO's IR is currently supported, passed encoding: {0:?}")] InvalidEncoding(GraphEncoding), #[error("OpenVINO expects only two buffers (i.e. [ir, weights]), passed: {0}")] @@ -34,9 +36,21 @@ impl<'a> WasiEphemeralNn for WasiNnCtx { if encoding != GraphEncoding::Openvino { return Err(UsageError::InvalidEncoding(encoding).into()); } + if builders.len() != 2 { return Err(UsageError::InvalidNumberOfBuilders(builders.len()).into()); } + + // Construct the context if none is present; this is done lazily (i.e. upon actually loading + // a model) because it may fail to find and load the OpenVINO libraries. The laziness limits + // the extent of the error only to wasi-nn users, not all WASI users. + if self.ctx.borrow().core.is_none() { + self.ctx + .borrow_mut() + .core + .replace(openvino::Core::new(None)?); + } + let builders = builders.as_ptr(); let xml = builders.read()?.as_slice()?; let weights = builders.add(1)?.read()?.as_slice()?; @@ -44,11 +58,15 @@ impl<'a> WasiEphemeralNn for WasiNnCtx { .ctx .borrow_mut() .core + .as_mut() + .ok_or(UsageError::InvalidContext)? .read_network_from_buffer(&xml, &weights)?; let executable_graph = self .ctx .borrow_mut() .core + .as_mut() + .ok_or(UsageError::InvalidContext)? .load_network(&graph, map_execution_target_to_string(target))?; let id = self .ctx @@ -94,7 +112,7 @@ impl<'a> WasiEphemeralNn for WasiNnCtx { .dimensions .as_slice()? .iter() - .map(|d| *d as u64) + .map(|d| *d as usize) .collect::>(); let precision = match tensor.type_ { TensorType::F16 => Precision::FP16, diff --git a/crates/wasi-nn/src/witx.rs b/crates/wasi-nn/src/witx.rs index 32cc0167d877..8244f9964443 100644 --- a/crates/wasi-nn/src/witx.rs +++ b/crates/wasi-nn/src/witx.rs @@ -14,7 +14,8 @@ impl<'a> types::UserErrorConversion for WasiNnCtx { fn nn_errno_from_wasi_nn_error(&self, e: WasiNnError) -> Result { eprintln!("Host error: {:?}", e); match e { - WasiNnError::OpenvinoError(_) => unimplemented!(), + WasiNnError::OpenvinoSetupError(_) => unimplemented!(), + WasiNnError::OpenvinoInferenceError(_) => unimplemented!(), WasiNnError::GuestError(_) => unimplemented!(), WasiNnError::UsageError(_) => unimplemented!(), } diff --git a/deny.toml b/deny.toml index d9baefa13683..a63b6864e462 100644 --- a/deny.toml +++ b/deny.toml @@ -23,6 +23,7 @@ allow = [ "Apache-2.0", "BSD-2-Clause", "CC0-1.0", + "ISC", "MIT", "MPL-2.0", "Zlib", diff --git a/src/commands/compile.rs b/src/commands/compile.rs index 1ad611623c16..9d601ab7a03b 100644 --- a/src/commands/compile.rs +++ b/src/commands/compile.rs @@ -28,7 +28,7 @@ lazy_static::lazy_static! { Compiling for a specific platform (Linux) and CPU preset (Skylake):\n\ \n \ wasmtime compile --target x86_64-unknown-linux --cranelift-enable skylake foo.wasm\n", - crate::WASM_FEATURES.as_str() + crate::FLAG_EXPLANATIONS.as_str() ) }; } diff --git a/src/commands/run.rs b/src/commands/run.rs index 9c57add91fee..4d6bce5b0b5f 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -1,6 +1,6 @@ //! The module that implements the `wasmtime run` command. -use crate::CommonOptions; +use crate::{CommonOptions, WasiModules}; use anyhow::{bail, Context as _, Result}; use std::thread; use std::time::Duration; @@ -70,7 +70,7 @@ fn parse_preloads(s: &str) -> Result<(String, PathBuf)> { lazy_static::lazy_static! { static ref AFTER_HELP: String = { - crate::WASM_FEATURES.to_string() + crate::FLAG_EXPLANATIONS.to_string() }; } @@ -146,7 +146,13 @@ impl RunCommand { let argv = self.compute_argv(); let mut linker = Linker::new(&store); - populate_with_wasi(&mut linker, preopen_dirs, &argv, &self.vars)?; + populate_with_wasi( + &mut linker, + preopen_dirs, + &argv, + &self.vars, + &self.common.wasi_modules.unwrap_or(WasiModules::default()), + )?; // Load the preload wasm modules. for (name, path) in self.preloads.iter() { @@ -351,6 +357,7 @@ fn populate_with_wasi( preopen_dirs: Vec<(String, Dir)>, argv: &[String], vars: &[(String, String)], + wasi_modules: &WasiModules, ) -> Result<()> { // Add the current snapshot to the linker. let mut builder = WasiCtxBuilder::new(); @@ -360,25 +367,40 @@ fn populate_with_wasi( builder = builder.preopened_dir(dir, name)?; } - Wasi::new(linker.store(), builder.build()?).add_to_linker(linker)?; + if wasi_modules.wasi_common { + Wasi::new(linker.store(), builder.build()?).add_to_linker(linker)?; + } - #[cfg(feature = "wasi-nn")] - { - use std::cell::RefCell; - use std::rc::Rc; - let wasi_nn = WasiNn::new(linker.store(), Rc::new(RefCell::new(WasiNnCtx::new()?))); - wasi_nn.add_to_linker(linker)?; + if wasi_modules.wasi_nn { + #[cfg(not(feature = "wasi-nn"))] + { + bail!("Cannot enable wasi-nn when the binary is not compiled with this feature."); + } + #[cfg(feature = "wasi-nn")] + { + use std::cell::RefCell; + use std::rc::Rc; + let wasi_nn = WasiNn::new(linker.store(), Rc::new(RefCell::new(WasiNnCtx::new()?))); + wasi_nn.add_to_linker(linker)?; + } } - #[cfg(feature = "wasi-crypto")] - { - use std::cell::RefCell; - use std::rc::Rc; - let cx_crypto = Rc::new(RefCell::new(WasiCryptoCtx::new())); - WasiCryptoCommon::new(linker.store(), cx_crypto.clone()).add_to_linker(linker)?; - WasiCryptoAsymmetricCommon::new(linker.store(), cx_crypto.clone()).add_to_linker(linker)?; - WasiCryptoSignatures::new(linker.store(), cx_crypto.clone()).add_to_linker(linker)?; - WasiCryptoSymmetric::new(linker.store(), cx_crypto).add_to_linker(linker)?; + if wasi_modules.wasi_crypto { + #[cfg(not(feature = "wasi-crypto"))] + { + bail!("Cannot enable wasi-crypto when the binary is not compiled with this feature."); + } + #[cfg(feature = "wasi-crypto")] + { + use std::cell::RefCell; + use std::rc::Rc; + let cx_crypto = Rc::new(RefCell::new(WasiCryptoCtx::new())); + WasiCryptoCommon::new(linker.store(), cx_crypto.clone()).add_to_linker(linker)?; + WasiCryptoAsymmetricCommon::new(linker.store(), cx_crypto.clone()) + .add_to_linker(linker)?; + WasiCryptoSignatures::new(linker.store(), cx_crypto.clone()).add_to_linker(linker)?; + WasiCryptoSymmetric::new(linker.store(), cx_crypto).add_to_linker(linker)?; + } } Ok(()) diff --git a/src/commands/wasm2obj.rs b/src/commands/wasm2obj.rs index ab914d8edc1b..b6426ed13a95 100644 --- a/src/commands/wasm2obj.rs +++ b/src/commands/wasm2obj.rs @@ -18,7 +18,7 @@ lazy_static::lazy_static! { The default is a dummy environment that produces placeholder values.\n\ \n\ {}", - crate::WASM_FEATURES.as_str() + crate::FLAG_EXPLANATIONS.as_str() ) }; } diff --git a/src/commands/wast.rs b/src/commands/wast.rs index cd749e61a640..08ad8e0a2ab2 100644 --- a/src/commands/wast.rs +++ b/src/commands/wast.rs @@ -9,7 +9,7 @@ use wasmtime_wast::WastContext; lazy_static::lazy_static! { static ref AFTER_HELP: String = { - crate::WASM_FEATURES.to_string() + crate::FLAG_EXPLANATIONS.to_string() }; } diff --git a/src/lib.rs b/src/lib.rs index 3485b6288975..facb0383c283 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,19 +43,47 @@ const SUPPORTED_WASM_FEATURES: &[(&str, &str)] = &[ ("threads", "enables support for WebAssembly threads"), ]; +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", + ), +]; + lazy_static::lazy_static! { - static ref WASM_FEATURES: String = { + static ref FLAG_EXPLANATIONS: String = { use std::fmt::Write; let mut s = String::new(); + + // Explain --wasm-features. writeln!(&mut s, "Supported values for `--wasm-features`:").unwrap(); writeln!(&mut s).unwrap(); - let max = SUPPORTED_WASM_FEATURES.iter().max_by_key(|(name, _)| name.len()).unwrap(); - for (name, desc) in SUPPORTED_WASM_FEATURES.iter() { writeln!(&mut s, "{:width$} {}", name, desc, width = max.0.len() + 2).unwrap(); } + writeln!(&mut s).unwrap(); + + // Explain --wasi-modules. + writeln!(&mut s, "Supported values for `--wasi-modules`:").unwrap(); + writeln!(&mut s).unwrap(); + let max = SUPPORTED_WASI_MODULES.iter().max_by_key(|(name, _)| name.len()).unwrap(); + for (name, desc) in SUPPORTED_WASI_MODULES.iter() { + writeln!(&mut s, "{:width$} {}", name, desc, width = max.0.len() + 2).unwrap(); + } writeln!(&mut s).unwrap(); writeln!(&mut s, "Features prefixed with '-' will be disabled.").unwrap(); @@ -186,6 +214,10 @@ struct CommonOptions { #[structopt(long, value_name = "FEATURE,FEATURE,...", parse(try_from_str = parse_wasm_features))] wasm_features: Option, + /// Enables or disables WASI modules + #[structopt(long, value_name = "MODULE,MODULE,...", parse(try_from_str = parse_wasi_modules))] + wasi_modules: Option, + /// Use Lightbeam for all compilation #[structopt(long, conflicts_with = "cranelift")] lightbeam: bool, @@ -408,6 +440,75 @@ fn parse_wasm_features(features: &str) -> Result { memory64: false, }) } + +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() { @@ -574,4 +675,61 @@ mod test { feature_test!(test_simd_feature, simd, "simd"); feature_test!(test_threads_feature, threads, "threads"); feature_test!(test_multi_memory_feature, multi_memory, "multi-memory"); + + #[test] + fn test_default_modules() { + let options = CommonOptions::from_iter_safe(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::from_iter_safe(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::from_iter_safe(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::from_iter_safe(vec!["foo", "--wasi-modules=-default"]).unwrap(); + assert_eq!( + options.wasi_modules.unwrap(), + WasiModules { + wasi_common: false, + wasi_nn: false, + wasi_crypto: false + } + ); + } }