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 available 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        pub extern "C" fn #outer_fn_name(packed_buffer: u64) -> u64 {
 71            // setup
 72            let data = unsafe { ::plugin::__Buffer::from_u64(packed_buffer).to_vec() };
 73
 74            // operation
 75            let data: #ty = match ::plugin::bincode::deserialize(&data) {
 76                Ok(d) => d,
 77                Err(e) => panic!("Data passed to function not deserializable."),
 78            };
 79            let result = #inner_fn_name(#args);
 80            let new_data: Result<Vec<u8>, _> = ::plugin::bincode::serialize(&result);
 81            let new_data = new_data.unwrap();
 82
 83            // teardown
 84            let new_buffer = unsafe { ::plugin::__Buffer::from_vec(new_data) }.into_u64();
 85            return new_buffer;
 86        }
 87    })
 88}
 89
 90/// Attribute macro to be used guest-side within a plugin.
 91/// ```ignore
 92/// #[import]
 93/// pub fn operating_system_name() -> String;
 94/// ```
 95/// This macro makes a function defined host-side available guest-side.
 96/// Note that all arguments and return types must be `serde`.
 97/// All that's provided is a signature, as the function is implemented host-side.
 98#[proc_macro_attribute]
 99pub fn import(args: TokenStream, function: TokenStream) -> TokenStream {
100    if !args.is_empty() {
101        panic!("The import attribute does not take any arguments");
102    }
103
104    let fn_declare = parse_macro_input!(function as ForeignItemFn);
105
106    if !fn_declare.sig.generics.params.is_empty() {
107        panic!("Exported functions can not take generic parameters");
108    }
109
110    // let inner_fn_name = format_ident!("{}", fn_declare.sig.ident);
111    let extern_fn_name = format_ident!("__{}", fn_declare.sig.ident);
112
113    let (args, tys): (Vec<Ident>, Vec<Type>) = fn_declare
114        .sig
115        .inputs
116        .clone()
117        .into_iter()
118        .map(|x| match x {
119            FnArg::Receiver(_) => {
120                panic!("All arguments must have specified types, no `self` allowed")
121            }
122            FnArg::Typed(t) => {
123                if let Pat::Ident(i) = *t.pat {
124                    (i.ident, *t.ty)
125                } else {
126                    panic!("All function arguments must be identifiers");
127                }
128            }
129        })
130        .unzip();
131
132    let body = TokenStream::from(quote! {
133        {
134            // setup
135            let data: (#( #tys ),*) = (#( #args ),*);
136            let data = ::plugin::bincode::serialize(&data).unwrap();
137            let buffer = unsafe { ::plugin::__Buffer::from_vec(data) };
138
139            // operation
140            let new_buffer = unsafe { #extern_fn_name(buffer.into_u64()) };
141            let new_data = unsafe { ::plugin::__Buffer::from_u64(new_buffer).to_vec() };
142
143            // teardown
144            match ::plugin::bincode::deserialize(&new_data) {
145                Ok(d) => d,
146                Err(e) => panic!("Data returned from function not deserializable."),
147            }
148        }
149    });
150
151    let block = parse_macro_input!(body as Block);
152
153    let inner_fn = ItemFn {
154        attrs: fn_declare.attrs,
155        vis: fn_declare.vis,
156        sig: fn_declare.sig,
157        block: Box::new(block),
158    };
159
160    TokenStream::from(quote! {
161        extern "C" {
162            fn #extern_fn_name(buffer: u64) -> u64;
163        }
164
165        #[no_mangle]
166        #inner_fn
167    })
168}