[RISCV] Implement KCFI operand bundle lowering

With `-fsanitize=kcfi` (Kernel Control-Flow Integrity), Clang emits
"kcfi" operand bundles to indirect call instructions. Similarly to
the target-specific lowering added in D119296, implement KCFI operand
bundle lowering for RISC-V.

This patch disables the generic KCFI pass for RISC-V in Clang, and
adds the KCFI machine function pass in `RISCVPassConfig::addPreSched`
to emit target-specific `KCFI_CHECK` pseudo instructions before calls
that have KCFI operand bundles. The machine function pass also bundles
the instructions to ensure we emit the checks immediately before the
calls, which is not possible with the generic pass.

`KCFI_CHECK` instructions are lowered in `RISCVAsmPrinter` to a
contiguous code sequence that traps if the expected hash in the
operand bundle doesn't match the hash before the target function
address. This patch emits an `ebreak` instruction for error handling
to match the Linux kernel's `BUG()` implementation. Just like for X86,
we also emit trap locations to a `.kcfi_traps` section to support
error handling, as we cannot embed additional information to the trap
instruction itself.

Reviewed By: MaskRay

Differential Revision: https://reviews.llvm.org/D148385
This commit is contained in:
Sami Tolvanen 2023-06-23 16:31:42 +00:00
parent fd65b8da80
commit 62fa708ceb
14 changed files with 641 additions and 2 deletions

View File

@ -631,7 +631,7 @@ static void addKCFIPass(const Triple &TargetTriple, const LangOptions &LangOpts,
PassBuilder &PB) {
// If the back-end supports KCFI operand bundle lowering, skip KCFIPass.
if (TargetTriple.getArch() == llvm::Triple::x86_64 ||
TargetTriple.isAArch64(64))
TargetTriple.isAArch64(64) || TargetTriple.isRISCV())
return;
// Ensure we lower KCFI operand bundles with -O0.

View File

@ -19,6 +19,7 @@
#include "RISCVMachineFunctionInfo.h"
#include "RISCVTargetMachine.h"
#include "TargetInfo/RISCVTargetInfo.h"
#include "llvm/ADT/APInt.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/CodeGen/AsmPrinter.h"
@ -72,6 +73,7 @@ public:
typedef std::tuple<unsigned, uint32_t> HwasanMemaccessTuple;
std::map<HwasanMemaccessTuple, MCSymbol *> HwasanMemaccessSymbols;
void LowerHWASAN_CHECK_MEMACCESS(const MachineInstr &MI);
void LowerKCFI_CHECK(const MachineInstr &MI);
void EmitHwasanMemaccessSymbols(Module &M);
// Wrapper needed for tblgenned pseudo lowering.
@ -150,6 +152,9 @@ void RISCVAsmPrinter::emitInstruction(const MachineInstr *MI) {
case RISCV::HWASAN_CHECK_MEMACCESS_SHORTGRANULES:
LowerHWASAN_CHECK_MEMACCESS(*MI);
return;
case RISCV::KCFI_CHECK:
LowerKCFI_CHECK(*MI);
return;
case RISCV::PseudoRVVInitUndefM1:
case RISCV::PseudoRVVInitUndefM2:
case RISCV::PseudoRVVInitUndefM4:
@ -305,6 +310,92 @@ void RISCVAsmPrinter::LowerHWASAN_CHECK_MEMACCESS(const MachineInstr &MI) {
EmitToStreamer(*OutStreamer, MCInstBuilder(RISCV::PseudoCALL).addExpr(Expr));
}
void RISCVAsmPrinter::LowerKCFI_CHECK(const MachineInstr &MI) {
Register AddrReg = MI.getOperand(0).getReg();
assert(std::next(MI.getIterator())->isCall() &&
"KCFI_CHECK not followed by a call instruction");
assert(std::next(MI.getIterator())->getOperand(0).getReg() == AddrReg &&
"KCFI_CHECK call target doesn't match call operand");
// Temporary registers for comparing the hashes. If a register is used
// for the call target, or reserved by the user, we can clobber another
// temporary register as the check is immediately followed by the
// call. The check defaults to X6/X7, but can fall back to X28-X31 if
// needed.
unsigned ScratchRegs[] = {RISCV::X6, RISCV::X7};
unsigned NextReg = RISCV::X28;
auto isRegAvailable = [&](unsigned Reg) {
return Reg != AddrReg && !STI->isRegisterReservedByUser(Reg);
};
for (auto &Reg : ScratchRegs) {
if (isRegAvailable(Reg))
continue;
while (!isRegAvailable(NextReg))
++NextReg;
Reg = NextReg++;
if (Reg > RISCV::X31)
report_fatal_error("Unable to find scratch registers for KCFI_CHECK");
}
if (AddrReg == RISCV::X0) {
// Checking X0 makes no sense. Instead of emitting a load, zero
// ScratchRegs[0].
EmitToStreamer(*OutStreamer, MCInstBuilder(RISCV::ADDI)
.addReg(ScratchRegs[0])
.addReg(RISCV::X0)
.addImm(0));
} else {
// Adjust the offset for patchable-function-prefix. This assumes that
// patchable-function-prefix is the same for all functions.
int NopSize = STI->hasStdExtCOrZca() ? 2 : 4;
int64_t PrefixNops = 0;
(void)MI.getMF()
->getFunction()
.getFnAttribute("patchable-function-prefix")
.getValueAsString()
.getAsInteger(10, PrefixNops);
// Load the target function type hash.
EmitToStreamer(*OutStreamer, MCInstBuilder(RISCV::LW)
.addReg(ScratchRegs[0])
.addReg(AddrReg)
.addImm(-(PrefixNops * NopSize + 4)));
}
// Load the expected 32-bit type hash.
const int64_t Type = MI.getOperand(1).getImm();
const int64_t Hi20 = ((Type + 0x800) >> 12) & 0xFFFFF;
const int64_t Lo12 = SignExtend64<12>(Type);
if (Hi20) {
EmitToStreamer(
*OutStreamer,
MCInstBuilder(RISCV::LUI).addReg(ScratchRegs[1]).addImm(Hi20));
}
if (Lo12 || Hi20 == 0) {
EmitToStreamer(*OutStreamer,
MCInstBuilder((STI->hasFeature(RISCV::Feature64Bit) && Hi20)
? RISCV::ADDIW
: RISCV::ADDI)
.addReg(ScratchRegs[1])
.addReg(ScratchRegs[1])
.addImm(Lo12));
}
// Compare the hashes and trap if there's a mismatch.
MCSymbol *Pass = OutContext.createTempSymbol();
EmitToStreamer(*OutStreamer,
MCInstBuilder(RISCV::BEQ)
.addReg(ScratchRegs[0])
.addReg(ScratchRegs[1])
.addExpr(MCSymbolRefExpr::create(Pass, OutContext)));
MCSymbol *Trap = OutContext.createTempSymbol();
OutStreamer->emitLabel(Trap);
EmitToStreamer(*OutStreamer, MCInstBuilder(RISCV::EBREAK));
emitKCFITrapEntry(*MI.getMF(), Trap);
OutStreamer->emitLabel(Pass);
}
void RISCVAsmPrinter::EmitHwasanMemaccessSymbols(Module &M) {
if (HwasanMemaccessSymbols.empty())
return;

View File

@ -15395,17 +15395,24 @@ SDValue RISCVTargetLowering::LowerCall(CallLoweringInfo &CLI,
if (Glue.getNode())
Ops.push_back(Glue);
assert((!CLI.CFIType || CLI.CB->isIndirectCall()) &&
"Unexpected CFI type for a direct call");
// Emit the call.
SDVTList NodeTys = DAG.getVTList(MVT::Other, MVT::Glue);
if (IsTailCall) {
MF.getFrameInfo().setHasTailCall();
SDValue Ret = DAG.getNode(RISCVISD::TAIL, DL, NodeTys, Ops);
if (CLI.CFIType)
Ret.getNode()->setCFIType(CLI.CFIType->getZExtValue());
DAG.addNoMergeSiteInfo(Ret.getNode(), CLI.NoMerge);
return Ret;
}
Chain = DAG.getNode(RISCVISD::CALL, DL, NodeTys, Ops);
if (CLI.CFIType)
Chain.getNode()->setCFIType(CLI.CFIType->getZExtValue());
DAG.addNoMergeSiteInfo(Chain.getNode(), CLI.NoMerge);
Glue = Chain.getValue(1);
@ -16864,6 +16871,24 @@ bool RISCVTargetLowering::lowerInterleavedStore(StoreInst *SI,
return true;
}
MachineInstr *
RISCVTargetLowering::EmitKCFICheck(MachineBasicBlock &MBB,
MachineBasicBlock::instr_iterator &MBBI,
const TargetInstrInfo *TII) const {
assert(MBBI->isCall() && MBBI->getCFIType() &&
"Invalid call instruction for a KCFI check");
assert(is_contained({RISCV::PseudoCALLIndirect, RISCV::PseudoTAILIndirect},
MBBI->getOpcode()));
MachineOperand &Target = MBBI->getOperand(0);
Target.setIsRenamable(false);
return BuildMI(MBB, MBBI, MBBI->getDebugLoc(), TII->get(RISCV::KCFI_CHECK))
.addReg(Target.getReg())
.addImm(MBBI->getCFIType())
.getInstr();
}
#define GET_REGISTER_MATCHER
#include "RISCVGenAsmMatcher.inc"

View File

@ -759,6 +759,12 @@ public:
bool lowerInterleavedStore(StoreInst *SI, ShuffleVectorInst *SVI,
unsigned Factor) const override;
bool supportKCFIBundles() const override { return true; }
MachineInstr *EmitKCFICheck(MachineBasicBlock &MBB,
MachineBasicBlock::instr_iterator &MBBI,
const TargetInstrInfo *TII) const override;
/// RISCVCCAssignFn - This target-specific function extends the default
/// CCValAssign with additional information used to lower RISC-V calling
/// conventions.

View File

@ -1265,6 +1265,9 @@ unsigned RISCVInstrInfo::getInstSizeInBytes(const MachineInstr &MI) const {
}
}
if (Opcode == TargetOpcode::BUNDLE)
return getInstBundleLength(MI);
if (MI.getParent() && MI.getParent()->getParent()) {
if (isCompressibleInst(MI, STI))
return 2;
@ -1272,6 +1275,17 @@ unsigned RISCVInstrInfo::getInstSizeInBytes(const MachineInstr &MI) const {
return get(Opcode).getSize();
}
unsigned RISCVInstrInfo::getInstBundleLength(const MachineInstr &MI) const {
unsigned Size = 0;
MachineBasicBlock::const_instr_iterator I = MI.getIterator();
MachineBasicBlock::const_instr_iterator E = MI.getParent()->instr_end();
while (++I != E && I->isInsideBundle()) {
assert(!I->isBundle() && "No nested bundle!");
Size += getInstSizeInBytes(*I);
}
return Size;
}
bool RISCVInstrInfo::isAsCheapAsAMove(const MachineInstr &MI) const {
const unsigned Opcode = MI.getOpcode();
switch (Opcode) {

View File

@ -237,6 +237,9 @@ public:
protected:
const RISCVSubtarget &STI;
private:
unsigned getInstBundleLength(const MachineInstr &MI) const;
};
namespace RISCV {

View File

@ -1887,6 +1887,13 @@ def HWASAN_CHECK_MEMACCESS_SHORTGRANULES
[(int_hwasan_check_memaccess_shortgranules X5, GPRJALR:$ptr,
(i32 timm:$accessinfo))]>;
// This gets lowered into a 20-byte instruction sequence (at most)
let hasSideEffects = 0, mayLoad = 1, mayStore = 0,
Defs = [ X6, X7, X28, X29, X30, X31 ], Size = 20 in {
def KCFI_CHECK
: Pseudo<(outs), (ins GPRJALR:$ptr, i32imm:$type), []>, Sched<[]>;
}
/// Simple optimization
def : Pat<(XLenVT (add GPR:$rs1, (AddiPair:$rs2))),
(ADDI (ADDI GPR:$rs1, (AddiPairImmLarge AddiPair:$rs2)),

View File

@ -76,6 +76,7 @@ extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializeRISCVTarget() {
RegisterTargetMachine<RISCVTargetMachine> Y(getTheRISCV64Target());
auto *PR = PassRegistry::getPassRegistry();
initializeGlobalISel(*PR);
initializeKCFIPass(*PR);
initializeRISCVMakeCompressibleOptPass(*PR);
initializeRISCVGatherScatterLoweringPass(*PR);
initializeRISCVCodeGenPreparePass(*PR);
@ -333,7 +334,10 @@ bool RISCVPassConfig::addGlobalInstructionSelect() {
return false;
}
void RISCVPassConfig::addPreSched2() {}
void RISCVPassConfig::addPreSched2() {
// Emit KCFI checks for indirect calls.
addPass(createKCFIPass());
}
void RISCVPassConfig::addPreEmitPass() {
addPass(&BranchRelaxationPassID);
@ -357,6 +361,11 @@ void RISCVPassConfig::addPreEmitPass2() {
// possibility for other passes to break the requirements for forward
// progress in the LR/SC block.
addPass(createRISCVExpandAtomicPseudoPass());
// KCFI indirect call checks are lowered to a bundle.
addPass(createUnpackMachineBundles([&](const MachineFunction &MF) {
return MF.getFunction().getParent()->getModuleFlag("kcfi");
}));
}
void RISCVPassConfig::addMachineSSAOptimization() {

View File

@ -51,6 +51,7 @@
; CHECK-NEXT: Machine Optimization Remark Emitter
; CHECK-NEXT: Prologue/Epilogue Insertion & Frame Finalization
; CHECK-NEXT: Post-RA pseudo instruction expansion pass
; CHECK-NEXT: Insert KCFI indirect call checks
; CHECK-NEXT: Analyze Machine Code For Garbage Collection
; CHECK-NEXT: Insert fentry calls
; CHECK-NEXT: Insert XRay ops
@ -66,6 +67,7 @@
; CHECK-NEXT: Stack Frame Layout Analysis
; CHECK-NEXT: RISC-V pseudo instruction expansion pass
; CHECK-NEXT: RISC-V atomic pseudo instruction expansion pass
; CHECK-NEXT: Unpack machine instruction bundles
; CHECK-NEXT: Lazy Machine Block Frequency Analysis
; CHECK-NEXT: Machine Optimization Remark Emitter
; CHECK-NEXT: RISC-V Assembly Printer

View File

@ -155,6 +155,7 @@
; CHECK-NEXT: Tail Duplication
; CHECK-NEXT: Machine Copy Propagation Pass
; CHECK-NEXT: Post-RA pseudo instruction expansion pass
; CHECK-NEXT: Insert KCFI indirect call checks
; CHECK-NEXT: MachineDominator Tree Construction
; CHECK-NEXT: Machine Natural Loop Construction
; CHECK-NEXT: Post RA top-down list latency scheduler
@ -180,6 +181,7 @@
; CHECK-NEXT: RISC-V Zcmp move merging pass
; CHECK-NEXT: RISC-V pseudo instruction expansion pass
; CHECK-NEXT: RISC-V atomic pseudo instruction expansion pass
; CHECK-NEXT: Unpack machine instruction bundles
; CHECK-NEXT: Lazy Machine Block Frequency Analysis
; CHECK-NEXT: Machine Optimization Remark Emitter
; CHECK-NEXT: RISC-V Assembly Printer

View File

@ -0,0 +1,175 @@
# NOTE: Assertions have been autogenerated by utils/update_mir_test_checks.py UTC_ARGS: --version 2
# RUN: llc -mtriple=riscv64 -stop-after=finalize-isel -o - %s | FileCheck %s
--- |
; ModuleID = '<stdin>'
source_filename = "<stdin>"
target datalayout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128"
target triple = "riscv64"
define void @f1(ptr noundef %x) !kcfi_type !1 {
call void %x() [ "kcfi"(i32 12345678) ]
ret void
}
define void @f2(ptr noundef %x) #0 {
tail call void %x() [ "kcfi"(i32 12345678) ]
ret void
}
attributes #0 = { "patchable-function-entry"="2" }
!llvm.module.flags = !{!0}
!0 = !{i32 4, !"kcfi", i32 1}
!1 = !{i32 12345678}
...
---
name: f1
alignment: 4
exposesReturnsTwice: false
legalized: false
regBankSelected: false
selected: false
failedISel: false
tracksRegLiveness: true
hasWinCFI: false
callsEHReturn: false
callsUnwindInit: false
hasEHCatchret: false
hasEHScopes: false
hasEHFunclets: false
isOutlined: false
debugInstrRef: false
failsVerification: false
tracksDebugUserValues: false
registers:
- { id: 0, class: gprjalr, preferred-register: '' }
liveins:
- { reg: '$x10', virtual-reg: '%0' }
frameInfo:
isFrameAddressTaken: false
isReturnAddressTaken: false
hasStackMap: false
hasPatchPoint: false
stackSize: 0
offsetAdjustment: 0
maxAlignment: 1
adjustsStack: false
hasCalls: true
stackProtector: ''
functionContext: ''
maxCallFrameSize: 4294967295
cvBytesOfCalleeSavedRegisters: 0
hasOpaqueSPAdjustment: false
hasVAStart: false
hasMustTailInVarArgFunc: false
hasTailCall: false
localFrameSize: 0
savePoint: ''
restorePoint: ''
fixedStack: []
stack: []
callSites: []
debugValueSubstitutions: []
constants: []
machineFunctionInfo:
varArgsFrameIndex: 0
varArgsSaveSize: 0
body: |
bb.0 (%ir-block.0):
liveins: $x10
; CHECK-LABEL: name: f1
; CHECK: liveins: $x10, $x10, $x10
; CHECK-NEXT: {{ $}}
; CHECK-NEXT: [[COPY:%[0-9]+]]:gprjalr = COPY $x10
; CHECK-NEXT: [[COPY1:%[0-9]+]]:gprjalr = COPY $x10
; CHECK-NEXT: [[COPY2:%[0-9]+]]:gprjalr = COPY $x10
; CHECK-NEXT: ADJCALLSTACKDOWN 0, 0, implicit-def dead $x2, implicit $x2
; CHECK-NEXT: PseudoCALLIndirect [[COPY2]], csr_ilp32_lp64, implicit-def dead $x1, implicit-def $x2, cfi-type 12345678
; CHECK-NEXT: ADJCALLSTACKUP 0, 0, implicit-def dead $x2, implicit $x2
; CHECK-NEXT: PseudoRET
; CHECK-NEXT: {{ $}}
; CHECK-NEXT: .1 (%ir-block.0):
; CHECK-NEXT: ADJCALLSTACKDOWN 0, 0, implicit-def dead $x2, implicit $x2
; CHECK-NEXT: PseudoCALLIndirect [[COPY]], csr_ilp32_lp64, implicit-def dead $x1, implicit-def $x2, cfi-type 12345678
; CHECK-NEXT: ADJCALLSTACKUP 0, 0, implicit-def dead $x2, implicit $x2
; CHECK-NEXT: PseudoRET
%0:gprjalr = COPY $x10
ADJCALLSTACKDOWN 0, 0, implicit-def dead $x2, implicit $x2
PseudoCALLIndirect %0, csr_ilp32_lp64, implicit-def dead $x1, implicit-def $x2, cfi-type 12345678
ADJCALLSTACKUP 0, 0, implicit-def dead $x2, implicit $x2
PseudoRET
...
---
name: f2
alignment: 4
exposesReturnsTwice: false
legalized: false
regBankSelected: false
selected: false
failedISel: false
tracksRegLiveness: true
hasWinCFI: false
callsEHReturn: false
callsUnwindInit: false
hasEHCatchret: false
hasEHScopes: false
hasEHFunclets: false
isOutlined: false
debugInstrRef: false
failsVerification: false
tracksDebugUserValues: false
registers:
- { id: 0, class: gprtc, preferred-register: '' }
liveins:
- { reg: '$x10', virtual-reg: '%0' }
frameInfo:
isFrameAddressTaken: false
isReturnAddressTaken: false
hasStackMap: false
hasPatchPoint: false
stackSize: 0
offsetAdjustment: 0
maxAlignment: 1
adjustsStack: false
hasCalls: false
stackProtector: ''
functionContext: ''
maxCallFrameSize: 4294967295
cvBytesOfCalleeSavedRegisters: 0
hasOpaqueSPAdjustment: false
hasVAStart: false
hasMustTailInVarArgFunc: false
hasTailCall: true
localFrameSize: 0
savePoint: ''
restorePoint: ''
fixedStack: []
stack: []
callSites: []
debugValueSubstitutions: []
constants: []
machineFunctionInfo:
varArgsFrameIndex: 0
varArgsSaveSize: 0
body: |
bb.0 (%ir-block.0):
liveins: $x10
; CHECK-LABEL: name: f2
; CHECK: liveins: $x10, $x10, $x10
; CHECK-NEXT: {{ $}}
; CHECK-NEXT: [[COPY:%[0-9]+]]:gprtc = COPY $x10
; CHECK-NEXT: [[COPY1:%[0-9]+]]:gprtc = COPY $x10
; CHECK-NEXT: [[COPY2:%[0-9]+]]:gprtc = COPY $x10
; CHECK-NEXT: PseudoTAILIndirect [[COPY2]], implicit $x2, cfi-type 12345678
; CHECK-NEXT: {{ $}}
; CHECK-NEXT: .1 (%ir-block.0):
; CHECK-NEXT: PseudoTAILIndirect [[COPY]], implicit $x2, cfi-type 12345678
%0:gprtc = COPY $x10
PseudoTAILIndirect %0, implicit $x2, cfi-type 12345678
...

View File

@ -0,0 +1,54 @@
; RUN: llc -mtriple=riscv64 -verify-machineinstrs < %s | FileCheck %s --check-prefixes=CHECK,NOC
; RUN: llc -mtriple=riscv64 -mattr=+c -verify-machineinstrs < %s | FileCheck %s --check-prefixes=CHECK,C
; NOC: .p2align 2
; C: .p2align 1
; CHECK-NOT: nop
; CHECK: .word 12345678
; CHECK-LABEL: f1:
define void @f1(ptr noundef %x) !kcfi_type !1 {
; CHECK: lw t1, -4(a0)
call void %x() [ "kcfi"(i32 12345678) ]
ret void
}
; NOC: .p2align 2
; C: .p2align 1
; CHECK-NOT: .word
; CHECK-NOT: nop
; CHECK-LABEL: f2:
define void @f2(ptr noundef %x) {
; CHECK: lw t1, -4(a0)
call void %x() [ "kcfi"(i32 12345678) ]
ret void
}
; NOC: .p2align 2
; C: .p2align 1
; CHECK: .word 12345678
; CHECK-COUNT-11: nop
; CHECK-LABEL: f3:
define void @f3(ptr noundef %x) #0 !kcfi_type !1 {
; NOC: lw t1, -48(a0)
; C: lw t1, -26(a0)
call void %x() [ "kcfi"(i32 12345678) ]
ret void
}
; NOC: .p2align 2
; C: .p2align 1
; CHECK-NOT: .word
; CHECK-COUNT-11: nop
; CHECK-LABEL: f4:
define void @f4(ptr noundef %x) #0 {
; NOC: lw t1, -48(a0)
; C: lw t1, -26(a0)
call void %x() [ "kcfi"(i32 12345678) ]
ret void
}
attributes #0 = { "patchable-function-prefix"="11" }
!llvm.module.flags = !{!0}
!0 = !{i32 4, !"kcfi", i32 1}
!1 = !{i32 12345678}

View File

@ -0,0 +1,66 @@
; RUN: llc -mtriple=riscv32 -verify-machineinstrs -riscv-no-aliases < %s \
; RUN: | FileCheck %s --check-prefixes=CHECK,RV32
; RUN: llc -mtriple=riscv64 -verify-machineinstrs -riscv-no-aliases < %s \
; RUN: | FileCheck %s --check-prefixes=CHECK,RV64
; CHECK: .word 12345678
define void @f1(ptr noundef %x) !kcfi_type !1 {
; CHECK-LABEL: f1:
; CHECK: # %bb.0:
; CHECK: lw t1, -4(a0)
; CHECK-NEXT: lui t2, 3014
; RV32-NEXT: addi t2, t2, 334
; RV64-NEXT: addiw t2, t2, 334
; CHECK-NEXT: beq t1, t2, .Ltmp0
; CHECK-NEXT: .Ltmp1:
; CHECK-NEXT: ebreak
; CHECK-NEXT: .section .kcfi_traps,"ao",@progbits,.text
; CHECK-NEXT: .Ltmp2:
; CHECK-NEXT: .word .Ltmp1-.Ltmp2
; CHECK-NEXT: .text
; CHECK-NEXT: .Ltmp0:
; CHECK-NEXT: jalr ra, 0(a0)
call void %x() [ "kcfi"(i32 12345678) ]
; CHECK: lw t1, -4(s0)
; CHECK-NEXT: addi t2, t2, 1234
; CHECK-NEXT: beq t1, t2, .Ltmp3
; CHECK-NEXT: .Ltmp4:
; CHECK-NEXT: ebreak
; CHECK-NEXT: .section .kcfi_traps,"ao",@progbits,.text
; CHECK-NEXT: .Ltmp5:
; CHECK-NEXT: .word .Ltmp4-.Ltmp5
; CHECK-NEXT: .text
; CHECK-NEXT: .Ltmp3:
; CHECK-NEXT: jalr ra, 0(s0)
call void %x() [ "kcfi"(i32 1234) ]
ret void
}
; CHECK-NOT: .word:
define void @f2(ptr noundef %x) #0 {
; CHECK-LABEL: f2:
; CHECK: # %bb.0:
; CHECK-NEXT: addi zero, zero, 0
; CHECK-NEXT: addi zero, zero, 0
; CHECK-NEXT: lw t1, -4(a0)
; CHECK-NEXT: lui t2, 3014
; RV32-NEXT: addi t2, t2, 334
; RV64-NEXT: addiw t2, t2, 334
; CHECK-NEXT: beq t1, t2, .Ltmp6
; CHECK-NEXT: .Ltmp7:
; CHECK-NEXT: ebreak
; CHECK-NEXT: .section .kcfi_traps,"ao",@progbits,.text
; CHECK-NEXT: .Ltmp8:
; CHECK-NEXT: .word .Ltmp7-.Ltmp8
; CHECK-NEXT: .text
; CHECK-NEXT: .Ltmp6:
; CHECK-NEXT: jalr zero, 0(a0)
tail call void %x() [ "kcfi"(i32 12345678) ]
ret void
}
attributes #0 = { "patchable-function-entry"="2" }
!llvm.module.flags = !{!0}
!0 = !{i32 4, !"kcfi", i32 1}
!1 = !{i32 12345678}

View File

@ -0,0 +1,185 @@
# NOTE: Assertions have been autogenerated by utils/update_mir_test_checks.py UTC_ARGS: --version 2
# RUN: llc -mtriple=riscv64 -stop-after=kcfi -o - %s | FileCheck %s
--- |
; ModuleID = '<stdin>'
source_filename = "<stdin>"
target datalayout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128"
target triple = "riscv64"
define void @f1(ptr noundef %x) !kcfi_type !1 {
call void %x() [ "kcfi"(i32 12345678) ]
ret void
}
define void @f2(ptr noundef %x) #0 {
tail call void %x() [ "kcfi"(i32 12345678) ]
ret void
}
attributes #0 = { "patchable-function-entry"="2" }
!llvm.module.flags = !{!0}
!0 = !{i32 4, !"kcfi", i32 1}
!1 = !{i32 12345678}
...
---
name: f1
alignment: 4
exposesReturnsTwice: false
legalized: false
regBankSelected: false
selected: false
failedISel: false
tracksRegLiveness: true
hasWinCFI: false
callsEHReturn: false
callsUnwindInit: false
hasEHCatchret: false
hasEHScopes: false
hasEHFunclets: false
isOutlined: false
debugInstrRef: false
failsVerification: false
tracksDebugUserValues: true
registers: []
liveins:
- { reg: '$x10', virtual-reg: '' }
frameInfo:
isFrameAddressTaken: false
isReturnAddressTaken: false
hasStackMap: false
hasPatchPoint: false
stackSize: 16
offsetAdjustment: 0
maxAlignment: 8
adjustsStack: true
hasCalls: true
stackProtector: ''
functionContext: ''
maxCallFrameSize: 0
cvBytesOfCalleeSavedRegisters: 0
hasOpaqueSPAdjustment: false
hasVAStart: false
hasMustTailInVarArgFunc: false
hasTailCall: false
localFrameSize: 0
savePoint: ''
restorePoint: ''
fixedStack: []
stack:
- { id: 0, name: '', type: spill-slot, offset: -8, size: 8, alignment: 8,
stack-id: default, callee-saved-register: '$x1', callee-saved-restored: true,
debug-info-variable: '', debug-info-expression: '', debug-info-location: '' }
callSites: []
debugValueSubstitutions: []
constants: []
machineFunctionInfo:
varArgsFrameIndex: 0
varArgsSaveSize: 0
body: |
bb.0 (%ir-block.0):
liveins: $x10, $x1
; CHECK-LABEL: name: f1
; CHECK: liveins: $x1, $x10
; CHECK-NEXT: {{ $}}
; CHECK-NEXT: $x2 = frame-setup ADDI $x2, -16
; CHECK-NEXT: frame-setup CFI_INSTRUCTION def_cfa_offset 16
; CHECK-NEXT: SD $x1, $x2, 8 :: (store (s64) into %stack.1)
; CHECK-NEXT: frame-setup CFI_INSTRUCTION offset $x1, -8
; CHECK-NEXT: $x2 = frame-setup ADDI $x2, -16
; CHECK-NEXT: frame-setup CFI_INSTRUCTION def_cfa_offset 16
; CHECK-NEXT: SD $x1, $x2, 8 :: (store (s64) into %stack.0)
; CHECK-NEXT: frame-setup CFI_INSTRUCTION offset $x1, -8
; CHECK-NEXT: BUNDLE implicit-def dead $x6, implicit-def dead $x7, implicit-def dead $x28, implicit-def dead $x29, implicit-def dead $x30, implicit-def dead $x31, implicit-def dead $x1, implicit-def $x2, implicit $x10 {
; CHECK-NEXT: KCFI_CHECK $x10, 12345678, implicit-def $x6, implicit-def $x7, implicit-def $x28, implicit-def $x29, implicit-def $x30, implicit-def $x31
; CHECK-NEXT: PseudoCALLIndirect $x10, csr_ilp32_lp64, implicit-def dead $x1, implicit-def $x2
; CHECK-NEXT: }
; CHECK-NEXT: dead $x1 = LD $x2, 8 :: (load (s64) from %stack.0)
; CHECK-NEXT: $x2 = frame-destroy ADDI $x2, 16
; CHECK-NEXT: $x1 = LD $x2, 8 :: (load (s64) from %stack.1)
; CHECK-NEXT: $x2 = frame-destroy ADDI $x2, 16
; CHECK-NEXT: PseudoRET
$x2 = frame-setup ADDI $x2, -16
frame-setup CFI_INSTRUCTION def_cfa_offset 16
SD killed $x1, $x2, 8 :: (store (s64) into %stack.0)
frame-setup CFI_INSTRUCTION offset $x1, -8
BUNDLE implicit-def $x6, implicit-def $x7, implicit-def $x28, implicit-def $x29, implicit-def $x30, implicit-def $x31, implicit-def dead $x1, implicit-def $x2, implicit killed $x10 {
KCFI_CHECK $x10, 12345678, implicit-def $x6, implicit-def $x7, implicit-def $x28, implicit-def $x29, implicit-def $x30, implicit-def $x31
PseudoCALLIndirect killed $x10, csr_ilp32_lp64, implicit-def dead $x1, implicit-def $x2
}
$x1 = LD $x2, 8 :: (load (s64) from %stack.0)
$x2 = frame-destroy ADDI $x2, 16
PseudoRET
...
---
name: f2
alignment: 4
exposesReturnsTwice: false
legalized: false
regBankSelected: false
selected: false
failedISel: false
tracksRegLiveness: true
hasWinCFI: false
callsEHReturn: false
callsUnwindInit: false
hasEHCatchret: false
hasEHScopes: false
hasEHFunclets: false
isOutlined: false
debugInstrRef: false
failsVerification: false
tracksDebugUserValues: true
registers: []
liveins:
- { reg: '$x10', virtual-reg: '' }
frameInfo:
isFrameAddressTaken: false
isReturnAddressTaken: false
hasStackMap: false
hasPatchPoint: false
stackSize: 0
offsetAdjustment: 0
maxAlignment: 1
adjustsStack: false
hasCalls: false
stackProtector: ''
functionContext: ''
maxCallFrameSize: 0
cvBytesOfCalleeSavedRegisters: 0
hasOpaqueSPAdjustment: false
hasVAStart: false
hasMustTailInVarArgFunc: false
hasTailCall: true
localFrameSize: 0
savePoint: ''
restorePoint: ''
fixedStack: []
stack: []
callSites: []
debugValueSubstitutions: []
constants: []
machineFunctionInfo:
varArgsFrameIndex: 0
varArgsSaveSize: 0
body: |
bb.0 (%ir-block.0):
liveins: $x10
; CHECK-LABEL: name: f2
; CHECK: liveins: $x10
; CHECK-NEXT: {{ $}}
; CHECK-NEXT: BUNDLE implicit-def dead $x6, implicit-def dead $x7, implicit-def dead $x28, implicit-def dead $x29, implicit-def dead $x30, implicit-def dead $x31, implicit $x10, implicit $x2 {
; CHECK-NEXT: KCFI_CHECK $x10, 12345678, implicit-def $x6, implicit-def $x7, implicit-def $x28, implicit-def $x29, implicit-def $x30, implicit-def $x31
; CHECK-NEXT: PseudoTAILIndirect $x10, implicit $x2
; CHECK-NEXT: }
BUNDLE implicit-def $x6, implicit-def $x7, implicit-def $x28, implicit-def $x29, implicit-def $x30, implicit-def $x31, implicit killed $x10, implicit $x2 {
KCFI_CHECK $x10, 12345678, implicit-def $x6, implicit-def $x7, implicit-def $x28, implicit-def $x29, implicit-def $x30, implicit-def $x31
PseudoTAILIndirect killed $x10, implicit $x2
}
...