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::{
__internal::{display, type_id},
Cdef, CdefBuilder, FfiReturnConvention, Metatype, MetatypeBuilder, ToFfi, Type, TypeBuilder,
UnsafeExternCFn,
};
use luaify::luaify;
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> {
fn build(s: &mut CdefBuilder) {
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::<unsafe extern "C" fn(*mut Self)>("__drop");
.field::<UnsafeExternCFn<(&mut Self,), <F::Output as ToFfi>::To>>("__take")
.field::<UnsafeExternCFn<(&mut Self,), ()>>("__drop");
}
}

View File

@ -4,11 +4,12 @@ use std::{
collections::HashSet,
ffi::{c_double, c_float, c_void},
fmt::{self, Display, Formatter, Write},
marker::PhantomData,
mem, slice,
};
pub mod future;
pub mod option;
// pub mod option;
pub mod string;
#[doc(hidden)]
@ -140,8 +141,8 @@ pub struct Registry {
impl Registry {
pub fn new() -> Self {
let mut s = Self::default();
s.declare::<extern "C" fn(ptr: *const c_void)>(KEEP_FN);
s.declare::<unsafe extern "C" fn(ptr: *const u8, len: usize) -> bool>(IS_UTF8_FN);
s.declare::<UnsafeExternCFn<(*const c_void,), ()>>(KEEP_FN);
s.declare::<UnsafeExternCFn<(*const u8, usize), bool>>(IS_UTF8_FN);
s
}
@ -542,12 +543,11 @@ unsafe impl ToFfi for () {
conv == FfiReturnConvention::Void,
"void type cannot be instantiated"
);
""
}
}
macro_rules! impl_copy_primitive {
macro_rules! impl_primitive_abi {
($rtype:ty, $ctype:expr, $ltype:expr $(, $unwrap:expr)?) => {
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.
//
unsafe impl FromFfi for $rtype {
type From = $rtype;
type FromArg = $rtype;
type From = Self;
type FromArg = Self;
fn prelude(arg: &str) -> impl Display {
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 {
type To = $rtype;
type To = Self;
fn convert(self) -> Self::To {
self
@ -585,7 +585,7 @@ macro_rules! impl_copy_primitive {
FfiReturnConvention::Void => unreachable!(),
FfiReturnConvention::ByValue => {},
// 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))?;)? },
}
}))
@ -594,147 +594,98 @@ macro_rules! impl_copy_primitive {
};
}
impl_copy_primitive!(bool, "bool", "boolean", |n| display!("{n} ~= 0"));
impl_copy_primitive!(u8, "uint8_t", "number", |n| display!("tonumber({n})"));
impl_copy_primitive!(u16, "uint16_t", "number", |n| display!("tonumber({n})"));
impl_copy_primitive!(u32, "uint32_t", "number", |n| display!("tonumber({n})"));
impl_copy_primitive!(u64, "uint64_t", "number");
impl_copy_primitive!(usize, "uintptr_t", "number");
impl_copy_primitive!(i8, "int8_t", "number", |n| display!("tonumber({n})"));
impl_copy_primitive!(i16, "int16_t", "number", |n| display!("tonumber({n})"));
impl_copy_primitive!(i32, "int32_t", "number", |n| display!("tonumber({n})"));
impl_copy_primitive!(i64, "int64_t", "number");
impl_copy_primitive!(isize, "intptr_t", "number");
impl_copy_primitive!(c_float, "float", "number", |n| display!("tonumber({n})"));
impl_copy_primitive!(c_double, "double", "number", |n| display!("tonumber({n})"));
impl_primitive_abi!(bool, "bool", "boolean", |n| display!("{n} ~= 0"));
impl_primitive_abi!(u8, "uint8_t", "number", |n| display!("tonumber({n})"));
impl_primitive_abi!(u16, "uint16_t", "number", |n| display!("tonumber({n})"));
impl_primitive_abi!(u32, "uint32_t", "number", |n| display!("tonumber({n})"));
impl_primitive_abi!(u64, "uint64_t", "number");
impl_primitive_abi!(usize, "uintptr_t", "number");
impl_primitive_abi!(i8, "int8_t", "number", |n| display!("tonumber({n})"));
impl_primitive_abi!(i16, "int16_t", "number", |n| display!("tonumber({n})"));
impl_primitive_abi!(i32, "int32_t", "number", |n| display!("tonumber({n})"));
impl_primitive_abi!(i64, "int64_t", "number");
impl_primitive_abi!(isize, "intptr_t", "number");
impl_primitive_abi!(c_float, "float", "number", |n| display!("tonumber({n})"));
impl_primitive_abi!(c_double, "double", "number", |n| display!("tonumber({n})"));
unsafe impl<T: Type> Type for *const T {
fn name() -> impl Display {
display!("const_{}_ptr", T::name())
}
macro_rules! impl_const_ptr {
($ty:ty) => {
unsafe impl<T: Type> Type for $ty {
fn name() -> impl Display {
display!("const_{}_ptr", T::name())
}
fn cdecl(name: impl Display) -> impl Display {
T::cdecl(display!("const *{name}"))
}
fn cdecl(name: impl Display) -> impl Display {
T::cdecl(display!("const *{name}"))
}
fn build(b: &mut TypeBuilder) {
b.include::<T>();
}
fn build(b: &mut TypeBuilder) {
b.include::<T>();
}
}
};
}
unsafe impl<T: Type> Type for *mut T {
fn name() -> impl Display {
display!("{}_ptr", T::name())
}
impl_const_ptr!(*const T);
impl_const_ptr!(&T);
impl_const_ptr!(Option<&T>);
fn cdecl(name: impl Display) -> impl Display {
T::cdecl(display!("*{name}"))
}
macro_rules! impl_mut_ptr {
($ty:ty) => {
unsafe impl<T: Type> Type for $ty {
fn name() -> impl Display {
display!("{}_ptr", T::name())
}
fn build(b: &mut TypeBuilder) {
b.include::<T>();
}
fn cdecl(name: impl Display) -> impl Display {
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:
// * the pointer value itself (`T *`), or
// * the pointer to the base of the cdata payload (`T`).
//
unsafe impl<T: Type> FromFfi for *const T {
type From = *const T;
type FromArg = *const T;
// * a reference which gets converted to a pointer (`T &` cdata), or
// * the pointer value itself (`T *` cdata), or
// * 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 {
from
}
fn convert(from: Self::From) -> Self {
from
}
fn convert_arg(from: Self::FromArg) -> Self {
from
}
fn convert_arg(from: Self::FromArg) -> Self {
from
}
}
};
}
unsafe impl<T: Type> FromFfi for *mut T {
type From = *mut T;
type FromArg = *mut T;
impl_ptr_fromabi!(*const T);
impl_ptr_fromabi!(*mut T);
impl_ptr_fromabi!(Option<&T>);
impl_ptr_fromabi!(Option<&mut T>);
fn convert(from: Self::From) -> Self {
from
}
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;
unsafe impl<'s, T: Type> FromFfi for &'s T {
type From = Option<&'s T>;
type FromArg = Option<&'s T>;
fn prelude(arg: &str) -> impl Display {
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 {
debug_assert!(
!from.is_null(),
"<&T>::convert() called on a null pointer when it was checked to be non-null"
from.is_some(),
"<&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 {
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
// 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
// `FromFfi` for function pointers (see below).
//
type From = *mut T;
type FromArg = *mut T;
// The runtime does not keep any references to Rust user objects boxed in cdata (futures are
// 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 {
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 {
debug_assert!(
!from.is_null(),
"<&mut T>::convert() called on a null pointer when it was checked to be non-null"
from.is_some(),
"<&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 {
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
// 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 {
(fn($($arg:tt),*) -> $ret:tt) => {
impl_function!((extern "C" fn($($arg),*) -> $ret), fn($($arg),*) -> $ret);
impl_function!((unsafe extern "C" fn($($arg),*) -> $ret), fn($($arg),*) -> $ret);
impl_function!(UnsafeExternCFn, 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
// 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
// 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 {
disp(|f| Ok({
write!(f, "fn_{}", $ret::name())?;
@ -872,7 +855,7 @@ macro_rules! impl_function {
b.include::<$ret>();
}
}
}
};
}
impl_function!(fn() -> Z);

View File

@ -67,7 +67,7 @@ fn generate_impls(imp: &mut ItemImpl) -> Result<TokenStream> {
let ffi_register_drop = quote! {
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));
}
};
@ -280,9 +280,9 @@ fn generate_ffi_register(func: &FfiFunction) -> Result<TokenStream> {
};
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 {
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 {