extern crate alloc;
#[cfg(not(test))]
use alloc::vec::Vec;
use core::ffi::c_void;
use core::fmt::Debug;
use libffi_sys::ffi_cif;
use super::{DynamicClosureArgs, DynamicClosureCall, DynamicClosureRet};
use crate::FnPtr;
use crate::abi::Abi;
use crate::closure::BaseClosure;
use crate::closure::raw::Context;
use crate::errors::ClosureAllocationError;
use crate::function::raw::{Cif, MAX_ARGS};
use crate::types::{FfiTypeLayout, Type};
struct DynamicClosureContext<FN, CONTEXT>
where
FN: Fn(DynamicClosureCall<CONTEXT>),
{
callback: FN,
argument_types: Vec<Type>,
argument_layouts: Vec<FfiTypeLayout>,
return_type_and_layout: Option<(Type, FfiTypeLayout)>,
context: CONTEXT,
}
pub struct DynamicClosure<FN, CONTEXT>
where
FN: Fn(DynamicClosureCall<CONTEXT>) + Send + Sync,
CONTEXT: Send + Sync,
{
closure: BaseClosure<DynamicClosureContext<FN, CONTEXT>>,
}
impl<FN, CONTEXT> DynamicClosure<FN, CONTEXT>
where
FN: Fn(DynamicClosureCall<CONTEXT>) + Send + Sync,
CONTEXT: Send + Sync,
{
pub fn new<'args, I>(
callback: FN,
argument_types: I,
return_type: Option<&Type>,
context: CONTEXT,
) -> Self
where
I: IntoIterator<Item = &'args Type>,
{
Self::try_new(callback, argument_types, return_type, context)
.expect("Libffi was unable to allocate the closure.")
}
pub fn with_abi<'args, I>(
callback: FN,
argument_types: I,
return_type: Option<&Type>,
abi: Abi,
context: CONTEXT,
) -> Self
where
I: IntoIterator<Item = &'args Type>,
{
Self::try_with_abi(callback, argument_types, return_type, abi, context)
.expect("Libffi was unable to allocate the closure.")
}
pub fn try_new<'args, I>(
callback: FN,
argument_types: I,
return_type: Option<&Type>,
context: CONTEXT,
) -> Result<Self, ClosureAllocationError>
where
I: IntoIterator<Item = &'args Type>,
{
Self::try_with_abi(
callback,
argument_types,
return_type,
Abi::default(),
context,
)
}
pub fn try_with_abi<'args, I>(
callback: FN,
argument_types: I,
return_type: Option<&Type>,
abi: Abi,
context: CONTEXT,
) -> Result<Self, ClosureAllocationError>
where
I: IntoIterator<Item = &'args Type>,
{
let argument_types: Vec<Type> =
argument_types.into_iter().take(MAX_ARGS).cloned().collect();
let cif = Cif::new(abi, &argument_types, return_type);
let context = Context::new(DynamicClosureContext {
callback,
argument_layouts: cif.argument_layouts(),
return_type_and_layout: return_type
.map(|return_type| (return_type.clone(), cif.return_layout())),
argument_types,
context,
});
let closure =
unsafe { BaseClosure::try_new(cif, dynamic_closure_callback::<FN, CONTEXT>, context)? };
Ok(Self { closure })
}
pub fn as_fn_ptr(&self) -> FnPtr {
self.closure.as_fn_ptr()
}
}
impl<FN, CONTEXT> Debug for DynamicClosure<FN, CONTEXT>
where
FN: Fn(DynamicClosureCall<CONTEXT>) + Send + Sync,
CONTEXT: Send + Sync,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("DynamicClosure")
.field("closure", &self.closure)
.finish()
}
}
unsafe extern "C" fn dynamic_closure_callback<FN, CONTEXT>(
_cif: *mut ffi_cif,
result_ptr: *mut c_void,
arguments_ptr: *mut *mut c_void,
context_ptr: *mut c_void,
) where
FN: Fn(DynamicClosureCall<CONTEXT>) + Send + Sync,
CONTEXT: Send + Sync,
{
let context = unsafe { &*context_ptr.cast::<DynamicClosureContext<FN, CONTEXT>>() };
let ret = context
.return_type_and_layout
.as_ref()
.map(|(return_type, return_layout)| DynamicClosureRet {
ty: return_type,
layout: return_layout,
ptr: result_ptr,
});
let args = DynamicClosureArgs {
argument_types: &context.argument_types,
argument_layouts: &context.argument_layouts,
argument_array: arguments_ptr.cast_const().cast(),
};
let call = DynamicClosureCall {
args,
ret,
context: &context.context,
};
(context.callback)(call);
}
#[cfg(test)]
mod tests {
use alloc::sync::Arc;
use core::mem::MaybeUninit;
use core::sync::atomic::{AtomicUsize, Ordering};
use super::*;
use crate::function::{Function, Ret, arg, ret};
use crate::test_utils::{
F32_ARG, F64_ARG, I8_ARG, I16_ARG, I32_ARG, I64_ARG, ISIZE_ARG, PTR_ARG, STRUCT_ARG,
TestStruct, U8_ARG, U16_ARG, U32_ARG, U64_ARG, USIZE_ARG,
};
use crate::types::FfiType;
fn noop_callback(_: DynamicClosureCall<Arc<()>>) {}
#[test]
#[cfg_attr(miri, ignore)]
fn test_no_args_no_return_context_lifetime_all_abis() {
for abi in Abi::ABIS {
let context = Arc::new(());
let context_probe = Arc::downgrade(&context);
let closure = DynamicClosure::with_abi(noop_callback, &[], None, abi, context);
assert!(context_probe.upgrade().is_some());
let function = Function::with_abi(closure.as_fn_ptr(), &[], None, abi);
unsafe {
function.call([], Ret::void());
}
assert!(context_probe.upgrade().is_some());
drop(closure);
assert!(context_probe.upgrade().is_none());
}
}
fn test_callback(call: DynamicClosureCall<&AtomicUsize>) {
macro_rules! test_argument {
($arg:ident, $ret:ident, $arg_type:ty, $expected:expr) => {{
let actual_arg = unsafe {
let mut actual_arg = MaybeUninit::<$arg_type>::uninit();
$arg.copy_to(&mut actual_arg);
actual_arg.assume_init()
};
assert_eq!(actual_arg, $expected);
if let Some(ret) = $ret.as_mut()
&& ret.ty() == $arg.ty()
{
unsafe {
ret.write(actual_arg);
}
}
}};
}
let DynamicClosureCall {
args,
mut ret,
context,
} = call;
for arg in &args {
match arg.ty() {
Type::I8 => test_argument!(arg, ret, i8, I8_ARG),
Type::I16 => test_argument!(arg, ret, i16, I16_ARG),
Type::I32 => test_argument!(arg, ret, i32, I32_ARG),
Type::I64 => test_argument!(arg, ret, i64, I64_ARG),
Type::Isize => test_argument!(arg, ret, isize, ISIZE_ARG),
Type::U8 => test_argument!(arg, ret, u8, U8_ARG),
Type::U16 => test_argument!(arg, ret, u16, U16_ARG),
Type::U32 => test_argument!(arg, ret, u32, U32_ARG),
Type::U64 => test_argument!(arg, ret, u64, U64_ARG),
Type::Usize => test_argument!(arg, ret, usize, USIZE_ARG),
Type::F32 => test_argument!(arg, ret, f32, F32_ARG),
Type::F64 => test_argument!(arg, ret, f64, F64_ARG),
Type::Pointer => test_argument!(arg, ret, *const c_void, PTR_ARG.0),
Type::Struct(_) => {
assert_eq!(arg.ty(), &TestStruct::ffi_type());
test_argument!(arg, ret, TestStruct, STRUCT_ARG);
}
}
}
context.fetch_add(1, Ordering::Relaxed);
}
macro_rules! test_identity_closure_for_type_all_abis {
(fn $testname:ident($type:ty, $value:expr)) => {
#[test]
#[cfg_attr(miri, ignore)]
fn $testname() {
let ffi_type = <$type as FfiType>::ffi_type();
for abi in Abi::ABIS {
let context = AtomicUsize::new(0);
let closure = DynamicClosure::with_abi(
test_callback,
core::slice::from_ref(&ffi_type),
Some(&ffi_type),
abi,
&context,
);
let function = Function::with_abi(
closure.as_fn_ptr(),
&[<$type as FfiType>::ffi_type()],
Some(&<$type as FfiType>::ffi_type()),
abi,
);
let mut return_value = MaybeUninit::<$type>::uninit();
unsafe {
function.call([arg(&$value)], ret(&mut return_value));
}
let return_value = unsafe { return_value.assume_init() };
assert_eq!(
return_value, $value,
"Invalid return value from the identity function using the ABI {abi:?}."
);
assert_eq!(context.load(Ordering::Relaxed), 1);
}
}
};
}
test_identity_closure_for_type_all_abis!(fn test_i8_identity_closure(i8, I8_ARG));
test_identity_closure_for_type_all_abis!(fn test_i16_identity_closure(i16, I16_ARG));
test_identity_closure_for_type_all_abis!(fn test_i32_identity_closure(i32, I32_ARG));
test_identity_closure_for_type_all_abis!(fn test_i64_identity_closure(i64, I64_ARG));
test_identity_closure_for_type_all_abis!(fn test_isize_identity_closure(isize, ISIZE_ARG));
test_identity_closure_for_type_all_abis!(fn test_u8_identity_closure(u8, U8_ARG));
test_identity_closure_for_type_all_abis!(fn test_u16_identity_closure(u16, U16_ARG));
test_identity_closure_for_type_all_abis!(fn test_u32_identity_closure(u32, U32_ARG));
test_identity_closure_for_type_all_abis!(fn test_u64_identity_closure(u64, U64_ARG));
test_identity_closure_for_type_all_abis!(fn test_usize_identity_closure(usize, USIZE_ARG));
test_identity_closure_for_type_all_abis!(fn test_f32_identity_closure(f32, F32_ARG));
test_identity_closure_for_type_all_abis!(fn test_f64_identity_closure(f64, F64_ARG));
test_identity_closure_for_type_all_abis!(fn test_ptr_identity_closure(*const c_void, PTR_ARG.0));
test_identity_closure_for_type_all_abis!(fn test_test_struct_identity_closure(TestStruct, STRUCT_ARG));
#[test]
#[cfg_attr(miri, ignore)]
fn test_all_types_all_abis() {
#[rustfmt::skip]
let arg_types = [
Type::I8, Type::I16, Type::I32, Type::I64, Type::Isize, Type::U8, Type::U16, Type::U32,
Type::U64, Type::Usize, Type::F32, Type::F64, Type::Pointer,
<TestStruct as FfiType>::ffi_type(),
];
#[rustfmt::skip]
let args = [
arg(&I8_ARG), arg(&I16_ARG), arg(&I32_ARG), arg(&I64_ARG), arg(&ISIZE_ARG),
arg(&U8_ARG), arg(&U16_ARG), arg(&U32_ARG), arg(&U64_ARG), arg(&USIZE_ARG),
arg(&F32_ARG), arg(&F64_ARG), arg(&PTR_ARG.0), arg(&STRUCT_ARG),
];
for abi in Abi::ABIS {
let context = AtomicUsize::new(0);
let closure = DynamicClosure::with_abi(test_callback, &arg_types, None, abi, &context);
let function = Function::with_abi(closure.as_fn_ptr(), &arg_types, None, abi);
unsafe {
function.call(args.clone(), Ret::void());
}
}
}
}