[llvm] [Hexagon] Add HVX caller-save remark pass for call-site diagnostics (PR #189188)
Brian Cain via llvm-commits
llvm-commits at lists.llvm.org
Wed Apr 29 07:13:07 PDT 2026
https://github.com/androm3da updated https://github.com/llvm/llvm-project/pull/189188
>From 19f946f91c08625d6dec01e924fd8268f1f37041 Mon Sep 17 00:00:00 2001
From: Brian Cain <brian.cain at oss.qualcomm.com>
Date: Sat, 28 Mar 2026 10:53:01 -0700
Subject: [PATCH 1/3] [Hexagon] Add HVX caller-save remark pass for call-site
diagnostics
Add a new MachineFunctionPass (HexagonHVXSaveRemark) that emits
optimization analysis remarks when HVX vector registers must be
saved and restored around function calls. All HVX registers are
caller-saved (Section 5.3 of the Hexagon ABI), so any HVX value
live across a call requires a save/restore pair on the stack.
Each HVX vector is 64 or 128 bytes, making this overhead expensive.
The pass exits when remarks are not requested (-Rpass-analysis=hexagon-hvx-save)
or when HVX is not enabled. A byte threshold (default 1024, tunable via
-hexagon-hvx-save-threshold) filters out functions with only a
small number of saves. The remarks help programmers identify call
sites where inlining, hoisting, or sinking could reduce the
save/restore cost.
---
llvm/lib/Target/Hexagon/CMakeLists.txt | 1 +
llvm/lib/Target/Hexagon/Hexagon.h | 2 +
.../Target/Hexagon/HexagonHVXSaveRemark.cpp | 116 ++++++++++++++++++
.../Target/Hexagon/HexagonTargetMachine.cpp | 3 +
llvm/test/CodeGen/Hexagon/hvx-save-remarks.ll | 46 +++++++
5 files changed, 168 insertions(+)
create mode 100644 llvm/lib/Target/Hexagon/HexagonHVXSaveRemark.cpp
create mode 100644 llvm/test/CodeGen/Hexagon/hvx-save-remarks.ll
diff --git a/llvm/lib/Target/Hexagon/CMakeLists.txt b/llvm/lib/Target/Hexagon/CMakeLists.txt
index b4716693c3dc9..872820ca26068 100644
--- a/llvm/lib/Target/Hexagon/CMakeLists.txt
+++ b/llvm/lib/Target/Hexagon/CMakeLists.txt
@@ -42,6 +42,7 @@ add_llvm_target(HexagonCodeGen
HexagonGlobalScheduler.cpp
HexagonLiveVariables.cpp
HexagonHardwareLoops.cpp
+ HexagonHVXSaveRemark.cpp
HexagonHazardRecognizer.cpp
HexagonInstrInfo.cpp
HexagonISelDAGToDAG.cpp
diff --git a/llvm/lib/Target/Hexagon/Hexagon.h b/llvm/lib/Target/Hexagon/Hexagon.h
index 1db2326b274dc..8ecd3520fc660 100644
--- a/llvm/lib/Target/Hexagon/Hexagon.h
+++ b/llvm/lib/Target/Hexagon/Hexagon.h
@@ -43,6 +43,7 @@ void initializeHexagonGenMemAbsolutePass(PassRegistry &);
void initializeHexagonGenMuxPass(PassRegistry &);
void initializeHexagonGlobalSchedulerPass(PassRegistry &);
void initializeHexagonHardwareLoopsPass(PassRegistry &);
+void initializeHexagonHVXSaveRemarkPass(PassRegistry &);
void initializeHexagonLiveVariablesPass(PassRegistry &);
void initializeHexagonLoopIdiomRecognizeLegacyPassPass(PassRegistry &);
void initializeHexagonLoopAlignPass(PassRegistry &);
@@ -97,6 +98,7 @@ FunctionPass *createHexagonGenMux();
FunctionPass *createHexagonGenPredicate();
FunctionPass *createHexagonGlobalScheduler();
FunctionPass *createHexagonHardwareLoops();
+FunctionPass *createHexagonHVXSaveRemark();
FunctionPass *createHexagonISelDag(HexagonTargetMachine &TM,
CodeGenOptLevel OptLevel);
FunctionPass *createHexagonLoopAlign();
diff --git a/llvm/lib/Target/Hexagon/HexagonHVXSaveRemark.cpp b/llvm/lib/Target/Hexagon/HexagonHVXSaveRemark.cpp
new file mode 100644
index 0000000000000..2631d4a05c16a
--- /dev/null
+++ b/llvm/lib/Target/Hexagon/HexagonHVXSaveRemark.cpp
@@ -0,0 +1,116 @@
+//===- HexagonHVXSaveRemark.cpp - Remark on HVX saves around calls --------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Diagnostic pass that emits optimization remarks when HVX vector registers
+// must be saved and restored around function calls. All HVX registers are
+// caller-saved (Section 5.3 of the Hexagon ABI), so every HVX value that is
+// live across a call requires a save/restore pair on the stack. Each HVX
+// vector is 64 or 128 bytes (depending on the mode), making this overhead
+// expensive. The remarks help programmers identify call sites where inlining,
+// hoisting, or sinking the call could reduce the save/restore cost.
+//
+//===----------------------------------------------------------------------===//
+
+#include "HexagonSubtarget.h"
+#include "llvm/Analysis/OptimizationRemarkEmitter.h"
+#include "llvm/CodeGen/MachineFrameInfo.h"
+#include "llvm/CodeGen/MachineFunction.h"
+#include "llvm/CodeGen/MachineFunctionPass.h"
+#include "llvm/CodeGen/MachineOptimizationRemarkEmitter.h"
+#include "llvm/InitializePasses.h"
+#include "llvm/Pass.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/Debug.h"
+
+using namespace llvm;
+
+#define DEBUG_TYPE "hexagon-hvx-save"
+
+static cl::opt<unsigned> HVXSaveThreshold(
+ "hexagon-hvx-save-threshold", cl::Hidden, cl::init(1024),
+ cl::desc("Minimum bytes of HVX caller-saves to trigger a remark"));
+
+namespace {
+
+struct HexagonHVXSaveRemark : public MachineFunctionPass {
+ static char ID;
+
+ HexagonHVXSaveRemark() : MachineFunctionPass(ID) {}
+
+ bool runOnMachineFunction(MachineFunction &MF) override {
+ MachineOptimizationRemarkEmitter MORE(MF, nullptr);
+ if (!MORE.allowExtraAnalysis(DEBUG_TYPE))
+ return false;
+
+ const HexagonSubtarget &HST = MF.getSubtarget<HexagonSubtarget>();
+ if (!HST.useHVXOps())
+ return false;
+
+ // Identify HVX caller-save slots by matching stack-slot sizes against
+ // the HVX vector length (single vectors and vector pairs).
+ const MachineFrameInfo &MFI = MF.getFrameInfo();
+ unsigned HVXLen = HST.getVectorLength();
+ unsigned NumVecSaves = 0;
+ unsigned NumPairSaves = 0;
+
+ for (int I = MFI.getObjectIndexBegin(), E = MFI.getObjectIndexEnd(); I < E;
+ ++I) {
+ if (!MFI.isSpillSlotObjectIndex(I))
+ continue;
+ int64_t Size = MFI.getObjectSize(I);
+ if (Size == (int64_t)HVXLen)
+ ++NumVecSaves;
+ else if (Size == (int64_t)(2 * HVXLen))
+ ++NumPairSaves;
+ }
+
+ unsigned TotalSaves = NumVecSaves + NumPairSaves;
+ unsigned TotalBytes = NumVecSaves * HVXLen + NumPairSaves * 2 * HVXLen;
+
+ if (TotalBytes < HVXSaveThreshold)
+ return false;
+
+ // Emit a remark on each call site.
+ for (const MachineBasicBlock &MBB : MF) {
+ for (const MachineInstr &MI : MBB) {
+ if (!MI.isCall())
+ continue;
+
+ MORE.emit([&]() {
+ using namespace ore;
+ MachineOptimizationRemarkAnalysis R(DEBUG_TYPE, "HVXSaveAroundCall",
+ MI.getDebugLoc(), &MBB);
+ R << NV("NumSaves", TotalSaves) << " HVX caller-saved register(s) ("
+ << NV("TotalBytes", TotalBytes)
+ << " bytes) saved and restored around call";
+ return R;
+ });
+ }
+ }
+
+ return false;
+ }
+
+ StringRef getPassName() const override { return "Hexagon HVX Save Remarks"; }
+
+ void getAnalysisUsage(AnalysisUsage &AU) const override {
+ AU.setPreservesAll();
+ MachineFunctionPass::getAnalysisUsage(AU);
+ }
+};
+
+char HexagonHVXSaveRemark::ID = 0;
+
+} // end anonymous namespace
+
+INITIALIZE_PASS(HexagonHVXSaveRemark, DEBUG_TYPE, "Hexagon HVX Save Remarks",
+ false, false)
+
+FunctionPass *llvm::createHexagonHVXSaveRemark() {
+ return new HexagonHVXSaveRemark();
+}
diff --git a/llvm/lib/Target/Hexagon/HexagonTargetMachine.cpp b/llvm/lib/Target/Hexagon/HexagonTargetMachine.cpp
index e1af6ec8bb116..ca4bccc482a89 100644
--- a/llvm/lib/Target/Hexagon/HexagonTargetMachine.cpp
+++ b/llvm/lib/Target/Hexagon/HexagonTargetMachine.cpp
@@ -198,6 +198,7 @@ LLVMInitializeHexagonTarget() {
initializeHexagonGlobalSchedulerPass(PR);
initializeHexagonLiveVariablesPass(PR);
initializeHexagonHardwareLoopsPass(PR);
+ initializeHexagonHVXSaveRemarkPass(PR);
initializeHexagonLoopIdiomRecognizeLegacyPassPass(PR);
initializeHexagonNewValueJumpPass(PR);
initializeHexagonOptAddrModePass(PR);
@@ -490,6 +491,8 @@ void HexagonPassConfig::addPreEmitPass() {
if (EnableVectorPrint)
addPass(createHexagonVectorPrint());
+ addPass(createHexagonHVXSaveRemark());
+
// Add CFI instructions if necessary.
addPass(createHexagonCallFrameInformation());
}
diff --git a/llvm/test/CodeGen/Hexagon/hvx-save-remarks.ll b/llvm/test/CodeGen/Hexagon/hvx-save-remarks.ll
new file mode 100644
index 0000000000000..572625c4c5d31
--- /dev/null
+++ b/llvm/test/CodeGen/Hexagon/hvx-save-remarks.ll
@@ -0,0 +1,46 @@
+; RUN: llc -mtriple=hexagon -mcpu=hexagonv68 -mattr=+hvxv68,+hvx-length128b \
+; RUN: -pass-remarks-analysis=hexagon-hvx-save %s -o /dev/null 2>&1 \
+; RUN: | FileCheck %s
+
+;; Test that the HVX save remark pass reports caller-save costs around calls.
+;; All HVX registers are caller-saved, so any HVX value live across a call
+;; requires a save/restore pair on the stack. The default threshold is 1024
+;; bytes, so we need at least 8 x 128-byte vectors live across the call.
+
+; CHECK: remark: {{.*}} HVX caller-saved register(s) ({{[0-9]+}} bytes) saved and restored around call
+; CHECK-NOT: remark:
+
+declare void @bar()
+
+;; 8 HVX vectors live across a call (8 x 128 = 1024 bytes) -- meets threshold.
+define void @test_hvx_save_around_call(ptr %p0, ptr %p1, ptr %p2, ptr %p3,
+ ptr %p4, ptr %p5, ptr %p6, ptr %p7) {
+entry:
+ %v0 = load <32 x i32>, ptr %p0, align 128
+ %v1 = load <32 x i32>, ptr %p1, align 128
+ %v2 = load <32 x i32>, ptr %p2, align 128
+ %v3 = load <32 x i32>, ptr %p3, align 128
+ %v4 = load <32 x i32>, ptr %p4, align 128
+ %v5 = load <32 x i32>, ptr %p5, align 128
+ %v6 = load <32 x i32>, ptr %p6, align 128
+ %v7 = load <32 x i32>, ptr %p7, align 128
+ call void @bar()
+ store <32 x i32> %v0, ptr %p0, align 128
+ store <32 x i32> %v1, ptr %p1, align 128
+ store <32 x i32> %v2, ptr %p2, align 128
+ store <32 x i32> %v3, ptr %p3, align 128
+ store <32 x i32> %v4, ptr %p4, align 128
+ store <32 x i32> %v5, ptr %p5, align 128
+ store <32 x i32> %v6, ptr %p6, align 128
+ store <32 x i32> %v7, ptr %p7, align 128
+ ret void
+}
+
+;; Single HVX vector live across call (128 bytes) -- below threshold.
+define void @test_below_threshold(ptr %p) {
+entry:
+ %v = load <32 x i32>, ptr %p, align 128
+ call void @bar()
+ store <32 x i32> %v, ptr %p, align 128
+ ret void
+}
>From 6606af5bede0d0375b1e635268e3c36b582c54f0 Mon Sep 17 00:00:00 2001
From: Brian Cain <brian.cain at oss.qualcomm.com>
Date: Tue, 28 Apr 2026 21:03:15 -0700
Subject: [PATCH 2/3] fixup! [Hexagon] Add HVX caller-save remark pass for
call-site diagnostics
Use getAnalysis<MachineOptimizationRemarkEmitterPass> for MORE in
HexagonHVXSaveRemark instead of constructing a stack-local object.
---
llvm/lib/Target/Hexagon/HexagonHVXSaveRemark.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/llvm/lib/Target/Hexagon/HexagonHVXSaveRemark.cpp b/llvm/lib/Target/Hexagon/HexagonHVXSaveRemark.cpp
index 2631d4a05c16a..46697fb8bd568 100644
--- a/llvm/lib/Target/Hexagon/HexagonHVXSaveRemark.cpp
+++ b/llvm/lib/Target/Hexagon/HexagonHVXSaveRemark.cpp
@@ -43,7 +43,7 @@ struct HexagonHVXSaveRemark : public MachineFunctionPass {
HexagonHVXSaveRemark() : MachineFunctionPass(ID) {}
bool runOnMachineFunction(MachineFunction &MF) override {
- MachineOptimizationRemarkEmitter MORE(MF, nullptr);
+ auto &MORE = getAnalysis<MachineOptimizationRemarkEmitterPass>().getORE();
if (!MORE.allowExtraAnalysis(DEBUG_TYPE))
return false;
@@ -99,6 +99,7 @@ struct HexagonHVXSaveRemark : public MachineFunctionPass {
StringRef getPassName() const override { return "Hexagon HVX Save Remarks"; }
void getAnalysisUsage(AnalysisUsage &AU) const override {
+ AU.addRequired<MachineOptimizationRemarkEmitterPass>();
AU.setPreservesAll();
MachineFunctionPass::getAnalysisUsage(AU);
}
>From 13aee2895c29b918d92bc18b018441a2d739b2b1 Mon Sep 17 00:00:00 2001
From: Brian Cain <brian.cain at oss.qualcomm.com>
Date: Wed, 29 Apr 2026 06:23:53 -0700
Subject: [PATCH 3/3] fixup! [Hexagon] Add HVX caller-save remark pass for
call-site diagnostics
Address review feedback:
- Change threshold from bytes (1024) to register count (8) so it works
sensibly in both 64B and 128B HVX modes.
- Rewrite pass to report per-call-site information by counting HVX
spill stores adjacent to each call, rather than attributing all
function-wide spill slots to every call site.
- Use exact counts in the test instead of regex patterns.
---
.../Target/Hexagon/HexagonHVXSaveRemark.cpp | 102 +++++++++++-------
llvm/test/CodeGen/Hexagon/hvx-save-remarks.ll | 6 +-
2 files changed, 68 insertions(+), 40 deletions(-)
diff --git a/llvm/lib/Target/Hexagon/HexagonHVXSaveRemark.cpp b/llvm/lib/Target/Hexagon/HexagonHVXSaveRemark.cpp
index 46697fb8bd568..9eb5e793dc39e 100644
--- a/llvm/lib/Target/Hexagon/HexagonHVXSaveRemark.cpp
+++ b/llvm/lib/Target/Hexagon/HexagonHVXSaveRemark.cpp
@@ -18,10 +18,10 @@
#include "HexagonSubtarget.h"
#include "llvm/Analysis/OptimizationRemarkEmitter.h"
-#include "llvm/CodeGen/MachineFrameInfo.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/MachineFunctionPass.h"
#include "llvm/CodeGen/MachineOptimizationRemarkEmitter.h"
+#include "llvm/CodeGen/PseudoSourceValue.h"
#include "llvm/InitializePasses.h"
#include "llvm/Pass.h"
#include "llvm/Support/CommandLine.h"
@@ -32,8 +32,9 @@ using namespace llvm;
#define DEBUG_TYPE "hexagon-hvx-save"
static cl::opt<unsigned> HVXSaveThreshold(
- "hexagon-hvx-save-threshold", cl::Hidden, cl::init(1024),
- cl::desc("Minimum bytes of HVX caller-saves to trigger a remark"));
+ "hexagon-hvx-save-threshold", cl::Hidden, cl::init(8),
+ cl::desc("Minimum number of HVX caller-saved registers live across a call "
+ "to trigger a remark"));
namespace {
@@ -42,6 +43,34 @@ struct HexagonHVXSaveRemark : public MachineFunctionPass {
HexagonHVXSaveRemark() : MachineFunctionPass(ID) {}
+ /// Return true if MI is an HVX vector spill to a stack slot.
+ static bool isHVXSpill(const MachineInstr &MI, unsigned HVXLen) {
+ if (MI.mayStore() && MI.hasOneMemOperand()) {
+ const MachineMemOperand *MMO = *MI.memoperands_begin();
+ if (MMO->getSize().hasValue() && MMO->getPseudoValue() &&
+ isa<FixedStackPseudoSourceValue>(MMO->getPseudoValue()))
+ return MMO->getSize().getValue() == HVXLen ||
+ MMO->getSize().getValue() == 2 * HVXLen;
+ }
+ return false;
+ }
+
+ /// Count HVX spills in a bundle or single instruction that contains a call.
+ static unsigned countSpillsInBundle(const MachineInstr &BundleOrMI,
+ unsigned HVXLen) {
+ unsigned Count = 0;
+ if (BundleOrMI.isBundle()) {
+ auto MII = BundleOrMI.getIterator();
+ for (++MII; MII->isBundledWithPred(); ++MII)
+ if (isHVXSpill(*MII, HVXLen))
+ ++Count;
+ } else {
+ if (isHVXSpill(BundleOrMI, HVXLen))
+ ++Count;
+ }
+ return Count;
+ }
+
bool runOnMachineFunction(MachineFunction &MF) override {
auto &MORE = getAnalysis<MachineOptimizationRemarkEmitterPass>().getORE();
if (!MORE.allowExtraAnalysis(DEBUG_TYPE))
@@ -51,45 +80,44 @@ struct HexagonHVXSaveRemark : public MachineFunctionPass {
if (!HST.useHVXOps())
return false;
- // Identify HVX caller-save slots by matching stack-slot sizes against
- // the HVX vector length (single vectors and vector pairs).
- const MachineFrameInfo &MFI = MF.getFrameInfo();
unsigned HVXLen = HST.getVectorLength();
- unsigned NumVecSaves = 0;
- unsigned NumPairSaves = 0;
-
- for (int I = MFI.getObjectIndexBegin(), E = MFI.getObjectIndexEnd(); I < E;
- ++I) {
- if (!MFI.isSpillSlotObjectIndex(I))
- continue;
- int64_t Size = MFI.getObjectSize(I);
- if (Size == (int64_t)HVXLen)
- ++NumVecSaves;
- else if (Size == (int64_t)(2 * HVXLen))
- ++NumPairSaves;
- }
-
- unsigned TotalSaves = NumVecSaves + NumPairSaves;
- unsigned TotalBytes = NumVecSaves * HVXLen + NumPairSaves * 2 * HVXLen;
-
- if (TotalBytes < HVXSaveThreshold)
- return false;
- // Emit a remark on each call site.
for (const MachineBasicBlock &MBB : MF) {
- for (const MachineInstr &MI : MBB) {
- if (!MI.isCall())
+ for (auto I = MBB.instr_begin(), E = MBB.instr_end(); I != E; ++I) {
+ const MachineInstr &MI = *I;
+ if (!MI.isCall() || MI.isBundledWithPred())
continue;
- MORE.emit([&]() {
- using namespace ore;
- MachineOptimizationRemarkAnalysis R(DEBUG_TYPE, "HVXSaveAroundCall",
- MI.getDebugLoc(), &MBB);
- R << NV("NumSaves", TotalSaves) << " HVX caller-saved register(s) ("
- << NV("TotalBytes", TotalBytes)
- << " bytes) saved and restored around call";
- return R;
- });
+ // Count HVX spills in the bundle containing this call (spills are
+ // often packetized together with the call instruction).
+ unsigned NumSpills = countSpillsInBundle(MI, HVXLen);
+
+ // Also count HVX spills in immediately preceding bundles/instructions
+ // that are purely spill operations.
+ auto BundleIt = MachineBasicBlock::const_iterator(MI);
+ while (BundleIt != MBB.begin()) {
+ --BundleIt;
+ unsigned PrevSpills = countSpillsInBundle(*BundleIt, HVXLen);
+ if (PrevSpills == 0)
+ break;
+ NumSpills += PrevSpills;
+ }
+
+ LLVM_DEBUG(dbgs() << "HVXSaveRemark: call in " << MF.getName()
+ << " has " << NumSpills << " HVX spills\n");
+
+ if (NumSpills >= HVXSaveThreshold) {
+ unsigned TotalBytes = NumSpills * HVXLen;
+ MORE.emit([&]() {
+ MachineOptimizationRemarkAnalysis R(DEBUG_TYPE, "HVXSaveAroundCall",
+ MI.getDebugLoc(), &MBB);
+ R << ore::NV("NumSaves", NumSpills)
+ << " HVX caller-saved register(s) ("
+ << ore::NV("TotalBytes", TotalBytes)
+ << " bytes) live across call";
+ return R;
+ });
+ }
}
}
diff --git a/llvm/test/CodeGen/Hexagon/hvx-save-remarks.ll b/llvm/test/CodeGen/Hexagon/hvx-save-remarks.ll
index 572625c4c5d31..6fd9db16e9860 100644
--- a/llvm/test/CodeGen/Hexagon/hvx-save-remarks.ll
+++ b/llvm/test/CodeGen/Hexagon/hvx-save-remarks.ll
@@ -4,10 +4,10 @@
;; Test that the HVX save remark pass reports caller-save costs around calls.
;; All HVX registers are caller-saved, so any HVX value live across a call
-;; requires a save/restore pair on the stack. The default threshold is 1024
-;; bytes, so we need at least 8 x 128-byte vectors live across the call.
+;; requires a save/restore pair on the stack. The default threshold is 8
+;; registers, so we need at least 8 HVX vectors live across the call.
-; CHECK: remark: {{.*}} HVX caller-saved register(s) ({{[0-9]+}} bytes) saved and restored around call
+; CHECK: remark: {{.*}} 8 HVX caller-saved register(s) (1024 bytes) live across call
; CHECK-NOT: remark:
declare void @bar()
More information about the llvm-commits
mailing list