Skip to content

Commit 3feaf74

Browse files
committed
[Swift] Add arm64 calling conventions
The Swift ABI repurposes three callee-saved registers for implicit parameters (self, error, and async context). Supporting the various combinations of these requires registering several different calling conventions. The demangler is taught to explicitly apply these calling conventions to functions that need them.
1 parent 1fb9828 commit 3feaf74

File tree

3 files changed

+199
-3
lines changed

3 files changed

+199
-3
lines changed

arch/arm64/arch_arm64.cpp

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2765,6 +2765,115 @@ class AppleArm64SystemCallConvention : public CallingConvention
27652765
virtual bool IsEligibleForHeuristics() override { return false; }
27662766
};
27672767

2768+
2769+
// Swift calling convention for ARM64.
2770+
//
2771+
// The Swift ABI repurposes three callee-saved registers for implicit parameters:
2772+
// x20 - self (swiftself): context/self parameter, passed as the last integer argument
2773+
// x21 - error (swifterror): caller initializes to zero, callee sets to error pointer on throw
2774+
// x22 - async context (swiftasync): implicit async context for async functions
2775+
//
2776+
// Each combination of these three properties requires a distinct calling convention,
2777+
// since it changes which registers are arguments, callee-saved, or implicitly defined.
2778+
// The naming scheme is "swift" with optional suffixes: "-self", "-throws", "-async".
2779+
class SwiftArm64CallingConvention : public CallingConvention
2780+
{
2781+
bool m_hasSelf;
2782+
bool m_throws;
2783+
bool m_isAsync;
2784+
2785+
public:
2786+
SwiftArm64CallingConvention(Architecture* arch, const string& name, bool hasSelf, bool throws, bool isAsync)
2787+
: CallingConvention(arch, name), m_hasSelf(hasSelf), m_throws(throws), m_isAsync(isAsync)
2788+
{
2789+
}
2790+
2791+
virtual vector<uint32_t> GetIntegerArgumentRegisters() override
2792+
{
2793+
// Self goes first because our function types list self as the first
2794+
// parameter. Parameters are assigned to registers sequentially, so
2795+
// this ensures self -> x20 and remaining args -> x0-x7.
2796+
// Async context and error go last as implicit trailing registers.
2797+
vector<uint32_t> regs;
2798+
if (m_hasSelf)
2799+
regs.push_back(REG_X20);
2800+
regs.insert(regs.end(), {REG_X0, REG_X1, REG_X2, REG_X3, REG_X4, REG_X5, REG_X6, REG_X7});
2801+
if (m_isAsync)
2802+
regs.push_back(REG_X22);
2803+
if (m_throws)
2804+
regs.push_back(REG_X21);
2805+
return regs;
2806+
}
2807+
2808+
virtual vector<uint32_t> GetFloatArgumentRegisters() override
2809+
{
2810+
return vector<uint32_t> {REG_V0, REG_V1, REG_V2, REG_V3, REG_V4, REG_V5, REG_V6, REG_V7};
2811+
}
2812+
2813+
virtual vector<uint32_t> GetCallerSavedRegisters() override
2814+
{
2815+
vector<uint32_t> regs {REG_X0, REG_X1, REG_X2, REG_X3, REG_X4, REG_X5, REG_X6, REG_X7, REG_X8,
2816+
REG_X9, REG_X10, REG_X11, REG_X12, REG_X13, REG_X14, REG_X15, REG_X16, REG_X17, REG_X18,
2817+
REG_X30, REG_V0, REG_V1, REG_V2, REG_V3, REG_V4, REG_V5, REG_V6, REG_V7, REG_V16, REG_V17,
2818+
REG_V18, REG_V19, REG_V20, REG_V21, REG_V22, REG_V23, REG_V24, REG_V25, REG_V26, REG_V27,
2819+
REG_V28, REG_V29, REG_V30, REG_V31};
2820+
// When used as special registers, they are no longer callee-saved.
2821+
if (m_hasSelf)
2822+
regs.push_back(REG_X20);
2823+
if (m_throws)
2824+
regs.push_back(REG_X21);
2825+
if (m_isAsync)
2826+
regs.push_back(REG_X22);
2827+
return regs;
2828+
}
2829+
2830+
virtual vector<uint32_t> GetCalleeSavedRegisters() override
2831+
{
2832+
vector<uint32_t> regs {REG_X19, REG_X23, REG_X24, REG_X25, REG_X26, REG_X27, REG_X28, REG_X29};
2833+
// Only include x20/x21/x22 as callee-saved when they are NOT repurposed.
2834+
if (!m_hasSelf)
2835+
regs.push_back(REG_X20);
2836+
if (!m_throws)
2837+
regs.push_back(REG_X21);
2838+
if (!m_isAsync)
2839+
regs.push_back(REG_X22);
2840+
return regs;
2841+
}
2842+
2843+
virtual vector<uint32_t> GetImplicitlyDefinedRegisters() override
2844+
{
2845+
vector<uint32_t> regs;
2846+
// Throwing functions implicitly define x21 (the error register) on return.
2847+
if (m_throws)
2848+
regs.push_back(REG_X21);
2849+
return regs;
2850+
}
2851+
2852+
virtual uint32_t GetIntegerReturnValueRegister() override { return REG_X0; }
2853+
2854+
virtual uint32_t GetFloatReturnValueRegister() override { return REG_V0; }
2855+
2856+
virtual vector<uint32_t> GetRequiredArgumentRegisters() override
2857+
{
2858+
vector<uint32_t> regs;
2859+
if (m_hasSelf)
2860+
regs.push_back(REG_X20);
2861+
if (m_throws)
2862+
regs.push_back(REG_X21);
2863+
if (m_isAsync)
2864+
regs.push_back(REG_X22);
2865+
return regs;
2866+
}
2867+
2868+
virtual bool AreArgumentRegistersUsedForVarArgs() override { return false; }
2869+
2870+
virtual bool IsEligibleForHeuristics() override
2871+
{
2872+
return m_hasSelf || m_throws || m_isAsync;
2873+
}
2874+
};
2875+
2876+
27682877
#define PAGE(x) (uint32_t)((x) >> 12)
27692878
#define PAGE_OFF(x) (uint32_t)((x)&0xfff)
27702879
#define PAGE_NO_OFF(x) (uint32_t)((x)&0xFFFFF000)
@@ -3550,6 +3659,20 @@ extern "C"
35503659
conv = new AppleArm64CallingConvention(arm64);
35513660
arm64->RegisterCallingConvention(conv);
35523661

3662+
// Register Swift calling conventions (all combinations of self/throws/async).
3663+
for (int swiftFlags = 0; swiftFlags < 8; swiftFlags++)
3664+
{
3665+
bool hasSelf = (swiftFlags & 1) != 0;
3666+
bool throws = (swiftFlags & 2) != 0;
3667+
bool isAsync = (swiftFlags & 4) != 0;
3668+
string name = "swift";
3669+
if (hasSelf) name += "-self";
3670+
if (throws) name += "-throws";
3671+
if (isAsync) name += "-async";
3672+
conv = new SwiftArm64CallingConvention(arm64, name, hasSelf, throws, isAsync);
3673+
arm64->RegisterCallingConvention(conv);
3674+
}
3675+
35533676
for (uint32_t i = REG_X0; i <= REG_X28; i++)
35543677
{
35553678
if (i == REG_X16 || i == REG_X17 || i == REG_X18) // reserved by os.

plugins/workflow_swift/src/demangler/function_type.rs

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,51 @@ use swift_demangler::{
1010

1111
use super::type_reconstruction::{make_named_type_ref, TypeRefExt};
1212

13+
/// Architecture-specific register assignments for Swift implicit parameters.
14+
struct PlatformAbi {
15+
arch: CoreArchitecture,
16+
error_reg: &'static str,
17+
async_context_reg: &'static str,
18+
}
19+
20+
impl PlatformAbi {
21+
fn for_arch(arch: &CoreArchitecture) -> Option<Self> {
22+
match arch.name().as_ref() {
23+
"aarch64" => Some(Self {
24+
arch: *arch,
25+
error_reg: "x21",
26+
async_context_reg: "x22",
27+
}),
28+
_ => None,
29+
}
30+
}
31+
32+
fn error_location(&self) -> Option<Variable> {
33+
self.register_variable(self.error_reg)
34+
}
35+
36+
fn async_context_location(&self) -> Option<Variable> {
37+
self.register_variable(self.async_context_reg)
38+
}
39+
40+
fn register_variable(&self, name: &str) -> Option<Variable> {
41+
let reg = self.arch.register_by_name(name)?;
42+
Some(Variable::new(
43+
VariableSourceType::RegisterVariableSourceType,
44+
0,
45+
reg.id().0 as i64,
46+
))
47+
}
48+
}
49+
1350
/// Swift calling convention builder.
1451
///
1552
/// Tracks which implicit parameters (self, error, async context) are present,
1653
/// constructs the corresponding `FunctionParameter`s, and resolves the correct
1754
/// architecture-specific calling convention when building the final function type.
1855
struct CallingConvention {
1956
arch: CoreArchitecture,
57+
abi: Option<PlatformAbi>,
2058
flags: u8,
2159
leading_params: Vec<FunctionParameter>,
2260
trailing_params: Vec<FunctionParameter>,
@@ -30,6 +68,7 @@ impl CallingConvention {
3068
fn for_arch(arch: &CoreArchitecture) -> Self {
3169
Self {
3270
arch: *arch,
71+
abi: PlatformAbi::for_arch(arch),
3372
flags: 0,
3473
leading_params: Vec::new(),
3574
trailing_params: Vec::new(),
@@ -94,6 +133,8 @@ impl CallingConvention {
94133
/// Prepends `self` before `params` and appends error/async context after,
95134
/// then looks up the appropriate calling convention by name on the architecture.
96135
fn build_type(self, ret_type: &Type, params: Vec<FunctionParameter>) -> Ref<Type> {
136+
let cc_name = self.cc_name();
137+
97138
let mut all_params = self.leading_params;
98139
all_params.extend(params);
99140
all_params.extend(self.trailing_params);
@@ -103,7 +144,7 @@ impl CallingConvention {
103144
all_params.push(FunctionParameter {
104145
ty: error_ty.into(),
105146
name: "error".to_string(),
106-
location: None,
147+
location: self.abi.as_ref().and_then(|a| a.error_location()),
107148
});
108149
}
109150

@@ -112,11 +153,29 @@ impl CallingConvention {
112153
all_params.push(FunctionParameter {
113154
ty: ptr_ty.into(),
114155
name: "asyncContext".to_string(),
115-
location: None,
156+
location: self.abi.as_ref().and_then(|a| a.async_context_location()),
116157
});
117158
}
118159

119-
Type::function(ret_type, all_params, false)
160+
if let Some(cc) = self.arch.calling_convention_by_name(&cc_name) {
161+
Type::function_with_opts(ret_type, &all_params, false, cc, Conf::new(0, 0))
162+
} else {
163+
Type::function(ret_type, all_params, false)
164+
}
165+
}
166+
167+
fn cc_name(&self) -> String {
168+
let mut name = String::from("swift");
169+
if self.flags & Self::SELF != 0 {
170+
name.push_str("-self");
171+
}
172+
if self.flags & Self::THROWS != 0 {
173+
name.push_str("-throws");
174+
}
175+
if self.flags & Self::ASYNC != 0 {
176+
name.push_str("-async");
177+
}
178+
name
120179
}
121180
}
122181

rust/src/architecture.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1262,6 +1262,20 @@ pub trait ArchitectureExt: Architecture {
12621262
}
12631263
}
12641264

1265+
fn calling_convention_by_name(&self, name: &str) -> Option<Ref<CoreCallingConvention>> {
1266+
let name = name.to_cstr();
1267+
unsafe {
1268+
let result = NonNull::new(BNGetArchitectureCallingConventionByName(
1269+
self.as_ref().handle,
1270+
name.as_ptr(),
1271+
))?;
1272+
Some(CoreCallingConvention::ref_from_raw(
1273+
result.as_ptr(),
1274+
self.as_ref().handle(),
1275+
))
1276+
}
1277+
}
1278+
12651279
fn calling_conventions(&self) -> Array<CoreCallingConvention> {
12661280
unsafe {
12671281
let mut count = 0;

0 commit comments

Comments
 (0)