This commit is contained in:
2025-06-22 15:11:37 +10:00
parent 5be3f2970c
commit 0667f79ff5
14 changed files with 508 additions and 319 deletions

View File

@@ -22,7 +22,7 @@ pub fn transform(_args: Args, mut item: Item) -> Result<TokenStream> {
_ => syn_error!(item, "expected struct or enum"),
};
let mod_name = format_ident!("__cdef__{name}");
let mod_name = format_ident!("__{name}_cdef");
Ok(quote! {
#[repr(C)]
@@ -42,23 +42,28 @@ pub fn transform(_args: Args, mut item: Item) -> Result<TokenStream> {
fn generate_type(ty: &Ident) -> Result<TokenStream> {
let ffi = ffi_crate();
let fmt = quote!(::std::format!);
let name_fmt = LitStr::new(&format!("{ty}"), ty.span());
let cdecl_fmt = LitStr::new(&format!("struct {ty} {{name}}"), ty.span());
let name = LitStr::new(&format!("{ty}"), ty.span());
let cdecl_fmt = LitStr::new(&format!("struct {ty} {{}}"), ty.span());
Ok(quote! {
unsafe impl #ffi::Type for #ty {
fn name() -> ::std::string::String {
#fmt(#name_fmt)
fn name() -> impl ::std::fmt::Display {
#name
}
fn cdecl(name: impl ::std::fmt::Display) -> ::std::string::String {
#fmt(#cdecl_fmt)
fn cdecl(name: impl ::std::fmt::Display) -> impl ::std::fmt::Display {
#fmt(#cdecl_fmt, name)
}
fn build(b: &mut #ffi::TypeBuilder) {
b.cdef::<Self>().metatype::<Self>();
}
}
unsafe impl #ffi::ToFfi for #ty {
type To = Self;
fn convert(self) -> Self::To { self }
}
})
}
@@ -113,6 +118,11 @@ struct CField {
attrs: CFieldAttrs,
}
#[derive(Default)]
struct CFieldAttrs {
opaque: bool,
}
fn to_cfields(fields: &mut Fields) -> Result<Vec<CField>> {
match fields {
Fields::Named(fields) => fields.named.iter_mut(),
@@ -133,11 +143,6 @@ fn to_cfields(fields: &mut Fields) -> Result<Vec<CField>> {
.collect()
}
#[derive(Default)]
struct CFieldAttrs {
opaque: bool,
}
fn parse_attrs(attrs: &mut Vec<Attribute>) -> Result<CFieldAttrs> {
let mut parsed = CFieldAttrs::default();
let mut i = 0;

View File

@@ -11,7 +11,7 @@ pub fn transform(mut imp: ItemImpl) -> Result<TokenStream> {
);
let impls = generate_impls(&mut imp)?;
let mod_name = format_ident!("__metatype__{}", ty_name(&imp.self_ty)?);
let mod_name = format_ident!("__{}_metatype", ty_name(&imp.self_ty)?);
Ok(quote! {
#imp
@@ -31,25 +31,30 @@ fn generate_impls(imp: &mut ItemImpl) -> Result<TokenStream> {
let ffi = ffi_crate();
let ffi_funcs = get_ffi_functions(imp)?;
// wrapper extern "C" functions that call the actual implementation
let ffi_wrappers: Vec<_> = ffi_funcs
.iter()
.map(generate_ffi_wrapper)
.collect::<Result<_>>()?;
// ffi function registration code
let ffi_register: Vec<_> = ffi_funcs
.iter()
.map(generate_ffi_register)
.collect::<Result<_>>()?;
let ffi_drop_fn = format_ident!("__ffi_drop");
let ffi_drop_name = format!("{ty_name}_drop");
let ffi_drop_rname = format_ident!("__ffi_drop");
let ffi_drop_cname = format!("{ty_name}_drop");
// ffi function symbol export code
let ffi_exports = {
let mut names = vec![&ffi_drop_fn];
let mut names = vec![&ffi_drop_rname];
names.extend(ffi_funcs.iter().map(|f| &f.rust_name));
generate_ffi_exports(&ty, names.into_iter())?
};
// lua function registration code
let lua_funcs = get_lua_functions(imp)?;
let lua_register: Vec<_> = lua_funcs
.iter()
@@ -57,6 +62,15 @@ fn generate_impls(imp: &mut ItemImpl) -> Result<TokenStream> {
.collect::<Result<_>>()?;
Ok(quote! {
impl #ty {
#(#ffi_wrappers)*
#[unsafe(export_name = #ffi_drop_cname)]
unsafe extern "C" fn #ffi_drop_rname(ptr: *mut Self) {
unsafe { ::std::ptr::drop_in_place(ptr) }
}
}
unsafe impl #ffi::Metatype for #ty {
type Target = Self;
@@ -64,17 +78,8 @@ fn generate_impls(imp: &mut ItemImpl) -> Result<TokenStream> {
#(#ffi_register)*
#(#lua_register)*
b.declare::<unsafe extern "C" fn(*mut Self)>(#ffi_drop_name);
b.metatable_raw("gc", ::std::format_args!("C.{}", #ffi_drop_name));
}
}
impl #ty {
#(#ffi_wrappers)*
#[unsafe(export_name = #ffi_drop_name)]
unsafe extern "C" fn #ffi_drop_fn(&mut self) {
unsafe { ::std::ptr::drop_in_place(self) }
b.declare::<unsafe extern "C" fn(*mut Self)>(#ffi_drop_cname);
b.metatable_raw("gc", ::std::format_args!("__C.{}", #ffi_drop_cname));
}
}
@@ -89,7 +94,13 @@ struct FfiFunction {
c_name: String,
params: Vec<PatType>,
ret: Type,
ret_out: bool,
ret_by_out: bool,
attrs: FfiFunctionAttrs,
}
#[derive(Default)]
struct FfiFunctionAttrs {
metatable: Option<LitStr>,
}
fn get_ffi_functions(imp: &mut ItemImpl) -> Result<Vec<FfiFunction>> {
@@ -103,6 +114,7 @@ fn get_ffi_functions(imp: &mut ItemImpl) -> Result<Vec<FfiFunction>> {
{
func.sig.abi = None;
// normalise inputs to PatType
let params = func
.sig
.inputs
@@ -118,13 +130,14 @@ fn get_ffi_functions(imp: &mut ItemImpl) -> Result<Vec<FfiFunction>> {
})
.collect::<Result<_>>()?;
// normalise output to Type
let ret = match func.sig.output {
ReturnType::Default => parse_quote!(()),
ReturnType::Type(_, ref ty) => (**ty).clone(),
};
// whether to use out-param for return values
let ret_out = !is_primitive(&ret);
let ret_by_out = !is_primitive(&ret);
funcs.push(FfiFunction {
name: func.sig.ident.clone(),
@@ -133,7 +146,8 @@ fn get_ffi_functions(imp: &mut ItemImpl) -> Result<Vec<FfiFunction>> {
c_name: format!("{}_{}", ty_name(&imp.self_ty)?, func.sig.ident),
params,
ret,
ret_out,
ret_by_out,
attrs: parse_ffi_function_attrs(&mut func.attrs)?,
});
}
}
@@ -141,6 +155,27 @@ fn get_ffi_functions(imp: &mut ItemImpl) -> Result<Vec<FfiFunction>> {
Ok(funcs)
}
fn parse_ffi_function_attrs(attrs: &mut Vec<Attribute>) -> Result<FfiFunctionAttrs> {
let mut parsed = FfiFunctionAttrs::default();
let mut i = 0;
while let Some(attr) = attrs.get(i) {
if let Some(name) = attr.path().get_ident() {
if name == "metatable" {
parsed.metatable = attr.parse_args()?;
attrs.remove(i);
continue;
} else if name == "new" {
parsed.metatable = parse_quote!("new");
attrs.remove(i);
continue;
}
}
i += 1;
}
Ok(parsed)
}
#[derive(Debug)]
enum FfiArgType {
Default,
@@ -150,14 +185,6 @@ fn get_ffi_arg_type(_ty: &Type) -> FfiArgType {
FfiArgType::Default
}
fn escape_self(name: &Ident) -> Ident {
if name == "self" {
format_ident!("__self")
} else {
name.clone()
}
}
fn generate_ffi_wrapper(func: &FfiFunction) -> Result<TokenStream> {
let ffi = ffi_crate();
let name = &func.name;
@@ -166,36 +193,35 @@ fn generate_ffi_wrapper(func: &FfiFunction) -> Result<TokenStream> {
let mut params = vec![];
let mut args = vec![];
for param in func.params.iter() {
let name = escape_self(pat_ident(&param.pat)?);
for (i, param) in func.params.iter().enumerate() {
let name = format_ident!("__arg{i}");
let ty = &param.ty;
match get_ffi_arg_type(ty) {
FfiArgType::Default => {
params.push(quote! { #name: <#ty as #ffi::FromFfi>::From });
args.push(quote! { <#ty as #ffi::FromFfi>::convert(#name) });
params.push(quote! { #name: <#ty as #ffi::FromFfi>::FromArg });
args.push(quote! { <#ty as #ffi::FromFfi>::convert_arg(#name) });
}
}
}
// make return by out-param the first parameter
let (ret, do_ret) = if func.ret_out {
let (ret, call) = if func.ret_by_out {
// make return by out-param the first parameter
let ret = &func.ret;
params.insert(0, quote! { __ret_out: *mut #ret });
params.insert(0, quote! { __out: *mut #ret });
(
quote! { () },
quote! { unsafe { ::std::ptr::write(__ret_out, __ret) }; },
quote!(()),
quote! { ::std::ptr::write(__out, Self::#name(#(#args),*)) },
)
} else {
let ret = &func.ret;
(quote! { #ret }, quote! { return __ret; })
(quote! { #ret }, quote! { Self::#name(#(#args),*) })
};
Ok(quote! {
#[unsafe(export_name = #c_name)]
unsafe extern "C" fn #rust_name(#(#params),*) -> #ret {
let __ret = Self::#name(#(#args),*);
#do_ret
unsafe { #call }
}
})
}
@@ -204,35 +230,53 @@ fn generate_ffi_register(func: &FfiFunction) -> Result<TokenStream> {
let ffi = ffi_crate();
let lua_name = &func.lua_name;
let c_name = &func.c_name;
let mut params = vec![];
let mut asserts = vec![];
let mut register = vec![];
for param in func.params.iter() {
let name = format!("{}", pat_ident(&param.pat)?);
let ty = &param.ty;
params.push(match get_ffi_arg_type(ty) {
FfiArgType::Default => quote! { b.param::<#ty>(#name); },
});
match get_ffi_arg_type(ty) {
FfiArgType::Default => {
params.push(quote! { <#ty as #ffi::FromFfi>::FromArg });
register.push(quote! { b.param::<#ty>(#name); })
}
};
}
let ret = &func.ret;
let ret_conv = if is_unit(ret) {
quote! { #ffi::FfiReturnConvention::Void }
} else if func.ret_out {
asserts.push(quote! { #ffi::__internal::assert_type_ne_all!(#ret, ()); });
quote! { #ffi::FfiReturnConvention::OutParam }
} else if func.ret_by_out {
quote! { #ffi::FfiReturnConvention::ByOutParam }
} else {
asserts.push(quote! { #ffi::__internal::assert_type_ne_all!(#ret, ()); });
quote! { #ffi::FfiReturnConvention::ByValue }
};
let declare = quote! {
b.declare::<unsafe extern "C" fn(#(#params),*) -> #ret>(#c_name);
};
let register = match func.attrs.metatable {
Some(ref mt) => quote! {
b.metatable(#mt, |b| {
#(#register)*
b.call::<#ret>(#c_name, #ret_conv);
});
},
None => quote! {
b.index(#lua_name, |b| {
#(#register)*
b.call::<#ret>(#c_name, #ret_conv);
});
},
};
Ok(quote! {
b.index(#lua_name, |b| {
#(#asserts)*
#(#params)*
b.call::<#ret>(#c_name, #ret_conv);
});
#declare
#register
})
}
@@ -241,7 +285,8 @@ fn generate_ffi_exports<'a>(
names: impl Iterator<Item = &'a Ident>,
) -> Result<TokenStream> {
Ok(quote! {
// hack to prevent ffi functions from being dead code-eliminated
// this ensures ffi function symbol exports are actually present in the resulting binary,
// otherwise they may get dead code-eliminated before it reaches the linker
#[used]
static __FFI_EXPORTS: &[fn()] = unsafe {
&[#(::std::mem::transmute(#ty::#names as *const ())),*]
@@ -265,6 +310,7 @@ fn get_lua_functions(imp: &mut ItemImpl) -> Result<Vec<LuaFunction>> {
&& let Some(ref abi) = abi.name
&& abi.value() == "Lua"
{
// normalise inputs to PatType
let params = func
.sig
.inputs
@@ -281,6 +327,13 @@ fn get_lua_functions(imp: &mut ItemImpl) -> Result<Vec<LuaFunction>> {
})
.collect::<Result<_>>()?;
// shouldn't specify an output type
syn_assert!(
matches!(func.sig.output, ReturnType::Default),
func.sig.output,
"cannot have return type"
);
funcs.push(LuaFunction {
name: format!("{}", func.sig.ident),
body: func.block.clone(),

View File

@@ -1,5 +1,5 @@
use std::env;
use syn::*;
use syn::{spanned::Spanned, *};
macro_rules! syn_error {
($src:expr, $($fmt:expr),+) => {{
@@ -35,11 +35,15 @@ pub fn ty_name(ty: &Type) -> Result<&Ident> {
}
}
pub fn pat_ident(pat: &Pat) -> Result<&Ident> {
match pat {
Pat::Ident(ident) => Ok(&ident.ident),
pub fn pat_ident(pat: &Pat) -> Result<Ident> {
Ok(match pat {
Pat::Ident(ident) => match ident.subpat {
Some((_, ref subpat)) => syn_error!(subpat, "unexpected subpattern"),
None => ident.ident.clone(),
},
Pat::Wild(wild) => Ident::new("_", wild.span()),
_ => syn_error!(pat, "expected ident"),
}
})
}
pub fn is_unit(ty: &Type) -> bool {