use crate::abi;
use crate::resolve;
use core::ffi::c_void;
pub struct ViewHandle {
bytes: Vec<u8>,
pub(crate) meta: *const c_void,
pub(crate) wt: *const c_void,
}
impl ViewHandle {
pub(crate) unsafe fn wrap(
value_ptr: *const c_void,
meta: *const c_void,
wt: *const c_void,
) -> Self {
unsafe {
debug_assert!(!meta.is_null() && !wt.is_null(), "view needs a type and a witness");
let n = abi::type_bytes(meta);
let copy = abi::type_size(meta);
let mut bytes = vec![0u8; n.max(1)];
if copy > 0 {
core::ptr::copy_nonoverlapping(value_ptr as *const u8, bytes.as_mut_ptr(), copy);
}
Self { bytes, meta, wt }
}
}
pub(crate) unsafe fn adopt(
mut bytes: Vec<u8>,
meta: *const c_void,
wt: *const c_void,
) -> Self {
unsafe {
debug_assert!(!meta.is_null() && !wt.is_null(), "view needs a type and a witness");
debug_assert!(bytes.len() >= abi::type_size(meta), "buffer smaller than its type");
if bytes.is_empty() {
bytes.push(0);
}
Self { bytes, meta, wt }
}
}
pub(crate) fn from_anyview(anyview: u64) -> Self {
debug_assert!(anyview != 0, "AnyView pointer is null");
Self {
bytes: anyview.to_le_bytes().to_vec(),
meta: resolve::anyview_meta(),
wt: resolve::anyview_wt(),
}
}
pub(crate) fn value_ptr(&self) -> *const c_void {
self.bytes.as_ptr() as *const c_void
}
pub(crate) fn into_anyview_word(self) -> u64 {
unsafe {
if self.meta == resolve::anyview_meta() {
let w = u64::from_le_bytes(self.bytes[..8].try_into().expect("AnyView word"));
core::mem::forget(self);
return w;
}
let w = abi::anyview_wrap(self.value_ptr(), self.meta, self.wt);
core::mem::forget(self);
w
}
}
pub(crate) fn to_anyview_word(&self) -> u64 {
self.clone().into_anyview_word()
}
pub fn into_anyview(self) -> Self {
if self.meta == resolve::anyview_meta() {
return self;
}
Self::from_anyview(self.into_anyview_word())
}
pub fn as_ptr(&self) -> *mut c_void {
self.to_anyview_word() as *mut c_void
}
}
impl Clone for ViewHandle {
fn clone(&self) -> Self {
unsafe {
let vwt = &*swift_runtime_sys::SwiftABI::get_value_witness_table(self.meta);
let mut bytes = vec![0u8; self.bytes.len()];
if vwt.size > 0 {
(vwt.initialize_with_copy)(
bytes.as_mut_ptr() as *mut c_void,
self.bytes.as_ptr() as *mut c_void,
self.meta,
);
}
Self { bytes, meta: self.meta, wt: self.wt }
}
}
}
impl Drop for ViewHandle {
fn drop(&mut self) {
unsafe {
let vwt = &*swift_runtime_sys::SwiftABI::get_value_witness_table(self.meta);
if vwt.size > 0 {
(vwt.destroy)(self.bytes.as_mut_ptr() as *mut c_void, self.meta);
}
}
}
}
pub fn dynamic_type_name(view: &ViewHandle) -> String {
crate::diag::type_name(view.meta)
}
pub fn dump_value(view: &ViewHandle) -> String {
dump_value_to_depth(view, DUMP_MAX_DEPTH)
}
pub fn dump_value_to_depth(view: &ViewHandle, max_depth: u64) -> String {
let out = unsafe {
crate::diag::dump_any(view.value_ptr() as *mut c_void, view.meta, max_depth)
};
normalise_dump(&out)
}
const DUMP_MAX_DEPTH: u64 = 12;
pub fn normalise_dump(s: &str) -> String {
let bytes = s.as_bytes();
let mut out: Vec<u8> = Vec::with_capacity(bytes.len());
let mut i = 0;
while i < bytes.len() {
if bytes[i..].starts_with(b"0x") {
out.extend_from_slice(b"0x*");
i += 2;
while i < bytes.len() && (bytes[i] as char).is_ascii_hexdigit() {
i += 1;
}
} else if bytes[i..].starts_with(b"pointerValue: ") {
out.extend_from_slice(b"pointerValue: *");
i += "pointerValue: ".len();
while i < bytes.len() && bytes[i].is_ascii_digit() {
i += 1;
}
} else if let Some(prefix) = [
&b"Namespace.ID(id: "[..],
&b"namespace: ID(id: "[..],
]
.into_iter()
.find(|p| bytes[i..].starts_with(p))
{
out.extend_from_slice(prefix);
i += prefix.len();
let start = i;
while i < bytes.len() && bytes[i].is_ascii_digit() {
i += 1;
}
let allocated = &bytes[start..i] != b"0";
out.extend_from_slice(if allocated { b"<allocated>" } else { b"0" });
} else if bytes[i..].starts_with(b"Variable(") {
out.extend_from_slice(b"Variable(*");
i += "Variable(".len();
while i < bytes.len() && bytes[i] != b')' {
i += 1;
}
} else if bytes[i..].starts_with(b"View.task @ ") {
out.extend_from_slice(b"View.task @ *");
i += "View.task @ ".len();
while i < bytes.len() && bytes[i] != b'"' {
i += 1;
}
} else if bytes[i..].starts_with(b"(unknown context at $") {
out.extend_from_slice(b"(unknown context)");
i += "(unknown context at $".len();
while i < bytes.len() && bytes[i] != b')' {
i += 1;
}
i += 1;
} else if bytes[i..].starts_with(b" #") {
out.extend_from_slice(b" #");
i += 2;
while i < bytes.len() && bytes[i].is_ascii_digit() {
i += 1;
}
} else {
out.push(bytes[i]);
i += 1;
}
}
let once = String::from_utf8_lossy(&out).into_owned();
let mut lines: Vec<String> = Vec::new();
let mut in_rope_version = false;
let mut in_variable_id = false;
let mut in_namespace_id = false;
let mut in_attributed_guts = false;
let mut skipping: Option<usize> = None;
for line in once.lines() {
let t = line.trim_start();
let indent = line.len() - t.len();
if let Some(depth) = skipping {
let glyph = t.starts_with('-') || t.starts_with('\u{25bf}') || t.starts_with('\u{25b9}');
if indent > depth || !glyph {
continue;
}
skipping = None;
}
if t.starts_with("- immutable: ") && t.contains("CharacterSet") {
lines.push(format!("{}- immutable: *", &line[..indent]));
skipping = Some(indent);
continue;
}
if in_variable_id && t.starts_with("- id: ") {
lines.push(format!("{}- id: *", &line[..indent]));
in_variable_id = false;
continue;
}
if in_namespace_id && t.starts_with("- id: ") {
let allocated = t["- id: ".len()..].trim() != "0";
let shown = if allocated { "<allocated>" } else { "0" };
lines.push(format!("{}- id: {shown}", &line[..indent]));
in_namespace_id = false;
continue;
}
if in_rope_version && t.starts_with("- _value: ") {
lines.push(format!("{}- _value: *", &line[..line.len() - t.len()]));
} else if in_attributed_guts && t.starts_with("- version: ") {
lines.push(format!("{}- version: *", &line[..line.len() - t.len()]));
} else {
lines.push(line.to_string());
}
in_rope_version = t.ends_with("CollectionsInternal._RopeVersion");
if t.ends_with("VariableID") {
in_variable_id = true;
}
if t.ends_with("SwiftUI.Namespace.ID") {
in_namespace_id = true;
}
if t.ends_with("Foundation.AttributedString.Guts #") {
in_attributed_guts = true;
} else if t.starts_with("- trackedRanges:") {
in_attributed_guts = false;
}
}
let mut joined = lines.join("\n");
if once.ends_with('\n') {
joined.push('\n');
}
joined
}
pub(crate) unsafe fn text_value(s: &str) -> [u64; 4] {
unsafe {
let ss = abi::swift_string(s);
let s0 = u64::from_le_bytes(ss[..8].try_into().expect("string lo"));
let s1 = u64::from_le_bytes(ss[8..].try_into().expect("string hi"));
let (k0, k1, k2, k3) = localized_string_key(s0, s1);
let (t0, t1, t2, t3): (u64, u64, u64, u64);
core::arch::asm!(
"sub sp, sp, #32",
"stp xzr, xzr, [sp]",
"stp xzr, xzr, [sp, #16]",
"blr {f}",
"add sp, sp, #32",
f = in(reg) resolve::text_lsk_init(),
inout("x0") k0 => t0, inout("x1") k1 => t1,
inout("x2") k2 => t2, inout("x3") k3 => t3,
in("x4") 0u64, in("x5") 0u64, in("x6") 0u64, in("x7") 0u64,
in("x20") resolve::text_meta(),
lateout("x8") _, lateout("x9") _, lateout("x10") _, lateout("x11") _,
lateout("x12") _, lateout("x13") _, lateout("x14") _, lateout("x15") _,
lateout("x16") _, lateout("x17") _, lateout("lr") _, clobber_abi("C"),
);
[t0, t1, t2, t3]
}
}
unsafe fn modifier_lsk(f: *const c_void, o: *const c_void, v: &ViewHandle, s: &str) -> ViewHandle {
unsafe {
let ss = abi::swift_string(s);
let s0 = u64::from_le_bytes(ss[..8].try_into().expect("string lo"));
let s1 = u64::from_le_bytes(ss[8..].try_into().expect("string hi"));
let (k0, k1, k2, k3) = localized_string_key(s0, s1);
opaque_modifier(f, o, v, &[k0, k1, k2, k3], [0.0; 4])
}
}
unsafe fn modifier_text(f: *const c_void, o: *const c_void, v: &ViewHandle, s: &str) -> ViewHandle {
unsafe {
let t = text_value(s);
opaque_modifier(f, o, v, &t, [0.0; 4])
}
}
unsafe fn modifier_opt_bool(
f: *const c_void,
o: *const c_void,
v: &ViewHandle,
value: Option<bool>,
) -> ViewHandle {
unsafe {
let mut buf = [0u8; 1];
if let Some(b) = value {
buf[0] = u8::from(b);
}
abi::store_optional_tag(&mut buf, resolve::bool_meta(), value.is_some());
opaque_modifier(f, o, v, &[u64::from(buf[0])], [0.0; 4])
}
}
unsafe fn modifier_opt_color(
f: *const c_void,
o: *const c_void,
v: &ViewHandle,
value: Option<crate::Color>,
) -> ViewHandle {
unsafe {
let word: u64 = match value {
Some(c) => color_value(c.r, c.g, c.b, c.a),
None => 0,
};
opaque_modifier(f, o, v, &[word], [0.0; 4])
}
}
unsafe fn binding_words(binding: &mut [u8]) -> [u64; 3] {
unsafe {
let p = binding.as_ptr();
[
(p as *const u64).read_unaligned(),
(p.add(8) as *const u64).read_unaligned(),
u64::from(*p.add(16)),
]
}
}
unsafe fn gesture_variant(
func: *const c_void,
oret: *const c_void,
view: &ViewHandle,
minimum_distance: f64,
action: impl Fn() + 'static,
) -> ViewHandle {
unsafe {
let g_meta = abi::metadata0(resolve::drag_gesture_ma());
let space_meta = abi::metadata0(resolve::local_space_ma());
let mut gesture = vec![0u8; abi::type_bytes(g_meta)];
let mut space = [0u8; 8];
core::arch::asm!("blr {f}", f = in(reg) resolve::drag_gesture_init(),
in("d0") minimum_distance,
in("x0") space.as_mut_ptr(),
in("x1") space_meta,
in("x2") resolve::local_space_wt(),
in("x8") gesture.as_mut_ptr(),
in("x20") g_meta,
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("x21") _,
clobber_abi("C"),
);
let g_wt = abi::get_view_wt_for_proto(g_meta, resolve::gesture_proto());
assert!(!g_wt.is_null(), "DragGesture: Gesture");
let ended_meta =
abi::generic_metadata2(resolve::ended_gesture_ma(), g_meta, g_wt);
let mut ended = vec![0u8; abi::type_bytes(ended_meta)];
let ctx = Box::leak(Box::new(abi::ValueClosureCtx::new(
invoke_value_action,
boxed_ctx(Box::new(action) as Box<dyn Fn()>),
))) as *mut abi::ValueClosureCtx;
core::arch::asm!("blr {f}", f = in(reg) resolve::gesture_on_ended(),
in("x0") abi::value_closure_thunk as *const () as u64,
in("x1") ctx as u64,
in("x2") g_meta,
in("x3") g_wt,
in("x8") ended.as_mut_ptr(),
in("x20") gesture.as_mut_ptr(),
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("x21") _, clobber_abi("C"),
);
let ended_wt = abi::get_view_wt_for_proto(ended_meta, resolve::gesture_proto());
assert!(!ended_wt.is_null(), "_EndedGesture<DragGesture>: Gesture");
let subst = [view.meta, ended_meta, view.wt, ended_wt];
let (meta, wt, size) = opaque_result_type(oret, &subst);
let mut result = vec![0u8; size];
let this = view.value_ptr();
core::arch::asm!("blr {f}", f = in(reg) func,
in("x0") ended.as_mut_ptr(),
in("x1") 3u64, in("x2") view.meta,
in("x3") ended_meta,
in("x4") view.wt,
in("x5") ended_wt,
in("x8") result.as_mut_ptr(),
in("x20") this,
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("x21") _, clobber_abi("C"),
);
ViewHandle::wrap(result.as_ptr() as _, meta, wt)
}
}
unsafe fn style_modifier(
func: *const c_void,
oret: *const c_void,
view: &ViewHandle,
style_ma: *const c_void,
style_wt: *const c_void,
style_init: *const c_void,
) -> ViewHandle {
unsafe {
let style_meta = abi::metadata0(style_ma);
let mut style = vec![0u8; abi::type_bytes(style_meta)];
core::arch::asm!("blr {f}", f = in(reg) style_init,
in("x8") style.as_mut_ptr(),
in("x20") style_meta,
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("x21") _, clobber_abi("C"),
);
let subst = [view.meta, style_meta, view.wt, style_wt];
let (meta, wt, size) = opaque_result_type(oret, &subst);
let mut result = vec![0u8; size];
let this = view.value_ptr();
core::arch::asm!("blr {f}", f = in(reg) func,
in("x0") style.as_mut_ptr(),
in("x1") view.meta,
in("x2") style_meta,
in("x3") view.wt,
in("x4") style_wt,
in("x8") result.as_mut_ptr(),
in("x20") this,
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("x21") _, clobber_abi("C"),
);
ViewHandle::wrap(result.as_ptr() as _, meta, wt)
}
}
unsafe fn opaque_modifier(
func: *const c_void,
oret: *const c_void,
view: &ViewHandle,
ints: &[u64],
floats: [f64; 4],
) -> ViewHandle {
unsafe {
let subst = [view.meta, view.wt];
let (meta, wt, size) = opaque_result_type(oret, &subst);
let r = abi::call_modifier(func, view.value_ptr(), view.meta, view.wt, ints, floats, size);
ViewHandle::wrap(r.as_ptr() as _, meta, wt)
}
}
unsafe fn modifier0(f: *const c_void, o: *const c_void, v: &ViewHandle) -> ViewHandle {
unsafe { opaque_modifier(f, o, v, &[], [0.0; 4]) }
}
unsafe fn modifier_f64(f: *const c_void, o: *const c_void, v: &ViewHandle, x: f64) -> ViewHandle {
unsafe { opaque_modifier(f, o, v, &[], [x, 0.0, 0.0, 0.0]) }
}
unsafe fn modifier_bool(f: *const c_void, o: *const c_void, v: &ViewHandle, x: bool) -> ViewHandle {
unsafe { opaque_modifier(f, o, v, &[u64::from(x)], [0.0; 4]) }
}
unsafe fn modifier_i64(f: *const c_void, o: *const c_void, v: &ViewHandle, x: i64) -> ViewHandle {
unsafe { opaque_modifier(f, o, v, &[x as u64], [0.0; 4]) }
}
unsafe fn modifier_str(f: *const c_void, o: *const c_void, v: &ViewHandle, x: &str) -> ViewHandle {
unsafe {
let s = abi::swift_string(x);
let w0 = u64::from_le_bytes(s[..8].try_into().expect("string lo"));
let w1 = u64::from_le_bytes(s[8..].try_into().expect("string hi"));
opaque_modifier(f, o, v, &[w0, w1], [0.0; 4])
}
}
pub(crate) unsafe fn wrap_owned(
buf: Vec<u8>,
meta: *const c_void,
wt: *const c_void,
) -> ViewHandle {
unsafe {
let n = abi::type_size(meta);
assert!(buf.len() >= n, "{} needs {n} bytes, buffer was {}",
crate::diag::type_name(meta), buf.len());
ViewHandle::adopt(buf, meta, wt)
}
}
pub(crate) unsafe fn wrap_buf(
buf: &[u8],
meta: *const c_void,
wt: *const c_void,
) -> ViewHandle {
unsafe {
let n = abi::type_size(meta);
assert!(buf.len() >= n, "{} needs {n} bytes, buffer was {}",
crate::diag::type_name(meta), buf.len());
ViewHandle::wrap(buf.as_ptr() as _, meta, wt)
}
}
fn leak_retained(view: &ViewHandle) -> u64 {
view.to_anyview_word()
}
unsafe impl Send for ViewHandle {}
impl std::fmt::Debug for ViewHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "ViewHandle({})", dynamic_type_name(self))
}
}
pub fn meta_of(view: &ViewHandle) -> *const c_void {
view.meta
}
pub(crate) unsafe fn substitute_content(
known: *const c_void,
args: usize,
meta_at: usize,
wt_at: usize,
content_meta: *const c_void,
content_wt: *const c_void,
) -> Option<(*const c_void, *const c_void)> {
unsafe { substitute_args(known, args, &[(meta_at, content_meta)], &[(wt_at, content_wt)]) }
}
pub(crate) unsafe fn substitute_args(
known: *const c_void,
args: usize,
params: &[(usize, *const c_void)],
witnesses: &[(usize, *const c_void)],
) -> Option<(*const c_void, *const c_void)> {
unsafe {
let base = (known as *const *const c_void).add(2);
let mut vec: Vec<*const c_void> = (0..args).map(|i| *base.add(i)).collect();
for &(at, value) in params.iter().chain(witnesses) {
if at >= args {
return None;
}
vec[at] = value;
}
let meta = abi::generic_metadata_from(known, &vec);
if meta.is_null() {
return None;
}
let built = (meta as *const *const c_void).add(2);
if params.iter().any(|&(at, value)| *built.add(at) != value) {
return None;
}
let wt = abi::get_view_wt(meta);
if wt.is_null() { None } else { Some((meta, wt)) }
}
}
macro_rules! swift_cases {
(
$(#[$meta:meta])*
$vis:vis enum $name:ident {
$($(#[$vmeta:meta])* $variant:ident = $tag:literal),* $(,)?
}
) => {
$(#[$meta])*
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u32)]
$vis enum $name {
$($(#[$vmeta])* $variant = $tag),*
}
impl $name {
pub(crate) fn tag(self) -> u32 {
self as u32
}
}
};
}
macro_rules! modifiers {
($(
$(#[$meta:meta])*
fn $name:ident ( $($arg:ident : $ty:ty),* $(,)? )
= $apply:ident ( $func:ident, $oret:ident $(, $extra:expr)* $(,)? );
)*) => {$(
$(#[$meta])*
pub fn $name(view: &ViewHandle $(, $arg: $ty)*) -> ViewHandle {
unsafe { $apply(resolve::$func(), resolve::$oret(), view $(, $extra)*) }
}
)*};
}
mod accessibility;
mod binding;
mod catalogue;
mod closures;
mod dragdrop;
mod collections;
mod compositing;
mod customlayout;
mod customshape;
mod customstyle;
mod concrete;
mod conditional;
mod controls;
mod effects;
mod enums;
mod generated;
mod focus;
mod gestures;
mod input;
mod generics;
mod hover;
mod interaction;
mod interop;
#[cfg(not(target_os = "macos"))]
mod ios;
mod layout;
mod materials;
mod namespace;
#[cfg(target_os = "macos")]
mod representable;
mod search;
mod navigation;
mod optionsets;
mod plain;
mod presentation;
mod primitives;
mod sets;
mod shapes;
mod stacks;
mod strings;
mod styles;
mod timeline;
mod toolbar;
pub(crate) mod types;
#[doc(hidden)]
pub use types::bench_result_type_paths;
mod typography;
pub use accessibility::*;
pub use binding::*;
pub use catalogue::*;
pub use dragdrop::*;
pub use focus::*;
pub use input::*;
#[cfg(target_os = "macos")]
pub use representable::*;
pub use search::*;
pub use closures::*;
pub use collections::*;
pub use compositing::*;
pub use customlayout::*;
pub use customshape::*;
pub use customstyle::*;
pub use concrete::*;
pub use conditional::*;
pub use controls::*;
pub use effects::*;
pub use enums::*;
pub use generated::*;
pub use gestures::*;
pub use generics::*;
pub use hover::*;
pub use interaction::*;
pub use interop::*;
#[cfg(not(target_os = "macos"))]
pub use ios::*;
pub use layout::*;
pub use materials::*;
pub use namespace::*;
pub use navigation::*;
pub use optionsets::*;
pub use plain::*;
pub use presentation::*;
pub use primitives::*;
pub use sets::*;
pub use shapes::*;
pub use stacks::*;
pub use strings::*;
pub use styles::*;
pub use timeline::*;
pub use toolbar::*;
pub use typography::*;
pub(crate) use types::*;
pub fn handle_word(view: &ViewHandle) -> u64 {
view.to_anyview_word()
}
pub fn wrap_word(anyview: u64) -> ViewHandle {
ViewHandle::from_anyview(anyview)
}
pub(crate) fn boxed_ctx<F: ?Sized>(f: Box<F>) -> *mut core::ffi::c_void {
Box::into_raw(Box::new(f)) as *mut core::ffi::c_void
}
pub(crate) unsafe fn boxed_ref<'a, F: ?Sized>(payload: *mut core::ffi::c_void) -> &'a F {
unsafe { &*(payload as *const Box<F>) }
}