From a5d49c07a7fb10209529e00040f5bf761c22d7fa Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 4 Mar 2021 12:57:28 -0800 Subject: [PATCH 1/7] wasmtime: add `Linker::instantiate_async` to go with `Instance::new_async` --- crates/wasmtime/src/linker.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/wasmtime/src/linker.rs b/crates/wasmtime/src/linker.rs index 5875cf8891f8..e5397556f97b 100644 --- a/crates/wasmtime/src/linker.rs +++ b/crates/wasmtime/src/linker.rs @@ -601,6 +601,16 @@ impl Linker { Instance::new(&self.store, module, &imports) } + /// Attempts to instantiate the `module` provided. This is the same as [`Linker::instantiate`], + /// except for async `Store`s. + #[cfg(feature = "async")] + #[cfg_attr(nightlydoc, doc(cfg(feature = "async")))] + pub async fn instantiate_async(&self, module: &Module) -> Result { + let imports = self.compute_imports(module)?; + + Instance::new_async(&self.store, module, &imports).await + } + fn compute_imports(&self, module: &Module) -> Result> { module .imports() From f11cd8e7b18130d5d43d85304add98b3b38a5168 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 4 Mar 2021 17:26:09 -0800 Subject: [PATCH 2/7] wiggle: add support for async traits; ABI func is now generic of ctx * ctx parameter no longer accepted by wiggle::from_witx macro. * optional async_ parameter specifies which functions are async. * re-export async_trait::async_trait, so users don't have to take a dep. --- crates/wiggle/Cargo.toml | 1 + ...error_transform.rs => codegen_settings.rs} | 22 +++- crates/wiggle/generate/src/config.rs | 119 ++++++++++++----- crates/wiggle/generate/src/funcs.rs | 42 ++++-- crates/wiggle/generate/src/lib.rs | 14 +- crates/wiggle/generate/src/module_trait.rs | 21 ++- crates/wiggle/generate/src/names.rs | 12 +- crates/wiggle/macro/src/lib.rs | 120 +++++++++++++----- crates/wiggle/src/lib.rs | 4 +- 9 files changed, 259 insertions(+), 96 deletions(-) rename crates/wiggle/generate/src/{error_transform.rs => codegen_settings.rs} (80%) diff --git a/crates/wiggle/Cargo.toml b/crates/wiggle/Cargo.toml index 88c8315bdce0..a5fdbf7cbd9c 100644 --- a/crates/wiggle/Cargo.toml +++ b/crates/wiggle/Cargo.toml @@ -16,6 +16,7 @@ witx = { path = "../wasi-common/WASI/tools/witx", version = "0.9", optional = tr wiggle-macro = { path = "macro", version = "0.23.0" } tracing = "0.1.15" bitflags = "1.2" +async-trait = "0.1.42" [badges] maintenance = { status = "actively-developed" } diff --git a/crates/wiggle/generate/src/error_transform.rs b/crates/wiggle/generate/src/codegen_settings.rs similarity index 80% rename from crates/wiggle/generate/src/error_transform.rs rename to crates/wiggle/generate/src/codegen_settings.rs index 56fa10eaf0ad..c51c87e9dde7 100644 --- a/crates/wiggle/generate/src/error_transform.rs +++ b/crates/wiggle/generate/src/codegen_settings.rs @@ -1,10 +1,28 @@ -use crate::config::ErrorConf; +use crate::config::{AsyncConf, ErrorConf}; use anyhow::{anyhow, Error}; use proc_macro2::TokenStream; use quote::quote; use std::collections::HashMap; use std::rc::Rc; -use witx::{Document, Id, NamedType, TypeRef}; +use witx::{Document, Id, InterfaceFunc, Module, NamedType, TypeRef}; + +pub struct CodegenSettings { + pub errors: ErrorTransform, + async_: AsyncConf, +} +impl CodegenSettings { + pub fn new(error_conf: &ErrorConf, async_: &AsyncConf, doc: &Document) -> Result { + let errors = ErrorTransform::new(error_conf, doc)?; + Ok(Self { + errors, + async_: async_.clone(), + }) + } + pub fn is_async(&self, module: &Module, func: &InterfaceFunc) -> bool { + self.async_ + .is_async(module.name.as_str(), func.name.as_str()) + } +} pub struct ErrorTransform { m: Vec, diff --git a/crates/wiggle/generate/src/config.rs b/crates/wiggle/generate/src/config.rs index 520532a3ecfa..b6467da60998 100644 --- a/crates/wiggle/generate/src/config.rs +++ b/crates/wiggle/generate/src/config.rs @@ -12,22 +12,22 @@ use { #[derive(Debug, Clone)] pub struct Config { pub witx: WitxConf, - pub ctx: CtxConf, pub errors: ErrorConf, + pub async_: AsyncConf, } #[derive(Debug, Clone)] pub enum ConfigField { Witx(WitxConf), - Ctx(CtxConf), Error(ErrorConf), + Async(AsyncConf), } mod kw { syn::custom_keyword!(witx); syn::custom_keyword!(witx_literal); - syn::custom_keyword!(ctx); syn::custom_keyword!(errors); + syn::custom_keyword!(async_); } impl Parse for ConfigField { @@ -41,14 +41,14 @@ impl Parse for ConfigField { input.parse::()?; input.parse::()?; Ok(ConfigField::Witx(WitxConf::Literal(input.parse()?))) - } else if lookahead.peek(kw::ctx) { - input.parse::()?; - input.parse::()?; - Ok(ConfigField::Ctx(input.parse()?)) } else if lookahead.peek(kw::errors) { input.parse::()?; input.parse::()?; Ok(ConfigField::Error(input.parse()?)) + } else if lookahead.peek(kw::async_) { + input.parse::()?; + input.parse::()?; + Ok(ConfigField::Async(input.parse()?)) } else { Err(lookahead.error()) } @@ -58,8 +58,8 @@ impl Parse for ConfigField { impl Config { pub fn build(fields: impl Iterator, err_loc: Span) -> Result { let mut witx = None; - let mut ctx = None; let mut errors = None; + let mut async_ = None; for f in fields { match f { ConfigField::Witx(c) => { @@ -68,28 +68,26 @@ impl Config { } witx = Some(c); } - ConfigField::Ctx(c) => { - if ctx.is_some() { - return Err(Error::new(err_loc, "duplicate `ctx` field")); - } - ctx = Some(c); - } ConfigField::Error(c) => { if errors.is_some() { return Err(Error::new(err_loc, "duplicate `errors` field")); } errors = Some(c); } + ConfigField::Async(c) => { + if async_.is_some() { + return Err(Error::new(err_loc, "duplicate `async` field")); + } + async_ = Some(c); + } } } Ok(Config { witx: witx .take() .ok_or_else(|| Error::new(err_loc, "`witx` field required"))?, - ctx: ctx - .take() - .ok_or_else(|| Error::new(err_loc, "`ctx` field required"))?, errors: errors.take().unwrap_or_default(), + async_: async_.take().unwrap_or_default(), }) } @@ -216,19 +214,6 @@ impl Parse for Literal { } } -#[derive(Debug, Clone)] -pub struct CtxConf { - pub name: Ident, -} - -impl Parse for CtxConf { - fn parse(input: ParseStream) -> Result { - Ok(CtxConf { - name: input.parse()?, - }) - } -} - #[derive(Clone, Default, Debug)] /// Map from abi error type to rich error type pub struct ErrorConf(HashMap); @@ -294,3 +279,77 @@ impl Parse for ErrorConfField { }) } } + +#[derive(Clone, Default, Debug)] +/// Modules and funcs that should be async +pub struct AsyncConf(HashMap>); + +impl AsyncConf { + pub fn is_async(&self, module: &str, function: &str) -> bool { + self.0 + .get(module) + .and_then(|fs| fs.iter().find(|f| *f == function)) + .is_some() + } +} + +impl Parse for AsyncConf { + fn parse(input: ParseStream) -> Result { + let content; + let _ = braced!(content in input); + let items: Punctuated = + content.parse_terminated(Parse::parse)?; + let mut m: HashMap> = HashMap::new(); + use std::collections::hash_map::Entry; + for i in items { + let function_names = i + .function_names + .iter() + .map(|i| i.to_string()) + .collect::>(); + match m.entry(i.module_name.to_string()) { + Entry::Occupied(o) => o.into_mut().extend(function_names), + Entry::Vacant(v) => { + v.insert(function_names); + } + } + } + Ok(AsyncConf(m)) + } +} + +#[derive(Clone)] +pub struct AsyncConfField { + pub module_name: Ident, + pub function_names: Vec, + pub err_loc: Span, +} + +impl Parse for AsyncConfField { + fn parse(input: ParseStream) -> Result { + let err_loc = input.span(); + let module_name = input.parse::()?; + let _doublecolon: Token![::] = input.parse()?; + let lookahead = input.lookahead1(); + if lookahead.peek(syn::token::Brace) { + let content; + let _ = braced!(content in input); + let function_names: Punctuated = + content.parse_terminated(Parse::parse)?; + Ok(AsyncConfField { + module_name, + function_names: function_names.iter().cloned().collect(), + err_loc, + }) + } else if lookahead.peek(Ident) { + let name = input.parse()?; + Ok(AsyncConfField { + module_name, + function_names: vec![name], + err_loc, + }) + } else { + Err(lookahead.error()) + } + } +} diff --git a/crates/wiggle/generate/src/funcs.rs b/crates/wiggle/generate/src/funcs.rs index 187178e8b6f7..af15709d0bbf 100644 --- a/crates/wiggle/generate/src/funcs.rs +++ b/crates/wiggle/generate/src/funcs.rs @@ -1,4 +1,4 @@ -use crate::error_transform::ErrorTransform; +use crate::codegen_settings::CodegenSettings; use crate::lifetimes::anon_lifetime; use crate::module_trait::passed_by_reference; use crate::names::Names; @@ -12,11 +12,10 @@ pub fn define_func( names: &Names, module: &witx::Module, func: &witx::InterfaceFunc, - errxform: &ErrorTransform, + settings: &CodegenSettings, ) -> TokenStream { let rt = names.runtime_mod(); let ident = names.func(&func.name); - let ctx_type = names.ctx_type(); let (wasm_params, wasm_results) = func.wasm_signature(); let param_names = (0..wasm_params.len()) @@ -37,6 +36,7 @@ pub fn define_func( }; let mut body = TokenStream::new(); + let mut required_impls = vec![names.trait_name(&module.name)]; func.call_interface( &module.name, &mut Rust { @@ -48,16 +48,22 @@ pub fn define_func( names, module, funcname: func.name.as_str(), - errxform, + settings, + required_impls: &mut required_impls, }, ); + let asyncness = if settings.is_async(&module, &func) { + quote!(async) + } else { + quote!() + }; let mod_name = &module.name.as_str(); let func_name = &func.name.as_str(); quote! { #[allow(unreachable_code)] // deals with warnings in noreturn functions - pub fn #ident( - ctx: &#ctx_type, + pub #asyncness fn #ident( + ctx: &(impl #(#required_impls)+*), memory: &dyn #rt::GuestMemory, #(#abi_params),* ) -> Result<#abi_ret, #rt::Trap> { @@ -85,7 +91,16 @@ struct Rust<'a> { names: &'a Names, module: &'a witx::Module, funcname: &'a str, - errxform: &'a ErrorTransform, + settings: &'a CodegenSettings, + required_impls: &'a mut Vec, +} + +impl Rust<'_> { + fn required_impl(&mut self, i: Ident) { + if !self.required_impls.contains(&i) { + self.required_impls.push(i); + } + } } impl witx::Bindgen for Rust<'_> { @@ -205,8 +220,16 @@ impl witx::Bindgen for Rust<'_> { let trait_name = self.names.trait_name(&self.module.name); let ident = self.names.func(&func.name); + if self.settings.is_async(&self.module, &func) { + self.src.extend(quote! { + let ret = #trait_name::#ident(ctx, #(#args),*).await; + }) + } else { + self.src.extend(quote! { + let ret = #trait_name::#ident(ctx, #(#args),*); + }) + }; self.src.extend(quote! { - let ret = #trait_name::#ident(ctx, #(#args),*); #rt::tracing::event!( #rt::tracing::Level::TRACE, result = #rt::tracing::field::debug(&ret), @@ -226,9 +249,10 @@ impl witx::Bindgen for Rust<'_> { // enum, and *then* we lower to an i32. Instruction::EnumLower { ty } => { let val = operands.pop().unwrap(); - let val = match self.errxform.for_name(ty) { + let val = match self.settings.errors.for_name(ty) { Some(custom) => { let method = self.names.user_error_conversion_method(&custom); + self.required_impl(quote::format_ident!("UserErrorConversion")); quote!(UserErrorConversion::#method(ctx, #val)?) } None => val, diff --git a/crates/wiggle/generate/src/lib.rs b/crates/wiggle/generate/src/lib.rs index 2e172633dfaf..90cca766d134 100644 --- a/crates/wiggle/generate/src/lib.rs +++ b/crates/wiggle/generate/src/lib.rs @@ -1,5 +1,5 @@ +mod codegen_settings; pub mod config; -mod error_transform; mod funcs; mod lifetimes; mod module_trait; @@ -11,14 +11,14 @@ use lifetimes::anon_lifetime; use proc_macro2::{Literal, TokenStream}; use quote::quote; +pub use codegen_settings::{CodegenSettings, UserErrorType}; pub use config::Config; -pub use error_transform::{ErrorTransform, UserErrorType}; pub use funcs::define_func; pub use module_trait::define_module_trait; pub use names::Names; pub use types::define_datatype; -pub fn generate(doc: &witx::Document, names: &Names, errs: &ErrorTransform) -> TokenStream { +pub fn generate(doc: &witx::Document, names: &Names, settings: &CodegenSettings) -> TokenStream { // TODO at some point config should grow more ability to configure name // overrides. let rt = names.runtime_mod(); @@ -49,7 +49,7 @@ pub fn generate(doc: &witx::Document, names: &Names, errs: &ErrorTransform) -> T } }; - let user_error_methods = errs.iter().map(|errtype| { + let user_error_methods = settings.errors.iter().map(|errtype| { let abi_typename = names.type_ref(&errtype.abi_type(), anon_lifetime()); let user_typename = errtype.typename(); let methodname = names.user_error_conversion_method(&errtype); @@ -64,12 +64,10 @@ pub fn generate(doc: &witx::Document, names: &Names, errs: &ErrorTransform) -> T let modname = names.module(&module.name); let fs = module .funcs() - .map(|f| define_func(&names, &module, &f, &errs)); - let modtrait = define_module_trait(&names, &module, &errs); - let ctx_type = names.ctx_type(); + .map(|f| define_func(&names, &module, &f, &settings)); + let modtrait = define_module_trait(&names, &module, &settings); quote!( pub mod #modname { - use super::#ctx_type; use super::types::*; #(#fs)* diff --git a/crates/wiggle/generate/src/module_trait.rs b/crates/wiggle/generate/src/module_trait.rs index 30dae0007668..2db27490ab77 100644 --- a/crates/wiggle/generate/src/module_trait.rs +++ b/crates/wiggle/generate/src/module_trait.rs @@ -1,7 +1,7 @@ use proc_macro2::TokenStream; use quote::quote; -use crate::error_transform::ErrorTransform; +use crate::codegen_settings::CodegenSettings; use crate::lifetimes::{anon_lifetime, LifetimeExt}; use crate::names::Names; use witx::Module; @@ -15,8 +15,9 @@ pub fn passed_by_reference(ty: &witx::Type) -> bool { } } -pub fn define_module_trait(names: &Names, m: &Module, errxform: &ErrorTransform) -> TokenStream { +pub fn define_module_trait(names: &Names, m: &Module, settings: &CodegenSettings) -> TokenStream { let traitname = names.trait_name(&m.name); + let rt = names.runtime_mod(); let traitmethods = m.funcs().map(|f| { // Check if we're returning an entity anotated with a lifetime, // in which case, we'll need to annotate the function itself, and @@ -43,7 +44,6 @@ pub fn define_module_trait(names: &Names, m: &Module, errxform: &ErrorTransform) quote!(#arg_name: #arg_type) }); - let rt = names.runtime_mod(); let result = match f.results.len() { 0 if f.noreturn => quote!(#rt::Trap), 0 => quote!(()), @@ -61,7 +61,7 @@ pub fn define_module_trait(names: &Names, m: &Module, errxform: &ErrorTransform) None => quote!(()), }; let err = match err { - Some(ty) => match errxform.for_abi_error(ty) { + Some(ty) => match settings.errors.for_abi_error(ty) { Some(custom) => { let tn = custom.typename(); quote!(super::#tn) @@ -75,13 +75,22 @@ pub fn define_module_trait(names: &Names, m: &Module, errxform: &ErrorTransform) _ => unimplemented!(), }; + let asyncness = if settings.is_async(&m, &f) { + quote!(async) + } else { + quote!() + }; + if is_anonymous { - quote!(fn #funcname(&self, #(#args),*) -> #result; ) + quote!(#asyncness fn #funcname(&self, #(#args),*) -> #result; ) } else { - quote!(fn #funcname<#lifetime>(&self, #(#args),*) -> #result;) + quote!(#asyncness fn #funcname<#lifetime>(&self, #(#args),*) -> #result;) } }); + quote! { + use #rt::async_trait; + #[async_trait(?Send)] pub trait #traitname { #(#traitmethods)* } diff --git a/crates/wiggle/generate/src/names.rs b/crates/wiggle/generate/src/names.rs index b9eb9e47528f..ae3b6bc74bfe 100644 --- a/crates/wiggle/generate/src/names.rs +++ b/crates/wiggle/generate/src/names.rs @@ -7,20 +7,12 @@ use witx::{BuiltinType, Id, Type, TypeRef, WasmType}; use crate::{lifetimes::LifetimeExt, UserErrorType}; pub struct Names { - ctx_type: Ident, runtime_mod: TokenStream, } impl Names { - pub fn new(ctx_type: &Ident, runtime_mod: TokenStream) -> Names { - Names { - ctx_type: ctx_type.clone(), - runtime_mod, - } - } - - pub fn ctx_type(&self) -> Ident { - self.ctx_type.clone() + pub fn new(runtime_mod: TokenStream) -> Names { + Names { runtime_mod } } pub fn runtime_mod(&self) -> TokenStream { diff --git a/crates/wiggle/macro/src/lib.rs b/crates/wiggle/macro/src/lib.rs index a3c10afb8603..e7ea3455f080 100644 --- a/crates/wiggle/macro/src/lib.rs +++ b/crates/wiggle/macro/src/lib.rs @@ -15,9 +15,11 @@ use syn::parse_macro_input; /// Rust-idiomatic snake\_case. /// /// * For each `@interface func` defined in a witx module, an abi-level -/// function is generated which takes ABI-level arguments, along with a -/// "context" struct (whose type is given by the `ctx` field in the -/// macro invocation) and a `GuestMemory` implementation. +/// function is generated which takes ABI-level arguments, along with +/// a ref that impls the module trait, and a `GuestMemory` implementation. +/// Users typically won't use these abi-level functions: The `wasmtime-wiggle` +/// and `lucet-wiggle` crates adapt these to work with a particular WebAssembly +/// engine. /// /// * A public "module trait" is defined (called the module name, in /// SnakeCase) which has a `&self` method for each function in the @@ -27,57 +29,94 @@ use syn::parse_macro_input; /// Arguments are provided using Rust struct value syntax. /// /// * `witx` takes a list of string literal paths. Paths are relative to the -/// CARGO_MANIFEST_DIR of the crate where the macro is invoked. -/// * `ctx` takes a type name. This type must implement all of the module -/// traits +/// CARGO_MANIFEST_DIR of the crate where the macro is invoked. Alternatively, +/// `witx_literal` takes a string containing a complete witx document. +/// * Optional: `errors` takes a mapping of witx identifiers to types, e.g +/// `{ errno => YourErrnoType }`. This allows you to use the `UserErrorConversion` +/// trait to map these rich errors into the flat witx type, or to terminate +/// WebAssembly execution by trapping. +/// * Optional: `async_` takes a set of witx modules and functions which are +/// made Rust `async` functions in the module trait. /// /// ## Example /// /// ``` -/// use wiggle::{GuestPtr, GuestErrorType}; -/// -/// /// The test witx file `arrays.witx` lives in the test directory. For a -/// /// full-fledged example with runtime tests, see `tests/arrays.rs` and -/// /// the rest of the files in that directory. +/// use wiggle::GuestPtr; /// wiggle::from_witx!({ -/// witx: ["../tests/arrays.witx"], -/// ctx: YourCtxType, +/// witx_literal: " +/// (typename $errno +/// (enum (@witx tag u32) +/// $ok +/// $invalid_arg +/// $io +/// $overflow)) +/// (typename $alias_to_float f32) +/// (module $example +/// (@interface func (export \"int_float_args\") +/// (param $an_int u32) +/// (param $some_floats (list f32)) +/// (result $r (expected (error $errno)))) +/// (@interface func (export \"double_int_return_float\") +/// (param $an_int u32) +/// (result $r (expected $alias_to_float (error $errno))))) +/// ", +/// errors: { errno => YourRichError }, +/// async_: { example::double_int_return_float }, /// }); /// -/// /// The `ctx` type for this wiggle invocation. +/// /// Witx generates a set of traits, which the user must impl on a +/// /// type they define. We call this the ctx type. It stores any context +/// /// these functions need to execute. /// pub struct YourCtxType {} /// -/// /// `arrays.witx` contains one module called `arrays`. So, we must -/// /// implement this one method trait for our ctx type: -/// impl arrays::Arrays for YourCtxType { +/// /// Witx provides a hook to translate "rich" (arbitrary Rust type) errors +/// /// into the flat error enums used at the WebAssembly interface. You will +/// /// need to impl the `types::UserErrorConversion` trait to provide a translation +/// /// from this rich type. +/// #[derive(Debug)] +/// pub enum YourRichError { +/// InvalidArg(String), +/// Io(std::io::Error), +/// Overflow, +/// Trap(String), +/// } +/// +/// /// The above witx text contains one module called `$example`. So, we must +/// /// implement this one method trait for our ctx type. +/// #[wiggle::async_trait(?Send)] +/// /// We specified in the `async_` field that `example::double_int_return_float` +/// /// is an asynchronous method. Therefore, we use the `async_trait` proc macro +/// /// (re-exported by wiggle from the crate of the same name) to define this +/// /// trait, so that `double_int_return_float` can be an `async fn`. +/// impl example::Example for YourCtxType { /// /// The arrays module has two methods, shown here. /// /// Note that the `GuestPtr` type comes from `wiggle`, /// /// whereas the witx-defined types like `Excuse` and `Errno` come /// /// from the `pub mod types` emitted by the `wiggle::from_witx!` /// /// invocation above. -/// fn reduce_excuses(&self, _a: &GuestPtr<[GuestPtr]>) -/// -> Result { +/// fn int_float_args(&self, _int: u32, _floats: &GuestPtr<[f32]>) +/// -> Result<(), YourRichError> { /// unimplemented!() /// } -/// fn populate_excuses(&self, _a: &GuestPtr<[GuestPtr]>) -/// -> Result<(), types::Errno> { -/// unimplemented!() +/// async fn double_int_return_float(&self, int: u32) +/// -> Result { +/// Ok(int.checked_mul(2).ok_or(YourRichError::Overflow)? as f32) /// } /// } /// -/// /// For all types used in the `Error` position of a `Result` in the module -/// /// traits, you must implement `GuestErrorType` which tells wiggle-generated +/// /// For all types used in the `error` an `expected` in the witx document, +/// /// you must implement `GuestErrorType` which tells wiggle-generated /// /// code what value to return when the method returns Ok(...). -/// impl GuestErrorType for types::Errno { +/// impl wiggle::GuestErrorType for types::Errno { /// fn success() -> Self { /// unimplemented!() /// } /// } /// /// /// The `types::GuestErrorConversion` trait is also generated with a method for -/// /// each type used in the `Error` position. This trait allows wiggle-generated +/// /// each type used in the `error` position. This trait allows wiggle-generated /// /// code to convert a `wiggle::GuestError` into the right error type. The trait -/// /// must be implemented for the user's `ctx` type. +/// /// must be implemented for the user's ctx type. /// /// impl types::GuestErrorConversion for YourCtxType { /// fn into_errno(&self, _e: wiggle::GuestError) -> types::Errno { @@ -85,6 +124,26 @@ use syn::parse_macro_input; /// } /// } /// +/// /// If you specify a `error` mapping to the macro, you must implement the +/// /// `types::UserErrorConversion` for your ctx type as well. This trait gives +/// /// you an opportunity to store or log your rich error type, while returning +/// /// a basic witx enum to the WebAssembly caller. It also gives you the ability +/// /// to terminate WebAssembly execution by trapping. +/// +/// impl types::UserErrorConversion for YourCtxType { +/// fn errno_from_your_rich_error(&self, e: YourRichError) +/// -> Result +/// { +/// println!("Rich error: {:?}", e); +/// match e { +/// YourRichError::InvalidArg{..} => Ok(types::Errno::InvalidArg), +/// YourRichError::Io{..} => Ok(types::Errno::Io), +/// YourRichError::Overflow => Ok(types::Errno::Overflow), +/// YourRichError::Trap(s) => Err(wiggle::Trap::String(s)), +/// } +/// } +/// } +/// /// # fn main() { println!("this fools doc tests into compiling the above outside a function body") /// # } /// ``` @@ -93,10 +152,11 @@ pub fn from_witx(args: TokenStream) -> TokenStream { let config = parse_macro_input!(args as wiggle_generate::Config); let doc = config.load_document(); - let names = wiggle_generate::Names::new(&config.ctx.name, quote!(wiggle)); + let names = wiggle_generate::Names::new(quote!(wiggle)); - let error_transform = wiggle_generate::ErrorTransform::new(&config.errors, &doc) - .expect("validating error transform"); + let error_transform = + wiggle_generate::CodegenSettings::new(&config.errors, &config.async_, &doc) + .expect("validating codegen settings"); let code = wiggle_generate::generate(&doc, &names, &error_transform); let metadata = if cfg!(feature = "wiggle_metadata") { diff --git a/crates/wiggle/src/lib.rs b/crates/wiggle/src/lib.rs index 24a4ca7c4077..76d3f0fcbbaa 100644 --- a/crates/wiggle/src/lib.rs +++ b/crates/wiggle/src/lib.rs @@ -6,8 +6,10 @@ use std::slice; use std::str; use std::sync::Arc; -pub use bitflags; pub use wiggle_macro::from_witx; +// re-exports so users of wiggle don't need to track the dependency: +pub use async_trait::async_trait; +pub use bitflags; #[cfg(feature = "wiggle_metadata")] pub use witx; From c4d8e2323a7c68346aa38df266dea300ef375043 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 4 Mar 2021 17:27:34 -0800 Subject: [PATCH 3/7] wiggle tests: fixes for new syntax --- .../wiggle/test-helpers/examples/tracing.rs | 1 - crates/wiggle/tests/atoms.rs | 1 - crates/wiggle/tests/atoms_async.rs | 139 ++++++++++++++++++ crates/wiggle/tests/errors.rs | 2 - crates/wiggle/tests/flags.rs | 1 - crates/wiggle/tests/handles.rs | 1 - crates/wiggle/tests/ints.rs | 1 - crates/wiggle/tests/keywords.rs | 4 - crates/wiggle/tests/lists.rs | 1 - crates/wiggle/tests/pointers.rs | 1 - crates/wiggle/tests/records.rs | 1 - crates/wiggle/tests/strings.rs | 1 - crates/wiggle/tests/variant.rs | 1 - crates/wiggle/tests/wasi.rs | 1 - 14 files changed, 139 insertions(+), 17 deletions(-) create mode 100644 crates/wiggle/tests/atoms_async.rs diff --git a/crates/wiggle/test-helpers/examples/tracing.rs b/crates/wiggle/test-helpers/examples/tracing.rs index fe2dd51d8e7e..1e1517d8d9ee 100644 --- a/crates/wiggle/test-helpers/examples/tracing.rs +++ b/crates/wiggle/test-helpers/examples/tracing.rs @@ -24,7 +24,6 @@ witx_literal: " (param $s $s) (result $err (expected $t (error $errno))))) ", - ctx: WasiCtx, errors: { errno => RichError }, }); diff --git a/crates/wiggle/tests/atoms.rs b/crates/wiggle/tests/atoms.rs index b39bec5b1559..9aabbc74c5f4 100644 --- a/crates/wiggle/tests/atoms.rs +++ b/crates/wiggle/tests/atoms.rs @@ -4,7 +4,6 @@ use wiggle_test::{impl_errno, HostMemory, MemArea, WasiCtx}; wiggle::from_witx!({ witx: ["$CARGO_MANIFEST_DIR/tests/atoms.witx"], - ctx: WasiCtx, }); impl_errno!(types::Errno, types::GuestErrorConversion); diff --git a/crates/wiggle/tests/atoms_async.rs b/crates/wiggle/tests/atoms_async.rs new file mode 100644 index 000000000000..5fe20b326b94 --- /dev/null +++ b/crates/wiggle/tests/atoms_async.rs @@ -0,0 +1,139 @@ +use proptest::prelude::*; +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; +use wiggle::GuestMemory; +use wiggle_test::{impl_errno, HostMemory, MemArea, WasiCtx}; + +wiggle::from_witx!({ + witx: ["$CARGO_MANIFEST_DIR/tests/atoms.witx"], + async_: { + atoms::{int_float_args, double_int_return_float} + } +}); + +impl_errno!(types::Errno, types::GuestErrorConversion); + +#[wiggle::async_trait(?Send)] +impl<'a> atoms::Atoms for WasiCtx<'a> { + async fn int_float_args(&self, an_int: u32, an_float: f32) -> Result<(), types::Errno> { + println!("INT FLOAT ARGS: {} {}", an_int, an_float); + Ok(()) + } + async fn double_int_return_float( + &self, + an_int: u32, + ) -> Result { + Ok((an_int as f32) * 2.0) + } +} + +// There's nothing meaningful to test here - this just demonstrates the test machinery + +#[derive(Debug)] +struct IntFloatExercise { + pub an_int: u32, + pub an_float: f32, +} + +impl IntFloatExercise { + pub fn test(&self) { + let ctx = WasiCtx::new(); + let host_memory = HostMemory::new(); + + let e = run(atoms::int_float_args( + &ctx, + &host_memory, + self.an_int as i32, + self.an_float, + )); + + assert_eq!(e, Ok(types::Errno::Ok as i32), "int_float_args error"); + } + + pub fn strat() -> BoxedStrategy { + (prop::num::u32::ANY, prop::num::f32::ANY) + .prop_map(|(an_int, an_float)| IntFloatExercise { an_int, an_float }) + .boxed() + } +} + +proptest! { + #[test] + fn int_float_exercise(e in IntFloatExercise::strat()) { + e.test() + } +} +#[derive(Debug)] +struct DoubleIntExercise { + pub input: u32, + pub return_loc: MemArea, +} + +impl DoubleIntExercise { + pub fn test(&self) { + let ctx = WasiCtx::new(); + let host_memory = HostMemory::new(); + + let e = run(atoms::double_int_return_float( + &ctx, + &host_memory, + self.input as i32, + self.return_loc.ptr as i32, + )); + + let return_val = host_memory + .ptr::(self.return_loc.ptr) + .read() + .expect("failed to read return"); + assert_eq!(e, Ok(types::Errno::Ok as i32), "errno"); + assert_eq!(return_val, (self.input as f32) * 2.0, "return val"); + } + + pub fn strat() -> BoxedStrategy { + (prop::num::u32::ANY, HostMemory::mem_area_strat(4)) + .prop_map(|(input, return_loc)| DoubleIntExercise { input, return_loc }) + .boxed() + } +} + +proptest! { + #[test] + fn double_int_return_float(e in DoubleIntExercise::strat()) { + e.test() + } +} + +fn run(future: F) -> F::Output { + let mut f = Pin::from(Box::new(future)); + let waker = dummy_waker(); + let mut cx = Context::from_waker(&waker); + loop { + match f.as_mut().poll(&mut cx) { + Poll::Ready(val) => break val, + Poll::Pending => {} + } + } +} + +fn dummy_waker() -> Waker { + return unsafe { Waker::from_raw(clone(5 as *const _)) }; + + unsafe fn clone(ptr: *const ()) -> RawWaker { + assert_eq!(ptr as usize, 5); + const VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop); + RawWaker::new(ptr, &VTABLE) + } + + unsafe fn wake(ptr: *const ()) { + assert_eq!(ptr as usize, 5); + } + + unsafe fn wake_by_ref(ptr: *const ()) { + assert_eq!(ptr as usize, 5); + } + + unsafe fn drop(ptr: *const ()) { + assert_eq!(ptr as usize, 5); + } +} diff --git a/crates/wiggle/tests/errors.rs b/crates/wiggle/tests/errors.rs index 53d39a233d97..ec9291ff5373 100644 --- a/crates/wiggle/tests/errors.rs +++ b/crates/wiggle/tests/errors.rs @@ -23,7 +23,6 @@ mod convert_just_errno { (param $strike u32) (result $err (expected (error $errno))))) ", - ctx: WasiCtx, errors: { errno => RichError }, }); @@ -133,7 +132,6 @@ mod convert_multiple_error_types { (param $drink u32) (@witx noreturn))) ", - ctx: WasiCtx, errors: { errno => RichError, errno2 => AnotherRichError }, }); diff --git a/crates/wiggle/tests/flags.rs b/crates/wiggle/tests/flags.rs index b97285a94a71..1a1f98c6c5d5 100644 --- a/crates/wiggle/tests/flags.rs +++ b/crates/wiggle/tests/flags.rs @@ -5,7 +5,6 @@ use wiggle_test::{impl_errno, HostMemory, MemArea, WasiCtx}; wiggle::from_witx!({ witx: ["$CARGO_MANIFEST_DIR/tests/flags.witx"], - ctx: WasiCtx, }); impl_errno!(types::Errno, types::GuestErrorConversion); diff --git a/crates/wiggle/tests/handles.rs b/crates/wiggle/tests/handles.rs index d562e0bb14f6..15fb0d36be36 100644 --- a/crates/wiggle/tests/handles.rs +++ b/crates/wiggle/tests/handles.rs @@ -6,7 +6,6 @@ const FD_VAL: u32 = 123; wiggle::from_witx!({ witx: ["$CARGO_MANIFEST_DIR/tests/handles.witx"], - ctx: WasiCtx, }); impl_errno!(types::Errno, types::GuestErrorConversion); diff --git a/crates/wiggle/tests/ints.rs b/crates/wiggle/tests/ints.rs index d09615cca697..23310ee561e2 100644 --- a/crates/wiggle/tests/ints.rs +++ b/crates/wiggle/tests/ints.rs @@ -5,7 +5,6 @@ use wiggle_test::{impl_errno, HostMemory, MemArea, WasiCtx}; wiggle::from_witx!({ witx: ["$CARGO_MANIFEST_DIR/tests/ints.witx"], - ctx: WasiCtx, }); impl_errno!(types::Errno, types::GuestErrorConversion); diff --git a/crates/wiggle/tests/keywords.rs b/crates/wiggle/tests/keywords.rs index 1aa84e188ae6..fb93d7b3b362 100644 --- a/crates/wiggle/tests/keywords.rs +++ b/crates/wiggle/tests/keywords.rs @@ -13,7 +13,6 @@ mod enum_test { $2big ) )", - ctx: DummyCtx, }); } @@ -31,7 +30,6 @@ mod module_trait_fn_and_arg_test { (param $virtual u32) ) )", - ctx: WasiCtx, }); impl<'a> self_::Self_ for WasiCtx<'a> { #[allow(unused_variables)] @@ -51,7 +49,6 @@ mod struct_test { (field $mut s32) ) )", - ctx: DummyCtx, }); } @@ -59,6 +56,5 @@ mod struct_test { mod union_test { wiggle::from_witx!({ witx: ["$CARGO_MANIFEST_DIR/tests/keywords_union.witx"], - ctx: DummyCtx, }); } diff --git a/crates/wiggle/tests/lists.rs b/crates/wiggle/tests/lists.rs index dd6f92c5d3e2..d2f76fd152b2 100644 --- a/crates/wiggle/tests/lists.rs +++ b/crates/wiggle/tests/lists.rs @@ -4,7 +4,6 @@ use wiggle_test::{impl_errno, HostMemory, MemArea, MemAreas, WasiCtx}; wiggle::from_witx!({ witx: ["$CARGO_MANIFEST_DIR/tests/lists.witx"], - ctx: WasiCtx, }); impl_errno!(types::Errno, types::GuestErrorConversion); diff --git a/crates/wiggle/tests/pointers.rs b/crates/wiggle/tests/pointers.rs index ebae550fc3f1..96e33a734dbc 100644 --- a/crates/wiggle/tests/pointers.rs +++ b/crates/wiggle/tests/pointers.rs @@ -4,7 +4,6 @@ use wiggle_test::{impl_errno, HostMemory, MemArea, WasiCtx}; wiggle::from_witx!({ witx: ["$CARGO_MANIFEST_DIR/tests/pointers.witx"], - ctx: WasiCtx, }); impl_errno!(types::Errno, types::GuestErrorConversion); diff --git a/crates/wiggle/tests/records.rs b/crates/wiggle/tests/records.rs index be44a44afce5..fa448c3dc303 100644 --- a/crates/wiggle/tests/records.rs +++ b/crates/wiggle/tests/records.rs @@ -4,7 +4,6 @@ use wiggle_test::{impl_errno, HostMemory, MemArea, MemAreas, WasiCtx}; wiggle::from_witx!({ witx: ["$CARGO_MANIFEST_DIR/tests/records.witx"], - ctx: WasiCtx, }); impl_errno!(types::Errno, types::GuestErrorConversion); diff --git a/crates/wiggle/tests/strings.rs b/crates/wiggle/tests/strings.rs index 0caeb73782d4..420a5c0a626c 100644 --- a/crates/wiggle/tests/strings.rs +++ b/crates/wiggle/tests/strings.rs @@ -4,7 +4,6 @@ use wiggle_test::{impl_errno, HostMemory, MemArea, MemAreas, WasiCtx}; wiggle::from_witx!({ witx: ["$CARGO_MANIFEST_DIR/tests/strings.witx"], - ctx: WasiCtx, }); impl_errno!(types::Errno, types::GuestErrorConversion); diff --git a/crates/wiggle/tests/variant.rs b/crates/wiggle/tests/variant.rs index 364fbbf7cf31..5bddfb6aab2d 100644 --- a/crates/wiggle/tests/variant.rs +++ b/crates/wiggle/tests/variant.rs @@ -4,7 +4,6 @@ use wiggle_test::{impl_errno, HostMemory, MemArea, WasiCtx}; wiggle::from_witx!({ witx: ["$CARGO_MANIFEST_DIR/tests/variant.witx"], - ctx: WasiCtx, }); impl_errno!(types::Errno, types::GuestErrorConversion); diff --git a/crates/wiggle/tests/wasi.rs b/crates/wiggle/tests/wasi.rs index 95c146152680..6dc6d84ea5fc 100644 --- a/crates/wiggle/tests/wasi.rs +++ b/crates/wiggle/tests/wasi.rs @@ -8,7 +8,6 @@ use wiggle_test::WasiCtx; wiggle::from_witx!({ witx: ["$CARGO_MANIFEST_DIR/tests/wasi.witx"], - ctx: WasiCtx, }); // The only test in this file is to verify that the witx document provided by the From ff59797ad0902a23bc37b5409d5c4c8bc4c8acb7 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 4 Mar 2021 17:27:44 -0800 Subject: [PATCH 4/7] wasmtime_wiggle: support for async, and add an integration test --- crates/wiggle/wasmtime/Cargo.toml | 14 +- crates/wiggle/wasmtime/macro/Cargo.toml | 3 + crates/wiggle/wasmtime/macro/src/config.rs | 47 +++++- crates/wiggle/wasmtime/macro/src/lib.rs | 101 +++++++---- crates/wiggle/wasmtime/tests/atoms.witx | 25 +++ crates/wiggle/wasmtime/tests/atoms_async.rs | 175 ++++++++++++++++++++ 6 files changed, 331 insertions(+), 34 deletions(-) create mode 100644 crates/wiggle/wasmtime/tests/atoms.witx create mode 100644 crates/wiggle/wasmtime/tests/atoms_async.rs diff --git a/crates/wiggle/wasmtime/Cargo.toml b/crates/wiggle/wasmtime/Cargo.toml index 31f45eeaf3e5..c9174b9edc41 100644 --- a/crates/wiggle/wasmtime/Cargo.toml +++ b/crates/wiggle/wasmtime/Cargo.toml @@ -17,10 +17,22 @@ witx = { version = "0.9", path = "../../wasi-common/WASI/tools/witx", optional = wiggle = { path = "..", version = "0.23.0" } wiggle-borrow = { path = "../borrow", version = "0.23.0" } +[dev-dependencies] +anyhow = "1" +proptest = "0.10" + +[[test]] +name = "atoms_async" +path = "tests/atoms_async.rs" +required-features = ["async", "wasmtime/wat"] + [badges] maintenance = { status = "actively-developed" } [features] +# Async support for wasmtime +async = [ 'wasmtime/async', 'wasmtime-wiggle-macro/async' ] + # The wiggle proc-macro emits some code (inside `pub mod metadata`) guarded # by the `wiggle_metadata` feature flag. We use this feature flag so that # users of wiggle are not forced to take a direct dependency on the `witx` @@ -33,4 +45,4 @@ wiggle_metadata = ['witx', "wiggle/wiggle_metadata"] # the logs out of wiggle-generated libraries. tracing_log = [ "wiggle/tracing_log" ] -default = ["wiggle_metadata" ] +default = ["wiggle_metadata", "async"] diff --git a/crates/wiggle/wasmtime/macro/Cargo.toml b/crates/wiggle/wasmtime/macro/Cargo.toml index fd4b365f0d2e..e1f5f6d8deca 100644 --- a/crates/wiggle/wasmtime/macro/Cargo.toml +++ b/crates/wiggle/wasmtime/macro/Cargo.toml @@ -24,3 +24,6 @@ proc-macro2 = "1.0" [badges] maintenance = { status = "actively-developed" } +[features] +async = [] +default = [] diff --git a/crates/wiggle/wasmtime/macro/src/config.rs b/crates/wiggle/wasmtime/macro/src/config.rs index 5e95bad957fb..9c5ee7b77635 100644 --- a/crates/wiggle/wasmtime/macro/src/config.rs +++ b/crates/wiggle/wasmtime/macro/src/config.rs @@ -1,3 +1,4 @@ +pub use wiggle_generate::config::AsyncConf; use { proc_macro2::Span, std::collections::HashMap, @@ -7,15 +8,16 @@ use { punctuated::Punctuated, Error, Ident, Path, Result, Token, }, - wiggle_generate::config::{CtxConf, WitxConf}, + wiggle_generate::config::WitxConf, }; - #[derive(Debug, Clone)] pub struct Config { pub target: TargetConf, pub witx: WitxConf, pub ctx: CtxConf, pub modules: ModulesConf, + #[cfg(feature = "async")] + pub async_: AsyncConf, } #[derive(Debug, Clone)] @@ -24,6 +26,8 @@ pub enum ConfigField { Witx(WitxConf), Ctx(CtxConf), Modules(ModulesConf), + #[cfg(feature = "async")] + Async(AsyncConf), } mod kw { @@ -35,6 +39,7 @@ mod kw { syn::custom_keyword!(name); syn::custom_keyword!(docs); syn::custom_keyword!(function_override); + syn::custom_keyword!(async_); } impl Parse for ConfigField { @@ -60,6 +65,20 @@ impl Parse for ConfigField { input.parse::()?; input.parse::()?; Ok(ConfigField::Modules(input.parse()?)) + } else if lookahead.peek(kw::async_) { + input.parse::()?; + input.parse::()?; + #[cfg(feature = "async")] + { + Ok(ConfigField::Async(input.parse()?)) + } + #[cfg(not(feature = "async"))] + { + Err(syn::Error::new( + input.span(), + "async_ not supported, enable cargo feature \"async\"", + )) + } } else { Err(lookahead.error()) } @@ -72,6 +91,8 @@ impl Config { let mut witx = None; let mut ctx = None; let mut modules = None; + #[cfg(feature = "async")] + let mut async_ = None; for f in fields { match f { ConfigField::Target(c) => { @@ -98,6 +119,13 @@ impl Config { } modules = Some(c); } + #[cfg(feature = "async")] + ConfigField::Async(c) => { + if async_.is_some() { + return Err(Error::new(err_loc, "duplicate `async_` field")); + } + async_ = Some(c); + } } } Ok(Config { @@ -105,6 +133,8 @@ impl Config { witx: witx.ok_or_else(|| Error::new(err_loc, "`witx` field required"))?, ctx: ctx.ok_or_else(|| Error::new(err_loc, "`ctx` field required"))?, modules: modules.ok_or_else(|| Error::new(err_loc, "`modules` field required"))?, + #[cfg(feature = "async")] + async_: async_.unwrap_or_default(), }) } @@ -128,6 +158,19 @@ impl Parse for Config { } } +#[derive(Debug, Clone)] +pub struct CtxConf { + pub name: syn::Type, +} + +impl Parse for CtxConf { + fn parse(input: ParseStream) -> Result { + Ok(CtxConf { + name: input.parse()?, + }) + } +} + #[derive(Debug, Clone)] pub struct TargetConf { pub path: Path, diff --git a/crates/wiggle/wasmtime/macro/src/lib.rs b/crates/wiggle/wasmtime/macro/src/lib.rs index a2d05f729d39..199036e79c5e 100644 --- a/crates/wiggle/wasmtime/macro/src/lib.rs +++ b/crates/wiggle/wasmtime/macro/src/lib.rs @@ -6,7 +6,7 @@ use wiggle_generate::Names; mod config; -use config::{ModuleConf, TargetConf}; +use config::{AsyncConf, ModuleConf, TargetConf}; /// Define the structs required to integrate a Wiggle implementation with Wasmtime. /// @@ -46,13 +46,25 @@ use config::{ModuleConf, TargetConf}; pub fn wasmtime_integration(args: TokenStream) -> TokenStream { let config = parse_macro_input!(args as config::Config); let doc = config.load_document(); - let names = Names::new(&config.ctx.name, quote!(wasmtime_wiggle)); + let names = Names::new(quote!(wasmtime_wiggle)); + + #[cfg(feature = "async")] + let async_config = config.async_.clone(); + #[cfg(not(feature = "async"))] + let async_config = AsyncConf::default(); let modules = config.modules.iter().map(|(name, module_conf)| { let module = doc .module(&witx::Id::new(name)) .unwrap_or_else(|| panic!("witx document did not contain module named '{}'", name)); - generate_module(&module, &module_conf, &names, &config.target) + generate_module( + &module, + &module_conf, + &names, + &config.target, + &config.ctx.name, + &async_config, + ) }); quote!( #(#modules)* ).into() } @@ -62,6 +74,8 @@ fn generate_module( module_conf: &ModuleConf, names: &Names, target_conf: &TargetConf, + ctx_type: &syn::Type, + async_conf: &AsyncConf, ) -> TokenStream2 { let fields = module.funcs().map(|f| { let name_ident = names.func(&f.name); @@ -88,9 +102,14 @@ fn generate_module( let module_id = names.module(&module.name); let target_module = quote! { #target_path::#module_id }; - let ctor_externs = module - .funcs() - .map(|f| generate_func(&f, names, &target_module)); + let ctor_externs = module.funcs().map(|f| { + generate_func( + &f, + names, + &target_module, + async_conf.is_async(module.name.as_str(), f.name.as_str()), + ) + }); let type_name = module_conf.name.clone(); let type_docs = module_conf @@ -107,8 +126,6 @@ contained in the `cx` parameter.", module_conf.name.to_string() ); - let ctx_type = names.ctx_type(); - quote! { #type_docs pub struct #type_name { @@ -150,6 +167,7 @@ fn generate_func( func: &witx::InterfaceFunc, names: &Names, target_module: &TokenStream2, + is_async: bool, ) -> TokenStream2 { let name_ident = names.func(&func.name); @@ -172,31 +190,52 @@ fn generate_func( let runtime = names.runtime_mod(); - quote! { - let my_cx = cx.clone(); - let #name_ident = wasmtime::Func::wrap( - store, - move |caller: wasmtime::Caller<'_> #(,#arg_decls)*| -> Result<#ret_ty, wasmtime::Trap> { - unsafe { - let mem = match caller.get_export("memory") { - Some(wasmtime::Extern::Memory(m)) => m, - _ => { - return Err(wasmtime::Trap::new("missing required memory export")); - } - }; - let mem = #runtime::WasmtimeGuestMemory::new(mem); - let result = #target_module::#name_ident( - &mut my_cx.borrow_mut(), - &mem, - #(#arg_names),* - ); - match result { - Ok(r) => Ok(r.into()), - Err(wasmtime_wiggle::Trap::String(err)) => Err(wasmtime::Trap::new(err)), - Err(wasmtime_wiggle::Trap::I32Exit(err)) => Err(wasmtime::Trap::i32_exit(err)), - } + let await_ = if is_async { quote!(.await) } else { quote!() }; + + let closure_body = quote! { + unsafe { + let mem = match caller.get_export("memory") { + Some(wasmtime::Extern::Memory(m)) => m, + _ => { + return Err(wasmtime::Trap::new("missing required memory export")); } + }; + let mem = #runtime::WasmtimeGuestMemory::new(mem); + let result = #target_module::#name_ident( + &mut *my_cx.borrow_mut(), + &mem, + #(#arg_names),* + ) #await_; + match result { + Ok(r) => Ok(r.into()), + Err(wasmtime_wiggle::Trap::String(err)) => Err(wasmtime::Trap::new(err)), + Err(wasmtime_wiggle::Trap::I32Exit(err)) => Err(wasmtime::Trap::i32_exit(err)), + } + } + + }; + if is_async { + let wrapper = quote::format_ident!("wrap{}_async", params.len()); + quote! { + let #name_ident = wasmtime::Func::#wrapper( + store, + cx.clone(), + move |caller: wasmtime::Caller<'_>, my_cx: &Rc> #(,#arg_decls)*| + -> Box>> + { + Box::new(async move { #closure_body }) } ); + } + } else { + quote! { + let my_cx = cx.clone(); + let #name_ident = wasmtime::Func::wrap( + store, + move |caller: wasmtime::Caller<'_> #(,#arg_decls)*| -> Result<#ret_ty, wasmtime::Trap> { + #closure_body + } + ); + } } } diff --git a/crates/wiggle/wasmtime/tests/atoms.witx b/crates/wiggle/wasmtime/tests/atoms.witx new file mode 100644 index 000000000000..af5955dbf0dc --- /dev/null +++ b/crates/wiggle/wasmtime/tests/atoms.witx @@ -0,0 +1,25 @@ + +(typename $errno + (enum (@witx tag u32) + ;;; Success + $ok + ;;; Invalid argument + $invalid_arg + ;;; I really don't want to + $dont_want_to + ;;; I am physically unable to + $physically_unable + ;;; Well, that's a picket line alright! + $picket_line)) + +(typename $alias_to_float f32) + +(module $atoms + (@interface func (export "int_float_args") + (param $an_int u32) + (param $an_float f32) + (result $error (expected (error $errno)))) + (@interface func (export "double_int_return_float") + (param $an_int u32) + (result $error (expected $alias_to_float (error $errno)))) +) diff --git a/crates/wiggle/wasmtime/tests/atoms_async.rs b/crates/wiggle/wasmtime/tests/atoms_async.rs new file mode 100644 index 000000000000..f31da11ecb9d --- /dev/null +++ b/crates/wiggle/wasmtime/tests/atoms_async.rs @@ -0,0 +1,175 @@ +use std::cell::RefCell; +use std::future::Future; +use std::pin::Pin; +use std::rc::Rc; +use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; + +wasmtime_wiggle::from_witx!({ + witx: ["$CARGO_MANIFEST_DIR/tests/atoms.witx"], + async_: { + atoms::{double_int_return_float} + } +}); + +wasmtime_wiggle::wasmtime_integration!({ + target: crate, + witx: ["$CARGO_MANIFEST_DIR/tests/atoms.witx"], + ctx: Ctx, + modules: { atoms => { name: Atoms } }, + async_: { + atoms::double_int_return_float + } +}); + +pub struct Ctx; +impl wiggle::GuestErrorType for types::Errno { + fn success() -> Self { + types::Errno::Ok + } +} + +#[wasmtime_wiggle::async_trait(?Send)] +impl atoms::Atoms for Ctx { + fn int_float_args(&self, an_int: u32, an_float: f32) -> Result<(), types::Errno> { + println!("INT FLOAT ARGS: {} {}", an_int, an_float); + Ok(()) + } + async fn double_int_return_float( + &self, + an_int: u32, + ) -> Result { + Ok((an_int as f32) * 2.0) + } +} + +#[test] +fn test_sync_host_func() { + let store = async_store(); + + let ctx = Rc::new(RefCell::new(Ctx)); + let atoms = Atoms::new(&store, ctx.clone()); + + let shim_mod = shim_module(&store); + let mut linker = wasmtime::Linker::new(&store); + atoms.add_to_linker(&mut linker).unwrap(); + let shim_inst = run(linker.instantiate_async(&shim_mod)).unwrap(); + + let results = run(shim_inst + .get_func("int_float_args_shim") + .unwrap() + .call_async(&[0i32.into(), 123.45f32.into()])) + .unwrap(); + + assert_eq!(results.len(), 1, "one return value"); + assert_eq!( + results[0].unwrap_i32(), + types::Errno::Ok as i32, + "int_float_args errno" + ); +} + +#[test] +fn test_async_host_func() { + let store = async_store(); + + let ctx = Rc::new(RefCell::new(Ctx)); + let atoms = Atoms::new(&store, ctx.clone()); + + let shim_mod = shim_module(&store); + let mut linker = wasmtime::Linker::new(&store); + atoms.add_to_linker(&mut linker).unwrap(); + let shim_inst = run(linker.instantiate_async(&shim_mod)).unwrap(); + + let input: i32 = 123; + let result_location: i32 = 0; + + let results = run(shim_inst + .get_func("double_int_return_float_shim") + .unwrap() + .call_async(&[input.into(), result_location.into()])) + .unwrap(); + + assert_eq!(results.len(), 1, "one return value"); + assert_eq!( + results[0].unwrap_i32(), + types::Errno::Ok as i32, + "double_int_return_float errno" + ); + + // The actual result is in memory: + let mem = shim_inst.get_memory("memory").unwrap(); + let mut result_bytes: [u8; 4] = [0, 0, 0, 0]; + mem.read(result_location as usize, &mut result_bytes) + .unwrap(); + let result = f32::from_le_bytes(result_bytes); + assert_eq!((input * 2) as f32, result); +} + +fn run(future: F) -> F::Output { + let mut f = Pin::from(Box::new(future)); + let waker = dummy_waker(); + let mut cx = Context::from_waker(&waker); + loop { + match f.as_mut().poll(&mut cx) { + Poll::Ready(val) => break val, + Poll::Pending => {} + } + } +} + +fn dummy_waker() -> Waker { + return unsafe { Waker::from_raw(clone(5 as *const _)) }; + + unsafe fn clone(ptr: *const ()) -> RawWaker { + assert_eq!(ptr as usize, 5); + const VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop); + RawWaker::new(ptr, &VTABLE) + } + + unsafe fn wake(ptr: *const ()) { + assert_eq!(ptr as usize, 5); + } + + unsafe fn wake_by_ref(ptr: *const ()) { + assert_eq!(ptr as usize, 5); + } + + unsafe fn drop(ptr: *const ()) { + assert_eq!(ptr as usize, 5); + } +} +fn async_store() -> wasmtime::Store { + let engine = wasmtime::Engine::default(); + wasmtime::Store::new_async(&engine) +} + +// Wiggle expects the caller to have an exported memory. Wasmtime can only +// provide this if the caller is a WebAssembly module, so we need to write +// a shim module: +fn shim_module(store: &wasmtime::Store) -> wasmtime::Module { + wasmtime::Module::new( + store.engine(), + r#" + (module + (memory 1) + (export "memory" (memory 0)) + (import "atoms" "int_float_args" (func $int_float_args (param i32 f32) (result i32))) + (import "atoms" "double_int_return_float" (func $double_int_return_float (param i32 i32) (result i32))) + + (func $int_float_args_shim (param i32 f32) (result i32) + local.get 0 + local.get 1 + call $int_float_args + ) + (func $double_int_return_float_shim (param i32 i32) (result i32) + local.get 0 + local.get 1 + call $double_int_return_float + ) + (export "int_float_args_shim" (func $int_float_args_shim)) + (export "double_int_return_float_shim" (func $double_int_return_float_shim)) + ) + "#, + ) + .unwrap() +} From bcebdd43ef40b5e7034a5794142586f6834df3cc Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 4 Mar 2021 17:28:20 -0800 Subject: [PATCH 5/7] wiggle use sites: remove ctx argument --- crates/wasi-common/src/snapshots/preview_0.rs | 1 - crates/wasi-common/src/snapshots/preview_1.rs | 1 - crates/wasi-crypto/src/wiggle_interfaces/mod.rs | 1 - crates/wasi-nn/src/witx.rs | 1 - 4 files changed, 4 deletions(-) diff --git a/crates/wasi-common/src/snapshots/preview_0.rs b/crates/wasi-common/src/snapshots/preview_0.rs index 4d1ee4c77737..fc92e76f006d 100644 --- a/crates/wasi-common/src/snapshots/preview_0.rs +++ b/crates/wasi-common/src/snapshots/preview_0.rs @@ -15,7 +15,6 @@ use wiggle::GuestPtr; wiggle::from_witx!({ witx: ["$WASI_ROOT/phases/old/snapshot_0/witx/wasi_unstable.witx"], - ctx: WasiCtx, errors: { errno => Error }, }); diff --git a/crates/wasi-common/src/snapshots/preview_1.rs b/crates/wasi-common/src/snapshots/preview_1.rs index e6db3fb761db..88b07b1f9ad9 100644 --- a/crates/wasi-common/src/snapshots/preview_1.rs +++ b/crates/wasi-common/src/snapshots/preview_1.rs @@ -21,7 +21,6 @@ use wiggle::GuestPtr; wiggle::from_witx!({ witx: ["$WASI_ROOT/phases/snapshot/witx/wasi_snapshot_preview1.witx"], - ctx: WasiCtx, errors: { errno => Error }, }); diff --git a/crates/wasi-crypto/src/wiggle_interfaces/mod.rs b/crates/wasi-crypto/src/wiggle_interfaces/mod.rs index dd32f6cdea1f..41119300c0dc 100644 --- a/crates/wasi-crypto/src/wiggle_interfaces/mod.rs +++ b/crates/wasi-crypto/src/wiggle_interfaces/mod.rs @@ -2,7 +2,6 @@ pub use wasi_crypto::CryptoCtx as WasiCryptoCtx; wiggle::from_witx!({ witx: ["$CARGO_MANIFEST_DIR/spec/witx/wasi_ephemeral_crypto.witx"], - ctx: WasiCryptoCtx }); pub mod wasi_modules { diff --git a/crates/wasi-nn/src/witx.rs b/crates/wasi-nn/src/witx.rs index 011c173ad85f..72120276b7b4 100644 --- a/crates/wasi-nn/src/witx.rs +++ b/crates/wasi-nn/src/witx.rs @@ -5,7 +5,6 @@ use crate::ctx::WasiNnError; // Generate the traits and types of wasi-nn in several Rust modules (e.g. `types`). wiggle::from_witx!({ witx: ["$WASI_ROOT/phases/ephemeral/witx/wasi_ephemeral_nn.witx"], - ctx: WasiNnCtx, errors: { nn_errno => WasiNnError } }); From af49505e7321564d7f5f709e8afe401b22f48db4 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 4 Mar 2021 17:28:31 -0800 Subject: [PATCH 6/7] bump cargo.lock, add wiggle-generate and wiggle-macro to workspace wiggle-macro doc tests weren't being run, so docs had gotten out of sync. --- Cargo.lock | 14 ++++++++++++++ Cargo.toml | 2 ++ 2 files changed, 16 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index fd67cb53e13b..304e6ab68349 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,6 +125,17 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "async-trait" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atty" version = "0.2.14" @@ -3473,6 +3484,8 @@ dependencies = [ name = "wasmtime-wiggle" version = "0.23.0" dependencies = [ + "anyhow", + "proptest", "wasmtime", "wasmtime-wiggle-macro", "wiggle", @@ -3531,6 +3544,7 @@ dependencies = [ name = "wiggle" version = "0.23.0" dependencies = [ + "async-trait", "bitflags", "proptest", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index e198f4bfc5c8..60e65c6cd0ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,6 +74,8 @@ members = [ "crates/misc/run-examples", "crates/misc/rust", "crates/wiggle", + "crates/wiggle/generate", + "crates/wiggle/macro", "crates/wiggle/wasmtime", "crates/wasi-common", "crates/wasi-common/cap-std-sync", From 84df5fa54a1573fdf7d68f9899fdf141a1e59d91 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Fri, 5 Mar 2021 08:55:49 -0800 Subject: [PATCH 7/7] use the async keyword as syntax in the macro invocation --- crates/wiggle/generate/src/config.rs | 5 ++--- crates/wiggle/macro/src/lib.rs | 4 ++-- crates/wiggle/tests/atoms_async.rs | 2 +- crates/wiggle/wasmtime/macro/src/config.rs | 9 ++++----- crates/wiggle/wasmtime/tests/atoms_async.rs | 4 ++-- 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/crates/wiggle/generate/src/config.rs b/crates/wiggle/generate/src/config.rs index b6467da60998..a430a8547a02 100644 --- a/crates/wiggle/generate/src/config.rs +++ b/crates/wiggle/generate/src/config.rs @@ -27,7 +27,6 @@ mod kw { syn::custom_keyword!(witx); syn::custom_keyword!(witx_literal); syn::custom_keyword!(errors); - syn::custom_keyword!(async_); } impl Parse for ConfigField { @@ -45,8 +44,8 @@ impl Parse for ConfigField { input.parse::()?; input.parse::()?; Ok(ConfigField::Error(input.parse()?)) - } else if lookahead.peek(kw::async_) { - input.parse::()?; + } else if lookahead.peek(Token![async]) { + input.parse::()?; input.parse::()?; Ok(ConfigField::Async(input.parse()?)) } else { diff --git a/crates/wiggle/macro/src/lib.rs b/crates/wiggle/macro/src/lib.rs index e7ea3455f080..09ab2f6353e8 100644 --- a/crates/wiggle/macro/src/lib.rs +++ b/crates/wiggle/macro/src/lib.rs @@ -35,7 +35,7 @@ use syn::parse_macro_input; /// `{ errno => YourErrnoType }`. This allows you to use the `UserErrorConversion` /// trait to map these rich errors into the flat witx type, or to terminate /// WebAssembly execution by trapping. -/// * Optional: `async_` takes a set of witx modules and functions which are +/// * Optional: `async` takes a set of witx modules and functions which are /// made Rust `async` functions in the module trait. /// /// ## Example @@ -61,7 +61,7 @@ use syn::parse_macro_input; /// (result $r (expected $alias_to_float (error $errno))))) /// ", /// errors: { errno => YourRichError }, -/// async_: { example::double_int_return_float }, +/// async: { example::double_int_return_float }, /// }); /// /// /// Witx generates a set of traits, which the user must impl on a diff --git a/crates/wiggle/tests/atoms_async.rs b/crates/wiggle/tests/atoms_async.rs index 5fe20b326b94..bdaf32d7969a 100644 --- a/crates/wiggle/tests/atoms_async.rs +++ b/crates/wiggle/tests/atoms_async.rs @@ -7,7 +7,7 @@ use wiggle_test::{impl_errno, HostMemory, MemArea, WasiCtx}; wiggle::from_witx!({ witx: ["$CARGO_MANIFEST_DIR/tests/atoms.witx"], - async_: { + async: { atoms::{int_float_args, double_int_return_float} } }); diff --git a/crates/wiggle/wasmtime/macro/src/config.rs b/crates/wiggle/wasmtime/macro/src/config.rs index 9c5ee7b77635..76a23dd31fbb 100644 --- a/crates/wiggle/wasmtime/macro/src/config.rs +++ b/crates/wiggle/wasmtime/macro/src/config.rs @@ -39,7 +39,6 @@ mod kw { syn::custom_keyword!(name); syn::custom_keyword!(docs); syn::custom_keyword!(function_override); - syn::custom_keyword!(async_); } impl Parse for ConfigField { @@ -65,8 +64,8 @@ impl Parse for ConfigField { input.parse::()?; input.parse::()?; Ok(ConfigField::Modules(input.parse()?)) - } else if lookahead.peek(kw::async_) { - input.parse::()?; + } else if lookahead.peek(Token![async]) { + input.parse::()?; input.parse::()?; #[cfg(feature = "async")] { @@ -76,7 +75,7 @@ impl Parse for ConfigField { { Err(syn::Error::new( input.span(), - "async_ not supported, enable cargo feature \"async\"", + "async not supported, enable cargo feature \"async\"", )) } } else { @@ -122,7 +121,7 @@ impl Config { #[cfg(feature = "async")] ConfigField::Async(c) => { if async_.is_some() { - return Err(Error::new(err_loc, "duplicate `async_` field")); + return Err(Error::new(err_loc, "duplicate `async` field")); } async_ = Some(c); } diff --git a/crates/wiggle/wasmtime/tests/atoms_async.rs b/crates/wiggle/wasmtime/tests/atoms_async.rs index f31da11ecb9d..e37d11c06afe 100644 --- a/crates/wiggle/wasmtime/tests/atoms_async.rs +++ b/crates/wiggle/wasmtime/tests/atoms_async.rs @@ -6,7 +6,7 @@ use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; wasmtime_wiggle::from_witx!({ witx: ["$CARGO_MANIFEST_DIR/tests/atoms.witx"], - async_: { + async: { atoms::{double_int_return_float} } }); @@ -16,7 +16,7 @@ wasmtime_wiggle::wasmtime_integration!({ witx: ["$CARGO_MANIFEST_DIR/tests/atoms.witx"], ctx: Ctx, modules: { atoms => { name: Atoms } }, - async_: { + async: { atoms::double_int_return_float } });