Skip to main content

hyperlight_guest_bin/guest_function/
definition.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2025 The Hyperlight Authors.
3
4use alloc::format;
5use alloc::string::String;
6use alloc::vec::Vec;
7
8use hyperlight_common::flatbuffer_wrappers::function_call::FunctionCall;
9use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterType, ReturnType};
10use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode;
11use hyperlight_common::flatbuffer_wrappers::util::get_flatbuffer_result;
12use hyperlight_common::for_each_tuple;
13use hyperlight_common::func::{
14    Function, ParameterTuple, ResultType, ReturnValue, SupportedReturnType,
15};
16use hyperlight_guest::error::{HyperlightGuestError, Result};
17
18/// The function pointer type for Rust guest functions.
19pub type GuestFunc = fn(FunctionCall) -> Result<Vec<u8>>;
20
21/// The definition of a function exposed from the guest to the host.
22///
23/// The type parameter `F` is the function pointer type. For Rust guests this
24/// is [`GuestFunc`]; the C API uses its own `CGuestFunc` type.
25#[derive(Debug, Clone)]
26pub struct GuestFunctionDefinition<F: Copy> {
27    /// The function name
28    pub function_name: String,
29    /// The type of the parameter values for the host function call.
30    pub parameter_types: Vec<ParameterType>,
31    /// The type of the return value from the host function call
32    pub return_type: ReturnType,
33    /// The function pointer to the guest function.
34    pub function_pointer: F,
35}
36
37/// Trait for functions that can be converted to a `fn(FunctionCall) -> Result<Vec<u8>>`
38#[doc(hidden)]
39pub trait IntoGuestFunction<Output, Args>
40where
41    Self: Function<Output, Args, HyperlightGuestError>,
42    Self: Copy + 'static,
43    Output: SupportedReturnType,
44    Args: ParameterTuple,
45{
46    #[doc(hidden)]
47    const ASSERT_ZERO_SIZED: ();
48
49    /// Convert the function into a `fn(FunctionCall) -> Result<Vec<u8>>`
50    fn into_guest_function(self) -> fn(FunctionCall) -> Result<Vec<u8>>;
51}
52
53/// Trait for functions that can be converted to a `GuestFunctionDefinition<GuestFunc>`
54pub trait AsGuestFunctionDefinition<Output, Args>
55where
56    Self: Function<Output, Args, HyperlightGuestError>,
57    Self: IntoGuestFunction<Output, Args>,
58    Output: SupportedReturnType,
59    Args: ParameterTuple,
60{
61    /// Get the `GuestFunctionDefinition` for this function
62    fn as_guest_function_definition(
63        &self,
64        name: impl Into<String>,
65    ) -> GuestFunctionDefinition<GuestFunc>;
66}
67
68fn into_flatbuffer_result(value: ReturnValue) -> Vec<u8> {
69    match value {
70        ReturnValue::Void(()) => get_flatbuffer_result(()),
71        ReturnValue::Int(i) => get_flatbuffer_result(i),
72        ReturnValue::UInt(u) => get_flatbuffer_result(u),
73        ReturnValue::Long(l) => get_flatbuffer_result(l),
74        ReturnValue::ULong(ul) => get_flatbuffer_result(ul),
75        ReturnValue::Float(f) => get_flatbuffer_result(f),
76        ReturnValue::Double(d) => get_flatbuffer_result(d),
77        ReturnValue::Bool(b) => get_flatbuffer_result(b),
78        ReturnValue::String(s) => get_flatbuffer_result(s.as_str()),
79        ReturnValue::VecBytes(v) => get_flatbuffer_result(v.as_slice()),
80    }
81}
82
83macro_rules! impl_host_function {
84    ([$N:expr] ($($p:ident: $P:ident),*)) => {
85        impl<F, R, $($P),*> IntoGuestFunction<R::ReturnType, ($($P,)*)> for F
86        where
87            F: Fn($($P),*) -> R,
88            F: Function<R::ReturnType, ($($P,)*), HyperlightGuestError>,
89            F: Copy + 'static, // Copy implies that F has no Drop impl
90            ($($P,)*): ParameterTuple,
91            R: ResultType<HyperlightGuestError>,
92        {
93            // Only functions that can be coerced into a function pointer (i.e., "fn" types)
94            // can be registered as guest functions.
95            //
96            // Note that the "Fn" trait is different from "fn" types in Rust.
97            // "fn" is a type, while "Fn" is a trait.
98            // For example, closures that capture environment implement "Fn" but cannot be
99            // coerced to function pointers.
100            // This means that the closure returned by `into_guest_function` can not capture
101            // any environment, not event `self`, and we must only rely on the type system
102            // to call the correct function.
103            //
104            // Ideally we would implement IntoGuestFunction for any F that can be converted
105            // into a function pointer, but currently there's no way to express that in Rust's
106            // type system.
107            // Therefore, to ensure that F is a "fn" type, we enforce that F is zero-sized
108            // has no Drop impl (the latter is enforced by the Copy bound), and it doesn't
109            // capture any lifetimes (not even through a marker type like PhantomData).
110            //
111            // Note that implementing IntoGuestFunction for "fn($(P),*) -> R" is not an option
112            // either, "fn($(P),*) -> R" is a type that's shared for all function pointers with
113            // that signature, e.g., "fn add(a: i32, b: i32) -> i32 { a + b }" and
114            // "fn sub(a: i32, b: i32) -> i32 { a - b }" both can be coerced to the same
115            // "fn(i32, i32) -> i32" type, so we would need to capture self (a function pointer)
116            // to know exactly which function to call.
117
118            #[doc(hidden)]
119            const ASSERT_ZERO_SIZED: () = const {
120                assert!(core::mem::size_of::<Self>() == 0)
121            };
122
123            fn into_guest_function(self) -> fn(FunctionCall) -> Result<Vec<u8>> {
124                |fc: FunctionCall| {
125                    // SAFETY: This is safe because:
126                    //  1. F is zero-sized (enforced by the ASSERT_ZERO_SIZED const).
127                    //  2. F has no Drop impl (enforced by the Copy bound).
128                    // Therefore, creating an instance of F is safe.
129                    let this = unsafe { core::mem::zeroed::<F>() };
130                    let params = fc.parameters.unwrap_or_default();
131                    let params = <($($P,)*) as ParameterTuple>::from_value(params)?;
132                    let result = Function::<R::ReturnType, ($($P,)*), HyperlightGuestError>::call(&this, params)?;
133                    Ok(into_flatbuffer_result(result.into_value()))
134                }
135            }
136        }
137    };
138}
139
140impl<F, Args, Output> AsGuestFunctionDefinition<Output, Args> for F
141where
142    F: IntoGuestFunction<Output, Args>,
143    Args: ParameterTuple,
144    Output: SupportedReturnType,
145{
146    fn as_guest_function_definition(
147        &self,
148        name: impl Into<String>,
149    ) -> GuestFunctionDefinition<GuestFunc> {
150        let parameter_types = Args::TYPE.to_vec();
151        let return_type = Output::TYPE;
152        let function_pointer = self.into_guest_function();
153
154        GuestFunctionDefinition {
155            function_name: name.into(),
156            parameter_types,
157            return_type,
158            function_pointer,
159        }
160    }
161}
162
163for_each_tuple!(impl_host_function);
164
165impl<F: Copy> GuestFunctionDefinition<F> {
166    /// Create a new `GuestFunctionDefinition`.
167    pub fn new(
168        function_name: String,
169        parameter_types: Vec<ParameterType>,
170        return_type: ReturnType,
171        function_pointer: F,
172    ) -> Self {
173        Self {
174            function_name,
175            parameter_types,
176            return_type,
177            function_pointer,
178        }
179    }
180
181    /// Create a new `GuestFunctionDefinition<GuestFunc>` from a function that
182    /// implements `AsGuestFunctionDefinition`.
183    pub fn from_fn<Output, Args>(
184        function_name: String,
185        function: impl AsGuestFunctionDefinition<Output, Args>,
186    ) -> GuestFunctionDefinition<GuestFunc>
187    where
188        Args: ParameterTuple,
189        Output: SupportedReturnType,
190    {
191        function.as_guest_function_definition(function_name)
192    }
193
194    /// Verify that `self` has same signature as the provided `parameter_types`.
195    pub fn verify_parameters(&self, parameter_types: &[ParameterType]) -> Result<()> {
196        // Verify that the function does not have more than `MAX_PARAMETERS` parameters.
197        const MAX_PARAMETERS: usize = 11;
198        if parameter_types.len() > MAX_PARAMETERS {
199            return Err(HyperlightGuestError::new(
200                ErrorCode::GuestError,
201                format!(
202                    "Function {} has too many parameters: {} (max allowed is {}).",
203                    self.function_name,
204                    parameter_types.len(),
205                    MAX_PARAMETERS
206                ),
207            ));
208        }
209
210        if self.parameter_types.len() != parameter_types.len() {
211            return Err(HyperlightGuestError::new(
212                ErrorCode::GuestFunctionIncorrecNoOfParameters,
213                format!(
214                    "Called function {} with {} parameters but it takes {}.",
215                    self.function_name,
216                    parameter_types.len(),
217                    self.parameter_types.len()
218                ),
219            ));
220        }
221
222        for (i, parameter_type) in self.parameter_types.iter().enumerate() {
223            if parameter_type != &parameter_types[i] {
224                return Err(HyperlightGuestError::new(
225                    ErrorCode::GuestFunctionParameterTypeMismatch,
226                    format!(
227                        "Expected parameter type {:?} for parameter index {} of function {} but got {:?}.",
228                        parameter_type, i, self.function_name, parameter_types[i]
229                    ),
230                ));
231            }
232        }
233
234        Ok(())
235    }
236}