[llvm] [LoopFusion] Emit optimization remarks regardless of statistics (PR #202012)
Madhur Amilkanthwar via llvm-commits
llvm-commits at lists.llvm.org
Wed Jun 10 02:22:24 PDT 2026
https://github.com/madhur13490 updated https://github.com/llvm/llvm-project/pull/202012
>From faadcad8656ed16e8bbde4b4181a926163f77c11 Mon Sep 17 00:00:00 2001
From: Madhur Amilkanthwar <madhura at nvidia.com>
Date: Fri, 5 Jun 2026 01:51:46 -0700
Subject: [PATCH] [LoopFusion] Emit optimization remarks regardless of
statistics
The fusion remark helpers built their messages from a Statistic's name
and description and guarded the ORE.emit calls with #if LLVM_ENABLE_STATS,
so a Release build with statistics disabled emitted no -Rpass /
-Rpass-missed remarks at all.
Make the remark helpers take the remark name and message as explicit
strings so they are implemented independently of the statistics, and
emit the remarks unconditionally. The statistics keep using the plain
STATISTIC macro and are incremented at the call sites.
---
llvm/lib/Transforms/Scalar/LoopFuse.cpp | 107 +++++++++++++++---------
1 file changed, 68 insertions(+), 39 deletions(-)
diff --git a/llvm/lib/Transforms/Scalar/LoopFuse.cpp b/llvm/lib/Transforms/Scalar/LoopFuse.cpp
index 3263d8b40aa83..7ead0228647aa 100644
--- a/llvm/lib/Transforms/Scalar/LoopFuse.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopFuse.cpp
@@ -172,19 +172,25 @@ struct FusionCandidate {
for (BasicBlock *BB : L->blocks()) {
if (BB->hasAddressTaken()) {
invalidate();
- reportInvalidCandidate(AddressTakenBB);
+ ++AddressTakenBB;
+ reportInvalidCandidate("AddressTakenBB",
+ "Basic block has address taken");
return;
}
for (Instruction &I : *BB) {
if (I.mayThrow()) {
invalidate();
- reportInvalidCandidate(MayThrowException);
+ ++MayThrowException;
+ reportInvalidCandidate("MayThrowException",
+ "Loop may throw an exception");
return;
}
if (I.isVolatile()) {
invalidate();
- reportInvalidCandidate(ContainsVolatileAccess);
+ ++ContainsVolatileAccess;
+ reportInvalidCandidate("ContainsVolatileAccess",
+ "Loop contains a volatile access");
return;
}
// Atomic accesses impose ordering/synchronization constraints that the
@@ -192,7 +198,9 @@ struct FusionCandidate {
// them across the fused body could be unsafe.
if (I.isAtomic()) {
invalidate();
- reportInvalidCandidate(ContainsAtomicAccess);
+ ++ContainsAtomicAccess;
+ reportInvalidCandidate("ContainsAtomicAccess",
+ "Loop contains an atomic access");
return;
}
if (I.mayWriteToMemory())
@@ -297,18 +305,23 @@ struct FusionCandidate {
if (!SE.hasLoopInvariantBackedgeTakenCount(L)) {
LLVM_DEBUG(dbgs() << "Loop " << L->getName()
<< " trip count not computable!\n");
- return reportInvalidCandidate(UnknownTripCount);
+ ++UnknownTripCount;
+ return reportInvalidCandidate("UnknownTripCount",
+ "Loop has unknown trip count");
}
if (!L->isLoopSimplifyForm()) {
LLVM_DEBUG(dbgs() << "Loop " << L->getName()
<< " is not in simplified form!\n");
- return reportInvalidCandidate(NotSimplifiedForm);
+ ++NotSimplifiedForm;
+ return reportInvalidCandidate("NotSimplifiedForm",
+ "Loop is not in simplified form");
}
if (!L->isRotatedForm()) {
LLVM_DEBUG(dbgs() << "Loop " << L->getName() << " is not rotated!\n");
- return reportInvalidCandidate(NotRotated);
+ ++NotRotated;
+ return reportInvalidCandidate("NotRotated", "Candidate is not rotated");
}
return true;
@@ -327,19 +340,20 @@ struct FusionCandidate {
Valid = false;
}
- bool reportInvalidCandidate(Statistic &Stat) const {
+ // Emit an analysis remark explaining why this loop cannot be fused. The
+ // remark is built from explicit strings so it does not depend on whether
+ // statistics are enabled. \p RemarkName is the -Rpass remark identifier and
+ // \p RemarkMsg the human-readable reason.
+ bool reportInvalidCandidate(StringRef RemarkName, StringRef RemarkMsg) const {
using namespace ore;
ORE.emit(OptimizationRemarkAnalysis(DEBUG_TYPE, "InvalidCandidate",
L->getStartLoc(), L->getHeader())
<< "Loop is not a candidate for fusion");
-#if LLVM_ENABLE_STATS
- ++Stat;
- ORE.emit(OptimizationRemarkAnalysis(DEBUG_TYPE, Stat.getName(),
+ ORE.emit(OptimizationRemarkAnalysis(DEBUG_TYPE, RemarkName,
L->getStartLoc(), L->getHeader())
<< "[" << L->getHeader()->getParent()->getName() << "]: "
- << "Loop is not a candidate for fusion: " << Stat.getDesc());
-#endif
+ << "Loop is not a candidate for fusion: " << RemarkMsg);
return false;
}
};
@@ -787,8 +801,10 @@ struct LoopFuser {
if (!SameTripCount) {
LLVM_DEBUG(dbgs() << "Fusion candidates do not have identical trip "
"counts. Not fusing.\n");
- reportLoopFusion<OptimizationRemarkMissed>(FC0, FC1,
- NonEqualTripCount);
+ ++NonEqualTripCount;
+ reportLoopFusion<OptimizationRemarkMissed>(
+ FC0, FC1, "NonEqualTripCount",
+ "Loop trip counts are not the same");
continue;
}
@@ -796,8 +812,10 @@ struct LoopFuser {
(FC0.GuardBranch && !FC1.GuardBranch)) {
LLVM_DEBUG(dbgs() << "The one of candidate is guarded while the "
"another one is not. Not fusing.\n");
+ ++OnlySecondCandidateIsGuarded;
reportLoopFusion<OptimizationRemarkMissed>(
- FC0, FC1, OnlySecondCandidateIsGuarded);
+ FC0, FC1, "OnlySecondCandidateIsGuarded",
+ "The second candidate is guarded while the first one is not");
continue;
}
@@ -809,8 +827,10 @@ struct LoopFuser {
!haveIdenticalGuards(FC0, FC1)) {
LLVM_DEBUG(dbgs() << "Fusion candidates do not have identical "
"guards. Not Fusing.\n");
- reportLoopFusion<OptimizationRemarkMissed>(FC0, FC1,
- NonIdenticalGuards);
+ ++NonIdenticalGuards;
+ reportLoopFusion<OptimizationRemarkMissed>(
+ FC0, FC1, "NonIdenticalGuards",
+ "Candidates have different guards");
continue;
}
}
@@ -823,8 +843,11 @@ struct LoopFuser {
&PDT, &DI)) {
LLVM_DEBUG(dbgs() << "Fusion candidate contains unsafe "
"instructions in exit block. Not fusing.\n");
- reportLoopFusion<OptimizationRemarkMissed>(FC0, FC1,
- NonEmptyExitBlock);
+ ++NonEmptyExitBlock;
+ reportLoopFusion<OptimizationRemarkMissed>(
+ FC0, FC1, "NonEmptyExitBlock",
+ "Candidate has a non-empty exit block with "
+ "instructions that cannot be moved");
continue;
}
@@ -834,8 +857,11 @@ struct LoopFuser {
&DI)) {
LLVM_DEBUG(dbgs() << "Fusion candidate contains unsafe "
"instructions in guard block. Not fusing.\n");
- reportLoopFusion<OptimizationRemarkMissed>(FC0, FC1,
- NonEmptyGuardBlock);
+ ++NonEmptyGuardBlock;
+ reportLoopFusion<OptimizationRemarkMissed>(
+ FC0, FC1, "NonEmptyGuardBlock",
+ "Candidate has a non-empty guard block with "
+ "instructions that cannot be moved");
continue;
}
}
@@ -844,8 +870,9 @@ struct LoopFuser {
// violate them.
if (!dependencesAllowFusion(FC0, FC1)) {
LLVM_DEBUG(dbgs() << "Memory dependencies do not allow fusion!\n");
- reportLoopFusion<OptimizationRemarkMissed>(FC0, FC1,
- InvalidDependencies);
+ ++InvalidDependencies;
+ reportLoopFusion<OptimizationRemarkMissed>(
+ FC0, FC1, "InvalidDependencies", "Dependencies prevent fusion");
continue;
}
@@ -868,8 +895,11 @@ struct LoopFuser {
LLVM_DEBUG(dbgs() << "Could not hoist/sink all instructions in "
"Fusion Candidate Pre-header.\n"
<< "Not Fusing.\n");
- reportLoopFusion<OptimizationRemarkMissed>(FC0, FC1,
- NonEmptyPreheader);
+ ++NonEmptyPreheader;
+ reportLoopFusion<OptimizationRemarkMissed>(
+ FC0, FC1, "NonEmptyPreheader",
+ "Loop has a non-empty preheader with instructions that "
+ "cannot be moved");
continue;
}
}
@@ -878,8 +908,9 @@ struct LoopFuser {
LLVM_DEBUG(dbgs() << "\tFusion appears to be "
<< (BeneficialToFuse ? "" : "un") << "profitable!\n");
if (!BeneficialToFuse) {
- reportLoopFusion<OptimizationRemarkMissed>(FC0, FC1,
- FusionNotBeneficial);
+ ++FusionNotBeneficial;
+ reportLoopFusion<OptimizationRemarkMissed>(
+ FC0, FC1, "FusionNotBeneficial", "Fusion is not beneficial");
continue;
}
// All analysis has completed and has determined that fusion is legal
@@ -903,8 +934,9 @@ struct LoopFuser {
// Note this needs to be done *before* performFusion because
// performFusion will change the original loops, making it not
// possible to identify them after fusion is complete.
+ ++FuseCounter;
reportLoopFusion<OptimizationRemark>((Peel ? FC0Copy : FC0), FC1,
- FuseCounter);
+ "FuseCounter", "Loops fused");
FusionCandidate FusedCand(performFusion((Peel ? FC0Copy : FC0), FC1),
DT, &PDT, ORE, FC0Copy.PP);
@@ -1598,19 +1630,16 @@ struct LoopFuser {
/// <Cand1 Preheader> and <Cand2 Preheader>: <Stat Description>
template <typename RemarkKind>
void reportLoopFusion(const FusionCandidate &FC0, const FusionCandidate &FC1,
- Statistic &Stat) {
+ StringRef RemarkName, StringRef RemarkMsg) {
assert(FC0.Preheader && FC1.Preheader &&
"Expecting valid fusion candidates");
using namespace ore;
-#if LLVM_ENABLE_STATS
- ++Stat;
- ORE.emit(RemarkKind(DEBUG_TYPE, Stat.getName(), FC0.L->getStartLoc(),
- FC0.Preheader)
- << "[" << FC0.Preheader->getParent()->getName()
- << "]: " << NV("Cand1", StringRef(FC0.Preheader->getName()))
- << " and " << NV("Cand2", StringRef(FC1.Preheader->getName()))
- << ": " << Stat.getDesc());
-#endif
+ ORE.emit(
+ RemarkKind(DEBUG_TYPE, RemarkName, FC0.L->getStartLoc(), FC0.Preheader)
+ << "[" << FC0.Preheader->getParent()->getName()
+ << "]: " << NV("Cand1", StringRef(FC0.Preheader->getName())) << " and "
+ << NV("Cand2", StringRef(FC1.Preheader->getName())) << ": "
+ << RemarkMsg);
}
/// Fuse two guarded fusion candidates, creating a new fused loop.
More information about the llvm-commits
mailing list