swiftui_native/app.rs
1//! SwiftUI App lifecycle — pure Rust, no Swift compiler.
2//!
3//! Constructs a SwiftUI `App` conformance at run time by building:
4//! 1. A zero-size struct metadata (the "app type")
5//! 2. An `App` protocol witness table with `body.getter`
6//! 3. A scene for that body to return — see [`crate::scene`]
7//!
8//! Then calls `App.main()`, which enters the SwiftUI event loop.
9//!
10//! # Example
11//! ```no_run
12//! use swiftui_native::{app, views};
13//!
14//! app::run(|| views::text("Hello from pure Rust!"));
15//! ```
16//!
17//! A whole app rather than one window:
18//!
19//! ```no_run
20//! use swiftui_native::{app, scene, views};
21//!
22//! app::run_scene(|| scene::group(vec![
23//! scene::window_group(|| views::text("main")),
24//! scene::settings(|| views::text("preferences")),
25//! ]));
26//! ```
27
28use crate::resolve;
29use crate::scene::SceneHandle;
30use core::ffi::c_void;
31
32/// The scene builder, called by SwiftUI every time it reads `App.body`.
33type SceneBuilder = Box<dyn Fn() -> SceneHandle>;
34
35/// Global storage for it, since the witness is a bare function pointer.
36static mut SCENE_BUILDER: Option<SceneBuilder> = None;
37
38/// Launch a SwiftUI app with a Rust-built root view. **Never returns.**
39///
40/// A `WindowGroup` around whatever the builder returns, which is the shape of
41/// the shortest useful app. [`run_scene`] is the general form.
42///
43/// The body is anything that is a view — a `View` from the chainable DSL, a
44/// `ViewHandle`, or a `&str` — so the entry point does not end in a conversion
45/// the way it used to.
46///
47/// ```no_run
48/// # use swiftui_native::{app, prelude::*};
49/// app::run(|| text("Hello!").padding(20.0));
50/// ```
51pub fn run<V: crate::view::IntoView>(builder: impl Fn() -> V + 'static) -> ! {
52 run_scene(move || crate::scene::window_group(&builder))
53}
54
55/// Launch a SwiftUI app whose `body` is any scene. **Never returns.**
56///
57/// The builder is called once before `App.main()` to learn what type the body
58/// is — an `App` conformance names its `Body` in its witness table, so the type
59/// has to be known before SwiftUI is told about it — and then by SwiftUI on
60/// every read. Returning a *different* scene type on a later call is rejected
61/// rather than written into a slot sized for the first one.
62///
63/// ```no_run
64/// # use swiftui_native::{app, scene, views};
65/// app::run_scene(|| scene::window_group(|| views::text("hi")));
66/// ```
67pub fn run_scene(builder: impl Fn() -> SceneHandle + 'static) -> ! {
68 unsafe {
69 SCENE_BUILDER = Some(Box::new(builder));
70 launch_app();
71 }
72 std::process::exit(0);
73}
74
75// `App.body` returns its Body indirectly in x8. Route the witness through a
76// thunk that moves x8 into the first C argument register; grabbing x8 with an
77// inline `asm!("", out("x8") p)` inside the witness only works as long as the
78// compiler emits no prologue ahead of it, which is not something to rely on.
79core::arch::global_asm!(
80 ".p2align 2",
81 ".globl _rswift_app_body_thunk",
82 "_rswift_app_body_thunk:",
83 "mov x0, x8",
84 "b _rswift_app_body_getter",
85);
86
87unsafe extern "C" {
88 #[link_name = "rswift_app_body_thunk"]
89 fn body_thunk();
90}
91
92static mut BODY_GETTER_IMPL: Option<unsafe extern "C" fn(*mut u8)> = None;
93
94/// The `Body` type the conformance was built with, so a later mismatch is caught.
95static BODY_META: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
96
97/// `App.init()` witness. The app type is zero-size, so there is nothing to write
98/// into the `@out` slot in x8.
99unsafe extern "C" fn init_witness() {}
100
101/// `App.body.getter` — builds the scene and moves it into `out`.
102///
103/// The result slot is SwiftUI's, and it is exactly the stride of the `Body` type
104/// named in the witness table. A builder that returned something else would
105/// write past it, so the type is checked rather than trusted: the message names
106/// both types, which is the only way this is debuggable from the outside.
107unsafe extern "C" fn body_getter(out: *mut u8) {
108 let scene = unsafe { crate::static_mut(&raw mut SCENE_BUILDER).as_ref() }
109 .expect("scene builder not set")();
110 let expected = BODY_META.load(std::sync::atomic::Ordering::Acquire) as *const c_void;
111 assert_eq!(
112 scene.meta(),
113 expected,
114 "the scene builder returned {} but the App conformance was built for {} — \
115 `run_scene`'s closure must produce the same scene type every time",
116 scene.type_name(),
117 crate::diag::type_name(expected),
118 );
119 unsafe { scene.move_into(out) };
120}
121
122/// Build a complete, runtime-synthesised `RustApp: App` conformance.
123///
124/// Returns `(type metadata, witness table)`.
125///
126/// Everything lives in one allocation so that every relative pointer is
127/// trivially within `i32` range — the descriptors are full of 32-bit
128/// self-relative offsets, and a heap block far from the binary would otherwise
129/// overflow them.
130///
131/// Two things are needed beyond the metadata itself:
132///
133/// * A real nominal type descriptor. `App.main()` reaches
134/// `isCanonicalStaticallySpecializedGenericMetadata()`, which dereferences the
135/// descriptor to ask whether the type is generic; a null one faults there.
136/// * A registered protocol conformance. SwiftUI resolves `A.Body` by demangling
137/// a dependent member type, which means looking the `A: App` conformance up
138/// dynamically. Passing the witness table by hand is not enough — without a
139/// conformance record `swift_conformsToProtocol` finds nothing.
140///
141/// Real Swift conformances use resilient witnesses plus a generic witness table
142/// pattern (flags `0x30000`). This one is the simple form instead: no
143/// conditional requirements and a direct witness table pattern, which the
144/// runtime uses as-is.
145unsafe fn build_app_conformance(
146 body_meta: *const c_void,
147 body_scene_wt: *const c_void,
148) -> (*const c_void, *const c_void) {
149 const MODULE_NAME: &[u8] = b"rswift\0";
150 const TYPE_NAME: &[u8] = b"RustApp\0";
151
152 const MODULE_OFF: usize = 0; // module context descriptor (12)
153 const MODULE_NAME_OFF: usize = 16;
154 const STRUCT_OFF: usize = 32; // struct descriptor (28)
155 const TYPE_NAME_OFF: usize = 64;
156 const PROTO_SLOT_OFF: usize = 80; // pointer to SwiftUI.App's descriptor
157 const CONF_OFF: usize = 96; // conformance descriptor (16)
158 const RECORD_OFF: usize = 112; // conformance record (relative i32)
159 const WT_OFF: usize = 128; // witness table (5 words)
160 const TOTAL: usize = 192;
161
162 let block = unsafe { libc::malloc(TOTAL) } as *mut u8;
163 assert!(!block.is_null());
164 unsafe {
165 core::ptr::write_bytes(block, 0, TOTAL);
166 core::ptr::copy_nonoverlapping(
167 MODULE_NAME.as_ptr(), block.add(MODULE_NAME_OFF), MODULE_NAME.len());
168 core::ptr::copy_nonoverlapping(
169 TYPE_NAME.as_ptr(), block.add(TYPE_NAME_OFF), TYPE_NAME.len());
170 }
171
172 /// Relative pointers are signed 32-bit offsets from the field's own address.
173 unsafe fn rel(field: *const u8, target: *const u8) -> i32 {
174 i32::try_from(target as isize - field as isize).expect("relative pointer out of range")
175 }
176
177 unsafe {
178 // Module context descriptor: Kind = Module (0) | IsUnique (0x40).
179 let m = block.add(MODULE_OFF);
180 *(m as *mut u32) = 0x40;
181 *(m.add(4) as *mut i32) = 0;
182 *(m.add(8) as *mut i32) = rel(m.add(8), block.add(MODULE_NAME_OFF));
183
184 // Struct descriptor: Kind = Struct (17) | IsUnique (0x40), non-generic.
185 let d = block.add(STRUCT_OFF);
186 *(d as *mut u32) = 0x51;
187 *(d.add(4) as *mut i32) = rel(d.add(4), m);
188 *(d.add(8) as *mut i32) = rel(d.add(8), block.add(TYPE_NAME_OFF));
189 *(d.add(12) as *mut i32) = 0; // no metadata access function
190 *(d.add(16) as *mut i32) = 0; // no field descriptor
191 *(d.add(20) as *mut u32) = 0; // NumFields
192 *(d.add(24) as *mut u32) = 0; // FieldOffsetVectorOffset
193
194 // Struct metadata: [VWT][Kind = 0x200][Descriptor]; the metadata pointer
195 // addresses the Kind word. The empty-tuple VWT describes a zero-size value.
196 let empty_vwt = resolve::sym(c"$sytWV");
197 assert!(!empty_vwt.is_null(), "Empty tuple VWT not found");
198 let meta = libc::malloc(24) as *mut *const c_void;
199 assert!(!meta.is_null());
200 *meta.add(0) = empty_vwt;
201 *(meta.add(1) as *mut usize) = 0x200;
202 *meta.add(2) = d as *const c_void;
203 let app_meta = (meta as *const u8).add(8) as *const c_void;
204
205 // Witness table.
206 // [0] conformance descriptor
207 // [1] associated conformance Body: Scene
208 // [2] associated type Body
209 // [3] body.getter
210 // [4] init()
211 //
212 // Slots [1] and [2] normally hold *tagged* references to mangled names.
213 // The runtime checks the low bit first and takes whatever is there as a
214 // direct pointer when it is clear, so resolved values can go straight in.
215 let wt = block.add(WT_OFF) as *mut *const c_void;
216 *wt.add(0) = block.add(CONF_OFF) as *const c_void;
217 *wt.add(1) = body_scene_wt;
218 *wt.add(2) = body_meta;
219 *wt.add(3) = body_thunk as *const c_void;
220 *wt.add(4) = init_witness as *const c_void;
221
222 // Protocol conformance descriptor.
223 *(block.add(PROTO_SLOT_OFF) as *mut *const c_void) = resolve::app_proto();
224 let c = block.add(CONF_OFF);
225 // Protocol: relative *indirectable* pointer — low bit set means "the
226 // target is a pointer slot", which keeps SwiftUI's descriptor reachable
227 // no matter how far away it is mapped.
228 *(c as *mut i32) = rel(c, block.add(PROTO_SLOT_OFF)) | 1;
229 // TypeRef: direct relative pointer to the nominal type descriptor.
230 *(c.add(4) as *mut i32) = rel(c.add(4), d);
231 // WitnessTablePattern: used directly, since this conformance is neither
232 // generic nor resilient.
233 *(c.add(8) as *mut i32) = rel(c.add(8), block.add(WT_OFF));
234 // Flags: type reference kind 0 (direct type descriptor), nothing else.
235 *(c.add(12) as *mut u32) = 0;
236
237 // Conformance record: a relative pointer to the descriptor.
238 let record = block.add(RECORD_OFF);
239 *(record as *mut i32) = rel(record, c);
240
241 type Register = unsafe extern "C" fn(*const u8, *const u8);
242 let reg = libc::dlsym(
243 (-2isize) as *mut c_void,
244 c"swift_registerProtocolConformances".as_ptr(),
245 );
246 assert!(!reg.is_null(), "swift_registerProtocolConformances not found");
247 let reg: Register = core::mem::transmute(reg);
248 reg(record, record.add(4));
249
250 (app_meta, wt as *const c_void)
251 }
252}
253
254/// Branch target of [`body_thunk`], which has already moved x8 into x0.
255#[unsafe(no_mangle)]
256unsafe extern "C" fn rswift_app_body_getter(out: *mut u8) {
257 unsafe { BODY_GETTER_IMPL.expect("body getter not installed")(out) }
258}
259
260/// Assemble the conformance and hand it to `App.main()`, which does not return.
261///
262/// # Safety
263/// Call once, from `main`, with the scene builder already installed.
264unsafe fn launch_app() {
265 use std::sync::atomic::Ordering::Release;
266
267 unsafe {
268 // Build the scene once to learn its type. An `App` conformance names its
269 // `Body` in two witness slots — the associated type and its `Scene`
270 // conformance — and the runtime reads them while resolving `A.Body`, so
271 // both have to be filled before SwiftUI is told the conformance exists.
272 let probe =
273 crate::static_mut(&raw mut SCENE_BUILDER).as_ref().expect("scene builder not set")();
274 let body_meta = probe.meta();
275 let body_wt = probe.wt();
276 drop(probe);
277 BODY_META.store(body_meta as usize, Release);
278 BODY_GETTER_IMPL = Some(body_getter);
279
280 let (app_meta, app_wt) = build_app_conformance(body_meta, body_wt);
281
282 // App.main() — x0 = x20 = A.Type, x1 = A: App witness table
283 core::arch::asm!("blr {main}",
284 main = in(reg) resolve::app_main(),
285 in("x0") app_meta,
286 in("x1") app_wt,
287 in("x20") app_meta,
288 lateout("x2") _, lateout("x3") _,
289 lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
290 lateout("x8") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
291 lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
292 lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x21") _,
293 clobber_abi("C"),
294 );
295 }
296}