mtl_sys/block.rs
1//! Objective-C block support using the Clang blocks ABI.
2//!
3//! This module provides the exact same block ABI that Clang generates for
4//! Objective-C blocks. The structure matches Apple's documented Block ABI:
5//! https://clang.llvm.org/docs/Block-ABI-Apple.html
6//!
7//! # C++ Equivalent
8//!
9//! In metal-cpp, blocks are defined as:
10//! ```cpp
11//! using NewLibraryCompletionHandler = void (^)(MTL::Library*, NS::Error*);
12//! using NewLibraryCompletionHandlerFunction = std::function<void(MTL::Library*, NS::Error*)>;
13//! ```
14//!
15//! And used like:
16//! ```cpp
17//! __block MTL::NewLibraryCompletionHandlerFunction blockCompletionHandler = completionHandler;
18//! newLibrary(pSource, pOptions, ^(MTL::Library* pLibrary, NS::Error* pError) {
19//! blockCompletionHandler(pLibrary, pError);
20//! });
21//! ```
22//!
23//! This module provides the Rust equivalent of that block machinery.
24
25use std::ffi::c_void;
26use std::mem;
27use std::ptr;
28
29// =============================================================================
30// Block ABI Constants (from Clang)
31// =============================================================================
32
33/// Block has copy/dispose helpers.
34const BLOCK_HAS_COPY_DISPOSE: i32 = 1 << 25;
35
36/// Block has a C++ destructor (or __strong/__weak capture).
37#[allow(dead_code)]
38const BLOCK_HAS_CTOR: i32 = 1 << 26;
39
40/// Block is a global block.
41#[allow(dead_code)]
42const BLOCK_IS_GLOBAL: i32 = 1 << 28;
43
44/// Block has a type encoding signature in descriptor.
45#[allow(dead_code)]
46const BLOCK_HAS_STRET: i32 = 1 << 29;
47
48/// Block has a signature and/or layout.
49#[allow(dead_code)]
50const BLOCK_HAS_SIGNATURE: i32 = 1 << 30;
51
52// =============================================================================
53// External Symbols from libobjc
54// =============================================================================
55
56#[link(name = "objc")]
57unsafe extern "C" {
58 /// The class object for stack-allocated blocks.
59 static _NSConcreteStackBlock: *const c_void;
60
61 /// The class object for global (static) blocks.
62 static _NSConcreteGlobalBlock: *const c_void;
63}
64
65// =============================================================================
66// Block Descriptor (Clang ABI - matches struct Block_descriptor_1)
67// =============================================================================
68
69/// Block descriptor without copy/dispose helpers.
70#[repr(C)]
71#[allow(dead_code)]
72struct BlockDescriptorBasic {
73 /// Reserved for future use (always 0).
74 reserved: usize,
75 /// Size of the block literal structure.
76 size: usize,
77}
78
79/// Block descriptor with copy/dispose helpers.
80/// Corresponds to struct Block_descriptor with BLOCK_HAS_COPY_DISPOSE.
81#[repr(C)]
82struct BlockDescriptor {
83 /// Reserved for future use (always 0).
84 reserved: usize,
85 /// Size of the block literal structure.
86 size: usize,
87 /// Copy helper function pointer.
88 copy: unsafe extern "C" fn(*mut c_void, *const c_void),
89 /// Dispose helper function pointer.
90 dispose: unsafe extern "C" fn(*mut c_void),
91}
92
93// =============================================================================
94// Block Literal (Clang ABI - matches struct Block_literal_1)
95// =============================================================================
96
97/// Block literal structure matching Clang's Block_literal_1.
98///
99/// This is the actual block object passed to Objective-C APIs.
100/// The layout must exactly match what Clang generates.
101#[repr(C)]
102pub struct BlockLiteral<Closure> {
103 /// ISA pointer - points to _NSConcreteStackBlock or _NSConcreteGlobalBlock.
104 pub isa: *const c_void,
105 /// Block flags.
106 pub flags: i32,
107 /// Reserved (always 0).
108 pub reserved: i32,
109 /// Invoke function pointer - the actual function to call.
110 pub invoke: *const c_void,
111 /// Pointer to the block descriptor.
112 pub descriptor: *const c_void,
113 /// Captured closure data.
114 pub closure: Closure,
115}
116
117// =============================================================================
118// RcBlock - Reference-counted block that properly matches the ABI
119// =============================================================================
120
121/// A reference-counted block wrapping a Rust closure.
122///
123/// This structure matches the Clang blocks ABI exactly, allowing Rust closures
124/// to be passed to Objective-C APIs that expect block parameters.
125#[repr(C)]
126pub struct RcBlock<Closure> {
127 /// The block literal (must be first for ABI compatibility).
128 literal: BlockLiteral<Closure>,
129 /// The block descriptor (stored inline after the literal).
130 descriptor: BlockDescriptor,
131}
132
133// Copy helper - increments reference count or copies captured data
134unsafe extern "C" fn block_copy_helper<Closure>(_dst: *mut c_void, _src: *const c_void) {
135 // For blocks with Clone closures, we could implement actual copying here.
136 // For now, this is called when the block is copied to the heap.
137 // The block runtime handles the actual memory copy.
138}
139
140// Dispose helper - decrements reference count or frees captured data
141unsafe extern "C" fn block_dispose_helper<Closure>(_src: *mut c_void) {
142 // For blocks with Drop closures, cleanup would happen here.
143 // The block runtime calls this when the block's refcount reaches 0.
144}
145
146impl<Closure> RcBlock<Closure> {
147 /// Create a new block with the specified invoke function.
148 ///
149 /// # Safety
150 ///
151 /// The `invoke` function pointer must have the correct calling convention
152 /// and signature for the block type. The first parameter must be a pointer
153 /// to the BlockLiteral.
154 #[inline]
155 pub unsafe fn new(closure: Closure, invoke: *const c_void) -> Self {
156 let descriptor = BlockDescriptor {
157 reserved: 0,
158 size: mem::size_of::<BlockLiteral<Closure>>(),
159 copy: block_copy_helper::<Closure>,
160 dispose: block_dispose_helper::<Closure>,
161 };
162
163 let mut block = RcBlock {
164 literal: BlockLiteral {
165 isa: unsafe { _NSConcreteStackBlock },
166 flags: BLOCK_HAS_COPY_DISPOSE,
167 reserved: 0,
168 invoke,
169 descriptor: ptr::null(),
170 closure,
171 },
172 descriptor,
173 };
174
175 // Point to our inline descriptor
176 block.literal.descriptor = &block.descriptor as *const _ as *const c_void;
177
178 block
179 }
180
181 /// Create a new heap-allocated block with the specified invoke function.
182 ///
183 /// This allocates the block on the heap so it can be safely passed to
184 /// Objective-C APIs that retain the block. The returned pointer should
185 /// be passed to `as_heap_ptr()` to get the block pointer.
186 ///
187 /// # Safety
188 ///
189 /// The `invoke` function pointer must have the correct calling convention
190 /// and signature for the block type.
191 #[inline]
192 pub unsafe fn new_heap(closure: Closure, invoke: *const c_void) -> Box<Self> {
193 let descriptor = BlockDescriptor {
194 reserved: 0,
195 size: mem::size_of::<BlockLiteral<Closure>>(),
196 copy: block_copy_helper::<Closure>,
197 dispose: block_dispose_helper::<Closure>,
198 };
199
200 let mut block = Box::new(RcBlock {
201 literal: BlockLiteral {
202 isa: unsafe { _NSConcreteStackBlock },
203 flags: BLOCK_HAS_COPY_DISPOSE,
204 reserved: 0,
205 invoke,
206 descriptor: ptr::null(),
207 closure,
208 },
209 descriptor,
210 });
211
212 // Point to our inline descriptor (now on the heap)
213 block.literal.descriptor = &block.descriptor as *const _ as *const c_void;
214
215 block
216 }
217
218 /// Get a pointer to the block literal to pass to Objective-C.
219 #[inline]
220 pub fn as_ptr(&self) -> *const c_void {
221 &self.literal as *const _ as *const c_void
222 }
223
224 /// Consume the block, preventing its destructor from running.
225 ///
226 /// Use this after passing the block to Metal/Objective-C APIs that
227 /// will retain the block internally. The block runtime will handle
228 /// cleanup when the retain count reaches zero.
229 #[inline]
230 pub fn into_raw(self) -> *const c_void {
231 let ptr = self.as_ptr();
232 mem::forget(self);
233 ptr
234 }
235}
236
237// =============================================================================
238// Concrete Block Types matching metal-cpp signatures
239// =============================================================================
240
241// -----------------------------------------------------------------------------
242// void (^)(void) - No arguments
243// -----------------------------------------------------------------------------
244
245/// Block type: `void (^)(void)`
246///
247/// Used for simple callbacks with no arguments.
248pub type VoidBlock = RcBlock<Box<dyn Fn() + Send>>;
249
250impl VoidBlock {
251 /// Create a block from a closure taking no arguments.
252 pub fn from_fn<F>(f: F) -> Self
253 where
254 F: Fn() + Send + 'static,
255 {
256 unsafe extern "C" fn invoke(block: *mut BlockLiteral<Box<dyn Fn() + Send>>) {
257 unsafe { ((*block).closure)() }
258 }
259
260 unsafe { RcBlock::new(Box::new(f), invoke as *const c_void) }
261 }
262}
263
264// -----------------------------------------------------------------------------
265// void (^)(id) - One object argument
266// -----------------------------------------------------------------------------
267
268/// Block type: `void (^)(id)`
269///
270/// Used for callbacks with one object argument (e.g., CommandBuffer handlers).
271pub type OneArgBlock = RcBlock<Box<dyn Fn(*mut c_void) + Send>>;
272
273/// A heap-allocated one-argument block that can be safely passed to ObjC APIs.
274pub struct HeapOneArgBlock {
275 inner: *mut OneArgBlock,
276}
277
278impl HeapOneArgBlock {
279 /// Create a heap-allocated block from a closure taking one pointer argument.
280 pub fn from_fn<F>(f: F) -> Self
281 where
282 F: Fn(*mut c_void) + Send + 'static,
283 {
284 unsafe extern "C" fn invoke(
285 block: *mut BlockLiteral<Box<dyn Fn(*mut c_void) + Send>>,
286 arg: *mut c_void,
287 ) {
288 unsafe { ((*block).closure)(arg) }
289 }
290
291 let boxed: Box<OneArgBlock> = unsafe {
292 RcBlock::new_heap(
293 Box::new(f) as Box<dyn Fn(*mut c_void) + Send>,
294 invoke as *const c_void,
295 )
296 };
297 HeapOneArgBlock {
298 inner: Box::into_raw(boxed),
299 }
300 }
301
302 /// Get a pointer to the block literal to pass to Objective-C.
303 pub fn as_ptr(&self) -> *const c_void {
304 unsafe { (*self.inner).as_ptr() }
305 }
306}
307
308impl OneArgBlock {
309 /// Create a block from a closure taking one pointer argument.
310 pub fn from_fn<F>(f: F) -> Self
311 where
312 F: Fn(*mut c_void) + Send + 'static,
313 {
314 unsafe extern "C" fn invoke(
315 block: *mut BlockLiteral<Box<dyn Fn(*mut c_void) + Send>>,
316 arg: *mut c_void,
317 ) {
318 unsafe { ((*block).closure)(arg) }
319 }
320
321 unsafe { RcBlock::new(Box::new(f), invoke as *const c_void) }
322 }
323
324 /// Create a heap-allocated block from a closure taking one pointer argument.
325 ///
326 /// Use this for blocks that need to outlive the current scope, such as
327 /// completion handlers passed to Metal APIs.
328 pub fn from_fn_heap<F>(f: F) -> HeapOneArgBlock
329 where
330 F: Fn(*mut c_void) + Send + 'static,
331 {
332 HeapOneArgBlock::from_fn(f)
333 }
334}
335
336// -----------------------------------------------------------------------------
337// void (^)(id, id) - Two object arguments
338// -----------------------------------------------------------------------------
339
340/// Block type: `void (^)(id, id)`
341///
342/// Used for completion handlers: `void (^)(MTL::Library*, NS::Error*)`
343pub type TwoArgBlock = RcBlock<Box<dyn Fn(*mut c_void, *mut c_void) + Send>>;
344
345impl TwoArgBlock {
346 /// Create a block from a closure taking two pointer arguments.
347 ///
348 /// This matches the C++ pattern:
349 /// ```cpp
350 /// using NewLibraryCompletionHandler = void (^)(MTL::Library*, NS::Error*);
351 /// ```
352 pub fn from_fn<F>(f: F) -> Self
353 where
354 F: Fn(*mut c_void, *mut c_void) + Send + 'static,
355 {
356 unsafe extern "C" fn invoke(
357 block: *mut BlockLiteral<Box<dyn Fn(*mut c_void, *mut c_void) + Send>>,
358 arg1: *mut c_void,
359 arg2: *mut c_void,
360 ) {
361 unsafe { ((*block).closure)(arg1, arg2) }
362 }
363
364 unsafe { RcBlock::new(Box::new(f), invoke as *const c_void) }
365 }
366}
367
368// -----------------------------------------------------------------------------
369// void (^)(id, id, id) - Three object arguments
370// -----------------------------------------------------------------------------
371
372/// Block type: `void (^)(id, id, id)`
373///
374/// Used for completion handlers with reflection:
375/// `void (^)(MTL::RenderPipelineState*, MTL::RenderPipelineReflection*, NS::Error*)`
376#[allow(clippy::type_complexity)]
377pub type ThreeArgBlock = RcBlock<Box<dyn Fn(*mut c_void, *mut c_void, *mut c_void) + Send>>;
378
379#[allow(clippy::type_complexity)]
380impl ThreeArgBlock {
381 /// Create a block from a closure taking three pointer arguments.
382 ///
383 /// This matches the C++ pattern:
384 /// ```cpp
385 /// using NewRenderPipelineStateWithReflectionCompletionHandler =
386 /// void (^)(MTL::RenderPipelineState*, MTL::RenderPipelineReflection*, NS::Error*);
387 /// ```
388 pub fn from_fn<F>(f: F) -> Self
389 where
390 F: Fn(*mut c_void, *mut c_void, *mut c_void) + Send + 'static,
391 {
392 #[allow(clippy::type_complexity)]
393 unsafe extern "C" fn invoke(
394 block: *mut BlockLiteral<Box<dyn Fn(*mut c_void, *mut c_void, *mut c_void) + Send>>,
395 arg1: *mut c_void,
396 arg2: *mut c_void,
397 arg3: *mut c_void,
398 ) {
399 unsafe { ((*block).closure)(arg1, arg2, arg3) }
400 }
401
402 unsafe { RcBlock::new(Box::new(f), invoke as *const c_void) }
403 }
404}
405
406// -----------------------------------------------------------------------------
407// void (^)(id, uint64_t) - Object and value arguments
408// -----------------------------------------------------------------------------
409
410/// Block type: `void (^)(id, uint64_t)`
411///
412/// Used for SharedEvent notifications: `void (^)(MTL::SharedEvent*, uint64_t)`
413pub type EventBlock = RcBlock<Box<dyn Fn(*mut c_void, u64) + Send>>;
414
415impl EventBlock {
416 /// Create a block for SharedEvent notifications.
417 ///
418 /// This matches the C++ pattern for event signaling.
419 pub fn from_fn<F>(f: F) -> Self
420 where
421 F: Fn(*mut c_void, u64) + Send + 'static,
422 {
423 unsafe extern "C" fn invoke(
424 block: *mut BlockLiteral<Box<dyn Fn(*mut c_void, u64) + Send>>,
425 event: *mut c_void,
426 value: u64,
427 ) {
428 unsafe { ((*block).closure)(event, value) }
429 }
430
431 unsafe { RcBlock::new(Box::new(f), invoke as *const c_void) }
432 }
433}
434
435// -----------------------------------------------------------------------------
436// void (^)(void*, size_t) - Buffer deallocator
437// -----------------------------------------------------------------------------
438
439/// Block type: `void (^)(void*, size_t)`
440///
441/// Used for buffer deallocators in `newBufferWithBytesNoCopy:...deallocator:`.
442pub type DeallocatorBlock = RcBlock<Box<dyn Fn(*mut c_void, usize) + Send>>;
443
444impl DeallocatorBlock {
445 /// Create a block for buffer deallocation.
446 ///
447 /// The closure receives the buffer pointer and its size.
448 pub fn from_fn<F>(f: F) -> Self
449 where
450 F: Fn(*mut c_void, usize) + Send + 'static,
451 {
452 unsafe extern "C" fn invoke(
453 block: *mut BlockLiteral<Box<dyn Fn(*mut c_void, usize) + Send>>,
454 ptr: *mut c_void,
455 size: usize,
456 ) {
457 unsafe { ((*block).closure)(ptr, size) }
458 }
459
460 unsafe { RcBlock::new(Box::new(f), invoke as *const c_void) }
461 }
462}
463
464// =============================================================================
465// Type Aliases matching metal-cpp naming
466// =============================================================================
467
468/// Completion handler for library creation.
469///
470/// C++ equivalent: `using NewLibraryCompletionHandler = void (^)(MTL::Library*, NS::Error*);`
471pub type NewLibraryCompletionHandler = TwoArgBlock;
472
473/// Completion handler for render pipeline state creation.
474///
475/// C++ equivalent: `using NewRenderPipelineStateCompletionHandler = void (^)(MTL::RenderPipelineState*, NS::Error*);`
476pub type NewRenderPipelineStateCompletionHandler = TwoArgBlock;
477
478/// Completion handler for compute pipeline state creation.
479///
480/// C++ equivalent: `using NewComputePipelineStateCompletionHandler = void (^)(MTL::ComputePipelineState*, NS::Error*);`
481pub type NewComputePipelineStateCompletionHandler = TwoArgBlock;
482
483/// Completion handler for render pipeline state with reflection.
484///
485/// C++ equivalent: `using NewRenderPipelineStateWithReflectionCompletionHandler =
486/// void (^)(MTL::RenderPipelineState*, MTL::RenderPipelineReflection*, NS::Error*);`
487pub type NewRenderPipelineStateWithReflectionCompletionHandler = ThreeArgBlock;
488
489/// Completion handler for compute pipeline state with reflection.
490///
491/// C++ equivalent: `using NewComputePipelineStateWithReflectionCompletionHandler =
492/// void (^)(MTL::ComputePipelineState*, MTL::ComputePipelineReflection*, NS::Error*);`
493pub type NewComputePipelineStateWithReflectionCompletionHandler = ThreeArgBlock;
494
495/// Handler for command buffer completion.
496///
497/// Used with `addCompletedHandler:` and `addScheduledHandler:`.
498pub type CommandBufferHandler = OneArgBlock;
499
500/// Handler for drawable presentation.
501///
502/// Used with `addPresentedHandler:`.
503pub type DrawablePresentedHandler = OneArgBlock;
504
505/// Handler for device notifications.
506///
507/// C++ equivalent: `using DeviceNotificationHandlerBlock =
508/// void (^)(MTL::Device* pDevice, MTL::DeviceNotificationName notifyName);`
509pub type DeviceNotificationHandler = TwoArgBlock;
510
511/// Handler for SharedEvent notifications.
512pub type SharedEventNotificationHandler = EventBlock;
513
514// -----------------------------------------------------------------------------
515// void (^)(id, id, isize, id) - Four arguments (for LogState)
516// -----------------------------------------------------------------------------
517
518/// Block type: `void (^)(id, id, isize, id)`
519///
520/// Used for log handlers: `void (^)(NS::String*, NS::String*, LogLevel, NS::String*)`
521/// LogLevel in Metal is NS::Integer (isize on 64-bit platforms).
522#[allow(clippy::type_complexity)]
523pub type LogHandlerBlock =
524 RcBlock<Box<dyn Fn(*mut c_void, *mut c_void, isize, *mut c_void) + Send>>;
525
526#[allow(clippy::type_complexity)]
527impl LogHandlerBlock {
528 /// Create a block for log handling.
529 ///
530 /// The closure receives (subsystem, category, level, message).
531 pub fn from_fn<F>(f: F) -> Self
532 where
533 F: Fn(*mut c_void, *mut c_void, isize, *mut c_void) + Send + 'static,
534 {
535 #[allow(clippy::type_complexity)]
536 unsafe extern "C" fn invoke(
537 block: *mut BlockLiteral<
538 Box<dyn Fn(*mut c_void, *mut c_void, isize, *mut c_void) + Send>,
539 >,
540 subsystem: *mut c_void,
541 category: *mut c_void,
542 level: isize,
543 message: *mut c_void,
544 ) {
545 unsafe { ((*block).closure)(subsystem, category, level, message) }
546 }
547
548 unsafe { RcBlock::new(Box::new(f), invoke as *const c_void) }
549 }
550}
551
552// =============================================================================
553// Tests
554// =============================================================================
555
556#[cfg(test)]
557mod tests {
558 use super::*;
559
560 #[test]
561 fn test_block_literal_size() {
562 // Verify the block literal has the expected layout
563 assert!(mem::size_of::<BlockLiteral<()>>() > 0);
564 // isa (8) + flags (4) + reserved (4) + invoke (8) + descriptor (8) + closure
565 assert_eq!(
566 mem::size_of::<BlockLiteral<()>>(),
567 8 + 4 + 4 + 8 + 8 // 32 bytes minimum on 64-bit
568 );
569 }
570
571 #[test]
572 fn test_void_block_creation() {
573 let called = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
574 let called_clone = called.clone();
575
576 let block = VoidBlock::from_fn(move || {
577 called_clone.store(true, std::sync::atomic::Ordering::SeqCst);
578 });
579
580 assert!(!block.as_ptr().is_null());
581 }
582
583 #[test]
584 fn test_two_arg_block_creation() {
585 let block = TwoArgBlock::from_fn(|_obj, _err| {
586 // Callback logic
587 });
588
589 assert!(!block.as_ptr().is_null());
590 }
591
592 #[test]
593 fn test_event_block_creation() {
594 let block = EventBlock::from_fn(|_event, _value| {
595 // Closure will be called with event and value
596 });
597
598 assert!(!block.as_ptr().is_null());
599 }
600
601 #[test]
602 fn test_block_into_raw() {
603 let block = VoidBlock::from_fn(|| {});
604 let ptr = block.into_raw();
605 assert!(!ptr.is_null());
606 // Note: memory is intentionally leaked here for Metal to manage
607 }
608}