[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