Skip to content

Commit 4dec0e4

Browse files
committed
refactor: make guest dispatch API version-resilient
- Rename guest_dispatch_function to guest_dispatch_function_v2 to force linker errors for out-of-date downstream code - Add guest_dispatch! macro so users don't define the symbol by hand; future signature changes will produce compile errors automatically - Parameterize GuestFunctionDefinition<F: Copy> over the function pointer type, eliminating transmutes in the C API dispatch path - Remove redundant clone in print_output_with_host_print now that FunctionCall is passed by value Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
1 parent b164763 commit 4dec0e4

File tree

12 files changed

+120
-70
lines changed

12 files changed

+120
-70
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
44

55
## [Prerelease] - Unreleased
66

7+
### Changed
8+
* **Breaking:** Change `guest_dispatch_function` signature by @ludfjig in https://github.com/hyperlight-dev/hyperlight/pull/1241
9+
- The fallback dispatch function now receives `FunctionCall` by value instead of by reference.
10+
- Rename `guest_dispatch_function` to `guest_dispatch_function_v2` and change the signature from `fn(fc: &FunctionCall) -> Result<Vec<u8>>` to `fn(fc: FunctionCall) -> Result<Vec<u8>>`.
11+
- Rust guests are encouraged to use the new `hyperlight_guest_bin::guest_dispatch!` macro instead of defining the symbol by hand.
12+
- `GuestFunctionDefinition::new` now takes a typed function pointer instead of `usize`, so callers passing `func as usize` will get a compile error.
13+
714
## [v0.12.0] - 2025-12-09
815

916
### Fixed

README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,10 @@ pub extern "C" fn hyperlight_main() {
9797
// any initialization code goes here
9898
}
9999

100-
#[no_mangle]
101-
pub fn guest_dispatch_function(function_call: FunctionCall) -> Result<Vec<u8>> {
100+
hyperlight_guest_bin::guest_dispatch!(|function_call| {
102101
let function_name = function_call.function_name;
103102
bail!(ErrorCode::GuestFunctionNotFound => "{function_name}");
104-
}
103+
});
105104
```
106105

107106
Build the guest using [cargo-hyperlight](https://github.com/hyperlight-dev/cargo-hyperlight):

docs/how-to-build-a-hyperlight-guest-binary.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ binary can be used with Hyperlight:
99
`pub fn hyperlight_main()`
1010
- Hyperlight expects
1111
`hl_Vec* c_guest_dispatch_function(const hl_FunctionCall *functioncall)` or
12-
`pub fn guest_dispatch_function(function_call: FunctionCall) -> Result<Vec<u8>>`
13-
to be defined in the binary so that in case the host calls a function that is
12+
the `guest_dispatch!` macro (Rust) to be used in the binary so that in case the host calls a function that is
1413
not registered by the guest, this function is called instead.
1514
- to be callable by the host, a function needs to be registered by the guest in
1615
the `hyperlight_main` function.

src/hyperlight_component_util/src/guest.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ fn emit_export_extern_decl<'a, 'b, 'c>(
210210
let marshal_result = emit_hl_marshal_result(s, ret.clone(), &ft.result);
211211
let trait_path = s.cur_trait_path();
212212
quote! {
213-
fn #n<T: Guest>(fc: &::hyperlight_common::flatbuffer_wrappers::function_call::FunctionCall) -> ::hyperlight_guest::error::Result<::alloc::vec::Vec<u8>> {
213+
fn #n<T: Guest>(fc: ::hyperlight_common::flatbuffer_wrappers::function_call::FunctionCall) -> ::hyperlight_guest::error::Result<::alloc::vec::Vec<u8>> {
214214
<T as Guest>::with_guest_state(|state| {
215215
#(#pds)*
216216
#(#get_instance)*
@@ -223,7 +223,7 @@ fn emit_export_extern_decl<'a, 'b, 'c>(
223223
::alloc::string::ToString::to_string(#fname),
224224
::alloc::vec![#(#pts),*],
225225
::hyperlight_common::flatbuffer_wrappers::function_types::ReturnType::VecBytes,
226-
#n::<T> as usize
226+
#n::<T>
227227
)
228228
);
229229
}

src/hyperlight_guest_bin/src/guest_function/call.rs

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ use tracing::{Span, instrument};
2727

2828
use crate::{GUEST_HANDLE, REGISTERED_GUEST_FUNCTIONS};
2929

30-
type GuestFunc = fn(FunctionCall) -> Result<Vec<u8>>;
31-
3230
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
3331
pub(crate) fn call_guest_function(function_call: FunctionCall) -> Result<Vec<u8>> {
3432
// Validate this is a Guest Function Call
@@ -59,23 +57,19 @@ pub(crate) fn call_guest_function(function_call: FunctionCall) -> Result<Vec<u8>
5957
// Verify that the function call has the correct parameter types and length.
6058
registered_function_definition.verify_parameters(&function_call_parameter_types)?;
6159

62-
let p_function = unsafe {
63-
let function_pointer = registered_function_definition.function_pointer;
64-
core::mem::transmute::<usize, GuestFunc>(function_pointer)
65-
};
66-
67-
p_function(function_call)
60+
(registered_function_definition.function_pointer)(function_call)
6861
} else {
69-
// The given function is not registered. The guest should implement a function called guest_dispatch_function to handle this.
62+
// The given function is not registered. The guest should implement a function called
63+
// guest_dispatch_function_v2 to handle this. Use the `guest_dispatch!` macro to define it.
7064

7165
// TODO: ideally we would define a default implementation of this with weak linkage so the guest is not required
7266
// to implement the function but its seems that weak linkage is an unstable feature so for now its probably better
7367
// to not do that.
7468
unsafe extern "Rust" {
75-
fn guest_dispatch_function(function_call: FunctionCall) -> Result<Vec<u8>>;
69+
fn guest_dispatch_function_v2(function_call: FunctionCall) -> Result<Vec<u8>>;
7670
}
7771

78-
unsafe { guest_dispatch_function(function_call) }
72+
unsafe { guest_dispatch_function_v2(function_call) }
7973
}
8074
}
8175

src/hyperlight_guest_bin/src/guest_function/definition.rs

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,23 @@ use hyperlight_common::func::{
2828
};
2929
use hyperlight_guest::error::{HyperlightGuestError, Result};
3030

31-
/// The definition of a function exposed from the guest to the host
32-
#[derive(Debug, Clone, PartialEq, Eq)]
33-
pub struct GuestFunctionDefinition {
31+
/// The function pointer type for Rust guest functions.
32+
pub type GuestFunc = fn(FunctionCall) -> Result<Vec<u8>>;
33+
34+
/// The definition of a function exposed from the guest to the host.
35+
///
36+
/// The type parameter `F` is the function pointer type. For Rust guests this
37+
/// is [`GuestFunc`]; the C API uses its own `CGuestFunc` type.
38+
#[derive(Debug, Clone)]
39+
pub struct GuestFunctionDefinition<F: Copy> {
3440
/// The function name
3541
pub function_name: String,
3642
/// The type of the parameter values for the host function call.
3743
pub parameter_types: Vec<ParameterType>,
3844
/// The type of the return value from the host function call
3945
pub return_type: ReturnType,
40-
/// The function pointer to the guest function
41-
pub function_pointer: usize,
46+
/// The function pointer to the guest function.
47+
pub function_pointer: F,
4248
}
4349

4450
/// Trait for functions that can be converted to a `fn(FunctionCall) -> Result<Vec<u8>>`
@@ -57,7 +63,7 @@ where
5763
fn into_guest_function(self) -> fn(FunctionCall) -> Result<Vec<u8>>;
5864
}
5965

60-
/// Trait for functions that can be converted to a `GuestFunctionDefinition`
66+
/// Trait for functions that can be converted to a `GuestFunctionDefinition<GuestFunc>`
6167
pub trait AsGuestFunctionDefinition<Output, Args>
6268
where
6369
Self: Function<Output, Args, HyperlightGuestError>,
@@ -66,7 +72,10 @@ where
6672
Args: ParameterTuple,
6773
{
6874
/// Get the `GuestFunctionDefinition` for this function
69-
fn as_guest_function_definition(&self, name: impl Into<String>) -> GuestFunctionDefinition;
75+
fn as_guest_function_definition(
76+
&self,
77+
name: impl Into<String>,
78+
) -> GuestFunctionDefinition<GuestFunc>;
7079
}
7180

7281
fn into_flatbuffer_result(value: ReturnValue) -> Vec<u8> {
@@ -147,11 +156,13 @@ where
147156
Args: ParameterTuple,
148157
Output: SupportedReturnType,
149158
{
150-
fn as_guest_function_definition(&self, name: impl Into<String>) -> GuestFunctionDefinition {
159+
fn as_guest_function_definition(
160+
&self,
161+
name: impl Into<String>,
162+
) -> GuestFunctionDefinition<GuestFunc> {
151163
let parameter_types = Args::TYPE.to_vec();
152164
let return_type = Output::TYPE;
153165
let function_pointer = self.into_guest_function();
154-
let function_pointer = function_pointer as usize;
155166

156167
GuestFunctionDefinition {
157168
function_name: name.into(),
@@ -164,13 +175,13 @@ where
164175

165176
for_each_tuple!(impl_host_function);
166177

167-
impl GuestFunctionDefinition {
178+
impl<F: Copy> GuestFunctionDefinition<F> {
168179
/// Create a new `GuestFunctionDefinition`.
169180
pub fn new(
170181
function_name: String,
171182
parameter_types: Vec<ParameterType>,
172183
return_type: ReturnType,
173-
function_pointer: usize,
184+
function_pointer: F,
174185
) -> Self {
175186
Self {
176187
function_name,
@@ -180,12 +191,12 @@ impl GuestFunctionDefinition {
180191
}
181192
}
182193

183-
/// Create a new `GuestFunctionDefinition` from a function that implements
184-
/// `AsGuestFunctionDefinition`.
194+
/// Create a new `GuestFunctionDefinition<GuestFunc>` from a function that
195+
/// implements `AsGuestFunctionDefinition`.
185196
pub fn from_fn<Output, Args>(
186197
function_name: String,
187198
function: impl AsGuestFunctionDefinition<Output, Args>,
188-
) -> Self
199+
) -> GuestFunctionDefinition<GuestFunc>
189200
where
190201
Args: ParameterTuple,
191202
Output: SupportedReturnType,

src/hyperlight_guest_bin/src/guest_function/register.rs

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,27 @@ use alloc::string::String;
1919

2020
use hyperlight_common::func::{ParameterTuple, SupportedReturnType};
2121

22-
use super::definition::GuestFunctionDefinition;
22+
use super::definition::{GuestFunc, GuestFunctionDefinition};
2323
use crate::REGISTERED_GUEST_FUNCTIONS;
2424
use crate::guest_function::definition::AsGuestFunctionDefinition;
2525

2626
/// Represents the functions that the guest exposes to the host.
27-
#[derive(Debug, Default, Clone)]
28-
pub struct GuestFunctionRegister {
27+
#[derive(Debug, Clone)]
28+
pub struct GuestFunctionRegister<F: Copy> {
2929
/// Currently registered guest functions
30-
guest_functions: BTreeMap<String, GuestFunctionDefinition>,
30+
guest_functions: BTreeMap<String, GuestFunctionDefinition<F>>,
3131
}
3232

33-
impl GuestFunctionRegister {
34-
/// Create a new `GuestFunctionDetails`.
33+
impl<F: Copy> Default for GuestFunctionRegister<F> {
34+
fn default() -> Self {
35+
Self {
36+
guest_functions: BTreeMap::new(),
37+
}
38+
}
39+
}
40+
41+
impl<F: Copy> GuestFunctionRegister<F> {
42+
/// Create a new `GuestFunctionRegister`.
3543
pub const fn new() -> Self {
3644
Self {
3745
guest_functions: BTreeMap::new(),
@@ -44,12 +52,19 @@ impl GuestFunctionRegister {
4452
/// otherwise the previous `GuestFunctionDefinition` is returned.
4553
pub fn register(
4654
&mut self,
47-
guest_function: GuestFunctionDefinition,
48-
) -> Option<GuestFunctionDefinition> {
55+
guest_function: GuestFunctionDefinition<F>,
56+
) -> Option<GuestFunctionDefinition<F>> {
4957
self.guest_functions
5058
.insert(guest_function.function_name.clone(), guest_function)
5159
}
5260

61+
/// Gets a `GuestFunctionDefinition` by its `name` field.
62+
pub fn get(&self, function_name: &str) -> Option<&GuestFunctionDefinition<F>> {
63+
self.guest_functions.get(function_name)
64+
}
65+
}
66+
67+
impl GuestFunctionRegister<GuestFunc> {
5368
pub fn register_fn<Output, Args>(
5469
&mut self,
5570
name: impl Into<String>,
@@ -61,14 +76,9 @@ impl GuestFunctionRegister {
6176
let gfd = f.as_guest_function_definition(name);
6277
self.register(gfd);
6378
}
64-
65-
/// Gets a `GuestFunctionDefinition` by its `name` field.
66-
pub fn get(&self, function_name: &str) -> Option<&GuestFunctionDefinition> {
67-
self.guest_functions.get(function_name)
68-
}
6979
}
7080

71-
pub fn register_function(function_definition: GuestFunctionDefinition) {
81+
pub fn register_function(function_definition: GuestFunctionDefinition<GuestFunc>) {
7282
unsafe {
7383
// This is currently safe, because we are single threaded, but we
7484
// should find a better way to do this, see issue #808

src/hyperlight_guest_bin/src/host_comm.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@ pub fn read_n_bytes_from_user_memory(num: u64) -> Result<Vec<u8>> {
8080
///
8181
/// This function requires memory to be setup to be used. In particular, the
8282
/// existence of the input and output memory regions.
83-
pub fn print_output_with_host_print(function_call: &FunctionCall) -> Result<Vec<u8>> {
83+
pub fn print_output_with_host_print(function_call: FunctionCall) -> Result<Vec<u8>> {
8484
let handle = unsafe { GUEST_HANDLE };
85-
if let ParameterValue::String(message) = function_call.parameters.clone().unwrap()[0].clone() {
85+
if let ParameterValue::String(message) = function_call.parameters.unwrap().remove(0) {
8686
let res = handle.call_host_function::<i32>(
8787
"HostPrint",
8888
Some(Vec::from(&[ParameterValue::String(message.to_string())])),

src/hyperlight_guest_bin/src/lib.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,41 @@ pub mod host_comm;
5151
pub mod memory;
5252
pub mod paging;
5353

54+
/// Defines the fallback dispatch function for guest function calls that were
55+
/// not registered via [`#[guest_function]`](crate::guest_function) or
56+
/// [`register_function`](crate::guest_function::register::register_function).
57+
///
58+
/// Registered guest functions are dispatched directly and do not go through
59+
/// this function. Only calls whose name has no registration end up here.
60+
///
61+
/// Use this macro instead of defining the symbol by hand so that signature
62+
/// changes in future versions of `hyperlight_guest_bin` produce a compile
63+
/// error.
64+
///
65+
/// The body receives a [`FunctionCall`](hyperlight_common::flatbuffer_wrappers::function_call::FunctionCall)
66+
/// by value and must return `Result<Vec<u8>>`.
67+
///
68+
/// The macro expansion references `hyperlight_common`, `hyperlight_guest`,
69+
/// and `alloc`. The calling crate must have these as direct dependencies
70+
/// (or `extern crate alloc` for `no_std` crates).
71+
///
72+
/// ```ignore
73+
/// guest_dispatch!(|function_call| {
74+
/// bail!(ErrorCode::GuestFunctionNotFound => "{}", function_call.function_name);
75+
/// });
76+
/// ```
77+
#[macro_export]
78+
macro_rules! guest_dispatch {
79+
(|$fc:ident| $body:expr) => {
80+
#[unsafe(no_mangle)]
81+
pub fn guest_dispatch_function_v2(
82+
$fc: ::hyperlight_common::flatbuffer_wrappers::function_call::FunctionCall,
83+
) -> ::hyperlight_guest::error::Result<::alloc::vec::Vec<u8>> {
84+
$body
85+
}
86+
};
87+
}
88+
5489
// Globals
5590
#[cfg(feature = "mem_profile")]
5691
struct ProfiledLockedHeap<const ORDER: usize>(LockedHeap<ORDER>);
@@ -116,7 +151,7 @@ pub(crate) static HEAP_ALLOCATOR: ProfiledLockedHeap<32> =
116151
ProfiledLockedHeap(LockedHeap::<32>::empty());
117152

118153
pub static mut GUEST_HANDLE: GuestHandle = GuestHandle::new();
119-
pub(crate) static mut REGISTERED_GUEST_FUNCTIONS: GuestFunctionRegister =
154+
pub(crate) static mut REGISTERED_GUEST_FUNCTIONS: GuestFunctionRegister<GuestFunc> =
120155
GuestFunctionRegister::new();
121156

122157
/// The size of one page in the host OS, which may have some impacts
@@ -278,3 +313,5 @@ pub mod __private {
278313

279314
#[cfg(feature = "macros")]
280315
pub use hyperlight_guest_macro::{guest_function, host_function};
316+
317+
use crate::guest_function::definition::GuestFunc;

src/hyperlight_guest_capi/src/dispatch.rs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ use alloc::boxed::Box;
1818
use alloc::slice;
1919
use alloc::vec::Vec;
2020
use core::ffi::{CStr, c_char};
21-
use core::mem;
2221

2322
use hyperlight_common::flatbuffer_wrappers::function_call::FunctionCall;
2423
use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterType, ReturnType};
@@ -29,7 +28,8 @@ use hyperlight_guest_bin::guest_function::register::GuestFunctionRegister;
2928
use hyperlight_guest_bin::host_comm::call_host_function_without_returning_result;
3029

3130
use crate::types::{FfiFunctionCall, FfiVec};
32-
static mut REGISTERED_C_GUEST_FUNCTIONS: GuestFunctionRegister = GuestFunctionRegister::new();
31+
static mut REGISTERED_C_GUEST_FUNCTIONS: GuestFunctionRegister<CGuestFunc> =
32+
GuestFunctionRegister::new();
3333

3434
type CGuestFunc = extern "C" fn(&FfiFunctionCall) -> Box<FfiVec>;
3535

@@ -40,7 +40,7 @@ unsafe extern "C" {
4040
}
4141

4242
#[unsafe(no_mangle)]
43-
pub fn guest_dispatch_function(function_call: FunctionCall) -> Result<Vec<u8>> {
43+
pub fn guest_dispatch_function_v2(function_call: FunctionCall) -> Result<Vec<u8>> {
4444
// Use &raw const to get an immutable reference to the static HashMap
4545
// this is to avoid the clippy warning "shared reference to mutable static"
4646
if let Some(registered_func) =
@@ -55,10 +55,7 @@ pub fn guest_dispatch_function(function_call: FunctionCall) -> Result<Vec<u8>> {
5555
registered_func.verify_parameters(&function_call_parameter_types)?;
5656

5757
let ffi_func_call = FfiFunctionCall::from_function_call(function_call)?;
58-
59-
let guest_func =
60-
unsafe { mem::transmute::<usize, CGuestFunc>(registered_func.function_pointer) };
61-
let function_result = guest_func(&ffi_func_call);
58+
let function_result = (registered_func.function_pointer)(&ffi_func_call);
6259

6360
unsafe { Ok(FfiVec::into_vec(*function_result)) }
6461
} else {
@@ -94,8 +91,7 @@ pub extern "C" fn hl_register_function_definition(
9491

9592
let func_params = unsafe { slice::from_raw_parts(params_type, param_no).to_vec() };
9693

97-
let func_def =
98-
GuestFunctionDefinition::new(func_name, func_params, return_type, func_ptr as usize);
94+
let func_def = GuestFunctionDefinition::new(func_name, func_params, return_type, func_ptr);
9995

10096
// Use &raw mut to get a mutable raw pointer, then dereference it
10197
// this is to avoid the clippy warning "shared reference to mutable static"

0 commit comments

Comments
 (0)