swiftui-native 0.0.1

SwiftUI from pure Rust — no Swift bridge, no shim, only swift-runtime calls
Documentation
//! Swift values: enum tags, optionals, strings and arrays.

use super::*;
use core::ffi::c_void;

/// Create a Swift.String from a Rust `&str`, copying the bytes.
///
/// The copy is the whole point. The obvious constructor,
/// `String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)`, does *not*
/// copy — it wraps the caller's pointer and assumes the bytes are immortal, as
/// a literal baked into the binary's constant data would be. That happens to
/// work for `&'static str` and silently rots for anything built at runtime: a
/// `format!` result is freed while SwiftUI still holds the pointer, so the text
/// renders as garbage or as nothing at all.
/// Select a case of a multi-case enum.
///
/// A no-payload enum is *usually* laid out as its case index, and writing that
/// index into the buffer usually works. Not always: `SwiftUI.Visibility` comes
/// out as `Visibility.()` — a case that does not exist — because its layout is
/// not what the index suggests. The enum's own witness knows, so it does it.
///
/// `destructiveInjectEnumTag` is the third of the three witnesses an *enum*
/// value witness table carries beyond the ordinary ones, which sit immediately
/// after the extra-inhabitant count.
///
/// # Safety
/// `metadata` must describe an enum and `buf` must be at least its stride.
pub unsafe fn inject_enum_tag(buf: &mut [u8], metadata: *const c_void, tag: u32) {
    unsafe {
        // [8 function pointers][size][stride][flags: u32][extraInhabitantCount: u32]
        // then getEnumTag, destructiveProjectEnumData, destructiveInjectEnumTag.
        const ENUM_WITNESSES: usize = 8 * 8 + 8 + 8 + 4 + 4;
        let vwt = *(metadata.cast::<*const u8>().offset(-1));
        let inject = *(vwt.add(ENUM_WITNESSES + 16) as *const *const c_void);
        type Inject = unsafe extern "C" fn(*mut c_void, u32, *const c_void);
        let f: Inject = core::mem::transmute(inject);
        f(buf.as_mut_ptr() as *mut c_void, tag, metadata);
    }
}

/// Which case a multi-payload enum holds.
///
/// The inverse of [`inject_enum_tag`], reading the `getEnumTag` witness rather
/// than the `destructiveInjectEnumTag` one. Distinct from
/// [`read_optional_tag`], which is for the *single-payload* case and takes the
/// payload's witness rather than the enum's.
///
/// # Safety
/// `value` must address an initialised value of `metadata`'s type, and that type
/// must be an enum.
pub unsafe fn enum_tag(value: *const c_void, metadata: *const c_void) -> u32 {
    unsafe {
        // [8 function pointers][size][stride][flags: u32][extraInhabitantCount: u32]
        // then getEnumTag, destructiveProjectEnumData, destructiveInjectEnumTag.
        const ENUM_WITNESSES: usize = 8 * 8 + 8 + 8 + 4 + 4;
        let vwt = *(metadata.cast::<*const u8>().offset(-1));
        let get = *(vwt.add(ENUM_WITNESSES) as *const *const c_void);
        type Get = unsafe extern "C" fn(*const c_void, *const c_void) -> u32;
        let f: Get = core::mem::transmute(get);
        f(value, metadata)
    }
}

/// A no-payload enum's case, ready to pass in a register.
///
/// Small enums are loadable: `SwiftUI.Visibility` is one byte and travels in
/// x0, not by pointer. Handing the callee a pointer instead leaves it reading
/// the address as a case number, which SwiftUI renders without complaint and
/// `dump` shows as `Visibility.()` — a case that does not exist.
///
/// # Safety
/// `metadata` must describe an enum of at most eight bytes.
pub unsafe fn enum_argument(metadata: *const c_void, tag: u32) -> u64 {
    unsafe {
        let mut buf = [0u8; 8];
        let size = type_size(metadata).min(8);
        inject_enum_tag(&mut buf[..size.max(1)], metadata, tag);
        u64::from_le_bytes(buf)
    }
}

/// Write `Optional<T>`'s discriminator into `buf`.
///
/// The witness must come from **`T`**, not from `Optional<T>`. It means "this
/// type, with `emptyCases` extra empty cases appended", so calling it on the
/// Optional's own table describes `Optional<Optional<T>>` and puts the tag one
/// too high.
///
/// That mistake is close to undetectable from the outside. For a small enum
/// payload the wrong tag is still a *valid* `Optional` bit pattern, so it reads
/// back as `.none` through the same wrong witness, and SwiftUI renders it as
/// the default — `Font.Design?` came out as `0x05` where Swift writes `0x04`,
/// and only reflecting over the value showed it.
///
/// # Safety
/// `payload_meta` must be `T`'s metadata and `buf` must be at least
/// `Optional<T>`'s stride.
pub unsafe fn store_optional_tag(buf: &mut [u8], payload_meta: *const c_void, is_some: bool) {
    unsafe {
        let vwt = &*swift_runtime_sys::SwiftABI::get_value_witness_table(payload_meta);
        // whichCase 0 selects the payload; 1 selects the first empty case.
        (vwt.store_enum_tag_single_payload)(
            buf.as_mut_ptr() as *mut c_void,
            u32::from(!is_some),
            1,
            payload_meta,
        );
    }
}

/// [`store_optional_tag`] writing through a raw pointer rather than a slice.
///
/// For an `@out` buffer SwiftUI supplied: the size is not ours to know, and a
/// slice would have to claim one.
///
/// # Safety
/// `buf` must address at least `Optional<T>`'s stride, and `payload_meta` must be
/// `T`'s metadata — **not** the optional's. See [`store_optional_tag`] for why
/// that distinction is nearly undetectable when it is wrong.
pub unsafe fn store_optional_tag_at(
    buf: *mut u8,
    payload_meta: *const c_void,
    is_some: bool,
) {
    unsafe {
        let vwt = &*swift_runtime_sys::SwiftABI::get_value_witness_table(payload_meta);
        (vwt.store_enum_tag_single_payload)(
            buf as *mut c_void,
            u32::from(!is_some),
            1,
            payload_meta,
        );
    }
}

/// Which case an `Optional<T>` holds.
///
/// The inverse of [`store_optional_tag`], and with the same rule about whose
/// witness to use: `payload_meta` is **`T`**'s, not the Optional's.
///
/// # Safety
/// `payload_meta` must be `T`'s metadata and `buf` at least `Optional<T>`'s
/// stride.
pub unsafe fn read_optional_tag(buf: &[u8], payload_meta: *const c_void) -> bool {
    unsafe {
        let vwt = &*swift_runtime_sys::SwiftABI::get_value_witness_table(payload_meta);
        // 0 is the payload case; anything else is one of the empty ones.
        (vwt.get_enum_tag_single_payload)(buf.as_ptr() as *const c_void, 1, payload_meta) == 0
    }
}

/// Read a Swift `String` value back into a Rust one.
///
/// The inverse of [`swift_string`], and not by symmetry: a long string's bytes
/// live in a `__StringStorage` object whose layout is an implementation detail.
/// Bridging to `NSString` and asking for its UTF-8 goes through supported API
/// instead, at the cost of an allocation — fine for diagnostics, which is all
/// this is for.
///
/// # Safety
/// `value` must be a valid Swift `String`.
pub unsafe fn read_swift_string(value: [u8; 16]) -> String {
    unsafe {
        let _pool = crate::objc::AutoreleasePool::new();
        let mut value = value;
        let object: u64;
        core::arch::asm!("blr {f}", f = in(reg) resolve::bridge_to_objc(),
            in("x0") value.as_mut_ptr(),
            in("x1") resolve::string_meta(),
            lateout("x0") object,
            lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
            lateout("x6") _, lateout("x7") _, lateout("x8") _, lateout("x9") _,
            lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
            lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
            lateout("lr") _, lateout("x20") _, lateout("x21") _, clobber_abi("C"),
        );
        if object == 0 {
            return String::new();
        }
        let utf8 = crate::objc::send_ret_ptr(object as *mut c_void, crate::objc::sel(c"UTF8String"));
        let out = if utf8.is_null() {
            String::new()
        } else {
            core::ffi::CStr::from_ptr(utf8 as *const i8).to_string_lossy().into_owned()
        };
        // Nothing is released here. The bridge returns the `NSString` at +0, so
        // it belongs to the pool above, and the `String` arrives *by value* —
        // these bytes are a copy of the caller's, and letting go of the storage
        // they share would leave the caller holding a dangling one. Both
        // mistakes look like a crash somewhere else entirely.
        out
    }
}

/// # Safety
/// Calls into the Swift runtime, so the Swift stdlib must be loaded. The
/// returned sixteen bytes are a `+1` `String`: for anything over fifteen UTF-8
/// bytes they hold a reference the caller must hand on or release.
pub unsafe fn swift_string(s: &str) -> [u8; 16] {
    // Up to 15 UTF-8 bytes live inline in the String value itself, so there is
    // nothing to own. On 64-bit little-endian, byte 15 is `0xE0 | count`.
    if s.len() <= 15 {
        let mut buf = [0u8; 16];
        buf[..s.len()].copy_from_slice(s.as_bytes());
        buf[15] = 0xE0 | s.len() as u8;
        return buf;
    }

    // Anything longer needs storage Swift owns. `String.init(cString:)` copies,
    // so the temporary NUL-terminated buffer can die right after the call.
    //
    // Interior NUL bytes are not representable through this initialiser and are
    // dropped; UI text containing them is pathological.
    let owned;
    let c = match std::ffi::CString::new(s.as_bytes()) {
        Ok(c) => c,
        Err(_) => {
            owned = s.replace('\0', "");
            std::ffi::CString::new(owned.as_bytes()).expect("NULs stripped")
        }
    };

    let (r0, r1): (u64, u64);
    unsafe {
        core::arch::asm!("blr {f}", f = in(reg) resolve::string_cstring_init(),
            in("x0") c.as_ptr(),
            in("x20") resolve::string_meta(),
            lateout("x0") r0, lateout("x1") r1,
            lateout("x2") _, lateout("x3") _, lateout("x4") _, lateout("x5") _,
            lateout("x6") _, lateout("x7") _, lateout("x8") _, lateout("x9") _,
            lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
            lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
            lateout("lr") _, clobber_abi("C"),
        );
    }
    let mut buf = [0u8; 16];
    buf[..8].copy_from_slice(&r0.to_le_bytes());
    buf[8..].copy_from_slice(&r1.to_le_bytes());
    buf
}