https://github.com/androm3da updated 
https://github.com/llvm/llvm-project/pull/200508

>From 65c9001cb6beebe8385d056dc356f2c0756fdf46 Mon Sep 17 00:00:00 2001
From: Brian Cain <[email protected]>
Date: Thu, 28 May 2026 20:16:39 -0700
Subject: [PATCH 1/2] [Hexagon] Add ShadowCallStack support

Implement the software ShadowCallStack for Hexagon.

On Hexagon, r19 is used as the shadow stack pointer (reserved via
-ffixed-r19). On function entry the LR (r31) is saved to the shadow
stack and the pointer is advanced; on exit the LR is restored from the
shadow stack before returning.

Prologue sequence:
  r19 = add(r19, #4)
  memw(r19+#-4) = r31

Epilogue sequence (between deallocframe/jumpr r31):
  r31 = memw(r19+#-4)
  r19 = add(r19, #-4)

SCS is only emitted when hasFP() is true: the function uses allocframe.
Leaf functions that never touch the stack do not need protection.

When SCS is active the combined L4_return (deallocframe+jump) is not
used; instead L2_deallocframe and PS_jmpret are kept separate so the
shadow-stack restore can be inserted between them.
---
 clang/lib/Driver/SanitizerArgs.cpp            |   8 +
 clang/lib/Driver/ToolChains/Linux.cpp         |   2 +
 .../fsanitize-shadow-call-stack-hexagon.c     |  12 ++
 .../cmake/Modules/AllSupportedArchDefs.cmake  |   2 +-
 .../test/shadowcallstack/libc_support.h       |  26 +++
 compiler-rt/test/shadowcallstack/lit.cfg.py   |   8 +-
 .../test/shadowcallstack/minimal_runtime.h    |   2 +
 .../Target/Hexagon/HexagonFrameLowering.cpp   | 154 ++++++++++++++++--
 .../test/CodeGen/Hexagon/shadow-call-stack.ll | 133 +++++++++++++++
 9 files changed, 332 insertions(+), 15 deletions(-)
 create mode 100644 clang/test/Driver/fsanitize-shadow-call-stack-hexagon.c
 create mode 100644 llvm/test/CodeGen/Hexagon/shadow-call-stack.ll

diff --git a/clang/lib/Driver/SanitizerArgs.cpp 
b/clang/lib/Driver/SanitizerArgs.cpp
index 74ebd0bf375d3..77ad06cfcbd0d 100644
--- a/clang/lib/Driver/SanitizerArgs.cpp
+++ b/clang/lib/Driver/SanitizerArgs.cpp
@@ -784,6 +784,14 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC,
         << "-ffixed-x18";
   }
 
+  if ((Kinds & SanitizerKind::ShadowCallStack) &&
+      TC.getTriple().getArch() == llvm::Triple::hexagon &&
+      !Args.hasArg(options::OPT_ffixed_r19) && DiagnoseErrors) {
+    D.Diag(diag::err_drv_argument_only_allowed_with)
+        << lastArgumentForMask(D, Args, Kinds & SanitizerKind::ShadowCallStack)
+        << "-ffixed-r19";
+  }
+
   // Report error if there are non-trapping sanitizers that require
   // c++abi-specific  parts of UBSan runtime, and they are not provided by the
   // toolchain. We don't have a good way to check the latter, so we just
diff --git a/clang/lib/Driver/ToolChains/Linux.cpp 
b/clang/lib/Driver/ToolChains/Linux.cpp
index 512788d235fec..1111d547fbe15 100644
--- a/clang/lib/Driver/ToolChains/Linux.cpp
+++ b/clang/lib/Driver/ToolChains/Linux.cpp
@@ -1006,6 +1006,8 @@ Linux::getSupportedSanitizers(StringRef BoundArch,
   if (IsX86_64 || IsAArch64 || IsRISCV64) {
     Res |= SanitizerKind::HWAddress;
   }
+  if (IsHexagon)
+    Res |= SanitizerKind::ShadowCallStack;
   if (IsX86_64 || IsAArch64) {
     Res |= SanitizerKind::KernelHWAddress;
   }
diff --git a/clang/test/Driver/fsanitize-shadow-call-stack-hexagon.c 
b/clang/test/Driver/fsanitize-shadow-call-stack-hexagon.c
new file mode 100644
index 0000000000000..89d186315d9c1
--- /dev/null
+++ b/clang/test/Driver/fsanitize-shadow-call-stack-hexagon.c
@@ -0,0 +1,12 @@
+// Test that -fsanitize=shadow-call-stack on Hexagon requires -ffixed-r19.
+
+// RUN: not %clang --target=hexagon-unknown-linux-musl \
+// RUN:   -fsanitize=shadow-call-stack %s -### 2>&1 \
+// RUN:   | FileCheck %s --check-prefix=HEXAGON-SCS-NO-R19
+
+// RUN: %clang --target=hexagon-unknown-linux-musl \
+// RUN:   -fsanitize=shadow-call-stack -ffixed-r19 %s -### 2>&1 \
+// RUN:   | FileCheck %s --check-prefix=HEXAGON-SCS-WITH-R19
+
+// HEXAGON-SCS-NO-R19: error: invalid argument '-fsanitize=shadow-call-stack' 
only allowed with '-ffixed-r19'
+// HEXAGON-SCS-WITH-R19: "-fsanitize=shadow-call-stack"
diff --git a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake 
b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
index a535cf9e3a8da..9c03892dffe5a 100644
--- a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
+++ b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
@@ -125,7 +125,7 @@ set(ALL_XRAY_SUPPORTED_ARCH ${X86_64} ${ARM32} ${ARM64} 
${MIPS32} ${MIPS64}
     powerpc64le ${HEXAGON} ${LOONGARCH64} ${RISCV32} ${RISCV64} ${S390X})
 endif()
 set(ALL_XRAY_DSO_SUPPORTED_ARCH ${X86_64} ${ARM64})
-set(ALL_SHADOWCALLSTACK_SUPPORTED_ARCH ${ARM64})
+set(ALL_SHADOWCALLSTACK_SUPPORTED_ARCH ${ARM64} ${HEXAGON})
 
 if (UNIX)
   if (OS_NAME MATCHES "Linux")
diff --git a/compiler-rt/test/shadowcallstack/libc_support.h 
b/compiler-rt/test/shadowcallstack/libc_support.h
index 9c8e0566380bc..00a3a38a8780b 100644
--- a/compiler-rt/test/shadowcallstack/libc_support.h
+++ b/compiler-rt/test/shadowcallstack/libc_support.h
@@ -32,6 +32,32 @@ __attribute__((noinline)) void scs_fputs_stdout(const char 
*p) {
       : "x0", "x1", "x2", "x8");
 }
 
+#elif defined(__hexagon__)
+#  ifndef __linux__
+#    error Unsupported platform
+#  endif
+
+size_t scs_strlen(const char *p) {
+  size_t retval = 0;
+  while (*p++)
+    retval++;
+  return retval;
+}
+
+__attribute__((noinline)) void scs_fputs_stdout(const char *p) {
+  size_t len = scs_strlen(p);
+  __asm__ __volatile__("{\n\t"
+                       "  r0 = #1\n\t" // fd = stdout
+                       "  r1 = %[buf]\n\t"
+                       "  r2 = %[count]\n\t"
+                       "  r6 = #64\n\t" // __NR_write
+                       "}\n\t"
+                       "trap0(#1)\n\t"
+                       :
+                       : [buf] "r"(p), [count] "r"(len)
+                       : "r0", "r1", "r2", "r6", "memory");
+}
+
 #else
 #error Unsupported platform
 #endif
diff --git a/compiler-rt/test/shadowcallstack/lit.cfg.py 
b/compiler-rt/test/shadowcallstack/lit.cfg.py
index 5b95deb1b0986..81151b0b7cbbc 100644
--- a/compiler-rt/test/shadowcallstack/lit.cfg.py
+++ b/compiler-rt/test/shadowcallstack/lit.cfg.py
@@ -25,6 +25,8 @@
 scs_arch_cflags = config.target_cflags
 if config.target_arch == "aarch64":
     scs_arch_cflags += " -ffixed-x18 "
+elif config.target_arch == "hexagon":
+    scs_arch_cflags += " -ffixed-r19 "
 config.substitutions.append(
     (
         "%clang_scs ",
@@ -32,5 +34,9 @@
     )
 )
 
-if config.target_os not in ["Linux"] or config.target_arch not in ["aarch64", 
"riscv64"]:
+if config.target_os not in ["Linux"] or config.target_arch not in [
+    "aarch64",
+    "hexagon",
+    "riscv64",
+]:
     config.unsupported = True
diff --git a/compiler-rt/test/shadowcallstack/minimal_runtime.h 
b/compiler-rt/test/shadowcallstack/minimal_runtime.h
index fab4fdf8006f7..09a6bcf1efa57 100644
--- a/compiler-rt/test/shadowcallstack/minimal_runtime.h
+++ b/compiler-rt/test/shadowcallstack/minimal_runtime.h
@@ -19,6 +19,8 @@ static void __shadowcallstack_init() {
 
 #if defined(__aarch64__)
   __asm__ __volatile__("mov x18, %0" ::"r"(stack));
+#elif defined(__hexagon__)
+  __asm__ __volatile__("r19 = %0" ::"r"(stack));
 #else
 #error Unsupported platform
 #endif
diff --git a/llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp 
b/llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp
index 0f73efc243986..efa4a5f726f00 100644
--- a/llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp
+++ b/llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp
@@ -21,6 +21,8 @@
 #include "llvm/ADT/SetVector.h"
 #include "llvm/ADT/SmallSet.h"
 #include "llvm/ADT/SmallVector.h"
+#include "llvm/BinaryFormat/Dwarf.h"
+#include "llvm/CodeGen/CFIInstBuilder.h"
 #include "llvm/CodeGen/LivePhysRegs.h"
 #include "llvm/CodeGen/MachineBasicBlock.h"
 #include "llvm/CodeGen/MachineDominators.h"
@@ -148,6 +150,89 @@
 
 using namespace llvm;
 
+static void emitSCSPrologue(MachineFunction &MF, MachineBasicBlock &MBB,
+                            MachineBasicBlock::iterator MI,
+                            const DebugLoc &DL) {
+  if (!MF.getFunction().hasFnAttribute(Attribute::ShadowCallStack))
+    return;
+
+  const auto &HST = MF.getSubtarget<HexagonSubtarget>();
+  // Hexagon saves LR (R31) via allocframe. If there is no frame, LR is
+  // not on the regular stack and does not need shadow-stack protection.
+  if (!HST.getFrameLowering()->hasFP(MF))
+    return;
+
+  Register SCSPReg = Hexagon::R19;
+  if (!MF.getSubtarget().isRegisterReservedByUser(SCSPReg))
+    report_fatal_error("Must reserve r19 to use shadow call stack on Hexagon");
+
+  const auto &HII = *HST.getInstrInfo();
+
+  // r19 = add(r19, #4)
+  BuildMI(MBB, MI, DL, HII.get(Hexagon::A2_addi), SCSPReg)
+      .addReg(SCSPReg)
+      .addImm(4)
+      .setMIFlag(MachineInstr::FrameSetup);
+  // memw(r19 + #-4) = r31
+  BuildMI(MBB, MI, DL, HII.get(Hexagon::S2_storeri_io))
+      .addReg(SCSPReg)
+      .addImm(-4)
+      .addReg(Hexagon::R31)
+      .setMIFlag(MachineInstr::FrameSetup);
+
+  MBB.addLiveIn(SCSPReg);
+
+  if (!MF.needsFrameMoves())
+    return;
+
+  // CFI: DW_CFA_val_expression for the SCS register, DW_OP_bregN -4
+  // Tells the unwinder that the SCS register at entry = current value - 4.
+  const auto &TRI = *MF.getSubtarget().getRegisterInfo();
+  unsigned DwarfSCSReg = TRI.getDwarfRegNum(SCSPReg, /*IsEH=*/true);
+  // DW_OP_breg0..DW_OP_breg31 (0x70..0x8f) are 32 opcodes indexed by
+  // register number, so the register number must fit in [0, 31].
+  assert(DwarfSCSReg < 32 && "SCS register should be < 32");
+  const char CFIInst[] = {
+      (char)dwarf::DW_CFA_val_expression,
+      (char)DwarfSCSReg,
+      2, // expression length
+      (char)(unsigned)(dwarf::DW_OP_breg0 + DwarfSCSReg),
+      (char)(-4 & 0x7f), // SLEB128 -4
+  };
+  CFIInstBuilder(MBB, MI, MachineInstr::FrameSetup)
+      .buildEscape(StringRef(CFIInst, sizeof(CFIInst)));
+}
+
+static void emitSCSEpilogue(MachineFunction &MF, MachineBasicBlock &MBB,
+                            MachineBasicBlock::iterator MI,
+                            const DebugLoc &DL) {
+  if (!MF.getFunction().hasFnAttribute(Attribute::ShadowCallStack))
+    return;
+
+  // hasFP() is true at both call sites: the non-vararg path in
+  // insertEpilogueInBlock returns early when !hasFP(), and the vararg+musl
+  // path is inside the hasFP() branch.  Check defensively.
+  if (!MF.getSubtarget<HexagonSubtarget>().getFrameLowering()->hasFP(MF))
+    report_fatal_error("SCS epilogue requires a frame");
+
+  Register SCSPReg = Hexagon::R19;
+  const auto &HII = *MF.getSubtarget<HexagonSubtarget>().getInstrInfo();
+
+  // r31 = memw(r19 + #-4)
+  BuildMI(MBB, MI, DL, HII.get(Hexagon::L2_loadri_io), Hexagon::R31)
+      .addReg(SCSPReg)
+      .addImm(-4)
+      .setMIFlag(MachineInstr::FrameDestroy);
+  // r19 = add(r19, #-4)
+  BuildMI(MBB, MI, DL, HII.get(Hexagon::A2_addi), SCSPReg)
+      .addReg(SCSPReg)
+      .addImm(-4)
+      .setMIFlag(MachineInstr::FrameDestroy);
+
+  if (MF.needsFrameMoves())
+    CFIInstBuilder(MBB, MI, MachineInstr::FrameDestroy).buildRestore(SCSPReg);
+}
+
 static cl::opt<bool> DisableDeallocRet("disable-hexagon-dealloc-ret",
     cl::Hidden, cl::desc("Disable Dealloc Return for Hexagon target"));
 
@@ -511,6 +596,19 @@ void HexagonFrameLowering::emitPrologue(MachineFunction 
&MF,
   bool PrologueStubs = false;
   insertCSRSpillsInBlock(*PrologB, CSI, HRI, PrologueStubs);
   insertPrologueInBlock(*PrologB, PrologueStubs);
+  // Insert the SCS prologue after all FrameSetup instructions so that it
+  // follows allocframe and any CSR spills in the instruction stream.  The
+  // packetizer may still fuse the SCS store with the first call in the
+  // function, but because Hexagon packets use old-value reads the original
+  // R31 is always what is stored.
+  {
+    MachineBasicBlock::iterator AfterProlog = PrologB->begin();
+    while (AfterProlog != PrologB->end() &&
+           AfterProlog->getFlag(MachineInstr::FrameSetup))
+      ++AfterProlog;
+    DebugLoc PrologDL = PrologB->findDebugLoc(AfterProlog);
+    emitSCSPrologue(MF, *PrologB, AfterProlog, PrologDL);
+  }
   updateEntryPaths(MF, *PrologB);
 
   if (EpilogB) {
@@ -786,6 +884,8 @@ void 
HexagonFrameLowering::insertEpilogueInBlock(MachineBasicBlock &MBB) const {
 
   // Handle EH_RETURN.
   if (RetOpc == Hexagon::EH_RETURN_JMPR) {
+    // EH paths overwrite R31 with a handler address; the shadow stack is
+    // not read on this path, so no SCS epilogue is needed.
     BuildMI(MBB, InsertPt, dl, HII.get(Hexagon::L2_deallocframe))
         .addDef(Hexagon::D15)
         .addReg(Hexagon::R30);
@@ -797,6 +897,10 @@ void 
HexagonFrameLowering::insertEpilogueInBlock(MachineBasicBlock &MBB) const {
 
   // Check for RESTORE_DEALLOC_RET* tail call. Don't emit an extra dealloc-
   // frame instruction if we encounter it.
+  // These spill-stub tail calls include r19 in their save range, but SCS
+  // requires -ffixed-r19, which prevents the allocator from selecting stubs
+  // that cover r19. The two features are therefore mutually exclusive and no
+  // SCS epilogue is needed here.
   if (RetOpc == Hexagon::RESTORE_DEALLOC_RET_JMP_V4 ||
       RetOpc == Hexagon::RESTORE_DEALLOC_RET_JMP_V4_PIC ||
       RetOpc == Hexagon::RESTORE_DEALLOC_RET_JMP_V4_EXT ||
@@ -816,29 +920,45 @@ void 
HexagonFrameLowering::insertEpilogueInBlock(MachineBasicBlock &MBB) const {
   // It is possible that the restoring code is a call to a library function.
   // All of the restore* functions include "deallocframe", so we need to make
   // sure that we don't add an extra one.
+  bool NeedsSCS = MF.getFunction().hasFnAttribute(Attribute::ShadowCallStack);
   bool NeedsDeallocframe = true;
+  unsigned PrevOpc = 0;
   if (!MBB.empty() && InsertPt != MBB.begin()) {
     MachineBasicBlock::iterator PrevIt = std::prev(InsertPt);
-    unsigned COpc = PrevIt->getOpcode();
-    if (COpc == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4 ||
-        COpc == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_PIC ||
-        COpc == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_EXT ||
-        COpc == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_EXT_PIC ||
-        COpc == Hexagon::PS_call_nr || COpc == Hexagon::PS_callr_nr)
+    PrevOpc = PrevIt->getOpcode();
+    if (PrevOpc == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4 ||
+        PrevOpc == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_PIC ||
+        PrevOpc == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_EXT ||
+        PrevOpc == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_EXT_PIC ||
+        PrevOpc == Hexagon::PS_call_nr || PrevOpc == Hexagon::PS_callr_nr)
       NeedsDeallocframe = false;
   }
 
   if (!MF.getSubtarget<HexagonSubtarget>().isEnvironmentMusl() ||
       !MF.getFunction().isVarArg()) {
-    if (!NeedsDeallocframe)
+    if (!NeedsDeallocframe) {
+      // RESTORE_DEALLOC_BEFORE_TAILCALL stubs include r19 in their save range,
+      // but SCS requires -ffixed-r19 which prevents the allocator from
+      // selecting stubs that cover r19, so SCS and stubs are mutually
+      // exclusive.  PS_call_nr/PS_callr_nr are noreturn calls so the shadow
+      // stack entry is never read - no SCS epilogue is needed on either path.
+      if (NeedsSCS && PrevOpc != Hexagon::PS_call_nr &&
+          PrevOpc != Hexagon::PS_callr_nr)
+        report_fatal_error("SCS with RESTORE_DEALLOC stub: "
+                           "-ffixed-r19 should have prevented this");
       return;
+    }
     // If the returning instruction is PS_jmpret, replace it with
     // dealloc_return, otherwise just add deallocframe. The function
     // could be returning via a tail call.
-    if (RetOpc != Hexagon::PS_jmpret || DisableDeallocRet) {
+    if (RetOpc != Hexagon::PS_jmpret || DisableDeallocRet || NeedsSCS) {
       BuildMI(MBB, InsertPt, dl, HII.get(Hexagon::L2_deallocframe))
       .addDef(Hexagon::D15)
       .addReg(Hexagon::R30);
+      // When shadow call stack is active, overwrite R31 restored by
+      // deallocframe with the shadow-stack copy, then retract the pointer.
+      if (NeedsSCS)
+        emitSCSEpilogue(MF, MBB, InsertPt, dl);
       return;
     }
     unsigned NewOpc = Hexagon::L4_return;
@@ -858,11 +978,14 @@ void 
HexagonFrameLowering::insertEpilogueInBlock(MachineBasicBlock &MBB) const {
     MachineBasicBlock::iterator Term = MBB.getFirstTerminator();
     MachineBasicBlock::iterator I = (Term == MBB.begin()) ? MBB.end()
                                                           : std::prev(Term);
-    if (I == MBB.end() ||
-       (I->getOpcode() != Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_EXT &&
-        I->getOpcode() != Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_EXT_PIC 
&&
-        I->getOpcode() != Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4 &&
-        I->getOpcode() != Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_PIC))
+    bool HasRestoreStub =
+        I != MBB.end() &&
+        (I->getOpcode() == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_EXT ||
+         I->getOpcode() ==
+             Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_EXT_PIC ||
+         I->getOpcode() == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4 ||
+         I->getOpcode() == Hexagon::RESTORE_DEALLOC_BEFORE_TAILCALL_V4_PIC);
+    if (!HasRestoreStub)
       BuildMI(MBB, InsertPt, dl, HII.get(Hexagon::L2_deallocframe))
         .addDef(Hexagon::D15)
         .addReg(Hexagon::R30);
@@ -870,6 +993,11 @@ void 
HexagonFrameLowering::insertEpilogueInBlock(MachineBasicBlock &MBB) const {
       BuildMI(MBB, InsertPt, dl, HII.get(Hexagon::A2_addi), SP)
         .addReg(SP)
         .addImm(RegisterSavedAreaSizePlusPadding);
+    // RESTORE_DEALLOC stubs are mutually exclusive with SCS (-ffixed-r19
+    // prevents stubs that cover r19), so only emit SCS epilogue when we
+    // emitted our own deallocframe above.
+    if (NeedsSCS && !HasRestoreStub)
+      emitSCSEpilogue(MF, MBB, InsertPt, dl);
   }
 }
 
diff --git a/llvm/test/CodeGen/Hexagon/shadow-call-stack.ll 
b/llvm/test/CodeGen/Hexagon/shadow-call-stack.ll
new file mode 100644
index 0000000000000..83f02d8afcdbf
--- /dev/null
+++ b/llvm/test/CodeGen/Hexagon/shadow-call-stack.ll
@@ -0,0 +1,133 @@
+; RUN: llc -mtriple=hexagon -mattr=+reserved-r19 < %s | FileCheck %s
+;; Test that the backend fatally errors without reserved-r19 (backstop for
+;; the driver diagnostic in SanitizerArgs.cpp).
+; RUN: not --crash llc -mtriple=hexagon < %s 2>&1 | FileCheck %s 
--check-prefix=ERR
+; RUN: llc -mtriple=hexagon -mattr=+reserved-r19 < %s | FileCheck %s 
--check-prefix=CFI
+; RUN: llc -mtriple=hexagon-unknown-linux-musl -mattr=+reserved-r19 < %s | 
FileCheck %s --check-prefix=MUSL
+
+;; Leaf function - no LR spill, SCS should not emit any r19 instructions.
+; CHECK-LABEL: leaf:
+; CHECK-NOT: r19
+; CHECK: jumpr r31
+
+;; Non-leaf function - SCS emits prologue (addi + store) and epilogue (load + 
addi).
+;; The SCS store is fused into the same packet as the first call; because
+;; Hexagon packets use old-value reads the original R31 is saved regardless.
+;; The epilogue load and addi are also in the same packet; the load uses the
+;; old (pre-decrement) r19 value per Hexagon packet semantics, and the -4
+;; offset correctly addresses the saved slot.
+; CHECK-LABEL: nonleaf:
+; CHECK:      r19 = add(r19,#4)
+; CHECK:      call bar
+; CHECK:      memw(r19+#-4) = r31
+; CHECK-DAG:  r19 = add(r19,#-4)
+; CHECK-DAG:  r31 = memw(r19+#-4)
+; CHECK:      jumpr r31
+
+;; Multi-call function - only one SCS prologue/epilogue pair, not one per call.
+; CHECK-LABEL: twocalls:
+; CHECK:      r19 = add(r19,#4)
+; CHECK:      call bar
+; CHECK:      memw(r19+#-4) = r31
+; CHECK:      call bar
+; CHECK-DAG:  r19 = add(r19,#-4)
+; CHECK-DAG:  r31 = memw(r19+#-4)
+; CHECK:      jumpr r31
+
+;; Conditional call (shrink-wrapping): the early-return path is a leaf and
+;; has no SCS prologue/epilogue.  The call path gets the SCS pair.
+; CHECK-LABEL: condcall:
+; CHECK:       if (!p0.new) jumpr:nt r31
+; CHECK:       r19 = add(r19,#4)
+; CHECK:       call bar
+; CHECK:       memw(r19+#-4) = r31
+; CHECK-DAG:   r19 = add(r19,#-4)
+; CHECK-DAG:   r31 = memw(r19+#-4)
+; CHECK:       jumpr r31
+
+;; Tail call - SCS prologue and epilogue are both emitted; the epilogue
+;; instructions and the tail jump are fused into the same packet.
+; CHECK-LABEL: tailcall:
+; CHECK:      r19 = add(r19,#4)
+; CHECK:      memw(r19+#-4) = r31
+; CHECK-DAG:  r19 = add(r19,#-4)
+; CHECK-DAG:  r31 = memw(r19+#-4)
+; CHECK-DAG:  jump bar
+
+;; Noreturn call - SCS prologue is emitted but no SCS epilogue since the
+;; function never returns.
+; CHECK-LABEL: noret:
+; CHECK:      r19 = add(r19,#4)
+; CHECK:      memw(r19+#-4) = r31
+; CHECK-NOT:  r31 = memw
+; CHECK-NOT:  r19 = add(r19,#-4)
+; CHECK-LABEL: nonleaf_cfi:
+
+;; Without r19 reserved, SCS should report an error.
+; ERR: Must reserve r19 to use shadow call stack on Hexagon
+
+;; Non-leaf with uwtable - exercises CFI escape (DW_CFA_val_expression for r19)
+;; and cfi_restore on epilogue.
+; CFI-LABEL: nonleaf_cfi:
+; CFI:        r19 = add(r19,#4)
+; CFI:        memw(r19+#-4) = r31
+; CFI:        .cfi_escape 0x16, 0x13, 0x02, 0x83, 0x7c
+; CFI-DAG:    r31 = memw(r19+#-4)
+; CFI-DAG:    r19 = add(r19,#-4)
+; CFI:        .cfi_restore r19
+; CFI:        jumpr r31
+
+;; Musl vararg - exercises the vararg epilogue path with SCS.
+; MUSL-LABEL: vararg_musl:
+; MUSL:       r19 = add(r19,#4)
+; MUSL:       memw(r19+#-4) = r31
+; MUSL-DAG:   r19 = add(r19,#-4)
+; MUSL-DAG:   r31 = memw(r19+#-4)
+; MUSL:       jumpr r31
+
+declare void @bar()
+
+define void @leaf() shadowcallstack nounwind {
+  ret void
+}
+
+define void @nonleaf() shadowcallstack nounwind {
+  call void @bar()
+  ret void
+}
+
+define void @twocalls() shadowcallstack nounwind {
+  call void @bar()
+  call void @bar()
+  ret void
+}
+
+define void @condcall(i1 %cond) shadowcallstack nounwind {
+  br i1 %cond, label %call, label %ret
+call:
+  call void @bar()
+  br label %ret
+ret:
+  ret void
+}
+
+define void @tailcall() shadowcallstack nounwind {
+  call void @bar()
+  tail call void @bar()
+  ret void
+}
+
+define void @noret() shadowcallstack nounwind {
+  call void @bar() noreturn
+  unreachable
+}
+
+define void @nonleaf_cfi() shadowcallstack uwtable {
+  call void @bar()
+  ret void
+}
+
+define void @vararg_musl(i32 %a, ...) shadowcallstack nounwind {
+  call void @bar()
+  ret void
+}

>From a73b1c8d16552809936a9d5e0fc3744d570141c5 Mon Sep 17 00:00:00 2001
From: Brian Cain <[email protected]>
Date: Tue, 23 Jun 2026 17:53:02 -0700
Subject: [PATCH 2/2] fixup! [Hexagon] Add ShadowCallStack support

---
 llvm/test/CodeGen/Hexagon/shadow-call-stack.ll | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/llvm/test/CodeGen/Hexagon/shadow-call-stack.ll 
b/llvm/test/CodeGen/Hexagon/shadow-call-stack.ll
index 83f02d8afcdbf..ae6b254f5ed8e 100644
--- a/llvm/test/CodeGen/Hexagon/shadow-call-stack.ll
+++ b/llvm/test/CodeGen/Hexagon/shadow-call-stack.ll
@@ -20,8 +20,10 @@
 ; CHECK:      r19 = add(r19,#4)
 ; CHECK:      call bar
 ; CHECK:      memw(r19+#-4) = r31
+; CHECK:      {
 ; CHECK-DAG:  r19 = add(r19,#-4)
 ; CHECK-DAG:  r31 = memw(r19+#-4)
+; CHECK:      }
 ; CHECK:      jumpr r31
 
 ;; Multi-call function - only one SCS prologue/epilogue pair, not one per call.
@@ -30,8 +32,10 @@
 ; CHECK:      call bar
 ; CHECK:      memw(r19+#-4) = r31
 ; CHECK:      call bar
+; CHECK:      {
 ; CHECK-DAG:  r19 = add(r19,#-4)
 ; CHECK-DAG:  r31 = memw(r19+#-4)
+; CHECK:      }
 ; CHECK:      jumpr r31
 
 ;; Conditional call (shrink-wrapping): the early-return path is a leaf and
@@ -41,8 +45,10 @@
 ; CHECK:       r19 = add(r19,#4)
 ; CHECK:       call bar
 ; CHECK:       memw(r19+#-4) = r31
+; CHECK:       {
 ; CHECK-DAG:   r19 = add(r19,#-4)
 ; CHECK-DAG:   r31 = memw(r19+#-4)
+; CHECK:       }
 ; CHECK:       jumpr r31
 
 ;; Tail call - SCS prologue and epilogue are both emitted; the epilogue
@@ -50,9 +56,11 @@
 ; CHECK-LABEL: tailcall:
 ; CHECK:      r19 = add(r19,#4)
 ; CHECK:      memw(r19+#-4) = r31
+; CHECK:      {
 ; CHECK-DAG:  r19 = add(r19,#-4)
 ; CHECK-DAG:  r31 = memw(r19+#-4)
 ; CHECK-DAG:  jump bar
+; CHECK:      }
 
 ;; Noreturn call - SCS prologue is emitted but no SCS epilogue since the
 ;; function never returns.
@@ -72,8 +80,10 @@
 ; CFI:        r19 = add(r19,#4)
 ; CFI:        memw(r19+#-4) = r31
 ; CFI:        .cfi_escape 0x16, 0x13, 0x02, 0x83, 0x7c
+; CFI:        {
 ; CFI-DAG:    r31 = memw(r19+#-4)
 ; CFI-DAG:    r19 = add(r19,#-4)
+; CFI:        }
 ; CFI:        .cfi_restore r19
 ; CFI:        jumpr r31
 
@@ -81,8 +91,10 @@
 ; MUSL-LABEL: vararg_musl:
 ; MUSL:       r19 = add(r19,#4)
 ; MUSL:       memw(r19+#-4) = r31
+; MUSL:       {
 ; MUSL-DAG:   r19 = add(r19,#-4)
 ; MUSL-DAG:   r31 = memw(r19+#-4)
+; MUSL:       }
 ; MUSL:       jumpr r31
 
 declare void @bar()

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to