use core::ffi::c_void;
use core::mem::MaybeUninit;
use libffi_sys::ffi_cif;
use crate::return_buffer::write_closure_result;
use crate::types::{FfiType, Type};
#[cfg(msan)]
use crate::{__msan_poison, __msan_test_shadow, __msan_unpoison};
pub(crate) mod internal {
pub struct Token;
pub trait FfiReturnTypeSuper {}
pub trait FfiArgsSuper {}
pub trait CallbackSuper<ARGS, RET> {}
}
pub unsafe trait FfiReturnType: internal::FfiReturnTypeSuper {
#[doc(hidden)]
fn return_type(_: internal::Token) -> Option<Type>;
}
impl<T> internal::FfiReturnTypeSuper for T where T: FfiType {}
unsafe impl<T> FfiReturnType for T
where
T: FfiType,
{
fn return_type(_: internal::Token) -> Option<Type> {
Some(<T as FfiType>::ffi_type())
}
}
impl internal::FfiReturnTypeSuper for () {}
unsafe impl FfiReturnType for () {
fn return_type(_: internal::Token) -> Option<Type> {
None
}
}
pub unsafe trait FfiArgs: internal::FfiArgsSuper {
#[doc(hidden)]
type TypeArray;
#[doc(hidden)]
fn as_type_array() -> Self::TypeArray;
}
impl internal::FfiArgsSuper for () {}
unsafe impl FfiArgs for () {
type TypeArray = [Type; 0];
fn as_type_array() -> Self::TypeArray {
[]
}
}
macro_rules! count_idents {
() => {0usize};
($head:ident $(,)*) => {1usize};
($head:ident , $($tail:ident),+ $(,)*) => {1usize + count_idents!($($tail),*)};
}
const _: () = {
assert!(count_idents!() == 0);
assert!(count_idents!(a) == 1);
assert!(count_idents!(a, b) == 2);
assert!(count_idents!(a, b, c, d, e) == 5);
};
macro_rules! impl_ffi_args_for_tuple {
($($placeholder:ident),* $(,)*) => {
impl<$($placeholder,)*> internal::FfiArgsSuper for ($($placeholder,)*)
where
$($placeholder: FfiType,)*
{}
unsafe impl<$($placeholder,)*> FfiArgs for ($($placeholder,)*)
where
$($placeholder: FfiType,)*
{
type TypeArray = [Type; count_idents!($($placeholder,)*)];
fn as_type_array() -> Self::TypeArray {
[
$(<$placeholder as FfiType>::ffi_type(),)*
]
}
}
};
}
impl_ffi_args_for_tuple!(T0);
impl_ffi_args_for_tuple!(T0, T1);
impl_ffi_args_for_tuple!(T0, T1, T2);
impl_ffi_args_for_tuple!(T0, T1, T2, T3);
impl_ffi_args_for_tuple!(T0, T1, T2, T3, T4);
impl_ffi_args_for_tuple!(T0, T1, T2, T3, T4, T5);
impl_ffi_args_for_tuple!(T0, T1, T2, T3, T4, T5, T6);
impl_ffi_args_for_tuple!(T0, T1, T2, T3, T4, T5, T6, T7);
impl_ffi_args_for_tuple!(T0, T1, T2, T3, T4, T5, T6, T7, T8);
impl_ffi_args_for_tuple!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9);
impl_ffi_args_for_tuple!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
impl_ffi_args_for_tuple!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
impl_ffi_args_for_tuple!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12);
impl_ffi_args_for_tuple!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13);
impl_ffi_args_for_tuple!(
T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14
);
impl_ffi_args_for_tuple!(
T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15
);
pub unsafe trait Callback<ARGS, RET>:
internal::CallbackSuper<ARGS, RET> + Sized + Send + Sync
where
ARGS: FfiArgs,
RET: FfiReturnType,
{
#[doc(hidden)]
unsafe extern "C" fn callback(
cif: *mut ffi_cif,
result_ptr: *mut MaybeUninit<RET>,
args: *mut *mut c_void,
closure: *const Self,
);
}
macro_rules! read_single_argument {
($args_ptr:ident, $name:ident: $type:ident) => {
let $name: $type = unsafe {
let arg_ptr = *$args_ptr;
#[cfg(msan)]
let poisoned = {
if __msan_test_shadow(arg_ptr, size_of::<$type>()) != -1 {
__msan_unpoison(arg_ptr.cast(), size_of::<$type>());
true
} else {
false
}
};
let val: $type = if cfg!(all(target_arch = "x86", target_os = "windows")) {
arg_ptr.cast::<$type>().read_unaligned()
} else {
arg_ptr.cast::<$type>().read()
};
#[cfg(msan)]
if poisoned {
__msan_poison(arg_ptr.cast(), size_of::<$type>());
}
val
};
};
}
macro_rules! read_next_arguments {
($args_ptr:ident) => {};
($args_ptr:ident, $name:ident: $type:ident $(, $rest_name:ident: $rest_type:ident)* $(,)*) => {
let $args_ptr = unsafe { $args_ptr.add(1) };
read_single_argument!($args_ptr, $name: $type);
read_next_arguments!($args_ptr $(, $rest_name: $rest_type)*);
};
}
macro_rules! read_arguments {
($args_ptr:ident,) => {};
($args_ptr:ident, $name:ident: $type:ident $(, $rest_name:ident: $rest_type:ident)* $(,)*) => {
read_single_argument!($args_ptr, $name: $type);
read_next_arguments!($args_ptr $(, $rest_name: $rest_type)*);
};
}
macro_rules! impl_callback_for_fn {
(fn($($name:ident: $type:ident),* $(,)*)) => {
impl<$($type,)* FN> internal::CallbackSuper<($($type,)*), ()> for FN
where
$($type: FfiType,)*
FN: Fn($($type),*) + Send + Sync,
{}
unsafe impl<$($type,)* FN> Callback<($($type,)*), ()> for FN
where
$($type: FfiType,)*
FN: Fn($($type),*) + Send + Sync,
{
unsafe extern "C" fn callback(
_cif: *mut ffi_cif,
_result_ptr: *mut MaybeUninit<()>,
_args: *mut *mut c_void,
closure: *const FN,
) {
read_arguments!(_args, $($name: $type),*);
unsafe { (*closure)($($name,)*) }
}
}
impl<$($type,)* RET, FN> internal::CallbackSuper<($($type,)*), RET> for FN
where
$($type: FfiType,)*
RET: FfiType,
FN: Fn($($type),*) -> RET + Send + Sync,
{}
unsafe impl<$($type,)* RET, FN> Callback<($($type,)*), RET> for FN
where
$($type: FfiType,)*
RET: FfiType,
FN: Fn($($type),*) -> RET + Send + Sync,
{
unsafe extern "C" fn callback(
_cif: *mut ffi_cif,
result_ptr: *mut MaybeUninit<RET>,
_args: *mut *mut c_void,
closure: *const FN,
) {
read_arguments!(_args, $($name: $type),*);
let return_value = unsafe { (*closure)($($name,)*) };
unsafe { write_closure_result(return_value, &<RET as FfiType>::ffi_type(), result_ptr) }
}
}
};
}
impl_callback_for_fn!(fn());
impl_callback_for_fn!(fn(v0: T0));
impl_callback_for_fn!(fn(v0: T0, v1: T1));
impl_callback_for_fn!(fn(v0: T0, v1: T1, v2: T2));
impl_callback_for_fn!(fn(v0: T0, v1: T1, v2: T2, v3: T3));
impl_callback_for_fn!(fn(v0: T0, v1: T1, v2: T2, v3: T3, v4: T4));
impl_callback_for_fn!(fn(v0: T0, v1: T1, v2: T2, v3: T3, v4: T4, v5: T5));
impl_callback_for_fn!(fn(v0: T0, v1: T1, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6));
impl_callback_for_fn!(fn(v0: T0, v1: T1, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, v7: T7));
impl_callback_for_fn!(fn(v0: T0, v1: T1, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, v7: T7, v8: T8));
impl_callback_for_fn!(
fn(v0: T0, v1: T1, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, v7: T7, v8: T8, v9: T9)
);
impl_callback_for_fn!(
fn(v0: T0, v1: T1, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, v7: T7, v8: T8, v9: T9, v10: T10)
);
impl_callback_for_fn!(
fn(v0: T0, v1: T1, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, v7: T7, v8: T8, v9: T9, v10: T10,
v11: T11)
);
impl_callback_for_fn!(
fn(v0: T0, v1: T1, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, v7: T7, v8: T8, v9: T9, v10: T10,
v11: T11, v12: T12)
);
impl_callback_for_fn!(
fn(v0: T0, v1: T1, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, v7: T7, v8: T8, v9: T9, v10: T10,
v11: T11, v12: T12, v13: T13)
);
impl_callback_for_fn!(
fn(v0: T0, v1: T1, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, v7: T7, v8: T8, v9: T9, v10: T10,
v11: T11, v12: T12, v13: T13, v14: T14)
);
impl_callback_for_fn!(
fn(v0: T0, v1: T1, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, v7: T7, v8: T8, v9: T9, v10: T10,
v11: T11, v12: T12, v13: T13, v14: T14, v15: T15)
);
#[cfg(test)]
mod tests {
use core::ptr::null_mut;
use std::ptr;
use std::sync::atomic::{AtomicUsize, Ordering};
use super::*;
use crate::test_utils::{I16_ARG, U8_ARG, U32_ARG, U64_ARG};
unsafe fn call_callback<ARGS, RET, FN>(
cif: *mut ffi_cif,
result_ptr: *mut MaybeUninit<RET>,
args: *mut *mut c_void,
closure: &FN,
) where
ARGS: FfiArgs,
RET: FfiReturnType,
FN: Callback<ARGS, RET>,
{
unsafe { FN::callback(cif, result_ptr, args, ptr::from_ref(closure)) }
}
#[test]
fn callback_no_args_no_ret_runs_closure() {
let flag = AtomicUsize::new(0);
let closure = || flag.store(0xABCD, Ordering::Relaxed);
unsafe {
call_callback(null_mut(), null_mut(), null_mut(), &closure);
}
assert_eq!(flag.load(Ordering::Relaxed), 0xABCD);
}
#[test]
fn callback_no_args_with_return_writes_result() {
let closure = || 0xDEAD_BEEFu32;
let mut ret = MaybeUninit::<usize>::uninit();
unsafe {
call_callback(null_mut(), ret.as_mut_ptr().cast(), null_mut(), &closure);
}
let value = unsafe { ret.assume_init() };
let u32_value = u32::try_from(value).unwrap();
assert_eq!(u32_value, 0xDEAD_BEEF);
}
#[test]
fn callback_two_args_with_return_reads_args_and_writes_result() {
let closure = |a: u32, b: u64| u64::from(a) + b;
let args: [*mut c_void; 2] = [
(&raw const U32_ARG).cast_mut().cast(),
(&raw const U64_ARG).cast_mut().cast(),
];
let mut ret = MaybeUninit::<u64>::uninit();
unsafe {
call_callback(
null_mut(),
ret.as_mut_ptr().cast(),
args.as_ptr().cast_mut(),
&closure,
);
}
let value = unsafe { ret.assume_init() };
let expected = u64::from(U32_ARG) + U64_ARG;
assert_eq!(value, expected);
}
#[test]
fn callback_three_args_no_ret_reads_args() {
let closure = |a: u8, b: i16, c: u32| {
assert_eq!(a, U8_ARG);
assert_eq!(b, I16_ARG);
assert_eq!(c, U32_ARG);
};
let args: [*mut c_void; 3] = [
(&raw const U8_ARG).cast_mut().cast(),
(&raw const I16_ARG).cast_mut().cast(),
(&raw const U32_ARG).cast_mut().cast(),
];
unsafe {
call_callback(null_mut(), null_mut(), args.as_ptr().cast_mut(), &closure);
}
}
#[test]
fn callback_sixteen_args_with_return_reads_args_and_writes_result() {
let values = [0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
let args: [*mut c_void; 16] =
core::array::from_fn(|index| ptr::from_ref(&values[index]).cast_mut().cast());
#[rustfmt::skip]
let closure =
|v0: u8, v1: u8, v2: u8, v3: u8, v4: u8, v5: u8, v6: u8, v7: u8, v8: u8, v9: u8, v10: u8,
v11: u8, v12: u8, v13: u8, v14: u8, v15: u8| -> u64 {
let received = [
v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15,
];
assert_eq!(received, values);
received.into_iter().map(u64::from).sum()
};
let mut ret = MaybeUninit::<u64>::uninit();
unsafe {
#[rustfmt::skip]
call_callback::<
(u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8),
u64,
_,
>(
null_mut(),
ret.as_mut_ptr().cast(),
args.as_ptr().cast_mut(),
&closure,
);
}
let value = unsafe { ret.assume_init() };
let expected = values.into_iter().map(u64::from).sum();
assert_eq!(value, expected);
}
}