Skip to main content

swiftui_native/abi/
modifier.rs

1//! Applying a `View` modifier, and wrapping the result back into `AnyView`.
2
3use super::*;
4use core::ffi::c_void;
5
6/// Wrap any view value in AnyView.
7///
8/// ARM64 calling convention (from SIL + ASM analysis):
9///   x0 = pointer to V value (@in, consumed) — may be arbitrary for zero-size types
10///   x1 = V.Type metadata (generic type parameter)
11///   x2 = V:View witness table
12///   Returns: x0 = AnyView (8-byte class ref, owned)
13/// # Safety
14/// `value_ptr` must address an initialised value of the type `value_meta`
15/// describes, and `value_view_wt` must be that type's `View` conformance. The
16/// value is `@in` — consumed — so it must be a `+1` copy the caller gives up.
17pub unsafe fn anyview_wrap(
18    value_ptr: *const c_void,
19    value_meta: *const c_void,
20    value_view_wt: *const c_void,
21) -> u64 {
22    unsafe {
23        let func = resolve::anyview_init();
24        let vwt = &*swift_runtime_sys::SwiftABI::get_value_witness_table(value_meta);
25        // For @in: need a valid pointer even for zero-size types.
26        let mut stack_buf = [0u8; 128];
27        let actual_ptr = if vwt.size == 0 {
28            stack_buf.as_mut_ptr() as *const c_void
29        } else if vwt.size <= 128 {
30            core::ptr::copy_nonoverlapping(
31                value_ptr as *const u8, stack_buf.as_mut_ptr(), vwt.size,
32            );
33            stack_buf.as_mut_ptr() as *const c_void
34        } else {
35            value_ptr
36        };
37        let result: u64;
38        core::arch::asm!(
39            "blr {func}",
40            func = in(reg) func,
41            in("x0") actual_ptr,      // @in V (pointer to value)
42            in("x1") value_meta,      // V.Type (generic metadata)
43            in("x2") value_view_wt,   // V:View witness table
44            lateout("x0") result,
45            lateout("x1") _, lateout("x2") _, lateout("x3") _,
46            lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
47            lateout("x8") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
48            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
49            lateout("x16") _, lateout("x17") _, lateout("lr") _,
50            lateout("x20") _, lateout("x21") _,
51            clobber_abi("C"),
52        );
53        result
54    }
55}
56
57/// Call a generic View modifier on an AnyView.
58///
59/// ARM64 calling convention (from ASM analysis of View.padding):
60///   x0  = Self.Type (AnyView metadata)
61///   x1  = Self:View WT (AnyView:View witness table)
62///   x8  = @out result pointer
63///   x20 = @in_guaranteed self pointer (points to AnyView 8-byte value on stack)
64///   d0  = Double/CGFloat argument
65///
66/// Returns raw bytes of result (ModifiedContent<AnyView, Modifier>).
67/// # Safety
68/// `func` must be a `View` modifier taking one `CGFloat`, `recv` must address
69/// a live value of the type `recv_meta` describes, and `recv_wt` must be that
70/// type's `View` conformance. The returned bytes are `+1`.
71pub unsafe fn call_modifier_d0(
72    func: *const c_void,
73    recv: *const c_void,
74    recv_meta: *const c_void,
75    recv_wt: *const c_void,
76    d0_arg: f64,
77    result_size: usize,
78) -> Vec<u8> {
79    unsafe {
80        let mut result = vec![0u8; result_size];
81        core::arch::asm!(
82            "blr {func}",
83            func = in(reg) func,
84            in("x0") recv_meta,                          // Self.Type
85            in("x1") recv_wt,                            // Self:View WT
86            in("x8") result.as_mut_ptr(),                // @out result
87            in("x20") recv,                              // @in_guaranteed self
88            in("d0") d0_arg,                             // Double/CGFloat arg
89            lateout("x0") _, lateout("x1") _, lateout("x2") _, lateout("x3") _,
90            lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
91            lateout("x9") _, lateout("x10") _, lateout("x11") _,
92            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
93            lateout("x16") _, lateout("x17") _, lateout("lr") _,
94            clobber_abi("C"),
95        );
96        result
97    }
98}
99
100/// Call a `View` modifier whose integer arguments are given in full.
101///
102/// [`call_modifier`] appends `Self.Type` and the `Self: View` witness table
103/// itself, which is right for the great majority of modifiers and wrong for
104/// every generic one: `id<H: Hashable>` and `contentShape<S: Shape>` take *two*
105/// metadata arguments and *two* witness tables, and they interleave with the
106/// value arguments in a way no fixed tail can express. Here the caller supplies
107/// the whole integer sequence, type arguments and witness tables included.
108///
109/// All eight argument registers are always loaded. A callee reads only the ones
110/// its signature names, so padding the sequence with zeroes is free and saves
111/// dispatching on the argument count.
112///
113/// # Safety
114/// `func` must be the modifier, and `ints`/`floats` must match its signature —
115/// including the trailing metadata and witness tables, which are not implied.
116pub unsafe fn call_modifier_generic(
117    func: *const c_void,
118    recv: *const c_void,
119    ints: &[u64],
120    floats: [f64; 4],
121    result_size: usize,
122) -> Vec<u8> {
123    let mut result = vec![0u8; result_size];
124    // `self` is `@in_guaranteed`, so x20 holds the *address* of the value. That
125    // used to be the address of a local holding an `AnyView` word; now it is the
126    // address of the view's own bytes, whatever type they are.
127    unsafe {
128        crate::abi::call_spilled(func, ints, floats, recv as u64, result.as_mut_ptr());
129    }
130    result
131}
132
133/// Apply a `View` modifier that takes only plain arguments.
134///
135/// The register layout follows one rule: floating-point arguments fill d0
136/// upward, integer and pointer arguments fill x0 upward, and `Self.Type` then
137/// the `Self: View` witness table follow immediately after the last integer
138/// argument. So the only thing that shifts the layout is how many *integer*
139/// arguments the modifier takes — unused float registers are simply ignored.
140///
141/// Which is why this is [`call_modifier_generic`] with a fixed two-word tail:
142/// an ordinary modifier's trailing metadata and witness table are exactly what
143/// a generic one spells out for itself. A `LocalizedStringKey` is four words
144/// and a `Binding` is three, so seven and eight arguments push that tail onto
145/// the stack; the generic call handles the spill.
146///
147/// Modifiers with extra generic parameters take further type and witness-table
148/// arguments and call [`call_modifier_generic`] directly.
149///
150/// # Safety
151/// `func` must be the modifier, and `ints`/`floats` must match its signature.
152pub unsafe fn call_modifier(
153    func: *const c_void,
154    recv: *const c_void,
155    recv_meta: *const c_void,
156    recv_wt: *const c_void,
157    ints: &[u64],
158    floats: [f64; 4],
159    result_size: usize,
160) -> Vec<u8> {
161    let mut all = Vec::with_capacity(ints.len() + 2);
162    all.extend_from_slice(ints);
163    // `Self.Type` and `Self: View` — the receiver's own, not `AnyView`'s.
164    all.push(recv_meta as u64);
165    all.push(recv_wt as u64);
166    unsafe { call_modifier_generic(func, recv, &all, floats, result_size) }
167}