[llvm] [ConstantFolding] Compare a global and an inbounds GEP (PR #192909)
via llvm-commits
llvm-commits at lists.llvm.org
Sun May 10 09:51:49 PDT 2026
https://github.com/mwb-cde updated https://github.com/llvm/llvm-project/pull/192909
>From d4452f45288f879cdb692c2cad8c5d0ae2d83cde Mon Sep 17 00:00:00 2001
From: "mwb.cde" <mwb.cde at gmail.com>
Date: Sun, 19 Apr 2026 09:38:52 +0100
Subject: [PATCH] [ConstantFolding] Compare a global and an inbounds GEP
An inbounds GEP with constant, non-zero indices can sometimes be
compared against the address of a global. Try to use this when
constant-folding compares.
Fixes https://github.com/llvm/llvm-project/issues/48801
---
llvm/lib/Analysis/ConstantFolding.cpp | 58 ++++++++++
.../Transforms/EarlyCSE/cmp-gep-global.ll | 104 ++++++++++++++++++
.../binop-select-cast-of-select-cond.ll | 4 +-
...hoist-xor-by-constant-from-xor-by-value.ll | 5 +-
llvm/test/Transforms/InstSimplify/compare.ll | 14 ++-
5 files changed, 176 insertions(+), 9 deletions(-)
create mode 100644 llvm/test/Transforms/EarlyCSE/cmp-gep-global.ll
diff --git a/llvm/lib/Analysis/ConstantFolding.cpp b/llvm/lib/Analysis/ConstantFolding.cpp
index e035cd1bc1ac0..b16961bac386e 100644
--- a/llvm/lib/Analysis/ConstantFolding.cpp
+++ b/llvm/lib/Analysis/ConstantFolding.cpp
@@ -1328,6 +1328,31 @@ Constant *llvm::ConstantFoldInstOperands(const Instruction *I,
AllowNonDeterministic);
}
+// Try to decide if an inbounds GEP is entirely inside the object it points to.
+// Return std::nullopt if not possible.
+static std::optional<bool> isGEPInObject(const GEPOperator *GEP,
+ const DataLayout &DL) {
+ if (!GEP->isInBounds() || !GEP->hasAllConstantIndices()) {
+ return std::nullopt;
+ }
+ if (const auto *GVar = dyn_cast<GlobalVariable>(GEP->getPointerOperand())) {
+ if (GVar->isInterposable() || GVar->hasGlobalUnnamedAddr() ||
+ isa<GlobalAlias>(GVar))
+ return std::nullopt;
+ Type *GVType = GVar->getValueType();
+ if (!GVType->isSized() || GVType->isEmptyTy())
+ return std::nullopt;
+ const auto Bitwidth = DL.getIndexTypeSizeInBits(GEP->getType());
+ APInt ObjectSize(Bitwidth, GVar->getGlobalSize(DL));
+ APInt GEPOffset(Bitwidth, 0);
+ GEP->accumulateConstantOffset(DL, GEPOffset);
+ if (GEPOffset.ult(ObjectSize))
+ return true;
+ return false;
+ }
+ return false;
+}
+
Constant *llvm::ConstantFoldCompareInstOperands(
unsigned IntPredicate, Constant *Ops0, Constant *Ops1, const DataLayout &DL,
const TargetLibraryInfo *TLI, const Instruction *I) {
@@ -1420,6 +1445,39 @@ Constant *llvm::ConstantFoldCompareInstOperands(
Ops0->getContext(),
ICmpInst::compare(Offset0, Offset1,
ICmpInst::getSignedPredicate(Predicate)));
+
+ // Try to fold a GEP/GlobalVariable comparison.
+ if (IsEqPred && isa<GlobalVariable>(Stripped0) &&
+ isa<GlobalVariable>(Stripped1)) {
+ const auto CanFoldPtr = [](Constant *Op) -> bool {
+ if (const auto *GV = dyn_cast<GlobalVariable>(Op)) {
+ if (GV->isInterposable() || GV->hasGlobalUnnamedAddr() ||
+ isa<GlobalAlias>(GV))
+ return false;
+ Type *GVType = GV->getValueType();
+ if (GVType->isSized() && !GVType->isEmptyTy())
+ return true;
+ }
+ return false;
+ };
+ bool InObject0 = CanFoldPtr(Ops0);
+ if (const auto *GEP = dyn_cast<GEPOperator>(Ops0)) {
+ if (auto CanDecide = isGEPInObject(GEP, DL))
+ InObject0 = *CanDecide;
+ else
+ InObject0 = false;
+ }
+ bool InObject1 = CanFoldPtr(Ops1);
+ if (const auto *GEP = dyn_cast<GEPOperator>(Ops1)) {
+ if (auto CanDecide = isGEPInObject(GEP, DL))
+ InObject1 = *CanDecide;
+ else
+ InObject1 = false;
+ }
+ if (InObject0 && InObject1)
+ return ConstantInt::getBool(Ops0->getContext(),
+ Predicate == CmpInst::ICMP_NE);
+ }
}
} else if (isa<ConstantExpr>(Ops1)) {
// If RHS is a constant expression, but the left side isn't, swap the
diff --git a/llvm/test/Transforms/EarlyCSE/cmp-gep-global.ll b/llvm/test/Transforms/EarlyCSE/cmp-gep-global.ll
new file mode 100644
index 0000000000000..9faad7a94942f
--- /dev/null
+++ b/llvm/test/Transforms/EarlyCSE/cmp-gep-global.ll
@@ -0,0 +1,104 @@
+; RUN: opt -S -passes=early-cse < %s | FileCheck %s
+
+%struct.anon = type { i32, i32 }
+
+declare void @foo() #2
+
+ at d = global %struct.anon zeroinitializer, align 4
+ at c = global %struct.anon zeroinitializer, align 4
+
+define i32 @test_different() #0 {
+; CHECK-LABEL: @test_different
+; CHECK-NEXT: entry:
+; CHECK-NEXT: br i1 false, label %{{.+}}, label %{{.+}}
+entry:
+ %cmp = icmp eq ptr @c, @d
+ br i1 %cmp, label %if.then, label %if.end
+
+if.then: ; preds = %entry
+ call void @foo()
+ br label %if.end
+
+if.end: ; preds = %if.then, %entry
+ ret i32 0
+}
+
+define i32 @test_different_first() #0 {
+; CHECK-LABEL: @test_different_first
+; CHECK-NEXT: entry:
+; CHECK-NEXT: br i1 false, label %{{.+}}, label %{{.+}}
+entry:
+ %cmp = icmp eq ptr @c, @d
+ br i1 %cmp, label %if.then, label %if.end
+
+if.then: ; preds = %entry
+ call void @foo()
+ br label %if.end
+
+if.end: ; preds = %if.then, %entry
+ ret i32 0
+}
+
+define i32 @test_different_offset() #0 {
+; CHECK-LABEL: @test_different_offset
+; CHECK-NEXT: entry:
+; CHECK-NEXT: br i1 false, label %{{.+}}, label %{{.+}}
+entry:
+ %cmp = icmp eq ptr @c, getelementptr inbounds nuw (i8, ptr @d, i64 4)
+ br i1 %cmp, label %if.then, label %if.end
+
+if.then: ; preds = %entry
+ call void @foo()
+ br label %if.end
+
+if.end: ; preds = %if.then, %entry
+ ret i32 0
+}
+
+define i32 @test_same() #0 {
+; CHECK-LABEL: @test_same
+; CHECK-NEXT: entry:
+; CHECK-NEXT: br i1 true, label %{{.+}}, label %{{.+}}
+entry:
+ %cmp = icmp eq ptr @c, @c
+ br i1 %cmp, label %if.then, label %if.end
+
+if.then: ; preds = %entry
+ call void @foo()
+ br label %if.end
+
+if.end: ; preds = %if.then, %entry
+ ret i32 0
+}
+
+define i32 @test_same_first() #0 {
+; CHECK-LABEL: @test_same_first
+; CHECK-NEXT: entry:
+; CHECK-NEXT: br i1 true, label %{{.+}}, label %{{.+}}
+entry:
+ %cmp = icmp eq ptr @c, @c
+ br i1 %cmp, label %if.then, label %if.end
+
+if.then: ; preds = %entry
+ call void @foo()
+ br label %if.end
+
+if.end: ; preds = %if.then, %entry
+ ret i32 0
+}
+
+define i32 @test_same_offset() #0 {
+; CHECK-LABEL: @test_same_offset
+; CHECK-NEXT: entry:
+; CHECK-NEXT: br i1 false, label %{{.+}}, label %{{.+}}
+entry:
+ %cmp = icmp eq ptr @c, getelementptr inbounds nuw (i8, ptr @c, i64 4)
+ br i1 %cmp, label %if.then, label %if.end
+
+if.then: ; preds = %entry
+ call void @foo()
+ br label %if.end
+
+if.end: ; preds = %if.then, %entry
+ ret i32 0
+}
diff --git a/llvm/test/Transforms/InstCombine/binop-select-cast-of-select-cond.ll b/llvm/test/Transforms/InstCombine/binop-select-cast-of-select-cond.ll
index 41367d55d8894..8d61cab73e549 100644
--- a/llvm/test/Transforms/InstCombine/binop-select-cast-of-select-cond.ll
+++ b/llvm/test/Transforms/InstCombine/binop-select-cast-of-select-cond.ll
@@ -231,9 +231,7 @@ define <2 x i8> @vectorized_add(<2 x i1> %c, <2 x i8> %arg) {
define i64 @pr64669(i64 %a) {
; CHECK-LABEL: define i64 @pr64669
; CHECK-SAME: (i64 [[A:%.*]]) {
-; CHECK-NEXT: [[CMP_NOT:%.*]] = icmp eq ptr getelementptr inbounds nuw (i8, ptr @b, i64 100), @c
-; CHECK-NEXT: [[TMP1:%.*]] = add i64 [[A]], 1
-; CHECK-NEXT: [[ADD:%.*]] = select i1 [[CMP_NOT]], i64 0, i64 [[TMP1]]
+; CHECK-NEXT: [[ADD:%.*]] = add nsw i64 [[A]], 1
; CHECK-NEXT: ret i64 [[ADD]]
;
%cmp = icmp ne ptr getelementptr inbounds ([72 x i32], ptr @b, i64 0, i64 25), @c
diff --git a/llvm/test/Transforms/InstCombine/hoist-xor-by-constant-from-xor-by-value.ll b/llvm/test/Transforms/InstCombine/hoist-xor-by-constant-from-xor-by-value.ll
index 804843779b25e..395be457cf90a 100644
--- a/llvm/test/Transforms/InstCombine/hoist-xor-by-constant-from-xor-by-value.ll
+++ b/llvm/test/Transforms/InstCombine/hoist-xor-by-constant-from-xor-by-value.ll
@@ -94,12 +94,9 @@ entry:
define i16 @constantexpr2() {
; CHECK-LABEL: @constantexpr2(
-; CHECK-NEXT: [[I0:%.*]] = icmp ne ptr getelementptr inbounds nuw (i8, ptr @global_constant3, i64 40), @global_constant4
-; CHECK-NEXT: [[I1:%.*]] = zext i1 [[I0]] to i16
; CHECK-NEXT: [[I2:%.*]] = load ptr, ptr @global_constant5, align 1
; CHECK-NEXT: [[I3:%.*]] = load i16, ptr [[I2]], align 1
-; CHECK-NEXT: [[I4:%.*]] = xor i16 [[I3]], [[I1]]
-; CHECK-NEXT: [[I5:%.*]] = xor i16 [[I4]], -1
+; CHECK-NEXT: [[I5:%.*]] = xor i16 [[I3]], -2
; CHECK-NEXT: ret i16 [[I5]]
;
%i0 = icmp ne ptr getelementptr inbounds ([6 x [1 x i64]], ptr @global_constant3, i16 0, i16 5, i16 0), @global_constant4
diff --git a/llvm/test/Transforms/InstSimplify/compare.ll b/llvm/test/Transforms/InstSimplify/compare.ll
index ba10ea532a34a..26100848d1f23 100644
--- a/llvm/test/Transforms/InstSimplify/compare.ll
+++ b/llvm/test/Transforms/InstSimplify/compare.ll
@@ -3357,8 +3357,7 @@ define i1 @globals_inequal() {
; TODO: Never equal
define i1 @globals_offset_inequal() {
; CHECK-LABEL: @globals_offset_inequal(
-; CHECK-NEXT: [[RES:%.*]] = icmp ne ptr getelementptr inbounds nuw (i8, ptr @A, i32 1), getelementptr inbounds nuw (i8, ptr @B, i32 1)
-; CHECK-NEXT: ret i1 [[RES]]
+; CHECK-NEXT: ret i1 true
;
%a.off = getelementptr i8, ptr @A, i32 1
%b.off = getelementptr i8, ptr @B, i32 1
@@ -3366,6 +3365,17 @@ define i1 @globals_offset_inequal() {
ret i1 %res
}
+define i1 @globals_offset_past_end_inequal() {
+; CHECK-LABEL: @globals_offset_past_end_inequal(
+; CHECK-NEXT: [[RES:%.*]] = icmp ne ptr getelementptr inbounds nuw (i8, ptr @A, i32 4), getelementptr inbounds nuw (i8, ptr @B, i32 4)
+; CHECK-NEXT: ret i1 [[RES]]
+;
+ %a.off = getelementptr i32, ptr @A, i32 1
+ %b.off = getelementptr i32, ptr @B, i32 1
+ %res = icmp ne ptr %a.off, %b.off
+ ret i1 %res
+}
+
; Never equal
define i1 @test_byval_global_inequal(ptr byval(i32) %a) {
More information about the llvm-commits
mailing list