[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:
1 parent
9e445a6171
commit
5aeb89c435
12 files changed
+364
-96
No files matched your search
@@ -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
|
||||
|
||||
@@ -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)`
|
||||
|
||||
@@ -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]>,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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='
|
||||
@@ -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"
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in new issue
Block a user