diff --git a/CHANGELOG.md b/CHANGELOG.md index 50941edc5..0d4523389 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Prerelease] - Unreleased +### Changed +* **Breaking:** Change `guest_dispatch_function` signature by @ludfjig in https://github.com/hyperlight-dev/hyperlight/pull/1241 + - The fallback dispatch function now receives `FunctionCall` by value instead of by reference. + - Rename `guest_dispatch_function` to `guest_dispatch_function_v2` and change the signature from `fn(fc: &FunctionCall) -> Result>` to `fn(fc: FunctionCall) -> Result>`. + - Rust guests are encouraged to use the new `hyperlight_guest_bin::guest_dispatch!` macro instead of defining the symbol by hand. + - `GuestFunctionDefinition::new` now takes a typed function pointer instead of `usize`, so callers passing `func as usize` will get a compile error. + ## [v0.12.0] - 2025-12-09 ### Fixed diff --git a/README.md b/README.md index 0312124c1..8315196f8 100644 --- a/README.md +++ b/README.md @@ -97,11 +97,10 @@ pub extern "C" fn hyperlight_main() { // any initialization code goes here } -#[no_mangle] -pub fn guest_dispatch_function(function_call: FunctionCall) -> Result> { +hyperlight_guest_bin::guest_dispatch!(|function_call| { let function_name = function_call.function_name; bail!(ErrorCode::GuestFunctionNotFound => "{function_name}"); -} +}); ``` Build the guest using [cargo-hyperlight](https://github.com/hyperlight-dev/cargo-hyperlight): diff --git a/docs/how-to-build-a-hyperlight-guest-binary.md b/docs/how-to-build-a-hyperlight-guest-binary.md index a76e43d4b..a9551bebd 100644 --- a/docs/how-to-build-a-hyperlight-guest-binary.md +++ b/docs/how-to-build-a-hyperlight-guest-binary.md @@ -9,8 +9,7 @@ binary can be used with Hyperlight: `pub fn hyperlight_main()` - Hyperlight expects `hl_Vec* c_guest_dispatch_function(const hl_FunctionCall *functioncall)` or - `pub fn guest_dispatch_function(function_call: FunctionCall) -> Result>` - to be defined in the binary so that in case the host calls a function that is + the `guest_dispatch!` macro (Rust) to be used in the binary so that in case the host calls a function that is not registered by the guest, this function is called instead. - to be callable by the host, a function needs to be registered by the guest in the `hyperlight_main` function. diff --git a/src/hyperlight_component_util/src/guest.rs b/src/hyperlight_component_util/src/guest.rs index 09e909034..32f534fcd 100644 --- a/src/hyperlight_component_util/src/guest.rs +++ b/src/hyperlight_component_util/src/guest.rs @@ -210,7 +210,7 @@ fn emit_export_extern_decl<'a, 'b, 'c>( let marshal_result = emit_hl_marshal_result(s, ret.clone(), &ft.result); let trait_path = s.cur_trait_path(); quote! { - fn #n(fc: &::hyperlight_common::flatbuffer_wrappers::function_call::FunctionCall) -> ::hyperlight_guest::error::Result<::alloc::vec::Vec> { + fn #n(fc: ::hyperlight_common::flatbuffer_wrappers::function_call::FunctionCall) -> ::hyperlight_guest::error::Result<::alloc::vec::Vec> { ::with_guest_state(|state| { #(#pds)* #(#get_instance)* @@ -223,7 +223,7 @@ fn emit_export_extern_decl<'a, 'b, 'c>( ::alloc::string::ToString::to_string(#fname), ::alloc::vec![#(#pts),*], ::hyperlight_common::flatbuffer_wrappers::function_types::ReturnType::VecBytes, - #n:: as usize + #n:: ) ); } diff --git a/src/hyperlight_guest_bin/src/guest_function/call.rs b/src/hyperlight_guest_bin/src/guest_function/call.rs index c460b06c6..479fb642e 100644 --- a/src/hyperlight_guest_bin/src/guest_function/call.rs +++ b/src/hyperlight_guest_bin/src/guest_function/call.rs @@ -27,8 +27,6 @@ use tracing::{Span, instrument}; use crate::{GUEST_HANDLE, REGISTERED_GUEST_FUNCTIONS}; -type GuestFunc = fn(&FunctionCall) -> Result>; - #[instrument(skip_all, parent = Span::current(), level= "Trace")] pub(crate) fn call_guest_function(function_call: FunctionCall) -> Result> { // Validate this is a Guest Function Call @@ -59,23 +57,19 @@ pub(crate) fn call_guest_function(function_call: FunctionCall) -> Result // Verify that the function call has the correct parameter types and length. registered_function_definition.verify_parameters(&function_call_parameter_types)?; - let p_function = unsafe { - let function_pointer = registered_function_definition.function_pointer; - core::mem::transmute::(function_pointer) - }; - - p_function(&function_call) + (registered_function_definition.function_pointer)(function_call) } else { - // The given function is not registered. The guest should implement a function called guest_dispatch_function to handle this. + // The given function is not registered. The guest should implement a function called + // guest_dispatch_function_v2 to handle this. Use the `guest_dispatch!` macro to define it. // TODO: ideally we would define a default implementation of this with weak linkage so the guest is not required // to implement the function but its seems that weak linkage is an unstable feature so for now its probably better // to not do that. unsafe extern "Rust" { - fn guest_dispatch_function(function_call: FunctionCall) -> Result>; + fn guest_dispatch_function_v2(function_call: FunctionCall) -> Result>; } - unsafe { guest_dispatch_function(function_call) } + unsafe { guest_dispatch_function_v2(function_call) } } } diff --git a/src/hyperlight_guest_bin/src/guest_function/definition.rs b/src/hyperlight_guest_bin/src/guest_function/definition.rs index f889f4fb6..c96f2e9fe 100644 --- a/src/hyperlight_guest_bin/src/guest_function/definition.rs +++ b/src/hyperlight_guest_bin/src/guest_function/definition.rs @@ -28,20 +28,26 @@ use hyperlight_common::func::{ }; use hyperlight_guest::error::{HyperlightGuestError, Result}; -/// The definition of a function exposed from the guest to the host -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct GuestFunctionDefinition { +/// The function pointer type for Rust guest functions. +pub type GuestFunc = fn(FunctionCall) -> Result>; + +/// The definition of a function exposed from the guest to the host. +/// +/// The type parameter `F` is the function pointer type. For Rust guests this +/// is [`GuestFunc`]; the C API uses its own `CGuestFunc` type. +#[derive(Debug, Clone)] +pub struct GuestFunctionDefinition { /// The function name pub function_name: String, /// The type of the parameter values for the host function call. pub parameter_types: Vec, /// The type of the return value from the host function call pub return_type: ReturnType, - /// The function pointer to the guest function - pub function_pointer: usize, + /// The function pointer to the guest function. + pub function_pointer: F, } -/// Trait for functions that can be converted to a `fn(&FunctionCall) -> Result>` +/// Trait for functions that can be converted to a `fn(FunctionCall) -> Result>` #[doc(hidden)] pub trait IntoGuestFunction where @@ -53,11 +59,11 @@ where #[doc(hidden)] const ASSERT_ZERO_SIZED: (); - /// Convert the function into a `fn(&FunctionCall) -> Result>` - fn into_guest_function(self) -> fn(&FunctionCall) -> Result>; + /// Convert the function into a `fn(FunctionCall) -> Result>` + fn into_guest_function(self) -> fn(FunctionCall) -> Result>; } -/// Trait for functions that can be converted to a `GuestFunctionDefinition` +/// Trait for functions that can be converted to a `GuestFunctionDefinition` pub trait AsGuestFunctionDefinition where Self: Function, @@ -66,7 +72,10 @@ where Args: ParameterTuple, { /// Get the `GuestFunctionDefinition` for this function - fn as_guest_function_definition(&self, name: impl Into) -> GuestFunctionDefinition; + fn as_guest_function_definition( + &self, + name: impl Into, + ) -> GuestFunctionDefinition; } fn into_flatbuffer_result(value: ReturnValue) -> Vec { @@ -124,14 +133,14 @@ macro_rules! impl_host_function { assert!(core::mem::size_of::() == 0) }; - fn into_guest_function(self) -> fn(&FunctionCall) -> Result> { - |fc: &FunctionCall| { + fn into_guest_function(self) -> fn(FunctionCall) -> Result> { + |fc: FunctionCall| { // SAFETY: This is safe because: // 1. F is zero-sized (enforced by the ASSERT_ZERO_SIZED const). // 2. F has no Drop impl (enforced by the Copy bound). // Therefore, creating an instance of F is safe. let this = unsafe { core::mem::zeroed::() }; - let params = fc.parameters.clone().unwrap_or_default(); + let params = fc.parameters.unwrap_or_default(); let params = <($($P,)*) as ParameterTuple>::from_value(params)?; let result = Function::::call(&this, params)?; Ok(into_flatbuffer_result(result.into_value())) @@ -147,11 +156,13 @@ where Args: ParameterTuple, Output: SupportedReturnType, { - fn as_guest_function_definition(&self, name: impl Into) -> GuestFunctionDefinition { + fn as_guest_function_definition( + &self, + name: impl Into, + ) -> GuestFunctionDefinition { let parameter_types = Args::TYPE.to_vec(); let return_type = Output::TYPE; let function_pointer = self.into_guest_function(); - let function_pointer = function_pointer as usize; GuestFunctionDefinition { function_name: name.into(), @@ -164,13 +175,13 @@ where for_each_tuple!(impl_host_function); -impl GuestFunctionDefinition { +impl GuestFunctionDefinition { /// Create a new `GuestFunctionDefinition`. pub fn new( function_name: String, parameter_types: Vec, return_type: ReturnType, - function_pointer: usize, + function_pointer: F, ) -> Self { Self { function_name, @@ -180,12 +191,12 @@ impl GuestFunctionDefinition { } } - /// Create a new `GuestFunctionDefinition` from a function that implements - /// `AsGuestFunctionDefinition`. + /// Create a new `GuestFunctionDefinition` from a function that + /// implements `AsGuestFunctionDefinition`. pub fn from_fn( function_name: String, function: impl AsGuestFunctionDefinition, - ) -> Self + ) -> GuestFunctionDefinition where Args: ParameterTuple, Output: SupportedReturnType, diff --git a/src/hyperlight_guest_bin/src/guest_function/register.rs b/src/hyperlight_guest_bin/src/guest_function/register.rs index ec64bda2e..96cf21099 100644 --- a/src/hyperlight_guest_bin/src/guest_function/register.rs +++ b/src/hyperlight_guest_bin/src/guest_function/register.rs @@ -19,19 +19,27 @@ use alloc::string::String; use hyperlight_common::func::{ParameterTuple, SupportedReturnType}; -use super::definition::GuestFunctionDefinition; +use super::definition::{GuestFunc, GuestFunctionDefinition}; use crate::REGISTERED_GUEST_FUNCTIONS; use crate::guest_function::definition::AsGuestFunctionDefinition; /// Represents the functions that the guest exposes to the host. -#[derive(Debug, Default, Clone)] -pub struct GuestFunctionRegister { +#[derive(Debug, Clone)] +pub struct GuestFunctionRegister { /// Currently registered guest functions - guest_functions: BTreeMap, + guest_functions: BTreeMap>, } -impl GuestFunctionRegister { - /// Create a new `GuestFunctionDetails`. +impl Default for GuestFunctionRegister { + fn default() -> Self { + Self { + guest_functions: BTreeMap::new(), + } + } +} + +impl GuestFunctionRegister { + /// Create a new `GuestFunctionRegister`. pub const fn new() -> Self { Self { guest_functions: BTreeMap::new(), @@ -44,12 +52,19 @@ impl GuestFunctionRegister { /// otherwise the previous `GuestFunctionDefinition` is returned. pub fn register( &mut self, - guest_function: GuestFunctionDefinition, - ) -> Option { + guest_function: GuestFunctionDefinition, + ) -> Option> { self.guest_functions .insert(guest_function.function_name.clone(), guest_function) } + /// Gets a `GuestFunctionDefinition` by its `name` field. + pub fn get(&self, function_name: &str) -> Option<&GuestFunctionDefinition> { + self.guest_functions.get(function_name) + } +} + +impl GuestFunctionRegister { pub fn register_fn( &mut self, name: impl Into, @@ -61,14 +76,9 @@ impl GuestFunctionRegister { let gfd = f.as_guest_function_definition(name); self.register(gfd); } - - /// Gets a `GuestFunctionDefinition` by its `name` field. - pub fn get(&self, function_name: &str) -> Option<&GuestFunctionDefinition> { - self.guest_functions.get(function_name) - } } -pub fn register_function(function_definition: GuestFunctionDefinition) { +pub fn register_function(function_definition: GuestFunctionDefinition) { unsafe { // This is currently safe, because we are single threaded, but we // should find a better way to do this, see issue #808 diff --git a/src/hyperlight_guest_bin/src/host_comm.rs b/src/hyperlight_guest_bin/src/host_comm.rs index 23951e06b..743e797a7 100644 --- a/src/hyperlight_guest_bin/src/host_comm.rs +++ b/src/hyperlight_guest_bin/src/host_comm.rs @@ -80,9 +80,9 @@ pub fn read_n_bytes_from_user_memory(num: u64) -> Result> { /// /// This function requires memory to be setup to be used. In particular, the /// existence of the input and output memory regions. -pub fn print_output_with_host_print(function_call: &FunctionCall) -> Result> { +pub fn print_output_with_host_print(function_call: FunctionCall) -> Result> { let handle = unsafe { GUEST_HANDLE }; - if let ParameterValue::String(message) = function_call.parameters.clone().unwrap()[0].clone() { + if let ParameterValue::String(message) = function_call.parameters.unwrap().remove(0) { let res = handle.call_host_function::( "HostPrint", Some(Vec::from(&[ParameterValue::String(message.to_string())])), diff --git a/src/hyperlight_guest_bin/src/lib.rs b/src/hyperlight_guest_bin/src/lib.rs index 69ffc37af..83f611264 100644 --- a/src/hyperlight_guest_bin/src/lib.rs +++ b/src/hyperlight_guest_bin/src/lib.rs @@ -51,6 +51,41 @@ pub mod host_comm; pub mod memory; pub mod paging; +/// Defines the fallback dispatch function for guest function calls that were +/// not registered via [`#[guest_function]`](crate::guest_function) or +/// [`register_function`](crate::guest_function::register::register_function). +/// +/// Registered guest functions are dispatched directly and do not go through +/// this function. Only calls whose name has no registration end up here. +/// +/// Use this macro instead of defining the symbol by hand so that signature +/// changes in future versions of `hyperlight_guest_bin` produce a compile +/// error. +/// +/// The body receives a [`FunctionCall`](hyperlight_common::flatbuffer_wrappers::function_call::FunctionCall) +/// by value and must return `Result>`. +/// +/// The macro expansion references `hyperlight_common`, `hyperlight_guest`, +/// and `alloc`. The calling crate must have these as direct dependencies +/// (or `extern crate alloc` for `no_std` crates). +/// +/// ```ignore +/// guest_dispatch!(|function_call| { +/// bail!(ErrorCode::GuestFunctionNotFound => "{}", function_call.function_name); +/// }); +/// ``` +#[macro_export] +macro_rules! guest_dispatch { + (|$fc:ident| $body:expr) => { + #[unsafe(no_mangle)] + pub fn guest_dispatch_function_v2( + $fc: ::hyperlight_common::flatbuffer_wrappers::function_call::FunctionCall, + ) -> ::hyperlight_guest::error::Result<::alloc::vec::Vec> { + $body + } + }; +} + // Globals #[cfg(feature = "mem_profile")] struct ProfiledLockedHeap(LockedHeap); @@ -116,7 +151,7 @@ pub(crate) static HEAP_ALLOCATOR: ProfiledLockedHeap<32> = ProfiledLockedHeap(LockedHeap::<32>::empty()); pub static mut GUEST_HANDLE: GuestHandle = GuestHandle::new(); -pub(crate) static mut REGISTERED_GUEST_FUNCTIONS: GuestFunctionRegister = +pub(crate) static mut REGISTERED_GUEST_FUNCTIONS: GuestFunctionRegister = GuestFunctionRegister::new(); /// The size of one page in the host OS, which may have some impacts @@ -278,3 +313,5 @@ pub mod __private { #[cfg(feature = "macros")] pub use hyperlight_guest_macro::{guest_function, host_function}; + +use crate::guest_function::definition::GuestFunc; diff --git a/src/hyperlight_guest_capi/src/dispatch.rs b/src/hyperlight_guest_capi/src/dispatch.rs index 953cc48b6..6400ca80a 100644 --- a/src/hyperlight_guest_capi/src/dispatch.rs +++ b/src/hyperlight_guest_capi/src/dispatch.rs @@ -18,18 +18,17 @@ use alloc::boxed::Box; use alloc::slice; use alloc::vec::Vec; use core::ffi::{CStr, c_char}; -use core::mem; -use hyperlight_common::flatbuffer_wrappers::function_call::FunctionCall; use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterType, ReturnType}; use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; -use hyperlight_guest::error::{HyperlightGuestError, Result}; +use hyperlight_guest::error::HyperlightGuestError; use hyperlight_guest_bin::guest_function::definition::GuestFunctionDefinition; use hyperlight_guest_bin::guest_function::register::GuestFunctionRegister; use hyperlight_guest_bin::host_comm::call_host_function_without_returning_result; use crate::types::{FfiFunctionCall, FfiVec}; -static mut REGISTERED_C_GUEST_FUNCTIONS: GuestFunctionRegister = GuestFunctionRegister::new(); +static mut REGISTERED_C_GUEST_FUNCTIONS: GuestFunctionRegister = + GuestFunctionRegister::new(); type CGuestFunc = extern "C" fn(&FfiFunctionCall) -> Box; @@ -39,8 +38,7 @@ unsafe extern "C" { fn c_guest_dispatch_function(function_call: &FfiFunctionCall) -> *mut FfiVec; } -#[unsafe(no_mangle)] -pub fn guest_dispatch_function(function_call: FunctionCall) -> Result> { +hyperlight_guest_bin::guest_dispatch!(|function_call| { // Use &raw const to get an immutable reference to the static HashMap // this is to avoid the clippy warning "shared reference to mutable static" if let Some(registered_func) = @@ -55,10 +53,7 @@ pub fn guest_dispatch_function(function_call: FunctionCall) -> Result> { registered_func.verify_parameters(&function_call_parameter_types)?; let ffi_func_call = FfiFunctionCall::from_function_call(function_call)?; - - let guest_func = - unsafe { mem::transmute::(registered_func.function_pointer) }; - let function_result = guest_func(&ffi_func_call); + let function_result = (registered_func.function_pointer)(&ffi_func_call); unsafe { Ok(FfiVec::into_vec(*function_result)) } } else { @@ -80,7 +75,7 @@ pub fn guest_dispatch_function(function_call: FunctionCall) -> Result> { Ok(unsafe { FfiVec::into_vec(*result) }) } } -} +}); #[unsafe(no_mangle)] pub extern "C" fn hl_register_function_definition( @@ -94,8 +89,7 @@ pub extern "C" fn hl_register_function_definition( let func_params = unsafe { slice::from_raw_parts(params_type, param_no).to_vec() }; - let func_def = - GuestFunctionDefinition::new(func_name, func_params, return_type, func_ptr as usize); + let func_def = GuestFunctionDefinition::new(func_name, func_params, return_type, func_ptr); // Use &raw mut to get a mutable raw pointer, then dereference it // this is to avoid the clippy warning "shared reference to mutable static" diff --git a/src/tests/rust_guests/simpleguest/src/main.rs b/src/tests/rust_guests/simpleguest/src/main.rs index 80f734224..839fa3cf5 100644 --- a/src/tests/rust_guests/simpleguest/src/main.rs +++ b/src/tests/rust_guests/simpleguest/src/main.rs @@ -44,7 +44,7 @@ use hyperlight_common::flatbuffer_wrappers::util::get_flatbuffer_result; use hyperlight_guest::error::{HyperlightGuestError, Result}; use hyperlight_guest::exit::{abort_with_code, abort_with_code_and_message}; use hyperlight_guest_bin::exception::arch::{Context, ExceptionInfo}; -use hyperlight_guest_bin::guest_function::definition::GuestFunctionDefinition; +use hyperlight_guest_bin::guest_function::definition::{GuestFunc, GuestFunctionDefinition}; use hyperlight_guest_bin::guest_function::register::register_function; use hyperlight_guest_bin::host_comm::{ 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<()> { #[no_mangle] #[instrument(skip_all, parent = Span::current(), level= "Trace")] pub extern "C" fn hyperlight_main() { - let print_output_def = GuestFunctionDefinition::new( + let print_output_def = GuestFunctionDefinition::::new( "PrintOutputWithHostPrint".to_string(), Vec::from(&[ParameterType::String]), ReturnType::Int, - print_output_with_host_print as usize, + print_output_with_host_print, ); register_function(print_output_def); } @@ -814,9 +814,7 @@ fn fuzz_host_function(func: FunctionCall) -> Result> { } } -#[no_mangle] -#[instrument(skip_all, parent = Span::current(), level= "Trace")] -pub fn guest_dispatch_function(function_call: FunctionCall) -> Result> { +hyperlight_guest_bin::guest_dispatch!(|function_call| { // This test checks the stack behavior of the input/output buffer // by calling the host before serializing the function call. // 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> { } Ok(get_flatbuffer_result(99)) -} +}); diff --git a/src/tests/rust_guests/witguest/src/main.rs b/src/tests/rust_guests/witguest/src/main.rs index 307d9cca6..c82e10def 100644 --- a/src/tests/rust_guests/witguest/src/main.rs +++ b/src/tests/rust_guests/witguest/src/main.rs @@ -233,13 +233,12 @@ pub extern "C" fn hyperlight_main() { } use ::alloc::vec::Vec; -use ::hyperlight_common::flatbuffer_wrappers::function_call::FunctionCall; use ::hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode; -use ::hyperlight_guest::error::{HyperlightGuestError, Result}; -#[no_mangle] -pub fn guest_dispatch_function(function_call: FunctionCall) -> Result> { +use ::hyperlight_guest::error::HyperlightGuestError; + +hyperlight_guest_bin::guest_dispatch!(|function_call| { Err(HyperlightGuestError::new( ErrorCode::GuestFunctionNotFound, function_call.function_name.clone(), )) -} +});