swiftui-native 0.0.1

SwiftUI from pure Rust — no Swift bridge, no shim, only swift-runtime calls
Documentation
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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
//! `WritableKeyPath` built at run time, without a compiler.
//!
//! `\EnvironmentValues.contentTransition` is not a symbol. It is a *pattern* —
//! a compact, compiler-emitted description that `swift_getKeyPath` turns into a
//! key path object — and several of SwiftUI's modifiers are
//! `@_alwaysEmitIntoClient` wrappers whose inlined body is
//! `environment(\.someKey, value)`. Reaching those from Rust means emitting the
//! pattern.
//!
//! # The pattern
//!
//! Forty-eight bytes, every internal reference a *32-bit* offset from the field
//! holding it:
//!
//! | offset | contents |
//! |--------|----------|
//! | `+0`   | relative pointer to an 8-byte instantiation cache, which must be writable |
//! | `+4`   | zero |
//! | `+8`   | relative pointer to the root type's mangled name, bit 0 set |
//! | `+12`  | relative pointer to the value type's mangled name, bit 0 set |
//! | `+16`  | zero |
//! | `+20`  | `KeyPathBuffer::Header` — `0x80000018`: trivial, 24 bytes of components |
//! | `+24`  | component header `0` — kind *External*, no substitutions |
//! | `+28`  | relative pointer to the property descriptor, with bit 0 set for *indirect* |
//! | `+32`  | component header `0x02c00002` — kind *Computed*, mutating, settable, id resolved by indirect pointer |
//! | `+36`  | relative pointer to a word holding the property's getter — the component's identity |
//! | `+40`  | relative pointer to the getter thunk |
//! | `+44`  | relative pointer to the setter thunk |
//!
//! The external component names the resilient property so the runtime can use
//! the framework's own key path if it has one; the computed component after it
//! is the local fallback, and the one that actually runs.
//!
//! # Why a `static`
//!
//! Those offsets are `i32`, and the thunks below are code — they live in this
//! binary's text segment, which is further from the heap than two gigabytes
//! spans. So the pattern cannot be a `Box`: it is a `static mut`, which puts it
//! in the same image as the thunks and makes every offset small. The same
//! constraint bit [`crate::conformance`], where the fix was one allocation; here
//! it has to be static storage.

use core::ffi::c_void;

/// The accessors one key path needs, resolved at run time.
#[derive(Clone, Copy)]
pub struct Property<'a> {
    /// The property's getter, `…vg`.
    pub getter: *const c_void,
    /// The property's setter, `…vs`.
    pub setter: *const c_void,
    /// The property descriptor, `…vpMV`.
    pub descriptor: *const c_void,
    /// The root type's mangled name, without the `$s` prefix.
    ///
    /// Borrowed only for the call: [`build`] copies it into the slot, which is
    /// what has to outlive everything.
    pub root: &'a [u8],
    /// The value type's mangled name, without the `$s` prefix.
    pub value: &'a [u8],
}

/// Storage for one key path pattern, laid out so every offset is small.
#[repr(C, align(8))]
pub struct Slot {
    cache: u64,
    pattern: [u32; 12],
    descriptor: u64,
    id: u64,
    root: [u8; 64],
    value: [u8; 64],
}

impl Slot {
    /// An empty slot, to be filled by [`build`].
    pub const fn new() -> Self {
        Slot {
            cache: 0,
            pattern: [0; 12],
            descriptor: 0,
            id: 0,
            root: [0; 64],
            value: [0; 64],
        }
    }
}

impl Default for Slot {
    fn default() -> Self {
        Self::new()
    }
}

/// `KeyPathBuffer::Header`: trivial, with 24 bytes of components after it.
const BUFFER_HEADER: u32 = 0x8000_0018;
/// Component kind *External*, with no generic substitutions.
const EXTERNAL_COMPONENT: u32 = 0;
/// Component kind *Computed*, mutating and settable, id resolved indirectly.
const COMPUTED_COMPONENT: u32 = 0x02c0_0002;

/// A 32-bit relative offset from `at` to `to`, checked rather than truncated.
unsafe fn offset(to: *const u8, at: *const u8) -> i32 {
    let delta = to as isize - at as isize;
    i32::try_from(delta).expect("key path relative pointer out of range")
}

/// Fill `slot` in and hand back a `WritableKeyPath` for `property`.
///
/// The returned object is retained for the process's lifetime: SwiftUI keeps
/// the key path inside whatever modifier it was handed to.
///
/// # Safety
/// `slot` must be a `static mut` in this image, used by one property only, and
/// `thunks` must be the pair below whose target words this call also sets.
pub unsafe fn build(
    slot: &'static mut Slot,
    property: Property<'_>,
    thunks: (*const c_void, *const c_void, *mut *const c_void, *mut *const c_void),
) -> u64 {
    unsafe {
        let (get_thunk, set_thunk, get_target, set_target) = thunks;
        *get_target = property.getter;
        *set_target = property.setter;
        slot.descriptor = property.descriptor as u64;
        slot.id = property.getter as u64;
        slot.root[..property.root.len()].copy_from_slice(property.root);
        slot.value[..property.value.len()].copy_from_slice(property.value);

        let base = slot.pattern.as_ptr() as *const u8;
        let at = |i: usize| base.add(i * 4);
        slot.pattern[0] = offset(&slot.cache as *const u64 as *const u8, at(0)) as u32;
        slot.pattern[1] = 0;
        // Bit 0 says "the target is a *mangled name*", not type metadata. With
        // it clear the runtime reads the string as a `Metadata *` and walks off
        // into `checkTransitiveCompleteness` with a pointer to ASCII.
        slot.pattern[2] = (offset(slot.root.as_ptr(), at(2)) | 1) as u32;
        slot.pattern[3] = (offset(slot.value.as_ptr(), at(3)) | 1) as u32;
        slot.pattern[4] = 0;
        slot.pattern[5] = BUFFER_HEADER;
        slot.pattern[6] = EXTERNAL_COMPONENT;
        // Bit 0 marks the reference indirect: the offset reaches a *word holding*
        // the descriptor, not the descriptor, because a resilient property's
        // descriptor is in another image and further away than an i32 spans.
        slot.pattern[7] = (offset(&slot.descriptor as *const u64 as *const u8, at(7)) | 1) as u32;
        slot.pattern[8] = COMPUTED_COMPONENT;
        slot.pattern[9] = offset(&slot.id as *const u64 as *const u8, at(9)) as u32;
        slot.pattern[10] = offset(get_thunk as *const u8, at(10)) as u32;
        slot.pattern[11] = offset(set_thunk as *const u8, at(11)) as u32;

        type GetKeyPath = unsafe extern "C" fn(*const c_void, *const c_void) -> u64;
        let f: GetKeyPath = core::mem::transmute(crate::resolve::get_key_path());
        f(base as *const c_void, core::ptr::null())
    }
}

// A key path's getter is called with the root's address in x0 and the result's
// in x8; a property getter wants the root in x20 and leaves x8 alone. So the
// thunk is one move and a tail call — which matters, because a tail call is
// what keeps x8 intact.
//
// A setter is the same shape: the new value is already in x0 where the property
// setter wants it, and only the root has to move from x1 to x20.
//
// One pair per key path, because a thunk has no context to carry its target in.
// They are handed out in order by `next_thunks`, so no call site picks an index.
macro_rules! key_path_thunks {
    ($($get:ident, $set:ident, $get_sym:literal, $set_sym:literal,
       $get_tgt:ident, $set_tgt:ident;)*) => {$(
        crate::thunk::runtime_thunk! {
            /// A key path getter: root in x0, result through x8.
            fn $get = $get_sym via $get_tgt {
                prologue: ["mov x20, x0"], branch: "br", epilogue: [],
            }
            /// A key path setter: new value in x0, root in x1.
            fn $set = $set_sym via $set_tgt {
                prologue: ["mov x20, x1"], branch: "br", epilogue: [],
            }
        }
    )*};
}

key_path_thunks! {
    kp0_get, kp0_set, "rswift_kp0_get", "rswift_kp0_set", RSWIFT_KP0_GET, RSWIFT_KP0_SET;
    kp1_get, kp1_set, "rswift_kp1_get", "rswift_kp1_set", RSWIFT_KP1_GET, RSWIFT_KP1_SET;
    kp2_get, kp2_set, "rswift_kp2_get", "rswift_kp2_set", RSWIFT_KP2_GET, RSWIFT_KP2_SET;
    kp3_get, kp3_set, "rswift_kp3_get", "rswift_kp3_set", RSWIFT_KP3_GET, RSWIFT_KP3_SET;
    kp4_get, kp4_set, "rswift_kp4_get", "rswift_kp4_set", RSWIFT_KP4_GET, RSWIFT_KP4_SET;
    kp5_get, kp5_set, "rswift_kp5_get", "rswift_kp5_set", RSWIFT_KP5_GET, RSWIFT_KP5_SET;
    kp6_get, kp6_set, "rswift_kp6_get", "rswift_kp6_set", RSWIFT_KP6_GET, RSWIFT_KP6_SET;
    kp7_get, kp7_set, "rswift_kp7_get", "rswift_kp7_set", RSWIFT_KP7_GET, RSWIFT_KP7_SET;
    kp8_get, kp8_set, "rswift_kp8_get", "rswift_kp8_set", RSWIFT_KP8_GET, RSWIFT_KP8_SET;
    kp9_get, kp9_set, "rswift_kp9_get", "rswift_kp9_set", RSWIFT_KP9_GET, RSWIFT_KP9_SET;
    kp10_get, kp10_set, "rswift_kp10_get", "rswift_kp10_set", RSWIFT_KP10_GET, RSWIFT_KP10_SET;
    kp11_get, kp11_set, "rswift_kp11_get", "rswift_kp11_set", RSWIFT_KP11_GET, RSWIFT_KP11_SET;
    kp12_get, kp12_set, "rswift_kp12_get", "rswift_kp12_set", RSWIFT_KP12_GET, RSWIFT_KP12_SET;
    kp13_get, kp13_set, "rswift_kp13_get", "rswift_kp13_set", RSWIFT_KP13_GET, RSWIFT_KP13_SET;
    kp14_get, kp14_set, "rswift_kp14_get", "rswift_kp14_set", RSWIFT_KP14_GET, RSWIFT_KP14_SET;
    kp15_get, kp15_set, "rswift_kp15_get", "rswift_kp15_set", RSWIFT_KP15_GET, RSWIFT_KP15_SET;
    kp16_get, kp16_set, "rswift_kp16_get", "rswift_kp16_set", RSWIFT_KP16_GET, RSWIFT_KP16_SET;
    kp17_get, kp17_set, "rswift_kp17_get", "rswift_kp17_set", RSWIFT_KP17_GET, RSWIFT_KP17_SET;
    kp18_get, kp18_set, "rswift_kp18_get", "rswift_kp18_set", RSWIFT_KP18_GET, RSWIFT_KP18_SET;
    kp19_get, kp19_set, "rswift_kp19_get", "rswift_kp19_set", RSWIFT_KP19_GET, RSWIFT_KP19_SET;
    kp20_get, kp20_set, "rswift_kp20_get", "rswift_kp20_set", RSWIFT_KP20_GET, RSWIFT_KP20_SET;
    kp21_get, kp21_set, "rswift_kp21_get", "rswift_kp21_set", RSWIFT_KP21_GET, RSWIFT_KP21_SET;
    kp22_get, kp22_set, "rswift_kp22_get", "rswift_kp22_set", RSWIFT_KP22_GET, RSWIFT_KP22_SET;
    kp23_get, kp23_set, "rswift_kp23_get", "rswift_kp23_set", RSWIFT_KP23_GET, RSWIFT_KP23_SET;
    kp24_get, kp24_set, "rswift_kp24_get", "rswift_kp24_set", RSWIFT_KP24_GET, RSWIFT_KP24_SET;
    kp25_get, kp25_set, "rswift_kp25_get", "rswift_kp25_set", RSWIFT_KP25_GET, RSWIFT_KP25_SET;
    kp26_get, kp26_set, "rswift_kp26_get", "rswift_kp26_set", RSWIFT_KP26_GET, RSWIFT_KP26_SET;
    kp27_get, kp27_set, "rswift_kp27_get", "rswift_kp27_set", RSWIFT_KP27_GET, RSWIFT_KP27_SET;
    kp28_get, kp28_set, "rswift_kp28_get", "rswift_kp28_set", RSWIFT_KP28_GET, RSWIFT_KP28_SET;
    kp29_get, kp29_set, "rswift_kp29_get", "rswift_kp29_set", RSWIFT_KP29_GET, RSWIFT_KP29_SET;
    kp30_get, kp30_set, "rswift_kp30_get", "rswift_kp30_set", RSWIFT_KP30_GET, RSWIFT_KP30_SET;
    kp31_get, kp31_set, "rswift_kp31_get", "rswift_kp31_set", RSWIFT_KP31_GET, RSWIFT_KP31_SET;
}

/// How many key paths this crate can build.
pub const THUNK_COUNT: usize = 32;

/// The thunk pair at `index`.
///
/// One pair per key path, because a thunk has no context to carry its target
/// in. Out of range is a panic rather than a wrap-around: the old dispatcher
/// answered every index past the last one with the *last* pair, so a ninth key
/// path silently read the eighth one's property. Prefer [`next_thunks`], which
/// makes the question not arise.
pub fn thunks(index: usize) -> (*const c_void, *const c_void, *mut *const c_void, *mut *const c_void) {
    match index {
        0 => (kp0_get as *const c_void, kp0_set as *const c_void,
              &raw mut RSWIFT_KP0_GET, &raw mut RSWIFT_KP0_SET),
        1 => (kp1_get as *const c_void, kp1_set as *const c_void,
              &raw mut RSWIFT_KP1_GET, &raw mut RSWIFT_KP1_SET),
        2 => (kp2_get as *const c_void, kp2_set as *const c_void,
              &raw mut RSWIFT_KP2_GET, &raw mut RSWIFT_KP2_SET),
        3 => (kp3_get as *const c_void, kp3_set as *const c_void,
              &raw mut RSWIFT_KP3_GET, &raw mut RSWIFT_KP3_SET),
        4 => (kp4_get as *const c_void, kp4_set as *const c_void,
              &raw mut RSWIFT_KP4_GET, &raw mut RSWIFT_KP4_SET),
        5 => (kp5_get as *const c_void, kp5_set as *const c_void,
              &raw mut RSWIFT_KP5_GET, &raw mut RSWIFT_KP5_SET),
        6 => (kp6_get as *const c_void, kp6_set as *const c_void,
              &raw mut RSWIFT_KP6_GET, &raw mut RSWIFT_KP6_SET),
        7 => (kp7_get as *const c_void, kp7_set as *const c_void,
              &raw mut RSWIFT_KP7_GET, &raw mut RSWIFT_KP7_SET),
        8 => (kp8_get as *const c_void, kp8_set as *const c_void,
              &raw mut RSWIFT_KP8_GET, &raw mut RSWIFT_KP8_SET),
        9 => (kp9_get as *const c_void, kp9_set as *const c_void,
              &raw mut RSWIFT_KP9_GET, &raw mut RSWIFT_KP9_SET),
        10 => (kp10_get as *const c_void, kp10_set as *const c_void,
              &raw mut RSWIFT_KP10_GET, &raw mut RSWIFT_KP10_SET),
        11 => (kp11_get as *const c_void, kp11_set as *const c_void,
              &raw mut RSWIFT_KP11_GET, &raw mut RSWIFT_KP11_SET),
        12 => (kp12_get as *const c_void, kp12_set as *const c_void,
              &raw mut RSWIFT_KP12_GET, &raw mut RSWIFT_KP12_SET),
        13 => (kp13_get as *const c_void, kp13_set as *const c_void,
              &raw mut RSWIFT_KP13_GET, &raw mut RSWIFT_KP13_SET),
        14 => (kp14_get as *const c_void, kp14_set as *const c_void,
              &raw mut RSWIFT_KP14_GET, &raw mut RSWIFT_KP14_SET),
        15 => (kp15_get as *const c_void, kp15_set as *const c_void,
              &raw mut RSWIFT_KP15_GET, &raw mut RSWIFT_KP15_SET),
        16 => (kp16_get as *const c_void, kp16_set as *const c_void,
              &raw mut RSWIFT_KP16_GET, &raw mut RSWIFT_KP16_SET),
        17 => (kp17_get as *const c_void, kp17_set as *const c_void,
              &raw mut RSWIFT_KP17_GET, &raw mut RSWIFT_KP17_SET),
        18 => (kp18_get as *const c_void, kp18_set as *const c_void,
              &raw mut RSWIFT_KP18_GET, &raw mut RSWIFT_KP18_SET),
        19 => (kp19_get as *const c_void, kp19_set as *const c_void,
              &raw mut RSWIFT_KP19_GET, &raw mut RSWIFT_KP19_SET),
        20 => (kp20_get as *const c_void, kp20_set as *const c_void,
              &raw mut RSWIFT_KP20_GET, &raw mut RSWIFT_KP20_SET),
        21 => (kp21_get as *const c_void, kp21_set as *const c_void,
              &raw mut RSWIFT_KP21_GET, &raw mut RSWIFT_KP21_SET),
        22 => (kp22_get as *const c_void, kp22_set as *const c_void,
              &raw mut RSWIFT_KP22_GET, &raw mut RSWIFT_KP22_SET),
        23 => (kp23_get as *const c_void, kp23_set as *const c_void,
              &raw mut RSWIFT_KP23_GET, &raw mut RSWIFT_KP23_SET),
        24 => (kp24_get as *const c_void, kp24_set as *const c_void,
              &raw mut RSWIFT_KP24_GET, &raw mut RSWIFT_KP24_SET),
        25 => (kp25_get as *const c_void, kp25_set as *const c_void,
              &raw mut RSWIFT_KP25_GET, &raw mut RSWIFT_KP25_SET),
        26 => (kp26_get as *const c_void, kp26_set as *const c_void,
              &raw mut RSWIFT_KP26_GET, &raw mut RSWIFT_KP26_SET),
        27 => (kp27_get as *const c_void, kp27_set as *const c_void,
              &raw mut RSWIFT_KP27_GET, &raw mut RSWIFT_KP27_SET),
        28 => (kp28_get as *const c_void, kp28_set as *const c_void,
              &raw mut RSWIFT_KP28_GET, &raw mut RSWIFT_KP28_SET),
        29 => (kp29_get as *const c_void, kp29_set as *const c_void,
              &raw mut RSWIFT_KP29_GET, &raw mut RSWIFT_KP29_SET),
        30 => (kp30_get as *const c_void, kp30_set as *const c_void,
              &raw mut RSWIFT_KP30_GET, &raw mut RSWIFT_KP30_SET),
        31 => (kp31_get as *const c_void, kp31_set as *const c_void,
              &raw mut RSWIFT_KP31_GET, &raw mut RSWIFT_KP31_SET),
        _ => panic!(
            "key path {index} is past the {THUNK_COUNT} thunk pairs; \
             add more to key_path_thunks!"
        ),
    }
}

/// Take the next unused thunk pair.
///
/// Callers used to pass an index they had chosen by reading the other call
/// sites, which is a collision waiting to happen — two key paths sharing a pair
/// would dispatch to whichever was built last, with nothing to say so. Each key
/// path is built once and caches its result, so handing them out in order is
/// enough.
pub fn next_thunks() -> (*const c_void, *const c_void, *mut *const c_void, *mut *const c_void) {
    static NEXT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
    let i = NEXT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
    assert!(
        i < THUNK_COUNT,
        "more than {THUNK_COUNT} key paths; add more pairs to key_path_thunks!"
    );
    thunks(i)
}

// ── Read-only computed key paths, with no Swift property behind them ──

/// `KeyPathBuffer::Header` for a get-only computed component: trivial, 12 bytes.
const COMPUTED_BUFFER_HEADER: u32 = 0x8000_000C;
/// Component kind *Computed*, get-only, identity resolved as a direct pointer.
const COMPUTED_GET_ONLY: u32 = 0x0200_0000;

/// Storage for one get-only computed key path pattern.
///
/// Nine words rather than [`Slot`]'s twelve: there is no *External* component,
/// because there is no resilient framework property to defer to. The whole key
/// path is the local computed component.
#[repr(C, align(8))]
pub struct ComputedSlot {
    cache: u64,
    pattern: [u32; 9],
    _pad: u32,
    root: [u8; 48],
    value: [u8; 48],
}

impl ComputedSlot {
    /// An empty slot, to be filled by [`build_computed`].
    pub const fn new() -> Self {
        ComputedSlot { cache: 0, pattern: [0; 9], _pad: 0, root: [0; 48], value: [0; 48] }
    }
}

impl Default for ComputedSlot {
    fn default() -> Self {
        Self::new()
    }
}

/// A `KeyPath<Root, Value>` whose getter is a Rust function.
///
/// `OutlineGroup` needs `KeyPath<Element, Data?>`, and no type in any linked
/// framework has a recursive `children` property with an exported property
/// descriptor — so there is nothing for the [`Slot`] shape's *External*
/// component to name. A get-only computed component needs no such property: it
/// carries a getter and an identity, and the runtime calls the getter.
///
/// The key path's *class* metadata is `KeyPath<Root, Value>`, built by the
/// runtime from the two mangled names below. That matters beyond type checking:
/// `OutlineGroup.init` is passed metadata only for its `Leaf`, and recovers
/// `Data`, `ID` and `Element` from the key paths it is handed.
///
/// # Safety
/// `slot` must be a `static mut` in this image — the pattern's internal
/// references are 32-bit, and [`build`] explains why that rules out the heap.
/// `getter` must have the thunk shape below: root in x0, result through x8.
pub unsafe fn build_computed(
    slot: &'static mut ComputedSlot,
    root: &[u8],
    value: &[u8],
    getter: *const c_void,
) -> u64 {
    unsafe {
        slot.root[..root.len()].copy_from_slice(root);
        slot.value[..value.len()].copy_from_slice(value);

        let base = slot.pattern.as_ptr() as *const u8;
        let at = |i: usize| base.add(i * 4);
        slot.pattern[0] = offset(&slot.cache as *const u64 as *const u8, at(0)) as u32;
        slot.pattern[1] = 0;
        slot.pattern[2] = (offset(slot.root.as_ptr(), at(2)) | 1) as u32;
        slot.pattern[3] = (offset(slot.value.as_ptr(), at(3)) | 1) as u32;
        slot.pattern[4] = 0;
        slot.pattern[5] = COMPUTED_BUFFER_HEADER;
        slot.pattern[6] = COMPUTED_GET_ONLY;
        // The identity is what key path equality compares. Swift uses the
        // property's own getter; any stable, unique pointer does, and the thunk
        // is one.
        slot.pattern[7] = offset(getter as *const u8, at(7)) as u32;
        slot.pattern[8] = offset(getter as *const u8, at(8)) as u32;

        type GetKeyPath = unsafe extern "C" fn(*const c_void, *const c_void) -> u64;
        let f: GetKeyPath = core::mem::transmute(crate::resolve::get_key_path());
        f(base as *const c_void, core::ptr::null())
    }
}

// A computed key path's getter thunk is
//   (@out Value in x8, @in_guaranteed Root in x0, arguments in x1, size in x2)
// so the only shuffle needed is x8 into an argument register. There is no
// context register, which is why each key path needs its own thunk rather than
// one dispatcher — the same reason the pairs above are numbered.
core::arch::global_asm!(
    ".p2align 2",
    ".globl _rswift_kp_nil_children_get",
    "_rswift_kp_nil_children_get:",
    "mov x1, x0",
    "mov x0, x8",
    "b _rswift_kp_nil_children_body",
);

unsafe extern "C" {
    /// Getter for a `KeyPath<Int, [Int]?>` that always answers `nil`.
    #[link_name = "rswift_kp_nil_children_get"]
    pub fn kp_nil_children_get();
}

#[unsafe(no_mangle)]
unsafe extern "C" fn rswift_kp_nil_children_body(out: *mut u8, _root: *const u8) {
    // `Array` is one non-null word, so `Optional<Array>` spends the null pointer
    // on `.none` — writing zero *is* writing `nil`, and nothing is retained.
    unsafe { core::ptr::write_bytes(out, 0, 8) };
}

// ── Identity key paths ──

/// `KeyPathBuffer::Header` for a key path with no components at all.
const IDENTITY_BUFFER_HEADER: u32 = 0x8000_0000;

/// Storage for one identity key path pattern.
///
/// Six words and no components — `\T.self` is not a property access, it is the
/// absence of one, and the header says so by declaring zero bytes of components.
#[repr(C, align(8))]
pub struct IdentitySlot {
    cache: u64,
    pattern: [u32; 6],
    _pad: [u32; 2],
    root: [u8; 48],
    value: [u8; 48],
}

impl IdentitySlot {
    /// An empty slot, to be filled by [`build_identity`].
    pub const fn new() -> Self {
        IdentitySlot { cache: 0, pattern: [0; 6], _pad: [0; 2], root: [0; 48], value: [0; 48] }
    }
}

impl Default for IdentitySlot {
    fn default() -> Self {
        Self::new()
    }
}

/// `\Root.self`, built at run time.
///
/// Worth having a third shape for. A computed key path describes itself by
/// asking `dladdr` what symbol sits at its getter, so one synthesised here
/// reflects as `\Int.<computed 0x…>` where a Swift property reflects as
/// `\Int.someName` — a difference in the two binaries' symbol tables rather than
/// in the key paths. An identity key path has no getter to name, so both sides
/// build the same six words and reflect identically.
///
/// (That naming is only as good as the symbol table, incidentally: with `-O`,
/// `\Int.pubIdx` printed as `\Int.comp` because two getters with identical
/// bodies had been merged.)
///
/// # Safety
/// `slot` must be a `static mut` in this image, for the reason [`build`] gives.
pub unsafe fn build_identity(slot: &'static mut IdentitySlot, root: &[u8], value: &[u8]) -> u64 {
    unsafe {
        slot.root[..root.len()].copy_from_slice(root);
        slot.value[..value.len()].copy_from_slice(value);

        let base = slot.pattern.as_ptr() as *const u8;
        let at = |i: usize| base.add(i * 4);
        slot.pattern[0] = offset(&slot.cache as *const u64 as *const u8, at(0)) as u32;
        slot.pattern[1] = 0;
        slot.pattern[2] = (offset(slot.root.as_ptr(), at(2)) | 1) as u32;
        slot.pattern[3] = (offset(slot.value.as_ptr(), at(3)) | 1) as u32;
        slot.pattern[4] = 0;
        slot.pattern[5] = IDENTITY_BUFFER_HEADER;

        type GetKeyPath = unsafe extern "C" fn(*const c_void, *const c_void) -> u64;
        let f: GetKeyPath = core::mem::transmute(crate::resolve::get_key_path());
        f(base as *const c_void, core::ptr::null())
    }
}


// ── Settable computed key paths, with no property behind them ──

/// `KeyPathBuffer::Header`: trivial, 16 bytes of components — a computed
/// component with an identity, a getter *and* a setter.
const WRITABLE_BUFFER_HEADER: u32 = 0x8000_0010;
/// Component kind *Computed*, settable and mutating, identity resolved as a
/// direct pointer.
///
/// [`COMPUTED_COMPONENT`] is the same kind with bit 1 set, which makes the
/// identity an *indirect* reference — right when the identity is a resilient
/// property's descriptor in another image, wrong here, where it is a thunk in
/// this one. This is the value `swiftc` emits for `\FocusedValues.myProperty`,
/// read out of its IR rather than assembled from the bit definitions.
const WRITABLE_COMPUTED_COMPONENT: u32 = 0x02c0_0000;

/// Storage for one settable computed key path pattern.
///
/// Ten words rather than [`Slot`]'s twelve: there is no *External* component,
/// because there is no exported property to defer to — `FocusedValues` has none
/// at all. And four words of component rather than three, because a settable one
/// carries a setter after its getter.
#[repr(C, align(8))]
pub struct WritableComputedSlot {
    cache: u64,
    pattern: [u32; 10],
    _pad: [u32; 2],
    root: [u8; 64],
    value: [u8; 64],
}

impl WritableComputedSlot {
    /// An empty slot, to be filled by [`build_writable_computed`].
    pub const fn new() -> Self {
        WritableComputedSlot {
            cache: 0,
            pattern: [0; 10],
            _pad: [0; 2],
            root: [0; 64],
            value: [0; 64],
        }
    }
}

impl Default for WritableComputedSlot {
    fn default() -> Self {
        Self::new()
    }
}

/// A `WritableKeyPath<Root, Value>` whose accessors are Rust functions.
///
/// What a *client* key path into a resilient struct looks like: no property
/// descriptor to name, just a getter and a setter the client emitted. Swift
/// builds exactly this for `\FocusedValues.myFocus`, where `myFocus` is a
/// computed property the client declared — and the reason this exists is that
/// `FocusedValues` exports no properties, so [`build`]'s *External* component
/// has nothing to point at.
///
/// # Safety
/// `slot` must be a `static mut` in this image, used by one key path only —
/// [`build`] explains why the heap will not do. `getter` must take the root in
/// x0 and write through x8; `setter` must take the new value in x0 and the root
/// in x1.
pub unsafe fn build_writable_computed(
    slot: &'static mut WritableComputedSlot,
    root: &[u8],
    value: &[u8],
    getter: *const c_void,
    setter: *const c_void,
) -> u64 {
    unsafe {
        slot.root[..root.len()].copy_from_slice(root);
        slot.value[..value.len()].copy_from_slice(value);

        let base = slot.pattern.as_ptr() as *const u8;
        let at = |i: usize| base.add(i * 4);
        slot.pattern[0] = offset(&slot.cache as *const u64 as *const u8, at(0)) as u32;
        slot.pattern[1] = 0;
        slot.pattern[2] = (offset(slot.root.as_ptr(), at(2)) | 1) as u32;
        slot.pattern[3] = (offset(slot.value.as_ptr(), at(3)) | 1) as u32;
        slot.pattern[4] = 0;
        slot.pattern[5] = WRITABLE_BUFFER_HEADER;
        slot.pattern[6] = WRITABLE_COMPUTED_COMPONENT;
        // The identity is what key path equality compares. Swift uses the
        // property's own getter; the getter thunk is unique per key path, which
        // is the property this needs.
        slot.pattern[7] = offset(getter as *const u8, at(7)) as u32;
        slot.pattern[8] = offset(getter as *const u8, at(8)) as u32;
        slot.pattern[9] = offset(setter as *const u8, at(9)) as u32;

        type GetKeyPath = unsafe extern "C" fn(*const c_void, *const c_void) -> u64;
        let f: GetKeyPath = core::mem::transmute(crate::resolve::get_key_path());
        f(base as *const c_void, core::ptr::null())
    }
}

// One thunk pair per focused key, because a key path accessor has nowhere to
// carry a context: the index is baked into the instruction stream.
//
// SwiftUI calls a key path getter with the result's address in x8, the root's in
// x0, the arguments in x1 and their size in x2; a setter with the new value in
// x0, the root in x1, and the same pair after. Both are shuffled into the C
// argument registers, with the slot index appended, so the bodies are ordinary
// Rust functions.
macro_rules! value_thunks {
    ($($i:literal, $get:ident, $set:ident, $gsym:literal, $ssym:literal,
       $gtgt:ident, $stgt:ident;)*) => {
        $(
            crate::thunk::runtime_thunk! {
                /// A synthesised key path's getter.
                fn $get = $gsym via $gtgt {
                    prologue: ["mov x1, x0", "mov x0, x8", concat!("mov x2, #", $i)],
                    branch: "br", epilogue: [],
                }
                /// A synthesised key path's setter.
                fn $set = $ssym via $stgt {
                    prologue: [concat!("mov x2, #", $i)],
                    branch: "br", epilogue: [],
                }
            }
        )*

        /// How many key paths this crate can synthesise from scratch.
        ///
        /// Shared between every kind: a focused-value key, an environment key, a
        /// container-value key and each named property of an observable model all
        /// need a thunk pair and a pattern slot, and nothing about any of them
        /// makes its pool private. [`next_value_slot`] hands them out in order so
        /// no caller picks an index.
        ///
        /// The count is a *declaration* count, not a runtime one: each slot is a
        /// pair of thunks with its index assembled into the instruction stream,
        /// so raising it means adding lines to the invocation below. Observable
        /// properties are what made sixteen tight — a model with four properties
        /// takes four — so there are thirty-two.
        pub const VALUE_SLOTS: usize = [$($i),*].len();

        /// The thunk pair for slot `index`, and the words they dispatch through.
        pub fn value_thunks(
            index: usize,
        ) -> (*const c_void, *const c_void, *mut *const c_void, *mut *const c_void) {
            match index {
                $($i => (
                    $get as *const c_void,
                    $set as *const c_void,
                    &raw mut $gtgt,
                    &raw mut $stgt,
                ),)*
                _ => panic!(
                    "key path slot {index} is past the {} this crate has",
                    VALUE_SLOTS,
                ),
            }
        }
    };
}

value_thunks! {
    0, fk0_get, fk0_set, "rswift_fk0_get", "rswift_fk0_set", RSWIFT_FK0_GET, RSWIFT_FK0_SET;
    1, fk1_get, fk1_set, "rswift_fk1_get", "rswift_fk1_set", RSWIFT_FK1_GET, RSWIFT_FK1_SET;
    2, fk2_get, fk2_set, "rswift_fk2_get", "rswift_fk2_set", RSWIFT_FK2_GET, RSWIFT_FK2_SET;
    3, fk3_get, fk3_set, "rswift_fk3_get", "rswift_fk3_set", RSWIFT_FK3_GET, RSWIFT_FK3_SET;
    4, fk4_get, fk4_set, "rswift_fk4_get", "rswift_fk4_set", RSWIFT_FK4_GET, RSWIFT_FK4_SET;
    5, fk5_get, fk5_set, "rswift_fk5_get", "rswift_fk5_set", RSWIFT_FK5_GET, RSWIFT_FK5_SET;
    6, fk6_get, fk6_set, "rswift_fk6_get", "rswift_fk6_set", RSWIFT_FK6_GET, RSWIFT_FK6_SET;
    7, fk7_get, fk7_set, "rswift_fk7_get", "rswift_fk7_set", RSWIFT_FK7_GET, RSWIFT_FK7_SET;
    8, fk8_get, fk8_set, "rswift_fk8_get", "rswift_fk8_set", RSWIFT_FK8_GET, RSWIFT_FK8_SET;
    9, fk9_get, fk9_set, "rswift_fk9_get", "rswift_fk9_set", RSWIFT_FK9_GET, RSWIFT_FK9_SET;
    10, fk10_get, fk10_set, "rswift_fk10_get", "rswift_fk10_set", RSWIFT_FK10_GET, RSWIFT_FK10_SET;
    11, fk11_get, fk11_set, "rswift_fk11_get", "rswift_fk11_set", RSWIFT_FK11_GET, RSWIFT_FK11_SET;
    12, fk12_get, fk12_set, "rswift_fk12_get", "rswift_fk12_set", RSWIFT_FK12_GET, RSWIFT_FK12_SET;
    13, fk13_get, fk13_set, "rswift_fk13_get", "rswift_fk13_set", RSWIFT_FK13_GET, RSWIFT_FK13_SET;
    14, fk14_get, fk14_set, "rswift_fk14_get", "rswift_fk14_set", RSWIFT_FK14_GET, RSWIFT_FK14_SET;
    15, fk15_get, fk15_set, "rswift_fk15_get", "rswift_fk15_set", RSWIFT_FK15_GET, RSWIFT_FK15_SET;
    16, fk16_get, fk16_set, "rswift_fk16_get", "rswift_fk16_set", RSWIFT_FK16_GET, RSWIFT_FK16_SET;
    17, fk17_get, fk17_set, "rswift_fk17_get", "rswift_fk17_set", RSWIFT_FK17_GET, RSWIFT_FK17_SET;
    18, fk18_get, fk18_set, "rswift_fk18_get", "rswift_fk18_set", RSWIFT_FK18_GET, RSWIFT_FK18_SET;
    19, fk19_get, fk19_set, "rswift_fk19_get", "rswift_fk19_set", RSWIFT_FK19_GET, RSWIFT_FK19_SET;
    20, fk20_get, fk20_set, "rswift_fk20_get", "rswift_fk20_set", RSWIFT_FK20_GET, RSWIFT_FK20_SET;
    21, fk21_get, fk21_set, "rswift_fk21_get", "rswift_fk21_set", RSWIFT_FK21_GET, RSWIFT_FK21_SET;
    22, fk22_get, fk22_set, "rswift_fk22_get", "rswift_fk22_set", RSWIFT_FK22_GET, RSWIFT_FK22_SET;
    23, fk23_get, fk23_set, "rswift_fk23_get", "rswift_fk23_set", RSWIFT_FK23_GET, RSWIFT_FK23_SET;
    24, fk24_get, fk24_set, "rswift_fk24_get", "rswift_fk24_set", RSWIFT_FK24_GET, RSWIFT_FK24_SET;
    25, fk25_get, fk25_set, "rswift_fk25_get", "rswift_fk25_set", RSWIFT_FK25_GET, RSWIFT_FK25_SET;
    26, fk26_get, fk26_set, "rswift_fk26_get", "rswift_fk26_set", RSWIFT_FK26_GET, RSWIFT_FK26_SET;
    27, fk27_get, fk27_set, "rswift_fk27_get", "rswift_fk27_set", RSWIFT_FK27_GET, RSWIFT_FK27_SET;
    28, fk28_get, fk28_set, "rswift_fk28_get", "rswift_fk28_set", RSWIFT_FK28_GET, RSWIFT_FK28_SET;
    29, fk29_get, fk29_set, "rswift_fk29_get", "rswift_fk29_set", RSWIFT_FK29_GET, RSWIFT_FK29_SET;
    30, fk30_get, fk30_set, "rswift_fk30_get", "rswift_fk30_set", RSWIFT_FK30_GET, RSWIFT_FK30_SET;
    31, fk31_get, fk31_set, "rswift_fk31_get", "rswift_fk31_set", RSWIFT_FK31_GET, RSWIFT_FK31_SET;
}

/// Pattern storage for the synthesised key paths.
///
/// `static mut` and not a `Vec`: every internal reference in a pattern is a
/// 32-bit offset and the thunks it points at are in this image's text segment,
/// further from the heap than two gigabytes spans. [`build`] says the same at
/// more length. It lives here, beside the thunks, because the pool is shared.
static mut SLOTS: [WritableComputedSlot; VALUE_SLOTS] =
    [const { WritableComputedSlot::new() }; VALUE_SLOTS];

/// Hand out the next unused slot index.
pub fn next_value_slot() -> usize {
    use std::sync::atomic::{AtomicUsize, Ordering};
    static NEXT: AtomicUsize = AtomicUsize::new(0);
    let i = NEXT.fetch_add(1, Ordering::Relaxed);
    assert!(
        i < VALUE_SLOTS,
        "this crate can synthesise {VALUE_SLOTS} key paths; the {}th was asked for",
        i + 1,
    );
    i
}

/// The pattern slot at `index`.
///
/// # Safety
/// One caller per index — [`next_value_slot`] is what guarantees that.
pub unsafe fn value_slot(index: usize) -> &'static mut WritableComputedSlot {
    unsafe { &mut SLOTS[index] }
}