[DSE] Introduce eliminateRedundantStoresViaDominatingConditions (#181709)

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.

This also generalizes `dominatingConditionImpliesValue` transform, which
was previously taking into account only the immediate dominator.

Compile-time:
https://llvm-compile-time-tracker.com/compare.php?from=f8906704104e446a7482aeca32d058b91867e05c&to=24c5d61f1e28acbe6a59ea4e9a5da0ffcee3bf1a&stat=instructions:u.

Compile-time w/ limit on recursion:
https://llvm-compile-time-tracker.com/compare.php?from=24c5d61f1e28acbe6a59ea4e9a5da0ffcee3bf1a&to=9889567fe8a0515ab895b22003c93fabfd9ac4e5&stat=instructions:u.
Seems to alleviate the small regression in stage2-O3, but seemingly adds
one in stage2-O0-g.
This commit is contained in:
Antonio Frighetto 2026-04-04 12:55:25 +02:00 committed by GitHub
parent 48c59d1a97
commit d27cbc5fa9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 121 additions and 65 deletions

View File

@ -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();

View File

@ -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]]