[llvm] 79775da - [VPlan] Add vputils::reconstructSSA (#212209)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Sep 10 22:48:55 PDT 2026
Author: Luke Lau
Date: 2026-09-11T05:48:49Z
New Revision: 79775da1838e64b0a6c294ff25483484841910a1
URL: https://github.com/llvm/llvm-project/commit/79775da1838e64b0a6c294ff25483484841910a1
DIFF: https://github.com/llvm/llvm-project/commit/79775da1838e64b0a6c294ff25483484841910a1.diff
LOG: [VPlan] Add vputils::reconstructSSA (#212209)
Adds a simple implementation of the algorithm described in "Simple and
Efficient SSA Construction" by Braun et al.
https://www.researchgate.net/publication/236997796_Simple_and_Efficient_Construction_of_Static_Single_Assignment_Form
This can be used to reconstruct SSA form whenever the control flow graph
is changed, which #201784 plans to use to model predicated early exiting
conditions.
Added three unit tests for now which should exercise all the code paths,
including what happens when there's a cycle.
Added:
Modified:
llvm/lib/Transforms/Vectorize/VPlanUtils.cpp
llvm/lib/Transforms/Vectorize/VPlanUtils.h
llvm/unittests/Transforms/Vectorize/VPlanTest.cpp
Removed:
################################################################################
diff --git a/llvm/lib/Transforms/Vectorize/VPlanUtils.cpp b/llvm/lib/Transforms/Vectorize/VPlanUtils.cpp
index 8327b30c7583e..a39760f3a34d1 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanUtils.cpp
+++ b/llvm/lib/Transforms/Vectorize/VPlanUtils.cpp
@@ -1366,3 +1366,42 @@ void vputils::detail::pullOutPermutationsImpl(
}
}
}
+
+// Implements the algorithm described in "Simple and Efficient Construction of
+// Static Single Assignment Form" by Braun et al.
+VPValue *vputils::reconstructSSA(VPBasicBlock *VPBB,
+ DenseMap<VPBasicBlock *, VPValue *> &Defs) {
+ assert(!Defs.empty() && "Defs shouldn't be empty");
+ assert(VPBB->getPlan() && "VPBB isn't reachable from entry");
+ if (VPValue *Def = Defs.lookup(VPBB))
+ return Def;
+ // If the entry block is reached and there's still no def, then Defs is
+ // missing a definition that covers this path.
+ assert(VPBB->getNumPredecessors() && "Not all paths have def");
+
+ if (VPBlockBase *Pred = VPBB->getSinglePredecessor())
+ return reconstructSSA(cast<VPBasicBlock>(Pred), Defs);
+
+ // Multiple predecessors, create a join.
+ Type *Ty = Defs.begin()->second->getScalarType();
+ VPPhi *Phi = VPBuilder(VPBB, VPBB->getFirstNonPhi())
+ .createScalarPhi({}, DebugLoc::getUnknown(), "", {}, Ty);
+ Defs[VPBB] = Phi;
+ for (auto *Pred : VPBB->predecessors())
+ Phi->addIncoming(reconstructSSA(cast<VPBasicBlock>(Pred), Defs));
+
+ // Fold away trivial phis.
+ // TODO: Remove phi users which have become trivial too.
+ if (all_equal(Phi->incoming_values())) {
+ VPValue *Common = Phi->getIncomingValue(0);
+ Phi->replaceAllUsesWith(Common);
+ for (auto &[_, V] : Defs)
+ if (V == Phi)
+ V = Common;
+ Defs[VPBB] = Common;
+ Phi->eraseFromParent();
+ return Common;
+ }
+
+ return Phi;
+}
diff --git a/llvm/lib/Transforms/Vectorize/VPlanUtils.h b/llvm/lib/Transforms/Vectorize/VPlanUtils.h
index 738a5bc8b8066..d98925635c602 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanUtils.h
+++ b/llvm/lib/Transforms/Vectorize/VPlanUtils.h
@@ -223,6 +223,15 @@ SmallVector<VPUser *> collectUsersRecursively(VPValue *V);
VPIRValue *tryToFoldLiveIns(VPSingleDefRecipe &R, ArrayRef<VPValue *> Operands,
const DataLayout &DL);
+/// Insert phis to reconstruct SSA for a single value starting from \p VPBB. \p
+/// Defs is a map of definitions at specific blocks. Returns the
+/// reconstructed value at VPBB. Use if the CFG has been modified such that a
+/// def no longer dominates all its uses. Every block leading to VPBB must be
+/// reachable from the entry and the plan must be plain-CFG (not contain any
+/// regions).
+LLVM_ABI_FOR_TEST VPValue *
+reconstructSSA(VPBasicBlock *VPBB, DenseMap<VPBasicBlock *, VPValue *> &Defs);
+
/// Denominator of the frequencies computed by computeExecutionFrequencies, i.e.
/// the frequency of a block that always executes. Wider than
/// BranchProbability's 31-bit one, which truncates rarely executed blocks to 0.
diff --git a/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp b/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp
index 8e9b33e99ddb3..079e04e9c7f22 100644
--- a/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp
+++ b/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp
@@ -1900,6 +1900,312 @@ TEST_F(VPUtilsTest, IsUniformAcrossVFsAndUFsForSingleScalarOpcodes) {
EXPECT_FALSE(vputils::isUniformAcrossVFsAndUFs(FirstActiveLaneNonUniform));
}
+static VPValue *reconstructSSA(VPBasicBlock *VPBB,
+ DenseMap<VPBasicBlock *, VPValue *> &&Defs) {
+ return vputils::reconstructSSA(VPBB, Defs);
+}
+
+TEST_F(VPUtilsTest, ReconstructSSA) {
+ VPlan &Plan = getPlan();
+ VPBasicBlock *VPBB1 = Plan.getEntry();
+ VPBasicBlock *VPBB2 = Plan.createVPBasicBlock("");
+ VPBasicBlock *VPBB3 = Plan.createVPBasicBlock("");
+ VPBasicBlock *VPBB4 = Plan.createVPBasicBlock("");
+
+ // VPBB1
+ // / \
+ // VPBB2 VPBB3
+ // \ /
+ // VPBB4
+ VPBlockUtils::connectBlocks(VPBB1, VPBB2);
+ VPBlockUtils::connectBlocks(VPBB1, VPBB3);
+ VPBlockUtils::connectBlocks(VPBB2, VPBB4);
+ VPBlockUtils::connectBlocks(VPBB3, VPBB4);
+
+ VPValue *C = Plan.getConstantInt(32, 1);
+ VPIRFlags AddFlags = VPIRFlags::getDefaultFlags(Instruction::Add);
+ auto *Def1 = new VPInstruction(Instruction::Add, {C, C}, AddFlags);
+ VPBB1->appendRecipe(Def1);
+ auto *Def2 = new VPInstruction(Instruction::Add, {C, C}, AddFlags);
+ VPBB2->appendRecipe(Def2);
+
+ auto *Res =
+ cast<VPPhi>(reconstructSSA(VPBB4, {{VPBB1, Def1}, {VPBB2, Def2}}));
+ EXPECT_EQ(Res->getIncomingValueForBlock(VPBB2), Def2);
+ EXPECT_EQ(Res->getIncomingValueForBlock(VPBB3), Def1);
+}
+
+TEST_F(VPUtilsTest, ReconstructSSAPoisonExample) {
+ // Test that the resulting phi isn't affected by the parent block of any
+ // definition. The resulting value in VPBB4 should be Def1 on any path that
+ // goes through VPPB2, and poison otherwise.
+ VPlan &Plan = getPlan();
+ VPBasicBlock *VPBB1 = Plan.getEntry();
+ VPBasicBlock *VPBB2 = Plan.createVPBasicBlock("");
+ VPBasicBlock *VPBB3 = Plan.createVPBasicBlock("");
+ VPBasicBlock *VPBB4 = Plan.createVPBasicBlock("");
+
+ // VPBB1
+ // / \
+ // VPBB2 VPBB3
+ // \ /
+ // VPBB4
+ VPBlockUtils::connectBlocks(VPBB1, VPBB2);
+ VPBlockUtils::connectBlocks(VPBB1, VPBB3);
+ VPBlockUtils::connectBlocks(VPBB2, VPBB4);
+ VPBlockUtils::connectBlocks(VPBB3, VPBB4);
+
+ VPValue *C = Plan.getConstantInt(32, 1);
+ VPValue *Poison = Plan.getPoison(C->getScalarType());
+ VPIRFlags AddFlags = VPIRFlags::getDefaultFlags(Instruction::Add);
+ auto *Def1 = new VPInstruction(Instruction::Add, {C, C}, AddFlags);
+ VPBB1->appendRecipe(Def1);
+
+ auto *Res =
+ cast<VPPhi>(reconstructSSA(VPBB4, {{VPBB1, Poison}, {VPBB2, Def1}}));
+ EXPECT_EQ(Res->getIncomingValueForBlock(VPBB2), Def1);
+ EXPECT_EQ(Res->getIncomingValueForBlock(VPBB3), Poison);
+}
+
+TEST_F(VPUtilsTest, ReconstructSSAMultiplePhis) {
+ VPlan &Plan = getPlan();
+ VPBasicBlock *VPBB1 = Plan.getEntry();
+ VPBasicBlock *VPBB2 = Plan.createVPBasicBlock("");
+ VPBasicBlock *VPBB3 = Plan.createVPBasicBlock("");
+ VPBasicBlock *VPBB4 = Plan.createVPBasicBlock("");
+ VPBasicBlock *VPBB5 = Plan.createVPBasicBlock("");
+ VPBasicBlock *VPBB6 = Plan.createVPBasicBlock("");
+
+ // VPBB1
+ // / \
+ // VPBB2 VPBB3
+ // \ / \
+ // VPBB4 VPBB5
+ // \ /
+ // VPBB6
+ VPBlockUtils::connectBlocks(VPBB1, VPBB2);
+ VPBlockUtils::connectBlocks(VPBB1, VPBB3);
+ VPBlockUtils::connectBlocks(VPBB2, VPBB4);
+ VPBlockUtils::connectBlocks(VPBB3, VPBB4);
+ VPBlockUtils::connectBlocks(VPBB3, VPBB5);
+ VPBlockUtils::connectBlocks(VPBB4, VPBB6);
+ VPBlockUtils::connectBlocks(VPBB5, VPBB6);
+
+ VPValue *C = Plan.getConstantInt(32, 1);
+ VPIRFlags AddFlags = VPIRFlags::getDefaultFlags(Instruction::Add);
+ auto *Def2 = new VPInstruction(Instruction::Add, {C, C}, AddFlags);
+ VPBB2->appendRecipe(Def2);
+ auto *Def3 = new VPInstruction(Instruction::Add, {C, C}, AddFlags);
+ VPBB3->appendRecipe(Def3);
+
+ auto *Phi6 =
+ cast<VPPhi>(reconstructSSA(VPBB6, {{VPBB2, Def2}, {VPBB3, Def3}}));
+ EXPECT_EQ(Phi6->getIncomingValueForBlock(VPBB5), Def3);
+ EXPECT_TRUE(isa<VPPhi>(Phi6->getIncomingValueForBlock(VPBB4)));
+
+ auto *Phi4 = cast<VPPhi>(Phi6->getIncomingValueForBlock(VPBB4));
+ EXPECT_EQ(Phi4->getIncomingValueForBlock(VPBB2), Def2);
+ EXPECT_EQ(Phi4->getIncomingValueForBlock(VPBB3), Def3);
+}
+
+TEST_F(VPUtilsTest, ReconstructSSAFold) {
+ VPlan &Plan = getPlan();
+ VPBasicBlock *VPBB1 = Plan.getEntry();
+ VPBasicBlock *VPBB2 = Plan.createVPBasicBlock("");
+ VPBasicBlock *VPBB3 = Plan.createVPBasicBlock("");
+ VPBasicBlock *VPBB4 = Plan.createVPBasicBlock("");
+
+ // VPBB1
+ // / \
+ // VPBB2 VPBB3
+ // \ /
+ // VPBB4
+ VPBlockUtils::connectBlocks(VPBB1, VPBB2);
+ VPBlockUtils::connectBlocks(VPBB1, VPBB3);
+ VPBlockUtils::connectBlocks(VPBB2, VPBB4);
+ VPBlockUtils::connectBlocks(VPBB3, VPBB4);
+
+ VPValue *C = Plan.getConstantInt(32, 1);
+ VPIRFlags AddFlags = VPIRFlags::getDefaultFlags(Instruction::Add);
+ auto *Def = new VPInstruction(Instruction::Add, {C, C}, AddFlags);
+ VPBB1->appendRecipe(Def);
+
+ // Check that phis with all equal incoming values are folded away.
+ EXPECT_EQ(reconstructSSA(VPBB4, {{VPBB2, Def}, {VPBB3, Def}}), Def);
+}
+
+TEST_F(VPUtilsTest, ReconstructSSACycle) {
+ VPlan &Plan = getPlan();
+ VPBasicBlock *VPBB1 = Plan.getEntry();
+ VPBasicBlock *VPBB2 = Plan.createVPBasicBlock("");
+ VPBasicBlock *VPBB3 = Plan.createVPBasicBlock("");
+ VPBasicBlock *VPBB4 = Plan.createVPBasicBlock("");
+
+ // VPBB1
+ // |
+ // VPBB2
+ // / | ^
+ // VPBB3 | |
+ // \ | /
+ // VPBB4
+ VPBlockUtils::connectBlocks(VPBB1, VPBB2);
+ VPBlockUtils::connectBlocks(VPBB2, VPBB3);
+ VPBlockUtils::connectBlocks(VPBB2, VPBB4);
+ VPBlockUtils::connectBlocks(VPBB3, VPBB4);
+ VPBlockUtils::connectBlocks(VPBB4, VPBB2);
+
+ VPValue *C = Plan.getConstantInt(32, 1);
+ VPIRFlags AddFlags = VPIRFlags::getDefaultFlags(Instruction::Add);
+ auto *Def1 = new VPInstruction(Instruction::Add, {C, C}, AddFlags);
+ VPBB1->appendRecipe(Def1);
+ auto *Def2 = new VPInstruction(Instruction::Add, {C, C}, AddFlags);
+ VPBB3->appendRecipe(Def2);
+
+ auto *Phi1 =
+ cast<VPPhi>(reconstructSSA(VPBB4, {{VPBB1, Def1}, {VPBB3, Def2}}));
+ EXPECT_EQ(Phi1->getIncomingValueForBlock(VPBB3), Def2);
+ EXPECT_TRUE(isa<VPPhi>(Phi1->getIncomingValueForBlock(VPBB2)));
+
+ auto *Phi2 = cast<VPPhi>(Phi1->getIncomingValueForBlock(VPBB2));
+ EXPECT_EQ(Phi2->getIncomingValueForBlock(VPBB4), Phi1);
+ EXPECT_EQ(Phi2->getIncomingValueForBlock(VPBB1), Def1);
+}
+
+#if defined(GTEST_HAS_DEATH_TEST) && !defined(NDEBUG)
+TEST_F(VPUtilsTest, ReconstructSSAUnreachableCycle) {
+ VPlan &Plan = getPlan();
+ VPBasicBlock *VPBB1 = Plan.getEntry();
+ VPBasicBlock *VPBB2 = Plan.createVPBasicBlock("");
+
+ // VPBB1 VPBB2 <-+
+ // | |
+ // +-----+
+ VPBlockUtils::connectBlocks(VPBB2, VPBB2);
+
+ VPValue *C = Plan.getConstantInt(32, 1);
+ VPIRFlags AddFlags = VPIRFlags::getDefaultFlags(Instruction::Add);
+ auto *Def1 = new VPInstruction(Instruction::Add, {C, C}, AddFlags);
+ VPBB1->appendRecipe(Def1);
+
+ EXPECT_DEATH(reconstructSSA(VPBB2, {{VPBB1, Def1}}),
+ "VPlan without any entry node without predecessors");
+}
+
+TEST_F(VPUtilsTest, ReconstructSSAUnreachableCyclePredecessor) {
+ VPlan &Plan = getPlan();
+ VPBasicBlock *VPBB1 = Plan.getEntry();
+ VPBasicBlock *VPBB2 = Plan.createVPBasicBlock("");
+ VPBasicBlock *VPBB3 = Plan.createVPBasicBlock("");
+
+ // VPBB1 VPBB2 <-+
+ // \ / | |
+ // \ / +-----+
+ // VPBB3
+ VPBlockUtils::connectBlocks(VPBB2, VPBB2);
+ VPBlockUtils::connectBlocks(VPBB1, VPBB3);
+ VPBlockUtils::connectBlocks(VPBB2, VPBB3);
+
+ VPValue *C = Plan.getConstantInt(32, 1);
+ VPIRFlags AddFlags = VPIRFlags::getDefaultFlags(Instruction::Add);
+ auto *Def1 = new VPInstruction(Instruction::Add, {C, C}, AddFlags);
+ VPBB1->appendRecipe(Def1);
+
+ EXPECT_DEATH(reconstructSSA(VPBB3, {{VPBB1, Def1}}),
+ "VPlan without any entry node without predecessors");
+}
+#endif
+
+TEST_F(VPUtilsTest, ReconstructSSADuplicatePredecessor) {
+ VPlan &Plan = getPlan();
+ VPBasicBlock *VPBB1 = Plan.getEntry();
+ VPBasicBlock *VPBB2 = Plan.createVPBasicBlock("");
+ VPBasicBlock *VPBB3 = Plan.createVPBasicBlock("");
+
+ // VPBB1
+ // / \ \
+ // | | VPBB2
+ // \ / /
+ // VPBB3
+ VPBlockUtils::connectBlocks(VPBB1, VPBB3);
+ VPBlockUtils::connectBlocks(VPBB1, VPBB3);
+ VPBlockUtils::connectBlocks(VPBB1, VPBB2);
+ VPBlockUtils::connectBlocks(VPBB2, VPBB3);
+
+ VPValue *C = Plan.getConstantInt(32, 1);
+ VPIRFlags AddFlags = VPIRFlags::getDefaultFlags(Instruction::Add);
+ auto *Def1 = new VPInstruction(Instruction::Add, {C, C}, AddFlags);
+ VPBB1->appendRecipe(Def1);
+ auto *Def2 = new VPInstruction(Instruction::Add, {C, C}, AddFlags);
+ VPBB2->appendRecipe(Def2);
+
+ auto *Phi =
+ cast<VPPhi>(reconstructSSA(VPBB3, {{VPBB1, Def1}, {VPBB2, Def2}}));
+ EXPECT_EQ(Phi->getIncomingValue(0), Def1);
+ EXPECT_EQ(Phi->getIncomingValue(1), Def1);
+ EXPECT_EQ(Phi->getIncomingValue(2), Def2);
+}
+
+TEST_F(VPUtilsTest, ReconstructSSADuplicatePredecessorAllEqual) {
+ VPlan &Plan = getPlan();
+ VPBasicBlock *VPBB1 = Plan.getEntry();
+ VPBasicBlock *VPBB2 = Plan.createVPBasicBlock("");
+ VPBasicBlock *VPBB3 = Plan.createVPBasicBlock("");
+
+ // VPBB1
+ // / \ \
+ // / \ \
+ // VPBB2 VPBB3
+ VPBlockUtils::connectBlocks(VPBB1, VPBB2);
+ VPBlockUtils::connectBlocks(VPBB1, VPBB3);
+ VPBlockUtils::connectBlocks(VPBB1, VPBB3);
+
+ VPValue *C = Plan.getConstantInt(32, 1);
+ VPIRFlags AddFlags = VPIRFlags::getDefaultFlags(Instruction::Add);
+ auto *Def1 = new VPInstruction(Instruction::Add, {C, C}, AddFlags);
+ VPBB1->appendRecipe(Def1);
+
+ EXPECT_EQ(reconstructSSA(VPBB3, {{VPBB1, Def1}}), Def1);
+ EXPECT_EQ(reconstructSSA(VPBB2, {{VPBB1, Def1}}), Def1);
+}
+
+TEST_F(VPUtilsTest, ReconstructSSAStaleDefAfterFold) {
+ VPlan &Plan = getPlan();
+ VPBasicBlock *VPBB1 = Plan.getEntry();
+ VPBasicBlock *VPBB2 = Plan.createVPBasicBlock("");
+ VPBasicBlock *VPBB3 = Plan.createVPBasicBlock("");
+ VPBasicBlock *VPBB4 = Plan.createVPBasicBlock("");
+ VPBasicBlock *VPBB5 = Plan.createVPBasicBlock("");
+
+ // VPBB1
+ // |
+ // +-> VPBB2
+ // | | |
+ // | VPBB3 --+
+ // | | | |
+ // +-- VPBB4 |
+ // | |
+ // VPBB5 <-+
+ VPBlockUtils::connectBlocks(VPBB1, VPBB2);
+ VPBlockUtils::connectBlocks(VPBB4, VPBB2);
+ VPBlockUtils::connectBlocks(VPBB2, VPBB3);
+ VPBlockUtils::connectBlocks(VPBB2, VPBB3);
+ VPBlockUtils::connectBlocks(VPBB3, VPBB4);
+ VPBlockUtils::connectBlocks(VPBB3, VPBB4);
+ VPBlockUtils::connectBlocks(VPBB3, VPBB5);
+ VPBlockUtils::connectBlocks(VPBB4, VPBB5);
+
+ VPValue *C = Plan.getConstantInt(32, 1);
+ VPIRFlags AddFlags = VPIRFlags::getDefaultFlags(Instruction::Add);
+ auto *Def = new VPInstruction(Instruction::Add, {C, C}, AddFlags);
+ VPBB1->appendRecipe(Def);
+
+ // The phi VPBB4 is folded to the phi in VPBB3, then the phi in VPBB3 is
+ // folded to the phi in VPBB2. Make sure that we update Defs[VPBB4] from
+ // phi at VPPB3->phi at VPBB2.
+ auto *Res = cast<VPPhi>(reconstructSSA(VPBB5, {{VPBB1, Def}}));
+ EXPECT_EQ(Res->getParent(), VPBB2);
+}
+
TEST_F(VPBasicBlockTest, VPRegionValueClonePropagatesMaterialized) {
VPlan &Plan = getPlan();
VPBasicBlock *Preheader = Plan.getEntry();
More information about the llvm-commits
mailing list