https://github.com/dyung updated https://github.com/llvm/llvm-project/pull/215633
>From 4e60bd7a63b0d1a8407efb1702b53faec44e2980 Mon Sep 17 00:00:00 2001 From: Brian Cain <[email protected]> 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) _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
