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 + } + ); + } }