Skip to main content

swiftui_native/
keypath.rs

1//! `WritableKeyPath` built at run time, without a compiler.
2//!
3//! `\EnvironmentValues.contentTransition` is not a symbol. It is a *pattern* —
4//! a compact, compiler-emitted description that `swift_getKeyPath` turns into a
5//! key path object — and several of SwiftUI's modifiers are
6//! `@_alwaysEmitIntoClient` wrappers whose inlined body is
7//! `environment(\.someKey, value)`. Reaching those from Rust means emitting the
8//! pattern.
9//!
10//! # The pattern
11//!
12//! Forty-eight bytes, every internal reference a *32-bit* offset from the field
13//! holding it:
14//!
15//! | offset | contents |
16//! |--------|----------|
17//! | `+0`   | relative pointer to an 8-byte instantiation cache, which must be writable |
18//! | `+4`   | zero |
19//! | `+8`   | relative pointer to the root type's mangled name, bit 0 set |
20//! | `+12`  | relative pointer to the value type's mangled name, bit 0 set |
21//! | `+16`  | zero |
22//! | `+20`  | `KeyPathBuffer::Header` — `0x80000018`: trivial, 24 bytes of components |
23//! | `+24`  | component header `0` — kind *External*, no substitutions |
24//! | `+28`  | relative pointer to the property descriptor, with bit 0 set for *indirect* |
25//! | `+32`  | component header `0x02c00002` — kind *Computed*, mutating, settable, id resolved by indirect pointer |
26//! | `+36`  | relative pointer to a word holding the property's getter — the component's identity |
27//! | `+40`  | relative pointer to the getter thunk |
28//! | `+44`  | relative pointer to the setter thunk |
29//!
30//! The external component names the resilient property so the runtime can use
31//! the framework's own key path if it has one; the computed component after it
32//! is the local fallback, and the one that actually runs.
33//!
34//! # Why a `static`
35//!
36//! Those offsets are `i32`, and the thunks below are code — they live in this
37//! binary's text segment, which is further from the heap than two gigabytes
38//! spans. So the pattern cannot be a `Box`: it is a `static mut`, which puts it
39//! in the same image as the thunks and makes every offset small. The same
40//! constraint bit [`crate::conformance`], where the fix was one allocation; here
41//! it has to be static storage.
42
43use core::ffi::c_void;
44
45/// The accessors one key path needs, resolved at run time.
46#[derive(Clone, Copy)]
47pub struct Property<'a> {
48    /// The property's getter, `…vg`.
49    pub getter: *const c_void,
50    /// The property's setter, `…vs`.
51    pub setter: *const c_void,
52    /// The property descriptor, `…vpMV`.
53    pub descriptor: *const c_void,
54    /// The root type's mangled name, without the `$s` prefix.
55    ///
56    /// Borrowed only for the call: [`build`] copies it into the slot, which is
57    /// what has to outlive everything.
58    pub root: &'a [u8],
59    /// The value type's mangled name, without the `$s` prefix.
60    pub value: &'a [u8],
61}
62
63/// Storage for one key path pattern, laid out so every offset is small.
64#[repr(C, align(8))]
65pub struct Slot {
66    cache: u64,
67    pattern: [u32; 12],
68    descriptor: u64,
69    id: u64,
70    root: [u8; 64],
71    value: [u8; 64],
72}
73
74impl Slot {
75    /// An empty slot, to be filled by [`build`].
76    pub const fn new() -> Self {
77        Slot {
78            cache: 0,
79            pattern: [0; 12],
80            descriptor: 0,
81            id: 0,
82            root: [0; 64],
83            value: [0; 64],
84        }
85    }
86}
87
88impl Default for Slot {
89    fn default() -> Self {
90        Self::new()
91    }
92}
93
94/// `KeyPathBuffer::Header`: trivial, with 24 bytes of components after it.
95const BUFFER_HEADER: u32 = 0x8000_0018;
96/// Component kind *External*, with no generic substitutions.
97const EXTERNAL_COMPONENT: u32 = 0;
98/// Component kind *Computed*, mutating and settable, id resolved indirectly.
99const COMPUTED_COMPONENT: u32 = 0x02c0_0002;
100
101/// A 32-bit relative offset from `at` to `to`, checked rather than truncated.
102unsafe fn offset(to: *const u8, at: *const u8) -> i32 {
103    let delta = to as isize - at as isize;
104    i32::try_from(delta).expect("key path relative pointer out of range")
105}
106
107/// Fill `slot` in and hand back a `WritableKeyPath` for `property`.
108///
109/// The returned object is retained for the process's lifetime: SwiftUI keeps
110/// the key path inside whatever modifier it was handed to.
111///
112/// # Safety
113/// `slot` must be a `static mut` in this image, used by one property only, and
114/// `thunks` must be the pair below whose target words this call also sets.
115pub unsafe fn build(
116    slot: &'static mut Slot,
117    property: Property<'_>,
118    thunks: (*const c_void, *const c_void, *mut *const c_void, *mut *const c_void),
119) -> u64 {
120    unsafe {
121        let (get_thunk, set_thunk, get_target, set_target) = thunks;
122        *get_target = property.getter;
123        *set_target = property.setter;
124        slot.descriptor = property.descriptor as u64;
125        slot.id = property.getter as u64;
126        slot.root[..property.root.len()].copy_from_slice(property.root);
127        slot.value[..property.value.len()].copy_from_slice(property.value);
128
129        let base = slot.pattern.as_ptr() as *const u8;
130        let at = |i: usize| base.add(i * 4);
131        slot.pattern[0] = offset(&slot.cache as *const u64 as *const u8, at(0)) as u32;
132        slot.pattern[1] = 0;
133        // Bit 0 says "the target is a *mangled name*", not type metadata. With
134        // it clear the runtime reads the string as a `Metadata *` and walks off
135        // into `checkTransitiveCompleteness` with a pointer to ASCII.
136        slot.pattern[2] = (offset(slot.root.as_ptr(), at(2)) | 1) as u32;
137        slot.pattern[3] = (offset(slot.value.as_ptr(), at(3)) | 1) as u32;
138        slot.pattern[4] = 0;
139        slot.pattern[5] = BUFFER_HEADER;
140        slot.pattern[6] = EXTERNAL_COMPONENT;
141        // Bit 0 marks the reference indirect: the offset reaches a *word holding*
142        // the descriptor, not the descriptor, because a resilient property's
143        // descriptor is in another image and further away than an i32 spans.
144        slot.pattern[7] = (offset(&slot.descriptor as *const u64 as *const u8, at(7)) | 1) as u32;
145        slot.pattern[8] = COMPUTED_COMPONENT;
146        slot.pattern[9] = offset(&slot.id as *const u64 as *const u8, at(9)) as u32;
147        slot.pattern[10] = offset(get_thunk as *const u8, at(10)) as u32;
148        slot.pattern[11] = offset(set_thunk as *const u8, at(11)) as u32;
149
150        type GetKeyPath = unsafe extern "C" fn(*const c_void, *const c_void) -> u64;
151        let f: GetKeyPath = core::mem::transmute(crate::resolve::get_key_path());
152        f(base as *const c_void, core::ptr::null())
153    }
154}
155
156// A key path's getter is called with the root's address in x0 and the result's
157// in x8; a property getter wants the root in x20 and leaves x8 alone. So the
158// thunk is one move and a tail call — which matters, because a tail call is
159// what keeps x8 intact.
160//
161// A setter is the same shape: the new value is already in x0 where the property
162// setter wants it, and only the root has to move from x1 to x20.
163//
164// One pair per key path, because a thunk has no context to carry its target in.
165// They are handed out in order by `next_thunks`, so no call site picks an index.
166macro_rules! key_path_thunks {
167    ($($get:ident, $set:ident, $get_sym:literal, $set_sym:literal,
168       $get_tgt:ident, $set_tgt:ident;)*) => {$(
169        crate::thunk::runtime_thunk! {
170            /// A key path getter: root in x0, result through x8.
171            fn $get = $get_sym via $get_tgt {
172                prologue: ["mov x20, x0"], branch: "br", epilogue: [],
173            }
174            /// A key path setter: new value in x0, root in x1.
175            fn $set = $set_sym via $set_tgt {
176                prologue: ["mov x20, x1"], branch: "br", epilogue: [],
177            }
178        }
179    )*};
180}
181
182key_path_thunks! {
183    kp0_get, kp0_set, "rswift_kp0_get", "rswift_kp0_set", RSWIFT_KP0_GET, RSWIFT_KP0_SET;
184    kp1_get, kp1_set, "rswift_kp1_get", "rswift_kp1_set", RSWIFT_KP1_GET, RSWIFT_KP1_SET;
185    kp2_get, kp2_set, "rswift_kp2_get", "rswift_kp2_set", RSWIFT_KP2_GET, RSWIFT_KP2_SET;
186    kp3_get, kp3_set, "rswift_kp3_get", "rswift_kp3_set", RSWIFT_KP3_GET, RSWIFT_KP3_SET;
187    kp4_get, kp4_set, "rswift_kp4_get", "rswift_kp4_set", RSWIFT_KP4_GET, RSWIFT_KP4_SET;
188    kp5_get, kp5_set, "rswift_kp5_get", "rswift_kp5_set", RSWIFT_KP5_GET, RSWIFT_KP5_SET;
189    kp6_get, kp6_set, "rswift_kp6_get", "rswift_kp6_set", RSWIFT_KP6_GET, RSWIFT_KP6_SET;
190    kp7_get, kp7_set, "rswift_kp7_get", "rswift_kp7_set", RSWIFT_KP7_GET, RSWIFT_KP7_SET;
191    kp8_get, kp8_set, "rswift_kp8_get", "rswift_kp8_set", RSWIFT_KP8_GET, RSWIFT_KP8_SET;
192    kp9_get, kp9_set, "rswift_kp9_get", "rswift_kp9_set", RSWIFT_KP9_GET, RSWIFT_KP9_SET;
193    kp10_get, kp10_set, "rswift_kp10_get", "rswift_kp10_set", RSWIFT_KP10_GET, RSWIFT_KP10_SET;
194    kp11_get, kp11_set, "rswift_kp11_get", "rswift_kp11_set", RSWIFT_KP11_GET, RSWIFT_KP11_SET;
195    kp12_get, kp12_set, "rswift_kp12_get", "rswift_kp12_set", RSWIFT_KP12_GET, RSWIFT_KP12_SET;
196    kp13_get, kp13_set, "rswift_kp13_get", "rswift_kp13_set", RSWIFT_KP13_GET, RSWIFT_KP13_SET;
197    kp14_get, kp14_set, "rswift_kp14_get", "rswift_kp14_set", RSWIFT_KP14_GET, RSWIFT_KP14_SET;
198    kp15_get, kp15_set, "rswift_kp15_get", "rswift_kp15_set", RSWIFT_KP15_GET, RSWIFT_KP15_SET;
199    kp16_get, kp16_set, "rswift_kp16_get", "rswift_kp16_set", RSWIFT_KP16_GET, RSWIFT_KP16_SET;
200    kp17_get, kp17_set, "rswift_kp17_get", "rswift_kp17_set", RSWIFT_KP17_GET, RSWIFT_KP17_SET;
201    kp18_get, kp18_set, "rswift_kp18_get", "rswift_kp18_set", RSWIFT_KP18_GET, RSWIFT_KP18_SET;
202    kp19_get, kp19_set, "rswift_kp19_get", "rswift_kp19_set", RSWIFT_KP19_GET, RSWIFT_KP19_SET;
203    kp20_get, kp20_set, "rswift_kp20_get", "rswift_kp20_set", RSWIFT_KP20_GET, RSWIFT_KP20_SET;
204    kp21_get, kp21_set, "rswift_kp21_get", "rswift_kp21_set", RSWIFT_KP21_GET, RSWIFT_KP21_SET;
205    kp22_get, kp22_set, "rswift_kp22_get", "rswift_kp22_set", RSWIFT_KP22_GET, RSWIFT_KP22_SET;
206    kp23_get, kp23_set, "rswift_kp23_get", "rswift_kp23_set", RSWIFT_KP23_GET, RSWIFT_KP23_SET;
207    kp24_get, kp24_set, "rswift_kp24_get", "rswift_kp24_set", RSWIFT_KP24_GET, RSWIFT_KP24_SET;
208    kp25_get, kp25_set, "rswift_kp25_get", "rswift_kp25_set", RSWIFT_KP25_GET, RSWIFT_KP25_SET;
209    kp26_get, kp26_set, "rswift_kp26_get", "rswift_kp26_set", RSWIFT_KP26_GET, RSWIFT_KP26_SET;
210    kp27_get, kp27_set, "rswift_kp27_get", "rswift_kp27_set", RSWIFT_KP27_GET, RSWIFT_KP27_SET;
211    kp28_get, kp28_set, "rswift_kp28_get", "rswift_kp28_set", RSWIFT_KP28_GET, RSWIFT_KP28_SET;
212    kp29_get, kp29_set, "rswift_kp29_get", "rswift_kp29_set", RSWIFT_KP29_GET, RSWIFT_KP29_SET;
213    kp30_get, kp30_set, "rswift_kp30_get", "rswift_kp30_set", RSWIFT_KP30_GET, RSWIFT_KP30_SET;
214    kp31_get, kp31_set, "rswift_kp31_get", "rswift_kp31_set", RSWIFT_KP31_GET, RSWIFT_KP31_SET;
215}
216
217/// How many key paths this crate can build.
218pub const THUNK_COUNT: usize = 32;
219
220/// The thunk pair at `index`.
221///
222/// One pair per key path, because a thunk has no context to carry its target
223/// in. Out of range is a panic rather than a wrap-around: the old dispatcher
224/// answered every index past the last one with the *last* pair, so a ninth key
225/// path silently read the eighth one's property. Prefer [`next_thunks`], which
226/// makes the question not arise.
227pub fn thunks(index: usize) -> (*const c_void, *const c_void, *mut *const c_void, *mut *const c_void) {
228    match index {
229        0 => (kp0_get as *const c_void, kp0_set as *const c_void,
230              &raw mut RSWIFT_KP0_GET, &raw mut RSWIFT_KP0_SET),
231        1 => (kp1_get as *const c_void, kp1_set as *const c_void,
232              &raw mut RSWIFT_KP1_GET, &raw mut RSWIFT_KP1_SET),
233        2 => (kp2_get as *const c_void, kp2_set as *const c_void,
234              &raw mut RSWIFT_KP2_GET, &raw mut RSWIFT_KP2_SET),
235        3 => (kp3_get as *const c_void, kp3_set as *const c_void,
236              &raw mut RSWIFT_KP3_GET, &raw mut RSWIFT_KP3_SET),
237        4 => (kp4_get as *const c_void, kp4_set as *const c_void,
238              &raw mut RSWIFT_KP4_GET, &raw mut RSWIFT_KP4_SET),
239        5 => (kp5_get as *const c_void, kp5_set as *const c_void,
240              &raw mut RSWIFT_KP5_GET, &raw mut RSWIFT_KP5_SET),
241        6 => (kp6_get as *const c_void, kp6_set as *const c_void,
242              &raw mut RSWIFT_KP6_GET, &raw mut RSWIFT_KP6_SET),
243        7 => (kp7_get as *const c_void, kp7_set as *const c_void,
244              &raw mut RSWIFT_KP7_GET, &raw mut RSWIFT_KP7_SET),
245        8 => (kp8_get as *const c_void, kp8_set as *const c_void,
246              &raw mut RSWIFT_KP8_GET, &raw mut RSWIFT_KP8_SET),
247        9 => (kp9_get as *const c_void, kp9_set as *const c_void,
248              &raw mut RSWIFT_KP9_GET, &raw mut RSWIFT_KP9_SET),
249        10 => (kp10_get as *const c_void, kp10_set as *const c_void,
250              &raw mut RSWIFT_KP10_GET, &raw mut RSWIFT_KP10_SET),
251        11 => (kp11_get as *const c_void, kp11_set as *const c_void,
252              &raw mut RSWIFT_KP11_GET, &raw mut RSWIFT_KP11_SET),
253        12 => (kp12_get as *const c_void, kp12_set as *const c_void,
254              &raw mut RSWIFT_KP12_GET, &raw mut RSWIFT_KP12_SET),
255        13 => (kp13_get as *const c_void, kp13_set as *const c_void,
256              &raw mut RSWIFT_KP13_GET, &raw mut RSWIFT_KP13_SET),
257        14 => (kp14_get as *const c_void, kp14_set as *const c_void,
258              &raw mut RSWIFT_KP14_GET, &raw mut RSWIFT_KP14_SET),
259        15 => (kp15_get as *const c_void, kp15_set as *const c_void,
260              &raw mut RSWIFT_KP15_GET, &raw mut RSWIFT_KP15_SET),
261        16 => (kp16_get as *const c_void, kp16_set as *const c_void,
262              &raw mut RSWIFT_KP16_GET, &raw mut RSWIFT_KP16_SET),
263        17 => (kp17_get as *const c_void, kp17_set as *const c_void,
264              &raw mut RSWIFT_KP17_GET, &raw mut RSWIFT_KP17_SET),
265        18 => (kp18_get as *const c_void, kp18_set as *const c_void,
266              &raw mut RSWIFT_KP18_GET, &raw mut RSWIFT_KP18_SET),
267        19 => (kp19_get as *const c_void, kp19_set as *const c_void,
268              &raw mut RSWIFT_KP19_GET, &raw mut RSWIFT_KP19_SET),
269        20 => (kp20_get as *const c_void, kp20_set as *const c_void,
270              &raw mut RSWIFT_KP20_GET, &raw mut RSWIFT_KP20_SET),
271        21 => (kp21_get as *const c_void, kp21_set as *const c_void,
272              &raw mut RSWIFT_KP21_GET, &raw mut RSWIFT_KP21_SET),
273        22 => (kp22_get as *const c_void, kp22_set as *const c_void,
274              &raw mut RSWIFT_KP22_GET, &raw mut RSWIFT_KP22_SET),
275        23 => (kp23_get as *const c_void, kp23_set as *const c_void,
276              &raw mut RSWIFT_KP23_GET, &raw mut RSWIFT_KP23_SET),
277        24 => (kp24_get as *const c_void, kp24_set as *const c_void,
278              &raw mut RSWIFT_KP24_GET, &raw mut RSWIFT_KP24_SET),
279        25 => (kp25_get as *const c_void, kp25_set as *const c_void,
280              &raw mut RSWIFT_KP25_GET, &raw mut RSWIFT_KP25_SET),
281        26 => (kp26_get as *const c_void, kp26_set as *const c_void,
282              &raw mut RSWIFT_KP26_GET, &raw mut RSWIFT_KP26_SET),
283        27 => (kp27_get as *const c_void, kp27_set as *const c_void,
284              &raw mut RSWIFT_KP27_GET, &raw mut RSWIFT_KP27_SET),
285        28 => (kp28_get as *const c_void, kp28_set as *const c_void,
286              &raw mut RSWIFT_KP28_GET, &raw mut RSWIFT_KP28_SET),
287        29 => (kp29_get as *const c_void, kp29_set as *const c_void,
288              &raw mut RSWIFT_KP29_GET, &raw mut RSWIFT_KP29_SET),
289        30 => (kp30_get as *const c_void, kp30_set as *const c_void,
290              &raw mut RSWIFT_KP30_GET, &raw mut RSWIFT_KP30_SET),
291        31 => (kp31_get as *const c_void, kp31_set as *const c_void,
292              &raw mut RSWIFT_KP31_GET, &raw mut RSWIFT_KP31_SET),
293        _ => panic!(
294            "key path {index} is past the {THUNK_COUNT} thunk pairs; \
295             add more to key_path_thunks!"
296        ),
297    }
298}
299
300/// Take the next unused thunk pair.
301///
302/// Callers used to pass an index they had chosen by reading the other call
303/// sites, which is a collision waiting to happen — two key paths sharing a pair
304/// would dispatch to whichever was built last, with nothing to say so. Each key
305/// path is built once and caches its result, so handing them out in order is
306/// enough.
307pub fn next_thunks() -> (*const c_void, *const c_void, *mut *const c_void, *mut *const c_void) {
308    static NEXT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
309    let i = NEXT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
310    assert!(
311        i < THUNK_COUNT,
312        "more than {THUNK_COUNT} key paths; add more pairs to key_path_thunks!"
313    );
314    thunks(i)
315}
316
317// ── Read-only computed key paths, with no Swift property behind them ──
318
319/// `KeyPathBuffer::Header` for a get-only computed component: trivial, 12 bytes.
320const COMPUTED_BUFFER_HEADER: u32 = 0x8000_000C;
321/// Component kind *Computed*, get-only, identity resolved as a direct pointer.
322const COMPUTED_GET_ONLY: u32 = 0x0200_0000;
323
324/// Storage for one get-only computed key path pattern.
325///
326/// Nine words rather than [`Slot`]'s twelve: there is no *External* component,
327/// because there is no resilient framework property to defer to. The whole key
328/// path is the local computed component.
329#[repr(C, align(8))]
330pub struct ComputedSlot {
331    cache: u64,
332    pattern: [u32; 9],
333    _pad: u32,
334    root: [u8; 48],
335    value: [u8; 48],
336}
337
338impl ComputedSlot {
339    /// An empty slot, to be filled by [`build_computed`].
340    pub const fn new() -> Self {
341        ComputedSlot { cache: 0, pattern: [0; 9], _pad: 0, root: [0; 48], value: [0; 48] }
342    }
343}
344
345impl Default for ComputedSlot {
346    fn default() -> Self {
347        Self::new()
348    }
349}
350
351/// A `KeyPath<Root, Value>` whose getter is a Rust function.
352///
353/// `OutlineGroup` needs `KeyPath<Element, Data?>`, and no type in any linked
354/// framework has a recursive `children` property with an exported property
355/// descriptor — so there is nothing for the [`Slot`] shape's *External*
356/// component to name. A get-only computed component needs no such property: it
357/// carries a getter and an identity, and the runtime calls the getter.
358///
359/// The key path's *class* metadata is `KeyPath<Root, Value>`, built by the
360/// runtime from the two mangled names below. That matters beyond type checking:
361/// `OutlineGroup.init` is passed metadata only for its `Leaf`, and recovers
362/// `Data`, `ID` and `Element` from the key paths it is handed.
363///
364/// # Safety
365/// `slot` must be a `static mut` in this image — the pattern's internal
366/// references are 32-bit, and [`build`] explains why that rules out the heap.
367/// `getter` must have the thunk shape below: root in x0, result through x8.
368pub unsafe fn build_computed(
369    slot: &'static mut ComputedSlot,
370    root: &[u8],
371    value: &[u8],
372    getter: *const c_void,
373) -> u64 {
374    unsafe {
375        slot.root[..root.len()].copy_from_slice(root);
376        slot.value[..value.len()].copy_from_slice(value);
377
378        let base = slot.pattern.as_ptr() as *const u8;
379        let at = |i: usize| base.add(i * 4);
380        slot.pattern[0] = offset(&slot.cache as *const u64 as *const u8, at(0)) as u32;
381        slot.pattern[1] = 0;
382        slot.pattern[2] = (offset(slot.root.as_ptr(), at(2)) | 1) as u32;
383        slot.pattern[3] = (offset(slot.value.as_ptr(), at(3)) | 1) as u32;
384        slot.pattern[4] = 0;
385        slot.pattern[5] = COMPUTED_BUFFER_HEADER;
386        slot.pattern[6] = COMPUTED_GET_ONLY;
387        // The identity is what key path equality compares. Swift uses the
388        // property's own getter; any stable, unique pointer does, and the thunk
389        // is one.
390        slot.pattern[7] = offset(getter as *const u8, at(7)) as u32;
391        slot.pattern[8] = offset(getter as *const u8, at(8)) as u32;
392
393        type GetKeyPath = unsafe extern "C" fn(*const c_void, *const c_void) -> u64;
394        let f: GetKeyPath = core::mem::transmute(crate::resolve::get_key_path());
395        f(base as *const c_void, core::ptr::null())
396    }
397}
398
399// A computed key path's getter thunk is
400//   (@out Value in x8, @in_guaranteed Root in x0, arguments in x1, size in x2)
401// so the only shuffle needed is x8 into an argument register. There is no
402// context register, which is why each key path needs its own thunk rather than
403// one dispatcher — the same reason the pairs above are numbered.
404core::arch::global_asm!(
405    ".p2align 2",
406    ".globl _rswift_kp_nil_children_get",
407    "_rswift_kp_nil_children_get:",
408    "mov x1, x0",
409    "mov x0, x8",
410    "b _rswift_kp_nil_children_body",
411);
412
413unsafe extern "C" {
414    /// Getter for a `KeyPath<Int, [Int]?>` that always answers `nil`.
415    #[link_name = "rswift_kp_nil_children_get"]
416    pub fn kp_nil_children_get();
417}
418
419#[unsafe(no_mangle)]
420unsafe extern "C" fn rswift_kp_nil_children_body(out: *mut u8, _root: *const u8) {
421    // `Array` is one non-null word, so `Optional<Array>` spends the null pointer
422    // on `.none` — writing zero *is* writing `nil`, and nothing is retained.
423    unsafe { core::ptr::write_bytes(out, 0, 8) };
424}
425
426// ── Identity key paths ──
427
428/// `KeyPathBuffer::Header` for a key path with no components at all.
429const IDENTITY_BUFFER_HEADER: u32 = 0x8000_0000;
430
431/// Storage for one identity key path pattern.
432///
433/// Six words and no components — `\T.self` is not a property access, it is the
434/// absence of one, and the header says so by declaring zero bytes of components.
435#[repr(C, align(8))]
436pub struct IdentitySlot {
437    cache: u64,
438    pattern: [u32; 6],
439    _pad: [u32; 2],
440    root: [u8; 48],
441    value: [u8; 48],
442}
443
444impl IdentitySlot {
445    /// An empty slot, to be filled by [`build_identity`].
446    pub const fn new() -> Self {
447        IdentitySlot { cache: 0, pattern: [0; 6], _pad: [0; 2], root: [0; 48], value: [0; 48] }
448    }
449}
450
451impl Default for IdentitySlot {
452    fn default() -> Self {
453        Self::new()
454    }
455}
456
457/// `\Root.self`, built at run time.
458///
459/// Worth having a third shape for. A computed key path describes itself by
460/// asking `dladdr` what symbol sits at its getter, so one synthesised here
461/// reflects as `\Int.<computed 0x…>` where a Swift property reflects as
462/// `\Int.someName` — a difference in the two binaries' symbol tables rather than
463/// in the key paths. An identity key path has no getter to name, so both sides
464/// build the same six words and reflect identically.
465///
466/// (That naming is only as good as the symbol table, incidentally: with `-O`,
467/// `\Int.pubIdx` printed as `\Int.comp` because two getters with identical
468/// bodies had been merged.)
469///
470/// # Safety
471/// `slot` must be a `static mut` in this image, for the reason [`build`] gives.
472pub unsafe fn build_identity(slot: &'static mut IdentitySlot, root: &[u8], value: &[u8]) -> u64 {
473    unsafe {
474        slot.root[..root.len()].copy_from_slice(root);
475        slot.value[..value.len()].copy_from_slice(value);
476
477        let base = slot.pattern.as_ptr() as *const u8;
478        let at = |i: usize| base.add(i * 4);
479        slot.pattern[0] = offset(&slot.cache as *const u64 as *const u8, at(0)) as u32;
480        slot.pattern[1] = 0;
481        slot.pattern[2] = (offset(slot.root.as_ptr(), at(2)) | 1) as u32;
482        slot.pattern[3] = (offset(slot.value.as_ptr(), at(3)) | 1) as u32;
483        slot.pattern[4] = 0;
484        slot.pattern[5] = IDENTITY_BUFFER_HEADER;
485
486        type GetKeyPath = unsafe extern "C" fn(*const c_void, *const c_void) -> u64;
487        let f: GetKeyPath = core::mem::transmute(crate::resolve::get_key_path());
488        f(base as *const c_void, core::ptr::null())
489    }
490}
491
492
493// ── Settable computed key paths, with no property behind them ──
494
495/// `KeyPathBuffer::Header`: trivial, 16 bytes of components — a computed
496/// component with an identity, a getter *and* a setter.
497const WRITABLE_BUFFER_HEADER: u32 = 0x8000_0010;
498/// Component kind *Computed*, settable and mutating, identity resolved as a
499/// direct pointer.
500///
501/// [`COMPUTED_COMPONENT`] is the same kind with bit 1 set, which makes the
502/// identity an *indirect* reference — right when the identity is a resilient
503/// property's descriptor in another image, wrong here, where it is a thunk in
504/// this one. This is the value `swiftc` emits for `\FocusedValues.myProperty`,
505/// read out of its IR rather than assembled from the bit definitions.
506const WRITABLE_COMPUTED_COMPONENT: u32 = 0x02c0_0000;
507
508/// Storage for one settable computed key path pattern.
509///
510/// Ten words rather than [`Slot`]'s twelve: there is no *External* component,
511/// because there is no exported property to defer to — `FocusedValues` has none
512/// at all. And four words of component rather than three, because a settable one
513/// carries a setter after its getter.
514#[repr(C, align(8))]
515pub struct WritableComputedSlot {
516    cache: u64,
517    pattern: [u32; 10],
518    _pad: [u32; 2],
519    root: [u8; 64],
520    value: [u8; 64],
521}
522
523impl WritableComputedSlot {
524    /// An empty slot, to be filled by [`build_writable_computed`].
525    pub const fn new() -> Self {
526        WritableComputedSlot {
527            cache: 0,
528            pattern: [0; 10],
529            _pad: [0; 2],
530            root: [0; 64],
531            value: [0; 64],
532        }
533    }
534}
535
536impl Default for WritableComputedSlot {
537    fn default() -> Self {
538        Self::new()
539    }
540}
541
542/// A `WritableKeyPath<Root, Value>` whose accessors are Rust functions.
543///
544/// What a *client* key path into a resilient struct looks like: no property
545/// descriptor to name, just a getter and a setter the client emitted. Swift
546/// builds exactly this for `\FocusedValues.myFocus`, where `myFocus` is a
547/// computed property the client declared — and the reason this exists is that
548/// `FocusedValues` exports no properties, so [`build`]'s *External* component
549/// has nothing to point at.
550///
551/// # Safety
552/// `slot` must be a `static mut` in this image, used by one key path only —
553/// [`build`] explains why the heap will not do. `getter` must take the root in
554/// x0 and write through x8; `setter` must take the new value in x0 and the root
555/// in x1.
556pub unsafe fn build_writable_computed(
557    slot: &'static mut WritableComputedSlot,
558    root: &[u8],
559    value: &[u8],
560    getter: *const c_void,
561    setter: *const c_void,
562) -> u64 {
563    unsafe {
564        slot.root[..root.len()].copy_from_slice(root);
565        slot.value[..value.len()].copy_from_slice(value);
566
567        let base = slot.pattern.as_ptr() as *const u8;
568        let at = |i: usize| base.add(i * 4);
569        slot.pattern[0] = offset(&slot.cache as *const u64 as *const u8, at(0)) as u32;
570        slot.pattern[1] = 0;
571        slot.pattern[2] = (offset(slot.root.as_ptr(), at(2)) | 1) as u32;
572        slot.pattern[3] = (offset(slot.value.as_ptr(), at(3)) | 1) as u32;
573        slot.pattern[4] = 0;
574        slot.pattern[5] = WRITABLE_BUFFER_HEADER;
575        slot.pattern[6] = WRITABLE_COMPUTED_COMPONENT;
576        // The identity is what key path equality compares. Swift uses the
577        // property's own getter; the getter thunk is unique per key path, which
578        // is the property this needs.
579        slot.pattern[7] = offset(getter as *const u8, at(7)) as u32;
580        slot.pattern[8] = offset(getter as *const u8, at(8)) as u32;
581        slot.pattern[9] = offset(setter as *const u8, at(9)) as u32;
582
583        type GetKeyPath = unsafe extern "C" fn(*const c_void, *const c_void) -> u64;
584        let f: GetKeyPath = core::mem::transmute(crate::resolve::get_key_path());
585        f(base as *const c_void, core::ptr::null())
586    }
587}
588
589// One thunk pair per focused key, because a key path accessor has nowhere to
590// carry a context: the index is baked into the instruction stream.
591//
592// SwiftUI calls a key path getter with the result's address in x8, the root's in
593// x0, the arguments in x1 and their size in x2; a setter with the new value in
594// x0, the root in x1, and the same pair after. Both are shuffled into the C
595// argument registers, with the slot index appended, so the bodies are ordinary
596// Rust functions.
597macro_rules! value_thunks {
598    ($($i:literal, $get:ident, $set:ident, $gsym:literal, $ssym:literal,
599       $gtgt:ident, $stgt:ident;)*) => {
600        $(
601            crate::thunk::runtime_thunk! {
602                /// A synthesised key path's getter.
603                fn $get = $gsym via $gtgt {
604                    prologue: ["mov x1, x0", "mov x0, x8", concat!("mov x2, #", $i)],
605                    branch: "br", epilogue: [],
606                }
607                /// A synthesised key path's setter.
608                fn $set = $ssym via $stgt {
609                    prologue: [concat!("mov x2, #", $i)],
610                    branch: "br", epilogue: [],
611                }
612            }
613        )*
614
615        /// How many key paths this crate can synthesise from scratch.
616        ///
617        /// Shared between every kind: a focused-value key, an environment key, a
618        /// container-value key and each named property of an observable model all
619        /// need a thunk pair and a pattern slot, and nothing about any of them
620        /// makes its pool private. [`next_value_slot`] hands them out in order so
621        /// no caller picks an index.
622        ///
623        /// The count is a *declaration* count, not a runtime one: each slot is a
624        /// pair of thunks with its index assembled into the instruction stream,
625        /// so raising it means adding lines to the invocation below. Observable
626        /// properties are what made sixteen tight — a model with four properties
627        /// takes four — so there are thirty-two.
628        pub const VALUE_SLOTS: usize = [$($i),*].len();
629
630        /// The thunk pair for slot `index`, and the words they dispatch through.
631        pub fn value_thunks(
632            index: usize,
633        ) -> (*const c_void, *const c_void, *mut *const c_void, *mut *const c_void) {
634            match index {
635                $($i => (
636                    $get as *const c_void,
637                    $set as *const c_void,
638                    &raw mut $gtgt,
639                    &raw mut $stgt,
640                ),)*
641                _ => panic!(
642                    "key path slot {index} is past the {} this crate has",
643                    VALUE_SLOTS,
644                ),
645            }
646        }
647    };
648}
649
650value_thunks! {
651    0, fk0_get, fk0_set, "rswift_fk0_get", "rswift_fk0_set", RSWIFT_FK0_GET, RSWIFT_FK0_SET;
652    1, fk1_get, fk1_set, "rswift_fk1_get", "rswift_fk1_set", RSWIFT_FK1_GET, RSWIFT_FK1_SET;
653    2, fk2_get, fk2_set, "rswift_fk2_get", "rswift_fk2_set", RSWIFT_FK2_GET, RSWIFT_FK2_SET;
654    3, fk3_get, fk3_set, "rswift_fk3_get", "rswift_fk3_set", RSWIFT_FK3_GET, RSWIFT_FK3_SET;
655    4, fk4_get, fk4_set, "rswift_fk4_get", "rswift_fk4_set", RSWIFT_FK4_GET, RSWIFT_FK4_SET;
656    5, fk5_get, fk5_set, "rswift_fk5_get", "rswift_fk5_set", RSWIFT_FK5_GET, RSWIFT_FK5_SET;
657    6, fk6_get, fk6_set, "rswift_fk6_get", "rswift_fk6_set", RSWIFT_FK6_GET, RSWIFT_FK6_SET;
658    7, fk7_get, fk7_set, "rswift_fk7_get", "rswift_fk7_set", RSWIFT_FK7_GET, RSWIFT_FK7_SET;
659    8, fk8_get, fk8_set, "rswift_fk8_get", "rswift_fk8_set", RSWIFT_FK8_GET, RSWIFT_FK8_SET;
660    9, fk9_get, fk9_set, "rswift_fk9_get", "rswift_fk9_set", RSWIFT_FK9_GET, RSWIFT_FK9_SET;
661    10, fk10_get, fk10_set, "rswift_fk10_get", "rswift_fk10_set", RSWIFT_FK10_GET, RSWIFT_FK10_SET;
662    11, fk11_get, fk11_set, "rswift_fk11_get", "rswift_fk11_set", RSWIFT_FK11_GET, RSWIFT_FK11_SET;
663    12, fk12_get, fk12_set, "rswift_fk12_get", "rswift_fk12_set", RSWIFT_FK12_GET, RSWIFT_FK12_SET;
664    13, fk13_get, fk13_set, "rswift_fk13_get", "rswift_fk13_set", RSWIFT_FK13_GET, RSWIFT_FK13_SET;
665    14, fk14_get, fk14_set, "rswift_fk14_get", "rswift_fk14_set", RSWIFT_FK14_GET, RSWIFT_FK14_SET;
666    15, fk15_get, fk15_set, "rswift_fk15_get", "rswift_fk15_set", RSWIFT_FK15_GET, RSWIFT_FK15_SET;
667    16, fk16_get, fk16_set, "rswift_fk16_get", "rswift_fk16_set", RSWIFT_FK16_GET, RSWIFT_FK16_SET;
668    17, fk17_get, fk17_set, "rswift_fk17_get", "rswift_fk17_set", RSWIFT_FK17_GET, RSWIFT_FK17_SET;
669    18, fk18_get, fk18_set, "rswift_fk18_get", "rswift_fk18_set", RSWIFT_FK18_GET, RSWIFT_FK18_SET;
670    19, fk19_get, fk19_set, "rswift_fk19_get", "rswift_fk19_set", RSWIFT_FK19_GET, RSWIFT_FK19_SET;
671    20, fk20_get, fk20_set, "rswift_fk20_get", "rswift_fk20_set", RSWIFT_FK20_GET, RSWIFT_FK20_SET;
672    21, fk21_get, fk21_set, "rswift_fk21_get", "rswift_fk21_set", RSWIFT_FK21_GET, RSWIFT_FK21_SET;
673    22, fk22_get, fk22_set, "rswift_fk22_get", "rswift_fk22_set", RSWIFT_FK22_GET, RSWIFT_FK22_SET;
674    23, fk23_get, fk23_set, "rswift_fk23_get", "rswift_fk23_set", RSWIFT_FK23_GET, RSWIFT_FK23_SET;
675    24, fk24_get, fk24_set, "rswift_fk24_get", "rswift_fk24_set", RSWIFT_FK24_GET, RSWIFT_FK24_SET;
676    25, fk25_get, fk25_set, "rswift_fk25_get", "rswift_fk25_set", RSWIFT_FK25_GET, RSWIFT_FK25_SET;
677    26, fk26_get, fk26_set, "rswift_fk26_get", "rswift_fk26_set", RSWIFT_FK26_GET, RSWIFT_FK26_SET;
678    27, fk27_get, fk27_set, "rswift_fk27_get", "rswift_fk27_set", RSWIFT_FK27_GET, RSWIFT_FK27_SET;
679    28, fk28_get, fk28_set, "rswift_fk28_get", "rswift_fk28_set", RSWIFT_FK28_GET, RSWIFT_FK28_SET;
680    29, fk29_get, fk29_set, "rswift_fk29_get", "rswift_fk29_set", RSWIFT_FK29_GET, RSWIFT_FK29_SET;
681    30, fk30_get, fk30_set, "rswift_fk30_get", "rswift_fk30_set", RSWIFT_FK30_GET, RSWIFT_FK30_SET;
682    31, fk31_get, fk31_set, "rswift_fk31_get", "rswift_fk31_set", RSWIFT_FK31_GET, RSWIFT_FK31_SET;
683}
684
685/// Pattern storage for the synthesised key paths.
686///
687/// `static mut` and not a `Vec`: every internal reference in a pattern is a
688/// 32-bit offset and the thunks it points at are in this image's text segment,
689/// further from the heap than two gigabytes spans. [`build`] says the same at
690/// more length. It lives here, beside the thunks, because the pool is shared.
691static mut SLOTS: [WritableComputedSlot; VALUE_SLOTS] =
692    [const { WritableComputedSlot::new() }; VALUE_SLOTS];
693
694/// Hand out the next unused slot index.
695pub fn next_value_slot() -> usize {
696    use std::sync::atomic::{AtomicUsize, Ordering};
697    static NEXT: AtomicUsize = AtomicUsize::new(0);
698    let i = NEXT.fetch_add(1, Ordering::Relaxed);
699    assert!(
700        i < VALUE_SLOTS,
701        "this crate can synthesise {VALUE_SLOTS} key paths; the {}th was asked for",
702        i + 1,
703    );
704    i
705}
706
707/// The pattern slot at `index`.
708///
709/// # Safety
710/// One caller per index — [`next_value_slot`] is what guarantees that.
711pub unsafe fn value_slot(index: usize) -> &'static mut WritableComputedSlot {
712    unsafe { &mut SLOTS[index] }
713}