Skip to main content

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}