1use proc_macro::TokenStream;
11use quote::{format_ident, quote};
12
13#[proc_macro_attribute]
20pub fn yog_export(_attr: TokenStream, item: TokenStream) -> TokenStream {
21 if let Ok(s) = syn::parse2::<syn::ItemStruct>(proc_macro2::TokenStream::from(item.clone())) {
23 let vis = &s.vis;
24 let ident = &s.ident;
25 let generics = &s.generics;
26 let fields = &s.fields;
27 let attrs = &s.attrs;
28 return TokenStream::from(quote! {
29 #[derive(::yog_api::rkyv::Archive, ::yog_api::rkyv::Serialize, ::yog_api::rkyv::Deserialize)]
30 #(#attrs)*
31 #vis struct #ident #generics #fields
32 });
33 }
34 if let Ok(e) = syn::parse2::<syn::ItemEnum>(proc_macro2::TokenStream::from(item.clone())) {
35 let vis = &e.vis;
36 let ident = &e.ident;
37 let generics = &e.generics;
38 let variants = &e.variants;
39 let attrs = &e.attrs;
40 return TokenStream::from(quote! {
41 #[derive(::yog_api::rkyv::Archive, ::yog_api::rkyv::Serialize, ::yog_api::rkyv::Deserialize)]
42 #(#attrs)*
43 #vis enum #ident #generics { #variants }
44 });
45 }
46
47 let func = syn::parse_macro_input!(item as syn::ItemFn);
48 let name = &func.sig.ident;
49 let vis = &func.vis;
50 let sig = &func.sig;
51 let block = &func.block;
52 let name_str = name.to_string();
53 let wrap_name = format_ident!("__yog_wrap_{}", name);
54 let ctor_name = format_ident!("__yog_ctor_register_{}", name);
55
56 let output = match &func.sig.output {
57 syn::ReturnType::Default => quote! { () },
58 syn::ReturnType::Type(_, ty) => quote! { #ty },
59 };
60
61 fn is_registry_ty(ty: &syn::Type) -> bool {
67 let ty = match ty {
68 syn::Type::Reference(r) => &*r.elem,
69 other => other,
70 };
71 quote! { #ty }
72 .to_string()
73 .replace(' ', "")
74 .ends_with("Registry")
75 }
76
77 let mut data_params: Vec<&syn::PatType> = func
78 .sig
79 .inputs
80 .iter()
81 .filter_map(|arg| match arg {
82 syn::FnArg::Typed(pt) => Some(pt),
83 syn::FnArg::Receiver(_) => None,
84 })
85 .collect();
86
87 let has_registry_param = data_params
88 .first()
89 .map(|pt| is_registry_ty(&pt.ty))
90 .unwrap_or(false);
91 if has_registry_param {
92 data_params.remove(0);
93 }
94
95 let data_types: Vec<&syn::Type> = data_params.iter().map(|pt| &*pt.ty).collect();
96 let arg_idents: Vec<syn::Ident> = (0..data_types.len())
97 .map(|i| format_ident!("__yog_arg{}", i))
98 .collect();
99
100 let data_type = match data_types.len() {
101 0 => quote! { () },
102 1 => {
103 let t = data_types[0];
104 quote! { #t }
105 }
106 _ => quote! { (#(#data_types),*) },
107 };
108
109 let destructure = match arg_idents.len() {
110 0 => quote! {},
111 1 => {
112 let a = &arg_idents[0];
113 quote! { let #a = __yog_data; }
114 }
115 _ => quote! { let (#(#arg_idents),*) = __yog_data; },
116 };
117
118 let registry_setup = if has_registry_param {
119 quote! {
120 let mut __yog_registry = unsafe {
121 ::yog_api::Registry::from_raw(::yog_api::__current_api_ptr() as *const ::yog_api::YogApi)
122 };
123 }
124 } else {
125 quote! {}
126 };
127
128 let mut call_args: Vec<proc_macro2::TokenStream> = Vec::new();
129 if has_registry_param {
130 call_args.push(quote! { &mut __yog_registry });
131 }
132 call_args.extend(arg_idents.iter().map(|a| quote! { #a }));
133 let call_fn = quote! { #name(#(#call_args),*) };
134
135 let expanded = quote! {
136 #vis #sig #block
137
138 #[doc(hidden)]
139 #[unsafe(no_mangle)]
140 pub unsafe extern "C" fn #wrap_name(
141 input_ptr: *const u8, input_len: u32,
142 out_data: *mut *mut u8, out_len: *mut u32, out_cap: *mut u32,
143 ) {
144 let call = ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| {
148 let input_slice: &[u8] = if input_len == 0 {
149 &[]
150 } else {
151 unsafe { std::slice::from_raw_parts(input_ptr, input_len as usize) }
152 };
153 let __yog_data: #data_type = ::yog_api::rkyv::from_bytes::<_, ::yog_api::rkyv::rancor::Error>(input_slice)
154 .expect("yog: an export's arguments didn't decode");
155 #destructure
156 #registry_setup
157 let result: #output = #call_fn;
158 ::yog_api::rkyv::to_bytes::<::yog_api::rkyv::rancor::Error>(&result).map(|a| a.to_vec()).ok()
159 }));
160 let Ok(Some(bytes)) = call else {
161 unsafe {
162 *out_data = std::ptr::null_mut();
163 *out_len = 0;
164 *out_cap = 0;
165 }
166 return;
167 };
168 unsafe {
169 *out_data = bytes.as_ptr() as *mut u8;
170 *out_len = bytes.len() as u32;
171 *out_cap = bytes.capacity() as u32;
172 }
173 std::mem::forget(bytes);
174 }
175
176 #[::yog_api::ctor::ctor(unsafe, crate_path = ::yog_api::ctor)]
180 #[doc(hidden)]
181 fn #ctor_name() {
182 ::yog_api::__yog_export_registry().lock().unwrap().push(
183 ::yog_api::YogExportEntry { name: #name_str, ptr: #wrap_name as usize }
184 );
185 }
186 };
187
188 TokenStream::from(expanded)
189}
190
191#[proc_macro_attribute]
198pub fn bench(_attr: TokenStream, item: TokenStream) -> TokenStream {
199 let mut f = syn::parse_macro_input!(item as syn::ItemFn);
200 if matches!(f.vis, syn::Visibility::Inherited) {
201 f.vis = syn::parse_quote!(pub(crate));
202 }
203 TokenStream::from(quote! { #f })
204}
205
206#[proc_macro]
209pub fn import(input: TokenStream) -> TokenStream {
210 let import_block = syn::parse_macro_input!(input as ImportBlock);
211 let mod_name = &import_block.mod_name;
212 let mod_ident = format_ident!("{}", mod_name.replace('-', "_"));
213 let mut items = proc_macro2::TokenStream::new();
214
215 for item in &import_block.items {
216 match item {
217 ImportItem::Func(func) => {
218 items.extend(generate_import_fn(func, mod_name));
219 }
220 ImportItem::Struct(s) => {
221 let vis = &s.vis;
222 let ident = &s.ident;
223 let generics = &s.generics;
224 let fields = &s.fields;
225 let attrs = &s.attrs;
226 items.extend(quote! {
227 #[derive(::yog_api::rkyv::Archive, ::yog_api::rkyv::Serialize, ::yog_api::rkyv::Deserialize)]
228 #(#attrs)* #vis struct #ident #generics #fields
229 });
230 }
231 }
232 }
233
234 let out = quote! { pub mod #mod_ident { #items } };
235 TokenStream::from(out)
236}
237
238struct ImportFn {
240 vis: syn::Visibility,
241 sig: syn::Signature,
242}
243
244fn generate_import_fn(func: &ImportFn, mod_name: &str) -> proc_macro2::TokenStream {
245 let name = &func.sig.ident;
246 let name_str = name.to_string();
247 let slot_name = format_ident!("__YOG_SLOT_{}", name_str.to_uppercase());
250 let bind_name = format_ident!("__yog_bind_{}", name);
251 let ctor_name = format_ident!("__yog_import_ctor_{}", name);
252 let fn_sig = &func.sig;
253 let vis = &func.vis;
254 let inputs = &fn_sig.inputs;
255 let output = &fn_sig.output;
256
257 let output_type = match output {
258 syn::ReturnType::Default => quote! { () },
259 syn::ReturnType::Type(_, ty) => quote! { #ty },
260 };
261
262 let arg_names: Vec<_> = inputs
263 .iter()
264 .map(|arg| match arg {
265 syn::FnArg::Typed(pat_type) => &pat_type.pat,
266 _ => unreachable!(),
267 })
268 .collect();
269
270 let serialize_block = if inputs.is_empty() {
271 quote! { let input_bytes = Vec::new(); }
272 } else {
273 quote! {
274 let aligned = ::yog_api::rkyv::to_bytes::<::yog_api::rkyv::rancor::Error>(&(#(#arg_names),*)).unwrap_or_default();
275 let input_bytes: Vec<u8> = aligned.to_vec();
276 }
277 };
278
279 let wrapper_ty = quote! { ::yog_api::YogExportFn };
280
281 quote! {
282 #[allow(non_snake_case)]
283 #vis fn #name(#inputs) #output {
284 #serialize_block
285 let f = #slot_name.get()
286 .expect(concat!("yog: import '", #mod_name, ":", #name_str, "' not bound"));
287 let output = ::yog_api::__interop_call(*f, &input_bytes, concat!(#mod_name, ":", #name_str));
288 ::yog_api::rkyv::from_bytes::<#output_type, ::yog_api::rkyv::rancor::Error>(&output)
289 .expect(concat!("yog: the result of '", #mod_name, ":", #name_str, "' didn't decode"))
290 }
291
292 static #slot_name: ::std::sync::OnceLock<#wrapper_ty> = ::std::sync::OnceLock::new();
293
294 #[doc(hidden)] #[unsafe(no_mangle)]
295 pub unsafe extern "C" fn #bind_name(ptr: *const ::std::os::raw::c_void) {
296 let f: #wrapper_ty = unsafe { ::std::mem::transmute(ptr) };
297 #slot_name.set(f).ok();
298 }
299
300 #[::yog_api::ctor::ctor(unsafe, crate_path = ::yog_api::ctor)]
303 #[doc(hidden)]
304 fn #ctor_name() {
305 ::yog_api::__yog_import_registry().lock().unwrap().push(::yog_api::YogImportEntry {
306 mod_id: #mod_name,
307 symbol: #name_str,
308 bind_fn: #bind_name as *const () as usize,
309 });
310 }
311 }
312}
313
314struct ImportBlock {
317 mod_name: String,
318 items: Vec<ImportItem>,
319}
320enum ImportItem {
321 Func(ImportFn),
322 Struct(syn::ItemStruct),
323}
324
325impl syn::parse::Parse for ImportBlock {
326 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
327 let from_kw: syn::Ident = input.parse()?;
328 if from_kw != "from" {
329 return Err(syn::Error::new(from_kw.span(), "expected `from`"));
330 }
331 let lit: syn::LitStr = input.parse()?;
332 let mod_name = lit.value();
333 let content;
334 syn::braced!(content in input);
335 let mut items = Vec::new();
336 while !content.is_empty() {
337 let attrs = content.call(syn::Attribute::parse_outer)?;
338 let vis: syn::Visibility = content.parse()?;
339 if content.peek(syn::Token![fn]) {
340 let sig: syn::Signature = content.parse()?;
341 if content.peek(syn::token::Brace) {
342 let _body: syn::Block = content.parse()?;
343 } else {
344 content.parse::<syn::Token![;]>()?;
345 }
346 items.push(ImportItem::Func(ImportFn { vis, sig }));
347 } else {
348 let mut s: syn::ItemStruct = content.parse()?;
349 s.attrs = attrs;
350 s.vis = vis;
351 items.push(ImportItem::Struct(s));
352 let _ = content.parse::<syn::Token![;]>();
353 }
354 }
355 Ok(ImportBlock { mod_name, items })
356 }
357}