Skip to content

Commit dce9c25

Browse files
committed
Add guest macro crate
Signed-off-by: Jorge Prendes <jorge.prendes@gmail.com>
1 parent 8ebe6c6 commit dce9c25

File tree

6 files changed

+270
-0
lines changed

6 files changed

+270
-0
lines changed

Cargo.lock

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ members = [
1515
"src/hyperlight_testing",
1616
"fuzz",
1717
"src/hyperlight_guest_bin",
18+
"src/hyperlight_guest_macro",
1819
"src/hyperlight_component_util",
1920
"src/hyperlight_component_macro",
2021
"src/trace_dump",
@@ -41,6 +42,7 @@ hyperlight-common = { path = "src/hyperlight_common", version = "0.9.0", default
4142
hyperlight-host = { path = "src/hyperlight_host", version = "0.9.0", default-features = false }
4243
hyperlight-guest = { path = "src/hyperlight_guest", version = "0.9.0", default-features = false }
4344
hyperlight-guest-bin = { path = "src/hyperlight_guest_bin", version = "0.9.0", default-features = false }
45+
hyperlight-guest-macro = { path = "src/hyperlight_guest_macro", version = "0.9.0", default-features = false }
4446
hyperlight-testing = { path = "src/hyperlight_testing", default-features = false }
4547
hyperlight-guest-tracing = { path = "src/hyperlight_guest_tracing", version = "0.9.0", default-features = false }
4648
hyperlight-guest-tracing-macro = { path = "src/hyperlight_guest_tracing_macro", version = "0.9.0", default-features = false }

src/hyperlight_guest_bin/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,18 @@ libc = [] # compile musl libc
1919
printf = [ "libc" ] # compile printf
2020
trace_guest = ["hyperlight-common/trace_guest", "hyperlight-guest/trace_guest", "hyperlight-guest-tracing/trace"]
2121
mem_profile = ["hyperlight-common/unwind_guest","hyperlight-common/mem_profile"]
22+
# Not enabled by default as with rustc < 1.89, linkme requires the
23+
# -znostart-stop-gc linker flag. Fixed in rustc 1.89+.
24+
macros = ["dep:hyperlight-guest-macro", "dep:linkme"]
2225

2326
[dependencies]
2427
hyperlight-guest = { workspace = true, default-features = false }
2528
hyperlight-common = { workspace = true, default-features = false }
2629
hyperlight-guest-tracing = { workspace = true, default-features = false }
30+
hyperlight-guest-macro = { workspace = true, default-features = false, optional = true }
2731
buddy_system_allocator = "0.11.0"
2832
log = { version = "0.4", default-features = false }
33+
linkme = { version = "0.3.33", optional = true }
2934
spin = "0.10.0"
3035

3136
[lints]

src/hyperlight_guest_bin/src/lib.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,11 @@ pub extern "C" fn entrypoint(peb_address: u64, seed: u64, ops: u64, max_log_leve
206206
.expect("Invalid log level");
207207
init_logger(max_log_level);
208208

209+
#[cfg(feature = "macros")]
210+
for registration in __private::GUEST_FUNCTION_INIT {
211+
registration();
212+
}
213+
209214
trace!("hyperlight_main",
210215
hyperlight_main();
211216
);
@@ -214,3 +219,17 @@ pub extern "C" fn entrypoint(peb_address: u64, seed: u64, ops: u64, max_log_leve
214219

215220
halt();
216221
}
222+
223+
#[cfg(feature = "macros")]
224+
#[doc(hidden)]
225+
pub mod __private {
226+
pub use hyperlight_common::func::ResultType;
227+
pub use hyperlight_guest::error::HyperlightGuestError;
228+
pub use linkme;
229+
230+
#[linkme::distributed_slice]
231+
pub static GUEST_FUNCTION_INIT: [fn()];
232+
}
233+
234+
#[cfg(feature = "macros")]
235+
pub use hyperlight_guest_macro::{guest_function, host_function};
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[package]
2+
name = "hyperlight-guest-macro"
3+
version.workspace = true
4+
edition.workspace = true
5+
rust-version.workspace = true
6+
license.workspace = true
7+
homepage.workspace = true
8+
repository.workspace = true
9+
readme.workspace = true
10+
description = """
11+
Macros for registering guest and host functions on hyperlight guests binaries.
12+
"""
13+
14+
[dependencies]
15+
syn = { version = "2", features = ["full"] }
16+
quote = "1"
17+
proc-macro2 = "1.0"
18+
proc-macro-crate = "3.3.0"
19+
20+
[lib]
21+
proc-macro = true
22+
23+
[lints]
24+
workspace = true
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
use proc_macro::TokenStream;
2+
use proc_macro_crate::{FoundCrate, crate_name};
3+
use quote::quote;
4+
use syn::parse::{Error, Parse, ParseStream, Result};
5+
use syn::spanned::Spanned as _;
6+
use syn::{ForeignItemFn, ItemFn, LitStr, Pat, parse_macro_input};
7+
8+
enum NameArg {
9+
None,
10+
Name(LitStr),
11+
}
12+
13+
impl Parse for NameArg {
14+
fn parse(input: ParseStream) -> Result<Self> {
15+
if input.is_empty() {
16+
return Ok(NameArg::None);
17+
}
18+
let name: LitStr = input.parse()?;
19+
if !input.is_empty() {
20+
return Err(Error::new(input.span(), "expected a single identifier"));
21+
}
22+
Ok(NameArg::Name(name))
23+
}
24+
}
25+
26+
#[proc_macro_attribute]
27+
pub fn guest_function(attr: TokenStream, item: TokenStream) -> TokenStream {
28+
let crate_name =
29+
crate_name("hyperlight-guest-bin").expect("hyperlight-guest-bin must be a dependency");
30+
let crate_name = match crate_name {
31+
FoundCrate::Itself => quote! {crate},
32+
FoundCrate::Name(name) => {
33+
let ident = syn::Ident::new(&name, proc_macro2::Span::call_site());
34+
quote! {::#ident}
35+
}
36+
};
37+
38+
let fn_declaration = parse_macro_input!(item as ItemFn);
39+
40+
let ident = fn_declaration.sig.ident.clone();
41+
42+
let exported_name = match parse_macro_input!(attr as NameArg) {
43+
NameArg::None => quote! { stringify!(#ident) },
44+
NameArg::Name(name) => quote! { #name },
45+
};
46+
47+
if let Some(syn::FnArg::Receiver(arg)) = fn_declaration.sig.inputs.first() {
48+
return Error::new(
49+
arg.span(),
50+
"Receiver (self) argument is not allowed in guest functions",
51+
)
52+
.to_compile_error()
53+
.into();
54+
}
55+
56+
if fn_declaration.sig.asyncness.is_some() {
57+
return Error::new(
58+
fn_declaration.sig.asyncness.span(),
59+
"Async functions are not allowed in guest functions",
60+
)
61+
.to_compile_error()
62+
.into();
63+
}
64+
65+
let output = quote! {
66+
#fn_declaration
67+
68+
const _: () = {
69+
#[#crate_name::__private::linkme::distributed_slice(#crate_name::__private::GUEST_FUNCTION_INIT)]
70+
#[linkme(crate = #crate_name::__private::linkme)]
71+
static REGISTRATION: fn() = || {
72+
hyperlight_guest_bin::guest_function::register::register_fn(#exported_name, #ident);
73+
};
74+
};
75+
};
76+
77+
output.into()
78+
}
79+
80+
#[proc_macro_attribute]
81+
pub fn host_function(attr: TokenStream, item: TokenStream) -> TokenStream {
82+
let crate_name =
83+
crate_name("hyperlight-guest-bin").expect("hyperlight-guest-bin must be a dependency");
84+
let crate_name = match crate_name {
85+
FoundCrate::Itself => quote! {crate},
86+
FoundCrate::Name(name) => {
87+
let ident = syn::Ident::new(&name, proc_macro2::Span::call_site());
88+
quote! {::#ident}
89+
}
90+
};
91+
92+
let fn_declaration = parse_macro_input!(item as ForeignItemFn);
93+
94+
let ForeignItemFn {
95+
attrs,
96+
vis,
97+
sig,
98+
semi_token: _,
99+
} = fn_declaration;
100+
101+
let ident = sig.ident.clone();
102+
103+
let exported_name = match parse_macro_input!(attr as NameArg) {
104+
NameArg::None => quote! { stringify!(#ident) },
105+
NameArg::Name(name) => quote! { #name },
106+
};
107+
108+
let mut args = vec![];
109+
for arg in sig.inputs.iter() {
110+
match arg {
111+
syn::FnArg::Receiver(_) => {
112+
return Error::new(
113+
arg.span(),
114+
"Receiver (self) argument is not allowed in guest functions",
115+
)
116+
.to_compile_error()
117+
.into();
118+
}
119+
syn::FnArg::Typed(arg) => {
120+
let Pat::Ident(pat) = *arg.pat.clone() else {
121+
return Error::new(
122+
arg.span(),
123+
"Only named arguments are allowed in host functions",
124+
)
125+
.to_compile_error()
126+
.into();
127+
};
128+
129+
if pat.attrs.len() > 0 {
130+
return Error::new(
131+
arg.span(),
132+
"Attributes are not allowed on host function arguments",
133+
)
134+
.to_compile_error()
135+
.into();
136+
}
137+
138+
if pat.by_ref.is_some() {
139+
return Error::new(
140+
arg.span(),
141+
"By-ref arguments are not allowed in host functions",
142+
)
143+
.to_compile_error()
144+
.into();
145+
}
146+
147+
if pat.mutability.is_some() {
148+
return Error::new(
149+
arg.span(),
150+
"Mutable arguments are not allowed in host functions",
151+
)
152+
.to_compile_error()
153+
.into();
154+
}
155+
156+
if pat.subpat.is_some() {
157+
return Error::new(
158+
arg.span(),
159+
"Sub-patterns are not allowed in host functions",
160+
)
161+
.to_compile_error()
162+
.into();
163+
}
164+
165+
let ident = pat.ident.clone();
166+
167+
args.push(quote! { #ident });
168+
}
169+
}
170+
}
171+
172+
let ret: proc_macro2::TokenStream = match &sig.output {
173+
syn::ReturnType::Default => quote! { quote! { () } },
174+
syn::ReturnType::Type(_, ty) => {
175+
quote! { #ty }
176+
}
177+
};
178+
179+
let output = quote! {
180+
#(#attrs)* #vis #sig {
181+
use #crate_name::__private::{ResultType, HyperlightGuestError};
182+
use #crate_name::host_comm::call_host;
183+
<#ret as ResultType<HyperlightGuestError>>::from_result(call_host(#exported_name, (#(#args,)*)))
184+
}
185+
};
186+
187+
output.into()
188+
}

0 commit comments

Comments
 (0)