Skip to content

Commit 9d80744

Browse files
committed
Add comments on tricky areas
Signed-off-by: Jorge Prendes <[email protected]>
1 parent 511912b commit 9d80744

File tree

5 files changed

+169
-9
lines changed

5 files changed

+169
-9
lines changed

src/hyperlight_guest_bin/src/guest_function/definition.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,31 @@ macro_rules! impl_host_function {
9494
($($P,)*): ParameterTuple,
9595
R: ResultType<HyperlightGuestError>,
9696
{
97+
// Only functions that can be coerced into a function pointer (i.e., "fn" types)
98+
// can be registered as guest functions.
99+
//
100+
// Note that the "Fn" trait is different from "fn" types in Rust.
101+
// "fn" is a type, while "Fn" is a trait.
102+
// For example, closures that capture environment implement "Fn" but cannot be
103+
// coerced to function pointers.
104+
// This means that the closure returned by `into_guest_function` can not capture
105+
// any environment, not event `self`, and we must only rely on the type system
106+
// to call the correct function.
107+
//
108+
// Ideally we would implement IntoGuestFunction for any F that can be converted
109+
// into a function pointer, but currently there's no way to express that in Rust's
110+
// type system.
111+
// Therefore, to ensure that F is a "fn" type, we enforce that F is zero-sized
112+
// has no Drop impl (the latter is enforced by the Copy bound), and it doesn't
113+
// capture any lifetimes (not even through a marker type like PhantomData).
114+
//
115+
// Note that implementing IntoGuestFunction for "fn($(P),*) -> R" is not an option
116+
// either, "fn($(P),*) -> R" is a type that's shared for all function pointers with
117+
// that signature, e.g., "fn add(a: i32, b: i32) -> i32 { a + b }" and
118+
// "fn sub(a: i32, b: i32) -> i32 { a - b }" both can be coerced to the same
119+
// "fn(i32, i32) -> i32" type, so we would need to capture self (a function pointer)
120+
// to know exactly which function to call.
121+
97122
#[doc(hidden)]
98123
const ASSERT_ZERO_SIZED: () = const {
99124
assert!(core::mem::size_of::<Self>() == 0)

src/hyperlight_guest_bin/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,8 @@ pub mod __private {
276276
impl FromResult for $ty {
277277
type Output = Self;
278278
fn from_result(res: Result<Self::Output, HyperlightGuestError>) -> Self {
279+
// Unwrapping here is fine as this would only run in a guest
280+
// and not in the host.
279281
res.unwrap()
280282
}
281283
}

src/hyperlight_guest_macro/src/lib.rs

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,16 @@ use syn::parse::{Error, Parse, ParseStream, Result};
2121
use syn::spanned::Spanned as _;
2222
use syn::{ForeignItemFn, ItemFn, LitStr, Pat, parse_macro_input};
2323

24+
/// Represents the optional name argument for the guest_function and host_function macros.
2425
enum NameArg {
2526
None,
2627
Name(LitStr),
2728
}
2829

2930
impl 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]
4389
pub 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]
97195
pub 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;

src/hyperlight_host/src/func/host_functions.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,26 +64,26 @@ where
6464
Args: ParameterTuple,
6565
Output: SupportedReturnType,
6666
{
67-
// This is a thin wrapper around a `Fn(Args) -> Result<Output>`.
68-
// But unlike `Fn` which is a trait, this is a concrete type.
67+
// This is a thin wrapper around a `Function<Output, Args, HyperlightError>`.
68+
// But unlike `Function<..>` which is a trait, this is a concrete type.
6969
// This allows us to:
7070
// 1. Impose constraints on the function arguments and return type.
7171
// 2. Impose a single function signature.
7272
//
73-
// This second point is important because the `Fn` trait is generic
74-
// over the function arguments (with an associated return type).
75-
// This means that a given type could implement `Fn` for multiple
73+
// This second point is important because the `Function<..>` trait is generic
74+
// over the function arguments and return type.
75+
// This means that a given type could implement `Function<..>` for multiple
7676
// function signatures.
7777
// This means we can't do something like:
7878
// ```rust,ignore
7979
// impl<Args, Output, F> SomeTrait for F
8080
// where
81-
// F: Fn(Args) -> Result<Output>,
81+
// F: Function<Output, Args, HyperlightError>,
8282
// { ... }
8383
// ```
84-
// because the concrete type F might implement `Fn` for multiple times,
85-
// and that would means implementing `SomeTrait` multiple times for the
86-
// same type.
84+
// because the concrete type F might implement `Function<..>` for multiple
85+
// arguments and return types, and that would means implementing `SomeTrait`
86+
// multiple times for the same type F.
8787

8888
// Use Arc in here instead of Box because it's useful in tests and
8989
// presumably in other places to be able to clone a HostFunction and

src/hyperlight_host/src/sandbox/initialized_multi_use.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,8 @@ impl MultiUseSandbox {
450450
Output::TYPE,
451451
args.into_value(),
452452
);
453+
// Use the ? operator to allow converting any hyperlight_common::func::Error
454+
// returned by from_value into a HyperlightError
453455
let ret = Output::from_value(ret?)?;
454456
Ok(ret)
455457
})

0 commit comments

Comments
 (0)