[llvm] [DeadStoreElimination] Visit blocks in RPO traversal when initializing DSEState (PR #137815)

Antonio Frighetto via llvm-commits llvm-commits at lists.llvm.org
Tue Apr 29 07:06:29 PDT 2025


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

>From 96f27233a7051530e3a021ff9f3c535ecf0e1d2a Mon Sep 17 00:00:00 2001
From: Antonio Frighetto <me at antoniofrighetto.com>
Date: Tue, 29 Apr 2025 14:59:40 +0200
Subject: [PATCH 1/3] [DeadStoreElimination] Introduce test for PR137815 (NFC)

---
 .../traversal-order-end-of-function.ll        | 37 +++++++++++++++++++
 1 file changed, 37 insertions(+)
 create mode 100644 llvm/test/Transforms/DeadStoreElimination/traversal-order-end-of-function.ll

diff --git a/llvm/test/Transforms/DeadStoreElimination/traversal-order-end-of-function.ll b/llvm/test/Transforms/DeadStoreElimination/traversal-order-end-of-function.ll
new file mode 100644
index 0000000000000..4c81fc8871711
--- /dev/null
+++ b/llvm/test/Transforms/DeadStoreElimination/traversal-order-end-of-function.ll
@@ -0,0 +1,37 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt -passes=dse -S -debug -disable-output %s 2>&1 | FileCheck %s
+; REQUIRES: asserts
+
+; Ensure MemoryDefs are visited bottom-up in eliminateDeadWritesAtEndOfFunction.
+
+; CHECK:      Trying to eliminate MemoryDefs at the end of the function
+; CHECK-NEXT:   Check if def 1 = MemoryDef(liveOnEntry) (  call void @llvm.memset.p0.i64(ptr noundef %array, i8 0, i64 100, i1 false)) is at the end the function
+; CHECK-NEXT:   ... hit read clobber   %array.curr = load i32, ptr %array.idx, align 4.
+; CHECK-NEXT:   Check if def 2 = MemoryDef(4) (  store i32 %sum, ptr %array.idx, align 4) is at the end the function
+define void @reverse_post_order_traversal_visit_test(ptr noundef %A) {
+entry:
+  %array = alloca [25 x i32], align 16
+  call void @llvm.memset.p0.i64(ptr noundef %array, i8 0, i64 100, i1 false)
+  br label %loop
+
+loop:                                             ; preds = %loop, %entry
+  %array.prev = phi i32 [ 0, %entry ], [ %sum, %loop ]
+  %iv = phi i64 [ 1, %entry ], [ %iv.next, %loop ]
+  %A.idx = getelementptr inbounds i32, ptr %A, i64 %iv
+  %A.curr = load i32, ptr %A.idx, align 4
+  %A.curr.sum.array.prev = add nsw i32 %A.curr, %array.prev
+  %array.idx = getelementptr inbounds [25 x i32], ptr %array, i64 0, i64 %iv
+  %array.curr = load i32, ptr %array.idx, align 4
+  %sum = add nsw i32 %A.curr.sum.array.prev, %array.curr
+  store i32 %sum, ptr %array.idx, align 4
+  %iv.next = add nuw nsw i64 %iv, 1
+  %cond = icmp eq i64 %iv.next, 25
+  br i1 %cond, label %exit, label %loop
+
+exit:                                             ; preds = %loop
+  call void @opaque(ptr noundef %array.idx)
+  ret void
+}
+
+declare void @llvm.memset.p0.i64(ptr, i8, i64, i1)
+declare void @opaque(ptr)

>From 35a54ee5e6a7f0c634eaa766155350a1e967f66e Mon Sep 17 00:00:00 2001
From: Antonio Frighetto <me at antoniofrighetto.com>
Date: Tue, 29 Apr 2025 15:42:20 +0200
Subject: [PATCH 2/3] [DeadStoreElimination] Visit blocks in RPO traversal when
 initializing DSEState

`eliminateDeadWritesAtEndOfFunction` is supposed to walk MemoryDefs
bottom-up though this may not always be the case as such definitions
are gathered post-order. Switch to reverse post-order when collecting
blocks, thus ensuring last defs are processed first.
---
 .../Transforms/Scalar/DeadStoreElimination.cpp  | 17 +++++++++--------
 .../traversal-order-end-of-function.ll          |  3 ++-
 2 files changed, 11 insertions(+), 9 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp b/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
index 0521df8b818cf..234ba0151ac9f 100644
--- a/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
@@ -970,9 +970,9 @@ struct DSEState {
   DenseMap<const Value *, bool> InvisibleToCallerAfterRet;
   // Keep track of blocks with throwing instructions not modeled in MemorySSA.
   SmallPtrSet<BasicBlock *, 16> ThrowingBlocks;
-  // Post-order numbers for each basic block. Used to figure out if memory
+  // Reverse post-order numbers for each basic block. Used to figure out if memory
   // accesses are executed before another access.
-  DenseMap<BasicBlock *, unsigned> PostOrderNumbers;
+  DenseMap<BasicBlock *, unsigned> RPONumbers;
 
   /// Keep track of instructions (partly) overlapping with killing MemoryDefs per
   /// basic block.
@@ -1001,9 +1001,10 @@ struct DSEState {
         PDT(PDT), TLI(TLI), DL(F.getDataLayout()), LI(LI) {
     // Collect blocks with throwing instructions not modeled in MemorySSA and
     // alloc-like objects.
-    unsigned PO = 0;
-    for (BasicBlock *BB : post_order(&F)) {
-      PostOrderNumbers[BB] = PO++;
+    unsigned RPO = 0;
+    ReversePostOrderTraversal<Function *> RPOT(&F);
+    for (BasicBlock *BB : RPOT) {
+      RPONumbers[BB] = RPO++;
       for (Instruction &I : *BB) {
         MemoryAccess *MA = MSSA.getMemoryAccess(&I);
         if (I.mayThrow() && !MA)
@@ -1761,8 +1762,8 @@ struct DSEState {
       if (MemoryDef *UseDef = dyn_cast<MemoryDef>(UseAccess)) {
         if (isCompleteOverwrite(MaybeDeadLoc, MaybeDeadI, UseInst)) {
           BasicBlock *MaybeKillingBlock = UseInst->getParent();
-          if (PostOrderNumbers.find(MaybeKillingBlock)->second <
-              PostOrderNumbers.find(MaybeDeadAccess->getBlock())->second) {
+          if (RPONumbers.find(MaybeKillingBlock)->second >
+              RPONumbers.find(MaybeDeadAccess->getBlock())->second) {
             if (!isInvisibleToCallerAfterRet(KillingUndObj)) {
               LLVM_DEBUG(dbgs()
                          << "    ... found killing def " << *UseInst << "\n");
@@ -2451,7 +2452,7 @@ DSEState::eliminateDeadDefs(const MemoryLocationWrapper &KillingLocWrapper) {
         // We only consider incoming MemoryAccesses that come before the
         // MemoryPhi. Otherwise we could discover candidates that do not
         // strictly dominate our starting def.
-        if (PostOrderNumbers[IncomingBlock] > PostOrderNumbers[PhiBlock])
+        if (RPONumbers[IncomingBlock] < RPONumbers[PhiBlock])
           ToCheck.insert(IncomingAccess);
       }
       continue;
diff --git a/llvm/test/Transforms/DeadStoreElimination/traversal-order-end-of-function.ll b/llvm/test/Transforms/DeadStoreElimination/traversal-order-end-of-function.ll
index 4c81fc8871711..900f6671637f2 100644
--- a/llvm/test/Transforms/DeadStoreElimination/traversal-order-end-of-function.ll
+++ b/llvm/test/Transforms/DeadStoreElimination/traversal-order-end-of-function.ll
@@ -5,9 +5,10 @@
 ; Ensure MemoryDefs are visited bottom-up in eliminateDeadWritesAtEndOfFunction.
 
 ; CHECK:      Trying to eliminate MemoryDefs at the end of the function
+; CHECK-NEXT:   Check if def 2 = MemoryDef(4) (  store i32 %sum, ptr %array.idx, align 4) is at the end the function
+; CHECK-NEXT:   ... hit read clobber   call void @opaque(ptr noundef %array.idx).
 ; CHECK-NEXT:   Check if def 1 = MemoryDef(liveOnEntry) (  call void @llvm.memset.p0.i64(ptr noundef %array, i8 0, i64 100, i1 false)) is at the end the function
 ; CHECK-NEXT:   ... hit read clobber   %array.curr = load i32, ptr %array.idx, align 4.
-; CHECK-NEXT:   Check if def 2 = MemoryDef(4) (  store i32 %sum, ptr %array.idx, align 4) is at the end the function
 define void @reverse_post_order_traversal_visit_test(ptr noundef %A) {
 entry:
   %array = alloca [25 x i32], align 16

>From 9fb299865d002469c275e437dc306d69eaede090 Mon Sep 17 00:00:00 2001
From: Antonio Frighetto <me at antoniofrighetto.com>
Date: Tue, 29 Apr 2025 16:05:32 +0200
Subject: [PATCH 3/3] !fixup clang-format

---
 llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp b/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
index 234ba0151ac9f..ab72e7578fe94 100644
--- a/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
@@ -970,8 +970,8 @@ struct DSEState {
   DenseMap<const Value *, bool> InvisibleToCallerAfterRet;
   // Keep track of blocks with throwing instructions not modeled in MemorySSA.
   SmallPtrSet<BasicBlock *, 16> ThrowingBlocks;
-  // Reverse post-order numbers for each basic block. Used to figure out if memory
-  // accesses are executed before another access.
+  // Reverse post-order numbers for each basic block. Used to figure out if
+  // memory accesses are executed before another access.
   DenseMap<BasicBlock *, unsigned> RPONumbers;
 
   /// Keep track of instructions (partly) overlapping with killing MemoryDefs per



More information about the llvm-commits mailing list