lucet_runtime_macros/
lib.rs

1extern crate proc_macro;
2use proc_macro::TokenStream;
3use quote::quote;
4use syn::spanned::Spanned;
5
6/// This attribute generates a Lucet hostcall from a standalone Rust function that takes a `&mut
7/// Vmctx` as its first argument.
8///
9/// It is important to use this attribute for hostcalls, rather than exporting them
10/// directly. Otherwise the behavior of instance termination and timeouts are
11/// undefined. Additionally, the attribute makes the resulting function `unsafe extern "C"`
12/// regardless of how the function is defined, as this ABI is required for all hostcalls.
13///
14/// In most cases, you will want to also provide the `#[no_mangle]` attribute and `pub` visibility
15/// in order for the hostcall to be exported from the final executable.
16///
17/// ```ignore
18/// #[lucet_hostcall]
19/// #[no_mangle]
20/// pub fn yield_5(vmctx: &mut Vmctx) {
21///     vmctx.yield_val(5);
22/// }
23/// ```
24///
25/// Note that `lucet-runtime` must be a dependency of any crate where this attribute is used, and it
26/// may not be renamed (this restriction may be lifted once [this
27/// issue](https://github.com/rust-lang/rust/issues/54363) is resolved).
28#[proc_macro_attribute]
29pub fn lucet_hostcall(_attr: TokenStream, item: TokenStream) -> TokenStream {
30    // determine whether we need to import from `lucet_runtime_internals`; this is useful if we want
31    // to define a hostcall for a target (or tests, more concretely) that doesn't depend on
32    // `lucet-runtime`
33    let in_internals = std::env::var("CARGO_PKG_NAME").unwrap() == "lucet-runtime-internals";
34
35    let mut hostcall = syn::parse_macro_input!(item as syn::ItemFn);
36    let hostcall_ident = hostcall.sig.ident.clone();
37
38    // use the same attributes and visibility as the impl hostcall
39    let attrs = hostcall.attrs.clone();
40    let vis = hostcall.vis.clone();
41
42    // remove #[no_mangle] from the attributes of the impl hostcall if it's there
43    hostcall
44        .attrs
45        .retain(|attr| !attr.path.is_ident("no_mangle"));
46    // make the impl hostcall private
47    hostcall.vis = syn::Visibility::Inherited;
48
49    // modify the type signature of the exported raw hostcall based on the original signature
50    let mut raw_sig = hostcall.sig.clone();
51
52    // hostcalls are always unsafe
53    raw_sig.unsafety = Some(syn::Token![unsafe](raw_sig.span()));
54
55    // hostcalls are always extern "C"
56    raw_sig.abi = Some(syn::parse_quote!(extern "C"));
57
58    let vmctx_mod = if in_internals {
59        quote! { lucet_runtime_internals::vmctx }
60    } else {
61        quote! { lucet_runtime::vmctx }
62    };
63
64    // replace the first argument to the raw hostcall with the vmctx pointer
65    if let Some(arg0) = raw_sig.inputs.iter_mut().nth(0) {
66        let lucet_vmctx: syn::FnArg = syn::parse_quote!(vmctx_raw: *mut #vmctx_mod::lucet_vmctx);
67        *arg0 = lucet_vmctx;
68    }
69
70    // the args after the first to provide to the hostcall impl
71    let impl_args = hostcall
72        .sig
73        .inputs
74        .iter()
75        .skip(1)
76        .map(|arg| match arg {
77            syn::FnArg::Receiver(_) => {
78                // this case is an error, but we produce some valid rust code anyway so that the
79                // compiler can produce a more meaningful error message at a later point
80                let s = syn::Token![self](arg.span());
81                quote!(#s)
82            }
83            syn::FnArg::Typed(syn::PatType { pat, .. }) => quote!(#pat),
84        })
85        .collect::<Vec<_>>();
86
87    let termination_details = if in_internals {
88        quote! { lucet_runtime_internals::instance::TerminationDetails }
89    } else {
90        quote! { lucet_runtime::TerminationDetails }
91    };
92
93    let raw_hostcall = quote! {
94        #(#attrs)*
95        #vis
96        #raw_sig {
97            #[inline(always)]
98            #hostcall
99
100            let mut vmctx = #vmctx_mod::Vmctx::from_raw(vmctx_raw);
101            #vmctx_mod::VmctxInternal::instance_mut(&mut vmctx).uninterruptable(|| {
102                let res = std::panic::catch_unwind(move || {
103                    #hostcall_ident(&mut #vmctx_mod::Vmctx::from_raw(vmctx_raw), #(#impl_args),*)
104                });
105                match res {
106                    Ok(res) => res,
107                    Err(e) => {
108                        match e.downcast::<#termination_details>() {
109                            Ok(details) => {
110                                #vmctx_mod::Vmctx::from_raw(vmctx_raw).terminate_no_unwind(*details)
111                            },
112                            Err(e) => std::panic::resume_unwind(e),
113                        }
114                    }
115                }
116            })
117        }
118    };
119    raw_hostcall.into()
120}