Skip to main content

hyperlight_component_util/
host.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2025 The Hyperlight Authors.
3
4use proc_macro2::{Ident, TokenStream};
5use quote::{ToTokens, format_ident, quote};
6
7use crate::emit::{
8    FnName, ResourceItemName, State, WitName, find_colliding_import_names, import_member_names,
9    kebab_to_exports_name, kebab_to_fn, kebab_to_getter, kebab_to_imports_name, kebab_to_namespace,
10    kebab_to_type, kebab_to_var, split_wit_name,
11};
12use crate::etypes::{Component, ExternDecl, ExternDesc, Instance, Tyvar};
13use crate::hl::{
14    emit_fn_hl_name, emit_hl_marshal_param, emit_hl_marshal_result, emit_hl_unmarshal_param,
15    emit_hl_unmarshal_result,
16};
17use crate::{resource, rtypes};
18
19/// Emit (via returning) code to be added to an `impl <instance trait>
20/// for Guest {}` declaration that implements this extern declaration
21/// in terms of Hyperlight guest calls
22fn emit_export_extern_decl<'a, 'b, 'c>(
23    s: &'c mut State<'a, 'b>,
24    ed: &'c ExternDecl<'b>,
25) -> TokenStream {
26    match &ed.desc {
27        ExternDesc::CoreModule(_) => panic!("core module (im/ex)ports are not supported"),
28        ExternDesc::Func(ft) => {
29            match kebab_to_fn(ed.kebab_name) {
30                FnName::Plain(n) => {
31                    let param_decls = ft
32                        .params
33                        .iter()
34                        .map(|p| rtypes::emit_func_param(s, p))
35                        .collect::<Vec<_>>();
36                    let result_decl = rtypes::emit_func_result(s, &ft.result);
37                    let hln = emit_fn_hl_name(s, ed.kebab_name);
38                    let ret = format_ident!("ret");
39                    let marshal = ft
40                        .params
41                        .iter()
42                        .map(|p| emit_hl_marshal_param(s, kebab_to_var(p.name.name), &p.ty))
43                        .collect::<Vec<_>>();
44                    let unmarshal = emit_hl_unmarshal_result(s, ret.clone(), &ft.result);
45                    quote! {
46                        fn #n(&mut self, #(#param_decls),*) -> #result_decl {
47                            let mut to_cleanup = Vec::<Box<dyn Drop>>::new();
48                            let marshalled = {
49                                let mut rts = self.rt
50                                    .lock()
51                                    .map_err(<::hyperlight_host::error::HyperlightError as From<_>>::from)?;
52                                #[allow(clippy::unused_unit)]
53                                (#(#marshal,)*)
54                            };
55                            let #ret = ::hyperlight_host::sandbox::Callable::call::<::std::vec::Vec::<u8>>(&mut self.sb,
56                                #hln,
57                                marshalled,
58                            )?;
59                            #[allow(clippy::unused_unit)]
60                            let mut rts = self.rt
61                                .lock()
62                                .map_err(<::hyperlight_host::error::HyperlightError as From<_>>::from)?;
63                            #[allow(clippy::unused_unit)]
64                            ::std::result::Result::Ok(#unmarshal)
65                        }
66                    }
67                }
68                FnName::Associated(_, _) =>
69                // this can be fixed when the guest wasm and
70                // general macros are split
71                {
72                    panic!("guest resources are not currently supported")
73                }
74            }
75        }
76        ExternDesc::Type(_) => {
77            // no runtime representation is needed for types
78            quote! {}
79        }
80        ExternDesc::Instance(it) => {
81            let wn = split_wit_name(ed.kebab_name);
82            emit_export_instance(s, wn.clone(), it);
83
84            let getter = kebab_to_getter(wn.name);
85            let tn = kebab_to_type(wn.name);
86            quote! {
87                type #tn = Self;
88                #[allow(refining_impl_trait)]
89                fn #getter<'a>(&'a mut self) -> &'a mut Self {
90                    self
91                }
92            }
93        }
94        ExternDesc::Component(_) => {
95            panic!("nested components not yet supported in rust bindings");
96        }
97    }
98}
99
100/// Emit (via mutating `s`) an `impl <instance trait> for Host {}`
101/// declaration that implements this exported instance in terms of
102/// hyperlight guest calls
103fn emit_export_instance<'a, 'b, 'c>(s: &'c mut State<'a, 'b>, wn: WitName, it: &'c Instance<'b>) {
104    let mut s = s.with_cursor(wn.namespace_idents());
105    s.cur_helper_mod = Some(kebab_to_namespace(wn.name));
106
107    let exports = it
108        .exports
109        .iter()
110        .map(|ed| emit_export_extern_decl(&mut s, ed))
111        .collect::<Vec<_>>();
112
113    let ns = wn.namespace_path();
114    let nsi = wn.namespace_idents();
115    let trait_name = kebab_to_type(wn.name);
116    let r#trait = s.r#trait(&nsi, trait_name.clone());
117    let tvs = r#trait
118        .tvs
119        .iter()
120        .map(|(_, (tv, _))| tv.unwrap())
121        .collect::<Vec<_>>();
122    let tvs = tvs
123        .iter()
124        .map(|tv| rtypes::emit_var_ref(&mut s, &Tyvar::Bound(*tv)))
125        .collect::<Vec<_>>();
126    let (root_ns, root_base_name) = s.root_component_name.unwrap();
127    let wrapper_name = kebab_to_wrapper_name(root_base_name);
128    let imports_name = kebab_to_imports_name(root_base_name);
129    let trait_path = if ns.is_empty() {
130        quote! { #trait_name }
131    } else {
132        quote! { #ns::#trait_name }
133    };
134    s.root_mod.items.extend(quote! {
135        impl<I: #root_ns::#imports_name<::hyperlight_common::component::Negative>, S: ::hyperlight_host::sandbox::Callable> #trait_path <::hyperlight_common::component::Positive #(,#tvs)*> for #wrapper_name<I, S> {
136            #(#exports)*
137        }
138    });
139}
140
141/// Keep track of how to get the portion of the state that corresponds
142/// to the instance that we are presently emitting
143#[derive(Clone)]
144struct SelfInfo {
145    orig_id: Ident,
146    /// identifier + trait bound
147    type_id: Vec<(Ident, TokenStream)>,
148    outer_id: Ident,
149    inner_preamble: TokenStream,
150    inner_id: Ident,
151}
152impl SelfInfo {
153    fn new(orig_id: Ident, imports_trait_bound: TokenStream) -> Self {
154        let outer_id = format_ident!("captured_{}", orig_id);
155        let inner_id = format_ident!("slf");
156        SelfInfo {
157            orig_id,
158            type_id: vec![(format_ident!("I"), imports_trait_bound)],
159            inner_preamble: quote! {
160                let mut #inner_id = #outer_id.lock()
161                .map_err(<::hyperlight_host::error::HyperlightError as From<_>>::from)?;
162                let mut #inner_id = ::std::ops::DerefMut::deref_mut(&mut #inner_id);
163            },
164            outer_id,
165            inner_id,
166        }
167    }
168    fn type_inst(&self) -> TokenStream {
169        self.type_id[1..]
170            .iter()
171            .fold(
172                (
173                    self.type_id[0].0.to_token_stream() as TokenStream,
174                    &self.type_id[0].1,
175                ),
176                |(toks, last_bound), (tid, next_bound)| {
177                    (quote! { <#toks as #last_bound>::#tid }, next_bound)
178                },
179            )
180            .0
181    }
182    /// Adjust a [`SelfInfo`] to get the portion of the state for the
183    /// current instance via calling the given getter
184    fn with_getter(
185        &self,
186        tp: TokenStream,
187        type_name: Ident,
188        type_bound: TokenStream,
189        getter: Ident,
190    ) -> Self {
191        let mut toks = self.inner_preamble.clone();
192        let id = self.inner_id.clone();
193        let type_inst = self.type_inst();
194        toks.extend(quote! {
195            let mut #id = #tp::#getter(::std::borrow::BorrowMut::<#type_inst>::borrow_mut(&mut #id));
196        });
197        let mut type_id = self.type_id.clone();
198        type_id.push((type_name, type_bound));
199        SelfInfo {
200            orig_id: self.orig_id.clone(),
201            type_id,
202            outer_id: self.outer_id.clone(),
203            inner_preamble: toks,
204            inner_id: id,
205        }
206    }
207}
208
209/// Emit (via returning) code to register this particular extern definition with
210/// Hyperlight as a host function
211///
212/// - `get_self`: a [`SelfInfo`] that details how to get from the root
213///   component implementation's state to the state for the
214///   implementation of this instance.
215fn emit_import_extern_decl<'a, 'b, 'c>(
216    s: &'c mut State<'a, 'b>,
217    get_self: SelfInfo,
218    ed: &'c ExternDecl<'b>,
219) -> TokenStream {
220    match &ed.desc {
221        ExternDesc::CoreModule(_) => panic!("core module (im/ex)ports are not supported"),
222        ExternDesc::Func(ft) => {
223            let hln = emit_fn_hl_name(s, ed.kebab_name);
224            tracing::debug!("providing host function {}", hln);
225            let (pds, pus) = ft
226                .params
227                .iter()
228                .map(|p| {
229                    let id = kebab_to_var(p.name.name);
230                    (
231                        quote! { #id: ::std::vec::Vec<u8> },
232                        emit_hl_unmarshal_param(s, id, &p.ty),
233                    )
234                })
235                .unzip::<_, _, Vec<_>, Vec<_>>();
236            let tp = s.cur_trait_path();
237            let callname = match kebab_to_fn(ed.kebab_name) {
238                FnName::Plain(n) => quote! { #tp::#n },
239                FnName::Associated(r, m) => {
240                    let hp = s.helper_path();
241                    match m {
242                        ResourceItemName::Constructor => quote! { #hp #r::new },
243                        ResourceItemName::Method(mn) => quote! { #hp #r::#mn },
244                        ResourceItemName::Static(mn) => quote! { #hp #r::#mn },
245                    }
246                }
247            };
248            let type_inst = get_self.type_inst();
249            let SelfInfo {
250                orig_id,
251                outer_id,
252                inner_preamble,
253                inner_id,
254                ..
255            } = get_self;
256            let ret = format_ident!("ret");
257            let marshal_result = emit_hl_marshal_result(s, ret.clone(), &ft.result);
258            quote! {
259                let #outer_id = #orig_id.clone();
260                let captured_rts = rts.clone();
261                sb.register_host_function(#hln, move |#(#pds),*| {
262                    let mut rts = captured_rts.lock()
263                        .map_err(<::hyperlight_host::error::HyperlightError as From<_>>::from)?;
264                    #inner_preamble
265                    let #ret = #callname(
266                        ::std::borrow::BorrowMut::<#type_inst>::borrow_mut(
267                            &mut #inner_id
268                        ),
269                        #(#pus),*
270                    );
271                    Ok(#marshal_result)
272                })?;
273            }
274        }
275        ExternDesc::Type(_) => {
276            // no runtime representation is needed for types
277            quote! {}
278        }
279        ExternDesc::Instance(it) => {
280            let mut s = s.clone();
281            let wn = split_wit_name(ed.kebab_name);
282            let (type_name, getter) = import_member_names(&wn, &s.colliding_import_names);
283            let tp = s.cur_trait_path();
284            let trait_path = wn
285                .namespace_idents()
286                .iter()
287                .chain(&[kebab_to_type(wn.name)])
288                .cloned()
289                .collect::<Vec<_>>();
290            let trait_ref =
291                rtypes::trait_ref(&mut s, rtypes::EmitPositivity::Opposite, true, &trait_path);
292            let get_self = get_self.with_getter(tp, type_name, trait_ref, getter);
293            emit_import_instance(&mut s, get_self, wn.clone(), it)
294        }
295        ExternDesc::Component(_) => {
296            panic!("nested components not yet supported in rust bindings");
297        }
298    }
299}
300
301/// Emit (via returning) code to register each export of the given
302/// instance with Hyperlight as a host function.
303///
304/// - `get_self`: a [`SelfInfo`] that details how to get from the root
305///   component implementation's state to the state for the
306///   implementation of this instance. This should already have been
307///   updated for this instance by the caller!
308fn emit_import_instance<'a, 'b, 'c>(
309    s: &'c mut State<'a, 'b>,
310    get_self: SelfInfo,
311    wn: WitName,
312    it: &'c Instance<'b>,
313) -> TokenStream {
314    let mut s = s.with_cursor(wn.namespace_idents());
315    s.cur_helper_mod = Some(kebab_to_namespace(wn.name));
316    s.cur_trait = Some(kebab_to_type(wn.name));
317
318    let imports = it
319        .exports
320        .iter()
321        .map(|ed| emit_import_extern_decl(&mut s, get_self.clone(), ed))
322        .collect::<Vec<_>>();
323
324    quote! { #(#imports)* }
325}
326
327/// From a kebab name for a Component, derive something suitable for
328/// use as the name of the wrapper struct that will implement its
329/// exports in terms of guest function calls.
330fn kebab_to_wrapper_name(trait_name: &str) -> Ident {
331    format_ident!("{}Sandbox", kebab_to_type(trait_name))
332}
333
334/// Emit (via mutating `s`):
335/// - a resource table for each resource exported by this component
336/// - a wrapper type encapsulating a sandbox and a wrapper table that
337///   implements the relevant export trait
338/// - an implementation of the component trait itself for Hyperlight's
339///   `UninitializedSandbox` that makes it easy to instantiate
340fn emit_component<'a, 'b, 'c>(s: &'c mut State<'a, 'b>, wn: WitName, ct: &'c Component<'b>) {
341    let mut s = s.with_cursor(wn.namespace_idents());
342    let ns = wn.namespace_path();
343    let r#trait = kebab_to_type(wn.name);
344    let import_trait = kebab_to_imports_name(wn.name);
345    let export_trait = kebab_to_exports_name(wn.name);
346    let wrapper_name = kebab_to_wrapper_name(wn.name);
347    let import_id = format_ident!("imports");
348
349    let rtsid = format_ident!("{}Resources", r#trait);
350    s.import_param_var = Some(format_ident!("I"));
351    s.positivity_param = Some(quote! { ::hyperlight_common::component::Positive });
352    s.colliding_import_names = find_colliding_import_names(&ct.imports);
353    resource::emit_tables(
354        &mut s,
355        rtsid.clone(),
356        quote! { #ns::#import_trait<::hyperlight_common::component::Negative> },
357        None,
358        false,
359    );
360
361    s.var_offset = ct.instance.evars.len();
362    s.cur_trait = Some(import_trait.clone());
363    let imports = ct
364        .imports
365        .iter()
366        .map(|ed| {
367            emit_import_extern_decl(
368                &mut s,
369                SelfInfo::new(
370                    import_id.clone(),
371                    quote! { #ns::#import_trait<::hyperlight_common::component::Negative> },
372                ),
373                ed,
374            )
375        })
376        .collect::<Vec<_>>();
377    s.var_offset = 0;
378
379    s.root_component_name = Some((ns.clone(), wn.name));
380    s.cur_trait = Some(export_trait.clone());
381    s.import_param_var = Some(format_ident!("I"));
382    s.positivity_param = Some(quote! { ::hyperlight_common::component::Positive });
383    // See Note [Origin paths and self parameters in impl codegen for higher-order components]
384    // in emit.rs
385    s.self_param_var =
386        Some(quote! { <Self as #ns::#export_trait<I, ::hyperlight_common::component::Negative>> });
387
388    let exports = ct
389        .instance
390        .unqualified
391        .exports
392        .iter()
393        .map(|ed| emit_export_extern_decl(&mut s, ed))
394        .collect::<Vec<_>>();
395
396    s.root_mod.items.extend(quote! {
397        pub struct #wrapper_name<T: #ns::#import_trait<::hyperlight_common::component::Negative>, S: ::hyperlight_host::sandbox::Callable> {
398            pub(crate) sb: S,
399            pub(crate) rt: ::std::sync::Arc<::std::sync::Mutex<#rtsid<T>>>,
400        }
401        pub(crate) fn register_host_functions<I: #ns::#import_trait<::hyperlight_common::component::Negative> + ::std::marker::Send + 'static, S: ::hyperlight_host::func::Registerable>(sb: &mut S, i: I) -> <::hyperlight_common::component::Positive as ::hyperlight_common::component::Positivity>::CallResult<::std::sync::Arc<::std::sync::Mutex<#rtsid<I>>>> {
402            let rts = ::std::sync::Arc::new(::std::sync::Mutex::new(#rtsid::new()));
403            let #import_id = ::std::sync::Arc::new(::std::sync::Mutex::new(i));
404            #(#imports)*
405            Ok(rts)
406        }
407        impl<I: #ns::#import_trait<::hyperlight_common::component::Negative> + ::std::marker::Send, S: ::hyperlight_host::sandbox::Callable> #ns::#export_trait<::hyperlight_common::component::Positive, I> for #wrapper_name<I, S> {
408            #(#exports)*
409        }
410        impl #ns::#r#trait<::hyperlight_common::component::Positive> for ::hyperlight_host::sandbox::UninitializedSandbox {
411            type Exports<I: #ns::#import_trait<::hyperlight_common::component::Negative> + ::std::marker::Send> = #wrapper_name<I, ::hyperlight_host::sandbox::initialized_multi_use::MultiUseSandbox>;
412            fn instantiate<I: #ns::#import_trait<::hyperlight_common::component::Negative> + ::std::marker::Send + 'static>(mut self, i: I) -> <::hyperlight_common::component::Positive as ::hyperlight_common::component::Positivity>::CallResult<Self::Exports<I>> {
413                let rts = register_host_functions(&mut self, i)?;
414                let sb = self.evolve()?;
415                Ok(#wrapper_name {
416                    sb,
417                    rt: rts,
418                })
419            }
420        }
421    });
422}
423
424/// See [`emit_component`]
425pub fn emit_toplevel<'a, 'b, 'c>(s: &'c mut State<'a, 'b>, n: &str, ct: &'c Component<'b>) {
426    s.is_impl = true;
427    tracing::debug!("\n\n=== starting host emit ===\n");
428    let wn = split_wit_name(n);
429    emit_component(s, wn, ct)
430}