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