[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