make wasmtime-wasi configurable at macro whether its real async or block_on

This commit is contained in:
Pat Hickey
2021-04-13 12:27:01 -07:00
parent 8f9fb1f4e2
commit 201da20c63
4 changed files with 138 additions and 61 deletions

View File

@@ -280,12 +280,14 @@ impl Parse for ErrorConfField {
} }
#[derive(Clone, Default, Debug)] #[derive(Clone, Default, Debug)]
/// Modules and funcs that should be async /// Modules and funcs that have async signatures
pub struct AsyncConf(HashMap<String, Vec<String>>); pub struct AsyncConf {
functions: HashMap<String, Vec<String>>,
}
impl AsyncConf { impl AsyncConf {
pub fn is_async(&self, module: &str, function: &str) -> bool { pub fn is_async(&self, module: &str, function: &str) -> bool {
self.0 self.functions
.get(module) .get(module)
.and_then(|fs| fs.iter().find(|f| *f == function)) .and_then(|fs| fs.iter().find(|f| *f == function))
.is_some() .is_some()
@@ -298,7 +300,7 @@ impl Parse for AsyncConf {
let _ = braced!(content in input); let _ = braced!(content in input);
let items: Punctuated<AsyncConfField, Token![,]> = let items: Punctuated<AsyncConfField, Token![,]> =
content.parse_terminated(Parse::parse)?; content.parse_terminated(Parse::parse)?;
let mut m: HashMap<String, Vec<String>> = HashMap::new(); let mut functions: HashMap<String, Vec<String>> = HashMap::new();
use std::collections::hash_map::Entry; use std::collections::hash_map::Entry;
for i in items { for i in items {
let function_names = i let function_names = i
@@ -306,14 +308,14 @@ impl Parse for AsyncConf {
.iter() .iter()
.map(|i| i.to_string()) .map(|i| i.to_string())
.collect::<Vec<String>>(); .collect::<Vec<String>>();
match m.entry(i.module_name.to_string()) { match functions.entry(i.module_name.to_string()) {
Entry::Occupied(o) => o.into_mut().extend(function_names), Entry::Occupied(o) => o.into_mut().extend(function_names),
Entry::Vacant(v) => { Entry::Vacant(v) => {
v.insert(function_names); v.insert(function_names);
} }
} }
} }
Ok(AsyncConf(m)) Ok(AsyncConf { functions })
} }
} }

View File

@@ -1,4 +1,4 @@
pub use wiggle_generate::config::AsyncConf; use wiggle_generate::config::AsyncConfField;
use { use {
proc_macro2::Span, proc_macro2::Span,
std::collections::HashMap, std::collections::HashMap,
@@ -37,6 +37,7 @@ mod kw {
syn::custom_keyword!(name); syn::custom_keyword!(name);
syn::custom_keyword!(docs); syn::custom_keyword!(docs);
syn::custom_keyword!(function_override); syn::custom_keyword!(function_override);
syn::custom_keyword!(block_on);
} }
impl Parse for ConfigField { impl Parse for ConfigField {
@@ -66,6 +67,12 @@ impl Parse for ConfigField {
input.parse::<Token![async]>()?; input.parse::<Token![async]>()?;
input.parse::<Token![:]>()?; input.parse::<Token![:]>()?;
Ok(ConfigField::Async(input.parse()?)) Ok(ConfigField::Async(input.parse()?))
} else if lookahead.peek(kw::block_on) {
input.parse::<kw::block_on>()?;
input.parse::<Token![:]>()?;
let mut async_conf: AsyncConf = input.parse()?;
async_conf.blocking = true;
Ok(ConfigField::Async(async_conf))
} else { } else {
Err(lookahead.error()) Err(lookahead.error())
} }
@@ -261,3 +268,76 @@ impl Parse for ModulesConf {
}) })
} }
} }
#[derive(Clone, Default, Debug)]
/// Modules and funcs that have async signatures
pub struct AsyncConf {
blocking: bool,
functions: HashMap<String, Vec<String>>,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Asyncness {
/// Wiggle function is synchronous, wasmtime Func is synchronous
Sync,
/// Wiggle function is asynchronous, but wasmtime Func is synchronous
Blocking,
/// Wiggle function and wasmtime Func are asynchronous.
Async,
}
impl Asyncness {
pub fn is_sync(&self) -> bool {
match self {
Asyncness::Sync => true,
_ => false,
}
}
}
impl AsyncConf {
pub fn is_async(&self, module: &str, function: &str) -> Asyncness {
if self
.functions
.get(module)
.and_then(|fs| fs.iter().find(|f| *f == function))
.is_some()
{
if self.blocking {
Asyncness::Blocking
} else {
Asyncness::Async
}
} else {
Asyncness::Sync
}
}
}
impl Parse for AsyncConf {
fn parse(input: ParseStream) -> Result<Self> {
let content;
let _ = braced!(content in input);
let items: Punctuated<AsyncConfField, Token![,]> =
content.parse_terminated(Parse::parse)?;
let mut functions: HashMap<String, Vec<String>> = 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::<Vec<String>>();
match functions.entry(i.module_name.to_string()) {
Entry::Occupied(o) => o.into_mut().extend(function_names),
Entry::Vacant(v) => {
v.insert(function_names);
}
}
}
Ok(AsyncConf {
functions,
blocking: false,
})
}
}

View File

@@ -6,7 +6,7 @@ use wiggle_generate::Names;
mod config; mod config;
use config::{AsyncConf, ModuleConf, TargetConf}; use config::{AsyncConf, Asyncness, ModuleConf, TargetConf};
/// Define the structs required to integrate a Wiggle implementation with Wasmtime. /// Define the structs required to integrate a Wiggle implementation with Wasmtime.
/// ///
@@ -101,14 +101,19 @@ fn generate_module(
let mut ctor_externs = Vec::new(); let mut ctor_externs = Vec::new();
let mut host_funcs = Vec::new(); let mut host_funcs = Vec::new();
#[cfg(not(feature = "async"))]
let mut requires_dummy_executor = false; let mut requires_dummy_executor = false;
for f in module.funcs() { for f in module.funcs() {
let is_async = async_conf.is_async(module.name.as_str(), f.name.as_str()); let asyncness = async_conf.is_async(module.name.as_str(), f.name.as_str());
#[cfg(not(feature = "async"))] match asyncness {
if is_async { Asyncness::Blocking => requires_dummy_executor = true,
requires_dummy_executor = true; Asyncness::Async => {
assert!(
cfg!(feature = "async"),
"generating async wasmtime Funcs requires cargo feature \"async\""
);
}
_ => {}
} }
generate_func( generate_func(
&module_id, &module_id,
@@ -116,7 +121,7 @@ fn generate_module(
names, names,
&target_module, &target_module,
ctx_type, ctx_type,
is_async, asyncness,
&mut fns, &mut fns,
&mut ctor_externs, &mut ctor_externs,
&mut host_funcs, &mut host_funcs,
@@ -160,14 +165,11 @@ contained in the `cx` parameter.",
} }
}); });
#[cfg(not(feature = "async"))]
let dummy_executor = if requires_dummy_executor { let dummy_executor = if requires_dummy_executor {
dummy_executor() dummy_executor()
} else { } else {
quote!() quote!()
}; };
#[cfg(feature = "async")]
let dummy_executor = quote!();
quote! { quote! {
#type_docs #type_docs
@@ -243,7 +245,7 @@ fn generate_func(
names: &Names, names: &Names,
target_module: &TokenStream2, target_module: &TokenStream2,
ctx_type: &syn::Type, ctx_type: &syn::Type,
is_async: bool, asyncness: Asyncness,
fns: &mut Vec<TokenStream2>, fns: &mut Vec<TokenStream2>,
ctors: &mut Vec<TokenStream2>, ctors: &mut Vec<TokenStream2>,
host_funcs: &mut Vec<(witx::Id, TokenStream2)>, host_funcs: &mut Vec<(witx::Id, TokenStream2)>,
@@ -271,8 +273,16 @@ fn generate_func(
_ => unimplemented!(), _ => unimplemented!(),
}; };
let async_ = if is_async { quote!(async) } else { quote!() }; let async_ = if asyncness.is_sync() {
let await_ = if is_async { quote!(.await) } else { quote!() }; quote!()
} else {
quote!(async)
};
let await_ = if asyncness.is_sync() {
quote!()
} else {
quote!(.await)
};
let runtime = names.runtime_mod(); let runtime = names.runtime_mod();
let fn_ident = format_ident!("{}_{}", module_ident, name_ident); let fn_ident = format_ident!("{}_{}", module_ident, name_ident);
@@ -296,9 +306,8 @@ fn generate_func(
} }
}); });
if is_async { match asyncness {
#[cfg(feature = "async")] Asyncness::Async => {
{
let wrapper = format_ident!("wrap{}_async", params.len()); let wrapper = format_ident!("wrap{}_async", params.len());
ctors.push(quote! { ctors.push(quote! {
let #name_ident = wasmtime::Func::#wrapper( let #name_ident = wasmtime::Func::#wrapper(
@@ -311,9 +320,7 @@ fn generate_func(
); );
}); });
} }
Asyncness::Blocking => {
#[cfg(not(feature = "async"))]
{
// Emit a synchronous function. Self::#fn_ident returns a Future, so we need to // Emit a synchronous function. Self::#fn_ident returns a Future, so we need to
// use a dummy executor to let any synchronous code inside there execute correctly. If // use a dummy executor to let any synchronous code inside there execute correctly. If
// the future ends up Pending, this func will Trap. // the future ends up Pending, this func will Trap.
@@ -327,7 +334,7 @@ fn generate_func(
); );
}); });
} }
} else { Asyncness::Sync => {
ctors.push(quote! { ctors.push(quote! {
let my_ctx = ctx.clone(); let my_ctx = ctx.clone();
let #name_ident = wasmtime::Func::wrap( let #name_ident = wasmtime::Func::wrap(
@@ -338,10 +345,10 @@ fn generate_func(
); );
}); });
} }
}
let host_wrapper = if is_async { let host_wrapper = match asyncness {
#[cfg(feature = "async")] Asyncness::Async => {
{
let wrapper = format_ident!("wrap{}_host_func_async", params.len()); let wrapper = format_ident!("wrap{}_host_func_async", params.len());
quote! { quote! {
config.#wrapper( config.#wrapper(
@@ -361,8 +368,7 @@ fn generate_func(
} }
} }
#[cfg(not(feature = "async"))] Asyncness::Blocking => {
{
// Emit a synchronous host function. Self::#fn_ident returns a Future, so we need to // Emit a synchronous host function. Self::#fn_ident returns a Future, so we need to
// use a dummy executor to let any synchronous code inside there execute correctly. If // use a dummy executor to let any synchronous code inside there execute correctly. If
// the future ends up Pending, this func will Trap. // the future ends up Pending, this func will Trap.
@@ -380,7 +386,7 @@ fn generate_func(
); );
} }
} }
} else { Asyncness::Sync => {
quote! { quote! {
config.wrap_host_func( config.wrap_host_func(
module, module,
@@ -394,10 +400,11 @@ fn generate_func(
}, },
); );
} }
}
}; };
host_funcs.push((func.name.clone(), host_wrapper)); host_funcs.push((func.name.clone(), host_wrapper));
} }
#[cfg(not(feature = "async"))]
fn dummy_executor() -> TokenStream2 { fn dummy_executor() -> TokenStream2 {
quote! { quote! {
fn run_in_dummy_executor<F: std::future::Future>(future: F) -> F::Output { fn run_in_dummy_executor<F: std::future::Future>(future: F) -> F::Output {

View File

@@ -1,11 +1,3 @@
#![allow(unused)]
// These tests are designed to check the behavior with the crate's async feature (& wasmtimes async
// feature) disabled. Run with:
// `cargo test --no-default-features --features wasmtime/wat --test atoms_sync`
#[cfg(feature = "async")]
#[test]
fn these_tests_require_async_feature_disabled() {}
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
@@ -21,7 +13,7 @@ wasmtime_wiggle::wasmtime_integration!({
witx: ["$CARGO_MANIFEST_DIR/tests/atoms.witx"], witx: ["$CARGO_MANIFEST_DIR/tests/atoms.witx"],
ctx: Ctx, ctx: Ctx,
modules: { atoms => { name: Atoms } }, modules: { atoms => { name: Atoms } },
async: { block_on: {
atoms::double_int_return_float atoms::double_int_return_float
} }
}); });
@@ -94,7 +86,6 @@ fn run_double_int_return_float(linker: &wasmtime::Linker) {
assert_eq!((input * 2) as f32, result); assert_eq!((input * 2) as f32, result);
} }
#[cfg(not(feature = "async"))]
#[test] #[test]
fn test_sync_host_func() { fn test_sync_host_func() {
let store = store(); let store = store();
@@ -108,7 +99,6 @@ fn test_sync_host_func() {
run_int_float_args(&linker); run_int_float_args(&linker);
} }
#[cfg(not(feature = "async"))]
#[test] #[test]
fn test_async_host_func() { fn test_async_host_func() {
let store = store(); let store = store();
@@ -122,7 +112,6 @@ fn test_async_host_func() {
run_double_int_return_float(&linker); run_double_int_return_float(&linker);
} }
#[cfg(not(feature = "async"))]
#[test] #[test]
fn test_sync_config_host_func() { fn test_sync_config_host_func() {
let mut config = wasmtime::Config::new(); let mut config = wasmtime::Config::new();
@@ -137,7 +126,6 @@ fn test_sync_config_host_func() {
run_int_float_args(&linker); run_int_float_args(&linker);
} }
#[cfg(not(feature = "async"))]
#[test] #[test]
fn test_async_config_host_func() { fn test_async_config_host_func() {
let mut config = wasmtime::Config::new(); let mut config = wasmtime::Config::new();