Skip to main content

swiftui_native/abi/
closure.rs

1//! Rust functions Swift can call back into.
2
3use core::ffi::c_void;
4
5/// Context for a Swift `() -> T` closure implemented in Rust.
6///
7/// Swift treats a closure context as a reference-counted heap object. A
8/// non-escaping closure never touches the count, but an escaping one — a
9/// Button's action, say — is stored by the receiver, which retains it now and
10/// releases it later. Handing Swift a plain `Box` means it eventually calls
11/// `swift_release` on memory that is not a Swift object, and the process dies
12/// with SIGBUS long after the call that caused it.
13///
14/// So the first two words mirror a Swift object header and carry the refcount
15/// pattern of a known immortal object, which makes retain and release no-ops.
16#[repr(C)]
17pub struct ClosureCtx {
18    isa: *const c_void,
19    refcount: u64,
20    body: unsafe extern "C" fn(*mut u8, *mut c_void),
21    payload: *mut c_void,
22}
23
24/// The refcount word Swift uses for statically allocated, never-freed objects.
25pub(crate) const IMMORTAL_REFCOUNT: u64 = 0x8000_0004_ffff_ffff;
26
27/// Metadata of a known-immortal runtime object, reused as a plausible `isa`.
28pub(crate) fn immortal_isa() -> *const c_void {
29    // Resolved once: every `ClosureCtx` asks for this, and there are as many of
30    // those as there are closures handed to SwiftUI.
31    static ISA: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
32    *ISA.get_or_init(|| {
33        unsafe extern "C" {
34            fn dlsym(handle: *mut c_void, symbol: *const core::ffi::c_char) -> *mut c_void;
35        }
36        let obj = unsafe { dlsym((-2isize) as *mut c_void, c"_swiftEmptyArrayStorage".as_ptr()) };
37        if obj.is_null() {
38            0
39        } else {
40            unsafe { *(obj as *const *const c_void) as usize }
41        }
42    }) as *const c_void
43}
44
45impl ClosureCtx {
46    /// `body` receives `(out_ptr, payload)` and must initialise `*out_ptr` with
47    /// a `+1` value (or ignore it, for a closure returning `Void`).
48    pub fn new(
49        body: unsafe extern "C" fn(*mut u8, *mut c_void),
50        payload: *mut c_void,
51    ) -> Self {
52        Self { isa: immortal_isa(), refcount: IMMORTAL_REFCOUNT, body, payload }
53    }
54}
55
56// Swift passes a closure's indirect (`@out`) result in x8 and the closure
57// context in x20 — neither of which is reachable from a plain `extern "C"` fn
58// signature. This thunk moves them into the C argument registers and tail-calls
59// the dispatcher, so closure bodies can be ordinary Rust functions.
60core::arch::global_asm!(
61    ".p2align 2",
62    ".globl _rswift_swift_closure_thunk",
63    "_rswift_swift_closure_thunk:",
64    "mov x0, x8",
65    "mov x1, x20",
66    "b _rswift_swift_closure_dispatch",
67);
68
69unsafe extern "C" {
70    /// Address to hand to Swift as the closure function pointer.
71    #[link_name = "rswift_swift_closure_thunk"]
72    pub fn swift_closure_thunk();
73}
74
75#[unsafe(no_mangle)]
76unsafe extern "C" fn rswift_swift_closure_dispatch(out: *mut u8, ctx: *mut ClosureCtx) {
77    let ctx = unsafe { &*ctx };
78    unsafe { (ctx.body)(out, ctx.payload) };
79}
80
81// A closure that takes one argument, such as `ForEach`'s `(Int) -> Content`.
82// The argument arrives in x0, the indirect result in x8 and the context in x20,
83// so all three are shuffled into the C argument registers. The moves are ordered
84// so x0 is read before it is overwritten.
85core::arch::global_asm!(
86    ".p2align 2",
87    ".globl _rswift_index_closure_thunk",
88    "_rswift_index_closure_thunk:",
89    "mov x2, x20",
90    "mov x1, x0",
91    "mov x0, x8",
92    "b _rswift_index_closure_dispatch",
93);
94
95unsafe extern "C" {
96    /// Address to hand to Swift for a one-argument closure.
97    #[link_name = "rswift_index_closure_thunk"]
98    pub fn index_closure_thunk();
99}
100
101/// The context an `@isolated(any)` async closure carries.
102///
103/// Such a closure is not two words of function and context — the *isolation* it
104/// runs on has to travel with it, and Swift puts it in the context: a heap box
105/// of an object header followed by an `(any Actor)?`. All-zero is `nil`, which
106/// means nonisolated, which is what a closure written `{ }` with no actor
107/// context gets.
108///
109/// The refcount is the immortal pattern for the same reason [`ClosureCtx`] uses
110/// it: SwiftUI stores the closure and releases it later, and this is not a real
111/// Swift object to be freed.
112#[repr(C)]
113pub struct IsolatedContext {
114    isa: *const c_void,
115    refcount: u64,
116    isolation: [u64; 2],
117}
118
119impl IsolatedContext {
120    /// A context carrying no isolation.
121    pub fn nonisolated() -> Self {
122        Self { isa: immortal_isa(), refcount: IMMORTAL_REFCOUNT, isolation: [0; 2] }
123    }
124}
125
126/// Context for a Swift `(Int) -> T` closure implemented in Rust.
127#[repr(C)]
128pub struct IndexClosureCtx {
129    isa: *const c_void,
130    refcount: u64,
131    body: unsafe extern "C" fn(*mut u8, i64, *mut c_void),
132    payload: *mut c_void,
133}
134
135impl IndexClosureCtx {
136    pub fn new(
137        body: unsafe extern "C" fn(*mut u8, i64, *mut c_void),
138        payload: *mut c_void,
139    ) -> Self {
140        Self { isa: immortal_isa(), refcount: IMMORTAL_REFCOUNT, body, payload }
141    }
142}
143
144#[unsafe(no_mangle)]
145unsafe extern "C" fn rswift_index_closure_dispatch(
146    out: *mut u8,
147    index: i64,
148    ctx: *mut IndexClosureCtx,
149) {
150    let ctx = unsafe { &*ctx };
151    unsafe { (ctx.body)(out, index, ctx.payload) };
152}
153
154// A closure that *consumes* one argument, such as a `Binding` setter's
155// `(Value) -> ()`. The value arrives by pointer in x0 and the context in x20;
156// only the context needs moving, so x0 passes straight through.
157core::arch::global_asm!(
158    ".p2align 2",
159    ".globl _rswift_value_closure_thunk",
160    "_rswift_value_closure_thunk:",
161    "mov x1, x20",
162    "b _rswift_value_closure_dispatch",
163);
164
165unsafe extern "C" {
166    /// Address to hand to Swift for a one-argument, no-result closure.
167    #[link_name = "rswift_value_closure_thunk"]
168    pub fn value_closure_thunk();
169}
170
171/// Context for a Swift `(Value) -> ()` closure implemented in Rust.
172#[repr(C)]
173pub struct ValueClosureCtx {
174    isa: *const c_void,
175    refcount: u64,
176    body: unsafe extern "C" fn(*const u8, *mut c_void),
177    payload: *mut c_void,
178}
179
180impl ValueClosureCtx {
181    pub fn new(
182        body: unsafe extern "C" fn(*const u8, *mut c_void),
183        payload: *mut c_void,
184    ) -> Self {
185        Self { isa: immortal_isa(), refcount: IMMORTAL_REFCOUNT, body, payload }
186    }
187}
188
189#[unsafe(no_mangle)]
190unsafe extern "C" fn rswift_value_closure_dispatch(value: *const u8, ctx: *mut ValueClosureCtx) {
191    let ctx = unsafe { &*ctx };
192    unsafe { (ctx.body)(value, ctx.payload) };
193}
194
195// A closure taking two arguments and returning nothing, such as `onChange`'s
196// `(V, V) -> ()`. Both values arrive by pointer in x0 and x1 (V is generic, so
197// they are indirect); only the context needs moving.
198core::arch::global_asm!(
199    ".p2align 2",
200    ".globl _rswift_pair_closure_thunk",
201    "_rswift_pair_closure_thunk:",
202    "mov x2, x20",
203    "b _rswift_pair_closure_dispatch",
204);
205
206unsafe extern "C" {
207    /// Address to hand to Swift for a two-argument, no-result closure.
208    #[link_name = "rswift_pair_closure_thunk"]
209    pub fn pair_closure_thunk();
210}
211
212/// Context for a Swift `(A, A) -> ()` closure implemented in Rust.
213#[repr(C)]
214pub struct PairClosureCtx {
215    isa: *const c_void,
216    refcount: u64,
217    body: unsafe extern "C" fn(*const u8, *const u8, *mut c_void),
218    payload: *mut c_void,
219}
220
221impl PairClosureCtx {
222    pub fn new(
223        body: unsafe extern "C" fn(*const u8, *const u8, *mut c_void),
224        payload: *mut c_void,
225    ) -> Self {
226        Self { isa: immortal_isa(), refcount: IMMORTAL_REFCOUNT, body, payload }
227    }
228}
229
230#[unsafe(no_mangle)]
231unsafe extern "C" fn rswift_pair_closure_dispatch(
232    a: *const u8,
233    b: *const u8,
234    ctx: *mut PairClosureCtx,
235) {
236    let ctx = unsafe { &*ctx };
237    unsafe { (ctx.body)(a, b, ctx.payload) };
238}
239
240// A closure taking one argument that may be up to three registers wide, such
241// as `(HoverPhase) -> ()`. A frozen enum of seventeen bytes arrives *in* x0-x2;
242// a resilient one of a single byte arrives as a pointer in x0. One thunk covers
243// both, because forwarding registers the callee ignores costs nothing — while
244// the narrow `value_closure_thunk` cannot, since it overwrites x1 with the
245// context.
246core::arch::global_asm!(
247    ".p2align 2",
248    ".globl _rswift_wide_closure_thunk",
249    "_rswift_wide_closure_thunk:",
250    "mov x3, x20",
251    "b _rswift_wide_closure_dispatch",
252);
253
254unsafe extern "C" {
255    /// Address to hand to Swift for a one-argument, no-result closure whose
256    /// argument may not fit in a single register.
257    #[link_name = "rswift_wide_closure_thunk"]
258    pub fn wide_closure_thunk();
259}
260
261/// Context for a Swift `(V) -> ()` closure where `V` may span three registers.
262#[repr(C)]
263pub struct WideClosureCtx {
264    isa: *const c_void,
265    refcount: u64,
266    body: unsafe extern "C" fn(u64, u64, u64, *mut c_void),
267    payload: *mut c_void,
268}
269
270impl WideClosureCtx {
271    pub fn new(
272        body: unsafe extern "C" fn(u64, u64, u64, *mut c_void),
273        payload: *mut c_void,
274    ) -> Self {
275        Self { isa: immortal_isa(), refcount: IMMORTAL_REFCOUNT, body, payload }
276    }
277}
278
279#[unsafe(no_mangle)]
280unsafe extern "C" fn rswift_wide_closure_dispatch(
281    a0: u64,
282    a1: u64,
283    a2: u64,
284    ctx: *mut WideClosureCtx,
285) {
286    let ctx = unsafe { &*ctx };
287    unsafe { (ctx.body)(a0, a1, a2, ctx.payload) };
288}
289
290/// Which case of an enum a value holds.
291///
292/// The inverse of [`super::inject_enum_tag`], and the first of the three enum value
293/// witnesses rather than the third.
294///
295/// # Safety
296/// `value` must point at a valid value of the enum `metadata` describes.
297pub unsafe fn read_enum_tag(value: *const u8, metadata: *const c_void) -> u32 {
298    unsafe {
299        const ENUM_WITNESSES: usize = 8 * 8 + 8 + 8 + 4 + 4;
300        let vwt = *(metadata.cast::<*const u8>().offset(-1));
301        let get = *(vwt.add(ENUM_WITNESSES) as *const *const c_void);
302        type Get = unsafe extern "C" fn(*const c_void, *const c_void) -> u32;
303        let f: Get = core::mem::transmute(get);
304        f(value as *const c_void, metadata)
305    }
306}
307
308/// Resolve a Swift type from its mangled name string.
309///
310/// Results are cached. Demangling walks the name, instantiates generic metadata
311/// and consults the runtime's caches, which costs on the order of a microsecond
312/// — and the same handful of names are resolved on *every* view construction, so
313/// uncached it dominates the cost of building a tree.
314/// Resolve a *fixed* mangled name, caching in one word at the call site.
315///
316/// This is what the Swift compiler emits for the same job: a global per site,
317/// a relaxed load, a null check, and the slow path taken once. The shared map
318/// behind [`resolve_type_by_mangled_name`] has to hash the whole mangled string
319/// under a mutex — 266 ns against the 416 the runtime call costs, so it earns
320/// its keep, but a site that always asks for the same type should not be paying
321/// either.
322///
323/// Only for names that cannot vary between calls. A site whose name is computed
324/// would cache the first answer and hand it back for every later one.
325#[macro_export]
326macro_rules! type_by_name {
327    ($bytes:expr) => {{
328        static SITE: ::std::sync::atomic::AtomicUsize = ::std::sync::atomic::AtomicUsize::new(0);
329        let hit = SITE.load(::std::sync::atomic::Ordering::Relaxed);
330        if hit != 0 {
331            hit as *const ::core::ffi::c_void
332        } else {
333            let m = $crate::abi::resolve_type_by_mangled_name($bytes);
334            SITE.store(m as usize, ::std::sync::atomic::Ordering::Relaxed);
335            m
336        }
337    }};
338}
339
340/// # Safety
341/// `mangled` must be a well-formed mangled type name without its `$s` prefix.
342/// The runtime parses it, and a malformed one is not merely a null answer.
343pub unsafe fn resolve_type_by_mangled_name(mangled: &[u8]) -> *const c_void {
344    use std::collections::HashMap;
345    use std::sync::{Mutex, OnceLock};
346    static CACHE: OnceLock<Mutex<HashMap<Vec<u8>, usize>>> = OnceLock::new();
347    let cache = CACHE.get_or_init(|| Mutex::new(HashMap::new()));
348
349    if let Ok(map) = cache.lock() {
350        if let Some(&hit) = map.get(mangled) {
351            return hit as *const c_void;
352        }
353    }
354    let resolved = unsafe {
355        swift_runtime_sys::RuntimeRaw::swift_getTypeByMangledNameInEnvironment(
356            mangled.as_ptr(),
357            mangled.len(),
358            core::ptr::null(),
359            0,
360        )
361    };
362    if !resolved.is_null() {
363        if let Ok(mut map) = cache.lock() {
364            map.insert(mangled.to_vec(), resolved as usize);
365        }
366    }
367    resolved
368}
369
370// A closure returning a single word — an `Array`, a class reference, a `Bool`.
371// Swift returns a loadable one-word result in x0, not through x8, so this
372// thunk moves only the context and lets the return value pass straight back.
373core::arch::global_asm!(
374    ".p2align 2",
375    ".globl _rswift_word_closure_thunk",
376    "_rswift_word_closure_thunk:",
377    "mov x1, x0",
378    "mov x0, x20",
379    "b _rswift_word_closure_dispatch",
380);
381
382unsafe extern "C" {
383    /// Address to hand to Swift for a closure returning one word.
384    #[link_name = "rswift_word_closure_thunk"]
385    pub fn word_closure_thunk();
386}
387
388/// Context for a Swift closure returning a single word.
389///
390/// `body` also receives whatever was in x0 on entry, so one context serves
391/// both `() -> T` and `(U) -> T` where each of `T` and `U` is one word.
392#[repr(C)]
393pub struct WordClosureCtx {
394    isa: *const c_void,
395    refcount: u64,
396    body: unsafe extern "C" fn(*mut c_void, u64) -> u64,
397    payload: *mut c_void,
398}
399
400impl WordClosureCtx {
401    pub fn new(
402        body: unsafe extern "C" fn(*mut c_void, u64) -> u64,
403        payload: *mut c_void,
404    ) -> Self {
405        Self { isa: immortal_isa(), refcount: IMMORTAL_REFCOUNT, body, payload }
406    }
407}
408
409#[unsafe(no_mangle)]
410unsafe extern "C" fn rswift_word_closure_dispatch(ctx: *mut WordClosureCtx, arg: u64) -> u64 {
411    let ctx = unsafe { &*ctx };
412    unsafe { (ctx.body)(ctx.payload, arg) }
413}
414
415// A closure returning a `Double` — an alignment guide, a length. Swift returns
416// it in d0, which no integer-returning thunk can carry back.
417core::arch::global_asm!(
418    ".p2align 2",
419    ".globl _rswift_float_closure_thunk",
420    "_rswift_float_closure_thunk:",
421    "mov x1, x20",
422    "b _rswift_float_closure_dispatch",
423);
424
425unsafe extern "C" {
426    /// Address to hand to Swift for a closure returning a `Double`.
427    #[link_name = "rswift_float_closure_thunk"]
428    pub fn float_closure_thunk();
429}
430
431/// Context for a Swift closure returning a `Double`.
432#[repr(C)]
433pub struct FloatClosureCtx {
434    isa: *const c_void,
435    refcount: u64,
436    body: unsafe extern "C" fn(u64, *mut c_void) -> f64,
437    payload: *mut c_void,
438}
439
440impl FloatClosureCtx {
441    pub fn new(
442        body: unsafe extern "C" fn(u64, *mut c_void) -> f64,
443        payload: *mut c_void,
444    ) -> Self {
445        Self { isa: immortal_isa(), refcount: IMMORTAL_REFCOUNT, body, payload }
446    }
447}
448
449#[unsafe(no_mangle)]
450unsafe extern "C" fn rswift_float_closure_dispatch(arg: u64, ctx: *mut FloatClosureCtx) -> f64 {
451    let ctx = unsafe { &*ctx };
452    unsafe { (ctx.body)(arg, ctx.payload) }
453}
454
455// A closure returning *two* words. `draggable(_:)` takes an autoclosure
456// `() -> T`, and the smallest useful `T` is `String` — sixteen bytes, which is
457// loadable, so Swift returns it in x0/x1 rather than through x8. The
458// single-word thunk next door cannot carry the second half back.
459core::arch::global_asm!(
460    ".p2align 2",
461    ".globl _rswift_pair_return_closure_thunk",
462    "_rswift_pair_return_closure_thunk:",
463    "mov x0, x20",
464    "b _rswift_pair_return_closure_dispatch",
465);
466
467unsafe extern "C" {
468    /// Address to hand to Swift for a `() -> (word, word)` closure.
469    #[link_name = "rswift_pair_return_closure_thunk"]
470    pub fn pair_return_closure_thunk();
471}
472
473/// Two words, laid out so the ABI returns them in x0 and x1.
474#[repr(C)]
475pub struct Pair(pub u64, pub u64);
476
477/// Context for a Swift closure returning two words.
478#[repr(C)]
479pub struct PairReturnClosureCtx {
480    isa: *const c_void,
481    refcount: u64,
482    body: unsafe extern "C" fn(*mut c_void) -> Pair,
483    payload: *mut c_void,
484}
485
486impl PairReturnClosureCtx {
487    pub fn new(body: unsafe extern "C" fn(*mut c_void) -> Pair, payload: *mut c_void) -> Self {
488        Self { isa: immortal_isa(), refcount: IMMORTAL_REFCOUNT, body, payload }
489    }
490}
491
492#[unsafe(no_mangle)]
493unsafe extern "C" fn rswift_pair_return_closure_dispatch(ctx: *mut PairReturnClosureCtx) -> Pair {
494    let ctx = unsafe { &*ctx };
495    unsafe { (ctx.body)(ctx.payload) }
496}