hyperlight_component_util/
host.rs

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