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