swiftui_native/abi/value.rs
1//! Swift values: enum tags, optionals, strings and arrays.
2
3use super::*;
4use core::ffi::c_void;
5
6/// Create a Swift.String from a Rust `&str`, copying the bytes.
7///
8/// The copy is the whole point. The obvious constructor,
9/// `String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)`, does *not*
10/// copy — it wraps the caller's pointer and assumes the bytes are immortal, as
11/// a literal baked into the binary's constant data would be. That happens to
12/// work for `&'static str` and silently rots for anything built at runtime: a
13/// `format!` result is freed while SwiftUI still holds the pointer, so the text
14/// renders as garbage or as nothing at all.
15/// Select a case of a multi-case enum.
16///
17/// A no-payload enum is *usually* laid out as its case index, and writing that
18/// index into the buffer usually works. Not always: `SwiftUI.Visibility` comes
19/// out as `Visibility.()` — a case that does not exist — because its layout is
20/// not what the index suggests. The enum's own witness knows, so it does it.
21///
22/// `destructiveInjectEnumTag` is the third of the three witnesses an *enum*
23/// value witness table carries beyond the ordinary ones, which sit immediately
24/// after the extra-inhabitant count.
25///
26/// # Safety
27/// `metadata` must describe an enum and `buf` must be at least its stride.
28pub unsafe fn inject_enum_tag(buf: &mut [u8], metadata: *const c_void, tag: u32) {
29 unsafe {
30 // [8 function pointers][size][stride][flags: u32][extraInhabitantCount: u32]
31 // then getEnumTag, destructiveProjectEnumData, destructiveInjectEnumTag.
32 const ENUM_WITNESSES: usize = 8 * 8 + 8 + 8 + 4 + 4;
33 let vwt = *(metadata.cast::<*const u8>().offset(-1));
34 let inject = *(vwt.add(ENUM_WITNESSES + 16) as *const *const c_void);
35 type Inject = unsafe extern "C" fn(*mut c_void, u32, *const c_void);
36 let f: Inject = core::mem::transmute(inject);
37 f(buf.as_mut_ptr() as *mut c_void, tag, metadata);
38 }
39}
40
41/// Which case a multi-payload enum holds.
42///
43/// The inverse of [`inject_enum_tag`], reading the `getEnumTag` witness rather
44/// than the `destructiveInjectEnumTag` one. Distinct from
45/// [`read_optional_tag`], which is for the *single-payload* case and takes the
46/// payload's witness rather than the enum's.
47///
48/// # Safety
49/// `value` must address an initialised value of `metadata`'s type, and that type
50/// must be an enum.
51pub unsafe fn enum_tag(value: *const c_void, metadata: *const c_void) -> u32 {
52 unsafe {
53 // [8 function pointers][size][stride][flags: u32][extraInhabitantCount: u32]
54 // then getEnumTag, destructiveProjectEnumData, destructiveInjectEnumTag.
55 const ENUM_WITNESSES: usize = 8 * 8 + 8 + 8 + 4 + 4;
56 let vwt = *(metadata.cast::<*const u8>().offset(-1));
57 let get = *(vwt.add(ENUM_WITNESSES) as *const *const c_void);
58 type Get = unsafe extern "C" fn(*const c_void, *const c_void) -> u32;
59 let f: Get = core::mem::transmute(get);
60 f(value, metadata)
61 }
62}
63
64/// A no-payload enum's case, ready to pass in a register.
65///
66/// Small enums are loadable: `SwiftUI.Visibility` is one byte and travels in
67/// x0, not by pointer. Handing the callee a pointer instead leaves it reading
68/// the address as a case number, which SwiftUI renders without complaint and
69/// `dump` shows as `Visibility.()` — a case that does not exist.
70///
71/// # Safety
72/// `metadata` must describe an enum of at most eight bytes.
73pub unsafe fn enum_argument(metadata: *const c_void, tag: u32) -> u64 {
74 unsafe {
75 let mut buf = [0u8; 8];
76 let size = type_size(metadata).min(8);
77 inject_enum_tag(&mut buf[..size.max(1)], metadata, tag);
78 u64::from_le_bytes(buf)
79 }
80}
81
82/// Write `Optional<T>`'s discriminator into `buf`.
83///
84/// The witness must come from **`T`**, not from `Optional<T>`. It means "this
85/// type, with `emptyCases` extra empty cases appended", so calling it on the
86/// Optional's own table describes `Optional<Optional<T>>` and puts the tag one
87/// too high.
88///
89/// That mistake is close to undetectable from the outside. For a small enum
90/// payload the wrong tag is still a *valid* `Optional` bit pattern, so it reads
91/// back as `.none` through the same wrong witness, and SwiftUI renders it as
92/// the default — `Font.Design?` came out as `0x05` where Swift writes `0x04`,
93/// and only reflecting over the value showed it.
94///
95/// # Safety
96/// `payload_meta` must be `T`'s metadata and `buf` must be at least
97/// `Optional<T>`'s stride.
98pub unsafe fn store_optional_tag(buf: &mut [u8], payload_meta: *const c_void, is_some: bool) {
99 unsafe {
100 let vwt = &*swift_runtime_sys::SwiftABI::get_value_witness_table(payload_meta);
101 // whichCase 0 selects the payload; 1 selects the first empty case.
102 (vwt.store_enum_tag_single_payload)(
103 buf.as_mut_ptr() as *mut c_void,
104 u32::from(!is_some),
105 1,
106 payload_meta,
107 );
108 }
109}
110
111/// [`store_optional_tag`] writing through a raw pointer rather than a slice.
112///
113/// For an `@out` buffer SwiftUI supplied: the size is not ours to know, and a
114/// slice would have to claim one.
115///
116/// # Safety
117/// `buf` must address at least `Optional<T>`'s stride, and `payload_meta` must be
118/// `T`'s metadata — **not** the optional's. See [`store_optional_tag`] for why
119/// that distinction is nearly undetectable when it is wrong.
120pub unsafe fn store_optional_tag_at(
121 buf: *mut u8,
122 payload_meta: *const c_void,
123 is_some: bool,
124) {
125 unsafe {
126 let vwt = &*swift_runtime_sys::SwiftABI::get_value_witness_table(payload_meta);
127 (vwt.store_enum_tag_single_payload)(
128 buf as *mut c_void,
129 u32::from(!is_some),
130 1,
131 payload_meta,
132 );
133 }
134}
135
136/// Which case an `Optional<T>` holds.
137///
138/// The inverse of [`store_optional_tag`], and with the same rule about whose
139/// witness to use: `payload_meta` is **`T`**'s, not the Optional's.
140///
141/// # Safety
142/// `payload_meta` must be `T`'s metadata and `buf` at least `Optional<T>`'s
143/// stride.
144pub unsafe fn read_optional_tag(buf: &[u8], payload_meta: *const c_void) -> bool {
145 unsafe {
146 let vwt = &*swift_runtime_sys::SwiftABI::get_value_witness_table(payload_meta);
147 // 0 is the payload case; anything else is one of the empty ones.
148 (vwt.get_enum_tag_single_payload)(buf.as_ptr() as *const c_void, 1, payload_meta) == 0
149 }
150}
151
152/// Read a Swift `String` value back into a Rust one.
153///
154/// The inverse of [`swift_string`], and not by symmetry: a long string's bytes
155/// live in a `__StringStorage` object whose layout is an implementation detail.
156/// Bridging to `NSString` and asking for its UTF-8 goes through supported API
157/// instead, at the cost of an allocation — fine for diagnostics, which is all
158/// this is for.
159///
160/// # Safety
161/// `value` must be a valid Swift `String`.
162pub unsafe fn read_swift_string(value: [u8; 16]) -> String {
163 unsafe {
164 let _pool = crate::objc::AutoreleasePool::new();
165 let mut value = value;
166 let object: u64;
167 core::arch::asm!("blr {f}", f = in(reg) resolve::bridge_to_objc(),
168 in("x0") value.as_mut_ptr(),
169 in("x1") resolve::string_meta(),
170 lateout("x0") object,
171 lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
172 lateout("x6") _, lateout("x7") _, lateout("x8") _, lateout("x9") _,
173 lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
174 lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
175 lateout("lr") _, lateout("x20") _, lateout("x21") _, clobber_abi("C"),
176 );
177 if object == 0 {
178 return String::new();
179 }
180 let utf8 = crate::objc::send_ret_ptr(object as *mut c_void, crate::objc::sel(c"UTF8String"));
181 let out = if utf8.is_null() {
182 String::new()
183 } else {
184 core::ffi::CStr::from_ptr(utf8 as *const i8).to_string_lossy().into_owned()
185 };
186 // Nothing is released here. The bridge returns the `NSString` at +0, so
187 // it belongs to the pool above, and the `String` arrives *by value* —
188 // these bytes are a copy of the caller's, and letting go of the storage
189 // they share would leave the caller holding a dangling one. Both
190 // mistakes look like a crash somewhere else entirely.
191 out
192 }
193}
194
195/// # Safety
196/// Calls into the Swift runtime, so the Swift stdlib must be loaded. The
197/// returned sixteen bytes are a `+1` `String`: for anything over fifteen UTF-8
198/// bytes they hold a reference the caller must hand on or release.
199pub unsafe fn swift_string(s: &str) -> [u8; 16] {
200 // Up to 15 UTF-8 bytes live inline in the String value itself, so there is
201 // nothing to own. On 64-bit little-endian, byte 15 is `0xE0 | count`.
202 if s.len() <= 15 {
203 let mut buf = [0u8; 16];
204 buf[..s.len()].copy_from_slice(s.as_bytes());
205 buf[15] = 0xE0 | s.len() as u8;
206 return buf;
207 }
208
209 // Anything longer needs storage Swift owns. `String.init(cString:)` copies,
210 // so the temporary NUL-terminated buffer can die right after the call.
211 //
212 // Interior NUL bytes are not representable through this initialiser and are
213 // dropped; UI text containing them is pathological.
214 let owned;
215 let c = match std::ffi::CString::new(s.as_bytes()) {
216 Ok(c) => c,
217 Err(_) => {
218 owned = s.replace('\0', "");
219 std::ffi::CString::new(owned.as_bytes()).expect("NULs stripped")
220 }
221 };
222
223 let (r0, r1): (u64, u64);
224 unsafe {
225 core::arch::asm!("blr {f}", f = in(reg) resolve::string_cstring_init(),
226 in("x0") c.as_ptr(),
227 in("x20") resolve::string_meta(),
228 lateout("x0") r0, lateout("x1") r1,
229 lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
230 lateout("x6") _, lateout("x7") _, lateout("x8") _, lateout("x9") _,
231 lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
232 lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
233 lateout("lr") _, clobber_abi("C"),
234 );
235 }
236 let mut buf = [0u8; 16];
237 buf[..8].copy_from_slice(&r0.to_le_bytes());
238 buf[8..].copy_from_slice(&r1.to_le_bytes());
239 buf
240}