hyperlight_component_util/
guest.rs1use 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
31fn 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 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 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
135fn 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
168fn 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 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 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
246fn 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
270fn 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
335pub 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 pub trait Guest: #ns::#export_trait<Host> {
359 fn with_guest_state<R, F: FnOnce(&mut Self) -> R>(f: F) -> R;
360 }
361 pub fn hyperlight_guest_init<T: Guest>() {
363 #tokens
364 }
365 });
366}