swiftui-native 0.0.1

SwiftUI from pure Rust — no Swift bridge, no shim, only swift-runtime calls
Documentation
//! Applying a `View` modifier, and wrapping the result back into `AnyView`.

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

/// Wrap any view value in AnyView.
///
/// ARM64 calling convention (from SIL + ASM analysis):
///   x0 = pointer to V value (@in, consumed) — may be arbitrary for zero-size types
///   x1 = V.Type metadata (generic type parameter)
///   x2 = V:View witness table
///   Returns: x0 = AnyView (8-byte class ref, owned)
/// # Safety
/// `value_ptr` must address an initialised value of the type `value_meta`
/// describes, and `value_view_wt` must be that type's `View` conformance. The
/// value is `@in` — consumed — so it must be a `+1` copy the caller gives up.
pub unsafe fn anyview_wrap(
    value_ptr: *const c_void,
    value_meta: *const c_void,
    value_view_wt: *const c_void,
) -> u64 {
    unsafe {
        let func = resolve::anyview_init();
        let vwt = &*swift_runtime_sys::SwiftABI::get_value_witness_table(value_meta);
        // For @in: need a valid pointer even for zero-size types.
        let mut stack_buf = [0u8; 128];
        let actual_ptr = if vwt.size == 0 {
            stack_buf.as_mut_ptr() as *const c_void
        } else if vwt.size <= 128 {
            core::ptr::copy_nonoverlapping(
                value_ptr as *const u8, stack_buf.as_mut_ptr(), vwt.size,
            );
            stack_buf.as_mut_ptr() as *const c_void
        } else {
            value_ptr
        };
        let result: u64;
        core::arch::asm!(
            "blr {func}",
            func = in(reg) func,
            in("x0") actual_ptr,      // @in V (pointer to value)
            in("x1") value_meta,      // V.Type (generic metadata)
            in("x2") value_view_wt,   // V:View witness table
            lateout("x0") result,
            lateout("x1") _, 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"),
        );
        result
    }
}

/// Call a generic View modifier on an AnyView.
///
/// ARM64 calling convention (from ASM analysis of View.padding):
///   x0  = Self.Type (AnyView metadata)
///   x1  = Self:View WT (AnyView:View witness table)
///   x8  = @out result pointer
///   x20 = @in_guaranteed self pointer (points to AnyView 8-byte value on stack)
///   d0  = Double/CGFloat argument
///
/// Returns raw bytes of result (ModifiedContent<AnyView, Modifier>).
/// # Safety
/// `func` must be a `View` modifier taking one `CGFloat`, `recv` must address
/// a live value of the type `recv_meta` describes, and `recv_wt` must be that
/// type's `View` conformance. The returned bytes are `+1`.
pub unsafe fn call_modifier_d0(
    func: *const c_void,
    recv: *const c_void,
    recv_meta: *const c_void,
    recv_wt: *const c_void,
    d0_arg: f64,
    result_size: usize,
) -> Vec<u8> {
    unsafe {
        let mut result = vec![0u8; result_size];
        core::arch::asm!(
            "blr {func}",
            func = in(reg) func,
            in("x0") recv_meta,                          // Self.Type
            in("x1") recv_wt,                            // Self:View WT
            in("x8") result.as_mut_ptr(),                // @out result
            in("x20") recv,                              // @in_guaranteed self
            in("d0") d0_arg,                             // Double/CGFloat arg
            lateout("x0") _, lateout("x1") _, lateout("x2") _, lateout("x3") _,
            lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
            lateout("x9") _, lateout("x10") _, lateout("x11") _,
            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
            lateout("x16") _, lateout("x17") _, lateout("lr") _,
            clobber_abi("C"),
        );
        result
    }
}

/// Call a `View` modifier whose integer arguments are given in full.
///
/// [`call_modifier`] appends `Self.Type` and the `Self: View` witness table
/// itself, which is right for the great majority of modifiers and wrong for
/// every generic one: `id<H: Hashable>` and `contentShape<S: Shape>` take *two*
/// metadata arguments and *two* witness tables, and they interleave with the
/// value arguments in a way no fixed tail can express. Here the caller supplies
/// the whole integer sequence, type arguments and witness tables included.
///
/// All eight argument registers are always loaded. A callee reads only the ones
/// its signature names, so padding the sequence with zeroes is free and saves
/// dispatching on the argument count.
///
/// # Safety
/// `func` must be the modifier, and `ints`/`floats` must match its signature —
/// including the trailing metadata and witness tables, which are not implied.
pub unsafe fn call_modifier_generic(
    func: *const c_void,
    recv: *const c_void,
    ints: &[u64],
    floats: [f64; 4],
    result_size: usize,
) -> Vec<u8> {
    let mut result = vec![0u8; result_size];
    // `self` is `@in_guaranteed`, so x20 holds the *address* of the value. That
    // used to be the address of a local holding an `AnyView` word; now it is the
    // address of the view's own bytes, whatever type they are.
    unsafe {
        crate::abi::call_spilled(func, ints, floats, recv as u64, result.as_mut_ptr());
    }
    result
}

/// Apply a `View` modifier that takes only plain arguments.
///
/// The register layout follows one rule: floating-point arguments fill d0
/// upward, integer and pointer arguments fill x0 upward, and `Self.Type` then
/// the `Self: View` witness table follow immediately after the last integer
/// argument. So the only thing that shifts the layout is how many *integer*
/// arguments the modifier takes — unused float registers are simply ignored.
///
/// Which is why this is [`call_modifier_generic`] with a fixed two-word tail:
/// an ordinary modifier's trailing metadata and witness table are exactly what
/// a generic one spells out for itself. A `LocalizedStringKey` is four words
/// and a `Binding` is three, so seven and eight arguments push that tail onto
/// the stack; the generic call handles the spill.
///
/// Modifiers with extra generic parameters take further type and witness-table
/// arguments and call [`call_modifier_generic`] directly.
///
/// # Safety
/// `func` must be the modifier, and `ints`/`floats` must match its signature.
pub unsafe fn call_modifier(
    func: *const c_void,
    recv: *const c_void,
    recv_meta: *const c_void,
    recv_wt: *const c_void,
    ints: &[u64],
    floats: [f64; 4],
    result_size: usize,
) -> Vec<u8> {
    let mut all = Vec::with_capacity(ints.len() + 2);
    all.extend_from_slice(ints);
    // `Self.Type` and `Self: View` — the receiver's own, not `AnyView`'s.
    all.push(recv_meta as u64);
    all.push(recv_wt as u64);
    unsafe { call_modifier_generic(func, recv, &all, floats, result_size) }
}