hyperlight_guest_macro/
lib.rs

1/*
2Copyright 2025 The Hyperlight Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17use proc_macro::TokenStream;
18use proc_macro_crate::{FoundCrate, crate_name};
19use quote::quote;
20use syn::parse::{Error, Parse, ParseStream, Result};
21use syn::spanned::Spanned as _;
22use syn::{ForeignItemFn, ItemFn, LitStr, Pat, parse_macro_input};
23
24/// Represents the optional name argument for the guest_function and host_function macros.
25enum NameArg {
26    None,
27    Name(LitStr),
28}
29
30impl Parse for NameArg {
31    fn parse(input: ParseStream) -> Result<Self> {
32        // accepts either nothing or a single string literal
33        // anything else is an error
34        if input.is_empty() {
35            return Ok(NameArg::None);
36        }
37        let name: LitStr = input.parse()?;
38        if !input.is_empty() {
39            return Err(Error::new(input.span(), "expected a single identifier"));
40        }
41        Ok(NameArg::Name(name))
42    }
43}
44
45/// Attribute macro to mark a function as a guest function.
46/// This will register the function so that it can be called by the host.
47///
48/// If a name is provided as an argument, that name will be used to register the function.
49/// Otherwise, the function's identifier will be used.
50///
51/// The function arguments must be supported parameter types, and the return type must be
52/// a supported return type or a `Result<T, HyperlightGuestError>` with T being a supported
53/// return type.
54///
55/// # Note
56/// The function will be registered with the host at program initialization regardless of
57/// the visibility modifier used (e.g., `pub`, `pub(crate)`, etc.).
58/// This means that a private functions can be called by the host from beyond its normal
59/// visibility scope.
60///
61/// # Example
62/// ```ignore
63/// use hyperlight_guest_bin::guest_function;
64/// #[guest_function]
65/// fn my_guest_function(arg1: i32, arg2: String) -> i32 {
66///     arg1 + arg2.len() as i32
67/// }
68/// ```
69///
70/// or with a custom name:
71/// ```ignore
72/// use hyperlight_guest_bin::guest_function;
73/// #[guest_function("custom_name")]
74/// fn my_guest_function(arg1: i32, arg2: String) -> i32 {
75///     arg1 + arg2.len() as i32
76/// }
77/// ```
78///
79/// or with a Result return type:
80/// ```ignore
81/// use hyperlight_guest_bin::guest_function;
82/// use hyperlight_guest::bail;
83/// #[guest_function]
84/// fn my_guest_function(arg1: i32, arg2: String) -> Result<i32, HyperlightGuestError> {
85///     bail!("An error occurred");
86/// }
87/// ```
88#[proc_macro_attribute]
89pub fn guest_function(attr: TokenStream, item: TokenStream) -> TokenStream {
90    // Obtain the crate name for hyperlight-guest-bin
91    let crate_name =
92        crate_name("hyperlight-guest-bin").expect("hyperlight-guest-bin must be a dependency");
93    let crate_name = match crate_name {
94        FoundCrate::Itself => quote! {crate},
95        FoundCrate::Name(name) => {
96            let ident = syn::Ident::new(&name, proc_macro2::Span::call_site());
97            quote! {::#ident}
98        }
99    };
100
101    // Parse the function definition that we will be working with, and
102    // early return if parsing as `ItemFn` fails.
103    let fn_declaration = parse_macro_input!(item as ItemFn);
104
105    // Obtain the name of the function being decorated.
106    let ident = fn_declaration.sig.ident.clone();
107
108    // Determine the name used to register the function, either
109    // the provided name or the function's identifier.
110    let exported_name = match parse_macro_input!(attr as NameArg) {
111        NameArg::None => quote! { stringify!(#ident) },
112        NameArg::Name(name) => quote! { #name },
113    };
114
115    // Small sanity checks to improve error messages.
116    // These checks are not strictly necessary, as the generated code
117    // would fail to compile anyway (due to the trait bounds of `register_fn`),
118    // but they provide better feedback to the user of the macro.
119
120    // Check that there are no receiver arguments (i.e., `self`, `&self`, `Box<Self>`, etc).
121    if let Some(syn::FnArg::Receiver(arg)) = fn_declaration.sig.inputs.first() {
122        return Error::new(
123            arg.span(),
124            "Receiver (self) argument is not allowed in guest functions",
125        )
126        .to_compile_error()
127        .into();
128    }
129
130    // Check that the function is not async.
131    if fn_declaration.sig.asyncness.is_some() {
132        return Error::new(
133            fn_declaration.sig.asyncness.span(),
134            "Async functions are not allowed in guest functions",
135        )
136        .to_compile_error()
137        .into();
138    }
139
140    // The generated code will replace the decorated code, so we need to
141    // include the original function declaration in the output.
142    let output = quote! {
143        #fn_declaration
144
145        const _: () = {
146            // Add the function registration in the GUEST_FUNCTION_INIT distributed slice
147            // so that it can be registered at program initialization
148            #[#crate_name::__private::linkme::distributed_slice(#crate_name::__private::GUEST_FUNCTION_INIT)]
149            #[linkme(crate = #crate_name::__private::linkme)]
150            static REGISTRATION: fn() = || {
151                #crate_name::guest_function::register::register_fn(#exported_name, #ident);
152            };
153        };
154    };
155
156    output.into()
157}
158
159/// Attribute macro to mark a function as a host function.
160/// This will generate a function that calls the host function with the same name.
161///
162/// If a name is provided as an argument, that name will be used to call the host function.
163/// Otherwise, the function's identifier will be used.
164///
165/// The function arguments must be supported parameter types, and the return type must be
166/// a supported return type or a `Result<T, HyperlightGuestError>` with T being a supported
167/// return type.
168///
169/// # Panic
170/// If the return type is not a Result, the generated function will panic if the host function
171/// returns an error.
172///
173/// # Example
174/// ```ignore
175/// use hyperlight_guest_bin::host_function;
176/// #[host_function]
177/// fn my_host_function(arg1: i32, arg2: String) -> i32;
178/// ```
179///
180/// or with a custom name:
181/// ```ignore
182/// use hyperlight_guest_bin::host_function;
183/// #[host_function("custom_name")]
184/// fn my_host_function(arg1: i32, arg2: String) -> i32;
185/// ```
186///
187/// or with a Result return type:
188/// ```ignore
189/// use hyperlight_guest_bin::host_function;
190/// use hyperlight_guest::error::HyperlightGuestError;
191/// #[host_function]
192/// fn my_host_function(arg1: i32, arg2: String) -> Result<i32, HyperlightGuestError>;
193/// ```
194#[proc_macro_attribute]
195pub fn host_function(attr: TokenStream, item: TokenStream) -> TokenStream {
196    // Obtain the crate name for hyperlight-guest-bin
197    let crate_name =
198        crate_name("hyperlight-guest-bin").expect("hyperlight-guest-bin must be a dependency");
199    let crate_name = match crate_name {
200        FoundCrate::Itself => quote! {crate},
201        FoundCrate::Name(name) => {
202            let ident = syn::Ident::new(&name, proc_macro2::Span::call_site());
203            quote! {::#ident}
204        }
205    };
206
207    // Parse the function declaration that we will be working with, and
208    // early return if parsing as `ForeignItemFn` fails.
209    // A function declaration without a body is a foreign item function, as that's what
210    // you would use when declaring an FFI function.
211    let fn_declaration = parse_macro_input!(item as ForeignItemFn);
212
213    // Destructure the foreign item function to get its components.
214    let ForeignItemFn {
215        attrs,
216        vis,
217        sig,
218        semi_token: _,
219    } = fn_declaration;
220
221    // Obtain the name of the function being decorated.
222    let ident = sig.ident.clone();
223
224    // Determine the name used to call the host function, either
225    // the provided name or the function's identifier.
226    let exported_name = match parse_macro_input!(attr as NameArg) {
227        NameArg::None => quote! { stringify!(#ident) },
228        NameArg::Name(name) => quote! { #name },
229    };
230
231    // Build the list of argument identifiers to pass to the call_host function.
232    // While doing that, also do some sanity checks to improve error messages.
233    // These checks are not strictly necessary, as the generated code would fail
234    // to compile anyway due to either:
235    // * the trait bounds of `call_host`
236    // * the generated code having invalid syntax
237    // but they provide better feedback to the user of the macro, especially in
238    // the case of invalid syntax.
239    let mut args = vec![];
240    for arg in sig.inputs.iter() {
241        match arg {
242            // Reject receiver arguments (i.e., `self`, `&self`, `Box<Self>`, etc).
243            syn::FnArg::Receiver(_) => {
244                return Error::new(
245                    arg.span(),
246                    "Receiver (self) argument is not allowed in guest functions",
247                )
248                .to_compile_error()
249                .into();
250            }
251            syn::FnArg::Typed(arg) => {
252                // A typed argument: `name: Type`
253                // Technically, the `name` part can be any pattern, e.g., destructuring patterns
254                // like `(a, b): (i32, u64)`, but we only allow simple identifiers here
255                // to keep things simple.
256
257                // Reject anything that is not a simple identifier.
258                let Pat::Ident(pat) = *arg.pat.clone() else {
259                    return Error::new(
260                        arg.span(),
261                        "Only named arguments are allowed in host functions",
262                    )
263                    .to_compile_error()
264                    .into();
265                };
266
267                // Reject any argument with attributes, e.g., `#[cfg(feature = "gdb")] name: Type`
268                if !pat.attrs.is_empty() {
269                    return Error::new(
270                        arg.span(),
271                        "Attributes are not allowed on host function arguments",
272                    )
273                    .to_compile_error()
274                    .into();
275                }
276
277                // Reject any argument passed by reference
278                if pat.by_ref.is_some() {
279                    return Error::new(
280                        arg.span(),
281                        "By-ref arguments are not allowed in host functions",
282                    )
283                    .to_compile_error()
284                    .into();
285                }
286
287                // Reject any mutable argument, e.g., `mut name: Type`
288                if pat.mutability.is_some() {
289                    return Error::new(
290                        arg.span(),
291                        "Mutable arguments are not allowed in host functions",
292                    )
293                    .to_compile_error()
294                    .into();
295                }
296
297                // Reject any sub-patterns
298                if pat.subpat.is_some() {
299                    return Error::new(
300                        arg.span(),
301                        "Sub-patterns are not allowed in host functions",
302                    )
303                    .to_compile_error()
304                    .into();
305                }
306
307                let ident = pat.ident.clone();
308
309                // All checks passed, add the identifier to the argument list.
310                args.push(quote! { #ident });
311            }
312        }
313    }
314
315    // Determine the return type of the function.
316    // If the return type is not specified, it is `()`.
317    let ret: proc_macro2::TokenStream = match &sig.output {
318        syn::ReturnType::Default => quote! { quote! { () } },
319        syn::ReturnType::Type(_, ty) => {
320            quote! { #ty }
321        }
322    };
323
324    // Take the parts of the function declaration and generate a function definition
325    // matching the provided declaration, but with a body that calls the host function.
326    let output = quote! {
327        #(#attrs)* #vis #sig {
328            use #crate_name::__private::FromResult;
329            use #crate_name::host_comm::call_host;
330            <#ret as FromResult>::from_result(call_host(#exported_name, (#(#args,)*)))
331        }
332    };
333
334    output.into()
335}