generate a module trait and call it

This commit is contained in:
Pat Hickey
2020-01-23 12:46:57 -08:00
parent cb24fd97c0
commit b4f21752b0
7 changed files with 139 additions and 17 deletions

View File

@@ -1,4 +1,4 @@
use proc_macro2::TokenStream; use proc_macro2::{Ident, TokenStream};
use quote::quote; use quote::quote;
use crate::names::Names; use crate::names::Names;
@@ -6,10 +6,6 @@ use crate::names::Names;
// FIXME need to template what argument is required to an import function - some context // FIXME need to template what argument is required to an import function - some context
// struct (e.g. WasiCtx) should be provided at the invocation of the `gen` proc macro. // struct (e.g. WasiCtx) should be provided at the invocation of the `gen` proc macro.
// //
// Additionally - need to template how to transform GuestValueError and MemoryError into
// the error type returned! From impl is a good start, but what if we want to log
// a more informative error? Maybe the conversion should be a (generated, for each by-value first
// return type) trait impled by WasiCtx?
pub fn define_func(names: &Names, func: &witx::InterfaceFunc) -> TokenStream { pub fn define_func(names: &Names, func: &witx::InterfaceFunc) -> TokenStream {
let ident = names.func(&func.name); let ident = names.func(&func.name);
@@ -38,7 +34,7 @@ pub fn define_func(names: &Names, func: &witx::InterfaceFunc) -> TokenStream {
.chain(func.results.iter().skip(1)) .chain(func.results.iter().skip(1))
.map(arg_signature); .map(arg_signature);
let abi_args = quote!( let abi_args = quote!(
wasi_ctx: &mut WasiCtx, memory: GuestMemory, ctx: &mut WasiCtx, memory: ::memory::GuestMemory,
#(#params),* #(#params),*
); );
let abi_ret = if let Some(first_result) = func.results.get(0) { let abi_ret = if let Some(first_result) = func.results.get(0) {
@@ -47,10 +43,70 @@ pub fn define_func(names: &Names, func: &witx::InterfaceFunc) -> TokenStream {
_ => unreachable!("first result should always be passed by value"), _ => unreachable!("first result should always be passed by value"),
} }
} else if func.noreturn { } else if func.noreturn {
quote!(!) // Ideally we would return `quote!(!)` here, but, we'd have to change
// the error handling logic in all the marshalling code to never return,
// and instead provide some other way to bail to the context...
// noreturn func
unimplemented!("noreturn funcs not supported yet!")
} else { } else {
quote!(()) quote!(())
}; };
quote!(pub fn #ident(#abi_args) -> #abi_ret { unimplemented!() }) let err_type = func
.results
.get(0)
.map(|res| names.type_ref(&res.tref))
.unwrap_or_else(|| abi_ret.clone());
let err_val = func
.results
.get(0)
.map(|_res| quote!(#abi_ret::from(e)))
.unwrap_or_else(|| quote!(()));
let marshal_args = func
.params
.iter()
.map(|param| match param.tref.type_().passed_by() {
witx::TypePassedBy::Value(_atom) => {
// FIXME atom -> param.tref can be either an `as` conversion, or `try_from`
let name = names.func_param(&param.name);
let interface_type = names.type_ref(&param.tref);
quote!( let #name = #name as #interface_type; )
}
_ => unimplemented!(),
});
let trait_args = func
.params
.iter()
.map(|param| names.func_param(&param.name));
let trait_rets = func
.results
.iter()
.skip(1)
.map(|result| names.func_param(&result.name))
.collect::<Vec<Ident>>();
let (trait_rets, trait_bindings) = if trait_rets.is_empty() {
(quote!({}), quote!(_))
} else {
let tuple = quote!((#(#trait_rets),*));
(tuple.clone(), tuple)
};
let marshal_rets = func
.results
.iter()
.skip(1)
.map(|_result| quote! { unimplemented!("convert result..."); });
quote!(pub fn #ident(#abi_args) -> #abi_ret {
#(#marshal_args)*
let #trait_bindings = match ctx.#ident(#(#trait_args),*) {
Ok(#trait_bindings) => #trait_rets,
Err(e) => { return #err_val; },
};
#(#marshal_rets)*
let success:#err_type = ::memory::GuestError::success();
#abi_ret::from(success)
})
} }

View File

@@ -1,6 +1,7 @@
extern crate proc_macro; extern crate proc_macro;
mod funcs; mod funcs;
mod module_trait;
mod names; mod names;
mod parse; mod parse;
mod types; mod types;
@@ -10,6 +11,7 @@ use proc_macro2::TokenStream as TokenStream2;
use quote::quote; use quote::quote;
use funcs::define_func; use funcs::define_func;
use module_trait::define_module_trait;
use names::Names; use names::Names;
use types::define_datatype; use types::define_datatype;
@@ -27,12 +29,14 @@ pub fn from_witx(args: TokenStream) -> TokenStream {
let modules = doc.modules().map(|module| { let modules = doc.modules().map(|module| {
let modname = names.module(&module.name); let modname = names.module(&module.name);
let fs = module.funcs().map(|f| define_func(&names, &f)); let fs = module.funcs().map(|f| define_func(&names, &f));
let modtrait = define_module_trait(&names, &module);
quote!( quote!(
mod #modname { mod #modname {
use super::*; use super::WasiCtx;
use super::types::*; use super::types::*;
use memory::*;
#(#fs)* #(#fs)*
#modtrait
} }
) )
}); });

View File

@@ -0,0 +1,33 @@
use proc_macro2::TokenStream;
use quote::quote;
use crate::names::Names;
use witx::Module;
pub fn define_module_trait(names: &Names, m: &Module) -> TokenStream {
let traitname = names.trait_name(&m.name);
let traitmethods = m.funcs().map(|f| {
let funcname = names.func(&f.name);
let args = f.params.iter().map(|arg| {
let arg_name = names.func_param(&arg.name);
let arg_type = names.type_ref(&arg.tref);
quote!(#arg_name: #arg_type)
});
let rets = f
.results
.iter()
.skip(1)
.map(|ret| names.type_ref(&ret.tref));
let err = f
.results
.get(0)
.map(|err_result| names.type_ref(&err_result.tref))
.unwrap_or(quote!(()));
quote!(fn #funcname(&mut self, #(#args),*) -> Result<(#(#rets),*), #err>;)
});
quote! {
pub trait #traitname {
#(#traitmethods)*
}
}
}

View File

@@ -42,7 +42,6 @@ impl Names {
} }
} }
#[allow(unused)]
pub fn type_ref(&self, tref: &TypeRef) -> TokenStream { pub fn type_ref(&self, tref: &TypeRef) -> TokenStream {
match tref { match tref {
TypeRef::Name(nt) => { TypeRef::Name(nt) => {
@@ -69,6 +68,10 @@ impl Names {
format_ident!("{}", id.as_str().to_snake_case()) format_ident!("{}", id.as_str().to_snake_case())
} }
pub fn trait_name(&self, id: &Id) -> Ident {
format_ident!("{}", id.as_str().to_camel_case())
}
pub fn func(&self, id: &Id) -> Ident { pub fn func(&self, id: &Id) -> Ident {
format_ident!("{}", id.as_str().to_snake_case()) format_ident!("{}", id.as_str().to_snake_case())
} }

View File

@@ -32,6 +32,7 @@ fn define_enum(names: &Names, name: &witx::Id, e: &witx::EnumDatatype) -> TokenS
let ident = names.type_(&name); let ident = names.type_(&name);
let repr = int_repr_tokens(e.repr); let repr = int_repr_tokens(e.repr);
let signed_repr = int_signed_repr_tokens(e.repr);
let variant_names = e.variants.iter().map(|v| names.enum_variant(&v.name)); let variant_names = e.variants.iter().map(|v| names.enum_variant(&v.name));
let tryfrom_repr_cases = e.variants.iter().enumerate().map(|(n, v)| { let tryfrom_repr_cases = e.variants.iter().enumerate().map(|(n, v)| {
@@ -62,6 +63,13 @@ fn define_enum(names: &Names, name: &witx::Id, e: &witx::EnumDatatype) -> TokenS
} }
} }
impl ::std::convert::TryFrom<#signed_repr> for #ident {
type Error = ::memory::GuestValueError;
fn try_from(value: #signed_repr) -> Result<#ident, ::memory::GuestValueError> {
#ident::try_from(value as #repr)
}
}
impl From<#ident> for #repr { impl From<#ident> for #repr {
fn from(e: #ident) -> #repr { fn from(e: #ident) -> #repr {
match e { match e {
@@ -70,6 +78,12 @@ fn define_enum(names: &Names, name: &witx::Id, e: &witx::EnumDatatype) -> TokenS
} }
} }
impl From<#ident> for #signed_repr {
fn from(e: #ident) -> #signed_repr {
#repr::from(e) as #signed_repr
}
}
impl ::memory::GuestType for #ident { impl ::memory::GuestType for #ident {
fn size() -> u32 { fn size() -> u32 {
::std::mem::size_of::<#repr>() as u32 ::std::mem::size_of::<#repr>() as u32
@@ -109,3 +123,11 @@ fn int_repr_tokens(int_repr: witx::IntRepr) -> TokenStream {
witx::IntRepr::U64 => quote!(u64), witx::IntRepr::U64 => quote!(u64),
} }
} }
fn int_signed_repr_tokens(int_repr: witx::IntRepr) -> TokenStream {
match int_repr {
witx::IntRepr::U8 => quote!(i8),
witx::IntRepr::U16 => quote!(i16),
witx::IntRepr::U32 => quote!(i32),
witx::IntRepr::U64 => quote!(i64),
}
}

View File

@@ -69,7 +69,7 @@ builtin_copy!(u8, i8, u16, i16, u32, i32, u64, i64, f32, f64, usize, char);
pub trait GuestError { pub trait GuestError {
type Context; type Context;
fn is_success(&self) -> bool; fn success() -> Self;
fn from_memory_error(memory_error: MemoryError, ctx: &mut Self::Context) -> Self; fn from_memory_error(memory_error: MemoryError, ctx: &mut Self::Context) -> Self;
fn from_value_error(value_error: GuestValueError, ctx: &mut Self::Context) -> Self; fn from_value_error(value_error: GuestValueError, ctx: &mut Self::Context) -> Self;
} }

View File

@@ -7,17 +7,21 @@ pub mod test {
value_errors: Vec<::memory::GuestValueError>, value_errors: Vec<::memory::GuestValueError>,
} }
impl foo::Foo for WasiCtx {
fn bar(&mut self, an_int: u32, an_float: f32) -> Result<(), types::Errno> {
println!("BAR: {} {}", an_int, an_float);
Ok(())
}
}
// Errno is used as a first return value in the functions above, therefore // Errno is used as a first return value in the functions above, therefore
// it must implement GuestError with type Context = WasiCtx. // it must implement GuestError with type Context = WasiCtx.
// The context type should let you do logging or debugging or whatever you need // The context type should let you do logging or debugging or whatever you need
// with these errors. We just push them to vecs. // with these errors. We just push them to vecs.
impl ::memory::GuestError for types::Errno { impl ::memory::GuestError for types::Errno {
type Context = WasiCtx; type Context = WasiCtx;
fn is_success(&self) -> bool { fn success() -> types::Errno {
match self { types::Errno::Ok
types::Errno::Ok => true,
_ => false,
}
} }
fn from_memory_error(e: ::memory::MemoryError, ctx: &mut WasiCtx) -> types::Errno { fn from_memory_error(e: ::memory::MemoryError, ctx: &mut WasiCtx) -> types::Errno {
ctx.mem_errors.push(e); ctx.mem_errors.push(e);