From c1fd87473f7640f2d7152278a186cf0e219dd509 Mon Sep 17 00:00:00 2001 From: James Sturtevant Date: Fri, 7 Aug 2026 14:37:20 -0700 Subject: [PATCH] feat: host_bindgen! returns Result from guest-implemented functions A guest that traps, or a sandbox call that fails, surfaces as hyperlight_host::Result instead of a panic inside generated code. Instance and resource traits take a trailing InterfaceDirection parameter that decides how a function result and a borrowed handle are represented. Exported is the direction that runs host to guest, so a call returns a Result and a handle is a plain borrow. Imported runs guest to host, where a call returns the bare value and a handle arrives through the resource tables. The parameter defaults to Imported, so implementations of imported interfaces are written exactly as before. hyperlight_host::component holds the trait and its two markers. Keeping them there rather than emitting them alongside every set of bindings keeps them clear of the wit namespaces, where a package name could collide. The trait is sealed, since those two directions are the only ones. Deciding this per position rather than per occurrence is what makes one trait able to serve an interface that a world both imports and exports, which the test wit does with `roundtrip`. Generating two differently shaped traits instead would need the two occurrences to be told apart everywhere they are named, and would leave a resource shared between an import and an export carrying whichever signature was emitted first. State::is_export is gone. It recorded the direction at generation time, which is only correct while no item occurs on both sides. Signed-off-by: James Sturtevant --- CHANGELOG.md | 3 + src/hyperlight_component_util/src/emit.rs | 103 +++++++++++++++++--- src/hyperlight_component_util/src/guest.rs | 2 - src/hyperlight_component_util/src/host.rs | 26 ++--- src/hyperlight_component_util/src/rtypes.rs | 76 ++++++++++----- src/hyperlight_host/src/component.rs | 65 ++++++++++++ src/hyperlight_host/src/lib.rs | 2 + src/hyperlight_host/tests/wit_test.rs | 65 ++++++++---- src/tests/rust_guests/witguest/guest.wit | 5 + src/tests/rust_guests/witguest/src/main.rs | 10 ++ 10 files changed, 283 insertions(+), 74 deletions(-) create mode 100644 src/hyperlight_host/src/component.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 0546852eb..cbff55cfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Added ### Changed +* **Breaking:** `host_bindgen!` returns `hyperlight_host::Result` from every + guest-implemented function instead of panicking when the guest traps or the + call fails. * **Breaking:** Guest MSR state is now saved and restored across snapshots. `SandboxConfiguration::guest_msrs` declares the MSRs a guest depends on: declared MSRs are captured in a snapshot and restored, while every other MSR diff --git a/src/hyperlight_component_util/src/emit.rs b/src/hyperlight_component_util/src/emit.rs index 143eef91f..11d56de49 100644 --- a/src/hyperlight_component_util/src/emit.rs +++ b/src/hyperlight_component_util/src/emit.rs @@ -132,6 +132,28 @@ fn component_first_camel(s: &str) -> String { result } +/// Which `hyperlight_host::component::InterfaceDirection` marker to name at +/// the point we are emitting. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum InterfaceDirection { + /// Written `Imported`. + Imported, + /// Written `Exported`. + Exported, + /// Inside a trait generic over its direction, so name that trait's own + /// parameter, `D`. + Generic, +} +impl InterfaceDirection { + pub fn ident(&self) -> Ident { + match self { + InterfaceDirection::Imported => format_ident!("Imported"), + InterfaceDirection::Exported => format_ident!("Exported"), + InterfaceDirection::Generic => format_ident!("D"), + } + } +} + /// A representation of a trait definition that we will eventually /// emit. This is used to allow easily adding onto the trait each time /// we see an extern decl. @@ -149,6 +171,9 @@ pub struct Trait { pub tvs: BTreeMap, TokenStream)>, /// Raw tokens of the contents of the trait pub items: TokenStream, + /// The trailing direction parameter declaration, if this trait has one. + /// Last so that it can carry a default. + pub direction: Option, } impl Trait { pub fn new() -> Self { @@ -156,6 +181,7 @@ impl Trait { supertraits: BTreeMap::new(), tvs: BTreeMap::new(), items: TokenStream::new(), + direction: None, } } /// Collect the component tyvar indices that correspond to the @@ -194,11 +220,17 @@ impl Trait { /// Build a token stream for the type variable part of the trait /// declaration pub fn tv_toks(&mut self) -> TokenStream { + let mut toks = Vec::new(); if !self.tvs.is_empty() { - let toks = self.tv_toks_inner(); - quote! { <#toks> } - } else { + toks.push(self.tv_toks_inner()); + } + if let Some(d) = &self.direction { + toks.push(d.clone()); + } + if toks.is_empty() { quote! {} + } else { + quote! { <#(#toks),*> } } } /// Build a token stream for this entire trait definition @@ -390,8 +422,9 @@ pub struct State<'a, 'b> { /// wasmtime guest emit. When that is refactored to use the host /// guest emit, this can go away. pub is_wasmtime_guest: bool, - /// Are we working on an export or an import of the component type? - pub is_export: bool, + /// The direction the code we are emitting is written against. Guest + /// bindings ignore it, since they never wrap a result or a borrow. + pub direction: InterfaceDirection, /// Set of interface names that collide across different packages /// (e.g. "types" appears in both wasi:filesystem/types and wasi:http/types). /// When a name is in this set, the parent namespace is prepended to @@ -448,7 +481,7 @@ impl<'a, 'b> State<'a, 'b> { root_component_name: None, is_guest, is_wasmtime_guest, - is_export: false, + direction: InterfaceDirection::Imported, colliding_import_names: HashSet::new(), } } @@ -470,7 +503,7 @@ impl<'a, 'b> State<'a, 'b> { root_component_name: self.root_component_name.clone(), is_guest: self.is_guest, is_wasmtime_guest: self.is_wasmtime_guest, - is_export: self.is_export, + direction: self.direction, colliding_import_names: self.colliding_import_names.clone(), } } @@ -587,6 +620,50 @@ impl<'a, 'b> State<'a, 'b> { } quote! { #(#s::)* } } + /// Path to the direction trait and its markers + pub fn direction_path(&self) -> TokenStream { + quote! { ::hyperlight_host::component } + } + /// The token that names `d` + pub fn direction_marker_for(&self, d: InterfaceDirection) -> TokenStream { + let id = d.ident(); + match d { + InterfaceDirection::Generic => quote! { #id }, + _ => { + let dp = self.direction_path(); + quote! { #dp::#id } + } + } + } + /// The token that names the current direction + pub fn direction_marker(&self) -> TokenStream { + self.direction_marker_for(self.direction) + } + /// The direction argument to pass when naming an instance or resource + /// trait. [`None`] for guest bindings, whose traits take no such + /// parameter. + pub fn direction_arg(&self) -> Option { + self.direction_arg_at(self.direction) + } + /// [`State::direction_arg`] for a trait whose direction is fixed by where + /// its wit item is defined rather than by where we refer to it from + pub fn direction_arg_at(&self, d: InterfaceDirection) -> Option { + if self.is_guest { + return None; + } + Some(self.direction_marker_for(d)) + } + /// The trailing direction parameter to declare on an instance or + /// resource trait + pub fn direction_param(&self) -> Option { + if self.is_guest { + return None; + } + let dp = self.direction_path(); + let d = InterfaceDirection::Generic.ident(); + let dflt = InterfaceDirection::Imported.ident(); + Some(quote! { #d: #dp::InterfaceDirection = #dp::#dflt }) + } /// Construct a namespace token stream that can be emitted in the /// current module to refer to a name in the helper module pub fn helper_path(&self) -> TokenStream { @@ -657,13 +734,11 @@ impl<'a, 'b> State<'a, 'b> { /// Add an import/export to [`State::origin`], reflecting that we are now /// looking at code underneath it /// - /// origin_was_export differs from s.is_export in that s.is_export - /// keeps track of whether the item overall was imported or exported - /// from the root component (taking into account positivity), whereas - /// origin_was_export just checks if this particular extern_decl was - /// imported or exported from its parent instance (and so e.g. an - /// export of an instance that is imported by the root component has - /// !s.is_export && origin_was_export) + /// `origin_was_export` says whether this particular extern_decl was + /// imported or exported from its parent instance, which is not the same + /// as whether the item is imported or exported by the root component. + /// An export of an instance that the root component imports has + /// `origin_was_export` set. pub fn push_origin<'c>(&'c mut self, origin_was_export: bool, name: &'b str) -> State<'c, 'b> { let mut s = self.clone(); s.origin.push(if origin_was_export { diff --git a/src/hyperlight_component_util/src/guest.rs b/src/hyperlight_component_util/src/guest.rs index 18e555e3d..480782bea 100644 --- a/src/hyperlight_component_util/src/guest.rs +++ b/src/hyperlight_component_util/src/guest.rs @@ -315,8 +315,6 @@ fn emit_component<'a, 'b, 'c>( s.var_offset = 0; - s.is_export = true; - let exports = ct .instance .unqualified diff --git a/src/hyperlight_component_util/src/host.rs b/src/hyperlight_component_util/src/host.rs index eb94c699e..fb61e624d 100644 --- a/src/hyperlight_component_util/src/host.rs +++ b/src/hyperlight_component_util/src/host.rs @@ -18,9 +18,9 @@ use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; use crate::emit::{ - FnName, ResourceItemName, State, WitName, find_colliding_import_names, import_member_names, - kebab_to_exports_name, kebab_to_fn, kebab_to_getter, kebab_to_imports_name, kebab_to_namespace, - kebab_to_type, kebab_to_var, split_wit_name, + FnName, InterfaceDirection, ResourceItemName, State, WitName, find_colliding_import_names, + import_member_names, kebab_to_exports_name, kebab_to_fn, kebab_to_getter, + kebab_to_imports_name, kebab_to_namespace, kebab_to_type, kebab_to_var, split_wit_name, }; use crate::etypes::{Component, ExternDecl, ExternDesc, Instance, Tyvar}; use crate::hl::{ @@ -59,19 +59,18 @@ fn emit_export_extern_decl<'a, 'b, 'c>( fn #n(&mut self, #(#param_decls),*) -> #result_decl { let mut to_cleanup = Vec::>::new(); let marshalled = { - let mut rts = self.rt.lock().unwrap(); + let mut rts = self.rt.lock()?; #[allow(clippy::unused_unit)] (#(#marshal,)*) }; let #ret = ::hyperlight_host::sandbox::Callable::call::<::std::vec::Vec::>(&mut self.sb, #hln, marshalled, - ); - let ::std::result::Result::Ok(#ret) = #ret else { panic!("bad return from guest {:?}", #ret) }; + )?; #[allow(clippy::unused_unit)] - let mut rts = self.rt.lock().unwrap(); + let mut rts = self.rt.lock()?; #[allow(clippy::unused_unit)] - #unmarshal + ::std::result::Result::Ok(#unmarshal) } } } @@ -129,10 +128,13 @@ fn emit_export_instance<'a, 'b, 'c>(s: &'c mut State<'a, 'b>, wn: WitName, it: & .iter() .map(|(_, (tv, _))| tv.unwrap()) .collect::>(); - let tvs = tvs + let mut tvs = tvs .iter() .map(|tv| rtypes::emit_var_ref(&mut s, &Tyvar::Bound(*tv))) .collect::>(); + if let Some(d) = s.direction_arg() { + tvs.push(d); + } let (root_ns, root_base_name) = s.root_component_name.unwrap(); let wrapper_name = kebab_to_wrapper_name(root_base_name); let imports_name = kebab_to_imports_name(root_base_name); @@ -166,7 +168,7 @@ impl SelfInfo { orig_id, type_id: vec![format_ident!("I")], inner_preamble: quote! { - let mut #inner_id = #outer_id.lock().unwrap(); + let mut #inner_id = #outer_id.lock()?; let mut #inner_id = ::std::ops::DerefMut::deref_mut(&mut #inner_id); }, outer_id, @@ -245,7 +247,7 @@ fn emit_import_extern_decl<'a, 'b, 'c>( let #outer_id = #orig_id.clone(); let captured_rts = rts.clone(); sb.register_host_function(#hln, move |#(#pds),*| { - let mut rts = captured_rts.lock().unwrap(); + let mut rts = captured_rts.lock()?; #inner_preamble let #ret = #callname( ::std::borrow::BorrowMut::<#(#type_id)::*>::borrow_mut( @@ -347,7 +349,7 @@ fn emit_component<'a, 'b, 'c>(s: &'c mut State<'a, 'b>, wn: WitName, ct: &'c Com s.root_component_name = Some((ns.clone(), wn.name)); s.cur_trait = Some(export_trait.clone()); s.import_param_var = Some(format_ident!("I")); - s.is_export = true; + s.direction = InterfaceDirection::Exported; let exports = ct .instance diff --git a/src/hyperlight_component_util/src/rtypes.rs b/src/hyperlight_component_util/src/rtypes.rs index ffa5041c1..4f3fe6070 100644 --- a/src/hyperlight_component_util/src/rtypes.rs +++ b/src/hyperlight_component_util/src/rtypes.rs @@ -24,9 +24,10 @@ use quote::{format_ident, quote}; use syn::Ident; use crate::emit::{ - FnName, ResourceItemName, State, WitName, find_colliding_import_names, import_member_names, - kebab_to_cons, kebab_to_exports_name, kebab_to_flags_const, kebab_to_fn, kebab_to_getter, - kebab_to_imports_name, kebab_to_namespace, kebab_to_type, kebab_to_var, split_wit_name, + FnName, InterfaceDirection, ResourceItemName, State, WitName, find_colliding_import_names, + import_member_names, kebab_to_cons, kebab_to_exports_name, kebab_to_flags_const, kebab_to_fn, + kebab_to_getter, kebab_to_imports_name, kebab_to_namespace, kebab_to_type, kebab_to_var, + split_wit_name, }; use crate::etypes::{ self, Component, Defined, ExternDecl, ExternDesc, Func, Handleable, ImportExport, Instance, @@ -35,12 +36,23 @@ use crate::etypes::{ /// When referring to an instance or resource trait, emit a token /// stream that instantiates any types it is parametrized by with our -/// own best understanding of how to name the relevant type variables +/// own best understanding of how to name the relevant type variables, +/// followed by the direction we are referring from. fn emit_tvis(s: &mut State, tvs: Vec) -> TokenStream { - let tvs = tvs + let dir = s.direction; + emit_tvis_at(s, tvs, dir) +} + +/// [`emit_tvis`] for a trait whose direction is fixed by where its wit item +/// is declared rather than by where we refer to it from. +fn emit_tvis_at(s: &mut State, tvs: Vec, dir: InterfaceDirection) -> TokenStream { + let mut tvs = tvs .iter() .map(|tv| emit_var_ref(s, &Tyvar::Bound(*tv))) .collect::>(); + if let Some(d) = s.direction_arg_at(dir) { + tvs.push(d); + } if !tvs.is_empty() { quote! { <#(#tvs),*> } } else { @@ -132,7 +144,13 @@ fn emit_resource_ref(s: &mut State, n: u32, path: Vec) -> TokenStr trait_path.push(instance_mod.clone()); trait_path.push(rtrait.clone()); let t = s.resolve_trait_immut(true, &trait_path); - let tvis = emit_tvis(s, t.tv_idxs()); + // A resource belongs to the interface that declares it, so one on an + // imported instance stays imported even where an export mentions it. + let tvis = if instance.imported() { + emit_tvis_at(s, t.tv_idxs(), InterfaceDirection::Imported) + } else { + emit_tvis(s, t.tv_idxs()) + }; let trait_ref = if tns.is_empty() { quote! { #rp #instance_mod::#rtrait } } else { @@ -367,11 +385,9 @@ pub fn emit_value(s: &mut State, vt: &Value) -> TokenStream { } } else { let vr = emit_var_ref(s, tv); - if s.is_export { - quote! { &#vr } - } else { - quote! { ::hyperlight_common::resource::BorrowedResourceGuard<#vr> } - } + let dp = s.direction_path(); + let d = s.direction_marker(); + quote! { <#d as #dp::InterfaceDirection>::Borrow<'_, #vr> } } } }, @@ -573,10 +589,16 @@ pub fn emit_func_param(s: &mut State, p: &Param) -> TokenStream { /// Precondition: the result type must only be a named result if there /// are no names in it (i.e. a unit type) pub fn emit_func_result(s: &mut State, r: &etypes::Result<'_>) -> TokenStream { - match r { + let result = match r { Some(vt) => emit_value(s, vt), None => quote! { () }, + }; + if s.is_guest { + return result; } + let dp = s.direction_path(); + let d = s.direction_marker(); + quote! { <#d as #dp::InterfaceDirection>::CallResult<#result> } } /// Emit a Rust typeversion of a component function type. This is only @@ -654,8 +676,7 @@ fn emit_type_alias TokenStream>( /// Emit (via returning) a Rust trait item corresponding to this /// extern decl /// -/// See note on emit.rs push_origin for the difference between -/// origin_was_export and s.is_export. +/// See note on emit.rs push_origin for what origin_was_export means. fn emit_extern_decl<'a, 'b, 'c>( origin_was_export: bool, s: &'c mut State<'a, 'b>, @@ -681,6 +702,9 @@ fn emit_extern_decl<'a, 'b, 'c>( FnName::Associated(r, n) => { let mut s = s.helper(); s.cur_trait = Some(r.clone()); + s.direction = InterfaceDirection::Generic; + let dp = s.direction_param(); + s.cur_trait().direction = dp; let mut needs_vars = BTreeSet::new(); let mut sv = s.with_needs_vars(&mut needs_vars); let params = ft @@ -746,6 +770,9 @@ fn emit_extern_decl<'a, 'b, 'c>( s.add_helper_supertrait(rn.clone()); let mut s = s.helper(); s.cur_trait = Some(rn.clone()); + s.direction = InterfaceDirection::Generic; + let dp = s.direction_param(); + s.cur_trait().direction = dp; s.cur_trait().items.extend(quote! { type T: ::core::marker::Send; }); @@ -762,16 +789,8 @@ fn emit_extern_decl<'a, 'b, 'c>( emit_instance(&mut s, wn.clone(), it); let nsids = wn.namespace_idents(); - let repr = s.r#trait(&nsids, kebab_to_type(wn.name)); - let vs = if !repr.tvs.is_empty() { - let vs = repr.tvs.clone(); - let tvs = vs - .iter() - .map(|(_, (tv, _))| emit_var_ref(&mut s, &Tyvar::Bound(tv.unwrap()))); - quote! { <#(#tvs),*> } - } else { - TokenStream::new() - }; + let repr_tvs = s.r#trait(&nsids, kebab_to_type(wn.name)).tv_idxs(); + let vs = emit_tvis(&mut s, repr_tvs); let (member_tn, member_getter) = if origin_was_export { (kebab_to_type(wn.name), kebab_to_getter(wn.name)) @@ -822,6 +841,12 @@ fn emit_instance<'a, 'b, 'c>(s: &'c mut State<'a, 'b>, wn: WitName, it: &'c Inst return; } + // A world can import and export the same interface, so the trait is + // generic over direction and each use site picks the marker. + s.direction = InterfaceDirection::Generic; + let dp = s.direction_param(); + s.cur_trait().direction = dp; + let mut needs_vars = BTreeSet::new(); let mut sv = s.with_needs_vars(&mut needs_vars); @@ -882,6 +907,7 @@ fn emit_component<'a, 'b, 'c>(s: &'c mut State<'a, 'b>, wn: WitName, ct: &'c Com .collect::>(); s.cur_trait = Some(import_name.clone()); s.colliding_import_names = find_colliding_import_names(&ct.imports); + s.direction = InterfaceDirection::Imported; let imports = ct .imports .iter() @@ -891,7 +917,7 @@ fn emit_component<'a, 'b, 'c>(s: &'c mut State<'a, 'b>, wn: WitName, ct: &'c Com s.adjust_vars(ct.instance.evars.len() as u32); s.import_param_var = Some(format_ident!("I")); - s.is_export = true; + s.direction = InterfaceDirection::Exported; let export_name = kebab_to_exports_name(wn.name); *s.bound_vars = ct diff --git a/src/hyperlight_host/src/component.rs b/src/hyperlight_host/src/component.rs new file mode 100644 index 000000000..7de71daa6 --- /dev/null +++ b/src/hyperlight_host/src/component.rs @@ -0,0 +1,65 @@ +/* +Copyright 2025 The Hyperlight Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + */ + +//! Support types for the bindings that `host_bindgen!` generates. + +use hyperlight_common::resource::BorrowedResourceGuard; + +mod private { + pub trait Sealed {} +} + +/// Whether a component imports or exports an interface, and so which way +/// calls to it cross the VM boundary. +/// +/// A wit interface can appear as both an import and an export of one world, +/// so a single generated trait has to serve both. Instance and resource +/// traits take a trailing parameter of this trait, and each use site picks +/// the marker. +/// +/// Sealed: [`Imported`] and [`Exported`] are the only directions. +pub trait InterfaceDirection: private::Sealed { + /// How a call to one of the interface's functions returns. + type CallResult; + /// How a borrowed resource handle reaches the implementation. + type Borrow<'a, T: 'a>; +} + +/// The component imports the interface, so calls run guest to host and the +/// host implements it. +pub struct Imported; + +/// The component exports the interface, so calls run host to guest and the +/// guest implements it. +pub struct Exported; + +impl private::Sealed for Imported {} +impl private::Sealed for Exported {} + +impl InterfaceDirection for Imported { + /// A host implementation is called directly, so it cannot fail. + type CallResult = T; + /// A handle arrives as an index into the resource table, held borrowed + /// for the duration of the call. + type Borrow<'a, T: 'a> = BorrowedResourceGuard<'a, T>; +} + +impl InterfaceDirection for Exported { + /// Every call crosses into the VM, where the guest can trap. + type CallResult = crate::Result; + /// The host owns the value, so it hands out a plain reference. + type Borrow<'a, T: 'a> = &'a T; +} diff --git a/src/hyperlight_host/src/lib.rs b/src/hyperlight_host/src/lib.rs index 162d0420f..e68e9e6e4 100644 --- a/src/hyperlight_host/src/lib.rs +++ b/src/hyperlight_host/src/lib.rs @@ -49,6 +49,8 @@ use std::sync::Once; pub(crate) mod built_info { include!(concat!(env!("OUT_DIR"), "/built.rs")); } +/// Support types for the bindings that `host_bindgen!` generates +pub mod component; /// Dealing with errors, including errors across VM boundaries pub mod error; /// Wrappers for host and guest functions. diff --git a/src/hyperlight_host/tests/wit_test.rs b/src/hyperlight_host/tests/wit_test.rs index df3342568..4ca5d5b84 100644 --- a/src/hyperlight_host/tests/wit_test.rs +++ b/src/hyperlight_host/tests/wit_test.rs @@ -293,9 +293,12 @@ fn sb() -> TestSandbox { mod wit_test { + use hyperlight_host::HyperlightError; use proptest::prelude::*; - use crate::bindings::test::wit::{Roundtrip, TestExports, TestHostResource, roundtrip}; + use crate::bindings::test::wit::{ + Failable, Roundtrip, TestExports, TestHostResource, roundtrip, + }; use crate::sb; prop_compose! { @@ -347,7 +350,7 @@ mod wit_test { proptest! { #[test] fn $fn(x $($ty)*) { - assert_eq!(x, sb().roundtrip().$fn(x.clone())) + assert_eq!(x, sb().roundtrip().$fn(x.clone()).unwrap()) } } } @@ -394,7 +397,16 @@ mod wit_test { #[test] fn test_roundtrip_no_result() { - sb().roundtrip().roundtrip_no_result(42); + sb().roundtrip().roundtrip_no_result(42).unwrap(); + } + + #[test] + fn test_guest_trap_returns_error() { + let err = sb().failable().will_trap().unwrap_err(); + assert!( + matches!(err, HyperlightError::GuestAborted(_, _)), + "unexpected error: {err:?}" + ); } use std::sync::atomic::Ordering::Relaxed; @@ -404,7 +416,7 @@ mod wit_test { let guard = crate::SERIALIZE_TEST_RESOURCE_TESTS.lock(); crate::HAS_BEEN_DROPPED.store(false, Relaxed); { - sb().test_host_resource().test_uses_locally(); + sb().test_host_resource().test_uses_locally().unwrap(); } assert!(crate::HAS_BEEN_DROPPED.load(Relaxed)); drop(guard); @@ -416,10 +428,10 @@ mod wit_test { { let mut sb = sb(); let inst = sb.test_host_resource(); - let r = inst.test_makes(); - inst.test_accepts_borrow(&r); - inst.test_accepts_own(r); - inst.test_returns(); + let r = inst.test_makes().unwrap(); + inst.test_accepts_borrow(&r).unwrap(); + inst.test_accepts_own(r).unwrap(); + inst.test_returns().unwrap(); } assert!(crate::HAS_BEEN_DROPPED.load(Relaxed)); drop(guard); @@ -482,6 +494,8 @@ mod bindgen_test_case_bindings { hyperlight_component_macro::host_bindgen!(wit: "../tests/rust_guests/witguest/bindgen-test-cases"); } mod bindgen_test_cases { + use hyperlight_host::component::Exported; + use crate::bindgen_test_case_bindings::*; #[test] @@ -495,29 +509,38 @@ mod bindgen_test_cases { #[allow(dead_code)] struct ExportHost; - impl test::bindgen_test_cases::Executor for ExportHost { - fn execute(&mut self) -> test::bindgen_test_cases::executor::ExecutionResult { - test::bindgen_test_cases::executor::ExecutionResult { + impl test::bindgen_test_cases::Executor for ExportHost { + fn execute( + &mut self, + ) -> hyperlight_host::Result { + Ok(test::bindgen_test_cases::executor::ExecutionResult { message: String::from("executed"), - } + }) } } - impl test::bindgen_test_cases::Types for ExportHost { - fn get_status(&mut self) -> test::bindgen_test_cases::types::Status { - test::bindgen_test_cases::types::Status { + impl test::bindgen_test_cases::Types for ExportHost { + fn get_status( + &mut self, + ) -> hyperlight_host::Result { + Ok(test::bindgen_test_cases::types::Status { message: String::from("ok"), - } + }) } } - impl test::bindgen_test_cases::UsesExportedTypes - for ExportHost + impl + test::bindgen_test_cases::UsesExportedTypes< + test::bindgen_test_cases::types::Status, + Exported, + > for ExportHost { - fn get_status(&mut self) -> test::bindgen_test_cases::types::Status { - test::bindgen_test_cases::types::Status { + fn get_status( + &mut self, + ) -> hyperlight_host::Result { + Ok(test::bindgen_test_cases::types::Status { message: String::from("ok"), - } + }) } } diff --git a/src/tests/rust_guests/witguest/guest.wit b/src/tests/rust_guests/witguest/guest.wit index 9ed164b80..3000e0105 100644 --- a/src/tests/rust_guests/witguest/guest.wit +++ b/src/tests/rust_guests/witguest/guest.wit @@ -5,6 +5,7 @@ world test { import host-resource; export roundtrip; export test-host-resource; + export failable; } interface roundtrip { @@ -89,4 +90,8 @@ interface test-host-resource { test-accepts-borrow: func(x: borrow); test-accepts-own: func(x: own); test-returns: func() -> own; +} + +interface failable { + will-trap: func() -> string; } \ No newline at end of file diff --git a/src/tests/rust_guests/witguest/src/main.rs b/src/tests/rust_guests/witguest/src/main.rs index 307d9cca6..57219a97d 100644 --- a/src/tests/rust_guests/witguest/src/main.rs +++ b/src/tests/rust_guests/witguest/src/main.rs @@ -204,6 +204,12 @@ impl test::wit::TestHostResource<::T> for Guest { } } +impl test::wit::Failable for Guest { + fn will_trap(&mut self) -> String { + panic!("deliberate guest crash") + } +} + #[allow(refining_impl_trait)] impl test::wit::TestExports for Guest { type Roundtrip = Self; @@ -214,6 +220,10 @@ impl test::wit::TestExports for Guest { fn test_host_resource(&mut self) -> &mut Self { self } + type Failable = Self; + fn failable(&mut self) -> &mut Self { + self + } } static GUEST_STATE: Mutex = Mutex::new(Guest {