Skip to main content

hyperlight_guest_bin/guest_function/
definition.rs

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