swiftui-native 0.0.1

SwiftUI from pure Rust — no Swift bridge, no shim, only swift-runtime calls
Documentation
//! SwiftUI App lifecycle — pure Rust, no Swift compiler.
//!
//! Constructs a SwiftUI `App` conformance at run time by building:
//! 1. A zero-size struct metadata (the "app type")
//! 2. An `App` protocol witness table with `body.getter`
//! 3. A scene for that body to return — see [`crate::scene`]
//!
//! Then calls `App.main()`, which enters the SwiftUI event loop.
//!
//! # Example
//! ```no_run
//! use swiftui_native::{app, views};
//!
//! app::run(|| views::text("Hello from pure Rust!"));
//! ```
//!
//! A whole app rather than one window:
//!
//! ```no_run
//! use swiftui_native::{app, scene, views};
//!
//! app::run_scene(|| scene::group(vec![
//!     scene::window_group(|| views::text("main")),
//!     scene::settings(|| views::text("preferences")),
//! ]));
//! ```

use crate::resolve;
use crate::scene::SceneHandle;
use core::ffi::c_void;

/// The scene builder, called by SwiftUI every time it reads `App.body`.
type SceneBuilder = Box<dyn Fn() -> SceneHandle>;

/// Global storage for it, since the witness is a bare function pointer.
static mut SCENE_BUILDER: Option<SceneBuilder> = None;

/// Launch a SwiftUI app with a Rust-built root view. **Never returns.**
///
/// A `WindowGroup` around whatever the builder returns, which is the shape of
/// the shortest useful app. [`run_scene`] is the general form.
///
/// The body is anything that is a view — a `View` from the chainable DSL, a
/// `ViewHandle`, or a `&str` — so the entry point does not end in a conversion
/// the way it used to.
///
/// ```no_run
/// # use swiftui_native::{app, prelude::*};
/// app::run(|| text("Hello!").padding(20.0));
/// ```
pub fn run<V: crate::view::IntoView>(builder: impl Fn() -> V + 'static) -> ! {
    run_scene(move || crate::scene::window_group(&builder))
}

/// Launch a SwiftUI app whose `body` is any scene. **Never returns.**
///
/// The builder is called once before `App.main()` to learn what type the body
/// is — an `App` conformance names its `Body` in its witness table, so the type
/// has to be known before SwiftUI is told about it — and then by SwiftUI on
/// every read. Returning a *different* scene type on a later call is rejected
/// rather than written into a slot sized for the first one.
///
/// ```no_run
/// # use swiftui_native::{app, scene, views};
/// app::run_scene(|| scene::window_group(|| views::text("hi")));
/// ```
pub fn run_scene(builder: impl Fn() -> SceneHandle + 'static) -> ! {
    unsafe {
        SCENE_BUILDER = Some(Box::new(builder));
        launch_app();
    }
    std::process::exit(0);
}

// `App.body` returns its Body indirectly in x8. Route the witness through a
// thunk that moves x8 into the first C argument register; grabbing x8 with an
// inline `asm!("", out("x8") p)` inside the witness only works as long as the
// compiler emits no prologue ahead of it, which is not something to rely on.
core::arch::global_asm!(
    ".p2align 2",
    ".globl _rswift_app_body_thunk",
    "_rswift_app_body_thunk:",
    "mov x0, x8",
    "b _rswift_app_body_getter",
);

unsafe extern "C" {
    #[link_name = "rswift_app_body_thunk"]
    fn body_thunk();
}

static mut BODY_GETTER_IMPL: Option<unsafe extern "C" fn(*mut u8)> = None;

/// The `Body` type the conformance was built with, so a later mismatch is caught.
static BODY_META: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);

/// `App.init()` witness. The app type is zero-size, so there is nothing to write
/// into the `@out` slot in x8.
unsafe extern "C" fn init_witness() {}

/// `App.body.getter` — builds the scene and moves it into `out`.
///
/// The result slot is SwiftUI's, and it is exactly the stride of the `Body` type
/// named in the witness table. A builder that returned something else would
/// write past it, so the type is checked rather than trusted: the message names
/// both types, which is the only way this is debuggable from the outside.
unsafe extern "C" fn body_getter(out: *mut u8) {
    let scene = unsafe { crate::static_mut(&raw mut SCENE_BUILDER).as_ref() }
        .expect("scene builder not set")();
    let expected = BODY_META.load(std::sync::atomic::Ordering::Acquire) as *const c_void;
    assert_eq!(
        scene.meta(),
        expected,
        "the scene builder returned {} but the App conformance was built for {} — \
         `run_scene`'s closure must produce the same scene type every time",
        scene.type_name(),
        crate::diag::type_name(expected),
    );
    unsafe { scene.move_into(out) };
}

/// Build a complete, runtime-synthesised `RustApp: App` conformance.
///
/// Returns `(type metadata, witness table)`.
///
/// Everything lives in one allocation so that every relative pointer is
/// trivially within `i32` range — the descriptors are full of 32-bit
/// self-relative offsets, and a heap block far from the binary would otherwise
/// overflow them.
///
/// Two things are needed beyond the metadata itself:
///
/// * A real nominal type descriptor. `App.main()` reaches
///   `isCanonicalStaticallySpecializedGenericMetadata()`, which dereferences the
///   descriptor to ask whether the type is generic; a null one faults there.
/// * A registered protocol conformance. SwiftUI resolves `A.Body` by demangling
///   a dependent member type, which means looking the `A: App` conformance up
///   dynamically. Passing the witness table by hand is not enough — without a
///   conformance record `swift_conformsToProtocol` finds nothing.
///
/// Real Swift conformances use resilient witnesses plus a generic witness table
/// pattern (flags `0x30000`). This one is the simple form instead: no
/// conditional requirements and a direct witness table pattern, which the
/// runtime uses as-is.
unsafe fn build_app_conformance(
    body_meta: *const c_void,
    body_scene_wt: *const c_void,
) -> (*const c_void, *const c_void) {
    const MODULE_NAME: &[u8] = b"rswift\0";
    const TYPE_NAME: &[u8] = b"RustApp\0";

    const MODULE_OFF: usize = 0;        // module context descriptor (12)
    const MODULE_NAME_OFF: usize = 16;
    const STRUCT_OFF: usize = 32;       // struct descriptor (28)
    const TYPE_NAME_OFF: usize = 64;
    const PROTO_SLOT_OFF: usize = 80;   // pointer to SwiftUI.App's descriptor
    const CONF_OFF: usize = 96;         // conformance descriptor (16)
    const RECORD_OFF: usize = 112;      // conformance record (relative i32)
    const WT_OFF: usize = 128;          // witness table (5 words)
    const TOTAL: usize = 192;

    let block = unsafe { libc::malloc(TOTAL) } as *mut u8;
    assert!(!block.is_null());
    unsafe {
        core::ptr::write_bytes(block, 0, TOTAL);
        core::ptr::copy_nonoverlapping(
            MODULE_NAME.as_ptr(), block.add(MODULE_NAME_OFF), MODULE_NAME.len());
        core::ptr::copy_nonoverlapping(
            TYPE_NAME.as_ptr(), block.add(TYPE_NAME_OFF), TYPE_NAME.len());
    }

    /// Relative pointers are signed 32-bit offsets from the field's own address.
    unsafe fn rel(field: *const u8, target: *const u8) -> i32 {
        i32::try_from(target as isize - field as isize).expect("relative pointer out of range")
    }

    unsafe {
        // Module context descriptor: Kind = Module (0) | IsUnique (0x40).
        let m = block.add(MODULE_OFF);
        *(m as *mut u32) = 0x40;
        *(m.add(4) as *mut i32) = 0;
        *(m.add(8) as *mut i32) = rel(m.add(8), block.add(MODULE_NAME_OFF));

        // Struct descriptor: Kind = Struct (17) | IsUnique (0x40), non-generic.
        let d = block.add(STRUCT_OFF);
        *(d as *mut u32) = 0x51;
        *(d.add(4) as *mut i32) = rel(d.add(4), m);
        *(d.add(8) as *mut i32) = rel(d.add(8), block.add(TYPE_NAME_OFF));
        *(d.add(12) as *mut i32) = 0; // no metadata access function
        *(d.add(16) as *mut i32) = 0; // no field descriptor
        *(d.add(20) as *mut u32) = 0; // NumFields
        *(d.add(24) as *mut u32) = 0; // FieldOffsetVectorOffset

        // Struct metadata: [VWT][Kind = 0x200][Descriptor]; the metadata pointer
        // addresses the Kind word. The empty-tuple VWT describes a zero-size value.
        let empty_vwt = resolve::sym(c"$sytWV");
        assert!(!empty_vwt.is_null(), "Empty tuple VWT not found");
        let meta = libc::malloc(24) as *mut *const c_void;
        assert!(!meta.is_null());
        *meta.add(0) = empty_vwt;
        *(meta.add(1) as *mut usize) = 0x200;
        *meta.add(2) = d as *const c_void;
        let app_meta = (meta as *const u8).add(8) as *const c_void;

        // Witness table.
        //   [0] conformance descriptor
        //   [1] associated conformance  Body: Scene
        //   [2] associated type         Body
        //   [3] body.getter
        //   [4] init()
        //
        // Slots [1] and [2] normally hold *tagged* references to mangled names.
        // The runtime checks the low bit first and takes whatever is there as a
        // direct pointer when it is clear, so resolved values can go straight in.
        let wt = block.add(WT_OFF) as *mut *const c_void;
        *wt.add(0) = block.add(CONF_OFF) as *const c_void;
        *wt.add(1) = body_scene_wt;
        *wt.add(2) = body_meta;
        *wt.add(3) = body_thunk as *const c_void;
        *wt.add(4) = init_witness as *const c_void;

        // Protocol conformance descriptor.
        *(block.add(PROTO_SLOT_OFF) as *mut *const c_void) = resolve::app_proto();
        let c = block.add(CONF_OFF);
        // Protocol: relative *indirectable* pointer — low bit set means "the
        // target is a pointer slot", which keeps SwiftUI's descriptor reachable
        // no matter how far away it is mapped.
        *(c as *mut i32) = rel(c, block.add(PROTO_SLOT_OFF)) | 1;
        // TypeRef: direct relative pointer to the nominal type descriptor.
        *(c.add(4) as *mut i32) = rel(c.add(4), d);
        // WitnessTablePattern: used directly, since this conformance is neither
        // generic nor resilient.
        *(c.add(8) as *mut i32) = rel(c.add(8), block.add(WT_OFF));
        // Flags: type reference kind 0 (direct type descriptor), nothing else.
        *(c.add(12) as *mut u32) = 0;

        // Conformance record: a relative pointer to the descriptor.
        let record = block.add(RECORD_OFF);
        *(record as *mut i32) = rel(record, c);

        type Register = unsafe extern "C" fn(*const u8, *const u8);
        let reg = libc::dlsym(
            (-2isize) as *mut c_void,
            c"swift_registerProtocolConformances".as_ptr(),
        );
        assert!(!reg.is_null(), "swift_registerProtocolConformances not found");
        let reg: Register = core::mem::transmute(reg);
        reg(record, record.add(4));

        (app_meta, wt as *const c_void)
    }
}

/// Branch target of [`body_thunk`], which has already moved x8 into x0.
#[unsafe(no_mangle)]
unsafe extern "C" fn rswift_app_body_getter(out: *mut u8) {
    unsafe { BODY_GETTER_IMPL.expect("body getter not installed")(out) }
}

/// Assemble the conformance and hand it to `App.main()`, which does not return.
///
/// # Safety
/// Call once, from `main`, with the scene builder already installed.
unsafe fn launch_app() {
    use std::sync::atomic::Ordering::Release;

    unsafe {
        // Build the scene once to learn its type. An `App` conformance names its
        // `Body` in two witness slots — the associated type and its `Scene`
        // conformance — and the runtime reads them while resolving `A.Body`, so
        // both have to be filled before SwiftUI is told the conformance exists.
        let probe =
            crate::static_mut(&raw mut SCENE_BUILDER).as_ref().expect("scene builder not set")();
        let body_meta = probe.meta();
        let body_wt = probe.wt();
        drop(probe);
        BODY_META.store(body_meta as usize, Release);
        BODY_GETTER_IMPL = Some(body_getter);

        let (app_meta, app_wt) = build_app_conformance(body_meta, body_wt);

        // App.main() — x0 = x20 = A.Type, x1 = A: App witness table
        core::arch::asm!("blr {main}",
            main = in(reg) resolve::app_main(),
            in("x0") app_meta,
            in("x1") app_wt,
            in("x20") app_meta,
            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("x21") _,
            clobber_abi("C"),
        );
    }
}