wasmtime_rust_macro/
lib.rs1extern 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}