[llvm] [AArch64][PAuth] Fix return-address auth for swifttailcc with FPDiff > 0 (PR #203340)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Jun 11 09:50:04 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-backend-aarch64
Author: Jon Roelofs (jroelofs)
<details>
<summary>Changes</summary>
When a swifttailcc tail call has FPDiff > 0 (the caller received more stack argument space than the callee pops), the epilogue contains an SP adjustment to discard the leftover argument space. The existing code treated both FPDiff < 0 and FPDiff > 0 uniformly in a single 'FPDiff != 0' block, using AUTI[AB]1716 with a reconstructed entry-SP in x16 for both cases.
For FPDiff < 0 (callee pops more) that reconstruction is necessary and correct. For FPDiff > 0 it is wrong: by the time we enter the block the post-index LDP has already adjusted SP back to the frame base, but the 'add sp, sp, #N' argument pop has not yet run. Entry SP equals the current SP at that point, so AUTI[AB]SP would work directly, but instead the combined block bumped SP via StackOffset::getFixed(-FPDiff) which overshoots, and then emits AUTIA1716 with a wrong discriminator. Worse yet, the SP restore had already been emitted *before* the auth, leaving the live argument stack below SP and outside the red-zone during the authentication window.
Fix by splitting the block on the sign of ArgumentStackToRestore:
* `< 0`: reconstruct entry SP in x16, save LR in x17, authenticate with AUTI[AB]1716 (or AUTI[AB]171615 / PACM+AUTI[AB]1716 for PAuthLR).
* `> 0`: temporarily remove the 'add sp, sp, #N' SP-modifying instructions from before the auth instruction (SPMods shuffle), authenticate with AUTI[AB]SP (SP == entry SP at this point), then re-insert the SP adjustment afterward.
---
Full diff: https://github.com/llvm/llvm-project/pull/203340.diff
3 Files Affected:
- (modified) llvm/lib/Target/AArch64/AArch64PointerAuth.cpp (+38-8)
- (modified) llvm/test/CodeGen/AArch64/pauth-lr-tail-call-fpdiff.ll (+6-6)
- (added) llvm/test/CodeGen/AArch64/swifttail-ptrauth.ll (+202)
``````````diff
diff --git a/llvm/lib/Target/AArch64/AArch64PointerAuth.cpp b/llvm/lib/Target/AArch64/AArch64PointerAuth.cpp
index 252bd696665db..1fda99e276f6a 100644
--- a/llvm/lib/Target/AArch64/AArch64PointerAuth.cpp
+++ b/llvm/lib/Target/AArch64/AArch64PointerAuth.cpp
@@ -186,21 +186,24 @@ void AArch64PointerAuthImpl::authenticateLR(
return;
}
- // When FPDiff != 0 (tail call with callee-popped stack arg space), SP has
- // been adjusted and no longer matches the entry SP used as the signing
- // modifier. We must reconstruct entry SP for authentication.
auto &AFL = *static_cast<const AArch64FrameLowering *>(
MF.getSubtarget().getFrameLowering());
- if (int64_t FPDiff = AFL.getArgumentStackToRestore(MF, MBB)) {
- // Use AUTI[AB]1716 variants: x17=LR, x16=entry_SP.
+ int64_t ArgumentStackToRestore = AFL.getArgumentStackToRestore(MF, MBB);
+
+ // When ArgumentStackToRestore < 0, the tail callee pops more argument space
+ // than this function received, so after the frame teardown SP is below the
+ // entry SP used as the signing modifier. Reconstruct entry SP in x16 and
+ // authenticate using AUTI[AB]1716 (x17=LR, x16=entry_SP).
+ if (ArgumentStackToRestore < 0) {
+ emitFrameOffset(MBB, MBBI, DL, AArch64::X16, AArch64::SP,
+ StackOffset::getFixed(-ArgumentStackToRestore), TII,
+ MachineInstr::FrameDestroy);
+
BuildMI(MBB, MBBI, DL, TII->get(AArch64::ORRXrs), AArch64::X17)
.addReg(AArch64::XZR)
.addReg(AArch64::LR)
.addImm(0)
.setMIFlag(MachineInstr::FrameDestroy);
- emitFrameOffset(MBB, MBBI, DL, AArch64::X16, AArch64::SP,
- StackOffset::getFixed(-FPDiff), TII,
- MachineInstr::FrameDestroy);
if (MFnI->branchProtectionPAuthLR() && Subtarget->hasPAuthLR()) {
assert(PACSym && "No PAC instruction to refer to");
@@ -241,6 +244,29 @@ void AArch64PointerAuthImpl::authenticateLR(
return;
}
+ // When ArgumentStackToRestore > 0, this function received more argument
+ // space than the tail callee pops. The epilogue contains an SP adjustment
+ // (e.g. "add sp, sp, #N") to discard the leftover argument space. We must
+ // authenticate *before* that adjustment so that AUTI[AB]SP sees the entry
+ // SP discriminator. Move any such SP-adjusting instructions to after the
+ // authentication instruction.
+ //
+ // We cannot simply bump SP first and then use AUTI[AB]SP with the bumped
+ // value, because the live arguments would fall below SP and potentially
+ // outside the red-zone.
+ SmallVector<MachineInstr *, 2> SPMods;
+ if (ArgumentStackToRestore > 0) {
+ for (auto I = MBBI; I->getFlag(MachineInstr::FrameDestroy); --I) {
+ if ((I->getOpcode() == AArch64::ADDXri ||
+ I->getOpcode() == AArch64::SUBXri) &&
+ I->getOperand(0).getReg() == AArch64::SP &&
+ I->getOperand(1).getReg() == AArch64::SP)
+ SPMods.push_back(&*I);
+ }
+ }
+ for (auto *MI : SPMods)
+ MI->removeFromParent();
+
if (MFnI->branchProtectionPAuthLR() && Subtarget->hasPAuthLR()) {
assert(PACSym && "No PAC instruction to refer to");
emitPACCFI(MBB, MBBI, MachineInstr::FrameDestroy, EmitAsyncCFI);
@@ -251,6 +277,7 @@ void AArch64PointerAuthImpl::authenticateLR(
} else {
if (MFnI->branchProtectionPAuthLR()) {
emitPACSymOffsetIntoReg(*TII, MBB, MBBI, DL, PACSym, AArch64::X16);
+
BuildMI(MBB, MBBI, DL, TII->get(AArch64::PACM))
.setMIFlag(MachineInstr::FrameDestroy);
emitPACCFI(MBB, MBBI, MachineInstr::FrameDestroy, EmitAsyncCFI);
@@ -266,6 +293,9 @@ void AArch64PointerAuthImpl::authenticateLR(
BuildMI(MBB, MBBI, DL, TII->get(AArch64::SEH_PACSignLR))
.setMIFlag(MachineInstr::FrameDestroy);
}
+
+ for (auto *MI : SPMods)
+ MBB.insert(MBBI, MI);
}
unsigned llvm::AArch64PAuth::getCheckerSizeInBytes(AuthCheckMethod Method) {
diff --git a/llvm/test/CodeGen/AArch64/pauth-lr-tail-call-fpdiff.ll b/llvm/test/CodeGen/AArch64/pauth-lr-tail-call-fpdiff.ll
index f26ff897cf352..67bff0b15fd49 100644
--- a/llvm/test/CodeGen/AArch64/pauth-lr-tail-call-fpdiff.ll
+++ b/llvm/test/CodeGen/AArch64/pauth-lr-tail-call-fpdiff.ll
@@ -60,8 +60,8 @@ define swifttailcc void @tail_call_fpdiff_a_key(ptr swiftasync %ctx) "branch-pro
; CHECK-NEXT: .cfi_def_cfa_offset 16
; CHECK-NEXT: .cfi_restore w30
; CHECK-NEXT: .cfi_restore w29
-; CHECK-NEXT: mov x17, x30
; CHECK-NEXT: add x16, sp, #16
+; CHECK-NEXT: mov x17, x30
; COMPAT-NEXT: adrp x15, .Ltmp0
; COMPAT-NEXT: add x15, x15, :lo12:.Ltmp0
@@ -138,8 +138,8 @@ define swifttailcc void @tail_call_fpdiff_b_key(ptr swiftasync %ctx) "branch-pro
; CHECK-NEXT: .cfi_def_cfa_offset 16
; CHECK-NEXT: .cfi_restore w30
; CHECK-NEXT: .cfi_restore w29
-; CHECK-NEXT: mov x17, x30
; CHECK-NEXT: add x16, sp, #16
+; CHECK-NEXT: mov x17, x30
; COMPAT-NEXT: adrp x15, .Ltmp1
; COMPAT-NEXT: add x15, x15, :lo12:.Ltmp1
@@ -241,7 +241,7 @@ define swifttailcc void @tail_call_no_fpdiff_b_key(ptr swiftasync %ctx) "branch-
; COMPAT-NEXT: hint #27
; V83A-NEXT: hint #39
-; V83A-NEXT: .cfi_negate_ra_state_with_pc
+; V83A-NEXT: .cfi_negate_ra_state_with_pc
; V83A-NEXT: .Ltmp3:
; V83A-NEXT: pacibsp
@@ -341,8 +341,8 @@ define swifttailcc void @indirect_tail_call_fpdiff_a_key(ptr swiftasync %ctx, pt
; CHECK-NEXT: .cfi_def_cfa_offset 16
; CHECK-NEXT: .cfi_restore w30
; CHECK-NEXT: .cfi_restore w29
-; CHECK-NEXT: mov x17, x30
; CHECK-NEXT: add x16, sp, #16
+; CHECK-NEXT: mov x17, x30
; COMPAT-NEXT: adrp x15, .Ltmp4
; COMPAT-NEXT: add x15, x15, :lo12:.Ltmp4
@@ -420,8 +420,8 @@ define swifttailcc void @indirect_tail_call_fpdiff_b_key(ptr swiftasync %ctx, pt
; CHECK-NEXT: .cfi_def_cfa_offset 16
; CHECK-NEXT: .cfi_restore w30
; CHECK-NEXT: .cfi_restore w29
-; CHECK-NEXT: mov x17, x30
; CHECK-NEXT: add x16, sp, #16
+; CHECK-NEXT: mov x17, x30
; COMPAT-NEXT: adrp x15, .Ltmp5
; COMPAT-NEXT: add x15, x15, :lo12:.Ltmp5
@@ -523,7 +523,7 @@ define swifttailcc void @indirect_tail_call_no_fpdiff_b_key(ptr swiftasync %ctx,
; COMPAT-NEXT: hint #27
; V83A-NEXT: hint #39
-; V83A-NEXT: .cfi_negate_ra_state_with_pc
+; V83A-NEXT: .cfi_negate_ra_state_with_pc
; V83A-NEXT: .Ltmp7:
; V83A-NEXT: pacibsp
diff --git a/llvm/test/CodeGen/AArch64/swifttail-ptrauth.ll b/llvm/test/CodeGen/AArch64/swifttail-ptrauth.ll
new file mode 100644
index 0000000000000..0c018a5738753
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/swifttail-ptrauth.ll
@@ -0,0 +1,202 @@
+; RUN: llc -mtriple=aarch64 < %s | FileCheck --check-prefixes=CHECK,COMPAT %s
+; RUN: llc -mtriple=aarch64 -mattr=v8.3a < %s | FileCheck --check-prefixes=CHECK,V83A %s
+; RUN: llc -mtriple=aarch64 -mattr=v9a -mattr=pauth-lr < %s | FileCheck --check-prefixes=CHECK,V9A %s
+; RUN: sed 's/"branch-protection-pauth-lr" //g' %s \
+; RUN: | llc -mtriple=aarch64 -mattr=v9a \
+; RUN: | FileCheck --check-prefixes=CHECK,PAUTH %s
+
+; These tests cover the interaction between swifttailcc and return-address
+; signing. The key invariant is that AUTIASP/AUTIBSP uses the current SP as the
+; discriminator, which must equal the entry SP at the point of signing. When a
+; tail call involves a stack-argument size mismatch (FPDiff != 0) this invariant
+; can be violated if we're not careful.
+
+declare swifttailcc void @callee_stack0()
+declare swifttailcc void @callee_stack8([8 x i64], i64)
+
+; FPDiff == 0: SP is unchanged at epilogue. autiasp is safe.
+define swifttailcc void @caller_to0_from0() "branch-protection-pauth-lr" "sign-return-address"="all" "frame-pointer"="all" nounwind uwtable(async) {
+; CHECK-LABEL: caller_to0_from0:
+; CHECK: // %bb.0:
+
+; COMPAT-NEXT: hint #39
+; COMPAT-NEXT: .cfi_negate_ra_state_with_pc
+; COMPAT-NEXT: .Ltmp0:
+; COMPAT-NEXT: hint #25
+
+; V83A-NEXT: hint #39
+; V83A-NEXT: .cfi_negate_ra_state_with_pc
+; V83A-NEXT: .Ltmp0:
+; V83A-NEXT: paciasp
+
+; V9A-NEXT: .cfi_negate_ra_state_with_pc
+; V9A-NEXT: .Ltmp0:
+; V9A-NEXT: paciasppc
+
+; PAUTH-NEXT: paciasp
+; PAUTH-NEXT: .cfi_negate_ra_state
+
+; CHECK-NEXT: stp x29, x30, [sp, #-16]! // 16-byte Folded Spill
+; CHECK-NEXT: .cfi_def_cfa_offset 16
+; CHECK-NEXT: mov x29, sp
+; CHECK-NEXT: .cfi_def_cfa w29, 16
+; CHECK-NEXT: .cfi_offset w30, -8
+; CHECK-NEXT: .cfi_offset w29, -16
+; CHECK-NEXT: .cfi_def_cfa wsp, 16
+; CHECK-NEXT: ldp x29, x30, [sp], #16 // 16-byte Folded Reload
+; CHECK-NEXT: .cfi_def_cfa_offset 0
+; CHECK-NEXT: .cfi_restore w30
+; CHECK-NEXT: .cfi_restore w29
+
+; COMPAT-NEXT: adrp x16, .Ltmp0
+; COMPAT-NEXT: add x16, x16, :lo12:.Ltmp0
+; COMPAT-NEXT: hint #39
+; COMPAT-NEXT: .cfi_negate_ra_state_with_pc
+; COMPAT-NEXT: hint #29
+
+; V83A-NEXT: adrp x16, .Ltmp0
+; V83A-NEXT: add x16, x16, :lo12:.Ltmp0
+; V83A-NEXT: hint #39
+; V83A-NEXT: .cfi_negate_ra_state_with_pc
+; V83A-NEXT: autiasp
+
+; V9A-NEXT: .cfi_negate_ra_state_with_pc
+; V9A-NEXT: autiasppc .Ltmp0
+
+; PAUTH-NEXT: autiasp
+; PAUTH-NEXT: .cfi_negate_ra_state
+
+; CHECK-NEXT: b callee_stack0
+ tail call swifttailcc void @callee_stack0()
+ ret void
+}
+
+; FPDiff > 0: caller received 8 bytes of stack args, callee receives 0.
+; SP has been bumped by the post-index LDP. We must authenticate with the
+; entry SP discriminator *before* popping the leftover argument space.
+; The correct sequence is: autiasp (SP == entry SP here), then add sp, #16.
+;
+; Key point: we must NOT bump SP before autiasp, because that leaves the
+; live argument space below SP and potentially outside the red-zone.
+define swifttailcc void @caller_to0_from8([8 x i64], i64) "branch-protection-pauth-lr" "sign-return-address"="all" "frame-pointer"="all" uwtable(async) {
+; CHECK-LABEL: caller_to0_from8:
+; CHECK: // %bb.0:
+
+; COMPAT-NEXT: hint #39
+; COMPAT-NEXT: .cfi_negate_ra_state_with_pc
+; COMPAT-NEXT: .Ltmp1:
+; COMPAT-NEXT: hint #25
+
+; V83A-NEXT: hint #39
+; V83A-NEXT: .cfi_negate_ra_state_with_pc
+; V83A-NEXT: .Ltmp1:
+; V83A-NEXT: paciasp
+
+; V9A-NEXT: .cfi_negate_ra_state_with_pc
+; V9A-NEXT: .Ltmp1:
+; V9A-NEXT: paciasppc
+
+; PAUTH-NEXT: paciasp
+; PAUTH-NEXT: .cfi_negate_ra_state
+
+; CHECK-NEXT: stp x29, x30, [sp, #-16]! // 16-byte Folded Spill
+; CHECK-NEXT: .cfi_def_cfa_offset 16
+; CHECK-NEXT: mov x29, sp
+; CHECK-NEXT: .cfi_def_cfa w29, 16
+; CHECK-NEXT: .cfi_offset w30, -8
+; CHECK-NEXT: .cfi_offset w29, -16
+; CHECK-NEXT: .cfi_def_cfa wsp, 16
+; CHECK-NEXT: ldp x29, x30, [sp], #16 // 16-byte Folded Reload
+; CHECK-NEXT: .cfi_def_cfa_offset 0
+; CHECK-NEXT: .cfi_def_cfa_offset -16
+; CHECK-NEXT: .cfi_restore w30
+; CHECK-NEXT: .cfi_restore w29
+
+; COMPAT-NEXT: adrp x16, .Ltmp1
+; COMPAT-NEXT: add x16, x16, :lo12:.Ltmp1
+; COMPAT-NEXT: hint #39
+; COMPAT-NEXT: .cfi_negate_ra_state_with_pc
+; COMPAT-NEXT: hint #29
+
+; V83A-NEXT: adrp x16, .Ltmp1
+; V83A-NEXT: add x16, x16, :lo12:.Ltmp1
+; V83A-NEXT: hint #39
+; V83A-NEXT: .cfi_negate_ra_state_with_pc
+; V83A-NEXT: autiasp
+
+; V9A-NEXT: .cfi_negate_ra_state_with_pc
+; V9A-NEXT: autiasppc .Ltmp1
+
+; PAUTH-NEXT: autiasp
+; PAUTH-NEXT: .cfi_negate_ra_state
+
+; CHECK-NEXT: add sp, sp, #16
+; CHECK-NEXT: b callee_stack0
+ tail call swifttailcc void @callee_stack0()
+ ret void
+}
+
+; FPDiff < 0: callee receives 8 bytes of stack args, caller received 0.
+; Entry SP is above current SP; reconstruct it in x16 and use autia1716.
+define swifttailcc void @caller_to8_from0() "branch-protection-pauth-lr" "sign-return-address"="all" "frame-pointer"="all" uwtable(async) {
+; CHECK-LABEL: caller_to8_from0:
+; CHECK: // %bb.0:
+
+; COMPAT-NEXT: hint #39
+; COMPAT-NEXT: .cfi_negate_ra_state_with_pc
+; COMPAT-NEXT: .Ltmp2:
+; COMPAT-NEXT: hint #25
+
+; V83A-NEXT: hint #39
+; V83A-NEXT: .cfi_negate_ra_state_with_pc
+; V83A-NEXT: .Ltmp2:
+; V83A-NEXT: paciasp
+
+; V9A-NEXT: .cfi_negate_ra_state_with_pc
+; V9A-NEXT: .Ltmp2:
+; V9A-NEXT: paciasppc
+
+; PAUTH-NEXT: paciasp
+; PAUTH-NEXT: .cfi_negate_ra_state
+
+; CHECK-NEXT: stp x29, x30, [sp, #-32]! // 16-byte Folded Spill
+; CHECK-NEXT: .cfi_def_cfa_offset 32
+; CHECK-NEXT: mov x29, sp
+; CHECK-NEXT: .cfi_def_cfa w29, 32
+; CHECK-NEXT: .cfi_offset w30, -24
+; CHECK-NEXT: .cfi_offset w29, -32
+; CHECK-NEXT: mov w8, #42 // =0x2a
+; CHECK-NEXT: str x8, [x29, #16]
+; CHECK-NEXT: .cfi_def_cfa wsp, 32
+; CHECK-NEXT: ldp x29, x30, [sp], #16 // 16-byte Folded Reload
+; CHECK-NEXT: .cfi_def_cfa_offset 16
+; CHECK-NEXT: .cfi_restore w30
+; CHECK-NEXT: .cfi_restore w29
+; CHECK-NEXT: add x16, sp, #16
+; CHECK-NEXT: mov x17, x30
+
+; COMPAT-NEXT: adrp x15, .Ltmp2
+; COMPAT-NEXT: add x15, x15, :lo12:.Ltmp2
+; COMPAT-NEXT: hint #39
+; COMPAT-NEXT: .cfi_negate_ra_state_with_pc
+; COMPAT-NEXT: hint #12
+
+; V83A-NEXT: adrp x15, .Ltmp2
+; V83A-NEXT: add x15, x15, :lo12:.Ltmp2
+; V83A-NEXT: hint #39
+; V83A-NEXT: .cfi_negate_ra_state_with_pc
+; V83A-NEXT: autia1716
+
+; V9A-NEXT: adrp x15, .Ltmp2
+; V9A-NEXT: add x15, x15, :lo12:.Ltmp2
+; V9A-NEXT: .cfi_negate_ra_state_with_pc
+; V9A-NEXT: autia171615
+
+; PAUTH-NEXT: autia1716
+; PAUTH-NEXT: .cfi_negate_ra_state
+
+; CHECK-NEXT: mov x30, x17
+; CHECK-NEXT: b callee_stack8
+ tail call swifttailcc void @callee_stack8([8 x i64] undef, i64 42)
+ ret void
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/203340
More information about the llvm-commits
mailing list