From b743ff2003a2e391972330aa8e8437c6356e580a Mon Sep 17 00:00:00 2001 From: Eduardo Rodrigues Date: Tue, 22 Aug 2023 13:12:25 +0200 Subject: [PATCH] feat(wasmtime-cli): add async support flag Within the wasmtime CLI, the current default behavior is to only inject the synchronous functions to linkers. This will add a flag called `--async` that will inject the asynchronous one instead. --- Cargo.lock | 1 + Cargo.toml | 38 +++++++++++++++++++----- src/commands/run.rs | 67 ++++++++++++++++++++++++++++++++---------- tests/all/cli_tests.rs | 35 ++++++++++++++++++++++ 4 files changed, 119 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4999c5e69230..d11c787392c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3423,6 +3423,7 @@ dependencies = [ "criterion", "env_logger 0.10.0", "filecheck", + "futures", "humantime 2.1.0", "libc", "listenfd", diff --git a/Cargo.toml b/Cargo.toml index a49b8fd53ba0..b4a34c7cbd5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ wasmtime-wasi-http = { workspace = true, optional = true } wasmtime-runtime = { workspace = true } clap = { workspace = true, features = ["color", "suggestions", "derive"] } anyhow = { workspace = true } +futures = { workspace = true } target-lexicon = { workspace = true } humantime = "2.0.0" once_cell = { workspace = true } @@ -51,14 +52,24 @@ rustix = { workspace = true, features = ["mm", "param"] } [dev-dependencies] # depend again on wasmtime to activate its default features for tests -wasmtime = { workspace = true, features = ['component-model', 'async', 'default', 'winch'] } +wasmtime = { workspace = true, features = [ + 'component-model', + 'async', + 'default', + 'winch', +] } env_logger = { workspace = true } log = { workspace = true } filecheck = { workspace = true } tempfile = { workspace = true } test-programs = { path = "crates/test-programs" } wasmtime-runtime = { workspace = true } -tokio = { version = "1.8.0", features = ["rt", "time", "macros", "rt-multi-thread"] } +tokio = { version = "1.8.0", features = [ + "rt", + "time", + "macros", + "rt-multi-thread", +] } wast = { workspace = true } criterion = "0.5.0" num_cpus = "1.13.0" @@ -187,7 +198,9 @@ byte-array-literals = { path = "crates/wasi-preview1-component-adapter/byte-arra regalloc2 = "0.9.2" # cap-std family: -target-lexicon = { version = "0.12.3", default-features = false, features = ["std"] } +target-lexicon = { version = "0.12.3", default-features = false, features = [ + "std", +] } cap-std = "2.0.0" cap-rand = { version = "2.0.0", features = ["small_rng"] } cap-fs-ext = "2.0.0" @@ -215,8 +228,15 @@ wit-component = "0.13.2" # Non-Bytecode Alliance maintained dependencies: # -------------------------- -object = { version = "0.31.1", default-features = false, features = ['read_core', 'elf', 'std'] } -gimli = { version = "0.27.0", default-features = false, features = ['read', 'std'] } +object = { version = "0.31.1", default-features = false, features = [ + 'read_core', + 'elf', + 'std', +] } +gimli = { version = "0.27.0", default-features = false, features = [ + 'read', + 'std', +] } anyhow = "1.0.22" windows-sys = "0.48.0" env_logger = "0.10" @@ -255,6 +275,7 @@ default = [ "jitdump", "wasmtime/wat", "wasmtime/parallel-compilation", + "wasmtime/async", "vtune", "wasi-nn", "wasi-threads", @@ -266,12 +287,15 @@ vtune = ["wasmtime/vtune"] wasi-nn = ["dep:wasmtime-wasi-nn"] wasi-threads = ["dep:wasmtime-wasi-threads"] wasi-http = ["dep:wasmtime-wasi-http"] -pooling-allocator = ["wasmtime/pooling-allocator", "wasmtime-cli-flags/pooling-allocator"] +pooling-allocator = [ + "wasmtime/pooling-allocator", + "wasmtime-cli-flags/pooling-allocator", +] all-arch = ["wasmtime/all-arch"] component-model = [ "wasmtime/component-model", "wasmtime-wast/component-model", - "wasmtime-cli-flags/component-model" + "wasmtime-cli-flags/component-model", ] winch = ["wasmtime/winch"] wmemcheck = ["wasmtime/wmemcheck"] diff --git a/src/commands/run.rs b/src/commands/run.rs index 89a050e1b105..3e33e381ef59 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -91,6 +91,11 @@ fn parse_profile(s: &str) -> Result { } } +fn run_future(future: F) -> F::Output { + let mut f = Box::pin(future); + futures::executor::block_on(f.as_mut()) +} + static AFTER_HELP: Lazy = Lazy::new(|| crate::FLAG_EXPLANATIONS.to_string()); /// Runs a WebAssembly module @@ -254,6 +259,10 @@ pub struct RunCommand { /// removed. For now this is primarily here for testing. #[clap(long)] preview2: bool, + + /// Indicates that the async support will be enabled + #[clap(long = "async")] + async_support: bool, } #[derive(Clone)] @@ -312,6 +321,9 @@ impl RunCommand { } None => {} } + if self.async_support { + config.async_support(true); + } config.wmemcheck(self.wmemcheck); @@ -597,7 +609,12 @@ impl RunCommand { CliLinker::Core(linker) => { // Use "" as a default module name. let module = module.unwrap_core(); - linker.module(&mut *store, "", &module).context(format!( + if self.async_support { + run_future(linker.module_async(&mut *store, "", &module)) + } else { + linker.module(&mut *store, "", &module) + } + .context(format!( "failed to instantiate {:?}", self.module_and_args[0] ))?; @@ -626,17 +643,24 @@ impl RunCommand { let component = module.unwrap_component(); - let (command, _instance) = preview2::command::sync::Command::instantiate( - &mut *store, - &component, - &linker, - )?; - - let result = command - .wasi_cli_run() - .call_run(&mut *store) - .context("failed to invoke `run` function") - .map_err(|e| self.handle_coredump(e)); + let result = if self.async_support { + let (command, _instance) = + run_future(preview2::command::Command::instantiate_async( + &mut *store, + component, + linker, + ))?; + run_future(command.wasi_cli_run().call_run(&mut *store)) + } else { + let (command, _instance) = preview2::command::sync::Command::instantiate( + &mut *store, + component, + linker, + )?; + command.wasi_cli_run().call_run(&mut *store) + } + .context("failed to invoke `run` function") + .map_err(|e| self.handle_coredump(e)); // Translate the `Result<(),()>` produced by wasm into a feigned // explicit exit here with status 1 if `Err(())` is returned. @@ -690,7 +714,12 @@ impl RunCommand { // Invoke the function and then afterwards print all the results that came // out, if there are any. let mut results = vec![Val::null(); ty.results().len()]; - let invoke_res = func.call(store, &values, &mut results).with_context(|| { + let invoke_res = if self.async_support { + run_future(func.call_async(store, &values, &mut results)) + } else { + func.call(store, &values, &mut results) + } + .with_context(|| { if let Some(name) = &self.invoke { format!("failed to invoke `{}`", name) } else { @@ -876,7 +905,11 @@ impl RunCommand { match linker { CliLinker::Core(linker) => { if self.preview2 { - wasmtime_wasi::preview2::preview1::add_to_linker_sync(linker)?; + if self.async_support { + preview2::preview1::add_to_linker_async(linker)?; + } else { + preview2::preview1::add_to_linker_sync(linker)?; + } self.set_preview2_ctx(store)?; } else { wasmtime_wasi::add_to_linker(linker, |host| { @@ -887,7 +920,11 @@ impl RunCommand { } #[cfg(feature = "component-model")] CliLinker::Component(linker) => { - wasmtime_wasi::preview2::command::sync::add_to_linker(linker)?; + if self.async_support { + preview2::command::add_to_linker(linker)?; + } else { + preview2::command::sync::add_to_linker(linker)?; + } self.set_preview2_ctx(store)?; } } diff --git a/tests/all/cli_tests.rs b/tests/all/cli_tests.rs index 1f8200cc82af..337d2f270293 100644 --- a/tests/all/cli_tests.rs +++ b/tests/all/cli_tests.rs @@ -730,6 +730,14 @@ fn wasi_misaligned_pointer() -> Result<()> { Ok(()) } +#[test] +fn hello_with_async() -> Result<()> { + let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot1.wat")?; + let stdout = run_wasmtime(&["--disable-cache", "--async", wasm.path().to_str().unwrap()])?; + assert_eq!(stdout, "Hello, world!\n"); + Ok(()) +} + #[test] #[ignore] // FIXME(#6811) currently is flaky and may produce no output fn hello_with_preview2() -> Result<()> { @@ -743,6 +751,20 @@ fn hello_with_preview2() -> Result<()> { Ok(()) } +#[test] +#[ignore] // FIXME(#6811) currently is flaky and may produce no output +fn hello_with_preview2_and_async() -> Result<()> { + let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot1.wat")?; + let stdout = run_wasmtime(&[ + "--disable-cache", + "--preview2", + "--async", + wasm.path().to_str().unwrap(), + ])?; + assert_eq!(stdout, "Hello, world!\n"); + Ok(()) +} + #[test] #[cfg_attr(not(feature = "component-model"), ignore)] fn component_missing_feature() -> Result<()> { @@ -808,6 +830,19 @@ fn run_basic_component() -> Result<()> { Ok(()) } +#[test] +#[cfg_attr(not(feature = "component-model"), ignore)] +fn run_basic_component_async() -> Result<()> { + let wasm = build_wasm("tests/all/cli_tests/component-basic.wat")?; + run_wasmtime(&[ + "--disable-cache", + "--wasm-features=component-model", + "--async", + wasm.path().to_str().unwrap(), + ])?; + Ok(()) +} + #[test] #[cfg_attr(not(feature = "component-model"), ignore)] fn run_precompiled_component() -> Result<()> {