diff --git a/crates/luaffi/src/future.rs b/crates/luaffi/src/future.rs index 2bdbf01..d898b33 100644 --- a/crates/luaffi/src/future.rs +++ b/crates/luaffi/src/future.rs @@ -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> { sig: Signature, poll: fn(Pin<&mut Self>, cx: &mut Context) -> Poll<()>, state: State, - take: unsafe extern "C" fn(&mut Self) -> ::To, + take: unsafe extern "C" fn(&mut Self) -> ::Into, drop: unsafe extern "C" fn(&mut Self), } @@ -94,7 +94,7 @@ impl> lua_future { } } - unsafe extern "C" fn take(&mut self) -> ::To { + unsafe extern "C" fn take(&mut self) -> ::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 + 'static> Type for lua_future { display!("future__{:x}", type_id::()) } + fn ty() -> TypeType { + TypeType::Aggregate + } + fn cdecl(name: impl Display) -> impl Display { - display!("struct future__{:x} {name}", type_id::()) + display!("struct {} {name}", Self::name()) } fn build(s: &mut TypeBuilder) { @@ -148,7 +152,7 @@ unsafe impl + 'static> Type for lua_future { unsafe impl + 'static> Cdef for lua_future { fn build(s: &mut CdefBuilder) { s.field_opaque(mem::offset_of!(Self, take)) // opaque .sig, .poll and .state - .field::::To>>("__take") + .field::::Into>>("__take") .field::>("__drop"); } } @@ -162,24 +166,25 @@ unsafe impl + 'static> Metatype for lua_future { } unsafe impl + 'static> IntoFfi for lua_future { - type To = lua_future; + type Into = lua_future; - 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(); {}", - ::postlude(ret, FfiReturnConvention::ByValue) + "__yield({ret}); {ret} = __gc({ret}, nil):__take(); {}", + ::postlude(ret) ) } } diff --git a/crates/luaffi/src/lib.rs b/crates/luaffi/src/lib.rs index 1468c02..72e097b 100644 --- a/crates/luaffi/src/lib.rs +++ b/crates/luaffi/src/lib.rs @@ -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(&mut self, name: impl Display) -> &mut Self { + assert!(T::ty() != TypeType::Void, "cannot declare void type"); self.include::() .funcs .insert(name.to_string()) @@ -159,6 +160,7 @@ impl Registry { } pub fn preload(&mut self, name: impl Display) -> &mut Self { + assert!(T::ty() != TypeType::Void, "cannot declare void type"); self.include::(); 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(&mut self, name: impl Display) -> &mut Self { + assert!(T::ty() != TypeType::Void, "cannot declare void field"); self.registry.include::(); 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(&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(&mut self, func: impl Display, ret: FfiReturnConvention) { + pub fn call(&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 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 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 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 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; diff --git a/crates/luaffi/src/string.rs b/crates/luaffi/src/string.rs index f1ac7dc..1f6c50e 100644 --- a/crates/luaffi/src/string.rs +++ b/crates/luaffi/src/string.rs @@ -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>) -> 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 { + 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); +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 { - 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 = as IntoFfi>::To; - - fn convert(self) -> Self::To { - as IntoFfi>::convert(self.into()) - } - - fn postlude(ret: &str, conv: FfiReturnConvention) -> impl Display { - as IntoFfi>::postlude(ret, conv) - } -} - -unsafe impl IntoFfi for String { - type To = as IntoFfi>::To; - - fn convert(self) -> Self::To { - as IntoFfi>::convert(self.into_bytes()) - } - - fn postlude(ret: &str, conv: FfiReturnConvention) -> impl Display { - 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); -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, lua_buffer::null()); +impl_optional_into!(BString, lua_buffer::null()); +impl_optional_into!(String, lua_buffer::null()); diff --git a/crates/luaffi_impl/src/cdef.rs b/crates/luaffi_impl/src/cdef.rs index e0fb428..a7be0ec 100644 --- a/crates/luaffi_impl/src/cdef.rs +++ b/crates/luaffi_impl/src/cdef.rs @@ -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 { 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 { 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 { // 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 { @@ -77,13 +81,14 @@ fn generate_cdef_structure(str: &mut ItemStruct) -> Result { 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 { @@ -95,22 +100,24 @@ fn generate_cdef_enum(enu: &mut ItemEnum) -> Result { 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::>>()?; - 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> { +fn get_cfields(fields: &mut Fields) -> Result> { 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> { .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) -> Result { +fn parse_cfield_attrs(attrs: &mut Vec) -> Result { 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) -> Result { Ok(parsed) } -fn generate_build_cdef(fields: &[CField]) -> Result { - let mut body = vec![quote! { +fn generate_cdef_build(fields: &[CField]) -> Result { + 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 { 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)*)) } diff --git a/crates/luaffi_impl/src/metatype.rs b/crates/luaffi_impl/src/metatype.rs index 32e7c09..fb27951 100644 --- a/crates/luaffi_impl/src/metatype.rs +++ b/crates/luaffi_impl/src/metatype.rs @@ -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 { syn_assert!( @@ -13,94 +16,76 @@ pub fn transform(mut imp: ItemImpl) -> Result { 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 { + 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::>()?; - - // ffi function registration code - let ffi_register: Vec<_> = ffi_funcs - .iter() - .map(generate_ffi_register) - .collect::>()?; - - 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::() { - 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::>()?; + 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 { }) } +#[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 { + 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, ret: Type, - ret_by_out: bool, attrs: FfiFunctionAttrs, } #[derive(Default)] struct FfiFunctionAttrs { - metatable: Option, + metamethod: Option, } fn get_ffi_functions(imp: &mut ItemImpl) -> Result> { @@ -136,49 +203,24 @@ fn get_ffi_functions(imp: &mut ItemImpl) -> Result> { 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::>()?; - - // 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) -> Result()?.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 { +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, + build: Vec, +} + +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 = parse_quote!(#(#shim_params),*); + tys.iter().map(|pat| (*pat.ty).clone()).collect::>() + }; + + 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 { - let ffi = ffi_crate(); - let lua_name = &func.lua_name; - let c_name = &func.c_name; +fn generate_ffi_exports(registry: &FfiRegistry) -> Result { + 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, -) -> Result { - 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, body: Block, + attrs: LuaFunctionAttrs, +} + +#[derive(Default)] +struct LuaFunctionAttrs { + metamethod: Option, } fn get_lua_functions(imp: &mut ItemImpl) -> Result> { @@ -357,15 +450,15 @@ fn get_lua_functions(imp: &mut ItemImpl) -> Result> { 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::>()?; - 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> { ); 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> { Ok(funcs) } -fn generate_lua_register(func: &LuaFunction) -> Result { +fn parse_lua_function_attrs(attrs: &mut Vec) -> Result { + 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, +} + +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::() { + // 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::() { + // 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(()) } diff --git a/crates/luaffi_impl/src/utils.rs b/crates/luaffi_impl/src/utils.rs index 377dacd..07c7f6d 100644 --- a/crates/luaffi_impl/src/utils.rs +++ b/crates/luaffi_impl/src/utils.rs @@ -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!(