[llvm] [LoopUnroll][NFC] Add optimization remark for skipped vectorized/interleaved loops (PR #189472)
Ahmad Yasin via llvm-commits
llvm-commits at lists.llvm.org
Thu Apr 9 15:24:00 PDT 2026
https://github.com/ayasin-a updated https://github.com/llvm/llvm-project/pull/189472
>From 8d815ca69ca9e7d25d772c2cf28142e17456bcef Mon Sep 17 00:00:00 2001
From: Ahmad Yasin <ahmad.yasin at apple.com>
Date: Mon, 30 Mar 2026 22:17:15 +0300
Subject: [PATCH 1/5] [LoopUnroll] Add optimization remark when unrolling is
skipped for vectorized/interleaved loops
---
llvm/lib/Transforms/Scalar/LoopUnrollPass.cpp | 32 +++++++++++++++++++
1 file changed, 32 insertions(+)
diff --git a/llvm/lib/Transforms/Scalar/LoopUnrollPass.cpp b/llvm/lib/Transforms/Scalar/LoopUnrollPass.cpp
index 9fdf7ef1b0a86..e92c30ee425ca 100644
--- a/llvm/lib/Transforms/Scalar/LoopUnrollPass.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopUnrollPass.cpp
@@ -1390,6 +1390,38 @@ tryToUnrollLoop(Loop *L, DominatorTree &DT, LoopInfo *LI, ScalarEvolution &SE,
if (!UP.Count) {
LLVM_DEBUG(dbgs().indent(1)
<< "Not unrolling: no viable strategy found.\n");
+ // If the loop was vectorized or interleaved, emit a remark explaining why
+ // unrolling was skipped.
+ if (getBooleanLoopAttribute(L, "llvm.loop.isvectorized")) {
+ bool HasVectorInstr = any_of(L->blocks(), [](BasicBlock *BB) {
+ return any_of(*BB, [](Instruction &I) {
+ return I.getType()->isVectorTy();
+ });
+ });
+ if (!HasVectorInstr) {
+ // Scalar remainder loop — don't emit a remark; it's expected not to
+ // unroll the remainder.
+ } else {
+ std::optional<int> IC =
+ getOptionalIntLoopAttribute(L, "llvm.loop.interleave.count");
+ if (IC && *IC > 1)
+ ORE.emit([&]() {
+ return OptimizationRemarkMissed(DEBUG_TYPE,
+ "UnrollSkippedInterleaved",
+ L->getStartLoc(), L->getHeader())
+ << "loop not unrolled: interleaved by the vectorizer with "
+ "interleave count "
+ << ore::NV("InterleaveCount", *IC);
+ });
+ else
+ ORE.emit([&]() {
+ return OptimizationRemarkMissed(DEBUG_TYPE,
+ "UnrollSkippedVectorized",
+ L->getStartLoc(), L->getHeader())
+ << "loop not unrolled: already vectorized";
+ });
+ }
+ }
return LoopUnrollResult::Unmodified;
}
>From eec24e9673e5a14cce5ba3d1930de37ae96c43af Mon Sep 17 00:00:00 2001
From: Ahmad Yasin <ahmad.yasin at apple.com>
Date: Mon, 30 Mar 2026 23:28:57 +0300
Subject: [PATCH 2/5] tests for remark when unrolling is skipped for
vectorized/interleaved loops
---
.../Transforms/LoopUnroll/loop-remarks.ll | 72 +++++++++++++++++++
1 file changed, 72 insertions(+)
diff --git a/llvm/test/Transforms/LoopUnroll/loop-remarks.ll b/llvm/test/Transforms/LoopUnroll/loop-remarks.ll
index fb2ab7502ffb4..3ea80bcf1bce7 100644
--- a/llvm/test/Transforms/LoopUnroll/loop-remarks.ll
+++ b/llvm/test/Transforms/LoopUnroll/loop-remarks.ll
@@ -1,6 +1,7 @@
; RUN: opt < %s -S -passes=loop-unroll -pass-remarks=loop-unroll -unroll-count=16 2>&1 | FileCheck -check-prefix=COMPLETE-UNROLL %s
; RUN: opt < %s -S -passes=loop-unroll -pass-remarks=loop-unroll -unroll-count=4 2>&1 | FileCheck -check-prefix=PARTIAL-UNROLL %s
; RUN: opt < %s -S -passes=loop-unroll -pass-remarks=loop-unroll -unroll-count=4 -unroll-runtime=true -unroll-remainder 2>&1 | FileCheck %s --check-prefix=RUNTIME-UNROLL
+; RUN: opt < %s -S -passes=loop-unroll -pass-remarks-missed=loop-unroll 2>&1 | FileCheck -check-prefix=VECTORIZED %s
; COMPLETE-UNROLL: remark: {{.*}}: completely unrolled loop with 16 iterations
; PARTIAL-UNROLL: remark: {{.*}}: unrolled loop by a factor of 4
@@ -46,3 +47,74 @@ for.end: ; preds = %for.body
}
declare i32 @baz(i32)
+
+; Vectorized loop with vector instructions — expect "already vectorized" remark.
+; VECTORIZED: remark: {{.*}}: loop not unrolled: already vectorized
+define void @vectorized_loop(ptr noalias %a, ptr noalias %b, i64 %n) {
+entry:
+ br label %vector.body
+
+vector.body:
+ %index = phi i64 [ 0, %entry ], [ %index.next, %vector.body ]
+ %gep.a = getelementptr inbounds float, ptr %a, i64 %index
+ %wide.load = load <4 x float>, ptr %gep.a, align 4
+ %gep.b = getelementptr inbounds float, ptr %b, i64 %index
+ %wide.load2 = load <4 x float>, ptr %gep.b, align 4
+ %add = fadd <4 x float> %wide.load, %wide.load2
+ store <4 x float> %add, ptr %gep.a, align 4
+ %index.next = add nuw i64 %index, 4
+ %cmp = icmp eq i64 %index.next, %n
+ br i1 %cmp, label %exit, label %vector.body, !llvm.loop !0
+
+exit:
+ ret void
+}
+
+; Interleaved loop (interleave count > 1) — expect "interleaved" remark.
+; VECTORIZED: remark: {{.*}}: loop not unrolled: interleaved by the vectorizer with interleave count 4
+define void @interleaved_loop(ptr noalias %a, ptr noalias %b, i64 %n) {
+entry:
+ br label %vector.body
+
+vector.body:
+ %index = phi i64 [ 0, %entry ], [ %index.next, %vector.body ]
+ %gep.a = getelementptr inbounds float, ptr %a, i64 %index
+ %wide.load = load <4 x float>, ptr %gep.a, align 4
+ %gep.b = getelementptr inbounds float, ptr %b, i64 %index
+ %wide.load2 = load <4 x float>, ptr %gep.b, align 4
+ %add = fadd <4 x float> %wide.load, %wide.load2
+ store <4 x float> %add, ptr %gep.a, align 4
+ %index.next = add nuw i64 %index, 16
+ %cmp = icmp eq i64 %index.next, %n
+ br i1 %cmp, label %exit, label %vector.body, !llvm.loop !2
+
+exit:
+ ret void
+}
+
+; Scalar remainder loop (isvectorized but no vector instructions) — no remark.
+; VECTORIZED-NOT: remark: {{.*}} scalar_remainder
+define void @scalar_remainder(ptr noalias %a, ptr noalias %b, i64 %n) {
+entry:
+ br label %for.body
+
+for.body:
+ %i = phi i64 [ 0, %entry ], [ %i.next, %for.body ]
+ %gep.a = getelementptr inbounds float, ptr %a, i64 %i
+ %val.a = load float, ptr %gep.a, align 4
+ %gep.b = getelementptr inbounds float, ptr %b, i64 %i
+ %val.b = load float, ptr %gep.b, align 4
+ %add = fadd float %val.a, %val.b
+ store float %add, ptr %gep.a, align 4
+ %i.next = add nuw i64 %i, 1
+ %cmp = icmp eq i64 %i.next, %n
+ br i1 %cmp, label %exit, label %for.body, !llvm.loop !0
+
+exit:
+ ret void
+}
+
+!0 = distinct !{!0, !4}
+!2 = distinct !{!2, !4, !5}
+!4 = !{!"llvm.loop.isvectorized", i32 1}
+!5 = !{!"llvm.loop.interleave.count", i32 4}
>From b131b1ac72f3eedf7de686756f96ca4de45b05b1 Mon Sep 17 00:00:00 2001
From: Ahmad Yasin <ahmad.yasin at apple.com>
Date: Wed, 1 Apr 2026 01:23:01 +0300
Subject: [PATCH 3/5] Emit optimization remark with target-provided reason when
unrolling is skipped
Add an UnrollSkipReason string field to UnrollingPreferences that targets
can populate to explain why they declined to unroll. The remark is emitted
at the generic !UP.Count check in tryToUnrollLoop(), so it fires for all
rejected unrolling strategies (partial, runtime, etc).
Initially, AArch64 populates the reason at every early-return in
getAppleRuntimeUnrollPreferences() and getUnrollingPreferences(), covering
vectorized loops, loops with calls, and all Apple runtime unroll heuristics.
This replaces the previous implementation which inferred the skip reason by
checking vectorization/interleave metadata at the !UP.Count site, which
could misattribute the cause of unrolling being skipped. The new approach
reports the *dominant* reason from the code path that made the decision.
---
.../llvm/Analysis/TargetTransformInfo.h | 3 +
.../AArch64/AArch64TargetTransformInfo.cpp | 58 ++++++++++++---
llvm/lib/Transforms/Scalar/LoopUnrollPass.cpp | 42 +++--------
.../Transforms/LoopUnroll/loop-remarks.ll | 72 -------------------
4 files changed, 61 insertions(+), 114 deletions(-)
diff --git a/llvm/include/llvm/Analysis/TargetTransformInfo.h b/llvm/include/llvm/Analysis/TargetTransformInfo.h
index 531c5b1179fe4..1b92264d242ef 100644
--- a/llvm/include/llvm/Analysis/TargetTransformInfo.h
+++ b/llvm/include/llvm/Analysis/TargetTransformInfo.h
@@ -732,6 +732,9 @@ class TargetTransformInfo {
/// Fall back to the generic logic to determine whether multi-exit unrolling
/// is profitable if set to false.
bool RuntimeUnrollMultiExit;
+ /// Optional reason why unrolling was not enabled by the target.
+ /// Used to provide detailed diagnostics in optimization remarks.
+ std::string UnrollSkipReason;
/// Allow unrolling to add parallel reduction phis.
bool AddAdditionalAccumulators;
};
diff --git a/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp b/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
index de578ea29cbe9..e98a5756720fa 100644
--- a/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
+++ b/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
@@ -5260,12 +5260,19 @@ getAppleRuntimeUnrollPreferences(Loop *L, ScalarEvolution &SE,
// likely with complex control flow). Note that the heuristics here may be
// overly conservative and we err on the side of avoiding runtime unrolling
// rather than unroll excessively. They are all subject to further refinement.
- if (!L->isInnermost() || L->getNumBlocks() > 8)
+ if (!L->isInnermost() || L->getNumBlocks() > 8) {
+ UP.UnrollSkipReason = !L->isInnermost()
+ ? "runtime-Apple: not an innermost loop"
+ : "runtime-Apple: loop has too many blocks (" +
+ std::to_string(L->getNumBlocks()) + " > 8)";
return;
+ }
// Loops with multiple exits are handled by common code.
- if (!L->getExitBlock())
+ if (!L->getExitBlock()) {
+ UP.UnrollSkipReason = "runtime-Apple: loop has multiple exits";
return;
+ }
// Check if the loop contains any reductions that could be parallelized when
// unrolling. If so, enable partial unrolling, if the trip count is know to be
@@ -5287,14 +5294,27 @@ getAppleRuntimeUnrollPreferences(Loop *L, ScalarEvolution &SE,
const SCEV *BTC = SE.getSymbolicMaxBackedgeTakenCount(L);
if (isa<SCEVConstant>(BTC) || isa<SCEVCouldNotCompute>(BTC) ||
(SE.getSmallConstantMaxTripCount(L) > 0 &&
- SE.getSmallConstantMaxTripCount(L) <= 32))
+ SE.getSmallConstantMaxTripCount(L) <= 32)) {
+ UP.UnrollSkipReason =
+ isa<SCEVConstant>(BTC) ? "runtime-Apple: loop has constant trip count"
+ : isa<SCEVCouldNotCompute>(BTC)
+ ? "runtime-Apple: could not compute backedge-taken count"
+ : "runtime-Apple: loop has small max trip count (" +
+ std::to_string(SE.getSmallConstantMaxTripCount(L)) +
+ " <= 32)";
return;
+ }
- if (findStringMetadataForLoop(L, "llvm.loop.isvectorized"))
+ if (findStringMetadataForLoop(L, "llvm.loop.isvectorized")) {
+ UP.UnrollSkipReason = "runtime-Apple: loop already vectorized";
return;
+ }
- if (SE.getSymbolicMaxBackedgeTakenCount(L) != SE.getBackedgeTakenCount(L))
+ if (SE.getSymbolicMaxBackedgeTakenCount(L) != SE.getBackedgeTakenCount(L)) {
+ UP.UnrollSkipReason = "runtime-Apple: symbolic max backedge-taken count "
+ "differs from backedge-taken count";
return;
+ }
// Limit to loops with trip counts that are cheap to expand.
UP.SCEVExpansionBudget = 1;
@@ -5314,8 +5334,11 @@ getAppleRuntimeUnrollPreferences(Loop *L, ScalarEvolution &SE,
// Estimate the size of the loop.
unsigned Size;
unsigned Width = 10;
- if (!isLoopSizeWithinBudget(L, TTI, Width, &Size))
+ if (!isLoopSizeWithinBudget(L, TTI, Width, &Size)) {
+ UP.UnrollSkipReason = "runtime-Apple: loop size exceeds budget (" +
+ std::to_string(Width) + ")";
return;
+ }
// Try to find an unroll count that maximizes the use of the instruction
// window, i.e. trying to fetch as many instructions per cycle as possible.
@@ -5335,8 +5358,11 @@ getAppleRuntimeUnrollPreferences(Loop *L, ScalarEvolution &SE,
UC++;
}
- if (BestUC == 1)
+ if (BestUC == 1) {
+ UP.UnrollSkipReason =
+ "runtime-Apple: no beneficial unroll count found (BestUC=1)";
return;
+ }
SmallPtrSet<Value *, 8> LoadedValuesPlus;
SmallVector<StoreInst *> Stores;
@@ -5361,8 +5387,11 @@ getAppleRuntimeUnrollPreferences(Loop *L, ScalarEvolution &SE,
if (none_of(Stores, [&LoadedValuesPlus](StoreInst *SI) {
return LoadedValuesPlus.contains(SI->getOperand(0));
- }))
+ })) {
+ UP.UnrollSkipReason = "runtime-Apple: no load/store dependencies found "
+ "that would benefit from unrolling";
return;
+ }
UP.Runtime = true;
UP.DefaultUnrollRuntimeCount = BestUC;
@@ -5374,8 +5403,11 @@ getAppleRuntimeUnrollPreferences(Loop *L, ScalarEvolution &SE,
auto *Term = dyn_cast<CondBrInst>(Header->getTerminator());
SmallVector<BasicBlock *> Preds(predecessors(Latch));
if (!Term || Preds.size() == 1 || !llvm::is_contained(Preds, Header) ||
- none_of(Preds, [L](BasicBlock *Pred) { return L->contains(Pred); }))
+ none_of(Preds, [L](BasicBlock *Pred) { return L->contains(Pred); })) {
+ UP.UnrollSkipReason = "runtime-Apple: loop structure unsuitable for "
+ "early-continue optimization";
return;
+ }
std::function<bool(Instruction *, unsigned)> DependsOnLoopLoad =
[&](Instruction *I, unsigned Depth) -> bool {
@@ -5396,6 +5428,9 @@ getAppleRuntimeUnrollPreferences(Loop *L, ScalarEvolution &SE,
m_Value())) &&
DependsOnLoopLoad(I, 0)) {
UP.Runtime = true;
+ } else {
+ UP.UnrollSkipReason = "runtime-Apple: branch condition does not depend on "
+ "a loop-varying load";
}
}
@@ -5426,13 +5461,16 @@ void AArch64TTIImpl::getUnrollingPreferences(
// Both auto-vectorized loops and the scalar remainder have the
// isvectorized attribute, so differentiate between them by the presence
// of vector instructions.
- if (IsVectorized && I.getType()->isVectorTy())
+ if (IsVectorized && I.getType()->isVectorTy()) {
+ UP.UnrollSkipReason = "AArch64: loop already vectorized";
return;
+ }
if (isa<CallBase>(I)) {
if (isa<CallInst>(I) || isa<InvokeInst>(I))
if (const Function *F = cast<CallBase>(I).getCalledFunction())
if (!isLoweredToCall(F))
continue;
+ UP.UnrollSkipReason = "AArch64: loop contains a call";
return;
}
diff --git a/llvm/lib/Transforms/Scalar/LoopUnrollPass.cpp b/llvm/lib/Transforms/Scalar/LoopUnrollPass.cpp
index e92c30ee425ca..7072d38167342 100644
--- a/llvm/lib/Transforms/Scalar/LoopUnrollPass.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopUnrollPass.cpp
@@ -1390,38 +1390,16 @@ tryToUnrollLoop(Loop *L, DominatorTree &DT, LoopInfo *LI, ScalarEvolution &SE,
if (!UP.Count) {
LLVM_DEBUG(dbgs().indent(1)
<< "Not unrolling: no viable strategy found.\n");
- // If the loop was vectorized or interleaved, emit a remark explaining why
- // unrolling was skipped.
- if (getBooleanLoopAttribute(L, "llvm.loop.isvectorized")) {
- bool HasVectorInstr = any_of(L->blocks(), [](BasicBlock *BB) {
- return any_of(*BB, [](Instruction &I) {
- return I.getType()->isVectorTy();
- });
- });
- if (!HasVectorInstr) {
- // Scalar remainder loop — don't emit a remark; it's expected not to
- // unroll the remainder.
- } else {
- std::optional<int> IC =
- getOptionalIntLoopAttribute(L, "llvm.loop.interleave.count");
- if (IC && *IC > 1)
- ORE.emit([&]() {
- return OptimizationRemarkMissed(DEBUG_TYPE,
- "UnrollSkippedInterleaved",
- L->getStartLoc(), L->getHeader())
- << "loop not unrolled: interleaved by the vectorizer with "
- "interleave count "
- << ore::NV("InterleaveCount", *IC);
- });
- else
- ORE.emit([&]() {
- return OptimizationRemarkMissed(DEBUG_TYPE,
- "UnrollSkippedVectorized",
- L->getStartLoc(), L->getHeader())
- << "loop not unrolled: already vectorized";
- });
- }
- }
+ ORE.emit([&]() {
+ auto Remark = OptimizationRemarkMissed(DEBUG_TYPE, "UnrollSkipped",
+ L->getStartLoc(), L->getHeader())
+ << "loop not unrolled: ";
+ if (!UP.UnrollSkipReason.empty())
+ Remark << UP.UnrollSkipReason;
+ else
+ Remark << "no details available";
+ return Remark;
+ });
return LoopUnrollResult::Unmodified;
}
diff --git a/llvm/test/Transforms/LoopUnroll/loop-remarks.ll b/llvm/test/Transforms/LoopUnroll/loop-remarks.ll
index 3ea80bcf1bce7..fb2ab7502ffb4 100644
--- a/llvm/test/Transforms/LoopUnroll/loop-remarks.ll
+++ b/llvm/test/Transforms/LoopUnroll/loop-remarks.ll
@@ -1,7 +1,6 @@
; RUN: opt < %s -S -passes=loop-unroll -pass-remarks=loop-unroll -unroll-count=16 2>&1 | FileCheck -check-prefix=COMPLETE-UNROLL %s
; RUN: opt < %s -S -passes=loop-unroll -pass-remarks=loop-unroll -unroll-count=4 2>&1 | FileCheck -check-prefix=PARTIAL-UNROLL %s
; RUN: opt < %s -S -passes=loop-unroll -pass-remarks=loop-unroll -unroll-count=4 -unroll-runtime=true -unroll-remainder 2>&1 | FileCheck %s --check-prefix=RUNTIME-UNROLL
-; RUN: opt < %s -S -passes=loop-unroll -pass-remarks-missed=loop-unroll 2>&1 | FileCheck -check-prefix=VECTORIZED %s
; COMPLETE-UNROLL: remark: {{.*}}: completely unrolled loop with 16 iterations
; PARTIAL-UNROLL: remark: {{.*}}: unrolled loop by a factor of 4
@@ -47,74 +46,3 @@ for.end: ; preds = %for.body
}
declare i32 @baz(i32)
-
-; Vectorized loop with vector instructions — expect "already vectorized" remark.
-; VECTORIZED: remark: {{.*}}: loop not unrolled: already vectorized
-define void @vectorized_loop(ptr noalias %a, ptr noalias %b, i64 %n) {
-entry:
- br label %vector.body
-
-vector.body:
- %index = phi i64 [ 0, %entry ], [ %index.next, %vector.body ]
- %gep.a = getelementptr inbounds float, ptr %a, i64 %index
- %wide.load = load <4 x float>, ptr %gep.a, align 4
- %gep.b = getelementptr inbounds float, ptr %b, i64 %index
- %wide.load2 = load <4 x float>, ptr %gep.b, align 4
- %add = fadd <4 x float> %wide.load, %wide.load2
- store <4 x float> %add, ptr %gep.a, align 4
- %index.next = add nuw i64 %index, 4
- %cmp = icmp eq i64 %index.next, %n
- br i1 %cmp, label %exit, label %vector.body, !llvm.loop !0
-
-exit:
- ret void
-}
-
-; Interleaved loop (interleave count > 1) — expect "interleaved" remark.
-; VECTORIZED: remark: {{.*}}: loop not unrolled: interleaved by the vectorizer with interleave count 4
-define void @interleaved_loop(ptr noalias %a, ptr noalias %b, i64 %n) {
-entry:
- br label %vector.body
-
-vector.body:
- %index = phi i64 [ 0, %entry ], [ %index.next, %vector.body ]
- %gep.a = getelementptr inbounds float, ptr %a, i64 %index
- %wide.load = load <4 x float>, ptr %gep.a, align 4
- %gep.b = getelementptr inbounds float, ptr %b, i64 %index
- %wide.load2 = load <4 x float>, ptr %gep.b, align 4
- %add = fadd <4 x float> %wide.load, %wide.load2
- store <4 x float> %add, ptr %gep.a, align 4
- %index.next = add nuw i64 %index, 16
- %cmp = icmp eq i64 %index.next, %n
- br i1 %cmp, label %exit, label %vector.body, !llvm.loop !2
-
-exit:
- ret void
-}
-
-; Scalar remainder loop (isvectorized but no vector instructions) — no remark.
-; VECTORIZED-NOT: remark: {{.*}} scalar_remainder
-define void @scalar_remainder(ptr noalias %a, ptr noalias %b, i64 %n) {
-entry:
- br label %for.body
-
-for.body:
- %i = phi i64 [ 0, %entry ], [ %i.next, %for.body ]
- %gep.a = getelementptr inbounds float, ptr %a, i64 %i
- %val.a = load float, ptr %gep.a, align 4
- %gep.b = getelementptr inbounds float, ptr %b, i64 %i
- %val.b = load float, ptr %gep.b, align 4
- %add = fadd float %val.a, %val.b
- store float %add, ptr %gep.a, align 4
- %i.next = add nuw i64 %i, 1
- %cmp = icmp eq i64 %i.next, %n
- br i1 %cmp, label %exit, label %for.body, !llvm.loop !0
-
-exit:
- ret void
-}
-
-!0 = distinct !{!0, !4}
-!2 = distinct !{!2, !4, !5}
-!4 = !{!"llvm.loop.isvectorized", i32 1}
-!5 = !{!"llvm.loop.interleave.count", i32 4}
>From 03273eb0965b175da972fe69bdd6c33b2dce4855 Mon Sep 17 00:00:00 2001
From: Ahmad Yasin <ahmad.yasin at apple.com>
Date: Wed, 1 Apr 2026 14:26:25 +0300
Subject: [PATCH 4/5] [LoopVectorize] Return early from canVectorize after loop
nest CFG failure to fix assertion in DoExtraAnalysis mode
---
llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp | 3 +++
1 file changed, 3 insertions(+)
diff --git a/llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp b/llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp
index 6e4ecbdecb7cf..b180ca9901d77 100644
--- a/llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp
+++ b/llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp
@@ -1945,6 +1945,9 @@ bool LoopVectorizationLegality::canVectorize(bool UseVPlanNativePath) {
} else {
return false;
}
+ // Do not proceed to further legality checks that assume valid loop
+ // structure (single backedge, preheader, latch, etc.).
+ return Result;
}
// We need to have a loop header.
>From 350bea26b79b7001005a48d997c659fac6bddc84 Mon Sep 17 00:00:00 2001
From: Ahmad Yasin <ahmad.yasin at apple.com>
Date: Fri, 10 Apr 2026 01:23:47 +0300
Subject: [PATCH 5/5] Set UnrollSkipReason when post-target unrolling paths
override or fall through
---
llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp b/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
index e98a5756720fa..e694b1492caa7 100644
--- a/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
+++ b/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
@@ -5490,6 +5490,7 @@ void AArch64TTIImpl::getUnrollingPreferences(
// If this is a small, multi-exit loop similar to something like std::find,
// then there is typically a performance improvement achieved by unrolling.
if (!L->getExitBlock() && shouldUnrollMultiExitLoop(L, SE, *this)) {
+ UP.UnrollSkipReason.clear();
UP.RuntimeUnrollMultiExit = true;
UP.Runtime = true;
// Limit unroll count.
@@ -5506,6 +5507,7 @@ void AArch64TTIImpl::getUnrollingPreferences(
// unchanged
if (ST->getProcFamily() != AArch64Subtarget::Generic &&
!ST->getSchedModel().isOutOfOrder()) {
+ UP.UnrollSkipReason.clear();
UP.Runtime = true;
UP.Partial = true;
UP.UnrollRemainder = true;
@@ -5517,8 +5519,13 @@ void AArch64TTIImpl::getUnrollingPreferences(
// Force unrolling small loops can be very useful because of the branch
// taken cost of the backedge.
- if (Cost < Aarch64ForceUnrollThreshold)
+ if (Cost < Aarch64ForceUnrollThreshold) {
+ UP.UnrollSkipReason.clear();
UP.Force = true;
+ } else if (UP.UnrollSkipReason.empty()) {
+ UP.UnrollSkipReason =
+ "AArch64: no target-specific unrolling strategy matched";
+ }
}
void AArch64TTIImpl::getPeelingPreferences(Loop *L, ScalarEvolution &SE,
More information about the llvm-commits
mailing list