[llvm] [VPlan] Fix isLatch to handle BranchOnTwoConds, check verifier.(NFC) (PR #192808)
Florian Hahn via llvm-commits
llvm-commits at lists.llvm.org
Sat Apr 18 12:48:10 PDT 2026
https://github.com/fhahn created https://github.com/llvm/llvm-project/pull/192808
Update `isLatch` to handle BranchOnTwoConds,
in preparation to enable running the verifier on earlier VPlans.
>From 6cb487f1d6692ac780b507df4ac14318c27ea6ec Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Sat, 28 Feb 2026 09:19:10 +0000
Subject: [PATCH] [VPlan] Fix isLatch to handle BranchOnTwoConds, add verifier
test. (NFCI)
---
llvm/lib/Transforms/Vectorize/VPlanUtils.cpp | 6 +-
.../Transforms/Vectorize/VPlanVerifier.cpp | 7 ++
.../Vectorize/VPlanVerifierTest.cpp | 70 +++++++++++++++++++
3 files changed, 80 insertions(+), 3 deletions(-)
diff --git a/llvm/lib/Transforms/Vectorize/VPlanUtils.cpp b/llvm/lib/Transforms/Vectorize/VPlanUtils.cpp
index e33a936dd9978..9f73ba77039c2 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanUtils.cpp
+++ b/llvm/lib/Transforms/Vectorize/VPlanUtils.cpp
@@ -662,11 +662,11 @@ bool VPBlockUtils::isHeader(const VPBlockBase *VPB,
bool VPBlockUtils::isLatch(const VPBlockBase *VPB,
const VPDominatorTree &VPDT) {
- // A latch has a header as its second successor, with its other successor
+ // A latch has a header as its last successor, with its other successors
// leaving the loop. A preheader OTOH has a header as its first (and only)
// successor.
- return VPB->getNumSuccessors() == 2 &&
- VPBlockUtils::isHeader(VPB->getSuccessors()[1], VPDT);
+ return VPB->getNumSuccessors() >= 2 &&
+ VPBlockUtils::isHeader(VPB->getSuccessors().back(), VPDT);
}
std::optional<MemoryLocation>
diff --git a/llvm/lib/Transforms/Vectorize/VPlanVerifier.cpp b/llvm/lib/Transforms/Vectorize/VPlanVerifier.cpp
index 1dff1831c305c..61038bcf3f224 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanVerifier.cpp
+++ b/llvm/lib/Transforms/Vectorize/VPlanVerifier.cpp
@@ -385,6 +385,13 @@ bool VPlanVerifier::verifyBlock(const VPBlockBase *VPB) {
return false;
}
}
+ // For plain CFG VPlans (no parent region), verify header/latch structure.
+ if (VPBB && !VPBB->getParent() && VPBlockUtils::isHeader(VPBB, VPDT) &&
+ !VPBlockUtils::isLatch(VPB->getPredecessors()[1], VPDT)) {
+ errs() << "Header's second predecessor must be the latch!\n";
+ return false;
+ }
+
return !VPBB || verifyVPBasicBlock(VPBB);
}
diff --git a/llvm/unittests/Transforms/Vectorize/VPlanVerifierTest.cpp b/llvm/unittests/Transforms/Vectorize/VPlanVerifierTest.cpp
index 2472e18b654e2..8f5c3280d3f22 100644
--- a/llvm/unittests/Transforms/Vectorize/VPlanVerifierTest.cpp
+++ b/llvm/unittests/Transforms/Vectorize/VPlanVerifierTest.cpp
@@ -444,4 +444,74 @@ TEST_F(VPIRVerifierTest, testVerifyIRPhiInExitVPIRBB) {
::testing::internal::GetCapturedStderr().c_str());
#endif
}
+
+TEST_F(VPIRVerifierTest, BranchOnTwoCondsLatchHeaderVerification) {
+ const char *ModuleString =
+ "define void @f(ptr %array, ptr %pred) {\n"
+ "entry:\n"
+ " br label %for.body\n"
+ "for.body:\n"
+ " %iv = phi i64 [ 0, %entry ], [ %iv.next, %for.inc ]\n"
+ " %st.addr = getelementptr inbounds i16, ptr %array, i64 %iv\n"
+ " %data = load i16, ptr %st.addr, align 2\n"
+ " %inc = add nsw i16 %data, 1\n"
+ " store i16 %inc, ptr %st.addr, align 2\n"
+ " %uncountable.addr = getelementptr inbounds nuw i16, ptr %pred, i64 "
+ "%iv\n"
+ " %uncountable.val = load i16, ptr %uncountable.addr, align 2\n"
+ " %uncountable.cond = icmp sgt i16 %uncountable.val, 500\n"
+ " br i1 %uncountable.cond, label %exit, label %for.inc\n"
+ "for.inc:\n"
+ " %iv.next = add nuw nsw i64 %iv, 1\n"
+ " %countable.cond = icmp eq i64 %iv.next, 20\n"
+ " br i1 %countable.cond, label %exit, label %for.body\n"
+ "exit:\n"
+ " ret void\n"
+ "}\n";
+
+ Module &M = parseModule(ModuleString);
+
+ Function *F = M.getFunction("f");
+ BasicBlock *LoopHeader = F->getEntryBlock().getSingleSuccessor();
+ doAnalysis(*F);
+
+ Loop *L = LI->getLoopFor(LoopHeader);
+ PredicatedScalarEvolution PSE(*SE, *L);
+
+ // Build a plain CFG VPlan with BranchOnTwoConds as the latch terminator
+ // (3 successors), without wrapping blocks in loop regions.
+ auto Plan =
+ VPlanTransforms::buildVPlan0(L, *LI, IntegerType::get(*Ctx, 64), {}, PSE);
+ VPlanTransforms::handleEarlyExits(*Plan, /*HasUncountableExit=*/true);
+ VPlanTransforms::addMiddleCheck(*Plan, /*RequiresScalarEpilogueCheck=*/true,
+ /*TailFolded=*/false);
+
+ auto *MiddleVPBB =
+ cast<VPBasicBlock>(Plan->getScalarPreheader()->getPredecessors()[0]);
+ auto *Latch = cast<VPBasicBlock>(MiddleVPBB->getSinglePredecessor());
+ auto *Header = Latch->getSuccessors().back();
+ ASSERT_EQ(Header->getPredecessors()[1], Latch);
+ ASSERT_EQ(Latch->getNumSuccessors(), 3u);
+ auto *Term = cast<VPInstruction>(&Latch->back());
+ EXPECT_EQ(Term->getOpcode(), VPInstruction::BranchOnTwoConds);
+
+ // Verify the plan is valid; this exercises isLatch with a 3-successor latch.
+ EXPECT_TRUE(verifyVPlanIsValid(*Plan));
+
+ // Swap the latch's first and last successors, placing the header at index 0
+ // instead of the last position. isLatch checks the last successor, so the
+ // latch is no longer recognized, triggering the header predecessor check.
+ auto &Succs = Latch->getSuccessors();
+ std::swap(Succs[0], Succs[2]);
+
+#if GTEST_HAS_STREAM_REDIRECTION
+ ::testing::internal::CaptureStderr();
+#endif
+ EXPECT_FALSE(verifyVPlanIsValid(*Plan));
+#if GTEST_HAS_STREAM_REDIRECTION
+ EXPECT_STREQ("Header's second predecessor must be the latch!\n",
+ ::testing::internal::GetCapturedStderr().c_str());
+#endif
+}
+
} // namespace
More information about the llvm-commits
mailing list