Skip to content

Commit f9c0f9c

Browse files
committed
[RISCV] Add initial stack clash protection
Enable `-fstack-clash-protection` for RISCV and stack probe for function prologues. We probe the stack by creating an unrolled loop that allocates and probe the stack in ProbeSize chunks, this is not ideal if the loop has many iterations.
1 parent 026fbe5 commit f9c0f9c

File tree

10 files changed

+292
-25
lines changed

10 files changed

+292
-25
lines changed

clang/lib/Driver/ToolChains/Clang.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3775,7 +3775,8 @@ static void RenderSCPOptions(const ToolChain &TC, const ArgList &Args,
37753775
return;
37763776

37773777
if (!EffectiveTriple.isX86() && !EffectiveTriple.isSystemZ() &&
3778-
!EffectiveTriple.isPPC64() && !EffectiveTriple.isAArch64())
3778+
!EffectiveTriple.isPPC64() && !EffectiveTriple.isAArch64() &&
3779+
!EffectiveTriple.isRISCV())
37793780
return;
37803781

37813782
Args.addOptInFlag(CmdArgs, options::OPT_fstack_clash_protection,

llvm/lib/Target/RISCV/RISCVFrameLowering.cpp

Lines changed: 71 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -580,26 +580,74 @@ static MCCFIInstruction createDefCFAOffset(const TargetRegisterInfo &TRI,
580580
Comment.str());
581581
}
582582

583+
// Allocate stack space and probe it if necessary.
583584
void RISCVFrameLowering::allocateStack(MachineBasicBlock &MBB,
584585
MachineBasicBlock::iterator MBBI,
585-
MachineFunction &MF, StackOffset Offset,
586-
uint64_t RealStackSize,
587-
bool EmitCFI) const {
586+
MachineFunction &MF, uint64_t Offset,
587+
uint64_t RealStackSize, bool EmitCFI,
588+
bool NeedProbe,
589+
uint64_t ProbeSize) const {
588590
DebugLoc DL;
589591
const RISCVRegisterInfo *RI = STI.getRegisterInfo();
590592
const RISCVInstrInfo *TII = STI.getInstrInfo();
591593

592-
RI->adjustReg(MBB, MBBI, DL, SPReg, SPReg, Offset, MachineInstr::FrameSetup,
593-
getStackAlign());
594+
// Simply allocate the stack if it's not big enough to require a probe.
595+
if (!NeedProbe || Offset <= ProbeSize) {
596+
RI->adjustReg(MBB, MBBI, DL, SPReg, SPReg, StackOffset::getFixed(-Offset),
597+
MachineInstr::FrameSetup, getStackAlign());
594598

595-
if (EmitCFI) {
596-
// Emit ".cfi_def_cfa_offset RealStackSize"
597-
unsigned CFIIndex = MF.addFrameInst(
598-
MCCFIInstruction::cfiDefCfaOffset(nullptr, RealStackSize));
599-
BuildMI(MBB, MBBI, DL, TII->get(TargetOpcode::CFI_INSTRUCTION))
600-
.addCFIIndex(CFIIndex)
601-
.setMIFlag(MachineInstr::FrameSetup);
599+
if (EmitCFI) {
600+
// Emit ".cfi_def_cfa_offset RealStackSize"
601+
unsigned CFIIndex = MF.addFrameInst(
602+
MCCFIInstruction::cfiDefCfaOffset(nullptr, RealStackSize));
603+
BuildMI(MBB, MBBI, DL, TII->get(TargetOpcode::CFI_INSTRUCTION))
604+
.addCFIIndex(CFIIndex)
605+
.setMIFlag(MachineInstr::FrameSetup);
606+
}
607+
608+
return;
602609
}
610+
611+
// Do an unrolled probe loop.
612+
uint64_t CurrentOffset = 0;
613+
bool IsRV64 = STI.is64Bit();
614+
while (CurrentOffset + ProbeSize <= Offset) {
615+
RI->adjustReg(MBB, MBBI, DL, SPReg, SPReg,
616+
StackOffset::getFixed(-ProbeSize), MachineInstr::FrameSetup,
617+
getStackAlign());
618+
// s[d|w] zero, 0(sp)
619+
BuildMI(MBB, MBBI, DL, TII->get(IsRV64 ? RISCV::SD : RISCV::SW))
620+
.addReg(RISCV::X0)
621+
.addReg(SPReg)
622+
.addImm(0)
623+
.setMIFlags(MachineInstr::FrameSetup);
624+
625+
CurrentOffset += ProbeSize;
626+
if (EmitCFI) {
627+
// Emit ".cfi_def_cfa_offset CurrentOffset"
628+
unsigned CFIIndex = MF.addFrameInst(
629+
MCCFIInstruction::cfiDefCfaOffset(nullptr, CurrentOffset));
630+
BuildMI(MBB, MBBI, DL, TII->get(TargetOpcode::CFI_INSTRUCTION))
631+
.addCFIIndex(CFIIndex)
632+
.setMIFlag(MachineInstr::FrameSetup);
633+
}
634+
}
635+
636+
uint64_t Residual = Offset - CurrentOffset;
637+
if (Residual) {
638+
RI->adjustReg(MBB, MBBI, DL, SPReg, SPReg, StackOffset::getFixed(-Residual),
639+
MachineInstr::FrameSetup, getStackAlign());
640+
if (EmitCFI) {
641+
// Emit ".cfi_def_cfa_offset Offset"
642+
unsigned CFIIndex =
643+
MF.addFrameInst(MCCFIInstruction::cfiDefCfaOffset(nullptr, Offset));
644+
BuildMI(MBB, MBBI, DL, TII->get(TargetOpcode::CFI_INSTRUCTION))
645+
.addCFIIndex(CFIIndex)
646+
.setMIFlag(MachineInstr::FrameSetup);
647+
}
648+
}
649+
650+
return;
603651
}
604652

605653
void RISCVFrameLowering::emitPrologue(MachineFunction &MF,
@@ -716,11 +764,14 @@ void RISCVFrameLowering::emitPrologue(MachineFunction &MF,
716764
getPushOrLibCallsSavedInfo(MF, CSI));
717765
}
718766

719-
if (StackSize != 0) {
720-
// Allocate space on the stack if necessary.
721-
allocateStack(MBB, MBBI, MF, StackOffset::getFixed(-StackSize),
722-
RealStackSize, /*EmitCFI=*/true);
723-
}
767+
// Allocate space on the stack if necessary.
768+
auto &Subtarget = MF.getSubtarget<RISCVSubtarget>();
769+
const RISCVTargetLowering *TLI = Subtarget.getTargetLowering();
770+
bool NeedProbe = TLI->hasInlineStackProbe(MF);
771+
uint64_t ProbeSize = TLI->getStackProbeSize(MF, getStackAlign());
772+
if (StackSize != 0)
773+
allocateStack(MBB, MBBI, MF, StackSize, RealStackSize, /*EmitCFI=*/true,
774+
NeedProbe, ProbeSize);
724775

725776
// The frame pointer is callee-saved, and code has been generated for us to
726777
// save it to the stack. We need to skip over the storing of callee-saved
@@ -761,8 +812,9 @@ void RISCVFrameLowering::emitPrologue(MachineFunction &MF,
761812
assert(SecondSPAdjustAmount > 0 &&
762813
"SecondSPAdjustAmount should be greater than zero");
763814

764-
allocateStack(MBB, MBBI, MF, StackOffset::getFixed(-SecondSPAdjustAmount),
765-
getStackSizeWithRVVPadding(MF), !hasFP(MF));
815+
allocateStack(MBB, MBBI, MF, SecondSPAdjustAmount,
816+
getStackSizeWithRVVPadding(MF), !hasFP(MF), NeedProbe,
817+
ProbeSize);
766818
}
767819

768820
if (RVVStackSize) {

llvm/lib/Target/RISCV/RISCVFrameLowering.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,9 @@ class RISCVFrameLowering : public TargetFrameLowering {
7979
}
8080

8181
void allocateStack(MachineBasicBlock &MBB, MachineBasicBlock::iterator MBBI,
82-
MachineFunction &MF, StackOffset Offset,
83-
uint64_t RealStackSize, bool EmitCFI) const;
82+
MachineFunction &MF, uint64_t Offset,
83+
uint64_t RealStackSize, bool EmitCFI, bool NeedProbe,
84+
uint64_t ProbeSize) const;
8485

8586
protected:
8687
const RISCVSubtarget &STI;

llvm/lib/Target/RISCV/RISCVISelLowering.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22176,3 +22176,25 @@ namespace llvm::RISCVVIntrinsicsTable {
2217622176
#include "RISCVGenSearchableTables.inc"
2217722177

2217822178
} // namespace llvm::RISCVVIntrinsicsTable
22179+
22180+
bool RISCVTargetLowering::hasInlineStackProbe(const MachineFunction &MF) const {
22181+
22182+
// If the function specifically requests inline stack probes, emit them.
22183+
if (MF.getFunction().hasFnAttribute("probe-stack"))
22184+
return MF.getFunction().getFnAttribute("probe-stack").getValueAsString() ==
22185+
"inline-asm";
22186+
22187+
return false;
22188+
}
22189+
22190+
unsigned RISCVTargetLowering::getStackProbeSize(const MachineFunction &MF,
22191+
Align StackAlign) const {
22192+
// The default stack probe size is 4096 if the function has no
22193+
// stack-probe-size attribute.
22194+
const Function &Fn = MF.getFunction();
22195+
unsigned StackProbeSize =
22196+
Fn.getFnAttributeAsParsedInteger("stack-probe-size", 4096);
22197+
// Round down to the stack alignment.
22198+
StackProbeSize = alignDown(StackProbeSize, StackAlign.value());
22199+
return StackProbeSize ? StackProbeSize : StackAlign.value();
22200+
}

llvm/lib/Target/RISCV/RISCVISelLowering.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -919,6 +919,11 @@ class RISCVTargetLowering : public TargetLowering {
919919
MachineBasicBlock::instr_iterator &MBBI,
920920
const TargetInstrInfo *TII) const override;
921921

922+
/// True if stack clash protection is enabled for this functions.
923+
bool hasInlineStackProbe(const MachineFunction &MF) const override;
924+
925+
unsigned getStackProbeSize(const MachineFunction &MF, Align StackAlign) const;
926+
922927
private:
923928
void analyzeInputArgs(MachineFunction &MF, CCState &CCInfo,
924929
const SmallVectorImpl<ISD::InputArg> &Ins, bool IsRet,

llvm/lib/Target/RISCV/RISCVMachineFunctionInfo.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
#include "RISCVMachineFunctionInfo.h"
14+
#include "llvm/IR/Module.h"
1415

1516
using namespace llvm;
1617

@@ -26,6 +27,35 @@ MachineFunctionInfo *RISCVMachineFunctionInfo::clone(
2627
return DestMF.cloneInfo<RISCVMachineFunctionInfo>(*this);
2728
}
2829

30+
RISCVMachineFunctionInfo::RISCVMachineFunctionInfo(const Function &F,
31+
const RISCVSubtarget *STI) {
32+
33+
// The default stack probe size is 4096 if the function has no
34+
// stack-probe-size attribute. This is a safe default because it is the
35+
// smallest possible guard page size.
36+
uint64_t ProbeSize = 4096;
37+
if (F.hasFnAttribute("stack-probe-size"))
38+
ProbeSize = F.getFnAttributeAsParsedInteger("stack-probe-size");
39+
else if (const auto *PS = mdconst::extract_or_null<ConstantInt>(
40+
F.getParent()->getModuleFlag("stack-probe-size")))
41+
ProbeSize = PS->getZExtValue();
42+
assert(int64_t(ProbeSize) > 0 && "Invalid stack probe size");
43+
44+
// Round down to the stack alignment.
45+
uint64_t StackAlign =
46+
STI->getFrameLowering()->getTransientStackAlign().value();
47+
ProbeSize = std::max(StackAlign, alignDown(ProbeSize, StackAlign));
48+
StringRef ProbeKind;
49+
if (F.hasFnAttribute("probe-stack"))
50+
ProbeKind = F.getFnAttribute("probe-stack").getValueAsString();
51+
else if (const auto *PS = dyn_cast_or_null<MDString>(
52+
F.getParent()->getModuleFlag("probe-stack")))
53+
ProbeKind = PS->getString();
54+
if (ProbeKind.size()) {
55+
StackProbeSize = ProbeSize;
56+
}
57+
}
58+
2959
void yaml::RISCVMachineFunctionInfo::mappingImpl(yaml::IO &YamlIO) {
3060
MappingTraits<RISCVMachineFunctionInfo>::mapping(YamlIO, *this);
3161
}

llvm/lib/Target/RISCV/RISCVMachineFunctionInfo.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,10 @@ class RISCVMachineFunctionInfo : public MachineFunctionInfo {
7676
unsigned RVPushRegs = 0;
7777
int RVPushRlist = llvm::RISCVZC::RLISTENCODE::INVALID_RLIST;
7878

79+
int64_t StackProbeSize = 0;
80+
7981
public:
80-
RISCVMachineFunctionInfo(const Function &F, const TargetSubtargetInfo *STI) {}
82+
RISCVMachineFunctionInfo(const Function &F, const RISCVSubtarget *STI);
8183

8284
MachineFunctionInfo *
8385
clone(BumpPtrAllocator &Allocator, MachineFunction &DestMF,

llvm/lib/Target/RISCV/RISCVTargetMachine.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,8 +271,8 @@ RISCVTargetMachine::getSubtargetImpl(const Function &F) const {
271271
MachineFunctionInfo *RISCVTargetMachine::createMachineFunctionInfo(
272272
BumpPtrAllocator &Allocator, const Function &F,
273273
const TargetSubtargetInfo *STI) const {
274-
return RISCVMachineFunctionInfo::create<RISCVMachineFunctionInfo>(Allocator,
275-
F, STI);
274+
return RISCVMachineFunctionInfo::create<RISCVMachineFunctionInfo>(
275+
Allocator, F, static_cast<const RISCVSubtarget *>(STI));
276276
}
277277

278278
TargetTransformInfo
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
2+
; RUN: llc -mtriple=riscv64 -mattr=+m -O2 < %s \
3+
; RUN: | FileCheck %s -check-prefix=RV64I
4+
; RUN: llc -mtriple=riscv32 -mattr=+m -O2 < %s \
5+
; RUN: | FileCheck %s -check-prefix=RV32I
6+
7+
; Tests copied from PowerPC.
8+
9+
; Free probe
10+
define i8 @f0() #0 nounwind {
11+
; RV64I-LABEL: f0:
12+
; RV64I: # %bb.0: # %entry
13+
; RV64I-NEXT: addi sp, sp, -64
14+
; RV64I-NEXT: li a0, 3
15+
; RV64I-NEXT: sb a0, 0(sp)
16+
; RV64I-NEXT: lbu a0, 0(sp)
17+
; RV64I-NEXT: addi sp, sp, 64
18+
; RV64I-NEXT: ret
19+
;
20+
; RV32I-LABEL: f0:
21+
; RV32I: # %bb.0: # %entry
22+
; RV32I-NEXT: addi sp, sp, -64
23+
; RV32I-NEXT: li a0, 3
24+
; RV32I-NEXT: sb a0, 0(sp)
25+
; RV32I-NEXT: lbu a0, 0(sp)
26+
; RV32I-NEXT: addi sp, sp, 64
27+
; RV32I-NEXT: ret
28+
entry:
29+
%a = alloca i8, i64 64
30+
%b = getelementptr inbounds i8, ptr %a, i64 63
31+
store volatile i8 3, ptr %a
32+
%c = load volatile i8, ptr %a
33+
ret i8 %c
34+
}
35+
36+
define i8 @f1() #0 nounwind {
37+
; RV64I-LABEL: f1:
38+
; RV64I: # %bb.0: # %entry
39+
; RV64I-NEXT: lui a0, 1
40+
; RV64I-NEXT: sub sp, sp, a0
41+
; RV64I-NEXT: sd zero, 0(sp)
42+
; RV64I-NEXT: addi sp, sp, -16
43+
; RV64I-NEXT: li a0, 3
44+
; RV64I-NEXT: sb a0, 16(sp)
45+
; RV64I-NEXT: lbu a0, 16(sp)
46+
; RV64I-NEXT: lui a1, 1
47+
; RV64I-NEXT: addiw a1, a1, 16
48+
; RV64I-NEXT: add sp, sp, a1
49+
; RV64I-NEXT: ret
50+
;
51+
; RV32I-LABEL: f1:
52+
; RV32I: # %bb.0: # %entry
53+
; RV32I-NEXT: lui a0, 1
54+
; RV32I-NEXT: sub sp, sp, a0
55+
; RV32I-NEXT: sw zero, 0(sp)
56+
; RV32I-NEXT: addi sp, sp, -16
57+
; RV32I-NEXT: li a0, 3
58+
; RV32I-NEXT: sb a0, 16(sp)
59+
; RV32I-NEXT: lbu a0, 16(sp)
60+
; RV32I-NEXT: lui a1, 1
61+
; RV32I-NEXT: addi a1, a1, 16
62+
; RV32I-NEXT: add sp, sp, a1
63+
; RV32I-NEXT: ret
64+
entry:
65+
%a = alloca i8, i64 4096
66+
%b = getelementptr inbounds i8, ptr %a, i64 63
67+
store volatile i8 3, ptr %a
68+
%c = load volatile i8, ptr %a
69+
ret i8 %c
70+
}
71+
72+
attributes #0 = { "probe-stack"="inline-asm" }

0 commit comments

Comments
 (0)