wasmtime_wiggle: when async feature is disabled, run async on dummy executor
This commit is contained in:
@@ -26,6 +26,11 @@ name = "atoms_async"
|
||||
path = "tests/atoms_async.rs"
|
||||
required-features = ["async", "wasmtime/wat"]
|
||||
|
||||
[[test]]
|
||||
name = "atoms_sync"
|
||||
path = "tests/atoms_sync.rs"
|
||||
required-features = ["wasmtime/wat"]
|
||||
|
||||
[badges]
|
||||
maintenance = { status = "actively-developed" }
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ pub struct Config {
|
||||
pub witx: WitxConf,
|
||||
pub ctx: CtxConf,
|
||||
pub modules: ModulesConf,
|
||||
#[cfg(feature = "async")]
|
||||
pub async_: AsyncConf,
|
||||
}
|
||||
|
||||
@@ -26,7 +25,6 @@ pub enum ConfigField {
|
||||
Witx(WitxConf),
|
||||
Ctx(CtxConf),
|
||||
Modules(ModulesConf),
|
||||
#[cfg(feature = "async")]
|
||||
Async(AsyncConf),
|
||||
}
|
||||
|
||||
@@ -67,17 +65,7 @@ impl Parse for ConfigField {
|
||||
} else if lookahead.peek(Token![async]) {
|
||||
input.parse::<Token![async]>()?;
|
||||
input.parse::<Token![:]>()?;
|
||||
#[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\"",
|
||||
))
|
||||
}
|
||||
Ok(ConfigField::Async(input.parse()?))
|
||||
} else {
|
||||
Err(lookahead.error())
|
||||
}
|
||||
@@ -90,7 +78,6 @@ 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 {
|
||||
@@ -118,7 +105,6 @@ impl Config {
|
||||
}
|
||||
modules = Some(c);
|
||||
}
|
||||
#[cfg(feature = "async")]
|
||||
ConfigField::Async(c) => {
|
||||
if async_.is_some() {
|
||||
return Err(Error::new(err_loc, "duplicate `async` field"));
|
||||
@@ -132,7 +118,6 @@ 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(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -48,11 +48,6 @@ pub fn wasmtime_integration(args: TokenStream) -> TokenStream {
|
||||
let doc = config.load_document();
|
||||
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))
|
||||
@@ -63,7 +58,7 @@ pub fn wasmtime_integration(args: TokenStream) -> TokenStream {
|
||||
&names,
|
||||
&config.target,
|
||||
&config.ctx.name,
|
||||
&async_config,
|
||||
&config.async_,
|
||||
)
|
||||
});
|
||||
quote!( #(#modules)* ).into()
|
||||
@@ -106,14 +101,22 @@ fn generate_module(
|
||||
let mut ctor_externs = Vec::new();
|
||||
let mut host_funcs = Vec::new();
|
||||
|
||||
#[cfg(not(feature = "async"))]
|
||||
let mut requires_dummy_executor = false;
|
||||
|
||||
for f in module.funcs() {
|
||||
let is_async = async_conf.is_async(module.name.as_str(), f.name.as_str());
|
||||
#[cfg(not(feature = "async"))]
|
||||
if is_async {
|
||||
requires_dummy_executor = true;
|
||||
}
|
||||
generate_func(
|
||||
&module_id,
|
||||
&f,
|
||||
names,
|
||||
&target_module,
|
||||
ctx_type,
|
||||
async_conf.is_async(module.name.as_str(), f.name.as_str()),
|
||||
is_async,
|
||||
&mut fns,
|
||||
&mut ctor_externs,
|
||||
&mut host_funcs,
|
||||
@@ -157,6 +160,15 @@ contained in the `cx` parameter.",
|
||||
}
|
||||
});
|
||||
|
||||
#[cfg(not(feature = "async"))]
|
||||
let dummy_executor = if requires_dummy_executor {
|
||||
dummy_executor()
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
#[cfg(feature = "async")]
|
||||
let dummy_executor = quote!();
|
||||
|
||||
quote! {
|
||||
#type_docs
|
||||
pub struct #type_name {
|
||||
@@ -219,6 +231,8 @@ contained in the `cx` parameter.",
|
||||
}
|
||||
|
||||
#(#fns)*
|
||||
|
||||
#dummy_executor
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,8 +297,10 @@ fn generate_func(
|
||||
});
|
||||
|
||||
if is_async {
|
||||
let wrapper = format_ident!("wrap{}_async", params.len());
|
||||
ctors.push(quote! {
|
||||
#[cfg(feature = "async")]
|
||||
{
|
||||
let wrapper = format_ident!("wrap{}_async", params.len());
|
||||
ctors.push(quote! {
|
||||
let #name_ident = wasmtime::Func::#wrapper(
|
||||
store,
|
||||
ctx.clone(),
|
||||
@@ -294,6 +310,23 @@ fn generate_func(
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "async"))]
|
||||
{
|
||||
// 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
|
||||
// the future ends up Pending, this func will Trap.
|
||||
ctors.push(quote! {
|
||||
let my_ctx = ctx.clone();
|
||||
let #name_ident = wasmtime::Func::wrap(
|
||||
store,
|
||||
move |caller: wasmtime::Caller #(, #arg_decls)*| -> Result<#ret_ty, wasmtime::Trap> {
|
||||
Self::run_in_dummy_executor(Self::#fn_ident(&caller, &mut my_ctx.borrow_mut() #(, #arg_names)*))
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
ctors.push(quote! {
|
||||
let my_ctx = ctx.clone();
|
||||
@@ -307,22 +340,45 @@ fn generate_func(
|
||||
}
|
||||
|
||||
let host_wrapper = if is_async {
|
||||
let wrapper = format_ident!("wrap{}_host_func_async", params.len());
|
||||
quote! {
|
||||
config.#wrapper(
|
||||
module,
|
||||
field,
|
||||
move |caller #(,#arg_decls)*|
|
||||
-> Box<dyn std::future::Future<Output = Result<#ret_ty, wasmtime::Trap>>> {
|
||||
Box::new(async move {
|
||||
let ctx = caller.store()
|
||||
#[cfg(feature = "async")]
|
||||
{
|
||||
let wrapper = format_ident!("wrap{}_host_func_async", params.len());
|
||||
quote! {
|
||||
config.#wrapper(
|
||||
module,
|
||||
field,
|
||||
move |caller #(,#arg_decls)*|
|
||||
-> Box<dyn std::future::Future<Output = Result<#ret_ty, wasmtime::Trap>>> {
|
||||
Box::new(async move {
|
||||
let ctx = caller.store()
|
||||
.get::<std::rc::Rc<std::cell::RefCell<#ctx_type>>>()
|
||||
.ok_or_else(|| wasmtime::Trap::new("context is missing in the store"))?;
|
||||
let result = Self::#fn_ident(&caller, &mut ctx.borrow_mut() #(, #arg_names)*).await;
|
||||
result
|
||||
})
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "async"))]
|
||||
{
|
||||
// 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
|
||||
// the future ends up Pending, this func will Trap.
|
||||
quote! {
|
||||
config.wrap_host_func(
|
||||
module,
|
||||
field,
|
||||
move |caller: wasmtime::Caller #(, #arg_decls)*| -> Result<#ret_ty, wasmtime::Trap> {
|
||||
let ctx = caller
|
||||
.store()
|
||||
.get::<std::rc::Rc<std::cell::RefCell<#ctx_type>>>()
|
||||
.ok_or_else(|| wasmtime::Trap::new("context is missing in the store"))?;
|
||||
let result = Self::#fn_ident(&caller, &mut ctx.borrow_mut() #(, #arg_names)*).await;
|
||||
result
|
||||
})
|
||||
}
|
||||
);
|
||||
Self::run_in_dummy_executor(Self::#fn_ident(&caller, &mut ctx.borrow_mut() #(, #arg_names)*))
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
@@ -334,11 +390,52 @@ fn generate_func(
|
||||
.store()
|
||||
.get::<std::rc::Rc<std::cell::RefCell<#ctx_type>>>()
|
||||
.ok_or_else(|| wasmtime::Trap::new("context is missing in the store"))?;
|
||||
let result = Self::#fn_ident(&caller, &mut ctx.borrow_mut() #(, #arg_names)*);
|
||||
result
|
||||
Self::#fn_ident(&caller, &mut ctx.borrow_mut() #(, #arg_names)*)
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
host_funcs.push((func.name.clone(), host_wrapper));
|
||||
}
|
||||
#[cfg(not(feature = "async"))]
|
||||
fn dummy_executor() -> TokenStream2 {
|
||||
quote! {
|
||||
fn run_in_dummy_executor<F: std::future::Future>(future: F) -> F::Output {
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
|
||||
|
||||
let mut f = Pin::from(Box::new(future));
|
||||
let waker = dummy_waker();
|
||||
let mut cx = Context::from_waker(&waker);
|
||||
match f.as_mut().poll(&mut cx) {
|
||||
Poll::Ready(val) => return val,
|
||||
Poll::Pending => {
|
||||
panic!("Cannot wait on pending future: must enable wiggle \"async\" future and execute on an async Store")
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
188
crates/wiggle/wasmtime/tests/atoms_sync.rs
Normal file
188
crates/wiggle/wasmtime/tests/atoms_sync.rs
Normal file
@@ -0,0 +1,188 @@
|
||||
#![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::rc::Rc;
|
||||
|
||||
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]
|
||||
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<types::AliasToFloat, types::Errno> {
|
||||
Ok((an_int as f32) * 2.0)
|
||||
}
|
||||
}
|
||||
|
||||
fn run_int_float_args(linker: &wasmtime::Linker) {
|
||||
let shim_mod = shim_module(linker.store());
|
||||
let shim_inst = linker.instantiate(&shim_mod).unwrap();
|
||||
|
||||
let results = shim_inst
|
||||
.get_func("int_float_args_shim")
|
||||
.unwrap()
|
||||
.call(&[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"
|
||||
);
|
||||
}
|
||||
|
||||
fn run_double_int_return_float(linker: &wasmtime::Linker) {
|
||||
let shim_mod = shim_module(linker.store());
|
||||
let shim_inst = linker.instantiate(&shim_mod).unwrap();
|
||||
|
||||
let input: i32 = 123;
|
||||
let result_location: i32 = 0;
|
||||
|
||||
let results = shim_inst
|
||||
.get_func("double_int_return_float_shim")
|
||||
.unwrap()
|
||||
.call(&[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);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "async"))]
|
||||
#[test]
|
||||
fn test_sync_host_func() {
|
||||
let store = store();
|
||||
|
||||
let ctx = Rc::new(RefCell::new(Ctx));
|
||||
let atoms = Atoms::new(&store, ctx.clone());
|
||||
|
||||
let mut linker = wasmtime::Linker::new(&store);
|
||||
atoms.add_to_linker(&mut linker).unwrap();
|
||||
|
||||
run_int_float_args(&linker);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "async"))]
|
||||
#[test]
|
||||
fn test_async_host_func() {
|
||||
let store = store();
|
||||
|
||||
let ctx = Rc::new(RefCell::new(Ctx));
|
||||
let atoms = Atoms::new(&store, ctx.clone());
|
||||
|
||||
let mut linker = wasmtime::Linker::new(&store);
|
||||
atoms.add_to_linker(&mut linker).unwrap();
|
||||
|
||||
run_double_int_return_float(&linker);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "async"))]
|
||||
#[test]
|
||||
fn test_sync_config_host_func() {
|
||||
let mut config = wasmtime::Config::new();
|
||||
Atoms::add_to_config(&mut config);
|
||||
|
||||
let engine = wasmtime::Engine::new(&config).unwrap();
|
||||
let store = wasmtime::Store::new(&engine);
|
||||
|
||||
assert!(Atoms::set_context(&store, Ctx).is_ok());
|
||||
|
||||
let linker = wasmtime::Linker::new(&store);
|
||||
run_int_float_args(&linker);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "async"))]
|
||||
#[test]
|
||||
fn test_async_config_host_func() {
|
||||
let mut config = wasmtime::Config::new();
|
||||
Atoms::add_to_config(&mut config);
|
||||
|
||||
let engine = wasmtime::Engine::new(&config).unwrap();
|
||||
let store = wasmtime::Store::new(&engine);
|
||||
|
||||
assert!(Atoms::set_context(&store, Ctx).is_ok());
|
||||
|
||||
let linker = wasmtime::Linker::new(&store);
|
||||
run_double_int_return_float(&linker);
|
||||
}
|
||||
|
||||
fn store() -> wasmtime::Store {
|
||||
wasmtime::Store::new(&wasmtime::Engine::new(&wasmtime::Config::new()).unwrap())
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
Reference in New Issue
Block a user