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