Refactor luaffi proc-macro
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
use crate::utils::{ffi_crate, syn_assert, syn_error};
|
||||
use darling::FromMeta;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{ext::IdentExt, *};
|
||||
use quote::{format_ident, quote, quote_spanned};
|
||||
use syn::{ext::IdentExt, spanned::Spanned, *};
|
||||
|
||||
#[derive(Debug, FromMeta)]
|
||||
pub struct Args {}
|
||||
@@ -24,35 +24,39 @@ pub fn transform(_args: Args, mut item: Item) -> Result<TokenStream> {
|
||||
|
||||
let mod_name = format_ident!("__{name}_cdef");
|
||||
|
||||
Ok(quote! {
|
||||
Ok(quote!(
|
||||
#[repr(C)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#item
|
||||
|
||||
#[doc(hidden)]
|
||||
#[allow(unused, non_snake_case)]
|
||||
/// Automatically generated by luaffi.
|
||||
mod #mod_name {
|
||||
use super::*;
|
||||
#impl_type
|
||||
#impl_cdef
|
||||
}
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
fn generate_type(ty: &Ident) -> Result<TokenStream> {
|
||||
let ffi = ffi_crate();
|
||||
let fmt = quote!(::std::format!);
|
||||
let name = LitStr::new(&format!("{}", ty.unraw()), ty.span());
|
||||
let cdecl_fmt = LitStr::new(&format!("struct {} {{}}", ty.unraw()), ty.span());
|
||||
let span = ty.span();
|
||||
let name = LitStr::new(&ty.unraw().to_string(), span);
|
||||
|
||||
Ok(quote! {
|
||||
Ok(quote_spanned!(span =>
|
||||
unsafe impl #ffi::Type for #ty {
|
||||
fn name() -> impl ::std::fmt::Display {
|
||||
#name
|
||||
}
|
||||
|
||||
fn ty() -> #ffi::TypeType {
|
||||
#ffi::TypeType::Aggregate
|
||||
}
|
||||
|
||||
fn cdecl(name: impl ::std::fmt::Display) -> impl ::std::fmt::Display {
|
||||
#fmt(#cdecl_fmt, name)
|
||||
::std::format!("struct {} {name}", #name)
|
||||
}
|
||||
|
||||
fn build(b: &mut #ffi::TypeBuilder) {
|
||||
@@ -62,10 +66,10 @@ fn generate_type(ty: &Ident) -> Result<TokenStream> {
|
||||
|
||||
// SAFETY: we can always implement `IntoFfi` because it transfers ownership from Rust to Lua
|
||||
unsafe impl #ffi::IntoFfi for #ty {
|
||||
type To = Self;
|
||||
fn convert(self) -> Self::To { self }
|
||||
type Into = Self;
|
||||
fn convert(self) -> Self::Into { self }
|
||||
}
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
fn generate_cdef_structure(str: &mut ItemStruct) -> Result<TokenStream> {
|
||||
@@ -77,13 +81,14 @@ fn generate_cdef_structure(str: &mut ItemStruct) -> Result<TokenStream> {
|
||||
|
||||
let ffi = ffi_crate();
|
||||
let ty = &str.ident;
|
||||
let build = generate_build_cdef(&to_cfields(&mut str.fields)?)?;
|
||||
let span = ty.span();
|
||||
let build = generate_cdef_build(&get_cfields(&mut str.fields)?)?;
|
||||
|
||||
Ok(quote! {
|
||||
Ok(quote_spanned!(span =>
|
||||
unsafe impl #ffi::Cdef for #ty {
|
||||
fn build(b: &mut #ffi::CdefBuilder) { #build }
|
||||
}
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
fn generate_cdef_enum(enu: &mut ItemEnum) -> Result<TokenStream> {
|
||||
@@ -95,22 +100,24 @@ fn generate_cdef_enum(enu: &mut ItemEnum) -> Result<TokenStream> {
|
||||
|
||||
let ffi = ffi_crate();
|
||||
let ty = &enu.ident;
|
||||
let span = ty.span();
|
||||
let build = enu
|
||||
.variants
|
||||
.iter_mut()
|
||||
.map(|variant| {
|
||||
let build = generate_build_cdef(&to_cfields(&mut variant.fields)?)?;
|
||||
Ok(quote! { b.inner_struct(|b| { #build }); })
|
||||
let span = variant.span();
|
||||
let build = generate_cdef_build(&get_cfields(&mut variant.fields)?)?;
|
||||
Ok(quote_spanned!(span => b.inner_struct(|b| { #build })))
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
Ok(quote! {
|
||||
Ok(quote_spanned!(span =>
|
||||
unsafe impl #ffi::Cdef for #ty {
|
||||
fn build(b: &mut #ffi::CdefBuilder) {
|
||||
b.field::<::std::ffi::c_int>("__tag").inner_union(|b| { #(#build)* });
|
||||
b.field::<::std::ffi::c_int>("__tag").inner_union(|b| { #(#build;)* });
|
||||
}
|
||||
}
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
struct CField {
|
||||
@@ -124,7 +131,7 @@ struct CFieldAttrs {
|
||||
opaque: bool,
|
||||
}
|
||||
|
||||
fn to_cfields(fields: &mut Fields) -> Result<Vec<CField>> {
|
||||
fn get_cfields(fields: &mut Fields) -> Result<Vec<CField>> {
|
||||
match fields {
|
||||
Fields::Named(fields) => fields.named.iter_mut(),
|
||||
Fields::Unnamed(fields) => fields.unnamed.iter_mut(),
|
||||
@@ -134,17 +141,17 @@ fn to_cfields(fields: &mut Fields) -> Result<Vec<CField>> {
|
||||
.map(|(i, field)| {
|
||||
Ok(CField {
|
||||
name: match field.ident {
|
||||
Some(ref name) => format!("{}", name.unraw()),
|
||||
Some(ref name) => name.unraw().to_string(),
|
||||
None => format!("__{i}"),
|
||||
},
|
||||
ty: field.ty.clone(),
|
||||
attrs: parse_attrs(&mut field.attrs)?,
|
||||
attrs: parse_cfield_attrs(&mut field.attrs)?,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn parse_attrs(attrs: &mut Vec<Attribute>) -> Result<CFieldAttrs> {
|
||||
fn parse_cfield_attrs(attrs: &mut Vec<Attribute>) -> Result<CFieldAttrs> {
|
||||
let mut parsed = CFieldAttrs::default();
|
||||
let mut i = 0;
|
||||
while let Some(attr) = attrs.get(i) {
|
||||
@@ -161,11 +168,11 @@ fn parse_attrs(attrs: &mut Vec<Attribute>) -> Result<CFieldAttrs> {
|
||||
Ok(parsed)
|
||||
}
|
||||
|
||||
fn generate_build_cdef(fields: &[CField]) -> Result<TokenStream> {
|
||||
let mut body = vec![quote! {
|
||||
fn generate_cdef_build(fields: &[CField]) -> Result<TokenStream> {
|
||||
let mut body = vec![quote!(
|
||||
let mut offset = 0;
|
||||
let mut align = 1;
|
||||
}];
|
||||
)];
|
||||
|
||||
fn offset(i: usize) -> Ident {
|
||||
format_ident!("offset{i}")
|
||||
@@ -174,40 +181,41 @@ fn generate_build_cdef(fields: &[CField]) -> Result<TokenStream> {
|
||||
let max = quote!(::std::cmp::Ord::max);
|
||||
let size_of = quote!(::std::mem::size_of);
|
||||
let align_of = quote!(::std::mem::align_of);
|
||||
|
||||
for (i, field) in fields.iter().enumerate() {
|
||||
let ty = &field.ty;
|
||||
let offset = offset(i);
|
||||
body.push(quote! {
|
||||
body.push(quote_spanned!(ty.span() =>
|
||||
// round up current offset to the alignment of field type for field offset
|
||||
offset = (offset + #align_of::<#ty>() - 1) & !(#align_of::<#ty>() - 1);
|
||||
align = #max(align, #align_of::<#ty>());
|
||||
let #offset = offset;
|
||||
offset += #size_of::<#ty>();
|
||||
});
|
||||
));
|
||||
}
|
||||
|
||||
body.push(quote! {
|
||||
body.push(quote!(
|
||||
// round up final offset to the total alignment of struct for struct size
|
||||
let size = (offset + align - 1) & !(align - 1);
|
||||
});
|
||||
));
|
||||
|
||||
let len = fields.len();
|
||||
for (i, field) in fields.iter().enumerate() {
|
||||
let name = &field.name;
|
||||
let ty = &field.ty;
|
||||
if field.attrs.opaque {
|
||||
body.push(if i == len - 1 {
|
||||
body.push(if field.attrs.opaque {
|
||||
if i == len - 1 {
|
||||
let a = offset(i);
|
||||
quote! { b.field_opaque(size - #a); } // last field
|
||||
quote_spanned!(ty.span() => b.field_opaque(size - #a);) // last field
|
||||
} else {
|
||||
let a = offset(i);
|
||||
let b = offset(i + 1);
|
||||
quote! { b.field_opaque(#b - #a); }
|
||||
});
|
||||
quote_spanned!(ty.span() => b.field_opaque(#b - #a);)
|
||||
}
|
||||
} else {
|
||||
body.push(quote! { b.field::<#ty>(#name); });
|
||||
}
|
||||
quote_spanned!(ty.span() => b.field::<#ty>(#name);)
|
||||
});
|
||||
}
|
||||
|
||||
Ok(quote! { #(#body)* })
|
||||
Ok(quote!(#(#body)*))
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use crate::utils::{ffi_crate, is_primitive, is_unit, pat_ident, syn_assert, ty_name};
|
||||
use crate::utils::{
|
||||
ffi_crate, is_primitivelike, is_unit, pat_ident, syn_assert, syn_error, ty_name,
|
||||
};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{ext::IdentExt, *};
|
||||
use quote::{ToTokens, format_ident, quote, quote_spanned};
|
||||
use std::{collections::HashSet, fmt};
|
||||
use syn::{ext::IdentExt, punctuated::Punctuated, spanned::Spanned, *};
|
||||
|
||||
pub fn transform(mut imp: ItemImpl) -> Result<TokenStream> {
|
||||
syn_assert!(
|
||||
@@ -13,94 +16,76 @@ 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)?);
|
||||
|
||||
Ok(quote! {
|
||||
Ok(quote!(
|
||||
#imp
|
||||
|
||||
#[doc(hidden)]
|
||||
#[allow(unused, non_snake_case)]
|
||||
/// Automatically generated by luaffi.
|
||||
mod #mod_name {
|
||||
use super::*;
|
||||
#impls
|
||||
}
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
fn generate_impls(imp: &mut ItemImpl) -> Result<TokenStream> {
|
||||
let ffi = ffi_crate();
|
||||
let ty = imp.self_ty.clone();
|
||||
let ty_name = ty_name(&ty)?;
|
||||
let mut ffi_funcs = FfiRegistry::new(ty_name.clone());
|
||||
let mut lua_funcs = LuaRegistry::new(ty_name.clone());
|
||||
let mut mms = HashSet::new();
|
||||
let mut lua_drop = None;
|
||||
|
||||
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_register_new = match ffi_funcs
|
||||
.iter()
|
||||
.find(|f| f.attrs.metatable.as_deref() == Some("new"))
|
||||
{
|
||||
Some(_) => None,
|
||||
None => Some({
|
||||
// fallback error constructor to prevent creating uninitialised ctypes
|
||||
let err = format!(r#"function() error("type '{ty_name}' has no constructor"); end"#);
|
||||
quote! { b.metatable_raw("new", #err); }
|
||||
}),
|
||||
};
|
||||
|
||||
let ffi_drop_rname = format_ident!("__ffi_drop");
|
||||
let ffi_drop_cname = format!("{ty_name}_drop");
|
||||
let ffi_wrapper_drop = quote! {
|
||||
#[unsafe(export_name = #ffi_drop_cname)]
|
||||
unsafe extern "C" fn #ffi_drop_rname(ptr: *mut Self) {
|
||||
unsafe { ::std::ptr::drop_in_place(ptr) }
|
||||
for func in get_ffi_functions(imp)? {
|
||||
if let Some(mm) = func.attrs.metamethod {
|
||||
syn_assert!(
|
||||
mms.insert(mm),
|
||||
func.name,
|
||||
"metamethod `{mm}` already defined"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let ffi_register_drop = quote! {
|
||||
if ::std::mem::needs_drop::<Self>() {
|
||||
b.declare::<#ffi::UnsafeExternCFn<(*mut Self,), ()>>(#ffi_drop_cname);
|
||||
b.metatable_raw("gc", ::std::format_args!("__C.{}", #ffi_drop_cname));
|
||||
add_ffi_function(&mut ffi_funcs, &func)?;
|
||||
}
|
||||
|
||||
for func in get_lua_functions(imp)? {
|
||||
if let Some(mm) = func.attrs.metamethod {
|
||||
syn_assert!(
|
||||
mms.insert(mm),
|
||||
func.name,
|
||||
"metamethod `{mm}` already defined"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// ffi function symbol export code
|
||||
let ffi_exports = {
|
||||
let mut names = vec![&ffi_drop_rname];
|
||||
names.extend(ffi_funcs.iter().map(|f| &f.rust_name));
|
||||
generate_ffi_exports(&ty, names.into_iter())?
|
||||
};
|
||||
if func.attrs.metamethod == Some(Metamethod::Gc) {
|
||||
lua_drop = Some(func);
|
||||
} else {
|
||||
add_lua_function(&mut lua_funcs, &func)?;
|
||||
}
|
||||
}
|
||||
|
||||
// lua function registration code
|
||||
let lua_funcs = get_lua_functions(imp)?;
|
||||
let lua_register: Vec<_> = lua_funcs
|
||||
.iter()
|
||||
.map(generate_lua_register)
|
||||
.collect::<Result<_>>()?;
|
||||
if !mms.contains(&Metamethod::New) {
|
||||
inject_fallback_new(&mut lua_funcs)?;
|
||||
}
|
||||
|
||||
inject_merged_drop(&mut ffi_funcs, lua_drop.as_ref())?;
|
||||
|
||||
let ffi_shims = &ffi_funcs.shims;
|
||||
let ffi_build = &ffi_funcs.build;
|
||||
let lua_build = &lua_funcs.build;
|
||||
let ffi_exports = generate_ffi_exports(&ffi_funcs)?;
|
||||
|
||||
Ok(quote! {
|
||||
impl #ty {
|
||||
#(#ffi_wrappers)*
|
||||
#ffi_wrapper_drop
|
||||
}
|
||||
impl #ty { #(#ffi_shims)* }
|
||||
|
||||
unsafe impl #ffi::Metatype for #ty {
|
||||
type Target = Self;
|
||||
|
||||
fn build(b: &mut #ffi::MetatypeBuilder) {
|
||||
#(#ffi_register)*
|
||||
#(#lua_register)*
|
||||
|
||||
#ffi_register_new
|
||||
#ffi_register_drop
|
||||
#(#ffi_build)*
|
||||
#(#lua_build)*
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,20 +93,102 @@ fn generate_impls(imp: &mut ItemImpl) -> Result<TokenStream> {
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
enum Metamethod {
|
||||
// known luajit metamethods (see lj_obj.h)
|
||||
// index, newindex, mode and metatable are not included
|
||||
Gc,
|
||||
Eq,
|
||||
Len,
|
||||
Lt,
|
||||
Le,
|
||||
Concat,
|
||||
Call,
|
||||
Add,
|
||||
Sub,
|
||||
Mul,
|
||||
Div,
|
||||
Mod,
|
||||
Pow,
|
||||
Unm,
|
||||
ToString,
|
||||
New,
|
||||
Pairs,
|
||||
Ipairs,
|
||||
}
|
||||
|
||||
impl TryFrom<&Ident> for Metamethod {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: &Ident) -> Result<Self> {
|
||||
Ok(match value.to_string().as_str() {
|
||||
"gc" => Self::Gc,
|
||||
"eq" => Self::Eq,
|
||||
"len" => Self::Len,
|
||||
"lt" => Self::Lt,
|
||||
"le" => Self::Le,
|
||||
"concat" => Self::Concat,
|
||||
"call" => Self::Call,
|
||||
"add" => Self::Add,
|
||||
"sub" => Self::Sub,
|
||||
"mul" => Self::Mul,
|
||||
"div" => Self::Div,
|
||||
"mod" => Self::Mod,
|
||||
"pow" => Self::Pow,
|
||||
"unm" => Self::Unm,
|
||||
"tostring" => Self::ToString,
|
||||
"new" => Self::New,
|
||||
"pairs" => Self::Pairs,
|
||||
"ipairs" => Self::Ipairs,
|
||||
_ => syn_error!(value, "unknown metamethod"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Metamethod {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let name = match self {
|
||||
Self::Gc => "gc",
|
||||
Self::Eq => "eq",
|
||||
Self::Len => "len",
|
||||
Self::Lt => "lt",
|
||||
Self::Le => "le",
|
||||
Self::Concat => "concat",
|
||||
Self::Call => "call",
|
||||
Self::Add => "add",
|
||||
Self::Sub => "sub",
|
||||
Self::Mul => "mul",
|
||||
Self::Div => "div",
|
||||
Self::Mod => "mod",
|
||||
Self::Pow => "pow",
|
||||
Self::Unm => "unm",
|
||||
Self::ToString => "tostring",
|
||||
Self::New => "new",
|
||||
Self::Pairs => "pairs",
|
||||
Self::Ipairs => "ipairs",
|
||||
};
|
||||
|
||||
write!(f, "{name}")
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for Metamethod {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let name = self.to_string();
|
||||
tokens.extend(quote!(#name));
|
||||
}
|
||||
}
|
||||
|
||||
struct FfiFunction {
|
||||
name: Ident,
|
||||
rust_name: Ident,
|
||||
lua_name: String,
|
||||
c_name: String,
|
||||
params: Vec<PatType>,
|
||||
ret: Type,
|
||||
ret_by_out: bool,
|
||||
attrs: FfiFunctionAttrs,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct FfiFunctionAttrs {
|
||||
metatable: Option<String>,
|
||||
metamethod: Option<Metamethod>,
|
||||
}
|
||||
|
||||
fn get_ffi_functions(imp: &mut ItemImpl) -> Result<Vec<FfiFunction>> {
|
||||
@@ -136,49 +203,24 @@ fn get_ffi_functions(imp: &mut ItemImpl) -> Result<Vec<FfiFunction>> {
|
||||
func.sig.abi = None;
|
||||
|
||||
// normalise inputs to PatType
|
||||
let params = func
|
||||
.sig
|
||||
.inputs
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
Ok(match arg {
|
||||
funcs.push(FfiFunction {
|
||||
name: func.sig.ident.clone(),
|
||||
params: func
|
||||
.sig
|
||||
.inputs
|
||||
.iter()
|
||||
.map(|arg| match arg {
|
||||
FnArg::Receiver(recv) => {
|
||||
let ty = &recv.ty;
|
||||
parse_quote! { self: #ty }
|
||||
parse_quote_spanned!(ty.span() => self: #ty)
|
||||
}
|
||||
FnArg::Typed(ty) => ty.clone(),
|
||||
})
|
||||
})
|
||||
.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.
|
||||
//
|
||||
// out-param return convention isn't strictly necessary (luajit can handle them fine),
|
||||
// but luajit doesn't jit compile aggregate returns yet, so this is more of a
|
||||
// performance optimisation. https://luajit.org/ext_ffi_semantics.html#status
|
||||
//
|
||||
// right now this just heuristically looks for common primitive identifiers like `i32`
|
||||
// and `usize` which has its limitations when it comes to type aliases (proc-macro can't
|
||||
// see them), but the worst thing that can happen with a false detection is an
|
||||
// unnecessarily boxed primitive that gets just unwrapped, or an aggregate suboptimally
|
||||
// returned by-value. it should be correct for 99% of rust code that isn't doing
|
||||
// anything weird.
|
||||
let ret_by_out = !is_primitive(&ret);
|
||||
|
||||
funcs.push(FfiFunction {
|
||||
name: func.sig.ident.clone(),
|
||||
rust_name: format_ident!("__ffi_{}", func.sig.ident.unraw()),
|
||||
lua_name: format!("{}", func.sig.ident.unraw()),
|
||||
c_name: format!("{}_{}", ty_name(&imp.self_ty)?, func.sig.ident.unraw()),
|
||||
params,
|
||||
ret,
|
||||
ret_by_out,
|
||||
.collect(),
|
||||
ret: match func.sig.output {
|
||||
ReturnType::Default => parse_quote_spanned!(func.sig.span() => ()),
|
||||
ReturnType::Type(_, ref ty) => (**ty).clone(),
|
||||
},
|
||||
attrs: parse_ffi_function_attrs(&mut func.attrs)?,
|
||||
});
|
||||
}
|
||||
@@ -191,150 +233,201 @@ fn parse_ffi_function_attrs(attrs: &mut Vec<Attribute>) -> Result<FfiFunctionAtt
|
||||
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 = Some(attr.parse_args::<LitStr>()?.value());
|
||||
attrs.remove(i);
|
||||
continue;
|
||||
} else if name == "new" {
|
||||
parsed.metatable = Some("new".into());
|
||||
attrs.remove(i);
|
||||
continue;
|
||||
if let Some(name) = attr.path().get_ident()
|
||||
&& let Ok(method) = Metamethod::try_from(name)
|
||||
{
|
||||
match method {
|
||||
Metamethod::Gc => syn_error!(attr, "implement `Drop` instead"),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
parsed.metamethod = Some(method);
|
||||
attrs.remove(i);
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
Ok(parsed)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum FfiArgType {
|
||||
enum FfiParameterType {
|
||||
Default,
|
||||
}
|
||||
|
||||
fn get_ffi_arg_type(_ty: &Type) -> FfiArgType {
|
||||
FfiArgType::Default
|
||||
fn get_ffi_param_type(_ty: &Type) -> FfiParameterType {
|
||||
FfiParameterType::Default
|
||||
}
|
||||
|
||||
fn generate_ffi_wrapper(func: &FfiFunction) -> Result<TokenStream> {
|
||||
enum FfiReturnType {
|
||||
Void,
|
||||
Primitive,
|
||||
Aggregate,
|
||||
}
|
||||
|
||||
fn get_ffi_ret_type(ty: &Type) -> FfiReturnType {
|
||||
// aggregate type returns use an out-param instead of return by-value.
|
||||
//
|
||||
// out-param isn't strictly necessary (luajit can handle them fine), but luajit doesn't jit
|
||||
// compile aggregate returns yet, so this is more of a performance optimisation.
|
||||
// https://luajit.org/ext_ffi_semantics.html#status
|
||||
//
|
||||
// right now this just heuristically looks for common primitive identifiers like `i32` and
|
||||
// `usize` which has its limitations when it comes to type aliases (proc-macro can't see them),
|
||||
// but the worst thing that can happen with a false detection is an unnecessarily boxed
|
||||
// primitive that gets just unwrapped, or an aggregate suboptimally returned by-value. it should
|
||||
// be correct for 99% of rust code that isn't doing anything weird.
|
||||
//
|
||||
// the builder below has additional assertions to confirm whether our detection was correct.
|
||||
if is_unit(ty) {
|
||||
FfiReturnType::Void
|
||||
} else if is_primitivelike(ty) {
|
||||
FfiReturnType::Primitive
|
||||
} else {
|
||||
FfiReturnType::Aggregate
|
||||
}
|
||||
}
|
||||
|
||||
struct FfiRegistry {
|
||||
ty: Ident,
|
||||
shims: Vec<ImplItemFn>,
|
||||
build: Vec<TokenStream>,
|
||||
}
|
||||
|
||||
impl FfiRegistry {
|
||||
fn new(ty: Ident) -> Self {
|
||||
Self {
|
||||
ty,
|
||||
shims: vec![],
|
||||
build: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_ffi_function(registry: &mut FfiRegistry, func: &FfiFunction) -> Result<()> {
|
||||
let ffi = ffi_crate();
|
||||
let name = &func.name;
|
||||
let rust_name = &func.rust_name;
|
||||
let c_name = &func.c_name;
|
||||
let mut params = vec![];
|
||||
let mut args = vec![];
|
||||
let ty = ®istry.ty;
|
||||
let func_name = &func.name;
|
||||
let shim_name = format_ident!("__ffi_{}", func_name.unraw());
|
||||
let lua_name = format!("{}", func_name.unraw());
|
||||
let c_name = format!("{}_{}", ty.unraw(), func_name.unraw());
|
||||
|
||||
for (i, param) in func.params.iter().enumerate() {
|
||||
let name = format_ident!("arg{i}");
|
||||
let ty = ¶m.ty;
|
||||
let func_params = &func.params; // target function parameters
|
||||
let func_ret = &func.ret; // target function return type
|
||||
let mut func_args = vec![]; // target function arguments
|
||||
|
||||
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) });
|
||||
let mut shim_params = vec![]; // shim function parameters
|
||||
let mut shim_ret = quote_spanned!(func_ret.span() => // shim function return type
|
||||
<#func_ret as #ffi::IntoFfi>::Into
|
||||
);
|
||||
|
||||
let mut asserts = vec![]; // compile-time builder asserts
|
||||
let mut build = vec![]; // ffi builder body
|
||||
|
||||
// for __new metamethods, ignore the first argument (ctype of self, for which there is no
|
||||
// equivalent in C)
|
||||
if func.attrs.metamethod == Some(Metamethod::New) {
|
||||
build.push(quote!(
|
||||
b.param_ignored();
|
||||
));
|
||||
}
|
||||
|
||||
for (i, param) in func_params.iter().enumerate() {
|
||||
let func_param = ¶m.ty;
|
||||
let shim_param = format_ident!("arg{i}");
|
||||
let name = pat_ident(¶m.pat)?.unraw().to_string();
|
||||
|
||||
match get_ffi_param_type(func_param) {
|
||||
FfiParameterType::Default => {
|
||||
shim_params.push(quote_spanned!(param.span() =>
|
||||
#shim_param: <#func_param as #ffi::FromFfi>::From
|
||||
));
|
||||
func_args.push(quote_spanned!(param.span() =>
|
||||
<#func_param as #ffi::FromFfi>::convert(#shim_param)
|
||||
));
|
||||
build.push(quote_spanned!(param.span() =>
|
||||
b.param::<#func_param>(#name);
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (ret, call) = if func.ret_by_out {
|
||||
// make return by out-param the first parameter
|
||||
let ret = &func.ret;
|
||||
params.insert(0, quote! { out: *mut <#ret as #ffi::IntoFfi>::To });
|
||||
(
|
||||
quote!(()),
|
||||
quote! { ::std::ptr::write(out, <#ret as #ffi::IntoFfi>::convert(Self::#name(#(#args),*))) },
|
||||
)
|
||||
} else {
|
||||
let ret = &func.ret;
|
||||
(
|
||||
quote! { <#ret as #ffi::IntoFfi>::To },
|
||||
quote! { <#ret as #ffi::IntoFfi>::convert(Self::#name(#(#args),*)) },
|
||||
)
|
||||
let mut shim_body = quote_spanned!(func_name.span() => // shim function body
|
||||
<#func_ret as #ffi::IntoFfi>::convert(Self::#func_name(#(#func_args),*))
|
||||
);
|
||||
|
||||
match get_ffi_ret_type(func_ret) {
|
||||
FfiReturnType::Void => {
|
||||
asserts.push(quote_spanned!(func_ret.span() =>
|
||||
<<#func_ret as #ffi::IntoFfi>::Into as #ffi::Type>::ty() == #ffi::TypeType::Void
|
||||
));
|
||||
}
|
||||
FfiReturnType::Primitive => {
|
||||
asserts.push(quote_spanned!(func_ret.span() =>
|
||||
<<#func_ret as #ffi::IntoFfi>::Into as #ffi::Type>::ty() == #ffi::TypeType::Primitive
|
||||
));
|
||||
}
|
||||
FfiReturnType::Aggregate => {
|
||||
asserts.push(quote_spanned!(func_ret.span() =>
|
||||
<<#func_ret as #ffi::IntoFfi>::Into as #ffi::Type>::ty() == #ffi::TypeType::Aggregate
|
||||
));
|
||||
|
||||
shim_params.insert(0, quote!(out: *mut #shim_ret));
|
||||
(shim_body, shim_ret) = (quote!(::std::ptr::write(out, #shim_body)), quote!(()));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(quote! {
|
||||
build.push(quote_spanned!(func_name.span() =>
|
||||
b.call::<#func_ret>(#c_name);
|
||||
));
|
||||
|
||||
let shim_params_ty = {
|
||||
let tys: Punctuated<PatType, Token![,]> = parse_quote!(#(#shim_params),*);
|
||||
tys.iter().map(|pat| (*pat.ty).clone()).collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
registry.build.push(quote!(
|
||||
#(::std::assert!(#asserts);)*
|
||||
b.declare::<#ffi::UnsafeExternCFn<(#(#shim_params_ty,)*), #shim_ret>>(#c_name);
|
||||
));
|
||||
|
||||
registry.build.push(match func.attrs.metamethod {
|
||||
Some(ref mm) => quote!(b.metatable(#mm, |b| { #(#build)* });),
|
||||
None => quote!(b.index(#lua_name, |b| { #(#build)* });),
|
||||
});
|
||||
|
||||
registry.shims.push(parse_quote_spanned!(func_name.span() =>
|
||||
#[unsafe(export_name = #c_name)]
|
||||
unsafe extern "C" fn #rust_name(#(#params),*) -> #ret { #call }
|
||||
})
|
||||
unsafe extern "C" fn #shim_name(#(#shim_params),*) -> #shim_ret { #shim_body }
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_ffi_register(func: &FfiFunction) -> Result<TokenStream> {
|
||||
let ffi = ffi_crate();
|
||||
let lua_name = &func.lua_name;
|
||||
let c_name = &func.c_name;
|
||||
fn generate_ffi_exports(registry: &FfiRegistry) -> Result<TokenStream> {
|
||||
let ty = ®istry.ty;
|
||||
let names = registry.shims.iter().map(|f| &f.sig.ident);
|
||||
|
||||
let mut params = vec![];
|
||||
let mut register = vec![];
|
||||
|
||||
// for __new metamethods, ignore the first argument (ctype of self)
|
||||
if func.attrs.metatable.as_deref() == Some("new") {
|
||||
register.push(quote! { b.param_ignored(); });
|
||||
}
|
||||
|
||||
for param in func.params.iter() {
|
||||
let name = format!("{}", pat_ident(¶m.pat)?);
|
||||
let ty = ¶m.ty;
|
||||
|
||||
match get_ffi_arg_type(ty) {
|
||||
FfiArgType::Default => {
|
||||
params.push(quote! { <#ty as #ffi::FromFfi>::From });
|
||||
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_by_out {
|
||||
quote! { #ffi::FfiReturnConvention::ByOutParam }
|
||||
} else {
|
||||
quote! { #ffi::FfiReturnConvention::ByValue }
|
||||
};
|
||||
|
||||
let declare = if func.ret_by_out {
|
||||
quote! { b.declare::<#ffi::UnsafeExternCFn<(*mut <#ret as #ffi::IntoFfi>::To, #(#params,)*), ()>>(#c_name); }
|
||||
} else {
|
||||
quote! { b.declare::<#ffi::UnsafeExternCFn<(#(#params,)*), <#ret as #ffi::IntoFfi>::To>>(#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! { #declare #register })
|
||||
}
|
||||
|
||||
fn generate_ffi_exports<'a>(
|
||||
ty: &Type,
|
||||
names: impl Iterator<Item = &'a Ident>,
|
||||
) -> Result<TokenStream> {
|
||||
Ok(quote! {
|
||||
Ok(quote_spanned!(ty.span() =>
|
||||
// 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 ())),*]
|
||||
};
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
struct LuaFunction {
|
||||
name: String,
|
||||
name: Ident,
|
||||
params: Vec<Pat>,
|
||||
body: Block,
|
||||
attrs: LuaFunctionAttrs,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct LuaFunctionAttrs {
|
||||
metamethod: Option<Metamethod>,
|
||||
}
|
||||
|
||||
fn get_lua_functions(imp: &mut ItemImpl) -> Result<Vec<LuaFunction>> {
|
||||
@@ -357,15 +450,15 @@ fn get_lua_functions(imp: &mut ItemImpl) -> Result<Vec<LuaFunction>> {
|
||||
FnArg::Receiver(recv) => {
|
||||
syn_assert!(ty_name(&recv.ty)? == "Self", recv, "must be `self`");
|
||||
syn_assert!(recv.mutability.is_none(), recv, "cannot be mut");
|
||||
Pat::Type(parse_quote! { self: cdata })
|
||||
Pat::Type(parse_quote_spanned!(recv.span() => self: cdata))
|
||||
}
|
||||
FnArg::Typed(ty) => Pat::Type(ty.clone()),
|
||||
})
|
||||
})
|
||||
.collect::<Result<_>>()?;
|
||||
|
||||
if let Some(_) = func.sig.variadic {
|
||||
params.push(parse_quote!(variadic!()));
|
||||
if let Some(ref variadic) = func.sig.variadic {
|
||||
params.push(parse_quote_spanned!(variadic.span() => variadic!()));
|
||||
}
|
||||
|
||||
// shouldn't specify an output type
|
||||
@@ -376,9 +469,10 @@ fn get_lua_functions(imp: &mut ItemImpl) -> Result<Vec<LuaFunction>> {
|
||||
);
|
||||
|
||||
funcs.push(LuaFunction {
|
||||
name: format!("{}", func.sig.ident.unraw()),
|
||||
body: func.block.clone(),
|
||||
name: func.sig.ident.clone(),
|
||||
params,
|
||||
body: func.block.clone(),
|
||||
attrs: parse_lua_function_attrs(&mut func.attrs)?,
|
||||
});
|
||||
|
||||
imp.items.remove(i);
|
||||
@@ -390,13 +484,122 @@ fn get_lua_functions(imp: &mut ItemImpl) -> Result<Vec<LuaFunction>> {
|
||||
Ok(funcs)
|
||||
}
|
||||
|
||||
fn generate_lua_register(func: &LuaFunction) -> Result<TokenStream> {
|
||||
fn parse_lua_function_attrs(attrs: &mut Vec<Attribute>) -> Result<LuaFunctionAttrs> {
|
||||
let mut parsed = LuaFunctionAttrs::default();
|
||||
let mut i = 0;
|
||||
while let Some(attr) = attrs.get(i) {
|
||||
if let Some(name) = attr.path().get_ident()
|
||||
&& let Ok(method) = Metamethod::try_from(name)
|
||||
{
|
||||
match method {
|
||||
Metamethod::New => syn_error!(attr, r#"cannot be applied to a lua function"#),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
parsed.metamethod = Some(method);
|
||||
attrs.remove(i);
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(parsed)
|
||||
}
|
||||
|
||||
struct LuaRegistry {
|
||||
ty: Ident,
|
||||
build: Vec<TokenStream>,
|
||||
}
|
||||
|
||||
impl LuaRegistry {
|
||||
fn new(ty: Ident) -> Self {
|
||||
Self { ty, build: vec![] }
|
||||
}
|
||||
}
|
||||
|
||||
fn add_lua_function(registry: &mut LuaRegistry, func: &LuaFunction) -> Result<()> {
|
||||
let ffi = ffi_crate();
|
||||
let name = &func.name;
|
||||
let luaify = quote!(#ffi::__internal::luaify!);
|
||||
let name = func.name.unraw().to_string();
|
||||
let params = &func.params;
|
||||
let body = &func.body;
|
||||
|
||||
Ok(quote! {
|
||||
b.index_raw(#name, #ffi::__internal::luaify!(|#(#params),*| #body));
|
||||
})
|
||||
registry.build.push(match func.attrs.metamethod {
|
||||
Some(ref mm) => quote!(b.metatable_raw(#mm, #luaify(|#(#params),*| #body));),
|
||||
None => quote!(b.index_raw(#name, #luaify(|#(#params),*| #body));),
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn inject_fallback_new(registry: &mut LuaRegistry) -> Result<()> {
|
||||
let ty = ®istry.ty;
|
||||
let lua = format!(
|
||||
r#"function() error("type '{}' has no constructor"); end"#,
|
||||
ty.unraw(),
|
||||
);
|
||||
|
||||
registry.build.push(quote!(
|
||||
b.metatable_raw("new", #lua);
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn inject_merged_drop(registry: &mut FfiRegistry, lua: Option<&LuaFunction>) -> Result<()> {
|
||||
let ffi = ffi_crate();
|
||||
let luaify = quote!(#ffi::__internal::luaify!);
|
||||
let ty = ®istry.ty;
|
||||
let shim_name = format_ident!("__ffi_drop");
|
||||
let c_name = format_ident!("{}_drop", ty.unraw());
|
||||
let c_name_str = c_name.to_string();
|
||||
|
||||
if let Some(lua) = lua {
|
||||
syn_assert!(
|
||||
lua.params.len() == 1,
|
||||
lua.name,
|
||||
"finaliser must take exactly one parameter"
|
||||
);
|
||||
|
||||
syn_assert!(
|
||||
pat_ident(&lua.params[0])? == "self",
|
||||
lua.params[0],
|
||||
"finaliser parameter must be `self`"
|
||||
);
|
||||
|
||||
let params = &lua.params;
|
||||
let body = &lua.body;
|
||||
|
||||
registry.build.push(quote_spanned!(ty.span() =>
|
||||
if ::std::mem::needs_drop::<Self>() {
|
||||
// if we have both a lua-side finaliser and a rust drop, then merge the finalisers
|
||||
// by doing the lua part first then drop rust
|
||||
b.declare::<#ffi::UnsafeExternCFn<(*mut Self,), ()>>(#c_name_str);
|
||||
b.metatable_raw("gc", #luaify(|self| {
|
||||
raw!(#luaify(#body)); // embed the lua part inside a do block
|
||||
__C::#c_name(self);
|
||||
}));
|
||||
} else {
|
||||
// we only have a lua-side finaliser
|
||||
b.metatable_raw("gc", #luaify(|#(#params),*| #body));
|
||||
}
|
||||
));
|
||||
} else {
|
||||
registry.build.push(quote_spanned!(ty.span() =>
|
||||
if ::std::mem::needs_drop::<Self>() {
|
||||
// we only have a rust drop
|
||||
b.declare::<#ffi::UnsafeExternCFn<(*mut Self,), ()>>(#c_name_str);
|
||||
b.metatable_raw("gc", #luaify(|self| { __C::#c_name(self); }));
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
registry.shims.push(parse_quote_spanned!(ty.span() =>
|
||||
#[unsafe(export_name = #c_name_str)]
|
||||
unsafe extern "C" fn #shim_name(ptr: *mut Self) {
|
||||
unsafe { ::std::ptr::drop_in_place(ptr) }
|
||||
}
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ use syn::{spanned::Spanned, *};
|
||||
|
||||
macro_rules! syn_error {
|
||||
($src:expr, $($fmt:expr),+) => {{
|
||||
use syn::spanned::*;
|
||||
return Err(syn::Error::new($src.span(), format!($($fmt),*)));
|
||||
}};
|
||||
}
|
||||
@@ -56,11 +55,11 @@ pub fn is_unit(ty: &Type) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_primitive(ty: &Type) -> bool {
|
||||
pub fn is_primitivelike(ty: &Type) -> bool {
|
||||
match ty {
|
||||
Type::Tuple(tuple) if tuple.elems.is_empty() => true, // unit type
|
||||
Type::Reference(_) | Type::Ptr(_) => true,
|
||||
Type::Paren(paren) => is_primitive(&paren.elem),
|
||||
Type::Paren(paren) => is_primitivelike(&paren.elem),
|
||||
Type::Path(path) => {
|
||||
if let Some(name) = path.path.get_ident() {
|
||||
matches!(
|
||||
|
||||
Reference in New Issue
Block a user