[llvm] [VPlan] Add vputils::reconstructSSA (PR #212209)

Luke Lau via llvm-commits llvm-commits at lists.llvm.org
Sun Aug 2 23:15:26 PDT 2026


https://github.com/lukel97 updated https://github.com/llvm/llvm-project/pull/212209

>From 032d55446433d3437a94dc9d47d3aeec39029ef6 Mon Sep 17 00:00:00 2001
From: Luke Lau <luke at igalia.com>
Date: Mon, 27 Jul 2026 16:54:22 +0800
Subject: [PATCH 1/3] [VPlan] Add vputils::reconstructSSA

Split off from #201784

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.
---
 llvm/lib/Transforms/Vectorize/VPlanUtils.cpp  | 38 ++++++++
 llvm/lib/Transforms/Vectorize/VPlanUtils.h    |  6 ++
 .../Transforms/Vectorize/VPlanTest.cpp        | 93 +++++++++++++++++++
 3 files changed, 137 insertions(+)

diff --git a/llvm/lib/Transforms/Vectorize/VPlanUtils.cpp b/llvm/lib/Transforms/Vectorize/VPlanUtils.cpp
index 0bcd4dd8aba5b..487f965c65916 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanUtils.cpp
+++ b/llvm/lib/Transforms/Vectorize/VPlanUtils.cpp
@@ -1143,3 +1143,41 @@ void vputils::detail::pullOutPermutationsImpl(
     }
   }
 }
+
+/// Implements the algorithm described in "Simple and Efficient Construction of
+/// Static Single Assignment Form" by Braun et al.
+static VPValue *reconstructSSAImpl(VPBasicBlock *VPBB,
+                                   DenseMap<VPBasicBlock *, VPValue *> &Defs) {
+  if (VPValue *Def = Defs.lookup(VPBB))
+    return Def;
+  assert(VPBB->getNumPredecessors() && "Not all paths have def");
+
+  if (VPBlockBase *Pred = VPBB->getSinglePredecessor())
+    return reconstructSSAImpl(cast<VPBasicBlock>(Pred), Defs);
+
+  // Multiple predecessors, create a join.
+  Type *Ty = Defs.begin()->second->getScalarType();
+  auto *Phi = new VPPhi({}, {}, DebugLoc::getUnknown(), "", Ty);
+  VPBB->insert(Phi, VPBB->getFirstNonPhi());
+  Defs[VPBB] = Phi;
+  for (auto *Pred : VPBB->predecessors())
+    Phi->addIncoming(reconstructSSAImpl(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);
+    Phi->eraseFromParent();
+    Defs[VPBB] = Common;
+    return Common;
+  }
+
+  return Phi;
+}
+
+VPValue *vputils::reconstructSSA(DenseMap<VPBasicBlock *, VPValue *> Defs,
+                                 VPBasicBlock *VPBB) {
+  assert(!Defs.empty() && "Defs shouldn't be empty");
+  return reconstructSSAImpl(VPBB, Defs);
+}
diff --git a/llvm/lib/Transforms/Vectorize/VPlanUtils.h b/llvm/lib/Transforms/Vectorize/VPlanUtils.h
index 8c0690a82cf95..88de0d4ee7dc4 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanUtils.h
+++ b/llvm/lib/Transforms/Vectorize/VPlanUtils.h
@@ -214,6 +214,12 @@ 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.
+VPValue *reconstructSSA(DenseMap<VPBasicBlock *, VPValue *> Defs,
+                        VPBasicBlock *VPBB);
 namespace detail {
 
 /// Template-independent implementation for pullOutPermutations.
diff --git a/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp b/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp
index f10435dfb2da6..d8fba47428389 100644
--- a/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp
+++ b/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp
@@ -1898,6 +1898,99 @@ TEST_F(VPUtilsTest, IsUniformAcrossVFsAndUFsForSingleScalarOpcodes) {
   EXPECT_FALSE(vputils::isUniformAcrossVFsAndUFs(FirstActiveLaneNonUniform));
 }
 
+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>(
+      vputils::reconstructSSA({{VPBB1, Def1}, {VPBB2, Def2}}, VPBB4));
+  EXPECT_EQ(Res->getIncomingValueForBlock(VPBB2), Def2);
+  EXPECT_EQ(Res->getIncomingValueForBlock(VPBB3), Def1);
+}
+
+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(vputils::reconstructSSA({{VPBB2, Def}, {VPBB3, Def}}, VPBB4), 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>(
+      vputils::reconstructSSA({{VPBB1, Def1}, {VPBB3, Def2}}, VPBB4));
+  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);
+}
+
 TEST_F(VPBasicBlockTest, VPRegionValueClonePropagatesMaterialized) {
   VPlan &Plan = getPlan();
   VPBasicBlock *Preheader = Plan.getEntry();

>From bab0cc606489d7b4de592fb7af6fd8aa328ebacf Mon Sep 17 00:00:00 2001
From: Luke Lau <luke at igalia.com>
Date: Tue, 28 Jul 2026 01:47:39 +0800
Subject: [PATCH 2/3] Add test with poison and test with multiple phis

---
 .../Transforms/Vectorize/VPlanTest.cpp        | 73 +++++++++++++++++++
 1 file changed, 73 insertions(+)

diff --git a/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp b/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp
index d8fba47428389..17d6a176dc490 100644
--- a/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp
+++ b/llvm/unittests/Transforms/Vectorize/VPlanTest.cpp
@@ -1928,6 +1928,79 @@ TEST_F(VPUtilsTest, ReconstructSSA) {
   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>(
+      vputils::reconstructSSA({{VPBB1, Poison}, {VPBB2, Def1}}, VPBB4));
+  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>(
+      vputils::reconstructSSA({{VPBB2, Def2}, {VPBB3, Def3}}, VPBB6));
+  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();

>From b23526e7ae8eb8c7a633b3bde837b9c00e8fec86 Mon Sep 17 00:00:00 2001
From: Luke Lau <luke at igalia.com>
Date: Tue, 28 Jul 2026 12:09:09 +0800
Subject: [PATCH 3/3] Add comment

---
 llvm/lib/Transforms/Vectorize/VPlanUtils.cpp | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/llvm/lib/Transforms/Vectorize/VPlanUtils.cpp b/llvm/lib/Transforms/Vectorize/VPlanUtils.cpp
index 487f965c65916..82fb8140d9d55 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanUtils.cpp
+++ b/llvm/lib/Transforms/Vectorize/VPlanUtils.cpp
@@ -1150,6 +1150,8 @@ static VPValue *reconstructSSAImpl(VPBasicBlock *VPBB,
                                    DenseMap<VPBasicBlock *, VPValue *> &Defs) {
   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())



More information about the llvm-commits mailing list