hyperlight_component_util/
guest.rs1use 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
19fn 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 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 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
126fn 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
153fn 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 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 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
231fn 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
255fn 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 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 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
334pub 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 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 pub fn hyperlight_guest_init<T: Guest>() {
362 #tokens
363 }
364 });
365}