[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