Skip to main content

hyperlight_component_util/
guest.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2025 The Hyperlight Authors.
3
4use proc_macro2::TokenStream;
5use quote::{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, Defined, ExternDecl, ExternDesc, Handleable, 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 (mostly via returning) code to be added to an `impl <instance
20/// trait> for Host {}` declaration that implements this extern
21/// declaration in terms of Hyperlight host calls.
22///
23/// For functions associated with a resource, this will instead mutate
24/// `s` to directly add them to the resource trait implementation and
25/// return an empty token stream.
26fn emit_import_extern_decl<'a, 'b, 'c>(
27    s: &'c mut State<'a, 'b>,
28    ed: &'c ExternDecl<'b>,
29) -> TokenStream {
30    match &ed.desc {
31        ExternDesc::CoreModule(_) => panic!("core module (im/ex)ports are not supported"),
32        ExternDesc::Func(ft) => {
33            let param_decls = ft
34                .params
35                .iter()
36                .map(|p| rtypes::emit_func_param(s, p))
37                .collect::<Vec<_>>();
38            let result_decl = rtypes::emit_func_result(s, &ft.result);
39            let hln = emit_fn_hl_name(s, ed.kebab_name);
40            let ret = format_ident!("ret");
41            let marshal = ft
42                .params
43                .iter()
44                .map(|p| {
45                    let me = emit_hl_marshal_param(s, kebab_to_var(p.name.name), &p.ty);
46                    quote! { args.push(::hyperlight_common::flatbuffer_wrappers::function_types::ParameterValue::VecBytes(#me)); }
47                })
48                .collect::<Vec<_>>();
49            let unmarshal = emit_hl_unmarshal_result(s, ret.clone(), &ft.result);
50            let fnname = kebab_to_fn(ed.kebab_name);
51            let n = match &fnname {
52                FnName::Plain(n) => quote! { #n },
53                FnName::Associated(_, m) => match m {
54                    ResourceItemName::Constructor => quote! { new },
55                    ResourceItemName::Method(mn) => quote! { #mn },
56                    ResourceItemName::Static(mn) => quote! { #mn },
57                },
58            };
59            let decl = quote! {
60                fn #n(&mut self, #(#param_decls),*) -> #result_decl {
61                    let mut args = ::alloc::vec::Vec::new();
62                    #(#marshal)*
63                    let #ret = ::hyperlight_guest_bin::host_comm::call_host_function::<::alloc::vec::Vec<u8>>(
64                        #hln,
65                        Some(args),
66                        ::hyperlight_common::flatbuffer_wrappers::function_types::ReturnType::VecBytes,
67                    );
68                    let ::core::result::Result::Ok(#ret) = #ret else { panic!("bad return from guest {:?}", #ret) };
69                    #[allow(clippy::unused_unit)]
70                    #unmarshal
71                }
72            };
73            match fnname {
74                FnName::Plain(_) => decl,
75                FnName::Associated(r, _) => {
76                    // if a resource type could depend on another
77                    // tyvar, there might be some complexities
78                    // here, but that is not the case at the
79                    // moment.
80                    let path = s.resource_trait_path(r);
81                    s.root_mod
82                        .r#impl(path, format_ident!("Host"))
83                        .1
84                        .extend(decl);
85                    TokenStream::new()
86                }
87            }
88        }
89        ExternDesc::Type(t) => match t {
90            Defined::Handleable(Handleable::Var(Tyvar::Bound(b))) => {
91                // only resources need something emitted
92                let noff = (s.var_offset as u32 + b) as usize;
93                let crate::etypes::TypeBound::SubResource = &s.bound_vars[noff].bound else {
94                    return quote! {};
95                };
96                let rtid = format_ident!("HostResource{}", noff);
97                let path = s.resource_trait_path(kebab_to_type(ed.kebab_name));
98                let r#impl = s.root_mod.r#impl(path, format_ident!("Host"));
99                r#impl.0 = quote! { <::hyperlight_common::component::Negative> };
100                r#impl.1.extend(quote! {
101                    type T = #rtid;
102                });
103                TokenStream::new()
104            }
105            _ => quote! {},
106        },
107        ExternDesc::Instance(it) => {
108            let wn = split_wit_name(ed.kebab_name);
109            emit_import_instance(s, wn.clone(), it);
110
111            let (tn, getter) = import_member_names(&wn, &s.colliding_import_names);
112            quote! {
113                type #tn = Self;
114                #[allow(refining_impl_trait)]
115                fn #getter<'a>(&'a mut self) -> &'a mut Self {
116                    self
117                }
118            }
119        }
120        ExternDesc::Component(_) => {
121            panic!("nested components not yet supported in rust bindings");
122        }
123    }
124}
125
126/// Emit (via mutating `s`) an `impl <instance trait> for Host {}`
127/// declaration that implements this imported instance in terms of
128/// hyperlight host calls
129fn emit_import_instance<'a, 'b, 'c>(s: &'c mut State<'a, 'b>, wn: WitName, it: &'c Instance<'b>) {
130    let mut s = s.with_cursor(wn.namespace_idents());
131    s.cur_helper_mod = Some(kebab_to_namespace(wn.name));
132
133    let imports = it
134        .exports
135        .iter()
136        .map(|ed| emit_import_extern_decl(&mut s, ed))
137        .collect::<Vec<_>>();
138
139    let trait_path = wn
140        .namespace_idents()
141        .iter()
142        .chain(&[kebab_to_type(wn.name)])
143        .cloned()
144        .collect::<Vec<_>>();
145    let trait_ref = rtypes::trait_ref(&mut s, rtypes::EmitPositivity::Opposite, true, &trait_path);
146    s.root_mod.items.extend(quote! {
147        impl #trait_ref for Host {
148            #(#imports)*
149        }
150    });
151}
152
153/// Emit (via returning) code to register this particular extern
154/// definition with Hyperlight as a callable function.
155fn emit_export_extern_decl<'a, 'b, 'c>(
156    s: &'c mut State<'a, 'b>,
157    path: Vec<String>,
158    ed: &'c ExternDecl<'b>,
159) -> TokenStream {
160    match &ed.desc {
161        ExternDesc::CoreModule(_) => panic!("core module (im/ex)ports are not supported"),
162        ExternDesc::Func(ft) => {
163            let fname = emit_fn_hl_name(s, ed.kebab_name);
164            let n = match kebab_to_fn(ed.kebab_name) {
165                FnName::Plain(n) => n,
166                FnName::Associated(_, _) => {
167                    panic!("resources exported from wasm not yet supported")
168                }
169            };
170            let pts = ft.params.iter().map(|_| quote! { ::hyperlight_common::flatbuffer_wrappers::function_types::ParameterType::VecBytes }).collect::<Vec<_>>();
171            let (pds, pus) = ft.params.iter().enumerate()
172                .map(|(i, p)| {
173                    let id = kebab_to_var(p.name.name);
174                    let pd = quote! { let ::hyperlight_common::flatbuffer_wrappers::function_types::ParameterValue::VecBytes(#id) = &fc.parameters.as_ref().unwrap()[#i] else { panic!("invariant violation: host passed non-VecBytes core hyperlight argument"); }; };
175                    let pu = emit_hl_unmarshal_param(s, id, &p.ty);
176                    (pd, pu)
177                })
178                .unzip::<_, _, Vec<_>, Vec<_>>();
179            let get_instance = path
180                .iter()
181                .map(|export| {
182                    let n = kebab_to_getter(split_wit_name(export).name);
183                    // TODO: Check that name resolution here works
184                    // properly with nested instances (not yet supported
185                    // in WIT, so we need to use a raw component type to
186                    // check)
187                    quote! {
188                        let mut state = state.#n();
189                        let state = ::core::borrow::BorrowMut::borrow_mut(&mut state);
190                    }
191                })
192                .collect::<Vec<_>>();
193            let ret = format_ident!("ret");
194            let marshal_result = emit_hl_marshal_result(s, ret.clone(), &ft.result);
195            let trait_path = s.cur_trait_path();
196            quote! {
197                fn #n<T: Guest>(fc: ::hyperlight_common::flatbuffer_wrappers::function_call::FunctionCall) -> ::hyperlight_guest::error::Result<::alloc::vec::Vec<u8>> {
198                    <T as Guest>::with_guest_state(|state| {
199                        #(#pds)*
200                        #(#get_instance)*
201                        let #ret = #trait_path::#n(state, #(#pus,)*);
202                        ::core::result::Result::Ok(::hyperlight_common::flatbuffer_wrappers::util::get_flatbuffer_result::<&[u8]>(&#marshal_result))
203                    })
204                }
205                ::hyperlight_guest_bin::guest_function::register::register_function(
206                    ::hyperlight_guest_bin::guest_function::definition::GuestFunctionDefinition::new(
207                        ::alloc::string::ToString::to_string(#fname),
208                        ::alloc::vec![#(#pts),*],
209                        ::hyperlight_common::flatbuffer_wrappers::function_types::ReturnType::VecBytes,
210                        #n::<T>
211                    )
212                );
213            }
214        }
215        ExternDesc::Type(_) => {
216            // no runtime representation is needed for types
217            quote! {}
218        }
219        ExternDesc::Instance(it) => {
220            let wn = split_wit_name(ed.kebab_name);
221            let mut path = path.clone();
222            path.push(ed.kebab_name.to_string());
223            emit_export_instance(s, wn.clone(), path, it)
224        }
225        ExternDesc::Component(_) => {
226            panic!("nested components not yet supported in rust bindings");
227        }
228    }
229}
230
231/// Emit (via returning) code to register each export of the given
232/// instance with Hyperlight as a callable function.
233///
234/// - `path`: the instance path (from the root component) where this
235///   definition may be found, used to locate the correct component of
236///   the guest state. This should already have been updated for this
237///   instance by the caller!
238fn emit_export_instance<'a, 'b, 'c>(
239    s: &'c mut State<'a, 'b>,
240    wn: WitName,
241    path: Vec<String>,
242    it: &'c Instance<'b>,
243) -> TokenStream {
244    let mut s = s.with_cursor(wn.namespace_idents());
245    s.cur_helper_mod = Some(kebab_to_namespace(wn.name));
246    s.cur_trait = Some(kebab_to_type(wn.name));
247    let exports = it
248        .exports
249        .iter()
250        .map(|ed| emit_export_extern_decl(&mut s, path.clone(), ed))
251        .collect::<Vec<_>>();
252    quote! { #(#exports)* }
253}
254
255/// Emit (via mutating `s`):
256/// - a resource table for each resource exported by this component
257/// - impl T for Host for each relevant trait T
258///
259/// Emit (via returning):
260/// - Hyperlight guest function ABI wrapper for each guest function
261/// - Hyperlight guest function register calls for each guest function
262fn emit_component<'a, 'b, 'c>(
263    s: &'c mut State<'a, 'b>,
264    wn: WitName,
265    ct: &'c Component<'b>,
266) -> TokenStream {
267    let mut s = s.with_cursor(wn.namespace_idents());
268    let ns = wn.namespace_path();
269    let r#trait = kebab_to_type(wn.name);
270    let import_trait = kebab_to_imports_name(wn.name);
271    let export_trait = kebab_to_exports_name(wn.name);
272    s.positivity_param = Some(quote! { ::hyperlight_common::component::Positive });
273    // We don't set s.self_param_var or s.import_param_var at all
274    // here, because they are currently obviated by the (s.is_guest &&
275    // s.is_impl) hack in rtypes::emit_resource_ref. For when we
276    // eventually do:
277    //
278    // See Note [Origin paths and self parameters in impl codegen for higher-order components]
279    // in emit.rs
280    s.colliding_import_names = find_colliding_import_names(&ct.imports);
281
282    let rtsid = format_ident!("{}Resources", r#trait);
283    resource::emit_tables(
284        &mut s,
285        rtsid.clone(),
286        quote! { #ns::#import_trait<::hyperlight_common::component::Negative> + ::core::marker::Send + 'static },
287        Some(quote! { #ns::#export_trait<::hyperlight_common::component::Positive, I> }),
288        true,
289    );
290    s.root_mod
291        .items
292        .extend(s.bound_vars.iter().enumerate().map(|(i, _)| {
293            let id = format_ident!("HostResource{}", i);
294            quote! {
295                pub struct #id { rep: u32 }
296            }
297        }));
298
299    s.var_offset = ct.instance.evars.len();
300    s.cur_trait = Some(import_trait.clone());
301    let imports = ct
302        .imports
303        .iter()
304        .map(|ed| emit_import_extern_decl(&mut s, ed))
305        .collect::<Vec<_>>();
306    s.var_offset = 0;
307    s.positivity_param = Some(quote! { ::hyperlight_common::component::Positive });
308    // We don't set s.self_param_var or s.import_param_var at all
309    // here, because it is currently obviated by the (s.is_guest &&
310    // s.is_impl) hack in rtypes::emit_resource_ref. For when we
311    // eventually do:
312    //
313    // See Note [Origin paths and self parameters in impl codegen for higher-order components]
314    // in emit.rs
315    s.cur_trait = Some(export_trait.clone());
316    let exports = ct
317        .instance
318        .unqualified
319        .exports
320        .iter()
321        .map(|ed| emit_export_extern_decl(&mut s, Vec::new(), ed))
322        .collect::<Vec<_>>();
323
324    s.root_mod.items.extend(quote! {
325        impl #ns::#import_trait<::hyperlight_common::component::Negative> for Host {
326            #(#imports)*
327        }
328    });
329    quote! {
330        #(#exports)*
331    }
332}
333
334/// In addition to the items emitted by [`emit_component`], mutate `s`
335/// to emit:
336/// - a dummy `Host` type to reflect host functions
337/// - a toplevel `Guest` trait that can be implemented to provide access to
338///   any guest state
339/// - a `hyperlight_guest_init` function that registers all guest
340/// - functions when given a type that implements the `Guest` trait
341pub fn emit_toplevel<'a, 'b, 'c>(s: &'c mut State<'a, 'b>, n: &str, ct: &'c Component<'b>) {
342    s.is_impl = true;
343    tracing::debug!("\n\n=== starting guest emit ===\n");
344    let wn = split_wit_name(n);
345
346    let ns = wn.namespace_path();
347    let export_trait = kebab_to_exports_name(wn.name);
348
349    let tokens = emit_component(s, wn, ct);
350
351    s.root_mod.items.extend(quote! {
352        pub struct Host {}
353
354        /// Because Hyperlight guest functions can't close over any
355        /// state, this function is used on each guest call to acquire
356        /// any state that the guest functions might need.
357        pub trait Guest: #ns::#export_trait<::hyperlight_common::component::Positive, Host> {
358            fn with_guest_state<R, F: FnOnce(&mut Self) -> R>(f: F) -> R;
359        }
360        /// Register all guest functions.
361        pub fn hyperlight_guest_init<T: Guest>() {
362            #tokens
363        }
364    });
365}