From 81497bd99dcd6e9d21835963d58be453df45c965 Mon Sep 17 00:00:00 2001 From: James Sturtevant Date: Tue, 24 Feb 2026 11:25:50 -0800 Subject: [PATCH 1/2] 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 --- CHANGELOG.md | 3 ++ Cargo.lock | 1 + Cargo.toml | 1 + src/hyperlight_wasm/Cargo.toml | 1 + .../src/sandbox/loaded_wasm_sandbox.rs | 23 +++++++++ .../src/sandbox/proto_wasm_sandbox.rs | 47 ++++++++++++++++++- .../src/sandbox/sandbox_builder.rs | 8 ---- src/wasm_runtime/src/hostfuncs.rs | 4 -- src/wasm_runtime/src/module.rs | 33 +++++++++++-- 9 files changed, 102 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0792293..eca48d8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Prerelease] - Unreleased +### Changed +- **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) + ## [v0.12.0] - 2025-12 ### Added diff --git a/Cargo.lock b/Cargo.lock index af2487e7..ccd8f98c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1543,6 +1543,7 @@ dependencies = [ "env_logger", "examples_common", "goblin", + "hyperlight-common", "hyperlight-component-macro", "hyperlight-host", "junction", diff --git a/Cargo.toml b/Cargo.toml index b5ae1597..6959cb6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,4 @@ readme = "README.md" [workspace.dependencies] hyperlight-host = { version = "0.12.0", default-features = false, features = ["executable_heap", "init-paging"] } +hyperlight-common = { version = "0.12.0", default-features = false } diff --git a/src/hyperlight_wasm/Cargo.toml b/src/hyperlight_wasm/Cargo.toml index ef53fed6..50c4d58a 100644 --- a/src/hyperlight_wasm/Cargo.toml +++ b/src/hyperlight_wasm/Cargo.toml @@ -61,6 +61,7 @@ test = true [dependencies] hyperlight-host = { workspace = true } +hyperlight-common = { workspace = true } libc = { version = "0.2.182" } once_cell = "1.21.3" tracing = "0.1.44" diff --git a/src/hyperlight_wasm/src/sandbox/loaded_wasm_sandbox.rs b/src/hyperlight_wasm/src/sandbox/loaded_wasm_sandbox.rs index c926b989..30010878 100644 --- a/src/hyperlight_wasm/src/sandbox/loaded_wasm_sandbox.rs +++ b/src/hyperlight_wasm/src/sandbox/loaded_wasm_sandbox.rs @@ -425,6 +425,29 @@ mod tests { assert_eq!(r, 0); } + #[test] + fn test_load_module_fails_with_missing_host_function() { + // HostFunction.aot imports "HostFuncWithBufferAndLength" from "env". + // Loading it without registering that host function should fail + // at instantiation time (linker.instantiate) because the import + // cannot be satisfied. + let proto_wasm_sandbox = SandboxBuilder::new().build().unwrap(); + + let wasm_sandbox = proto_wasm_sandbox.load_runtime().unwrap(); + + let result: std::result::Result = { + let mod_path = get_wasm_module_path("HostFunction.aot").unwrap(); + wasm_sandbox.load_module(mod_path) + }; + + let err = result.unwrap_err(); + let err_msg = format!("{:?}", err); + assert!( + err_msg.contains("HostFuncWithBufferAndLength"), + "Error should mention the missing host function, got: {err_msg}" + ); + } + fn call_funcs( mut loaded_wasm_sandbox: LoadedWasmSandbox, iterations: i32, diff --git a/src/hyperlight_wasm/src/sandbox/proto_wasm_sandbox.rs b/src/hyperlight_wasm/src/sandbox/proto_wasm_sandbox.rs index 24a7a0f5..0d2e7eea 100644 --- a/src/hyperlight_wasm/src/sandbox/proto_wasm_sandbox.rs +++ b/src/hyperlight_wasm/src/sandbox/proto_wasm_sandbox.rs @@ -14,6 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ +use hyperlight_common::flatbuffer_wrappers::function_types::{ParameterType, ReturnType}; +use hyperlight_common::flatbuffer_wrappers::host_function_definition::HostFunctionDefinition; +use hyperlight_common::flatbuffer_wrappers::host_function_details::HostFunctionDetails; use hyperlight_host::func::{HostFunction, ParameterTuple, Registerable, SupportedReturnType}; use hyperlight_host::sandbox::config::SandboxConfiguration; use hyperlight_host::{GuestBinary, Result, UninitializedSandbox, new_error}; @@ -31,6 +34,8 @@ use crate::build_info::BuildInfo; /// 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. pub struct ProtoWasmSandbox { pub(super) inner: Option, + /// Tracks registered host function definitions for pushing to the guest at load time + host_function_definitions: Vec, } impl Registerable for ProtoWasmSandbox { @@ -39,6 +44,13 @@ impl Registerable for ProtoWasmSandbox { name: &str, hf: impl Into>, ) -> Result<()> { + // Track the host function definition for pushing to guest at load time + self.host_function_definitions.push(HostFunctionDefinition { + function_name: name.to_string(), + parameter_types: Some(Args::TYPE.to_vec()), + return_type: Output::TYPE, + }); + self.inner .as_mut() .ok_or(new_error!("inner sandbox was none")) @@ -65,7 +77,18 @@ impl ProtoWasmSandbox { let inner = UninitializedSandbox::new(guest_binary, cfg)?; metrics::gauge!(METRIC_ACTIVE_PROTO_WASM_SANDBOXES).increment(1); metrics::counter!(METRIC_TOTAL_PROTO_WASM_SANDBOXES).increment(1); - Ok(Self { inner: Some(inner) }) + + // HostPrint is always registered by UninitializedSandbox, so include it by default + let host_function_definitions = vec![HostFunctionDefinition { + function_name: "HostPrint".to_string(), + parameter_types: Some(vec![ParameterType::String]), + return_type: ReturnType::Int, + }]; + + Ok(Self { + inner: Some(inner), + host_function_definitions, + }) } /// Load the Wasm runtime into the sandbox and return a `WasmSandbox` @@ -75,12 +98,22 @@ impl ProtoWasmSandbox { /// The returned `WasmSandbox` can be then be cached and used to load a different Wasm module. /// pub fn load_runtime(mut self) -> Result { + // Serialize host function definitions to push to the guest during InitWasmRuntime + let host_function_definitions = HostFunctionDetails { + host_functions: Some(std::mem::take(&mut self.host_function_definitions)), + }; + + let host_function_definitions_bytes: Vec = (&host_function_definitions) + .try_into() + .map_err(|e| new_error!("Failed to serialize host function details: {:?}", e))?; + let mut sandbox = match self.inner.take() { Some(s) => s.evolve()?, None => return Err(new_error!("No inner sandbox found.")), }; - let res: i32 = sandbox.call("InitWasmRuntime", ())?; + // Pass host function definitions to the guest as a parameter + let res: i32 = sandbox.call("InitWasmRuntime", (host_function_definitions_bytes,))?; if res != 0 { return Err(new_error!( "InitWasmRuntime Failed with error code {:?}", @@ -99,6 +132,13 @@ impl ProtoWasmSandbox { name: impl AsRef, host_func: impl Into>, ) -> Result<()> { + // Track the host function definition for pushing to guest at load time + self.host_function_definitions.push(HostFunctionDefinition { + function_name: name.as_ref().to_string(), + parameter_types: Some(Args::TYPE.to_vec()), + return_type: Output::TYPE, + }); + self.inner .as_mut() .ok_or(new_error!("inner sandbox was none"))? @@ -111,6 +151,9 @@ impl ProtoWasmSandbox { &mut self, print_func: impl Into>, ) -> Result<()> { + // HostPrint definition is already tracked from new() since + // UninitializedSandbox always registers a default HostPrint. + // This method only replaces the implementation, not the definition. self.inner .as_mut() .ok_or(new_error!("inner sandbox was none"))? diff --git a/src/hyperlight_wasm/src/sandbox/sandbox_builder.rs b/src/hyperlight_wasm/src/sandbox/sandbox_builder.rs index 0d2f7710..1c519f59 100644 --- a/src/hyperlight_wasm/src/sandbox/sandbox_builder.rs +++ b/src/hyperlight_wasm/src/sandbox/sandbox_builder.rs @@ -126,14 +126,6 @@ impl SandboxBuilder { self } - /// Set the size of the memory buffer that is made available - /// for serialising host function definitions the minimum value - /// is MIN_FUNCTION_DEFINITION_SIZE - pub fn with_function_definition_size(mut self, size: usize) -> Self { - self.config.set_host_function_definition_size(size); - self - } - /// Build the ProtoWasmSandbox pub fn build(self) -> Result { if !is_hypervisor_present() { diff --git a/src/wasm_runtime/src/hostfuncs.rs b/src/wasm_runtime/src/hostfuncs.rs index e468c557..ebf46df9 100644 --- a/src/wasm_runtime/src/hostfuncs.rs +++ b/src/wasm_runtime/src/hostfuncs.rs @@ -32,10 +32,6 @@ pub(crate) type HostFunctionDefinition = pub(crate) type HostFunctionDetails = hyperlight_common::flatbuffer_wrappers::host_function_details::HostFunctionDetails; -pub(crate) fn get_host_function_details() -> HostFunctionDetails { - hyperlight_guest_bin::host_comm::get_host_function_details() -} - pub(crate) fn hostfunc_type(d: &HostFunctionDefinition, e: &Engine) -> Result { let mut params = Vec::new(); let mut last_was_vec = false; diff --git a/src/wasm_runtime/src/module.rs b/src/wasm_runtime/src/module.rs index 195cf0ca..ba4fe082 100644 --- a/src/wasm_runtime/src/module.rs +++ b/src/wasm_runtime/src/module.rs @@ -96,7 +96,7 @@ pub fn guest_dispatch_function(function_call: FunctionCall) -> Result> { } #[instrument(skip_all, level = "Info")] -fn init_wasm_runtime() -> Result> { +fn init_wasm_runtime(function_call: &FunctionCall) -> Result> { let mut config = Config::new(); config.with_custom_code_memory(Some(alloc::sync::Arc::new(platform::WasmtimeCodeMemory {}))); #[cfg(gdb)] @@ -112,9 +112,32 @@ fn init_wasm_runtime() -> Result> { let mut linker = Linker::new(&engine); wasip1::register_handlers(&mut linker)?; - let hostfuncs = hostfuncs::get_host_function_details() - .host_functions - .unwrap_or_default(); + // Parse host function details pushed by the host as a parameter + let params = function_call.parameters.as_ref().ok_or_else(|| { + HyperlightGuestError::new( + ErrorCode::GuestFunctionParameterTypeMismatch, + "InitWasmRuntime: missing parameters".to_string(), + ) + })?; + + let bytes = match params.first() { + Some(ParameterValue::VecBytes(ref b)) => b, + _ => { + return Err(HyperlightGuestError::new( + ErrorCode::GuestFunctionParameterTypeMismatch, + "InitWasmRuntime: first parameter must be VecBytes".to_string(), + )) + } + }; + + let hfd: hostfuncs::HostFunctionDetails = bytes.as_slice().try_into().map_err(|e| { + HyperlightGuestError::new( + ErrorCode::GuestError, + alloc::format!("Failed to parse host function details: {:?}", e), + ) + })?; + let hostfuncs = hfd.host_functions.unwrap_or_default(); + for hostfunc in hostfuncs.iter() { let captured = hostfunc.clone(); linker.func_new( @@ -210,7 +233,7 @@ pub extern "C" fn hyperlight_main() { register_function(GuestFunctionDefinition::new( "InitWasmRuntime".to_string(), - vec![], + vec![ParameterType::VecBytes], ReturnType::Int, init_wasm_runtime as usize, )); From 34f68b7b9345de7453fa430c9b0269c2dad29223 Mon Sep 17 00:00:00 2001 From: James Sturtevant Date: Tue, 24 Feb 2026 11:44:06 -0800 Subject: [PATCH 2/2] Add [workspace] to excluded packages for worktree compatibility rust_wasm_samples, hyperlight_wasm_macro, and component_sample were missing empty [workspace] tables in their Cargo.toml files. Without this, Cargo resolves to the main checkout's workspace root when run from a git worktree, causing 'believes it's in a workspace' errors. wasm_runtime already had this marker. Signed-off-by: James Sturtevant --- src/component_sample/Cargo.toml | 1 + src/hyperlight_wasm_macro/Cargo.toml | 2 ++ src/rust_wasm_samples/Cargo.toml | 2 ++ src/wasm_runtime/src/component.rs | 2 +- 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/component_sample/Cargo.toml b/src/component_sample/Cargo.toml index 43ebd181..8f305af4 100644 --- a/src/component_sample/Cargo.toml +++ b/src/component_sample/Cargo.toml @@ -18,3 +18,4 @@ world = "example" [package.metadata.component.target.dependencies] +[workspace] # indicate that this crate is not part of any workspace diff --git a/src/hyperlight_wasm_macro/Cargo.toml b/src/hyperlight_wasm_macro/Cargo.toml index 18f29ca2..c3243ede 100644 --- a/src/hyperlight_wasm_macro/Cargo.toml +++ b/src/hyperlight_wasm_macro/Cargo.toml @@ -17,3 +17,5 @@ syn = { version = "2.0.117" } itertools = { version = "0.14.0" } prettyplease = { version = "0.2.37" } hyperlight-component-util = { version = "0.12.0" } + +[workspace] # indicate that this crate is not part of any workspace diff --git a/src/rust_wasm_samples/Cargo.toml b/src/rust_wasm_samples/Cargo.toml index 383d2472..b5684eec 100644 --- a/src/rust_wasm_samples/Cargo.toml +++ b/src/rust_wasm_samples/Cargo.toml @@ -14,5 +14,7 @@ opt-level = 'z' strip = true +[workspace] # indicate that this crate is not part of any workspace + [dependencies] diff --git a/src/wasm_runtime/src/component.rs b/src/wasm_runtime/src/component.rs index de07296a..f6439c3f 100644 --- a/src/wasm_runtime/src/component.rs +++ b/src/wasm_runtime/src/component.rs @@ -130,7 +130,7 @@ pub extern "C" fn hyperlight_main() { register_function(GuestFunctionDefinition::new( "InitWasmRuntime".to_string(), - vec![], + vec![ParameterType::VecBytes], ReturnType::Int, init_wasm_runtime as usize, ));