Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<u8>>` to `fn(fc: FunctionCall) -> Result<Vec<u8>>`.
- 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
Expand Down
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<u8>> {
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):
Expand Down
3 changes: 1 addition & 2 deletions docs/how-to-build-a-hyperlight-guest-binary.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<u8>>`
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.
Expand Down
4 changes: 2 additions & 2 deletions src/hyperlight_component_util/src/guest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: Guest>(fc: &::hyperlight_common::flatbuffer_wrappers::function_call::FunctionCall) -> ::hyperlight_guest::error::Result<::alloc::vec::Vec<u8>> {
fn #n<T: Guest>(fc: ::hyperlight_common::flatbuffer_wrappers::function_call::FunctionCall) -> ::hyperlight_guest::error::Result<::alloc::vec::Vec<u8>> {
<T as Guest>::with_guest_state(|state| {
#(#pds)*
#(#get_instance)*
Expand All @@ -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::<T> as usize
#n::<T>
)
);
}
Expand Down
16 changes: 5 additions & 11 deletions src/hyperlight_guest_bin/src/guest_function/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ use tracing::{Span, instrument};

use crate::{GUEST_HANDLE, REGISTERED_GUEST_FUNCTIONS};

type GuestFunc = fn(&FunctionCall) -> Result<Vec<u8>>;

#[instrument(skip_all, parent = Span::current(), level= "Trace")]
pub(crate) fn call_guest_function(function_call: FunctionCall) -> Result<Vec<u8>> {
// Validate this is a Guest Function Call
Expand Down Expand Up @@ -59,23 +57,19 @@ pub(crate) fn call_guest_function(function_call: FunctionCall) -> Result<Vec<u8>
// 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::<usize, GuestFunc>(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<Vec<u8>>;
fn guest_dispatch_function_v2(function_call: FunctionCall) -> Result<Vec<u8>>;
}

unsafe { guest_dispatch_function(function_call) }
unsafe { guest_dispatch_function_v2(function_call) }
}
}

Expand Down
51 changes: 31 additions & 20 deletions src/hyperlight_guest_bin/src/guest_function/definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<u8>>;

/// 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<F: Copy> {
/// The function name
pub function_name: String,
/// The type of the parameter values for the host function call.
pub parameter_types: Vec<ParameterType>,
/// 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<Vec<u8>>`
/// Trait for functions that can be converted to a `fn(FunctionCall) -> Result<Vec<u8>>`
#[doc(hidden)]
pub trait IntoGuestFunction<Output, Args>
where
Expand All @@ -53,11 +59,11 @@ where
#[doc(hidden)]
const ASSERT_ZERO_SIZED: ();

/// Convert the function into a `fn(&FunctionCall) -> Result<Vec<u8>>`
fn into_guest_function(self) -> fn(&FunctionCall) -> Result<Vec<u8>>;
/// Convert the function into a `fn(FunctionCall) -> Result<Vec<u8>>`
fn into_guest_function(self) -> fn(FunctionCall) -> Result<Vec<u8>>;
}

/// Trait for functions that can be converted to a `GuestFunctionDefinition`
/// Trait for functions that can be converted to a `GuestFunctionDefinition<GuestFunc>`
pub trait AsGuestFunctionDefinition<Output, Args>
where
Self: Function<Output, Args, HyperlightGuestError>,
Expand All @@ -66,7 +72,10 @@ where
Args: ParameterTuple,
{
/// Get the `GuestFunctionDefinition` for this function
fn as_guest_function_definition(&self, name: impl Into<String>) -> GuestFunctionDefinition;
fn as_guest_function_definition(
&self,
name: impl Into<String>,
) -> GuestFunctionDefinition<GuestFunc>;
}

fn into_flatbuffer_result(value: ReturnValue) -> Vec<u8> {
Expand Down Expand Up @@ -124,14 +133,14 @@ macro_rules! impl_host_function {
assert!(core::mem::size_of::<Self>() == 0)
};

fn into_guest_function(self) -> fn(&FunctionCall) -> Result<Vec<u8>> {
|fc: &FunctionCall| {
fn into_guest_function(self) -> fn(FunctionCall) -> Result<Vec<u8>> {
|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::<F>() };
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::<R::ReturnType, ($($P,)*), HyperlightGuestError>::call(&this, params)?;
Ok(into_flatbuffer_result(result.into_value()))
Expand All @@ -147,11 +156,13 @@ where
Args: ParameterTuple,
Output: SupportedReturnType,
{
fn as_guest_function_definition(&self, name: impl Into<String>) -> GuestFunctionDefinition {
fn as_guest_function_definition(
&self,
name: impl Into<String>,
) -> GuestFunctionDefinition<GuestFunc> {
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(),
Expand All @@ -164,13 +175,13 @@ where

for_each_tuple!(impl_host_function);

impl GuestFunctionDefinition {
impl<F: Copy> GuestFunctionDefinition<F> {
/// Create a new `GuestFunctionDefinition`.
pub fn new(
function_name: String,
parameter_types: Vec<ParameterType>,
return_type: ReturnType,
function_pointer: usize,
function_pointer: F,
) -> Self {
Self {
function_name,
Expand All @@ -180,12 +191,12 @@ impl GuestFunctionDefinition {
}
}

/// Create a new `GuestFunctionDefinition` from a function that implements
/// `AsGuestFunctionDefinition`.
/// Create a new `GuestFunctionDefinition<GuestFunc>` from a function that
/// implements `AsGuestFunctionDefinition`.
pub fn from_fn<Output, Args>(
function_name: String,
function: impl AsGuestFunctionDefinition<Output, Args>,
) -> Self
) -> GuestFunctionDefinition<GuestFunc>
where
Args: ParameterTuple,
Output: SupportedReturnType,
Expand Down
38 changes: 24 additions & 14 deletions src/hyperlight_guest_bin/src/guest_function/register.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<F: Copy> {
/// Currently registered guest functions
guest_functions: BTreeMap<String, GuestFunctionDefinition>,
guest_functions: BTreeMap<String, GuestFunctionDefinition<F>>,
}

impl GuestFunctionRegister {
/// Create a new `GuestFunctionDetails`.
impl<F: Copy> Default for GuestFunctionRegister<F> {
fn default() -> Self {
Self {
guest_functions: BTreeMap::new(),
}
}
}

impl<F: Copy> GuestFunctionRegister<F> {
/// Create a new `GuestFunctionRegister`.
pub const fn new() -> Self {
Self {
guest_functions: BTreeMap::new(),
Expand All @@ -44,12 +52,19 @@ impl GuestFunctionRegister {
/// otherwise the previous `GuestFunctionDefinition` is returned.
pub fn register(
&mut self,
guest_function: GuestFunctionDefinition,
) -> Option<GuestFunctionDefinition> {
guest_function: GuestFunctionDefinition<F>,
) -> Option<GuestFunctionDefinition<F>> {
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<F>> {
self.guest_functions.get(function_name)
}
}

impl GuestFunctionRegister<GuestFunc> {
pub fn register_fn<Output, Args>(
&mut self,
name: impl Into<String>,
Expand All @@ -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<GuestFunc>) {
unsafe {
// This is currently safe, because we are single threaded, but we
// should find a better way to do this, see issue #808
Expand Down
4 changes: 2 additions & 2 deletions src/hyperlight_guest_bin/src/host_comm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ pub fn read_n_bytes_from_user_memory(num: u64) -> Result<Vec<u8>> {
///
/// 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<Vec<u8>> {
pub fn print_output_with_host_print(function_call: FunctionCall) -> Result<Vec<u8>> {
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::<i32>(
"HostPrint",
Some(Vec::from(&[ParameterValue::String(message.to_string())])),
Expand Down
39 changes: 38 additions & 1 deletion src/hyperlight_guest_bin/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<u8>>`.
///
/// 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<u8>> {
$body
}
};
}

// Globals
#[cfg(feature = "mem_profile")]
struct ProfiledLockedHeap<const ORDER: usize>(LockedHeap<ORDER>);
Expand Down Expand Up @@ -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<GuestFunc> =
GuestFunctionRegister::new();

/// The size of one page in the host OS, which may have some impacts
Expand Down Expand Up @@ -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;
Loading