[llvm] [VPlan] Skip successors outside any loop when updating LoopInfo. (PR #190553)

Florian Hahn via llvm-commits llvm-commits at lists.llvm.org
Sun Apr 5 15:17:19 PDT 2026


https://github.com/fhahn created https://github.com/llvm/llvm-project/pull/190553

Successors outside of any loop do not contribute to the innermost loop, skip them to avoid incorrect results due to
getSmallestCommonLoop(nullptr, X) returning nullptr.

>From ec1f51e07cffe639cb1a6d875610bd8722bba157 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Sun, 5 Apr 2026 20:37:34 +0100
Subject: [PATCH] [VPlan] Skip successors outside any loop when updating
 LoopInfo.

Successors outside of any loop do not contribute to the innermost loop,
skip them to avoid incorrect results due to
getSmallestCommonLoop(nullptr, X) returning nullptr.
---
 llvm/lib/Transforms/Vectorize/VPlan.cpp       |  23 ++--
 .../early_exit_with_outer_loop.ll             | 115 ++++++++++++++++++
 2 files changed, 130 insertions(+), 8 deletions(-)

diff --git a/llvm/lib/Transforms/Vectorize/VPlan.cpp b/llvm/lib/Transforms/Vectorize/VPlan.cpp
index 919166099a4d1..994298c56bee0 100644
--- a/llvm/lib/Transforms/Vectorize/VPlan.cpp
+++ b/llvm/lib/Transforms/Vectorize/VPlan.cpp
@@ -962,9 +962,9 @@ void VPlan::execute(VPTransformState *State) {
   if (hasEarlyExit()) {
     // Fix up LoopInfo for extra dispatch blocks when vectorizing loops with
     // early exits. For dispatch blocks, we need to find the smallest common
-    // loop of all successors. Note: we only need to update loop info for blocks
-    // after the middle block, but there is no easy way to get those at this
-    // point.
+    // loop of all successors that are in a loop. Note: we only need to update
+    // loop info for blocks after the middle block, but there is no easy way to
+    // get those at this point.
     for (VPBlockBase *VPB : reverse(RPOT)) {
       auto *VPBB = dyn_cast<VPBasicBlock>(VPB);
       if (!VPBB || isa<VPIRBasicBlock>(VPBB))
@@ -974,11 +974,18 @@ void VPlan::execute(VPTransformState *State) {
       if (!L || any_of(successors(BB),
                        [L](BasicBlock *Succ) { return L->contains(Succ); }))
         continue;
-      // Find the innermost loop containing all successors.
-      Loop *Target = State->LI->getLoopFor(*succ_begin(BB));
-      for (BasicBlock *Succ : drop_begin(successors(BB)))
-        Target = State->LI->getSmallestCommonLoop(Target,
-                                                  State->LI->getLoopFor(Succ));
+      // Find the innermost loop containing all successors that are in a loop.
+      // Successors not in any loop don't constrain the target loop.
+      Loop *Target = nullptr;
+      for (BasicBlock *Succ : successors(BB)) {
+        Loop *SuccLoop = State->LI->getLoopFor(Succ);
+        if (!SuccLoop)
+          continue;
+        if (!Target)
+          Target = SuccLoop;
+        else
+          Target = State->LI->getSmallestCommonLoop(Target, SuccLoop);
+      }
       State->LI->removeBlock(BB);
       if (Target)
         Target->addBasicBlockToLoop(BB, *State->LI);
diff --git a/llvm/test/Transforms/LoopVectorize/early_exit_with_outer_loop.ll b/llvm/test/Transforms/LoopVectorize/early_exit_with_outer_loop.ll
index 721d14e571eb2..b02a96ab25f3c 100644
--- a/llvm/test/Transforms/LoopVectorize/early_exit_with_outer_loop.ll
+++ b/llvm/test/Transforms/LoopVectorize/early_exit_with_outer_loop.ll
@@ -415,3 +415,118 @@ early.exit.2:
 early.exit.3:
   ret i32 2
 }
+
+; Tests that dispatch blocks are correctly placed in the outermost loop when
+; the innermost loop has two uncountable early exits at different loop depths:
+; one to the outer loop and one outside all loops.
+define i64 @multi_early_exit_to_outer_and_outside(ptr dereferenceable(1024) %p1, ptr dereferenceable(1024) %p2, i1 %c) {
+; CHECK-LABEL: Loop info for function 'multi_early_exit_to_outer_and_outside':
+; CHECK-NEXT: Loop at depth 1 containing: %loop.outer<header>,%loop.middle,%loop.inner.end,%loop.middle.end,%loop.inner.found,%loop.outer.latch<latch><exiting>,%vector.ph,%vector.body,%vector.body.interim,%middle.block,%vector.early.exit.0,%vector.early.exit.check<exiting>
+; CHECK-NEXT:    Loop at depth 2 containing: %loop.middle<header>,%loop.inner.end<latch><exiting>,%vector.ph,%vector.body<exiting>,%vector.body.interim,%middle.block
+; CHECK-NEXT:        Loop at depth 3 containing: %vector.body<header><exiting>,%vector.body.interim<latch><exiting>
+entry:
+  br label %loop.outer
+
+loop.outer:
+  %outer.iv = phi i64 [ 0, %entry ], [ %outer.iv.next, %loop.outer.latch ]
+  br label %loop.middle
+
+loop.middle:
+  br label %loop.inner
+
+loop.inner:
+  %iv = phi i64 [ 0, %loop.middle ], [ %iv.next, %loop.inner.inc ]
+  %gep1 = getelementptr inbounds i8, ptr %p1, i64 %iv
+  %ld1 = load i8, ptr %gep1, align 1
+  %cmp.early1 = icmp eq i8 %ld1, 0
+  br i1 %cmp.early1, label %loop.inner.found, label %loop.inner.body
+
+loop.inner.body:
+  %gep2 = getelementptr inbounds i8, ptr %p2, i64 %iv
+  %ld2 = load i8, ptr %gep2, align 1
+  %cmp.early2 = icmp eq i8 %ld2, 0
+  br i1 %cmp.early2, label %exit, label %loop.inner.inc
+
+loop.inner.inc:
+  %iv.next = add nuw nsw i64 %iv, 1
+  %exitcond = icmp eq i64 %iv.next, 1024
+  br i1 %exitcond, label %loop.inner.end, label %loop.inner
+
+loop.inner.found:
+  br label %loop.outer.latch
+
+loop.inner.end:
+  br i1 %c, label %loop.middle, label %loop.middle.end
+
+loop.middle.end:
+  br label %loop.outer.latch
+
+loop.outer.latch:
+  %res = phi i64 [ 0, %loop.middle.end ], [ 42, %loop.inner.found ]
+  %outer.iv.next = add i64 %outer.iv, 1
+  %outer.cond = icmp eq i64 %outer.iv.next, 100
+  br i1 %outer.cond, label %loop.outer.end, label %loop.outer
+
+loop.outer.end:
+  ret i64 %res
+
+exit:
+  ret i64 1
+}
+
+; Same as above but with early exits reversed: the first early exit goes outside
+; all loops and the second goes to the outer loop.
+define i64 @multi_early_exit_to_outside_and_outer(ptr dereferenceable(1024) %p1, ptr dereferenceable(1024) %p2, i1 %c) {
+; CHECK-LABEL: Loop info for function 'multi_early_exit_to_outside_and_outer':
+; CHECK-NEXT: Loop at depth 1 containing: %loop.outer<header>,%loop.middle,%loop.inner.end,%loop.middle.end,%loop.inner.found,%loop.outer.latch<latch><exiting>,%vector.ph,%vector.body,%vector.body.interim,%middle.block,%vector.early.exit.1,%vector.early.exit.check<exiting>
+; CHECK-NEXT:    Loop at depth 2 containing: %loop.middle<header>,%loop.inner.end<latch><exiting>,%vector.ph,%vector.body<exiting>,%vector.body.interim,%middle.block
+; CHECK-NEXT:        Loop at depth 3 containing: %vector.body<header><exiting>,%vector.body.interim<latch><exiting>
+entry:
+  br label %loop.outer
+
+loop.outer:
+  %outer.iv = phi i64 [ 0, %entry ], [ %outer.iv.next, %loop.outer.latch ]
+  br label %loop.middle
+
+loop.middle:
+  br label %loop.inner
+
+loop.inner:
+  %iv = phi i64 [ 0, %loop.middle ], [ %iv.next, %loop.inner.inc ]
+  %gep1 = getelementptr inbounds i8, ptr %p1, i64 %iv
+  %ld1 = load i8, ptr %gep1, align 1
+  %cmp.early1 = icmp eq i8 %ld1, 0
+  br i1 %cmp.early1, label %exit, label %loop.inner.body
+
+loop.inner.body:
+  %gep2 = getelementptr inbounds i8, ptr %p2, i64 %iv
+  %ld2 = load i8, ptr %gep2, align 1
+  %cmp.early2 = icmp eq i8 %ld2, 0
+  br i1 %cmp.early2, label %loop.inner.found, label %loop.inner.inc
+
+loop.inner.inc:
+  %iv.next = add nuw nsw i64 %iv, 1
+  %exitcond = icmp eq i64 %iv.next, 1024
+  br i1 %exitcond, label %loop.inner.end, label %loop.inner
+
+loop.inner.found:
+  br label %loop.outer.latch
+
+loop.inner.end:
+  br i1 %c, label %loop.middle, label %loop.middle.end
+
+loop.middle.end:
+  br label %loop.outer.latch
+
+loop.outer.latch:
+  %res = phi i64 [ 0, %loop.middle.end ], [ 42, %loop.inner.found ]
+  %outer.iv.next = add i64 %outer.iv, 1
+  %outer.cond = icmp eq i64 %outer.iv.next, 100
+  br i1 %outer.cond, label %loop.outer.end, label %loop.outer
+
+loop.outer.end:
+  ret i64 %res
+
+exit:
+  ret i64 1
+}



More information about the llvm-commits mailing list