[llvm] [InstCombine] Relax singleUse for GEP(p, add(x, constant)) to 2 GEPs (PR #209551)

Abhay Kanhere via llvm-commits llvm-commits at lists.llvm.org
Tue Jul 14 09:53:30 PDT 2026


https://github.com/AbhayKanhere updated https://github.com/llvm/llvm-project/pull/209551

>From 7d2ce7618d3e010f56ba7842610b46a7e550eea4 Mon Sep 17 00:00:00 2001
From: Abhay Kanhere <a_kanhere at apple.com>
Date: Tue, 14 Jul 2026 09:33:23 -0700
Subject: [PATCH] [InstCombine] Relax singleUse for GEP(p,add(x,constant)) to 2
 GEPs

Bound checks for memory safety involve checking index expression before
access.
e.g.
  for ( i =0 ;i <n ; i++ )  {
   if( i - k > bound)
         llvm.trap()
    a[ i - k-1] ...
    a[ i - k]
   }

   so invariably, the index expression is no longer singleUse and this prevents
fold of GEP(p,ADD(x,y))) -> GEP( GEP(p,x),y). This later prevents optimizations
such as wideing the load or store

SingleUse check here is not a correctness check.
   Here we relax the requirement of singleUse for constant 'y'. This enables
later optimizations such as aggressive instcombine widening with simplified
GEP with constant offsets.
---
 .../InstCombine/InstructionCombining.cpp      |  6 +-
 .../InstCombine/gep-addlike-multiuse.ll       | 64 +++++++++++++++++++
 2 files changed, 68 insertions(+), 2 deletions(-)
 create mode 100644 llvm/test/Transforms/InstCombine/gep-addlike-multiuse.ll

diff --git a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
index 1e24ff8d51057..a149c45e12615 100644
--- a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
@@ -3652,9 +3652,11 @@ Instruction *InstCombinerImpl::visitGetElementPtrInst(GetElementPtrInst &GEP) {
     };
 
     // Try to replace ADD + GEP with GEP + GEP.
+    // Relax singleUse condition for constant added to GEP without risking
+    // infinite loop.
     Value *Idx1, *Idx2;
-    if (match(GEP.getOperand(1),
-              m_OneUse(m_AddLike(m_Value(Idx1), m_Value(Idx2))))) {
+    if (match(GEP.getOperand(1), m_AddLike(m_Value(Idx1), m_Value(Idx2))) &&
+        (GEP.getOperand(1)->hasOneUse() || match(Idx2, m_ConstantInt()))) {
       //   %idx = add i64 %idx1, %idx2
       //   %gep = getelementptr i32, ptr %ptr, i64 %idx
       // as:
diff --git a/llvm/test/Transforms/InstCombine/gep-addlike-multiuse.ll b/llvm/test/Transforms/InstCombine/gep-addlike-multiuse.ll
new file mode 100644
index 0000000000000..278d412e427f7
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/gep-addlike-multiuse.ll
@@ -0,0 +1,64 @@
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+; Bounds-checked array access `a[i + k]`: the index `add i, k` feeds BOTH the
+; bounds-check compare (which dominates the access) AND the element GEP, so it
+; is inherently multi-use. When the addend `k` is a constant we still split it
+; out of the GEP: the inner `gep base, %idx` is shared with sibling accesses
+; (CSE) and the exposed constant offset enables later optimizations
+; (e.g. AggressiveInstCombine load-widening) 
+
+define ptr @boundscheck_const_addend(ptr %base, i64 %idx, i64 %n) {
+; CHECK-LABEL: define ptr @boundscheck_const_addend(
+; CHECK-SAME: ptr [[BASE:%.*]], i64 [[IDX:%.*]], i64 [[N:%.*]]) {
+; CHECK-NEXT:    [[OFF:%.*]] = add nsw i64 [[IDX]], 3
+; CHECK-NEXT:    [[OOB:%.*]] = icmp ult i64 [[OFF]], [[N]]
+; CHECK-NEXT:    br i1 [[OOB]], label %[[OK:.*]], label %[[TRAP:.*]]
+; CHECK:       [[OK]]:
+; CHECK-NEXT:    [[TMP1:%.*]] = getelementptr i8, ptr [[BASE]], i64 [[IDX]]
+; CHECK-NEXT:    [[P:%.*]] = getelementptr i8, ptr [[TMP1]], i64 3
+; CHECK-NEXT:    ret ptr [[P]]
+; CHECK:       [[TRAP]]:
+; CHECK-NEXT:    call void @llvm.trap()
+; CHECK-NEXT:    unreachable
+;
+  %off = add nsw i64 %idx, 3
+  %oob = icmp uge i64 %off, %n
+  br i1 %oob, label %trap, label %ok
+
+ok:
+  %p = getelementptr inbounds i8, ptr %base, i64 %off
+  ret ptr %p
+
+trap:
+  call void @llvm.trap()
+  unreachable
+}
+
+; Negative: a multi-use add with a *variable* addend must NOT split unless profitable
+define ptr @boundscheck_var_addend(ptr %base, i64 %idx, i64 %v, i64 %n) {
+; CHECK-LABEL: define ptr @boundscheck_var_addend(
+; CHECK-SAME: ptr [[BASE:%.*]], i64 [[IDX:%.*]], i64 [[V:%.*]], i64 [[N:%.*]]) {
+; CHECK-NEXT:    [[OFF:%.*]] = add nsw i64 [[IDX]], [[V]]
+; CHECK-NEXT:    [[OOB:%.*]] = icmp ult i64 [[OFF]], [[N]]
+; CHECK-NEXT:    br i1 [[OOB]], label %[[OK:.*]], label %[[TRAP:.*]]
+; CHECK:       [[OK]]:
+; CHECK-NEXT:    [[P:%.*]] = getelementptr inbounds i8, ptr [[BASE]], i64 [[OFF]]
+; CHECK-NEXT:    ret ptr [[P]]
+; CHECK:       [[TRAP]]:
+; CHECK-NEXT:    call void @llvm.trap()
+; CHECK-NEXT:    unreachable
+;
+  %off = add nsw i64 %idx, %v
+  %oob = icmp uge i64 %off, %n
+  br i1 %oob, label %trap, label %ok
+
+ok:
+  %p = getelementptr inbounds i8, ptr %base, i64 %off
+  ret ptr %p
+
+trap:
+  call void @llvm.trap()
+  unreachable
+}
+
+declare void @llvm.trap()



More information about the llvm-commits mailing list