generate: now we have a way to do errors, i guess

This commit is contained in:
Pat Hickey
2020-01-22 20:47:40 -08:00
parent 97077954f8
commit c05475b806
8 changed files with 158 additions and 96 deletions

View File

@@ -0,0 +1,45 @@
use crate::names::Names;
use heck::SnakeCase;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use std::collections::HashSet;
use witx::{Document, TypeRef};
/// The context struct needs to implement a trait for converting memory and value errors into the
/// witx doc's error types.
pub fn define_error_trait(names: &Names, doc: &Document) -> TokenStream {
// All non-anonymous first return types are used to pass errors.
let error_typenames = doc
.modules()
.flat_map(|m| {
m.funcs()
.filter_map(|f| {
f.results.get(0).and_then(|r| match &r.tref {
TypeRef::Name(nt) => Some(nt.name.clone()),
_ => None,
})
})
.collect::<HashSet<witx::Id>>()
})
.collect::<HashSet<witx::Id>>();
let methods = error_typenames.iter().map(|typename| {
let tname = names.type_(typename);
let methodfragment = typename.as_str().to_snake_case();
let success = format_ident!("success_to_{}", methodfragment);
let memory_error = format_ident!("memory_error_to_{}", methodfragment);
let value_error = format_ident!("value_error_to_{}", methodfragment);
quote! {
fn #success(&mut self) -> #tname;
fn #memory_error(&mut self, err: ::memory::MemoryError) -> #tname;
fn #value_error(&mut self, err: ::memory::GuestValueError) -> #tname;
}
});
quote!(
pub trait WitxErrorConversion {
#(#methods)*
}
)
}

View File

@@ -3,44 +3,45 @@ use quote::quote;
use crate::names::Names; use crate::names::Names;
// FIXME need to template what arguments are 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.
// Rather than pass in memory as a `&mut [u8]` as today, require a `GuestMemory<'a>` be //
// passed in. // 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);
let mut args = quote!(wasi_ctx: &mut WasiCtx, memory: GuestMemory,);
let arg_signature = |param: &witx::InterfaceFuncParam| -> TokenStream { let arg_signature = |param: &witx::InterfaceFuncParam| -> TokenStream {
let name = names.func_param(&param.name); let name = names.func_param(&param.name);
match param.tref.type_().passed_by() { match param.tref.type_().passed_by() {
witx::TypePassedBy::Value(atom) => { witx::TypePassedBy::Value(atom) => {
let atom = names.atom_type(atom); let atom = names.atom_type(atom);
quote!(#name: #atom,) quote!(#name: #atom)
} }
witx::TypePassedBy::Pointer => { witx::TypePassedBy::Pointer => {
let atom = names.atom_type(witx::AtomType::I32); let atom = names.atom_type(witx::AtomType::I32);
quote!(#name: #atom,) quote!(#name: #atom)
} }
witx::TypePassedBy::PointerLengthPair => { witx::TypePassedBy::PointerLengthPair => {
let atom = names.atom_type(witx::AtomType::I32); let atom = names.atom_type(witx::AtomType::I32);
let len_name = names.func_len_param(&param.name); let len_name = names.func_len_param(&param.name);
quote!(#name: #atom, #len_name, #atom,) quote!(#name: #atom, #len_name: #atom)
} }
} }
}; };
for param in func.params.iter() { let params = func
args.extend(arg_signature(param)); .params
} .iter()
.chain(func.results.iter().skip(1))
if let Some(arg_results) = func.results.get(1..) { .map(arg_signature);
for result in arg_results { let abi_args = quote!(
args.extend(arg_signature(result)) wasi_ctx: &mut WasiCtx, memory: GuestMemory,
} #(#params),*
} );
let abi_ret = if let Some(first_result) = func.results.get(0) {
let ret = if let Some(first_result) = func.results.get(0) {
match first_result.tref.type_().passed_by() { match first_result.tref.type_().passed_by() {
witx::TypePassedBy::Value(atom) => names.atom_type(atom), witx::TypePassedBy::Value(atom) => names.atom_type(atom),
_ => unreachable!("first result should always be passed by value"), _ => unreachable!("first result should always be passed by value"),
@@ -51,5 +52,5 @@ pub fn define_func(names: &Names, func: &witx::InterfaceFunc) -> TokenStream {
quote!(()) quote!(())
}; };
quote!(pub fn #ident(#args) -> #ret { unimplemented!() }) quote!(pub fn #ident(#abi_args) -> #abi_ret { unimplemented!() })
} }

View File

@@ -1,5 +1,6 @@
extern crate proc_macro; extern crate proc_macro;
mod errors;
mod funcs; mod funcs;
mod names; mod names;
mod parse; mod parse;
@@ -9,6 +10,7 @@ use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2; use proc_macro2::TokenStream as TokenStream2;
use quote::quote; use quote::quote;
use errors::define_error_trait;
use funcs::define_func; use funcs::define_func;
use names::Names; use names::Names;
use types::define_datatype; use types::define_datatype;
@@ -22,29 +24,28 @@ pub fn from_witx(args: TokenStream) -> TokenStream {
let doc = witx::load(&witx_paths).expect("loading witx"); let doc = witx::load(&witx_paths).expect("loading witx");
let mut types = TokenStream2::new(); let types = doc.typenames().map(|t| define_datatype(&names, &t));
for namedtype in doc.typenames() {
let def = define_datatype(&names, &namedtype);
types.extend(def);
}
let mut modules = TokenStream2::new(); let modules = doc.modules().map(|module| {
for module in doc.modules() {
let modname = names.module(&module.name); let modname = names.module(&module.name);
let fs = module.funcs().map(|f| define_func(&names, &f));
let mut fs = TokenStream2::new(); quote!(
for func in module.funcs() {
fs.extend(define_func(&names, &func));
}
modules.extend(quote!(
mod #modname { mod #modname {
use super::*; use super::*;
use super::types::*; use super::types::*;
use memory::*; use memory::*;
#fs #(#fs)*
} }
)); )
} });
TokenStream::from(quote!(mod types { #types } #modules)) let error_trait = define_error_trait(&names, &doc);
TokenStream::from(quote!(
mod types {
#(#types)*
#error_trait
}
#(#modules)*
))
} }

View File

@@ -12,8 +12,9 @@ impl Names {
pub fn new() -> Names { pub fn new() -> Names {
Names {} Names {}
} }
pub fn type_(&self, id: &Id) -> Ident { pub fn type_(&self, id: &Id) -> TokenStream {
format_ident!("{}", id.as_str().to_camel_case()) let ident = format_ident!("{}", id.as_str().to_camel_case());
quote!(#ident)
} }
pub fn builtin_type(&self, b: BuiltinType) -> TokenStream { pub fn builtin_type(&self, b: BuiltinType) -> TokenStream {
match b { match b {

View File

@@ -31,71 +31,68 @@ fn define_alias(names: &Names, name: &witx::Id, to: &witx::NamedType) -> TokenSt
fn define_enum(names: &Names, name: &witx::Id, e: &witx::EnumDatatype) -> TokenStream { fn define_enum(names: &Names, name: &witx::Id, e: &witx::EnumDatatype) -> TokenStream {
let ident = names.type_(&name); let ident = names.type_(&name);
let mut output = TokenStream::new();
let repr = int_repr_tokens(e.repr); let repr = int_repr_tokens(e.repr);
output.extend(quote!(#[repr(#repr)]));
output.extend(quote!(#[derive(Copy, Clone, Debug, std::hash::Hash, Eq, PartialEq)]));
let mut variants = TokenStream::new(); let variant_names = e.variants.iter().map(|v| names.enum_variant(&v.name));
let mut to_repr_cases = TokenStream::new(); let tryfrom_repr_cases = e.variants.iter().enumerate().map(|(n, v)| {
let mut tryfrom_repr_cases = TokenStream::new(); let variant_name = names.enum_variant(&v.name);
for (n, variant) in e.variants.iter().enumerate() {
let n = n as u32; let n = n as u32;
let variant_name = names.enum_variant(&variant.name); quote!(#n => Ok(#ident::#variant_name))
variants.extend(quote!(#variant_name,)); });
tryfrom_repr_cases.extend(quote!(#n => Ok(#ident::#variant_name),)); let to_repr_cases = e.variants.iter().enumerate().map(|(n, v)| {
to_repr_cases.extend(quote!(#ident::#variant_name => #n,)); let variant_name = names.enum_variant(&v.name);
} let n = n as u32;
quote!(#ident::#variant_name => #n)
});
tryfrom_repr_cases quote! {
.extend(quote!(_ => Err(::memory::GuestValueError::InvalidEnum(stringify!(#ident))))); #[repr(#repr)]
#[derive(Copy, Clone, Debug, ::std::hash::Hash, Eq, PartialEq)]
pub enum #ident {
#(#variant_names),*
}
output.extend(quote!(pub enum #ident { impl ::std::convert::TryFrom<#repr> for #ident {
#variants type Error = ::memory::GuestValueError;
} fn try_from(value: #repr) -> Result<#ident, ::memory::GuestValueError> {
match value {
#(#tryfrom_repr_cases),*,
_ => Err(::memory::GuestValueError::InvalidEnum(stringify!(#ident))),
}
}
}
impl ::std::convert::TryFrom<#repr> for #ident { impl From<#ident> for #repr {
type Error = ::memory::GuestValueError; fn from(e: #ident) -> #repr {
fn try_from(value: #repr) -> Result<#ident, ::memory::GuestValueError> { match e {
match value { #(#to_repr_cases),*
#tryfrom_repr_cases }
}
}
impl ::memory::GuestType for #ident {
fn size() -> u32 {
::std::mem::size_of::<#repr>() as u32
}
fn name() -> &'static str {
stringify!(#ident)
}
}
impl ::memory::GuestTypeCopy for #ident {
fn read_val(src: ::memory::GuestPtr<#ident>) -> Result<#ident, ::memory::GuestValueError> {
use ::std::convert::TryInto;
let val = unsafe { ::std::ptr::read_unaligned(src.ptr() as *const #repr) };
val.try_into()
}
fn write_val(val: #ident, dest: ::memory::GuestPtrMut<#ident>) {
let val: #repr = val.into();
unsafe {
::std::ptr::write_unaligned(dest.ptr_mut() as *mut #repr, val)
};
} }
} }
} }
impl From<#ident> for #repr {
fn from(e: #ident) -> #repr {
match e {
#to_repr_cases
}
}
}
impl ::memory::GuestType for #ident {
fn size() -> u32 {
::std::mem::size_of::<#repr>() as u32
}
fn name() -> &'static str {
stringify!(#ident)
}
}
impl ::memory::GuestTypeCopy for #ident {
fn read_val(src: ::memory::GuestPtr<#ident>) -> Result<#ident, ::memory::GuestValueError> {
use ::std::convert::TryInto;
let val = unsafe { ::std::ptr::read_unaligned(src.ptr() as *const #repr) };
val.try_into()
}
fn write_val(val: #ident, dest: ::memory::GuestPtrMut<#ident>) {
let val: #repr = val.into();
unsafe {
::std::ptr::write_unaligned(dest.ptr_mut() as *mut #repr, val)
};
}
}
));
output
} }
fn define_builtin(names: &Names, name: &witx::Id, builtin: witx::BuiltinType) -> TokenStream { fn define_builtin(names: &Names, name: &witx::Id, builtin: witx::BuiltinType) -> TokenStream {

View File

@@ -4,5 +4,5 @@ mod memory;
mod region; mod region;
pub use guest_type::{GuestType, GuestTypeClone, GuestTypeCopy, GuestValueError}; pub use guest_type::{GuestType, GuestTypeClone, GuestTypeCopy, GuestValueError};
pub use memory::{GuestMemory, GuestPtr, GuestPtrMut}; pub use memory::{GuestMemory, GuestPtr, GuestPtrMut, MemoryError};
pub use region::Region; pub use region::Region;

View File

@@ -1,6 +1,21 @@
pub mod test { pub mod test {
pub struct WasiCtx {} // FIXME: parameterize macro on what ctx type is used here
generate::from_witx!("test.witx"); generate::from_witx!("test.witx");
pub struct WasiCtx {} // FIXME: parameterize macro on what ctx type is used here
impl types::WitxErrorConversion for WasiCtx {
fn success_to_errno(&mut self) -> types::Errno {
types::Errno::Ok
}
fn memory_error_to_errno(&mut self, e: ::memory::MemoryError) -> types::Errno {
eprintln!("memory error: {:?}", e);
types::Errno::InvalidArg
}
fn value_error_to_errno(&mut self, e: ::memory::GuestValueError) -> types::Errno {
eprintln!("guest value error: {:?}", e);
types::Errno::InvalidArg
}
}
} }
/* /*
pub mod wasi { pub mod wasi {

View File

@@ -2,6 +2,8 @@
(typename $errno (typename $errno
(enum u32 (enum u32
$ok
$invalid_arg
$dont_want_to $dont_want_to
$physically_unable $physically_unable
$picket_line)) $picket_line))