diff --git a/Cargo.toml b/Cargo.toml index 8681c3d..2d96aba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,8 +24,12 @@ byteorder = { version = "1.0", default-features = false } hashmap_core = { version = "0.1.9", optional = true } memory_units = "0.3.0" libm = { version = "0.1.2", optional = true } +wasmi-derive = { version = "0.1", path = "derive" } [dev-dependencies] assert_matches = "1.1" rand = "0.4.2" wabt = "0.6" + +[workspace] +members = ["derive"] diff --git a/derive/Cargo.toml b/derive/Cargo.toml new file mode 100644 index 0000000..a8f7f91 --- /dev/null +++ b/derive/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "wasmi-derive" +version = "0.1.0" +authors = ["Sergey Pepyakin "] +edition = "2018" + +[lib] +proc-macro = true + +[dependencies] +quote = "0.6" +syn = { version = "0.15.0", features = ['full'] } +proc-macro2 = "0.4.9" diff --git a/derive/src/codegen.rs b/derive/src/codegen.rs new file mode 100644 index 0000000..59fb7cd --- /dev/null +++ b/derive/src/codegen.rs @@ -0,0 +1,124 @@ +use crate::model::{ExtDefinition, ExternalFunc}; +use proc_macro2::{Ident, Literal, Span, TokenStream}; +use quote::{quote, quote_spanned, ToTokens}; + + +pub fn codegen(ext_def: &ExtDefinition, to: &mut TokenStream) { + let mut externals = TokenStream::new(); + let mut module_resolver = TokenStream::new(); + + // TODO: Come up with a name. + let mut new_name = "_WASMI_IMPLS_".to_string(); + new_name.push_str("NAME".to_string().trim_start_matches("r#")); + let dummy_const = Ident::new(&new_name, Span::call_site()); + + derive_externals(ext_def, &mut externals); + derive_module_resolver(ext_def, &mut module_resolver); + + (quote! { + const #dummy_const: () = { + extern crate wasmi as _wasmi; + + use _wasmi::{ + Trap, RuntimeValue, RuntimeArgs, Externals, + derive_support::WasmResult, + }; + + #externals + #module_resolver + }; + }).to_tokens(to); +} + +fn gen_dispatch_func_arm(func: &ExternalFunc) -> TokenStream { + let index = func.index as usize; + let name = Ident::new(&func.name, Span::call_site()); + let return_ty_span = func.return_ty.clone().unwrap_or_else(|| Span::call_site()); + + let mut args = vec![]; + let mut unmarshall_args = TokenStream::new(); + for (i, arg_span) in func.args.iter().cloned().enumerate() { + let mut arg_name = "arg".to_string(); + arg_name.push_str(&i.to_string()); + let arg_name = Ident::new(&arg_name, arg_span.clone()); + + (quote_spanned! {arg_span=> + let #arg_name = + args.next() + .and_then(|rt_val| rt_val.try_into()) + .unwrap(); + }).to_tokens(&mut unmarshall_args); + + args.push(quote_spanned! {arg_span=> #arg_name }); + } + + let prologue = quote! { + let mut args = args.as_ref().iter(); + #unmarshall_args + }; + let epilogue = quote_spanned! {return_ty_span=> + WasmResult::to_wasm_result(r) + }; + + (quote! { + #index => { + #prologue + let r = self.#name( #(#args),* ); + #epilogue + } + }) + + // let body = $crate::wasm_utils::constrain_closure::< + // <$returns as $crate::wasm_utils::ConvertibleToWasm>::NativeType, _ + // >(|| { + // unmarshall_args!($body, $objectname, $args_iter, $( $names : $params ),*) + // }); + // let r = body()?; + // return Ok(Some({ use $crate::wasm_utils::ConvertibleToWasm; r.to_runtime_value() })) +} + +fn derive_externals(ext_def: &ExtDefinition, to: &mut TokenStream) { + let (impl_generics, ty_generics, where_clause) = ext_def.generics.split_for_impl(); + let ty = &ext_def.ty; + + let mut match_arms = vec![]; + for func in &ext_def.funcs { + match_arms.push(gen_dispatch_func_arm(func)); + } + + (quote::quote! { + impl #impl_generics Externals for #ty #where_clause { + fn invoke_index( + &mut self, + index: usize, + args: RuntimeArgs, + ) -> Result, Trap> { + match index { + #(#match_arms),* + _ => panic!("fn with index {} is undefined", index), + } + } + + // ... + } + }).to_tokens(to); +} + +fn derive_module_resolver(ext_def: &ExtDefinition, to: &mut TokenStream) { + (quote::quote! { + impl #impl_generics ModuleImportResolver for #ty #where_clause { + fn invoke_index( + &mut self, + index: usize, + args: RuntimeArgs, + ) -> Result, Trap> { + match index { + #(#match_arms),* + _ => panic!("fn with index {} is undefined", index), + } + } + + // ... + } + }).to_tokens(to); +} diff --git a/derive/src/lib.rs b/derive/src/lib.rs new file mode 100644 index 0000000..eca13e2 --- /dev/null +++ b/derive/src/lib.rs @@ -0,0 +1,34 @@ +extern crate proc_macro; + +mod model; +mod parser; +mod codegen; + +use proc_macro::TokenStream; + +#[proc_macro_attribute] +pub fn derive_externals(attr: TokenStream, input: TokenStream) -> TokenStream { + let mut input: proc_macro2::TokenStream = input.into(); + + let ext_def = parser::parse(input.clone()).unwrap(); + codegen::codegen(&ext_def, &mut input); + + // We need to generate two types: + // - Externals + // - ModuleImportResolver + + // - for each of declared method collect it's name and it's signature. + // - assign a method index for each method + // - generate a switch for `Externals` that takes the input `index` and jumps + // on the corresponding match arm, which the wrapper. + // The wrapper decodes arguments, calls to the function and handles the result. + // - generate a switch / ifs chain for `ModuleImportResolver`. In each arm it checks if the function + // has an appropriate arguments, and if so allocates a host function with the corresponding index. + // + // and we will then need to return both the original implementation and the generated implementation + // of externals. + + // println!("{:?}", quote::quote! { #input }.to_string()); + let input = input.into(); + input +} diff --git a/derive/src/model.rs b/derive/src/model.rs new file mode 100644 index 0000000..acd2481 --- /dev/null +++ b/derive/src/model.rs @@ -0,0 +1,39 @@ +pub enum ValueType { + I32, + I64, + F32, + F64, +} + +pub struct Signature { + pub params: Vec, + pub return_ty: Option, +} + +pub struct Param { + span: proc_macro2::Span, + generated_name: String, +} + +pub struct ExternalFunc { + /// Assigned index of this function. + pub index: u32, + pub name: String, + // TODO: Rename args to params + pub args: Vec, + pub return_ty: Option, + // TODO: remove + pub arity: usize, +} + +/// The core structure that contains the list of all functions +/// and the data required for implementing a trait. +pub struct ExtDefinition { + /// List of all external functions. + pub funcs: Vec, + /// The generics required to implement a trait for this type. + pub generics: syn::Generics, + /// The type declaration to implement to implement a trait, most typically + /// represented by a structure. + pub ty: Box, +} diff --git a/derive/src/parser.rs b/derive/src/parser.rs new file mode 100644 index 0000000..a38d17e --- /dev/null +++ b/derive/src/parser.rs @@ -0,0 +1,46 @@ +use crate::model::{self, ExtDefinition, ExternalFunc}; +use syn::{ItemImpl, ImplItem, ImplItemMethod, FnArg, ReturnType}; +use syn::spanned::Spanned; + +/// Parse an incoming stream of tokens into a list of external functions. +pub fn parse(input: proc_macro2::TokenStream) -> Result { + let item_impl = syn::parse2::(input).map_err(|_| ())?; + + let mut funcs = vec![]; + + for item in item_impl.items { + match item { + ImplItem::Method(ImplItemMethod { + sig, + .. + }) => { + let index = funcs.len() as u32; + + // self TODO: handle this properly + let args = sig.decl.inputs.iter().skip(1).enumerate().map(|input| { + input.span() + }).collect::>(); + + let return_ty = match sig.decl.output { + ReturnType::Default => None, + ReturnType::Type(_, ty) => Some(ty.span()), + }; + + funcs.push(ExternalFunc { + index, + name: sig.ident.to_string(), + args, + return_ty, + arity: sig.decl.inputs.len() - 1, // self TODO: handle this properly + }); + }, + _ => {}, + } + } + + Ok(ExtDefinition { + funcs, + generics: item_impl.generics.clone(), + ty: item_impl.self_ty.clone(), + }) +} diff --git a/src/derive_support.rs b/src/derive_support.rs new file mode 100644 index 0000000..830fbe1 --- /dev/null +++ b/src/derive_support.rs @@ -0,0 +1,48 @@ +use {ValueType, RuntimeValue, Trap}; + +pub trait ConvertibleToWasm { + const VALUE_TYPE: ValueType; + type NativeType; + fn to_runtime_value(self) -> RuntimeValue; +} + +impl ConvertibleToWasm for i32 { type NativeType = i32; const VALUE_TYPE: ValueType = ValueType::I32; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I32(self) } } +impl ConvertibleToWasm for u32 { type NativeType = u32; const VALUE_TYPE: ValueType = ValueType::I32; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I32(self as i32) } } +impl ConvertibleToWasm for i64 { type NativeType = i64; const VALUE_TYPE: ValueType = ValueType::I64; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I64(self) } } +impl ConvertibleToWasm for u64 { type NativeType = u64; const VALUE_TYPE: ValueType = ValueType::I64; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I64(self as i64) } } +impl ConvertibleToWasm for isize { type NativeType = i32; const VALUE_TYPE: ValueType = ValueType::I32; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I32(self as i32) } } +impl ConvertibleToWasm for usize { type NativeType = u32; const VALUE_TYPE: ValueType = ValueType::I32; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I32(self as u32 as i32) } } +impl ConvertibleToWasm for *const T { type NativeType = u32; const VALUE_TYPE: ValueType = ValueType::I32; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I32(self as isize as i32) } } +impl ConvertibleToWasm for *mut T { type NativeType = u32; const VALUE_TYPE: ValueType = ValueType::I32; fn to_runtime_value(self) -> RuntimeValue { RuntimeValue::I32(self as isize as i32) } } + +pub trait WasmResult { + fn to_wasm_result(self) -> Result, Trap>; +} + +impl WasmResult for () { + fn to_wasm_result(self) -> Result, Trap> { + Ok(None) + } +} + +impl> WasmResult for Result { + fn to_wasm_result(self) -> Result, Trap> { + self + .map(|v| Some(v.to_runtime_value())) + .map_err(Into::into) + } +} + +impl> WasmResult for Result<(), E> { + fn to_wasm_result(self) -> Result, Trap> { + self + .map(|_| None) + .map_err(Into::into) + } +} + +impl WasmResult for R { + fn to_wasm_result(self) -> Result, Trap> { + Ok(Some(self.to_runtime_value())) + } +} diff --git a/src/lib.rs b/src/lib.rs index f92c4d3..6aeaf83 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -396,6 +396,9 @@ mod types; mod validation; mod value; +// TODO: feature +pub mod derive_support; + #[cfg(test)] mod tests; diff --git a/tests/derives.rs b/tests/derives.rs new file mode 100644 index 0000000..82fb150 --- /dev/null +++ b/tests/derives.rs @@ -0,0 +1,40 @@ +extern crate wasmi_derive; +extern crate wasmi; + +use wasmi_derive::derive_externals; +use wasmi::HostError; +use std::fmt; + +#[derive(Debug)] +struct NoInfoError; +impl HostError for NoInfoError {} +impl fmt::Display for NoInfoError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "NoInfoError") + } +} + +struct NonStaticExternals<'a> { + state: &'a mut usize, +} + +#[derive_externals] +impl<'a> NonStaticExternals<'a> { + pub fn hello(&self, a: u32, b: u32) -> u32 { + a + b + } + + pub fn increment(&mut self) { + *self.state += 1; + } + + pub fn traps(&self) -> Result<(), NoInfoError> { + Err(NoInfoError) + } + + pub fn fart(&self, inbound_fart: Fart) -> Result { + Ok(inbound_fart) + } +} + +pub struct Fart;