1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
//! # Raw bindings to `Block.h`

use core::cell::UnsafeCell;
use core::ffi::c_void;
use core::marker::{PhantomData, PhantomPinned};
use std::os::raw::c_int;

/// Type for block class ISAs.
///
/// This will likely become an extern type in the future.
#[repr(C)]
#[allow(missing_debug_implementations)]
pub struct Class {
    /// The size probably doesn't really matter here, as we only ever use the
    /// classes behind pointers, but let's import it with the correct size to
    /// be sure.
    ///
    /// This applies with the compiler-rt runtime and with Apple's runtime.
    #[cfg(not(any(feature = "gnustep-1-7", feature = "unstable-objfw")))]
    _priv: [*mut c_void; 32],

    /// The size of this is unknown, so let's use a ZST so the compiler
    /// doesn't assume anything about the size.
    #[cfg(any(feature = "gnustep-1-7", feature = "unstable-objfw"))]
    _priv: [u8; 0],

    /// Mark as `!Send + !Sync + !Unpin` and as mutable behind shared
    /// references (`!Freeze`).
    ///
    /// Same as `objc_sys::OpaqueData`.
    _opaque: UnsafeCell<PhantomData<(*const UnsafeCell<()>, PhantomPinned)>>,
}

// TODO: Use `extern "C-unwind"` when in MSRV (because the runtime functions
// may call external routines).
extern "C" {
    /// Class ISA used for global blocks.
    pub static _NSConcreteGlobalBlock: Class;

    /// Class ISA used for stack blocks.
    pub static _NSConcreteStackBlock: Class;

    /// Copy/retain a block.
    ///
    /// When called on a:
    /// - Global block: Does nothing.
    /// - Stack block: `memmove`s the block to a new heap allocation, calls
    ///   the copy helper, and returns the new malloc block.
    /// - Malloc block: Increments the retain count.
    ///
    /// Returns `NULL` on allocation failure.
    #[doc(alias = "Block_copy")]
    pub fn _Block_copy(block: *const c_void) -> *mut c_void;

    /// Release a block.
    ///
    /// When called on a:
    /// - Global block: Does nothing.
    /// - Stack block: Does nothing.
    /// - Malloc block: Decrements the retain count, and if it reaches zero,
    ///   calls the dispose helper and frees the underlying storage.
    #[doc(alias = "Block_release")]
    pub fn _Block_release(block: *const c_void);

    /// Copy a block field or `__block` variable from one location to another.
    ///
    /// Called by C compilers to clone fields inside copy helper routines, and
    /// to handle memory management of `__block` marked variables.
    pub fn _Block_object_assign(dest_addr: *mut c_void, object: *const c_void, flags: c_int);

    /// Dispose an object previously copied using `_Block_object_assign`.
    ///
    /// Called by C compilers to drop fields inside dispose helper routines,
    /// and handle memory management of `__block` marked variables.
    pub fn _Block_object_dispose(object: *const c_void, flags: c_int);
}

/// `Block_private.h`
#[allow(missing_docs)]
#[cfg(any(test, feature = "unstable-private"))]
pub mod private {
    use super::*;
    #[cfg(any(doc, target_vendor = "apple", feature = "gnustep-1-7"))]
    use std::os::raw::c_char;
    #[cfg(any(doc, target_vendor = "apple", feature = "compiler-rt"))]
    use std::os::raw::c_ulong;

    extern "C" {
        pub static _NSConcreteMallocBlock: Class;
        #[cfg(any(doc, target_vendor = "apple", feature = "compiler-rt"))]
        pub static _NSConcreteAutoBlock: Class;
        #[cfg(any(doc, target_vendor = "apple", feature = "compiler-rt"))]
        pub static _NSConcreteFinalizingBlock: Class;
        #[cfg(any(doc, target_vendor = "apple", feature = "compiler-rt"))]
        pub static _NSConcreteWeakBlockVariable: Class;

        #[cfg(any(doc, target_vendor = "apple", feature = "compiler-rt"))]
        pub fn Block_size(block: *mut c_void) -> c_ulong; // usize

        // Whether the return value of the block is on the stack.
        // macOS 10.7
        #[cfg(any(doc, target_vendor = "apple"))]
        pub fn _Block_use_stret(block: *mut c_void) -> bool;

        // Returns a string describing the block's GC layout.
        // This uses the GC skip/scan encoding.
        // May return NULL.
        // macOS 10.7
        #[cfg(any(doc, target_vendor = "apple"))]
        pub fn _Block_layout(block: *mut c_void) -> *const c_char;

        // Returns a string describing the block's layout.
        // This uses the "extended layout" form described above.
        // May return NULL.
        // macOS 10.8
        #[cfg(any(doc, target_vendor = "apple"))]
        pub fn _Block_extended_layout(block: *mut c_void) -> *const c_char;

        // Callable only from the ARR weak subsystem while in exclusion zone
        // macOS 10.7
        #[cfg(any(doc, target_vendor = "apple"))]
        pub fn _Block_tryRetain(block: *const c_void) -> bool;

        // Callable only from the ARR weak subsystem while in exclusion zone
        // macOS 10.7
        #[cfg(any(doc, target_vendor = "apple"))]
        pub fn _Block_isDeallocating(block: *const c_void) -> bool;

        // indicates whether block was compiled with compiler that sets the ABI
        // related metadata bits
        // macOS 10.7
        #[cfg(any(doc, target_vendor = "apple", feature = "gnustep-1-7"))]
        pub fn _Block_has_signature(block: *mut c_void) -> bool;

        // Returns a string describing the block's parameter and return types.
        // The encoding scheme is the same as Objective-C @encode.
        // Returns NULL for blocks compiled with some compilers.
        // macOS 10.7
        #[cfg(any(doc, target_vendor = "apple", feature = "gnustep-1-7"))]
        pub fn _Block_signature(block: *mut c_void) -> *const c_char;
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use core::ptr;
    use std::println;

    #[test]
    fn smoke() {
        assert_eq!(unsafe { _Block_copy(ptr::null_mut()) }, ptr::null_mut());
        unsafe { _Block_release(ptr::null_mut()) };
    }

    #[test]
    fn test_linkable() {
        println!("{:?}", unsafe { ptr::addr_of!(_NSConcreteGlobalBlock) });
        println!("{:?}", unsafe { ptr::addr_of!(_NSConcreteStackBlock) });
        println!("{:?}", unsafe {
            ptr::addr_of!(private::_NSConcreteMallocBlock)
        });
        println!("{:p}", _Block_copy as unsafe extern "C" fn(_) -> _);
        println!(
            "{:p}",
            _Block_object_assign as unsafe extern "C" fn(_, _, _)
        );
        println!("{:p}", _Block_object_dispose as unsafe extern "C" fn(_, _));
        println!("{:p}", _Block_release as unsafe extern "C" fn(_));
    }
}