Skip to main content

hyperlight_host/func/
host_functions.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 std::sync::{Arc, Mutex};
18
19use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterValue, ReturnValue};
20use hyperlight_common::for_each_tuple;
21use hyperlight_common::func::{Error as FuncError, Function, ResultType};
22
23use super::{ParameterTuple, SupportedReturnType};
24use crate::sandbox::UninitializedSandbox;
25use crate::sandbox::host_funcs::FunctionEntry;
26use crate::{HyperlightError, Result, new_error};
27
28/// A sandbox on which (primitive) host functions can be registered
29///
30pub trait Registerable {
31    /// Register a primitive host function
32    fn register_host_function<Args: ParameterTuple, Output: SupportedReturnType>(
33        &mut self,
34        name: &str,
35        hf: impl Into<HostFunction<Output, Args>>,
36    ) -> Result<()>;
37}
38impl Registerable for UninitializedSandbox {
39    fn register_host_function<Args: ParameterTuple, Output: SupportedReturnType>(
40        &mut self,
41        name: &str,
42        hf: impl Into<HostFunction<Output, Args>>,
43    ) -> Result<()> {
44        let mut hfs = self
45            .host_funcs
46            .try_lock()
47            .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?;
48
49        let entry = FunctionEntry {
50            function: hf.into().into(),
51            parameter_types: Args::TYPE,
52            return_type: Output::TYPE,
53        };
54
55        (*hfs).register_host_function(name.to_string(), entry)
56    }
57}
58
59/// Allow registering host functions on an already-evolved
60/// [`crate::MultiUseSandbox`].
61///
62/// The primary entry point for host-function registration is the
63/// `UninitializedSandbox` impl above — that's the lifecycle phase
64/// where the guest hasn't yet been allowed to issue host calls.
65/// There are, however, cases where a `MultiUseSandbox` is obtained
66/// without traversing the `Uninitialized → evolve()` path:
67///
68/// - Sandboxes loaded from a persisted snapshot.
69/// - Any future API that yields a `MultiUseSandbox` directly.
70///
71/// In those cases the caller never had a chance to call
72/// `register_host_function` on an `UninitializedSandbox`, so we
73/// expose the same trait implementation here for late registration.
74/// The guest's host-function dispatcher resolves by name at call
75/// time, so inserting into the registry after `evolve()` is
76/// semantically safe as long as the first host-function invocation
77/// happens after registration completes.
78impl Registerable for crate::MultiUseSandbox {
79    fn register_host_function<Args: ParameterTuple, Output: SupportedReturnType>(
80        &mut self,
81        name: &str,
82        hf: impl Into<HostFunction<Output, Args>>,
83    ) -> Result<()> {
84        let mut hfs = self
85            .host_funcs
86            .try_lock()
87            .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?;
88
89        let entry = FunctionEntry {
90            function: hf.into().into(),
91            parameter_types: Args::TYPE,
92            return_type: Output::TYPE,
93        };
94
95        (*hfs).register_host_function(name.to_string(), entry)
96    }
97}
98
99/// A representation of a host function.
100/// This is a thin wrapper around a `Fn(Args) -> Result<Output>`.
101#[derive(Clone)]
102pub struct HostFunction<Output, Args>
103where
104    Args: ParameterTuple,
105    Output: SupportedReturnType,
106{
107    // This is a thin wrapper around a `Function<Output, Args, HyperlightError>`.
108    // But unlike `Function<..>` which is a trait, this is a concrete type.
109    // This allows us to:
110    //  1. Impose constraints on the function arguments and return type.
111    //  2. Impose a single function signature.
112    //
113    // This second point is important because the `Function<..>` trait is generic
114    // over the function arguments and return type.
115    // This means that a given type could implement `Function<..>` for multiple
116    // function signatures.
117    // This means we can't do something like:
118    // ```rust,ignore
119    // impl<Args, Output, F> SomeTrait for F
120    // where
121    //     F: Function<Output, Args, HyperlightError>,
122    // { ... }
123    // ```
124    // because the concrete type F might implement `Function<..>` for multiple
125    // arguments and return types, and that would means implementing `SomeTrait`
126    // multiple times for the same type F.
127
128    // Use Arc in here instead of Box because it's useful in tests and
129    // presumably in other places to be able to clone a HostFunction and
130    // use it across different sandboxes.
131    func: Arc<dyn Function<Output, Args, HyperlightError> + Send + Sync + 'static>,
132}
133
134pub(crate) struct TypeErasedHostFunction {
135    func: Box<dyn Fn(Vec<ParameterValue>) -> Result<ReturnValue> + Send + Sync + 'static>,
136}
137
138impl<Args, Output> HostFunction<Output, Args>
139where
140    Args: ParameterTuple,
141    Output: SupportedReturnType,
142{
143    /// Call the host function with the given arguments.
144    pub fn call(&self, args: Args) -> Result<Output> {
145        self.func.call(args)
146    }
147}
148
149impl TypeErasedHostFunction {
150    pub(crate) fn call(&self, args: Vec<ParameterValue>) -> Result<ReturnValue> {
151        (self.func)(args)
152    }
153}
154
155impl From<FuncError> for HyperlightError {
156    fn from(e: FuncError) -> Self {
157        match e {
158            FuncError::ParameterValueConversionFailure(from, to) => {
159                HyperlightError::ParameterValueConversionFailure(from, to)
160            }
161            FuncError::ReturnValueConversionFailure(from, to) => {
162                HyperlightError::ReturnValueConversionFailure(from, to)
163            }
164            FuncError::UnexpectedNoOfArguments(got, expected) => {
165                HyperlightError::UnexpectedNoOfArguments(got, expected)
166            }
167            FuncError::UnexpectedParameterValueType(got, expected) => {
168                HyperlightError::UnexpectedParameterValueType(got, expected)
169            }
170            FuncError::UnexpectedReturnValueType(got, expected) => {
171                HyperlightError::UnexpectedReturnValueType(got, expected)
172            }
173        }
174    }
175}
176
177impl<Args, Output> From<HostFunction<Output, Args>> for TypeErasedHostFunction
178where
179    Args: ParameterTuple,
180    Output: SupportedReturnType,
181{
182    fn from(func: HostFunction<Output, Args>) -> TypeErasedHostFunction {
183        TypeErasedHostFunction {
184            func: Box::new(move |args: Vec<ParameterValue>| {
185                let args = Args::from_value(args)?;
186                Ok(func.call(args)?.into_value())
187            }),
188        }
189    }
190}
191
192macro_rules! impl_host_function {
193    ([$N:expr] ($($p:ident: $P:ident),*)) => {
194        /*
195        // Normally for a `Fn + Send + Sync` we don't need to use a Mutex
196        // like we do in the case of a `FnMut`.
197        // However, we can't implement `IntoHostFunction` for `Fn` and `FnMut`
198        // because `FnMut` is a supertrait of `Fn`.
199         */
200
201        impl<F, R, $($P),*> From<F> for HostFunction<R::ReturnType, ($($P,)*)>
202        where
203            F: FnMut($($P),*) -> R + Send + 'static,
204            ($($P,)*): ParameterTuple,
205            R: ResultType<HyperlightError>,
206        {
207            fn from(func: F) -> HostFunction<R::ReturnType, ($($P,)*)> {
208                let func = Mutex::new(func);
209                let func = move |$($p: $P,)*| {
210                    let mut func = func.lock().map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?;
211                    (func)($($p),*).into_result()
212                };
213                let func = Arc::new(func);
214                HostFunction { func }
215            }
216        }
217    };
218}
219
220for_each_tuple!(impl_host_function);
221
222pub(crate) fn register_host_function<Args: ParameterTuple, Output: SupportedReturnType>(
223    func: impl Into<HostFunction<Output, Args>>,
224    sandbox: &mut UninitializedSandbox,
225    name: &str,
226) -> Result<()> {
227    let func = func.into().into();
228
229    let entry = FunctionEntry {
230        function: func,
231        parameter_types: Args::TYPE,
232        return_type: Output::TYPE,
233    };
234
235    sandbox
236        .host_funcs
237        .try_lock()
238        .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?
239        .register_host_function(name.to_string(), entry)?;
240
241    Ok(())
242}