swiftui_native/runtime.rs
1//! The Swift runtime's own entry points, made safe to call.
2//!
3//! [`crate::abi`] is about *calling SwiftUI* — the calling convention, metadata,
4//! witness tables. This module is about the runtime underneath it: the parts of
5//! `libswiftCore` a Swift program uses without ever naming them, because the
6//! compiler emits the calls. There is no compiler here, so they are called by
7//! hand.
8//!
9//! Three groups, and they are here together because each is the same kind of
10//! thing — a runtime service a Rust caller has no other way to reach:
11//!
12//! * **Dynamic casts.** `as?` and `as!`, and the twenty other entry points
13//! behind them. What makes this worth having is that a [`ViewHandle`] carries
14//! its metadata at run time, so "is this really a `Text`?" is a question that
15//! can be *asked* rather than tracked.
16//! * **Errors.** A Swift `Error` is a box, `swift_allocError` makes one, and a
17//! Rust type can be the thing inside it once its `Error` conformance is
18//! synthesised. That is what lets Rust hand an error *to* Swift.
19//! * **Tasks.** [`detached`] runs a Rust closure as a real Swift `Task`, on
20//! Swift's own global executor, with cancellation visible from both sides.
21//! [`crate::asyncfn`] supplies the async closures SwiftUI's `.task` takes;
22//! this starts one from nothing.
23//!
24//! Everything here is declared in `swift-runtime-sys`; what this adds is the
25//! safety argument, the ownership rules, and tests that the calls do what they
26//! are supposed to.
27
28use crate::abi;
29use crate::views::ViewHandle;
30use core::ffi::c_void;
31
32// ── Dynamic casts ──
33
34/// Flags for [`swift_runtime_sys::DynamicCast::swift_dynamicCast`].
35///
36/// Only the two that matter to a caller who owns the source value and wants to
37/// keep it: the cast copies rather than consumes, and leaves the source alone if
38/// it fails.
39const CAST_COPY: u32 = 0;
40
41/// Is `value` of type `target`?
42///
43/// `x is T`, without producing the result. The source is left untouched, so this
44/// is safe to ask of a value someone else owns.
45///
46/// # Safety
47/// `value` must address an initialised value of `source`'s type, and both
48/// metadata pointers must be valid.
49pub unsafe fn is_type(
50 value: *const c_void,
51 source: *const c_void,
52 target: *const c_void,
53) -> bool {
54 unsafe {
55 // The destination has to exist even for a question: the runtime writes
56 // the cast result into it on success, and a null destination is not a
57 // way to ask without receiving.
58 let mut scratch = vec![0u8; abi::type_bytes(target).max(1)];
59 let ok = swift_runtime_sys::DynamicCast::swift_dynamicCast(
60 scratch.as_mut_ptr() as *mut c_void,
61 value as *mut c_void,
62 source,
63 target,
64 CAST_COPY,
65 );
66 if ok {
67 let vwt = &*swift_runtime_sys::SwiftABI::get_value_witness_table(target);
68 if vwt.size > 0 {
69 (vwt.destroy)(scratch.as_mut_ptr() as *mut c_void, target);
70 }
71 }
72 ok
73 }
74}
75
76/// Is the view a `T`, where `T` is named by its mangled type?
77///
78/// The question a `ViewHandle` makes askable: it carries its own metadata, so
79/// "did that modifier really build a `ModifiedContent`?" is answerable at run
80/// time. `dynamic_type_name` gives the same information as a *string*; this
81/// gives it as a decision, and without parsing.
82///
83/// ```no_run
84/// # use swiftui_native::{runtime, views, mangle};
85/// let t = views::text("hi");
86/// assert!(runtime::view_is(&t, &mangle::nominal(&["SwiftUI", "Text"], mangle::Kind::Struct)));
87/// ```
88pub fn view_is(view: &ViewHandle, mangled: &str) -> bool {
89 unsafe {
90 let target = abi::resolve_type_by_mangled_name(mangled.as_bytes());
91 if target.is_null() {
92 return false;
93 }
94 is_type(view.value_ptr(), views_meta(view), target)
95 }
96}
97
98/// A handle's metadata, without widening `ViewHandle`'s own surface.
99fn views_meta(view: &ViewHandle) -> *const c_void {
100 crate::views::meta_of(view)
101}
102
103/// Cast a class reference to another class, or `None`.
104///
105/// `object as? SomeClass` for the class-to-class case, which has its own entry
106/// point because it needs no value buffer — a class reference is one word, and
107/// the answer is the same word or nothing.
108///
109/// # Safety
110/// `object` must be a live Swift or Objective-C class instance and `target`
111/// class metadata.
112pub unsafe fn cast_class(object: *const c_void, target: *const c_void) -> Option<*const c_void> {
113 let out =
114 unsafe { swift_runtime_sys::DynamicCast::swift_dynamicCastClass(object, target) };
115 (!out.is_null()).then_some(out)
116}
117
118/// Cast a *metatype* to another metatype, or `None`.
119///
120/// `T.self as? U.Type`. Distinct from [`cast_class`] because a metatype is not
121/// an instance: the runtime compares the types themselves.
122///
123/// # Safety
124/// Both arguments must be type metadata.
125pub unsafe fn cast_metatype(
126 source: *const c_void,
127 target: *const c_void,
128) -> Option<*const c_void> {
129 let out = unsafe {
130 swift_runtime_sys::DynamicCast::swift_dynamicCastMetatype(source, target)
131 };
132 (!out.is_null()).then_some(out)
133}
134
135// ── Errors ──
136
137/// A Swift `Error` box, owned.
138///
139/// What `throw` produces and `catch` receives: a refcounted box holding a value
140/// and the `Error` conformance that describes it. Rust can make one, which is
141/// how a Rust callback answers a Swift API that wants a failure.
142pub struct SwiftError {
143 boxed: *mut c_void,
144 payload_meta: *const c_void,
145}
146
147impl SwiftError {
148 /// Box a value as an `Error`.
149 ///
150 /// # Safety
151 /// `value` must address an initialised value of `meta`'s type, and `wt` must
152 /// be that type's `Error` conformance. The value is *copied* into the box, so
153 /// the caller keeps its own.
154 pub unsafe fn new(
155 value: *const c_void,
156 meta: *const c_void,
157 wt: *const c_void,
158 ) -> Self {
159 unsafe {
160 let pair = swift_runtime_sys::ErrorHandling::swift_allocError(
161 meta,
162 wt,
163 value as *mut c_void,
164 // `isTake: false` — the box gets a copy and the caller keeps the
165 // original. Passing `true` here hands ownership over, and then
166 // dropping the caller's copy destroys the box's value.
167 false,
168 );
169 assert!(!pair.object.is_null(), "swift_allocError returned nothing");
170 Self { boxed: pair.object, payload_meta: meta }
171 }
172 }
173
174 /// The box, for handing to Swift.
175 ///
176 /// Borrowed: Swift is expected to retain it if it keeps it.
177 pub fn as_ptr(&self) -> *mut c_void {
178 self.boxed
179 }
180
181 /// The type of the value inside, as the runtime spells it.
182 ///
183 /// Read back out of the box rather than remembered, which is what makes this
184 /// a check on `swift_allocError` rather than an echo of its argument.
185 pub fn payload_type_name(&self) -> String {
186 unsafe {
187 let mut scratch: *mut c_void = core::ptr::null_mut();
188 let mut out = swift_runtime_sys::ErrorHandling::ErrorValueResult {
189 value: core::ptr::null(),
190 error_type: core::ptr::null(),
191 error_conformance: core::ptr::null(),
192 };
193 swift_runtime_sys::ErrorHandling::swift_getErrorValue(
194 self.boxed,
195 &mut scratch,
196 &mut out,
197 );
198 assert!(!out.error_type.is_null(), "the error box has no type");
199 crate::diag::type_name(out.error_type)
200 }
201 }
202
203 /// The value inside, as bytes.
204 ///
205 /// # Safety
206 /// The slice borrows the box's storage and is only valid while it lives.
207 pub unsafe fn payload(&self) -> &[u8] {
208 unsafe {
209 let mut scratch: *mut c_void = core::ptr::null_mut();
210 let mut out = swift_runtime_sys::ErrorHandling::ErrorValueResult {
211 value: core::ptr::null(),
212 error_type: core::ptr::null(),
213 error_conformance: core::ptr::null(),
214 };
215 swift_runtime_sys::ErrorHandling::swift_getErrorValue(
216 self.boxed,
217 &mut scratch,
218 &mut out,
219 );
220 core::slice::from_raw_parts(
221 out.value as *const u8,
222 abi::type_size(self.payload_meta),
223 )
224 }
225 }
226}
227
228impl Clone for SwiftError {
229 fn clone(&self) -> Self {
230 unsafe {
231 swift_runtime_sys::ErrorHandling::swift_errorRetain(self.boxed);
232 }
233 Self { boxed: self.boxed, payload_meta: self.payload_meta }
234 }
235}
236
237impl Drop for SwiftError {
238 fn drop(&mut self) {
239 unsafe { swift_runtime_sys::ErrorHandling::swift_errorRelease(self.boxed) };
240 }
241}
242
243/// An `Error` holding one `Int`, for the cases that only need to say *which*.
244///
245/// The `Error` conformance is synthesised the same way every other one in this
246/// crate is — `Error` has no requirements of its own beyond the defaults, so the
247/// witness table is all defaults and the interesting part is that it is
248/// *registered*, which is what makes the box catchable as an `Error` on the
249/// Swift side.
250pub fn error_code(code: i64) -> SwiftError {
251 unsafe {
252 let (meta, wt) = *error_carrier();
253 let mut value = code;
254 SwiftError::new(
255 &mut value as *mut i64 as *const c_void,
256 meta as *const c_void,
257 wt as *const c_void,
258 )
259 }
260}
261
262/// The carrier type and its `Error` conformance, minted once.
263fn error_carrier() -> &'static (usize, usize) {
264 static C: std::sync::OnceLock<(usize, usize)> = std::sync::OnceLock::new();
265 C.get_or_init(|| unsafe {
266 let (descriptor, meta) =
267 crate::state::emit_int_carrier(b"RustError\0", b"6rswift9RustErrorV\0");
268 let protocol = crate::conformance::Protocol::from_symbol(c"$ss5ErrorMp");
269 // `Error` is all defaults: `_domain`, `_code`, `_userInfo` and
270 // `_getEmbeddedNSError` each have one. A conformance is still needed —
271 // the box records it, and `swift_getErrorValue` hands it back.
272 let witnesses: Vec<crate::conformance::Witness> = (0..protocol.requirement_count())
273 .map(|_| crate::conformance::Witness::Default)
274 .collect();
275 let conformance = Box::leak(Box::new(crate::conformance::synthesize(
276 &protocol,
277 descriptor,
278 &witnesses,
279 )));
280 (meta as usize, conformance.witness_table() as usize)
281 })
282}
283
284// ── Tasks ──
285
286/// A Swift `Task` this crate started, and owns.
287///
288/// `swift_task_create` hands the task back at `+1` — Swift's own `Task` value
289/// holds that reference and releases it when the value goes out of scope, and so
290/// does this. Dropping the handle while the task is still running is fine: the
291/// runtime keeps its own reference until the task completes.
292pub struct Task {
293 task: *mut c_void,
294 state: std::sync::Arc<crate::asyncfn::TaskState>,
295}
296
297// The handle is a refcounted pointer and a shared signal, both of which the
298// Swift runtime and `Arc` make safe to move and share between threads. Starting
299// a task on one thread and waiting for it on another is the whole point.
300unsafe impl Send for Task {}
301unsafe impl Sync for Task {}
302
303impl Task {
304 /// Has the task been cancelled?
305 pub fn is_cancelled(&self) -> bool {
306 unsafe { task_is_cancelled(self.task) }
307 }
308
309 /// Ask the task to stop.
310 ///
311 /// Cooperative, as in Swift: it sets a flag the body is expected to check
312 /// with [`current_task`]. Nothing is interrupted.
313 pub fn cancel(&self) {
314 unsafe { task_cancel(self.task) };
315 }
316
317 /// The task's base priority, as Swift's `TaskPriority` numbers it.
318 pub fn priority(&self) -> u32 {
319 unsafe { task_base_priority(self.task) }
320 }
321
322 /// Has the closure returned?
323 ///
324 /// Does not block. True once the body has run, whether it returned or
325 /// panicked.
326 pub fn is_finished(&self) -> bool {
327 self.state.is_finished()
328 }
329
330 /// Block until the closure has returned.
331 ///
332 /// A Swift `Task`'s result is delivered by *awaiting* it, and Rust has
333 /// nothing to await from — so the entry signals a condition variable on the
334 /// way out and this waits on it. That makes a detached task usable from
335 /// ordinary synchronous Rust, which is what it is for here.
336 ///
337 /// Waiting on the executor's own thread would deadlock, so do not call this
338 /// from inside another task.
339 pub fn wait(&self) {
340 self.state.wait(None);
341 }
342
343 /// [`wait`](Self::wait) with a limit. `true` if the task finished in time.
344 pub fn wait_timeout(&self, limit: std::time::Duration) -> bool {
345 self.state.wait(Some(limit))
346 }
347}
348
349impl Drop for Task {
350 fn drop(&mut self) {
351 // The `+1` `swift_task_create` returned. Swift's `Task` releases here
352 // too; the runtime holds its own reference until the task completes, so
353 // this does not cut a running task short.
354 unsafe { swift_runtime_sys::RuntimeRaw::swift_release(self.task) };
355 }
356}
357
358/// The task running right now — *borrowed*, not owned.
359///
360/// A separate type from [`Task`] because the two differ in what they may do: a
361/// reference obtained from `swift_task_getCurrent` is not one this crate owns,
362/// so releasing it would be an over-release, and there is no completion signal
363/// for a task somebody else started.
364pub struct CurrentTask {
365 task: *mut c_void,
366}
367
368impl CurrentTask {
369 /// Has this task been cancelled?
370 ///
371 /// The question a long-running body should be asking.
372 pub fn is_cancelled(&self) -> bool {
373 unsafe { task_is_cancelled(self.task) }
374 }
375
376 /// This task's base priority.
377 pub fn priority(&self) -> u32 {
378 unsafe { task_base_priority(self.task) }
379 }
380}
381
382/// Run `body` as a detached Swift `Task`, at the default priority.
383///
384/// See [`detached_with_priority`], which this calls.
385pub fn detached(body: impl FnOnce() + Send + 'static) -> Task {
386 spawn(None, Box::new(body))
387}
388
389/// Run `body` as a detached Swift `Task` at a chosen priority.
390///
391/// The closure runs on Swift's global concurrent executor, so it must be `Send`
392/// and must not touch anything that wants the main thread — which is most of
393/// SwiftUI. It is `FnOnce`, because a task runs its body exactly once and owns
394/// it: values can be moved in, and the closure is dropped as soon as it returns.
395///
396/// A panic inside it is caught and reported rather than unwound into the Swift
397/// async frame, where there is no landing pad; the task still completes.
398///
399/// As many at once as you like. `swift_task_create`'s fifth argument is the
400/// closure context and the entry receives it in x20, so each task carries its
401/// own closure — unlike the `.task` modifier, whose async closure is handed back
402/// the context *SwiftUI* was given and so has to live in one of three fixed
403/// slots. [`crate::asyncfn`] describes that ceiling; this is the same machinery
404/// without it.
405///
406/// ```no_run
407/// # use swiftui_native::runtime;
408/// let work = runtime::detached(move || {
409/// // …on a cooperative thread…
410/// });
411/// work.wait();
412/// ```
413///
414/// # Two things this got wrong, both worth knowing
415///
416/// `swift_task_create` faults *inside itself* when either is wrong, which is a
417/// long way from the mistake. Both were found by breaking on the real call in a
418/// Swift binary and reading the registers.
419///
420/// * **The flags are not zero.** `Task.detached` passes `1 << 12` —
421/// `Task_EnqueueJob` — which makes `swift_task_create` schedule the task
422/// itself. Passing zero and calling `swift_task_enqueue` afterwards is not the
423/// same thing.
424/// * **`$sytN` is not `()`'s metadata.** It is the *full* metadata record, whose
425/// first word is the value witness table; the metadata address point is eight
426/// bytes further on. The runtime reads the result type's value witnesses to
427/// size the future, so an address eight bytes low reads a function pointer as
428/// a size. [`crate::abi::resolve_type_by_mangled_name`] returns the address
429/// point, which is what Swift passes.
430///
431/// The empty tuple is *special* in this, and it is worth saying so before
432/// someone generalises the lesson: `$sSiN`, `$sSSN`, `$sSbN` and `$sSdN` were
433/// checked against the same lookup and are exactly the address points, which
434/// is why the rest of this crate uses them directly. A tuple has no nominal
435/// descriptor, so its metadata symbol names a different thing.
436pub fn detached_with_priority(
437 priority: crate::views::TaskPriority,
438 body: impl FnOnce() + Send + 'static,
439) -> Task {
440 spawn(Some(priority), Box::new(body))
441}
442
443/// `Task_EnqueueJob` — bit 12 of `TaskCreateFlags`. The task is scheduled as
444/// part of being created, which is what `Task.detached` asks for.
445const ENQUEUE_JOB: usize = 1 << 12;
446
447fn spawn(
448 priority: Option<crate::views::TaskPriority>,
449 body: Box<dyn FnOnce() + Send>,
450) -> Task {
451 let state = std::sync::Arc::new(crate::asyncfn::TaskState::new());
452 unsafe {
453 let (record, context) = crate::asyncfn::task_entry_point(body, &state);
454 // `Task<Void, Never>`: the success type is `()`.
455 let unit = crate::abi::resolve_type_by_mangled_name(b"yt");
456 assert!(!unit.is_null(), "() metadata");
457
458 // The low eight bits of the flags are the priority, and the number is
459 // read out of Swift's own static rather than written down: a
460 // `TaskPriority` is a `UInt8` wrapper, so the first byte of the value its
461 // getter returns *is* the raw priority. Zero means "unspecified", which
462 // is what `Task.detached` passes when no priority is given.
463 let raw = priority.map_or(0usize, |p| {
464 let meta = crate::abi::metadata0(crate::resolve::task_priority_ma());
465 assert!(!meta.is_null(), "TaskPriority metadata");
466 let bytes = crate::views::resilient_static(meta, p.getter());
467 usize::from(bytes[0])
468 });
469
470 let created = task_create()(
471 ENQUEUE_JOB | raw,
472 // No options: no task name, no initial executor, no task group.
473 core::ptr::null_mut(),
474 unit,
475 record,
476 context,
477 );
478 assert!(!created.task.is_null(), "swift_task_create returned nothing");
479 Task { task: created.task, state }
480 }
481}
482
483// ── The concurrency entry points, resolved rather than linked ──
484//
485// `swift-runtime-sys` declares these as ordinary `extern "C"`, which is a `-sys`
486// crate's job. Calling them that way, though, puts
487// `@rpath/libswift_Concurrency.dylib` in the load commands of whatever links this
488// crate — and that library exists only in the dyld shared cache, so the *final
489// binary* needs `/usr/lib/swift` on its `LC_RPATH` to find it. A build script's
490// `rustc-link-arg` reaches its own package's targets and not a dependent's, so
491// this crate cannot add that rpath on a consumer's behalf: an external crate
492// linked cleanly and then died in dyld with "Library not loaded".
493//
494// So they are looked up at run time, as every SwiftUI symbol in this crate is.
495// The library is in the process either way — SwiftUI links it — and nothing names
496// it in a load command the loader then has to resolve against a path.
497
498/// `swift_task_create(flags, options, resultType, function, closureContext)`.
499type TaskCreate = unsafe extern "C" fn(
500 usize,
501 *mut c_void,
502 *const c_void,
503 *const c_void,
504 *mut c_void,
505) -> swift_runtime_sys::ConcurrencyRuntime::AsyncTaskAndContext;
506
507unsafe fn task_create() -> TaskCreate {
508 unsafe { core::mem::transmute(crate::resolve::require(c"swift_task_create")) }
509}
510
511unsafe fn task_of<R>(name: &'static core::ffi::CStr) -> unsafe extern "C" fn(*mut c_void) -> R {
512 unsafe { core::mem::transmute(crate::resolve::require(name)) }
513}
514
515/// `swift_task_getCurrent()` — null outside a task.
516unsafe fn task_get_current() -> *mut c_void {
517 let f: unsafe extern "C" fn() -> *mut c_void =
518 unsafe { core::mem::transmute(crate::resolve::require(c"swift_task_getCurrent")) };
519 unsafe { f() }
520}
521
522/// `swift_task_isCancelled(task)`.
523unsafe fn task_is_cancelled(task: *mut c_void) -> bool {
524 unsafe { task_of::<bool>(c"swift_task_isCancelled")(task) }
525}
526
527/// `swift_task_cancel(task)`.
528unsafe fn task_cancel(task: *mut c_void) {
529 unsafe { task_of::<()>(c"swift_task_cancel")(task) }
530}
531
532/// `swift_task_basePriority(task)`.
533unsafe fn task_base_priority(task: *mut c_void) -> u32 {
534 unsafe { task_of::<u32>(c"swift_task_basePriority")(task) }
535}
536
537/// The task running right now, if any.
538///
539/// Null outside a task, which is what makes it useful: a Rust closure handed to
540/// `.task { }` or to [`detached`] can tell whether Swift's scheduler is what
541/// called it, and can ask that task whether it has been cancelled — which is the
542/// whole of structured cancellation from this side.
543///
544/// ```no_run
545/// # use swiftui_native::{runtime, views};
546/// let v = views::task(
547/// &views::text("loading"),
548/// 0,
549/// views::TaskPriority::Utility,
550/// file!(),
551/// line!() as i64,
552/// || {
553/// while !runtime::current_task().is_some_and(|t| t.is_cancelled()) {
554/// // …work…
555/// break;
556/// }
557/// },
558/// );
559/// ```
560pub fn current_task() -> Option<CurrentTask> {
561 let task = unsafe { task_get_current() };
562 (!task.is_null()).then_some(CurrentTask { task })
563}