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
31 changes: 29 additions & 2 deletions src/hyperlight_component_util/src/emit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,8 +375,35 @@ pub struct State<'a, 'b> {
/// implements the imports of a component
pub import_param_var: Option<Ident>,
/// The Rust type parameter used to represent the current Rust
/// state type
pub self_param_var: Option<Ident>,
/// state type. In particular, this should let one find a `Self`
/// which Rust understands to be an impl of the instance trait
/// that `s.origin` refers to.
///
/// # Note \[Origin paths and self parameters in impl codegen for higher-order components\]
///
/// When extending impl (host.rs/guest.rs, as opposed to
/// rtypes.rs) codegen to higher-order components, it's not
/// entirely clear whether we should have this/the origin path
/// always refer to the true root component, or just to the
/// nearest component in which we are currently working. At first
/// glance, updating the origin path at all during impl codegen
/// seems like a bad idea, since the impl should be instantiating
/// the Rust tyvars and can't generate new ones for
/// non-locally-defined types the way that rtypes codegen
/// can. However, it may be the case that referring to a tyvar
/// that does not follow our local definedness rules at the
/// granularity of components will be impossible due to the
/// `outer_boundary` rules! If this does turn out to be the case,
/// then updating `origin` on every instance the way that rtypes
/// does will still be a bad idea, but we might want to consider
/// updating it once per /component/.
///
/// Whether or not `origin` gets updated, the trait bounds in
/// `self_param_var` need to match this. Currently, code in
/// host.rs/guest.rs does not update `origin`, but does update
/// `self_param_var`, which will need to be fixed when extending
/// higher-order component bindings generation to impls.
pub self_param_var: Option<TokenStream>,
/// Whether we are emitting an implementation of the component
/// interfaces, or just the types of the interface
pub is_impl: bool,
Expand Down
35 changes: 20 additions & 15 deletions src/hyperlight_component_util/src/guest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,21 +146,15 @@ fn emit_import_instance<'a, 'b, 'c>(s: &'c mut State<'a, 'b>, wn: WitName, it: &
.map(|ed| emit_import_extern_decl(&mut s, ed))
.collect::<Vec<_>>();

let ns = wn.namespace_path();
let nsi = wn.namespace_idents();
let trait_name = kebab_to_type(wn.name);
let r#trait = s.r#trait(&nsi, trait_name.clone());
let tvs = r#trait
.tvs
.iter()
.map(|(_, (tv, _))| tv.unwrap())
.collect::<Vec<_>>();
let tvs = tvs
let trait_path = wn
.namespace_idents()
.iter()
.map(|tv| rtypes::emit_var_ref(&mut s, &Tyvar::Bound(*tv)))
.chain(&[kebab_to_type(wn.name)])
.cloned()
.collect::<Vec<_>>();
let trait_ref = rtypes::trait_ref(&mut s, true, &trait_path);
s.root_mod.items.extend(quote! {
impl #ns::#trait_name <#(#tvs),*> for Host {
impl #trait_ref for Host {
#(#imports)*
}
});
Expand Down Expand Up @@ -285,8 +279,13 @@ 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.import_param_var = Some(format_ident!("I"));
s.self_param_var = Some(format_ident!("S"));
// 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
// eventually do:
//
// See Note [Origin paths and self parameters in impl codegen for higher-order components]
// in emit.rs
s.colliding_import_names = find_colliding_import_names(&ct.imports);

let rtsid = format_ident!("{}Resources", r#trait);
Expand All @@ -313,8 +312,14 @@ fn emit_component<'a, 'b, 'c>(
.iter()
.map(|ed| emit_import_extern_decl(&mut s, ed))
.collect::<Vec<_>>();

s.var_offset = 0;
// 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());
Expand Down
63 changes: 51 additions & 12 deletions src/hyperlight_component_util/src/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ limitations under the License.
*/

use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};
use quote::{ToTokens, format_ident, quote};

use crate::emit::{
FnName, ResourceItemName, State, WitName, find_colliding_import_names, import_member_names,
Expand Down Expand Up @@ -153,18 +153,19 @@ fn emit_export_instance<'a, 'b, 'c>(s: &'c mut State<'a, 'b>, wn: WitName, it: &
#[derive(Clone)]
struct SelfInfo {
orig_id: Ident,
type_id: Vec<Ident>,
/// identifier + trait bound
type_id: Vec<(Ident, TokenStream)>,
outer_id: Ident,
inner_preamble: TokenStream,
inner_id: Ident,
}
impl SelfInfo {
fn new(orig_id: Ident) -> Self {
fn new(orig_id: Ident, imports_trait_bound: TokenStream) -> Self {
let outer_id = format_ident!("captured_{}", orig_id);
let inner_id = format_ident!("slf");
SelfInfo {
orig_id,
type_id: vec![format_ident!("I")],
type_id: vec![(format_ident!("I"), imports_trait_bound)],
inner_preamble: quote! {
let mut #inner_id = #outer_id.lock().unwrap();
let mut #inner_id = ::std::ops::DerefMut::deref_mut(&mut #inner_id);
Expand All @@ -173,16 +174,37 @@ impl SelfInfo {
inner_id,
}
}
fn type_inst(&self) -> TokenStream {
self.type_id[1..]
.iter()
.fold(
(
self.type_id[0].0.to_token_stream() as TokenStream,
&self.type_id[0].1,
),
Comment thread
syntactically marked this conversation as resolved.
Resolved
|(toks, last_bound), (tid, next_bound)| {
(quote! { <#toks as #last_bound>::#tid }, next_bound)
},
)
.0
}
/// Adjust a [`SelfInfo`] to get the portion of the state for the
/// current instance via calling the given getter
fn with_getter(&self, tp: TokenStream, type_name: Ident, getter: Ident) -> Self {
fn with_getter(
&self,
tp: TokenStream,
type_name: Ident,
type_bound: TokenStream,
getter: Ident,
) -> Self {
let mut toks = self.inner_preamble.clone();
let id = self.inner_id.clone();
let mut type_id = self.type_id.clone();
let type_inst = self.type_inst();
toks.extend(quote! {
let mut #id = #tp::#getter(::std::borrow::BorrowMut::<#(#type_id)::*>::borrow_mut(&mut #id));
let mut #id = #tp::#getter(::std::borrow::BorrowMut::<#type_inst>::borrow_mut(&mut #id));
});
type_id.push(type_name);
let mut type_id = self.type_id.clone();
type_id.push((type_name, type_bound));
SelfInfo {
orig_id: self.orig_id.clone(),
type_id,
Expand Down Expand Up @@ -232,12 +254,13 @@ fn emit_import_extern_decl<'a, 'b, 'c>(
}
}
};
let type_inst = get_self.type_inst();
let SelfInfo {
orig_id,
type_id,
outer_id,
inner_preamble,
inner_id,
..
} = get_self;
let ret = format_ident!("ret");
let marshal_result = emit_hl_marshal_result(s, ret.clone(), &ft.result);
Expand All @@ -248,7 +271,7 @@ fn emit_import_extern_decl<'a, 'b, 'c>(
let mut rts = captured_rts.lock().unwrap();
#inner_preamble
let #ret = #callname(
::std::borrow::BorrowMut::<#(#type_id)::*>::borrow_mut(
::std::borrow::BorrowMut::<#type_inst>::borrow_mut(
&mut #inner_id
),
#(#pus),*
Expand All @@ -267,7 +290,14 @@ fn emit_import_extern_decl<'a, 'b, 'c>(
let wn = split_wit_name(ed.kebab_name);
let (type_name, getter) = import_member_names(&wn, &s.colliding_import_names);
let tp = s.cur_trait_path();
let get_self = get_self.with_getter(tp, type_name, getter);
let trait_path = wn
.namespace_idents()
.iter()
.chain(&[kebab_to_type(wn.name)])
.cloned()
.collect::<Vec<_>>();
let trait_ref = rtypes::trait_ref(&mut s, true, &trait_path);
let get_self = get_self.with_getter(tp, type_name, trait_ref, getter);
emit_import_instance(&mut s, get_self, wn.clone(), it)
}
ExternDesc::Component(_) => {
Expand Down Expand Up @@ -340,13 +370,22 @@ fn emit_component<'a, 'b, 'c>(s: &'c mut State<'a, 'b>, wn: WitName, ct: &'c Com
let imports = ct
.imports
.iter()
.map(|ed| emit_import_extern_decl(&mut s, SelfInfo::new(import_id.clone()), ed))
.map(|ed| {
emit_import_extern_decl(
&mut s,
SelfInfo::new(import_id.clone(), quote! { #ns::#import_trait }),
ed,
)
})
.collect::<Vec<_>>();
s.var_offset = 0;

s.root_component_name = Some((ns.clone(), wn.name));
s.cur_trait = Some(export_trait.clone());
s.import_param_var = Some(format_ident!("I"));
// See Note [Origin paths and self parameters in impl codegen for higher-order components]
// in emit.rs
s.self_param_var = Some(quote! { <Self as #ns::#export_trait<I>> });
s.is_export = true;

let exports = ct
Expand Down
96 changes: 48 additions & 48 deletions src/hyperlight_component_util/src/rtypes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ use crate::etypes::{
fn emit_tvis(s: &mut State, tvs: Vec<u32>) -> TokenStream {
let tvs = tvs
.iter()
.map(|tv| emit_var_ref(s, &Tyvar::Bound(*tv)))
.map(|tv| emit_var_ref_noff(s, *tv, false))
.collect::<Vec<_>>();
if !tvs.is_empty() {
quote! { <#(#tvs),*> }
Expand All @@ -48,6 +48,14 @@ fn emit_tvis(s: &mut State, tvs: Vec<u32>) -> TokenStream {
}
}

/// Construct a token stream referencing a trait at a given trait path
pub(crate) fn trait_ref(s: &mut State, absolute: bool, path: &[Ident]) -> TokenStream {
let rp = s.root_path();
let t = s.resolve_trait_immut(absolute, path);
let tvis = emit_tvis(s, t.tv_idxs());
quote! { #rp #(#path)::* #tvis }
}

/// Emit a token stream that references the type of a particular resource
///
/// - `n`: the absolute index (i.e. ignoring [`State::var_offset`]) of
Expand Down Expand Up @@ -90,55 +98,47 @@ fn emit_resource_ref(s: &mut State, n: u32, path: Vec<ImportExport>) -> TokenStr

// Generally speaking, the structure that we expect to see in
// `path` ends in an instance that exports the resource type,
// followed by the resource type itself. We locate the resource
// trait by using that final instance name directly; any other
// names are just used to get to the type that implements it
let instance = &path[path.len() - 2];
let iwn = split_wit_name(instance.name());
let extras = path[0..path.len() - 2]
.iter()
.map(|p| {
let wn = split_wit_name(p.name());
if p.imported() && s.colliding_import_names.contains(wn.name) {
let (tn, _) = import_member_names(&wn, &s.colliding_import_names);
tn
} else {
kebab_to_type(wn.name)
}
})
.collect::<Vec<_>>();
let extras = quote! { #(#extras::)* };
let rp = s.root_path();
let tns = iwn.namespace_path();
let instance_mod = kebab_to_namespace(iwn.name);
// Use the disambiguated trait member only for imported instances. Exported
// instances must keep their public WIT member names.
let instance_type = if instance.imported() {
let (tn, _) = import_member_names(&iwn, &s.colliding_import_names);
tn
} else {
kebab_to_type(iwn.name)
};
let mut sv = quote! { Self };
if instance.imported() {
// followed by the resource type itself.

let mut toks = quote! { Self };
if path[0].imported() {
if let Some(iv) = &s.import_param_var {
sv = quote! { #iv }
};
toks = quote! { #iv }
}
} else if let Some(s) = &s.self_param_var {
sv = quote! { #s }
};
let mut trait_path = Vec::new();
trait_path.extend(iwn.namespace_idents());
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());
let trait_ref = if tns.is_empty() {
quote! { #rp #instance_mod::#rtrait }
} else {
quote! { #rp #tns::#instance_mod::#rtrait }
};
quote! { <#sv::#extras #instance_type as #trait_ref #tvis>::T }
toks = quote! { #s }
}
// todo:this will need a bit of adjustment to work well with
// plainname externs, which may require keeping track of the last
// interfacename we saw
Comment thread
syntactically marked this conversation as resolved.
Resolved
for (i, p) in path[0..path.len() - 1].iter().enumerate() {
let iwn = split_wit_name(p.name());
let export_name = if p.imported() {
import_member_names(&iwn, &s.colliding_import_names).0
} else {
kebab_to_type(iwn.name)
};

// The final item in the path will be referring to the
// relevant resource trait, while the others will be referring
// instance traits on the way there. So, we need to treat them
// separately.
let namespace_suffix = if i == path.len() - 2 {
&[kebab_to_namespace(iwn.name), rtrait.clone()] as &[Ident]
} else {
&[kebab_to_type(iwn.name)] as &[Ident]
};
let trait_path = iwn
.namespace_idents()
.iter()
.chain(namespace_suffix.iter())
.cloned()
.collect::<Vec<_>>();
let trait_ref = trait_ref(s, true, &trait_path);

toks = quote! { <#toks::#export_name as #trait_ref> };
}
quote! { #toks::T }
}

/// Try to find a way to refer to the given type variable from the
Expand Down
21 changes: 21 additions & 0 deletions src/hyperlight_host/tests/wit_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -571,3 +571,24 @@ mod inline_bindgen_test {
assert_eq!(result.value, "inline");
}
}

mod deeply_nested {
hyperlight_component_macro::host_bindgen!({
Comment thread
syntactically marked this conversation as resolved.
Resolved
inline: r#"
(component
(type (export "world") (component
(export "test:deeply-nested/world" (component
(import "test:deeply-nested/int1" (instance $I1
(export "test:deeply-nested/int2" (instance
(export "r" (type $R (sub resource)))
(export "f" (func (param "r" (own $R))))))))
(alias export $I1 "test:deeply-nested/int2" (instance $I2))
(alias export $I2 "r" (type $R))
(import "test:deeply-nested/int3" (instance
(export "test:deeply-nested/int4" (instance
(export "r4" (type $R4 (eq $R)))
(export "g" (func (param "r4" (own $R4))))))))
(export "h" (func (param "r" (own $R)))))))))
"#,
});
}
Loading
Loading