Refactor luaffi proc-macro

This commit is contained in:
lumi 2025-06-24 19:30:39 +10:00
parent f8e7b8ae62
commit 3dd375b071
Signed by: luaneko
GPG Key ID: 406809B8763FF07A
6 changed files with 740 additions and 522 deletions

View File

@ -1,6 +1,6 @@
use crate::{
__internal::{display, type_id},
Cdef, CdefBuilder, FfiReturnConvention, IntoFfi, Metatype, MetatypeBuilder, Type, TypeBuilder,
Cdef, CdefBuilder, IntoFfi, Metatype, MetatypeBuilder, Type, TypeBuilder, TypeType,
UnsafeExternCFn,
};
use luaify::luaify;
@ -43,7 +43,7 @@ pub struct lua_future<F: Future<Output: IntoFfi>> {
sig: Signature,
poll: fn(Pin<&mut Self>, cx: &mut Context) -> Poll<()>,
state: State<F>,
take: unsafe extern "C" fn(&mut Self) -> <F::Output as IntoFfi>::To,
take: unsafe extern "C" fn(&mut Self) -> <F::Output as IntoFfi>::Into,
drop: unsafe extern "C" fn(&mut Self),
}
@ -94,7 +94,7 @@ impl<F: Future<Output: IntoFfi>> lua_future<F> {
}
}
unsafe extern "C" fn take(&mut self) -> <F::Output as IntoFfi>::To {
unsafe extern "C" fn take(&mut self) -> <F::Output as IntoFfi>::Into {
// `fut:__take()` returns the fulfilled value by-value (not by out-param) because if we
// preallocate a cdata for the out-param and the thread for some reason gets dropped and
// never resumed, the GC could call the destructor on an uninitialised cdata.
@ -136,8 +136,12 @@ unsafe impl<F: Future<Output: IntoFfi> + 'static> Type for lua_future<F> {
display!("future__{:x}", type_id::<F>())
}
fn ty() -> TypeType {
TypeType::Aggregate
}
fn cdecl(name: impl Display) -> impl Display {
display!("struct future__{:x} {name}", type_id::<F>())
display!("struct {} {name}", Self::name())
}
fn build(s: &mut TypeBuilder) {
@ -148,7 +152,7 @@ unsafe impl<F: Future<Output: IntoFfi> + 'static> Type for lua_future<F> {
unsafe impl<F: Future<Output: IntoFfi> + 'static> Cdef for lua_future<F> {
fn build(s: &mut CdefBuilder) {
s.field_opaque(mem::offset_of!(Self, take)) // opaque .sig, .poll and .state
.field::<UnsafeExternCFn<(&mut Self,), <F::Output as IntoFfi>::To>>("__take")
.field::<UnsafeExternCFn<(&mut Self,), <F::Output as IntoFfi>::Into>>("__take")
.field::<UnsafeExternCFn<(&mut Self,), ()>>("__drop");
}
}
@ -162,24 +166,25 @@ unsafe impl<F: Future<Output: IntoFfi> + 'static> Metatype for lua_future<F> {
}
unsafe impl<F: Future<Output: IntoFfi> + 'static> IntoFfi for lua_future<F> {
type To = lua_future<F>;
type Into = lua_future<F>;
fn convert(self) -> Self::To {
fn convert(self) -> Self::Into {
self
}
fn postlude(ret: &str, _conv: FfiReturnConvention) -> impl Display {
fn postlude(ret: &str) -> impl Display {
// When returning a future from Rust to Lua, yield it immediately to the runtime which will
// poll it to completion in the background, then take the fulfilled value once the thread
// gets resumed. Lua user code should never to worry about awaiting futures.
//
// Once the current thread gets resumed and we take the future's fulfilled value, we clear
// the finaliser on the future and forget it (there is nothing to call drop on).
// the finaliser on the future and forget it (there is nothing to drop once the value is
// taken).
//
// `coroutine.yield` is cached as `yield` and `ffi.gc` as `gc` in locals (see lib.rs)
// `coroutine.yield` is cached as `__yield` and `ffi.gc` as `__gc` in locals (see lib.rs)
display!(
"yield({ret}); {ret} = gc({ret}, nil):__take(); {}",
<F::Output as IntoFfi>::postlude(ret, FfiReturnConvention::ByValue)
"__yield({ret}); {ret} = __gc({ret}, nil):__take(); {}",
<F::Output as IntoFfi>::postlude(ret)
)
}
}

View File

@ -25,7 +25,7 @@ pub mod result;
// `ffi.keep(obj)`.
//
// https://github.com/LuaJIT/LuaJIT/issues/1167
pub const KEEP_FN: &str = "luaffi_keep";
pub(crate) const KEEP_FN: &str = "luaffi_keep";
#[unsafe(export_name = "luaffi_keep")]
extern "C" fn __keep(_ptr: *const c_void) {}
export![__keep];
@ -151,6 +151,7 @@ impl Registry {
}
pub fn declare<T: Type>(&mut self, name: impl Display) -> &mut Self {
assert!(T::ty() != TypeType::Void, "cannot declare void type");
self.include::<T>()
.funcs
.insert(name.to_string())
@ -159,6 +160,7 @@ impl Registry {
}
pub fn preload<T: Type>(&mut self, name: impl Display) -> &mut Self {
assert!(T::ty() != TypeType::Void, "cannot declare void type");
self.include::<T>();
let ct = T::name();
writeln!(
@ -189,6 +191,8 @@ impl Display for Registry {
pub unsafe trait Type {
fn name() -> impl Display;
fn ty() -> TypeType;
fn cdecl(name: impl Display) -> impl Display;
fn extern_cdecl(name: impl Display) -> impl Display {
Self::cdecl(name)
@ -197,6 +201,13 @@ pub unsafe trait Type {
fn build(b: &mut TypeBuilder);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TypeType {
Void,
Primitive,
Aggregate,
}
#[derive(Debug)]
pub struct TypeBuilder<'r> {
registry: &'r mut Registry,
@ -253,6 +264,7 @@ impl<'r> CdefBuilder<'r> {
}
pub fn field<T: Type>(&mut self, name: impl Display) -> &mut Self {
assert!(T::ty() != TypeType::Void, "cannot declare void field");
self.registry.include::<T>();
self.field_raw(T::cdecl(name))
}
@ -390,21 +402,13 @@ pub unsafe trait FromFfi: Sized {
}
pub unsafe trait IntoFfi: Sized {
type To: Type + Sized;
type Into: Type + Sized;
fn postlude(_ret: &str, _conv: FfiReturnConvention) -> impl Display {
fn postlude(_ret: &str) -> impl Display {
""
}
fn convert(self) -> Self::To;
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FfiReturnConvention {
Void,
#[default]
ByValue,
ByOutParam,
fn convert(self) -> Self::Into;
}
#[derive(Debug)]
@ -428,6 +432,11 @@ impl<'r, 'm> MetatypeMethodBuilder<'r, 'm> {
}
pub fn param<T: FromFfi>(&mut self, name: impl Display) -> &mut Self {
assert!(
T::From::ty() != TypeType::Void,
"cannot declare void parameter"
);
(!self.params.is_empty()).then(|| self.params.push_str(", "));
(!self.args.is_empty()).then(|| self.args.push_str(", "));
write!(self.params, "{name}").unwrap();
@ -468,7 +477,7 @@ impl<'r, 'm> MetatypeMethodBuilder<'r, 'm> {
self
}
pub fn call<T: IntoFfi>(&mut self, func: impl Display, ret: FfiReturnConvention) {
pub fn call<T: IntoFfi>(&mut self, func: impl Display) {
let Self {
metatype,
params,
@ -480,21 +489,21 @@ impl<'r, 'm> MetatypeMethodBuilder<'r, 'm> {
let lua = &mut metatype.lua;
write!(lua, "function({params}) {prelude}").unwrap();
match ret {
FfiReturnConvention::Void => {
match T::Into::ty() {
TypeType::Void => {
write!(lua, "__C.{func}({args}); {postlude}end").unwrap();
}
FfiReturnConvention::ByValue => {
let check = T::postlude("__res", ret);
TypeType::Primitive => {
let check = T::postlude("__res");
write!(
lua,
"local __res = __C.{func}({args}); {check}{postlude}return __res; end"
)
.unwrap();
}
FfiReturnConvention::ByOutParam => {
let ct = T::To::name();
let check = T::postlude("__res", ret);
TypeType::Aggregate => {
let ct = T::Into::name();
let check = T::postlude("__res");
write!(lua, "local __res = __new(__ct.{ct}); __C.{func}(__res").unwrap();
if !args.is_empty() {
write!(lua, ", {args}").unwrap();
@ -505,15 +514,51 @@ impl<'r, 'm> MetatypeMethodBuilder<'r, 'm> {
}
}
macro_rules! impl_primitive {
($rtype:ty, $ctype:expr) => {
unsafe impl Type for $rtype {
//
// SAFETY: Unit type return maps to a C void return, which is a nil return in lua. There is no
// equivalent to passing a unit type as an argument in C.
//
macro_rules! impl_void {
($rty:ty) => {
unsafe impl Type for $rty {
fn name() -> impl Display {
$ctype
"void"
}
fn ty() -> TypeType {
TypeType::Void
}
fn cdecl(name: impl Display) -> impl Display {
display!("{} {name}", $ctype)
display!("void {name}")
}
fn build(_b: &mut TypeBuilder) {}
}
unsafe impl IntoFfi for $rty {
type Into = ();
fn convert(self) -> Self::Into {}
}
};
}
impl_void!(());
impl_void!(c_void);
macro_rules! impl_primitive {
($rty:ty, $cty:expr) => {
unsafe impl Type for $rty {
fn name() -> impl Display {
$cty
}
fn ty() -> TypeType {
TypeType::Primitive
}
fn cdecl(name: impl Display) -> impl Display {
display!("{} {name}", $cty)
}
fn build(_b: &mut TypeBuilder) {}
@ -521,8 +566,6 @@ macro_rules! impl_primitive {
};
}
impl_primitive!((), "void");
impl_primitive!(c_void, "void");
impl_primitive!(bool, "bool");
impl_primitive!(u8, "uint8_t");
impl_primitive!(u16, "uint16_t");
@ -537,24 +580,6 @@ impl_primitive!(isize, "intptr_t");
impl_primitive!(c_float, "float");
impl_primitive!(c_double, "double");
unsafe impl IntoFfi for () {
//
// SAFETY: Unit type return maps to a C void return, which is a nil return in lua. There is no
// equivalent to passing a unit type as an argument in C. `c_void` cannot be returned from rust
// so it should return the unit type instead.
//
type To = ();
fn convert(self) -> Self::To {}
fn postlude(_ret: &str, conv: FfiReturnConvention) -> impl Display {
assert!(
conv == FfiReturnConvention::Void,
"void type cannot be instantiated"
);
""
}
}
unsafe impl FromFfi for bool {
type From = bool;
@ -570,24 +595,16 @@ unsafe impl FromFfi for bool {
}
unsafe impl IntoFfi for bool {
type To = bool;
type Into = bool;
fn convert(self) -> Self::To {
fn convert(self) -> Self::Into {
self
}
fn postlude(ret: &str, conv: FfiReturnConvention) -> impl Display {
disp(move |f| match conv {
FfiReturnConvention::Void => unreachable!(),
FfiReturnConvention::ByValue => Ok(()),
FfiReturnConvention::ByOutParam => write!(f, "{ret} = {ret} ~= 0; "),
})
}
}
macro_rules! impl_number_fromabi {
($rtype:ty) => {
unsafe impl FromFfi for $rtype {
($rty:ty) => {
unsafe impl FromFfi for $rty {
type From = Self;
fn prelude(arg: &str) -> impl Display {
@ -615,21 +632,13 @@ impl_number_fromabi!(f32);
impl_number_fromabi!(f64);
macro_rules! impl_number_intoabi {
($rtype:ty) => {
unsafe impl IntoFfi for $rtype {
type To = Self;
($rty:ty) => {
unsafe impl IntoFfi for $rty {
type Into = Self;
fn convert(self) -> Self::To {
fn convert(self) -> Self::Into {
self
}
fn postlude(ret: &str, conv: FfiReturnConvention) -> impl Display {
disp(move |f| match conv {
FfiReturnConvention::Void => unreachable!(),
FfiReturnConvention::ByValue => Ok(()),
FfiReturnConvention::ByOutParam => write!(f, "{ret} = tonumber({ret}); "),
})
}
}
};
}
@ -648,21 +657,20 @@ impl_number_intoabi!(c_float);
impl_number_intoabi!(c_double);
macro_rules! impl_bigint_intoabi {
($rtype:ty) => {
unsafe impl IntoFfi for $rtype {
type To = Self;
($rty:ty) => {
unsafe impl IntoFfi for $rty {
type Into = Self;
fn convert(self) -> Self::To {
fn convert(self) -> Self::Into {
self
}
fn postlude(ret: &str, conv: FfiReturnConvention) -> impl Display {
disp(move |f| match conv {
FfiReturnConvention::Void => unreachable!(),
FfiReturnConvention::ByValue | FfiReturnConvention::ByOutParam => {
write!(f, "{ret} = tonumber({ret}); ")
}
})
fn postlude(ret: &str) -> impl Display {
// this isn't "correct" per se, but it's much more ergonomic to work with numbers in
// lua than with long longs wrapped in cdata. we gracefully accept the loss of
// precision here and that 53 bits of precision for big integers are enough. (the
// vain of Lua 5.3 integer subtype ;D )
display!("{ret} = tonumber({ret}); ")
}
}
};
@ -682,6 +690,10 @@ macro_rules! impl_const_ptr {
display!("const_{}_ptr", T::name())
}
fn ty() -> TypeType {
TypeType::Primitive
}
fn cdecl(name: impl Display) -> impl Display {
T::cdecl(display!("const *{name}"))
}
@ -704,6 +716,10 @@ macro_rules! impl_mut_ptr {
display!("{}_ptr", T::name())
}
fn ty() -> TypeType {
TypeType::Primitive
}
fn cdecl(name: impl Display) -> impl Display {
T::cdecl(display!("*{name}"))
}
@ -753,13 +769,13 @@ impl_ptr_fromabi!(Option<&mut T>);
macro_rules! impl_ptr_intoabi {
($ty:ty) => {
unsafe impl<T: Type> IntoFfi for $ty {
type To = Self;
type Into = Self;
fn convert(self) -> Self::To {
fn convert(self) -> Self::Into {
self
}
fn postlude(ret: &str, _conv: FfiReturnConvention) -> impl Display {
fn postlude(ret: &str) -> impl Display {
display!("if {ret} == nil then {ret} = nil; end; ")
}
}
@ -822,9 +838,9 @@ impl_ref_fromabi!(&'s mut T);
macro_rules! impl_ref_intoabi {
($ty:ty) => {
unsafe impl<T: Type> IntoFfi for $ty {
type To = Self;
type Into = Self;
fn convert(self) -> Self::To {
fn convert(self) -> Self::Into {
self
}
}
@ -845,6 +861,10 @@ unsafe impl<T: Type> Type for [T] {
display!("{}_arr", T::name())
}
fn ty() -> TypeType {
TypeType::Aggregate
}
fn cdecl(name: impl Display) -> impl Display {
display!("{name}[]")
}
@ -859,6 +879,10 @@ unsafe impl<T: Type, const N: usize> Type for [T; N] {
display!("{}_arr{N}", T::name())
}
fn ty() -> TypeType {
TypeType::Aggregate
}
fn cdecl(name: impl Display) -> impl Display {
display!("{name}[{N}]")
}
@ -891,6 +915,10 @@ macro_rules! impl_function {
}))
}
fn ty() -> TypeType {
TypeType::Primitive
}
fn cdecl(name: impl Display) -> impl Display {
$ret::cdecl(disp(move |f| Ok({
let mut _n = 0;

View File

@ -1,13 +1,13 @@
use crate::{
__internal::{disp, display, export},
FfiReturnConvention, FromFfi, IntoFfi,
FromFfi, IntoFfi,
};
use bstr::{BStr, BString};
use luaffi_impl::{cdef, metatype};
use std::{fmt::Display, mem::ManuallyDrop, ptr, slice};
pub const IS_UTF8_FN: &str = "luaffi_is_utf8";
pub const DROP_BUFFER_FN: &str = "luaffi_drop_buffer";
pub(crate) const IS_UTF8_FN: &str = "luaffi_is_utf8";
pub(crate) const DROP_BUFFER_FN: &str = "luaffi_drop_buffer";
#[unsafe(export_name = "luaffi_is_utf8")]
unsafe extern "C" fn __is_utf8(ptr: *const u8, len: usize) -> bool {
@ -33,7 +33,16 @@ pub struct lua_buf {
#[metatype]
impl lua_buf {
fn null() -> Self {
// this takes a slice and decomposes it into its raw parts. caller should ensure the result is
// used only as long as the original buffer is still alive.
pub(crate) fn new(s: &[u8]) -> Self {
Self {
__ptr: s.as_ptr(),
__len: s.len(),
}
}
pub(crate) fn null() -> Self {
Self {
__ptr: ptr::null(),
__len: 0,
@ -41,6 +50,36 @@ impl lua_buf {
}
}
#[derive(Debug, Clone, Copy)]
#[cdef]
pub struct lua_buffer {
__ptr: *mut u8,
__len: usize,
__cap: usize,
}
#[metatype]
impl lua_buffer {
// this takes ownership of the Vec and decomposes it into its raw parts. the result must be
// dropped by `__drop_buffer` (see [`DROP_BUFFER_FN`]).
pub(crate) fn new(s: impl Into<Vec<u8>>) -> Self {
let s = s.into();
Self {
__cap: s.capacity(),
__len: s.len(),
__ptr: ManuallyDrop::new(s).as_mut_ptr(),
}
}
pub(crate) fn null() -> Self {
Self {
__ptr: ptr::null_mut(),
__len: 0,
__cap: 0,
}
}
}
unsafe impl<'s> FromFfi for &'s [u8] {
type From = Option<&'s lua_buf>;
@ -55,6 +94,8 @@ unsafe impl<'s> FromFfi for &'s [u8] {
f,
r#"assert(type({arg}) == "string", "string expected in argument '{arg}', got " .. type({arg})); "#
)?;
// SAFETY: the lua_buf is only valid for as long as the string is alive. we've ensured
// that it is alive for at least the duration of the ffi call via `require_keepalive()`.
write!(f, "{arg} = __new(__ct.lua_buf, {arg}, #{arg}); end; ")
})
}
@ -68,22 +109,6 @@ unsafe impl<'s> FromFfi for &'s [u8] {
}
}
unsafe impl<'s> FromFfi for &'s BStr {
type From = <&'s [u8] as FromFfi>::From;
fn require_keepalive() -> bool {
<&[u8] as FromFfi>::require_keepalive()
}
fn prelude(arg: &str) -> impl Display {
<&[u8] as FromFfi>::prelude(arg)
}
fn convert(from: Self::From) -> Self {
<&[u8] as FromFfi>::convert(from).into()
}
}
unsafe impl<'s> FromFfi for &'s str {
type From = Option<&'s lua_buf>;
@ -121,7 +146,77 @@ unsafe impl<'s> FromFfi for &'s str {
}
}
macro_rules! impl_optional_ref_from {
unsafe impl IntoFfi for &'static [u8] {
type Into = lua_buf;
fn convert(self) -> Self::Into {
// SAFETY: the slice is 'static so the resulting lua_buf is always valid
lua_buf::new(self)
}
fn postlude(ret: &str) -> impl Display {
display!("{ret} = __intern({ret}.__ptr, {ret}.__len)")
}
}
unsafe impl IntoFfi for Vec<u8> {
type Into = lua_buffer;
fn convert(self) -> Self::Into {
lua_buffer::new(self)
}
fn postlude(ret: &str) -> impl Display {
display!(
"do local __{ret} = {ret}; {ret} = __intern({ret}.__ptr, {ret}.__len); __C.{DROP_BUFFER_FN}(__{ret}); end; "
)
}
}
macro_rules! impl_from_via {
($ty:ty, $via:ty) => {
unsafe impl<'s> FromFfi for $ty {
type From = <$via as FromFfi>::From;
fn require_keepalive() -> bool {
<$via as FromFfi>::require_keepalive()
}
fn prelude(arg: &str) -> impl Display {
<$via as FromFfi>::prelude(arg)
}
fn convert(from: Self::From) -> Self {
<$via as FromFfi>::convert(from).into()
}
}
};
}
impl_from_via!(&'s BStr, &'s [u8]);
macro_rules! impl_into_via {
($ty:ty, $via:ty) => {
unsafe impl IntoFfi for $ty {
type Into = <$via as IntoFfi>::Into;
fn convert(self) -> Self::Into {
<$via as IntoFfi>::convert(self.into())
}
fn postlude(ret: &str) -> impl Display {
<$via as IntoFfi>::postlude(ret)
}
}
};
}
impl_into_via!(&'static BStr, &'static [u8]);
impl_into_via!(&'static str, &'static BStr);
impl_into_via!(BString, Vec<u8>);
impl_into_via!(String, BString);
macro_rules! impl_optional_from {
($ty:ty) => {
unsafe impl<'s> FromFfi for Option<$ty> {
type From = <$ty as FromFfi>::From;
@ -131,7 +226,7 @@ macro_rules! impl_optional_ref_from {
}
fn prelude(arg: &str) -> impl Display {
// avoid constructing a `lua_buf` at all for nil arguments
// just pass a null pointer if argument is nil
display!(
"if {arg} ~= nil then {}end; ",
<$ty as FromFfi>::prelude(arg)
@ -145,152 +240,32 @@ macro_rules! impl_optional_ref_from {
};
}
impl_optional_ref_from!(&'s [u8]);
impl_optional_ref_from!(&'s BStr);
impl_optional_ref_from!(&'s str);
unsafe impl IntoFfi for &'static [u8] {
type To = lua_buf;
fn convert(self) -> Self::To {
lua_buf {
__ptr: self.as_ptr(),
__len: self.len(),
}
}
fn postlude(ret: &str, _conv: FfiReturnConvention) -> impl Display {
display!("{ret} = __intern({ret}.__ptr, {ret}.__len)")
}
}
unsafe impl IntoFfi for &'static BStr {
type To = <&'static [u8] as IntoFfi>::To;
fn convert(self) -> Self::To {
<&[u8] as IntoFfi>::convert(self.as_ref())
}
fn postlude(ret: &str, conv: FfiReturnConvention) -> impl Display {
<&[u8] as IntoFfi>::postlude(ret, conv)
}
}
unsafe impl IntoFfi for &'static str {
type To = <&'static [u8] as IntoFfi>::To;
fn convert(self) -> Self::To {
<&[u8] as IntoFfi>::convert(self.as_bytes())
}
fn postlude(ret: &str, conv: FfiReturnConvention) -> impl Display {
<&[u8] as IntoFfi>::postlude(ret, conv)
}
}
macro_rules! impl_optional_ref_into {
($ty:ty) => {
unsafe impl IntoFfi for Option<$ty> {
type To = <$ty as IntoFfi>::To;
fn convert(self) -> Self::To {
self.map_or(lua_buf::null(), <$ty as IntoFfi>::convert)
}
fn postlude(ret: &str, conv: FfiReturnConvention) -> impl Display {
display!(
"if {ret}.__ptr == nil then {ret} = nil; else {}end; ",
<$ty as IntoFfi>::postlude(ret, conv)
)
}
}
};
}
impl_optional_ref_into!(&'static [u8]);
impl_optional_ref_into!(&'static BStr);
impl_optional_ref_into!(&'static str);
#[derive(Debug, Clone, Copy)]
#[cdef]
pub struct lua_buffer {
__ptr: *mut u8,
__len: usize,
__cap: usize,
}
#[metatype]
impl lua_buffer {
fn null() -> Self {
Self {
__ptr: ptr::null_mut(),
__len: 0,
__cap: 0,
}
}
}
unsafe impl IntoFfi for Vec<u8> {
type To = lua_buffer;
fn convert(self) -> Self::To {
lua_buffer {
__cap: self.capacity(),
__len: self.len(),
__ptr: ManuallyDrop::new(self).as_mut_ptr(),
}
}
fn postlude(ret: &str, _conv: FfiReturnConvention) -> impl Display {
display!(
"do local __{ret} = {ret}; {ret} = __intern({ret}.__ptr, {ret}.__len); __C.{DROP_BUFFER_FN}(__{ret}); end; "
)
}
}
unsafe impl IntoFfi for BString {
type To = <Vec<u8> as IntoFfi>::To;
fn convert(self) -> Self::To {
<Vec<u8> as IntoFfi>::convert(self.into())
}
fn postlude(ret: &str, conv: FfiReturnConvention) -> impl Display {
<Vec<u8> as IntoFfi>::postlude(ret, conv)
}
}
unsafe impl IntoFfi for String {
type To = <Vec<u8> as IntoFfi>::To;
fn convert(self) -> Self::To {
<Vec<u8> as IntoFfi>::convert(self.into_bytes())
}
fn postlude(ret: &str, conv: FfiReturnConvention) -> impl Display {
<Vec<u8> as IntoFfi>::postlude(ret, conv)
}
}
impl_optional_from!(&'s [u8]);
impl_optional_from!(&'s BStr);
impl_optional_from!(&'s str);
macro_rules! impl_optional_into {
($ty:ty) => {
($ty:ty, $null:expr) => {
unsafe impl IntoFfi for Option<$ty> {
type To = <$ty as IntoFfi>::To;
type Into = <$ty as IntoFfi>::Into;
fn convert(self) -> Self::To {
self.map_or(lua_buffer::null(), <$ty as IntoFfi>::convert)
fn convert(self) -> Self::Into {
self.map_or($null, <$ty as IntoFfi>::convert)
}
fn postlude(ret: &str, conv: FfiReturnConvention) -> impl Display {
fn postlude(ret: &str) -> impl Display {
display!(
"if {ret}.__ptr == nil then {ret} = nil; else {}end; ",
<$ty as IntoFfi>::postlude(ret, conv)
<$ty as IntoFfi>::postlude(ret)
)
}
}
};
}
impl_optional_into!(Vec<u8>);
impl_optional_into!(BString);
impl_optional_into!(String);
impl_optional_into!(&'static [u8], lua_buf::null());
impl_optional_into!(&'static BStr, lua_buf::null());
impl_optional_into!(&'static str, lua_buf::null());
impl_optional_into!(Vec<u8>, lua_buffer::null());
impl_optional_into!(BString, lua_buffer::null());
impl_optional_into!(String, lua_buffer::null());

View File

@ -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)*))
}

View File

@ -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 = &registry.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 = &param.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 = &param.ty;
let shim_param = format_ident!("arg{i}");
let name = pat_ident(&param.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 = &registry.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(&param.pat)?);
let ty = &param.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 = &registry.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 = &registry.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(())
}

View File

@ -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!(