Implement RFC 11: Redesigning Wasmtime's APIs (#2897)

Implement Wasmtime's new API as designed by RFC 11. This is quite a large commit which has had lots of discussion externally, so for more information it's best to read the RFC thread and the PR thread.
This commit is contained in:
Alex Crichton
2021-06-03 09:10:53 -05:00
committed by GitHub
parent a5a28b1c5b
commit 7a1b7cdf92
233 changed files with 13349 additions and 11997 deletions

View File

@@ -6,21 +6,29 @@ use std::collections::HashMap;
use std::rc::Rc;
use witx::{Document, Id, InterfaceFunc, Module, NamedType, TypeRef};
pub use crate::config::Asyncness;
pub struct CodegenSettings {
pub errors: ErrorTransform,
async_: AsyncConf,
pub async_: AsyncConf,
pub wasmtime: bool,
}
impl CodegenSettings {
pub fn new(error_conf: &ErrorConf, async_: &AsyncConf, doc: &Document) -> Result<Self, Error> {
pub fn new(
error_conf: &ErrorConf,
async_: &AsyncConf,
doc: &Document,
wasmtime: bool,
) -> Result<Self, Error> {
let errors = ErrorTransform::new(error_conf, doc)?;
Ok(Self {
errors,
async_: async_.clone(),
wasmtime,
})
}
pub fn is_async(&self, module: &Module, func: &InterfaceFunc) -> bool {
self.async_
.is_async(module.name.as_str(), func.name.as_str())
pub fn get_async(&self, module: &Module, func: &InterfaceFunc) -> Asyncness {
self.async_.get(module.name.as_str(), func.name.as_str())
}
}

View File

@@ -14,6 +14,16 @@ pub struct Config {
pub witx: WitxConf,
pub errors: ErrorConf,
pub async_: AsyncConf,
pub wasmtime: bool,
}
mod kw {
syn::custom_keyword!(witx);
syn::custom_keyword!(witx_literal);
syn::custom_keyword!(block_on);
syn::custom_keyword!(errors);
syn::custom_keyword!(target);
syn::custom_keyword!(wasmtime);
}
#[derive(Debug, Clone)]
@@ -21,12 +31,7 @@ pub enum ConfigField {
Witx(WitxConf),
Error(ErrorConf),
Async(AsyncConf),
}
mod kw {
syn::custom_keyword!(witx);
syn::custom_keyword!(witx_literal);
syn::custom_keyword!(errors);
Wasmtime(bool),
}
impl Parse for ConfigField {
@@ -48,8 +53,20 @@ impl Parse for ConfigField {
input.parse::<Token![async]>()?;
input.parse::<Token![:]>()?;
Ok(ConfigField::Async(AsyncConf {
blocking: false,
functions: input.parse()?,
}))
} else if lookahead.peek(kw::block_on) {
input.parse::<kw::block_on>()?;
input.parse::<Token![:]>()?;
Ok(ConfigField::Async(AsyncConf {
blocking: true,
functions: input.parse()?,
}))
} else if lookahead.peek(kw::wasmtime) {
input.parse::<kw::wasmtime>()?;
input.parse::<Token![:]>()?;
Ok(ConfigField::Wasmtime(input.parse::<syn::LitBool>()?.value))
} else {
Err(lookahead.error())
}
@@ -61,6 +78,7 @@ impl Config {
let mut witx = None;
let mut errors = None;
let mut async_ = None;
let mut wasmtime = None;
for f in fields {
match f {
ConfigField::Witx(c) => {
@@ -81,6 +99,12 @@ impl Config {
}
async_ = Some(c);
}
ConfigField::Wasmtime(c) => {
if wasmtime.is_some() {
return Err(Error::new(err_loc, "duplicate `wasmtime` field"));
}
wasmtime = Some(c);
}
}
}
Ok(Config {
@@ -89,6 +113,7 @@ impl Config {
.ok_or_else(|| Error::new(err_loc, "`witx` field required"))?,
errors: errors.take().unwrap_or_default(),
async_: async_.take().unwrap_or_default(),
wasmtime: wasmtime.unwrap_or(true),
})
}
@@ -284,9 +309,41 @@ impl Parse for ErrorConfField {
#[derive(Clone, Default, Debug)]
/// Modules and funcs that have async signatures
pub struct AsyncConf {
blocking: bool,
functions: AsyncFunctions,
}
#[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_async(&self) -> bool {
match self {
Self::Async => true,
_ => false,
}
}
pub fn is_blocking(&self) -> bool {
match self {
Self::Blocking => true,
_ => false,
}
}
pub fn is_sync(&self) -> bool {
match self {
Self::Sync => true,
_ => false,
}
}
}
#[derive(Clone, Debug)]
pub enum AsyncFunctions {
Some(HashMap<String, Vec<String>>),
@@ -299,15 +356,36 @@ impl Default for AsyncFunctions {
}
impl AsyncConf {
pub fn is_async(&self, module: &str, function: &str) -> bool {
pub fn get(&self, module: &str, function: &str) -> Asyncness {
let a = if self.blocking {
Asyncness::Blocking
} else {
Asyncness::Async
};
match &self.functions {
AsyncFunctions::Some(fs) => fs
.get(module)
.and_then(|fs| fs.iter().find(|f| *f == function))
.is_some(),
AsyncFunctions::All => true,
AsyncFunctions::Some(fs) => {
if fs
.get(module)
.and_then(|fs| fs.iter().find(|f| *f == function))
.is_some()
{
a
} else {
Asyncness::Sync
}
}
AsyncFunctions::All => a,
}
}
pub fn contains_async(&self, module: &witx::Module) -> bool {
for f in module.funcs() {
if self.get(module.name.as_str(), f.name.as_str()).is_async() {
return true;
}
}
false
}
}
impl Parse for AsyncFunctions {
@@ -378,3 +456,97 @@ impl Parse for AsyncConfField {
}
}
}
#[derive(Clone)]
pub struct WasmtimeConfig {
pub c: Config,
pub target: syn::Path,
}
#[derive(Clone)]
pub enum WasmtimeConfigField {
Core(ConfigField),
Target(syn::Path),
}
impl WasmtimeConfig {
pub fn build(fields: impl Iterator<Item = WasmtimeConfigField>, err_loc: Span) -> Result<Self> {
let mut target = None;
let mut cs = Vec::new();
for f in fields {
match f {
WasmtimeConfigField::Target(c) => {
if target.is_some() {
return Err(Error::new(err_loc, "duplicate `target` field"));
}
target = Some(c);
}
WasmtimeConfigField::Core(c) => cs.push(c),
}
}
let c = Config::build(cs.into_iter(), err_loc)?;
Ok(WasmtimeConfig {
c,
target: target
.take()
.ok_or_else(|| Error::new(err_loc, "`target` field required"))?,
})
}
}
impl Parse for WasmtimeConfig {
fn parse(input: ParseStream) -> Result<Self> {
let contents;
let _lbrace = braced!(contents in input);
let fields: Punctuated<WasmtimeConfigField, Token![,]> =
contents.parse_terminated(WasmtimeConfigField::parse)?;
Ok(WasmtimeConfig::build(fields.into_iter(), input.span())?)
}
}
impl Parse for WasmtimeConfigField {
fn parse(input: ParseStream) -> Result<Self> {
let lookahead = input.lookahead1();
if lookahead.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 {
Err(lookahead.error())
}
}
}

View File

@@ -14,6 +14,26 @@ pub fn define_func(
func: &witx::InterfaceFunc,
settings: &CodegenSettings,
) -> TokenStream {
let (ts, _bounds) = _define_func(names, module, func, settings);
ts
}
pub fn func_bounds(
names: &Names,
module: &witx::Module,
func: &witx::InterfaceFunc,
settings: &CodegenSettings,
) -> Vec<Ident> {
let (_ts, bounds) = _define_func(names, module, func, settings);
bounds
}
fn _define_func(
names: &Names,
module: &witx::Module,
func: &witx::InterfaceFunc,
settings: &CodegenSettings,
) -> (TokenStream, Vec<Ident>) {
let rt = names.runtime_mod();
let ident = names.func(&func.name);
@@ -36,7 +56,7 @@ pub fn define_func(
};
let mut body = TokenStream::new();
let mut required_impls = vec![names.trait_name(&module.name)];
let mut bounds = vec![names.trait_name(&module.name)];
func.call_interface(
&module.name,
&mut Rust {
@@ -49,37 +69,40 @@ pub fn define_func(
module,
funcname: func.name.as_str(),
settings,
required_impls: &mut required_impls,
bounds: &mut bounds,
},
);
let asyncness = if settings.is_async(&module, &func) {
quote!(async)
} else {
let asyncness = if settings.get_async(&module, &func).is_sync() {
quote!()
} else {
quote!(async)
};
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 #asyncness fn #ident(
ctx: &(impl #(#required_impls)+*),
memory: &dyn #rt::GuestMemory,
#(#abi_params),*
) -> Result<#abi_ret, #rt::Trap> {
use std::convert::TryFrom as _;
(
quote! {
#[allow(unreachable_code)] // deals with warnings in noreturn functions
pub #asyncness fn #ident(
ctx: &mut (impl #(#bounds)+*),
memory: &dyn #rt::GuestMemory,
#(#abi_params),*
) -> Result<#abi_ret, #rt::Trap> {
use std::convert::TryFrom as _;
let _span = #rt::tracing::span!(
#rt::tracing::Level::TRACE,
"wiggle abi",
module = #mod_name,
function = #func_name
);
let _enter = _span.enter();
let _span = #rt::tracing::span!(
#rt::tracing::Level::TRACE,
"wiggle abi",
module = #mod_name,
function = #func_name
);
let _enter = _span.enter();
#body
}
}
#body
}
},
bounds,
)
}
struct Rust<'a> {
@@ -92,13 +115,13 @@ struct Rust<'a> {
module: &'a witx::Module,
funcname: &'a str,
settings: &'a CodegenSettings,
required_impls: &'a mut Vec<Ident>,
bounds: &'a mut Vec<Ident>,
}
impl Rust<'_> {
fn required_impl(&mut self, i: Ident) {
if !self.required_impls.contains(&i) {
self.required_impls.push(i);
fn bound(&mut self, i: Ident) {
if !self.bounds.contains(&i) {
self.bounds.push(i);
}
}
}
@@ -222,13 +245,13 @@ 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) {
if self.settings.get_async(&self.module, &func).is_sync() {
self.src.extend(quote! {
let ret = #trait_name::#ident(ctx, #(#args),*).await;
let ret = #trait_name::#ident(ctx, #(#args),*);
})
} else {
self.src.extend(quote! {
let ret = #trait_name::#ident(ctx, #(#args),*);
let ret = #trait_name::#ident(ctx, #(#args),*).await;
})
};
self.src.extend(quote! {
@@ -254,7 +277,7 @@ impl witx::Bindgen for Rust<'_> {
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"));
self.bound(quote::format_ident!("UserErrorConversion"));
quote!(UserErrorConversion::#method(ctx, #val)?)
}
None => val,

View File

@@ -5,6 +5,7 @@ mod lifetimes;
mod module_trait;
mod names;
mod types;
pub mod wasmtime;
use heck::ShoutySnakeCase;
use lifetimes::anon_lifetime;
@@ -12,7 +13,7 @@ use proc_macro2::{Literal, TokenStream};
use quote::quote;
pub use codegen_settings::{CodegenSettings, UserErrorType};
pub use config::Config;
pub use config::{Config, WasmtimeConfig};
pub use funcs::define_func;
pub use module_trait::define_module_trait;
pub use names::Names;
@@ -42,7 +43,7 @@ pub fn generate(doc: &witx::Document, names: &Names, settings: &CodegenSettings)
let abi_typename = names.type_ref(&errtype.abi_type(), anon_lifetime());
let user_typename = errtype.typename();
let methodname = names.user_error_conversion_method(&errtype);
quote!(fn #methodname(&self, e: super::#user_typename) -> Result<#abi_typename, #rt::Trap>;)
quote!(fn #methodname(&mut self, e: super::#user_typename) -> Result<#abi_typename, #rt::Trap>;)
});
let user_error_conversion = quote! {
pub trait UserErrorConversion {
@@ -55,12 +56,20 @@ pub fn generate(doc: &witx::Document, names: &Names, settings: &CodegenSettings)
.funcs()
.map(|f| define_func(&names, &module, &f, &settings));
let modtrait = define_module_trait(&names, &module, &settings);
let wasmtime = if settings.wasmtime {
crate::wasmtime::link_module(&module, &names, None, &settings)
} else {
quote! {}
};
quote!(
pub mod #modname {
use super::types::*;
pub use super::types::UserErrorConversion;
#(#fs)*
#modtrait
#wasmtime
}
)
});

View File

@@ -75,16 +75,16 @@ pub fn define_module_trait(names: &Names, m: &Module, settings: &CodegenSettings
_ => unimplemented!(),
};
let asyncness = if settings.is_async(&m, &f) {
quote!(async)
} else {
let asyncness = if settings.get_async(&m, &f).is_sync() {
quote!()
} else {
quote!(async)
};
if is_anonymous {
quote!(#asyncness fn #funcname(&self, #(#args),*) -> #result; )
quote!(#asyncness fn #funcname(&mut self, #(#args),*) -> #result; )
} else {
quote!(#asyncness fn #funcname<#lifetime>(&self, #(#args),*) -> #result;)
quote!(#asyncness fn #funcname<#lifetime>(&mut self, #(#args),*) -> #result;)
}
});

View File

@@ -0,0 +1,186 @@
use crate::config::Asyncness;
use crate::funcs::func_bounds;
use crate::{CodegenSettings, Names};
use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote};
use std::collections::HashSet;
pub fn link_module(
module: &witx::Module,
names: &Names,
target_path: Option<&syn::Path>,
settings: &CodegenSettings,
) -> TokenStream {
let module_ident = names.module(&module.name);
let send_bound = if settings.async_.contains_async(module) {
quote! { + Send, T: Send }
} else {
quote! {}
};
let mut bodies = Vec::new();
let mut bounds = HashSet::new();
for f in module.funcs() {
let asyncness = settings.async_.get(module.name.as_str(), f.name.as_str());
bodies.push(generate_func(&module, &f, names, target_path, asyncness));
let bound = func_bounds(names, module, &f, settings);
for b in bound {
bounds.insert(b);
}
}
let ctx_bound = if let Some(target_path) = target_path {
let bounds = bounds
.into_iter()
.map(|b| quote!(#target_path::#module_ident::#b));
quote!( #(#bounds)+* #send_bound )
} else {
let bounds = bounds.into_iter();
quote!( #(#bounds)+* #send_bound )
};
let func_name = if target_path.is_none() {
format_ident!("add_to_linker")
} else {
format_ident!("add_{}_to_linker", module_ident)
};
let rt = names.runtime_mod();
quote! {
/// Adds all instance items to the specified `Linker`.
pub fn #func_name<T, U>(
linker: &mut #rt::wasmtime_crate::Linker<T>,
get_cx: impl Fn(&mut T) -> &mut U + Send + Sync + Copy + 'static,
) -> #rt::anyhow::Result<()>
where
U: #ctx_bound #send_bound
{
#(#bodies)*
Ok(())
}
}
}
fn generate_func(
module: &witx::Module,
func: &witx::InterfaceFunc,
names: &Names,
target_path: Option<&syn::Path>,
asyncness: Asyncness,
) -> TokenStream {
let rt = names.runtime_mod();
let module_str = module.name.as_str();
let module_ident = names.module(&module.name);
let field_str = func.name.as_str();
let field_ident = names.func(&func.name);
let (params, results) = func.wasm_signature();
let arg_names = (0..params.len())
.map(|i| Ident::new(&format!("arg{}", i), Span::call_site()))
.collect::<Vec<_>>();
let arg_decls = params
.iter()
.enumerate()
.map(|(i, ty)| {
let name = &arg_names[i];
let wasm = names.wasm_type(*ty);
quote! { #name: #wasm }
})
.collect::<Vec<_>>();
let ret_ty = match results.len() {
0 => quote!(()),
1 => names.wasm_type(results[0]),
_ => unimplemented!(),
};
let await_ = if asyncness.is_sync() {
quote!()
} else {
quote!(.await)
};
let abi_func = if let Some(target_path) = target_path {
quote!( #target_path::#module_ident::#field_ident )
} else {
quote!( #field_ident )
};
let body = quote! {
let mem = match caller.get_export("memory") {
Some(#rt::wasmtime_crate::Extern::Memory(m)) => m,
_ => {
return Err(#rt::wasmtime_crate::Trap::new("missing required memory export"));
}
};
// Note the unsafety here. Our goal is to simultaneously borrow the
// memory and custom data from `caller`, and the store it's connected
// to. Rust will not let us do that, however, because we must call two
// separate methods (both of which borrow the whole `caller`) and one of
// our borrows is mutable (the custom data).
//
// This operation, however, is safe because these borrows do not overlap
// and in the process of borrowing them mutability doesn't actually
// touch anything. This is akin to mutably borrowing two indices in an
// array, which is safe so long as the indices are separate.
//
// TODO: depending on how common this is for other users to run into we
// may wish to consider adding a dedicated method for this. For now the
// future of `GuestPtr` may be a bit hazy, so let's just get this
// working from the previous iteration for now.
let (ctx, mem) = unsafe {
let mem = &mut *(mem.data_mut(&mut caller) as *mut [u8]);
(get_cx(caller.data_mut()), #rt::wasmtime::WasmtimeGuestMemory::new(mem))
};
match #abi_func(ctx, &mem #(, #arg_names)*) #await_ {
Ok(r) => Ok(<#ret_ty>::from(r)),
Err(#rt::Trap::String(err)) => Err(#rt::wasmtime_crate::Trap::new(err)),
Err(#rt::Trap::I32Exit(err)) => Err(#rt::wasmtime_crate::Trap::i32_exit(err)),
}
};
match asyncness {
Asyncness::Async => {
let wrapper = format_ident!("func_wrap{}_async", params.len());
quote! {
linker.#wrapper(
#module_str,
#field_str,
move |mut caller: #rt::wasmtime_crate::Caller<'_, T> #(, #arg_decls)*| {
Box::new(async move { #body })
},
)?;
}
}
Asyncness::Blocking => {
quote! {
linker.func_wrap(
#module_str,
#field_str,
move |mut caller: #rt::wasmtime_crate::Caller<'_, T> #(, #arg_decls)*| -> Result<#ret_ty, #rt::wasmtime_crate::Trap> {
let result = async { #body };
#rt::run_in_dummy_executor(result)
},
)?;
}
}
Asyncness::Sync => {
quote! {
linker.func_wrap(
#module_str,
#field_str,
move |mut caller: #rt::wasmtime_crate::Caller<'_, T> #(, #arg_decls)*| -> Result<#ret_ty, #rt::wasmtime_crate::Trap> {
#body
},
)?;
}
}
}
}