[llvm] [AArch64] Mark X16 as clobbered in PAUTH_EPILOGUE for hint-based PAuthLR (PR #175991)

Victor Campos via llvm-commits llvm-commits at lists.llvm.org
Fri Feb 20 02:00:18 PST 2026


https://github.com/vhscampos updated https://github.com/llvm/llvm-project/pull/175991

>From 0e026698d547e466bef6ce50d2e3332265fe2099 Mon Sep 17 00:00:00 2001
From: Victor Campos <victor.campos at arm.com>
Date: Tue, 13 Jan 2026 20:26:30 +0000
Subject: [PATCH 1/2] [AArch64] Mark X16 as clobbered in PAUTH_EPILOGUE for
 hint-based PAuthLR

When users request branch protection with PAuthLR on targets that do not
support the PAuthLR instructions, the PAUTH_EPILOGUE falls back to using
hint-space instructions. This fallback sequence uses X16 as a temporary
register, but X16 was not listed in the clobber set.

Because Speculative Load Hardening uses X16, this omission made SLH
incompatible with this PAUTH_EPILOGUE path.

Mark X16 as clobbered so the compiler does not assume X16 is preserved across
the epilogue, restoring compatibility with Speculative Load Hardening and
avoiding incorrect register liveness assumptions. The clobber is added in C++
rather than TableGen, as X16 is only clobbered when PAuthLR is requested as a
branch protection variation and should not be treated as clobbered
unconditionally.
---
 .../Target/AArch64/AArch64FrameLowering.cpp   |   3 +-
 llvm/lib/Target/AArch64/AArch64InstrInfo.cpp  |  16 ++-
 llvm/lib/Target/AArch64/AArch64InstrInfo.h    |   6 +
 llvm/lib/Target/AArch64/AArch64InstrInfo.td   |   3 +
 .../AArch64/AArch64PrologueEpilogue.cpp       |   7 +-
 .../sign-return-address-pauthlr-slh.ll        | 103 ++++++++++++++++++
 6 files changed, 128 insertions(+), 10 deletions(-)
 create mode 100644 llvm/test/CodeGen/AArch64/sign-return-address-pauthlr-slh.ll

diff --git a/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp b/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
index e524c98984ee7..d26c81fe0866b 100644
--- a/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
+++ b/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
@@ -1197,8 +1197,7 @@ void AArch64FrameLowering::emitPacRetPlusLeafHardening(
     if (MBBI != MBB.end())
       DL = MBBI->getDebugLoc();
 
-    BuildMI(MBB, MBBI, DL, TII->get(AArch64::PAUTH_EPILOGUE))
-        .setMIFlag(MachineInstr::FrameDestroy);
+    TII->createPauthEpilogueInstr(MBB, DL);
   };
 
   // This should be in sync with PEIImpl::calculateSaveRestoreBlocks.
diff --git a/llvm/lib/Target/AArch64/AArch64InstrInfo.cpp b/llvm/lib/Target/AArch64/AArch64InstrInfo.cpp
index 755db8790003e..e49c7dc167f63 100644
--- a/llvm/lib/Target/AArch64/AArch64InstrInfo.cpp
+++ b/llvm/lib/Target/AArch64/AArch64InstrInfo.cpp
@@ -10719,14 +10719,13 @@ void AArch64InstrInfo::fixupPostOutline(MachineBasicBlock &MBB) const {
 static void signOutlinedFunction(MachineFunction &MF, MachineBasicBlock &MBB,
                                  const AArch64InstrInfo *TII,
                                  bool ShouldSignReturnAddr) {
+  assert(&MF.front() == &MBB);
   if (!ShouldSignReturnAddr)
     return;
 
   BuildMI(MBB, MBB.begin(), DebugLoc(), TII->get(AArch64::PAUTH_PROLOGUE))
       .setMIFlag(MachineInstr::FrameSetup);
-  BuildMI(MBB, MBB.getFirstInstrTerminator(), DebugLoc(),
-          TII->get(AArch64::PAUTH_EPILOGUE))
-      .setMIFlag(MachineInstr::FrameDestroy);
+  TII->createPauthEpilogueInstr(MBB, DebugLoc());
 }
 
 void AArch64InstrInfo::buildOutlinedFrame(
@@ -11217,6 +11216,17 @@ unsigned llvm::getBLRCallOpcode(const MachineFunction &MF) {
     return AArch64::BLR;
 }
 
+void AArch64InstrInfo::createPauthEpilogueInstr(MachineBasicBlock &MBB,
+                                                DebugLoc DL) const {
+  MachineBasicBlock::iterator InsertPt = MBB.getFirstTerminator();
+  auto Builder = BuildMI(MBB, InsertPt, DL, get(AArch64::PAUTH_EPILOGUE))
+                     .setMIFlag(MachineInstr::FrameDestroy);
+
+  const auto *AFI = MBB.getParent()->getInfo<AArch64FunctionInfo>();
+  if (AFI->branchProtectionPAuthLR() && !Subtarget.hasPAuthLR())
+    Builder.addReg(AArch64::X16, RegState::ImplicitDefine);
+}
+
 MachineBasicBlock::iterator
 AArch64InstrInfo::probedStackAlloc(MachineBasicBlock::iterator MBBI,
                                    Register TargetReg, bool FrameSetup) const {
diff --git a/llvm/lib/Target/AArch64/AArch64InstrInfo.h b/llvm/lib/Target/AArch64/AArch64InstrInfo.h
index 2ccde3e661de5..e40e37b0c526f 100644
--- a/llvm/lib/Target/AArch64/AArch64InstrInfo.h
+++ b/llvm/lib/Target/AArch64/AArch64InstrInfo.h
@@ -571,6 +571,12 @@ class AArch64InstrInfo final : public AArch64GenInstrInfo {
                                                Register TargetReg,
                                                bool FrameSetup) const;
 
+  /// Insert a `PAUTH_EPILOGUE` pseudo before the first terminator in \p MBB to
+  /// authenticate the return address. Adds an implicit def of X16 when the
+  /// branch protection uses PAuthLR but the subtarget lacks PAuthLR
+  /// instructions.
+  void createPauthEpilogueInstr(MachineBasicBlock &MBB, DebugLoc DL) const;
+
 #define GET_INSTRINFO_HELPER_DECLS
 #include "AArch64GenInstrInfo.inc"
 
diff --git a/llvm/lib/Target/AArch64/AArch64InstrInfo.td b/llvm/lib/Target/AArch64/AArch64InstrInfo.td
index 96d45d4d48652..209f8bfb8e595 100644
--- a/llvm/lib/Target/AArch64/AArch64InstrInfo.td
+++ b/llvm/lib/Target/AArch64/AArch64InstrInfo.td
@@ -2073,6 +2073,9 @@ def PAUTH_PROLOGUE : Pseudo<(outs), (ins), []>, Sched<[]> {
 // Insertion point of LR authentication code.
 // The RET terminator of the containing machine basic block may be replaced
 // with a combined RETA(A|B) instruction when rewriting this Pseudo.
+//
+// In the case where PAuthLR is requested but the subtarget lacks PAuthLR instructions,
+// this pseudo also implicitly defines X16. This is done in C++ code.
 def PAUTH_EPILOGUE : Pseudo<(outs), (ins), []>, Sched<[]>;
 }
 
diff --git a/llvm/lib/Target/AArch64/AArch64PrologueEpilogue.cpp b/llvm/lib/Target/AArch64/AArch64PrologueEpilogue.cpp
index 37a5f5b8b1c62..230ea947c5e54 100644
--- a/llvm/lib/Target/AArch64/AArch64PrologueEpilogue.cpp
+++ b/llvm/lib/Target/AArch64/AArch64PrologueEpilogue.cpp
@@ -1774,11 +1774,8 @@ void AArch64EpilogueEmitter::finalizeEpilogue() const {
   if (AFI->shouldSignReturnAddress(MF)) {
     // If pac-ret+leaf is in effect, PAUTH_EPILOGUE pseudo instructions
     // are inserted by emitPacRetPlusLeafHardening().
-    if (!AFL.shouldSignReturnAddressEverywhere(MF)) {
-      BuildMI(MBB, MBB.getFirstTerminator(), DL,
-              TII->get(AArch64::PAUTH_EPILOGUE))
-          .setMIFlag(MachineInstr::FrameDestroy);
-    }
+    if (!AFL.shouldSignReturnAddressEverywhere(MF))
+      TII->createPauthEpilogueInstr(MBB, DL);
     // AArch64PointerAuth pass will insert SEH_PACSignLR
     HasWinCFI |= NeedsWinCFI;
   }
diff --git a/llvm/test/CodeGen/AArch64/sign-return-address-pauthlr-slh.ll b/llvm/test/CodeGen/AArch64/sign-return-address-pauthlr-slh.ll
new file mode 100644
index 0000000000000..f74e369647730
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/sign-return-address-pauthlr-slh.ll
@@ -0,0 +1,103 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc -mtriple=aarch64-none-linux-gnu %s -o - | FileCheck %s --check-prefix=CHECK-NO-PAUTH
+; RUN: llc -mtriple=aarch64-none-linux-gnu -mattr=+pauth %s -o - | FileCheck %s --check-prefix=CHECK-PAUTH
+; RUN: llc -mtriple=aarch64-none-linux-gnu -mattr=+pauth-lr %s -o - | FileCheck %s --check-prefix=CHECK-PAUTHLR
+; RUN: llc -mtriple=aarch64-none-linux-gnu -mattr=+pauth,+pauth-lr %s -o - | FileCheck %s --check-prefix=CHECK-PAUTH-PAUTHLR
+
+define i32 @f() #0 {
+; CHECK-NO-PAUTH-LABEL: f:
+; CHECK-NO-PAUTH:       // %bb.0: // %entry
+; CHECK-NO-PAUTH-NEXT:    dsb sy
+; CHECK-NO-PAUTH-NEXT:    isb
+; CHECK-NO-PAUTH-NEXT:    hint #39
+; CHECK-NO-PAUTH-NEXT:    .cfi_negate_ra_state_with_pc
+; CHECK-NO-PAUTH-NEXT:  .Ltmp0:
+; CHECK-NO-PAUTH-NEXT:    hint #25
+; CHECK-NO-PAUTH-NEXT:    str x30, [sp, #-16]! // 8-byte Folded Spill
+; CHECK-NO-PAUTH-NEXT:    .cfi_def_cfa_offset 16
+; CHECK-NO-PAUTH-NEXT:    .cfi_offset w30, -16
+; CHECK-NO-PAUTH-NEXT:    hint #7
+; CHECK-NO-PAUTH-NEXT:    mov w0, wzr
+; CHECK-NO-PAUTH-NEXT:    //APP
+; CHECK-NO-PAUTH-NEXT:    //NO_APP
+; CHECK-NO-PAUTH-NEXT:    ldr x30, [sp], #16 // 8-byte Folded Reload
+; CHECK-NO-PAUTH-NEXT:    adrp x16, .Ltmp0
+; CHECK-NO-PAUTH-NEXT:    add x16, x16, :lo12:.Ltmp0
+; CHECK-NO-PAUTH-NEXT:    hint #39
+; CHECK-NO-PAUTH-NEXT:    hint #29
+; CHECK-NO-PAUTH-NEXT:    ret
+;
+; CHECK-PAUTH-LABEL: f:
+; CHECK-PAUTH:       // %bb.0: // %entry
+; CHECK-PAUTH-NEXT:    dsb sy
+; CHECK-PAUTH-NEXT:    isb
+; CHECK-PAUTH-NEXT:    hint #39
+; CHECK-PAUTH-NEXT:    .cfi_negate_ra_state_with_pc
+; CHECK-PAUTH-NEXT:  .Ltmp0:
+; CHECK-PAUTH-NEXT:    paciasp
+; CHECK-PAUTH-NEXT:    str x30, [sp, #-16]! // 8-byte Folded Spill
+; CHECK-PAUTH-NEXT:    .cfi_def_cfa_offset 16
+; CHECK-PAUTH-NEXT:    .cfi_offset w30, -16
+; CHECK-PAUTH-NEXT:    xpaci x30
+; CHECK-PAUTH-NEXT:    mov w0, wzr
+; CHECK-PAUTH-NEXT:    //APP
+; CHECK-PAUTH-NEXT:    //NO_APP
+; CHECK-PAUTH-NEXT:    ldr x30, [sp], #16 // 8-byte Folded Reload
+; CHECK-PAUTH-NEXT:    adrp x16, .Ltmp0
+; CHECK-PAUTH-NEXT:    add x16, x16, :lo12:.Ltmp0
+; CHECK-PAUTH-NEXT:    hint #39
+; CHECK-PAUTH-NEXT:    retaa
+;
+; CHECK-PAUTHLR-LABEL: f:
+; CHECK-PAUTHLR:       // %bb.0: // %entry
+; CHECK-PAUTHLR-NEXT:    cmp sp, #0
+; CHECK-PAUTHLR-NEXT:    csetm x16, ne
+; CHECK-PAUTHLR-NEXT:    .cfi_negate_ra_state_with_pc
+; CHECK-PAUTHLR-NEXT:  .Ltmp0:
+; CHECK-PAUTHLR-NEXT:    paciasppc
+; CHECK-PAUTHLR-NEXT:    str x30, [sp, #-16]! // 8-byte Folded Spill
+; CHECK-PAUTHLR-NEXT:    .cfi_def_cfa_offset 16
+; CHECK-PAUTHLR-NEXT:    .cfi_offset w30, -16
+; CHECK-PAUTHLR-NEXT:    hint #7
+; CHECK-PAUTHLR-NEXT:    mov w0, wzr
+; CHECK-PAUTHLR-NEXT:    //APP
+; CHECK-PAUTHLR-NEXT:    //NO_APP
+; CHECK-PAUTHLR-NEXT:    ldr x30, [sp], #16 // 8-byte Folded Reload
+; CHECK-PAUTHLR-NEXT:    and x30, x30, x16
+; CHECK-PAUTHLR-NEXT:    csdb
+; CHECK-PAUTHLR-NEXT:    mov x1, sp
+; CHECK-PAUTHLR-NEXT:    autiasppc .Ltmp0
+; CHECK-PAUTHLR-NEXT:    and x1, x1, x16
+; CHECK-PAUTHLR-NEXT:    mov sp, x1
+; CHECK-PAUTHLR-NEXT:    ret
+;
+; CHECK-PAUTH-PAUTHLR-LABEL: f:
+; CHECK-PAUTH-PAUTHLR:       // %bb.0: // %entry
+; CHECK-PAUTH-PAUTHLR-NEXT:    cmp sp, #0
+; CHECK-PAUTH-PAUTHLR-NEXT:    csetm x16, ne
+; CHECK-PAUTH-PAUTHLR-NEXT:    .cfi_negate_ra_state_with_pc
+; CHECK-PAUTH-PAUTHLR-NEXT:  .Ltmp0:
+; CHECK-PAUTH-PAUTHLR-NEXT:    paciasppc
+; CHECK-PAUTH-PAUTHLR-NEXT:    str x30, [sp, #-16]! // 8-byte Folded Spill
+; CHECK-PAUTH-PAUTHLR-NEXT:    .cfi_def_cfa_offset 16
+; CHECK-PAUTH-PAUTHLR-NEXT:    .cfi_offset w30, -16
+; CHECK-PAUTH-PAUTHLR-NEXT:    xpaci x30
+; CHECK-PAUTH-PAUTHLR-NEXT:    mov w0, wzr
+; CHECK-PAUTH-PAUTHLR-NEXT:    //APP
+; CHECK-PAUTH-PAUTHLR-NEXT:    //NO_APP
+; CHECK-PAUTH-PAUTHLR-NEXT:    ldr x30, [sp], #16 // 8-byte Folded Reload
+; CHECK-PAUTH-PAUTHLR-NEXT:    and x30, x30, x16
+; CHECK-PAUTH-PAUTHLR-NEXT:    csdb
+; CHECK-PAUTH-PAUTHLR-NEXT:    mov x1, sp
+; CHECK-PAUTH-PAUTHLR-NEXT:    and x1, x1, x16
+; CHECK-PAUTH-PAUTHLR-NEXT:    mov sp, x1
+; CHECK-PAUTH-PAUTHLR-NEXT:    retaasppc .Ltmp0
+entry:
+  %0 = tail call ptr @llvm.returnaddress(i32 0)
+  tail call void asm sideeffect "", "r"(ptr %0)
+  ret i32 0
+}
+
+declare ptr @llvm.returnaddress(i32 immarg)
+
+attributes #0 = { speculative_load_hardening "branch-protection-pauth-lr" "sign-return-address"="all" "sign-return-address-key"="a_key" }

>From 9a43071b4658eec9c82d6681641398d44b6cf62a Mon Sep 17 00:00:00 2001
From: Victor Campos <victor.campos at arm.com>
Date: Fri, 20 Feb 2026 09:58:11 +0000
Subject: [PATCH 2/2] Remove assert

---
 llvm/lib/Target/AArch64/AArch64InstrInfo.cpp | 1 -
 1 file changed, 1 deletion(-)

diff --git a/llvm/lib/Target/AArch64/AArch64InstrInfo.cpp b/llvm/lib/Target/AArch64/AArch64InstrInfo.cpp
index e49c7dc167f63..e0a9503ca7ef2 100644
--- a/llvm/lib/Target/AArch64/AArch64InstrInfo.cpp
+++ b/llvm/lib/Target/AArch64/AArch64InstrInfo.cpp
@@ -10719,7 +10719,6 @@ void AArch64InstrInfo::fixupPostOutline(MachineBasicBlock &MBB) const {
 static void signOutlinedFunction(MachineFunction &MF, MachineBasicBlock &MBB,
                                  const AArch64InstrInfo *TII,
                                  bool ShouldSignReturnAddr) {
-  assert(&MF.front() == &MBB);
   if (!ShouldSignReturnAddr)
     return;
 



More information about the llvm-commits mailing list