[llvm] [DSE] Handle provenance when eliminating tautological assignments (PR #184311)
Antonio Frighetto via llvm-commits
llvm-commits at lists.llvm.org
Wed Mar 4 01:36:29 PST 2026
https://github.com/antoniofrighetto updated https://github.com/llvm/llvm-project/pull/184311
>From e7db3f1d3df66e8d0e0adb13b14099e64b124f5a Mon Sep 17 00:00:00 2001
From: Antonio Frighetto <me at antoniofrighetto.com>
Date: Wed, 4 Mar 2026 10:35:21 +0100
Subject: [PATCH] [DSE] Handle provenance when eliminating tautological
assignments
Similarly to what already being done in GVN (fb632ed2377d280b581b8d4653b855e60d611f77),
when a dominating equality condition of two pointers holds, and the
value being stored is implied by such a condition, ensure the store
may be removed by leveraging `canReplacePointersIfEqual`, subject to
the known approximations.
Fixes: https://github.com/llvm/llvm-project/issues/184088.
---
.../Scalar/DeadStoreElimination.cpp | 7 ++
.../DeadStoreElimination/noop-stores.ll | 79 ++++++++++++++++---
2 files changed, 74 insertions(+), 12 deletions(-)
diff --git a/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp b/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
index b0a8d86a46578..85d7618b3239c 100644
--- a/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
@@ -41,6 +41,7 @@
#include "llvm/Analysis/AssumptionCache.h"
#include "llvm/Analysis/CaptureTracking.h"
#include "llvm/Analysis/GlobalsModRef.h"
+#include "llvm/Analysis/Loads.h"
#include "llvm/Analysis/LoopInfo.h"
#include "llvm/Analysis/MemoryBuiltins.h"
#include "llvm/Analysis/MemoryLocation.h"
@@ -2275,6 +2276,12 @@ bool DSEState::dominatingConditionImpliesValue(MemoryDef *Def) {
!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 &&
diff --git a/llvm/test/Transforms/DeadStoreElimination/noop-stores.ll b/llvm/test/Transforms/DeadStoreElimination/noop-stores.ll
index 283935d60a6da..54c6fbd8eb53c 100644
--- a/llvm/test/Transforms/DeadStoreElimination/noop-stores.ll
+++ b/llvm/test/Transforms/DeadStoreElimination/noop-stores.ll
@@ -325,7 +325,7 @@ define ptr @zero_memset_after_malloc(i64 %size) {
; based on pr25892_lite
define ptr @zero_memset_after_malloc_with_intermediate_clobbering(i64 %size) {
; CHECK-LABEL: @zero_memset_after_malloc_with_intermediate_clobbering(
-; CHECK-NEXT: [[CALL:%.*]] = call ptr @malloc(i64 [[SIZE:%.*]]) #[[ATTR7:[0-9]+]]
+; CHECK-NEXT: [[CALL:%.*]] = call ptr @malloc(i64 [[SIZE:%.*]]) #[[ATTR11:[0-9]+]]
; CHECK-NEXT: call void @clobber_memory(ptr [[CALL]])
; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr [[CALL]], i8 0, i64 [[SIZE]], i1 false)
; CHECK-NEXT: ret ptr [[CALL]]
@@ -339,7 +339,7 @@ define ptr @zero_memset_after_malloc_with_intermediate_clobbering(i64 %size) {
; based on pr25892_lite
define ptr @zero_memset_after_malloc_with_different_sizes(i64 %size) {
; CHECK-LABEL: @zero_memset_after_malloc_with_different_sizes(
-; CHECK-NEXT: [[CALL:%.*]] = call ptr @malloc(i64 [[SIZE:%.*]]) #[[ATTR7]]
+; CHECK-NEXT: [[CALL:%.*]] = call ptr @malloc(i64 [[SIZE:%.*]]) #[[ATTR11]]
; CHECK-NEXT: [[SIZE2:%.*]] = add nsw i64 [[SIZE]], -1
; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr [[CALL]], i8 0, i64 [[SIZE2]], i1 false)
; CHECK-NEXT: ret ptr [[CALL]]
@@ -376,9 +376,10 @@ define ptr @notmalloc_memset(i64 %size, ptr %notmalloc) {
; This should create a customalloc_zeroed call and eliminate the memset
define ptr @customalloc_memset(i64 %size, i64 %align) {
-; CHECK-LABEL: @customalloc_memset
-; CHECK-NEXT: [[CALL:%.*]] = call ptr @customalloc_zeroed(i64 [[SIZE:%.*]], i64 [[ALIGN:%.*]])
-; CHECK-NEXT: ret ptr [[CALL]]
+; CHECK-LABEL: @customalloc_memset(
+; CHECK-NEXT: [[CUSTOMALLOC_ZEROED:%.*]] = call ptr @customalloc_zeroed(i64 [[SIZE:%.*]], i64 [[ALIGN:%.*]])
+; CHECK-NEXT: ret ptr [[CUSTOMALLOC_ZEROED]]
+;
%call = call ptr @customalloc(i64 %size, i64 %align)
call void @llvm.memset.p0.i64(ptr %call, i8 0, i64 %size, i1 false)
ret ptr %call
@@ -390,9 +391,10 @@ declare ptr @customalloc_zeroed(i64, i64) allockind("alloc,zeroed") "alloc-famil
; This should create a customalloc_zeroed_custom_cc call and eliminate the memset while
; respecting the custom calling convention of the zeroed variant.
define cc99 ptr @customalloc_memset_custom_cc(i64 %size, i64 %align) {
-; CHECK-LABEL: @customalloc_memset_custom_cc
-; CHECK-NEXT: [[CALL:%.*]] = call cc99 ptr @customalloc_zeroed_custom_cc(i64 [[SIZE:%.*]], i64 [[ALIGN:%.*]])
-; CHECK-NEXT: ret ptr [[CALL]]
+; CHECK-LABEL: @customalloc_memset_custom_cc(
+; CHECK-NEXT: [[CUSTOMALLOC_ZEROED_CUSTOM_CC:%.*]] = call cc99 ptr @customalloc_zeroed_custom_cc(i64 [[SIZE:%.*]], i64 [[ALIGN:%.*]])
+; CHECK-NEXT: ret ptr [[CUSTOMALLOC_ZEROED_CUSTOM_CC]]
+;
%call = call cc99 ptr @customalloc_custom_cc(i64 %size, i64 %align)
call void @llvm.memset.p0.i64(ptr %call, i8 0, i64 %size, i1 false)
ret ptr %call
@@ -482,7 +484,7 @@ cleanup:
define ptr @malloc_with_no_nointer_null_check(i64 %0, i32 %1) {
; CHECK-LABEL: @malloc_with_no_nointer_null_check(
; CHECK-NEXT: entry:
-; CHECK-NEXT: [[CALL:%.*]] = call ptr @malloc(i64 [[TMP0:%.*]]) #[[ATTR7]]
+; CHECK-NEXT: [[CALL:%.*]] = call ptr @malloc(i64 [[TMP0:%.*]]) #[[ATTR11]]
; CHECK-NEXT: [[A:%.*]] = and i32 [[TMP1:%.*]], 32
; CHECK-NEXT: [[CMP:%.*]] = icmp eq i32 [[A]], 0
; CHECK-NEXT: br i1 [[CMP]], label [[CLEANUP:%.*]], label [[IF_END:%.*]]
@@ -507,7 +509,7 @@ cleanup:
; PR50143
define ptr @store_zero_after_calloc_inaccessiblememonly() {
; CHECK-LABEL: @store_zero_after_calloc_inaccessiblememonly(
-; CHECK-NEXT: [[CALL:%.*]] = tail call ptr @calloc(i64 1, i64 10) #[[ATTR7]]
+; CHECK-NEXT: [[CALL:%.*]] = tail call ptr @calloc(i64 1, i64 10) #[[ATTR11]]
; CHECK-NEXT: ret ptr [[CALL]]
;
%call = tail call ptr @calloc(i64 1, i64 10) inaccessiblememonly
@@ -600,7 +602,7 @@ define ptr @partial_zero_memset_and_store_with_dyn_index_after_calloc(i8 %v, i64
define ptr @zero_memset_after_calloc_inaccessiblememonly() {
; CHECK-LABEL: @zero_memset_after_calloc_inaccessiblememonly(
-; CHECK-NEXT: [[CALL:%.*]] = tail call ptr @calloc(i64 10000, i64 4) #[[ATTR7]]
+; CHECK-NEXT: [[CALL:%.*]] = tail call ptr @calloc(i64 10000, i64 4) #[[ATTR11]]
; CHECK-NEXT: ret ptr [[CALL]]
;
%call = tail call ptr @calloc(i64 10000, i64 4) inaccessiblememonly
@@ -696,7 +698,7 @@ if.end:
define ptr @readnone_malloc() {
; CHECK-LABEL: @readnone_malloc(
-; CHECK-NEXT: [[ALLOC:%.*]] = call ptr @malloc(i64 16) #[[ATTR8:[0-9]+]]
+; CHECK-NEXT: [[ALLOC:%.*]] = call ptr @malloc(i64 16) #[[ATTR12:[0-9]+]]
; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr [[ALLOC]], i8 0, i64 16, i1 false)
; CHECK-NEXT: ret ptr [[ALLOC]]
;
@@ -1179,3 +1181,56 @@ if.else:
end:
ret void
}
+
+; Should not optimize as `*x` and `y` ptrs may have different provenance.
+define void @remove_tautological_store_of_ptr(ptr %x, ptr %y) {
+; CHECK-LABEL: @remove_tautological_store_of_ptr(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[VAL:%.*]] = load ptr, ptr [[X:%.*]], align 8
+; CHECK-NEXT: [[CMP:%.*]] = icmp eq ptr [[VAL]], [[Y:%.*]]
+; CHECK-NEXT: br i1 [[CMP]], label [[THEN:%.*]], label [[END:%.*]]
+; CHECK: then:
+; CHECK-NEXT: store ptr [[Y]], ptr [[X]], align 8
+; CHECK-NEXT: br label [[END]]
+; CHECK: end:
+; CHECK-NEXT: ret void
+;
+entry:
+ %val = load ptr, ptr %x, align 8
+ %cmp = icmp eq ptr %val, %y
+ br i1 %cmp, label %then, label %end
+
+then:
+ store ptr %y, ptr %x, align 8
+ br label %end
+
+end:
+ ret void
+}
+
+; Dominating equality `*x == null` holds, the store would introduce nullary
+; provenance. Thanks to provenance monotonicity, we are allowed to replace a
+; pointer with nullary provenance with one with potentially non-nullary provenance.
+define void @remove_tautological_store_of_null(ptr %x) {
+; CHECK-LABEL: @remove_tautological_store_of_null(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[VAL:%.*]] = load ptr, ptr [[X:%.*]], align 8
+; CHECK-NEXT: [[CMP:%.*]] = icmp eq ptr [[VAL]], null
+; CHECK-NEXT: br i1 [[CMP]], label [[THEN:%.*]], label [[END:%.*]]
+; CHECK: then:
+; CHECK-NEXT: br label [[END]]
+; CHECK: end:
+; CHECK-NEXT: ret void
+;
+entry:
+ %val = load ptr, ptr %x, align 8
+ %cmp = icmp eq ptr %val, null
+ br i1 %cmp, label %then, label %end
+
+then:
+ store ptr null, ptr %x, align 8
+ br label %end
+
+end:
+ ret void
+}
More information about the llvm-commits
mailing list