diff --git a/crates/generate/src/errors.rs b/crates/generate/src/errors.rs new file mode 100644 index 0000000000..d1b4bbfbc3 --- /dev/null +++ b/crates/generate/src/errors.rs @@ -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::>() + }) + .collect::>(); + + 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)* + } + ) +} diff --git a/crates/generate/src/funcs.rs b/crates/generate/src/funcs.rs index 3f856b863a..bfe720f2d9 100644 --- a/crates/generate/src/funcs.rs +++ b/crates/generate/src/funcs.rs @@ -3,44 +3,45 @@ use quote::quote; 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. -// 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 { let ident = names.func(&func.name); - let mut args = quote!(wasi_ctx: &mut WasiCtx, memory: GuestMemory,); let arg_signature = |param: &witx::InterfaceFuncParam| -> TokenStream { let name = names.func_param(¶m.name); match param.tref.type_().passed_by() { witx::TypePassedBy::Value(atom) => { let atom = names.atom_type(atom); - quote!(#name: #atom,) + quote!(#name: #atom) } witx::TypePassedBy::Pointer => { let atom = names.atom_type(witx::AtomType::I32); - quote!(#name: #atom,) + quote!(#name: #atom) } witx::TypePassedBy::PointerLengthPair => { let atom = names.atom_type(witx::AtomType::I32); let len_name = names.func_len_param(¶m.name); - quote!(#name: #atom, #len_name, #atom,) + quote!(#name: #atom, #len_name: #atom) } } }; - for param in func.params.iter() { - args.extend(arg_signature(param)); - } - - if let Some(arg_results) = func.results.get(1..) { - for result in arg_results { - args.extend(arg_signature(result)) - } - } - - let ret = if let Some(first_result) = func.results.get(0) { + let params = func + .params + .iter() + .chain(func.results.iter().skip(1)) + .map(arg_signature); + let abi_args = quote!( + wasi_ctx: &mut WasiCtx, memory: GuestMemory, + #(#params),* + ); + let abi_ret = if let Some(first_result) = func.results.get(0) { match first_result.tref.type_().passed_by() { witx::TypePassedBy::Value(atom) => names.atom_type(atom), _ => 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!(pub fn #ident(#args) -> #ret { unimplemented!() }) + quote!(pub fn #ident(#abi_args) -> #abi_ret { unimplemented!() }) } diff --git a/crates/generate/src/lib.rs b/crates/generate/src/lib.rs index acf60e1401..830e92e900 100644 --- a/crates/generate/src/lib.rs +++ b/crates/generate/src/lib.rs @@ -1,5 +1,6 @@ extern crate proc_macro; +mod errors; mod funcs; mod names; mod parse; @@ -9,6 +10,7 @@ use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::quote; +use errors::define_error_trait; use funcs::define_func; use names::Names; 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 mut types = TokenStream2::new(); - for namedtype in doc.typenames() { - let def = define_datatype(&names, &namedtype); - types.extend(def); - } + let types = doc.typenames().map(|t| define_datatype(&names, &t)); - let mut modules = TokenStream2::new(); - for module in doc.modules() { + let modules = doc.modules().map(|module| { let modname = names.module(&module.name); - - let mut fs = TokenStream2::new(); - for func in module.funcs() { - fs.extend(define_func(&names, &func)); - } - modules.extend(quote!( + let fs = module.funcs().map(|f| define_func(&names, &f)); + quote!( mod #modname { use super::*; use super::types::*; 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)* + )) } diff --git a/crates/generate/src/names.rs b/crates/generate/src/names.rs index a674f13cfe..bd296ac88e 100644 --- a/crates/generate/src/names.rs +++ b/crates/generate/src/names.rs @@ -12,8 +12,9 @@ impl Names { pub fn new() -> Names { Names {} } - pub fn type_(&self, id: &Id) -> Ident { - format_ident!("{}", id.as_str().to_camel_case()) + pub fn type_(&self, id: &Id) -> TokenStream { + let ident = format_ident!("{}", id.as_str().to_camel_case()); + quote!(#ident) } pub fn builtin_type(&self, b: BuiltinType) -> TokenStream { match b { diff --git a/crates/generate/src/types.rs b/crates/generate/src/types.rs index e517134c9f..144b11f2e1 100644 --- a/crates/generate/src/types.rs +++ b/crates/generate/src/types.rs @@ -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 { let ident = names.type_(&name); - let mut output = TokenStream::new(); 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 mut to_repr_cases = TokenStream::new(); - let mut tryfrom_repr_cases = TokenStream::new(); - for (n, variant) in e.variants.iter().enumerate() { + 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 variant_name = names.enum_variant(&v.name); let n = n as u32; - let variant_name = names.enum_variant(&variant.name); - variants.extend(quote!(#variant_name,)); - tryfrom_repr_cases.extend(quote!(#n => Ok(#ident::#variant_name),)); - to_repr_cases.extend(quote!(#ident::#variant_name => #n,)); - } + quote!(#n => Ok(#ident::#variant_name)) + }); + let to_repr_cases = e.variants.iter().enumerate().map(|(n, v)| { + let variant_name = names.enum_variant(&v.name); + let n = n as u32; + quote!(#ident::#variant_name => #n) + }); - tryfrom_repr_cases - .extend(quote!(_ => Err(::memory::GuestValueError::InvalidEnum(stringify!(#ident))))); + quote! { + #[repr(#repr)] + #[derive(Copy, Clone, Debug, ::std::hash::Hash, Eq, PartialEq)] + pub enum #ident { + #(#variant_names),* + } - output.extend(quote!(pub enum #ident { - #variants - } + impl ::std::convert::TryFrom<#repr> for #ident { + 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 { - type Error = ::memory::GuestValueError; - fn try_from(value: #repr) -> Result<#ident, ::memory::GuestValueError> { - match value { - #tryfrom_repr_cases + 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) + }; } } } - - 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 { diff --git a/crates/memory/src/lib.rs b/crates/memory/src/lib.rs index 6b8edeb287..efc2317a47 100644 --- a/crates/memory/src/lib.rs +++ b/crates/memory/src/lib.rs @@ -4,5 +4,5 @@ mod memory; mod region; 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; diff --git a/src/lib.rs b/src/lib.rs index 227fdd8126..a146077675 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,21 @@ pub mod test { - pub struct WasiCtx {} // FIXME: parameterize macro on what ctx type is used here 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 { diff --git a/test.witx b/test.witx index 1750ec3d15..940f8594d8 100644 --- a/test.witx +++ b/test.witx @@ -2,6 +2,8 @@ (typename $errno (enum u32 + $ok + $invalid_arg $dont_want_to $physically_unable $picket_line))