[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