[llvm-branch-commits] [llvm] release/23.x: [Hexagon] Fix KCFI check truncating type id (#211854) (PR #215633)

via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Tue Aug 11 11:07:37 PDT 2026


https://github.com/llvmbot created https://github.com/llvm/llvm-project/pull/215633

Backport c88aeaf712c6694d0b29d312783d24cacc000a5a

Requested by: @androm3da

>From c7e4b7889b0ee0d32a10ee2845ea52e6b90b683f Mon Sep 17 00:00:00 2001
From: Brian Cain <brian.cain at oss.qualcomm.com>
Date: Tue, 11 Aug 2026 12:28:51 -0500
Subject: [PATCH] [Hexagon] Fix KCFI check truncating type id (#211854)

The KCFI indirect-call check is lowered directly to MCInst in the
Hexagon AsmPrinter. It omitted the constant-extender, causing
mismatches.

Packet canonicalization is how we should apply constant extenders,
duplex, compounds, etc.

Assisted-by: Claude
(cherry picked from commit c88aeaf712c6694d0b29d312783d24cacc000a5a)
---
 llvm/lib/Target/Hexagon/HexagonAsmPrinter.cpp |  39 +++-
 llvm/lib/Target/Hexagon/HexagonPseudo.td      |   4 +-
 llvm/test/CodeGen/Hexagon/kcfi-obj-vs-asm.ll  | 117 +++++++++++
 .../CodeGen/Hexagon/kcfi-packet-context.ll    | 193 ++++++++++++++++++
 .../CodeGen/Hexagon/kcfi-packetization.ll     | 113 ++++++++++
 llvm/test/CodeGen/Hexagon/kcfi.ll             |  62 +++---
 6 files changed, 489 insertions(+), 39 deletions(-)
 create mode 100644 llvm/test/CodeGen/Hexagon/kcfi-obj-vs-asm.ll
 create mode 100644 llvm/test/CodeGen/Hexagon/kcfi-packet-context.ll
 create mode 100644 llvm/test/CodeGen/Hexagon/kcfi-packetization.ll

diff --git a/llvm/lib/Target/Hexagon/HexagonAsmPrinter.cpp b/llvm/lib/Target/Hexagon/HexagonAsmPrinter.cpp
index 3925f9fea668a..b965f8c1fbc08 100644
--- a/llvm/lib/Target/Hexagon/HexagonAsmPrinter.cpp
+++ b/llvm/lib/Target/Hexagon/HexagonAsmPrinter.cpp
@@ -17,6 +17,7 @@
 #include "HexagonRegisterInfo.h"
 #include "HexagonSubtarget.h"
 #include "MCTargetDesc/HexagonInstPrinter.h"
+#include "MCTargetDesc/HexagonMCChecker.h"
 #include "MCTargetDesc/HexagonMCExpr.h"
 #include "MCTargetDesc/HexagonMCInstrInfo.h"
 #include "MCTargetDesc/HexagonMCTargetDesc.h"
@@ -1009,11 +1010,9 @@ void HexagonAsmPrinter::LowerKCFI_CHECK(const MachineInstr &MI) {
 
   // 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.
+  // Packet 1: load the type hash and materialize the expected hash together.
+  // The load offset only leaves its field for an implausible
+  // patchable-function-prefix, but extend it rather than truncate.
   //   { r_load = memw(r_addr + #offset); r_type = ##expected_hash }
   MCInst *LoadInst = OutContext.createMCInst();
   LoadInst->setOpcode(Hexagon::L2_loadri_io);
@@ -1030,12 +1029,35 @@ void HexagonAsmPrinter::LowerKCFI_CHECK(const MachineInstr &MI) {
   HexagonMCInstrInfo::setMustExtend(*TypeExpr, true);
   TypeInst->addOperand(MCOperand::createExpr(TypeExpr));
 
+  // setMustExtend() only records that an operand needs an extender; the
+  // extender still has to be inserted, and slot assignment has to place it
+  // ahead of what it extends.  HexagonLowerToMC()/emitInstruction() do both
+  // for the MachineInstr stream; packets built here get neither.
+  const MCInstrInfo &MCII = *Subtarget->getInstrInfo();
+
+  // Slot assignment is required for correctness, not just density: an extender
+  // encoded after its instruction is not a legal packet.  Passing a checker
+  // (rather than nullptr) is what makes the assert meaningful.
+  auto EmitPacket = [&](MCInst &MCB) {
+    HexagonMCChecker Checker(OutContext, MCII, *Subtarget, MCB,
+                             *OutContext.getRegisterInfo(),
+                             /*ReportErrors=*/false);
+    [[maybe_unused]] bool Ok = HexagonMCInstrInfo::canonicalizePacket(
+        MCII, *Subtarget, OutContext, MCB, &Checker);
+    assert(Ok && "KCFI packet failed MC canonicalization");
+    EmitToStreamer(*OutStreamer, MCB);
+  };
+
   MCInst LoadTypePacket;
   LoadTypePacket.setOpcode(Hexagon::BUNDLE);
   LoadTypePacket.addOperand(MCOperand::createImm(0));
+  HexagonMCInstrInfo::extendIfNeeded(OutContext, MCII, LoadTypePacket,
+                                     *LoadInst);
   LoadTypePacket.addOperand(MCOperand::createInst(LoadInst));
+  HexagonMCInstrInfo::extendIfNeeded(OutContext, MCII, LoadTypePacket,
+                                     *TypeInst);
   LoadTypePacket.addOperand(MCOperand::createInst(TypeInst));
-  EmitToStreamer(*OutStreamer, LoadTypePacket);
+  EmitPacket(LoadTypePacket);
 
   // Packet 3: Compare and branch if equal.
   //   { p0 = cmp.eq(r_load, r_type); if (p0.new) jump:t .Lpass }
@@ -1058,7 +1080,7 @@ void HexagonAsmPrinter::LowerKCFI_CHECK(const MachineInstr &MI) {
   CmpJmpPacket.addOperand(MCOperand::createImm(0));
   CmpJmpPacket.addOperand(MCOperand::createInst(CmpInst));
   CmpJmpPacket.addOperand(MCOperand::createInst(JumpInst));
-  EmitToStreamer(*OutStreamer, CmpJmpPacket);
+  EmitPacket(CmpJmpPacket);
 
   // Packet 4: Crash on mismatch via misaligned load.
   // Use the same mechanism as llvm.trap (PS_crash): a doubleword load from
@@ -1079,8 +1101,9 @@ void HexagonAsmPrinter::LowerKCFI_CHECK(const MachineInstr &MI) {
   MCInst CrashPacket;
   CrashPacket.setOpcode(Hexagon::BUNDLE);
   CrashPacket.addOperand(MCOperand::createImm(0));
+  HexagonMCInstrInfo::extendIfNeeded(OutContext, MCII, CrashPacket, *CrashInst);
   CrashPacket.addOperand(MCOperand::createInst(CrashInst));
-  EmitToStreamer(*OutStreamer, CrashPacket);
+  EmitPacket(CrashPacket);
 
   emitKCFITrapEntry(*MI.getMF(), TrapLabel);
   OutStreamer->emitLabel(Pass);
diff --git a/llvm/lib/Target/Hexagon/HexagonPseudo.td b/llvm/lib/Target/Hexagon/HexagonPseudo.td
index 8736d4f05c321..66c0809cf2622 100644
--- a/llvm/lib/Target/Hexagon/HexagonPseudo.td
+++ b/llvm/lib/Target/Hexagon/HexagonPseudo.td
@@ -632,8 +632,10 @@ def PS_crash: InstHexagon<(outs), (ins), "", [], "", PSEUDO, TypePSEUDO>;
 // misaligned load faults.
 // Defs: R6/R7 default scratch (R8 fallback if AddrReg conflicts), P0 for
 // compare, D13 for the crash load.
+// Size is the worst case: two extenders in the load packet, an uncompounded
+// compare/jump, and the extended crash load.
 let hasSideEffects = 1, mayLoad = 1, isPseudo = 1, isCodeGenOnly = 1,
-    Defs = [R6, R7, R8, P0, D13], Size = 28 in
+    Defs = [R6, R7, R8, P0, D13], Size = 32 in
 def KCFI_CHECK : InstHexagon<(outs), (ins IntRegs:$ptr, i32imm:$type),
                               "", [], "", PSEUDO, TypePSEUDO>;
 
diff --git a/llvm/test/CodeGen/Hexagon/kcfi-obj-vs-asm.ll b/llvm/test/CodeGen/Hexagon/kcfi-obj-vs-asm.ll
new file mode 100644
index 0000000000000..d90c864973ebd
--- /dev/null
+++ b/llvm/test/CodeGen/Hexagon/kcfi-obj-vs-asm.ll
@@ -0,0 +1,117 @@
+;; The compiler and the assembler must agree on the encoding of a KCFI check:
+;; LowerKCFI_CHECK() emitting packets by hand makes the two paths independent
+;; implementations of the same sequence.  Comparing the .text images needs no
+;; prediction of the right encoding, so it catches divergences nobody thought
+;; to write a CHECK line for.
+;;
+;; Debugging a failure: this only says the two paths disagree somewhere in the
+;; module, and they are not identically configured -- the asm parser
+;; canonicalizes with a checker and AttemptCompatibility, the printer with
+;; neither -- so a packet only the perf checker objects to can legitimately
+;; differ.  Check kcfi-packetization.ll first, then bisect by deleting
+;; functions.  It also covers .kcfi_traps, which objcopy cannot extract here
+;; because its relocations point at .text.
+
+; RUN: llc -mtriple=hexagon -filetype=obj < %s -o %t.direct.o
+; RUN: llc -mtriple=hexagon -filetype=asm < %s -o %t.s
+; RUN: llvm-mc -triple=hexagon -filetype=obj %t.s -o %t.viaasm.o
+; RUN: llvm-objcopy -O binary --only-section=.text %t.direct.o %t.direct.bin
+; RUN: llvm-objcopy -O binary --only-section=.text %t.viaasm.o %t.viaasm.bin
+; RUN: cmp %t.direct.bin %t.viaasm.bin
+
+; RUN: llc -mtriple=hexagon -mcpu=hexagonv68 -filetype=obj < %s -o %t.68.o
+; RUN: llc -mtriple=hexagon -mcpu=hexagonv68 -filetype=asm < %s -o %t.68.s
+; RUN: llvm-mc -triple=hexagon -mcpu=hexagonv68 -filetype=obj %t.68.s -o %t.68a.o
+; RUN: llvm-objcopy -O binary --only-section=.text %t.68.o %t.68.bin
+; RUN: llvm-objcopy -O binary --only-section=.text %t.68a.o %t.68a.bin
+; RUN: cmp %t.68.bin %t.68a.bin
+
+; RUN: llc -mtriple=hexagon -mcpu=hexagonv79 -filetype=obj < %s -o %t.79.o
+; RUN: llc -mtriple=hexagon -mcpu=hexagonv79 -filetype=asm < %s -o %t.79.s
+; RUN: llvm-mc -triple=hexagon -mcpu=hexagonv79 -filetype=obj %t.79.s -o %t.79a.o
+; RUN: llvm-objcopy -O binary --only-section=.text %t.79.o %t.79.bin
+; RUN: llvm-objcopy -O binary --only-section=.text %t.79a.o %t.79a.bin
+; RUN: cmp %t.79.bin %t.79a.bin
+
+;; The configuration the Hexagon Linux kernel actually builds with.
+; RUN: llc -mtriple=hexagon --disable-packetizer -filetype=obj < %s -o %t.np.o
+; RUN: llc -mtriple=hexagon --disable-packetizer -filetype=asm < %s -o %t.np.s
+; RUN: llvm-mc -triple=hexagon -filetype=obj %t.np.s -o %t.npa.o
+; RUN: llvm-objcopy -O binary --only-section=.text %t.np.o %t.np.bin
+; RUN: llvm-objcopy -O binary --only-section=.text %t.npa.o %t.npa.bin
+; RUN: cmp %t.np.bin %t.npa.bin
+
+;; Hash needing an extender, target in r0.
+define void @plain(ptr noundef %fp) {
+  call void %fp() [ "kcfi"(i32 12345678) ]
+  ret void
+}
+
+;; Hash small enough that a naive implementation might skip the extender.
+define void @small(ptr noundef %fp) {
+  call void %fp() [ "kcfi"(i32 7) ]
+  ret void
+}
+
+;; Hash with the top bit set: sign-extension mistakes show up here.
+define void @negative_hash(ptr noundef %fp) {
+  call void %fp() [ "kcfi"(i32 -559038737) ]
+  ret void
+}
+
+;; Six integer arguments occupy r0-r5, pushing the call target into the
+;; range where LowerKCFI_CHECK has to fall back off its default r6/r7
+;; scratch pair.
+define void @scratch_conflict(ptr noundef %fp, i32 %a, i32 %b, i32 %c,
+                              i32 %d, i32 %e, i32 %f) {
+  call void %fp(i32 %a, i32 %b, i32 %c, i32 %d, i32 %e, i32 %f)
+      [ "kcfi"(i32 12345678) ]
+  ret void
+}
+
+;; Several checks in one function, so any per-function state in the lowering
+;; has to be reset between them.
+define void @repeated(ptr noundef %f, ptr noundef %g, ptr noundef %h) {
+  call void %f() [ "kcfi"(i32 1) ]
+  call void %g() [ "kcfi"(i32 12345678) ]
+  call void %h() [ "kcfi"(i32 -1) ]
+  ret void
+}
+
+;; Tail position: the call is the last thing in the function.
+define void @tail(ptr noundef %fp) {
+  tail call void %fp() [ "kcfi"(i32 4321) ]
+  ret void
+}
+
+;; A noreturn target -- no return path after the call.
+define void @noreturn_target(ptr noundef %fp) {
+  call void %fp() #0 [ "kcfi"(i32 555) ]
+  unreachable
+}
+
+;; Check inside a loop body, next to the loop's own compare and branch.
+define void @in_loop(ptr noundef %fp, i32 %n) {
+entry:
+  br label %loop
+loop:
+  %i = phi i32 [ 0, %entry ], [ %inc, %loop ]
+  call void %fp() [ "kcfi"(i32 12345678) ]
+  %inc = add i32 %i, 1
+  %cmp = icmp slt i32 %inc, %n
+  br i1 %cmp, label %loop, label %exit
+exit:
+  ret void
+}
+
+;; The prefix form, where the load offset is not the default -4.
+define void @prefixed(ptr noundef %fp) #1 {
+  call void %fp() [ "kcfi"(i32 12345678) ]
+  ret void
+}
+
+attributes #0 = { noreturn }
+attributes #1 = { "patchable-function-prefix"="3" }
+
+!llvm.module.flags = !{!0}
+!0 = !{i32 4, !"kcfi", i32 1}
diff --git a/llvm/test/CodeGen/Hexagon/kcfi-packet-context.ll b/llvm/test/CodeGen/Hexagon/kcfi-packet-context.ll
new file mode 100644
index 0000000000000..fb14b3e7b25f8
--- /dev/null
+++ b/llvm/test/CodeGen/Hexagon/kcfi-packet-context.ll
@@ -0,0 +1,193 @@
+;; KCFI checks next to instructions with their own packetization constraints:
+;; slot-restricted cache ops, HVX, multiply-accumulates, loop compare-and-branch
+;; pairs, and register pressure that pushes the call target out of the scratch
+;; registers the lowering prefers.  Target intrinsics are used because they are
+;; the only way to place a *particular* instruction next to the check from IR.
+
+; RUN: llc -mtriple=hexagon -mattr=+hvxv68,+hvx-length128b -filetype=obj < %s \
+; RUN:   | llvm-objdump -d --no-show-raw-insn - | FileCheck %s
+
+; RUN: llc -mtriple=hexagon -mattr=+hvxv68,+hvx-length128b --disable-packetizer \
+; RUN:   -filetype=obj < %s | llvm-objdump -d --no-show-raw-insn - | FileCheck %s
+
+; RUN: llc -mtriple=hexagon -mattr=+hvxv68,+hvx-length128b -filetype=obj < %s -o %t.o
+; RUN: llc -mtriple=hexagon -mattr=+hvxv68,+hvx-length128b -filetype=asm < %s -o %t.s
+; RUN: llvm-mc -triple=hexagon -mattr=+hvxv68,+hvx-length128b -filetype=obj %t.s -o %t.a.o
+; RUN: llvm-objcopy -O binary --only-section=.text %t.o %t.bin
+; RUN: llvm-objcopy -O binary --only-section=.text %t.a.o %t.a.bin
+; RUN: cmp %t.bin %t.a.bin
+
+declare void @llvm.hexagon.Y2.dccleana(ptr)
+declare i32 @llvm.hexagon.A2.abs(i32)
+declare i32 @llvm.hexagon.M2.mpy.acc.sat.ll.s0(i32, i32, i32)
+declare <32 x i32> @llvm.hexagon.V6.vaddw.128B(<32 x i32>, <32 x i32>)
+declare <32 x i32> @llvm.hexagon.V6.vmpyiwb.128B(<32 x i32>, i32)
+declare void @llvm.prefetch.p0(ptr, i32, i32, i32)
+
+;; Cache operations are restricted to a single slot, so the packetizer cannot
+;; fold them into the check's packets.  The braces are what actually asserts
+;; that: each dccleana stands alone, and the call is not pulled in with either.
+define void @cache_ops(ptr noundef %fp, ptr %p) {
+; CHECK-LABEL: <cache_ops>:
+; CHECK:        { dccleana(r{{[0-9]+}}) }
+; CHECK-NEXT:   { immext(#
+; CHECK-NEXT:     r{{[0-9]+}} = ##0xbc614e
+; CHECK-NEXT:     r{{[0-9]+}} = memw(r0+#-0x4) }
+; CHECK-NEXT:   { p0 = cmp.eq(r{{[0-9]+}},r{{[0-9]+}})
+; CHECK:          if (p0.new) jump:t {{.*}} }
+; CHECK-NEXT:   { immext(#0xbadc0fc0)
+; CHECK-NEXT:     r{{[0-9]+}}:{{[0-9]+}} = memd(##0xbadc0fee) }
+; CHECK-NEXT:   { callr r0 }
+; CHECK-NEXT:   { dccleana(r{{[0-9]+}}) }
+  call void @llvm.hexagon.Y2.dccleana(ptr %p)
+  call void %fp() [ "kcfi"(i32 12345678) ]
+  call void @llvm.hexagon.Y2.dccleana(ptr %p)
+  ret void
+}
+
+;; HVX vector ops either side of the check.  Vector work uses slots 0 and 1 and
+;; forces vector spills around the call, which lands stores next to the check.
+define <32 x i32> @hvx_neighbors(ptr noundef %fp, <32 x i32> %a, <32 x i32> %b) {
+; CHECK-LABEL: <hvx_neighbors>:
+; CHECK:        v{{[0-9]+}}.w = vadd(v{{[0-9]+}}.w,v{{[0-9]+}}.w)
+; CHECK:        immext(#
+; CHECK-NEXT:   r{{[0-9]+}} = ##0x3e7
+; CHECK-NEXT:   r{{[0-9]+}} = memw(r0+#-0x4) }
+; CHECK:        immext(#0xbadc0fc0)
+; CHECK-NEXT:   r{{[0-9]+}}:{{[0-9]+}} = memd(##0xbadc0fee)
+; CHECK:        callr r0
+  %v = call <32 x i32> @llvm.hexagon.V6.vaddw.128B(<32 x i32> %a, <32 x i32> %b)
+  call void %fp() [ "kcfi"(i32 999) ]
+  %w = call <32 x i32> @llvm.hexagon.V6.vaddw.128B(<32 x i32> %v, <32 x i32> %b)
+  ret <32 x i32> %w
+}
+
+;; A vector multiply that needs a scalar operand keeps a GPR live across the
+;; check, competing with the scratch registers it wants.
+define <32 x i32> @hvx_scalar_operand(ptr noundef %fp, <32 x i32> %a, i32 %s) {
+; CHECK-LABEL: <hvx_scalar_operand>:
+; CHECK:        immext(#0xbadc0fc0)
+; CHECK-NEXT:   r{{[0-9]+}}:{{[0-9]+}} = memd(##0xbadc0fee)
+; CHECK:        callr r0
+  %v = call <32 x i32> @llvm.hexagon.V6.vmpyiwb.128B(<32 x i32> %a, i32 %s)
+  call void %fp() [ "kcfi"(i32 12345678) ]
+  %w = call <32 x i32> @llvm.hexagon.V6.vmpyiwb.128B(<32 x i32> %v, i32 %s)
+  ret <32 x i32> %w
+}
+
+;; A multiply-accumulate has a read-modify-write operand the packetizer tracks
+;; separately; keep one live across the check.
+define i32 @mpy_accumulate(ptr noundef %fp, i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: <mpy_accumulate>:
+; CHECK:        r{{[0-9]+}} += mpy(r{{[0-9]+}}.l,r{{[0-9]+}}.l):sat
+; CHECK:        immext(#
+; CHECK-NEXT:   r{{[0-9]+}} = ##0x4d2
+; CHECK:        immext(#0xbadc0fc0)
+; CHECK-NEXT:   r{{[0-9]+}}:{{[0-9]+}} = memd(##0xbadc0fee)
+  %m = call i32 @llvm.hexagon.M2.mpy.acc.sat.ll.s0(i32 %a, i32 %b, i32 %c)
+  call void %fp() [ "kcfi"(i32 1234) ]
+  %n = call i32 @llvm.hexagon.A2.abs(i32 %m)
+  ret i32 %n
+}
+
+;; dcfetch sits immediately before the check, and stays out of its packets.
+define void @prefetch_before(ptr noundef %fp, ptr %p) {
+; CHECK-LABEL: <prefetch_before>:
+; CHECK:        { dcfetch(r{{[0-9]+}}{{.*}}) }
+; CHECK-NEXT:   { immext(#
+; CHECK-NEXT:     r{{[0-9]+}} = ##0x63
+; CHECK-NEXT:     r{{[0-9]+}} = memw(r0+#-0x4) }
+; CHECK-NEXT:   { p0 = cmp.eq(r{{[0-9]+}},r{{[0-9]+}})
+; CHECK:          if (p0.new) jump:t {{.*}} }
+; CHECK-NEXT:   { immext(#0xbadc0fc0)
+; CHECK-NEXT:     r{{[0-9]+}}:{{[0-9]+}} = memd(##0xbadc0fee) }
+; CHECK-NEXT:   { callr r0 }
+  call void @llvm.prefetch.p0(ptr %p, i32 0, i32 3, i32 1)
+  call void %fp() [ "kcfi"(i32 99) ]
+  ret void
+}
+
+;; Six arguments fill r0-r5, forcing the lowering off its r6/r7 scratch pair.
+;; r8 cannot be encoded in the compare-jump compound, so the compare and jump
+;; stay two instructions.  Registers are spelled out and CHECK-NEXT used so the
+;; compounded form cannot match instead.
+define void @scratch_fallback(ptr noundef %fp, i32 %a, i32 %b, i32 %c,
+                              i32 %d, i32 %e, i32 %f) {
+; CHECK-LABEL: <scratch_fallback>:
+; CHECK:        { immext(#
+; CHECK-NEXT:     r7 = ##0xbc614e
+; CHECK-NEXT:     r8 = memw(r6+#-0x4) }
+; CHECK-NEXT:   { p0 = cmp.eq(r8,r7)
+; CHECK-NEXT:     if (p0.new) jump:t {{.*}} }
+; CHECK-NEXT:   { immext(#0xbadc0fc0)
+; CHECK-NEXT:     r{{[0-9]+}}:{{[0-9]+}} = memd(##0xbadc0fee) }
+; CHECK-NEXT:   { callr r6 }
+  call void %fp(i32 %a, i32 %b, i32 %c, i32 %d, i32 %e, i32 %f)
+      [ "kcfi"(i32 12345678) ]
+  ret void
+}
+
+;; The check inside a loop body, sharing the block with the loop's own
+;; compare-and-branch -- both want the predicate registers and slot 2/3.
+define void @loop_body(ptr noundef %fp, i32 %n) {
+; CHECK-LABEL: <loop_body>:
+; CHECK:        immext(#0xbadc0fc0)
+; CHECK-NEXT:   r{{[0-9]+}}:{{[0-9]+}} = memd(##0xbadc0fee)
+; CHECK:        callr
+entry:
+  br label %loop
+loop:
+  %i = phi i32 [ 0, %entry ], [ %inc, %loop ]
+  call void %fp() [ "kcfi"(i32 12345678) ]
+  %inc = add i32 %i, 1
+  %cmp = icmp slt i32 %inc, %n
+  br i1 %cmp, label %loop, label %exit
+exit:
+  ret void
+}
+
+;; Back-to-back indirect calls: three checks with nothing between them, so any
+;; state carried between packets shows up here.
+define void @back_to_back(ptr noundef %f, ptr noundef %g, ptr noundef %h) {
+; CHECK-LABEL: <back_to_back>:
+; CHECK:        immext(#0xbadc0fc0)
+; CHECK-NEXT:   r{{[0-9]+}}:{{[0-9]+}} = memd(##0xbadc0fee)
+; CHECK:        immext(#0xbadc0fc0)
+; CHECK-NEXT:   r{{[0-9]+}}:{{[0-9]+}} = memd(##0xbadc0fee)
+; CHECK:        immext(#0xbadc0fc0)
+; CHECK-NEXT:   r{{[0-9]+}}:{{[0-9]+}} = memd(##0xbadc0fee)
+  call void %f() [ "kcfi"(i32 1) ]
+  call void %g() [ "kcfi"(i32 2) ]
+  call void %h() [ "kcfi"(i32 3) ]
+  ret void
+}
+
+;; The target is loaded from memory immediately before the check, so the
+;; address register is defined in the packet right before the check's load.
+define void @target_from_memory(ptr noundef %slot) {
+; CHECK-LABEL: <target_from_memory>:
+; CHECK:        r{{[0-9]+}} = memw(r0
+; CHECK:        immext(#0xbadc0fc0)
+; CHECK-NEXT:   r{{[0-9]+}}:{{[0-9]+}} = memd(##0xbadc0fee)
+  %fp = load ptr, ptr %slot
+  call void %fp() [ "kcfi"(i32 12345678) ]
+  ret void
+}
+
+;; Conditional call: only one arm is checked, so the check's own branch has to
+;; coexist with the surrounding control flow.
+define void @conditional(ptr noundef %fp, i1 %c) {
+; CHECK-LABEL: <conditional>:
+; CHECK:        immext(#0xbadc0fc0)
+; CHECK-NEXT:   r{{[0-9]+}}:{{[0-9]+}} = memd(##0xbadc0fee)
+entry:
+  br i1 %c, label %do, label %skip
+do:
+  call void %fp() [ "kcfi"(i32 12345678) ]
+  br label %skip
+skip:
+  ret void
+}
+
+!llvm.module.flags = !{!0}
+!0 = !{i32 4, !"kcfi", i32 1}
diff --git a/llvm/test/CodeGen/Hexagon/kcfi-packetization.ll b/llvm/test/CodeGen/Hexagon/kcfi-packetization.ll
new file mode 100644
index 0000000000000..c0bb2d3d87ab6
--- /dev/null
+++ b/llvm/test/CodeGen/Hexagon/kcfi-packetization.ll
@@ -0,0 +1,113 @@
+;; Object-level checks of the KCFI check sequence, built by hand in
+;; HexagonAsmPrinter::LowerKCFI_CHECK().
+;;
+;; -filetype=obj, not asm: the assembler re-inserts extenders when it parses
+;; the text back in, so a missing one is invisible in -S output.  A truncated
+;; hash makes every passing check trap; a truncated 0xBADC0FEE assembles as a
+;; well-formed GP-relative load that does not fault at all.
+;;
+;; The { } delimiters are checked, not decoration: an extender only applies to
+;; the instruction after it in slot order, so the grouping and order are the
+;; property at issue.
+
+; RUN: llc -mtriple=hexagon -filetype=obj < %s | llvm-objdump -d --no-show-raw-insn - | FileCheck %s
+; RUN: llc -mtriple=hexagon -filetype=obj -mno-compound < %s | llvm-objdump -d --no-show-raw-insn - | FileCheck %s
+; RUN: llc -mtriple=hexagon -filetype=obj -mno-pairing < %s | llvm-objdump -d --no-show-raw-insn - | FileCheck %s
+; RUN: llc -mtriple=hexagon -filetype=obj -mattr=-packets < %s | llvm-objdump -d --no-show-raw-insn - | FileCheck %s
+
+;; The Hexagon Linux kernel builds with --disable-packetizer as a workaround
+;; for an unrelated backend issue, so that combination has to keep working.
+; RUN: llc -mtriple=hexagon -filetype=obj --disable-packetizer < %s | llvm-objdump -d --no-show-raw-insn - | FileCheck %s
+
+;; One trap-table entry per check and no others; a missing entry leaves a
+;; trapping check looking like an ordinary misaligned access.  Checked here
+;; because objcopy will not extract a section whose relocations point at .text.
+; RUN: llc -mtriple=hexagon -filetype=obj < %s -o %t.o
+; RUN: llvm-readobj -r %t.o | FileCheck %s --check-prefix=TRAPS
+; TRAPS:         .rela.kcfi_traps {
+; TRAPS-COUNT-6:   R_HEX_32_PCREL .text
+; TRAPS-NEXT:    }
+
+;; ...and across architecture versions, which differ in compound/duplex support.
+; RUN: llc -mtriple=hexagon -mcpu=hexagonv62 -filetype=obj < %s | llvm-objdump -d --no-show-raw-insn - | FileCheck %s
+; RUN: llc -mtriple=hexagon -mcpu=hexagonv68 -filetype=obj < %s | llvm-objdump -d --no-show-raw-insn - | FileCheck %s
+; RUN: llc -mtriple=hexagon -mcpu=hexagonv73 -filetype=obj < %s | llvm-objdump -d --no-show-raw-insn - | FileCheck %s
+; RUN: llc -mtriple=hexagon -mcpu=hexagonv79 -filetype=obj < %s | llvm-objdump -d --no-show-raw-insn - | FileCheck %s
+
+;; A hash that needs a constant extender (0xBC614E does not fit an s16).
+define void @big_hash(ptr noundef %fp) {
+; CHECK-LABEL: <big_hash>:
+; CHECK:        { immext(#
+; CHECK-NEXT:     r{{[0-9]+}} = ##0xbc614e
+; CHECK-NEXT:     r{{[0-9]+}} = memw(r0+#-0x4) }
+;; One packet whether or not the two compound, so do not pin the jump's line.
+; CHECK-NEXT:   { p0 = cmp.eq(r{{[0-9]+}},r{{[0-9]+}})
+; CHECK:          if (p0.new) jump:t {{.*}} }
+; CHECK-NEXT:   { immext(#0xbadc0fc0)
+; CHECK-NEXT:     r{{[0-9]+}}:{{[0-9]+}} = memd(##0xbadc0fee) }
+; CHECK-NEXT:   { callr r0 }
+  call void %fp() [ "kcfi"(i32 12345678) ]
+  ret void
+}
+
+;; A small hash is extended too, so the compare sees the full 32 bits.
+define void @small_hash(ptr noundef %fp) {
+; CHECK-LABEL: <small_hash>:
+; CHECK:        { immext(#
+; CHECK-NEXT:     r{{[0-9]+}} = ##0x4d2
+; CHECK-NEXT:     r{{[0-9]+}} = memw(r0+#-0x4) }
+; CHECK:        { immext(#0xbadc0fc0)
+; CHECK-NEXT:     r{{[0-9]+}}:{{[0-9]+}} = memd(##0xbadc0fee) }
+  call void %fp() [ "kcfi"(i32 1234) ]
+  ret void
+}
+
+;; Two checks in one function: each needs its own extenders and trap entry.
+define void @two_checks(ptr noundef %f, ptr noundef %g) {
+; CHECK-LABEL: <two_checks>:
+; CHECK:        { immext(#
+; CHECK-NEXT:     r{{[0-9]+}} = ##0xbc614e
+; CHECK-NEXT:     r{{[0-9]+}} = memw({{r[0-9]+}}+#-0x4) }
+; CHECK:        { immext(#0xbadc0fc0)
+; CHECK-NEXT:     r{{[0-9]+}}:{{[0-9]+}} = memd(##0xbadc0fee) }
+; CHECK:        { immext(#
+; CHECK-NEXT:     r{{[0-9]+}} = ##0x4d2
+; CHECK-NEXT:     r{{[0-9]+}} = memw({{r[0-9]+}}+#-0x4) }
+; CHECK:        { immext(#0xbadc0fc0)
+; CHECK-NEXT:     r{{[0-9]+}}:{{[0-9]+}} = memd(##0xbadc0fee) }
+  call void %f() [ "kcfi"(i32 12345678) ]
+  call void %g() [ "kcfi"(i32 1234) ]
+  ret void
+}
+
+;; patchable-function-prefix moves the hash back; the offset still fits.
+define void @prefixed(ptr noundef %fp) #0 {
+; CHECK-LABEL: <prefixed>:
+; CHECK:        { immext(#
+; CHECK-NEXT:     r{{[0-9]+}} = ##0xbc614e
+; CHECK-NEXT:     r{{[0-9]+}} = memw(r0+#-0xc) }
+; CHECK:        { immext(#0xbadc0fc0)
+; CHECK-NEXT:     r{{[0-9]+}}:{{[0-9]+}} = memd(##0xbadc0fee) }
+  call void %fp() [ "kcfi"(i32 12345678) ]
+  ret void
+}
+
+;; A prefix big enough to push the offset out of the 13-bit field (-4404), so
+;; the load needs an extender too -- four instructions, exactly a full packet.
+define void @big_prefix(ptr noundef %fp) #1 {
+; CHECK-LABEL: <big_prefix>:
+; CHECK:        { immext(#
+; CHECK-NEXT:     r{{[0-9]+}} = ##0xbc614e
+; CHECK-NEXT:     immext(#
+; CHECK-NEXT:     r{{[0-9]+}} = memw(r0+##-0x1134) }
+; CHECK:        { immext(#0xbadc0fc0)
+; CHECK-NEXT:     r{{[0-9]+}}:{{[0-9]+}} = memd(##0xbadc0fee) }
+  call void %fp() [ "kcfi"(i32 12345678) ]
+  ret void
+}
+
+attributes #0 = { "patchable-function-prefix"="2" }
+attributes #1 = { "patchable-function-prefix"="1100" }
+
+!llvm.module.flags = !{!0}
+!0 = !{i32 4, !"kcfi", i32 1}
diff --git a/llvm/test/CodeGen/Hexagon/kcfi.ll b/llvm/test/CodeGen/Hexagon/kcfi.ll
index 7fd2ee4a9e16d..18b9455149250 100644
--- a/llvm/test/CodeGen/Hexagon/kcfi.ll
+++ b/llvm/test/CodeGen/Hexagon/kcfi.ll
@@ -9,15 +9,18 @@
 ; 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
+; Braces are matched so the grouping is pinned, not just the instructions.
+; Extender placement is invisible here -- `##imm` prints the same either way --
+; so it is checked at the object level in kcfi-packetization.ll.
+; ASM:       r{{[0-9]+}} = ##12345678
+; ASM-NEXT:  r{{[0-9]+}} = memw(r0+#-4)
 ; ASM-NEXT:  }
 ; ASM-NEXT:  {
-; ASM-NEXT:    p0 = cmp.eq(r{{[0-9]+}},r{{[0-9]+}})
-; ASM-NEXT:    if (p0.new) jump:t
+; ASM-NEXT:    p0 = cmp.eq(r{{[0-9]+}},r{{[0-9]+}}); if (p0.new) jump:t
+; ASM-NEXT:  }
+; ASM:       {
+; ASM-NEXT:    r{{[0-9]+}}:{{[0-9]+}} = memd(##3134984174)
 ; ASM-NEXT:  }
-; ASM:       r{{[0-9]+}}:{{[0-9]+}} = memd(##3134984174)
 
 ; After ISel, the call should carry a cfi-type.
 ; ISEL-LABEL: name: f1
@@ -37,12 +40,11 @@ define void @f1(ptr noundef %x) !kcfi_type !1 {
 ; 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-NEXT:  r{{[0-9]+}} = ##1234
+; ASM:       r{{[0-9]+}} = ##1234
+; ASM-NEXT:  r{{[0-9]+}} = memw(r0+#-4)
 ; ASM-NEXT:  }
 ; ASM-NEXT:  {
-; ASM-NEXT:    p0 = cmp.eq(r{{[0-9]+}},r{{[0-9]+}})
-; ASM-NEXT:    if (p0.new) jump:t
+; ASM-NEXT:    p0 = cmp.eq(r{{[0-9]+}},r{{[0-9]+}}); if (p0.new) jump:t
 ; ASM-NEXT:  }
 ; ASM:       r{{[0-9]+}}:{{[0-9]+}} = memd(##3134984174)
 
@@ -56,12 +58,11 @@ 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:       r{{[0-9]+}} = ##12345678
+; ASM-NEXT:  r{{[0-9]+}} = memw(r0+#-4)
 ; ASM-NEXT:  }
 ; ASM-NEXT:  {
-; ASM-NEXT:    p0 = cmp.eq(r{{[0-9]+}},r{{[0-9]+}})
-; ASM-NEXT:    if (p0.new) jump:t
+; ASM-NEXT:    p0 = cmp.eq(r{{[0-9]+}},r{{[0-9]+}}); if (p0.new) jump:t
 ; ASM-NEXT:  }
 ; ASM:       r{{[0-9]+}}:{{[0-9]+}} = memd(##3134984174)
 
@@ -74,12 +75,11 @@ define void @f3(ptr noundef %x) #0 {
 ;; -(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:       r{{[0-9]+}} = ##12345678
+; ASM-NEXT:  r{{[0-9]+}} = memw(r0+#-12)
 ; ASM-NEXT:  }
 ; ASM-NEXT:  {
-; ASM-NEXT:    p0 = cmp.eq(r{{[0-9]+}},r{{[0-9]+}})
-; ASM-NEXT:    if (p0.new) jump:t
+; ASM-NEXT:    p0 = cmp.eq(r{{[0-9]+}},r{{[0-9]+}}); if (p0.new) jump:t
 ; ASM-NEXT:  }
 ; ASM:       r{{[0-9]+}}:{{[0-9]+}} = memd(##3134984174)
 
@@ -90,12 +90,11 @@ define void @f4_prefix(ptr noundef %x) #1 !kcfi_type !1 {
 ;; 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:       r{{[0-9]+}} = ##12345678
+; ASM-NEXT:  r{{[0-9]+}} = memw(r0+#-16)
 ; ASM-NEXT:  }
 ; ASM-NEXT:  {
-; ASM-NEXT:    p0 = cmp.eq(r{{[0-9]+}},r{{[0-9]+}})
-; ASM-NEXT:    if (p0.new) jump:t
+; ASM-NEXT:    p0 = cmp.eq(r{{[0-9]+}},r{{[0-9]+}}); if (p0.new) jump:t
 ; ASM-NEXT:  }
 ; ASM:       r{{[0-9]+}}:{{[0-9]+}} = memd(##3134984174)
 
@@ -108,10 +107,12 @@ define void @f5_prefix3(ptr noundef %x) #2 !kcfi_type !1 {
 ;; must use R8 instead.
 define void @f6_target_r6() {
 ; ASM-LABEL: f6_target_r6:
-; ASM:       r8 = memw(r6+#-4)
-; ASM-NEXT:  r7 = ##12345678
+; ASM:       r7 = ##12345678
+; ASM-NEXT:  r8 = memw(r6+#-4)
 ; ASM-NEXT:  }
 ; ASM-NEXT:  {
+;; r8 is outside the register range the compare-jump compound can
+;; encode, so these two stay separate instructions in the packet.
 ; ASM-NEXT:    p0 = cmp.eq(r8,r7)
 ; ASM-NEXT:    if (p0.new) jump:t
 ; ASM-NEXT:  }
@@ -132,10 +133,12 @@ define void @f6_target_r6() {
 ;; must use R8 instead.
 define void @f7_target_r7() {
 ; ASM-LABEL: f7_target_r7:
-; ASM:       r6 = memw(r7+#-4)
-; ASM-NEXT:  r8 = ##12345678
+; ASM:       r8 = ##12345678
+; ASM-NEXT:  r6 = memw(r7+#-4)
 ; ASM-NEXT:  }
 ; ASM-NEXT:  {
+;; r8 is outside the register range the compare-jump compound can
+;; encode, so these two stay separate instructions in the packet.
 ; ASM-NEXT:    p0 = cmp.eq(r6,r8)
 ; ASM-NEXT:    if (p0.new) jump:t
 ; ASM-NEXT:  }
@@ -155,12 +158,11 @@ define void @f7_target_r7() {
 ;; 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:       r{{[0-9]+}} = ##12345678
+; ASM-NEXT:  r{{[0-9]+}} = memw(r0+#-4)
 ; ASM-NEXT:  }
 ; ASM-NEXT:  {
-; ASM-NEXT:    p0 = cmp.eq(r{{[0-9]+}},r{{[0-9]+}})
-; ASM-NEXT:    if (p0.new) jump:t
+; ASM-NEXT:    p0 = cmp.eq(r{{[0-9]+}},r{{[0-9]+}}); if (p0.new) jump:t
 ; ASM-NEXT:  }
 ; ASM:       r{{[0-9]+}}:{{[0-9]+}} = memd(##3134984174)
 



More information about the llvm-branch-commits mailing list