nub_host_guest_macro/
lib.rs1use proc_macro::TokenStream;
22use proc_macro_crate::{FoundCrate, crate_name};
23use proc_macro2::Span;
24use quote::quote;
25use syn::parse::{Parse, ParseStream};
26use syn::spanned::Spanned;
27use syn::{Error, Expr, ItemFn, Token, parse_macro_input};
28
29struct FnIdArg(Expr);
34
35impl Parse for FnIdArg {
36 fn parse(input: ParseStream) -> syn::Result<Self> {
37 let ident: syn::Ident = input.parse()?;
38 if ident != "fn_id" {
39 return Err(Error::new(
40 ident.span(),
41 "expected `fn_id = <const u32 expression>`",
42 ));
43 }
44 let _eq: Token![=] = input.parse()?;
45 let expr: Expr = input.parse()?;
46 Ok(FnIdArg(expr))
47 }
48}
49
50fn resolve_crate(name: &str) -> proc_macro2::TokenStream {
51 match crate_name(name).unwrap_or_else(|_| panic!("`{name}` must be a dependency")) {
52 FoundCrate::Itself => quote! { crate },
53 FoundCrate::Name(found) => {
54 let ident = syn::Ident::new(&found, Span::call_site());
55 quote! { ::#ident }
56 }
57 }
58}
59
60#[proc_macro_attribute]
75pub fn guest_function(attr: TokenStream, item: TokenStream) -> TokenStream {
76 let crate_name = resolve_crate("hyperlight-guest-bin");
77 let FnIdArg(fn_id_expr) = parse_macro_input!(attr as FnIdArg);
78 let fn_declaration = parse_macro_input!(item as ItemFn);
79
80 let ident = fn_declaration.sig.ident.clone();
81
82 if let Some(syn::FnArg::Receiver(arg)) = fn_declaration.sig.inputs.first() {
84 return Error::new(arg.span(), "receiver (self) argument not allowed")
85 .to_compile_error()
86 .into();
87 }
88 if fn_declaration.sig.asyncness.is_some() {
89 return Error::new(
90 fn_declaration.sig.asyncness.span(),
91 "async guest functions not allowed",
92 )
93 .to_compile_error()
94 .into();
95 }
96
97 let registration_ident =
98 syn::Ident::new(&format!("__NUB_GUEST_FN_{ident}_ENTRY"), Span::call_site());
99
100 quote! {
101 #fn_declaration
102
103 #[#crate_name::__private::linkme::distributed_slice(
104 #crate_name::__private::GUEST_FUNCTION_TABLE
105 )]
106 #[linkme(crate = #crate_name::__private::linkme)]
107 static #registration_ident:
108 #crate_name::guest_function::register::GuestFnEntry =
109 #crate_name::guest_function::register::GuestFnEntry {
110 fn_id: (#fn_id_expr),
111 dispatcher: #ident,
112 };
113 }
114 .into()
115}
116
117#[proc_macro_attribute]
132pub fn host_function(attr: TokenStream, item: TokenStream) -> TokenStream {
133 let crate_name = resolve_crate("hyperlight-guest-bin");
134 let FnIdArg(fn_id_expr) = parse_macro_input!(attr as FnIdArg);
135 let fn_declaration = parse_macro_input!(item as syn::ForeignItemFn);
136
137 let syn::ForeignItemFn {
138 attrs, vis, sig, ..
139 } = fn_declaration;
140
141 if sig.inputs.len() != 1 {
143 return Error::new(sig.inputs.span(), "expected exactly one `&[u8]` parameter")
144 .to_compile_error()
145 .into();
146 }
147
148 let arg_ident = match sig.inputs.first().unwrap() {
151 syn::FnArg::Typed(pat_type) => match &*pat_type.pat {
152 syn::Pat::Ident(pat_ident) => pat_ident.ident.clone(),
153 _ => {
154 return Error::new(
155 pat_type.pat.span(),
156 "expected a plain identifier pattern (e.g. `payload: &[u8]`)",
157 )
158 .to_compile_error()
159 .into();
160 }
161 },
162 syn::FnArg::Receiver(arg) => {
163 return Error::new(arg.span(), "receiver (self) argument not allowed")
164 .to_compile_error()
165 .into();
166 }
167 };
168
169 quote! {
170 #(#attrs)*
171 #vis #sig {
172 #crate_name::host_comm::call_host_raw((#fn_id_expr), #arg_ident)
173 .expect("host function call failed")
174 }
175 }
176 .into()
177}