Skip to main content

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}