Skip to content

Commit d41ffeb

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 d41ffeb

File tree

11 files changed

+104
-71
lines changed

11 files changed

+104
-71
lines changed

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: 27 additions & 16 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> {
@@ -141,17 +150,19 @@ macro_rules! impl_host_function {
141150
};
142151
}
143152

144-
impl<F, Args, Output> AsGuestFunctionDefinition<Output, Args> for F
153+
impl<Func, Args, Output> AsGuestFunctionDefinition<Output, Args> for Func
145154
where
146-
F: IntoGuestFunction<Output, Args>,
155+
Func: IntoGuestFunction<Output, Args>,
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: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,19 @@ 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 = GuestFunc> {
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> GuestFunctionRegister<F> {
34+
/// Create a new `GuestFunctionRegister`.
3535
pub const fn new() -> Self {
3636
Self {
3737
guest_functions: BTreeMap::new(),
@@ -44,12 +44,19 @@ impl GuestFunctionRegister {
4444
/// otherwise the previous `GuestFunctionDefinition` is returned.
4545
pub fn register(
4646
&mut self,
47-
guest_function: GuestFunctionDefinition,
48-
) -> Option<GuestFunctionDefinition> {
47+
guest_function: GuestFunctionDefinition<F>,
48+
) -> Option<GuestFunctionDefinition<F>> {
4949
self.guest_functions
5050
.insert(guest_function.function_name.clone(), guest_function)
5151
}
5252

53+
/// Gets a `GuestFunctionDefinition` by its `name` field.
54+
pub fn get(&self, function_name: &str) -> Option<&GuestFunctionDefinition<F>> {
55+
self.guest_functions.get(function_name)
56+
}
57+
}
58+
59+
impl GuestFunctionRegister<GuestFunc> {
5360
pub fn register_fn<Output, Args>(
5461
&mut self,
5562
name: impl Into<String>,
@@ -61,14 +68,9 @@ impl GuestFunctionRegister {
6168
let gfd = f.as_guest_function_definition(name);
6269
self.register(gfd);
6370
}
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-
}
6971
}
7072

71-
pub fn register_function(function_definition: GuestFunctionDefinition) {
73+
pub fn register_function(function_definition: GuestFunctionDefinition<GuestFunc>) {
7274
unsafe {
7375
// This is currently safe, because we are single threaded, but we
7476
// 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: 35 additions & 0 deletions
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>);

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"

src/tests/rust_guests/simpleguest/src/main.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ use hyperlight_common::flatbuffer_wrappers::util::get_flatbuffer_result;
4444
use hyperlight_guest::error::{HyperlightGuestError, Result};
4545
use hyperlight_guest::exit::{abort_with_code, abort_with_code_and_message};
4646
use hyperlight_guest_bin::exception::arch::{Context, ExceptionInfo};
47-
use hyperlight_guest_bin::guest_function::definition::GuestFunctionDefinition;
47+
use hyperlight_guest_bin::guest_function::definition::{GuestFunc, GuestFunctionDefinition};
4848
use hyperlight_guest_bin::guest_function::register::register_function;
4949
use hyperlight_guest_bin::host_comm::{
5050
call_host_function, call_host_function_without_returning_result, get_host_return_value_raw,
@@ -652,11 +652,11 @@ fn call_host_expect_error(hostfuncname: String) -> Result<()> {
652652
#[no_mangle]
653653
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
654654
pub extern "C" fn hyperlight_main() {
655-
let print_output_def = GuestFunctionDefinition::new(
655+
let print_output_def = GuestFunctionDefinition::<GuestFunc>::new(
656656
"PrintOutputWithHostPrint".to_string(),
657657
Vec::from(&[ParameterType::String]),
658658
ReturnType::Int,
659-
print_output_with_host_print as usize,
659+
print_output_with_host_print,
660660
);
661661
register_function(print_output_def);
662662
}
@@ -814,9 +814,7 @@ fn fuzz_host_function(func: FunctionCall) -> Result<Vec<u8>> {
814814
}
815815
}
816816

817-
#[no_mangle]
818-
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
819-
pub fn guest_dispatch_function(function_call: FunctionCall) -> Result<Vec<u8>> {
817+
hyperlight_guest_bin::guest_dispatch!(|function_call| {
820818
// This test checks the stack behavior of the input/output buffer
821819
// by calling the host before serializing the function call.
822820
// If the stack is not working correctly, the input or output buffer will be
@@ -858,4 +856,4 @@ pub fn guest_dispatch_function(function_call: FunctionCall) -> Result<Vec<u8>> {
858856
}
859857

860858
Ok(get_flatbuffer_result(99))
861-
}
859+
});

0 commit comments

Comments
 (0)