swiftui-native 0.0.1

SwiftUI from pure Rust — no Swift bridge, no shim, only swift-runtime calls
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
//! Mangled result type names (from _mangledTypeName in Swift).

use super::*;
use crate::abi;
use core::ffi::c_void;
use crate::mangle;

// ── Mangled result type names (from _mangledTypeName in Swift) ──

mangle::result_types! {
    /// `ModifiedContent<AnyView, _ForegroundStyleModifier2<Color, Color>>`
    /// `ModifiedContent<AnyView, _ForegroundStyleModifier<LinearGradient>>`
    /// `TextEditor`
    pub(crate) TEXT_EDITOR_T = ty("TextEditor");
    /// `Link<Text>`
    pub(crate) LINK_T = ty("Link").of(vec![ty("Text")]);
    /// `Group<AnyView>`
    pub(crate) GROUP_T = ty("Group").of(vec![ty("AnyView")]);
    /// `ControlGroup<AnyView>`
    pub(crate) CONTROL_GROUP_T = ty("ControlGroup").of(vec![ty("AnyView")]);
    /// `ViewThatFits<AnyView>`
    pub(crate) VIEW_THAT_FITS_T = ty("ViewThatFits").of(vec![ty("AnyView")]);
}
mangle::modifier_types! {
    /// `SwiftUI.ModifiedContent<SwiftUI.AnyView, SwiftUI._PaddingLayout>`
    /// `ModifiedContent<AnyView, _PaddingLayout>`
    pub(crate) PADDING_T = ty("_PaddingLayout");
    /// `SwiftUI.ModifiedContent<SwiftUI.AnyView, SwiftUI._OpacityEffect>`
    /// `ModifiedContent<AnyView, _OpacityEffect>`
    pub(crate) OPACITY_T = ty("_OpacityEffect");
    /// `SwiftUI.ModifiedContent<SwiftUI.AnyView, SwiftUI._FrameLayout>`
    /// `ModifiedContent<AnyView, _FrameLayout>`
    pub(crate) FRAME_T = ty("_FrameLayout");
    /// `SwiftUI.ModifiedContent<SwiftUI.AnyView, SwiftUI._BackgroundStyleModifier<SwiftUI.Color>>`
    /// `ModifiedContent<AnyView, _BackgroundStyleModifier<Color>>`
    pub(crate) BG_T = ty("_BackgroundStyleModifier").of(vec![ty("Color")]);
    /// `SwiftUI.ModifiedContent<SwiftUI.AnyView, SwiftUI._ClipEffect<SwiftUI.RoundedRectangle>>`
    /// `ModifiedContent<AnyView, _ClipEffect<RoundedRectangle>>`
    pub(crate) CORNER_T = ty("_ClipEffect").of(vec![ty("RoundedRectangle")]);
    /// `.border(_:width:)` result. Every mangling in this block was taken from
    /// Swift's own `_mangledTypeName(type(of:))` rather than written by hand —
    /// these are long enough that a hand-written one silently fails to resolve.
    /// `ModifiedContent<AnyView, _OverlayModifier<_ShapeView<_StrokedShape<Rectangle._Inset>, Color>>>`
    pub(crate) BORDER_T = ty("_OverlayModifier").of(vec![ty("_ShapeView").of(vec![ty("_StrokedShape").of(vec![Type::Nominal { module: "SwiftUI", path: vec!["Rectangle", "_Inset"], kind: Kind::Struct }]), ty("Color")])]);
    /// `.font(_:)` result
    /// `ModifiedContent<AnyView, _EnvironmentKeyWritingModifier<Font?>>`
    pub(crate) FONT_T = ty("_EnvironmentKeyWritingModifier").of(vec![opt(ty("Font"))]);
    /// `.foregroundStyle(_:)` result
    /// `ModifiedContent<AnyView, _ForegroundStyleModifier<Color>>`
    pub(crate) FOREGROUND_T = ty("_ForegroundStyleModifier").of(vec![ty("Color")]);
    /// `.foregroundStyle(.secondary)` result
    /// `ModifiedContent<AnyView, _ForegroundStyleModifier<HierarchicalShapeStyle>>`
    pub(crate) FOREGROUND_HIERARCHICAL_T = ty("_ForegroundStyleModifier").of(vec![ty("HierarchicalShapeStyle")]);
    pub(crate) FOREGROUND_GRADIENT_T = ty("_ForegroundStyleModifier").of(vec![ty("LinearGradient")]);
    pub(crate) FOREGROUND2_T = ty("_ForegroundStyleModifier2").of(vec![ty("Color"), ty("Color")]);
    /// `_ForegroundStyleModifier3<Color, Color, Color>` — three palette layers.
    pub(crate) FOREGROUND3_T =
        ty("_ForegroundStyleModifier3").of(vec![ty("Color"), ty("Color"), ty("Color")]);
    /// `.bold()` result
    /// `ModifiedContent<AnyView, _EnvironmentKeyTransformModifier<[AnyFontModifier]>>`
    pub(crate) BOLD_T = ty("_EnvironmentKeyTransformModifier").of(vec![Type::Verbatim("Sa".into()).of(vec![ty_class("AnyFontModifier")])]);
}









/// A mangled type name with room for the answer beside it.
///
/// This is the shape Swift emits. A call site that needs a concrete type gets a
/// cache word of its own — `…Md` for metadata, `…WL` for a witness table — and
/// `__swift_instantiateConcreteTypeFromMangledNameV2` is a relaxed load, a null
/// check, and a release store on the miss. No lock and no hash, because the name
/// is fixed at the site and so the answer can live next to it.
///
/// [`result_type`] had to hash sixty-odd bytes of mangled name under a `Mutex`
/// to reach the same answer, on every modifier, on every view built. Here the
/// triple is cached on the `result_types!` entry itself: one type, one set of
/// words, reached by three relaxed loads.
///
/// `meta` doubles as the initialised flag — it is never null once resolved, and
/// the release store of it publishes the other two.
pub(crate) struct CachedType {
    mangled: std::sync::LazyLock<Vec<u8>>,
    meta: std::sync::atomic::AtomicUsize,
    wt: std::sync::atomic::AtomicUsize,
    size: std::sync::atomic::AtomicUsize,
}

impl CachedType {
    pub(crate) const fn new(mangled: std::sync::LazyLock<Vec<u8>>) -> Self {
        use std::sync::atomic::AtomicUsize;
        CachedType {
            mangled,
            meta: AtomicUsize::new(0),
            wt: AtomicUsize::new(0),
            size: AtomicUsize::new(0),
        }
    }

    /// The mangled name, for the callers that want the string rather than the
    /// resolved type — `demangle_type` in an assertion message, say.
    pub(crate) fn bytes(&self) -> &[u8] {
        &self.mangled
    }
}

impl std::ops::Deref for CachedType {
    type Target = [u8];
    fn deref(&self) -> &[u8] {
        &self.mangled
    }
}

/// A modifier's result type, composed against whatever it is applied to.
///
/// `.padding(4)` produces `ModifiedContent<Self, _PaddingLayout>`, and `Self` is
/// not knowable when the binding is written — it is whatever the receiver turned
/// out to be. While every handle was an `AnyView` this could be a fixed name;
/// now it has to be built per receiver, which is exactly what
/// `swift_getGenericMetadata` is for.
///
/// One cache word per receiver type, in a small map rather than a single word,
/// because the same modifier is applied to many different receivers. The modifier
/// half — `_PaddingLayout` — is resolved once and kept.
/// Public because `foreground_style_of` takes one; its fields and constructor
/// stay crate-private, so the only way to obtain one is the `modifier_types!`
/// table this crate declares.
/// Metadata, witness table and stride — what resolving a type answers.
///
/// Held as `usize` so a cache of them can be a `static`: the three are immortal
/// runtime pointers, and `*const c_void` is not `Send`.
pub(crate) type Resolved = (usize, usize, usize);

pub struct ModifierType {
    /// The *modifier*'s mangled name, not the `ModifiedContent` around it.
    mangled: std::sync::LazyLock<Vec<u8>>,
    modifier_meta: std::sync::atomic::AtomicUsize,
    composed: std::sync::OnceLock<std::sync::Mutex<std::collections::HashMap<usize, Resolved>>>,
    /// As `composed`, for a modifier that is itself generic over its content:
    /// keyed by receiver *and* content, since both vary.
    composed_with:
        std::sync::OnceLock<std::sync::Mutex<std::collections::HashMap<(usize, usize), Resolved>>>,
    /// The last answer `composed` gave, in one word.
    ///
    /// [`CachedType`] can keep its triple beside the name because the name is
    /// fixed; here the answer depends on the receiver, so the map has to stay.
    /// What the map costs is a `Mutex` and a hash on every modifier of every
    /// view — and the receiver is almost always the same one as last time, since
    /// a chain applies modifier after modifier to a single growing type.
    ///
    /// So: one slot, the shape Swift's `…Ml` cache word has. A relaxed load and
    /// a compare on the hit path, no lock and no hash. The entry is published
    /// whole rather than as four words, so a reader either sees the previous
    /// entry or the new one and never half of each; missing the cache costs a
    /// map lookup, which is what every call used to cost.
    last: std::sync::atomic::AtomicPtr<Composed>,
}

/// One resolved `ModifiedContent<recv, Modifier>`, published as a unit.
pub(crate) struct Composed {
    recv: usize,
    meta: usize,
    wt: usize,
    size: usize,
}

impl ModifierType {
    pub(crate) const fn new(mangled: std::sync::LazyLock<Vec<u8>>) -> Self {
        ModifierType {
            mangled,
            modifier_meta: std::sync::atomic::AtomicUsize::new(0),
            composed: std::sync::OnceLock::new(),
            composed_with: std::sync::OnceLock::new(),
            last: std::sync::atomic::AtomicPtr::new(core::ptr::null_mut()),
        }
    }


    /// Publish an entry into the one-slot cache.
    ///
    /// Leaked, and deliberately: the entry has to outlive every reader, there is
    /// no safe moment to free one while another thread may be mid-read, and the
    /// number of them is bounded by the distinct receiver types a modifier is
    /// ever applied to — a handful, allocated once each.
    fn remember(&self, recv: usize, meta: usize, wt: usize, size: usize) {
        use std::sync::atomic::Ordering;
        let entry = Box::into_raw(Box::new(Composed { recv, meta, wt, size }));
        self.last.store(entry, Ordering::Release);
    }

    /// `ModifiedContent<recv, Modifier<content>>`.
    ///
    /// For the modifiers that carry their content in their own type —
    /// `_OverlayModifier<V>`, `_BackgroundModifier<V>`, `_MaskAlignmentEffect<V>`.
    /// Naming one of those by mangled string fixes `V`, which is why they were
    /// spelled `<AnyView>` and the content had to be boxed to match. That name
    /// still gives an instantiation to read a descriptor from, and the runtime
    /// builds the rest.
    pub(crate) fn applied_to_content(
        &self,
        recv_meta: *const c_void,
        content_meta: *const c_void,
        content_wt: *const c_void,
    ) -> (*const c_void, *const c_void, usize) {
        let map = self
            .composed_with
            .get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()));
        let key = (recv_meta as usize, content_meta as usize);
        if let Ok(m) = map.lock() {
            if let Some(&(a, b, n)) = m.get(&key) {
                return (a as *const c_void, b as *const c_void, n);
            }
        }
        unsafe {
            let modifier =
                abi::generic_metadata_from(self.modifier_meta(), &[content_meta, content_wt]);
            assert!(
                !modifier.is_null(),
                "{} over {}",
                crate::diag::demangle_type(&self.mangled),
                crate::diag::type_name(content_meta)
            );
            let meta = abi::generic_metadata2(resolve::modified_content_ma(), recv_meta, modifier);
            assert!(
                !meta.is_null(),
                "ModifiedContent<{}, {}>",
                crate::diag::type_name(recv_meta),
                crate::diag::type_name(modifier)
            );
            let wt = abi::get_view_wt(meta);
            assert!(!wt.is_null(), "{} !: View", crate::diag::type_name(meta));
            let n = abi::type_bytes(meta);
            if let Ok(mut m) = map.lock() {
                m.insert(key, (meta as usize, wt as usize, n));
            }
            (meta, wt, n)
        }
    }

    /// The modifier's own metadata, resolved once.
    fn modifier_meta(&self) -> *const c_void {
        use std::sync::atomic::Ordering;
        let hit = self.modifier_meta.load(Ordering::Acquire);
        if hit != 0 {
            return hit as *const c_void;
        }
        let m = unsafe { abi::resolve_type_by_mangled_name(&self.mangled) };
        assert!(!m.is_null(), "Failed to resolve modifier: {}",
            crate::diag::demangle_type(&self.mangled));
        self.modifier_meta.store(m as usize, Ordering::Release);
        m
    }

    /// `ModifiedContent<recv, Self>`: its metadata, `View` witness and size.
    pub(crate) fn applied_to(&self, recv_meta: *const c_void) -> (*const c_void, *const c_void, usize) {
        use std::sync::atomic::Ordering;
        // The one-slot cache first. Acquire pairs with the release store below,
        // so everything the entry points at is visible once the pointer is.
        let hit = self.last.load(Ordering::Acquire);
        if !hit.is_null() {
            let e = unsafe { &*hit };
            if e.recv == recv_meta as usize {
                return (e.meta as *const c_void, e.wt as *const c_void, e.size);
            }
        }
        let map = self.composed.get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()));
        if let Ok(m) = map.lock() {
            if let Some(&(a, b, n)) = m.get(&(recv_meta as usize)) {
                self.remember(recv_meta as usize, a, b, n);
                return (a as *const c_void, b as *const c_void, n);
            }
        }
        unsafe {
            let meta = abi::generic_metadata2(
                resolve::modified_content_ma(),
                recv_meta,
                self.modifier_meta(),
            );
            assert!(!meta.is_null(), "ModifiedContent<{}, {}>",
                crate::diag::type_name(recv_meta),
                crate::diag::demangle_type(&self.mangled));
            let wt = abi::get_view_wt(meta);
            assert!(!wt.is_null(), "{} !: View", crate::diag::type_name(meta));
            let n = abi::type_bytes(meta);
            if let Ok(mut m) = map.lock() {
                m.insert(recv_meta as usize, (meta as usize, wt as usize, n));
            }
            self.remember(recv_meta as usize, meta as usize, wt as usize, n);
            (meta, wt, n)
        }
    }
}

/// What [`result_type`] accepts: a `result_types!` entry, which carries its own
/// cache, or a bare mangled name, which has to go through the shared map.
pub(crate) trait ResolvesToType {
    /// # Safety
    /// The name must be a type this process can instantiate.
    unsafe fn resolve(self) -> (*const c_void, *const c_void, usize);
}

impl ResolvesToType for &'static CachedType {
    unsafe fn resolve(self) -> (*const c_void, *const c_void, usize) {
        use std::sync::atomic::Ordering;
        let meta = self.meta.load(Ordering::Acquire);
        if meta != 0 {
            return (
                meta as *const c_void,
                self.wt.load(Ordering::Relaxed) as *const c_void,
                self.size.load(Ordering::Relaxed),
            );
        }
        let (m, w, n) = unsafe { resolve_uncached(&self.mangled) };
        self.wt.store(w as usize, Ordering::Relaxed);
        self.size.store(n, Ordering::Relaxed);
        self.meta.store(m as usize, Ordering::Release);
        (m, w, n)
    }
}

impl ResolvesToType for &[u8] {
    unsafe fn resolve(self) -> (*const c_void, *const c_void, usize) {
        unsafe { result_type_slow(self) }
    }
}

/// Resolve a modifier's concrete result type: metadata, `View` witness table,
/// and its exact size (so result buffers are never guessed).
pub(crate) unsafe fn result_type(
    key: impl ResolvesToType,
) -> (*const c_void, *const c_void, usize) {
    unsafe { key.resolve() }
}

/// The shared, map-backed path, for names that are not a `result_types!` entry.
unsafe fn result_type_slow(mangled: &[u8]) -> (*const c_void, *const c_void, usize) {
    use std::collections::HashMap;
    use std::sync::{Mutex, OnceLock};
    static CACHE: OnceLock<Mutex<HashMap<Vec<u8>, Resolved>>> = OnceLock::new();
    let cache = CACHE.get_or_init(|| Mutex::new(HashMap::new()));
    if let Ok(map) = cache.lock() {
        if let Some(&(m, w, n)) = map.get(mangled) {
            return (m as *const c_void, w as *const c_void, n);
        }
    }
    let (meta, wt, n) = unsafe { resolve_uncached(mangled) };
    if let Ok(mut map) = cache.lock() {
        map.insert(mangled.to_vec(), (meta as usize, wt as usize, n));
    }
    (meta, wt, n)
}

/// The work itself: demangle, instantiate, find the `View` conformance, measure.
unsafe fn resolve_uncached(mangled: &[u8]) -> (*const c_void, *const c_void, usize) {
    let meta = unsafe { abi::resolve_type_by_mangled_name(mangled) };
    assert!(!meta.is_null(), "Failed to resolve: {}", crate::diag::demangle_type(mangled));
    let wt = unsafe { abi::get_view_wt(meta) };
    assert!(!wt.is_null(), "{} !: View", crate::diag::demangle_type(mangled));
    // Stride, not size — callees may write the trailing padding.
    let n = unsafe { abi::type_bytes(meta) };
    (meta, wt, n)
}

/// Wrap a modifier's result, at the type the modifier actually produced.
/// Wrap a modifier's result, taking the buffer the callee wrote.
///
/// The buffer is already the right size and nothing else needs it, so it is
/// moved into the handle rather than copied into a second allocation — which is
/// one allocation per modifier instead of two.
///
/// # Safety
/// `result` must be the buffer the callee wrote for this modifier.
pub(crate) unsafe fn wrap_modified(
    result: Vec<u8>,
    modifier: &ModifierType,
    recv: &ViewHandle,
) -> ViewHandle {
    let (meta, wt, n) = modifier.applied_to(recv.meta);
    assert!(
        result.len() >= n,
        "{} needs {n} bytes, buffer was {} (receiver {})",
        crate::diag::type_name(meta), result.len(), crate::diag::type_name(recv.meta),
    );
    unsafe { ViewHandle::adopt(result, meta, wt) }
}

/// Time one resolution through each path, for `examples/bench_build`.
///
/// Building a whole view to compare caches turned out to be unmeasurable: the
/// Swift calls and the `AnyView` allocation swamp the lookup, and the same
/// binary drifted by 2x between batches on an idle machine. This calls the two
/// paths and nothing else, in one process, so the only difference left is the
/// one being measured.
#[doc(hidden)]
pub fn bench_result_type_paths(iters: u32) -> (f64, f64) {
    use std::time::Instant;
    // A `result_types!` entry reached both ways: by reference, which finds the
    // cache words beside it, and by its bytes, which cannot and takes the map.
    let cached = || unsafe { result_type(&GROUP_T) };
    let mapped = || unsafe { result_type(GROUP_T.bytes()) };
    std::hint::black_box(cached());
    std::hint::black_box(mapped());

    let mut best = (f64::MAX, f64::MAX);
    for _ in 0..9 {
        let t = Instant::now();
        for _ in 0..iters {
            std::hint::black_box(cached());
        }
        let a = t.elapsed().as_nanos() as f64 / f64::from(iters);
        let t = Instant::now();
        for _ in 0..iters {
            std::hint::black_box(mapped());
        }
        let b = t.elapsed().as_nanos() as f64 / f64::from(iters);
        best = (best.0.min(a), best.1.min(b));
    }
    best
}