Skip to main content

swiftui_native/views/
customlayout.rs

1//! A SwiftUI `Layout` whose algorithm is written in Rust.
2//!
3//! `Layout` is the protocol SwiftUI asks a *client* to conform to: you are
4//! handed the children and a size proposal, and you answer with a size and a
5//! position for each child. Everything else in this crate calls into SwiftUI;
6//! this is SwiftUI calling out, into Rust, in the middle of laying out a view
7//! tree.
8//!
9//! ```no_run
10//! # use swiftui_native::views::{self, Rect, Subviews};
11//! // A diagonal cascade: each child down and to the right of the last.
12//! let l = views::layout(
13//!     |subviews, _| (subviews.len() as f64 * 30.0 + 60.0, subviews.len() as f64 * 24.0 + 20.0),
14//!     |subviews, bounds: Rect, _| {
15//!         for i in 0..subviews.len() {
16//!             subviews.place(i, bounds.x + i as f64 * 30.0, bounds.y + i as f64 * 24.0);
17//!         }
18//!     },
19//! );
20//! let v = views::custom_layout(&l, &[views::text("a"), views::text("b")]);
21//! ```
22//!
23//! # How it is reached
24//!
25//! `MyLayout { … }` in Swift is `Layout.callAsFunction`, which is `@inlinable`;
26//! its body builds a `_VariadicView.Tree<_LayoutRoot<Self>, Content>`, and both
27//! of those initialisers *are* exported. So the tree is assembled here the way
28//! the inlined code assembles it — the same route, not a parallel one.
29//!
30//! The conformance itself is synthesised at run time by
31//! [`crate::conformance`], which already does this for `PreferenceKey` and
32//! four others. `Layout` has eleven requirements, seven of which carry a
33//! compiler-supplied default, so four are written here: the `Animatable` base,
34//! `makeCache`, `sizeThatFits` and `placeSubviews`.
35
36use super::{wrap_owned, ViewHandle};
37use crate::abi;
38use crate::resolve;
39use core::ffi::c_void;
40use std::sync::{Mutex, OnceLock};
41
42/// The rectangle a layout is given to fill.
43#[derive(Clone, Copy, Debug, PartialEq)]
44pub struct Rect {
45    pub x: f64,
46    pub y: f64,
47    pub width: f64,
48    pub height: f64,
49}
50
51/// What the parent is offering, each dimension possibly unspecified.
52///
53/// `None` means "as much or as little as you like" — SwiftUI's `nil`, which is
54/// not the same as zero and is why this is an `Option` rather than a float.
55#[derive(Clone, Copy, Debug, PartialEq)]
56pub struct Proposal {
57    pub width: Option<f64>,
58    pub height: Option<f64>,
59}
60
61impl Proposal {
62    /// Each dimension, or `fallback` where the parent said nothing.
63    pub fn or(self, fallback: f64) -> (f64, f64) {
64        (self.width.unwrap_or(fallback), self.height.unwrap_or(fallback))
65    }
66}
67
68/// The children, as SwiftUI hands them over.
69///
70/// Borrowed for the duration of one callback: the pointer is SwiftUI's and is
71/// not valid after the call returns, which is why this carries a lifetime and
72/// is never stored.
73pub struct Subviews<'a> {
74    raw: *mut c_void,
75    marker: core::marker::PhantomData<&'a ()>,
76}
77
78impl Subviews<'_> {
79    /// How many children there are.
80    pub fn len(&self) -> usize {
81        unsafe {
82            let start = call_index(resolve::layout_subviews_start(), self.raw);
83            let end = call_index(resolve::layout_subviews_end(), self.raw);
84            (end - start).max(0) as usize
85        }
86    }
87
88    /// Whether there are none.
89    pub fn is_empty(&self) -> bool {
90        self.len() == 0
91    }
92
93    /// The size child `i` would like, given a proposal.
94    ///
95    /// Asking is the point of a two-pass layout: a child that is offered
96    /// nothing reports its ideal size, and a layout that never asks can only
97    /// place things blindly.
98    pub fn size_that_fits(&self, i: usize, proposal: Proposal) -> (f64, f64) {
99        unsafe {
100            let mut sub = self.child(i);
101            let (w, wt, h, ht) = proposal_words(proposal);
102            let (rw, rh): (f64, f64);
103            core::arch::asm!("blr {f}", f = in(reg) resolve::layout_subview_size(),
104                in("x0") w, in("x1") wt, in("x2") h, in("x3") ht,
105                in("x20") sub.as_mut_ptr(),
106                lateout("d0") rw, lateout("d1") rh,
107                lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
108                lateout("x8") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
109                lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
110                lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x21") _,
111                clobber_abi("C"),
112            );
113            (rw, rh)
114        }
115    }
116
117    /// Put child `i` at `(x, y)`, its top-left corner there.
118    pub fn place(&self, i: usize, x: f64, y: f64) {
119        self.place_proposed(i, x, y, Proposal { width: None, height: None });
120    }
121
122    /// The same, offering the child a size.
123    pub fn place_proposed(&self, i: usize, x: f64, y: f64, proposal: Proposal) {
124        unsafe {
125            let mut sub = self.child(i);
126            let (w, wt, h, ht) = proposal_words(proposal);
127            // `place(at:anchor:proposal:)` — the point in d0/d1, the anchor
128            // (a `UnitPoint`) in d2/d3, the proposal in x0–x3, and the
129            // subview as `self`. `(0, 0)` is `.topLeading`, which is the
130            // anchor a caller thinking in rectangles expects.
131            core::arch::asm!("blr {f}", f = in(reg) resolve::layout_subview_place(),
132                in("d0") x, in("d1") y, in("d2") 0.0f64, in("d3") 0.0f64,
133                in("x0") w, in("x1") wt, in("x2") h, in("x3") ht,
134                in("x20") sub.as_mut_ptr(),
135                lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
136                lateout("x8") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
137                lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
138                lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x21") _,
139                clobber_abi("C"),
140            );
141        }
142    }
143
144    /// Child `i`'s bytes, through the exported subscript.
145    ///
146    /// # Safety
147    /// `i` must be in range; SwiftUI traps otherwise, as it would in Swift.
148    unsafe fn child(&self, i: usize) -> Vec<u8> {
149        unsafe {
150            let meta = abi::metadata0(resolve::layout_subview_ma());
151            assert!(!meta.is_null(), "LayoutSubview metadata");
152            let mut out = vec![0u8; abi::type_bytes(meta)];
153            core::arch::asm!("blr {f}", f = in(reg) resolve::layout_subviews_subscript(),
154                in("x0") i as i64,
155                in("x8") out.as_mut_ptr(),
156                in("x20") self.raw,
157                lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
158                lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x9") _,
159                lateout("x10") _, lateout("x11") _, lateout("x12") _, lateout("x13") _,
160                lateout("x14") _, lateout("x15") _, lateout("x16") _, lateout("x17") _,
161                lateout("lr") _, lateout("x21") _, clobber_abi("C"),
162            );
163            out
164        }
165    }
166}
167
168/// `(swiftself collection) -> Int`, for `startIndex` and `endIndex`.
169///
170/// # Safety
171/// `f` must be one of those getters and `raw` a live `LayoutSubviews`.
172unsafe fn call_index(f: *const c_void, raw: *mut c_void) -> i64 {
173    unsafe {
174        let out: i64;
175        core::arch::asm!("blr {f}", f = in(reg) f,
176            in("x20") raw,
177            lateout("x0") out,
178            lateout("x1") _, lateout("x2") _, lateout("x3") _, lateout("x4") _,
179            lateout("x5") _, lateout("x6") _, lateout("x7") _, lateout("x8") _,
180            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
181            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
182            lateout("x17") _, lateout("lr") _, lateout("x21") _, clobber_abi("C"),
183        );
184        out
185    }
186}
187
188/// A `ProposedViewSize` as the four words it travels in.
189///
190/// Each dimension is a `CGFloat?`: the bits of the double, then a tag that is
191/// 1 for `nil`. `.unspecified` is both tags set, which is what a layout that
192/// asks a child for its ideal size passes.
193fn proposal_words(p: Proposal) -> (u64, u64, u64, u64) {
194    let one = |v: Option<f64>| match v {
195        Some(x) => (x.to_bits(), 0u64),
196        None => (0u64, 1u64),
197    };
198    let (w, wt) = one(p.width);
199    let (h, ht) = one(p.height);
200    (w, wt, h, ht)
201}
202
203// ── The algorithms, kept on the Rust side ────────────────────────────────
204
205/// A layout algorithm.
206///
207/// The two passes are separate methods because SwiftUI runs them separately,
208/// and may run the first several times — once per candidate size — before
209/// running the second. A layout that placed children while sizing would place
210/// them repeatedly.
211///
212/// Implement this on a type when the layout has parameters or state:
213///
214/// ```no_run
215/// # use swiftui_native::views::{LayoutAlgorithm, Proposal, Rect, Subviews};
216/// struct Cascade { step: f64 }
217///
218/// impl LayoutAlgorithm for Cascade {
219///     fn size_that_fits(&self, subviews: &Subviews<'_>, _: Proposal) -> (f64, f64) {
220///         let n = subviews.len() as f64;
221///         (n * self.step + 60.0, n * self.step + 20.0)
222///     }
223///     fn place_subviews(&self, subviews: &Subviews<'_>, bounds: Rect, _: Proposal) {
224///         for i in 0..subviews.len() {
225///             let d = i as f64 * self.step;
226///             subviews.place(i, bounds.x + d, bounds.y + d);
227///         }
228///     }
229/// }
230/// ```
231///
232/// A pair of closures implements it too, through [`FnLayout`], so the simple
233/// case stays a call rather than a type.
234pub trait LayoutAlgorithm: Send + Sync + 'static {
235    /// How big this layout wants to be, given what the parent offers.
236    fn size_that_fits(&self, subviews: &Subviews<'_>, proposal: Proposal) -> (f64, f64);
237
238    /// Where each child goes, inside `bounds`.
239    fn place_subviews(&self, subviews: &Subviews<'_>, bounds: Rect, proposal: Proposal);
240}
241
242/// A [`LayoutAlgorithm`] made of two closures.
243///
244/// [`layout`] builds one of these. It exists as a named type rather than a
245/// pair of boxes so that the trait has something to be implemented *on* — and
246/// so a caller who wants to keep the layout around can name its type.
247pub struct FnLayout<S, P> {
248    size: S,
249    place: P,
250}
251
252impl<S, P> LayoutAlgorithm for FnLayout<S, P>
253where
254    S: Fn(&Subviews<'_>, Proposal) -> (f64, f64) + Send + Sync + 'static,
255    P: Fn(&Subviews<'_>, Rect, Proposal) + Send + Sync + 'static,
256{
257    fn size_that_fits(&self, subviews: &Subviews<'_>, proposal: Proposal) -> (f64, f64) {
258        (self.size)(subviews, proposal)
259    }
260
261    fn place_subviews(&self, subviews: &Subviews<'_>, bounds: Rect, proposal: Proposal) {
262        (self.place)(subviews, bounds, proposal)
263    }
264}
265
266fn registry() -> &'static Mutex<Vec<&'static dyn LayoutAlgorithm>> {
267    static R: OnceLock<Mutex<Vec<&'static dyn LayoutAlgorithm>>> = OnceLock::new();
268    R.get_or_init(|| Mutex::new(Vec::new()))
269}
270
271/// A layout algorithm, ready to be given children.
272///
273/// The value SwiftUI stores is an index into a Rust-side table, not the
274/// closures themselves: SwiftUI copies a `Layout` value around freely, and a
275/// `Box<dyn Fn>` is not something it can copy. An index is one word and
276/// trivially copyable, which is what its value witness needs it to be.
277pub struct CustomLayout {
278    index: i64,
279}
280
281/// Build a layout from a sizing pass and a placement pass.
282///
283/// The short form of [`register`]. For a layout with parameters, implement
284/// [`LayoutAlgorithm`] on a type and register that instead.
285pub fn layout(
286    size: impl Fn(&Subviews<'_>, Proposal) -> (f64, f64) + Send + Sync + 'static,
287    place: impl Fn(&Subviews<'_>, Rect, Proposal) + Send + Sync + 'static,
288) -> CustomLayout {
289    register(FnLayout { size, place })
290}
291
292/// Register any [`LayoutAlgorithm`], returning the handle SwiftUI stores.
293///
294/// The algorithm is leaked: SwiftUI keeps the `Layout` value for as long as
295/// the view exists, and the index inside it has to stay valid that long.
296pub fn register<L: LayoutAlgorithm>(algorithm: L) -> CustomLayout {
297    let leaked: &'static dyn LayoutAlgorithm = Box::leak(Box::new(algorithm));
298    let mut r = registry().lock().expect("layout registry");
299    r.push(leaked);
300    CustomLayout { index: r.len() as i64 - 1 }
301}
302
303/// The algorithm a `Self` value names.
304///
305/// # Safety
306/// `this` must point at a layout value this module wrote.
307unsafe fn algorithm_at(this: *const u8) -> &'static dyn LayoutAlgorithm {
308    let index = unsafe { *(this as *const i64) } as usize;
309    let r = registry().lock().expect("layout registry");
310    r.get(index).copied().expect("layout index out of range")
311}
312
313// ── The witnesses SwiftUI calls ──────────────────────────────────────────
314
315// `self` arrives in **x20** — the witness-method convention — and no Rust
316// signature can name it. Reading it back with an inline `asm!` at the top of the
317// function body is the obvious route and a wrong one: x20 is callee-saved, so the
318// compiler may spill it and reuse it in its own prologue, and by the time the
319// `asm!` runs it holds whatever the register allocator put there. That is exactly
320// what happened — at `opt-level=3` the two witnesses below read a clobbered x20
321// and faulted dereferencing null, while at `opt-level=0` the prologue happened
322// not to touch it and both worked. A release-only fault from code that looked
323// right in a debugger.
324//
325// The fix is the same as every other witness in this crate: a thunk moves x20 into
326// an argument register *before* any Rust code runs, and the handler takes it as an
327// ordinary parameter. Both witnesses use six integer argument registers, so x6 is
328// free and `this` becomes the seventh.
329core::arch::global_asm!(
330    ".p2align 2",
331    ".globl _rswift_layout_size_thunk",
332    "_rswift_layout_size_thunk:",
333    "mov x6, x20",
334    "b _rswift_layout_size_that_fits",
335    ".p2align 2",
336    ".globl _rswift_layout_place_thunk",
337    "_rswift_layout_place_thunk:",
338    "mov x6, x20",
339    "b _rswift_layout_place_subviews",
340);
341
342unsafe extern "C" {
343    #[link_name = "rswift_layout_size_thunk"]
344    fn size_that_fits_thunk();
345    #[link_name = "rswift_layout_place_thunk"]
346    fn place_subviews_thunk();
347}
348
349/// `makeCache(subviews:) -> Cache`, with `Cache == Void`.
350///
351/// Nothing to write: `Void` is zero bytes, so the indirect result is left
352/// alone. The requirement still has to be present — it has no default.
353///
354/// # Safety
355/// Called by SwiftUI under the witness-method convention.
356unsafe extern "C" fn make_cache() {}
357
358/// `CGSize`, laid out so the ABI returns it in d0/d1.
359///
360/// A Rust tuple is not `repr(C)` and so is not a homogeneous float aggregate;
361/// `CGSize` is, and comes back in the two float registers rather than through
362/// memory.
363#[repr(C)]
364struct Size {
365    width: f64,
366    height: f64,
367}
368
369/// `sizeThatFits(proposal:subviews:cache:) -> CGSize`.
370///
371/// # Safety
372/// Reached through `rswift_layout_size_thunk`: the proposal in x0–x3, the
373/// subviews in x4, the cache in x5, and `self` moved from x20 into x6.
374#[unsafe(no_mangle)]
375unsafe extern "C" fn rswift_layout_size_that_fits(
376    w: u64,
377    wt: u64,
378    h: u64,
379    ht: u64,
380    subviews: *mut c_void,
381    _cache: *mut c_void,
382    this: *const u8,
383) -> Size {
384    let algorithm = unsafe { algorithm_at(this) };
385    let subviews = Subviews { raw: subviews, marker: core::marker::PhantomData };
386    let proposal = Proposal {
387        width: (wt == 0).then(|| f64::from_bits(w)),
388        height: (ht == 0).then(|| f64::from_bits(h)),
389    };
390    let (w, h) = algorithm.size_that_fits(&subviews, proposal);
391    Size { width: w, height: h }
392}
393
394/// `placeSubviews(in:proposal:subviews:cache:)`.
395///
396/// # Safety
397/// As [`rswift_layout_size_that_fits`], with the bounds in d0–d3 — four ordinary
398/// float arguments, which AAPCS already puts where the witness convention wants
399/// them, so naming them as parameters is enough.
400#[unsafe(no_mangle)]
401unsafe extern "C" fn rswift_layout_place_subviews(
402    bx: f64,
403    by: f64,
404    bw: f64,
405    bh: f64,
406    w: u64,
407    wt: u64,
408    h: u64,
409    ht: u64,
410    subviews: *mut c_void,
411    _cache: *mut c_void,
412    this: *const u8,
413) {
414    let algorithm = unsafe { algorithm_at(this) };
415    let subviews = Subviews { raw: subviews, marker: core::marker::PhantomData };
416    let proposal = Proposal {
417        width: (wt == 0).then(|| f64::from_bits(w)),
418        height: (ht == 0).then(|| f64::from_bits(h)),
419    };
420    algorithm.place_subviews(&subviews, Rect { x: bx, y: by, width: bw, height: bh }, proposal);
421}
422
423/// The synthesised type a layout value *is*: one `Int`, the algorithm's index.
424///
425/// Returns its nominal descriptor and its metadata.
426fn carrier() -> (*const c_void, *const c_void) {
427    static C: OnceLock<(usize, usize)> = OnceLock::new();
428    let (d, m) = *C.get_or_init(|| unsafe {
429        let (d, m) = crate::state::emit_int_carrier(b"RustLayout\0", b"6rswift10RustLayoutV\0");
430        (d as usize, m as usize)
431    });
432    (d as *const c_void, m as *const c_void)
433}
434
435/// The `Layout` conformance, built once.
436fn conformance() -> &'static crate::conformance::Conformance {
437    use crate::conformance::{synthesize, Protocol, Witness};
438    static C: OnceLock<crate::conformance::Conformance> = OnceLock::new();
439    C.get_or_init(|| unsafe {
440        let protocol = Protocol::from_symbol(c"$s7SwiftUI6LayoutMp");
441        assert_eq!(protocol.requirement_count(), 11, "Layout changed shape");
442        synthesize(
443            &protocol,
444            // A type minted for the purpose, so `_LayoutRoot<…>` names
445            // something. Borrowing `Spacer` worked and rendered identically,
446            // and left the Swift type reading `_LayoutRoot<SwiftUI.Spacer>`
447            // where source gives `_LayoutRoot<MyLayout>`.
448            carrier().0,
449            &[
450                Witness::Metadata(resolve::hstack_layout_animatable_wt()),
451                Witness::Default,
452                Witness::Default,
453                Witness::Function(make_cache as *const c_void),
454                Witness::Default,
455                Witness::Default,
456                Witness::Function(size_that_fits_thunk as *const c_void),
457                Witness::Function(place_subviews_thunk as *const c_void),
458                Witness::Default,
459                Witness::Default,
460                Witness::Default,
461            ],
462        )
463    })
464}
465
466/// `MyLayout { … }` — the layout, given children.
467pub fn custom_layout(layout: &CustomLayout, children: &[ViewHandle]) -> ViewHandle {
468    unsafe {
469        let conformance = conformance();
470        let (_, carrier_meta) = carrier();
471
472        // The layout value: the algorithm's index, and nothing else.
473        let mut value = vec![0u8; abi::type_bytes(carrier_meta)];
474        value[..8].copy_from_slice(&layout.index.to_le_bytes());
475
476        // `_LayoutRoot<L>` — one parameter and its `Layout` conformance.
477        let root_meta = abi::generic_metadata2(
478            resolve::layout_root_ma(),
479            carrier_meta,
480            conformance.witness_table(),
481        );
482        assert!(!root_meta.is_null(), "_LayoutRoot metadata");
483        let mut root = vec![0u8; abi::type_bytes(root_meta)];
484        core::arch::asm!("blr {f}", f = in(reg) resolve::layout_root_init(),
485            in("x0") value.as_mut_ptr(),
486            in("x1") carrier_meta,
487            in("x2") conformance.witness_table(),
488            in("x8") root.as_mut_ptr(),
489            lateout("x3") _, lateout("x4") _, lateout("x5") _, lateout("x6") _,
490            lateout("x7") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
491            lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
492            lateout("x16") _, lateout("x17") _, lateout("lr") _, lateout("x20") _,
493            lateout("x21") _, clobber_abi("C"),
494        );
495
496        // The children, as a `TupleView` of their own types.
497        // The children's own types, the way every container in this crate
498        // builds them: one child is itself, several are a `TupleView`.
499        let (content_meta, _content_wt, offsets) = if children.len() == 1 {
500            (children[0].meta, children[0].wt, vec![0usize])
501        } else {
502            let elements: Vec<*const c_void> = children.iter().map(|c| c.meta).collect();
503            let tuple = abi::tuple_metadata(&elements);
504            assert!(!tuple.is_null(), "tuple metadata instantiation failed");
505            let offsets = abi::tuple_element_offsets(tuple, children.len());
506            let m = abi::generic_metadata1(resolve::tupleview_ma(), tuple);
507            assert!(!m.is_null(), "TupleView metadata instantiation failed");
508            let w = abi::get_view_wt(m);
509            assert!(!w.is_null(), "TupleView !: View");
510            (m, w, offsets)
511        };
512        let kids: Vec<super::TupleChild> = children
513            .iter()
514            .zip(offsets.iter())
515            .map(|(c, &offset)| super::TupleChild {
516                value: c.value_ptr(),
517                meta: c.meta,
518                offset,
519            })
520            .collect();
521        let payload = super::TuplePayload { children: kids.as_ptr(), count: kids.len() };
522        let mut ctx = abi::ClosureCtx::new(
523            super::emit_tuple,
524            &payload as *const super::TuplePayload as *mut c_void,
525        );
526
527        let root_wt = super::conformance_wt(root_meta, resolve::variadic_root_proto());
528
529        // `_VariadicView.Tree<_LayoutRoot<L>, Content>` — the type the inlined
530        // `callAsFunction` builds.
531        let meta = abi::generic_metadata_from_descriptor(
532            resolve::variadic_tree_desc(),
533            &[root_meta, content_meta, root_wt],
534        );
535        assert!(!meta.is_null(), "_VariadicView.Tree metadata");
536        let wt = abi::get_view_wt(meta);
537        assert!(!wt.is_null(), "_VariadicView.Tree !: View");
538
539        let mut result = vec![0u8; abi::type_bytes(meta)];
540        // `init(_:content:)`: x0 = @in root, x1/x2 = the content closure,
541        // x3 = Root.Type, x4 = Content.Type, x5 = Root: _VariadicView_Root WT.
542        core::arch::asm!("blr {f}", f = in(reg) resolve::variadic_tree_init(),
543            in("x0") root.as_mut_ptr(),
544            in("x1") abi::swift_closure_thunk as *const c_void,
545            in("x2") &mut ctx as *mut abi::ClosureCtx,
546            in("x3") root_meta,
547            in("x4") content_meta,
548            in("x5") root_wt,
549            in("x8") result.as_mut_ptr(),
550            lateout("x6") _, lateout("x7") _,
551            lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
552            lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
553            lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
554            clobber_abi("C"),
555        );
556        wrap_owned(result, meta, wt)
557    }
558}