use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{ItemTrait, TraitItem};
use crate::ir::{BufferStrategyAttr, InterfaceIR};
fn strip_optional_attrs(item: &ItemTrait) -> ItemTrait {
fn is_fidius_helper(attr: &syn::Attribute) -> bool {
attr.path().is_ident("optional")
|| attr.path().is_ident("method_meta")
|| attr.path().is_ident("trait_meta")
}
let mut cleaned = item.clone();
cleaned.attrs.retain(|attr| !is_fidius_helper(attr));
for trait_item in &mut cleaned.items {
if let TraitItem::Fn(method) = trait_item {
method.attrs.retain(|attr| !is_fidius_helper(attr));
}
}
cleaned
}
pub fn generate_interface(ir: &InterfaceIR) -> syn::Result<TokenStream> {
match ir.attrs.buffer_strategy {
BufferStrategyAttr::PluginAllocated | BufferStrategyAttr::Arena => {}
}
let cleaned_trait = strip_optional_attrs(&ir.original_trait);
let vtable = generate_vtable(ir);
let constants = generate_constants(ir);
let descriptor_builder = generate_descriptor_builder(ir);
let method_indices = generate_method_indices(ir);
let metadata = generate_metadata(ir);
let client = generate_client(ir);
let companion_mod = format_ident!("__fidius_{}", ir.trait_name);
Ok(quote! {
#cleaned_trait
#[allow(non_snake_case, non_upper_case_globals, dead_code)]
pub mod #companion_mod {
use super::*;
#vtable
#constants
#method_indices
#metadata
#descriptor_builder
}
#client
})
}
fn generate_metadata(ir: &InterfaceIR) -> TokenStream {
let crate_path = &ir.attrs.crate_path;
let trait_name = &ir.trait_name;
let mut per_method_arrays = Vec::new();
let mut table_entries = Vec::new();
let any_method_meta = ir.methods.iter().any(|m| !m.method_metas.is_empty());
if any_method_meta {
for method in &ir.methods {
let arr_name = format_ident!(
"__FIDIUS_METHOD_META_{}",
method.name.to_string().to_uppercase()
);
if method.method_metas.is_empty() {
table_entries.push(quote! {
#crate_path::descriptor::MethodMetaEntry {
kvs: ::std::ptr::null(),
kv_count: 0,
}
});
} else {
let kv_count = method.method_metas.len() as u32;
let kv_inits: Vec<TokenStream> = method
.method_metas
.iter()
.map(|kv| {
let k = &kv.key;
let v = &kv.value;
quote! {
#crate_path::descriptor::MetaKv {
key: concat!(#k, "\0").as_ptr() as *const ::std::ffi::c_char,
value: concat!(#v, "\0").as_ptr() as *const ::std::ffi::c_char,
}
}
})
.collect();
let arr_len = method.method_metas.len();
per_method_arrays.push(quote! {
static #arr_name: [#crate_path::descriptor::MetaKv; #arr_len] = [
#(#kv_inits),*
];
});
table_entries.push(quote! {
#crate_path::descriptor::MethodMetaEntry {
kvs: #arr_name.as_ptr(),
kv_count: #kv_count,
}
});
}
}
}
let method_count = ir.methods.len();
let table = if any_method_meta {
quote! {
pub static __FIDIUS_METHOD_META_TABLE: [#crate_path::descriptor::MethodMetaEntry; #method_count] = [
#(#table_entries),*
];
}
} else {
quote! {}
};
let trait_meta = if !ir.trait_metas.is_empty() {
let kv_inits: Vec<TokenStream> = ir
.trait_metas
.iter()
.map(|kv| {
let k = &kv.key;
let v = &kv.value;
quote! {
#crate_path::descriptor::MetaKv {
key: concat!(#k, "\0").as_ptr() as *const ::std::ffi::c_char,
value: concat!(#v, "\0").as_ptr() as *const ::std::ffi::c_char,
}
}
})
.collect();
let len = ir.trait_metas.len();
quote! {
pub static __FIDIUS_TRAIT_META: [#crate_path::descriptor::MetaKv; #len] = [
#(#kv_inits),*
];
}
} else {
quote! {}
};
let _ = trait_name;
quote! {
#(#per_method_arrays)*
#table
#trait_meta
}
}
fn generate_vtable(ir: &InterfaceIR) -> TokenStream {
let vtable_name = format_ident!("{}_VTable", ir.trait_name);
let fn_type = match ir.attrs.buffer_strategy {
BufferStrategyAttr::PluginAllocated => quote! {
unsafe extern "C" fn(
*const u8, u32,
*mut *mut u8, *mut u32,
) -> i32
},
BufferStrategyAttr::Arena => quote! {
unsafe extern "C" fn(
*const u8, u32,
*mut u8, u32,
*mut u32, *mut u32,
) -> i32
},
};
let fields: Vec<TokenStream> = ir
.methods
.iter()
.map(|m| {
let field_name = &m.name;
if m.optional_since.is_some() {
quote! { pub #field_name: Option<#fn_type> }
} else {
quote! { pub #field_name: #fn_type }
}
})
.collect();
let constructor_name = format_ident!("new_{}_vtable", ir.trait_name.to_string().to_lowercase());
let params: Vec<TokenStream> = ir
.methods
.iter()
.map(|m| {
let name = &m.name;
quote! { #name: #fn_type }
})
.collect();
let field_assigns: Vec<TokenStream> = ir
.methods
.iter()
.map(|m| {
let name = &m.name;
if m.optional_since.is_some() {
quote! { #name: Some(#name) }
} else {
quote! { #name: #name }
}
})
.collect();
quote! {
#[repr(C)]
pub struct #vtable_name {
#(#fields,)*
}
pub const fn #constructor_name(#(#params),*) -> #vtable_name {
#vtable_name {
#(#field_assigns,)*
}
}
}
}
fn generate_constants(ir: &InterfaceIR) -> TokenStream {
let trait_name = &ir.trait_name;
let required_sigs: Vec<&str> = ir
.methods
.iter()
.filter(|m| m.is_required())
.map(|m| m.signature_string.as_str())
.collect();
let hash_value = fidius_core::hash::interface_hash(&required_sigs);
let hash_name = format_ident!("{}_INTERFACE_HASH", trait_name);
let version_name = format_ident!("{}_INTERFACE_VERSION", trait_name);
let strategy_name = format_ident!("{}_BUFFER_STRATEGY", trait_name);
let version_val = ir.attrs.version;
let strategy_val = ir.attrs.buffer_strategy as u8;
let cap_constants: Vec<TokenStream> = ir
.methods
.iter()
.filter(|m| m.optional_since.is_some())
.enumerate()
.map(|(bit, m)| {
let const_name =
format_ident!("{}_CAP_{}", trait_name, m.name.to_string().to_uppercase());
let bit_val = 1u64 << bit;
quote! { pub const #const_name: u64 = #bit_val; }
})
.collect();
let optional_names_ident = format_ident!("{}_OPTIONAL_METHODS", trait_name);
let optional_names: Vec<String> = ir
.methods
.iter()
.filter(|m| m.optional_since.is_some())
.map(|m| m.name.to_string())
.collect();
quote! {
pub const #hash_name: u64 = #hash_value;
pub const #version_name: u32 = #version_val;
pub const #strategy_name: u8 = #strategy_val;
#(#cap_constants)*
pub const #optional_names_ident: &[&str] = &[#(#optional_names),*];
}
}
fn generate_descriptor_builder(ir: &InterfaceIR) -> TokenStream {
let trait_name = &ir.trait_name;
let crate_path = &ir.attrs.crate_path;
let vtable_name = format_ident!("{}_VTable", trait_name);
let fn_name = format_ident!(
"__fidius_build_{}_descriptor",
trait_name.to_string().to_lowercase()
);
let hash_name = format_ident!("{}_INTERFACE_HASH", trait_name);
let version_name = format_ident!("{}_INTERFACE_VERSION", trait_name);
let strategy_name = format_ident!("{}_BUFFER_STRATEGY", trait_name);
let interface_name_str = trait_name.to_string();
let interface_name_cstr_ident = format_ident!("__FIDIUS_INTERFACE_NAME_{}", trait_name);
let any_method_meta = ir.methods.iter().any(|m| !m.method_metas.is_empty());
let method_metadata_expr = if any_method_meta {
quote! { __FIDIUS_METHOD_META_TABLE.as_ptr() }
} else {
quote! { ::std::ptr::null() }
};
let has_trait_meta = !ir.trait_metas.is_empty();
let trait_meta_count = ir.trait_metas.len() as u32;
let trait_metadata_expr = if has_trait_meta {
quote! { __FIDIUS_TRAIT_META.as_ptr() }
} else {
quote! { ::std::ptr::null() }
};
quote! {
const #interface_name_cstr_ident: &std::ffi::CStr = {
unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(concat!(#interface_name_str, "\0").as_bytes()) }
};
pub const unsafe fn #fn_name(
plugin_name: *const std::ffi::c_char,
vtable: *const #vtable_name,
capabilities: u64,
free_buffer: Option<unsafe extern "C" fn(*mut u8, usize)>,
method_count: u32,
) -> #crate_path::descriptor::PluginDescriptor {
#crate_path::descriptor::PluginDescriptor {
descriptor_size: std::mem::size_of::<#crate_path::descriptor::PluginDescriptor>() as u32,
abi_version: #crate_path::descriptor::ABI_VERSION,
interface_name: #interface_name_cstr_ident.as_ptr(),
interface_hash: #hash_name,
interface_version: #version_name,
capabilities,
buffer_strategy: #strategy_name,
plugin_name,
vtable: vtable as *const std::ffi::c_void,
free_buffer,
method_count,
method_metadata: #method_metadata_expr,
trait_metadata: #trait_metadata_expr,
trait_metadata_count: #trait_meta_count,
}
}
}
}
fn generate_method_indices(ir: &InterfaceIR) -> TokenStream {
let indices: Vec<TokenStream> = ir
.methods
.iter()
.enumerate()
.map(|(i, m)| {
let const_name = format_ident!("METHOD_{}", m.name.to_string().to_uppercase());
let doc = format!("Vtable index for `{}`.", m.name);
quote! {
#[doc = #doc]
pub const #const_name: usize = #i;
}
})
.collect();
quote! { #(#indices)* }
}
fn generate_client(ir: &InterfaceIR) -> TokenStream {
let trait_name = &ir.trait_name;
let client_name = format_ident!("{}Client", trait_name);
let crate_path = &ir.attrs.crate_path;
let companion_mod = format_ident!("__fidius_{}", trait_name);
let hash_name = format_ident!("{}_INTERFACE_HASH", trait_name);
let methods: Vec<TokenStream> = ir
.methods
.iter()
.enumerate()
.map(|(i, m)| {
let method_name = &m.name;
let index = i;
let arg_types = &m.arg_types;
let arg_names = &m.arg_names;
let ret_type = match &m.return_type {
Some(ty) => quote! { #ty },
None => quote! { () },
};
let cap_check = if m.optional_since.is_some() {
let cap_bit = ir
.methods
.iter()
.filter(|mm| mm.optional_since.is_some())
.position(|mm| mm.name == m.name)
.unwrap_or(0) as u32;
quote! {
if !self.handle.has_capability(#cap_bit) {
return Err(#crate_path::CallError::NotImplemented { bit: #cap_bit });
}
}
} else {
quote! {}
};
quote! {
pub fn #method_name(
&self,
#(#arg_names: &#arg_types,)*
) -> ::std::result::Result<#ret_type, #crate_path::CallError> {
#cap_check
self.handle.call_method(#index, &(#(#arg_names,)*))
}
}
})
.collect();
quote! {
#[cfg(feature = "host")]
pub struct #client_name {
handle: #crate_path::PluginHandle,
}
#[cfg(feature = "host")]
impl #client_name {
pub fn from_handle(handle: #crate_path::PluginHandle) -> Self {
Self { handle }
}
pub fn in_process(plugin_name: &str) -> ::std::result::Result<Self, #crate_path::LoadError> {
let desc = #crate_path::PluginHandle::find_in_process_descriptor(plugin_name)?;
let expected_hash = #companion_mod::#hash_name;
if desc.interface_hash != expected_hash {
return Err(#crate_path::LoadError::InterfaceHashMismatch {
got: desc.interface_hash,
expected: expected_hash,
});
}
let handle = #crate_path::PluginHandle::from_descriptor(desc)?;
Ok(Self::from_handle(handle))
}
pub fn handle(&self) -> &#crate_path::PluginHandle {
&self.handle
}
#(#methods)*
}
}
}