use proc_macro::{Delimiter, TokenStream, TokenTree};
#[derive(Clone, Debug, PartialEq)]
enum Class {
Void,
Bool,
Word,
Double,
Float,
String,
StringRef,
ClassRef(String),
OptClassRef(String),
Indirect(String),
OptIndirect(String),
ValuePtr,
RawWord(String),
Words3,
OptPrimitive(String),
OptString,
Doubles(usize, String),
}
impl Class {
fn declared_class(&self) -> Option<(&str, String)> {
match self {
Class::Indirect(ty) | Class::OptIndirect(ty) => Some((ty, "Indirect".to_string())),
Class::RawWord(ty) => Some((ty, "Word".to_string())),
Class::Words3 => None,
Class::Doubles(count, ty) => Some((ty, format!("Doubles({count})"))),
_ => None,
}
}
fn is_indirect(&self) -> bool {
matches!(self, Class::Indirect(_) | Class::OptIndirect(_))
}
}
fn normalize(ty: &str) -> String {
let mut out = String::with_capacity(ty.len());
let mut last_space = true;
for ch in ty.chars() {
if ch.is_whitespace() {
if !last_space {
out.push(' ');
last_space = true;
}
} else {
out.push(ch);
last_space = false;
}
}
let mut out = out.trim().to_string();
for token in [":: ", " ::", "< ", " <", "> ", " >"] {
let tight: String = token.chars().filter(|c| !c.is_whitespace()).collect();
while out.contains(token) {
out = out.replace(token, &tight);
}
}
out
}
fn inner_of<'a>(ty: &'a str, name: &str) -> Option<&'a str> {
let head = format!("{name}<");
let rest = ty.strip_prefix(&head)?;
let inner = rest.strip_suffix('>')?;
Some(inner.trim())
}
fn last_segment(ty: &str) -> &str {
ty.rsplit("::").next().unwrap_or(ty).trim()
}
fn classify_ret(ty: &str) -> Class {
let ty = normalize(ty);
if ty.is_empty() || ty == "()" {
return Class::Void;
}
if let Some(inner) = inner_of(&ty, "Option") {
let inner = normalize(inner);
return match classify_ret(&inner) {
Class::ClassRef(t) => Class::OptClassRef(t),
Class::Indirect(t) => Class::OptIndirect(t),
Class::String => Class::OptString,
Class::Bool | Class::Word | Class::Double | Class::Float => Class::OptPrimitive(inner),
other => panic!("swift::call: cannot return Option<{inner}> ({other:?})"),
};
}
if let Some(inner) = inner_of(&ty, "Result") {
let (ok, _err) =
split_top(inner)
.into_iter()
.fold((String::new(), String::new()), |mut acc, part| {
if acc.0.is_empty() {
acc.0 = part;
} else {
acc.1 = part;
}
acc
});
return classify_ret(&ok);
}
if let Some(class) = container_class(&ty) {
return class;
}
if ty == "cm::Time" {
return Class::Words3;
}
if let Some(class) = doubles_class(&ty) {
return class;
}
match last_segment(&ty) {
"bool" => Class::Bool,
"f64" => Class::Double,
"f32" => Class::Float,
"isize" | "usize" | "i8" | "u8" | "i16" | "u16" | "i32" | "u32" | "i64" | "u64" => {
Class::Word
}
"String" => Class::String,
_ => {
if let Some(inner) = inner_of(&ty, "arc::R").or_else(|| inner_of(&ty, "R")) {
Class::ClassRef(normalize(inner))
} else {
Class::Indirect(ty.clone())
}
}
}
}
fn container_class(ty: &str) -> Option<Class> {
let bare = ty.strip_prefix("swift::").unwrap_or(ty);
["Array", "Set", "Dictionary"]
.iter()
.any(|container| bare.starts_with(&format!("{container}<")))
.then(|| Class::RawWord(ty.to_string()))
}
fn doubles_class(ty: &str) -> Option<Class> {
let count = match ty {
"cg::Rect" => 4,
"spatial::Vector3D" => 3,
"cg::Point" => 2,
_ => return None,
};
Some(Class::Doubles(count, ty.to_string()))
}
fn is_throwing(ty: &str) -> bool {
inner_of(&normalize(ty), "Result").is_some()
}
fn classify_arg(ty: &str) -> Class {
let ty = normalize(ty);
let borrowed = ty.starts_with('&');
let bare = ty.strip_prefix('&').unwrap_or(&ty).trim();
let bare = normalize(bare);
if last_segment(&bare) == "String" {
return if borrowed {
Class::StringRef
} else {
Class::String
};
}
if let Some(class) = doubles_class(&bare) {
return class;
}
if let Some(class) = container_class(&bare) {
return class;
}
match last_segment(&bare) {
"bool" => Class::Bool,
"f64" => Class::Double,
"f32" => Class::Float,
"isize" | "usize" | "i8" | "u8" | "i16" | "u16" | "i32" | "u32" | "i64" | "u64" => {
Class::Word
}
"String" => Class::String,
_ => {
match inner_of(&bare, "arc::R").or_else(|| inner_of(&bare, "R")) {
Some(inner) => Class::ClassRef(normalize(inner)),
None => Class::ValuePtr,
}
}
}
}
fn split_top(text: &str) -> Vec<String> {
let mut parts = Vec::new();
let mut depth = 0i32;
let mut current = String::new();
for ch in text.chars() {
match ch {
'<' | '(' | '[' => depth += 1,
'>' | ')' | ']' => depth -= 1,
',' if depth == 0 => {
parts.push(current.trim().to_string());
current = String::new();
continue;
}
_ => {}
}
current.push(ch);
}
if !current.trim().is_empty() {
parts.push(current.trim().to_string());
}
parts
}
struct Arg {
name: String,
ty: String,
}
struct Signature {
meta: String,
vis_and_qualifiers: String,
name: String,
generics: String,
args_source: String,
ret_source: String,
takes_self: bool,
args: Vec<Arg>,
}
fn parse_signature(func: TokenStream) -> Signature {
let mut iter = func.into_iter();
let mut meta: Vec<TokenTree> = Vec::new();
let mut qualifiers: Vec<TokenTree> = Vec::new();
while let Some(tt) = iter.next() {
match &tt {
TokenTree::Punct(p) if p.as_char() == '#' => {
meta.push(tt);
if let Some(next @ TokenTree::Group(_)) = iter.next() {
meta.push(next);
}
}
TokenTree::Ident(i) if i.to_string() == "fn" => break,
_ => qualifiers.push(tt),
}
}
let Some(TokenTree::Ident(name)) = iter.next() else {
panic!("swift::call: expected a function name");
};
let name = name.to_string();
let mut generics = Vec::new();
let args = loop {
match iter.next() {
Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Parenthesis => break g,
Some(tt) => generics.push(tt),
None => panic!("swift::call: expected an argument list"),
}
};
let mut ret = TokenStream::from_iter(iter).to_string();
let trimmed = ret.trim_end();
assert!(
trimmed.ends_with(';'),
"swift::call: the function must be a declaration ending in `;`"
);
ret = trimmed[..trimmed.len() - 1].to_string();
let ret_source = ret
.trim()
.strip_prefix("->")
.map(|r| r.trim().to_string())
.unwrap_or_default();
let args_source = args.to_string();
let mut takes_self = false;
let mut parsed = Vec::new();
for part in split_top(&normalize(args.stream().to_string().as_str())) {
if part.ends_with("self") {
takes_self = true;
continue;
}
let Some((name, ty)) = part.split_once(':') else {
panic!("swift::call: argument `{part}` needs a type");
};
parsed.push(Arg {
name: name.trim().to_string(),
ty: ty.trim().to_string(),
});
}
Signature {
meta: TokenStream::from_iter(meta).to_string(),
vis_and_qualifiers: TokenStream::from_iter(qualifiers).to_string(),
name,
generics: TokenStream::from_iter(generics).to_string(),
args_source,
ret_source,
takes_self,
args: parsed,
}
}
struct Operand {
reg: String,
input: Option<String>,
output: Option<String>,
}
impl Operand {
fn render(&self) -> String {
match (&self.input, &self.output) {
(Some(i), Some(o)) => format!("inlateout(\"{}\") {i} => {o},", self.reg),
(Some(i), None) => format!("in(\"{}\") {i},", self.reg),
(None, Some(o)) => format!("lateout(\"{}\") {o},", self.reg),
(None, None) => String::new(),
}
}
}
enum Symbol {
Mangled(String),
Decl(String),
}
struct Attr {
symbol: Symbol,
is_async: bool,
owned: Vec<String>,
}
fn parse_attr(attr: TokenStream) -> Attr {
let mut segments: Vec<Vec<TokenTree>> = vec![Vec::new()];
for tt in attr {
match &tt {
TokenTree::Punct(p) if p.as_char() == ',' => segments.push(Vec::new()),
_ => segments
.last_mut()
.expect("there is always a segment open")
.push(tt),
}
}
let mut symbol = None;
let mut is_async = false;
let mut owned = Vec::new();
for segment in segments {
let Some(head) = segment.first() else { continue };
match head.to_string().as_str() {
"async" => is_async = true,
"owned" => {
let Some(TokenTree::Group(group)) = segment.get(1) else {
panic!(
"swift::call: `owned` names the parameters the callee \
takes at `+1`, as in `owned(asset)`"
);
};
owned.extend(group.stream().into_iter().filter_map(|tt| match tt {
TokenTree::Ident(ident) => Some(ident.to_string()),
_ => None,
}));
}
"sym" => {
let literal = segment
.last()
.expect("`sym` is followed by the symbol")
.to_string();
symbol = Some(Symbol::Mangled(unescape(&literal)));
}
other => symbol = Some(Symbol::Decl(unescape(other))),
}
}
Attr {
symbol: symbol.expect("swift::call: expected a declaration or `sym = \"$s...\"`"),
is_async,
owned,
}
}
pub fn gen_metadata_accessor(args: TokenStream) -> TokenStream {
let text = args.to_string();
let (kind, path) = text
.split_once(',')
.unwrap_or_else(|| panic!("swift::metadata_accessor: expected `kind, \"Module.Type\"`"));
let kind = crate::swift_mangle::kind_letter(kind.trim())
.unwrap_or_else(|e| panic!("swift::metadata_accessor: {e}"));
let path = unescape(path.trim());
let path = path.as_str();
let symbol = crate::swift_mangle::mangle_metadata_accessor(path, kind)
.unwrap_or_else(|e| panic!("swift::metadata_accessor: cannot mangle `{path}`: {e}"));
format!(
"{{
unsafe extern \"C\" {{
#[link_name = \"{symbol}\"]
fn __swift_metadata_accessor();
}}
__swift_metadata_accessor as *const ()
}}"
)
.parse()
.expect("valid accessor expression")
}
fn unescape(literal: &str) -> String {
let body = literal
.strip_prefix('"')
.and_then(|l| l.strip_suffix('"'))
.unwrap_or(literal);
let chars: Vec<char> = body.chars().collect();
let mut out = String::with_capacity(body.len());
let mut index = 0;
while index < chars.len() {
if chars[index] != '\\' {
out.push(chars[index]);
index += 1;
continue;
}
index += 1;
match chars.get(index) {
Some('\n') => {
index += 1;
while matches!(chars.get(index), Some(c) if c.is_whitespace()) {
index += 1;
}
}
Some('n') => {
out.push('\n');
index += 1;
}
Some('t') => {
out.push('\t');
index += 1;
}
Some('r') => {
out.push('\r');
index += 1;
}
Some(other) => {
out.push(*other);
index += 1;
}
None => break,
}
}
out
}
pub fn gen_symbol(args: TokenStream) -> TokenStream {
let text = args.to_string();
let decl = text.trim();
let decl = unescape(decl);
let decl = decl.as_str();
let symbol = crate::swift_mangle::mangle(decl)
.unwrap_or_else(|e| panic!("swift::symbol: cannot mangle `{decl}`: {e}"));
format!(
"{{
unsafe extern \"C\" {{
#[link_name = \"{symbol}\"]
fn __swift_symbol();
}}
__swift_symbol as *const ()
}}"
)
.parse()
.expect("valid symbol expression")
}
pub fn gen_swift_call(attr: TokenStream, func: TokenStream) -> TokenStream {
let attr = parse_attr(attr);
let (link_name, alias, conventions, is_async) = match attr.symbol {
Symbol::Mangled(s) => (s, None, Vec::new(), attr.is_async),
Symbol::Decl(d) => {
let mangled = crate::swift_mangle::mangle(&d)
.unwrap_or_else(|e| panic!("swift::call: cannot mangle `{d}`: {e}"));
let conventions = crate::swift_mangle::param_conventions(&d)
.unwrap_or_else(|e| panic!("swift::call: cannot read `{d}`: {e}"));
let is_async = crate::swift_mangle::is_async(&d)
.unwrap_or_else(|e| panic!("swift::call: cannot read `{d}`: {e}"));
(mangled, Some(d), conventions, is_async)
}
};
assert!(
attr.owned.is_empty() || alias.is_none(),
"swift::call: the declaration already says what the callee takes at \
`+1`, so `owned` would only be a second answer to the same question"
);
let sig = parse_signature(func);
if is_async {
let async_fn = format!("{link_name}Tu");
return gen_async_call(
sig,
&link_name,
&async_fn,
&alias,
&conventions,
&attr.owned,
);
}
let ret_class = classify_ret(&sig.ret_source);
let throws = is_throwing(&sig.ret_source);
let mut int_args: Vec<String> = Vec::new();
let mut float_args: Vec<String> = Vec::new();
let mut float_tys: Vec<&str> = Vec::new();
let mut prelude = String::new();
for (index, arg) in sig.args.iter().enumerate() {
match classify_arg(&arg.ty) {
Class::Bool => int_args.push(format!("{} as usize", arg.name)),
Class::Word => int_args.push(format!("{} as usize", arg.name)),
Class::Double => {
float_args.push(format!("{} as f64", arg.name));
float_tys.push("f64");
}
Class::Float => {
float_args.push(format!("{} as f32", arg.name));
float_tys.push("f32");
}
class @ (Class::String | Class::StringRef) => {
let by_value = class == Class::String;
check_ownership(&alias, &conventions, index, &arg.name, by_value);
let raw = format!("__raw_{}", arg.name);
let take = if by_value {
format!("crate::swift::String::into_raw({})", arg.name)
} else {
format!("crate::swift::String::as_raw({})", arg.name)
};
prelude.push_str(&format!("let {raw} = {take};\n"));
int_args.push(format!("{raw}.word0"));
int_args.push(format!("{raw}.word1"));
}
Class::ValuePtr => int_args.push(format!(
"crate::swift::SwiftSelf::swift_self_ptr({}) as usize",
arg.name
)),
other => panic!("swift::call: argument `{}` is {other:?}", arg.name),
}
}
let self_operand = if sig.takes_self {
Some("crate::swift::SwiftSelf::swift_self_ptr(self) as usize".to_string())
} else {
Some("<Self as crate::swift::SwiftMetadata>::metadata() as usize".to_string())
};
let mut outs: Vec<(String, String)> = Vec::new(); let mut bindings = String::new();
let mut tail;
let mut checks = String::new();
if let Some((ty, class)) = ret_class.declared_class() {
checks.push_str(&format!(
"const {{
assert!(
<{ty} as crate::swift::SwiftAbi>::CLASS.tag()
== crate::swift::AbiClass::{class}.tag(),
\"swift::call: this type is not returned the way the call assumes\"
)
}};\n"
));
}
match &ret_class {
Class::Void => {
tail = "()".to_string();
}
Class::Bool => {
bindings.push_str("let __r0: usize;\n");
outs.push(("x0".into(), "__r0".into()));
tail = "__r0 & 1 != 0".to_string();
}
Class::Word => {
bindings.push_str("let __r0: usize;\n");
outs.push(("x0".into(), "__r0".into()));
tail = format!("__r0 as {}", ret_ok_type(&sig.ret_source));
}
Class::Double => {
bindings.push_str("let __d0: f64;\n");
outs.push(("d0".into(), "__d0".into()));
tail = "__d0".to_string();
}
Class::Float => {
bindings.push_str("let __s0: f32;\n");
outs.push(("s0".into(), "__s0".into()));
tail = "__s0".to_string();
}
Class::String => {
bindings.push_str("let (__r0, __r1): (usize, usize);\n");
outs.push(("x0".into(), "__r0".into()));
outs.push(("x1".into(), "__r1".into()));
tail =
"crate::swift::String::from_raw(crate::swift::RawString { word0: __r0, word1: __r1 })"
.to_string();
}
Class::ClassRef(_) | Class::OptClassRef(_) => {
bindings.push_str("let __r0: usize;\n");
outs.push(("x0".into(), "__r0".into()));
let ok = ret_ok_type(&sig.ret_source);
tail = if matches!(ret_class, Class::OptClassRef(_)) {
format!(
"if __r0 == 0 {{ None }} else {{ Some(crate::arc::R::from_raw(__r0 as *mut _)) }} as {ok}"
)
} else {
"crate::arc::R::from_raw(__r0 as *mut _)".to_string()
};
}
Class::RawWord(ty) => {
bindings.push_str("let __r0: usize;\n");
outs.push(("x0".into(), "__r0".into()));
tail = format!("<{ty}>::from_raw(__r0 as *mut ())");
}
Class::Words3 => {
bindings.push_str("let (__r0, __r1, __r2): (u64, u64, u64);\n");
outs.push(("x0".into(), "__r0".into()));
outs.push(("x1".into(), "__r1".into()));
outs.push(("x2".into(), "__r2".into()));
tail = format!(
"core::mem::transmute::<(u64, u64, u64), {}>((__r0, __r1, __r2))",
ret_ok_type(&sig.ret_source)
);
}
Class::OptPrimitive(ty) => {
bindings.push_str("let __r0: usize;\n");
outs.push(("x0".into(), "__r0".into()));
tail = format!(
"{{
let mut __opt = crate::swift::value::Storage::<crate::swift::value::Optional<{ty}>>::new();
crate::swift::value::Storage::as_mut_ptr(&mut __opt).cast::<usize>().write(__r0);
__opt.take()
}}"
);
}
Class::OptString => {
bindings.push_str("let (__r0, __r1): (usize, usize);\n");
outs.push(("x0".into(), "__r0".into()));
outs.push(("x1".into(), "__r1".into()));
tail = "(__r0 != 0 || __r1 != 0).then(|| \
crate::swift::String::from_raw(crate::swift::RawString { word0: __r0, word1: __r1 }))"
.to_string();
}
Class::Doubles(count, ty) => {
let names: Vec<String> = (0..*count).map(|i| format!("__d{i}")).collect();
bindings.push_str(&format!(
"let ({}): ({});\n",
names.join(", "),
vec!["f64"; *count].join(", ")
));
for (index, name) in names.iter().enumerate() {
outs.push((format!("d{index}"), name.clone()));
}
tail = format!(
"<{ty} as crate::swift::FromSwiftDoubles>::from_doubles(&[{}])",
names.join(", ")
);
}
Class::Indirect(ty) => {
prelude.push_str(&format!(
"let mut __out = <{ty} as crate::swift::value::SwiftOut>::out_buf();\n"
));
tail = format!("<{ty} as crate::swift::value::SwiftOut>::out_take(__out)");
}
Class::OptIndirect(ty) => {
prelude.push_str(&format!(
"let mut __out = crate::swift::value::Storage::<crate::swift::value::Optional<<{ty} as crate::swift::value::SwiftOptionalValue>::Marker>>::new();\n"
));
tail = format!(
"<{ty} as crate::swift::value::SwiftOptionalValue>::from_optional_storage(__out)"
);
}
other => panic!("swift::call: cannot return {other:?}"),
}
if throws {
bindings.push_str("let __error: *mut ();\n");
tail = format!(
"if __error.is_null() {{ Ok({tail}) }} else {{ Err(crate::arc::R::from_raw(crate::swift::abi::error_as_ns_error(__error).cast())) }}"
);
}
let indirect_slot = usize::from(ret_class.is_indirect());
let self_slot = 1;
let use_thunk = !throws
&& !matches!(ret_class, Class::Words3)
&& int_args.len() + indirect_slot + self_slot <= 8
&& float_args.len() <= 8;
if use_thunk {
return gen_thunk_call(
&sig,
&link_name,
&alias,
&ret_class,
&int_args,
&float_args,
&float_tys,
indirect_slot,
&self_operand.expect("a call always names a self operand"),
&checks,
&prelude,
&tail,
);
}
let mut operands: Vec<Operand> = Vec::new();
let int_ret: Vec<&(String, String)> = outs.iter().filter(|(r, _)| r.starts_with('x')).collect();
let float_ret: Vec<&(String, String)> = outs
.iter()
.filter(|(r, _)| r.starts_with('d') || r.starts_with('s'))
.collect();
let int_used = int_args.len().max(int_ret.len());
for index in 0..int_used {
let reg = format!("x{index}");
let input = int_args.get(index).cloned();
let output = int_ret.get(index).map(|(_, binding)| binding.clone());
operands.push(Operand { reg, input, output });
}
let float_used = float_args.len().max(float_ret.len());
for index in 0..float_used {
let input = float_args.get(index).cloned();
let output = float_ret.get(index).map(|(reg, binding)| {
(reg.clone(), binding.clone())
});
let reg = match &output {
Some((reg, _)) => reg
.replace(['d', 's'], "")
.parse::<usize>()
.ok()
.map_or_else(|| format!("d{index}"), |_| format!("{}{index}", ®[..1])),
None => format!("d{index}"),
};
operands.push(Operand {
reg,
input,
output: output.map(|(_, binding)| binding),
});
}
if ret_class.is_indirect() {
operands.push(Operand {
reg: "x8".into(),
input: Some(match &ret_class {
Class::Indirect(ty) => {
format!("<{ty} as crate::swift::value::SwiftOut>::out_ptr(&mut __out) as usize")
}
_ => "crate::swift::value::Storage::as_mut_ptr(&mut __out) as usize".to_string(),
}),
output: None,
});
}
if let Some(self_expr) = self_operand {
operands.push(Operand {
reg: "x20".into(),
input: Some(self_expr),
output: None,
});
}
if throws {
operands.push(Operand {
reg: "x21".into(),
input: Some("0usize".into()),
output: Some("__error".into()),
});
}
let operand_list: String = operands
.iter()
.map(|o| o.render())
.collect::<Vec<_>>()
.join("\n ");
let doc_alias = alias
.map(|a| format!("#[doc(alias = \"{a}\")]"))
.unwrap_or_default();
let Signature {
meta,
vis_and_qualifiers,
name,
generics,
args_source,
ret_source,
..
} = sig;
let ret_clause = if ret_source.is_empty() {
String::new()
} else {
format!("-> {ret_source}")
};
let out = format!(
"
{meta}
{doc_alias}
#[inline]
{vis_and_qualifiers} fn {name}{generics}{args_source} {ret_clause} {{
#[allow(non_snake_case)]
unsafe extern \"C\" {{
#[link_name = \"{link_name}\"]
fn __swift_callee();
}}
unsafe {{
{checks}
let __fn = __swift_callee as *const ();
{prelude}
{bindings}
core::arch::asm!(
\"blr {{__fn}}\",
__fn = in(reg) __fn,
{operand_list}
clobber_abi(\"C\"),
);
{tail}
}}
}}
"
);
out.parse()
.unwrap_or_else(|e| panic!("swift::call generated invalid code: {e}\n{out}"))
}
fn async_ok_type(ret: &str) -> String {
let ret = normalize(ret);
let inner = inner_of(&ret, "Result").unwrap_or_else(|| {
panic!(
"swift::call: a suspending call must return \
`Result<T, arc::R<ns::Error>>`, not `{ret}`"
)
});
split_top(inner)
.into_iter()
.next()
.expect("a Result names a success type")
}
fn with_trailing_arg(args_source: &str, extra: &str) -> String {
let trimmed = args_source.trim();
let inner = trimmed
.strip_prefix('(')
.and_then(|a| a.strip_suffix(')'))
.expect("an argument list is parenthesized")
.trim();
let inner = inner.strip_suffix(',').unwrap_or(inner).trim_end();
if inner.is_empty() {
format!("({extra})")
} else {
format!("({inner}, {extra})")
}
}
fn gen_async_call(
sig: Signature,
link_name: &str,
async_fn: &str,
alias: &Option<String>,
conventions: &[bool],
owned_params: &[String],
) -> TokenStream {
assert!(
sig.generics.trim().is_empty(),
"swift::call: a suspending call cannot be generic"
);
for name in owned_params {
assert!(
sig.args.iter().any(|arg| arg.name == *name),
"swift::call: `owned` names `{name}`, which is not a parameter"
);
}
let ret_class = classify_ret(&sig.ret_source);
async_ok_type(&sig.ret_source);
let mut owned: Vec<String> = Vec::new();
let mut owned_pat: Vec<String> = Vec::new();
let mut setters: Vec<String> = Vec::new();
let mut closure_prelude = String::new();
if sig.takes_self {
owned.push("crate::arc::Retain::retained(self)".to_string());
owned_pat.push("__self".to_string());
setters.push(".swift_self(__self.as_ptr().cast())".to_string());
} else {
setters.push(
".swift_self(<Self as crate::swift::SwiftMetadata>::metadata().cast_mut().cast())"
.to_string(),
);
}
let mut int_index = 0usize;
let mut float_index = 0usize;
let out_slot = matches!(ret_class, Class::Indirect(_)).then(|| {
let slot = owned.len();
let Class::Indirect(ty) = &ret_class else {
unreachable!()
};
owned.push(format!(
"<{ty} as crate::swift::value::SwiftOut>::out_buf()"
));
owned_pat.push("__out".to_string());
setters.push(format!(
".arg(0, <{ty} as crate::swift::value::SwiftOut>::out_ptr(__out))"
));
int_index += 1;
slot
});
for (index, arg) in sig.args.iter().enumerate() {
let name = &arg.name;
let consumed = conventions
.get(index)
.copied()
.unwrap_or_else(|| owned_params.iter().any(|owned| *owned == arg.name));
match classify_arg(&arg.ty) {
Class::Bool | Class::Word => {
setters.push(format!(".arg({int_index}, {name} as usize as *mut ())"));
int_index += 1;
}
Class::Double => {
setters.push(format!(".float({float_index}, {name} as f64)"));
float_index += 1;
}
Class::Float => panic!(
"swift::call: `{name}` is an `f32`, which a suspending call has \
no way to place yet"
),
Class::ClassRef(_) => {
assert!(
!arg.ty.trim().starts_with('&'),
"swift::call: `{name}` is borrowed, but a suspending call \
outlives the caller's frame, so a class argument has to be \
taken by value as `arc::R<_>`"
);
let slot = owned.len();
owned.push(name.clone());
owned_pat.push(format!("__a{slot}"));
setters.push(if consumed {
format!(
".arg({int_index}, crate::arc::Retain::retained(&**__a{slot}).into_raw().cast())"
)
} else {
format!(".arg({int_index}, __a{slot}.as_ptr().cast())")
});
int_index += 1;
}
Class::RawWord(ty) => {
assert!(
!arg.ty.trim().starts_with('&'),
"swift::call: `{name}` is borrowed, but a suspending call \
outlives the caller's frame, so it has to be taken by value"
);
let slot = owned.len();
owned.push(name.clone());
owned_pat.push(format!("__a{slot}"));
closure_prelude.push_str(&format!(
"const {{
assert!(
<{ty} as crate::swift::SwiftAbi>::CLASS.tag()
== crate::swift::AbiClass::Word.tag(),
\"swift::call: this type is not passed the way the call assumes\"
)
}};\n "
));
setters.push(format!(".arg({int_index}, __a{slot}.as_raw())"));
int_index += 1;
}
Class::Doubles(count, ty) => {
closure_prelude.push_str(&format!(
"const {{
assert!(
<{ty} as crate::swift::ToSwiftDoubles>::COUNT == {count},
\"swift::call: this type does not travel in the registers the call assumes\"
)
}};
let mut __fd{index} = [0f64; {count}];
crate::swift::ToSwiftDoubles::write_doubles(&{name}, &mut __fd{index});\n "
));
for offset in 0..count {
setters.push(format!(
".float({}, __fd{index}[{offset}])",
float_index + offset
));
}
float_index += count;
}
Class::ValuePtr => {
assert!(
!arg.ty.trim().starts_with('&'),
"swift::call: `{name}` is borrowed, but a suspending call \
outlives the caller's frame, so it has to be taken by value"
);
assert!(
!consumed,
"swift::call: `{name}` is consumed by the callee, which a \
suspending call cannot hand over yet"
);
let slot = owned.len();
owned.push(name.clone());
owned_pat.push(format!("__a{slot}"));
setters.push(format!(
".arg({int_index}, crate::swift::SwiftSelf::swift_self_ptr(__a{slot}).cast_mut())"
));
int_index += 1;
}
other => panic!("swift::call: argument `{name}` is {other:?}"),
}
}
let output = match &ret_class {
Class::Void => "|_, _| ()".to_string(),
Class::ClassRef(_) => "|_, __result| crate::arc::R::from_raw(__result.cast())".to_string(),
Class::Indirect(ty) => {
let slot = out_slot.expect("an indirect return owns its buffer");
let take: Vec<String> = (0..owned.len())
.map(|index| {
if index == slot {
"__out".to_string()
} else {
"_".to_string()
}
})
.collect();
format!(
"|__owned, _| {{ let ({},) = __owned; \
<{ty} as crate::swift::value::SwiftOut>::out_take(__out) }}",
take.join(", ")
)
}
other => panic!("swift::call: a suspending call cannot return {other:?}"),
};
let checks = match ret_class.declared_class() {
Some((ty, class)) => format!(
"const {{
assert!(
<{ty} as crate::swift::SwiftAbi>::CLASS.tag()
== crate::swift::AbiClass::{class}.tag(),
\"swift::call: this type is not returned the way the call assumes\"
)
}};"
),
None => String::new(),
};
let (owned_tuple, owned_pattern) = if owned.is_empty() {
("()".to_string(), "()".to_string())
} else {
(
format!("({},)", owned.join(", ")),
format!("({},)", owned_pat.join(", ")),
)
};
let setters = setters.join("\n ");
let doc_alias = alias
.as_ref()
.map(|a| format!("#[doc(alias = \"{a}\")]"))
.unwrap_or_default();
let Signature {
meta,
vis_and_qualifiers,
name,
args_source,
ret_source,
..
} = &sig;
let handler_args = with_trailing_arg(args_source, "__callback: __F");
let out = format!(
"
{meta}
{doc_alias}
#[inline]
{vis_and_qualifiers} fn {name}_handler<__F>{handler_args}
where
__F: FnOnce({ret_source}) + Send + 'static,
{{
#[allow(non_snake_case)]
unsafe extern \"C\" {{
#[link_name = \"{link_name}\"]
fn __swift_callee();
#[link_name = \"{async_fn}\"]
static __SWIFT_ASYNC_FN: u8;
}}
unsafe {{
{checks}
crate::swift::concurrency::call_async_result(
__swift_callee as *const (),
&raw const __SWIFT_ASYNC_FN,
{owned_tuple},
|{owned_pattern}| {{
{closure_prelude}crate::swift::concurrency::AsyncCallArgs::new()
{setters}
}},
{output},
__callback,
);
}}
}}
{meta}
{doc_alias}
#[cfg(feature = \"async\")]
#[inline]
{vis_and_qualifiers} fn {name}{args_source} -> impl core::future::Future<Output = {ret_source}> {{
#[allow(non_snake_case)]
unsafe extern \"C\" {{
#[link_name = \"{link_name}\"]
fn __swift_callee();
#[link_name = \"{async_fn}\"]
static __SWIFT_ASYNC_FN: u8;
}}
unsafe {{
{checks}
crate::swift::concurrency::call_async_future(
__swift_callee as *const (),
&raw const __SWIFT_ASYNC_FN,
{owned_tuple},
|{owned_pattern}| {{
{closure_prelude}crate::swift::concurrency::AsyncCallArgs::new()
{setters}
}},
{output},
)
}}
}}
"
);
out.parse()
.unwrap_or_else(|e| panic!("swift::call generated invalid code: {e}\n{out}"))
}
#[allow(clippy::too_many_arguments)]
fn gen_thunk_call(
sig: &Signature,
link_name: &str,
alias: &Option<String>,
ret_class: &Class,
int_args: &[String],
float_args: &[String],
float_tys: &[&str],
indirect_slot: usize,
self_operand: &str,
checks: &str,
prelude: &str,
tail: &str,
) -> TokenStream {
let mut params: Vec<String> = (0..int_args.len())
.map(|index| format!("__a{index}: usize"))
.collect();
let mut call_args: Vec<String> = int_args.to_vec();
if indirect_slot == 1 {
params.push("__out_ptr: *mut ()".to_string());
call_args.push(match ret_class {
Class::Indirect(ty) => {
format!("<{ty} as crate::swift::value::SwiftOut>::out_ptr(&mut __out)")
}
_ => "crate::swift::value::Storage::as_mut_ptr(&mut __out)".to_string(),
});
}
params.push("__self: usize".to_string());
call_args.push(self_operand.to_string());
for (index, ty) in float_tys.iter().enumerate() {
params.push(format!("__f{index}: {ty}"));
}
call_args.extend(float_args.iter().cloned());
let mut shuffle = String::from("\"stp x20, x30, [sp, #-16]!\",\n ");
if indirect_slot == 1 {
shuffle.push_str(&format!("\"mov x8, x{}\",\n ", int_args.len()));
}
shuffle.push_str(&format!(
"\"mov x20, x{}\",\n ",
int_args.len() + indirect_slot
));
let (thunk_ret, thunk_item, bind) = match ret_class {
Class::Void | Class::Indirect(_) | Class::OptIndirect(_) => {
(String::new(), String::new(), "__CALL__;".to_string())
}
Class::Bool
| Class::Word
| Class::ClassRef(_)
| Class::OptClassRef(_)
| Class::RawWord(_)
| Class::OptPrimitive(_) => (
"-> usize".to_string(),
String::new(),
"let __r0: usize = __CALL__;".to_string(),
),
Class::Double => (
"-> f64".to_string(),
String::new(),
"let __d0: f64 = __CALL__;".to_string(),
),
Class::Float => (
"-> f32".to_string(),
String::new(),
"let __s0: f32 = __CALL__;".to_string(),
),
Class::String | Class::OptString => (
"-> crate::swift::RawString".to_string(),
String::new(),
"let __rs = __CALL__;\nlet (__r0, __r1) = (__rs.word0, __rs.word1);".to_string(),
),
Class::Doubles(count, _) => {
let fields = vec!["f64"; *count].join(", ");
let names: Vec<String> = (0..*count).map(|i| format!("__d{i}")).collect();
let values: Vec<String> = (0..*count).map(|i| format!("__ds.{i}")).collect();
(
"-> __SwiftDoubles".to_string(),
format!("#[repr(C)] struct __SwiftDoubles({fields});\n"),
format!(
"let __ds = __CALL__;\nlet ({}) = ({});",
names.join(", "),
values.join(", ")
),
)
}
other => panic!("swift::call: cannot return {other:?} through a thunk"),
};
let bind = bind.replace(
"__CALL__",
&format!("__swift_thunk({})", call_args.join(", ")),
);
let doc_alias = alias
.as_ref()
.map(|a| format!("#[doc(alias = \"{a}\")]"))
.unwrap_or_default();
let Signature {
meta,
vis_and_qualifiers,
name,
generics,
args_source,
ret_source,
..
} = sig;
let ret_clause = if ret_source.is_empty() {
String::new()
} else {
format!("-> {ret_source}")
};
let params = params.join(", ");
let out = format!(
"
{meta}
{doc_alias}
#[inline]
{vis_and_qualifiers} fn {name}{generics}{args_source} {ret_clause} {{
#[allow(non_snake_case)]
unsafe extern \"C\" {{
#[link_name = \"{link_name}\"]
fn __swift_callee();
}}
{thunk_item}
#[unsafe(naked)]
#[allow(non_snake_case, improper_ctypes_definitions)]
unsafe extern \"C\" fn __swift_thunk({params}) {thunk_ret} {{
core::arch::naked_asm!(
{shuffle}\"bl {{__callee}}\",
\"ldp x20, x30, [sp], #16\",
\"ret\",
__callee = sym __swift_callee,
)
}}
unsafe {{
{checks}
{prelude}
{bind}
{tail}
}}
}}
"
);
out.parse()
.unwrap_or_else(|e| panic!("swift::call generated invalid thunk: {e}\n{out}"))
}
fn check_ownership(
alias: &Option<String>,
conventions: &[bool],
index: usize,
name: &str,
by_value: bool,
) {
let Some(decl) = alias else {
return;
};
let Some(&consumed) = conventions.get(index) else {
return;
};
if consumed != by_value {
let (wants, has) = if consumed {
("takes it at `+1`", "`&`")
} else {
("borrows it", "by value")
};
panic!(
"swift::call: `{decl}` {wants}, but `{name}` is declared {has}. \
Take a borrowed argument by reference and a consumed one by value."
);
}
}
fn ret_ok_type(ret: &str) -> String {
let ret = normalize(ret);
match inner_of(&ret, "Result") {
Some(inner) => split_top(inner)
.first()
.cloned()
.unwrap_or_else(|| ret.clone()),
None => ret,
}
}
#[cfg(test)]
mod tests {
use super::unescape;
#[test]
fn a_line_continuation_leaves_no_trace() {
assert_eq!(
"Speech.SpeechTranscriber(class).Result(struct).text: Foundation.AttributedString",
unescape(
r#""Speech.SpeechTranscriber(class).Result(struct).text: \
Foundation.AttributedString""#
)
);
assert_eq!("plain", unescape(r#""plain""#));
assert_eq!("a\"b", unescape(r#""a\"b""#));
}
}