Skip to content
Merged
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
70 changes: 70 additions & 0 deletions src/hyperlight_common/src/component.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
Copyright 2026 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 crate::resource::BorrowedResourceGuard;

mod private {
pub trait Sealed {}
}

/// Whether an instance/resource/etc is being used in a positive or
/// negative position in the top-level component type that governs the
/// interface. That is to say, whether the functions provided by this
/// instance/resource (or exported by this component) are expected to
/// be implemented in the guest and called on the host or vice versa.
///
/// We say that a piece of a top-level component type is in "negative
/// position" if it is on the left hand side of an odd number of
/// arrows, and positive otherwise. With only first-order component
/// types, this distinction collapses to whether it is part of an
/// import (negative) or export (positive), but with higher-order
/// components, this is no longer the case. For example, if a
/// component imports another component which itself imports some
/// functions, those functions are in positive position in the overall
/// type---because they are supplied by the guest when it instantiates
/// the component it imported---even though they are syntactically
/// imports.
pub trait Positivity: private::Sealed {
type NegativeOfThis: Positivity<NegativeOfThis = Self>;
/// How a borrowed resource handle reaches the implementation.
type Borrow<'a, T: 'a>;
}

/// A type is being used in a negative position in the overall type:
/// it is implemented by the host, and the guest calls it.
pub enum Negative {}

/// A type is being used in a positive position in the overall type:
/// it is implemented by the guest, and the host calls it.
pub enum Positive {}

impl private::Sealed for Negative {}
impl private::Sealed for Positive {}

impl Positivity for Negative {
type NegativeOfThis = Positive;
/// 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 Positivity for Positive {
type NegativeOfThis = Negative;
/// The host owns the value, so it hands out a plain reference.
type Borrow<'a, T: 'a> = &'a T;
}
4 changes: 4 additions & 0 deletions src/hyperlight_common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ limitations under the License.

extern crate alloc;

/// cbindgen:ignore
/// Support types for the bindings that `{host,guest}_bindgen!` generates
pub mod component;

pub mod flatbuffer_wrappers;
/// cbindgen:ignore
/// FlatBuffers-related utilities and (mostly) generated code
Expand Down
56 changes: 37 additions & 19 deletions src/hyperlight_component_util/src/emit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ fn component_first_camel(s: &str) -> String {
/// 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.
#[derive(Debug, Default)]
#[derive(Clone, Debug, Default)]
pub struct Trait {
/// A set of supertrait constraints, each associated with a
/// bindings module path
Expand Down Expand Up @@ -194,11 +194,12 @@ impl Trait {
/// Build a token stream for the type variable part of the trait
/// declaration
pub fn tv_toks(&mut self) -> TokenStream {
let p = quote! { P: ::hyperlight_common::component::Positivity };
if !self.tvs.is_empty() {
let toks = self.tv_toks_inner();
quote! { <#toks> }
quote! { <#p, #toks> }
} else {
quote! {}
quote! { <#p> }
}
}
/// Build a token stream for this entire trait definition
Expand Down Expand Up @@ -226,12 +227,12 @@ impl Trait {
/// A representation of a module definition that we will eventually
/// emit. This is used to allow easily adding onto the module each time
/// we see a relevant decl.
#[derive(Debug, Default)]
#[derive(Clone, Debug, Default)]
pub struct Mod {
pub submods: BTreeMap<Ident, Mod>,
pub items: TokenStream,
pub traits: BTreeMap<Ident, Trait>,
pub impls: BTreeMap<(Vec<Ident>, Ident), TokenStream>,
pub impls: BTreeMap<(Vec<Ident>, Ident), (TokenStream, TokenStream)>,
}
impl Mod {
pub fn empty() -> Self {
Expand Down Expand Up @@ -268,7 +269,7 @@ impl Mod {
///
/// Currently, we don't track much information about these, so
/// it's just a mutable token stream.
pub fn r#impl<'a>(&'a mut self, t: Vec<Ident>, i: Ident) -> &'a mut TokenStream {
pub fn r#impl<'a>(&'a mut self, t: Vec<Ident>, i: Ident) -> &'a mut (TokenStream, TokenStream) {
self.impls.entry((t, i)).or_default()
}
/// See [`State::adjust_vars`].
Expand All @@ -295,9 +296,9 @@ impl Mod {
tt.extend(t.into_tokens(n));
}
tt.extend(self.items);
for ((ns, i), t) in self.impls {
for ((ns, i), (tvi, t)) in self.impls {
tt.extend(quote! {
impl #(#ns)::* for #i { #t }
impl #(#ns)::* #tvi for #i { #t }
})
}
tt
Expand Down Expand Up @@ -404,6 +405,10 @@ pub struct State<'a, 'b> {
/// `self_param_var`, which will need to be fixed when extending
/// higher-order component bindings generation to impls.
pub self_param_var: Option<TokenStream>,
/// The Rust type parameter used to represent the type that
/// provides the positivity of the (eventual use of the) current
/// component
pub positivity_param: Option<TokenStream>,
/// Whether we are emitting an implementation of the component
/// interfaces, or just the types of the interface
pub is_impl: bool,
Expand All @@ -417,8 +422,6 @@ 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,
/// 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
Expand Down Expand Up @@ -471,11 +474,11 @@ impl<'a, 'b> State<'a, 'b> {
vars_needs_vars,
import_param_var: None,
self_param_var: None,
positivity_param: None,
is_impl: false,
root_component_name: None,
is_guest,
is_wasmtime_guest,
is_export: false,
colliding_import_names: HashSet::new(),
}
}
Expand All @@ -492,12 +495,12 @@ impl<'a, 'b> State<'a, 'b> {
cur_needs_vars: self.cur_needs_vars.as_deref_mut(),
vars_needs_vars: self.vars_needs_vars,
import_param_var: self.import_param_var.clone(),
positivity_param: self.positivity_param.clone(),
self_param_var: self.self_param_var.clone(),
is_impl: self.is_impl,
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,
colliding_import_names: self.colliding_import_names.clone(),
}
}
Expand Down Expand Up @@ -542,6 +545,17 @@ impl<'a, 'b> State<'a, 'b> {
s.cur_needs_vars = Some(needs_vars);
s
}
/// Copy the state, replacing its [`State::root_mod`] reference,
/// allowing a caller to capture _only_ the effects on
/// [`State::cur_needs_vars`]/[`State::vars_needs_vars`] of an
/// emit run with the resultant state
pub fn for_var_effects_only<F: for<'c> FnOnce(&mut State<'c, 'b>)>(&mut self, f: F) {
let mut new_mod = self.root_mod.clone();
let mut s = self.clone();
s.root_mod = &mut new_mod;
f(&mut s);
}
Comment thread
syntactically marked this conversation as resolved.

/// Record that an emit sequence needed a var, given an absolute
/// index for the var (i.e. ignoring [`State::var_offset`])
pub fn need_noff_var(&mut self, n: u32) {
Expand Down Expand Up @@ -684,13 +698,17 @@ 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 does not keep track of whether the item
/// overall was imported or exported from the root component
/// (taking into account positivity); it 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 origin_was_export). Any
/// decisions that depend on positivity from the root component
/// should be made part of the
/// [`hyperlight_common::component::Positivity`] trait, which
/// correctly handles the fact that the same interface trait may
/// be used in both positive and negative positions.
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 {
Expand Down
30 changes: 16 additions & 14 deletions src/hyperlight_component_util/src/guest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,10 @@ fn emit_import_extern_decl<'a, 'b, 'c>(
// here, but that is not the case at the
// moment.
let path = s.resource_trait_path(r);
s.root_mod.r#impl(path, format_ident!("Host")).extend(decl);
s.root_mod
.r#impl(path, format_ident!("Host"))
.1
.extend(decl);
TokenStream::new()
Comment thread
syntactically marked this conversation as resolved.
}
}
Expand All @@ -105,11 +108,11 @@ fn emit_import_extern_decl<'a, 'b, 'c>(
};
let rtid = format_ident!("HostResource{}", noff);
let path = s.resource_trait_path(kebab_to_type(ed.kebab_name));
s.root_mod
.r#impl(path, format_ident!("Host"))
.extend(quote! {
type T = #rtid;
});
let r#impl = s.root_mod.r#impl(path, format_ident!("Host"));
r#impl.0 = quote! { <::hyperlight_common::component::Negative> };
r#impl.1.extend(quote! {
type T = #rtid;
});
TokenStream::new()
}
_ => quote! {},
Expand Down Expand Up @@ -152,7 +155,7 @@ fn emit_import_instance<'a, 'b, 'c>(s: &'c mut State<'a, 'b>, wn: WitName, it: &
.chain(&[kebab_to_type(wn.name)])
.cloned()
.collect::<Vec<_>>();
let trait_ref = rtypes::trait_ref(&mut s, true, &trait_path);
let trait_ref = rtypes::trait_ref(&mut s, rtypes::EmitPositivity::Opposite, true, &trait_path);
s.root_mod.items.extend(quote! {
impl #trait_ref for Host {
#(#imports)*
Expand Down Expand Up @@ -279,6 +282,7 @@ fn emit_component<'a, 'b, 'c>(
let r#trait = kebab_to_type(wn.name);
let import_trait = kebab_to_imports_name(wn.name);
let export_trait = kebab_to_exports_name(wn.name);
s.positivity_param = Some(quote! { ::hyperlight_common::component::Positive });
// We don't set s.self_param_var or s.import_param_var at all
// here, because they are currently obviated by the (s.is_guest &&
// s.is_impl) hack in rtypes::emit_resource_ref. For when we
Expand All @@ -292,8 +296,8 @@ fn emit_component<'a, 'b, 'c>(
resource::emit_tables(
&mut s,
rtsid.clone(),
quote! { #ns::#import_trait + ::core::marker::Send + 'static },
Some(quote! { #ns::#export_trait<I> }),
quote! { #ns::#import_trait<::hyperlight_common::component::Negative> + ::core::marker::Send + 'static },
Some(quote! { #ns::#export_trait<::hyperlight_common::component::Positive, I> }),
true,
);
s.root_mod
Expand All @@ -313,17 +317,15 @@ fn emit_component<'a, 'b, 'c>(
.map(|ed| emit_import_extern_decl(&mut s, ed))
.collect::<Vec<_>>();
s.var_offset = 0;
s.positivity_param = Some(quote! { ::hyperlight_common::component::Positive });
// We don't set s.self_param_var or s.import_param_var at all
// here, because it is currently obviated by the (s.is_guest &&
// s.is_impl) hack in rtypes::emit_resource_ref. For when we
// eventually do:
//
// See Note [Origin paths and self parameters in impl codegen for higher-order components]
// in emit.rs

s.is_export = true;
s.cur_trait = Some(export_trait.clone());

let exports = ct
.instance
.unqualified
Expand All @@ -333,7 +335,7 @@ fn emit_component<'a, 'b, 'c>(
.collect::<Vec<_>>();

s.root_mod.items.extend(quote! {
impl #ns::#import_trait for Host {
impl #ns::#import_trait<::hyperlight_common::component::Negative> for Host {
#(#imports)*
}
});
Expand Down Expand Up @@ -365,7 +367,7 @@ pub fn emit_toplevel<'a, 'b, 'c>(s: &'c mut State<'a, 'b>, n: &str, ct: &'c Comp
/// Because Hyperlight guest functions can't close over any
/// state, this function is used on each guest call to acquire
/// any state that the guest functions might need.
pub trait Guest: #ns::#export_trait<Host> {
pub trait Guest: #ns::#export_trait<::hyperlight_common::component::Positive, Host> {
fn with_guest_state<R, F: FnOnce(&mut Self) -> R>(f: F) -> R;
}
/// Register all guest functions.
Expand Down
Loading
Loading