hyperlight_guest/
guest_function_call.rs

1/*
2Copyright 2024 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::vec::Vec;
19
20use hyperlight_common::flatbuffer_wrappers::function_call::{FunctionCall, FunctionCallType};
21use hyperlight_common::flatbuffer_wrappers::function_types::ParameterType;
22use hyperlight_common::flatbuffer_wrappers::guest_error::ErrorCode;
23
24use crate::entrypoint::halt;
25use crate::error::{HyperlightGuestError, Result};
26use crate::guest_error::{reset_error, set_error};
27use crate::shared_input_data::try_pop_shared_input_data_into;
28use crate::shared_output_data::push_shared_output_data;
29use crate::REGISTERED_GUEST_FUNCTIONS;
30
31type GuestFunc = fn(&FunctionCall) -> Result<Vec<u8>>;
32
33pub(crate) fn call_guest_function(function_call: FunctionCall) -> Result<Vec<u8>> {
34    // Validate this is a Guest Function Call
35    if function_call.function_call_type() != FunctionCallType::Guest {
36        return Err(HyperlightGuestError::new(
37            ErrorCode::GuestError,
38            format!(
39                "Invalid function call type: {:#?}, should be Guest.",
40                function_call.function_call_type()
41            ),
42        ));
43    }
44
45    // Find the function definition for the function call.
46    if let Some(registered_function_definition) =
47        unsafe { REGISTERED_GUEST_FUNCTIONS.get(&function_call.function_name) }
48    {
49        let function_call_parameter_types: Vec<ParameterType> = function_call
50            .parameters
51            .iter()
52            .flatten()
53            .map(|p| p.into())
54            .collect();
55
56        // Verify that the function call has the correct parameter types and length.
57        registered_function_definition.verify_parameters(&function_call_parameter_types)?;
58
59        let p_function = unsafe {
60            let function_pointer = registered_function_definition.function_pointer;
61            core::mem::transmute::<i64, GuestFunc>(function_pointer)
62        };
63
64        p_function(&function_call)
65    } else {
66        // The given function is not registered. The guest should implement a function called guest_dispatch_function to handle this.
67
68        // TODO: ideally we would define a default implementation of this with weak linkage so the guest is not required
69        // to implement the function but its seems that weak linkage is an unstable feature so for now its probably better
70        // to not do that.
71        extern "Rust" {
72            fn guest_dispatch_function(function_call: FunctionCall) -> Result<Vec<u8>>;
73        }
74
75        unsafe { guest_dispatch_function(function_call) }
76    }
77}
78
79// This function is marked as no_mangle/inline to prevent the compiler from inlining it , if its inlined the epilogue will not be called
80// and we will leak memory as the epilogue will not be called as halt() is not going to return.
81#[no_mangle]
82#[inline(never)]
83fn internal_dispatch_function() -> Result<()> {
84    reset_error();
85
86    #[cfg(debug_assertions)]
87    log::trace!("internal_dispatch_function");
88
89    let function_call = try_pop_shared_input_data_into::<FunctionCall>()
90        .expect("Function call deserialization failed");
91
92    let result_vec = call_guest_function(function_call).inspect_err(|e| {
93        set_error(e.kind.clone(), e.message.as_str());
94    })?;
95
96    push_shared_output_data(result_vec)
97}
98
99// This is implemented as a separate function to make sure that epilogue in the internal_dispatch_function is called before the halt()
100// which if it were included in the internal_dispatch_function cause the epilogue to not be called because the halt() would not return
101// when running in the hypervisor.
102pub(crate) extern "win64" fn dispatch_function() {
103    let _ = internal_dispatch_function();
104    halt();
105}