1use core::ffi::c_ulong;
2use core::fmt;
3use core::marker::PhantomData;
4use core::mem;
5use core::mem::MaybeUninit;
6use core::ops::Deref;
7use core::ptr::{self, NonNull};
8
9use crate::abi::{BlockDescriptor, BlockDescriptorPtr, BlockFlags, BlockHeader};
10use crate::debug::debug_block_header;
11use crate::{Block, BlockFn};
12
13const GLOBAL_DESCRIPTOR: BlockDescriptor = BlockDescriptor {
15 reserved: 0,
16 size: mem::size_of::<BlockHeader>() as c_ulong,
17};
18
19#[repr(C)]
32pub struct GlobalBlock<F: ?Sized> {
33 header: BlockHeader,
34 f: PhantomData<F>,
37}
38
39unsafe impl<F: ?Sized + BlockFn> Sync for GlobalBlock<F> {}
41unsafe impl<F: ?Sized + BlockFn> Send for GlobalBlock<F> {}
42
43impl<F: ?Sized> GlobalBlock<F> {
50 const FLAGS: BlockFlags = BlockFlags::BLOCK_IS_GLOBAL.union(BlockFlags::BLOCK_USE_STRET);
52
53 #[doc(hidden)]
54 #[allow(clippy::declare_interior_mutable_const)]
55 pub const __DEFAULT_HEADER: BlockHeader = BlockHeader {
56 isa: ptr::null_mut(),
58 flags: Self::FLAGS,
59 reserved: MaybeUninit::new(0),
60 invoke: None,
62 descriptor: BlockDescriptorPtr {
63 basic: &GLOBAL_DESCRIPTOR,
64 },
65 };
66
67 #[doc(hidden)]
69 #[inline]
70 pub const unsafe fn from_header(header: BlockHeader) -> Self {
71 Self {
72 header,
73 f: PhantomData,
74 }
75 }
76
77 }
79
80impl<F: ?Sized + BlockFn> Deref for GlobalBlock<F> {
81 type Target = Block<F>;
82
83 #[inline]
84 fn deref(&self) -> &Self::Target {
85 let ptr: NonNull<Self> = NonNull::from(self);
86 let ptr: NonNull<Block<F>> = ptr.cast();
87 unsafe { ptr.as_ref() }
92 }
93}
94
95impl<F: ?Sized> fmt::Debug for GlobalBlock<F> {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 let mut f = f.debug_struct("GlobalBlock");
98 debug_block_header(&self.header, &mut f);
99 f.finish_non_exhaustive()
100 }
101}
102
103#[macro_export]
172macro_rules! global_block {
173 (
175 $(#[$m:meta])*
176 $vis:vis static $name:ident = || $(-> $r:ty)? $body:block $(;)?
177 ) => {
178 $crate::global_block!(
179 $(#[$m])*
180 $vis static $name = |,| $(-> $r)? $body
181 );
182 };
183 (
184 $(#[$m:meta])*
185 $vis:vis static $name:ident = |$($a:ident: $t:ty),* $(,)?| $(-> $r:ty)? $body:block $(;)?
186 ) => {
187 $(#[$m])*
188 #[allow(unused_unsafe)]
189 $vis static $name: $crate::GlobalBlock<dyn Fn($($t),*) $(-> $r)? + 'static> = unsafe {
190 let mut header = $crate::GlobalBlock::<dyn Fn($($t),*) $(-> $r)? + 'static>::__DEFAULT_HEADER;
191 header.isa = ::core::ptr::addr_of!($crate::ffi::_NSConcreteGlobalBlock);
192 header.invoke = ::core::option::Option::Some({
193 unsafe extern "C-unwind" fn inner(
194 _: *mut $crate::GlobalBlock<dyn Fn($($t),*) $(-> $r)? + 'static>,
195 $($a: $t),*
196 ) $(-> $r)? {
197 $body
198 }
199
200 ::core::mem::transmute::<
202 unsafe extern "C-unwind" fn(*mut $crate::GlobalBlock<dyn Fn($($t),*) $(-> $r)? + 'static>, $($a: $t),*) $(-> $r)?,
203 unsafe extern "C-unwind" fn(),
204 >(inner)
205 });
206 $crate::GlobalBlock::from_header(header)
207 };
208 };
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214 use alloc::format;
215
216 global_block! {
217 pub(super) static NOOP_BLOCK = || {};
219 }
220
221 global_block! {
222 #[allow(unused)]
224 static BLOCK = |x: i32, y: i32, z: i32, w: i32,| -> i32 {
225 x + y + z + w
226 };
227 }
228
229 #[test]
230 fn test_noop_block() {
231 NOOP_BLOCK.call(());
232 }
233
234 #[test]
235 fn test_defined_in_function() {
236 global_block!(static MY_BLOCK = || -> i32 {
237 42
238 });
239 assert_eq!(MY_BLOCK.call(()), 42);
240 }
241
242 #[cfg(target_vendor = "apple")]
243 const DEBUG_BLOCKFLAGS: &str = r#"BlockFlags {
244 value: "00110000000000000000000000000000",
245 deallocating: false,
246 inline_layout_string: false,
247 small_descriptor: false,
248 is_noescape: false,
249 needs_free: false,
250 has_copy_dispose: false,
251 has_ctor: false,
252 is_gc: false,
253 is_global: true,
254 use_stret: true,
255 has_signature: false,
256 has_extended_layout: false,
257 over_referenced: false,
258 reference_count: 0,
259 ..
260 }"#;
261
262 #[cfg(not(target_vendor = "apple"))]
263 const DEBUG_BLOCKFLAGS: &str = r#"BlockFlags {
264 value: "00110000000000000000000000000000",
265 has_copy_dispose: false,
266 has_ctor: false,
267 is_global: true,
268 use_stret: true,
269 has_signature: false,
270 over_referenced: false,
271 reference_count: 0,
272 ..
273 }"#;
274
275 #[test]
276 fn test_debug() {
277 let invoke = NOOP_BLOCK.header.invoke.unwrap();
278 let size = mem::size_of::<BlockHeader>();
279 let maybeuninit = <MaybeUninit<i32>>::uninit();
280 let expected = format!(
281 "GlobalBlock {{
282 isa: _NSConcreteGlobalBlock,
283 flags: {DEBUG_BLOCKFLAGS},
284 reserved: {maybeuninit:?},
285 invoke: Some(
286 {invoke:#?},
287 ),
288 descriptor: BlockDescriptor {{
289 reserved: 0,
290 size: {size},
291 }},
292 ..
293}}"
294 );
295 assert_eq!(format!("{NOOP_BLOCK:#?}"), expected);
296 }
297
298 #[allow(dead_code)]
299 fn covariant<'f>(b: GlobalBlock<dyn Fn() + 'static>) -> GlobalBlock<dyn Fn() + 'f> {
300 b
301 }
302}