Skip to main content

cidre/
blocks.rs

1// https://opensource.apple.com/source/libclosure/libclosure-79/BlockImplementation.txt.auto.html
2// https://github.com/apple-oss-distributions/libclosure/blob/main/BlockImplementation.txt
3// https://developer.apple.com/documentation/swift/calling-objective-c-apis-asynchronously
4// https://github.com/apple/swift-corelibs-foundation/blob/main/Sources/BlocksRuntime/runtime.c
5
6use std::{
7    ffi::c_void, marker::PhantomData, marker::Send as MarkerSend, marker::Sync as MarkerSync, mem,
8};
9
10use crate::{arc, define_opts, ns, objc};
11
12#[cfg(feature = "custom-allocator")]
13use crate::cf;
14
15// block attributes
16
17#[derive(Debug)]
18pub struct NoEsc;
19#[derive(Debug)]
20pub struct Esc;
21#[derive(Debug)]
22pub struct Send;
23#[derive(Debug)]
24pub struct Sync;
25
26// attributted blocks
27
28pub type NoEscBlock<F> = Block<F, NoEsc>;
29pub type EscBlock<F> = Block<F, Esc>;
30pub type SendBlock<F> = Block<F, Send>;
31pub type SyncBlock<F> = Block<F, Sync>;
32
33pub type CompletionBlock = EscBlock<fn()>;
34pub type WorkBlock<Attr = Sync> = Block<fn(), Attr>;
35
36/// Error Completion Handler
37pub type ErrCh<E = ns::Error> = EscBlock<fn(error: Option<&E>)>;
38
39/// Result Completion Handler
40pub type ResultCh<T> = EscBlock<fn(Option<&T>, Option<&ns::Error>)>;
41
42#[derive(Debug)]
43#[repr(transparent)]
44pub struct Block<Sig, Attr = NoEsc>(ns::Id, PhantomData<(Sig, Attr)>);
45
46#[derive(Debug)]
47#[repr(transparent)]
48pub struct StackBlock<'a, Closure, Sig>(Layout1Mut<'a, Closure>, PhantomData<Sig>);
49
50#[derive(Debug)]
51#[repr(transparent)]
52pub struct StaticBlock<Sig>(Layout1, PhantomData<Sig>);
53
54impl<Sig> std::ops::Deref for Block<Sig, NoEsc> {
55    type Target = ns::Id;
56
57    fn deref(&self) -> &Self::Target {
58        unsafe { std::mem::transmute(self) }
59    }
60}
61
62impl<Sig, Attr> objc::Obj for Block<Sig, Attr> {
63    #[inline]
64    unsafe fn retain(id: &Self) -> arc::R<Self> {
65        unsafe { std::mem::transmute(_Block_copy(std::mem::transmute(id))) }
66    }
67
68    #[inline]
69    unsafe fn release(id: &mut Self) {
70        unsafe { _Block_release(std::mem::transmute(id)) }
71    }
72}
73
74impl<'a, Closure, Sig> std::ops::Deref for StackBlock<'a, Closure, Sig> {
75    type Target = Block<Sig, NoEsc>;
76
77    fn deref(&self) -> &Self::Target {
78        unsafe { std::mem::transmute(self) }
79    }
80}
81
82impl<'a, Closure, Sig> std::ops::DerefMut for StackBlock<'a, Closure, Sig> {
83    fn deref_mut(&mut self) -> &mut Self::Target {
84        unsafe { std::mem::transmute(self) }
85    }
86}
87
88macro_rules! call {
89    ($($a:ident: $t:ident),*) => {
90        impl<$($t,)* R, Attr> Block<fn($($t,)*) -> R, Attr> {
91            pub fn call(&mut self, $($a: $t),*) -> R {
92                let layout: &Layout1 = unsafe { std::mem::transmute(&self.0) };
93                let f: extern "C" fn(literal: &mut Self $(, $t)*) -> R = unsafe { std::mem::transmute(layout.invoke) };
94                f(self $(, $a)*)
95            }
96        }
97    };
98}
99
100macro_rules! invoke {
101    ($name:ident: $($a:ident: $t:ident),*) => {
102        extern "C" fn $name<$($t,)* R>(&mut self, $($a: $t),*) -> R
103        where
104            Closure: FnMut($($t,)*) -> R,
105        {
106            (self.closure)($($a,)*)
107        }
108    };
109}
110
111macro_rules! new {
112    ($name:ident, $invoke:ident: $($t:ident),* $(+ $l:lifetime)* $(+ $trait:ident)*) => {
113        pub fn $name<$($t,)* R, Closure>(closure: Closure) -> arc::R<Self>
114        where
115            Sig: Fn($($t,)*) -> R, // guard for Block Sig
116            for<'c> Closure: FnMut($($t,)*) -> R $(+ $l)* $(+ $trait)*,
117        {
118            let res = Layout2Mut::new(Layout2Mut::<Closure>::$invoke as _, closure);
119            unsafe { std::mem::transmute(res) }
120        }
121    };
122}
123
124macro_rules! with_fn {
125    ($name:ident: $($t:ident),* ) => {
126        pub const fn $name<$($t,)* R>(func: extern "C" fn (*const c_void, $($t,)*) -> R) -> StaticBlock<Sig>
127        {
128            let res = Layout1::with(func as _);
129            StaticBlock(res, PhantomData)
130        }
131    };
132}
133
134macro_rules! stack {
135    ($name:ident, $invoke:ident: $($t:ident),*) => {
136        #[inline]
137        pub const unsafe fn $name<$($t,)* R, Closure>(closure: &mut Closure) -> StackBlock<'_, Closure, Sig>
138        where
139            Sig: Fn($($t,)*) -> R, // guard for Block Sig
140            for<'c> Closure: FnMut($($t,)*) -> R
141        {
142            let layout = Layout1Mut::new(Layout1Mut::<Closure>::$invoke as _, closure);
143            StackBlock(layout, PhantomData)
144        }
145    };
146}
147
148impl<Sig, Attr> Block<Sig, Attr> {
149    with_fn!(with_fn0:);
150    with_fn!(with_fn1: A);
151    with_fn!(with_fn2: A, B);
152    with_fn!(with_fn3: A, B, C);
153    with_fn!(with_fn4: A, B, C, D);
154    with_fn!(with_fn5: A, B, C, D, E);
155    with_fn!(with_fn6: A, B, C, D, E, F);
156}
157
158impl<Sig> Block<Sig, NoEsc> {
159    stack!(stack0, invoke0:);
160    stack!(stack1, invoke1: A);
161    stack!(stack2, invoke2: A, B);
162    stack!(stack3, invoke3: A, B, C);
163    stack!(stack4, invoke4: A, B, C, D);
164    stack!(stack5, invoke5: A, B, C, D, E);
165    stack!(stack6, invoke6: A, B, C, D, E, F);
166
167    new!(new0, invoke0:);
168    new!(new1, invoke1: A);
169    new!(new2, invoke2: A, B);
170    new!(new3, invoke3: A, B, C);
171    new!(new4, invoke4: A, B, C, D);
172    new!(new5, invoke5: A, B, C, D, E);
173    new!(new6, invoke6: A, B, C, D, E, F);
174}
175
176impl<Sig> Block<Sig, Esc> {
177    new!(new0, invoke0: + 'static);
178    new!(new1, invoke1: A + 'static);
179    new!(new2, invoke2: A, B + 'static);
180    new!(new3, invoke3: A, B, C + 'static);
181    new!(new4, invoke4: A, B, C, D + 'static);
182    new!(new5, invoke5: A, B, C, D, E + 'static);
183    new!(new6, invoke6: A, B, C, D, E, F + 'static);
184
185    pub fn as_noesc_mut(&mut self) -> &mut Block<Sig, NoEsc> {
186        unsafe { std::mem::transmute(self) }
187    }
188}
189
190impl<Sig> Block<Sig, Send> {
191    new!(new0, invoke0: + 'static + MarkerSend);
192    new!(new1, invoke1: A + 'static + MarkerSend);
193    new!(new2, invoke2: A, B + 'static + MarkerSend);
194    new!(new3, invoke3: A, B, C + 'static + MarkerSend);
195    new!(new4, invoke4: A, B, C, D + 'static + MarkerSend);
196    new!(new5, invoke5: A, B, C, D, E + 'static + MarkerSend);
197    new!(new6, invoke6: A, B, C, D, E, F + 'static + MarkerSend);
198
199    pub fn as_esc_mut(&mut self) -> &mut Block<Sig, Esc> {
200        unsafe { std::mem::transmute(self) }
201    }
202
203    pub fn as_noesc_mut(&mut self) -> &mut Block<Sig, NoEsc> {
204        unsafe { std::mem::transmute(self) }
205    }
206}
207
208impl<Sig> Block<Sig, Sync> {
209    new!(new0, invoke0: + 'static + MarkerSync);
210    new!(new1, invoke1: A + 'static + MarkerSync);
211    new!(new2, invoke2: A, B + 'static + MarkerSync);
212    new!(new3, invoke3: A, B, C + 'static + MarkerSync);
213    new!(new4, invoke4: A, B, C, D + 'static + MarkerSync);
214    new!(new5, invoke5: A, B, C, D, E + 'static + MarkerSync);
215    new!(new6, invoke6: A, B, C, D, E, F + 'static + MarkerSync);
216
217    pub fn as_send_mut(&mut self) -> &mut Block<Sig, Send> {
218        unsafe { std::mem::transmute(self) }
219    }
220
221    pub fn as_esc_mut(&mut self) -> &mut Block<Sig, Esc> {
222        unsafe { std::mem::transmute(self) }
223    }
224
225    pub fn as_noesc_mut(&mut self) -> &mut Block<Sig, NoEsc> {
226        unsafe { std::mem::transmute(self) }
227    }
228}
229
230impl<Sig> StaticBlock<Sig> {
231    with_fn!(new0:);
232    with_fn!(new1: A);
233    with_fn!(new2: A, B);
234    with_fn!(new3: A, B, C);
235    with_fn!(new4: A, B, C, D);
236    with_fn!(new5: A, B, C, D, E);
237    with_fn!(new6: A, B, C, D, E, F);
238
239    pub fn as_sync_mut(&mut self) -> &mut Block<Sig, Sync> {
240        unsafe { std::mem::transmute(self) }
241    }
242
243    pub fn as_send_mut(&mut self) -> &mut Block<Sig, Send> {
244        unsafe { std::mem::transmute(self) }
245    }
246
247    pub fn as_esc_mut(&mut self) -> &mut Block<Sig, Esc> {
248        unsafe { std::mem::transmute(self) }
249    }
250
251    pub fn as_noesc_mut(&mut self) -> &mut Block<Sig, NoEsc> {
252        unsafe { std::mem::transmute(self) }
253    }
254}
255
256call!();
257call!(a:A);
258call!(a:A, b: B);
259call!(a:A, b: B, c: C);
260call!(a:A, b: B, c: C, d: D);
261call!(a:A, b: B, c: C, d: D, e: E);
262call!(a:A, b: B, c: C, d: D, e: E, f: F);
263call!(a:A, b: B, c: C, d: D, e: E, f: F, g: G);
264call!(a:A, b: B, c: C, d: D, e: E, f: F, g: G, h: H);
265call!(a:A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I);
266call!(a:A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J);
267call!(a:A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K);
268
269impl<A, Attr> Block<fn(a: Option<&A>), Attr> {
270    #[inline]
271    pub fn handle(&mut self, a: Option<&A>) {
272        let layout: &Layout1 = unsafe { std::mem::transmute(&self.0) };
273        let f: extern "C" fn(literal: &mut Self, a: Option<&A>) =
274            unsafe { std::mem::transmute(layout.invoke) };
275        f(self, a)
276    }
277}
278
279impl<A, B, Attr> Block<fn(a: Option<&A>, b: Option<&B>), Attr> {
280    #[inline]
281    pub fn handle(&mut self, a: Option<&A>, b: Option<&B>) {
282        let layout: &Layout1 = unsafe { std::mem::transmute(&self.0) };
283        let f: extern "C" fn(literal: &mut Self, a: Option<&A>, b: Option<&B>) =
284            unsafe { std::mem::transmute(layout.invoke) };
285        f(self, a, b)
286    }
287}
288
289//         // TODO: revisit
290//         unsafe impl<'a, $($t,)* R> Send for $bl_name<'a$(, $t)*, R> {}
291//         unsafe impl<'a, $($t,)* R> Sync for $bl_name<'a$(, $t)*, R> {}
292
293define_opts!(pub Flags(i32));
294
295impl Flags {
296    pub const NONE: Self = Self(0);
297
298    // runtime
299    pub const DEALLOCATING: Self = Self(1);
300
301    // runtime
302    pub const REFCOUNT_MASK: Self = Self(0xfffei32);
303
304    // compiler
305    // Set to true on blocks that have captures (and thus are not true
306    // global blocks) but are known not to escape for various other
307    // reasons. For backward compatibility with old runtimes, whenever
308    // IS_NOESCAPE is set, IS_GLOBAL is set too. Copying a
309    // non-escaping block returns the original block and releasing such a
310    // block is a no-op, which is exactly how global blocks are handled.
311    // pub const IS_NOESCAPE: Self = Self(1 << 23);
312
313    // runtime
314    pub const NEEDS_FREE: Self = Self(1 << 24);
315    // compiler
316    pub const HAS_COPY_DISPOSE: Self = Self(1 << 25);
317    // pub const HAS_CTOR: Self = Self(1 << 26);
318    // pub const IS_GC: Self = Self(1 << 27);
319    // pub const IS_GLOBAL: Self = Self(1 << 28);
320    // pub const USE_STRET: Self = Self(1 << 29);
321    // pub const HAS_SIGNATURE: Self = Self(1 << 30);
322    // pub const HAS_EXTENDED_LAYOUT: Self = Self(1 << 31);
323
324    const RETAINED_NEEDS_FREE: Self = Self(2 | Self::NEEDS_FREE.0);
325    const RETAINED_NEEDS_DROP: Self = Self(2 | Self::NEEDS_FREE.0 | Self::HAS_COPY_DISPOSE.0);
326}
327
328#[derive(Debug)]
329#[repr(C)]
330pub struct Desc1 {
331    reserved: usize,
332    size: usize,
333}
334
335#[derive(Debug)]
336#[repr(C)]
337pub struct Desc2<T: Sized> {
338    descriptor1: Desc1,
339    copy: extern "C" fn(dest: *mut c_void, src: *mut c_void),
340    dispose: extern "C" fn(literal: &mut T),
341}
342
343#[derive(Debug)]
344#[repr(C)]
345pub struct Layout1 {
346    isa: &'static objc::Class<ns::Id>,
347    flags: Flags,
348    reserved: i32,
349    invoke: *const c_void,
350    descriptor: &'static Desc1,
351}
352
353#[derive(Debug)]
354#[repr(C)]
355pub struct Layout1Mut<'a, Closure> {
356    isa: &'static objc::Class<ns::Id>,
357    flags: Flags,
358    reserved: i32,
359    invoke: *const c_void,
360    descriptor: &'a Desc1,
361    closure: &'a mut Closure,
362}
363
364#[derive(Debug)]
365#[repr(C)]
366struct Layout2Mut<'a, F: Sized + 'a> {
367    isa: &'static objc::Class<ns::Id>,
368    flags: Flags,
369    reserved: i32,
370    invoke: *const c_void,
371    descriptor: &'a Desc2<Self>,
372    closure: mem::ManuallyDrop<F>,
373}
374
375impl Layout1 {
376    const DESCRIPTOR: Desc1 = Desc1 {
377        reserved: 0,
378        size: std::mem::size_of::<Self>(),
379    };
380
381    pub const fn with(invoke: *const c_void) -> Self {
382        Self {
383            isa: unsafe { &_NSConcreteStackBlock },
384            flags: Flags::NONE,
385            reserved: 0,
386            invoke,
387            descriptor: &Self::DESCRIPTOR,
388        }
389    }
390}
391
392impl<'a, Closure> Layout1Mut<'a, Closure> {
393    const DESCRIPTOR_1: Desc1 = Desc1 {
394        reserved: 0,
395        size: std::mem::size_of::<&'static objc::Class<ns::Id>>()
396            + std::mem::size_of::<Flags>()
397            + std::mem::size_of::<i32>()
398            + std::mem::size_of::<*const c_void>()
399            + std::mem::size_of::<&'static Desc1>()
400            + std::mem::size_of::<&'static c_void>(), // emulating &mut F
401    };
402
403    invoke! {invoke0: }
404    invoke! {invoke1: a: A}
405    invoke! {invoke2: a: A, b: B}
406    invoke! {invoke3: a: A, b: B, c: C}
407    invoke! {invoke4: a: A, b: B, c: C, d: D}
408    invoke! {invoke5: a: A, b: B, c: C, d: D, e: E}
409    invoke! {invoke6: a: A, b: B, c: C, d: D, e: E, f: F}
410
411    const fn new(invoke: *const c_void, f: &'a mut Closure) -> Self {
412        Self {
413            isa: unsafe { &_NSConcreteStackBlock },
414            flags: Flags::NONE,
415            reserved: 0,
416            invoke,
417            descriptor: &Self::DESCRIPTOR_1,
418            closure: f,
419        }
420    }
421}
422
423extern "C" fn no_copy(_dest: *mut c_void, _src: *mut c_void) {
424    panic!("copy should not be called");
425}
426
427impl<'a, Closure: 'a + Sized> Layout2Mut<'a, Closure> {
428    const DESCRIPTOR_2: Desc2<Self> = Desc2 {
429        descriptor1: Desc1 {
430            reserved: 0,
431            size: std::mem::size_of::<Self>(),
432        },
433        copy: no_copy,
434        dispose: Self::dispose,
435    };
436
437    extern "C" fn dispose(block: &mut Self) {
438        debug_assert!(mem::needs_drop::<Closure>());
439        unsafe {
440            mem::ManuallyDrop::drop(&mut block.closure);
441        }
442    }
443
444    invoke! {invoke0: }
445    invoke! {invoke1: a: A}
446    invoke! {invoke2: a: A, b: B}
447    invoke! {invoke3: a: A, b: B, c: C}
448    invoke! {invoke4: a: A, b: B, c: C, d: D}
449    invoke! {invoke5: a: A, b: B, c: C, d: D, e: E}
450    invoke! {invoke6: a: A, b: B, c: C, d: D, e: E, f: F}
451
452    fn new(invoke: *const c_void, closure: Closure) -> &'a mut Self {
453        let flags = if mem::needs_drop::<Closure>() {
454            Flags::RETAINED_NEEDS_DROP
455        } else {
456            Flags::RETAINED_NEEDS_FREE
457        };
458
459        #[cfg(not(feature = "custom-allocator"))]
460        {
461            // we assume allocator is malloc. So it is safe
462            // to allocate with Box::new and leak
463            // so _Block_release will be able to free mem
464            let block = Box::new(Self {
465                isa: unsafe { &_NSConcreteMallocBlock },
466                flags,
467                reserved: 0,
468                invoke,
469                descriptor: &Self::DESCRIPTOR_2,
470                closure: mem::ManuallyDrop::new(closure),
471            });
472            Box::leak(block)
473        }
474        #[cfg(feature = "custom-allocator")]
475        {
476            // We can't use Box::new since global allocator could be changed.
477            // We use cf::Allocator to allocate block
478            // so _Block_release will be able to free mem
479            //
480            // Another option is to use _Block_copy from stacked block but
481            // it is another few function calls
482            let block = Self {
483                isa: unsafe { &_NSConcreteMallocBlock },
484                flags,
485                reserved: 0,
486                invoke,
487                descriptor: &Self::DESCRIPTOR_2,
488                closure: mem::ManuallyDrop::new(closure),
489            };
490
491            let layout = std::alloc::Layout::new::<Self>();
492
493            unsafe {
494                let ptr = cf::Allocator::allocate_size(layout.size());
495                *(ptr as *mut Self) = block;
496                std::mem::transmute(ptr)
497            }
498        }
499    }
500}
501
502#[link(name = "System", kind = "dylib")]
503unsafe extern "C-unwind" {
504    // static _NSConcreteGlobalBlock: objc::Class<ns::Id>;
505    static _NSConcreteStackBlock: objc::Class<ns::Id>;
506    static _NSConcreteMallocBlock: objc::Class<ns::Id>;
507
508    fn _Block_copy(block: *const c_void) -> *const c_void;
509    fn _Block_release(block: *const c_void);
510}
511
512#[cfg(test)]
513mod tests {
514
515    use crate::{blocks, dispatch};
516
517    #[derive(Debug)]
518    struct Foo;
519
520    impl Drop for Foo {
521        fn drop(&mut self) {
522            println!("dropped foo");
523        }
524    }
525
526    #[test]
527    fn simple_block() {
528        let foo = Foo;
529        // let rc = Rc::new(10);
530        let mut b = dispatch::Block::<blocks::Send>::new0(move || println!("nice {foo:?}"));
531
532        let q = dispatch::Queue::new();
533        q.async_b(&mut b);
534        q.async_b(&mut b);
535        q.async_mut(|| println!("nice"));
536        q.sync_mut(|| println!("fuck"));
537        // q.async_once(move || println!("nice {rc:?}"));
538
539        println!("finished");
540    }
541}
542
543#[cfg(feature = "async")]
544use parking_lot::Mutex;
545
546#[cfg(feature = "async")]
547use std::sync::Arc;
548
549#[cfg(feature = "async")]
550pub(crate) struct Shared<T> {
551    ready: Option<T>,
552    pending: Option<std::task::Waker>,
553}
554
555#[cfg(feature = "async")]
556impl<T> Shared<T> {
557    pub(crate) fn new() -> Arc<Mutex<Self>> {
558        Arc::new(Mutex::new(Self {
559            ready: None,
560            pending: None,
561        }))
562    }
563
564    pub fn ready(&mut self, result: T) {
565        self.ready = Some(result);
566
567        if let Some(waker) = self.pending.take() {
568            waker.wake();
569        }
570    }
571}
572
573#[cfg(feature = "async")]
574pub struct Completion<R>(pub(crate) Arc<Mutex<Shared<R>>>);
575
576#[cfg(feature = "async")]
577impl<T> std::future::Future for Completion<T> {
578    type Output = T;
579
580    fn poll(
581        self: std::pin::Pin<&mut Self>,
582        cx: &mut std::task::Context<'_>,
583    ) -> std::task::Poll<Self::Output> {
584        let mut lock = self.0.lock();
585
586        if let Some(r) = lock.ready.take() {
587            std::task::Poll::Ready(r)
588        } else {
589            lock.pending = Some(cx.waker().clone());
590            std::task::Poll::Pending
591        }
592    }
593}
594
595#[cfg(feature = "async")]
596pub fn comp0() -> (Completion<()>, arc::R<CompletionBlock>) {
597    let shared = Shared::new();
598    (
599        Completion(shared.clone()),
600        CompletionBlock::new0(move || shared.lock().ready(())),
601    )
602}
603
604#[cfg(feature = "async")]
605pub fn comp1<R: std::marker::Send>() -> (Completion<R>, arc::R<Block<fn(R), Send>>) {
606    let shared = Shared::new();
607    (
608        Completion(shared.clone()),
609        SendBlock::new1(move |v: R| shared.lock().ready(v)),
610    )
611}
612
613#[cfg(feature = "async")]
614pub fn retained1<R: arc::Retain + std::marker::Send>()
615-> (Completion<arc::R<R>>, arc::R<Block<fn(&R), Send>>) {
616    let shared = Shared::new();
617    (
618        Completion(shared.clone()),
619        SendBlock::new1(move |v: &R| shared.lock().ready(v.retained())),
620    )
621}
622
623#[cfg(feature = "async")]
624pub fn ok<'a>() -> (Completion<Result<(), arc::R<ns::Error>>>, arc::R<ErrCh>) {
625    let shared = Shared::new();
626    (
627        Completion(shared.clone()),
628        ErrCh::new1(move |error: Option<&ns::Error>| {
629            shared.lock().ready(match error {
630                None => Ok(()),
631                Some(err) => Err(err.retained()),
632            });
633        }),
634    )
635}
636
637#[cfg(feature = "async")]
638pub fn result<T: arc::Retain + std::marker::Send>() -> (
639    Completion<Result<arc::R<T>, arc::R<ns::Error>>>,
640    arc::R<ResultCh<T>>,
641) {
642    let shared = Shared::new();
643    (
644        Completion(shared.clone()),
645        ResultCh::<T>::new2(move |value: Option<&T>, error: Option<&ns::Error>| {
646            let res = match error {
647                None => Ok(unsafe { value.unwrap_unchecked().retained() }),
648                Some(err) => Err(err.retained()),
649            };
650
651            shared.lock().ready(res);
652        }),
653    )
654}