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