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