[llvm] [Hexagon] Add KCFI support for forward-edge control flow integrity (PR #191746)
via llvm-commits
llvm-commits at lists.llvm.org
Sun Apr 12 18:10:14 PDT 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-backend-hexagon
Author: Brian Cain (androm3da)
<details>
<summary>Changes</summary>
Add KCFI support for Hexagon. KCFI provides lightweight forward-edge CFI for indirect calls by embedding a type hash before each function and checking it before indirect calls, without requiring LTO.
---
Full diff: https://github.com/llvm/llvm-project/pull/191746.diff
8 Files Affected:
- (modified) llvm/lib/Target/Hexagon/HexagonAsmPrinter.cpp (+118)
- (modified) llvm/lib/Target/Hexagon/HexagonAsmPrinter.h (+3)
- (modified) llvm/lib/Target/Hexagon/HexagonISelLowering.cpp (+32-1)
- (modified) llvm/lib/Target/Hexagon/HexagonISelLowering.h (+6)
- (modified) llvm/lib/Target/Hexagon/HexagonMCInstLower.cpp (+4)
- (modified) llvm/lib/Target/Hexagon/HexagonPseudo.td (+10)
- (modified) llvm/lib/Target/Hexagon/HexagonTargetMachine.cpp (+4)
- (added) llvm/test/CodeGen/Hexagon/kcfi.ll (+70)
``````````diff
diff --git a/llvm/lib/Target/Hexagon/HexagonAsmPrinter.cpp b/llvm/lib/Target/Hexagon/HexagonAsmPrinter.cpp
index a83a221b1a9b1..716484ae1f954 100644
--- a/llvm/lib/Target/Hexagon/HexagonAsmPrinter.cpp
+++ b/llvm/lib/Target/Hexagon/HexagonAsmPrinter.cpp
@@ -801,6 +801,124 @@ void HexagonAsmPrinter::emitAttributes() {
HTS.emitTargetAttributes(*TM.getMCSubtargetInfo());
}
+void HexagonAsmPrinter::LowerKCFI_CHECK(const MachineInstr &MI) {
+ Register AddrReg = MI.getOperand(0).getReg();
+ const int64_t Type = MI.getOperand(1).getImm();
+ MachineBasicBlock::const_instr_iterator NextI = std::next(MI.getIterator());
+ assert(NextI != MI.getParent()->instr_end() && NextI->isCall() &&
+ "KCFI_CHECK not followed by a call instruction");
+ assert(NextI->getOperand(0).getReg() == AddrReg &&
+ "KCFI_CHECK call target doesn't match call operand");
+
+ // Scratch registers for the compare. Default to R6/R7 (caller-saved,
+ // in GeneralSubRegs for potential compounding). If AddrReg conflicts,
+ // fall back through other caller-saved registers.
+ unsigned ScratchRegs[] = {Hexagon::R6, Hexagon::R7};
+ unsigned NextReg = Hexagon::R8;
+ for (auto &Reg : ScratchRegs) {
+ if (Reg != AddrReg)
+ continue;
+ while (NextReg == AddrReg)
+ ++NextReg;
+ assert(NextReg <= Hexagon::R15 &&
+ "Unable to find scratch register for KCFI_CHECK");
+ Reg = NextReg++;
+ }
+ unsigned LoadReg = ScratchRegs[0];
+ unsigned TypeReg = ScratchRegs[1];
+ unsigned PredReg = Hexagon::P0;
+
+ // Adjust for patchable-function-prefix (nop padding before the function).
+ int64_t PrefixNops = 0;
+ (void)MI.getMF()
+ ->getFunction()
+ .getFnAttribute("patchable-function-prefix")
+ .getValueAsString()
+ .getAsInteger(10, PrefixNops);
+ int64_t Offset = -(PrefixNops * 4 + 4);
+
+ // Emit the KCFI check sequence as individual packets.
+ // Packet 1: Load the type hash from 4 bytes before the function entry.
+ // { r_load = memw(r_addr + #offset) }
+ MCInst *LoadInst = OutContext.createMCInst();
+ LoadInst->setOpcode(Hexagon::L2_loadri_io);
+ LoadInst->addOperand(MCOperand::createReg(LoadReg));
+ LoadInst->addOperand(MCOperand::createReg(AddrReg));
+ LoadInst->addOperand(MCOperand::createExpr(HexagonMCExpr::create(
+ MCConstantExpr::create(Offset, OutContext), OutContext)));
+
+ MCInst LoadPacket;
+ LoadPacket.setOpcode(Hexagon::BUNDLE);
+ LoadPacket.addOperand(MCOperand::createImm(0));
+ LoadPacket.addOperand(MCOperand::createInst(LoadInst));
+ EmitToStreamer(*OutStreamer, LoadPacket);
+
+ // Packet 2: Materialize the expected type hash.
+ // { r_type = ##expected_hash }
+ // This requires a constant extender (immext + transfer).
+ MCInst *TypeInst = OutContext.createMCInst();
+ TypeInst->setOpcode(Hexagon::A2_tfrsi);
+ TypeInst->addOperand(MCOperand::createReg(TypeReg));
+ auto *TypeExpr = HexagonMCExpr::create(
+ MCConstantExpr::create(Type, OutContext), OutContext);
+ HexagonMCInstrInfo::setMustExtend(*TypeExpr, true);
+ TypeInst->addOperand(MCOperand::createExpr(TypeExpr));
+
+ MCInst TypePacket;
+ TypePacket.setOpcode(Hexagon::BUNDLE);
+ TypePacket.addOperand(MCOperand::createImm(0));
+ TypePacket.addOperand(MCOperand::createInst(TypeInst));
+ EmitToStreamer(*OutStreamer, TypePacket);
+
+ // Packet 3: Compare and branch if equal.
+ // { p0 = cmp.eq(r_load, r_type); if (p0.new) jump:t .Lpass }
+ MCSymbol *Pass = OutContext.createTempSymbol();
+
+ MCInst *CmpInst = OutContext.createMCInst();
+ CmpInst->setOpcode(Hexagon::C2_cmpeq);
+ CmpInst->addOperand(MCOperand::createReg(PredReg));
+ CmpInst->addOperand(MCOperand::createReg(LoadReg));
+ CmpInst->addOperand(MCOperand::createReg(TypeReg));
+
+ MCInst *JumpInst = OutContext.createMCInst();
+ JumpInst->setOpcode(Hexagon::J2_jumptnewpt);
+ JumpInst->addOperand(MCOperand::createReg(PredReg));
+ JumpInst->addOperand(MCOperand::createExpr(HexagonMCExpr::create(
+ MCSymbolRefExpr::create(Pass, OutContext), OutContext)));
+
+ MCInst CmpJmpPacket;
+ CmpJmpPacket.setOpcode(Hexagon::BUNDLE);
+ CmpJmpPacket.addOperand(MCOperand::createImm(0));
+ CmpJmpPacket.addOperand(MCOperand::createInst(CmpInst));
+ CmpJmpPacket.addOperand(MCOperand::createInst(JumpInst));
+ EmitToStreamer(*OutStreamer, CmpJmpPacket);
+
+ // Packet 4: Crash on mismatch via misaligned load.
+ // Use the same mechanism as llvm.trap (PS_crash): a doubleword load from
+ // a misaligned address is guaranteed to fault in all execution modes,
+ // including kernel/monitor mode where trap0 may not generate a useful
+ // exception.
+ MCSymbol *TrapLabel = OutContext.createTempSymbol();
+ OutStreamer->emitLabel(TrapLabel);
+
+ MCInst *CrashInst = OutContext.createMCInst();
+ CrashInst->setOpcode(Hexagon::PS_loadrdabs);
+ CrashInst->addOperand(MCOperand::createReg(Hexagon::D13));
+ auto *CrashExpr = HexagonMCExpr::create(
+ MCConstantExpr::create(0xBADC0FEE, OutContext), OutContext);
+ HexagonMCInstrInfo::setMustExtend(*CrashExpr, true);
+ CrashInst->addOperand(MCOperand::createExpr(CrashExpr));
+
+ MCInst CrashPacket;
+ CrashPacket.setOpcode(Hexagon::BUNDLE);
+ CrashPacket.addOperand(MCOperand::createImm(0));
+ CrashPacket.addOperand(MCOperand::createInst(CrashInst));
+ EmitToStreamer(*OutStreamer, CrashPacket);
+
+ emitKCFITrapEntry(*MI.getMF(), TrapLabel);
+ OutStreamer->emitLabel(Pass);
+}
+
void HexagonAsmPrinter::EmitSled(const MachineInstr &MI, SledKind Kind) {
static const int8_t NoopsInSledCount = 6;
// We want to emit the following pattern:
diff --git a/llvm/lib/Target/Hexagon/HexagonAsmPrinter.h b/llvm/lib/Target/Hexagon/HexagonAsmPrinter.h
index 8e34d2bf9209b..2693f3e5d612e 100644
--- a/llvm/lib/Target/Hexagon/HexagonAsmPrinter.h
+++ b/llvm/lib/Target/Hexagon/HexagonAsmPrinter.h
@@ -67,6 +67,9 @@ class TargetMachine;
void LowerPATCHABLE_TAIL_CALL(const MachineInstr &MI);
void EmitSled(const MachineInstr &MI, SledKind Kind);
+ // KCFI check lowering.
+ void LowerKCFI_CHECK(const MachineInstr &MI);
+
void HexagonProcessInstruction(MCInst &Inst, const MachineInstr &MBB);
void printOperand(const MachineInstr *MI, unsigned OpNo, raw_ostream &O);
diff --git a/llvm/lib/Target/Hexagon/HexagonISelLowering.cpp b/llvm/lib/Target/Hexagon/HexagonISelLowering.cpp
index 6b2a963e2e777..20b87a8032aee 100644
--- a/llvm/lib/Target/Hexagon/HexagonISelLowering.cpp
+++ b/llvm/lib/Target/Hexagon/HexagonISelLowering.cpp
@@ -633,7 +633,10 @@ HexagonTargetLowering::LowerCall(TargetLowering::CallLoweringInfo &CLI,
if (CLI.IsTailCall) {
MFI.setHasTailCall();
- return DAG.getNode(HexagonISD::TC_RETURN, dl, MVT::Other, Ops);
+ SDValue Ret = DAG.getNode(HexagonISD::TC_RETURN, dl, MVT::Other, Ops);
+ if (CLI.CFIType)
+ Ret.getNode()->setCFIType(CLI.CFIType->getZExtValue());
+ return Ret;
}
// Set this here because we need to know this for "hasFP" in frame lowering.
@@ -643,6 +646,8 @@ HexagonTargetLowering::LowerCall(TargetLowering::CallLoweringInfo &CLI,
unsigned OpCode = DoesNotReturn ? HexagonISD::CALLnr : HexagonISD::CALL;
Chain = DAG.getNode(OpCode, dl, {MVT::Other, MVT::Glue}, Ops);
+ if (CLI.CFIType)
+ Chain.getNode()->setCFIType(CLI.CFIType->getZExtValue());
Glue = Chain.getValue(1);
// Create the CALLSEQ_END node.
@@ -3957,6 +3962,32 @@ HexagonTargetLowering::shouldExpandAtomicCmpXchgInIR(
return AtomicExpansionKind::LLSC;
}
+MachineInstr *
+HexagonTargetLowering::EmitKCFICheck(MachineBasicBlock &MBB,
+ MachineBasicBlock::instr_iterator &MBBI,
+ const TargetInstrInfo *TII) const {
+ assert(MBBI->isCall() && MBBI->getCFIType() &&
+ "Invalid call instruction for a KCFI check");
+
+ switch (MBBI->getOpcode()) {
+ case Hexagon::J2_callr:
+ case Hexagon::PS_call_nr:
+ case Hexagon::PS_tailcall_r:
+ break;
+ default:
+ llvm_unreachable("Unexpected CFI call opcode");
+ }
+
+ MachineOperand &Target = MBBI->getOperand(0);
+ assert(Target.isReg() && "Invalid target operand for an indirect call");
+ Target.setIsRenamable(false);
+
+ return BuildMI(MBB, MBBI, MBBI->getDebugLoc(), TII->get(Hexagon::KCFI_CHECK))
+ .addReg(Target.getReg())
+ .addImm(MBBI->getCFIType())
+ .getInstr();
+}
+
bool HexagonTargetLowering::isMaskAndCmp0FoldingBeneficial(
const Instruction &AndI) const {
// Only sink 'and' mask to cmp use block if it is masking a single bit since
diff --git a/llvm/lib/Target/Hexagon/HexagonISelLowering.h b/llvm/lib/Target/Hexagon/HexagonISelLowering.h
index 55be8fd043ad5..ddc81d5b87d68 100644
--- a/llvm/lib/Target/Hexagon/HexagonISelLowering.h
+++ b/llvm/lib/Target/Hexagon/HexagonISelLowering.h
@@ -287,6 +287,12 @@ class HexagonTargetLowering : public TargetLowering {
return AtomicExpansionKind::LLSC;
}
+ bool supportKCFIBundles() const override { return true; }
+
+ MachineInstr *EmitKCFICheck(MachineBasicBlock &MBB,
+ MachineBasicBlock::instr_iterator &MBBI,
+ const TargetInstrInfo *TII) const override;
+
private:
void initializeHVXLowering();
unsigned getPreferredHvxVectorAction(MVT VecTy) const;
diff --git a/llvm/lib/Target/Hexagon/HexagonMCInstLower.cpp b/llvm/lib/Target/Hexagon/HexagonMCInstLower.cpp
index 845e60bf1a2a1..16ec0d7325056 100644
--- a/llvm/lib/Target/Hexagon/HexagonMCInstLower.cpp
+++ b/llvm/lib/Target/Hexagon/HexagonMCInstLower.cpp
@@ -115,6 +115,10 @@ void llvm::HexagonLowerToMC(const MCInstrInfo &MCII, const MachineInstr *MI,
AP.EmitSled(*MI, HexagonAsmPrinter::SledKind::TAIL_CALL);
return;
}
+ if (MI->getOpcode() == Hexagon::KCFI_CHECK) {
+ AP.LowerKCFI_CHECK(*MI);
+ return;
+ }
MCInst *MCI = AP.OutContext.createMCInst();
MCI->setOpcode(MI->getOpcode());
diff --git a/llvm/lib/Target/Hexagon/HexagonPseudo.td b/llvm/lib/Target/Hexagon/HexagonPseudo.td
index 4e5e8c3d26f00..d75a5c0e51ce8 100644
--- a/llvm/lib/Target/Hexagon/HexagonPseudo.td
+++ b/llvm/lib/Target/Hexagon/HexagonPseudo.td
@@ -621,6 +621,16 @@ defm PS_storerd : NewCircularStore<DoubleRegs, WordAccess>;
let hasSideEffects = 1, isPseudo = 1, isCodeGenOnly = 1, isSolo = 1 in
def PS_crash: InstHexagon<(outs), (ins), "", [], "", PSEUDO, TypePSEUDO>;
+// KCFI type check pseudo -- lowered in the AsmPrinter to a
+// load-compare-trap sequence before indirect calls. On mismatch, a
+// misaligned load faults.
+// Defs: R6/R7 default scratch (R8 fallback if AddrReg conflicts), P0 for
+// compare, D13 for the crash load.
+let hasSideEffects = 1, mayLoad = 1, isPseudo = 1, isCodeGenOnly = 1,
+ Defs = [R6, R7, R8, P0, D13], Size = 28 in
+def KCFI_CHECK : InstHexagon<(outs), (ins IntRegs:$ptr, i32imm:$type),
+ "", [], "", PSEUDO, TypePSEUDO>;
+
// This is actual trap1 instruction from before v65. It's here since it is
// no longer included in DepInstrInfo.td.
def PS_trap1 : HInst<(outs), (ins u8_0Imm:$Ii), "trap1(#$Ii)", tc_53c851ab,
diff --git a/llvm/lib/Target/Hexagon/HexagonTargetMachine.cpp b/llvm/lib/Target/Hexagon/HexagonTargetMachine.cpp
index e42fb0b9866b3..f3b2699751d07 100644
--- a/llvm/lib/Target/Hexagon/HexagonTargetMachine.cpp
+++ b/llvm/lib/Target/Hexagon/HexagonTargetMachine.cpp
@@ -513,6 +513,10 @@ void HexagonPassConfig::addPreEmitPass() {
addPass(&HexagonLiveVariablesID);
}
+ // Emit KCFI checks for indirect calls. Must run before packetization so
+ // the check and call can be bundled together into a VLIW packet.
+ addPass(createKCFIPass());
+
// Packetization is mandatory: it handles gather/scatter at all opt levels.
addPass(createHexagonPacketizer(NoOpt));
diff --git a/llvm/test/CodeGen/Hexagon/kcfi.ll b/llvm/test/CodeGen/Hexagon/kcfi.ll
new file mode 100644
index 0000000000000..08ece32d4cdd8
--- /dev/null
+++ b/llvm/test/CodeGen/Hexagon/kcfi.ll
@@ -0,0 +1,70 @@
+; RUN: llc -mtriple=hexagon -verify-machineinstrs < %s | FileCheck %s --check-prefix=ASM
+; RUN: llc -mtriple=hexagon -verify-machineinstrs -stop-after=finalize-isel < %s \
+; RUN: | FileCheck %s --check-prefix=ISEL
+; RUN: llc -mtriple=hexagon -verify-machineinstrs -stop-after=kcfi < %s \
+; RUN: | FileCheck %s --check-prefix=KCFI
+
+; Verify KCFI type hash is emitted before the function.
+; ASM: .word 12345678
+; ASM-LABEL: f1:
+
+define void @f1(ptr noundef %x) !kcfi_type !1 {
+; ASM: r{{[0-9]+}} = memw(r0+#-4)
+; ASM: r{{[0-9]+}} = ##12345678
+; ASM: p0 = cmp.eq(r{{[0-9]+}},r{{[0-9]+}})
+; ASM-NEXT: if (p0.new) jump:t
+; ASM: r{{[0-9]+}}:{{[0-9]+}} = memd(##3134984174)
+
+; After ISel, the call should carry a cfi-type.
+; ISEL-LABEL: name: f1
+; ISEL: J2_callr %0,{{.*}} cfi-type 12345678
+
+; After the KCFI pass, the check and call are bundled.
+; KCFI-LABEL: name: f1
+; KCFI: BUNDLE{{.*}} {
+; KCFI-NEXT: KCFI_CHECK $r0, 12345678
+; KCFI-NEXT: J2_callr killed $r0
+; KCFI-NEXT: }
+
+ call void %x() [ "kcfi"(i32 12345678) ]
+ ret void
+}
+
+; Test with a second call using a different type hash.
+define void @f2(ptr noundef %x) !kcfi_type !2 {
+; ASM-LABEL: f2:
+; ASM: r{{[0-9]+}} = memw(r0+#-4)
+; ASM: r{{[0-9]+}} = ##1234
+; ASM: p0 = cmp.eq(r{{[0-9]+}},r{{[0-9]+}})
+; ASM-NEXT: if (p0.new) jump:t
+; ASM: r{{[0-9]+}}:{{[0-9]+}} = memd(##3134984174)
+
+ call void %x() [ "kcfi"(i32 1234) ]
+ ret void
+}
+
+; Test with patchable-function-entry (nops placed after the label,
+; so the KCFI offset is still -4).
+define void @f3(ptr noundef %x) #0 {
+; ASM-LABEL: f3:
+; ASM: nop
+; ASM: nop
+; ASM: r{{[0-9]+}} = memw(r0+#-4)
+; ASM: r{{[0-9]+}} = ##12345678
+; ASM: p0 = cmp.eq(r{{[0-9]+}},r{{[0-9]+}})
+; ASM-NEXT: if (p0.new) jump:t
+; ASM: r{{[0-9]+}}:{{[0-9]+}} = memd(##3134984174)
+
+ call void %x() [ "kcfi"(i32 12345678) ]
+ ret void
+}
+
+; Verify the .kcfi_traps section is emitted.
+; ASM: .section .kcfi_traps
+
+attributes #0 = { "patchable-function-entry"="2" }
+
+!llvm.module.flags = !{!0}
+!0 = !{i32 4, !"kcfi", i32 1}
+!1 = !{i32 12345678}
+!2 = !{i32 1234}
``````````
</details>
https://github.com/llvm/llvm-project/pull/191746
More information about the llvm-commits
mailing list