[llvm] [InstCombine] Always rewrite multi-use GEP for pointer difference (PR #142787)

Nikita Popov via llvm-commits llvm-commits at lists.llvm.org
Thu Jun 5 04:07:55 PDT 2025


https://github.com/nikic updated https://github.com/llvm/llvm-project/pull/142787

>From 6820867a3574084a2b4fef9f02e7cb2d829c8217 Mon Sep 17 00:00:00 2001
From: Nikita Popov <npopov at redhat.com>
Date: Wed, 4 Jun 2025 17:08:01 +0200
Subject: [PATCH 1/4] [InstCombine] Add test for sub gep with multi-use

---
 llvm/test/Transforms/InstCombine/sub-gep.ll | 32 +++++++++++++++++++++
 1 file changed, 32 insertions(+)

diff --git a/llvm/test/Transforms/InstCombine/sub-gep.ll b/llvm/test/Transforms/InstCombine/sub-gep.ll
index c86a1a37bd7ad..8c0b57228d9ea 100644
--- a/llvm/test/Transforms/InstCombine/sub-gep.ll
+++ b/llvm/test/Transforms/InstCombine/sub-gep.ll
@@ -760,6 +760,38 @@ entry:
   ret i64 %ret
 }
 
+declare void @use(ptr)
+
+define i64 @sub_multi_use(ptr %base, i64 %idx) {
+; CHECK-LABEL: @sub_multi_use(
+; CHECK-NEXT:    [[P2:%.*]] = getelementptr inbounds [0 x i32], ptr [[BASE:%.*]], i64 0, i64 [[IDX:%.*]]
+; CHECK-NEXT:    call void @use(ptr [[P2]])
+; CHECK-NEXT:    [[P2_IDX:%.*]] = shl nsw i64 [[IDX]], 2
+; CHECK-NEXT:    ret i64 [[P2_IDX]]
+;
+  %p2 = getelementptr inbounds [0 x i32], ptr %base, i64 0, i64 %idx
+  call void @use(ptr %p2)
+  %i1 = ptrtoint ptr %base to i64
+  %i2 = ptrtoint ptr %p2 to i64
+  %d = sub i64 %i2, %i1
+  ret i64 %d
+}
+
+define i64 @sub_multi_use_nuw(ptr %base, i64 %idx) {
+; CHECK-LABEL: @sub_multi_use_nuw(
+; CHECK-NEXT:    [[P2:%.*]] = getelementptr inbounds [0 x i32], ptr [[BASE:%.*]], i64 0, i64 [[IDX:%.*]]
+; CHECK-NEXT:    call void @use(ptr [[P2]])
+; CHECK-NEXT:    [[P2_IDX:%.*]] = shl nuw nsw i64 [[IDX]], 2
+; CHECK-NEXT:    ret i64 [[P2_IDX]]
+;
+  %p2 = getelementptr inbounds [0 x i32], ptr %base, i64 0, i64 %idx
+  call void @use(ptr %p2)
+  %i1 = ptrtoint ptr %base to i64
+  %i2 = ptrtoint ptr %p2 to i64
+  %d = sub nuw i64 %i2, %i1
+  ret i64 %d
+}
+
 define i1 @_gep_phi1(ptr %str1) {
 ; CHECK-LABEL: @_gep_phi1(
 ; CHECK-NEXT:  entry:

>From 1583757a80e948e2b12ef208c42add32dd200f18 Mon Sep 17 00:00:00 2001
From: Nikita Popov <npopov at redhat.com>
Date: Wed, 4 Jun 2025 17:10:15 +0200
Subject: [PATCH 2/4] [InstCombine] Always rewrite multi-use GEP for pointer
 difference

Resolve the TODO in OptimizePointerDifference: Always rewrite
(non-trivial, multi-use) GEPs to reuse the offset arithmetic,
even if there is only one GEP.
---
 llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp | 11 ++++-------
 llvm/test/Transforms/InstCombine/sub-gep.ll           |  8 ++++----
 2 files changed, 8 insertions(+), 11 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
index a9ac5ff9b9c89..fda56c93451e8 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
@@ -2101,21 +2101,18 @@ Value *InstCombinerImpl::OptimizePointerDifference(Value *LHS, Value *RHS,
   if (!GEP1)
     return nullptr;
 
+  // Emit the offset of the GEP as an intptr_t.
   // To avoid duplicating the offset arithmetic, rewrite the GEP to use the
   // computed offset. This may erase the original GEP, so be sure to cache the
   // nowrap flags before emitting the offset.
-  // TODO: We should probably do this even if there is only one GEP.
-  bool RewriteGEPs = GEP2 != nullptr;
-
-  // Emit the offset of the GEP and an intptr_t.
   GEPNoWrapFlags GEP1NW = GEP1->getNoWrapFlags();
-  Value *Result = EmitGEPOffset(GEP1, RewriteGEPs);
+  Value *Result = EmitGEPOffset(GEP1, /*RewriteGEP=*/true);
 
   // If this is a single inbounds GEP and the original sub was nuw,
   // then the final multiplication is also nuw.
   if (auto *I = dyn_cast<Instruction>(Result))
     if (IsNUW && !GEP2 && !Swapped && GEP1NW.isInBounds() &&
-        I->getOpcode() == Instruction::Mul)
+        I->getOpcode() == Instruction::Mul && I->use_empty())
       I->setHasNoUnsignedWrap();
 
   // If we have a 2nd GEP of the same base pointer, subtract the offsets.
@@ -2123,7 +2120,7 @@ Value *InstCombinerImpl::OptimizePointerDifference(Value *LHS, Value *RHS,
   // If both GEPs are nuw and the original sub is nuw, the new sub is also nuw.
   if (GEP2) {
     GEPNoWrapFlags GEP2NW = GEP2->getNoWrapFlags();
-    Value *Offset = EmitGEPOffset(GEP2, RewriteGEPs);
+    Value *Offset = EmitGEPOffset(GEP2, /*RewriteGEP=*/true);
     Result = Builder.CreateSub(Result, Offset, "gepdiff",
                                IsNUW && GEP1NW.hasNoUnsignedWrap() &&
                                    GEP2NW.hasNoUnsignedWrap(),
diff --git a/llvm/test/Transforms/InstCombine/sub-gep.ll b/llvm/test/Transforms/InstCombine/sub-gep.ll
index 8c0b57228d9ea..e6f6498e23389 100644
--- a/llvm/test/Transforms/InstCombine/sub-gep.ll
+++ b/llvm/test/Transforms/InstCombine/sub-gep.ll
@@ -764,9 +764,9 @@ declare void @use(ptr)
 
 define i64 @sub_multi_use(ptr %base, i64 %idx) {
 ; CHECK-LABEL: @sub_multi_use(
-; CHECK-NEXT:    [[P2:%.*]] = getelementptr inbounds [0 x i32], ptr [[BASE:%.*]], i64 0, i64 [[IDX:%.*]]
+; CHECK-NEXT:    [[P2_IDX:%.*]] = shl nsw i64 [[IDX:%.*]], 2
+; CHECK-NEXT:    [[P2:%.*]] = getelementptr inbounds i8, ptr [[BASE:%.*]], i64 [[P2_IDX]]
 ; CHECK-NEXT:    call void @use(ptr [[P2]])
-; CHECK-NEXT:    [[P2_IDX:%.*]] = shl nsw i64 [[IDX]], 2
 ; CHECK-NEXT:    ret i64 [[P2_IDX]]
 ;
   %p2 = getelementptr inbounds [0 x i32], ptr %base, i64 0, i64 %idx
@@ -779,9 +779,9 @@ define i64 @sub_multi_use(ptr %base, i64 %idx) {
 
 define i64 @sub_multi_use_nuw(ptr %base, i64 %idx) {
 ; CHECK-LABEL: @sub_multi_use_nuw(
-; CHECK-NEXT:    [[P2:%.*]] = getelementptr inbounds [0 x i32], ptr [[BASE:%.*]], i64 0, i64 [[IDX:%.*]]
+; CHECK-NEXT:    [[P2_IDX:%.*]] = shl nsw i64 [[IDX:%.*]], 2
+; CHECK-NEXT:    [[P2:%.*]] = getelementptr inbounds i8, ptr [[BASE:%.*]], i64 [[P2_IDX]]
 ; CHECK-NEXT:    call void @use(ptr [[P2]])
-; CHECK-NEXT:    [[P2_IDX:%.*]] = shl nuw nsw i64 [[IDX]], 2
 ; CHECK-NEXT:    ret i64 [[P2_IDX]]
 ;
   %p2 = getelementptr inbounds [0 x i32], ptr %base, i64 0, i64 %idx

>From 86d30ee65b5dd72afd82245d8ba7c3a572dfcf25 Mon Sep 17 00:00:00 2001
From: Nikita Popov <npopov at redhat.com>
Date: Thu, 5 Jun 2025 10:35:32 +0200
Subject: [PATCH 3/4] Clone instruction to add nuw flag

---
 llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp | 10 +++++++++-
 llvm/test/Transforms/InstCombine/sub-gep.ll           |  3 ++-
 2 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
index fda56c93451e8..16d087e49f222 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
@@ -2112,8 +2112,16 @@ Value *InstCombinerImpl::OptimizePointerDifference(Value *LHS, Value *RHS,
   // then the final multiplication is also nuw.
   if (auto *I = dyn_cast<Instruction>(Result))
     if (IsNUW && !GEP2 && !Swapped && GEP1NW.isInBounds() &&
-        I->getOpcode() == Instruction::Mul && I->use_empty())
+        I->getOpcode() == Instruction::Mul) {
+      if (!I->use_empty()) {
+        // If the offset calculation is reused by the GEP, add the nuw flag to
+        // a separate clone. This may improve folds and will get CSEd if not
+        // useful.
+        Result = I = I->clone();
+        Builder.Insert(I);
+      }
       I->setHasNoUnsignedWrap();
+    }
 
   // If we have a 2nd GEP of the same base pointer, subtract the offsets.
   // If both GEPs are inbounds, then the subtract does not have signed overflow.
diff --git a/llvm/test/Transforms/InstCombine/sub-gep.ll b/llvm/test/Transforms/InstCombine/sub-gep.ll
index e6f6498e23389..78ef91467b0ba 100644
--- a/llvm/test/Transforms/InstCombine/sub-gep.ll
+++ b/llvm/test/Transforms/InstCombine/sub-gep.ll
@@ -782,7 +782,8 @@ define i64 @sub_multi_use_nuw(ptr %base, i64 %idx) {
 ; CHECK-NEXT:    [[P2_IDX:%.*]] = shl nsw i64 [[IDX:%.*]], 2
 ; CHECK-NEXT:    [[P2:%.*]] = getelementptr inbounds i8, ptr [[BASE:%.*]], i64 [[P2_IDX]]
 ; CHECK-NEXT:    call void @use(ptr [[P2]])
-; CHECK-NEXT:    ret i64 [[P2_IDX]]
+; CHECK-NEXT:    [[D:%.*]] = shl nuw nsw i64 [[IDX]], 2
+; CHECK-NEXT:    ret i64 [[D]]
 ;
   %p2 = getelementptr inbounds [0 x i32], ptr %base, i64 0, i64 %idx
   call void @use(ptr %p2)

>From 11c8483144b17348c36700d04b6ffaa39a3eeaed Mon Sep 17 00:00:00 2001
From: Nikita Popov <npopov at redhat.com>
Date: Thu, 5 Jun 2025 13:06:48 +0200
Subject: [PATCH 4/4] Don't bother cloning if nuw is already set (from gep nuw)

---
 llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
index 16d087e49f222..ecd14996a9878 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
@@ -2112,7 +2112,7 @@ Value *InstCombinerImpl::OptimizePointerDifference(Value *LHS, Value *RHS,
   // then the final multiplication is also nuw.
   if (auto *I = dyn_cast<Instruction>(Result))
     if (IsNUW && !GEP2 && !Swapped && GEP1NW.isInBounds() &&
-        I->getOpcode() == Instruction::Mul) {
+        I->getOpcode() == Instruction::Mul && !I->hasNoUnsignedWrap()) {
       if (!I->use_empty()) {
         // If the offset calculation is reused by the GEP, add the nuw flag to
         // a separate clone. This may improve folds and will get CSEd if not



More information about the llvm-commits mailing list