[llvm-branch-commits] [DSE] Make DSE eliminate stores to objects with a sized dead_on_return (PR #173694)

via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Fri Dec 26 17:59:39 PST 2025


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: Aiden Grossman (boomanaiden154)

<details>
<summary>Changes</summary>

dead_on_return is made optionally sized in #<!-- -->171712. This patch adds
handling in DSE so that we can actually eliminate stores to pointer
parameters marked with a sized dead_on_return attribute. We do not
eliminate stores where the store may overlap with bytes that are not
known to be dead after return.


---
Full diff: https://github.com/llvm/llvm-project/pull/173694.diff


2 Files Affected:

- (modified) llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp (+22-5) 
- (modified) llvm/test/Transforms/DeadStoreElimination/simple.ll (+43) 


``````````diff
diff --git a/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp b/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
index 87393cd595e97..25d473b5beb72 100644
--- a/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
@@ -965,6 +965,7 @@ struct DSEState {
   // Keep track of all of the objects that are invisible to the caller after
   // the function returns.
   DenseMap<const Value *, bool> InvisibleToCallerAfterRet;
+  DenseMap<const Value *, uint64_t> InvisibleToCallerAfterRetBounded;
   // 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
@@ -1016,11 +1017,18 @@ struct DSEState {
 
     // Treat byval, inalloca or dead on return arguments the same as Allocas,
     // stores to them are dead at the end of the function.
-    for (Argument &AI : F.args())
+    for (Argument &AI : F.args()) {
       if (AI.hasPassPointeeByValueCopyAttr() ||
           (AI.getType()->isPointerTy() &&
            AI.getDeadOnReturnInfo().coversAllReachableMemory()))
         InvisibleToCallerAfterRet.insert({&AI, true});
+      if (AI.getType()->isPointerTy() &&
+          !AI.getDeadOnReturnInfo().coversAllReachableMemory()) {
+        if (uint64_t DeadOnReturnBytes =
+                AI.getDeadOnReturnInfo().getNumberOfDeadBytes())
+          InvisibleToCallerAfterRetBounded.insert({&AI, DeadOnReturnBytes});
+      }
+    }
 
     // Collect whether there is any irreducible control flow in the function.
     ContainsIrreducibleLoops = mayContainIrreducibleControl(F, &LI);
@@ -1206,11 +1214,20 @@ struct DSEState {
     return OW_None;
   }
 
-  bool isInvisibleToCallerAfterRet(const Value *V) {
+  bool isInvisibleToCallerAfterRet(const Value *V, const Value *Ptr, const LocationSize StoreSize) {
     if (isa<AllocaInst>(V))
       return true;
 
     auto I = InvisibleToCallerAfterRet.insert({V, false});
+    if (I.second && InvisibleToCallerAfterRetBounded.contains(V)) {
+      int64_t ValueOffset;
+      const Value *BaseValue =
+          GetPointerBaseWithConstantOffset(Ptr, ValueOffset, DL);
+      assert(BaseValue == V);
+      if (ValueOffset + StoreSize.toRaw() <
+          InvisibleToCallerAfterRetBounded[BaseValue])
+        return true;
+    }
     if (I.second && isInvisibleToCallerOnUnwind(V) && isNoAliasCall(V))
       I.first->second = capturesNothing(PointerMayBeCaptured(
           V, /*ReturnCaptures=*/true, CaptureComponents::Provenance));
@@ -1757,7 +1774,7 @@ struct DSEState {
           BasicBlock *MaybeKillingBlock = UseInst->getParent();
           if (PostOrderNumbers.find(MaybeKillingBlock)->second <
               PostOrderNumbers.find(MaybeDeadAccess->getBlock())->second) {
-            if (!isInvisibleToCallerAfterRet(KillingUndObj)) {
+            if (!isInvisibleToCallerAfterRet(KillingUndObj, KillingLoc.Ptr, KillingLoc.Size)) {
               LLVM_DEBUG(dbgs()
                          << "    ... found killing def " << *UseInst << "\n");
               KillingDefs.insert(UseInst);
@@ -1775,7 +1792,7 @@ struct DSEState {
     // For accesses to locations visible after the function returns, make sure
     // that the location is dead (=overwritten) along all paths from
     // MaybeDeadAccess to the exit.
-    if (!isInvisibleToCallerAfterRet(KillingUndObj)) {
+    if (!isInvisibleToCallerAfterRet(KillingUndObj, KillingLoc.Ptr, KillingLoc.Size)) {
       SmallPtrSet<BasicBlock *, 16> KillingBlocks;
       for (Instruction *KD : KillingDefs)
         KillingBlocks.insert(KD->getParent());
@@ -1982,7 +1999,7 @@ struct DSEState {
         // underlying objects is very uncommon. If it turns out to be important,
         // we can use getUnderlyingObjects here instead.
         const Value *UO = getUnderlyingObject(DefLoc->Ptr);
-        if (!isInvisibleToCallerAfterRet(UO))
+        if (!isInvisibleToCallerAfterRet(UO, DefLoc->Ptr, DefLoc->Size))
           continue;
 
         if (isWriteAtEndOfFunction(Def, *DefLoc)) {
diff --git a/llvm/test/Transforms/DeadStoreElimination/simple.ll b/llvm/test/Transforms/DeadStoreElimination/simple.ll
index 9d28395a4ccd0..7619842ea18cf 100644
--- a/llvm/test/Transforms/DeadStoreElimination/simple.ll
+++ b/llvm/test/Transforms/DeadStoreElimination/simple.ll
@@ -886,5 +886,48 @@ define ptr @test_dead_on_return_ptr_returned(ptr dead_on_return %p) {
   ret ptr %p
 }
 
+define void @test_dead_on_return_oob(ptr dead_on_return(4) %p) {
+; CHECK-LABEL: @test_dead_on_return_oob(
+; CHECK-NEXT:    [[LOCAL_VAR:%.*]] = alloca ptr, align 8
+; CHECK-NEXT:    call void @opaque(ptr [[LOCAL_VAR]])
+; CHECK-NEXT:    [[P1:%.*]] = getelementptr i8, ptr [[P:%.*]], i64 8
+; CHECK-NEXT:    store ptr [[LOCAL_VAR]], ptr [[P1]], align 8
+; CHECK-NEXT:    ret void
+;
+  %local.var = alloca ptr
+  call void @opaque(ptr %local.var)
+  %p1 = getelementptr i8, ptr %p, i64 8
+  store ptr %local.var, ptr %p1
+  ret void
+}
+
+define void @test_dead_on_return_inbounds(ptr dead_on_return(16) %p) {
+; CHECK-LABEL: @test_dead_on_return_inbounds(
+; CHECK-NEXT:    [[LOCAL_VAR:%.*]] = alloca ptr, align 8
+; CHECK-NEXT:    call void @opaque(ptr [[LOCAL_VAR]])
+; CHECK-NEXT:    ret void
+;
+  %local.var = alloca ptr
+  call void @opaque(ptr %local.var)
+  %p1 = getelementptr inbounds i8, ptr %p, i64 2
+  store ptr %local.var, ptr %p1
+  ret void
+}
+
+define void @test_on_return_overlapping_oob(ptr dead_on_return(8) %p) {
+; CHECK-LABEL: @test_on_return_overlapping_oob(
+; CHECK-NEXT:    [[LOCAL_VAR:%.*]] = alloca ptr, align 8
+; CHECK-NEXT:    call void @opaque(ptr [[LOCAL_VAR]])
+; CHECK-NEXT:    [[P1:%.*]] = getelementptr inbounds i8, ptr [[P:%.*]], i64 4
+; CHECK-NEXT:    store ptr [[LOCAL_VAR]], ptr [[P1]], align 8
+; CHECK-NEXT:    ret void
+;
+  %local.var = alloca ptr
+  call void @opaque(ptr %local.var)
+  %p1 = getelementptr inbounds i8, ptr %p, i64 4
+  store ptr %local.var, ptr %p1
+  ret void
+}
+
 declare void @opaque(ptr)
 declare void @maythrow() memory(none)

``````````

</details>


https://github.com/llvm/llvm-project/pull/173694


More information about the llvm-branch-commits mailing list