From efb997f706950b36ab77e100504897adeaa6b4f7 Mon Sep 17 00:00:00 2001 From: danbugs Date: Mon, 20 Apr 2026 22:16:35 +0000 Subject: [PATCH] feat(host): implement Registerable for MultiUseSandbox MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The primary entry point for registering host functions is the UninitializedSandbox — that's the lifecycle phase where the guest hasn't yet been allowed to issue host calls, so the registry can be populated safely up front. There are, however, cases where a MultiUseSandbox is obtained without traversing the Uninitialized → evolve() path: - Sandboxes loaded from a persisted snapshot. - Any future API that yields a MultiUseSandbox directly. In those cases the caller never had a chance to call register_host_function on an UninitializedSandbox. Today they have to drop down to internal fields or fork the registry manually. Extend the Registerable trait impl to MultiUseSandbox so late registration is a one-liner. Semantics are unchanged: the guest's dispatcher resolves host functions by name at call time, so inserting into the registry after evolve() is safe as long as the first host-call happens after registration completes. Also flips MultiUseSandbox.host_funcs from pub(super) to pub(crate) so the impl in src/func/host_functions.rs can reach it. Signed-off-by: danbugs --- .../src/func/host_functions.rs | 40 +++++++++++++++++++ .../src/sandbox/initialized_multi_use.rs | 2 +- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/hyperlight_host/src/func/host_functions.rs b/src/hyperlight_host/src/func/host_functions.rs index 6deaaa340..e87fa70b0 100644 --- a/src/hyperlight_host/src/func/host_functions.rs +++ b/src/hyperlight_host/src/func/host_functions.rs @@ -56,6 +56,46 @@ impl Registerable for UninitializedSandbox { } } +/// Allow registering host functions on an already-evolved +/// [`crate::MultiUseSandbox`]. +/// +/// The primary entry point for host-function registration is the +/// `UninitializedSandbox` impl above — that's the lifecycle phase +/// where the guest hasn't yet been allowed to issue host calls. +/// There are, however, cases where a `MultiUseSandbox` is obtained +/// without traversing the `Uninitialized → evolve()` path: +/// +/// - Sandboxes loaded from a persisted snapshot. +/// - Any future API that yields a `MultiUseSandbox` directly. +/// +/// In those cases the caller never had a chance to call +/// `register_host_function` on an `UninitializedSandbox`, so we +/// expose the same trait implementation here for late registration. +/// The guest's host-function dispatcher resolves by name at call +/// time, so inserting into the registry after `evolve()` is +/// semantically safe as long as the first host-function invocation +/// happens after registration completes. +impl Registerable for crate::MultiUseSandbox { + fn register_host_function( + &mut self, + name: &str, + hf: impl Into>, + ) -> Result<()> { + let mut hfs = self + .host_funcs + .try_lock() + .map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?; + + let entry = FunctionEntry { + function: hf.into().into(), + parameter_types: Args::TYPE, + return_type: Output::TYPE, + }; + + (*hfs).register_host_function(name.to_string(), entry) + } +} + /// A representation of a host function. /// This is a thin wrapper around a `Fn(Args) -> Result`. #[derive(Clone)] diff --git a/src/hyperlight_host/src/sandbox/initialized_multi_use.rs b/src/hyperlight_host/src/sandbox/initialized_multi_use.rs index 72de96035..5ecc901ed 100644 --- a/src/hyperlight_host/src/sandbox/initialized_multi_use.rs +++ b/src/hyperlight_host/src/sandbox/initialized_multi_use.rs @@ -86,7 +86,7 @@ pub struct MultiUseSandbox { id: u64, /// Whether this sandbox is poisoned poisoned: bool, - pub(super) host_funcs: Arc>, + pub(crate) host_funcs: Arc>, pub(crate) mem_mgr: SandboxMemoryManager, vm: HyperlightVm, #[cfg(gdb)]