[llvm] [ValueTracking] Prove constant-offset PtrAdd into known-size allocation is not poison. (PR #187825)
Weiwen He via llvm-commits
llvm-commits at lists.llvm.org
Mon Mar 30 09:01:15 PDT 2026
https://github.com/he-weiwen updated https://github.com/llvm/llvm-project/pull/187825
>From 015983247b44c62fb2d22c3f15279352b2548740 Mon Sep 17 00:00:00 2001
From: he-weiwen <he.weiwen at outlook.com>
Date: Fri, 6 Mar 2026 14:43:44 +0000
Subject: [PATCH] [ValueTracking] Prove constant-offset PtrAdd into known-size
allocation is non-poison
---
llvm/lib/Analysis/ValueTracking.cpp | 27 ++++++
.../Attributor/call-simplify-pointer-info.ll | 14 +--
.../freeze-gep-inbounds-known-allocation.ll | 90 +++++++++++++++++++
.../Transforms/InstCombine/select-and-or.ll | 2 +-
4 files changed, 126 insertions(+), 7 deletions(-)
create mode 100644 llvm/test/Transforms/InstCombine/freeze-gep-inbounds-known-allocation.ll
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index dc10580aa2dea..b6e8cafb95207 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -7798,6 +7798,33 @@ static bool isGuaranteedNotToBeUndefOrPoison(
isa<Function>(StrippedV) || isa<ConstantPointerNull>(StrippedV))
return true;
+ // A GEP matching with PtrAdd into a known-size allocation cannot produce
+ // poison if the constant offset in bytes is non-negative, within the
+ // allocated range, and does not wrap at truncation to the pointer index type.
+ // Note that for PtrAdd, the offset in bytes is equal to the first & only
+ // index, and when the index is non-negative, no signed wrap (nusw) implies no
+ // unsigned wrap (nuw).
+ const Value *BasePtr;
+ const APInt *ConstIdx;
+ if (match(V, m_PtrAdd(m_Value(BasePtr), m_APInt(ConstIdx))) &&
+ ConstIdx->isNonNegative()) {
+ const DataLayout *DL = nullptr;
+ if (auto *AI = dyn_cast<AllocaInst>(BasePtr))
+ DL = &AI->getDataLayout();
+ else if (auto *GV = dyn_cast<GlobalVariable>(BasePtr))
+ DL = &GV->getDataLayout();
+ if (DL) {
+ unsigned IdxBitWidth = DL->getIndexTypeSizeInBits(V->getType());
+ if (ConstIdx->isSignedIntN(IdxBitWidth)) {
+ bool CanBeNull, CanBeFreed;
+ uint64_t DerefBytes =
+ BasePtr->getPointerDereferenceableBytes(*DL, CanBeNull, CanBeFreed);
+ if (!CanBeNull && DerefBytes != 0 && ConstIdx->ule(DerefBytes))
+ return true;
+ }
+ }
+ }
+
auto OpCheck = [&](const Value *V) {
return isGuaranteedNotToBeUndefOrPoison(V, AC, CtxI, DT, Depth + 1, Kind);
};
diff --git a/llvm/test/Transforms/Attributor/call-simplify-pointer-info.ll b/llvm/test/Transforms/Attributor/call-simplify-pointer-info.ll
index 1d435815d89e3..f8d48f29cd0ba 100644
--- a/llvm/test/Transforms/Attributor/call-simplify-pointer-info.ll
+++ b/llvm/test/Transforms/Attributor/call-simplify-pointer-info.ll
@@ -8,7 +8,7 @@ define internal i8 @read_arg(ptr %p) {
; CGSCC-LABEL: define {{[^@]+}}@read_arg
; CGSCC-SAME: (ptr nofree noundef nonnull readonly captures(none) dereferenceable(1022) [[P:%.*]]) #[[ATTR0:[0-9]+]] {
; CGSCC-NEXT: entry:
-; CGSCC-NEXT: [[L:%.*]] = load i8, ptr [[P]], align 1
+; CGSCC-NEXT: [[L:%.*]] = load i8, ptr [[P]], align 1, !invariant.load [[META0:![0-9]+]]
; CGSCC-NEXT: ret i8 [[L]]
;
entry:
@@ -22,7 +22,7 @@ define internal i8 @read_arg_index(ptr %p, i64 %index) {
; CGSCC-SAME: (ptr nofree noundef nonnull readonly align 16 captures(none) dereferenceable(1024) [[P:%.*]]) #[[ATTR0]] {
; CGSCC-NEXT: entry:
; CGSCC-NEXT: [[G:%.*]] = getelementptr inbounds i8, ptr [[P]], i64 2
-; CGSCC-NEXT: [[L:%.*]] = load i8, ptr [[G]], align 1
+; CGSCC-NEXT: [[L:%.*]] = load i8, ptr [[G]], align 1, !invariant.load [[META0]]
; CGSCC-NEXT: ret i8 [[L]]
;
entry:
@@ -281,14 +281,16 @@ entry:
ret i8 %r
}
+;.
+; CGSCC: attributes #[[ATTR0]] = { mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: read) }
+; CGSCC: attributes #[[ATTR1]] = { mustprogress nofree nosync nounwind willreturn memory(none) }
+; CGSCC: attributes #[[ATTR2]] = { mustprogress nofree nosync nounwind willreturn memory(argmem: read) }
+; CGSCC: attributes #[[ATTR3]] = { nofree willreturn memory(read) }
;.
; TUNIT: attributes #[[ATTR0]] = { mustprogress nofree norecurse nosync nounwind willreturn memory(none) }
; TUNIT: attributes #[[ATTR1]] = { mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: read) }
; TUNIT: attributes #[[ATTR2]] = { nofree nosync nounwind willreturn memory(read) }
; TUNIT: attributes #[[ATTR3]] = { nofree norecurse nosync nounwind willreturn memory(read) }
;.
-; CGSCC: attributes #[[ATTR0]] = { mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: read) }
-; CGSCC: attributes #[[ATTR1]] = { mustprogress nofree nosync nounwind willreturn memory(none) }
-; CGSCC: attributes #[[ATTR2]] = { mustprogress nofree nosync nounwind willreturn memory(argmem: read) }
-; CGSCC: attributes #[[ATTR3]] = { nofree willreturn memory(read) }
+; CGSCC: [[META0]] = !{}
;.
diff --git a/llvm/test/Transforms/InstCombine/freeze-gep-inbounds-known-allocation.ll b/llvm/test/Transforms/InstCombine/freeze-gep-inbounds-known-allocation.ll
new file mode 100644
index 0000000000000..3bd15375c0bee
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/freeze-gep-inbounds-known-allocation.ll
@@ -0,0 +1,90 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+target datalayout = "e-p:64:64:64-p1:64:64:64:32-i8:8:8-i16:16:16-i32:32:32-i64:64:64"
+
+ at g = global [6 x i64] zeroinitializer, align 16
+ at g_as1_large = external addrspace(1) global [3000000000 x i8]
+
+define ptr @freeze_gep_inbounds_global() {
+; CHECK-LABEL: @freeze_gep_inbounds_global(
+; CHECK-NEXT: ret ptr getelementptr inbounds nuw (i8, ptr @g, i64 8)
+;
+ %fr = freeze ptr getelementptr inbounds (i8, ptr @g, i64 8)
+ ret ptr %fr
+}
+
+define ptr @freeze_gep_inbounds_alloca() {
+; CHECK-LABEL: @freeze_gep_inbounds_alloca(
+; CHECK-NEXT: [[A:%.*]] = alloca [32 x i8], align 1
+; CHECK-NEXT: [[GEP:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 16
+; CHECK-NEXT: ret ptr [[GEP]]
+;
+ %a = alloca [32 x i8], align 1
+ %gep = getelementptr inbounds i8, ptr %a, i64 16
+ %fr = freeze ptr %gep
+ ret ptr %fr
+}
+
+define ptr @freeze_gep_inbounds_global_boundary() {
+; CHECK-LABEL: @freeze_gep_inbounds_global_boundary(
+; CHECK-NEXT: ret ptr getelementptr inbounds nuw (i8, ptr @g, i64 48)
+;
+ %fr = freeze ptr getelementptr inbounds (i8, ptr @g, i64 48)
+ ret ptr %fr
+}
+
+define ptr @freeze_gep_inbounds_global_oob() {
+; CHECK-LABEL: @freeze_gep_inbounds_global_oob(
+; CHECK-NEXT: [[FR:%.*]] = freeze ptr getelementptr inbounds nuw (i8, ptr @g, i64 49)
+; CHECK-NEXT: ret ptr [[FR]]
+;
+ %fr = freeze ptr getelementptr inbounds (i8, ptr @g, i64 49)
+ ret ptr %fr
+}
+
+define ptr @freeze_gep_inbounds_global_negative() {
+; CHECK-LABEL: @freeze_gep_inbounds_global_negative(
+; CHECK-NEXT: [[FR:%.*]] = freeze ptr getelementptr inbounds (i8, ptr @g, i64 -1)
+; CHECK-NEXT: ret ptr [[FR]]
+;
+ %fr = freeze ptr getelementptr inbounds (i8, ptr @g, i64 -1)
+ ret ptr %fr
+}
+
+define ptr addrspace(1) @freeze_gep_inbounds_as1_truncation_violation() {
+; CHECK-LABEL: @freeze_gep_inbounds_as1_truncation_violation(
+; CHECK-NEXT: [[FR:%.*]] = freeze ptr addrspace(1) getelementptr inbounds (i8, ptr addrspace(1) @g_as1_large, i32 -2094967296)
+; CHECK-NEXT: ret ptr addrspace(1) [[FR]]
+;
+ %fr = freeze ptr addrspace(1) getelementptr inbounds
+ (i8, ptr addrspace(1) @g_as1_large, i64 2200000000)
+ ret ptr addrspace(1) %fr
+}
+
+define ptr @freeze_gep_inbounds_alloca_variable_offset(i64 %n) {
+; CHECK-LABEL: @freeze_gep_inbounds_alloca_variable_offset(
+; CHECK-NEXT: [[A:%.*]] = alloca [32 x i8], align 1
+; CHECK-NEXT: [[N_FR:%.*]] = freeze i64 [[N:%.*]]
+; CHECK-NEXT: [[GEP:%.*]] = getelementptr i8, ptr [[A]], i64 [[N_FR]]
+; CHECK-NEXT: ret ptr [[GEP]]
+;
+ %a = alloca [32 x i8], align 1
+ %gep = getelementptr inbounds i8, ptr %a, i64 %n
+ %fr = freeze ptr %gep
+ ret ptr %fr
+}
+
+define <4 x ptr> @freeze_gep_inbounds_alloca_splat_poison() {
+; CHECK-LABEL: @freeze_gep_inbounds_alloca_splat_poison(
+; CHECK-NEXT: [[A:%.*]] = alloca [32 x i8], align 1
+; CHECK-NEXT: [[GEP1:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 8
+; CHECK-NEXT: [[DOTSPLATINSERT:%.*]] = insertelement <4 x ptr> poison, ptr [[GEP1]], i64 0
+; CHECK-NEXT: [[FR:%.*]] = shufflevector <4 x ptr> [[DOTSPLATINSERT]], <4 x ptr> poison, <4 x i32> zeroinitializer
+; CHECK-NEXT: ret <4 x ptr> [[FR]]
+;
+ %a = alloca [32 x i8], align 1
+ %gep = getelementptr inbounds i8, ptr %a, <4 x i64> <i64 8, i64 poison, i64 8, i64 8>
+ %fr = freeze <4 x ptr> %gep
+ ret <4 x ptr> %fr
+}
\ No newline at end of file
diff --git a/llvm/test/Transforms/InstCombine/select-and-or.ll b/llvm/test/Transforms/InstCombine/select-and-or.ll
index ac1e3f05d6083..1961e415914a1 100644
--- a/llvm/test/Transforms/InstCombine/select-and-or.ll
+++ b/llvm/test/Transforms/InstCombine/select-and-or.ll
@@ -478,7 +478,7 @@ define i1 @demorgan_select_infloop2(i1 %L) {
; CHECK-LABEL: @demorgan_select_infloop2(
; CHECK-NEXT: [[NOT_L:%.*]] = xor i1 [[L:%.*]], true
; CHECK-NEXT: [[CMP2:%.*]] = icmp ne ptr getelementptr inbounds nuw (i8, ptr @g2, i64 2), @g1
-; CHECK-NEXT: [[C15:%.*]] = select i1 [[NOT_L]], i1 [[CMP2]], i1 false
+; CHECK-NEXT: [[C15:%.*]] = and i1 [[CMP2]], [[NOT_L]]
; CHECK-NEXT: ret i1 [[C15]]
;
%not.L = xor i1 %L, true
More information about the llvm-commits
mailing list