Skip to content

Commit 22b4939

Browse files
alexcrichtonbongjunj
authored andcommitted
Add a new -Sp3 option to the CLI (bytecodealliance#11423)
* Add a new `-Sp3` option to the CLI This commit adds a new WASI-specific CLI option dubbed "p3" which controls whether WASIp3 interfaces are added to the component linker and whether WASIp3 exports are searched for. Integration here is only added with `wasmtime run` and support for `wasmtime serve` will come once wasi:http is migrated to this repository from the p3-prototyping repository. The `-Sp3` option is off-by-default at this time but this is structured to enable it by default in the future. A test is added to ensure that WASIp3 is usable and a basic "hello world" works. Some internal refactoring was done to share more code between `run` and `serve` with respect to adding WASI interfaces to a linker. * Use a normal blocking test * Fix warning
1 parent bfafd0e commit 22b4939

File tree

7 files changed

+162
-60
lines changed

7 files changed

+162
-60
lines changed

Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,11 @@ wmemcheck = ["wasmtime/wmemcheck"]
485485
trace-log = ["wasmtime/trace-log"]
486486
memory-protection-keys = ["wasmtime-cli-flags/memory-protection-keys"]
487487
profile-pulley = ["wasmtime/profile-pulley"]
488-
component-model-async = ["wasmtime-cli-flags/component-model-async", "component-model"]
488+
component-model-async = [
489+
"wasmtime-cli-flags/component-model-async",
490+
"component-model",
491+
"wasmtime-wasi?/p3",
492+
]
489493

490494
# This feature, when enabled, will statically compile out all logging statements
491495
# throughout Wasmtime and its dependencies.

crates/cli-flags/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,8 @@ wasmtime_option_group! {
477477
/// Preset data for the In-Memory provider of WASI key-value API.
478478
#[serde(skip)]
479479
pub keyvalue_in_memory_data: Vec<KeyValuePair>,
480+
/// Enable support for WASIp3 APIs.
481+
pub p3: Option<bool>,
480482
}
481483

482484
enum Wasi {
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use test_programs::p3::*;
2+
3+
struct Component;
4+
5+
export!(Component);
6+
7+
impl exports::wasi::cli::run::Guest for Component {
8+
async fn run() -> Result<(), ()> {
9+
let (mut tx, rx) = wit_stream::new();
10+
wasi::cli::stdout::set_stdout(rx);
11+
tx.write(b"hello, world\n".to_vec()).await;
12+
Ok(())
13+
}
14+
}
15+
16+
fn main() {
17+
panic!("should call p3 entrypoint");
18+
}

src/commands/run.rs

Lines changed: 57 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -493,30 +493,13 @@ impl RunCommand {
493493
#[cfg(feature = "component-model")]
494494
CliLinker::Component(linker) => {
495495
let component = main_target.unwrap_component();
496-
if self.invoke.is_some() {
496+
let result = if self.invoke.is_some() {
497497
self.invoke_component(&mut *store, component, linker).await
498498
} else {
499-
let command = wasmtime_wasi::p2::bindings::Command::instantiate_async(
500-
&mut *store,
501-
component,
502-
linker,
503-
)
504-
.await?;
505-
506-
let result = command
507-
.wasi_cli_run()
508-
.call_run(&mut *store)
499+
self.run_command_component(&mut *store, component, linker)
509500
.await
510-
.context("failed to invoke `run` function")
511-
.map_err(|e| self.handle_core_dump(&mut *store, e));
512-
513-
// Translate the `Result<(),()>` produced by wasm into a feigned
514-
// explicit exit here with status 1 if `Err(())` is returned.
515-
result.and_then(|wasm_result| match wasm_result {
516-
Ok(()) => Ok(()),
517-
Err(()) => Err(wasmtime_wasi::I32Exit(1).into()),
518-
})
519-
}
501+
};
502+
result.map_err(|e| self.handle_core_dump(&mut *store, e))
520503
}
521504
};
522505
finish_epoch_handler(store);
@@ -596,6 +579,56 @@ impl RunCommand {
596579
Ok(())
597580
}
598581

582+
/// Execute the default behavior for components on the CLI, looking for
583+
/// `wasi:cli`-based commands and running their exported `run` function.
584+
#[cfg(feature = "component-model")]
585+
async fn run_command_component(
586+
&self,
587+
store: &mut Store<Host>,
588+
component: &wasmtime::component::Component,
589+
linker: &wasmtime::component::Linker<Host>,
590+
) -> Result<()> {
591+
let instance = linker.instantiate_async(&mut *store, component).await?;
592+
593+
let mut result = None;
594+
let _ = &mut result;
595+
596+
// If WASIp3 is enabled at compile time, enabled at runtime, and found
597+
// in this component then use that to generate the result.
598+
#[cfg(feature = "component-model-async")]
599+
if self.run.common.wasi.p3.unwrap_or(crate::common::P3_DEFAULT) {
600+
if let Ok(command) = wasmtime_wasi::p3::bindings::Command::new(&mut *store, &instance) {
601+
result = Some(
602+
instance
603+
.run_concurrent(&mut *store, async |store| {
604+
command.wasi_cli_run().call_run(store).await
605+
})
606+
.await?,
607+
);
608+
}
609+
}
610+
611+
let result = match result {
612+
Some(result) => result,
613+
// If WASIp3 wasn't found then fall back to requiring WASIp2 and
614+
// this'll report an error if the right export doesn't exist.
615+
None => {
616+
wasmtime_wasi::p2::bindings::Command::new(&mut *store, &instance)?
617+
.wasi_cli_run()
618+
.call_run(&mut *store)
619+
.await
620+
}
621+
};
622+
let wasm_result = result.context("failed to invoke `run` function")?;
623+
624+
// Translate the `Result<(),()>` produced by wasm into a feigned
625+
// explicit exit here with status 1 if `Err(())` is returned.
626+
match wasm_result {
627+
Ok(()) => Ok(()),
628+
Err(()) => Err(wasmtime_wasi::I32Exit(1).into()),
629+
}
630+
}
631+
599632
#[cfg(feature = "component-model")]
600633
fn search_component(
601634
engine: &Engine,
@@ -756,20 +789,8 @@ impl RunCommand {
756789
store: &mut Store<Host>,
757790
module: &RunTarget,
758791
) -> Result<()> {
759-
let mut cli = self.run.common.wasi.cli;
760-
761-
// Accept -Scommon as a deprecated alias for -Scli.
762-
if let Some(common) = self.run.common.wasi.common {
763-
if cli.is_some() {
764-
bail!(
765-
"The -Scommon option should not be use with -Scli as it is a deprecated alias"
766-
);
767-
} else {
768-
// In the future, we may add a warning here to tell users to use
769-
// `-S cli` instead of `-S common`.
770-
cli = Some(common);
771-
}
772-
}
792+
self.run.validate_p3_option()?;
793+
let cli = self.run.validate_cli_enabled()?;
773794

774795
if cli != Some(false) {
775796
match linker {
@@ -801,8 +822,7 @@ impl RunCommand {
801822
}
802823
#[cfg(feature = "component-model")]
803824
CliLinker::Component(linker) => {
804-
let link_options = self.run.compute_wasi_features();
805-
wasmtime_wasi::p2::add_to_linker_with_options_async(linker, &link_options)?;
825+
self.run.add_wasmtime_wasi_to_linker(linker)?;
806826
self.set_wasi_ctx(store)?;
807827
}
808828
}

src/commands/serve.rs

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -256,20 +256,8 @@ impl ServeCommand {
256256
}
257257

258258
fn add_to_linker(&self, linker: &mut Linker<Host>) -> Result<()> {
259-
let mut cli = self.run.common.wasi.cli;
260-
261-
// Accept -Scommon as a deprecated alias for -Scli.
262-
if let Some(common) = self.run.common.wasi.common {
263-
if cli.is_some() {
264-
bail!(
265-
"The -Scommon option should not be use with -Scli as it is a deprecated alias"
266-
);
267-
} else {
268-
// In the future, we may add a warning here to tell users to use
269-
// `-S cli` instead of `-S common`.
270-
cli = Some(common);
271-
}
272-
}
259+
self.run.validate_p3_option()?;
260+
let cli = self.run.validate_cli_enabled()?;
273261

274262
// Repurpose the `-Scli` flag of `wasmtime run` for `wasmtime serve`
275263
// to serve as a signal to enable all WASI interfaces instead of just
@@ -280,8 +268,7 @@ impl ServeCommand {
280268
// bindings which adds just those interfaces that the proxy interface
281269
// uses.
282270
if cli == Some(true) {
283-
let link_options = self.run.compute_wasi_features();
284-
wasmtime_wasi::p2::add_to_linker_with_options_async(linker, &link_options)?;
271+
self.run.add_wasmtime_wasi_to_linker(linker)?;
285272
wasmtime_wasi_http::add_only_http_to_linker_async(linker)?;
286273
} else {
287274
wasmtime_wasi_http::add_to_linker_async(linker)?;

src/common.rs

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,16 @@ use std::{fs::File, path::Path, time::Duration};
77
use wasmtime::{Engine, Module, Precompiled, StoreLimits, StoreLimitsBuilder};
88
use wasmtime_cli_flags::{CommonOptions, opt::WasmtimeOptionValue};
99
use wasmtime_wasi::WasiCtxBuilder;
10-
use wasmtime_wasi::p2::bindings::LinkOptions;
1110

1211
#[cfg(feature = "component-model")]
1312
use wasmtime::component::Component;
1413

14+
/// Whether or not WASIp3 is enabled by default.
15+
///
16+
/// Currently this is disabled (the `&& false`), but that'll get removed in the
17+
/// future.
18+
pub const P3_DEFAULT: bool = cfg!(feature = "component-model-async") && false;
19+
1520
pub enum RunTarget {
1621
Core(Module),
1722

@@ -336,11 +341,60 @@ impl RunCommon {
336341
Ok(listeners)
337342
}
338343

339-
pub fn compute_wasi_features(&self) -> LinkOptions {
340-
let mut options = LinkOptions::default();
341-
options.cli_exit_with_code(self.common.wasi.cli_exit_with_code.unwrap_or(false));
342-
options.network_error_code(self.common.wasi.network_error_code.unwrap_or(false));
343-
options
344+
pub fn validate_p3_option(&self) -> Result<()> {
345+
let p3 = self.common.wasi.p3.unwrap_or(P3_DEFAULT);
346+
if p3 && !cfg!(feature = "component-model-async") {
347+
bail!("support for WASIp3 disabled at compile time");
348+
}
349+
Ok(())
350+
}
351+
352+
pub fn validate_cli_enabled(&self) -> Result<Option<bool>> {
353+
let mut cli = self.common.wasi.cli;
354+
355+
// Accept -Scommon as a deprecated alias for -Scli.
356+
if let Some(common) = self.common.wasi.common {
357+
if cli.is_some() {
358+
bail!(
359+
"The -Scommon option should not be use with -Scli as it is a deprecated alias"
360+
);
361+
} else {
362+
// In the future, we may add a warning here to tell users to use
363+
// `-S cli` instead of `-S common`.
364+
cli = Some(common);
365+
}
366+
}
367+
368+
Ok(cli)
369+
}
370+
371+
/// Adds `wasmtime-wasi` interfaces (dubbed "-Scli" in the flags to the
372+
/// `wasmtime` command) to the `linker` provided.
373+
///
374+
/// This will handle adding various WASI standard versions to the linker
375+
/// internally.
376+
#[cfg(feature = "component-model")]
377+
pub fn add_wasmtime_wasi_to_linker<T>(
378+
&self,
379+
linker: &mut wasmtime::component::Linker<T>,
380+
) -> Result<()>
381+
where
382+
T: wasmtime_wasi::WasiView,
383+
{
384+
let mut p2_options = wasmtime_wasi::p2::bindings::LinkOptions::default();
385+
p2_options.cli_exit_with_code(self.common.wasi.cli_exit_with_code.unwrap_or(false));
386+
p2_options.network_error_code(self.common.wasi.network_error_code.unwrap_or(false));
387+
wasmtime_wasi::p2::add_to_linker_with_options_async(linker, &p2_options)?;
388+
389+
#[cfg(feature = "component-model-async")]
390+
if self.common.wasi.p3.unwrap_or(P3_DEFAULT) {
391+
let mut p3_options = wasmtime_wasi::p3::bindings::LinkOptions::default();
392+
p3_options.cli_exit_with_code(self.common.wasi.cli_exit_with_code.unwrap_or(false));
393+
wasmtime_wasi::p3::add_to_linker_with_options(linker, &p3_options)
394+
.context("failed to link `wasi:[email protected]`")?;
395+
}
396+
397+
Ok(())
344398
}
345399
}
346400

tests/all/cli_tests.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2123,6 +2123,23 @@ start a print 1234
21232123
cli_serve_guest_never_invoked_set(CLI_SERVE_TRAP_BEFORE_SET_COMPONENT).await
21242124
}
21252125

2126+
#[test]
2127+
fn cli_p3_hello_stdout() -> Result<()> {
2128+
let output = run_wasmtime(&[
2129+
"run",
2130+
"-Wcomponent-model-async",
2131+
"-Sp3",
2132+
CLI_P3_HELLO_STDOUT_COMPONENT,
2133+
]);
2134+
if cfg!(feature = "component-model-async") {
2135+
let output = output?;
2136+
assert_eq!(output, "hello, world\n");
2137+
} else {
2138+
assert!(output.is_err());
2139+
}
2140+
Ok(())
2141+
}
2142+
21262143
mod invoke {
21272144
use super::*;
21282145

0 commit comments

Comments
 (0)