lib.rs

  1use core::panic;
  2
  3use proc_macro::TokenStream;
  4use quote::{format_ident, quote};
  5use syn::{parse_macro_input, Block, FnArg, ForeignItemFn, Ident, ItemFn, Pat, Type, Visibility};
  6
  7/// Attribute macro to be used guest-side within a plugin.
  8/// ```ignore
  9/// #[export]
 10/// pub fn say_hello() -> String {
 11///     "Hello from Wasm".into()
 12/// }
 13/// ```
 14/// This macro makes a function defined guest-side avaliable host-side.
 15/// Note that all arguments and return types must be `serde`.
 16#[proc_macro_attribute]
 17pub fn export(args: TokenStream, function: TokenStream) -> TokenStream {
 18    if !args.is_empty() {
 19        panic!("The export attribute does not take any arguments");
 20    }
 21
 22    let inner_fn = parse_macro_input!(function as ItemFn);
 23
 24    if !inner_fn.sig.generics.params.is_empty() {
 25        panic!("Exported functions can not take generic parameters");
 26    }
 27
 28    if let Visibility::Public(_) = inner_fn.vis {
 29    } else {
 30        panic!("The export attribute only works for public functions");
 31    }
 32
 33    let inner_fn_name = format_ident!("{}", inner_fn.sig.ident);
 34    let outer_fn_name = format_ident!("__{}", inner_fn_name);
 35
 36    let variadic = inner_fn.sig.inputs.len();
 37    let i = (0..variadic).map(syn::Index::from);
 38    let t: Vec<Type> = inner_fn
 39        .sig
 40        .inputs
 41        .iter()
 42        .map(|x| match x {
 43            FnArg::Receiver(_) => {
 44                panic!("All arguments must have specified types, no `self` allowed")
 45            }
 46            FnArg::Typed(item) => *item.ty.clone(),
 47        })
 48        .collect();
 49
 50    // this is cursed...
 51    let (args, ty) = if variadic != 1 {
 52        (
 53            quote! {
 54                #( data.#i ),*
 55            },
 56            quote! {
 57                ( #( #t ),* )
 58            },
 59        )
 60    } else {
 61        let ty = &t[0];
 62        (quote! { data }, quote! { #ty })
 63    };
 64
 65    TokenStream::from(quote! {
 66        #[no_mangle]
 67        #inner_fn
 68
 69        #[no_mangle]
 70        // TODO: switch len from usize to u32?
 71        pub extern "C" fn #outer_fn_name(packed_buffer: u64) -> u64 {
 72            // setup
 73            let data = unsafe { ::plugin::__Buffer::from_u64(packed_buffer).to_vec() };
 74
 75            // operation
 76            let data: #ty = match ::plugin::bincode::deserialize(&data) {
 77                Ok(d) => d,
 78                Err(e) => panic!("Data passed to function not deserializable."),
 79            };
 80            let result = #inner_fn_name(#args);
 81            let new_data: Result<Vec<u8>, _> = ::plugin::bincode::serialize(&result);
 82            let new_data = new_data.unwrap();
 83
 84            // teardown
 85            let new_buffer = unsafe { ::plugin::__Buffer::from_vec(new_data) }.into_u64();
 86            return new_buffer;
 87        }
 88    })
 89}
 90
 91/// Attribute macro to be used guest-side within a plugin.
 92/// ```ignore
 93/// #[import]
 94/// pub fn operating_system_name() -> String;
 95/// ```
 96/// This macro makes a function defined host-side avaliable guest-side.
 97/// Note that all arguments and return types must be `serde`.
 98/// All that's provided is a signature, as the function is implemented host-side.
 99#[proc_macro_attribute]
100pub fn import(args: TokenStream, function: TokenStream) -> TokenStream {
101    if !args.is_empty() {
102        panic!("The import attribute does not take any arguments");
103    }
104
105    let fn_declare = parse_macro_input!(function as ForeignItemFn);
106
107    if !fn_declare.sig.generics.params.is_empty() {
108        panic!("Exported functions can not take generic parameters");
109    }
110
111    // let inner_fn_name = format_ident!("{}", fn_declare.sig.ident);
112    let extern_fn_name = format_ident!("__{}", fn_declare.sig.ident);
113
114    let (args, tys): (Vec<Ident>, Vec<Type>) = fn_declare
115        .sig
116        .inputs
117        .clone()
118        .into_iter()
119        .map(|x| match x {
120            FnArg::Receiver(_) => {
121                panic!("All arguments must have specified types, no `self` allowed")
122            }
123            FnArg::Typed(t) => {
124                if let Pat::Ident(i) = *t.pat {
125                    (i.ident, *t.ty)
126                } else {
127                    panic!("All function arguments must be identifiers");
128                }
129            }
130        })
131        .unzip();
132
133    let body = TokenStream::from(quote! {
134        {
135            // setup
136            let data: (#( #tys ),*) = (#( #args ),*);
137            let data = ::plugin::bincode::serialize(&data).unwrap();
138            let buffer = unsafe { ::plugin::__Buffer::from_vec(data) };
139
140            // operation
141            let new_buffer = unsafe { #extern_fn_name(buffer.into_u64()) };
142            let new_data = unsafe { ::plugin::__Buffer::from_u64(new_buffer).to_vec() };
143
144            // teardown
145            match ::plugin::bincode::deserialize(&new_data) {
146                Ok(d) => d,
147                Err(e) => panic!("Data returned from function not deserializable."),
148            }
149        }
150    });
151
152    let block = parse_macro_input!(body as Block);
153
154    let inner_fn = ItemFn {
155        attrs: fn_declare.attrs,
156        vis: fn_declare.vis,
157        sig: fn_declare.sig,
158        block: Box::new(block),
159    };
160
161    TokenStream::from(quote! {
162        extern "C" {
163            fn #extern_fn_name(buffer: u64) -> u64;
164        }
165
166        #[no_mangle]
167        #inner_fn
168    })
169}