[llvm] [SimplifyCFG] Add foldCondStoreToSelect optimization (PR #207654)

Karthika Devi C via llvm-commits llvm-commits at lists.llvm.org
Mon Jul 6 05:10:21 PDT 2026


https://github.com/kartcq updated https://github.com/llvm/llvm-project/pull/207654

>From eafe0f09205a0e5f526f48ddb4842a60f0e72ae7 Mon Sep 17 00:00:00 2001
From: Karthika Devi C <kartc at qti.qualcomm.com>
Date: Tue, 30 Jun 2026 10:39:17 -0700
Subject: [PATCH 1/3] [SimplifyCFG] Add foldCondStoreToSelect optimization

Add a new SimplifyCFG transform that folds a triangle+diamond CFG pattern
where three branches all store to the same address into a single
select+unconditional store in the head block.

Pattern matched:
  HeadBB: br i1 %cond1, ThenBB, ElseBB
  ThenBB: store VThen, addr; br MergeBB
  ElseBB: [cheap instrs]; br i1 %cond2, ElseThenBB, ElseElseBB
  ElseThenBB: store VElseThen, addr; br MergeBB
  ElseElseBB: store VElseElse, addr; br MergeBB

Transformed to:
  HeadBB: %sel = select cond1, VThen, (select cond2, VElseThen, VElseElse)
          store %sel, addr
          br MergeBB

When VThen == VElseThen, the select simplifies to:
  select (cond1 || cond2), VThen, VElseElse

This enables loop vectorization of patterns like:
  if (a) dst[i]=X; else if (b) dst[i]=X; else dst[i]=Y;

The transform is guarded by a new SimplifyCFGOptions flag
(FoldCondStoreToSelect) and uses TTI::isExpensiveToSpeculativelyExecute
for profitability checks. It is enabled in the function simplification
and vector pass pipelines at -O2 and above.

Fixes #207651
---
 .../Transforms/Utils/SimplifyCFGOptions.h     |   5 +
 llvm/lib/Passes/PassBuilder.cpp               |   2 +
 llvm/lib/Passes/PassBuilderPipelines.cpp      |   6 +-
 llvm/lib/Passes/PassRegistry.def              |   1 +
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp     | 282 ++++++++++++++++++
 5 files changed, 294 insertions(+), 2 deletions(-)

diff --git a/llvm/include/llvm/Transforms/Utils/SimplifyCFGOptions.h b/llvm/include/llvm/Transforms/Utils/SimplifyCFGOptions.h
index 2d0f95741077f..368adb4378a96 100644
--- a/llvm/include/llvm/Transforms/Utils/SimplifyCFGOptions.h
+++ b/llvm/include/llvm/Transforms/Utils/SimplifyCFGOptions.h
@@ -33,6 +33,7 @@ struct SimplifyCFGOptions {
   bool SimplifyCondBranch = true;
   bool SpeculateBlocks = true;
   bool SpeculateUnpredictables = false;
+  bool FoldCondStoreToSelect = false;
 
   AssumptionCache *AC = nullptr;
 
@@ -90,6 +91,10 @@ struct SimplifyCFGOptions {
     SpeculateUnpredictables = B;
     return *this;
   }
+  SimplifyCFGOptions &foldCondStoreToSelect(bool B) {
+    FoldCondStoreToSelect = B;
+    return *this;
+  }
 };
 
 } // namespace llvm
diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp
index 43c76c74c8930..831f89e543897 100644
--- a/llvm/lib/Passes/PassBuilder.cpp
+++ b/llvm/lib/Passes/PassBuilder.cpp
@@ -1241,6 +1241,8 @@ Expected<SimplifyCFGOptions> parseSimplifyCFGOptions(StringRef Params) {
       Result.sinkCommonInsts(Enable);
     } else if (ParamName == "speculate-unpredictables") {
       Result.speculateUnpredictables(Enable);
+    } else if (ParamName == "fold-cond-store-to-select") {
+      Result.foldCondStoreToSelect(Enable);
     } else if (Enable && ParamName.consume_front("bonus-inst-threshold=")) {
       APInt BonusInstThreshold;
       if (ParamName.getAsInteger(0, BonusInstThreshold))
diff --git a/llvm/lib/Passes/PassBuilderPipelines.cpp b/llvm/lib/Passes/PassBuilderPipelines.cpp
index 6b8f3c8806c86..0f243f01de704 100644
--- a/llvm/lib/Passes/PassBuilderPipelines.cpp
+++ b/llvm/lib/Passes/PassBuilderPipelines.cpp
@@ -828,7 +828,8 @@ PassBuilder::buildFunctionSimplificationPipeline(OptimizationLevel Level,
                                   .convertSwitchRangeToICmp(true)
                                   .convertSwitchToArithmetic(true)
                                   .hoistCommonInsts(true)
-                                  .sinkCommonInsts(true)));
+                                  .sinkCommonInsts(true)
+                                  .foldCondStoreToSelect(true)));
   FPM.addPass(InstCombinePass());
   invokePeepholeEPCallbacks(FPM, Level);
 
@@ -1436,7 +1437,8 @@ void PassBuilder::addVectorPasses(OptimizationLevel Level,
                                   .convertSwitchToLookupTable(true)
                                   .needCanonicalLoops(false)
                                   .hoistCommonInsts(true)
-                                  .sinkCommonInsts(true)));
+                                  .sinkCommonInsts(true)
+                                  .foldCondStoreToSelect(true)));
 
   if (isFullLTOPostLink(LTOPhase)) {
     FPM.addPass(SCCPPass());
diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def
index 04fa421cebb4b..16e4d51e69ed5 100644
--- a/llvm/lib/Passes/PassRegistry.def
+++ b/llvm/lib/Passes/PassRegistry.def
@@ -717,6 +717,7 @@ FUNCTION_PASS_WITH_PARAMS(
     "hoist-common-insts;no-hoist-loads-stores-with-cond-faulting;"
     "hoist-loads-stores-with-cond-faulting;no-sink-common-insts;"
     "sink-common-insts;no-speculate-unpredictables;speculate-unpredictables;"
+    "no-fold-cond-store-to-select;fold-cond-store-to-select;"
     "bonus-inst-threshold=N")
 FUNCTION_PASS_WITH_PARAMS(
     "speculative-execution", "SpeculativeExecutionPass",
diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 98593129cbb7f..374abb4a37a7b 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -4292,6 +4292,283 @@ static StoreInst *findUniqueStoreInBlocks(BasicBlock *BB1, BasicBlock *BB2) {
   return S;
 }
 
+/// Return true if two pointer-producing values refer to the same address.
+/// Strips pointer casts and matches identical GEPs with the same base and
+/// indices.
+static bool isSameGEPAddress(Value *A, Value *B) {
+  if (A == B)
+    return true;
+  A = A->stripPointerCasts();
+  B = B->stripPointerCasts();
+  if (A == B)
+    return true;
+  auto *GA = dyn_cast<GetElementPtrInst>(A);
+  auto *GB = dyn_cast<GetElementPtrInst>(B);
+  if (!GA || !GB)
+    return false;
+  if (GA->getPointerOperand()->stripPointerCasts() !=
+      GB->getPointerOperand()->stripPointerCasts())
+    return false;
+  if (GA->getSourceElementType() != GB->getSourceElementType())
+    return false;
+  if (GA->getNumIndices() != GB->getNumIndices())
+    return false;
+  return std::equal(GA->idx_begin(), GA->idx_end(), GB->idx_begin());
+}
+
+/// Find the unique simple store in \p BB. Returns nullptr if there is no
+/// store, if there are multiple stores, or if the store is not simple.
+static StoreInst *findUniqueSimpleStoreInBlock(BasicBlock *BB) {
+  StoreInst *Found = nullptr;
+  for (Instruction &I : *BB) {
+    auto *SI = dyn_cast<StoreInst>(&I);
+    if (!SI)
+      continue;
+    if (!SI->isSimple() || Found)
+      return nullptr;
+    Found = SI;
+  }
+  return Found;
+}
+
+/// Return true if none of the non-terminator, non-store instructions in \p BB
+/// are too expensive to speculate. Uses TTI::isExpensiveToSpeculativelyExecute
+/// which is the established LLVM norm for this check (e.g. CodeGenPrepare).
+/// \p FoldedStore is the store being folded and is considered free.
+static bool isBlockCheapToSpeculate(BasicBlock *BB, StoreInst *FoldedStore,
+                                    const TargetTransformInfo &TTI) {
+  for (Instruction &I : *BB) {
+    if (I.isTerminator())
+      continue;
+    if (&I == FoldedStore)
+      continue;
+    if (TTI.isExpensiveToSpeculativelyExecute(&I))
+      return false;
+  }
+  return true;
+}
+
+/// Fold a triangle+diamond CFG pattern where all three branches store to the
+/// same address into a single select+store in the head block.
+///
+///   HeadBB: br i1 %cond1, ThenBB, ElseBB
+///   ThenBB: store VThen, addr;  br MergeBB
+///   ElseBB: [cheap instrs]; br i1 %cond2, ElseThenBB, ElseElseBB
+///   ElseThenBB:  store VElseThen, addr;  br MergeBB
+///   ElseElseBB:  store VElseElse, addr;  br MergeBB
+///
+/// Transforms to:
+///   HeadBB: [hoisted ElseBB instrs]
+///           %sel = select cond1, VThen, (select cond2, VElseThen, VElseElse)
+///           store %sel, addr
+///           br MergeBB
+///
+/// The profitability check uses TTI::isExpensiveToSpeculativelyExecute on the
+/// four speculated blocks (ThenBB, ElseBB, ElseThenBB, ElseElseBB) — HeadBB
+/// instructions already execute unconditionally so they are not checked.
+static bool foldCondStoreToSelectImpl(BranchInst *BI, DomTreeUpdater *DTU,
+                                      const TargetTransformInfo &TTI) {
+  if (!BI->isConditional())
+    return false;
+
+  BasicBlock *HeadBB = BI->getParent();
+  BasicBlock *ThenBB = BI->getSuccessor(0);
+  BasicBlock *ElseBB = BI->getSuccessor(1);
+
+  // ThenBB: single predecessor (HeadBB), one simple store, unconditional
+  // branch to MergeBB.
+  if (ThenBB->getSinglePredecessor() != HeadBB)
+    return false;
+  BranchInst *ThenTerm = dyn_cast<BranchInst>(ThenBB->getTerminator());
+  if (!ThenTerm || ThenTerm->isConditional())
+    return false;
+  BasicBlock *MergeBB = ThenTerm->getSuccessor(0);
+
+  StoreInst *ThenStore = findUniqueSimpleStoreInBlock(ThenBB);
+  if (!ThenStore)
+    return false;
+
+  // ElseBB: single predecessor (HeadBB), conditional branch to two leaf
+  // blocks. May contain cheap instructions before the branch (e.g. icmp).
+  if (ElseBB->getSinglePredecessor() != HeadBB)
+    return false;
+  BranchInst *ElseTerm = dyn_cast<BranchInst>(ElseBB->getTerminator());
+  if (!ElseTerm || !ElseTerm->isConditional())
+    return false;
+
+  // Collect non-terminator instructions in ElseBB to hoist into HeadBB.
+  // They must be side-effect-free and not read/write memory.
+  SmallVector<Instruction *, 4> ElseBBInstsToHoist;
+  for (Instruction &I : *ElseBB) {
+    if (&I == ElseTerm)
+      break;
+    if (I.mayHaveSideEffects() || I.mayReadOrWriteMemory())
+      return false;
+    ElseBBInstsToHoist.push_back(&I);
+  }
+
+  BasicBlock *ElseThenBB = ElseTerm->getSuccessor(0);
+  BasicBlock *ElseElseBB = ElseTerm->getSuccessor(1);
+
+  // Each leaf block: single predecessor (ElseBB), one simple store,
+  // unconditional branch to MergeBB.
+  auto CheckLeafBlock = [&](BasicBlock *BB, StoreInst *&Store) -> bool {
+    if (BB->getSinglePredecessor() != ElseBB)
+      return false;
+    BranchInst *Term = dyn_cast<BranchInst>(BB->getTerminator());
+    if (!Term || Term->isConditional() || Term->getSuccessor(0) != MergeBB)
+      return false;
+    Store = findUniqueSimpleStoreInBlock(BB);
+    return Store != nullptr;
+  };
+
+  StoreInst *ElseThenStore, *ElseElseStore;
+  if (!CheckLeafBlock(ElseThenBB, ElseThenStore) ||
+      !CheckLeafBlock(ElseElseBB, ElseElseStore))
+    return false;
+
+  // All three stores must write to the same logical address and have the same
+  // value type.
+  Value *Addr = ThenStore->getPointerOperand();
+  if (!isSameGEPAddress(Addr, ElseThenStore->getPointerOperand()) ||
+      !isSameGEPAddress(Addr, ElseElseStore->getPointerOperand()))
+    return false;
+
+  Type *StoreTy = ThenStore->getValueOperand()->getType();
+  if (ElseThenStore->getValueOperand()->getType() != StoreTy ||
+      ElseElseStore->getValueOperand()->getType() != StoreTy)
+    return false;
+
+  // Profitability: reject if any of the four speculated blocks contains an
+  // instruction that is expensive to execute speculatively (e.g. fdiv).
+  // HeadBB is excluded — its instructions already run unconditionally.
+  if (!isBlockCheapToSpeculate(ThenBB, ThenStore, TTI) ||
+      !isBlockCheapToSpeculate(ElseBB, nullptr, TTI) ||
+      !isBlockCheapToSpeculate(ElseThenBB, ElseThenStore, TTI) ||
+      !isBlockCheapToSpeculate(ElseElseBB, ElseElseStore, TTI))
+    return false;
+
+  // MergeBB PHI nodes: all three leaf predecessors must supply the same value
+  // (since we're collapsing them into HeadBB).
+  for (PHINode &PHI : MergeBB->phis()) {
+    Value *Val = nullptr;
+    for (BasicBlock *Pred : {ThenBB, ElseThenBB, ElseElseBB}) {
+      int Idx = PHI.getBasicBlockIndex(Pred);
+      if (Idx < 0)
+        continue;
+      Value *InVal = PHI.getIncomingValue(Idx);
+      if (!Val)
+        Val = InVal;
+      else if (Val != InVal)
+        return false;
+    }
+  }
+
+  // All checks passed. Build select+store in HeadBB.
+  errs() << "[foldCondStore] firing in function: "
+         << HeadBB->getParent()->getName() << "\n";
+  IRBuilder<> Builder(BI);
+
+  // Hoist cheap ElseBB instructions (e.g. icmp for cond2) into HeadBB.
+  for (Instruction *I : ElseBBInstsToHoist)
+    I->moveBefore(BI->getIterator());
+
+  // Helper: if a value is defined inside one of the blocks being removed,
+  // hoist it into HeadBB (it must be a side-effect-free instruction whose
+  // operands are all available in HeadBB after the ElseBB hoist above).
+  auto IsInRemovedBlock = [&](Value *V) -> bool {
+    auto *I = dyn_cast<Instruction>(V);
+    if (!I)
+      return false;
+    BasicBlock *DefBB = I->getParent();
+    return DefBB == ThenBB || DefBB == ElseBB || DefBB == ElseThenBB ||
+           DefBB == ElseElseBB;
+  };
+  auto HoistIfNeeded = [&](Value *V) {
+    if (IsInRemovedBlock(V))
+      cast<Instruction>(V)->moveBefore(BI->getIterator());
+  };
+
+  // Hoist the store address and store values if they live in removed blocks.
+  HoistIfNeeded(Addr);
+  Value *VThen = ThenStore->getValueOperand();
+  Value *VElseThen = ElseThenStore->getValueOperand();
+  Value *VElseElse = ElseElseStore->getValueOperand();
+  HoistIfNeeded(VThen);
+  HoistIfNeeded(VElseThen);
+  HoistIfNeeded(VElseElse);
+
+  Value *Cond1 = BI->getCondition();
+  Value *Cond2 = ElseTerm->getCondition();
+
+  Value *SelVal;
+  if (VThen == VElseThen) {
+    // Simplify: select (cond1 || cond2), VThen, VElseElse
+    Value *CombinedCond = Builder.CreateOr(Cond1, Cond2, "cond.or");
+    SelVal = Builder.CreateSelect(CombinedCond, VThen, VElseElse, "store.sel");
+  } else {
+    // General: select cond1, VThen, (select cond2, VElseThen, VElseElse)
+    Value *InnerSel =
+        Builder.CreateSelect(Cond2, VElseThen, VElseElse, "store.sel.inner");
+    SelVal = Builder.CreateSelect(Cond1, VThen, InnerSel, "store.sel");
+  }
+
+  StoreInst *NewStore = Builder.CreateStore(SelVal, Addr);
+  NewStore->setAlignment(
+      std::min({ThenStore->getAlign(), ElseThenStore->getAlign(),
+                ElseElseStore->getAlign()}));
+
+  // Preserve metadata identical across all three original stores.
+  auto CopyIfEqual = [&](unsigned Kind) {
+    MDNode *MD = ThenStore->getMetadata(Kind);
+    if (MD && MD == ElseThenStore->getMetadata(Kind) &&
+        MD == ElseElseStore->getMetadata(Kind))
+      NewStore->setMetadata(Kind, MD);
+  };
+  CopyIfEqual(LLVMContext::MD_tbaa);
+  CopyIfEqual(LLVMContext::MD_alias_scope);
+  CopyIfEqual(LLVMContext::MD_noalias);
+  CopyIfEqual(LLVMContext::MD_nontemporal);
+  CopyIfEqual(LLVMContext::MD_access_group);
+
+  // Replace HeadBB's conditional branch with an unconditional branch to
+  // MergeBB.
+  Builder.CreateBr(MergeBB);
+  BI->eraseFromParent();
+
+  // Add HeadBB as the new incoming block for any MergeBB PHIs that had
+  // incoming values from the leaf blocks.
+  for (PHINode &PHI : MergeBB->phis()) {
+    Value *InVal = nullptr;
+    for (BasicBlock *Pred : {ThenBB, ElseThenBB, ElseElseBB}) {
+      int Idx = PHI.getBasicBlockIndex(Pred);
+      if (Idx < 0)
+        continue;
+      if (!InVal)
+        InVal = PHI.getIncomingValue(Idx);
+    }
+    if (InVal)
+      PHI.addIncoming(InVal, HeadBB);
+  }
+
+  // Detach the four now-unreachable blocks: removes their PHI contributions
+  // from successors and replaces their terminators with 'unreachable'.
+  // We do NOT erase them here — iterativelySimplifyCFG will see pred_empty
+  // and safely delete each one after BBIt has moved past it.
+  SmallVector<BasicBlock *, 4> DeadBlocks = {ThenBB, ElseBB, ElseThenBB,
+                                             ElseElseBB};
+  SmallVector<DominatorTree::UpdateType, 8> Updates;
+  detachDeadBlocks(DeadBlocks, DTU ? &Updates : nullptr,
+                   /*KeepOneInputPHIs=*/false);
+
+  if (DTU) {
+    Updates.push_back({DominatorTree::Insert, HeadBB, MergeBB});
+    DTU->applyUpdates(Updates);
+  }
+
+  return true;
+}
+
 static Value *ensureValueAvailableInSuccessor(Value *V, BasicBlock *BB,
                                               Value *AlternativeV = nullptr) {
   // PHI is going to be a PHI node that allows the value V that is defined in
@@ -8791,6 +9068,11 @@ bool SimplifyCFGOpt::simplifyCondBranch(CondBrInst *BI, IRBuilder<> &Builder) {
   if (mergeNestedCondBranch(BI, DTU))
     return requestResimplify();
 
+  // Fold triangle+diamond pattern where three branches all store to the same
+  // address into a select+store in the head block, enabling vectorization.
+  if (Options.FoldCondStoreToSelect && foldCondStoreToSelectImpl(BI, DTU, TTI))
+    return requestResimplify();
+
   return false;
 }
 

>From 31ad707a22914273529a42599de11500c3dca836 Mon Sep 17 00:00:00 2001
From: Karthika Devi C <kartc at qti.qualcomm.com>
Date: Mon, 6 Jul 2026 00:14:25 -0700
Subject: [PATCH 2/3] [SimplifyCFG] Fix deprecated BranchInst usage in
 foldCondStoreToSelect

Replace deprecated BranchInst with CondBrInst/UncondBrInst in
foldCondStoreToSelectImpl to fix -Werror,-Wdeprecated-declarations
build failures.

- Change function parameter from BranchInst* to CondBrInst*
- Remove redundant isConditional() check (CondBrInst is always conditional)
- Use UncondBrInst for unconditional branch terminators (ThenTerm, leaf blocks)
- Use CondBrInst for ElseTerm conditional branch
---
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp | 17 +++++++----------
 1 file changed, 7 insertions(+), 10 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 374abb4a37a7b..adc361a44a951 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -4366,11 +4366,8 @@ static bool isBlockCheapToSpeculate(BasicBlock *BB, StoreInst *FoldedStore,
 /// The profitability check uses TTI::isExpensiveToSpeculativelyExecute on the
 /// four speculated blocks (ThenBB, ElseBB, ElseThenBB, ElseElseBB) — HeadBB
 /// instructions already execute unconditionally so they are not checked.
-static bool foldCondStoreToSelectImpl(BranchInst *BI, DomTreeUpdater *DTU,
+static bool foldCondStoreToSelectImpl(CondBrInst *BI, DomTreeUpdater *DTU,
                                       const TargetTransformInfo &TTI) {
-  if (!BI->isConditional())
-    return false;
-
   BasicBlock *HeadBB = BI->getParent();
   BasicBlock *ThenBB = BI->getSuccessor(0);
   BasicBlock *ElseBB = BI->getSuccessor(1);
@@ -4379,8 +4376,8 @@ static bool foldCondStoreToSelectImpl(BranchInst *BI, DomTreeUpdater *DTU,
   // branch to MergeBB.
   if (ThenBB->getSinglePredecessor() != HeadBB)
     return false;
-  BranchInst *ThenTerm = dyn_cast<BranchInst>(ThenBB->getTerminator());
-  if (!ThenTerm || ThenTerm->isConditional())
+  UncondBrInst *ThenTerm = dyn_cast<UncondBrInst>(ThenBB->getTerminator());
+  if (!ThenTerm)
     return false;
   BasicBlock *MergeBB = ThenTerm->getSuccessor(0);
 
@@ -4392,8 +4389,8 @@ static bool foldCondStoreToSelectImpl(BranchInst *BI, DomTreeUpdater *DTU,
   // blocks. May contain cheap instructions before the branch (e.g. icmp).
   if (ElseBB->getSinglePredecessor() != HeadBB)
     return false;
-  BranchInst *ElseTerm = dyn_cast<BranchInst>(ElseBB->getTerminator());
-  if (!ElseTerm || !ElseTerm->isConditional())
+  CondBrInst *ElseTerm = dyn_cast<CondBrInst>(ElseBB->getTerminator());
+  if (!ElseTerm)
     return false;
 
   // Collect non-terminator instructions in ElseBB to hoist into HeadBB.
@@ -4415,8 +4412,8 @@ static bool foldCondStoreToSelectImpl(BranchInst *BI, DomTreeUpdater *DTU,
   auto CheckLeafBlock = [&](BasicBlock *BB, StoreInst *&Store) -> bool {
     if (BB->getSinglePredecessor() != ElseBB)
       return false;
-    BranchInst *Term = dyn_cast<BranchInst>(BB->getTerminator());
-    if (!Term || Term->isConditional() || Term->getSuccessor(0) != MergeBB)
+    UncondBrInst *Term = dyn_cast<UncondBrInst>(BB->getTerminator());
+    if (!Term || Term->getSuccessor(0) != MergeBB)
       return false;
     Store = findUniqueSimpleStoreInBlock(BB);
     return Store != nullptr;

>From 2b90d08bcec0547456128231028802fb6c10f012 Mon Sep 17 00:00:00 2001
From: Karthika Devi C <kartc at qti.qualcomm.com>
Date: Mon, 6 Jul 2026 05:08:35 -0700
Subject: [PATCH 3/3] [SimplifyCFG] Fix crash when folded blocks contain PHI
 nodes

Bail out of foldCondStoreToSelectImpl if any of the four blocks
(ThenBB, ElseBB, ElseThenBB, ElseElseBB) contain PHI nodes.

These are single-predecessor blocks so PHIs are degenerate, but
attempting to hoist or detach them causes a verifier failure:
"PHINode should have one entry for each predecessor of its parent
basic block".
---
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index adc361a44a951..dbaef4a3dd21b 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -4424,6 +4424,13 @@ static bool foldCondStoreToSelectImpl(CondBrInst *BI, DomTreeUpdater *DTU,
       !CheckLeafBlock(ElseElseBB, ElseElseStore))
     return false;
 
+  // Bail out if any of the blocks contain PHI nodes. These are single-
+  // predecessor blocks so PHIs here are degenerate and not expected in
+  // well-optimized IR.
+  if (!ThenBB->phis().empty() || !ElseBB->phis().empty() ||
+      !ElseThenBB->phis().empty() || !ElseElseBB->phis().empty())
+    return false;
+
   // All three stores must write to the same logical address and have the same
   // value type.
   Value *Addr = ThenStore->getPointerOperand();



More information about the llvm-commits mailing list