use core::ffi::c_ulong;
use core::ffi::c_void;
use core::fmt;
use core::marker::PhantomData;
use core::mem::{self, MaybeUninit};
use core::ops::Deref;
use core::panic::{RefUnwindSafe, UnwindSafe};
use core::ptr::{self, NonNull};
use objc2::encode::{EncodeArguments, EncodeReturn, Encoding, RefEncode};
use crate::abi::{
BlockDescriptor, BlockDescriptorCopyDispose, BlockDescriptorCopyDisposeSignature,
BlockDescriptorPtr, BlockDescriptorSignature, BlockFlags, BlockHeader,
};
use crate::debug::debug_block_header;
use crate::traits::{ManualBlockEncoding, ManualBlockEncodingExt, NoBlockEncoding, UserSpecified};
use crate::{ffi, Block, IntoBlock};
#[repr(C)]
pub struct StackBlock<'f, A, R, Closure> {
p: PhantomData<dyn Fn(A) -> R + Send + Sync + RefUnwindSafe + UnwindSafe + Unpin + 'f>,
header: BlockHeader,
pub(crate) closure: Closure,
}
unsafe impl<'f, A, R, Closure> RefEncode for StackBlock<'f, A, R, Closure>
where
A: EncodeArguments,
R: EncodeReturn,
Closure: IntoBlock<'f, A, R>,
{
const ENCODING_REF: Encoding = Encoding::Block;
}
impl<A, R, Closure> StackBlock<'_, A, R, Closure> {
const SIZE: c_ulong = mem::size_of::<Self>() as _;
unsafe extern "C-unwind" fn drop_closure(block: *mut c_void) {
let block: *mut Self = block.cast();
let closure = unsafe { ptr::addr_of_mut!((*block).closure) };
unsafe { ptr::drop_in_place(closure) };
}
const DESCRIPTOR_BASIC: BlockDescriptor = BlockDescriptor {
reserved: 0,
size: Self::SIZE,
};
}
impl<A, R, Closure: Clone> StackBlock<'_, A, R, Closure> {
unsafe extern "C-unwind" fn clone_closure(dst: *mut c_void, src: *const c_void) {
let dst: *mut Self = dst.cast();
let src: *const Self = src.cast();
let dst_closure = unsafe { ptr::addr_of_mut!((*dst).closure) };
let src_closure = unsafe { &*ptr::addr_of!((*src).closure) };
unsafe { ptr::write(dst_closure, src_closure.clone()) };
}
const DESCRIPTOR_WITH_CLONE: BlockDescriptorCopyDispose = BlockDescriptorCopyDispose {
reserved: 0,
size: Self::SIZE,
copy: Some(Self::clone_closure),
dispose: Some(Self::drop_closure),
};
}
impl<'f, A, R, Closure> StackBlock<'f, A, R, Closure>
where
A: EncodeArguments,
R: EncodeReturn,
Closure: IntoBlock<'f, A, R> + Clone,
{
#[inline]
pub fn new(closure: Closure) -> Self {
Self::maybe_encoded::<NoBlockEncoding<A, R>>(closure)
}
#[inline]
pub fn with_encoding<E>(closure: Closure) -> Self
where
E: ManualBlockEncoding<Arguments = A, Return = R>,
{
Self::maybe_encoded::<UserSpecified<E>>(closure)
}
#[inline]
fn maybe_encoded<E>(closure: Closure) -> Self
where
E: ManualBlockEncodingExt<Arguments = A, Return = R>,
{
let header = BlockHeader {
#[allow(unused_unsafe)]
isa: unsafe { ptr::addr_of!(ffi::_NSConcreteStackBlock) },
flags: BlockFlags::BLOCK_HAS_COPY_DISPOSE
| if !E::IS_NONE {
BlockFlags::BLOCK_HAS_SIGNATURE
} else {
BlockFlags::EMPTY
},
reserved: MaybeUninit::new(0),
invoke: Some(Closure::__get_invoke_stack_block()),
descriptor: if E::IS_NONE {
BlockDescriptorPtr {
with_copy_dispose: &Self::DESCRIPTOR_WITH_CLONE,
}
} else {
BlockDescriptorPtr {
with_copy_dispose_signature:
&<Self as EncodedCloneDescriptors<E>>::DESCRIPTOR_WITH_CLONE_AND_ENCODING,
}
},
};
Self {
p: PhantomData,
header,
closure,
}
}
}
impl<'f, A, R, Closure> StackBlock<'f, A, R, Closure> {
unsafe extern "C-unwind" fn empty_clone_closure(_dst: *mut c_void, _src: *const c_void) {
}
const DESCRIPTOR_WITH_DROP: BlockDescriptorCopyDispose = BlockDescriptorCopyDispose {
reserved: 0,
size: Self::SIZE,
copy: Some(Self::empty_clone_closure),
dispose: Some(Self::drop_closure),
};
#[inline]
pub(crate) unsafe fn new_no_clone<E>(closure: Closure) -> Self
where
A: EncodeArguments,
R: EncodeReturn,
Closure: IntoBlock<'f, A, R>,
E: ManualBlockEncodingExt<Arguments = A, Return = R>,
{
let flags = if mem::needs_drop::<Self>() {
BlockFlags::BLOCK_HAS_COPY_DISPOSE
} else {
BlockFlags::EMPTY
} | if !E::IS_NONE {
BlockFlags::BLOCK_HAS_SIGNATURE
} else {
BlockFlags::EMPTY
};
let descriptor = match (mem::needs_drop::<Self>(), E::IS_NONE) {
(true, true) => {
BlockDescriptorPtr {
with_copy_dispose: &Self::DESCRIPTOR_WITH_DROP,
}
}
(false, true) => {
BlockDescriptorPtr {
basic: &Self::DESCRIPTOR_BASIC,
}
}
(true, false) => {
BlockDescriptorPtr {
with_copy_dispose_signature:
&<Self as EncodedDescriptors<E>>::DESCRIPTOR_WITH_DROP_AND_ENCODING,
}
}
(false, false) => {
BlockDescriptorPtr {
with_signature:
&<Self as EncodedDescriptors<E>>::DESCRIPTOR_BASIC_WITH_ENCODING,
}
}
};
let header = BlockHeader {
#[allow(unused_unsafe)]
isa: unsafe { ptr::addr_of!(ffi::_NSConcreteStackBlock) },
flags,
reserved: MaybeUninit::new(0),
invoke: Some(Closure::__get_invoke_stack_block()),
descriptor,
};
Self {
p: PhantomData,
header,
closure,
}
}
}
trait EncodedDescriptors<E: ManualBlockEncoding> {
const DESCRIPTOR_BASIC_WITH_ENCODING: BlockDescriptorSignature;
const DESCRIPTOR_WITH_DROP_AND_ENCODING: BlockDescriptorCopyDisposeSignature;
}
impl<'f, A, R, Closure, E> EncodedDescriptors<E> for StackBlock<'f, A, R, Closure>
where
A: EncodeArguments,
R: EncodeReturn,
Closure: IntoBlock<'f, A, R>,
E: ManualBlockEncoding<Arguments = A, Return = R>,
{
const DESCRIPTOR_BASIC_WITH_ENCODING: BlockDescriptorSignature = BlockDescriptorSignature {
reserved: Self::DESCRIPTOR_BASIC.reserved,
size: Self::DESCRIPTOR_BASIC.size,
encoding: E::ENCODING_CSTR.as_ptr(),
};
const DESCRIPTOR_WITH_DROP_AND_ENCODING: BlockDescriptorCopyDisposeSignature =
BlockDescriptorCopyDisposeSignature {
reserved: Self::DESCRIPTOR_WITH_DROP.reserved,
size: Self::DESCRIPTOR_WITH_DROP.size,
copy: Self::DESCRIPTOR_WITH_DROP.copy,
dispose: Self::DESCRIPTOR_WITH_DROP.dispose,
encoding: E::ENCODING_CSTR.as_ptr(),
};
}
trait EncodedCloneDescriptors<E: ManualBlockEncoding> {
const DESCRIPTOR_WITH_CLONE_AND_ENCODING: BlockDescriptorCopyDisposeSignature;
}
impl<'f, A, R, Closure, E> EncodedCloneDescriptors<E> for StackBlock<'f, A, R, Closure>
where
A: EncodeArguments,
R: EncodeReturn,
Closure: IntoBlock<'f, A, R> + Clone,
E: ManualBlockEncoding<Arguments = A, Return = R>,
{
const DESCRIPTOR_WITH_CLONE_AND_ENCODING: BlockDescriptorCopyDisposeSignature =
BlockDescriptorCopyDisposeSignature {
reserved: Self::DESCRIPTOR_WITH_CLONE.reserved,
size: Self::DESCRIPTOR_WITH_CLONE.size,
copy: Self::DESCRIPTOR_WITH_CLONE.copy,
dispose: Self::DESCRIPTOR_WITH_CLONE.dispose,
encoding: E::ENCODING_CSTR.as_ptr(),
};
}
impl<A, R, Closure: Clone> Clone for StackBlock<'_, A, R, Closure> {
#[inline]
fn clone(&self) -> Self {
Self {
p: PhantomData,
header: self.header,
closure: self.closure.clone(),
}
}
}
impl<A, R, Closure: Copy> Copy for StackBlock<'_, A, R, Closure> {}
impl<'f, A, R, Closure> Deref for StackBlock<'f, A, R, Closure>
where
A: EncodeArguments,
R: EncodeReturn,
Closure: IntoBlock<'f, A, R>,
{
type Target = Block<Closure::Dyn>;
#[inline]
fn deref(&self) -> &Self::Target {
let ptr: NonNull<Self> = NonNull::from(self);
let ptr: NonNull<Block<Closure::Dyn>> = ptr.cast();
unsafe { ptr.as_ref() }
}
}
impl<A, R, Closure> fmt::Debug for StackBlock<'_, A, R, Closure> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut f = f.debug_struct("StackBlock");
debug_block_header(&self.header, &mut f);
f.finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_size() {
assert_eq!(
mem::size_of::<BlockHeader>(),
<StackBlock<'_, (), (), ()>>::SIZE as _,
);
assert_eq!(
mem::size_of::<BlockHeader>() + mem::size_of::<fn()>(),
<StackBlock<'_, (), (), fn()>>::SIZE as _,
);
}
#[allow(dead_code)]
fn covariant<'b, 'f>(
b: StackBlock<'static, (), (), impl Fn() + 'static>,
) -> StackBlock<'b, (), (), impl Fn() + 'f> {
b
}
}