[llvm] f8aa5f6 - [Hexagon] Add KCFI support for forward-edge control flow integrity (#191746)
via llvm-commits
llvm-commits at lists.llvm.org
Tue Jun 23 07:32:05 PDT 2026
Author: Brian Cain
Date: 2026-06-23T09:32:00-05:00
New Revision: f8aa5f66209d69c2c1f033317a4b4be961f4a78c
URL: https://github.com/llvm/llvm-project/commit/f8aa5f66209d69c2c1f033317a4b4be961f4a78c
DIFF: https://github.com/llvm/llvm-project/commit/f8aa5f66209d69c2c1f033317a4b4be961f4a78c.diff
LOG: [Hexagon] Add KCFI support for forward-edge control flow integrity (#191746)
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.
Added:
llvm/test/CodeGen/Hexagon/kcfi.ll
Modified:
llvm/lib/Target/Hexagon/HexagonAsmPrinter.cpp
llvm/lib/Target/Hexagon/HexagonAsmPrinter.h
llvm/lib/Target/Hexagon/HexagonISelLowering.cpp
llvm/lib/Target/Hexagon/HexagonISelLowering.h
llvm/lib/Target/Hexagon/HexagonMCInstLower.cpp
llvm/lib/Target/Hexagon/HexagonPseudo.td
llvm/lib/Target/Hexagon/HexagonTargetMachine.cpp
Removed:
################################################################################
diff --git a/llvm/lib/Target/Hexagon/HexagonAsmPrinter.cpp b/llvm/lib/Target/Hexagon/HexagonAsmPrinter.cpp
index 2a1427b38142c..61ca9020bb689 100644
--- a/llvm/lib/Target/Hexagon/HexagonAsmPrinter.cpp
+++ b/llvm/lib/Target/Hexagon/HexagonAsmPrinter.cpp
@@ -976,6 +976,115 @@ void HexagonAsmPrinter::LowerPATCHABLE_EVENT_CALL(const MachineInstr &MI,
Typed ? SledKind::TYPED_EVENT : SledKind::CUSTOM_EVENT, 2);
}
+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;
+ if (NextReg == AddrReg)
+ ++NextReg;
+ 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 = MI.getMF()->getFunction().getFnAttributeAsParsedInteger(
+ "patchable-function-prefix");
+ int64_t Offset = -(PrefixNops * 4 + 4);
+
+ // Emit the KCFI check sequence.
+ //
+ // Packet 1: Load the type hash and materialize the expected hash together.
+ // The load offset fits in the native instruction field for any
+ // patchable-function-prefix count, so it never requires a constant
+ // extender. This lets the extender for ##hash share the same packet,
+ // saving one packet compared to emitting them separately.
+ // { r_load = memw(r_addr + #offset); r_type = ##expected_hash }
+ 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 *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 LoadTypePacket;
+ LoadTypePacket.setOpcode(Hexagon::BUNDLE);
+ LoadTypePacket.addOperand(MCOperand::createImm(0));
+ LoadTypePacket.addOperand(MCOperand::createInst(LoadInst));
+ LoadTypePacket.addOperand(MCOperand::createInst(TypeInst));
+ EmitToStreamer(*OutStreamer, LoadTypePacket);
+
+ // 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 83621142ca5b5..1c71a2739bd49 100644
--- a/llvm/lib/Target/Hexagon/HexagonAsmPrinter.h
+++ b/llvm/lib/Target/Hexagon/HexagonAsmPrinter.h
@@ -68,6 +68,9 @@ class TargetMachine;
void LowerPATCHABLE_EVENT_CALL(const MachineInstr &MI, bool Typed);
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 b369bdff477d6..acafd754969d7 100644
--- a/llvm/lib/Target/Hexagon/HexagonISelLowering.cpp
+++ b/llvm/lib/Target/Hexagon/HexagonISelLowering.cpp
@@ -644,6 +644,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.
@@ -3971,6 +3973,31 @@ MachineBasicBlock *HexagonTargetLowering::EmitInstrWithCustomInserter(
}
}
+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_callr_nr:
+ 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 00152d7615769..87041e696f0f4 100644
--- a/llvm/lib/Target/Hexagon/HexagonISelLowering.h
+++ b/llvm/lib/Target/Hexagon/HexagonISelLowering.h
@@ -291,6 +291,12 @@ class HexagonTargetLowering : public TargetLowering {
EmitInstrWithCustomInserter(MachineInstr &MI,
MachineBasicBlock *BB) const override;
+ 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 c592a55615859..668c5e436e5ff 100644
--- a/llvm/lib/Target/Hexagon/HexagonMCInstLower.cpp
+++ b/llvm/lib/Target/Hexagon/HexagonMCInstLower.cpp
@@ -123,6 +123,10 @@ void llvm::HexagonLowerToMC(const MCInstrInfo &MCII, const MachineInstr *MI,
AP.LowerPATCHABLE_EVENT_CALL(*MI, true);
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 5c72b6cb20883..5d835c9c6e71c 100644
--- a/llvm/lib/Target/Hexagon/HexagonTargetMachine.cpp
+++ b/llvm/lib/Target/Hexagon/HexagonTargetMachine.cpp
@@ -508,6 +508,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..7fd2ee4a9e16d
--- /dev/null
+++ b/llvm/test/CodeGen/Hexagon/kcfi.ll
@@ -0,0 +1,191 @@
+; 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 {
+; Load and type-hash materialization are combined in one packet.
+; ASM: r{{[0-9]+}} = memw(r0+#-4)
+; ASM-NEXT: r{{[0-9]+}} = ##12345678
+; ASM-NEXT: }
+; ASM-NEXT: {
+; ASM-NEXT: p0 = cmp.eq(r{{[0-9]+}},r{{[0-9]+}})
+; ASM-NEXT: if (p0.new) jump:t
+; ASM-NEXT: }
+; 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
diff erent type hash.
+define void @f2(ptr noundef %x) !kcfi_type !2 {
+; ASM-LABEL: f2:
+; ASM: r{{[0-9]+}} = memw(r0+#-4)
+; ASM-NEXT: r{{[0-9]+}} = ##1234
+; ASM-NEXT: }
+; ASM-NEXT: {
+; ASM-NEXT: p0 = cmp.eq(r{{[0-9]+}},r{{[0-9]+}})
+; ASM-NEXT: if (p0.new) jump:t
+; ASM-NEXT: }
+; 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-NEXT: r{{[0-9]+}} = ##12345678
+; ASM-NEXT: }
+; ASM-NEXT: {
+; ASM-NEXT: p0 = cmp.eq(r{{[0-9]+}},r{{[0-9]+}})
+; ASM-NEXT: if (p0.new) jump:t
+; ASM-NEXT: }
+; ASM: r{{[0-9]+}}:{{[0-9]+}} = memd(##3134984174)
+
+ call void %x() [ "kcfi"(i32 12345678) ]
+ ret void
+}
+
+;; Test patchable-function-prefix: nops are placed before the function entry
+;; (after the type hash), so the KCFI load offset is adjusted from -4 to
+;; -(PrefixNops*4 + 4).
+define void @f4_prefix(ptr noundef %x) #1 !kcfi_type !1 {
+; ASM-LABEL: f4_prefix:
+; ASM: r{{[0-9]+}} = memw(r0+#-12)
+; ASM-NEXT: r{{[0-9]+}} = ##12345678
+; ASM-NEXT: }
+; ASM-NEXT: {
+; ASM-NEXT: p0 = cmp.eq(r{{[0-9]+}},r{{[0-9]+}})
+; ASM-NEXT: if (p0.new) jump:t
+; ASM-NEXT: }
+; ASM: r{{[0-9]+}}:{{[0-9]+}} = memd(##3134984174)
+
+ call void %x() [ "kcfi"(i32 12345678) ]
+ ret void
+}
+
+;; Test patchable-function-prefix with 3 nops: offset = -(3*4+4) = -16.
+define void @f5_prefix3(ptr noundef %x) #2 !kcfi_type !1 {
+; ASM-LABEL: f5_prefix3:
+; ASM: r{{[0-9]+}} = memw(r0+#-16)
+; ASM-NEXT: r{{[0-9]+}} = ##12345678
+; ASM-NEXT: }
+; ASM-NEXT: {
+; ASM-NEXT: p0 = cmp.eq(r{{[0-9]+}},r{{[0-9]+}})
+; ASM-NEXT: if (p0.new) jump:t
+; ASM-NEXT: }
+; ASM: r{{[0-9]+}}:{{[0-9]+}} = memd(##3134984174)
+
+ call void %x() [ "kcfi"(i32 12345678) ]
+ ret void
+}
+
+;; Test scratch register conflict: call target is R6. The default scratch
+;; registers are R6/R7, so when the target occupies R6, the load scratch
+;; must use R8 instead.
+define void @f6_target_r6() {
+; ASM-LABEL: f6_target_r6:
+; ASM: r8 = memw(r6+#-4)
+; ASM-NEXT: r7 = ##12345678
+; ASM-NEXT: }
+; ASM-NEXT: {
+; ASM-NEXT: p0 = cmp.eq(r8,r7)
+; ASM-NEXT: if (p0.new) jump:t
+; ASM-NEXT: }
+; ASM: r{{[0-9]+}}:{{[0-9]+}} = memd(##3134984174)
+
+; KCFI-LABEL: name: f6_target_r6
+; KCFI: BUNDLE{{.*}} {
+; KCFI-NEXT: KCFI_CHECK $r6, 12345678
+; KCFI-NEXT: J2_callr{{.*}}killed $r6
+; KCFI-NEXT: }
+
+ %target = call ptr asm sideeffect "", "={r6}"()
+ call void %target() [ "kcfi"(i32 12345678) ]
+ ret void
+}
+
+;; Test scratch register conflict: call target is R7. The type-hash scratch
+;; must use R8 instead.
+define void @f7_target_r7() {
+; ASM-LABEL: f7_target_r7:
+; ASM: r6 = memw(r7+#-4)
+; ASM-NEXT: r8 = ##12345678
+; ASM-NEXT: }
+; ASM-NEXT: {
+; ASM-NEXT: p0 = cmp.eq(r6,r8)
+; ASM-NEXT: if (p0.new) jump:t
+; ASM-NEXT: }
+; ASM: r{{[0-9]+}}:{{[0-9]+}} = memd(##3134984174)
+
+; KCFI-LABEL: name: f7_target_r7
+; KCFI: BUNDLE{{.*}} {
+; KCFI-NEXT: KCFI_CHECK $r7, 12345678
+; KCFI-NEXT: J2_callr{{.*}}killed $r7
+; KCFI-NEXT: }
+
+ %target = call ptr asm sideeffect "", "={r7}"()
+ call void %target() [ "kcfi"(i32 12345678) ]
+ ret void
+}
+
+;; Test noreturn indirect call with KCFI (uses PS_callr_nr opcode).
+define void @f8_noreturn(ptr noundef %x) {
+; ASM-LABEL: f8_noreturn:
+; ASM: r{{[0-9]+}} = memw(r0+#-4)
+; ASM-NEXT: r{{[0-9]+}} = ##12345678
+; ASM-NEXT: }
+; ASM-NEXT: {
+; ASM-NEXT: p0 = cmp.eq(r{{[0-9]+}},r{{[0-9]+}})
+; ASM-NEXT: if (p0.new) jump:t
+; ASM-NEXT: }
+; ASM: r{{[0-9]+}}:{{[0-9]+}} = memd(##3134984174)
+
+; ISEL-LABEL: name: f8_noreturn
+; ISEL: PS_callr_nr %0,{{.*}} cfi-type 12345678
+
+; KCFI-LABEL: name: f8_noreturn
+; KCFI: BUNDLE{{.*}} {
+; KCFI-NEXT: KCFI_CHECK $r0, 12345678
+; KCFI-NEXT: PS_callr_nr killed $r0
+; KCFI-NEXT: }
+
+ call void %x() #3 [ "kcfi"(i32 12345678) ]
+ unreachable
+}
+
+; Verify the .kcfi_traps section is emitted.
+; ASM: .section .kcfi_traps
+
+attributes #0 = { "patchable-function-entry"="2" }
+attributes #1 = { "patchable-function-prefix"="2" }
+attributes #2 = { "patchable-function-prefix"="3" }
+attributes #3 = { noreturn }
+
+!llvm.module.flags = !{!0}
+!0 = !{i32 4, !"kcfi", i32 1}
+!1 = !{i32 12345678}
+!2 = !{i32 1234}
More information about the llvm-commits
mailing list