[llvm] [DSE] Introduce `eliminateRedundantStoresViaDominatingConditions` (PR #181709)

Antonio Frighetto via llvm-commits llvm-commits at lists.llvm.org
Sat Apr 4 03:25:37 PDT 2026


https://github.com/antoniofrighetto updated https://github.com/llvm/llvm-project/pull/181709

>From 03e3341f91c3076cf169cad0ea2a894fe920090c Mon Sep 17 00:00:00 2001
From: Antonio Frighetto <me at antoniofrighetto.com>
Date: Sat, 4 Apr 2026 11:57:27 +0200
Subject: [PATCH] [DSE] Introduce
 `eliminateRedundantStoresViaDominatingConditions`

While optimizing tautological assignments, if there exists a dominating
condition that implies the value being stored in a pointer, and such a
condition appears in a node that dominates the store via equality edge,
then subsequent stores may be redundant, if no write occurs in between.
This is achieved via a DFS top-down walk of the dom-tree, collecting
dominating conditions and propagating them to each subtree, popping
them upon backtracking, with automatic scope management via
`ScopedHashTable`.

This also generalizes `dominatingConditionImpliesValue` transform,
which was previously taking into account only the immediate dominator.
---
 .../Scalar/DeadStoreElimination.cpp           | 183 ++++++++++++------
 .../dead-stores-via-dom-conditions.ll         |   3 -
 2 files changed, 121 insertions(+), 65 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp b/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
index d3ae9a6cf8d80..6704fd0ef86e0 100644
--- a/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
@@ -32,6 +32,7 @@
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/MapVector.h"
 #include "llvm/ADT/PostOrderIterator.h"
+#include "llvm/ADT/ScopedHashTable.h"
 #include "llvm/ADT/SetVector.h"
 #include "llvm/ADT/SmallPtrSet.h"
 #include "llvm/ADT/SmallVector.h"
@@ -174,6 +175,11 @@ static cl::opt<bool> EnableInitializesImprovement(
     "enable-dse-initializes-attr-improvement", cl::init(true), cl::Hidden,
     cl::desc("Enable the initializes attr improvement in DSE"));
 
+static cl::opt<unsigned> MaxDepthRecursion(
+    "dse-max-dom-cond-depth", cl::init(1024), cl::Hidden,
+    cl::desc("Max dominator tree recursion depth for eliminating redundant "
+             "stores via dominating conditions"));
+
 //===----------------------------------------------------------------------===//
 // Helper functions
 //===----------------------------------------------------------------------===//
@@ -1106,10 +1112,6 @@ struct DSEState {
   /// try folding it into a call to calloc.
   bool tryFoldIntoCalloc(MemoryDef *Def, const Value *DefUO);
 
-  // Check if there is a dominating condition, that implies that the value
-  // being stored in a ptr is already present in the ptr.
-  bool dominatingConditionImpliesValue(MemoryDef *Def);
-
   /// \returns true if \p Def is a no-op store, either because it
   /// directly stores back a loaded value or stores zero to a calloced object.
   bool storeIsNoop(MemoryDef *Def, const Value *DefUO);
@@ -1120,6 +1122,11 @@ struct DSEState {
   /// is already stored at the same location.
   bool eliminateRedundantStoresOfExistingValues();
 
+  /// If there is a dominating condition that implies the value being stored in
+  /// a pointer, and such a condition appears in a node that dominates the
+  /// store, then the store may be redundant if no write occurs in between.
+  bool eliminateRedundantStoresViaDominatingConditions();
+
   // Return the locations written by the initializes attribute.
   // Note that this function considers:
   // 1. Unwind edge: use "initializes" attribute only if the callee has
@@ -2133,6 +2140,115 @@ bool DSEState::eliminateDeadWritesAtEndOfFunction() {
   return MadeChange;
 }
 
+bool DSEState::eliminateRedundantStoresViaDominatingConditions() {
+  bool MadeChange = false;
+  LLVM_DEBUG(dbgs() << "Trying to eliminate MemoryDefs whose value being "
+                       "written is implied by a dominating condition\n");
+
+  using ConditionInfo = std::pair<Value *, Value *>;
+  using ScopedHTType = ScopedHashTable<ConditionInfo, Instruction *>;
+
+  // We maintain a scoped hash table of the active dominating conditions for a
+  // given node.
+  ScopedHTType ActiveConditions;
+  auto GetDominatingCondition = [&](BasicBlock *BB)
+      -> std::optional<std::tuple<ConditionInfo, Instruction *, BasicBlock *>> {
+    auto *BI = dyn_cast<CondBrInst>(BB->getTerminator());
+    if (!BI)
+      return std::nullopt;
+
+    // In case both blocks are the same, it is not possible to determine
+    // if optimization is possible. (We would not want to optimize a store
+    // in the FalseBB if condition is true and vice versa.)
+    if (BI->getSuccessor(0) == BI->getSuccessor(1))
+      return std::nullopt;
+
+    Instruction *ICmpL;
+    CmpPredicate Pred;
+    Value *StorePtr, *StoreVal;
+    if (!match(BI->getCondition(),
+               m_c_ICmp(Pred, m_Instruction(ICmpL, m_Load(m_Value(StorePtr))),
+                        m_Value(StoreVal))) ||
+        !ICmpInst::isEquality(Pred))
+      return std::nullopt;
+
+    // Ensure the replacement is allowed when comparing pointers, as
+    // the equality compares addresses only, not pointers' provenance.
+    if (StoreVal->getType()->isPointerTy() &&
+        !canReplacePointersIfEqual(StoreVal, ICmpL, DL))
+      return std::nullopt;
+
+    unsigned ImpliedSuccIdx = Pred == ICmpInst::ICMP_EQ ? 0 : 1;
+    BasicBlock *ImpliedSucc = BI->getSuccessor(ImpliedSuccIdx);
+    return {{ConditionInfo(StorePtr, StoreVal), ICmpL, ImpliedSucc}};
+  };
+
+  auto VisitNode = [&](DomTreeNode *Node, unsigned Depth, auto &Self) -> void {
+    if (Depth > MaxDepthRecursion)
+      return;
+
+    BasicBlock *BB = Node->getBlock();
+    // Check for redundant stores against active known conditions.
+    if (auto *Accesses = MSSA.getBlockDefs(BB)) {
+      for (auto &Access : make_early_inc_range(*Accesses)) {
+        auto *Def = dyn_cast<MemoryDef>(&Access);
+        if (!Def)
+          continue;
+
+        auto *SI = dyn_cast<StoreInst>(Def->getMemoryInst());
+        if (!SI || !SI->isUnordered())
+          continue;
+
+        Instruction *LI = ActiveConditions.lookup(
+            {SI->getPointerOperand(), SI->getValueOperand()});
+        if (!LI)
+          continue;
+
+        // Found a dominating condition that may imply the value being stored.
+        // Make sure there does not exist any clobbering access between the
+        // load and the potential redundant store.
+        MemoryAccess *LoadAccess = MSSA.getMemoryAccess(LI);
+        MemoryAccess *ClobberingAccess =
+            MSSA.getSkipSelfWalker()->getClobberingMemoryAccess(Def, BatchAA);
+        if (MSSA.dominates(ClobberingAccess, LoadAccess)) {
+          LLVM_DEBUG(dbgs()
+                     << "Removing No-Op Store:\n  DEAD: " << *SI << '\n');
+          deleteDeadInstruction(SI);
+          NumRedundantStores++;
+          MadeChange = true;
+        }
+      }
+    }
+
+    // See whether this basic block establishes a dominating condition.
+    auto MaybeCondition = GetDominatingCondition(BB);
+
+    for (DomTreeNode *Child : Node->children()) {
+      // RAII scope for the active conditions.
+      ScopedHTType::ScopeTy Scope(ActiveConditions);
+      if (MaybeCondition) {
+        const auto &[Cond, LI, ImpliedSucc] = *MaybeCondition;
+        if (DT.dominates(BasicBlockEdge(BB, ImpliedSucc), Child->getBlock())) {
+          // Found a condition that holds for this child, dominated by the
+          // current node via the equality edge. Propagate the condition to
+          // the children by pushing it onto the table.
+          ActiveConditions.insert(Cond, LI);
+        }
+      }
+
+      // Recursively visit the children of this node. Upon destruction, the no
+      // longer active condition before visiting any sibling nodes is popped
+      // from the active scope.
+      Self(Child, Depth + 1, Self);
+    }
+  };
+
+  // Do a DFS walk of the dom-tree.
+  VisitNode(DT.getRootNode(), 0, VisitNode);
+
+  return MadeChange;
+}
+
 bool DSEState::tryFoldIntoCalloc(MemoryDef *Def, const Value *DefUO) {
   Instruction *DefI = Def->getMemoryInst();
   MemSetInst *MemSet = dyn_cast<MemSetInst>(DefI);
@@ -2237,61 +2353,6 @@ bool DSEState::tryFoldIntoCalloc(MemoryDef *Def, const Value *DefUO) {
   return true;
 }
 
-bool DSEState::dominatingConditionImpliesValue(MemoryDef *Def) {
-  auto *StoreI = cast<StoreInst>(Def->getMemoryInst());
-  BasicBlock *StoreBB = StoreI->getParent();
-  Value *StorePtr = StoreI->getPointerOperand();
-  Value *StoreVal = StoreI->getValueOperand();
-
-  DomTreeNode *IDom = DT.getNode(StoreBB)->getIDom();
-  if (!IDom)
-    return false;
-
-  auto *BI = dyn_cast<CondBrInst>(IDom->getBlock()->getTerminator());
-  if (!BI)
-    return false;
-
-  // In case both blocks are the same, it is not possible to determine
-  // if optimization is possible. (We would not want to optimize a store
-  // in the FalseBB if condition is true and vice versa.)
-  if (BI->getSuccessor(0) == BI->getSuccessor(1))
-    return false;
-
-  Instruction *ICmpL;
-  CmpPredicate Pred;
-  if (!match(BI->getCondition(),
-             m_c_ICmp(Pred,
-                      m_CombineAnd(m_Load(m_Specific(StorePtr)),
-                                   m_Instruction(ICmpL)),
-                      m_Specific(StoreVal))) ||
-      !ICmpInst::isEquality(Pred))
-    return false;
-
-  // Ensure the replacement is allowed when comparing pointers, as
-  // the equality compares addresses only, not pointers' provenance.
-  if (StoreVal->getType()->isPointerTy() &&
-      !canReplacePointersIfEqual(StoreVal, ICmpL, DL))
-    return false;
-
-  // In case the else blocks also branches to the if block or the other way
-  // around it is not possible to determine if the optimization is possible.
-  if (Pred == ICmpInst::ICMP_EQ &&
-      !DT.dominates(BasicBlockEdge(BI->getParent(), BI->getSuccessor(0)),
-                    StoreBB))
-    return false;
-
-  if (Pred == ICmpInst::ICMP_NE &&
-      !DT.dominates(BasicBlockEdge(BI->getParent(), BI->getSuccessor(1)),
-                    StoreBB))
-    return false;
-
-  MemoryAccess *LoadAcc = MSSA.getMemoryAccess(ICmpL);
-  MemoryAccess *ClobAcc =
-      MSSA.getSkipSelfWalker()->getClobberingMemoryAccess(Def, BatchAA);
-
-  return MSSA.dominates(ClobAcc, LoadAcc);
-}
-
 bool DSEState::storeIsNoop(MemoryDef *Def, const Value *DefUO) {
   Instruction *DefI = Def->getMemoryInst();
   StoreInst *Store = dyn_cast<StoreInst>(DefI);
@@ -2320,9 +2381,6 @@ bool DSEState::storeIsNoop(MemoryDef *Def, const Value *DefUO) {
   if (!Store)
     return false;
 
-  if (dominatingConditionImpliesValue(Def))
-    return true;
-
   if (auto *LoadI = dyn_cast<LoadInst>(Store->getOperand(0))) {
     if (LoadI->getPointerOperand() == Store->getOperand(1)) {
       // Get the defining access for the load.
@@ -2729,6 +2787,7 @@ static bool eliminateDeadStores(Function &F, AliasAnalysis &AA, MemorySSA &MSSA,
 
   MadeChange |= State.eliminateRedundantStoresOfExistingValues();
   MadeChange |= State.eliminateDeadWritesAtEndOfFunction();
+  MadeChange |= State.eliminateRedundantStoresViaDominatingConditions();
 
   while (!State.ToRemove.empty()) {
     Instruction *DeadInst = State.ToRemove.pop_back_val();
diff --git a/llvm/test/Transforms/DeadStoreElimination/dead-stores-via-dom-conditions.ll b/llvm/test/Transforms/DeadStoreElimination/dead-stores-via-dom-conditions.ll
index c1f07c00d648b..53f570e2b3c53 100644
--- a/llvm/test/Transforms/DeadStoreElimination/dead-stores-via-dom-conditions.ll
+++ b/llvm/test/Transforms/DeadStoreElimination/dead-stores-via-dom-conditions.ll
@@ -20,7 +20,6 @@ define void @remove_tautological_store_via_dom_condition(ptr %x, i1 %c) {
 ; CHECK:       [[JOIN]]:
 ; CHECK-NEXT:    br label %[[INNER:.*]]
 ; CHECK:       [[INNER]]:
-; CHECK-NEXT:    store i32 0, ptr [[X]], align 4
 ; CHECK-NEXT:    br label %[[END]]
 ; CHECK:       [[END]]:
 ; CHECK-NEXT:    ret void
@@ -114,8 +113,6 @@ define void @remove_tautological_store_via_dom_condition_3(ptr %x, ptr %y, i1 %c
 ; CHECK-NEXT:    [[CMP_2:%.*]] = icmp eq i32 [[VAL_2]], 0
 ; CHECK-NEXT:    br i1 [[CMP_2]], label %[[IF_EQ:.*]], label %[[IF_ELSE:.*]]
 ; CHECK:       [[IF_EQ]]:
-; CHECK-NEXT:    store i32 0, ptr [[X]], align 4
-; CHECK-NEXT:    store i32 0, ptr [[Y]], align 4
 ; CHECK-NEXT:    br label %[[JOIN:.*]]
 ; CHECK:       [[IF_ELSE]]:
 ; CHECK-NEXT:    br label %[[JOIN]]



More information about the llvm-commits mailing list