Skip to content

Commit

Permalink
Refactor Wasmtime CLI to support components (bytecodealliance#6836)
Browse files Browse the repository at this point in the history
This commit refactors the `wasmtime` CLI executable to be able to
support not only compiling components but additionally executing
components. While I was doing this I've additionally added a new
`--preview2` argument to enable using the new experimental
implementation of preview1 based on preview2 type/structs. This is
off-by-default but is expected to become the default in the future.

Some notable features of this change are:

* The preview1-implemented-with-preview2 module now sports
  `add_to_linker_{async,sync}` to replace the previous `add_to_linker`
  which always did async.

* Some trait bounds in the preview1-implemented-with-preview2 module are
  simplified.

* Some minor changes were made to `wiggle`'s macros to support a "block
  on" that isn't the default wiggle dummy executor (as now we actually
  do need Tokio)

* Many options related to core wasm `Linker` configuration, such as
  `--default-values-unknown-imports`, are not implemented for components
  at this time. When used with components these options return an error.

* Construction of WASI contexts has been refactored to pass around fewer
  arguments to avoid threading through lots of values for both preview1
  and preview2.

* Reading the input to the Wasmtime CLI has been updated to read the
  input in the `run` subcommand before handing it off to the `wasmtime`
  crate's API to enable the CLI to use the contents of what's loaded to
  determine what to do next.

* Our generic `./ci/run-tests.sh` script has been updated to pass the
  `--features component-model` flag to ensure that this CLI support is
  tested during the normal test suite.

* The CLI support for `wasi-nn` supports components as well as core wasm
  modules.
  • Loading branch information
alexcrichton authored Aug 19, 2023
1 parent 6ddb7cc commit 367bdc8
Show file tree
Hide file tree
Showing 15 changed files with 677 additions and 269 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ wasmtime-wasi = { workspace = true, features = ["exit"] }
wasmtime-wasi-nn = { workspace = true, optional = true }
wasmtime-wasi-threads = { workspace = true, optional = true }
wasmtime-wasi-http = { workspace = true, optional = true }
wasmtime-runtime = { workspace = true }
clap = { workspace = true, features = ["color", "suggestions", "derive"] }
anyhow = { workspace = true }
target-lexicon = { workspace = true }
Expand Down
1 change: 1 addition & 0 deletions ci/run-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ cargo test \
--features "test-programs/test_programs" \
--features wasi-threads \
--features wasi-http \
--features component-model \
--workspace \
--exclude 'wasmtime-wasi-*' \
--exclude wasi-tests \
Expand Down
2 changes: 1 addition & 1 deletion crates/test-programs/tests/wasi-http-modules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ impl WasiHttpView for Ctx {
async fn instantiate_module(module: Module, ctx: Ctx) -> Result<(Store<Ctx>, Func), anyhow::Error> {
let mut linker = Linker::new(&ENGINE);
wasmtime_wasi_http::add_to_linker(&mut linker)?;
wasmtime_wasi::preview2::preview1::add_to_linker(&mut linker)?;
wasmtime_wasi::preview2::preview1::add_to_linker_async(&mut linker)?;

let mut store = Store::new(&ENGINE, ctx);

Expand Down
4 changes: 2 additions & 2 deletions crates/test-programs/tests/wasi-preview1-host-in-preview2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use tempfile::TempDir;
use wasmtime::{Config, Engine, Linker, Store};
use wasmtime_wasi::preview2::{
pipe::MemoryOutputPipe,
preview1::{add_to_linker, WasiPreview1Adapter, WasiPreview1View},
preview1::{add_to_linker_async, WasiPreview1Adapter, WasiPreview1View},
DirPerms, FilePerms, IsATTY, Table, WasiCtx, WasiCtxBuilder, WasiView,
};

Expand Down Expand Up @@ -34,7 +34,7 @@ async fn run(name: &str, inherit_stdio: bool) -> Result<()> {
let stderr = MemoryOutputPipe::new();
let r = {
let mut linker = Linker::new(&ENGINE);
add_to_linker(&mut linker)?;
add_to_linker_async(&mut linker)?;

// Create our wasi context.
// Additionally register any preopened directories if we have them.
Expand Down
4 changes: 4 additions & 0 deletions crates/wasi-nn/src/wit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ impl gen::inference::Host for WasiNnCtx {
}
}

impl gen::errors::Host for WasiNnCtx {}

impl gen::tensor::Host for WasiNnCtx {}

impl TryFrom<gen::graph::GraphEncoding> for crate::backend::BackendKind {
type Error = UsageError;
fn try_from(value: gen::graph::GraphEncoding) -> Result<Self, Self::Error> {
Expand Down
47 changes: 34 additions & 13 deletions crates/wasi/src/preview2/preview1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ impl WasiPreview1Adapter {

// Any context that needs to support preview 1 will impl this trait. They can
// construct the needed member with WasiPreview1Adapter::new().
pub trait WasiPreview1View: Send + Sync + WasiView {
pub trait WasiPreview1View: WasiView {
fn adapter(&self) -> &WasiPreview1Adapter;
fn adapter_mut(&mut self) -> &mut WasiPreview1Adapter;
}
Expand Down Expand Up @@ -390,23 +390,18 @@ trait WasiPreview1ViewExt:

impl<T: WasiPreview1View + preopens::Host> WasiPreview1ViewExt for T {}

pub fn add_to_linker<
T: WasiPreview1View
+ bindings::cli::environment::Host
+ bindings::cli::exit::Host
+ bindings::filesystem::types::Host
+ bindings::filesystem::preopens::Host
+ bindings::sync_io::poll::poll::Host
+ bindings::random::random::Host
+ bindings::io::streams::Host
+ bindings::clocks::monotonic_clock::Host
+ bindings::clocks::wall_clock::Host,
>(
pub fn add_to_linker_async<T: WasiPreview1View>(
linker: &mut wasmtime::Linker<T>,
) -> anyhow::Result<()> {
wasi_snapshot_preview1::add_to_linker(linker, |t| t)
}

pub fn add_to_linker_sync<T: WasiPreview1View>(
linker: &mut wasmtime::Linker<T>,
) -> anyhow::Result<()> {
sync::add_wasi_snapshot_preview1_to_linker(linker, |t| t)
}

// Generate the wasi_snapshot_preview1::WasiSnapshotPreview1 trait,
// and the module types.
// None of the generated modules, traits, or types should be used externally
Expand All @@ -425,6 +420,32 @@ wiggle::from_witx!({
errors: { errno => trappable Error },
});

mod sync {
use anyhow::Result;
use std::future::Future;

wiggle::wasmtime_integration!({
witx: ["$CARGO_MANIFEST_DIR/witx/wasi_snapshot_preview1.witx"],
target: super,
block_on[in_tokio]: {
wasi_snapshot_preview1::{
fd_advise, fd_close, fd_datasync, fd_fdstat_get, fd_filestat_get, fd_filestat_set_size,
fd_filestat_set_times, fd_read, fd_pread, fd_seek, fd_sync, fd_readdir, fd_write,
fd_pwrite, poll_oneoff, path_create_directory, path_filestat_get,
path_filestat_set_times, path_link, path_open, path_readlink, path_remove_directory,
path_rename, path_symlink, path_unlink_file
}
},
errors: { errno => trappable Error },
});

// Small wrapper around `in_tokio` to add a `Result` layer which is always
// `Ok`
fn in_tokio<F: Future>(future: F) -> Result<F::Output> {
Ok(crate::preview2::in_tokio(future))
}
}

impl wiggle::GuestErrorType for types::Errno {
fn success() -> Self {
Self::Success
Expand Down
6 changes: 6 additions & 0 deletions crates/wasi/src/preview2/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,3 +285,9 @@ impl Table {
})
}
}

impl Default for Table {
fn default() -> Self {
Table::new()
}
}
80 changes: 23 additions & 57 deletions crates/wiggle/generate/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use {
proc_macro2::Span,
proc_macro2::{Span, TokenStream},
std::{collections::HashMap, iter::FromIterator, path::PathBuf},
syn::{
braced, bracketed,
Expand Down Expand Up @@ -61,14 +61,21 @@ impl Parse for ConfigField {
input.parse::<Token![async]>()?;
input.parse::<Token![:]>()?;
Ok(ConfigField::Async(AsyncConf {
blocking: false,
block_with: None,
functions: input.parse()?,
}))
} else if lookahead.peek(kw::block_on) {
input.parse::<kw::block_on>()?;
let block_with = if input.peek(syn::token::Bracket) {
let content;
let _ = bracketed!(content in input);
content.parse()?
} else {
quote::quote!(wiggle::run_in_dummy_executor)
};
input.parse::<Token![:]>()?;
Ok(ConfigField::Async(AsyncConf {
blocking: true,
block_with: Some(block_with),
functions: input.parse()?,
}))
} else if lookahead.peek(kw::wasmtime) {
Expand Down Expand Up @@ -381,16 +388,16 @@ impl std::fmt::Debug for UserErrorConfField {
#[derive(Clone, Default, Debug)]
/// Modules and funcs that have async signatures
pub struct AsyncConf {
blocking: bool,
block_with: Option<TokenStream>,
functions: AsyncFunctions,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug)]
pub enum Asyncness {
/// Wiggle function is synchronous, wasmtime Func is synchronous
Sync,
/// Wiggle function is asynchronous, but wasmtime Func is synchronous
Blocking,
Blocking { block_with: TokenStream },
/// Wiggle function and wasmtime Func are asynchronous.
Async,
}
Expand All @@ -402,10 +409,10 @@ impl Asyncness {
_ => false,
}
}
pub fn is_blocking(&self) -> bool {
pub fn blocking(&self) -> Option<&TokenStream> {
match self {
Self::Blocking => true,
_ => false,
Self::Blocking { block_with } => Some(block_with),
_ => None,
}
}
pub fn is_sync(&self) -> bool {
Expand All @@ -429,10 +436,11 @@ impl Default for AsyncFunctions {

impl AsyncConf {
pub fn get(&self, module: &str, function: &str) -> Asyncness {
let a = if self.blocking {
Asyncness::Blocking
} else {
Asyncness::Async
let a = match &self.block_with {
Some(block_with) => Asyncness::Blocking {
block_with: block_with.clone(),
},
None => Asyncness::Async,
};
match &self.functions {
AsyncFunctions::Some(fs) => {
Expand Down Expand Up @@ -577,54 +585,12 @@ impl Parse for WasmtimeConfig {

impl Parse for WasmtimeConfigField {
fn parse(input: ParseStream) -> Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::target) {
if input.peek(kw::target) {
input.parse::<kw::target>()?;
input.parse::<Token![:]>()?;
Ok(WasmtimeConfigField::Target(input.parse()?))

// The remainder of this function is the ConfigField impl, wrapped in
// WasmtimeConfigField::Core. This is required to get the correct lookahead error.
} else if lookahead.peek(kw::witx) {
input.parse::<kw::witx>()?;
input.parse::<Token![:]>()?;
Ok(WasmtimeConfigField::Core(ConfigField::Witx(
WitxConf::Paths(input.parse()?),
)))
} else if lookahead.peek(kw::witx_literal) {
input.parse::<kw::witx_literal>()?;
input.parse::<Token![:]>()?;
Ok(WasmtimeConfigField::Core(ConfigField::Witx(
WitxConf::Literal(input.parse()?),
)))
} else if lookahead.peek(kw::errors) {
input.parse::<kw::errors>()?;
input.parse::<Token![:]>()?;
Ok(WasmtimeConfigField::Core(ConfigField::Error(
input.parse()?,
)))
} else if lookahead.peek(Token![async]) {
input.parse::<Token![async]>()?;
input.parse::<Token![:]>()?;
Ok(WasmtimeConfigField::Core(ConfigField::Async(AsyncConf {
blocking: false,
functions: input.parse()?,
})))
} else if lookahead.peek(kw::block_on) {
input.parse::<kw::block_on>()?;
input.parse::<Token![:]>()?;
Ok(WasmtimeConfigField::Core(ConfigField::Async(AsyncConf {
blocking: true,
functions: input.parse()?,
})))
} else if lookahead.peek(kw::mutable) {
input.parse::<kw::mutable>()?;
input.parse::<Token![:]>()?;
Ok(WasmtimeConfigField::Core(ConfigField::Mutable(
input.parse::<syn::LitBool>()?.value,
)))
} else {
Err(lookahead.error())
Ok(WasmtimeConfigField::Core(input.parse()?))
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions crates/wiggle/generate/src/wasmtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,14 +142,14 @@ fn generate_func(
}
}

Asyncness::Blocking => {
Asyncness::Blocking { block_with } => {
quote! {
linker.func_wrap(
#module_str,
#field_str,
move |mut caller: wiggle::wasmtime_crate::Caller<'_, T> #(, #arg_decls)*| -> wiggle::anyhow::Result<#ret_ty> {
let result = async { #body };
wiggle::run_in_dummy_executor(result)?
#block_with(result)?
},
)?;
}
Expand Down
26 changes: 7 additions & 19 deletions src/commands/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,25 +98,13 @@ impl CompileCommand {
output
});

// If the component-model proposal is enabled and the binary we're
// compiling looks like a component, tested by sniffing the first 8
// bytes with the current component model proposal.
#[cfg(feature = "component-model")]
{
if let Ok(wasmparser::Chunk::Parsed {
payload:
wasmparser::Payload::Version {
encoding: wasmparser::Encoding::Component,
..
},
..
}) = wasmparser::Parser::new(0).parse(&input, true)
{
fs::write(output, engine.precompile_component(&input)?)?;
return Ok(());
}
}
fs::write(output, engine.precompile_module(&input)?)?;
let output_bytes = if wasmparser::Parser::is_component(&input) {
engine.precompile_component(&input)?
} else {
engine.precompile_module(&input)?
};
fs::write(&output, output_bytes)
.with_context(|| format!("failed to write output: {}", output.display()))?;

Ok(())
}
Expand Down
Loading

0 comments on commit 367bdc8

Please sign in to comment.