[clang] [llvm] [Hexagon] Fix unusable SCS reg, make it selectable (PR #213820)
Brian Cain via cfe-commits
cfe-commits at lists.llvm.org
Mon Aug 10 05:31:30 PDT 2026
https://github.com/androm3da updated https://github.com/llvm/llvm-project/pull/213820
>From 37bfada2b7996a9b47bc762b6f2d990862cd396d Mon Sep 17 00:00:00 2001
From: Brian Cain <brian.cain at oss.qualcomm.com>
Date: Mon, 3 Aug 2026 18:47:27 -0500
Subject: [PATCH] [Hexagon] Fix unusable SCS reg, make it selectable
SCS hardcoded r19 as the shadow call stack pointer and required -ffixed-r19.
That was the wrong register to pick: r19 is precisely the one the intended
consumers cannot give up, so the feature was unusable in practice.
* The Hexagon Linux kernel already reserves r19 for its thread-info
pointer (arch/hexagon/Makefile: "TIR_NAME := r19", documented there as
not configurable because it is hard-coded in several files).
* hexagon-hypervisor reserves r20-r28 (kernel/CMakeLists.txt), with r28
bound to a register global (H2K_gp).
That leaves h2 only r16-r19, so no single hardcoded choice can serve both
consumers.
Intersecting that with the callee-saved regs leaves r1{6,7,8}. So the
new default is r18.
* Add scs-reg-r{16..27} subtarget features
* Add a -mscs-reg=<reg> flag. It's in m_Group instead of
m_hexagon_Features_Group, since the latter is consumed by
handleTargetFeaturesGroup() and would synthesize a bogus
"+scs-reg=" feature.
Assisted-by: Claude
---
clang/docs/ReleaseNotes.md | 5 +
clang/docs/ShadowCallStack.md | 16 +-
clang/include/clang/Options/Options.td | 8 +
clang/lib/Driver/SanitizerArgs.cpp | 57 +++++-
clang/lib/Driver/ToolChains/Hexagon.cpp | 17 ++
.../fsanitize-shadow-call-stack-hexagon.c | 57 +++++-
clang/test/Driver/hexagon-toolchain-linux.c | 12 +-
llvm/lib/Target/Hexagon/Hexagon.td | 8 +
.../Target/Hexagon/HexagonFrameLowering.cpp | 76 +++++---
llvm/lib/Target/Hexagon/HexagonSubtarget.cpp | 19 ++
llvm/lib/Target/Hexagon/HexagonSubtarget.h | 10 +
.../test/CodeGen/Hexagon/shadow-call-stack.ll | 171 ++++++++++++------
12 files changed, 362 insertions(+), 94 deletions(-)
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index fc947d05fad83..39f27ac3d94dc 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -179,6 +179,11 @@ features cannot lower the translation-unit ABI level;
the C++ standard library selected by the driver and the include directories
added for it.
+- Added `-mscs-reg=<reg>` on Hexagon to select which callee-saved register
+ (`r16`-`r27`, default `r18`) holds the shadow call stack pointer under
+ `-fsanitize=shadow-call-stack`. The selected register must also be reserved
+ with the matching `-ffixed-<reg>`.
+
### Deprecated Compiler Flags
### Modified Compiler Flags
diff --git a/clang/docs/ShadowCallStack.md b/clang/docs/ShadowCallStack.md
index 62bfaa83040ea..9505fbe28be04 100644
--- a/clang/docs/ShadowCallStack.md
+++ b/clang/docs/ShadowCallStack.md
@@ -7,7 +7,7 @@
## Introduction
ShadowCallStack is an instrumentation pass, currently only implemented for
-aarch64 and RISC-V, that protects programs against return address overwrites
+aarch64, RISC-V and Hexagon, that protects programs against return address overwrites
(e.g. stack buffer overflows.) It works by saving a function's return address
to a separately allocated 'shadow call stack' in the function prolog in
non-leaf functions and loading the return address from the shadow call stack
@@ -71,6 +71,16 @@ principle, a platform could choose to reserve and use another register for
ShadowCallStack, but this would be incompatible with the ABI standards
published in AAPCS64 and the RISC-V psABI.
+On Hexagon, `SCSReg` defaults to `r18` and is not fixed by the ABI, because
+the Hexagon ABI does not designate a platform register. Any of the
+callee-saved registers `r16`-`r27` may be used instead, selected with
+`-mscs-reg=<reg>`; caller-saved registers cannot hold the pointer across a
+call. The selected register must always be reserved with the matching
+`-ffixed-<reg>`, and clang errors out if it is not. This flexibility exists
+because different Hexagon environments have already claimed different
+registers -- for example the Hexagon Linux kernel reserves `r19` for its
+thread-info pointer, which is why `r18` rather than `r19` is the default.
+
Special unwind information is required on functions that are compiled
with ShadowCallStack and that may be unwound, i.e. functions compiled with
`-fexceptions` (which is the default in C++). Some unwinders (such as the
@@ -141,6 +151,10 @@ However, it is important to disable GP relaxation in the linker when using the
software based shadow call stack on RISC-V. This can be done with the
`--no-relax-gp` flag in GNU ld, and is off by default in LLD.
+On Hexagon you also need to reserve the shadow call stack pointer register,
+i.e. `-ffixed-r18` for the default, or `-mscs-reg=<reg> -ffixed-<reg>` to use
+a different one.
+
### Low-level API
#### `__has_feature(shadow_call_stack)`
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index cf66ee3e52f2d..5177656bece56 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -7181,6 +7181,14 @@ foreach i = {6-7} in
HelpText<"Reserve register r"#i#" (Hexagon only)">;
def ffixed_r19: Flag<["-"], "ffixed-r19">, Group<m_Group>,
HelpText<"Reserve register r19 (Hexagon/x86_64 only; x86_64 requires APX EGPR)">;
+// Deliberately in m_Group, not m_hexagon_Features_Group: the latter is swept
+// up by handleTargetFeaturesGroup(), which would turn this into a bogus
+// "+scs-reg=" target feature. It is translated explicitly in Hexagon.cpp.
+def mhexagon_scs_reg : Joined<["-"], "mscs-reg=">,
+ Group<m_Group>,
+ HelpText<"Register holding the shadow call stack pointer: one of the "
+ "callee-saved registers r16-r27, default r18 (Hexagon only). "
+ "The chosen register must also be reserved with -ffixed-<reg>.">;
} // let Flags = [TargetSpecific]
def mmemops : Flag<["-"], "mmemops">, Group<m_hexagon_Features_Group>,
Visibility<[ClangOption, CC1Option]>,
diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp
index 84a3238d07b7f..778cde8285aaf 100644
--- a/clang/lib/Driver/SanitizerArgs.cpp
+++ b/clang/lib/Driver/SanitizerArgs.cpp
@@ -163,6 +163,38 @@ static std::string describeSanitizeArg(const llvm::opt::Arg *A,
/// Sanitizers set.
static std::string toString(const clang::SanitizerSet &Sanitizers);
+/// Map a Hexagon callee-saved register number (16-27) to its -ffixed-rN
+/// option, used to check that the shadow call stack pointer is reserved.
+static options::ID getHexagonFixedRegOption(unsigned RegNo) {
+ switch (RegNo) {
+ case 16:
+ return options::OPT_ffixed_r16;
+ case 17:
+ return options::OPT_ffixed_r17;
+ case 18:
+ return options::OPT_ffixed_r18;
+ case 19:
+ return options::OPT_ffixed_r19;
+ case 20:
+ return options::OPT_ffixed_r20;
+ case 21:
+ return options::OPT_ffixed_r21;
+ case 22:
+ return options::OPT_ffixed_r22;
+ case 23:
+ return options::OPT_ffixed_r23;
+ case 24:
+ return options::OPT_ffixed_r24;
+ case 25:
+ return options::OPT_ffixed_r25;
+ case 26:
+ return options::OPT_ffixed_r26;
+ case 27:
+ return options::OPT_ffixed_r27;
+ }
+ llvm_unreachable("not a Hexagon callee-saved register");
+}
+
/// Produce a string containing comma-separated names of sanitizers and
/// sanitizer groups in \p Sanitizers set.
static std::string toStringWithGroups(const clang::SanitizerSet &Sanitizers);
@@ -784,11 +816,26 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC,
}
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";
+ TC.getTriple().getArch() == llvm::Triple::hexagon && DiagnoseErrors) {
+ // The register holding the shadow call stack pointer must be reserved, so
+ // that neither the register allocator uses it nor the prologue saves and
+ // restores it as an ordinary callee-saved register. It defaults to r18
+ // and is selectable with -mscs-reg=.
+ unsigned RegNo = 18;
+ if (Arg *A = Args.getLastArg(options::OPT_mhexagon_scs_reg)) {
+ StringRef Val(A->getValue());
+ unsigned Parsed = 0;
+ // An out-of-range or malformed value is diagnosed by the toolchain; fall
+ // back to the default here so we do not emit a second, confusing error.
+ if (Val.consume_front("r") && !Val.getAsInteger(10, Parsed) &&
+ Parsed >= 16 && Parsed <= 27)
+ RegNo = Parsed;
+ }
+ if (!Args.hasArg(getHexagonFixedRegOption(RegNo)))
+ D.Diag(diag::err_drv_argument_only_allowed_with)
+ << lastArgumentForMask(D, Args,
+ Kinds & SanitizerKind::ShadowCallStack)
+ << ("-ffixed-r" + Twine(RegNo)).str();
}
// Report error if there are non-trapping sanitizers that require
diff --git a/clang/lib/Driver/ToolChains/Hexagon.cpp b/clang/lib/Driver/ToolChains/Hexagon.cpp
index 2ddc15ddbd818..76c5cafd77bc9 100644
--- a/clang/lib/Driver/ToolChains/Hexagon.cpp
+++ b/clang/lib/Driver/ToolChains/Hexagon.cpp
@@ -893,6 +893,23 @@ void HexagonToolChain::addClangTargetOptions(const ArgList &DriverArgs,
CC1Args.push_back(Feature);
}
}
+
+ // Select the shadow call stack pointer register. It has to hold a value
+ // across arbitrary calls, so only the callee-saved registers r16-r27 are
+ // allowed (Hexagon ABI, "Register usage across calls").
+ if (Arg *A = DriverArgs.getLastArg(options::OPT_mhexagon_scs_reg)) {
+ StringRef Val(A->getValue());
+ unsigned RegNo = 0;
+ if (!Val.consume_front("r") || Val.getAsInteger(10, RegNo) || RegNo < 16 ||
+ RegNo > 27) {
+ getDriver().Diag(diag::err_drv_invalid_value)
+ << A->getSpelling() << A->getValue();
+ } else {
+ CC1Args.push_back("-target-feature");
+ CC1Args.push_back(DriverArgs.MakeArgString("+scs-reg-r" + Twine(RegNo)));
+ }
+ }
+
if (isAutoHVXEnabled(DriverArgs)) {
CC1Args.push_back("-mllvm");
CC1Args.push_back("-hexagon-autohvx");
diff --git a/clang/test/Driver/fsanitize-shadow-call-stack-hexagon.c b/clang/test/Driver/fsanitize-shadow-call-stack-hexagon.c
index 89d186315d9c1..ecab5e4d1ba82 100644
--- a/clang/test/Driver/fsanitize-shadow-call-stack-hexagon.c
+++ b/clang/test/Driver/fsanitize-shadow-call-stack-hexagon.c
@@ -1,12 +1,59 @@
-// Test that -fsanitize=shadow-call-stack on Hexagon requires -ffixed-r19.
+// Test that -fsanitize=shadow-call-stack on Hexagon requires the register
+// holding the shadow call stack pointer to be reserved, and that -mscs-reg=
+// selects which register that is.
// 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: | FileCheck %s --check-prefix=NO-FIXED
// RUN: %clang --target=hexagon-unknown-linux-musl \
+// RUN: -fsanitize=shadow-call-stack -ffixed-r18 %s -### 2>&1 \
+// RUN: | FileCheck %s --check-prefix=DEFAULT
+
+/// Reserving some other register does not satisfy the requirement.
+// RUN: not %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
+// RUN: | FileCheck %s --check-prefix=NO-FIXED
+
+/// -mscs-reg= moves the requirement to the selected register.
+// RUN: not %clang --target=hexagon-unknown-linux-musl \
+// RUN: -fsanitize=shadow-call-stack -mscs-reg=r16 -ffixed-r18 %s -### 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NO-FIXED-R16
+
+// RUN: %clang --target=hexagon-unknown-linux-musl \
+// RUN: -fsanitize=shadow-call-stack -mscs-reg=r16 -ffixed-r16 %s -### 2>&1 \
+// RUN: | FileCheck %s --check-prefix=SCS-R16
+
+/// -mscs-reg= is accepted without the sanitizer, and still sets the feature.
+// RUN: %clang --target=hexagon-unknown-linux-musl -mscs-reg=r27 %s -### 2>&1 \
+// RUN: | FileCheck %s --check-prefix=SCS-R27
+
+/// Only the callee-saved registers r16-r27 can hold the pointer across calls.
+// RUN: not %clang --target=hexagon-unknown-linux-musl \
+// RUN: -fsanitize=shadow-call-stack -mscs-reg=r15 -ffixed-r15 %s -### 2>&1 \
+// RUN: | FileCheck %s --check-prefix=BAD-REG
+
+// RUN: not %clang --target=hexagon-unknown-linux-musl \
+// RUN: -fsanitize=shadow-call-stack -mscs-reg=r28 -ffixed-r28 %s -### 2>&1 \
+// RUN: | FileCheck %s --check-prefix=BAD-REG-28
+
+// RUN: not %clang --target=hexagon-unknown-linux-musl \
+// RUN: -fsanitize=shadow-call-stack -mscs-reg=sp -ffixed-r18 %s -### 2>&1 \
+// RUN: | FileCheck %s --check-prefix=BAD-REG-SP
+
+// NO-FIXED: error: invalid argument '-fsanitize=shadow-call-stack' only allowed with '-ffixed-r18'
+// DEFAULT-DAG: "-target-feature" "+reserved-r18"
+// DEFAULT-DAG: "-fsanitize=shadow-call-stack"
+
+// NO-FIXED-R16: error: invalid argument '-fsanitize=shadow-call-stack' only allowed with '-ffixed-r16'
+// SCS-R16-DAG: "-target-feature" "+reserved-r16"
+// SCS-R16-DAG: "-target-feature" "+scs-reg-r16"
+// SCS-R16-DAG: "-fsanitize=shadow-call-stack"
+
+// SCS-R27: "-target-feature" "+scs-reg-r27"
+/// -mscs-reg= must not also be swept into a target feature by its option group.
+// SCS-R27-NOT: "+scs-reg="
-// HEXAGON-SCS-NO-R19: error: invalid argument '-fsanitize=shadow-call-stack' only allowed with '-ffixed-r19'
-// HEXAGON-SCS-WITH-R19: "-fsanitize=shadow-call-stack"
+// BAD-REG: error: invalid value 'r15' in '-mscs-reg='
+// BAD-REG-28: error: invalid value 'r28' in '-mscs-reg='
+// BAD-REG-SP: error: invalid value 'sp' in '-mscs-reg='
diff --git a/clang/test/Driver/hexagon-toolchain-linux.c b/clang/test/Driver/hexagon-toolchain-linux.c
index 1365c6adc22eb..5f998d2244067 100644
--- a/clang/test/Driver/hexagon-toolchain-linux.c
+++ b/clang/test/Driver/hexagon-toolchain-linux.c
@@ -272,20 +272,20 @@
// RUN: -ccc-install-dir %S/Inputs/hexagon_tree/Tools/bin \
// RUN: -mcpu=hexagonv60 \
// RUN: -fuse-ld=lld \
-// RUN: -fsanitize=shadow-call-stack -ffixed-r19 \
+// RUN: -fsanitize=shadow-call-stack -ffixed-r18 \
// RUN: --sysroot=%S/Inputs/basic_linux_libcxx_tree %s 2>&1 | FileCheck -check-prefix=CHECK-SCS %s
// CHECK-SCS: "-L{{[^"]*}}basic_linux_libcxx_tree{{/|\\\\}}usr{{/|\\\\}}lib{{/|\\\\}}scs"
// CHECK-SCS-SAME: "-L{{[^"]*}}basic_linux_libcxx_tree{{/|\\\\}}usr{{/|\\\\}}lib"
// -----------------------------------------------------------------------------
-// Library paths: -ffixed-r19 alone must NOT select the scs multilib
+// Library paths: -ffixed-r18 alone must NOT select the scs multilib
// -----------------------------------------------------------------------------
// RUN: %clang -### --target=hexagon-unknown-linux-musl \
// RUN: -ccc-install-dir %S/Inputs/hexagon_tree/Tools/bin \
// RUN: -mcpu=hexagonv60 \
// RUN: -fuse-ld=lld \
-// RUN: -ffixed-r19 \
-// RUN: --sysroot=%S/Inputs/basic_linux_libcxx_tree %s 2>&1 | FileCheck -check-prefix=CHECK-R19-ONLY %s
-// CHECK-R19-ONLY-NOT: "-L{{.*}}{{/|\\\\}}scs"
+// RUN: -ffixed-r18 \
+// RUN: --sysroot=%S/Inputs/basic_linux_libcxx_tree %s 2>&1 | FileCheck -check-prefix=CHECK-R18-ONLY %s
+// CHECK-R18-ONLY-NOT: "-L{{.*}}{{/|\\\\}}scs"
// -----------------------------------------------------------------------------
// Startup object: -fsanitize=shadow-call-stack links the scs crt1.o, not the
// base crt1.o. Selection is on the multilib in effect, not file presence, so
@@ -295,7 +295,7 @@
// RUN: -ccc-install-dir %S/Inputs/hexagon_tree/Tools/bin \
// RUN: -mcpu=hexagonv60 \
// RUN: -fuse-ld=lld \
-// RUN: -fsanitize=shadow-call-stack -ffixed-r19 \
+// RUN: -fsanitize=shadow-call-stack -ffixed-r18 \
// RUN: --sysroot=%S/Inputs/basic_linux_libcxx_tree %s 2>&1 | FileCheck -check-prefix=CHECK-SCS-CRT %s
// CHECK-SCS-CRT: "{{[^"]*}}basic_linux_libcxx_tree{{/|\\\\}}usr{{/|\\\\}}lib{{/|\\\\}}scs{{/|\\\\}}crt1.o"
// CHECK-SCS-CRT-NOT: "{{[^"]*}}basic_linux_libcxx_tree{{/|\\\\}}usr{{/|\\\\}}lib{{/|\\\\}}crt1.o"
diff --git a/llvm/lib/Target/Hexagon/Hexagon.td b/llvm/lib/Target/Hexagon/Hexagon.td
index b6fe3b4c80777..7625307172abf 100644
--- a/llvm/lib/Target/Hexagon/Hexagon.td
+++ b/llvm/lib/Target/Hexagon/Hexagon.td
@@ -120,6 +120,14 @@ foreach i = {6-28} in
def FeatureReservedR#i : SubtargetFeature<"reserved-r"#i,
"UserReservedRegister[Hexagon::R"#i#"]",
"true", "Reserve register R"#i>;
+// Selects which register holds the shadow call stack pointer. Only the
+// callee-saved registers (R16-R27, see the Hexagon ABI "Register usage across
+// calls") can hold a value across a call, so only those are offered here. At
+// most one may be selected; if none is, R18 is used (see getSCSPReg()).
+foreach i = {16-27} in
+ def FeatureSCSRegR#i : SubtargetFeature<"scs-reg-r"#i,
+ "SCSPointerRegister[Hexagon::R"#i#"]",
+ "true", "Use register R"#i#" as the shadow call stack pointer">;
def FeatureNoreturnStackElim: SubtargetFeature<"noreturn-stack-elim",
"NoreturnStackElim", "true",
"Eliminate stack allocation in a noreturn function when possible">;
diff --git a/llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp b/llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp
index 7f9c0ae985638..b665ef98c5e44 100644
--- a/llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp
+++ b/llvm/lib/Target/Hexagon/HexagonFrameLowering.cpp
@@ -163,18 +163,31 @@ static void emitSCSPrologue(MachineFunction &MF, MachineBasicBlock &MBB,
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");
+ // The shadow call stack pointer has to survive arbitrary calls, so it is
+ // always one of the callee-saved registers R16-R27 (Hexagon ABI, "Register
+ // usage across calls"). It must also be reserved: besides keeping the
+ // register allocator away from it, reserving it keeps it out of the
+ // callee-saved set, so it is never spilled and restored as an ordinary
+ // callee-saved register - which would leave the epilogue below reading the
+ // *caller's* shadow-stack slot. The spill stubs are handled separately in
+ // useSpillFunction()/useRestoreFunction().
+ Register SCSPReg = HST.getSCSPReg();
+ const auto &HRI = *HST.getRegisterInfo();
+ if (!HST.isRegisterReservedByUser(SCSPReg))
+ // Lower-cased to match the spelling of the -ffixed-<reg> flag the user
+ // needs to pass; TRI names the register "R18".
+ report_fatal_error(Twine("Must reserve ") +
+ StringRef(HRI.getName(SCSPReg)).lower() +
+ " to use shadow call stack on Hexagon");
const auto &HII = *HST.getInstrInfo();
- // r19 = add(r19, #4)
+ // SCSPReg = add(SCSPReg, #4)
BuildMI(MBB, MI, DL, HII.get(Hexagon::A2_addi), SCSPReg)
.addReg(SCSPReg)
.addImm(4)
.setMIFlag(MachineInstr::FrameSetup);
- // memw(r19 + #-4) = r31
+ // memw(SCSPReg + #-4) = r31
BuildMI(MBB, MI, DL, HII.get(Hexagon::S2_storeri_io))
.addReg(SCSPReg)
.addImm(-4)
@@ -188,8 +201,7 @@ static void emitSCSPrologue(MachineFunction &MF, MachineBasicBlock &MBB,
// 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);
+ unsigned DwarfSCSReg = HRI.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");
@@ -216,15 +228,16 @@ static void emitSCSEpilogue(MachineFunction &MF, MachineBasicBlock &MBB,
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();
+ const auto &HST = MF.getSubtarget<HexagonSubtarget>();
+ Register SCSPReg = HST.getSCSPReg();
+ const auto &HII = *HST.getInstrInfo();
- // r31 = memw(r19 + #-4)
+ // r31 = memw(SCSPReg + #-4)
BuildMI(MBB, MI, DL, HII.get(Hexagon::L2_loadri_io), Hexagon::R31)
.addReg(SCSPReg)
.addImm(-4)
.setMIFlag(MachineInstr::FrameDestroy);
- // r19 = add(r19, #-4)
+ // SCSPReg = add(SCSPReg, #-4)
BuildMI(MBB, MI, DL, HII.get(Hexagon::A2_addi), SCSPReg)
.addReg(SCSPReg)
.addImm(-4)
@@ -918,9 +931,8 @@ 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
+ // These are restore stubs, which useRestoreFunction() never selects when SCS
+ // is active (they do deallocframe+jumpr, bypassing the SCS epilogue), so no
// SCS epilogue is needed here.
if (RetOpc == Hexagon::RESTORE_DEALLOC_RET_JMP_V4 ||
RetOpc == Hexagon::RESTORE_DEALLOC_RET_JMP_V4_PIC ||
@@ -958,15 +970,14 @@ void HexagonFrameLowering::insertEpilogueInBlock(MachineBasicBlock &MBB) const {
if (!MF.getSubtarget<HexagonSubtarget>().isEnvironmentMusl() ||
!MF.getFunction().isVarArg()) {
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.
+ // RESTORE_DEALLOC_BEFORE_TAILCALL is a restore stub, which
+ // useRestoreFunction() never selects when SCS is active.
+ // 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");
+ "useRestoreFunction() should have prevented this");
return;
}
// If the returning instruction is PS_jmpret, replace it with
@@ -1014,9 +1025,9 @@ 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.
+ // RESTORE_DEALLOC stubs are never selected when SCS is active (see
+ // useRestoreFunction()), so only emit the SCS epilogue when we emitted
+ // our own deallocframe above.
if (NeedsSCS && !HasRestoreStub)
emitSCSEpilogue(MF, MBB, InsertPt, dl);
}
@@ -2996,6 +3007,25 @@ bool HexagonFrameLowering::useSpillFunction(const MachineFunction &MF,
if (NumCSI <= 1)
return false;
+ // Every spill stub saves the whole range starting at R16
+ // (__save_r16_through_rNN), so a stub whose range reached the shadow call
+ // stack pointer register would spill it along with the real callee-saved
+ // registers - and since the SCS register is reserved it is absent from CSI,
+ // so the stub's fixed frame layout would not match the one the compiler
+ // assigned.
+ //
+ // shouldInlineCSR() above already makes this unreachable: it only lets a
+ // stub through when CSI is a contiguous run of double registers starting at
+ // D8, and reserving the SCS register always breaks the double it belongs
+ // to, leaving its partner in CSI as a lone single register. This is a
+ // cheap safety net so the guarantee does not rest on that reasoning alone.
+ if (MF.getFunction().hasFnAttribute(Attribute::ShadowCallStack)) {
+ const auto &HST = MF.getSubtarget<HexagonSubtarget>();
+ Register MaxReg = getMaxCalleeSavedReg(CSI, *HST.getRegisterInfo());
+ if (HST.getSCSPReg().id() <= MaxReg.id())
+ return false;
+ }
+
unsigned Threshold = isOptSize(MF) ? SpillFuncThresholdOs
: SpillFuncThreshold;
return Threshold < NumCSI;
diff --git a/llvm/lib/Target/Hexagon/HexagonSubtarget.cpp b/llvm/lib/Target/Hexagon/HexagonSubtarget.cpp
index 66c8b0a67169d..b8838dc54da33 100644
--- a/llvm/lib/Target/Hexagon/HexagonSubtarget.cpp
+++ b/llvm/lib/Target/Hexagon/HexagonSubtarget.cpp
@@ -138,6 +138,25 @@ HexagonSubtarget::initializeSubtargetDependencies(StringRef CPU, StringRef FS) {
std::string FeatureString = Features.getString();
ParseSubtargetFeatures(CPUString, /*TuneCPU*/ CPUString, FeatureString);
+ // Resolve the shadow call stack pointer register. At most one "scs-reg-rN"
+ // feature may be given; R18 is the default. R18 is chosen because it is the
+ // lowest callee-saved register that neither the Hexagon Linux kernel (which
+ // reserves R19 for the thread-info pointer) nor code that reserves the upper
+ // callee-saved range already claims.
+ static_assert(Hexagon::R27 - Hexagon::R16 == 11,
+ "Callee-saved R16-R27 are assumed to be consecutive");
+ SCSPReg = Hexagon::R18;
+ bool SCSRegSelected = false;
+ for (unsigned Reg = Hexagon::R16; Reg <= Hexagon::R27; ++Reg) {
+ if (!SCSPointerRegister[Reg])
+ continue;
+ if (SCSRegSelected)
+ report_fatal_error(
+ "Only one shadow call stack pointer register may be selected");
+ SCSPReg = Reg;
+ SCSRegSelected = true;
+ }
+
if (useHVXV68Ops())
UseHVXFloatingPoint = UseHVXIEEEFPOps || UseHVXQFloatOps;
diff --git a/llvm/lib/Target/Hexagon/HexagonSubtarget.h b/llvm/lib/Target/Hexagon/HexagonSubtarget.h
index 2019bbf64b365..2b298f5adfa52 100644
--- a/llvm/lib/Target/Hexagon/HexagonSubtarget.h
+++ b/llvm/lib/Target/Hexagon/HexagonSubtarget.h
@@ -65,8 +65,13 @@ class HexagonSubtarget : public HexagonGenSubtargetInfo {
bool HasMemNoShuf = false;
bool EnableDuplex = false;
std::bitset<Hexagon::NUM_TARGET_REGS> UserReservedRegister;
+ std::bitset<Hexagon::NUM_TARGET_REGS> SCSPointerRegister;
bool NoreturnStackElim = false;
+ /// Register holding the shadow call stack pointer, resolved from
+ /// SCSPointerRegister in initializeSubtargetDependencies().
+ Register SCSPReg;
+
public:
Hexagon::ArchEnum HexagonArchVersion;
Hexagon::ArchEnum HexagonHVXVersion = Hexagon::ArchEnum::NoArch;
@@ -291,6 +296,11 @@ class HexagonSubtarget : public HexagonGenSubtargetInfo {
assert(i.id() < Hexagon::NUM_TARGET_REGS && "Register out of range");
return UserReservedRegister[i.id()];
}
+
+ /// Returns the register that holds the shadow call stack pointer. Defaults
+ /// to R18, overridable with the "scs-reg-rN" subtarget features.
+ Register getSCSPReg() const { return SCSPReg; }
+
bool usePredicatedCalls() const;
bool noreturnStackElim() const { return NoreturnStackElim; }
diff --git a/llvm/test/CodeGen/Hexagon/shadow-call-stack.ll b/llvm/test/CodeGen/Hexagon/shadow-call-stack.ll
index ddcca760cd474..3573a7910adc2 100644
--- a/llvm/test/CodeGen/Hexagon/shadow-call-stack.ll
+++ b/llvm/test/CodeGen/Hexagon/shadow-call-stack.ll
@@ -1,40 +1,74 @@
-; 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).
+;; r18 is the default shadow call stack pointer register; reserving it is all
+;; that is required.
+; RUN: llc -mtriple=hexagon -mattr=+reserved-r18 < %s | FileCheck %s
+; RUN: llc -mtriple=hexagon -mattr=+reserved-r18 < %s | FileCheck %s --check-prefix=CFI
+; RUN: llc -mtriple=hexagon-unknown-linux-musl -mattr=+reserved-r18 < %s | FileCheck %s --check-prefix=MUSL
+
+;; The backend fatally errors unless the SCS register is reserved (backstop for
+;; the driver diagnostic in SanitizerArgs.cpp). Reserving some other register
+;; does not help.
; 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
+; RUN: not --crash llc -mtriple=hexagon -mattr=+reserved-r19 < %s 2>&1 | FileCheck %s --check-prefix=ERR
+
+;; scs-reg-rN selects a different register; the diagnostic follows it.
+; RUN: llc -mtriple=hexagon -mattr=+scs-reg-r16,+reserved-r16 < %s \
+; RUN: | FileCheck %s --check-prefix=R16
+; RUN: not --crash llc -mtriple=hexagon -mattr=+scs-reg-r16,+reserved-r18 < %s 2>&1 \
+; RUN: | FileCheck %s --check-prefix=ERR16
+; RUN: not --crash llc -mtriple=hexagon -mattr=+scs-reg-r16,+scs-reg-r17 < %s 2>&1 \
+; RUN: | FileCheck %s --check-prefix=ERRMULTI
+
+;; Only one scs-reg-rN may be given.
+; ERRMULTI: Only one shadow call stack pointer register may be selected
+
+;; With r16 selected, the prologue/epilogue use r16 and leave r18 alone.
+; R16-LABEL: nonleaf:
+; R16: r16 = add(r16,#4)
+; R16: call bar
+; R16: memw(r16+#-4) = r31
+; R16: {
+; R16-DAG: r16 = add(r16,#-4)
+; R16-DAG: r31 = memw(r16+#-4)
+; R16: }
+; R16: jumpr r31
+
+;; Every spill stub saves the range starting at r16, so with r16 as the SCS
+;; register no stub can ever be used - reserving r16 removes d8 from the
+;; callee-saved set entirely.
+; R16-LABEL: minsize_multicall:
+; R16-NOT: __save_
+; R16-NOT: __restore_
-;; Leaf function - no LR spill, SCS should not emit any r19 instructions.
+;; Leaf function - no LR spill, SCS should not emit any r18 instructions.
; CHECK-LABEL: leaf:
-; CHECK-NOT: r19
+; CHECK-NOT: r18
; 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
+;; old (pre-decrement) r18 value per Hexagon packet semantics, and the -4
;; offset correctly addresses the saved slot.
; CHECK-LABEL: nonleaf:
-; CHECK: r19 = add(r19,#4)
+; CHECK: r18 = add(r18,#4)
; CHECK: call bar
-; CHECK: memw(r19+#-4) = r31
+; CHECK: memw(r18+#-4) = r31
; CHECK: {
-; CHECK-DAG: r19 = add(r19,#-4)
-; CHECK-DAG: r31 = memw(r19+#-4)
+; CHECK-DAG: r18 = add(r18,#-4)
+; CHECK-DAG: r31 = memw(r18+#-4)
; CHECK: }
; 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: r18 = add(r18,#4)
; CHECK: call bar
-; CHECK: memw(r19+#-4) = r31
+; CHECK: memw(r18+#-4) = r31
; CHECK: call bar
; CHECK: {
-; CHECK-DAG: r19 = add(r19,#-4)
-; CHECK-DAG: r31 = memw(r19+#-4)
+; CHECK-DAG: r18 = add(r18,#-4)
+; CHECK-DAG: r31 = memw(r18+#-4)
; CHECK: }
; CHECK: jumpr r31
@@ -42,44 +76,44 @@
;; 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: r18 = add(r18,#4)
; CHECK: call bar
-; CHECK: memw(r19+#-4) = r31
+; CHECK: memw(r18+#-4) = r31
; CHECK: {
-; CHECK-DAG: r19 = add(r19,#-4)
-; CHECK-DAG: r31 = memw(r19+#-4)
+; CHECK-DAG: r18 = add(r18,#-4)
+; CHECK-DAG: r31 = memw(r18+#-4)
; CHECK: }
; 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: r18 = add(r18,#4)
+; CHECK: memw(r18+#-4) = r31
; CHECK: {
-; CHECK-DAG: r19 = add(r19,#-4)
-; CHECK-DAG: r31 = memw(r19+#-4)
+; CHECK-DAG: r18 = add(r18,#-4)
+; CHECK-DAG: r31 = memw(r18+#-4)
; CHECK-DAG: jump bar
; CHECK: }
;; 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: r18 = add(r18,#4)
+; CHECK: memw(r18+#-4) = r31
; CHECK-NOT: r31 = memw
-; CHECK-NOT: r19 = add(r19,#-4)
+; CHECK-NOT: r18 = add(r18,#-4)
; CHECK-LABEL: nonleaf_cfi:
;; Minsize + multiple callee-saved registers: the restore stub
;; (__restore_r16_through_r17_and_deallocframe) must NOT be used when SCS is
;; active because it performs deallocframe+jumpr without the SCS epilogue.
; CHECK-LABEL: minsize_multicall:
-; CHECK: r19 = add(r19,#4)
-; CHECK: memw(r19+#-4) = r31
+; CHECK: r18 = add(r18,#4)
+; CHECK: memw(r18+#-4) = r31
; CHECK: {
-; CHECK-DAG: r19 = add(r19,#-4)
-; CHECK-DAG: r31 = memw(r19+#-4)
+; CHECK-DAG: r18 = add(r18,#-4)
+; CHECK-DAG: r31 = memw(r18+#-4)
; CHECK: }
; CHECK-NOT: __restore_
; CHECK: jumpr r31
@@ -88,49 +122,60 @@
;; stub (__restore_r16_through_r17_and_deallocframe_before_tailcall) must NOT be
;; used when SCS is active. The SCS epilogue and tail jump are fused together.
; CHECK-LABEL: minsize_tailcall:
-; CHECK: r19 = add(r19,#4)
-; CHECK: memw(r19+#-4) = r31
+; CHECK: r18 = add(r18,#4)
+; CHECK: memw(r18+#-4) = r31
; CHECK: {
-; CHECK-DAG: r19 = add(r19,#-4)
-; CHECK-DAG: r31 = memw(r19+#-4)
+; CHECK-DAG: r18 = add(r18,#-4)
+; CHECK-DAG: r31 = memw(r18+#-4)
; CHECK-DAG: jump bar
; CHECK: }
; CHECK-NOT: __restore_
;; Multiple return paths - each exit block gets its own SCS epilogue.
; CHECK-LABEL: multi_return:
-; CHECK: r19 = add(r19,#4)
-; CHECK: memw(r19+#-4) = r31
-; CHECK: r31 = memw(r19+#-4)
-; CHECK: r19 = add(r19,#-4)
+; CHECK: r18 = add(r18,#4)
+; CHECK: memw(r18+#-4) = r31
+; CHECK: r31 = memw(r18+#-4)
+; CHECK: r18 = add(r18,#-4)
; CHECK: jumpr r31
-; CHECK: r31 = memw(r19+#-4)
-; CHECK: r19 = add(r19,#-4)
+; CHECK: r31 = memw(r18+#-4)
+; CHECK: r18 = add(r18,#-4)
; CHECK: jumpr r31
-;; Without r19 reserved, SCS should report an error.
-; ERR: Must reserve r19 to use shadow call stack on Hexagon
+;; A minsize function using many callee-saved registers - without SCS this is
+;; the shape that gets a spill/restore stub. Reserving the SCS register breaks
+;; the r19:18 double, which leaves r19 in the callee-saved set as a lone single
+;; register and forces inline spills, so no stub covering r18 can be selected.
+;; This locks in the invariant that a stub never reaches the SCS register.
+; CHECK-LABEL: minsize_manycsr:
+; CHECK-NOT: __save_
+; CHECK-NOT: __restore_
+; CHECK: jumpr r31
+
+;; Without the SCS register reserved, SCS should report an error naming it.
+; ERR: Must reserve r18 to use shadow call stack on Hexagon
+; ERR16: Must reserve r16 to use shadow call stack on Hexagon
-;; Non-leaf with uwtable - exercises CFI escape (DW_CFA_val_expression for r19)
+;; Non-leaf with uwtable - exercises CFI escape (DW_CFA_val_expression for r18)
;; 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: r18 = add(r18,#4)
+; CFI: memw(r18+#-4) = r31
+; CFI: .cfi_escape 0x16, 0x12, 0x02, 0x82, 0x7c
; CFI: {
-; CFI-DAG: r31 = memw(r19+#-4)
-; CFI-DAG: r19 = add(r19,#-4)
+; CFI-DAG: r31 = memw(r18+#-4)
+; CFI-DAG: r18 = add(r18,#-4)
; CFI: }
-; CFI: .cfi_restore r19
+; CFI: .cfi_restore r18
; 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: r18 = add(r18,#4)
+; MUSL: memw(r18+#-4) = r31
; MUSL: {
-; MUSL-DAG: r19 = add(r19,#-4)
-; MUSL-DAG: r31 = memw(r19+#-4)
+; MUSL-DAG: r18 = add(r18,#-4)
+; MUSL-DAG: r31 = memw(r18+#-4)
; MUSL: }
; MUSL: jumpr r31
@@ -212,3 +257,21 @@ neg:
%r2 = call i32 @foo(i32 0)
ret i32 %r2
}
+
+define i32 @minsize_manycsr(i32 %x) shadowcallstack nounwind minsize
+ "disable-tail-calls"="true" {
+ %a = call i32 @foo(i32 %x)
+ %b = call i32 @foo(i32 %a)
+ %c = call i32 @foo(i32 %b)
+ %d = call i32 @foo(i32 %c)
+ %e = call i32 @foo(i32 %d)
+ %f = call i32 @foo(i32 %e)
+ %g = call i32 @foo(i32 %f)
+ %s1 = add i32 %a, %b
+ %s2 = add i32 %s1, %c
+ %s3 = add i32 %s2, %d
+ %s4 = add i32 %s3, %e
+ %s5 = add i32 %s4, %f
+ %s6 = add i32 %s5, %g
+ ret i32 %s6
+}
More information about the cfe-commits
mailing list