[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