[llvm] [AArch64] Codegen for AArch64 Return Address Signing Hardening (PR #176187)

Victor Campos via llvm-commits llvm-commits at lists.llvm.org
Thu Jan 15 07:46:52 PST 2026


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

>From 65786afece127e002985e93ba8c349a5e1abcb02 Mon Sep 17 00:00:00 2001
From: Victor Campos <victor.campos at arm.com>
Date: Wed, 14 Jan 2026 09:06:04 +0000
Subject: [PATCH 1/2] [AArch64] Codegen for AArch64 Return Address Signing
 Hardening

This patch implements the code generation for AArch64's Return Address
Signing Hardening, a mitigation [1] against the PACMAN attack [2].

A new function attribute, "sign-return-address-harden", is plumbed
through AArch64MachineFunctionInfo and interpreted by the AArch64
pointer authentication logic. When set to "load-return-address", the
backend emits a load from the return address before the return
instruction.

The code sequence selected depends on whether or not the target has
FEAT_PAUTH. This feature indicates the availability of non-hint space
Pointer Authentication instructions. If FEAT_PAUTH is on, this is an
example of a code sequence:

```
AUTIASP
MOV x0, x30
XPACI x0
LDR x0, [x0]
RET
```

If not, then codegen must use hint space instructions:

```
AUTIASP
MOV x0, x30
XPACLRI
LDR w30, [x30]
MOV x30, x0
RET
```

In both cases, x0 was chosen as a temporary register. The newly emitted
code sequence needs to clobber that one temporary register. To that
extent, the RegScavenger class is used to find one available register
and perform register spilling if none is available.

1: https://developer.arm.com/documentation/101754/0624/armclang-Reference/armclang-Command-line-Options/-mharden-pac-ret
2: https://pacmanattack.com
---
 llvm/lib/Target/AArch64/AArch64InstrInfo.cpp  |  19 ++
 .../AArch64/AArch64MachineFunctionInfo.cpp    |  12 +
 .../AArch64/AArch64MachineFunctionInfo.h      |  11 +
 .../lib/Target/AArch64/AArch64PointerAuth.cpp | 129 +++++++-
 ...ne-outliner-retaddr-sign-harden-exclude.ll | 105 ++++++
 ...chine-outliner-retaddr-sign-same-harden.ll | 107 ++++++
 .../sign-return-address-harden-fn-attr.ll     | 305 ++++++++++++++++++
 7 files changed, 686 insertions(+), 2 deletions(-)
 create mode 100644 llvm/test/CodeGen/AArch64/machine-outliner-retaddr-sign-harden-exclude.ll
 create mode 100644 llvm/test/CodeGen/AArch64/machine-outliner-retaddr-sign-same-harden.ll
 create mode 100644 llvm/test/CodeGen/AArch64/sign-return-address-harden-fn-attr.ll

diff --git a/llvm/lib/Target/AArch64/AArch64InstrInfo.cpp b/llvm/lib/Target/AArch64/AArch64InstrInfo.cpp
index f07211325393d..9dc323446eaa2 100644
--- a/llvm/lib/Target/AArch64/AArch64InstrInfo.cpp
+++ b/llvm/lib/Target/AArch64/AArch64InstrInfo.cpp
@@ -10013,6 +10013,23 @@ AArch64InstrInfo::getOutliningCandidateInfo(
                                    ->getInfo<AArch64FunctionInfo>()
                                    ->getSignReturnAddressCondition();
   if (RASignCondition != SignReturnAddress::None) {
+    // Candidates that have Return Address Signing Hardening enabled are
+    // discarded.
+    //
+    // In its current form, the machine outliner does not preserve X16/X17
+    // across outlined function calls, even though it should as they are
+    // caller-saved registers. And since the hardening based on load of return
+    // address clobbers one or both of these registers, if they are alive across
+    // a call their value would be lost due to the hardening mechanism.
+    llvm::erase_if(RepeatedSequenceLocs, [](outliner::Candidate &C) {
+      return C.getMF()
+          ->getInfo<AArch64FunctionInfo>()
+          ->shouldHardenSignReturnAddress();
+    });
+    // If the sequence doesn't have enough candidates left, then we're done.
+    if (RepeatedSequenceLocs.size() < MinRepeats)
+      return std::nullopt;
+
     // One PAC and one AUT instructions
     NumBytesToCreateFrame += 8;
 
@@ -10385,6 +10402,8 @@ void AArch64InstrInfo::mergeOutliningCandidateAttributes(
     F.addFnAttr(CFn.getFnAttribute("sign-return-address"));
   if (CFn.hasFnAttribute("sign-return-address-key"))
     F.addFnAttr(CFn.getFnAttribute("sign-return-address-key"));
+  if (CFn.hasFnAttribute("sign-return-address-harden"))
+    F.addFnAttr(CFn.getFnAttribute("sign-return-address-harden"));
 
   AArch64GenInstrInfo::mergeOutliningCandidateAttributes(F, Candidates);
 }
diff --git a/llvm/lib/Target/AArch64/AArch64MachineFunctionInfo.cpp b/llvm/lib/Target/AArch64/AArch64MachineFunctionInfo.cpp
index baeab6a33ff6a..612641a18d93f 100644
--- a/llvm/lib/Target/AArch64/AArch64MachineFunctionInfo.cpp
+++ b/llvm/lib/Target/AArch64/AArch64MachineFunctionInfo.cpp
@@ -120,6 +120,18 @@ AArch64FunctionInfo::AArch64FunctionInfo(const Function &F,
   // TODO: skip functions that have no instrumented allocas for optimization
   IsMTETagged = F.hasFnAttribute(Attribute::SanitizeMemTag);
 
+  if (Attribute SignReturnAddrHardenFnAttr =
+          F.getFnAttribute("sign-return-address-harden");
+      SignReturnAddrHardenFnAttr.isValid()) {
+    SignReturnAddressHardening =
+        StringSwitch<SignReturnAddressHardeningKind>(
+            SignReturnAddrHardenFnAttr.getValueAsString())
+            .Case("none", SignReturnAddressHardeningKind::None)
+            .Case("load-return-address",
+                  SignReturnAddressHardeningKind::LoadReturnAddress)
+            .Default(SignReturnAddressHardeningKind::None);
+  }
+
   // BTI/PAuthLR are set on the function attribute.
   BranchTargetEnforcement = F.hasFnAttribute("branch-target-enforcement");
   BranchProtectionPAuthLR = F.hasFnAttribute("branch-protection-pauth-lr");
diff --git a/llvm/lib/Target/AArch64/AArch64MachineFunctionInfo.h b/llvm/lib/Target/AArch64/AArch64MachineFunctionInfo.h
index 00e0c2511aaf0..74860359187f2 100644
--- a/llvm/lib/Target/AArch64/AArch64MachineFunctionInfo.h
+++ b/llvm/lib/Target/AArch64/AArch64MachineFunctionInfo.h
@@ -195,6 +195,12 @@ class AArch64FunctionInfo final : public MachineFunctionInfo {
   /// epilogue when using PC as a second salt (FEAT_PAuth_LR)
   MCSymbol *SignInstrLabel = nullptr;
 
+  /// SignReturnAddressHardening specifies the PAC-RET hardening scheme.
+  enum class SignReturnAddressHardeningKind {
+    None,
+    LoadReturnAddress
+  } SignReturnAddressHardening = SignReturnAddressHardeningKind::None;
+
   /// BranchTargetEnforcement enables placing BTI instructions at potential
   /// indirect branch destinations.
   bool BranchTargetEnforcement = false;
@@ -615,6 +621,11 @@ class AArch64FunctionInfo final : public MachineFunctionInfo {
 
   bool isMTETagged() const { return IsMTETagged; }
 
+  bool shouldHardenSignReturnAddress() const {
+    return SignReturnAddressHardening ==
+           SignReturnAddressHardeningKind::LoadReturnAddress;
+  }
+
   bool branchTargetEnforcement() const { return BranchTargetEnforcement; }
 
   bool branchProtectionPAuthLR() const { return BranchProtectionPAuthLR; }
diff --git a/llvm/lib/Target/AArch64/AArch64PointerAuth.cpp b/llvm/lib/Target/AArch64/AArch64PointerAuth.cpp
index 8bd7a7cb5a74a..c63da8c430125 100644
--- a/llvm/lib/Target/AArch64/AArch64PointerAuth.cpp
+++ b/llvm/lib/Target/AArch64/AArch64PointerAuth.cpp
@@ -11,11 +11,13 @@
 #include "AArch64.h"
 #include "AArch64InstrInfo.h"
 #include "AArch64MachineFunctionInfo.h"
+#include "AArch64RegisterInfo.h"
 #include "AArch64Subtarget.h"
 #include "llvm/CodeGen/CFIInstBuilder.h"
 #include "llvm/CodeGen/MachineBasicBlock.h"
 #include "llvm/CodeGen/MachineInstrBuilder.h"
 #include "llvm/CodeGen/MachineModuleInfo.h"
+#include "llvm/CodeGen/RegisterScavenging.h"
 
 using namespace llvm;
 using namespace llvm::AArch64PAuth;
@@ -24,6 +26,46 @@ using namespace llvm::AArch64PAuth;
 
 namespace {
 
+class RegScavengerHelper {
+private:
+  RegScavenger RS;
+  MachineFunction *MF = nullptr;
+  bool SpillSlotCreated = false;
+
+public:
+  RegScavengerHelper() = default;
+
+  void reset(MachineFunction &Func) {
+    MF = &Func;
+    SpillSlotCreated = false;
+  }
+
+  Register findRegister(MachineBasicBlock::iterator MBBI) {
+    assert(MBBI->getParent()->getParent() == MF &&
+           "MBBI does not belong to MF");
+    RS.enterBasicBlockEnd(*MBBI->getParent());
+    RS.backward(MBBI);
+    Register XReg = RS.FindUnusedReg(&AArch64::GPR64RegClass);
+    if (XReg != 0)
+      return XReg;
+    if (!SpillSlotCreated) {
+      MachineFrameInfo &MFI = MF->getFrameInfo();
+      const TargetRegisterInfo *TRI = MF->getSubtarget().getRegisterInfo();
+      int FI = MFI.CreateSpillStackObject(
+          TRI->getSpillSize(AArch64::GPR64RegClass),
+          TRI->getSpillAlign(AArch64::GPR64RegClass));
+      RS.addScavengingFrameIndex(FI);
+      SpillSlotCreated = true;
+    }
+    XReg = RS.scavengeRegisterBackwards(AArch64::GPR64RegClass, std::prev(MBBI),
+                                        true, 0);
+    if (XReg == 0)
+      reportFatalInternalError(
+          "AArch64PointerAuth: failed to scavenge a register!");
+    return XReg;
+  }
+};
+
 class AArch64PointerAuth : public MachineFunctionPass {
 public:
   static char ID;
@@ -37,12 +79,15 @@ class AArch64PointerAuth : public MachineFunctionPass {
 private:
   const AArch64Subtarget *Subtarget = nullptr;
   const AArch64InstrInfo *TII = nullptr;
+  RegScavengerHelper RSHelper;
 
   void signLR(MachineFunction &MF, MachineBasicBlock::iterator MBBI) const;
 
   void authenticateLR(MachineFunction &MF,
                       MachineBasicBlock::iterator MBBI) const;
 
+  bool emitSignReturnAddressHardening(MachineFunction &MF);
+
   bool checkAuthenticatedLR(MachineBasicBlock::iterator TI) const;
 };
 
@@ -177,8 +222,13 @@ void AArch64PointerAuth::authenticateLR(
   // From v8.3a onwards there are optimised authenticate LR and return
   // instructions, namely RETA{A,B}, that can be used instead. In this case the
   // DW_CFA_AARCH64_negate_ra_state can't be emitted.
-  bool TerminatorIsCombinable =
-      TI != MBB.end() && TI->getOpcode() == AArch64::RET;
+  //
+  // If the PAC-RET hardening based on load of return address is enabled,
+  // fallback to the use of AUTIASP/AUTIBSP and RET.
+  bool TerminatorIsCombinable = TI != MBB.end() &&
+                                TI->getOpcode() == AArch64::RET &&
+                                !MFnI->shouldHardenSignReturnAddress();
+
   MCSymbol *PACSym = MFnI->getSigningInstrLabel();
 
   if (Subtarget->hasPAuth() && TerminatorIsCombinable && !NeedsWinCFI &&
@@ -243,6 +293,7 @@ unsigned llvm::AArch64PAuth::getCheckerSizeInBytes(AuthCheckMethod Method) {
 bool AArch64PointerAuth::runOnMachineFunction(MachineFunction &MF) {
   Subtarget = &MF.getSubtarget<AArch64Subtarget>();
   TII = Subtarget->getInstrInfo();
+  RSHelper.reset(MF);
 
   SmallVector<MachineBasicBlock::instr_iterator> PAuthPseudoInstrs;
 
@@ -276,5 +327,79 @@ bool AArch64PointerAuth::runOnMachineFunction(MachineFunction &MF) {
     Modified = true;
   }
 
+  Modified |= emitSignReturnAddressHardening(MF);
+
+  return Modified;
+}
+
+bool AArch64PointerAuth::emitSignReturnAddressHardening(
+    MachineFunction &MF) {
+  const auto *FI = MF.getInfo<AArch64FunctionInfo>();
+  assert(FI && "FI can't be null");
+  if (!FI->shouldSignReturnAddress(MF) || !FI->shouldHardenSignReturnAddress())
+    return false;
+  assert(Subtarget && "Subtarget must be initialized");
+
+  bool Modified = false;
+  for (MachineBasicBlock &MBB : MF) {
+    if (!MBB.isReturnBlock())
+      continue;
+
+    MachineBasicBlock::iterator MBBI = MBB.getFirstTerminator();
+
+    if (MBBI == MBB.end() || MBBI->getOpcode() != AArch64::RET)
+      continue;
+
+    DebugLoc DL = MBBI->getDebugLoc();
+
+    Register XReg = RSHelper.findRegister(MBBI);
+
+    // Register copies are done using ORRXrs directly instead of using the
+    // pseudo-instruction COPY because this function can be called after
+    // pseudo-instruction expansion takes place, for example via the machine
+    // outliner pass.
+    BuildMI(MBB, MBBI, DL, TII->get(AArch64::ORRXrs), XReg)
+        .addUse(AArch64::XZR)
+        .addUse(AArch64::LR)
+        .addImm(0)
+        .setMIFlag(MachineInstr::FrameDestroy);
+
+    // The XPACI instruction is only available with FEAT_PAUTH. So if the
+    // subtarget does not have it, the alternative XPACLRI instruction must be
+    // used instead. The latter is in hint space, therefore can be present even
+    // if FEAT_PAUTH is absent.
+    if (Subtarget->hasPAuth()) {
+      BuildMI(MBB, MBBI, DL, TII->get(AArch64::XPACI), XReg)
+          .addUse(XReg)
+          .setMIFlag(MachineInstr::FrameDestroy);
+      Register WReg =
+          Subtarget->getRegisterInfo()->getSubReg(XReg, AArch64::sub_32);
+      BuildMI(MBB, MBBI, DL, TII->get(AArch64::LDRWui), WReg)
+          .addUse(XReg)
+          .addImm(0)
+          .setMIFlag(MachineInstr::FrameDestroy);
+    } else {
+      BuildMI(MBB, MBBI, DL, TII->get(AArch64::XPACLRI))
+          .setMIFlag(MachineInstr::FrameDestroy);
+      BuildMI(MBB, MBBI, DL, TII->get(AArch64::LDRWui), AArch64::W30)
+          .addUse(AArch64::LR)
+          .addImm(0)
+          .setMIFlag(MachineInstr::FrameDestroy);
+      BuildMI(MBB, MBBI, DL, TII->get(AArch64::ORRXrs), AArch64::LR)
+          .addUse(AArch64::XZR)
+          .addUse(XReg)
+          .addImm(0)
+          .setMIFlag(MachineInstr::FrameDestroy);
+      if (MBBI != MBB.end() && (MBBI->getOpcode() == AArch64::RET_ReallyLR ||
+                                MBBI->getOpcode() == AArch64::RET)) {
+        BuildMI(MBB, MBBI, DL, TII->get(AArch64::RET))
+            .addUse(AArch64::LR)
+            .copyImplicitOps(*MBBI);
+        MBB.erase(MBBI);
+      }
+    }
+    Modified = true;
+  }
+
   return Modified;
 }
diff --git a/llvm/test/CodeGen/AArch64/machine-outliner-retaddr-sign-harden-exclude.ll b/llvm/test/CodeGen/AArch64/machine-outliner-retaddr-sign-harden-exclude.ll
new file mode 100644
index 0000000000000..9b5b63d98ae9b
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/machine-outliner-retaddr-sign-harden-exclude.ll
@@ -0,0 +1,105 @@
+; RUN: llc -verify-machineinstrs -enable-machine-outliner -mtriple aarch64 %s -o - | FileCheck %s --check-prefix V8A
+; RUN: llc -verify-machineinstrs -enable-machine-outliner -mtriple aarch64 -mattr=+v8.3a %s -o - | FileCheck %s --check-prefix V83A
+
+define void @a() "sign-return-address"="all" "sign-return-address-key"="a_key" "sign-return-address-harden"="load-return-address" nounwind {
+; V8A-LABEL:     a:
+; V8A:           hint #25
+; V8A-NOT:       bl OUTLINED_FUNCTION
+; V8A:           hint #29
+; V8A-NEXT:      mov x0, x30
+; V8A-NEXT:      hint #7
+; V8A-NEXT:      ldr w30, [x30]
+; V8A-NEXT:      mov x30, x0
+; V8A-NEXT:      ret{{$}}
+
+; V83A-LABEL:    a:
+; V83A:          paciasp
+; V83A-NOT:      bl OUTLINED_FUNCTION
+; V83A:          autiasp
+; V83A-NEXT:     mov x0, x30
+; V83A-NEXT:     xpaci x0
+; V83A-NEXT:     ldr w0, [x0]
+; V83A-NEXT:     ret{{$}}
+  %1 = alloca i32, align 4
+  %2 = alloca i32, align 4
+  %3 = alloca i32, align 4
+  %4 = alloca i32, align 4
+  %5 = alloca i32, align 4
+  %6 = alloca i32, align 4
+  store i32 1, ptr %1, align 4
+  store i32 2, ptr %2, align 4
+  store i32 3, ptr %3, align 4
+  store i32 4, ptr %4, align 4
+  store i32 5, ptr %5, align 4
+  store i32 6, ptr %6, align 4
+  ret void
+}
+
+define void @b() "sign-return-address"="all" "sign-return-address-key"="a_key" nounwind {
+; V8A-LABEL:     b:
+; V8A:           hint #25
+; V8A-NEXT:      mov x0, x30
+; V8A-NEXT:      bl OUTLINED_FUNCTION_0
+; V8A-NEXT:      mov x30, x0
+; V8A-NEXT:      hint #29
+; V8A-NEXT:      ret
+
+; V83A-LABEL:    b:
+; V83A:          paciasp
+; V83A-NEXT:     mov x0, x30
+; V83A-NEXT:     bl OUTLINED_FUNCTION_0
+; V83A-NEXT:     mov x30, x0
+; V83A-NEXT:     retaa
+  %1 = alloca i32, align 4
+  %2 = alloca i32, align 4
+  %3 = alloca i32, align 4
+  %4 = alloca i32, align 4
+  %5 = alloca i32, align 4
+  %6 = alloca i32, align 4
+  store i32 1, ptr %1, align 4
+  store i32 2, ptr %2, align 4
+  store i32 3, ptr %3, align 4
+  store i32 4, ptr %4, align 4
+  store i32 5, ptr %5, align 4
+  store i32 6, ptr %6, align 4
+  ret void
+}
+
+define void @c() "sign-return-address"="all" "sign-return-address-key"="a_key" nounwind {
+; V8A-LABEL:     c:
+; V8A:           hint #25
+; V8A-NEXT:      mov x0, x30
+; V8A-NEXT:      bl OUTLINED_FUNCTION_0
+; V8A-NEXT:      mov x30, x0
+; V8A-NEXT:      hint #29
+; V8A-NEXT:      ret
+
+; V83A-LABEL:    c:
+; V83A:          paciasp
+; V83A-NEXT:     mov x0, x30
+; V83A-NEXT:     bl OUTLINED_FUNCTION_0
+; V83A-NEXT:     mov x30, x0
+; V83A-NEXT:     retaa
+  %1 = alloca i32, align 4
+  %2 = alloca i32, align 4
+  %3 = alloca i32, align 4
+  %4 = alloca i32, align 4
+  %5 = alloca i32, align 4
+  %6 = alloca i32, align 4
+  store i32 1, ptr %1, align 4
+  store i32 2, ptr %2, align 4
+  store i32 3, ptr %3, align 4
+  store i32 4, ptr %4, align 4
+  store i32 5, ptr %5, align 4
+  store i32 6, ptr %6, align 4
+  ret void
+}
+
+; V8A-LABEL:     OUTLINED_FUNCTION_0:
+; V8A:           hint #25
+; V8A:           hint #29
+; V8A:           ret
+
+; V83A-LABEL:    OUTLINED_FUNCTION_0:
+; V83A:          paciasp
+; V83A:          retaa
diff --git a/llvm/test/CodeGen/AArch64/machine-outliner-retaddr-sign-same-harden.ll b/llvm/test/CodeGen/AArch64/machine-outliner-retaddr-sign-same-harden.ll
new file mode 100644
index 0000000000000..1504007e7f80a
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/machine-outliner-retaddr-sign-same-harden.ll
@@ -0,0 +1,107 @@
+; RUN: llc -verify-machineinstrs -enable-machine-outliner -mtriple aarch64 %s -o - | FileCheck %s --check-prefix V8A
+; RUN: llc -verify-machineinstrs -enable-machine-outliner -mtriple aarch64 -mattr=+v8.3a %s -o - | FileCheck %s --check-prefix V83A
+
+define void @a() "sign-return-address"="all" "sign-return-address-key"="a_key" "sign-return-address-harden"="load-return-address" nounwind {
+; V8A-LABEL:     a:
+; V8A:           hint #25
+; V8A-NOT:       bl OUTLINED_FUNCTION
+; V8A:           hint #29
+; V8A-NEXT:      mov x0, x30
+; V8A-NEXT:      hint #7
+; V8A-NEXT:      ldr w30, [x30]
+; V8A-NEXT:      mov x30, x0
+; V8A-NEXT:      ret{{$}}
+
+; V83A-LABEL:    a:
+; V83A:          paciasp
+; V83A-NOT:      bl OUTLINED_FUNCTION
+; V83A:          autiasp
+; V83A-NEXT:     mov x0, x30
+; V83A-NEXT:     xpaci x0
+; V83A-NEXT:     ldr w0, [x0]
+; V83A-NEXT:     ret{{$}}
+  %1 = alloca i32, align 4
+  %2 = alloca i32, align 4
+  %3 = alloca i32, align 4
+  %4 = alloca i32, align 4
+  %5 = alloca i32, align 4
+  %6 = alloca i32, align 4
+  store i32 1, ptr %1, align 4
+  store i32 2, ptr %2, align 4
+  store i32 3, ptr %3, align 4
+  store i32 4, ptr %4, align 4
+  store i32 5, ptr %5, align 4
+  store i32 6, ptr %6, align 4
+  ret void
+}
+
+define void @b() "sign-return-address"="all" "sign-return-address-key"="a_key" "sign-return-address-harden"="load-return-address" nounwind {
+; V8A-LABEL:     b:
+; V8A:           hint #25
+; V8A-NOT:       bl OUTLINED_FUNCTION
+; V8A:           hint #29
+; V8A-NEXT:      mov x0, x30
+; V8A-NEXT:      hint #7
+; V8A-NEXT:      ldr w30, [x30]
+; V8A-NEXT:      mov x30, x0
+; V8A-NEXT:      ret{{$}}
+
+; V83A-LABEL:    b:
+; V83A:          paciasp
+; V83A-NOT:      bl OUTLINED_FUNCTION
+; V83A:          autiasp
+; V83A-NEXT:     mov x0, x30
+; V83A-NEXT:     xpaci x0
+; V83A-NEXT:     ldr w0, [x0]
+; V83A-NEXT:     ret{{$}}
+  %1 = alloca i32, align 4
+  %2 = alloca i32, align 4
+  %3 = alloca i32, align 4
+  %4 = alloca i32, align 4
+  %5 = alloca i32, align 4
+  %6 = alloca i32, align 4
+  store i32 1, ptr %1, align 4
+  store i32 2, ptr %2, align 4
+  store i32 3, ptr %3, align 4
+  store i32 4, ptr %4, align 4
+  store i32 5, ptr %5, align 4
+  store i32 6, ptr %6, align 4
+  ret void
+}
+
+define void @c() "sign-return-address"="all" "sign-return-address-key"="a_key" "sign-return-address-harden"="load-return-address" nounwind {
+; V8A-LABEL:     c:
+; V8A:           hint #25
+; V8A-NOT:       bl OUTLINED_FUNCTION
+; V8A:           hint #29
+; V8A-NEXT:      mov x0, x30
+; V8A-NEXT:      hint #7
+; V8A-NEXT:      ldr w30, [x30]
+; V8A-NEXT:      mov x30, x0
+; V8A-NEXT:      ret{{$}}
+
+; V83A-LABEL:    c:
+; V83A:          paciasp
+; V83A-NOT:      bl OUTLINED_FUNCTION
+; V83A:          autiasp
+; V83A-NEXT:     mov x0, x30
+; V83A-NEXT:     xpaci x0
+; V83A-NEXT:     ldr w0, [x0]
+; V83A-NEXT:     ret{{$}}
+  %1 = alloca i32, align 4
+  %2 = alloca i32, align 4
+  %3 = alloca i32, align 4
+  %4 = alloca i32, align 4
+  %5 = alloca i32, align 4
+  %6 = alloca i32, align 4
+  store i32 1, ptr %1, align 4
+  store i32 2, ptr %2, align 4
+  store i32 3, ptr %3, align 4
+  store i32 4, ptr %4, align 4
+  store i32 5, ptr %5, align 4
+  store i32 6, ptr %6, align 4
+  ret void
+}
+
+; V8A-NOT:       OUTLINED_FUNCTION_0:
+; V83A-NOT:      OUTLINED_FUNCTION_0:
diff --git a/llvm/test/CodeGen/AArch64/sign-return-address-harden-fn-attr.ll b/llvm/test/CodeGen/AArch64/sign-return-address-harden-fn-attr.ll
new file mode 100644
index 0000000000000..c0047528a461b
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/sign-return-address-harden-fn-attr.ll
@@ -0,0 +1,305 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+;       The 'ret' instructions' regexps have been modified to check they aren't followed by a PAUTH
+;       suffix like 'aa' or 'ab'
+; RUN: llc -mtriple=aarch64 < %s                      | FileCheck %s --check-prefix=CHECK-NO-PAUTH
+; RUN: llc -mtriple=aarch64 -mattr=+v8.3a,-pauth < %s | FileCheck %s --check-prefix=CHECK-NO-PAUTH
+; RUN: llc -mtriple=aarch64 -mattr=+v8.3a < %s        | FileCheck %s --check-prefix=CHECK-PAUTH
+; RUN: llc -mtriple=aarch64 -mattr=+pauth < %s        | FileCheck %s --check-prefix=CHECK-PAUTH
+
+declare i32 @foo(i32)
+
+define i32 @f0(i32 %a) #0 {
+; CHECK-NO-PAUTH-LABEL: f0:
+; CHECK-NO-PAUTH:       // %bb.0: // %entry
+; CHECK-NO-PAUTH-NEXT:    hint #25
+; CHECK-NO-PAUTH-NEXT:    .cfi_negate_ra_state
+; CHECK-NO-PAUTH-NEXT:    add w0, w0, #1
+; CHECK-NO-PAUTH-NEXT:    hint #29
+; CHECK-NO-PAUTH-NEXT:    mov x1, x30
+; CHECK-NO-PAUTH-NEXT:    hint #7
+; CHECK-NO-PAUTH-NEXT:    ldr w30, [x30]
+; CHECK-NO-PAUTH-NEXT:    mov x30, x1
+; CHECK-NO-PAUTH-NEXT:    ret{{$}}
+;
+; CHECK-PAUTH-LABEL: f0:
+; CHECK-PAUTH:       // %bb.0: // %entry
+; CHECK-PAUTH-NEXT:    paciasp
+; CHECK-PAUTH-NEXT:    .cfi_negate_ra_state
+; CHECK-PAUTH-NEXT:    add w0, w0, #1
+; CHECK-PAUTH-NEXT:    autiasp
+; CHECK-PAUTH-NEXT:    mov x1, x30
+; CHECK-PAUTH-NEXT:    xpaci x1
+; CHECK-PAUTH-NEXT:    ldr w1, [x1]
+; CHECK-PAUTH-NEXT:    ret{{$}}
+entry:
+  %add = add nsw i32 %a, 1
+  ret i32 %add
+}
+
+define i32 @f1(i32 %a) #1 {
+; CHECK-NO-PAUTH-LABEL: f1:
+; CHECK-NO-PAUTH:       // %bb.0: // %entry
+; CHECK-NO-PAUTH-NEXT:    .cfi_b_key_frame
+; CHECK-NO-PAUTH-NEXT:    hint #27
+; CHECK-NO-PAUTH-NEXT:    .cfi_negate_ra_state
+; CHECK-NO-PAUTH-NEXT:    add w0, w0, #1
+; CHECK-NO-PAUTH-NEXT:    hint #31
+; CHECK-NO-PAUTH-NEXT:    mov x1, x30
+; CHECK-NO-PAUTH-NEXT:    hint #7
+; CHECK-NO-PAUTH-NEXT:    ldr w30, [x30]
+; CHECK-NO-PAUTH-NEXT:    mov x30, x1
+; CHECK-NO-PAUTH-NEXT:    ret{{$}}
+;
+; CHECK-PAUTH-LABEL: f1:
+; CHECK-PAUTH:       // %bb.0: // %entry
+; CHECK-PAUTH-NEXT:    .cfi_b_key_frame
+; CHECK-PAUTH-NEXT:    pacibsp
+; CHECK-PAUTH-NEXT:    .cfi_negate_ra_state
+; CHECK-PAUTH-NEXT:    add w0, w0, #1
+; CHECK-PAUTH-NEXT:    autibsp
+; CHECK-PAUTH-NEXT:    mov x1, x30
+; CHECK-PAUTH-NEXT:    xpaci x1
+; CHECK-PAUTH-NEXT:    ldr w1, [x1]
+; CHECK-PAUTH-NEXT:    ret{{$}}
+entry:
+  %add = add nsw i32 %a, 1
+  ret i32 %add
+}
+
+define i32 @f2(i32 %a) #2 {
+; CHECK-NO-PAUTH-LABEL: f2:
+; CHECK-NO-PAUTH:       // %bb.0: // %entry
+; CHECK-NO-PAUTH-NEXT:    hint #25
+; CHECK-NO-PAUTH-NEXT:    .cfi_negate_ra_state
+; 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:    bl foo
+; CHECK-NO-PAUTH-NEXT:    ldr x30, [sp], #16 // 8-byte Folded Reload
+; CHECK-NO-PAUTH-NEXT:    hint #29
+; CHECK-NO-PAUTH-NEXT:    mov x1, x30
+; CHECK-NO-PAUTH-NEXT:    hint #7
+; CHECK-NO-PAUTH-NEXT:    ldr w30, [x30]
+; CHECK-NO-PAUTH-NEXT:    mov x30, x1
+; CHECK-NO-PAUTH-NEXT:    ret{{$}}
+;
+; CHECK-PAUTH-LABEL: f2:
+; CHECK-PAUTH:       // %bb.0: // %entry
+; CHECK-PAUTH-NEXT:    paciasp
+; CHECK-PAUTH-NEXT:    .cfi_negate_ra_state
+; 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:    bl foo
+; CHECK-PAUTH-NEXT:    ldr x30, [sp], #16 // 8-byte Folded Reload
+; CHECK-PAUTH-NEXT:    autiasp
+; CHECK-PAUTH-NEXT:    mov x1, x30
+; CHECK-PAUTH-NEXT:    xpaci x1
+; CHECK-PAUTH-NEXT:    ldr w1, [x1]
+; CHECK-PAUTH-NEXT:    ret{{$}}
+entry:
+  %call = call i32 @foo(i32 %a)
+  ret i32 %call
+}
+
+define i32 @f3(i32 %a) #3 {
+; CHECK-NO-PAUTH-LABEL: f3:
+; CHECK-NO-PAUTH:       // %bb.0: // %entry
+; CHECK-NO-PAUTH-NEXT:    .cfi_b_key_frame
+; CHECK-NO-PAUTH-NEXT:    hint #27
+; CHECK-NO-PAUTH-NEXT:    .cfi_negate_ra_state
+; 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:    bl foo
+; CHECK-NO-PAUTH-NEXT:    ldr x30, [sp], #16 // 8-byte Folded Reload
+; CHECK-NO-PAUTH-NEXT:    hint #31
+; CHECK-NO-PAUTH-NEXT:    mov x1, x30
+; CHECK-NO-PAUTH-NEXT:    hint #7
+; CHECK-NO-PAUTH-NEXT:    ldr w30, [x30]
+; CHECK-NO-PAUTH-NEXT:    mov x30, x1
+; CHECK-NO-PAUTH-NEXT:    ret{{$}}
+;
+; CHECK-PAUTH-LABEL: f3:
+; CHECK-PAUTH:       // %bb.0: // %entry
+; CHECK-PAUTH-NEXT:    .cfi_b_key_frame
+; CHECK-PAUTH-NEXT:    pacibsp
+; CHECK-PAUTH-NEXT:    .cfi_negate_ra_state
+; 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:    bl foo
+; CHECK-PAUTH-NEXT:    ldr x30, [sp], #16 // 8-byte Folded Reload
+; CHECK-PAUTH-NEXT:    autibsp
+; CHECK-PAUTH-NEXT:    mov x1, x30
+; CHECK-PAUTH-NEXT:    xpaci x1
+; CHECK-PAUTH-NEXT:    ldr w1, [x1]
+; CHECK-PAUTH-NEXT:    ret{{$}}
+entry:
+  %call = call i32 @foo(i32 %a)
+  ret i32 %call
+}
+
+define i32 @f4(i32 %a) #4 {
+; CHECK-NO-PAUTH-LABEL: f4:
+; CHECK-NO-PAUTH:       // %bb.0: // %entry
+; CHECK-NO-PAUTH-NEXT:    hint #25
+; CHECK-NO-PAUTH-NEXT:    .cfi_negate_ra_state
+; 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:    bl foo
+; CHECK-NO-PAUTH-NEXT:    ldr x30, [sp], #16 // 8-byte Folded Reload
+; CHECK-NO-PAUTH-NEXT:    hint #29
+; CHECK-NO-PAUTH-NEXT:    ret{{$}}
+;
+; CHECK-PAUTH-LABEL: f4:
+; CHECK-PAUTH:       // %bb.0: // %entry
+; CHECK-PAUTH-NEXT:    paciasp
+; CHECK-PAUTH-NEXT:    .cfi_negate_ra_state
+; 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:    bl foo
+; CHECK-PAUTH-NEXT:    ldr x30, [sp], #16 // 8-byte Folded Reload
+; CHECK-PAUTH-NEXT:    retaa
+entry:
+  %call = call i32 @foo(i32 %a)
+  ret i32 %call
+}
+
+define i32 @f5(i32 %a) #5 {
+; CHECK-NO-PAUTH-LABEL: f5:
+; CHECK-NO-PAUTH:       // %bb.0: // %entry
+; CHECK-NO-PAUTH-NEXT:    .cfi_b_key_frame
+; CHECK-NO-PAUTH-NEXT:    hint #27
+; CHECK-NO-PAUTH-NEXT:    .cfi_negate_ra_state
+; 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:    bl foo
+; CHECK-NO-PAUTH-NEXT:    ldr x30, [sp], #16 // 8-byte Folded Reload
+; CHECK-NO-PAUTH-NEXT:    hint #31
+; CHECK-NO-PAUTH-NEXT:    ret{{$}}
+;
+; CHECK-PAUTH-LABEL: f5:
+; CHECK-PAUTH:       // %bb.0: // %entry
+; CHECK-PAUTH-NEXT:    .cfi_b_key_frame
+; CHECK-PAUTH-NEXT:    pacibsp
+; CHECK-PAUTH-NEXT:    .cfi_negate_ra_state
+; 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:    bl foo
+; CHECK-PAUTH-NEXT:    ldr x30, [sp], #16 // 8-byte Folded Reload
+; CHECK-PAUTH-NEXT:    retab
+entry:
+  %call = call i32 @foo(i32 %a)
+  ret i32 %call
+}
+
+; Check that we don't harden functions which "return" with a
+; branch rather than a ret instruction.
+define i32 @f6(i32 %a) #0 {
+; CHECK-NO-PAUTH-LABEL: f6:
+; CHECK-NO-PAUTH:       // %bb.0: // %entry
+; CHECK-NO-PAUTH-NEXT:    hint #25
+; CHECK-NO-PAUTH-NEXT:    .cfi_negate_ra_state
+; CHECK-NO-PAUTH-NEXT:    hint #29
+; CHECK-NO-PAUTH-NEXT:    b foo
+;
+; CHECK-PAUTH-LABEL: f6:
+; CHECK-PAUTH:       // %bb.0: // %entry
+; CHECK-PAUTH-NEXT:    paciasp
+; CHECK-PAUTH-NEXT:    .cfi_negate_ra_state
+; CHECK-PAUTH-NEXT:    autiasp
+; CHECK-PAUTH-NEXT:    b foo
+entry:
+  %call = tail call i32 @foo(i32 %a)
+  ret i32 %call
+}
+
+define i32 @f7(ptr %fnptr) #0 {
+; CHECK-NO-PAUTH-LABEL: f7:
+; CHECK-NO-PAUTH:       // %bb.0: // %entry
+; CHECK-NO-PAUTH-NEXT:    hint #25
+; CHECK-NO-PAUTH-NEXT:    .cfi_negate_ra_state
+; CHECK-NO-PAUTH-NEXT:    hint #29
+; CHECK-NO-PAUTH-NEXT:    br x0
+;
+; CHECK-PAUTH-LABEL: f7:
+; CHECK-PAUTH:       // %bb.0: // %entry
+; CHECK-PAUTH-NEXT:    paciasp
+; CHECK-PAUTH-NEXT:    .cfi_negate_ra_state
+; CHECK-PAUTH-NEXT:    autiasp
+; CHECK-PAUTH-NEXT:    br x0
+entry:
+  %call = tail call i32 %fnptr()
+  ret i32 %call
+}
+
+; Check to see if pac-ret hardening is compatible with stack protection
+define void @stackprotector() #6 {
+; CHECK-NO-PAUTH-LABEL: stackprotector:
+; CHECK-NO-PAUTH:       // %bb.0:
+; CHECK-NO-PAUTH-NEXT:    hint #25
+; CHECK-NO-PAUTH-NEXT:    .cfi_negate_ra_state
+; CHECK-NO-PAUTH-NEXT:    sub sp, sp, #32
+; CHECK-NO-PAUTH-NEXT:    str x30, [sp, #16] // 8-byte Spill
+; CHECK-NO-PAUTH-NEXT:    .cfi_def_cfa_offset 32
+; CHECK-NO-PAUTH-NEXT:    .cfi_offset w30, -16
+; CHECK-NO-PAUTH-NEXT:    adrp x8, __stack_chk_guard
+; CHECK-NO-PAUTH-NEXT:    ldr x8, [x8, :lo12:__stack_chk_guard]
+; CHECK-NO-PAUTH-NEXT:    str x8, [sp, #8]
+; CHECK-NO-PAUTH-NEXT:    ldr x9, [sp, #8]
+; CHECK-NO-PAUTH-NEXT:    cmp x8, x9
+; CHECK-NO-PAUTH-NEXT:    b.ne .LBB8_2
+; CHECK-NO-PAUTH-NEXT:  // %bb.1:
+; CHECK-NO-PAUTH-NEXT:    ldr x30, [sp, #16] // 8-byte Reload
+; CHECK-NO-PAUTH-NEXT:    add sp, sp, #32
+; CHECK-NO-PAUTH-NEXT:    hint #29
+; CHECK-NO-PAUTH-NEXT:    mov x0, x30
+; CHECK-NO-PAUTH-NEXT:    hint #7
+; CHECK-NO-PAUTH-NEXT:    ldr w30, [x30]
+; CHECK-NO-PAUTH-NEXT:    mov x30, x0
+; CHECK-NO-PAUTH-NEXT:    ret{{$}}
+; CHECK-NO-PAUTH-NEXT:  .LBB8_2:
+; CHECK-NO-PAUTH-NEXT:    bl __stack_chk_fail
+;
+; CHECK-PAUTH-LABEL: stackprotector:
+; CHECK-PAUTH:       // %bb.0:
+; CHECK-PAUTH-NEXT:    paciasp
+; CHECK-PAUTH-NEXT:    .cfi_negate_ra_state
+; CHECK-PAUTH-NEXT:    sub sp, sp, #32
+; CHECK-PAUTH-NEXT:    str x30, [sp, #16] // 8-byte Spill
+; CHECK-PAUTH-NEXT:    .cfi_def_cfa_offset 32
+; CHECK-PAUTH-NEXT:    .cfi_offset w30, -16
+; CHECK-PAUTH-NEXT:    adrp x8, __stack_chk_guard
+; CHECK-PAUTH-NEXT:    ldr x8, [x8, :lo12:__stack_chk_guard]
+; CHECK-PAUTH-NEXT:    str x8, [sp, #8]
+; CHECK-PAUTH-NEXT:    ldr x9, [sp, #8]
+; CHECK-PAUTH-NEXT:    cmp x8, x9
+; CHECK-PAUTH-NEXT:    b.ne .LBB8_2
+; CHECK-PAUTH-NEXT:  // %bb.1:
+; CHECK-PAUTH-NEXT:    ldr x30, [sp, #16] // 8-byte Reload
+; CHECK-PAUTH-NEXT:    add sp, sp, #32
+; CHECK-PAUTH-NEXT:    autiasp
+; CHECK-PAUTH-NEXT:    mov x0, x30
+; CHECK-PAUTH-NEXT:    xpaci x0
+; CHECK-PAUTH-NEXT:    ldr w0, [x0]
+; CHECK-PAUTH-NEXT:    ret{{$}}
+; CHECK-PAUTH-NEXT:  .LBB8_2:
+; CHECK-PAUTH-NEXT:    bl __stack_chk_fail
+  ret void
+}
+
+attributes #0 = { "sign-return-address"="all" "sign-return-address-harden"="load-return-address" "sign-return-address-key"="a_key" }
+attributes #1 = { "sign-return-address"="all" "sign-return-address-harden"="load-return-address" "sign-return-address-key"="b_key" }
+
+attributes #2 = { "sign-return-address"="non-leaf" "sign-return-address-harden"="load-return-address" "sign-return-address-key"="a_key" }
+attributes #3 = { "sign-return-address"="non-leaf" "sign-return-address-harden"="load-return-address" "sign-return-address-key"="b_key" }
+
+attributes #4 = { "sign-return-address"="non-leaf" "sign-return-address-harden"="none" "sign-return-address-key"="a_key" }
+attributes #5 = { "sign-return-address"="non-leaf" "sign-return-address-harden"="none" "sign-return-address-key"="b_key" }
+
+attributes #6 = { sspreq "sign-return-address"="all" "sign-return-address-harden"="load-return-address" "sign-return-address-key"="a_key" }

>From 64830f45759705966f3cabc509529a4ec0085e5f Mon Sep 17 00:00:00 2001
From: Victor Campos <victor.campos at arm.com>
Date: Thu, 15 Jan 2026 15:44:58 +0000
Subject: [PATCH 2/2] Fix clang-format issue

---
 llvm/lib/Target/AArch64/AArch64PointerAuth.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/llvm/lib/Target/AArch64/AArch64PointerAuth.cpp b/llvm/lib/Target/AArch64/AArch64PointerAuth.cpp
index c63da8c430125..ce731cf0e230f 100644
--- a/llvm/lib/Target/AArch64/AArch64PointerAuth.cpp
+++ b/llvm/lib/Target/AArch64/AArch64PointerAuth.cpp
@@ -332,8 +332,7 @@ bool AArch64PointerAuth::runOnMachineFunction(MachineFunction &MF) {
   return Modified;
 }
 
-bool AArch64PointerAuth::emitSignReturnAddressHardening(
-    MachineFunction &MF) {
+bool AArch64PointerAuth::emitSignReturnAddressHardening(MachineFunction &MF) {
   const auto *FI = MF.getInfo<AArch64FunctionInfo>();
   assert(FI && "FI can't be null");
   if (!FI->shouldSignReturnAddress(MF) || !FI->shouldHardenSignReturnAddress())



More information about the llvm-commits mailing list