[llvm-branch-commits] [llvm] [GVN][MemDep] Recover affine-equal select-dependent load addresses via SCEV (PR #209826)
via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Sun Jul 19 23:36:54 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-transforms
Author: Madhur Amilkanthwar (madhur13490)
<details>
<summary>Changes</summary>
Add a ScalarEvolution hook to MemoryDependenceResults. When the syntactic
PHITransAddr match of a select-dependent load address fails for an arm,
recoverSelectArmAddr rewrites the address's recurrence phi with the selected
arm's SCEV and searches for an existing loaded-from pointer whose address
SCEV is equal, so the load-PRE-through-select path can reuse it. GVN wires
SE into MemDep, leaving all other MemDep users unaffected.
---
Full diff: https://github.com/llvm/llvm-project/pull/209826.diff
5 Files Affected:
- (modified) llvm/include/llvm/Analysis/MemoryDependenceAnalysis.h (+12)
- (modified) llvm/include/llvm/Transforms/Scalar/GVN.h (+3)
- (modified) llvm/lib/Analysis/MemoryDependenceAnalysis.cpp (+81)
- (modified) llvm/lib/Transforms/Scalar/GVN.cpp (+6)
- (added) llvm/test/Transforms/GVN/scev-select-load-address.ll (+73)
``````````diff
diff --git a/llvm/include/llvm/Analysis/MemoryDependenceAnalysis.h b/llvm/include/llvm/Analysis/MemoryDependenceAnalysis.h
index 459c8aeb5ab5b..98e7f84855b4a 100644
--- a/llvm/include/llvm/Analysis/MemoryDependenceAnalysis.h
+++ b/llvm/include/llvm/Analysis/MemoryDependenceAnalysis.h
@@ -32,6 +32,7 @@ namespace llvm {
class AssumptionCache;
class DominatorTree;
class PHITransAddr;
+class ScalarEvolution;
/// A memory dependence query can return one of three different answers.
class MemDepResult {
@@ -382,6 +383,12 @@ class MemoryDependenceResults {
PredIteratorCache PredCache;
EarliestEscapeAnalysis EEA;
+ /// Optional, opt-in ScalarEvolution used only to recover select-dependent
+ /// load addresses that are affine-equal (but not syntactically identical) to
+ /// an existing pointer. Null unless a client (currently GVN) sets it, so all
+ /// other MemDep users are unaffected.
+ ScalarEvolution *SE = nullptr;
+
unsigned DefaultBlockScanLimit;
/// Offsets to dependant clobber loads.
@@ -395,6 +402,11 @@ class MemoryDependenceResults {
: AA(AA), AC(AC), TLI(TLI), DT(DT), EEA(DT),
DefaultBlockScanLimit(DefaultBlockScanLimit) {}
+ /// Opt in to SCEV-based recovery of affine-equal select-dependent
+ /// addresses. Passing null (the default) preserves the syntactic-only
+ /// behavior.
+ void setScalarEvolution(ScalarEvolution *S) { SE = S; }
+
/// Handle invalidation in the new PM.
LLVM_ABI bool invalidate(Function &F, const PreservedAnalyses &PA,
FunctionAnalysisManager::Invalidator &Inv);
diff --git a/llvm/include/llvm/Transforms/Scalar/GVN.h b/llvm/include/llvm/Transforms/Scalar/GVN.h
index 0275c01b28020..554d66deecf1c 100644
--- a/llvm/include/llvm/Transforms/Scalar/GVN.h
+++ b/llvm/include/llvm/Transforms/Scalar/GVN.h
@@ -49,6 +49,7 @@ class GetElementPtrInst;
class ImplicitControlFlowTracking;
class LoadInst;
class LoopInfo;
+class ScalarEvolution;
class MemDepResult;
class MemoryAccess;
class MemoryDependenceResults;
@@ -260,6 +261,8 @@ class GVNPass : public OptionalPassInfoMixin<GVNPass> {
LoopInfo *LI = nullptr;
AAResults *AA = nullptr;
MemorySSAUpdater *MSSAU = nullptr;
+ // Prototype: SCEV handed to MemDep for affine select-address recovery.
+ ScalarEvolution *SE = nullptr;
ValueTable VN;
diff --git a/llvm/lib/Analysis/MemoryDependenceAnalysis.cpp b/llvm/lib/Analysis/MemoryDependenceAnalysis.cpp
index 6fda89af4867c..7cf1c7f35879b 100644
--- a/llvm/lib/Analysis/MemoryDependenceAnalysis.cpp
+++ b/llvm/lib/Analysis/MemoryDependenceAnalysis.cpp
@@ -25,6 +25,8 @@
#include "llvm/Analysis/MemoryBuiltins.h"
#include "llvm/Analysis/MemoryLocation.h"
#include "llvm/Analysis/PHITransAddr.h"
+#include "llvm/Analysis/ScalarEvolution.h"
+#include "llvm/Analysis/ScalarEvolutionExpressions.h"
#include "llvm/Analysis/TargetLibraryInfo.h"
#include "llvm/Analysis/ValueTracking.h"
#include "llvm/IR/BasicBlock.h"
@@ -1056,6 +1058,73 @@ MemoryDependenceResults::lookupNonLocalPointerDepVisited(BasicBlock *BB) const {
return NonLocalPointerDepVisited[BB->getNumber()].first;
}
+// When the syntactic PHI translation of a select-dependent load address fails
+// for one arm, try to recover an equivalent, already-existing address with
+// SCEV. The arm's address is the original address \p A with the recurrence
+// phi (whose \p PredBB incoming is a select on \p Cond) replaced by the value
+// chosen by \p CondVal. If SCEV proves that address equals the SCEV of an
+// existing pointer that is loaded from and dominates \p PredBB's terminator,
+// that pointer is returned so the load-PRE-through-select path can reuse it.
+// This closes the affine-address gap (different index with compensating
+// offsets) that the syntactic operand match cannot see. Returns null on
+// failure.
+static Value *recoverSelectArmAddr(Value *A, Value *Cond, BasicBlock *PredBB,
+ bool CondVal, ScalarEvolution &SE,
+ const DominatorTree &DT) {
+ if (!A || !SE.isSCEVable(A->getType()))
+ return nullptr;
+
+ const SCEV *S = SE.getSCEV(A);
+
+ // Locate the recurrence phi in the address whose PredBB incoming is a select
+ // on Cond, and the arm chosen by CondVal.
+ PHINode *RecPhi = nullptr;
+ Value *Arm = nullptr;
+ SCEVExprContains(S, [&](const SCEV *Sub) {
+ if (RecPhi)
+ return true;
+ auto *U = dyn_cast<SCEVUnknown>(Sub);
+ if (!U)
+ return false;
+ auto *PN = dyn_cast<PHINode>(U->getValue());
+ if (!PN || PN->getBasicBlockIndex(PredBB) < 0)
+ return false;
+ auto *SI = dyn_cast<SelectInst>(PN->getIncomingValueForBlock(PredBB));
+ if (!SI || SI->getCondition() != Cond)
+ return false;
+ RecPhi = PN;
+ Arm = CondVal ? SI->getTrueValue() : SI->getFalseValue();
+ return true;
+ });
+ if (!RecPhi || !Arm || !SE.isSCEVable(Arm->getType()))
+ return nullptr;
+
+ // The "keep" arm reproduces the original address exactly.
+ if (Arm == RecPhi)
+ return A;
+
+ ValueToSCEVMapTy Map;
+ Map[RecPhi] = SE.getSCEV(Arm);
+ const SCEV *Target = SCEVParameterRewriter::rewrite(S, SE, Map);
+ if (Target == S)
+ return nullptr;
+
+ // Return an existing loaded-from pointer with a matching address SCEV.
+ for (BasicBlock *BB = PredBB; BB; BB = BB->getSinglePredecessor())
+ for (Instruction &I : *BB) {
+ auto *LD = dyn_cast<LoadInst>(&I);
+ if (!LD)
+ continue;
+ Value *Ptr = LD->getPointerOperand();
+ if (!SE.isSCEVable(Ptr->getType()) ||
+ !DT.dominates(LD, PredBB->getTerminator()))
+ continue;
+ if (SE.getSCEV(Ptr) == Target)
+ return Ptr;
+ }
+ return nullptr;
+}
+
/// Perform a dependency query based on pointer/pointeesize starting at the end
/// of StartBB.
///
@@ -1378,6 +1447,18 @@ bool MemoryDependenceResults::getNonLocalPointerDepFromBB(
if (Value *Cond = PredPointer.getSelectCondition()) {
SelectAddr::SelectAddrs SelAddrs =
PHITransAddr(Pointer).translateValue(BB, Pred, &DT, Cond);
+ // If a side failed the syntactic match, try to recover an existing
+ // affine-equal address with SCEV (opt-in via setScalarEvolution).
+ if (SE && (!SelAddrs.first || !SelAddrs.second)) {
+ Value *A = Pointer.getAddr();
+ if (!SelAddrs.first)
+ SelAddrs.first = recoverSelectArmAddr(A, Cond, Pred,
+ /*CondVal=*/true, *SE, DT);
+ if (!SelAddrs.second)
+ SelAddrs.second =
+ recoverSelectArmAddr(A, Cond, Pred,
+ /*CondVal=*/false, *SE, DT);
+ }
if (SelAddrs.first && SelAddrs.second) {
Result.push_back(NonLocalDepResult(Pred, MemDepResult::getSelect(),
SelectAddr(Cond, SelAddrs)));
diff --git a/llvm/lib/Transforms/Scalar/GVN.cpp b/llvm/lib/Transforms/Scalar/GVN.cpp
index 517d33bfba103..2c5725a7817dd 100644
--- a/llvm/lib/Transforms/Scalar/GVN.cpp
+++ b/llvm/lib/Transforms/Scalar/GVN.cpp
@@ -41,6 +41,7 @@
#include "llvm/Analysis/MemorySSAUpdater.h"
#include "llvm/Analysis/OptimizationRemarkEmitter.h"
#include "llvm/Analysis/PHITransAddr.h"
+#include "llvm/Analysis/ScalarEvolution.h"
#include "llvm/Analysis/TargetLibraryInfo.h"
#include "llvm/Analysis/ValueTracking.h"
#include "llvm/IR/Attributes.h"
@@ -885,6 +886,11 @@ PreservedAnalyses GVNPass::run(Function &F, FunctionAnalysisManager &AM) {
MSSA = &AM.getResult<MemorySSAAnalysis>(F);
}
auto &ORE = AM.getResult<OptimizationRemarkEmitterAnalysis>(F);
+ // Hand SCEV to MemDep so its select-address translation can recover
+ // affine-equal addresses (reuses the load-PRE-through-select path).
+ SE = &AM.getResult<ScalarEvolutionAnalysis>(F);
+ if (MemDep)
+ MemDep->setScalarEvolution(SE);
bool Changed = runImpl(F, AC, DT, TLI, AA, MemDep, LI, &ORE,
MSSA ? &MSSA->getMSSA() : nullptr);
if (!Changed)
diff --git a/llvm/test/Transforms/GVN/scev-select-load-address.ll b/llvm/test/Transforms/GVN/scev-select-load-address.ll
new file mode 100644
index 0000000000000..2786b6759fc10
--- /dev/null
+++ b/llvm/test/Transforms/GVN/scev-select-load-address.ll
@@ -0,0 +1,73 @@
+; 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 floating-point argmin after IndVarSimplify has widened the index
+; recurrence to i64, as it reaches GVN in the real pipeline. Each iteration
+; reloads a[minidx] to compare against the scanned element. minidx is the latch
+; select of the previous iteration, so the reload address is select-dependent.
+; The address is a typed [4 x i8] GEP composed with a byte-offset i8 GEP, so the
+; select arm is affine-equal to the scanned-element pointer but not syntactically
+; identical: upstream's syntactic PHITransAddr match fails on that arm. MemDep
+; rewrites the recurrence phi with the selected arm's SCEV, proves the reloaded
+; address equals a previously loaded pointer, and forwards the value. The
+; a[minidx] reload should be eliminated and replaced by a running-minimum phi
+; fed by the value select.
+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
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/209826
More information about the llvm-branch-commits
mailing list