Skip to main content

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 the main entry point for the guest.
160/// This will generate a function that is called by the host at program initialization.
161///
162/// # Example
163/// ```ignore
164/// use hyperlight_guest_bin::main;
165/// #[main]
166/// fn main() {
167///     // do some initialization work here, e.g., initialize global state, etc.
168/// }
169/// ```
170#[proc_macro_attribute]
171pub fn main(_attr: TokenStream, item: TokenStream) -> TokenStream {
172    // Parse the function definition that we will be working with, and
173    // early return if parsing as `ItemFn` fails.
174    let fn_declaration = parse_macro_input!(item as ItemFn);
175
176    // Obtain the name of the function being decorated.
177    let ident = fn_declaration.sig.ident.clone();
178
179    // The generated code will replace the decorated code, so we need to
180    // include the original function declaration in the output.
181    let output = quote! {
182        #fn_declaration
183
184        const _: () = {
185            mod wrapper {
186                #[unsafe(no_mangle)]
187                pub extern "C" fn hyperlight_main() {
188                    super::#ident()
189                }
190            }
191        };
192    };
193
194    output.into()
195}
196
197/// Attribute macro to mark a function as the dispatch function for the guest.
198/// This is the function that will be called by the host when a function call is made
199/// to a function that is not registered with the host.
200///
201/// # Example
202/// ```ignore
203/// use hyperlight_guest_bin::dispatch;
204/// use hyperlight_guest::error::Result;
205/// use hyperlight_guest::bail;
206/// use hyperlight_common::flatbuffer_wrappers::function_call::FunctionCall;
207/// use hyperlight_common::flatbuffer_wrappers::util::get_flatbuffer_result;
208/// #[dispatch]
209/// fn dispatch(fc: FunctionCall) -> Result<Vec<u8>> {
210///     let name = &fc.function_name;
211///     if name == "greet" {
212///         return Ok(get_flatbuffer_result("Hello, world!"));
213///     }
214///     bail!("Unknown function: {name}");
215/// }
216/// ```
217#[proc_macro_attribute]
218pub fn dispatch(_attr: TokenStream, item: TokenStream) -> TokenStream {
219    // Obtain the crate name for hyperlight-guest-bin
220    let crate_name =
221        crate_name("hyperlight-guest-bin").expect("hyperlight-guest-bin must be a dependency");
222    let crate_name = match crate_name {
223        FoundCrate::Itself => quote! {crate},
224        FoundCrate::Name(name) => {
225            let ident = syn::Ident::new(&name, proc_macro2::Span::call_site());
226            quote! {::#ident}
227        }
228    };
229
230    // Parse the function definition that we will be working with, and
231    // early return if parsing as `ItemFn` fails.
232    let fn_declaration = parse_macro_input!(item as ItemFn);
233
234    // Obtain the name of the function being decorated.
235    let ident = fn_declaration.sig.ident.clone();
236
237    // The generated code will replace the decorated code, so we need to
238    // include the original function declaration in the output.
239    let output = quote! {
240        #fn_declaration
241
242        const _: () = {
243            mod wrapper {
244                use #crate_name::__private::{FunctionCall, HyperlightGuestError, Vec};
245                #[unsafe(no_mangle)]
246                pub fn guest_dispatch_function(function_call: FunctionCall) -> ::core::result::Result<Vec<u8>, HyperlightGuestError> {
247                    super::#ident(function_call)
248                }
249            }
250        };
251    };
252
253    output.into()
254}
255
256/// Attribute macro to mark a function as a host function.
257/// This will generate a function that calls the host function with the same name.
258///
259/// If a name is provided as an argument, that name will be used to call the host function.
260/// Otherwise, the function's identifier will be used.
261///
262/// The function arguments must be supported parameter types, and the return type must be
263/// a supported return type or a `Result<T, HyperlightGuestError>` with T being a supported
264/// return type.
265///
266/// # Panic
267/// If the return type is not a Result, the generated function will panic if the host function
268/// returns an error.
269///
270/// # Example
271/// ```ignore
272/// use hyperlight_guest_bin::host_function;
273/// #[host_function]
274/// fn my_host_function(arg1: i32, arg2: String) -> i32;
275/// ```
276///
277/// or with a custom name:
278/// ```ignore
279/// use hyperlight_guest_bin::host_function;
280/// #[host_function("custom_name")]
281/// fn my_host_function(arg1: i32, arg2: String) -> i32;
282/// ```
283///
284/// or with a Result return type:
285/// ```ignore
286/// use hyperlight_guest_bin::host_function;
287/// use hyperlight_guest::error::HyperlightGuestError;
288/// #[host_function]
289/// fn my_host_function(arg1: i32, arg2: String) -> Result<i32, HyperlightGuestError>;
290/// ```
291#[proc_macro_attribute]
292pub fn host_function(attr: TokenStream, item: TokenStream) -> TokenStream {
293    // Obtain the crate name for hyperlight-guest-bin
294    let crate_name =
295        crate_name("hyperlight-guest-bin").expect("hyperlight-guest-bin must be a dependency");
296    let crate_name = match crate_name {
297        FoundCrate::Itself => quote! {crate},
298        FoundCrate::Name(name) => {
299            let ident = syn::Ident::new(&name, proc_macro2::Span::call_site());
300            quote! {::#ident}
301        }
302    };
303
304    // Parse the function declaration that we will be working with, and
305    // early return if parsing as `ForeignItemFn` fails.
306    // A function declaration without a body is a foreign item function, as that's what
307    // you would use when declaring an FFI function.
308    let fn_declaration = parse_macro_input!(item as ForeignItemFn);
309
310    // Destructure the foreign item function to get its components.
311    let ForeignItemFn {
312        attrs,
313        vis,
314        sig,
315        semi_token: _,
316    } = fn_declaration;
317
318    // Obtain the name of the function being decorated.
319    let ident = sig.ident.clone();
320
321    // Determine the name used to call the host function, either
322    // the provided name or the function's identifier.
323    let exported_name = match parse_macro_input!(attr as NameArg) {
324        NameArg::None => quote! { stringify!(#ident) },
325        NameArg::Name(name) => quote! { #name },
326    };
327
328    // Build the list of argument identifiers to pass to the call_host function.
329    // While doing that, also do some sanity checks to improve error messages.
330    // These checks are not strictly necessary, as the generated code would fail
331    // to compile anyway due to either:
332    // * the trait bounds of `call_host`
333    // * the generated code having invalid syntax
334    // but they provide better feedback to the user of the macro, especially in
335    // the case of invalid syntax.
336    let mut args = vec![];
337    for arg in sig.inputs.iter() {
338        match arg {
339            // Reject receiver arguments (i.e., `self`, `&self`, `Box<Self>`, etc).
340            syn::FnArg::Receiver(_) => {
341                return Error::new(
342                    arg.span(),
343                    "Receiver (self) argument is not allowed in guest functions",
344                )
345                .to_compile_error()
346                .into();
347            }
348            syn::FnArg::Typed(arg) => {
349                // A typed argument: `name: Type`
350                // Technically, the `name` part can be any pattern, e.g., destructuring patterns
351                // like `(a, b): (i32, u64)`, but we only allow simple identifiers here
352                // to keep things simple.
353
354                // Reject anything that is not a simple identifier.
355                let Pat::Ident(pat) = *arg.pat.clone() else {
356                    return Error::new(
357                        arg.span(),
358                        "Only named arguments are allowed in host functions",
359                    )
360                    .to_compile_error()
361                    .into();
362                };
363
364                // Reject any argument with attributes, e.g., `#[cfg(feature = "gdb")] name: Type`
365                if !pat.attrs.is_empty() {
366                    return Error::new(
367                        arg.span(),
368                        "Attributes are not allowed on host function arguments",
369                    )
370                    .to_compile_error()
371                    .into();
372                }
373
374                // Reject any argument passed by reference
375                if pat.by_ref.is_some() {
376                    return Error::new(
377                        arg.span(),
378                        "By-ref arguments are not allowed in host functions",
379                    )
380                    .to_compile_error()
381                    .into();
382                }
383
384                // Reject any mutable argument, e.g., `mut name: Type`
385                if pat.mutability.is_some() {
386                    return Error::new(
387                        arg.span(),
388                        "Mutable arguments are not allowed in host functions",
389                    )
390                    .to_compile_error()
391                    .into();
392                }
393
394                // Reject any sub-patterns
395                if pat.subpat.is_some() {
396                    return Error::new(
397                        arg.span(),
398                        "Sub-patterns are not allowed in host functions",
399                    )
400                    .to_compile_error()
401                    .into();
402                }
403
404                let ident = pat.ident.clone();
405
406                // All checks passed, add the identifier to the argument list.
407                args.push(quote! { #ident });
408            }
409        }
410    }
411
412    // Determine the return type of the function.
413    // If the return type is not specified, it is `()`.
414    let ret: proc_macro2::TokenStream = match &sig.output {
415        syn::ReturnType::Default => quote! { quote! { () } },
416        syn::ReturnType::Type(_, ty) => {
417            quote! { #ty }
418        }
419    };
420
421    // Take the parts of the function declaration and generate a function definition
422    // matching the provided declaration, but with a body that calls the host function.
423    let output = quote! {
424        #(#attrs)* #vis #sig {
425            use #crate_name::__private::FromResult;
426            use #crate_name::host_comm::call_host;
427            <#ret as FromResult>::from_result(call_host(#exported_name, (#(#args,)*)))
428        }
429    };
430
431    output.into()
432}