[llvm] [InstSimplify] Consider `dereferenceable(N)` when simplifying pointer equalities (PR #203867)
Antonio Frighetto via llvm-commits
llvm-commits at lists.llvm.org
Mon Jun 15 09:11:17 PDT 2026
https://github.com/antoniofrighetto updated https://github.com/llvm/llvm-project/pull/203867
>From ff071f46563eb598539306c4a24c04ae339009e6 Mon Sep 17 00:00:00 2001
From: Antonio Frighetto <me at antoniofrighetto.com>
Date: Mon, 15 Jun 2026 12:07:58 +0200
Subject: [PATCH 1/4] [InstSimplify] Precommit tests (NFC)
---
llvm/test/Transforms/InstSimplify/compare.ll | 130 +++++++++++++++++++
1 file changed, 130 insertions(+)
diff --git a/llvm/test/Transforms/InstSimplify/compare.ll b/llvm/test/Transforms/InstSimplify/compare.ll
index ba10ea532a34a..559a24ab79bc1 100644
--- a/llvm/test/Transforms/InstSimplify/compare.ll
+++ b/llvm/test/Transforms/InstSimplify/compare.ll
@@ -3579,5 +3579,135 @@ declare i64 @llvm.vscale.i64()
; TODO: Add coverage for global aliases, link once, etc..
+; Test dereferenceable(N) arguments when folding pointer equalities.
+
+define i1 @icmp_eq_arg_derefable_and_alloca(ptr dereferenceable(24) %p) {
+; CHECK-LABEL: @icmp_eq_arg_derefable_and_alloca(
+; CHECK-NEXT: [[Q:%.*]] = alloca [3 x double], align 8
+; CHECK-NEXT: [[C:%.*]] = icmp eq ptr [[P:%.*]], [[Q]]
+; CHECK-NEXT: ret i1 [[C]]
+;
+ %q = alloca [3 x double], align 8
+ %c = icmp eq ptr %p, %q
+ ret i1 %c
+}
+
+define i1 @icmp_ne_arg_derefable_and_alloca(ptr dereferenceable(24) %p) {
+; CHECK-LABEL: @icmp_ne_arg_derefable_and_alloca(
+; CHECK-NEXT: [[Q:%.*]] = alloca [3 x double], align 8
+; CHECK-NEXT: [[C:%.*]] = icmp ne ptr [[P:%.*]], [[Q]]
+; CHECK-NEXT: ret i1 [[C]]
+;
+ %q = alloca [3 x double], align 8
+ %c = icmp ne ptr %p, %q
+ ret i1 %c
+}
+
+define i1 @icmp_eq_arg_derefable_null_valid_and_alloca(ptr dereferenceable(24) %p) null_pointer_is_valid {
+; CHECK-LABEL: @icmp_eq_arg_derefable_null_valid_and_alloca(
+; CHECK-NEXT: [[Q:%.*]] = alloca [3 x double], align 8
+; CHECK-NEXT: [[C:%.*]] = icmp eq ptr [[P:%.*]], [[Q]]
+; CHECK-NEXT: ret i1 [[C]]
+;
+ %q = alloca [3 x double], align 8
+ %c = icmp eq ptr %p, %q
+ ret i1 %c
+}
+
+define i1 @icmp_eq_arg_derefable_and_noalias_call(ptr dereferenceable(24) %p) {
+; CHECK-LABEL: @icmp_eq_arg_derefable_and_noalias_call(
+; CHECK-NEXT: [[Q:%.*]] = call noalias ptr @opaque()
+; CHECK-NEXT: [[C:%.*]] = icmp eq ptr [[P:%.*]], [[Q]]
+; CHECK-NEXT: ret i1 [[C]]
+;
+ %q = call noalias ptr @opaque()
+ %c = icmp eq ptr %p, %q
+ ret i1 %c
+}
+
+define i1 @icmp_eq_gep_of_arg_derefable_and_alloca(ptr dereferenceable(24) %p) {
+; CHECK-LABEL: @icmp_eq_gep_of_arg_derefable_and_alloca(
+; CHECK-NEXT: [[Q:%.*]] = alloca [3 x double], align 8
+; CHECK-NEXT: [[P2:%.*]] = getelementptr i8, ptr [[P:%.*]], i64 8
+; CHECK-NEXT: [[C:%.*]] = icmp eq ptr [[P2]], [[Q]]
+; CHECK-NEXT: ret i1 [[C]]
+;
+ %q = alloca [3 x double], align 8
+ %p2 = getelementptr i8, ptr %p, i64 8
+ %c = icmp eq ptr %p2, %q
+ ret i1 %c
+}
+
+%struct.S = type { [3 x double] }
+
+define i1 @icmp_eq_arg_derefable_and_byval_arg(ptr byval(%struct.S) %p, ptr dereferenceable(24) %q) {
+; CHECK-LABEL: @icmp_eq_arg_derefable_and_byval_arg(
+; CHECK-NEXT: [[C:%.*]] = icmp eq ptr [[P:%.*]], [[Q:%.*]]
+; CHECK-NEXT: ret i1 [[C]]
+;
+ %c = icmp eq ptr %p, %q
+ ret i1 %c
+}
+
+; Negative tests.
+
+define i1 @icmp_eq_no_arg_derefable(ptr %p) {
+; CHECK-LABEL: @icmp_eq_no_arg_derefable(
+; CHECK-NEXT: [[Q:%.*]] = alloca [3 x double], align 8
+; CHECK-NEXT: [[C:%.*]] = icmp eq ptr [[P:%.*]], [[Q]]
+; CHECK-NEXT: ret i1 [[C]]
+;
+ %q = alloca [3 x double], align 8
+ %c = icmp eq ptr %p, %q
+ ret i1 %c
+}
+
+define i1 @icmp_eq_gep_of_arg_derefable_past_one(ptr dereferenceable(24) %p) {
+; CHECK-LABEL: @icmp_eq_gep_of_arg_derefable_past_one(
+; CHECK-NEXT: [[Q:%.*]] = alloca [3 x double], align 8
+; CHECK-NEXT: [[P2:%.*]] = getelementptr i8, ptr [[P:%.*]], i64 24
+; CHECK-NEXT: [[C:%.*]] = icmp eq ptr [[P2]], [[Q]]
+; CHECK-NEXT: ret i1 [[C]]
+;
+ %q = alloca [3 x double], align 8
+ %p2 = getelementptr i8, ptr %p, i64 24
+ %c = icmp eq ptr %p2, %q
+ ret i1 %c
+}
+
+define i1 @icmp_eq_gep_of_alloca_past_one(ptr dereferenceable(24) %p) {
+; CHECK-LABEL: @icmp_eq_gep_of_alloca_past_one(
+; CHECK-NEXT: [[Q:%.*]] = alloca [10 x double], align 8
+; CHECK-NEXT: [[Q2:%.*]] = getelementptr i8, ptr [[Q]], i64 80
+; CHECK-NEXT: [[C:%.*]] = icmp eq ptr [[P:%.*]], [[Q2]]
+; CHECK-NEXT: ret i1 [[C]]
+;
+ %q = alloca [10 x double], align 8
+ %q2 = getelementptr i8, ptr %q, i64 80
+ %c = icmp eq ptr %p, %q2
+ ret i1 %c
+}
+
+define i1 @icmp_eq_arg_derefable_or_null_and_alloca(ptr dereferenceable_or_null(24) %p) {
+; CHECK-LABEL: @icmp_eq_arg_derefable_or_null_and_alloca(
+; CHECK-NEXT: [[Q:%.*]] = alloca [3 x double], align 8
+; CHECK-NEXT: [[C:%.*]] = icmp eq ptr [[P:%.*]], [[Q]]
+; CHECK-NEXT: ret i1 [[C]]
+;
+ %q = alloca [3 x double], align 8
+ %c = icmp eq ptr %p, %q
+ ret i1 %c
+}
+
+define i1 @icmp_eq_two_args_derefable(ptr dereferenceable(24) %p, ptr dereferenceable(24) %q) {
+; CHECK-LABEL: @icmp_eq_two_args_derefable(
+; CHECK-NEXT: [[C:%.*]] = icmp eq ptr [[P:%.*]], [[Q:%.*]]
+; CHECK-NEXT: ret i1 [[C]]
+;
+ %c = icmp eq ptr %p, %q
+ ret i1 %c
+}
+
+declare noalias ptr @opaque()
attributes #0 = { null_pointer_is_valid }
>From 434c328377da3735a1f15889a874b98797d4d7bb Mon Sep 17 00:00:00 2001
From: Antonio Frighetto <me at antoniofrighetto.com>
Date: Mon, 15 Jun 2026 12:08:51 +0200
Subject: [PATCH 2/4] [InstSimplify] Consider `dereferenceable(N)` when
simplifying pointer equalities
Extend `computePointerICmp` to leverage `dereferenceable(N)` attribute
when simplifying pointer equality comparisons. Per attribute semantics,
an argument pointer marked as such cannot be a one-past-the-end pointer
to some object, thus it cannot equal the start of an adjacent object.
This lets us prove inequality between a `dereferenceable` argument and
storage allocated within the function.
Fixes: https://github.com/llvm/llvm-project/issues/200511.
---
llvm/lib/Analysis/InstructionSimplify.cpp | 77 +++++++++++++-------
llvm/test/Transforms/InstSimplify/compare.ll | 23 ++----
2 files changed, 57 insertions(+), 43 deletions(-)
diff --git a/llvm/lib/Analysis/InstructionSimplify.cpp b/llvm/lib/Analysis/InstructionSimplify.cpp
index b66cdc4c54e78..c22d378a1b2e9 100644
--- a/llvm/lib/Analysis/InstructionSimplify.cpp
+++ b/llvm/lib/Analysis/InstructionSimplify.cpp
@@ -2634,6 +2634,16 @@ static Value *extractEquivalentCondition(Value *V, CmpPredicate Pred,
return nullptr;
}
+static bool isByValArg(const Value *V) {
+ const Argument *A = dyn_cast<Argument>(V);
+ return A && A->hasByValAttr();
+}
+
+static bool isDereferenceableArg(const Value *V) {
+ const Argument *A = dyn_cast<Argument>(V);
+ return A && A->getType()->isPointerTy() && A->getDereferenceableBytes() > 0;
+}
+
/// Return true if the underlying object (storage) must be disjoint from
/// storage returned by any noalias return call.
static bool isAllocDisjoint(const Value *V) {
@@ -2648,9 +2658,10 @@ static bool isAllocDisjoint(const Value *V) {
return (GV->hasLocalLinkage() || GV->hasHiddenVisibility() ||
GV->hasProtectedVisibility() || GV->hasGlobalUnnamedAddr()) &&
!GV->isThreadLocal();
- if (const Argument *A = dyn_cast<Argument>(V))
- return A->hasByValAttr();
- return false;
+ // Byval and dereferenceable arguments point to storage accessible to the
+ // caller, which is disjoint from the allocated storage returned by a noalias
+ // pointer.
+ return isByValArg(V) || isDereferenceableArg(V);
}
/// Return true if V1 and V2 are each the base of some distict storage region
@@ -2682,17 +2693,25 @@ static bool haveNonOverlappingStorage(const Value *V1, const Value *V2) {
//
// So, we'll assume that two non-empty allocas have different addresses
// for now.
- auto isByValArg = [](const Value *V) {
- const Argument *A = dyn_cast<Argument>(V);
- return A && A->hasByValAttr();
- };
-
- // Byval args are backed by store which does not overlap with each other,
- // allocas, or globals.
+ //
+ // Furthermore, an argument marked with the `dereferenceable(N)` attribute is
+ // guaranteed to point to N loadable bytes. Such a pointer cannot be a
+ // one-past-the-end pointer whose address happens to coincide with the start
+ // of another object (e.g., an alloca), as loading from a one-past-the-end
+ // address would be UB (thus, in contrast with the premise).
+
+ // Byval args are backed by storage that does not overlap with allocas,
+ // globals, other byval args, or any dereferenceable argument.
if (isByValArg(V1))
- return isa<AllocaInst>(V2) || isa<GlobalVariable>(V2) || isByValArg(V2);
+ return isa<AllocaInst>(V2) || isa<GlobalVariable>(V2) || isByValArg(V2) ||
+ isDereferenceableArg(V2);
if (isByValArg(V2))
- return isa<AllocaInst>(V1) || isa<GlobalVariable>(V1) || isByValArg(V1);
+ return isa<AllocaInst>(V1) || isa<GlobalVariable>(V1) || isByValArg(V1) ||
+ isDereferenceableArg(V1);
+
+ if ((isDereferenceableArg(V1) && isa<AllocaInst>(V2)) ||
+ (isDereferenceableArg(V2) && isa<AllocaInst>(V1)))
+ return true;
return isa<AllocaInst>(V1) &&
(isa<AllocaInst>(V2) || isa<GlobalVariable>(V2));
@@ -2766,23 +2785,29 @@ static Constant *computePointerICmp(CmpPredicate Pred, Value *LHS, Value *RHS,
if (ICmpInst::isEquality(Pred)) {
// Different non-empty allocations that exist at the same time have
// different addresses (if the program can tell). If the offsets are
- // within the bounds of their allocations (and not one-past-the-end!
- // so we can't use inbounds!), and their allocations aren't the same,
+ // within the bounds of their allocations (and not one-past-the-end,
+ // so inbounds is not sufficient), and their allocations aren't the same,
// the pointers are not equal.
if (haveNonOverlappingStorage(LHS, RHS)) {
uint64_t LHSSize, RHSSize;
ObjectSizeOpts Opts;
Opts.EvalMode = ObjectSizeOpts::Mode::Min;
- auto *F = [](Value *V) -> Function * {
- if (auto *I = dyn_cast<Instruction>(V))
- return I->getFunction();
- if (auto *A = dyn_cast<Argument>(V))
- return A->getParent();
- return nullptr;
- }(LHS);
+ const Function *F = Q.CxtI ? Q.CxtI->getFunction() : nullptr;
Opts.NullIsUnknownSize = F ? NullPointerIsDefined(F) : true;
- if (getObjectSize(LHS, LHSSize, DL, TLI, Opts) && LHSSize != 0 &&
- getObjectSize(RHS, RHSSize, DL, TLI, Opts) && RHSSize != 0) {
+
+ // Size of object V, falling back to `dereferenceable(N)` attribute on an
+ // argument when getObjectSize cannot determine a concrete size.
+ auto GetKnownSize = [&](Value *V, uint64_t &Size) {
+ if (getObjectSize(V, Size, DL, TLI, Opts) && Size != 0)
+ return true;
+ if (auto *A = dyn_cast<Argument>(V)) {
+ Size = A->getDereferenceableBytes();
+ return true;
+ }
+ return false;
+ };
+
+ if (GetKnownSize(LHS, LHSSize) && GetKnownSize(RHS, RHSSize)) {
APInt Dist = LHSOffset - RHSOffset;
if (Dist.isNonNegative() ? Dist.ult(LHSSize) : (-Dist).ult(RHSSize))
return ConstantInt::get(getCompareTy(LHS),
@@ -2800,7 +2825,7 @@ static Constant *computePointerICmp(CmpPredicate Pred, Value *LHS, Value *RHS,
getUnderlyingObjects(RHS, RHSUObjs);
// Is the set of underlying objects all noalias calls?
- auto IsNAC = [](ArrayRef<const Value *> Objects) {
+ auto IsNoAliasCall = [](ArrayRef<const Value *> Objects) {
return all_of(Objects, isNoAliasCall);
};
@@ -2811,8 +2836,8 @@ static Constant *computePointerICmp(CmpPredicate Pred, Value *LHS, Value *RHS,
return all_of(Objects, ::isAllocDisjoint);
};
- if ((IsNAC(LHSUObjs) && IsAllocDisjoint(RHSUObjs)) ||
- (IsNAC(RHSUObjs) && IsAllocDisjoint(LHSUObjs)))
+ if ((IsNoAliasCall(LHSUObjs) && IsAllocDisjoint(RHSUObjs)) ||
+ (IsNoAliasCall(RHSUObjs) && IsAllocDisjoint(LHSUObjs)))
return ConstantInt::get(getCompareTy(LHS),
!CmpInst::isTrueWhenEqual(Pred));
diff --git a/llvm/test/Transforms/InstSimplify/compare.ll b/llvm/test/Transforms/InstSimplify/compare.ll
index 559a24ab79bc1..79e7e105e1bd7 100644
--- a/llvm/test/Transforms/InstSimplify/compare.ll
+++ b/llvm/test/Transforms/InstSimplify/compare.ll
@@ -3583,9 +3583,7 @@ declare i64 @llvm.vscale.i64()
define i1 @icmp_eq_arg_derefable_and_alloca(ptr dereferenceable(24) %p) {
; CHECK-LABEL: @icmp_eq_arg_derefable_and_alloca(
-; CHECK-NEXT: [[Q:%.*]] = alloca [3 x double], align 8
-; CHECK-NEXT: [[C:%.*]] = icmp eq ptr [[P:%.*]], [[Q]]
-; CHECK-NEXT: ret i1 [[C]]
+; CHECK-NEXT: ret i1 false
;
%q = alloca [3 x double], align 8
%c = icmp eq ptr %p, %q
@@ -3594,9 +3592,7 @@ define i1 @icmp_eq_arg_derefable_and_alloca(ptr dereferenceable(24) %p) {
define i1 @icmp_ne_arg_derefable_and_alloca(ptr dereferenceable(24) %p) {
; CHECK-LABEL: @icmp_ne_arg_derefable_and_alloca(
-; CHECK-NEXT: [[Q:%.*]] = alloca [3 x double], align 8
-; CHECK-NEXT: [[C:%.*]] = icmp ne ptr [[P:%.*]], [[Q]]
-; CHECK-NEXT: ret i1 [[C]]
+; CHECK-NEXT: ret i1 true
;
%q = alloca [3 x double], align 8
%c = icmp ne ptr %p, %q
@@ -3605,9 +3601,7 @@ define i1 @icmp_ne_arg_derefable_and_alloca(ptr dereferenceable(24) %p) {
define i1 @icmp_eq_arg_derefable_null_valid_and_alloca(ptr dereferenceable(24) %p) null_pointer_is_valid {
; CHECK-LABEL: @icmp_eq_arg_derefable_null_valid_and_alloca(
-; CHECK-NEXT: [[Q:%.*]] = alloca [3 x double], align 8
-; CHECK-NEXT: [[C:%.*]] = icmp eq ptr [[P:%.*]], [[Q]]
-; CHECK-NEXT: ret i1 [[C]]
+; CHECK-NEXT: ret i1 false
;
%q = alloca [3 x double], align 8
%c = icmp eq ptr %p, %q
@@ -3617,8 +3611,7 @@ define i1 @icmp_eq_arg_derefable_null_valid_and_alloca(ptr dereferenceable(24) %
define i1 @icmp_eq_arg_derefable_and_noalias_call(ptr dereferenceable(24) %p) {
; CHECK-LABEL: @icmp_eq_arg_derefable_and_noalias_call(
; CHECK-NEXT: [[Q:%.*]] = call noalias ptr @opaque()
-; CHECK-NEXT: [[C:%.*]] = icmp eq ptr [[P:%.*]], [[Q]]
-; CHECK-NEXT: ret i1 [[C]]
+; CHECK-NEXT: ret i1 false
;
%q = call noalias ptr @opaque()
%c = icmp eq ptr %p, %q
@@ -3627,10 +3620,7 @@ define i1 @icmp_eq_arg_derefable_and_noalias_call(ptr dereferenceable(24) %p) {
define i1 @icmp_eq_gep_of_arg_derefable_and_alloca(ptr dereferenceable(24) %p) {
; CHECK-LABEL: @icmp_eq_gep_of_arg_derefable_and_alloca(
-; CHECK-NEXT: [[Q:%.*]] = alloca [3 x double], align 8
-; CHECK-NEXT: [[P2:%.*]] = getelementptr i8, ptr [[P:%.*]], i64 8
-; CHECK-NEXT: [[C:%.*]] = icmp eq ptr [[P2]], [[Q]]
-; CHECK-NEXT: ret i1 [[C]]
+; CHECK-NEXT: ret i1 false
;
%q = alloca [3 x double], align 8
%p2 = getelementptr i8, ptr %p, i64 8
@@ -3642,8 +3632,7 @@ define i1 @icmp_eq_gep_of_arg_derefable_and_alloca(ptr dereferenceable(24) %p) {
define i1 @icmp_eq_arg_derefable_and_byval_arg(ptr byval(%struct.S) %p, ptr dereferenceable(24) %q) {
; CHECK-LABEL: @icmp_eq_arg_derefable_and_byval_arg(
-; CHECK-NEXT: [[C:%.*]] = icmp eq ptr [[P:%.*]], [[Q:%.*]]
-; CHECK-NEXT: ret i1 [[C]]
+; CHECK-NEXT: ret i1 false
;
%c = icmp eq ptr %p, %q
ret i1 %c
>From 743f8889d46dd40c2c799ffaef1690a44a75cf4c Mon Sep 17 00:00:00 2001
From: Antonio Frighetto <me at antoniofrighetto.com>
Date: Mon, 15 Jun 2026 16:28:00 +0200
Subject: [PATCH 3/4] !fixup improve code, drop noalias, negative tests
---
llvm/lib/Analysis/InstructionSimplify.cpp | 21 +++--
llvm/test/Transforms/InstSimplify/compare.ll | 81 +++++++++++++++++---
2 files changed, 82 insertions(+), 20 deletions(-)
diff --git a/llvm/lib/Analysis/InstructionSimplify.cpp b/llvm/lib/Analysis/InstructionSimplify.cpp
index c22d378a1b2e9..7c9dcf9dd886e 100644
--- a/llvm/lib/Analysis/InstructionSimplify.cpp
+++ b/llvm/lib/Analysis/InstructionSimplify.cpp
@@ -2658,10 +2658,12 @@ static bool isAllocDisjoint(const Value *V) {
return (GV->hasLocalLinkage() || GV->hasHiddenVisibility() ||
GV->hasProtectedVisibility() || GV->hasGlobalUnnamedAddr()) &&
!GV->isThreadLocal();
- // Byval and dereferenceable arguments point to storage accessible to the
- // caller, which is disjoint from the allocated storage returned by a noalias
- // pointer.
- return isByValArg(V) || isDereferenceableArg(V);
+ // Byval arguments point to storage accessible to the caller, which is
+ // disjoint from the allocated storage returned by a noalias pointer.
+ // TODO: possibly extend this to `dereferenceable(N)` arguments once the LLVM
+ // allocator model and its interaction with `noalias` on return values is
+ // clarified.
+ return isByValArg(V);
}
/// Return true if V1 and V2 are each the base of some distict storage region
@@ -2798,13 +2800,10 @@ static Constant *computePointerICmp(CmpPredicate Pred, Value *LHS, Value *RHS,
// Size of object V, falling back to `dereferenceable(N)` attribute on an
// argument when getObjectSize cannot determine a concrete size.
auto GetKnownSize = [&](Value *V, uint64_t &Size) {
- if (getObjectSize(V, Size, DL, TLI, Opts) && Size != 0)
- return true;
- if (auto *A = dyn_cast<Argument>(V)) {
- Size = A->getDereferenceableBytes();
- return true;
- }
- return false;
+ bool CanBeNull;
+ Size = V->getPointerDereferenceableBytes(DL, CanBeNull,
+ /*CanBeFreed=*/nullptr);
+ return Size != 0 && !CanBeNull;
};
if (GetKnownSize(LHS, LHSSize) && GetKnownSize(RHS, RHSSize)) {
diff --git a/llvm/test/Transforms/InstSimplify/compare.ll b/llvm/test/Transforms/InstSimplify/compare.ll
index 79e7e105e1bd7..18768a9d24e31 100644
--- a/llvm/test/Transforms/InstSimplify/compare.ll
+++ b/llvm/test/Transforms/InstSimplify/compare.ll
@@ -3583,47 +3583,62 @@ declare i64 @llvm.vscale.i64()
define i1 @icmp_eq_arg_derefable_and_alloca(ptr dereferenceable(24) %p) {
; CHECK-LABEL: @icmp_eq_arg_derefable_and_alloca(
+; CHECK-NEXT: [[Q:%.*]] = alloca [3 x double], align 8
+; CHECK-NEXT: call void @escape(ptr [[Q]])
; CHECK-NEXT: ret i1 false
;
%q = alloca [3 x double], align 8
+ call void @escape(ptr %q)
%c = icmp eq ptr %p, %q
ret i1 %c
}
define i1 @icmp_ne_arg_derefable_and_alloca(ptr dereferenceable(24) %p) {
; CHECK-LABEL: @icmp_ne_arg_derefable_and_alloca(
+; CHECK-NEXT: [[Q:%.*]] = alloca [3 x double], align 8
+; CHECK-NEXT: call void @escape(ptr [[Q]])
; CHECK-NEXT: ret i1 true
;
%q = alloca [3 x double], align 8
+ call void @escape(ptr %q)
%c = icmp ne ptr %p, %q
ret i1 %c
}
define i1 @icmp_eq_arg_derefable_null_valid_and_alloca(ptr dereferenceable(24) %p) null_pointer_is_valid {
; CHECK-LABEL: @icmp_eq_arg_derefable_null_valid_and_alloca(
+; CHECK-NEXT: [[Q:%.*]] = alloca [3 x double], align 8
+; CHECK-NEXT: call void @escape(ptr [[Q]])
; CHECK-NEXT: ret i1 false
;
%q = alloca [3 x double], align 8
+ call void @escape(ptr %q)
%c = icmp eq ptr %p, %q
ret i1 %c
}
-define i1 @icmp_eq_arg_derefable_and_noalias_call(ptr dereferenceable(24) %p) {
-; CHECK-LABEL: @icmp_eq_arg_derefable_and_noalias_call(
-; CHECK-NEXT: [[Q:%.*]] = call noalias ptr @opaque()
+define i1 @icmp_eq_gep_of_arg_derefable_and_alloca(ptr dereferenceable(24) %p) {
+; CHECK-LABEL: @icmp_eq_gep_of_arg_derefable_and_alloca(
+; CHECK-NEXT: [[Q:%.*]] = alloca [3 x double], align 8
+; CHECK-NEXT: call void @escape(ptr [[Q]])
; CHECK-NEXT: ret i1 false
;
- %q = call noalias ptr @opaque()
- %c = icmp eq ptr %p, %q
+ %q = alloca [3 x double], align 8
+ call void @escape(ptr %q)
+ %p2 = getelementptr i8, ptr %p, i64 8
+ %c = icmp eq ptr %p2, %q
ret i1 %c
}
-define i1 @icmp_eq_gep_of_arg_derefable_and_alloca(ptr dereferenceable(24) %p) {
-; CHECK-LABEL: @icmp_eq_gep_of_arg_derefable_and_alloca(
+define i1 @icmp_eq_gep_of_arg_derefable_negative_offset(ptr dereferenceable(24) %p) {
+; CHECK-LABEL: @icmp_eq_gep_of_arg_derefable_negative_offset(
+; CHECK-NEXT: [[Q:%.*]] = alloca [3 x double], align 8
+; CHECK-NEXT: call void @escape(ptr [[Q]])
; CHECK-NEXT: ret i1 false
;
%q = alloca [3 x double], align 8
- %p2 = getelementptr i8, ptr %p, i64 8
+ call void @escape(ptr %q)
+ %p2 = getelementptr i8, ptr %p, i64 -8
%c = icmp eq ptr %p2, %q
ret i1 %c
}
@@ -3643,10 +3658,12 @@ define i1 @icmp_eq_arg_derefable_and_byval_arg(ptr byval(%struct.S) %p, ptr dere
define i1 @icmp_eq_no_arg_derefable(ptr %p) {
; CHECK-LABEL: @icmp_eq_no_arg_derefable(
; CHECK-NEXT: [[Q:%.*]] = alloca [3 x double], align 8
+; CHECK-NEXT: call void @escape(ptr [[Q]])
; CHECK-NEXT: [[C:%.*]] = icmp eq ptr [[P:%.*]], [[Q]]
; CHECK-NEXT: ret i1 [[C]]
;
%q = alloca [3 x double], align 8
+ call void @escape(ptr %q)
%c = icmp eq ptr %p, %q
ret i1 %c
}
@@ -3654,36 +3671,71 @@ define i1 @icmp_eq_no_arg_derefable(ptr %p) {
define i1 @icmp_eq_gep_of_arg_derefable_past_one(ptr dereferenceable(24) %p) {
; CHECK-LABEL: @icmp_eq_gep_of_arg_derefable_past_one(
; CHECK-NEXT: [[Q:%.*]] = alloca [3 x double], align 8
+; CHECK-NEXT: call void @escape(ptr [[Q]])
; CHECK-NEXT: [[P2:%.*]] = getelementptr i8, ptr [[P:%.*]], i64 24
; CHECK-NEXT: [[C:%.*]] = icmp eq ptr [[P2]], [[Q]]
; CHECK-NEXT: ret i1 [[C]]
;
%q = alloca [3 x double], align 8
+ call void @escape(ptr %q)
%p2 = getelementptr i8, ptr %p, i64 24
%c = icmp eq ptr %p2, %q
ret i1 %c
}
+; %p2 = %p - 32 may equal %q if %p happens to point just past the alloca.
+define i1 @icmp_eq_gep_of_arg_derefable_negative_offset_2(ptr dereferenceable(24) %p) {
+; CHECK-LABEL: @icmp_eq_gep_of_arg_derefable_negative_offset_2(
+; CHECK-NEXT: [[Q:%.*]] = alloca [3 x double], align 8
+; CHECK-NEXT: call void @escape(ptr [[Q]])
+; CHECK-NEXT: [[P2:%.*]] = getelementptr i8, ptr [[P:%.*]], i64 -32
+; CHECK-NEXT: [[C:%.*]] = icmp eq ptr [[P2]], [[Q]]
+; CHECK-NEXT: ret i1 [[C]]
+;
+ %q = alloca [3 x double], align 8
+ call void @escape(ptr %q)
+ %p2 = getelementptr i8, ptr %p, i64 -32
+ %c = icmp eq ptr %p2, %q
+ ret i1 %c
+}
+
define i1 @icmp_eq_gep_of_alloca_past_one(ptr dereferenceable(24) %p) {
; CHECK-LABEL: @icmp_eq_gep_of_alloca_past_one(
; CHECK-NEXT: [[Q:%.*]] = alloca [10 x double], align 8
+; CHECK-NEXT: call void @escape(ptr [[Q]])
; CHECK-NEXT: [[Q2:%.*]] = getelementptr i8, ptr [[Q]], i64 80
; CHECK-NEXT: [[C:%.*]] = icmp eq ptr [[P:%.*]], [[Q2]]
; CHECK-NEXT: ret i1 [[C]]
;
%q = alloca [10 x double], align 8
+ call void @escape(ptr %q)
%q2 = getelementptr i8, ptr %q, i64 80
%c = icmp eq ptr %p, %q2
ret i1 %c
}
+define i1 @icmp_eq_gep_of_alloca_negative_offset(ptr dereferenceable(24) %p) {
+; CHECK-LABEL: @icmp_eq_gep_of_alloca_negative_offset(
+; CHECK-NEXT: [[Q:%.*]] = alloca [3 x double], align 8
+; CHECK-NEXT: call void @escape(ptr [[Q]])
+; CHECK-NEXT: ret i1 false
+;
+ %q = alloca [3 x double], align 8
+ call void @escape(ptr %q)
+ %q2 = getelementptr i8, ptr %q, i64 -8
+ %c = icmp eq ptr %p, %q2
+ ret i1 %c
+}
+
define i1 @icmp_eq_arg_derefable_or_null_and_alloca(ptr dereferenceable_or_null(24) %p) {
; CHECK-LABEL: @icmp_eq_arg_derefable_or_null_and_alloca(
; CHECK-NEXT: [[Q:%.*]] = alloca [3 x double], align 8
+; CHECK-NEXT: call void @escape(ptr [[Q]])
; CHECK-NEXT: [[C:%.*]] = icmp eq ptr [[P:%.*]], [[Q]]
; CHECK-NEXT: ret i1 [[C]]
;
%q = alloca [3 x double], align 8
+ call void @escape(ptr %q)
%c = icmp eq ptr %p, %q
ret i1 %c
}
@@ -3697,6 +3749,17 @@ define i1 @icmp_eq_two_args_derefable(ptr dereferenceable(24) %p, ptr dereferenc
ret i1 %c
}
-declare noalias ptr @opaque()
+ at g = global [3 x double] zeroinitializer
+
+define i1 @icmp_eq_arg_derefable_and_global(ptr dereferenceable(24) %p) {
+; CHECK-LABEL: @icmp_eq_arg_derefable_and_global(
+; CHECK-NEXT: [[C:%.*]] = icmp eq ptr [[P:%.*]], @g
+; CHECK-NEXT: ret i1 [[C]]
+;
+ %c = icmp eq ptr %p, @g
+ ret i1 %c
+}
+
+declare void @escape(ptr)
attributes #0 = { null_pointer_is_valid }
>From ce60a4058319899f095c9e6066b5912d197380b4 Mon Sep 17 00:00:00 2001
From: Antonio Frighetto <me at antoniofrighetto.com>
Date: Mon, 15 Jun 2026 18:10:46 +0200
Subject: [PATCH 4/4] !fixup drop opts
---
llvm/lib/Analysis/InstructionSimplify.cpp | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/llvm/lib/Analysis/InstructionSimplify.cpp b/llvm/lib/Analysis/InstructionSimplify.cpp
index 7c9dcf9dd886e..3400839f7c7ea 100644
--- a/llvm/lib/Analysis/InstructionSimplify.cpp
+++ b/llvm/lib/Analysis/InstructionSimplify.cpp
@@ -2791,12 +2791,6 @@ static Constant *computePointerICmp(CmpPredicate Pred, Value *LHS, Value *RHS,
// so inbounds is not sufficient), and their allocations aren't the same,
// the pointers are not equal.
if (haveNonOverlappingStorage(LHS, RHS)) {
- uint64_t LHSSize, RHSSize;
- ObjectSizeOpts Opts;
- Opts.EvalMode = ObjectSizeOpts::Mode::Min;
- const Function *F = Q.CxtI ? Q.CxtI->getFunction() : nullptr;
- Opts.NullIsUnknownSize = F ? NullPointerIsDefined(F) : true;
-
// Size of object V, falling back to `dereferenceable(N)` attribute on an
// argument when getObjectSize cannot determine a concrete size.
auto GetKnownSize = [&](Value *V, uint64_t &Size) {
@@ -2806,6 +2800,7 @@ static Constant *computePointerICmp(CmpPredicate Pred, Value *LHS, Value *RHS,
return Size != 0 && !CanBeNull;
};
+ uint64_t LHSSize, RHSSize;
if (GetKnownSize(LHS, LHSSize) && GetKnownSize(RHS, RHSSize)) {
APInt Dist = LHSOffset - RHSOffset;
if (Dist.isNonNegative() ? Dist.ult(LHSSize) : (-Dist).ult(RHSSize))
More information about the llvm-commits
mailing list