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}