use proc_macro::TokenStream;
use quote::{format_ident, quote};
#[proc_macro_attribute]
pub fn yog_export(_attr: TokenStream, item: TokenStream) -> TokenStream {
if let Ok(s) = syn::parse2::<syn::ItemStruct>(proc_macro2::TokenStream::from(item.clone())) {
let vis = &s.vis;
let ident = &s.ident;
let generics = &s.generics;
let fields = &s.fields;
let attrs = &s.attrs;
return TokenStream::from(quote! {
#[derive(::yog_api::rkyv::Archive, ::yog_api::rkyv::Serialize, ::yog_api::rkyv::Deserialize)]
#(#attrs)*
#vis struct #ident #generics #fields
});
}
if let Ok(e) = syn::parse2::<syn::ItemEnum>(proc_macro2::TokenStream::from(item.clone())) {
let vis = &e.vis;
let ident = &e.ident;
let generics = &e.generics;
let variants = &e.variants;
let attrs = &e.attrs;
return TokenStream::from(quote! {
#[derive(::yog_api::rkyv::Archive, ::yog_api::rkyv::Serialize, ::yog_api::rkyv::Deserialize)]
#(#attrs)*
#vis enum #ident #generics { #variants }
});
}
let func = syn::parse_macro_input!(item as syn::ItemFn);
let name = &func.sig.ident;
let vis = &func.vis;
let sig = &func.sig;
let block = &func.block;
let name_str = name.to_string();
let wrap_name = format_ident!("__yog_wrap_{}", name);
let ctor_name = format_ident!("__yog_ctor_register_{}", name);
let output = match &func.sig.output {
syn::ReturnType::Default => quote! { () },
syn::ReturnType::Type(_, ty) => quote! { #ty },
};
fn is_registry_ty(ty: &syn::Type) -> bool {
let ty = match ty {
syn::Type::Reference(r) => &*r.elem,
other => other,
};
quote! { #ty }
.to_string()
.replace(' ', "")
.ends_with("Registry")
}
let mut data_params: Vec<&syn::PatType> = func
.sig
.inputs
.iter()
.filter_map(|arg| match arg {
syn::FnArg::Typed(pt) => Some(pt),
syn::FnArg::Receiver(_) => None,
})
.collect();
let has_registry_param = data_params
.first()
.map(|pt| is_registry_ty(&pt.ty))
.unwrap_or(false);
if has_registry_param {
data_params.remove(0);
}
let data_types: Vec<&syn::Type> = data_params.iter().map(|pt| &*pt.ty).collect();
let arg_idents: Vec<syn::Ident> = (0..data_types.len())
.map(|i| format_ident!("__yog_arg{}", i))
.collect();
let data_type = match data_types.len() {
0 => quote! { () },
1 => {
let t = data_types[0];
quote! { #t }
}
_ => quote! { (#(#data_types),*) },
};
let destructure = match arg_idents.len() {
0 => quote! {},
1 => {
let a = &arg_idents[0];
quote! { let #a = __yog_data; }
}
_ => quote! { let (#(#arg_idents),*) = __yog_data; },
};
let registry_setup = if has_registry_param {
quote! {
let mut __yog_registry = unsafe {
::yog_api::Registry::from_raw(::yog_api::__current_api_ptr() as *const ::yog_api::YogApi)
};
}
} else {
quote! {}
};
let mut call_args: Vec<proc_macro2::TokenStream> = Vec::new();
if has_registry_param {
call_args.push(quote! { &mut __yog_registry });
}
call_args.extend(arg_idents.iter().map(|a| quote! { #a }));
let call_fn = quote! { #name(#(#call_args),*) };
let expanded = quote! {
#vis #sig #block
#[doc(hidden)]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn #wrap_name(
input_ptr: *const u8, input_len: u32,
out_data: *mut *mut u8, out_len: *mut u32, out_cap: *mut u32,
) {
let call = ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| {
let input_slice: &[u8] = if input_len == 0 {
&[]
} else {
unsafe { std::slice::from_raw_parts(input_ptr, input_len as usize) }
};
let __yog_data: #data_type = ::yog_api::rkyv::from_bytes::<_, ::yog_api::rkyv::rancor::Error>(input_slice)
.expect("yog: an export's arguments didn't decode");
#destructure
#registry_setup
let result: #output = #call_fn;
::yog_api::rkyv::to_bytes::<::yog_api::rkyv::rancor::Error>(&result).map(|a| a.to_vec()).ok()
}));
let Ok(Some(bytes)) = call else {
unsafe {
*out_data = std::ptr::null_mut();
*out_len = 0;
*out_cap = 0;
}
return;
};
unsafe {
*out_data = bytes.as_ptr() as *mut u8;
*out_len = bytes.len() as u32;
*out_cap = bytes.capacity() as u32;
}
std::mem::forget(bytes);
}
#[::yog_api::ctor::ctor(unsafe, crate_path = ::yog_api::ctor)]
#[doc(hidden)]
fn #ctor_name() {
::yog_api::__yog_export_registry().lock().unwrap().push(
::yog_api::YogExportEntry { name: #name_str, ptr: #wrap_name as usize }
);
}
};
TokenStream::from(expanded)
}
#[proc_macro_attribute]
pub fn bench(_attr: TokenStream, item: TokenStream) -> TokenStream {
let mut f = syn::parse_macro_input!(item as syn::ItemFn);
if matches!(f.vis, syn::Visibility::Inherited) {
f.vis = syn::parse_quote!(pub(crate));
}
TokenStream::from(quote! { #f })
}
#[proc_macro]
pub fn import(input: TokenStream) -> TokenStream {
let import_block = syn::parse_macro_input!(input as ImportBlock);
let mod_name = &import_block.mod_name;
let mod_ident = format_ident!("{}", mod_name.replace('-', "_"));
let mut items = proc_macro2::TokenStream::new();
for item in &import_block.items {
match item {
ImportItem::Func(func) => {
items.extend(generate_import_fn(func, mod_name));
}
ImportItem::Struct(s) => {
let vis = &s.vis;
let ident = &s.ident;
let generics = &s.generics;
let fields = &s.fields;
let attrs = &s.attrs;
items.extend(quote! {
#[derive(::yog_api::rkyv::Archive, ::yog_api::rkyv::Serialize, ::yog_api::rkyv::Deserialize)]
#(#attrs)* #vis struct #ident #generics #fields
});
}
}
}
let out = quote! { pub mod #mod_ident { #items } };
TokenStream::from(out)
}
struct ImportFn {
vis: syn::Visibility,
sig: syn::Signature,
}
fn generate_import_fn(func: &ImportFn, mod_name: &str) -> proc_macro2::TokenStream {
let name = &func.sig.ident;
let name_str = name.to_string();
let slot_name = format_ident!("__YOG_SLOT_{}", name_str.to_uppercase());
let bind_name = format_ident!("__yog_bind_{}", name);
let ctor_name = format_ident!("__yog_import_ctor_{}", name);
let fn_sig = &func.sig;
let vis = &func.vis;
let inputs = &fn_sig.inputs;
let output = &fn_sig.output;
let output_type = match output {
syn::ReturnType::Default => quote! { () },
syn::ReturnType::Type(_, ty) => quote! { #ty },
};
let arg_names: Vec<_> = inputs
.iter()
.map(|arg| match arg {
syn::FnArg::Typed(pat_type) => &pat_type.pat,
_ => unreachable!(),
})
.collect();
let serialize_block = if inputs.is_empty() {
quote! { let input_bytes = Vec::new(); }
} else {
quote! {
let aligned = ::yog_api::rkyv::to_bytes::<::yog_api::rkyv::rancor::Error>(&(#(#arg_names),*)).unwrap_or_default();
let input_bytes: Vec<u8> = aligned.to_vec();
}
};
let wrapper_ty = quote! { ::yog_api::YogExportFn };
quote! {
#[allow(non_snake_case)]
#vis fn #name(#inputs) #output {
#serialize_block
let f = #slot_name.get()
.expect(concat!("yog: import '", #mod_name, ":", #name_str, "' not bound"));
let output = ::yog_api::__interop_call(*f, &input_bytes, concat!(#mod_name, ":", #name_str));
::yog_api::rkyv::from_bytes::<#output_type, ::yog_api::rkyv::rancor::Error>(&output)
.expect(concat!("yog: the result of '", #mod_name, ":", #name_str, "' didn't decode"))
}
static #slot_name: ::std::sync::OnceLock<#wrapper_ty> = ::std::sync::OnceLock::new();
#[doc(hidden)] #[unsafe(no_mangle)]
pub unsafe extern "C" fn #bind_name(ptr: *const ::std::os::raw::c_void) {
let f: #wrapper_ty = unsafe { ::std::mem::transmute(ptr) };
#slot_name.set(f).ok();
}
#[::yog_api::ctor::ctor(unsafe, crate_path = ::yog_api::ctor)]
#[doc(hidden)]
fn #ctor_name() {
::yog_api::__yog_import_registry().lock().unwrap().push(::yog_api::YogImportEntry {
mod_id: #mod_name,
symbol: #name_str,
bind_fn: #bind_name as *const () as usize,
});
}
}
}
struct ImportBlock {
mod_name: String,
items: Vec<ImportItem>,
}
enum ImportItem {
Func(ImportFn),
Struct(syn::ItemStruct),
}
impl syn::parse::Parse for ImportBlock {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let from_kw: syn::Ident = input.parse()?;
if from_kw != "from" {
return Err(syn::Error::new(from_kw.span(), "expected `from`"));
}
let lit: syn::LitStr = input.parse()?;
let mod_name = lit.value();
let content;
syn::braced!(content in input);
let mut items = Vec::new();
while !content.is_empty() {
let attrs = content.call(syn::Attribute::parse_outer)?;
let vis: syn::Visibility = content.parse()?;
if content.peek(syn::Token![fn]) {
let sig: syn::Signature = content.parse()?;
if content.peek(syn::token::Brace) {
let _body: syn::Block = content.parse()?;
} else {
content.parse::<syn::Token![;]>()?;
}
items.push(ImportItem::Func(ImportFn { vis, sig }));
} else {
let mut s: syn::ItemStruct = content.parse()?;
s.attrs = attrs;
s.vis = vis;
items.push(ImportItem::Struct(s));
let _ = content.parse::<syn::Token![;]>();
}
}
Ok(ImportBlock { mod_name, items })
}
}