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