[Hexagon] Fix unusable SCS reg, make it selectable (#213820)

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.

(cherry picked from commit 786ed0d4d3b337711e2d542369cb03369482a113)
This commit is contained in:
Brian Cain authored and Douglas Yung committed 2026-08-15 15:40:21 +00:00
1 parent 9e445a6171
commit 5aeb89c435
12 files changed
+364 -96

No files matched your search

+5
View File
@@ -455,6 +455,11 @@ latest release, please see the [Clang Web Site](https://clang.llvm.org) or the
necessary. `/d1nodatetime-` can be used to turn this feature off if
necessary to override the common build settings.
- 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
+15 -1
View File
@@ -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)`
+8
View File
@@ -7082,6 +7082,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]>,
+52 -5
View File
@@ -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
+17
View File
@@ -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");
@@ -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-r19 %s -### 2>&1 \
// RUN: | FileCheck %s --check-prefix=HEXAGON-SCS-WITH-R19
// RUN: -fsanitize=shadow-call-stack -ffixed-r18 %s -### 2>&1 \
// RUN: | FileCheck %s --check-prefix=DEFAULT
// HEXAGON-SCS-NO-R19: error: invalid argument '-fsanitize=shadow-call-stack' only allowed with '-ffixed-r19'
// HEXAGON-SCS-WITH-R19: "-fsanitize=shadow-call-stack"
/// 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=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="
// 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='
+6 -6
View File
@@ -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"
+8
View File
@@ -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">;
@@ -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);
}
@@ -2991,6 +3002,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;
@@ -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;
@@ -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 @@ public:
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; }
+118 -55
View File
@@ -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).
; 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
;; 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
;; Leaf function - no LR spill, SCS should not emit any r19 instructions.
;; 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: 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 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
;; Non-leaf with uwtable - exercises CFI escape (DW_CFA_val_expression for r19)
;; 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 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
}