Skip to content

Commit 877abeb

Browse files
committed
Add guest macro crate
Signed-off-by: Jorge Prendes <jorge.prendes@gmail.com>
1 parent 2efc352 commit 877abeb

File tree

6 files changed

+286
-0
lines changed

6 files changed

+286
-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: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
/*
2+
Copyright 2025 The Hyperlight Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
use proc_macro::TokenStream;
18+
use proc_macro_crate::{FoundCrate, crate_name};
19+
use quote::quote;
20+
use syn::parse::{Error, Parse, ParseStream, Result};
21+
use syn::spanned::Spanned as _;
22+
use syn::{ForeignItemFn, ItemFn, LitStr, Pat, parse_macro_input};
23+
24+
enum NameArg {
25+
None,
26+
Name(LitStr),
27+
}
28+
29+
impl Parse for NameArg {
30+
fn parse(input: ParseStream) -> Result<Self> {
31+
if input.is_empty() {
32+
return Ok(NameArg::None);
33+
}
34+
let name: LitStr = input.parse()?;
35+
if !input.is_empty() {
36+
return Err(Error::new(input.span(), "expected a single identifier"));
37+
}
38+
Ok(NameArg::Name(name))
39+
}
40+
}
41+
42+
#[proc_macro_attribute]
43+
pub fn guest_function(attr: TokenStream, item: TokenStream) -> TokenStream {
44+
let crate_name =
45+
crate_name("hyperlight-guest-bin").expect("hyperlight-guest-bin must be a dependency");
46+
let crate_name = match crate_name {
47+
FoundCrate::Itself => quote! {crate},
48+
FoundCrate::Name(name) => {
49+
let ident = syn::Ident::new(&name, proc_macro2::Span::call_site());
50+
quote! {::#ident}
51+
}
52+
};
53+
54+
let fn_declaration = parse_macro_input!(item as ItemFn);
55+
56+
let ident = fn_declaration.sig.ident.clone();
57+
58+
let exported_name = match parse_macro_input!(attr as NameArg) {
59+
NameArg::None => quote! { stringify!(#ident) },
60+
NameArg::Name(name) => quote! { #name },
61+
};
62+
63+
if let Some(syn::FnArg::Receiver(arg)) = fn_declaration.sig.inputs.first() {
64+
return Error::new(
65+
arg.span(),
66+
"Receiver (self) argument is not allowed in guest functions",
67+
)
68+
.to_compile_error()
69+
.into();
70+
}
71+
72+
if fn_declaration.sig.asyncness.is_some() {
73+
return Error::new(
74+
fn_declaration.sig.asyncness.span(),
75+
"Async functions are not allowed in guest functions",
76+
)
77+
.to_compile_error()
78+
.into();
79+
}
80+
81+
let output = quote! {
82+
#fn_declaration
83+
84+
const _: () = {
85+
#[#crate_name::__private::linkme::distributed_slice(#crate_name::__private::GUEST_FUNCTION_INIT)]
86+
#[linkme(crate = #crate_name::__private::linkme)]
87+
static REGISTRATION: fn() = || {
88+
hyperlight_guest_bin::guest_function::register::register_fn(#exported_name, #ident);
89+
};
90+
};
91+
};
92+
93+
output.into()
94+
}
95+
96+
#[proc_macro_attribute]
97+
pub fn host_function(attr: TokenStream, item: TokenStream) -> TokenStream {
98+
let crate_name =
99+
crate_name("hyperlight-guest-bin").expect("hyperlight-guest-bin must be a dependency");
100+
let crate_name = match crate_name {
101+
FoundCrate::Itself => quote! {crate},
102+
FoundCrate::Name(name) => {
103+
let ident = syn::Ident::new(&name, proc_macro2::Span::call_site());
104+
quote! {::#ident}
105+
}
106+
};
107+
108+
let fn_declaration = parse_macro_input!(item as ForeignItemFn);
109+
110+
let ForeignItemFn {
111+
attrs,
112+
vis,
113+
sig,
114+
semi_token: _,
115+
} = fn_declaration;
116+
117+
let ident = sig.ident.clone();
118+
119+
let exported_name = match parse_macro_input!(attr as NameArg) {
120+
NameArg::None => quote! { stringify!(#ident) },
121+
NameArg::Name(name) => quote! { #name },
122+
};
123+
124+
let mut args = vec![];
125+
for arg in sig.inputs.iter() {
126+
match arg {
127+
syn::FnArg::Receiver(_) => {
128+
return Error::new(
129+
arg.span(),
130+
"Receiver (self) argument is not allowed in guest functions",
131+
)
132+
.to_compile_error()
133+
.into();
134+
}
135+
syn::FnArg::Typed(arg) => {
136+
let Pat::Ident(pat) = *arg.pat.clone() else {
137+
return Error::new(
138+
arg.span(),
139+
"Only named arguments are allowed in host functions",
140+
)
141+
.to_compile_error()
142+
.into();
143+
};
144+
145+
if pat.attrs.len() > 0 {
146+
return Error::new(
147+
arg.span(),
148+
"Attributes are not allowed on host function arguments",
149+
)
150+
.to_compile_error()
151+
.into();
152+
}
153+
154+
if pat.by_ref.is_some() {
155+
return Error::new(
156+
arg.span(),
157+
"By-ref arguments are not allowed in host functions",
158+
)
159+
.to_compile_error()
160+
.into();
161+
}
162+
163+
if pat.mutability.is_some() {
164+
return Error::new(
165+
arg.span(),
166+
"Mutable arguments are not allowed in host functions",
167+
)
168+
.to_compile_error()
169+
.into();
170+
}
171+
172+
if pat.subpat.is_some() {
173+
return Error::new(
174+
arg.span(),
175+
"Sub-patterns are not allowed in host functions",
176+
)
177+
.to_compile_error()
178+
.into();
179+
}
180+
181+
let ident = pat.ident.clone();
182+
183+
args.push(quote! { #ident });
184+
}
185+
}
186+
}
187+
188+
let ret: proc_macro2::TokenStream = match &sig.output {
189+
syn::ReturnType::Default => quote! { quote! { () } },
190+
syn::ReturnType::Type(_, ty) => {
191+
quote! { #ty }
192+
}
193+
};
194+
195+
let output = quote! {
196+
#(#attrs)* #vis #sig {
197+
use #crate_name::__private::{ResultType, HyperlightGuestError};
198+
use #crate_name::host_comm::call_host;
199+
<#ret as ResultType<HyperlightGuestError>>::from_result(call_host(#exported_name, (#(#args,)*)))
200+
}
201+
};
202+
203+
output.into()
204+
}

0 commit comments

Comments
 (0)