[llvm] 0145a64 - [DSE] Make DSE eliminate stores to objects with a sized dead_on_return

via llvm-commits llvm-commits at lists.llvm.org
Fri Jan 23 07:40:49 PST 2026


Author: Aiden Grossman
Date: 2026-01-23T07:40:44-08:00
New Revision: 0145a643ccaf55adf33f117e50ff587a4d2b8d9c

URL: https://github.com/llvm/llvm-project/commit/0145a643ccaf55adf33f117e50ff587a4d2b8d9c
DIFF: https://github.com/llvm/llvm-project/commit/0145a643ccaf55adf33f117e50ff587a4d2b8d9c.diff

LOG: [DSE] Make DSE eliminate stores to objects with a sized dead_on_return

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.

Reviewers: nikic, antoniofrighetto, alinas, aeubanks

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

Added: 
    

Modified: 
    llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
    llvm/test/Transforms/DeadStoreElimination/simple.ll

Removed: 
    


################################################################################
diff  --git a/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp b/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
index 00e3ce18bd080..247b4d40eee4d 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,21 @@ 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())
-      if (AI.hasPassPointeeByValueCopyAttr() ||
-          (AI.getType()->isPointerTy() &&
-           AI.getDeadOnReturnInfo().coversAllReachableMemory()))
+    for (Argument &AI : F.args()) {
+      if (AI.hasPassPointeeByValueCopyAttr()) {
         InvisibleToCallerAfterRet.insert({&AI, true});
+        continue;
+      }
+
+      if (!AI.getType()->isPointerTy())
+        continue;
+
+      const DeadOnReturnInfo &Info = AI.getDeadOnReturnInfo();
+      if (Info.coversAllReachableMemory())
+        InvisibleToCallerAfterRet.insert({&AI, true});
+      else if (uint64_t DeadBytes = Info.getNumberOfDeadBytes())
+        InvisibleToCallerAfterRetBounded.insert({&AI, DeadBytes});
+    }
 
     // Collect whether there is any irreducible control flow in the function.
     ContainsIrreducibleLoops = mayContainIrreducibleControl(F, &LI);
@@ -1206,10 +1217,24 @@ 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 IBounded = InvisibleToCallerAfterRetBounded.find(V);
+    if (IBounded != InvisibleToCallerAfterRetBounded.end()) {
+      int64_t ValueOffset;
+      const Value *BaseValue =
+          GetPointerBaseWithConstantOffset(Ptr, ValueOffset, DL);
+      assert(BaseValue == V);
+      // This store is only invisible after return if we are in bounds of the
+      // range marked dead.
+      if (StoreSize.hasValue() &&
+          ValueOffset + StoreSize.getValue() <= IBounded->second &&
+          ValueOffset >= 0)
+        return true;
+    }
     auto I = InvisibleToCallerAfterRet.insert({V, false});
     if (I.second && isInvisibleToCallerOnUnwind(V) && isNoAliasCall(V))
       I.first->second = capturesNothing(PointerMayBeCaptured(
@@ -1757,7 +1782,8 @@ 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 +1801,8 @@ 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());
@@ -1879,6 +1906,7 @@ struct DSEState {
               if (CapturedBeforeReturn.erase(UO))
                 ShouldIterateEndOfFunctionDSE = true;
               InvisibleToCallerAfterRet.erase(UO);
+              InvisibleToCallerAfterRetBounded.erase(UO);
             }
           }
         }
@@ -1982,7 +2010,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..4467afb43f3b6 100644
--- a/llvm/test/Transforms/DeadStoreElimination/simple.ll
+++ b/llvm/test/Transforms/DeadStoreElimination/simple.ll
@@ -886,5 +886,74 @@ 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:    [[P1:%.*]] = getelementptr i8, ptr [[P:%.*]], i64 8
+; CHECK-NEXT:    store i64 0, ptr [[P1]], align 4
+; CHECK-NEXT:    ret void
+;
+  %p1 = getelementptr i8, ptr %p, i64 8
+  store i64 0, ptr %p1
+  ret void
+}
+
+define void @test_dead_on_return_zero_offset(ptr dead_on_return(8) %p) {
+; CHECK-LABEL: @test_dead_on_return_zero_offset(
+; CHECK-NEXT:    ret void
+;
+  store i64 0, ptr %p
+  ret void
+}
+
+define void @test_dead_on_return_inbounds(ptr dead_on_return(16) %p) {
+; CHECK-LABEL: @test_dead_on_return_inbounds(
+; CHECK-NEXT:    ret void
+;
+  %p1 = getelementptr inbounds i8, ptr %p, i64 2
+  store i64 0, ptr %p1
+  ret void
+}
+
+define void @test_dead_on_return_overlapping_oob(ptr dead_on_return(8) %p) {
+; CHECK-LABEL: @test_dead_on_return_overlapping_oob(
+; CHECK-NEXT:    [[P1:%.*]] = getelementptr inbounds i8, ptr [[P:%.*]], i64 4
+; CHECK-NEXT:    store i64 0, ptr [[P1]], align 4
+; CHECK-NEXT:    ret void
+;
+  %p1 = getelementptr inbounds i8, ptr %p, i64 4
+  store i64 0, ptr %p1
+  ret void
+}
+
+define void @test_dead_on_return_negative_oob(ptr dead_on_return(8) %p) {
+; CHECK-LABEL: @test_dead_on_return_negative_oob(
+; CHECK-NEXT:    [[P1:%.*]] = getelementptr inbounds i8, ptr [[P:%.*]], i64 -4
+; CHECK-NEXT:    store i64 0, ptr [[P1]], align 4
+; CHECK-NEXT:    ret void
+;
+  %p1 = getelementptr inbounds i8, ptr %p, i64 -4
+  store i64 0, ptr %p1
+  ret void
+}
+
+define void @test_dead_on_return_two_stores(ptr dead_on_return(16) %p) {
+; CHECK-LABEL: @test_dead_on_return_two_stores(
+; CHECK-NEXT:    ret void
+;
+  store i64 0, ptr %p
+  %p1 = getelementptr inbounds i8, ptr %p, i64 8
+  store i64 0, ptr %p1
+  ret void
+}
+
+define void @test_dead_on_return_variable_memset(ptr dead_on_return(8) %p, i64 %s) {
+; CHECK-LABEL: @test_dead_on_return_variable_memset(
+; CHECK-NEXT:    call void @llvm.memset.p0.i64(ptr [[P:%.*]], i8 0, i64 [[S:%.*]], i1 false)
+; CHECK-NEXT:    ret void
+;
+  call void @llvm.memset.p0.i64(ptr %p, i8 0, i64 %s, i1 false)
+  ret void
+}
+
 declare void @opaque(ptr)
 declare void @maythrow() memory(none)


        


More information about the llvm-commits mailing list