Skip to content

Commit a1dcdd4

Browse files
committed
Remove host function definition region dependency
Instead of using a shared memory region or a host function callback to provide host function definitions to the guest, the host now pushes the serialized definitions directly as a parameter to InitWasmRuntime. Signed-off-by: James Sturtevant <jsturtevant@gmail.com>
1 parent 3f57140 commit a1dcdd4

7 files changed

Lines changed: 100 additions & 19 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
55

66
## [Prerelease] - Unreleased
77

8+
### Changed
9+
- **BREAKING CHANGE:** Removed `SandboxBuilder::with_function_definition_size`. Host function definitions are now pushed to the guest at runtime load time instead of using a separate memory region. (#388)
10+
811
## [v0.12.0] - 2025-12
912

1013
### Added

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/hyperlight_wasm/src/sandbox/loaded_wasm_sandbox.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,29 @@ mod tests {
425425
assert_eq!(r, 0);
426426
}
427427

428+
#[test]
429+
fn test_load_module_fails_with_missing_host_function() {
430+
// HostFunction.aot imports "HostFuncWithBufferAndLength" from "env".
431+
// Loading it without registering that host function should fail
432+
// at instantiation time (linker.instantiate) because the import
433+
// cannot be satisfied.
434+
let proto_wasm_sandbox = SandboxBuilder::new().build().unwrap();
435+
436+
let wasm_sandbox = proto_wasm_sandbox.load_runtime().unwrap();
437+
438+
let result: std::result::Result<LoadedWasmSandbox, _> = {
439+
let mod_path = get_wasm_module_path("HostFunction.aot").unwrap();
440+
wasm_sandbox.load_module(mod_path)
441+
};
442+
443+
let err = result.unwrap_err();
444+
let err_msg = format!("{:?}", err);
445+
assert!(
446+
err_msg.contains("HostFuncWithBufferAndLength"),
447+
"Error should mention the missing host function, got: {err_msg}"
448+
);
449+
}
450+
428451
fn call_funcs(
429452
mut loaded_wasm_sandbox: LoadedWasmSandbox,
430453
iterations: i32,

src/hyperlight_wasm/src/sandbox/proto_wasm_sandbox.rs

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17+
use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterType, ReturnType};
18+
use hyperlight_common::flatbuffer_wrappers::host_function_definition::HostFunctionDefinition;
19+
use hyperlight_common::flatbuffer_wrappers::host_function_details::HostFunctionDetails;
1720
use hyperlight_host::func::{HostFunction, ParameterTuple, Registerable, SupportedReturnType};
1821
use hyperlight_host::sandbox::config::SandboxConfiguration;
1922
use hyperlight_host::{GuestBinary, Result, UninitializedSandbox, new_error};
@@ -31,6 +34,8 @@ use crate::build_info::BuildInfo;
3134
/// With that `WasmSandbox` you can load a Wasm module through the `load_module` method and get a `LoadedWasmSandbox` which can then execute functions defined in the Wasm module.
3235
pub struct ProtoWasmSandbox {
3336
pub(super) inner: Option<UninitializedSandbox>,
37+
/// Tracks registered host function definitions for pushing to the guest at load time
38+
host_function_definitions: Vec<HostFunctionDefinition>,
3439
}
3540

3641
impl Registerable for ProtoWasmSandbox {
@@ -39,6 +44,13 @@ impl Registerable for ProtoWasmSandbox {
3944
name: &str,
4045
hf: impl Into<HostFunction<Output, Args>>,
4146
) -> Result<()> {
47+
// Track the host function definition for pushing to guest at load time
48+
self.host_function_definitions.push(HostFunctionDefinition {
49+
function_name: name.to_string(),
50+
parameter_types: Some(Args::TYPE.to_vec()),
51+
return_type: Output::TYPE,
52+
});
53+
4254
self.inner
4355
.as_mut()
4456
.ok_or(new_error!("inner sandbox was none"))
@@ -65,7 +77,18 @@ impl ProtoWasmSandbox {
6577
let inner = UninitializedSandbox::new(guest_binary, cfg)?;
6678
metrics::gauge!(METRIC_ACTIVE_PROTO_WASM_SANDBOXES).increment(1);
6779
metrics::counter!(METRIC_TOTAL_PROTO_WASM_SANDBOXES).increment(1);
68-
Ok(Self { inner: Some(inner) })
80+
81+
// HostPrint is always registered by UninitializedSandbox, so include it by default
82+
let host_function_definitions = vec![HostFunctionDefinition {
83+
function_name: "HostPrint".to_string(),
84+
parameter_types: Some(vec![ParameterType::String]),
85+
return_type: ReturnType::Int,
86+
}];
87+
88+
Ok(Self {
89+
inner: Some(inner),
90+
host_function_definitions,
91+
})
6992
}
7093

7194
/// Load the Wasm runtime into the sandbox and return a `WasmSandbox`
@@ -75,12 +98,22 @@ impl ProtoWasmSandbox {
7598
/// The returned `WasmSandbox` can be then be cached and used to load a different Wasm module.
7699
///
77100
pub fn load_runtime(mut self) -> Result<WasmSandbox> {
101+
// Serialize host function definitions to push to the guest during InitWasmRuntime
102+
let host_function_definitions = HostFunctionDetails {
103+
host_functions: Some(std::mem::take(&mut self.host_function_definitions)),
104+
};
105+
106+
let host_function_definitions_bytes: Vec<u8> = (&host_function_definitions)
107+
.try_into()
108+
.map_err(|e| new_error!("Failed to serialize host function details: {:?}", e))?;
109+
78110
let mut sandbox = match self.inner.take() {
79111
Some(s) => s.evolve()?,
80112
None => return Err(new_error!("No inner sandbox found.")),
81113
};
82114

83-
let res: i32 = sandbox.call("InitWasmRuntime", ())?;
115+
// Pass host function definitions to the guest as a parameter
116+
let res: i32 = sandbox.call("InitWasmRuntime", (host_function_definitions_bytes,))?;
84117
if res != 0 {
85118
return Err(new_error!(
86119
"InitWasmRuntime Failed with error code {:?}",
@@ -99,6 +132,13 @@ impl ProtoWasmSandbox {
99132
name: impl AsRef<str>,
100133
host_func: impl Into<HostFunction<Output, Args>>,
101134
) -> Result<()> {
135+
// Track the host function definition for pushing to guest at load time
136+
self.host_function_definitions.push(HostFunctionDefinition {
137+
function_name: name.as_ref().to_string(),
138+
parameter_types: Some(Args::TYPE.to_vec()),
139+
return_type: Output::TYPE,
140+
});
141+
102142
self.inner
103143
.as_mut()
104144
.ok_or(new_error!("inner sandbox was none"))?
@@ -111,6 +151,9 @@ impl ProtoWasmSandbox {
111151
&mut self,
112152
print_func: impl Into<HostFunction<i32, (String,)>>,
113153
) -> Result<()> {
154+
// HostPrint definition is already tracked from new() since
155+
// UninitializedSandbox always registers a default HostPrint.
156+
// This method only replaces the implementation, not the definition.
114157
self.inner
115158
.as_mut()
116159
.ok_or(new_error!("inner sandbox was none"))?

src/hyperlight_wasm/src/sandbox/sandbox_builder.rs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -126,14 +126,6 @@ impl SandboxBuilder {
126126
self
127127
}
128128

129-
/// Set the size of the memory buffer that is made available
130-
/// for serialising host function definitions the minimum value
131-
/// is MIN_FUNCTION_DEFINITION_SIZE
132-
pub fn with_function_definition_size(mut self, size: usize) -> Self {
133-
self.config.set_host_function_definition_size(size);
134-
self
135-
}
136-
137129
/// Build the ProtoWasmSandbox
138130
pub fn build(self) -> Result<ProtoWasmSandbox> {
139131
if !is_hypervisor_present() {

src/hyperlight_wasm_runtime/src/hostfuncs.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,6 @@ pub(crate) type HostFunctionDefinition =
3232
pub(crate) type HostFunctionDetails =
3333
hyperlight_common::flatbuffer_wrappers::host_function_details::HostFunctionDetails;
3434

35-
pub(crate) fn get_host_function_details() -> HostFunctionDetails {
36-
hyperlight_guest_bin::host_comm::get_host_function_details()
37-
}
38-
3935
pub(crate) fn hostfunc_type(d: &HostFunctionDefinition, e: &Engine) -> Result<FuncType> {
4036
let mut params = Vec::new();
4137
let mut last_was_vec = false;

src/hyperlight_wasm_runtime/src/module.rs

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ pub fn guest_dispatch_function(function_call: FunctionCall) -> Result<Vec<u8>> {
9696
}
9797

9898
#[instrument(skip_all, level = "Info")]
99-
fn init_wasm_runtime() -> Result<Vec<u8>> {
99+
fn init_wasm_runtime(function_call: &FunctionCall) -> Result<Vec<u8>> {
100100
let mut config = Config::new();
101101
config.with_custom_code_memory(Some(alloc::sync::Arc::new(platform::WasmtimeCodeMemory {})));
102102
#[cfg(gdb)]
@@ -112,9 +112,32 @@ fn init_wasm_runtime() -> Result<Vec<u8>> {
112112
let mut linker = Linker::new(&engine);
113113
wasip1::register_handlers(&mut linker)?;
114114

115-
let hostfuncs = hostfuncs::get_host_function_details()
116-
.host_functions
117-
.unwrap_or_default();
115+
// Parse host function details pushed by the host as a parameter
116+
let params = function_call.parameters.as_ref().ok_or_else(|| {
117+
HyperlightGuestError::new(
118+
ErrorCode::GuestFunctionParameterTypeMismatch,
119+
"InitWasmRuntime: missing parameters".to_string(),
120+
)
121+
})?;
122+
123+
let bytes = match params.first() {
124+
Some(ParameterValue::VecBytes(ref b)) => b,
125+
_ => {
126+
return Err(HyperlightGuestError::new(
127+
ErrorCode::GuestFunctionParameterTypeMismatch,
128+
"InitWasmRuntime: first parameter must be VecBytes".to_string(),
129+
))
130+
}
131+
};
132+
133+
let hfd: hostfuncs::HostFunctionDetails = bytes.as_slice().try_into().map_err(|e| {
134+
HyperlightGuestError::new(
135+
ErrorCode::GuestError,
136+
alloc::format!("Failed to parse host function details: {:?}", e),
137+
)
138+
})?;
139+
let hostfuncs = hfd.host_functions.unwrap_or_default();
140+
118141
for hostfunc in hostfuncs.iter() {
119142
let captured = hostfunc.clone();
120143
linker.func_new(
@@ -210,7 +233,7 @@ pub extern "C" fn hyperlight_main() {
210233

211234
register_function(GuestFunctionDefinition::new(
212235
"InitWasmRuntime".to_string(),
213-
vec![],
236+
vec![ParameterType::VecBytes],
214237
ReturnType::Int,
215238
init_wasm_runtime as usize,
216239
));

0 commit comments

Comments
 (0)