hyperlight_component_util/
guest.rs1use 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
32fn 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 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 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
136fn 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
169fn 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 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 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
247fn 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
271fn 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
338pub 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 pub trait Guest: #ns::#export_trait<Host> {
362 fn with_guest_state<R, F: FnOnce(&mut Self) -> R>(f: F) -> R;
363 }
364 pub fn hyperlight_guest_init<T: Guest>() {
366 #tokens
367 }
368 });
369}