pub(super) mod closure;
use core::ffi::c_void;
use core::fmt::Debug;
use core::iter::FusedIterator;
use core::mem::MaybeUninit;
use core::slice;
use crate::return_buffer::{write_closure_result, write_closure_result_bytes};
use crate::types::{FfiTypeLayout, Type};
#[cfg(msan)]
use crate::{__msan_poison, __msan_test_shadow, __msan_unpoison};
pub struct DynamicClosureCall<'call, CONTEXT> {
args: DynamicClosureArgs<'call>,
ret: Option<DynamicClosureRet<'call>>,
context: &'call CONTEXT,
}
impl<'call, CONTEXT> DynamicClosureCall<'call, CONTEXT> {
pub fn args(&self) -> &DynamicClosureArgs<'call> {
&self.args
}
pub fn ret(&mut self) -> Option<&mut DynamicClosureRet<'call>> {
self.ret.as_mut()
}
pub fn context(&self) -> &CONTEXT {
self.context
}
}
impl<CONTEXT> Debug for DynamicClosureCall<'_, CONTEXT>
where
CONTEXT: Debug,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("DynamicClosureCall")
.field("args", &self.args)
.field("ret", &self.ret)
.field("context", &self.context)
.finish()
}
}
#[derive(Debug)]
pub struct DynamicClosureArgs<'call> {
argument_types: &'call [Type],
argument_layouts: &'call [FfiTypeLayout],
argument_array: *const *const c_void,
}
impl<'call> DynamicClosureArgs<'call> {
pub fn len(&self) -> usize {
self.argument_types.len()
}
pub fn is_empty(&self) -> bool {
self.argument_types.is_empty()
}
pub fn get(&self, index: usize) -> Option<DynamicClosureArg<'call>> {
let ty = self.argument_types.get(index)?;
let layout = self.argument_layouts.get(index)?;
let ptr = unsafe { *self.argument_array.add(index) };
Some(DynamicClosureArg { ty, layout, ptr })
}
pub fn iter(&self) -> DynamicClosureArgsIter<'_, 'call> {
self.into_iter()
}
}
impl<'args, 'call> IntoIterator for &'args DynamicClosureArgs<'call> {
type Item = DynamicClosureArg<'call>;
type IntoIter = DynamicClosureArgsIter<'args, 'call>;
fn into_iter(self) -> Self::IntoIter {
DynamicClosureArgsIter {
args: self,
index: 0,
}
}
}
#[derive(Debug)]
pub struct DynamicClosureArgsIter<'args, 'call> {
args: &'args DynamicClosureArgs<'call>,
index: usize,
}
impl<'call> Iterator for DynamicClosureArgsIter<'_, 'call> {
type Item = DynamicClosureArg<'call>;
fn next(&mut self) -> Option<Self::Item> {
let arg = self.args.get(self.index)?;
self.index += 1;
Some(arg)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.args.len() - self.index;
(remaining, Some(remaining))
}
}
impl ExactSizeIterator for DynamicClosureArgsIter<'_, '_> {}
impl FusedIterator for DynamicClosureArgsIter<'_, '_> {}
#[derive(Debug)]
pub struct DynamicClosureArg<'call> {
ty: &'call Type,
layout: &'call FfiTypeLayout,
ptr: *const c_void,
}
impl DynamicClosureArg<'_> {
pub fn ty(&self) -> &Type {
self.ty
}
pub fn layout(&self) -> &FfiTypeLayout {
self.layout
}
pub fn as_ptr(&self) -> *const c_void {
self.ptr
}
pub fn as_bytes(&self) -> &[MaybeUninit<u8>] {
unsafe { slice::from_raw_parts(self.ptr.cast(), self.layout.size) }
}
pub unsafe fn copy_to<ARG>(&self, dest: &mut MaybeUninit<ARG>)
where
ARG: Copy,
{
let arg_ptr = self.ptr.cast::<ARG>();
unsafe {
#[cfg(msan)]
let poisoned = {
if __msan_test_shadow(arg_ptr.cast(), size_of::<ARG>()) != -1 {
__msan_unpoison(arg_ptr.cast(), size_of::<ARG>());
true
} else {
false
}
};
if cfg!(not(all(target_arch = "x86", target_os = "windows"))) {
arg_ptr.copy_to(dest.as_mut_ptr(), 1);
} else {
dest.as_mut_ptr().write(arg_ptr.read_unaligned());
}
#[cfg(msan)]
if poisoned {
__msan_poison(arg_ptr.cast(), size_of::<ARG>());
}
}
}
}
#[derive(Debug)]
pub struct DynamicClosureRet<'call> {
ty: &'call Type,
layout: &'call FfiTypeLayout,
ptr: *mut c_void,
}
impl DynamicClosureRet<'_> {
pub fn ty(&self) -> &Type {
self.ty
}
pub fn layout(&self) -> &FfiTypeLayout {
self.layout
}
pub fn as_mut_ptr(&self) -> *mut c_void {
self.ptr
}
pub unsafe fn write_bytes(&mut self, result_bytes: &[u8]) {
unsafe {
write_closure_result_bytes(result_bytes, self.ty, self.layout.size, self.ptr);
}
}
pub unsafe fn write<RET>(&mut self, result: RET)
where
RET: Copy,
{
unsafe {
write_closure_result(result, self.ty, self.ptr.cast());
}
}
}
#[cfg(test)]
mod tests {
use core::ptr;
use super::*;
use crate::test_utils::{F64_ARG, I8_ARG, I16_ARG, U8_ARG, U16_ARG, U64_ARG};
#[cfg(target_pointer_width = "64")]
use crate::test_utils::{I32_ARG, U32_ARG};
fn verify_full_register_was_written(
return_buffer: MaybeUninit<usize>,
expected_result_bytes: &[u8],
) {
let actual_bytes = unsafe { return_buffer.assume_init() }.to_ne_bytes();
let mut expected_bytes = [0; size_of::<usize>()];
let result_offset = if cfg!(target_endian = "little") {
0
} else {
size_of::<usize>() - expected_result_bytes.len()
};
let result_end = result_offset + expected_result_bytes.len();
expected_bytes[result_offset..result_end].copy_from_slice(expected_result_bytes);
assert_eq!(actual_bytes, expected_bytes);
}
#[test]
fn args_expose_expected_memory_and_iterate_in_order() {
let u8_value = U8_ARG;
let i16_value = I16_ARG;
let f64_value = F64_ARG;
let argument_types = [Type::U8, Type::I16, Type::F64];
let argument_layouts = [
argument_types[0].layout(),
argument_types[1].layout(),
argument_types[2].layout(),
];
let argument_array = [
(&raw const u8_value).cast::<c_void>(),
(&raw const i16_value).cast::<c_void>(),
(&raw const f64_value).cast::<c_void>(),
];
let args = DynamicClosureArgs {
argument_types: &argument_types,
argument_layouts: &argument_layouts,
argument_array: argument_array.as_ptr(),
};
assert_eq!(args.len(), 3);
assert!(!args.is_empty());
assert!(args.get(3).is_none());
macro_rules! assert_argument {
($index:expr, $value:ident, $type:ty) => {{
let arg = args.get($index).unwrap();
assert_eq!(arg.ty(), &argument_types[$index]);
assert_eq!(arg.layout(), &argument_layouts[$index]);
assert_eq!(arg.as_ptr(), ptr::from_ref(&$value).cast());
unsafe {
let expected_bytes =
slice::from_raw_parts((&raw const $value).cast::<u8>(), size_of::<$type>());
assert_eq!(arg.as_bytes().assume_init_ref(), expected_bytes);
};
let mut copied = MaybeUninit::<$type>::uninit();
unsafe {
arg.copy_to(&mut copied);
}
assert_eq!(unsafe { copied.assume_init() }, $value);
}};
}
assert_argument!(0, u8_value, u8);
assert_argument!(1, i16_value, i16);
assert_argument!(2, f64_value, f64);
let mut iter = args.iter();
assert_eq!(iter.len(), 3);
assert_eq!(iter.size_hint(), (3, Some(3)));
assert_eq!(iter.next().unwrap().ty(), &argument_types[0]);
assert_eq!(iter.len(), 2);
assert_eq!(iter.size_hint(), (2, Some(2)));
assert_eq!(iter.next().unwrap().ty(), &argument_types[1]);
assert_eq!(iter.next().unwrap().ty(), &argument_types[2]);
assert_eq!(iter.len(), 0);
assert_eq!(iter.size_hint(), (0, Some(0)));
assert!(iter.next().is_none());
assert!(iter.next().is_none());
}
#[test]
fn ret_exposes_metadata_and_write_writes_scalar() {
let return_type = Type::U64;
let return_layout = return_type.layout();
let mut return_value = MaybeUninit::<u64>::uninit();
let return_ptr = return_value.as_mut_ptr().cast();
let mut ret = DynamicClosureRet {
ty: &return_type,
layout: &return_layout,
ptr: return_ptr,
};
assert_eq!(ret.ty(), &return_type);
assert_eq!(ret.layout(), &return_layout);
assert_eq!(ret.as_mut_ptr(), return_ptr);
unsafe {
ret.write(U64_ARG);
}
assert_eq!(unsafe { return_value.assume_init() }, U64_ARG);
}
#[test]
fn write_promotes_small_integers_to_full_register() {
fn verify<RET>(result: RET, return_type: &Type, expected_result_bytes: &[u8])
where
RET: Copy,
{
let return_layout = return_type.layout();
let mut return_buffer = MaybeUninit::new(usize::MAX);
let mut ret = DynamicClosureRet {
ty: return_type,
layout: &return_layout,
ptr: (&raw mut return_buffer).cast(),
};
unsafe {
ret.write(result);
}
verify_full_register_was_written(return_buffer, expected_result_bytes);
}
verify(I8_ARG, &Type::I8, &I8_ARG.to_ne_bytes());
verify(U8_ARG, &Type::U8, &U8_ARG.to_ne_bytes());
verify(I16_ARG, &Type::I16, &I16_ARG.to_ne_bytes());
verify(U16_ARG, &Type::U16, &U16_ARG.to_ne_bytes());
#[cfg(target_pointer_width = "64")]
{
verify(I32_ARG, &Type::I32, &I32_ARG.to_ne_bytes());
verify(U32_ARG, &Type::U32, &U32_ARG.to_ne_bytes());
}
}
#[test]
fn write_bytes_promotes_small_integers_to_full_register() {
fn verify(result_bytes: &[u8], return_type: &Type) {
let return_layout = return_type.layout();
let mut return_buffer = MaybeUninit::new(usize::MAX);
let mut ret = DynamicClosureRet {
ty: return_type,
layout: &return_layout,
ptr: (&raw mut return_buffer).cast(),
};
unsafe {
ret.write_bytes(result_bytes);
}
verify_full_register_was_written(return_buffer, result_bytes);
}
verify(&I8_ARG.to_ne_bytes(), &Type::I8);
verify(&U8_ARG.to_ne_bytes(), &Type::U8);
verify(&I16_ARG.to_ne_bytes(), &Type::I16);
verify(&U16_ARG.to_ne_bytes(), &Type::U16);
#[cfg(target_pointer_width = "64")]
{
verify(&I32_ARG.to_ne_bytes(), &Type::I32);
verify(&U32_ARG.to_ne_bytes(), &Type::U32);
}
}
}