Skip to content

Commit 6a09d85

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 f6bb44c commit 6a09d85

File tree

10 files changed

+291
-25
lines changed

10 files changed

+291
-25
lines changed

clang/lib/Driver/ToolChains/Clang.cpp

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

37763776
if (!EffectiveTriple.isX86() && !EffectiveTriple.isSystemZ() &&
3777-
!EffectiveTriple.isPPC64() && !EffectiveTriple.isAArch64())
3777+
!EffectiveTriple.isPPC64() && !EffectiveTriple.isAArch64() &&
3778+
!EffectiveTriple.isRISCV())
37783779
return;
37793780

37803781
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
@@ -610,26 +610,74 @@ static MCCFIInstruction createDefCFAOffset(const TargetRegisterInfo &TRI,
610610
Comment.str());
611611
}
612612

613+
// Allocate stack space and probe it if necessary.
613614
void RISCVFrameLowering::allocateStack(MachineBasicBlock &MBB,
614615
MachineBasicBlock::iterator MBBI,
615-
MachineFunction &MF, StackOffset Offset,
616-
uint64_t RealStackSize,
617-
bool EmitCFI) const {
616+
MachineFunction &MF, uint64_t Offset,
617+
uint64_t RealStackSize, bool EmitCFI,
618+
bool NeedProbe,
619+
uint64_t ProbeSize) const {
618620
DebugLoc DL;
619621
const RISCVRegisterInfo *RI = STI.getRegisterInfo();
620622
const RISCVInstrInfo *TII = STI.getInstrInfo();
621623

622-
RI->adjustReg(MBB, MBBI, DL, SPReg, SPReg, Offset, MachineInstr::FrameSetup,
623-
getStackAlign());
624+
// Simply allocate the stack if it's not big enough to require a probe.
625+
if (!NeedProbe || Offset <= ProbeSize) {
626+
RI->adjustReg(MBB, MBBI, DL, SPReg, SPReg, StackOffset::getFixed(-Offset),
627+
MachineInstr::FrameSetup, getStackAlign());
624628

625-
if (EmitCFI) {
626-
// Emit ".cfi_def_cfa_offset RealStackSize"
627-
unsigned CFIIndex = MF.addFrameInst(
628-
MCCFIInstruction::cfiDefCfaOffset(nullptr, RealStackSize));
629-
BuildMI(MBB, MBBI, DL, TII->get(TargetOpcode::CFI_INSTRUCTION))
630-
.addCFIIndex(CFIIndex)
631-
.setMIFlag(MachineInstr::FrameSetup);
629+
if (EmitCFI) {
630+
// Emit ".cfi_def_cfa_offset RealStackSize"
631+
unsigned CFIIndex = MF.addFrameInst(
632+
MCCFIInstruction::cfiDefCfaOffset(nullptr, RealStackSize));
633+
BuildMI(MBB, MBBI, DL, TII->get(TargetOpcode::CFI_INSTRUCTION))
634+
.addCFIIndex(CFIIndex)
635+
.setMIFlag(MachineInstr::FrameSetup);
636+
}
637+
638+
return;
632639
}
640+
641+
// Do an unrolled probe loop.
642+
uint64_t CurrentOffset = 0;
643+
bool IsRV64 = STI.is64Bit();
644+
while (CurrentOffset + ProbeSize <= Offset) {
645+
RI->adjustReg(MBB, MBBI, DL, SPReg, SPReg,
646+
StackOffset::getFixed(-ProbeSize), MachineInstr::FrameSetup,
647+
getStackAlign());
648+
// s[d|w] zero, 0(sp)
649+
BuildMI(MBB, MBBI, DL, TII->get(IsRV64 ? RISCV::SD : RISCV::SW))
650+
.addReg(RISCV::X0)
651+
.addReg(SPReg)
652+
.addImm(0)
653+
.setMIFlags(MachineInstr::FrameSetup);
654+
655+
CurrentOffset += ProbeSize;
656+
if (EmitCFI) {
657+
// Emit ".cfi_def_cfa_offset CurrentOffset"
658+
unsigned CFIIndex = MF.addFrameInst(
659+
MCCFIInstruction::cfiDefCfaOffset(nullptr, CurrentOffset));
660+
BuildMI(MBB, MBBI, DL, TII->get(TargetOpcode::CFI_INSTRUCTION))
661+
.addCFIIndex(CFIIndex)
662+
.setMIFlag(MachineInstr::FrameSetup);
663+
}
664+
}
665+
666+
uint64_t Residual = Offset - CurrentOffset;
667+
if (Residual) {
668+
RI->adjustReg(MBB, MBBI, DL, SPReg, SPReg, StackOffset::getFixed(-Residual),
669+
MachineInstr::FrameSetup, getStackAlign());
670+
if (EmitCFI) {
671+
// Emit ".cfi_def_cfa_offset Offset"
672+
unsigned CFIIndex =
673+
MF.addFrameInst(MCCFIInstruction::cfiDefCfaOffset(nullptr, Offset));
674+
BuildMI(MBB, MBBI, DL, TII->get(TargetOpcode::CFI_INSTRUCTION))
675+
.addCFIIndex(CFIIndex)
676+
.setMIFlag(MachineInstr::FrameSetup);
677+
}
678+
}
679+
680+
return;
633681
}
634682

635683
void RISCVFrameLowering::emitPrologue(MachineFunction &MF,
@@ -746,11 +794,14 @@ void RISCVFrameLowering::emitPrologue(MachineFunction &MF,
746794
getPushOrLibCallsSavedInfo(MF, CSI));
747795
}
748796

749-
if (StackSize != 0) {
750-
// Allocate space on the stack if necessary.
751-
allocateStack(MBB, MBBI, MF, StackOffset::getFixed(-StackSize),
752-
RealStackSize, /*EmitCFI=*/ true);
753-
}
797+
// Allocate space on the stack if necessary.
798+
auto &Subtarget = MF.getSubtarget<RISCVSubtarget>();
799+
const RISCVTargetLowering *TLI = Subtarget.getTargetLowering();
800+
bool NeedProbe = TLI->hasInlineStackProbe(MF);
801+
uint64_t ProbeSize = TLI->getStackProbeSize(MF, getStackAlign());
802+
if (StackSize != 0)
803+
allocateStack(MBB, MBBI, MF, StackSize, RealStackSize, /*EmitCFI=*/true,
804+
NeedProbe, ProbeSize);
754805

755806
// The frame pointer is callee-saved, and code has been generated for us to
756807
// save it to the stack. We need to skip over the storing of callee-saved
@@ -791,8 +842,9 @@ void RISCVFrameLowering::emitPrologue(MachineFunction &MF,
791842
assert(SecondSPAdjustAmount > 0 &&
792843
"SecondSPAdjustAmount should be greater than zero");
793844

794-
allocateStack(MBB, MBBI, MF, StackOffset::getFixed(-SecondSPAdjustAmount),
795-
getStackSizeWithRVVPadding(MF), !hasFP(MF));
845+
allocateStack(MBB, MBBI, MF, SecondSPAdjustAmount,
846+
getStackSizeWithRVVPadding(MF), !hasFP(MF), NeedProbe,
847+
ProbeSize);
796848
}
797849

798850
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
@@ -22156,3 +22156,25 @@ namespace llvm::RISCVVIntrinsicsTable {
2215622156
#include "RISCVGenSearchableTables.inc"
2215722157

2215822158
} // namespace llvm::RISCVVIntrinsicsTable
22159+
22160+
bool RISCVTargetLowering::hasInlineStackProbe(const MachineFunction &MF) const {
22161+
22162+
// If the function specifically requests inline stack probes, emit them.
22163+
if (MF.getFunction().hasFnAttribute("probe-stack"))
22164+
return MF.getFunction().getFnAttribute("probe-stack").getValueAsString() ==
22165+
"inline-asm";
22166+
22167+
return false;
22168+
}
22169+
22170+
unsigned RISCVTargetLowering::getStackProbeSize(const MachineFunction &MF,
22171+
Align StackAlign) const {
22172+
// The default stack probe size is 4096 if the function has no
22173+
// stack-probe-size attribute.
22174+
const Function &Fn = MF.getFunction();
22175+
unsigned StackProbeSize =
22176+
Fn.getFnAttributeAsParsedInteger("stack-probe-size", 4096);
22177+
// Round down to the stack alignment.
22178+
StackProbeSize = alignDown(StackProbeSize, StackAlign.value());
22179+
return StackProbeSize ? StackProbeSize : StackAlign.value();
22180+
}

llvm/lib/Target/RISCV/RISCVISelLowering.h

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

926+
/// True if stack clash protection is enabled for this functions.
927+
bool hasInlineStackProbe(const MachineFunction &MF) const override;
928+
929+
unsigned getStackProbeSize(const MachineFunction &MF, Align StackAlign) const;
930+
926931
private:
927932
void analyzeInputArgs(MachineFunction &MF, CCState &CCInfo,
928933
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: 6 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,
@@ -157,6 +159,9 @@ class RISCVMachineFunctionInfo : public MachineFunctionInfo {
157159

158160
bool isVectorCall() const { return IsVectorCall; }
159161
void setIsVectorCall() { IsVectorCall = true; }
162+
163+
bool hasStackProbing() const { return StackProbeSize != 0; }
164+
int64_t getStackProbeSize() const { return StackProbeSize; }
160165
};
161166

162167
} // end namespace llvm

llvm/lib/Target/RISCV/RISCVTargetMachine.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,8 +266,8 @@ RISCVTargetMachine::getSubtargetImpl(const Function &F) const {
266266
MachineFunctionInfo *RISCVTargetMachine::createMachineFunctionInfo(
267267
BumpPtrAllocator &Allocator, const Function &F,
268268
const TargetSubtargetInfo *STI) const {
269-
return RISCVMachineFunctionInfo::create<RISCVMachineFunctionInfo>(Allocator,
270-
F, STI);
269+
return RISCVMachineFunctionInfo::create<RISCVMachineFunctionInfo>(
270+
Allocator, F, static_cast<const RISCVSubtarget *>(STI));
271271
}
272272

273273
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)