[llvm] [InstCombine] Merge GEPs with the same index when the inner GEP has one use (PR #227960)

via llvm-commits llvm-commits at lists.llvm.org
Fri Oct 2 23:01:09 PDT 2026


https://github.com/RS-Gits updated https://github.com/llvm/llvm-project/pull/227960

>From 18871802bcaf6942df5a2b704e79fa18248b4ea1 Mon Sep 17 00:00:00 2001
From: RS-Gits <harish14rs at gmail.com>
Date: Thu, 1 Oct 2026 10:37:35 +0530
Subject: [PATCH 1/2] [InstCombine] Merge GEPs with the same index when the
 inner GEP has one use

Fold a chain of GEPs that use the same index into a single GEP:

  %g1 = getelementptr inbounds double, ptr %p, i64 %x
  %g2 = getelementptr inbounds double, ptr %g1, i64 %x
  -->
  %idx = shl i64 %x, 4
  %g2 = getelementptr inbounds i8, ptr %p, i64 %idx

The existing GEP merge only fires when the sum of the two indices
simplifies to an existing value. For x + x that is not the case, so the
GEPs were left alone. If the inner GEP has no other users, the merge
replaces two GEPs with one GEP and one add, which never increases the
instruction count, and the add becomes a shift.

The fold is limited to identical indices. Merging different indices
into gep p, (x + y) can be undone by other folds and loop forever.

Fixes #186298
---
 .../InstCombine/InstructionCombining.cpp      | 16 +++-
 .../InstCombine/gep-merge-same-index.ll       | 91 +++++++++++++++++++
 2 files changed, 104 insertions(+), 3 deletions(-)
 create mode 100644 llvm/test/Transforms/InstCombine/gep-merge-same-index.ll

diff --git a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
index 72adf311850a3..8b6f8349755e4 100644
--- a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
@@ -2965,9 +2965,19 @@ Instruction *InstCombinerImpl::visitGEPOfGEP(GetElementPtrInst &GEP,
   Value *Sum =
       simplifyAddInst(GO1, SO1, false, false, SQ.getWithInstruction(&GEP));
   // Only do the combine when we are sure the cost after the
-  // merge is never more than that before the merge.
-  if (Sum == nullptr)
-    return nullptr;
+  // merge is never more than that before the merge. If both indices are the
+  // same value, the merge still replaces two GEPs with a GEP and an add (which
+  // becomes a shift) as long as the source GEP has no other users. Do not do
+  // this for different indices: (gep p, (x + y)) may be split back into two
+  // GEPs by other folds.
+  if (Sum == nullptr) {
+    auto *GO1I = dyn_cast<Instruction>(GO1);
+    auto *SO1I = dyn_cast<Instruction>(SO1);
+    bool SameIndex = GO1 == SO1 || (GO1I && SO1I && GO1I->isIdenticalTo(SO1I));
+    if (!SameIndex || !Src->hasOneUse())
+      return nullptr;
+    Sum = Builder.CreateAdd(GO1, SO1);
+  }
 
   SmallVector<Value *, 8> Indices;
   Indices.append(Src->op_begin() + 1, Src->op_end() - 1);
diff --git a/llvm/test/Transforms/InstCombine/gep-merge-same-index.ll b/llvm/test/Transforms/InstCombine/gep-merge-same-index.ll
new file mode 100644
index 0000000000000..6753cf33f9a49
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/gep-merge-same-index.ll
@@ -0,0 +1,91 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt -S -passes=instcombine < %s | FileCheck %s
+
+declare void @use(ptr)
+
+; gep (gep p, x), x --> gep p, (x + x) when the inner gep has a single use.
+
+define ptr @gep_gep_same_index(ptr %p, i64 %x) {
+;
+; CHECK-LABEL: define ptr @gep_gep_same_index(
+; CHECK-SAME: ptr [[P:%.*]], i64 [[X:%.*]]) {
+; CHECK-NEXT:    [[G2_IDX:%.*]] = shl i64 [[X]], 4
+; CHECK-NEXT:    [[G2:%.*]] = getelementptr inbounds i8, ptr [[P]], i64 [[G2_IDX]]
+; CHECK-NEXT:    ret ptr [[G2]]
+;
+  %g1 = getelementptr inbounds double, ptr %p, i64 %x
+  %g2 = getelementptr inbounds double, ptr %g1, i64 %x
+  ret ptr %g2
+}
+
+define ptr @gep_gep_same_index_no_flags(ptr %p, i64 %x) {
+;
+; CHECK-LABEL: define ptr @gep_gep_same_index_no_flags(
+; CHECK-SAME: ptr [[P:%.*]], i64 [[X:%.*]]) {
+; CHECK-NEXT:    [[G2_IDX:%.*]] = shl i64 [[X]], 4
+; CHECK-NEXT:    [[G2:%.*]] = getelementptr i8, ptr [[P]], i64 [[G2_IDX]]
+; CHECK-NEXT:    ret ptr [[G2]]
+;
+  %g1 = getelementptr double, ptr %p, i64 %x
+  %g2 = getelementptr double, ptr %g1, i64 %x
+  ret ptr %g2
+}
+
+; Different indices.
+define ptr @gep_gep_different_index(ptr %p, i64 %x, i64 %y) {
+;
+; CHECK-LABEL: define ptr @gep_gep_different_index(
+; CHECK-SAME: ptr [[P:%.*]], i64 [[X:%.*]], i64 [[Y:%.*]]) {
+; CHECK-NEXT:    [[G1:%.*]] = getelementptr inbounds [8 x i8], ptr [[P]], i64 [[X]]
+; CHECK-NEXT:    [[G2:%.*]] = getelementptr inbounds [8 x i8], ptr [[G1]], i64 [[Y]]
+; CHECK-NEXT:    ret ptr [[G2]]
+;
+  %g1 = getelementptr inbounds double, ptr %p, i64 %x
+  %g2 = getelementptr inbounds double, ptr %g1, i64 %y
+  ret ptr %g2
+}
+
+define ptr @gep_gep_gep_gep_same_index(ptr %p, i64 %x) {
+;
+; CHECK-LABEL: define ptr @gep_gep_gep_gep_same_index(
+; CHECK-SAME: ptr [[P:%.*]], i64 [[X:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = shl i64 [[X]], 5
+; CHECK-NEXT:    [[G4:%.*]] = getelementptr inbounds i8, ptr [[P]], i64 [[TMP1]]
+; CHECK-NEXT:    ret ptr [[G4]]
+;
+  %g1 = getelementptr inbounds double, ptr %p, i64 %x
+  %g2 = getelementptr inbounds double, ptr %g1, i64 %x
+  %g3 = getelementptr inbounds double, ptr %g2, i64 %x
+  %g4 = getelementptr inbounds double, ptr %g3, i64 %x
+  ret ptr %g4
+}
+
+; The inner gep has another use: don't merge.
+define ptr @gep_gep_same_index_extra_use(ptr %p, i64 %x) {
+;
+; CHECK-LABEL: define ptr @gep_gep_same_index_extra_use(
+; CHECK-SAME: ptr [[P:%.*]], i64 [[X:%.*]]) {
+; CHECK-NEXT:    [[G1:%.*]] = getelementptr inbounds [8 x i8], ptr [[P]], i64 [[X]]
+; CHECK-NEXT:    call void @use(ptr [[G1]])
+; CHECK-NEXT:    [[G2:%.*]] = getelementptr inbounds [8 x i8], ptr [[G1]], i64 [[X]]
+; CHECK-NEXT:    ret ptr [[G2]]
+;
+  %g1 = getelementptr inbounds double, ptr %p, i64 %x
+  call void @use(ptr %g1)
+  %g2 = getelementptr inbounds double, ptr %g1, i64 %x
+  ret ptr %g2
+}
+
+; Different source element types.
+define ptr @gep_gep_same_index_different_types(ptr %p, i64 %x) {
+;
+; CHECK-LABEL: define ptr @gep_gep_same_index_different_types(
+; CHECK-SAME: ptr [[P:%.*]], i64 [[X:%.*]]) {
+; CHECK-NEXT:    [[G1:%.*]] = getelementptr inbounds [4 x i8], ptr [[P]], i64 [[X]]
+; CHECK-NEXT:    [[G2:%.*]] = getelementptr inbounds [8 x i8], ptr [[G1]], i64 [[X]]
+; CHECK-NEXT:    ret ptr [[G2]]
+;
+  %g1 = getelementptr inbounds i32, ptr %p, i64 %x
+  %g2 = getelementptr inbounds i64, ptr %g1, i64 %x
+  ret ptr %g2
+}

>From 0bdbff937ef0e06a121d383e63e2d8745c27c397 Mon Sep 17 00:00:00 2001
From: RS-Gits <harish14rs at gmail.com>
Date: Sat, 3 Oct 2026 11:27:19 +0530
Subject: [PATCH 2/2] [InstCombine] Emit shl instead of add when merging
 same-index GEPs

Address review feedback on the GEP merge:

- Emit the doubled index as a shift instead of an add. An add feeding a
  GEP can be split back into two GEPs by another fold, which could loop
  depending on the worklist order.
- Only merge when both indices are the same value. Merging identical
  instructions is the job of CSE.
---
 .../InstCombine/InstructionCombining.cpp      | 14 ++++------
 .../InstCombine/gep-merge-same-index.ll       | 26 +++++++++++++++++--
 2 files changed, 29 insertions(+), 11 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
index 8b6f8349755e4..a084c672772da 100644
--- a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
@@ -2966,17 +2966,13 @@ Instruction *InstCombinerImpl::visitGEPOfGEP(GetElementPtrInst &GEP,
       simplifyAddInst(GO1, SO1, false, false, SQ.getWithInstruction(&GEP));
   // Only do the combine when we are sure the cost after the
   // merge is never more than that before the merge. If both indices are the
-  // same value, the merge still replaces two GEPs with a GEP and an add (which
-  // becomes a shift) as long as the source GEP has no other users. Do not do
-  // this for different indices: (gep p, (x + y)) may be split back into two
-  // GEPs by other folds.
+  // same value, the merge still replaces two GEPs with a GEP and a shift as
+  // long as the source GEP has no other users. Emit the shift explicitly, as
+  // (gep p, (x + y)) may be split back into two GEPs by other folds.
   if (Sum == nullptr) {
-    auto *GO1I = dyn_cast<Instruction>(GO1);
-    auto *SO1I = dyn_cast<Instruction>(SO1);
-    bool SameIndex = GO1 == SO1 || (GO1I && SO1I && GO1I->isIdenticalTo(SO1I));
-    if (!SameIndex || !Src->hasOneUse())
+    if (GO1 != SO1 || !Src->hasOneUse())
       return nullptr;
-    Sum = Builder.CreateAdd(GO1, SO1);
+    Sum = Builder.CreateShl(GO1, ConstantInt::get(GO1->getType(), 1));
   }
 
   SmallVector<Value *, 8> Indices;
diff --git a/llvm/test/Transforms/InstCombine/gep-merge-same-index.ll b/llvm/test/Transforms/InstCombine/gep-merge-same-index.ll
index 6753cf33f9a49..48a4ab1af4dca 100644
--- a/llvm/test/Transforms/InstCombine/gep-merge-same-index.ll
+++ b/llvm/test/Transforms/InstCombine/gep-merge-same-index.ll
@@ -7,6 +7,7 @@ declare void @use(ptr)
 
 define ptr @gep_gep_same_index(ptr %p, i64 %x) {
 ;
+;
 ; CHECK-LABEL: define ptr @gep_gep_same_index(
 ; CHECK-SAME: ptr [[P:%.*]], i64 [[X:%.*]]) {
 ; CHECK-NEXT:    [[G2_IDX:%.*]] = shl i64 [[X]], 4
@@ -20,6 +21,7 @@ define ptr @gep_gep_same_index(ptr %p, i64 %x) {
 
 define ptr @gep_gep_same_index_no_flags(ptr %p, i64 %x) {
 ;
+;
 ; CHECK-LABEL: define ptr @gep_gep_same_index_no_flags(
 ; CHECK-SAME: ptr [[P:%.*]], i64 [[X:%.*]]) {
 ; CHECK-NEXT:    [[G2_IDX:%.*]] = shl i64 [[X]], 4
@@ -34,6 +36,7 @@ define ptr @gep_gep_same_index_no_flags(ptr %p, i64 %x) {
 ; Different indices.
 define ptr @gep_gep_different_index(ptr %p, i64 %x, i64 %y) {
 ;
+;
 ; CHECK-LABEL: define ptr @gep_gep_different_index(
 ; CHECK-SAME: ptr [[P:%.*]], i64 [[X:%.*]], i64 [[Y:%.*]]) {
 ; CHECK-NEXT:    [[G1:%.*]] = getelementptr inbounds [8 x i8], ptr [[P]], i64 [[X]]
@@ -45,12 +48,16 @@ define ptr @gep_gep_different_index(ptr %p, i64 %x, i64 %y) {
   ret ptr %g2
 }
 
+; Each pair is merged. Merging the two identical shifts is left to CSE.
 define ptr @gep_gep_gep_gep_same_index(ptr %p, i64 %x) {
 ;
+;
 ; CHECK-LABEL: define ptr @gep_gep_gep_gep_same_index(
 ; CHECK-SAME: ptr [[P:%.*]], i64 [[X:%.*]]) {
-; CHECK-NEXT:    [[TMP1:%.*]] = shl i64 [[X]], 5
-; CHECK-NEXT:    [[G4:%.*]] = getelementptr inbounds i8, ptr [[P]], i64 [[TMP1]]
+; CHECK-NEXT:    [[G2_IDX:%.*]] = shl i64 [[X]], 4
+; CHECK-NEXT:    [[G2:%.*]] = getelementptr inbounds i8, ptr [[P]], i64 [[G2_IDX]]
+; CHECK-NEXT:    [[G4_IDX:%.*]] = shl i64 [[X]], 4
+; CHECK-NEXT:    [[G4:%.*]] = getelementptr inbounds i8, ptr [[G2]], i64 [[G4_IDX]]
 ; CHECK-NEXT:    ret ptr [[G4]]
 ;
   %g1 = getelementptr inbounds double, ptr %p, i64 %x
@@ -63,6 +70,7 @@ define ptr @gep_gep_gep_gep_same_index(ptr %p, i64 %x) {
 ; The inner gep has another use: don't merge.
 define ptr @gep_gep_same_index_extra_use(ptr %p, i64 %x) {
 ;
+;
 ; CHECK-LABEL: define ptr @gep_gep_same_index_extra_use(
 ; CHECK-SAME: ptr [[P:%.*]], i64 [[X:%.*]]) {
 ; CHECK-NEXT:    [[G1:%.*]] = getelementptr inbounds [8 x i8], ptr [[P]], i64 [[X]]
@@ -79,6 +87,7 @@ define ptr @gep_gep_same_index_extra_use(ptr %p, i64 %x) {
 ; Different source element types.
 define ptr @gep_gep_same_index_different_types(ptr %p, i64 %x) {
 ;
+;
 ; CHECK-LABEL: define ptr @gep_gep_same_index_different_types(
 ; CHECK-SAME: ptr [[P:%.*]], i64 [[X:%.*]]) {
 ; CHECK-NEXT:    [[G1:%.*]] = getelementptr inbounds [4 x i8], ptr [[P]], i64 [[X]]
@@ -89,3 +98,16 @@ define ptr @gep_gep_same_index_different_types(ptr %p, i64 %x) {
   %g2 = getelementptr inbounds i64, ptr %g1, i64 %x
   ret ptr %g2
 }
+
+; Vector of indices.
+define <2 x ptr> @gep_gep_same_index_vector(ptr %p, <2 x i64> %x) {
+; CHECK-LABEL: define <2 x ptr> @gep_gep_same_index_vector(
+; CHECK-SAME: ptr [[P:%.*]], <2 x i64> [[X:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = shl <2 x i64> [[X]], splat (i64 1)
+; CHECK-NEXT:    [[G2:%.*]] = getelementptr inbounds [8 x i8], ptr [[P]], <2 x i64> [[TMP1]]
+; CHECK-NEXT:    ret <2 x ptr> [[G2]]
+;
+  %g1 = getelementptr inbounds double, ptr %p, <2 x i64> %x
+  %g2 = getelementptr inbounds double, <2 x ptr> %g1, <2 x i64> %x
+  ret <2 x ptr> %g2
+}



More information about the llvm-commits mailing list