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));
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);
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}