@@ -21,13 +21,16 @@ use syn::parse::{Error, Parse, ParseStream, Result};
2121use syn:: spanned:: Spanned as _;
2222use syn:: { ForeignItemFn , ItemFn , LitStr , Pat , parse_macro_input} ;
2323
24+ /// Represents the optional name argument for the guest_function and host_function macros.
2425enum NameArg {
2526 None ,
2627 Name ( LitStr ) ,
2728}
2829
2930impl Parse for NameArg {
3031 fn parse ( input : ParseStream ) -> Result < Self > {
32+ // accepts either nothing or a single string literal
33+ // anything else is an error
3134 if input. is_empty ( ) {
3235 return Ok ( NameArg :: None ) ;
3336 }
@@ -39,8 +42,52 @@ impl Parse for NameArg {
3942 }
4043}
4144
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+ /// ```
4288#[ proc_macro_attribute]
4389pub fn guest_function ( attr : TokenStream , item : TokenStream ) -> TokenStream {
90+ // Obtain the crate name for hyperlight-guest-bin
4491 let crate_name =
4592 crate_name ( "hyperlight-guest-bin" ) . expect ( "hyperlight-guest-bin must be a dependency" ) ;
4693 let crate_name = match crate_name {
@@ -51,15 +98,26 @@ pub fn guest_function(attr: TokenStream, item: TokenStream) -> TokenStream {
5198 }
5299 } ;
53100
101+ // Parse the function definition that we will be working with, and
102+ // early return if parsing as `ItemFn` fails.
54103 let fn_declaration = parse_macro_input ! ( item as ItemFn ) ;
55104
105+ // Obtain the name of the function being decorated.
56106 let ident = fn_declaration. sig . ident . clone ( ) ;
57107
108+ // Determine the name used to register the function, either
109+ // the provided name or the function's identifier.
58110 let exported_name = match parse_macro_input ! ( attr as NameArg ) {
59111 NameArg :: None => quote ! { stringify!( #ident) } ,
60112 NameArg :: Name ( name) => quote ! { #name } ,
61113 } ;
62114
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).
63121 if let Some ( syn:: FnArg :: Receiver ( arg) ) = fn_declaration. sig . inputs . first ( ) {
64122 return Error :: new (
65123 arg. span ( ) ,
@@ -69,6 +127,7 @@ pub fn guest_function(attr: TokenStream, item: TokenStream) -> TokenStream {
69127 . into ( ) ;
70128 }
71129
130+ // Check that the function is not async.
72131 if fn_declaration. sig . asyncness . is_some ( ) {
73132 return Error :: new (
74133 fn_declaration. sig . asyncness . span ( ) ,
@@ -78,10 +137,14 @@ pub fn guest_function(attr: TokenStream, item: TokenStream) -> TokenStream {
78137 . into ( ) ;
79138 }
80139
140+ // The generated code will replace the decorated code, so we need to
141+ // include the original function declaration in the output.
81142 let output = quote ! {
82143 #fn_declaration
83144
84145 const _: ( ) = {
146+ // Add the function registration in the GUEST_FUNCTION_INIT distributed slice
147+ // so that it can be registered at program initialization
85148 #[ #crate_name:: __private:: linkme:: distributed_slice( #crate_name:: __private:: GUEST_FUNCTION_INIT ) ]
86149 #[ linkme( crate = #crate_name:: __private:: linkme) ]
87150 static REGISTRATION : fn ( ) = || {
@@ -93,8 +156,44 @@ pub fn guest_function(attr: TokenStream, item: TokenStream) -> TokenStream {
93156 output. into ( )
94157}
95158
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+ /// ```
96194#[ proc_macro_attribute]
97195pub fn host_function ( attr : TokenStream , item : TokenStream ) -> TokenStream {
196+ // Obtain the crate name for hyperlight-guest-bin
98197 let crate_name =
99198 crate_name ( "hyperlight-guest-bin" ) . expect ( "hyperlight-guest-bin must be a dependency" ) ;
100199 let crate_name = match crate_name {
@@ -105,25 +204,42 @@ pub fn host_function(attr: TokenStream, item: TokenStream) -> TokenStream {
105204 }
106205 } ;
107206
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.
108211 let fn_declaration = parse_macro_input ! ( item as ForeignItemFn ) ;
109212
213+ // Destructure the foreign item function to get its components.
110214 let ForeignItemFn {
111215 attrs,
112216 vis,
113217 sig,
114218 semi_token : _,
115219 } = fn_declaration;
116220
221+ // Obtain the name of the function being decorated.
117222 let ident = sig. ident . clone ( ) ;
118223
224+ // Determine the name used to call the host function, either
225+ // the provided name or the function's identifier.
119226 let exported_name = match parse_macro_input ! ( attr as NameArg ) {
120227 NameArg :: None => quote ! { stringify!( #ident) } ,
121228 NameArg :: Name ( name) => quote ! { #name } ,
122229 } ;
123230
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.
124239 let mut args = vec ! [ ] ;
125240 for arg in sig. inputs . iter ( ) {
126241 match arg {
242+ // Reject receiver arguments (i.e., `self`, `&self`, `Box<Self>`, etc).
127243 syn:: FnArg :: Receiver ( _) => {
128244 return Error :: new (
129245 arg. span ( ) ,
@@ -133,6 +249,12 @@ pub fn host_function(attr: TokenStream, item: TokenStream) -> TokenStream {
133249 . into ( ) ;
134250 }
135251 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.
136258 let Pat :: Ident ( pat) = * arg. pat . clone ( ) else {
137259 return Error :: new (
138260 arg. span ( ) ,
@@ -142,6 +264,7 @@ pub fn host_function(attr: TokenStream, item: TokenStream) -> TokenStream {
142264 . into ( ) ;
143265 } ;
144266
267+ // Reject any argument with attributes, e.g., `#[cfg(feature = "gdb")] name: Type`
145268 if !pat. attrs . is_empty ( ) {
146269 return Error :: new (
147270 arg. span ( ) ,
@@ -151,6 +274,7 @@ pub fn host_function(attr: TokenStream, item: TokenStream) -> TokenStream {
151274 . into ( ) ;
152275 }
153276
277+ // Reject any argument passed by reference
154278 if pat. by_ref . is_some ( ) {
155279 return Error :: new (
156280 arg. span ( ) ,
@@ -160,6 +284,7 @@ pub fn host_function(attr: TokenStream, item: TokenStream) -> TokenStream {
160284 . into ( ) ;
161285 }
162286
287+ // Reject any mutable argument, e.g., `mut name: Type`
163288 if pat. mutability . is_some ( ) {
164289 return Error :: new (
165290 arg. span ( ) ,
@@ -169,6 +294,7 @@ pub fn host_function(attr: TokenStream, item: TokenStream) -> TokenStream {
169294 . into ( ) ;
170295 }
171296
297+ // Reject any sub-patterns
172298 if pat. subpat . is_some ( ) {
173299 return Error :: new (
174300 arg. span ( ) ,
@@ -180,18 +306,23 @@ pub fn host_function(attr: TokenStream, item: TokenStream) -> TokenStream {
180306
181307 let ident = pat. ident . clone ( ) ;
182308
309+ // All checks passed, add the identifier to the argument list.
183310 args. push ( quote ! { #ident } ) ;
184311 }
185312 }
186313 }
187314
315+ // Determine the return type of the function.
316+ // If the return type is not specified, it is `()`.
188317 let ret: proc_macro2:: TokenStream = match & sig. output {
189318 syn:: ReturnType :: Default => quote ! { quote! { ( ) } } ,
190319 syn:: ReturnType :: Type ( _, ty) => {
191320 quote ! { #ty }
192321 }
193322 } ;
194323
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.
195326 let output = quote ! {
196327 #( #attrs) * #vis #sig {
197328 use #crate_name:: __private:: FromResult ;
0 commit comments