Skip to main content

swiftui_native/abi/
call.rs

1//! Calling into Swift: the two shapes every call takes.
2//!
3//! Swift's arm64 convention is AAPCS with three additions, and all three are
4//! invisible to a Rust function signature:
5//!
6//! * **x8** holds the address of an indirect (`@out`) result. A function whose
7//!   return value is address-only — resilient, generic, or simply too big —
8//!   writes through x8 and returns nothing in x0.
9//! * **x20** is the *self* register. A method receives its receiver there: by
10//!   value when the receiver fits in one word, by address otherwise. A type's
11//!   metadata goes there too, for an initialiser or a static property.
12//! * **x21** is the error register, which nothing here throws through but
13//!   everything clobbers.
14//!
15//! Getting one of these wrong does not usually fault at the call. A function
16//! that returns through x8 while the caller reads x0 hands back whatever was
17//! left in the register — often a plausible-looking pointer that SwiftUI
18//! stores and dereferences much later. So the two primitives below always
19//! provide x8, always load every argument register, and always declare the
20//! whole caller-saved set as clobbered. Passing an argument a callee does not
21//! read costs nothing; omitting one it does read costs an afternoon.
22
23use core::ffi::c_void;
24
25/// The integer argument registers a call can fill, x0 through x7.
26pub type Ints = [u64; 8];
27
28/// The floating-point argument registers a call can fill, d0 through d3.
29pub type Floats = [f64; 4];
30
31/// Pad a slice of integer arguments out to all eight registers.
32///
33/// # Panics
34/// If more than eight are given. Arguments past the eighth go on the stack,
35/// which [`super::call_modifier_generic`] handles and this does not.
36pub fn ints(values: &[u64]) -> Ints {
37    assert!(values.len() <= 8, "{} integer arguments, but only eight registers", values.len());
38    let mut a = [0u64; 8];
39    a[..values.len()].copy_from_slice(values);
40    a
41}
42
43/// Call a Swift function whose result is written through x8.
44///
45/// `out` must point at storage of at least the result type's *stride* — a
46/// callee may write the trailing padding.
47///
48/// # Safety
49/// `func` must be a Swift function with this shape, and the arguments must
50/// match its signature: which registers, and whether each value travels
51/// directly or by address.
52pub unsafe fn call_out(
53    func: *const c_void,
54    args: Ints,
55    floats: Floats,
56    self_reg: u64,
57    out: *mut u8,
58) {
59    unsafe {
60        core::arch::asm!("blr {f}", f = in(reg) func,
61            in("x0") args[0], in("x1") args[1], in("x2") args[2], in("x3") args[3],
62            in("x4") args[4], in("x5") args[5], in("x6") args[6], in("x7") args[7],
63            in("d0") floats[0], in("d1") floats[1], in("d2") floats[2], in("d3") floats[3],
64            in("x8") out, in("x20") self_reg,
65            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
66            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
67            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
68        );
69    }
70}
71
72/// Call a Swift function with any number of integer arguments.
73///
74/// [`call_out`] covers the eight that fit in registers; this one spills the
75/// rest onto the stack, which is what the wider initialisers need — the widest
76/// here is `_TaskValueModifier2.init`, at ten.
77///
78/// The frame is always the same size, so one asm block covers every arity: a
79/// callee that takes fewer simply never reads the slots above its own.
80///
81/// # Safety
82/// As [`call_out`], and `ints` must match the callee's signature — including
83/// any trailing metadata and witness tables, which are not implied.
84pub unsafe fn call_spilled(
85    func: *const c_void,
86    ints: &[u64],
87    floats: Floats,
88    self_reg: u64,
89    out: *mut u8,
90) {
91    assert!(ints.len() <= 16, "call with {} integer arguments", ints.len());
92    let mut a = [0u64; 16];
93    a[..ints.len()].copy_from_slice(ints);
94    let spill = &a[8..];
95    unsafe {
96        core::arch::asm!(
97            "sub sp, sp, #64",
98            "stp {s0}, {s1}, [sp]",
99            "stp {s2}, {s3}, [sp, #16]",
100            "stp {s4}, {s5}, [sp, #32]",
101            "stp {s6}, {s7}, [sp, #48]",
102            "blr {f}",
103            "add sp, sp, #64",
104            f = in(reg) func,
105            s0 = in(reg) spill[0], s1 = in(reg) spill[1],
106            s2 = in(reg) spill[2], s3 = in(reg) spill[3],
107            s4 = in(reg) spill[4], s5 = in(reg) spill[5],
108            s6 = in(reg) spill[6], s7 = in(reg) spill[7],
109            in("x0") a[0], in("x1") a[1], in("x2") a[2], in("x3") a[3],
110            in("x4") a[4], in("x5") a[5], in("x6") a[6], in("x7") a[7],
111            in("d0") floats[0], in("d1") floats[1], in("d2") floats[2], in("d3") floats[3],
112            in("x8") out, in("x20") self_reg,
113            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
114            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
115            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
116        );
117    }
118}
119
120/// Call a Swift function whose result comes back in registers.
121///
122/// Returns x0, x1 and x2, which is every loadable result up to three words —
123/// a `Binding`, a `String`, a `Color`. A caller that wants one word reads the
124/// first; the rest cost nothing.
125///
126/// # Safety
127/// As [`call_out`], and the result type must be loadable rather than returned
128/// through x8. When in doubt it is returned through x8: guessing wrong this
129/// way yields a stale register rather than a crash.
130pub unsafe fn call_regs(
131    func: *const c_void,
132    args: Ints,
133    floats: Floats,
134    self_reg: u64,
135) -> [u64; 3] {
136    let (r0, r1, r2): (u64, u64, u64);
137    unsafe {
138        core::arch::asm!("blr {f}", f = in(reg) func,
139            inout("x0") args[0] => r0, inout("x1") args[1] => r1,
140            inout("x2") args[2] => r2, in("x3") args[3],
141            in("x4") args[4], in("x5") args[5], in("x6") args[6], in("x7") args[7],
142            in("d0") floats[0], in("d1") floats[1], in("d2") floats[2], in("d3") floats[3],
143            in("x20") self_reg,
144            lateout("x8") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
145            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
146            lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x21") _,
147            clobber_abi("C"),
148        );
149    }
150    [r0, r1, r2]
151}
152
153/// Call a Swift function whose result is a `Double` in d0.
154///
155/// # Safety
156/// As [`call_regs`].
157pub unsafe fn call_f64(func: *const c_void, args: Ints, floats: Floats, self_reg: u64) -> f64 {
158    let out: f64;
159    unsafe {
160        core::arch::asm!("blr {f}", f = in(reg) func,
161            in("x0") args[0], in("x1") args[1], in("x2") args[2], in("x3") args[3],
162            in("x4") args[4], in("x5") args[5], in("x6") args[6], in("x7") args[7],
163            inout("d0") floats[0] => out,
164            in("d1") floats[1], in("d2") floats[2], in("d3") floats[3],
165            in("x20") self_reg,
166            lateout("x8") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
167            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
168            lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x21") _,
169            clobber_abi("C"),
170        );
171    }
172    out
173}
174
175/// Read a `static var` whose value is written through x8.
176///
177/// The shape SwiftUI's resilient values take: the metatype goes in x20 and the
178/// value lands in a buffer the caller sizes from the type's stride.
179///
180/// # Safety
181/// `getter` must be a static property getter for the type `meta` describes.
182pub unsafe fn static_out(getter: *const c_void, meta: *const c_void, stride: usize) -> Vec<u8> {
183    let mut out = vec![0u8; stride.max(1)];
184    unsafe { call_out(getter, [0; 8], [0.0; 4], meta as u64, out.as_mut_ptr()) };
185    out
186}
187
188/// Read a `static var` whose value comes back in registers.
189///
190/// SwiftUI's nested `.Set` option sets and `Alignment` take this shape while
191/// its other resilient values take [`static_out`]'s, and nothing in the SDK
192/// says which is which — neither is marked `@frozen`. The distinction is
193/// recorded at each call site instead.
194///
195/// # Safety
196/// As [`static_out`].
197pub unsafe fn static_regs(getter: *const c_void, meta: *const c_void) -> [u64; 3] {
198    unsafe { call_regs(getter, [0; 8], [0.0; 4], meta as u64) }
199}