use core::fmt;
use core::marker::PhantomData;
use core::mem;
use core::mem::MaybeUninit;
use core::ops::Deref;
use core::ptr::{self, NonNull};
use std::os::raw::c_ulong;
use crate::abi::{BlockDescriptor, BlockDescriptorPtr, BlockFlags, BlockHeader};
use crate::debug::debug_block_header;
use crate::{Block, BlockFn};
const GLOBAL_DESCRIPTOR: BlockDescriptor = BlockDescriptor {
reserved: 0,
size: mem::size_of::<BlockHeader>() as c_ulong,
};
#[repr(C)]
pub struct GlobalBlock<F: ?Sized> {
header: BlockHeader,
f: PhantomData<F>,
}
unsafe impl<F: ?Sized + BlockFn> Sync for GlobalBlock<F> {}
unsafe impl<F: ?Sized + BlockFn> Send for GlobalBlock<F> {}
impl<F: ?Sized> GlobalBlock<F> {
const FLAGS: BlockFlags =
BlockFlags(BlockFlags::BLOCK_IS_GLOBAL.0 | BlockFlags::BLOCK_USE_STRET.0);
#[doc(hidden)]
pub const __DEFAULT_HEADER: BlockHeader = BlockHeader {
isa: ptr::null_mut(),
flags: Self::FLAGS,
reserved: MaybeUninit::new(0),
invoke: None,
descriptor: BlockDescriptorPtr {
basic: &GLOBAL_DESCRIPTOR,
},
};
#[doc(hidden)]
#[inline]
pub const unsafe fn from_header(header: BlockHeader) -> Self {
Self {
header,
f: PhantomData,
}
}
}
impl<F: ?Sized + BlockFn> Deref for GlobalBlock<F> {
type Target = Block<F>;
#[inline]
fn deref(&self) -> &Self::Target {
let ptr: NonNull<Self> = NonNull::from(self);
let ptr: NonNull<Block<F>> = ptr.cast();
unsafe { ptr.as_ref() }
}
}
impl<F: ?Sized> fmt::Debug for GlobalBlock<F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut f = f.debug_struct("GlobalBlock");
debug_block_header(&self.header, &mut f);
f.finish_non_exhaustive()
}
}
#[macro_export]
macro_rules! global_block {
(
$(#[$m:meta])*
$vis:vis static $name:ident = || $(-> $r:ty)? $body:block $(;)?
) => {
$crate::global_block!(
$(#[$m])*
$vis static $name = |,| $(-> $r)? $body
);
};
(
$(#[$m:meta])*
$vis:vis static $name:ident = |$($a:ident: $t:ty),* $(,)?| $(-> $r:ty)? $body:block $(;)?
) => {
$(#[$m])*
#[allow(unused_unsafe)]
$vis static $name: $crate::GlobalBlock<dyn Fn($($t),*) $(-> $r)? + 'static> = unsafe {
let mut header = $crate::GlobalBlock::<dyn Fn($($t),*) $(-> $r)? + 'static>::__DEFAULT_HEADER;
header.isa = ::core::ptr::addr_of!($crate::ffi::_NSConcreteGlobalBlock);
header.invoke = ::core::option::Option::Some({
unsafe extern "C" fn inner(_: *mut $crate::GlobalBlock<dyn Fn($($t),*) $(-> $r)? + 'static>, $($a: $t),*) $(-> $r)? {
$body
}
::core::mem::transmute::<
unsafe extern "C" fn(*mut $crate::GlobalBlock<dyn Fn($($t),*) $(-> $r)? + 'static>, $($a: $t),*) $(-> $r)?,
unsafe extern "C" fn(),
>(inner)
});
$crate::GlobalBlock::from_header(header)
};
};
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::format;
global_block! {
pub(super) static NOOP_BLOCK = || {};
}
global_block! {
#[allow(unused)]
static BLOCK = |x: i32, y: i32, z: i32, w: i32,| -> i32 {
x + y + z + w
};
}
#[test]
fn test_noop_block() {
NOOP_BLOCK.call(());
}
#[test]
fn test_defined_in_function() {
global_block!(static MY_BLOCK = || -> i32 {
42
});
assert_eq!(MY_BLOCK.call(()), 42);
}
#[cfg(target_vendor = "apple")]
const DEBUG_BLOCKFLAGS: &str = r#"BlockFlags {
value: "00110000000000000000000000000000",
deallocating: false,
inline_layout_string: false,
small_descriptor: false,
is_noescape: false,
needs_free: false,
has_copy_dispose: false,
has_ctor: false,
is_gc: false,
is_global: true,
use_stret: true,
has_signature: false,
has_extended_layout: false,
over_referenced: false,
reference_count: 0,
..
}"#;
#[cfg(not(target_vendor = "apple"))]
const DEBUG_BLOCKFLAGS: &str = r#"BlockFlags {
value: "00110000000000000000000000000000",
has_copy_dispose: false,
has_ctor: false,
is_global: true,
use_stret: true,
has_signature: false,
over_referenced: false,
reference_count: 0,
..
}"#;
#[test]
fn test_debug() {
let invoke = NOOP_BLOCK.header.invoke.unwrap();
let size = mem::size_of::<BlockHeader>();
let expected = format!(
"GlobalBlock {{
isa: _NSConcreteGlobalBlock,
flags: {DEBUG_BLOCKFLAGS},
reserved: core::mem::maybe_uninit::MaybeUninit<i32>,
invoke: Some(
{invoke:#?},
),
descriptor: BlockDescriptor {{
reserved: 0,
size: {size},
}},
..
}}"
);
assert_eq!(format!("{NOOP_BLOCK:#?}"), expected);
}
#[allow(dead_code)]
fn covariant<'f>(b: GlobalBlock<dyn Fn() + 'static>) -> GlobalBlock<dyn Fn() + 'f> {
b
}
}