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 {