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, &mut self.mgr)
56    }
57}
58
59/// A representation of a host function.
60/// This is a thin wrapper around a `Fn(Args) -> Result<Output>`.
61#[derive(Clone)]
62pub struct HostFunction<Output, Args>
63where
64    Args: ParameterTuple,
65    Output: SupportedReturnType,
66{
67    // This is a thin wrapper around a `Function<Output, Args, HyperlightError>`.
68    // But unlike `Function<..>` which is a trait, this is a concrete type.
69    // This allows us to:
70    //  1. Impose constraints on the function arguments and return type.
71    //  2. Impose a single function signature.
72    //
73    // This second point is important because the `Function<..>` trait is generic
74    // over the function arguments and return type.
75    // This means that a given type could implement `Function<..>` for multiple
76    // function signatures.
77    // This means we can't do something like:
78    // ```rust,ignore
79    // impl<Args, Output, F> SomeTrait for F
80    // where
81    //     F: Function<Output, Args, HyperlightError>,
82    // { ... }
83    // ```
84    // because the concrete type F might implement `Function<..>` for multiple
85    // arguments and return types, and that would means implementing `SomeTrait`
86    // multiple times for the same type F.
87
88    // Use Arc in here instead of Box because it's useful in tests and
89    // presumably in other places to be able to clone a HostFunction and
90    // use it across different sandboxes.
91    func: Arc<dyn Function<Output, Args, HyperlightError> + Send + Sync + 'static>,
92}
93
94pub(crate) struct TypeErasedHostFunction {
95    func: Box<dyn Fn(Vec<ParameterValue>) -> Result<ReturnValue> + Send + Sync + 'static>,
96}
97
98impl<Args, Output> HostFunction<Output, Args>
99where
100    Args: ParameterTuple,
101    Output: SupportedReturnType,
102{
103    /// Call the host function with the given arguments.
104    pub fn call(&self, args: Args) -> Result<Output> {
105        self.func.call(args)
106    }
107}
108
109impl TypeErasedHostFunction {
110    pub(crate) fn call(&self, args: Vec<ParameterValue>) -> Result<ReturnValue> {
111        (self.func)(args)
112    }
113}
114
115impl From<FuncError> for HyperlightError {
116    fn from(e: FuncError) -> Self {
117        match e {
118            FuncError::ParameterValueConversionFailure(from, to) => {
119                HyperlightError::ParameterValueConversionFailure(from, to)
120            }
121            FuncError::ReturnValueConversionFailure(from, to) => {
122                HyperlightError::ReturnValueConversionFailure(from, to)
123            }
124            FuncError::UnexpectedNoOfArguments(got, expected) => {
125                HyperlightError::UnexpectedNoOfArguments(got, expected)
126            }
127            FuncError::UnexpectedParameterValueType(got, expected) => {
128                HyperlightError::UnexpectedParameterValueType(got, expected)
129            }
130            FuncError::UnexpectedReturnValueType(got, expected) => {
131                HyperlightError::UnexpectedReturnValueType(got, expected)
132            }
133        }
134    }
135}
136
137impl<Args, Output> From<HostFunction<Output, Args>> for TypeErasedHostFunction
138where
139    Args: ParameterTuple,
140    Output: SupportedReturnType,
141{
142    fn from(func: HostFunction<Output, Args>) -> TypeErasedHostFunction {
143        TypeErasedHostFunction {
144            func: Box::new(move |args: Vec<ParameterValue>| {
145                let args = Args::from_value(args)?;
146                Ok(func.call(args)?.into_value())
147            }),
148        }
149    }
150}
151
152macro_rules! impl_host_function {
153    ([$N:expr] ($($p:ident: $P:ident),*)) => {
154        /*
155        // Normally for a `Fn + Send + Sync` we don't need to use a Mutex
156        // like we do in the case of a `FnMut`.
157        // However, we can't implement `IntoHostFunction` for `Fn` and `FnMut`
158        // because `FnMut` is a supertrait of `Fn`.
159         */
160
161        impl<F, R, $($P),*> From<F> for HostFunction<R::ReturnType, ($($P,)*)>
162        where
163            F: FnMut($($P),*) -> R + Send + 'static,
164            ($($P,)*): ParameterTuple,
165            R: ResultType<HyperlightError>,
166        {
167            fn from(func: F) -> HostFunction<R::ReturnType, ($($P,)*)> {
168                let func = Mutex::new(func);
169                let func = move |$($p: $P,)*| {
170                    let mut func = func.lock().map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?;
171                    (func)($($p),*).into_result()
172                };
173                let func = Arc::new(func);
174                HostFunction { func }
175            }
176        }
177    };
178}
179
180for_each_tuple!(impl_host_function);
181
182pub(crate) fn register_host_function<Args: ParameterTuple, Output: SupportedReturnType>(
183    func: impl Into<HostFunction<Output, Args>>,
184    sandbox: &mut UninitializedSandbox,
185    name: &str,
186) -> Result<()> {
187    let func = func.into().into();
188
189    let entry = FunctionEntry {
190        function: func,
191        parameter_types: Args::TYPE,
192        return_type: Output::TYPE,
193    };
194
195    sandbox
196        .host_funcs
197        .try_lock()
198        .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?
199        .register_host_function(name.to_string(), entry, &mut sandbox.mgr)?;
200
201    Ok(())
202}