Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
### Fixed
* Fix symbol resolution in guest core dumps for sandboxes created from snapshots by @ludfjig in https://github.com/hyperlight-dev/hyperlight/pull/1618
* Reject malformed OCI snapshot metadata and non-regular artifact files during load.
* Return guest call failures from `host_bindgen!` functions as `HyperlightError` values.

## [v0.16.0] - 2026-06-26

Expand Down
16 changes: 15 additions & 1 deletion src/hyperlight_component_util/src/emit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,18 @@ pub struct Trait {
pub tvs: BTreeMap<Ident, (Option<u32>, TokenStream)>,
/// Raw tokens of the contents of the trait
pub items: TokenStream,
/// Trait items keyed by their WIT names. A resource can be reached
/// more than once, e.g. via both the import-side and the export-side
/// trait of an instance, and its trait is shared.
pub extern_decls: BTreeMap<String, TokenStream>,
}
impl Trait {
pub fn new() -> Self {
Self {
supertraits: BTreeMap::new(),
tvs: BTreeMap::new(),
items: TokenStream::new(),
extern_decls: BTreeMap::new(),
}
}
/// Collect the component tyvar indices that correspond to the
Expand Down Expand Up @@ -217,8 +222,9 @@ impl Trait {
.collect::<Vec<_>>();
let tvs = self.tv_toks();
let items = &self.items;
let extern_decls = self.extern_decls.values();
quote! {
pub trait #n #tvs #trait_colon #(#supertraits)+* { #items }
pub trait #n #tvs #trait_colon #(#supertraits)+* { #items #(#extern_decls)* }
}
}
}
Expand All @@ -230,6 +236,10 @@ impl Trait {
pub struct Mod {
pub submods: BTreeMap<Ident, Mod>,
pub items: TokenStream,
/// Definitions keyed by their WIT names. An instance can be reached
/// more than once, e.g. as both an import and an export of a component,
/// and its helper module is shared.
pub extern_decls: BTreeMap<String, TokenStream>,
pub traits: BTreeMap<Ident, Trait>,
pub impls: BTreeMap<(Vec<Ident>, Ident), TokenStream>,
}
Expand All @@ -238,6 +248,7 @@ impl Mod {
Self {
submods: BTreeMap::new(),
items: TokenStream::new(),
extern_decls: BTreeMap::new(),
traits: BTreeMap::new(),
impls: BTreeMap::new(),
}
Expand Down Expand Up @@ -294,6 +305,9 @@ impl Mod {
for (n, mut t) in self.traits {
tt.extend(t.into_tokens(n));
}
for (_, decl) in self.extern_decls {
tt.extend(decl);
}
tt.extend(self.items);
for ((ns, i), t) in self.impls {
tt.extend(quote! {
Expand Down
15 changes: 7 additions & 8 deletions src/hyperlight_component_util/src/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Box<dyn Drop>>::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::<u8>>(&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
Ok(#unmarshal)
}
}
}
Expand Down Expand Up @@ -122,7 +121,7 @@ fn emit_export_instance<'a, 'b, 'c>(s: &'c mut State<'a, 'b>, wn: WitName, it: &

let ns = wn.namespace_path();
let nsi = wn.namespace_idents();
let trait_name = kebab_to_type(wn.name);
let trait_name = kebab_to_exports_name(wn.name);
let r#trait = s.r#trait(&nsi, trait_name.clone());
let tvs = r#trait
.tvs
Expand Down Expand Up @@ -166,7 +165,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,
Expand Down Expand Up @@ -245,7 +244,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(
Expand Down
2 changes: 1 addition & 1 deletion src/hyperlight_component_util/src/resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ pub fn emit_tables<'a, 'b, 'c>(
#sphantom
}
impl<I: #bound #sv> #rtsid<I #svs> {
fn new() -> Self {
pub(crate) fn new() -> Self {
#rtsid {
#(#inits,)*
_phantomI: ::core::marker::PhantomData,
Expand Down
62 changes: 44 additions & 18 deletions src/hyperlight_component_util/src/rtypes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -573,9 +573,14 @@ 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 && s.is_export {
quote! { ::hyperlight_host::Result<#result> }
} else {
result
}
}

Expand Down Expand Up @@ -681,6 +686,9 @@ fn emit_extern_decl<'a, 'b, 'c>(
FnName::Associated(r, n) => {
let mut s = s.helper();
s.cur_trait = Some(r.clone());
if s.cur_trait().extern_decls.contains_key(ed.kebab_name) {
return quote! {};
}
let mut needs_vars = BTreeSet::new();
let mut sv = s.with_needs_vars(&mut needs_vars);
let params = ft
Expand All @@ -690,21 +698,24 @@ fn emit_extern_decl<'a, 'b, 'c>(
.collect::<Vec<_>>();
match n {
ResourceItemName::Constructor => {
sv.cur_trait().items.extend(quote! {
fn new(&mut self, #(#params),*) -> Self::T;
});
sv.cur_trait().extern_decls.insert(
ed.kebab_name.to_string(),
quote! { fn new(&mut self, #(#params),*) -> Self::T; },
);
}
ResourceItemName::Method(n) => {
let result = emit_func_result(&mut sv, &ft.result);
sv.cur_trait().items.extend(quote! {
fn #n(&mut self, #(#params),*) -> #result;
});
sv.cur_trait().extern_decls.insert(
ed.kebab_name.to_string(),
quote! { fn #n(&mut self, #(#params),*) -> #result; },
);
}
ResourceItemName::Static(n) => {
let result = emit_func_result(&mut sv, &ft.result);
sv.cur_trait().items.extend(quote! {
fn #n(&mut self, #(#params),*) -> #result;
});
sv.cur_trait().extern_decls.insert(
ed.kebab_name.to_string(),
quote! { fn #n(&mut self, #(#params),*) -> #result; },
);
}
}
for v in needs_vars {
Expand All @@ -724,9 +735,14 @@ fn emit_extern_decl<'a, 'b, 'c>(
) -> TokenStream {
let id = kebab_to_type(ed.kebab_name);
let mut s = s.helper();
if s.cur_mod().extern_decls.contains_key(ed.kebab_name) {
return TokenStream::new();
}

let t = emit_defined(&mut s, v, id, t);
s.cur_mod().items.extend(t);
s.cur_mod()
.extern_decls
.insert(ed.kebab_name.to_string(), t);
TokenStream::new()
}
let edn: &'b str = ed.kebab_name;
Expand All @@ -746,9 +762,12 @@ 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.cur_trait().items.extend(quote! {
type T: ::core::marker::Send;
});
if !s.cur_trait().extern_decls.contains_key(ed.kebab_name) {
s.cur_trait().extern_decls.insert(
ed.kebab_name.to_string(),
quote! { type T: ::core::marker::Send; },
);
}
quote! {}
}
}
Expand All @@ -762,7 +781,12 @@ 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 trait_tn = if !s.is_guest && s.is_export {
kebab_to_exports_name(wn.name)
} else {
kebab_to_type(wn.name)
};
let repr = s.r#trait(&nsids, trait_tn.clone());
let vs = if !repr.tvs.is_empty() {
let vs = repr.tvs.clone();
let tvs = vs
Expand All @@ -780,7 +804,6 @@ fn emit_extern_decl<'a, 'b, 'c>(
};
let rp = s.root_path();
let tns = wn.namespace_path();
let trait_tn = kebab_to_type(wn.name);
let trait_bound = if tns.is_empty() {
quote! { #rp #trait_tn }
} else {
Expand All @@ -803,7 +826,11 @@ fn emit_instance<'a, 'b, 'c>(s: &'c mut State<'a, 'b>, wn: WitName, it: &'c Inst
tracing::debug!("emitting instance {:?}", wn);
let mut s = s.with_cursor(wn.namespace_idents());

let name = kebab_to_type(wn.name);
let name = if !s.is_guest && s.is_export {
kebab_to_exports_name(wn.name)
} else {
kebab_to_type(wn.name)
};

s.cur_helper_mod = Some(kebab_to_namespace(wn.name));
s.cur_trait = Some(name.clone());
Expand Down Expand Up @@ -857,7 +884,6 @@ fn emit_instance<'a, 'b, 'c>(s: &'c mut State<'a, 'b>, wn: WitName, it: &'c Inst
let id = s.noff_var_id(v);
s.cur_trait().tvs.insert(id, (Some(v), TokenStream::new()));
}

s.cur_trait().items.extend(quote! { #(#exports)* });
}

Expand Down
Loading
Loading