Make an UnsafeExternCFn struct to overcome fn pointer hrtb

This commit is contained in:
lumi 2025-06-23 17:40:18 +10:00
parent c39106b790
commit 2352ba66d4
Signed by: luaneko
GPG Key ID: 406809B8763FF07A
3 changed files with 138 additions and 154 deletions

View File

@ -1,6 +1,7 @@
use crate::{ use crate::{
__internal::{display, type_id}, __internal::{display, type_id},
Cdef, CdefBuilder, FfiReturnConvention, Metatype, MetatypeBuilder, ToFfi, Type, TypeBuilder, Cdef, CdefBuilder, FfiReturnConvention, Metatype, MetatypeBuilder, ToFfi, Type, TypeBuilder,
UnsafeExternCFn,
}; };
use luaify::luaify; use luaify::luaify;
use std::{ use std::{
@ -147,8 +148,8 @@ unsafe impl<F: Future<Output: ToFfi> + 'static> Type for lua_future<F> {
unsafe impl<F: Future<Output: ToFfi> + 'static> Cdef for lua_future<F> { unsafe impl<F: Future<Output: ToFfi> + 'static> Cdef for lua_future<F> {
fn build(s: &mut CdefBuilder) { fn build(s: &mut CdefBuilder) {
s.field_opaque(mem::offset_of!(Self, take)) // opaque .sig, .poll and .state s.field_opaque(mem::offset_of!(Self, take)) // opaque .sig, .poll and .state
.field::<unsafe extern "C" fn(*mut Self) -> <F::Output as ToFfi>::To>("__take") .field::<UnsafeExternCFn<(&mut Self,), <F::Output as ToFfi>::To>>("__take")
.field::<unsafe extern "C" fn(*mut Self)>("__drop"); .field::<UnsafeExternCFn<(&mut Self,), ()>>("__drop");
} }
} }

View File

@ -4,11 +4,12 @@ use std::{
collections::HashSet, collections::HashSet,
ffi::{c_double, c_float, c_void}, ffi::{c_double, c_float, c_void},
fmt::{self, Display, Formatter, Write}, fmt::{self, Display, Formatter, Write},
marker::PhantomData,
mem, slice, mem, slice,
}; };
pub mod future; pub mod future;
pub mod option; // pub mod option;
pub mod string; pub mod string;
#[doc(hidden)] #[doc(hidden)]
@ -140,8 +141,8 @@ pub struct Registry {
impl Registry { impl Registry {
pub fn new() -> Self { pub fn new() -> Self {
let mut s = Self::default(); let mut s = Self::default();
s.declare::<extern "C" fn(ptr: *const c_void)>(KEEP_FN); s.declare::<UnsafeExternCFn<(*const c_void,), ()>>(KEEP_FN);
s.declare::<unsafe extern "C" fn(ptr: *const u8, len: usize) -> bool>(IS_UTF8_FN); s.declare::<UnsafeExternCFn<(*const u8, usize), bool>>(IS_UTF8_FN);
s s
} }
@ -542,12 +543,11 @@ unsafe impl ToFfi for () {
conv == FfiReturnConvention::Void, conv == FfiReturnConvention::Void,
"void type cannot be instantiated" "void type cannot be instantiated"
); );
"" ""
} }
} }
macro_rules! impl_copy_primitive { macro_rules! impl_primitive_abi {
($rtype:ty, $ctype:expr, $ltype:expr $(, $unwrap:expr)?) => { ($rtype:ty, $ctype:expr, $ltype:expr $(, $unwrap:expr)?) => {
impl_primitive!($rtype, $ctype); impl_primitive!($rtype, $ctype);
@ -555,8 +555,8 @@ macro_rules! impl_copy_primitive {
// SAFETY: Primitive types are always copyable so we can pass and return them by value. // SAFETY: Primitive types are always copyable so we can pass and return them by value.
// //
unsafe impl FromFfi for $rtype { unsafe impl FromFfi for $rtype {
type From = $rtype; type From = Self;
type FromArg = $rtype; type FromArg = Self;
fn prelude(arg: &str) -> impl Display { fn prelude(arg: &str) -> impl Display {
display!(r#"assert(type({arg}) == "{0}", "{0} expected in argument '{arg}', got " .. type({arg})); "#, $ltype) display!(r#"assert(type({arg}) == "{0}", "{0} expected in argument '{arg}', got " .. type({arg})); "#, $ltype)
@ -572,7 +572,7 @@ macro_rules! impl_copy_primitive {
} }
unsafe impl ToFfi for $rtype { unsafe impl ToFfi for $rtype {
type To = $rtype; type To = Self;
fn convert(self) -> Self::To { fn convert(self) -> Self::To {
self self
@ -585,7 +585,7 @@ macro_rules! impl_copy_primitive {
FfiReturnConvention::Void => unreachable!(), FfiReturnConvention::Void => unreachable!(),
FfiReturnConvention::ByValue => {}, FfiReturnConvention::ByValue => {},
// if a primitive type for some reason gets returned by out-param, unwrap // if a primitive type for some reason gets returned by out-param, unwrap
// the cdata containing the value and convert it to the equivalent lua value // the cdata containing the value to convert it to the equivalent lua value
FfiReturnConvention::ByOutParam => { $(write!(f, "{ret} = {}; ", $unwrap(ret))?;)? }, FfiReturnConvention::ByOutParam => { $(write!(f, "{ret} = {}; ", $unwrap(ret))?;)? },
} }
})) }))
@ -594,147 +594,98 @@ macro_rules! impl_copy_primitive {
}; };
} }
impl_copy_primitive!(bool, "bool", "boolean", |n| display!("{n} ~= 0")); impl_primitive_abi!(bool, "bool", "boolean", |n| display!("{n} ~= 0"));
impl_copy_primitive!(u8, "uint8_t", "number", |n| display!("tonumber({n})")); impl_primitive_abi!(u8, "uint8_t", "number", |n| display!("tonumber({n})"));
impl_copy_primitive!(u16, "uint16_t", "number", |n| display!("tonumber({n})")); impl_primitive_abi!(u16, "uint16_t", "number", |n| display!("tonumber({n})"));
impl_copy_primitive!(u32, "uint32_t", "number", |n| display!("tonumber({n})")); impl_primitive_abi!(u32, "uint32_t", "number", |n| display!("tonumber({n})"));
impl_copy_primitive!(u64, "uint64_t", "number"); impl_primitive_abi!(u64, "uint64_t", "number");
impl_copy_primitive!(usize, "uintptr_t", "number"); impl_primitive_abi!(usize, "uintptr_t", "number");
impl_copy_primitive!(i8, "int8_t", "number", |n| display!("tonumber({n})")); impl_primitive_abi!(i8, "int8_t", "number", |n| display!("tonumber({n})"));
impl_copy_primitive!(i16, "int16_t", "number", |n| display!("tonumber({n})")); impl_primitive_abi!(i16, "int16_t", "number", |n| display!("tonumber({n})"));
impl_copy_primitive!(i32, "int32_t", "number", |n| display!("tonumber({n})")); impl_primitive_abi!(i32, "int32_t", "number", |n| display!("tonumber({n})"));
impl_copy_primitive!(i64, "int64_t", "number"); impl_primitive_abi!(i64, "int64_t", "number");
impl_copy_primitive!(isize, "intptr_t", "number"); impl_primitive_abi!(isize, "intptr_t", "number");
impl_copy_primitive!(c_float, "float", "number", |n| display!("tonumber({n})")); impl_primitive_abi!(c_float, "float", "number", |n| display!("tonumber({n})"));
impl_copy_primitive!(c_double, "double", "number", |n| display!("tonumber({n})")); impl_primitive_abi!(c_double, "double", "number", |n| display!("tonumber({n})"));
unsafe impl<T: Type> Type for *const T { macro_rules! impl_const_ptr {
fn name() -> impl Display { ($ty:ty) => {
display!("const_{}_ptr", T::name()) unsafe impl<T: Type> Type for $ty {
} fn name() -> impl Display {
display!("const_{}_ptr", T::name())
}
fn cdecl(name: impl Display) -> impl Display { fn cdecl(name: impl Display) -> impl Display {
T::cdecl(display!("const *{name}")) T::cdecl(display!("const *{name}"))
} }
fn build(b: &mut TypeBuilder) { fn build(b: &mut TypeBuilder) {
b.include::<T>(); b.include::<T>();
} }
}
};
} }
unsafe impl<T: Type> Type for *mut T { impl_const_ptr!(*const T);
fn name() -> impl Display { impl_const_ptr!(&T);
display!("{}_ptr", T::name()) impl_const_ptr!(Option<&T>);
}
fn cdecl(name: impl Display) -> impl Display { macro_rules! impl_mut_ptr {
T::cdecl(display!("*{name}")) ($ty:ty) => {
} unsafe impl<T: Type> Type for $ty {
fn name() -> impl Display {
display!("{}_ptr", T::name())
}
fn build(b: &mut TypeBuilder) { fn cdecl(name: impl Display) -> impl Display {
b.include::<T>(); T::cdecl(display!("*{name}"))
} }
fn build(b: &mut TypeBuilder) {
b.include::<T>();
}
}
};
} }
impl_mut_ptr!(*mut T);
impl_mut_ptr!(&mut T);
impl_mut_ptr!(Option<&mut T>);
// //
// SAFETY: Pass by value for pointers, which maps to a `cdata` argument in lua containing either: // SAFETY: Pass by value for pointers, which maps to a `cdata` argument in lua containing either:
// * the pointer value itself (`T *`), or
// * the pointer to the base of the cdata payload (`T`).
// //
unsafe impl<T: Type> FromFfi for *const T { // * a reference which gets converted to a pointer (`T &` cdata), or
type From = *const T; // * the pointer value itself (`T *` cdata), or
type FromArg = *const T; // * the pointer to the base of the cdata payload (`T` cdata).
//
// LuaJIT will check for pointer compatibility automatically.
//
macro_rules! impl_ptr_fromabi {
($ty:ty) => {
unsafe impl<T: Type> FromFfi for $ty {
type From = Self;
type FromArg = Self;
fn convert(from: Self::From) -> Self { fn convert(from: Self::From) -> Self {
from from
} }
fn convert_arg(from: Self::FromArg) -> Self { fn convert_arg(from: Self::FromArg) -> Self {
from from
} }
}
};
} }
unsafe impl<T: Type> FromFfi for *mut T { impl_ptr_fromabi!(*const T);
type From = *mut T; impl_ptr_fromabi!(*mut T);
type FromArg = *mut T; impl_ptr_fromabi!(Option<&T>);
impl_ptr_fromabi!(Option<&mut T>);
fn convert(from: Self::From) -> Self { unsafe impl<'s, T: Type> FromFfi for &'s T {
from type From = Option<&'s T>;
} type FromArg = Option<&'s T>;
fn convert_arg(from: Self::FromArg) -> Self {
from
}
}
//
// SAFETY: Return by value for pointers, which maps to a `cdata` return in lua containing the
// pointer (`T *`). We also map null pointers to `nil` for convenience (otherwise it's still a cdata
// value containing a null pointer)
//
unsafe impl<T: Type> ToFfi for *const T {
type To = *const T;
fn convert(self) -> Self::To {
self
}
fn postlude(ret: &str, _conv: FfiReturnConvention) -> impl Display {
display!("if {ret} == nil then {ret} = nil; end; ")
}
}
unsafe impl<T: Type> ToFfi for *mut T {
type To = *mut T;
fn convert(self) -> Self::To {
self
}
fn postlude(ret: &str, _conv: FfiReturnConvention) -> impl Display {
display!("if {ret} == nil then {ret} = nil; end; ")
}
}
//
// SAFETY: No `ToFfi` for references because we can't guarantee that the returned reference
// converted to a pointer will not outlive the pointee.
//
unsafe impl<T: Type> Type for &T {
fn name() -> impl Display {
display!("const_{}_ptr", T::name())
}
fn cdecl(name: impl Display) -> impl Display {
T::cdecl(display!("const *{name}"))
}
fn build(b: &mut TypeBuilder) {
b.include::<T>();
}
}
unsafe impl<T: Type> Type for &mut T {
fn name() -> impl Display {
display!("{}_ptr", T::name())
}
fn cdecl(name: impl Display) -> impl Display {
T::cdecl(display!("*{name}"))
}
fn build(b: &mut TypeBuilder) {
b.include::<T>();
}
}
//
// SAFETY: Pass by value for references, which have the same semantics as pointers (see above). Must
// ensure that the pointer is not nil before being converted to a reference.
//
unsafe impl<T: Type> FromFfi for &T {
type From = *const T;
type FromArg = *const T;
fn prelude(arg: &str) -> impl Display { fn prelude(arg: &str) -> impl Display {
display!(r#"assert({arg} ~= nil, "argument '{arg}' cannot be nil"); "#) display!(r#"assert({arg} ~= nil, "argument '{arg}' cannot be nil"); "#)
@ -742,19 +693,19 @@ unsafe impl<T: Type> FromFfi for &T {
fn convert(from: Self::From) -> Self { fn convert(from: Self::From) -> Self {
debug_assert!( debug_assert!(
!from.is_null(), from.is_some(),
"<&T>::convert() called on a null pointer when it was checked to be non-null" "<&T>::convert() called on a null reference when it was checked to be non-null"
); );
unsafe { &*from } unsafe { from.unwrap_unchecked() }
} }
fn convert_arg(from: Self::FromArg) -> Self { fn convert_arg(from: Self::FromArg) -> Self {
Self::convert(from) FromFfi::convert(from)
} }
} }
unsafe impl<T: Type> FromFfi for &mut T { unsafe impl<'s, T: Type> FromFfi for &'s mut T {
// //
// SAFETY: `FromFfi` for *mutable* references is safe because it is guaranteed that no two Rust // SAFETY: `FromFfi` for *mutable* references is safe because it is guaranteed that no two Rust
// code called via FFI can be running at the same time on the same OS thread (no Lua // code called via FFI can be running at the same time on the same OS thread (no Lua
@ -771,8 +722,11 @@ unsafe impl<T: Type> FromFfi for &mut T {
// FFI which could violate exclusive borrow semantics. This is prevented by not implementing // FFI which could violate exclusive borrow semantics. This is prevented by not implementing
// `FromFfi` for function pointers (see below). // `FromFfi` for function pointers (see below).
// //
type From = *mut T; // The runtime does not keep any references to Rust user objects boxed in cdata (futures are
type FromArg = *mut T; // the only exception; their ownership is transferred to the runtime via yield).
//
type From = Option<&'s mut T>;
type FromArg = Option<&'s mut T>;
fn prelude(arg: &str) -> impl Display { fn prelude(arg: &str) -> impl Display {
display!(r#"assert({arg} ~= nil, "argument '{arg}' cannot be nil"); "#) display!(r#"assert({arg} ~= nil, "argument '{arg}' cannot be nil"); "#)
@ -780,18 +734,46 @@ unsafe impl<T: Type> FromFfi for &mut T {
fn convert(from: Self::From) -> Self { fn convert(from: Self::From) -> Self {
debug_assert!( debug_assert!(
!from.is_null(), from.is_some(),
"<&mut T>::convert() called on a null pointer when it was checked to be non-null" "<&mut T>::convert() called on a null reference when it was checked to be non-null"
); );
unsafe { &mut *from } unsafe { from.unwrap_unchecked() }
} }
fn convert_arg(from: Self::FromArg) -> Self { fn convert_arg(from: Self::FromArg) -> Self {
Self::convert(from) FromFfi::convert(from)
} }
} }
//
// SAFETY: Return by value for pointers, which maps to a `cdata` return in lua containing the
// pointer (`T *`). We also map null pointers to `nil` for convenience (otherwise it's still a cdata
// value containing a null pointer)
//
macro_rules! impl_ptr_toabi {
($ty:ty) => {
unsafe impl<T: Type> ToFfi for $ty {
type To = Self;
fn convert(self) -> Self::To {
self
}
fn postlude(ret: &str, _conv: FfiReturnConvention) -> impl Display {
display!("if {ret} == nil then {ret} = nil; end; ")
}
}
};
}
impl_ptr_toabi!(*const T);
impl_ptr_toabi!(*mut T);
impl_ptr_toabi!(&'static T);
impl_ptr_toabi!(&'static mut T);
impl_ptr_toabi!(Option<&'static T>);
impl_ptr_toabi!(Option<&'static mut T>);
// //
// SAFETY: No `FromFfi` and `ToFfi` for arrays because passing or returning them by value is not a // SAFETY: No `FromFfi` and `ToFfi` for arrays because passing or returning them by value is not a
// thing in C (they are just pointers). // thing in C (they are just pointers).
@ -826,13 +808,14 @@ unsafe impl<T: Type, const N: usize> Type for [T; N] {
} }
} }
pub struct UnsafeExternCFn<In, Out>(PhantomData<unsafe extern "C" fn(In) -> Out>);
macro_rules! impl_function { macro_rules! impl_function {
(fn($($arg:tt),*) -> $ret:tt) => { (fn($($arg:tt),*) -> $ret:tt) => {
impl_function!((extern "C" fn($($arg),*) -> $ret), fn($($arg),*) -> $ret); impl_function!(UnsafeExternCFn, fn($($arg),*) -> $ret);
impl_function!((unsafe extern "C" fn($($arg),*) -> $ret), fn($($arg),*) -> $ret);
}; };
(($($type:tt)+), fn($($arg:tt),*) -> $ret:tt) => { ($ty:tt, fn($($arg:tt),*) -> $ret:tt) => {
// //
// SAFETY: No `FromFfi` for function pointers because of borrow safety invariants (see above // SAFETY: No `FromFfi` for function pointers because of borrow safety invariants (see above
// in `&mut T`). // in `&mut T`).
@ -840,7 +823,7 @@ macro_rules! impl_function {
// We also can't implement `ToFfi` because we can't call `FromFfi` and `ToFfi` for the // We also can't implement `ToFfi` because we can't call `FromFfi` and `ToFfi` for the
// function's respective argument and return values. // function's respective argument and return values.
// //
unsafe impl<$($arg: Type,)* $ret: Type> Type for $($type)+ { unsafe impl<$($arg: Type,)* $ret: Type> Type for $ty<($($arg,)*), $ret> {
fn name() -> impl Display { fn name() -> impl Display {
disp(|f| Ok({ disp(|f| Ok({
write!(f, "fn_{}", $ret::name())?; write!(f, "fn_{}", $ret::name())?;
@ -872,7 +855,7 @@ macro_rules! impl_function {
b.include::<$ret>(); b.include::<$ret>();
} }
} }
} };
} }
impl_function!(fn() -> Z); impl_function!(fn() -> Z);

View File

@ -67,7 +67,7 @@ fn generate_impls(imp: &mut ItemImpl) -> Result<TokenStream> {
let ffi_register_drop = quote! { let ffi_register_drop = quote! {
if ::std::mem::needs_drop::<Self>() { if ::std::mem::needs_drop::<Self>() {
b.declare::<unsafe extern "C" fn(*mut Self)>(#ffi_drop_cname); b.declare::<#ffi::UnsafeExternCFn<(*mut Self,), ()>>(#ffi_drop_cname);
b.metatable_raw("gc", ::std::format_args!("__C.{}", #ffi_drop_cname)); b.metatable_raw("gc", ::std::format_args!("__C.{}", #ffi_drop_cname));
} }
}; };
@ -280,9 +280,9 @@ fn generate_ffi_register(func: &FfiFunction) -> Result<TokenStream> {
}; };
let declare = if func.ret_by_out { let declare = if func.ret_by_out {
quote! { b.declare::<unsafe extern "C" fn(*mut #ret, #(#params),*)>(#c_name); } quote! { b.declare::<#ffi::UnsafeExternCFn<(*mut #ret, #(#params,)*), ()>>(#c_name); }
} else { } else {
quote! { b.declare::<unsafe extern "C" fn(#(#params),*) -> #ret>(#c_name); } quote! { b.declare::<#ffi::UnsafeExternCFn<(#(#params,)*), #ret>>(#c_name); }
}; };
let register = match func.attrs.metatable { let register = match func.attrs.metatable {