use super::*;
use crate::abi;
use crate::resolve;
use core::ffi::c_void;
use crate::mangle::{self, swift_type};
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum StackKind {
V,
H,
Z,
}
pub fn vstack(children: &[ViewHandle]) -> ViewHandle {
stack_impl(children, StackKind::V)
}
pub fn hstack(children: &[ViewHandle]) -> ViewHandle {
stack_impl(children, StackKind::H)
}
pub fn zstack(children: &[ViewHandle]) -> ViewHandle {
stack_impl(children, StackKind::Z)
}
pub fn zstack_aligned(children: &[ViewHandle], alignment: super::Alignment) -> ViewHandle {
match children.len() {
0 => empty_view(),
_ => unsafe { build_stack_aligned(children, StackKind::Z, None, alignment) },
}
}
pub(crate) fn stack_impl(children: &[ViewHandle], axis: StackKind) -> ViewHandle {
stack_spaced(children, axis, None)
}
pub(crate) fn stack_spaced(children: &[ViewHandle], axis: StackKind, spacing: Option<f64>) -> ViewHandle {
match children.len() {
0 => empty_view(),
1 if spacing.is_none() => children[0].clone(),
_ => unsafe { build_stack(children, axis, spacing) },
}
}
pub fn vstack_aligned(
children: &[ViewHandle],
alignment: super::HorizontalAlignment,
) -> ViewHandle {
vstack_aligned_spacing(children, alignment, None)
}
pub fn vstack_aligned_spacing(
children: &[ViewHandle],
alignment: super::HorizontalAlignment,
spacing: Option<f64>,
) -> ViewHandle {
unsafe {
build_stack_aligned_h(
children,
StackKind::V,
spacing,
super::Alignment::Center,
Some(alignment),
None,
)
}
}
pub fn hstack_aligned(
children: &[ViewHandle],
alignment: super::VerticalAlignment,
) -> ViewHandle {
hstack_aligned_spacing(children, alignment, None)
}
pub fn hstack_aligned_spacing(
children: &[ViewHandle],
alignment: super::VerticalAlignment,
spacing: Option<f64>,
) -> ViewHandle {
unsafe {
build_stack_aligned_h(
children,
StackKind::H,
spacing,
super::Alignment::Center,
None,
Some(alignment),
)
}
}
pub fn vstack_spacing(children: &[ViewHandle], spacing: f64) -> ViewHandle {
stack_spaced(children, StackKind::V, Some(spacing))
}
pub fn hstack_spacing(children: &[ViewHandle], spacing: f64) -> ViewHandle {
stack_spaced(children, StackKind::H, Some(spacing))
}
#[repr(C)]
pub(crate) struct TupleChild {
pub(crate) value: *const c_void,
pub(crate) meta: *const c_void,
pub(crate) offset: usize,
}
#[repr(C)]
pub(crate) struct TuplePayload {
pub(crate) children: *const TupleChild,
pub(crate) count: usize,
}
pub(crate) unsafe extern "C" fn emit_tuple(out: *mut u8, payload: *mut c_void) {
let p = unsafe { &*(payload as *const TuplePayload) };
for i in 0..p.count {
let c = unsafe { &*p.children.add(i) };
let vwt = unsafe { &*swift_runtime_sys::SwiftABI::get_value_witness_table(c.meta) };
if vwt.size > 0 {
unsafe {
(vwt.initialize_with_copy)(
out.add(c.offset) as *mut c_void,
c.value as *mut c_void,
c.meta,
);
}
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum Content {
Concrete,
Erased,
}
pub(crate) struct Child {
handle: ViewHandle,
word: u64,
kind: Content,
}
impl Child {
pub(crate) fn new(view: &ViewHandle, kind: Content) -> Self {
Child {
handle: view.clone(),
word: if kind == Content::Erased { view.to_anyview_word() } else { 0 },
kind,
}
}
pub(crate) fn element(&self) -> [TupleChild; 1] {
match self.kind {
Content::Concrete => [TupleChild {
value: self.handle.value_ptr(),
meta: self.handle.meta,
offset: 0,
}],
Content::Erased => [TupleChild {
value: &self.word as *const u64 as *const c_void,
meta: resolve::anyview_meta(),
offset: 0,
}],
}
}
pub(crate) fn leak(view: &ViewHandle, kind: Content) -> *mut c_void {
let kept: &'static Child = Box::leak(Box::new(Child::new(view, kind)));
let kids = Box::leak(Box::new(kept.element()));
let payload = Box::leak(Box::new(TuplePayload { children: kids.as_ptr(), count: 1 }));
payload as *mut TuplePayload as *mut c_void
}
}
pub(crate) unsafe fn static_getter_f64(getter: *const c_void, meta: *const c_void) -> f64 {
unsafe { abi::call_f64(getter, [0; 8], [0.0; 4], meta as u64) }
}
pub(crate) unsafe fn static_getter(getter: *const c_void, meta: *const c_void) -> (u64, u64) {
let r = unsafe { abi::static_regs(getter, meta) };
(r[0], r[1])
}
pub(crate) unsafe fn tuple_content(
children: &[ViewHandle],
) -> (*const c_void, *const c_void, Vec<usize>) {
assert!(!children.is_empty(), "a `@ViewBuilder` body with no children is `EmptyView`");
if children.len() == 1 {
return (children[0].meta, children[0].wt, vec![0usize]);
}
unsafe {
let elements: Vec<*const c_void> = children.iter().map(|c| c.meta).collect();
let tuple_meta = abi::tuple_metadata(&elements);
assert!(!tuple_meta.is_null(), "tuple metadata instantiation failed");
let offsets = abi::tuple_element_offsets(tuple_meta, children.len());
let m = abi::generic_metadata1(resolve::tupleview_ma(), tuple_meta);
assert!(!m.is_null(), "TupleView metadata instantiation failed");
let w = abi::get_view_wt(m);
assert!(!w.is_null(), "{} !: View", crate::diag::type_name(m));
(m, w, offsets)
}
}
pub(crate) fn tuple_children(children: &[ViewHandle], offsets: &[usize]) -> Vec<TupleChild> {
children
.iter()
.zip(offsets)
.map(|(c, &offset)| TupleChild { value: c.value_ptr(), meta: c.meta, offset })
.collect()
}
pub(crate) unsafe fn build_stack(
children: &[ViewHandle],
axis: StackKind,
spacing: Option<f64>,
) -> ViewHandle {
unsafe { build_stack_aligned(children, axis, spacing, super::Alignment::Center) }
}
pub(crate) unsafe fn build_stack_aligned(
children: &[ViewHandle],
axis: StackKind,
spacing: Option<f64>,
alignment: super::Alignment,
) -> ViewHandle {
unsafe { build_stack_aligned_h(children, axis, spacing, alignment, None, None) }
}
pub(crate) unsafe fn build_stack_aligned_h(
children: &[ViewHandle],
axis: StackKind,
spacing: Option<f64>,
alignment: super::Alignment,
horizontal: Option<super::HorizontalAlignment>,
vertical: Option<super::VerticalAlignment>,
) -> ViewHandle {
let (content_meta, content_wt, offsets) = unsafe { tuple_content(children) };
let stack_ma = match axis {
StackKind::V => resolve::vstack_ma(),
StackKind::H => resolve::hstack_ma(),
StackKind::Z => resolve::zstack_ma(),
};
let stack_meta = unsafe { abi::generic_metadata2(stack_ma, content_meta, content_wt) };
assert!(!stack_meta.is_null(), "stack metadata instantiation failed");
let stack_wt = unsafe { abi::get_view_wt(stack_meta) };
assert!(!stack_wt.is_null(), "{} !: View", crate::diag::type_name(stack_meta));
let kids = tuple_children(children, &offsets);
let payload = TuplePayload { children: kids.as_ptr(), count: kids.len() };
let mut ctx = abi::ClosureCtx::new(emit_tuple, &payload as *const TuplePayload as *mut c_void);
let thunk = abi::swift_closure_thunk as *const c_void;
let mut result = vec![0u8; unsafe { abi::type_bytes(stack_meta) }];
match axis {
StackKind::V | StackKind::H => {
let (align, _) = unsafe {
match axis {
StackKind::V => static_getter(
horizontal.map_or_else(resolve::halign_center, |h| h.resolved()),
resolve::halign_meta(),
),
_ => static_getter(
vertical.map_or_else(resolve::valign_center, |v| v.resolved()),
resolve::valign_meta(),
),
}
};
let init = if axis == StackKind::V { resolve::vstack_init() } else { resolve::hstack_init() };
let (spacing_bits, spacing_tag) = match spacing {
Some(v) => (v.to_bits(), 0u64),
None => (0u64, 1u64),
};
unsafe {
core::arch::asm!("blr {f}", f = in(reg) init,
in("x0") align,
in("x1") spacing_bits,
in("x2") spacing_tag,
in("x3") thunk,
in("x4") &mut ctx as *mut abi::ClosureCtx,
in("x5") content_meta,
in("x6") content_wt,
in("x8") result.as_mut_ptr(),
lateout("x0") _, lateout("x1") _, lateout("x2") _, lateout("x3") _,
lateout("x4") _, lateout("x5") _, lateout("x6") _, lateout("x7") _,
lateout("x9") _, lateout("x10") _, lateout("x11") _,
lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
lateout("x16") _, lateout("x17") _, lateout("lr") _,
lateout("x20") _, lateout("x21") _,
clobber_abi("C"),
);
}
}
StackKind::Z => {
let [h, v] = unsafe { alignment.words() };
unsafe {
core::arch::asm!("blr {f}", f = in(reg) resolve::zstack_init(),
in("x0") h,
in("x1") v,
in("x2") thunk,
in("x3") &mut ctx as *mut abi::ClosureCtx,
in("x4") content_meta,
in("x5") content_wt,
in("x8") result.as_mut_ptr(),
lateout("x6") _, lateout("x7") _,
lateout("x9") _, lateout("x10") _, lateout("x11") _,
lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
lateout("x16") _, lateout("x17") _, lateout("lr") _,
lateout("x20") _, lateout("x21") _,
clobber_abi("C"),
);
}
}
}
unsafe { wrap_owned(result, stack_meta, stack_wt) }
}
mangle::result_types! {
pub(crate) LABEL_T = ty("Label").of(vec![ty("Text"), ty("Image")]);
}
pub fn label(title: &str, system_image: &str) -> ViewHandle {
unsafe {
let ss = abi::swift_string(title);
let s0 = u64::from_le_bytes(ss[..8].try_into().unwrap());
let s1 = u64::from_le_bytes(ss[8..].try_into().unwrap());
let (k0, k1, k2, k3) = localized_string_key(s0, s1);
let img = abi::swift_string(system_image);
let i0 = u64::from_le_bytes(img[..8].try_into().unwrap());
let i1 = u64::from_le_bytes(img[8..].try_into().unwrap());
let (meta, wt, size) = result_type(&LABEL_T);
let mut result = vec![0u8; size];
core::arch::asm!("blr {f}", f = in(reg) resolve::label_init(),
in("x0") k0, in("x1") k1, in("x2") k2, in("x3") k3,
in("x4") i0, in("x5") i1,
in("x8") result.as_mut_ptr(),
lateout("x6") _, lateout("x7") _,
lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
clobber_abi("C"),
);
wrap_owned(result, meta, wt)
}
}
pub(crate) fn lazy_stack_impl(children: &[ViewHandle], vertical: bool) -> ViewHandle {
lazy_stack_aligned_impl(children, vertical, None, None)
}
pub(crate) fn lazy_stack_aligned_impl(
children: &[ViewHandle],
vertical: bool,
halign: Option<super::HorizontalAlignment>,
valign: Option<super::VerticalAlignment>,
) -> ViewHandle {
if children.is_empty() {
return empty_view();
}
unsafe {
let (content_meta, content_wt, offsets) = if children.len() == 1 {
(children[0].meta, children[0].wt, vec![0usize])
} else {
let elements: Vec<*const c_void> = children.iter().map(|c| c.meta).collect();
let tuple_meta = abi::tuple_metadata(&elements);
let offsets = abi::tuple_element_offsets(tuple_meta, children.len());
let m = abi::generic_metadata1(resolve::tupleview_ma(), tuple_meta);
let w = abi::get_view_wt(m);
assert!(!w.is_null(), "TupleView !: View");
(m, w, offsets)
};
let ma = if vertical { resolve::lazy_vstack_ma() } else { resolve::lazy_hstack_ma() };
let stack_meta = abi::generic_metadata2(ma, content_meta, content_wt);
let stack_wt = abi::get_view_wt(stack_meta);
assert!(!stack_wt.is_null(), "lazy stack !: View");
let kids: Vec<TupleChild> = children
.iter()
.zip(&offsets)
.map(|(c, &offset)| TupleChild { value: c.value_ptr(), meta: c.meta, offset })
.collect();
let payload = TuplePayload { children: kids.as_ptr(), count: kids.len() };
let mut ctx =
abi::ClosureCtx::new(emit_tuple, &payload as *const TuplePayload as *mut c_void);
let (align, _) = if vertical {
static_getter(
halign.map_or_else(resolve::halign_center, |h| h.resolved()),
resolve::halign_meta(),
)
} else {
static_getter(
valign.map_or_else(resolve::valign_center, |v| v.resolved()),
resolve::valign_meta(),
)
};
let init = if vertical { resolve::lazy_vstack_init() } else { resolve::lazy_hstack_init() };
let pinned_meta = crate::type_by_name!(swift_type!(SwiftUI.PinnedScrollableViews).as_bytes());
assert!(!pinned_meta.is_null(), "PinnedScrollableViews metadata");
let mut pinned = vec![0u8; abi::type_bytes(pinned_meta)];
let mut result = vec![0u8; abi::type_bytes(stack_meta)];
core::arch::asm!("blr {f}", f = in(reg) init,
in("x0") align,
in("x1") 0u64,
in("x2") 1u64,
in("x3") pinned.as_mut_ptr(),
in("x4") abi::swift_closure_thunk as *const c_void,
in("x5") &mut ctx as *mut abi::ClosureCtx,
in("x6") content_meta,
in("x7") content_wt,
in("x8") result.as_mut_ptr(),
lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
clobber_abi("C"),
);
wrap_owned(result, stack_meta, stack_wt)
}
}
pub fn lazy_vstack(children: &[ViewHandle]) -> ViewHandle {
lazy_stack_impl(children, true)
}
pub fn lazy_vstack_aligned(
children: &[ViewHandle],
alignment: super::HorizontalAlignment,
) -> ViewHandle {
lazy_stack_aligned_impl(children, true, Some(alignment), None)
}
pub fn lazy_hstack(children: &[ViewHandle]) -> ViewHandle {
lazy_stack_impl(children, false)
}
pub fn lazy_hstack_aligned(
children: &[ViewHandle],
alignment: super::VerticalAlignment,
) -> ViewHandle {
lazy_stack_aligned_impl(children, false, None, Some(alignment))
}
pub fn tuple_view(children: &[ViewHandle]) -> ViewHandle {
assert!(!children.is_empty(), "a TupleView needs at least one child");
if children.len() == 1 {
return children[0].clone();
}
unsafe {
let elements: Vec<*const c_void> = children.iter().map(|c| c.meta).collect();
let tuple_meta = abi::tuple_metadata(&elements);
assert!(!tuple_meta.is_null(), "tuple metadata instantiation failed");
let offsets = abi::tuple_element_offsets(tuple_meta, children.len());
let meta = abi::generic_metadata1(resolve::tupleview_ma(), tuple_meta);
assert!(!meta.is_null(), "TupleView metadata instantiation failed");
let wt = abi::get_view_wt(meta);
assert!(!wt.is_null(), "{} !: View", crate::diag::type_name(meta));
let kids: Vec<TupleChild> = children
.iter()
.zip(&offsets)
.map(|(c, &offset)| TupleChild { value: c.value_ptr(), meta: c.meta, offset })
.collect();
let payload = TuplePayload { children: kids.as_ptr(), count: kids.len() };
let mut out = vec![0u8; abi::type_bytes(meta)];
emit_tuple(out.as_mut_ptr(), &payload as *const TuplePayload as *mut c_void);
super::wrap_owned(out, meta, wt)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum GridTrack {
Fixed(f64),
}
unsafe fn grid_item(track: GridTrack) -> Vec<u8> {
unsafe {
let size_meta = abi::metadata0(resolve::grid_item_size_ma());
assert!(!size_meta.is_null(), "GridItem.Size metadata");
let mut size = vec![0u8; abi::type_bytes(size_meta)];
let GridTrack::Fixed(points) = track;
size[..8].copy_from_slice(&points.to_le_bytes());
let tag = abi::enum_case_index(size_meta, "fixed")
.expect("GridItem.Size has a `fixed` case");
abi::inject_enum_tag(&mut size, size_meta, tag as u32);
let item_meta = abi::metadata0(resolve::grid_item_ma());
assert!(!item_meta.is_null(), "GridItem metadata");
let mut item = vec![0u8; abi::type_bytes(item_meta)];
core::arch::asm!("blr {f}", f = in(reg) resolve::grid_item_init(),
in("x0") size.as_mut_ptr(),
in("x1") 0u64, in("x2") 1u64,
in("x3") 0u64, in("x4") 0u64, in("x5") 1u64,
in("x8") item.as_mut_ptr(),
lateout("x6") _, lateout("x7") _,
lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
clobber_abi("C"),
);
item
}
}
unsafe fn grid_item_array(tracks: &[GridTrack]) -> u64 {
unsafe {
let item_meta = abi::metadata0(resolve::grid_item_ma());
let stride = abi::type_bytes(item_meta);
let (array, buf): (u64, u64);
core::arch::asm!("blr {f}", f = in(reg) resolve::array_alloc_uninit(),
in("x0") tracks.len() as u64,
in("x1") item_meta,
lateout("x0") array, lateout("x1") buf,
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("x20") _, lateout("x21") _, clobber_abi("C"),
);
for (i, t) in tracks.iter().enumerate() {
let item = grid_item(*t);
core::ptr::copy_nonoverlapping(
item.as_ptr(),
(buf as *mut u8).add(i * stride),
item.len(),
);
}
array
}
}
pub fn lazy_vgrid(tracks: &[GridTrack], children: &[ViewHandle]) -> ViewHandle {
unsafe { lazy_grid(tracks, children, true) }
}
pub fn lazy_hgrid(tracks: &[GridTrack], children: &[ViewHandle]) -> ViewHandle {
unsafe { lazy_grid(tracks, children, false) }
}
unsafe fn lazy_grid(tracks: &[GridTrack], children: &[ViewHandle], vertical: bool) -> ViewHandle {
unsafe {
let (content_meta, content_wt, offsets) = if children.len() == 1 {
(children[0].meta, children[0].wt, vec![0usize])
} else {
let elements: Vec<*const c_void> = children.iter().map(|c| c.meta).collect();
let tuple_meta = abi::tuple_metadata(&elements);
assert!(!tuple_meta.is_null(), "tuple metadata instantiation failed");
let offsets = abi::tuple_element_offsets(tuple_meta, children.len());
let m = abi::generic_metadata1(resolve::tupleview_ma(), tuple_meta);
assert!(!m.is_null(), "TupleView metadata instantiation failed");
let w = abi::get_view_wt(m);
assert!(!w.is_null(), "{} !: View", crate::diag::type_name(m));
(m, w, offsets)
};
let ma = if vertical { resolve::lazy_vgrid_ma() } else { resolve::lazy_hgrid_ma() };
let meta = abi::generic_metadata2(ma, content_meta, content_wt);
assert!(!meta.is_null(), "lazy grid metadata");
let wt = abi::get_view_wt(meta);
assert!(!wt.is_null(), "lazy grid !: View");
let array = grid_item_array(tracks);
let (align, _) = if vertical {
static_getter(resolve::halign_center(), resolve::halign_meta())
} else {
static_getter(resolve::valign_center(), resolve::valign_meta())
};
let kids: Vec<TupleChild> = children
.iter()
.zip(&offsets)
.map(|(c, &offset)| TupleChild { value: c.value_ptr(), meta: c.meta, offset })
.collect();
let payload = TuplePayload { children: kids.as_ptr(), count: kids.len() };
let mut ctx =
abi::ClosureCtx::new(emit_tuple, &payload as *const TuplePayload as *mut c_void);
let pinned_meta = crate::type_by_name!(swift_type!(SwiftUI.PinnedScrollableViews).as_bytes());
assert!(!pinned_meta.is_null(), "PinnedScrollableViews metadata");
let mut pinned = vec![0u8; abi::type_bytes(pinned_meta)];
let mut result = vec![0u8; abi::type_bytes(meta)];
core::arch::asm!(
"sub sp, sp, #16",
"str {vw}, [sp]",
"blr {f}",
"add sp, sp, #16",
f = in(reg) if vertical { resolve::lazy_vgrid_init() } else { resolve::lazy_hgrid_init() },
vw = in(reg) content_wt,
in("x0") array,
in("x1") align,
in("x2") 0u64, in("x3") 1u64,
in("x4") pinned.as_mut_ptr(),
in("x5") abi::swift_closure_thunk as *const c_void,
in("x6") &mut ctx as *mut abi::ClosureCtx,
in("x7") content_meta,
in("x8") result.as_mut_ptr(),
lateout("x9") _, lateout("x10") _, lateout("x11") _, lateout("x12") _,
lateout("x13") _, lateout("x14") _, lateout("x15") _, lateout("x16") _,
lateout("x17") _, lateout("lr") _, lateout("x20") _, lateout("x21") _,
clobber_abi("C"),
);
wrap_owned(result, meta, wt)
}
}