swiftui-native 0.0.1

SwiftUI from pure Rust — no Swift bridge, no shim, only swift-runtime calls
Documentation
//! Calling into Swift: the two shapes every call takes.
//!
//! Swift's arm64 convention is AAPCS with three additions, and all three are
//! invisible to a Rust function signature:
//!
//! * **x8** holds the address of an indirect (`@out`) result. A function whose
//!   return value is address-only — resilient, generic, or simply too big —
//!   writes through x8 and returns nothing in x0.
//! * **x20** is the *self* register. A method receives its receiver there: by
//!   value when the receiver fits in one word, by address otherwise. A type's
//!   metadata goes there too, for an initialiser or a static property.
//! * **x21** is the error register, which nothing here throws through but
//!   everything clobbers.
//!
//! Getting one of these wrong does not usually fault at the call. A function
//! that returns through x8 while the caller reads x0 hands back whatever was
//! left in the register — often a plausible-looking pointer that SwiftUI
//! stores and dereferences much later. So the two primitives below always
//! provide x8, always load every argument register, and always declare the
//! whole caller-saved set as clobbered. Passing an argument a callee does not
//! read costs nothing; omitting one it does read costs an afternoon.

use core::ffi::c_void;

/// The integer argument registers a call can fill, x0 through x7.
pub type Ints = [u64; 8];

/// The floating-point argument registers a call can fill, d0 through d3.
pub type Floats = [f64; 4];

/// Pad a slice of integer arguments out to all eight registers.
///
/// # Panics
/// If more than eight are given. Arguments past the eighth go on the stack,
/// which [`super::call_modifier_generic`] handles and this does not.
pub fn ints(values: &[u64]) -> Ints {
    assert!(values.len() <= 8, "{} integer arguments, but only eight registers", values.len());
    let mut a = [0u64; 8];
    a[..values.len()].copy_from_slice(values);
    a
}

/// Call a Swift function whose result is written through x8.
///
/// `out` must point at storage of at least the result type's *stride* — a
/// callee may write the trailing padding.
///
/// # Safety
/// `func` must be a Swift function with this shape, and the arguments must
/// match its signature: which registers, and whether each value travels
/// directly or by address.
pub unsafe fn call_out(
    func: *const c_void,
    args: Ints,
    floats: Floats,
    self_reg: u64,
    out: *mut u8,
) {
    unsafe {
        core::arch::asm!("blr {f}", f = in(reg) func,
            in("x0") args[0], in("x1") args[1], in("x2") args[2], in("x3") args[3],
            in("x4") args[4], in("x5") args[5], in("x6") args[6], in("x7") args[7],
            in("d0") floats[0], in("d1") floats[1], in("d2") floats[2], in("d3") floats[3],
            in("x8") out, in("x20") self_reg,
            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
        );
    }
}

/// Call a Swift function with any number of integer arguments.
///
/// [`call_out`] covers the eight that fit in registers; this one spills the
/// rest onto the stack, which is what the wider initialisers need — the widest
/// here is `_TaskValueModifier2.init`, at ten.
///
/// The frame is always the same size, so one asm block covers every arity: a
/// callee that takes fewer simply never reads the slots above its own.
///
/// # Safety
/// As [`call_out`], and `ints` must match the callee's signature — including
/// any trailing metadata and witness tables, which are not implied.
pub unsafe fn call_spilled(
    func: *const c_void,
    ints: &[u64],
    floats: Floats,
    self_reg: u64,
    out: *mut u8,
) {
    assert!(ints.len() <= 16, "call with {} integer arguments", ints.len());
    let mut a = [0u64; 16];
    a[..ints.len()].copy_from_slice(ints);
    let spill = &a[8..];
    unsafe {
        core::arch::asm!(
            "sub sp, sp, #64",
            "stp {s0}, {s1}, [sp]",
            "stp {s2}, {s3}, [sp, #16]",
            "stp {s4}, {s5}, [sp, #32]",
            "stp {s6}, {s7}, [sp, #48]",
            "blr {f}",
            "add sp, sp, #64",
            f = in(reg) func,
            s0 = in(reg) spill[0], s1 = in(reg) spill[1],
            s2 = in(reg) spill[2], s3 = in(reg) spill[3],
            s4 = in(reg) spill[4], s5 = in(reg) spill[5],
            s6 = in(reg) spill[6], s7 = in(reg) spill[7],
            in("x0") a[0], in("x1") a[1], in("x2") a[2], in("x3") a[3],
            in("x4") a[4], in("x5") a[5], in("x6") a[6], in("x7") a[7],
            in("d0") floats[0], in("d1") floats[1], in("d2") floats[2], in("d3") floats[3],
            in("x8") out, in("x20") self_reg,
            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
        );
    }
}

/// Call a Swift function whose result comes back in registers.
///
/// Returns x0, x1 and x2, which is every loadable result up to three words —
/// a `Binding`, a `String`, a `Color`. A caller that wants one word reads the
/// first; the rest cost nothing.
///
/// # Safety
/// As [`call_out`], and the result type must be loadable rather than returned
/// through x8. When in doubt it is returned through x8: guessing wrong this
/// way yields a stale register rather than a crash.
pub unsafe fn call_regs(
    func: *const c_void,
    args: Ints,
    floats: Floats,
    self_reg: u64,
) -> [u64; 3] {
    let (r0, r1, r2): (u64, u64, u64);
    unsafe {
        core::arch::asm!("blr {f}", f = in(reg) func,
            inout("x0") args[0] => r0, inout("x1") args[1] => r1,
            inout("x2") args[2] => r2, in("x3") args[3],
            in("x4") args[4], in("x5") args[5], in("x6") args[6], in("x7") args[7],
            in("d0") floats[0], in("d1") floats[1], in("d2") floats[2], in("d3") floats[3],
            in("x20") self_reg,
            lateout("x8") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
            lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x21") _,
            clobber_abi("C"),
        );
    }
    [r0, r1, r2]
}

/// Call a Swift function whose result is a `Double` in d0.
///
/// # Safety
/// As [`call_regs`].
pub unsafe fn call_f64(func: *const c_void, args: Ints, floats: Floats, self_reg: u64) -> f64 {
    let out: f64;
    unsafe {
        core::arch::asm!("blr {f}", f = in(reg) func,
            in("x0") args[0], in("x1") args[1], in("x2") args[2], in("x3") args[3],
            in("x4") args[4], in("x5") args[5], in("x6") args[6], in("x7") args[7],
            inout("d0") floats[0] => out,
            in("d1") floats[1], in("d2") floats[2], in("d3") floats[3],
            in("x20") self_reg,
            lateout("x8") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
            lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x21") _,
            clobber_abi("C"),
        );
    }
    out
}

/// Read a `static var` whose value is written through x8.
///
/// The shape SwiftUI's resilient values take: the metatype goes in x20 and the
/// value lands in a buffer the caller sizes from the type's stride.
///
/// # Safety
/// `getter` must be a static property getter for the type `meta` describes.
pub unsafe fn static_out(getter: *const c_void, meta: *const c_void, stride: usize) -> Vec<u8> {
    let mut out = vec![0u8; stride.max(1)];
    unsafe { call_out(getter, [0; 8], [0.0; 4], meta as u64, out.as_mut_ptr()) };
    out
}

/// Read a `static var` whose value comes back in registers.
///
/// SwiftUI's nested `.Set` option sets and `Alignment` take this shape while
/// its other resilient values take [`static_out`]'s, and nothing in the SDK
/// says which is which — neither is marked `@frozen`. The distinction is
/// recorded at each call site instead.
///
/// # Safety
/// As [`static_out`].
pub unsafe fn static_regs(getter: *const c_void, meta: *const c_void) -> [u64; 3] {
    unsafe { call_regs(getter, [0; 8], [0.0; 4], meta as u64) }
}