[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