wasmtime_rust_macro/
lib.rs

1extern crate proc_macro;
2
3use proc_macro2::TokenStream;
4use quote::quote;
5use syn::spanned::Spanned;
6
7#[proc_macro_attribute]
8pub fn wasmtime(
9    _attr: proc_macro::TokenStream,
10    item: proc_macro::TokenStream,
11) -> proc_macro::TokenStream {
12    let item = syn::parse_macro_input!(item as syn::ItemTrait);
13    expand(item).unwrap_or_else(|e| e.to_compile_error()).into()
14}
15
16fn expand(item: syn::ItemTrait) -> syn::Result<TokenStream> {
17    let definition = generate_struct(&item)?;
18    let load = generate_load(&item)?;
19    let methods = generate_methods(&item)?;
20    let name = &item.ident;
21
22    Ok(quote! {
23        #definition
24        impl #name {
25            #load
26            #methods
27        }
28    })
29}
30
31fn generate_struct(item: &syn::ItemTrait) -> syn::Result<TokenStream> {
32    let vis = &item.vis;
33    let name = &item.ident;
34    let root = root();
35    Ok(quote! {
36        #vis struct #name {
37            instance: #root::wasmtime::Instance,
38            data: #root::wasmtime_interface_types::ModuleData,
39        }
40    })
41}
42
43fn generate_load(item: &syn::ItemTrait) -> syn::Result<TokenStream> {
44    let vis = &item.vis;
45    let name = &item.ident;
46    let root = root();
47    Ok(quote! {
48        #vis fn load_file(path: impl AsRef<std::path::Path>) -> #root::anyhow::Result<#name> {
49            Self::load_bytes(std::fs::read(path)?)
50        }
51
52        #vis fn load_bytes(bytes: impl AsRef<[u8]>) -> #root::anyhow::Result<#name> {
53            use #root::wasmtime::{Config, Extern, Engine, Store, Instance, Module};
54            use #root::anyhow::{bail, format_err};
55
56            let store = Store::default();
57
58            let data = #root::wasmtime_interface_types::ModuleData::new(bytes.as_ref())?;
59
60            let module = Module::new(&engine, bytes.as_ref())?;
61
62            let mut imports: Vec<Extern> = Vec::new();
63            if let Some(module_name) = data.find_wasi_module_name() {
64                let wasi_cx = #root::wasmtime_wasi::WasiCtxBuilder::new().build();
65                let wasi = #root::wasmtime_wasi::Wasi::new(&store, wasi_cx);
66                for i in module.imports().iter() {
67                    if i.module() != module_name {
68                        bail!("unknown import module {}", i.module());
69                    }
70                    if let Some(export) = wasi.get_export(i.name()) {
71                        imports.push(export.clone().into());
72                    } else {
73                        bail!("unknown import {}:{}", i.module(), i.name())
74                    }
75                }
76            }
77            let instance =
78                Instance::new(&store, &module, &imports).map_err(|t| format_err!("instantiation trap: {:?}", t))?;
79
80            Ok(#name { instance, data })
81        }
82    })
83}
84
85fn generate_methods(item: &syn::ItemTrait) -> syn::Result<TokenStream> {
86    macro_rules! bail {
87        ($e:expr, $($fmt:tt)*) => (
88            return Err(syn::Error::new($e.span(), format!($($fmt)*)));
89        )
90    }
91    let mut result = TokenStream::new();
92    let root = root();
93    let vis = &item.vis;
94
95    for item in item.items.iter() {
96        let method = match item {
97            syn::TraitItem::Method(f) => f,
98            other => bail!(other, "only methods are allowed"),
99        };
100        if let Some(e) = &method.default {
101            bail!(e, "cannot specify an implementation of methods");
102        }
103        if let Some(t) = &method.sig.constness {
104            bail!(t, "cannot be `const`");
105        }
106        if let Some(t) = &method.sig.asyncness {
107            bail!(t, "cannot be `async`");
108        }
109        match &method.sig.inputs.first() {
110            Some(syn::FnArg::Receiver(_)) => {}
111            Some(t) => bail!(t, "first arugment needs to be \"self\""),
112            None => bail!(
113                method.sig,
114                "trait method requires at least one argument which needs to be \"self\""
115            ),
116        }
117
118        let mut args = Vec::new();
119        for arg in method.sig.inputs.iter() {
120            let arg = match arg {
121                syn::FnArg::Receiver(_) => continue,
122                syn::FnArg::Typed(arg) => arg,
123            };
124            let ident = match &*arg.pat {
125                syn::Pat::Ident(i) => i,
126                other => bail!(other, "must use bare idents for arguments"),
127            };
128            if let Some(t) = &ident.by_ref {
129                bail!(t, "arguments cannot bind by reference");
130            }
131            if let Some(t) = &ident.mutability {
132                bail!(t, "arguments cannot be mutable");
133            }
134            if let Some((_, t)) = &ident.subpat {
135                bail!(t, "arguments cannot have sub-bindings");
136            }
137            let ident = &ident.ident;
138            args.push(quote! {
139                #root::wasmtime_interface_types::Value::from(#ident)
140            });
141        }
142
143        let convert_ret = match &method.sig.output {
144            syn::ReturnType::Default => {
145                quote! {
146                    <() as #root::FromVecValue>::from(results)
147                }
148            }
149            syn::ReturnType::Type(_, ty) => match &**ty {
150                syn::Type::Tuple(..) => {
151                    quote! { <#ty as #root::FromVecValue>::from(results) }
152                }
153                _ => {
154                    quote! { <(#ty,) as #root::FromVecValue>::from(results).map(|t| t.0) }
155                }
156            },
157        };
158
159        let sig = &method.sig;
160        let attrs = &method.attrs;
161        let name = &method.sig.ident;
162
163        result.extend(quote! {
164            #(#attrs)*
165            #vis #sig {
166                let args = [
167                    #(#args),*
168                ];
169                let results = self.data.invoke_export(
170                    &self.instance,
171                    stringify!(#name),
172                    &args,
173                ).expect("wasm execution failed");
174                #convert_ret.expect("failed to convert return type")
175            }
176        });
177    }
178
179    Ok(result)
180}
181
182fn root() -> TokenStream {
183    quote! { wasmtime_rust::__rt }
184}