[llvm-branch-commits] [llvm] [GVN][MemDep] Recover affine-equal select-dependent load addresses via SCEV (PR #209826)

Madhur Amilkanthwar via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Mon Aug 10 03:31:36 PDT 2026


https://github.com/madhur13490 updated https://github.com/llvm/llvm-project/pull/209826

>From 2aa7f969b1a37723730ec527dcbf8e74e86bfcc8 Mon Sep 17 00:00:00 2001
From: Madhur Amilkanthwar <madhura at nvidia.com>
Date: Mon, 10 Aug 2026 03:30:07 -0700
Subject: [PATCH] [GVN] Recover affine-equal select-dependent load addresses

Teach PHITransAddr to recover available select-arm addresses when a
constant GEP index adjustment is folded into a nested byte offset. This
lets GVN reuse the existing load-PRE path without depending on SCEV.
---
 llvm/include/llvm/Analysis/PHITransAddr.h     |   6 +
 llvm/lib/Analysis/PHITransAddr.cpp            | 106 +++++++++++++++---
 .../GVN/phitrans-gep-select-load-address.ll   |  67 +++++++++++
 3 files changed, 162 insertions(+), 17 deletions(-)
 create mode 100644 llvm/test/Transforms/GVN/phitrans-gep-select-load-address.ll

diff --git a/llvm/include/llvm/Analysis/PHITransAddr.h b/llvm/include/llvm/Analysis/PHITransAddr.h
index 90df01269057f..2a45539a70156 100644
--- a/llvm/include/llvm/Analysis/PHITransAddr.h
+++ b/llvm/include/llvm/Analysis/PHITransAddr.h
@@ -146,6 +146,12 @@ class PHITransAddr {
   LLVM_ABI bool verify() const;
 
 private:
+  /// Recover an available address when select-arm translation only fails
+  /// because a constant index delta is folded into a nested i8 GEP offset.
+  Value *findAvailableSelectArmAddr(Value *Cond, BasicBlock *CurBB,
+                                    BasicBlock *PredBB, const DominatorTree *DT,
+                                    bool CondVal) const;
+
   Value *translateSubExpr(Value *V, BasicBlock *CurBB, BasicBlock *PredBB,
                           const DominatorTree *DT, Value *Cond = nullptr,
                           bool CondVal = false);
diff --git a/llvm/lib/Analysis/PHITransAddr.cpp b/llvm/lib/Analysis/PHITransAddr.cpp
index 03b931a2f0587..203a2d4050d00 100644
--- a/llvm/lib/Analysis/PHITransAddr.cpp
+++ b/llvm/lib/Analysis/PHITransAddr.cpp
@@ -15,12 +15,15 @@
 #include "llvm/Analysis/ValueTracking.h"
 #include "llvm/Config/llvm-config.h"
 #include "llvm/IR/Constants.h"
+#include "llvm/IR/DataLayout.h"
 #include "llvm/IR/Dominators.h"
 #include "llvm/IR/Instructions.h"
+#include "llvm/IR/PatternMatch.h"
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/raw_ostream.h"
 using namespace llvm;
+using namespace llvm::PatternMatch;
 
 static cl::opt<bool> EnableAddPhiTranslation(
     "gvn-add-phi-translation", cl::init(false), cl::Hidden,
@@ -37,6 +40,31 @@ static bool canPHITrans(Instruction *Inst) {
   return false;
 }
 
+/// Return an existing GEP of \p SrcTy over \p Ops that dominates \p PredBB.
+static GetElementPtrInst *findAvailableGEP(Type *ResultTy, Type *SrcTy,
+                                           ArrayRef<Value *> Ops,
+                                           BasicBlock *CurBB,
+                                           BasicBlock *PredBB,
+                                           const DominatorTree *DT) {
+  assert(!Ops.empty() && "GEP needs a pointer operand");
+  Value *Ptr = Ops[0];
+  if (isa<ConstantData>(Ptr))
+    return nullptr;
+
+  for (User *U : Ptr->users()) {
+    auto *GEPI = dyn_cast<GetElementPtrInst>(U);
+    if (!GEPI || GEPI->getType() != ResultTy ||
+        GEPI->getSourceElementType() != SrcTy ||
+        GEPI->getNumOperands() != Ops.size() ||
+        GEPI->getParent()->getParent() != CurBB->getParent() ||
+        (DT && !DT->dominates(GEPI->getParent(), PredBB)))
+      continue;
+    if (std::equal(Ops.begin(), Ops.end(), GEPI->op_begin()))
+      return GEPI;
+  }
+  return nullptr;
+}
+
 #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
 LLVM_DUMP_METHOD void PHITransAddr::dump() const {
   if (!Addr) {
@@ -236,22 +264,8 @@ Value *PHITransAddr::translateSubExpr(Value *V, BasicBlock *CurBB,
     }
 
     // Scan to see if we have this GEP available.
-    Value *APHIOp = GEPOps[0];
-    if (isa<ConstantData>(APHIOp))
-      return nullptr;
-
-    for (User *U : APHIOp->users()) {
-      if (GetElementPtrInst *GEPI = dyn_cast<GetElementPtrInst>(U))
-        if (GEPI->getType() == GEP->getType() &&
-            GEPI->getSourceElementType() == GEP->getSourceElementType() &&
-            GEPI->getNumOperands() == GEPOps.size() &&
-            GEPI->getParent()->getParent() == CurBB->getParent() &&
-            (!DT || DT->dominates(GEPI->getParent(), PredBB))) {
-          if (std::equal(GEPOps.begin(), GEPOps.end(), GEPI->op_begin()))
-            return GEPI;
-        }
-    }
-    return nullptr;
+    return findAvailableGEP(GEP->getType(), GEP->getSourceElementType(), GEPOps,
+                            CurBB, PredBB, DT);
   }
 
   // Handle add with a constant RHS.
@@ -347,12 +361,70 @@ SelectAddr::SelectAddrs PHITransAddr::translateValue(BasicBlock *CurBB,
     // Work on a copy so that the original address state is preserved and the
     // other side can be translated independently.
     PHITransAddr Tmp(*this);
-    return Tmp.translateSubExpr(Tmp.Addr, CurBB, PredBB, DT, Cond, CondVal);
+    if (Value *V =
+            Tmp.translateSubExpr(Tmp.Addr, CurBB, PredBB, DT, Cond, CondVal))
+      return V;
+    // Syntactic match misses when a constant index delta is folded into an i8
+    // offset. Recover the affine-equal available pointer instead.
+    return findAvailableSelectArmAddr(Cond, CurBB, PredBB, DT, CondVal);
   };
 
   return {TranslateSide(/*CondVal=*/true), TranslateSide(/*CondVal=*/false)};
 }
 
+Value *PHITransAddr::findAvailableSelectArmAddr(Value *Cond, BasicBlock *CurBB,
+                                                BasicBlock *PredBB,
+                                                const DominatorTree *DT,
+                                                bool CondVal) const {
+  // Addr must be: gep i8, (gep Ty, Base, Index), Off.
+  auto *Outer = dyn_cast<GetElementPtrInst>(Addr);
+  if (!Outer || Outer->getNumIndices() != 1 ||
+      !Outer->getSourceElementType()->isIntegerTy(8))
+    return nullptr;
+  auto *OffCI = dyn_cast<ConstantInt>(Outer->getOperand(1));
+  auto *Inner = dyn_cast<GetElementPtrInst>(Outer->getPointerOperand());
+  if (!OffCI || !Inner || Inner->getNumIndices() != 1)
+    return nullptr;
+
+  auto *RecPhi = dyn_cast<PHINode>(Inner->getOperand(1));
+  if (!RecPhi || RecPhi->getParent() != CurBB)
+    return nullptr;
+  auto *SI = dyn_cast<SelectInst>(RecPhi->getIncomingValueForBlock(PredBB));
+  if (!SI || SI->getCondition() != Cond)
+    return nullptr;
+
+  Value *Arm = CondVal ? SI->getTrueValue() : SI->getFalseValue();
+  if (Arm == RecPhi)
+    return Addr;
+
+  // Fold add(IV, C) into the outer byte offset and look for that GEP.
+  Value *IV = nullptr;
+  const APInt *C = nullptr;
+  if (!match(Arm, m_c_Add(m_Value(IV), m_APInt(C))))
+    return nullptr;
+
+  Type *Ty = Inner->getSourceElementType();
+  TypeSize ElemSize = DL.getTypeAllocSize(Ty);
+  if (ElemSize.isScalable())
+    return nullptr;
+
+  unsigned BitWidth = OffCI->getBitWidth();
+  APInt TargetOff =
+      OffCI->getValue() +
+      C->sextOrTrunc(BitWidth) * APInt(BitWidth, ElemSize.getFixedValue());
+
+  Value *InnerOps[] = {Inner->getPointerOperand(), IV};
+  auto *InnerAvail =
+      findAvailableGEP(Inner->getType(), Ty, InnerOps, CurBB, PredBB, DT);
+  if (!InnerAvail)
+    return nullptr;
+
+  Value *OuterOps[] = {InnerAvail,
+                       ConstantInt::get(Outer->getContext(), TargetOff)};
+  return findAvailableGEP(Outer->getType(), Outer->getSourceElementType(),
+                          OuterOps, CurBB, PredBB, DT);
+}
+
 Value *PHITransAddr::getSelectCondition() const {
   for (Instruction *I : InstInputs)
     if (auto *SI = dyn_cast<SelectInst>(I))
diff --git a/llvm/test/Transforms/GVN/phitrans-gep-select-load-address.ll b/llvm/test/Transforms/GVN/phitrans-gep-select-load-address.ll
new file mode 100644
index 0000000000000..854a232cbcbf5
--- /dev/null
+++ b/llvm/test/Transforms/GVN/phitrans-gep-select-load-address.ll
@@ -0,0 +1,67 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -S -passes=gvn | FileCheck %s
+
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "aarch64-unknown-linux-gnu"
+
+; Down-counting FP argmin after IndVarSimplify widening. The reload of
+; a[minidx] uses gep([4 x i8], ...) + gep(i8, Off), so the select update arm
+; is affine-equal to the scanned pointer but not syntactically identical.
+; PHITransAddr folds the constant index delta into the byte offset and GVN
+; eliminates the reload.
+define i32 @fp_argmin_decreasing(ptr %a, i32 %start, i64 %tc0) {
+; CHECK-LABEL: @fp_argmin_decreasing(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[TMP0:%.*]] = sext i32 [[START:%.*]] to i64
+; CHECK-NEXT:    [[MIN_P0_PHI_TRANS_INSERT:%.*]] = getelementptr [4 x i8], ptr [[A:%.*]], i64 [[TMP0]]
+; CHECK-NEXT:    [[MIN_P_PHI_TRANS_INSERT:%.*]] = getelementptr i8, ptr [[MIN_P0_PHI_TRANS_INSERT]], i64 -4
+; CHECK-NEXT:    [[MIN_V_PRE:%.*]] = load float, ptr [[MIN_P_PHI_TRANS_INSERT]], align 4
+; CHECK-NEXT:    br label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[MIN_V:%.*]] = phi float [ [[TMP2:%.*]], [[LOOP]] ], [ [[MIN_V_PRE]], [[ENTRY:%.*]] ]
+; CHECK-NEXT:    [[INDVARS_IV:%.*]] = phi i64 [ [[INDVARS_IV_NEXT:%.*]], [[LOOP]] ], [ [[TMP0]], [[ENTRY]] ]
+; CHECK-NEXT:    [[CNT:%.*]] = phi i64 [ [[TC0:%.*]], [[ENTRY]] ], [ [[CNT_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[MIN_WIDE:%.*]] = phi i64 [ [[TMP0]], [[ENTRY]] ], [ [[MIN_NEXT_WIDE:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[INDVARS_IV_NEXT]] = add nsw i64 [[INDVARS_IV]], -1
+; CHECK-NEXT:    [[SCAN_P0:%.*]] = getelementptr [4 x i8], ptr [[A]], i64 [[INDVARS_IV]]
+; CHECK-NEXT:    [[SCAN_P:%.*]] = getelementptr i8, ptr [[SCAN_P0]], i64 -8
+; CHECK-NEXT:    [[SCAN_V:%.*]] = load float, ptr [[SCAN_P]], align 4
+; CHECK-NEXT:    [[MIN_P0:%.*]] = getelementptr [4 x i8], ptr [[A]], i64 [[MIN_WIDE]]
+; CHECK-NEXT:    [[MIN_P:%.*]] = getelementptr i8, ptr [[MIN_P0]], i64 -4
+; CHECK-NEXT:    [[C:%.*]] = fcmp fast olt float [[SCAN_V]], [[MIN_V]]
+; CHECK-NEXT:    [[MIN_NEXT_WIDE]] = select i1 [[C]], i64 [[INDVARS_IV_NEXT]], i64 [[MIN_WIDE]]
+; CHECK-NEXT:    [[TMP1:%.*]] = trunc nsw i64 [[MIN_NEXT_WIDE]] to i32
+; CHECK-NEXT:    [[CNT_NEXT]] = add nsw i64 [[CNT]], -1
+; CHECK-NEXT:    [[AGAIN:%.*]] = icmp sgt i64 [[CNT]], 1
+; CHECK-NEXT:    [[TMP2]] = select i1 [[C]], float [[SCAN_V]], float [[MIN_V]]
+; CHECK-NEXT:    br i1 [[AGAIN]], label [[LOOP]], label [[EXIT:%.*]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret i32 [[TMP1]]
+;
+entry:
+  %0 = sext i32 %start to i64
+  %1 = sext i32 %start to i64
+  br label %loop
+
+loop:
+  %indvars.iv = phi i64 [ %indvars.iv.next, %loop ], [ %0, %entry ]
+  %cnt = phi i64 [ %tc0, %entry ], [ %cnt.next, %loop ]
+  %min.wide = phi i64 [ %1, %entry ], [ %min.next.wide, %loop ]
+  %indvars.iv.next = add nsw i64 %indvars.iv, -1
+  %scan.p0 = getelementptr [4 x i8], ptr %a, i64 %indvars.iv
+  %scan.p = getelementptr i8, ptr %scan.p0, i64 -8
+  %scan.v = load float, ptr %scan.p, align 4
+  %min.p0 = getelementptr [4 x i8], ptr %a, i64 %min.wide
+  %min.p = getelementptr i8, ptr %min.p0, i64 -4
+  %min.v = load float, ptr %min.p, align 4
+  %c = fcmp fast olt float %scan.v, %min.v
+  %min.next.wide = select i1 %c, i64 %indvars.iv.next, i64 %min.wide
+  %2 = trunc nsw i64 %min.next.wide to i32
+  %cnt.next = add nsw i64 %cnt, -1
+  %again = icmp sgt i64 %cnt, 1
+  br i1 %again, label %loop, label %exit
+
+exit:
+  %min.lcssa = phi i32 [ %2, %loop ]
+  ret i32 %min.lcssa
+}



More information about the llvm-branch-commits mailing list