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