[llvm] Adding the support for vectorization of loops with load based tripcount (PR #209390)

Manish Srivastava via llvm-commits llvm-commits at lists.llvm.org
Thu Jul 16 22:59:17 PDT 2026


https://github.com/mk-srivastava updated https://github.com/llvm/llvm-project/pull/209390

>From 0adc4a1633addc0dbb28fc0ce47c0a29a804643e Mon Sep 17 00:00:00 2001
From: Manish Srivastava <manish.srivastava at amd.com>
Date: Tue, 14 Jul 2026 10:46:40 +0530
Subject: [PATCH 1/2] Adding the support for vectorization of loops with load
 based tripcount

---
 .../llvm/Analysis/LoopAccessAnalysis.h        |  25 ++-
 llvm/include/llvm/Analysis/ScalarEvolution.h  |  58 +++++-
 llvm/lib/Analysis/LoopAccessAnalysis.cpp      | 179 +++++++++++++++++-
 llvm/lib/Analysis/ScalarEvolution.cpp         | 126 +++++++++++-
 .../Utils/ScalarEvolutionExpander.cpp         |   2 +
 .../Vectorize/LoopVectorizationLegality.cpp   |   3 +-
 .../Transforms/Vectorize/LoopVectorize.cpp    |  87 ++++++++-
 .../dynamic-bound-array-element.ll            |  56 ++++++
 .../dynamic-bound-chained-loads.ll            |  60 ++++++
 .../LoopVectorize/dynamic-bound-fcmp-exit.ll  |  41 ++++
 .../LoopVectorize/dynamic-bound-lambda.ll     | 147 ++++++++++++++
 .../dynamic-bound-loop-varying.ll             |  54 ++++++
 .../LoopVectorize/dynamic-bound-simple.ll     | 144 ++++++++++++++
 .../dynamic-bound-stored-pointer.ll           |  47 +++++
 .../LoopVectorize/dynamic-bound-two-loads.ll  | 166 ++++++++++++++++
 .../dynamic-bound-volatile-load.ll            |  43 +++++
 16 files changed, 1219 insertions(+), 19 deletions(-)
 create mode 100644 llvm/test/Transforms/LoopVectorize/dynamic-bound-array-element.ll
 create mode 100644 llvm/test/Transforms/LoopVectorize/dynamic-bound-chained-loads.ll
 create mode 100644 llvm/test/Transforms/LoopVectorize/dynamic-bound-fcmp-exit.ll
 create mode 100644 llvm/test/Transforms/LoopVectorize/dynamic-bound-lambda.ll
 create mode 100644 llvm/test/Transforms/LoopVectorize/dynamic-bound-loop-varying.ll
 create mode 100644 llvm/test/Transforms/LoopVectorize/dynamic-bound-simple.ll
 create mode 100644 llvm/test/Transforms/LoopVectorize/dynamic-bound-stored-pointer.ll
 create mode 100644 llvm/test/Transforms/LoopVectorize/dynamic-bound-two-loads.ll
 create mode 100644 llvm/test/Transforms/LoopVectorize/dynamic-bound-volatile-load.ll

diff --git a/llvm/include/llvm/Analysis/LoopAccessAnalysis.h b/llvm/include/llvm/Analysis/LoopAccessAnalysis.h
index 392321448c895..d84fd0e03a5e9 100644
--- a/llvm/include/llvm/Analysis/LoopAccessAnalysis.h
+++ b/llvm/include/llvm/Analysis/LoopAccessAnalysis.h
@@ -26,6 +26,8 @@ namespace llvm {
 
 class AAResults;
 class DataLayout;
+class Instruction;
+class LoadInst;
 class Loop;
 class raw_ostream;
 class TargetTransformInfo;
@@ -694,7 +696,8 @@ class LoopAccessInfo {
                           const TargetTransformInfo *TTI,
                           const TargetLibraryInfo *TLI, AAResults *AA,
                           DominatorTree *DT, LoopInfo *LI, AssumptionCache *AC,
-                          bool AllowPartial = false);
+                          bool AllowPartial = false,
+                          ArrayRef<const SCEVTripCountInvariantPredicate *> TripCountInvariantPreds = {});
 
   /// Return true we can analyze the memory accesses in the loop and there are
   /// no memory dependence cycles. Note that for dependences between loads &
@@ -714,6 +717,10 @@ class LoopAccessInfo {
   /// could be analyzed.
   bool hasAllowPartial() const { return AllowPartial; }
 
+  ArrayRef<const SCEVTripCountInvariantPredicate *> getTripCountInvariantPreds() const {
+    return TripCountInvariantPreds;
+  }
+
   const RuntimePointerChecking *getRuntimePointerChecking() const {
     return PtrRtChecking.get();
   }
@@ -838,6 +845,10 @@ class LoopAccessInfo {
   /// memory accesses could be analyzed.
   bool AllowPartial;
 
+  /// Predicates for the loops inside the loop which are used to compute the tripcount and
+  /// which this analysis assumes to be invariant.
+  SmallVector<const SCEVTripCountInvariantPredicate *, 2> TripCountInvariantPreds;
+
   unsigned NumLoads = 0;
   unsigned NumStores = 0;
 
@@ -979,6 +990,15 @@ LLVM_ABI std::pair<const SCEV *, const SCEV *> getStartAndEndForAccess(
     DominatorTree *DT, AssumptionCache *AC,
     std::optional<ScalarEvolution::LoopGuards> &LoopGuards);
 
+/// This function will detect whether the loop is uncountabel because the tripcount
+/// is based on a load.
+/// If yes, then it will build a dependency chain of instructions, and a list of loads
+/// which are used to compute the tripcount.
+LLVM_ABI bool collectInvariantLoadsBoundChain(
+    Loop *L, ScalarEvolution *SE, DominatorTree *DT, AssumptionCache *AC,
+    SmallVectorImpl<Instruction *> &HoistedDeps,
+    SmallVectorImpl<LoadInst *> &BoundLoads);
+
 class LoopAccessInfoManager {
   /// The cache.
   DenseMap<Loop *, std::unique_ptr<LoopAccessInfo>> LoopAccessInfoMap;
@@ -1000,6 +1020,9 @@ class LoopAccessInfoManager {
 
   LLVM_ABI const LoopAccessInfo &getInfo(Loop &L, bool AllowPartial = false);
 
+  LLVM_ABI const LoopAccessInfo &getInfo(Loop &L, bool AllowPartial,
+        ArrayRef<const SCEVTripCountInvariantPredicate *> TripCountInvariantPreds);
+
   LLVM_ABI void clear();
 
   LLVM_ABI bool invalidate(Function &F, const PreservedAnalyses &PA,
diff --git a/llvm/include/llvm/Analysis/ScalarEvolution.h b/llvm/include/llvm/Analysis/ScalarEvolution.h
index 0a9a4370d5e80..f39553d3013d2 100644
--- a/llvm/include/llvm/Analysis/ScalarEvolution.h
+++ b/llvm/include/llvm/Analysis/ScalarEvolution.h
@@ -379,7 +379,7 @@ class SCEVPredicate : public FoldingSetNode {
   FoldingSetNodeIDRef FastID;
 
 public:
-  enum SCEVPredicateKind { P_Union, P_Compare, P_Wrap };
+  enum SCEVPredicateKind { P_Union, P_Compare, P_Wrap, P_TripCountInvariant };
 
 protected:
   SCEVPredicateKind Kind;
@@ -464,6 +464,37 @@ class LLVM_ABI SCEVComparePredicate final : public SCEVPredicate {
   }
 };
 
+/// This class extends the SCEVPredicate class to represent the assumption that the trip count of a loop is invariant.
+/// This predicate doesn't lower any numeric run-time check but solely depends on the memory alias checks generated
+/// for the loop by LoopVectorizer.
+/// NOTE: Users of this predicate make sure to verify that the memory alias checks are generated for the loop.
+class LLVM_ABI SCEVTripCountInvariantPredicate final : public SCEVPredicate {
+  const SCEV *TripCountLoad;
+  const SCEV *TripCountInvariantLoad;
+
+public:
+  SCEVTripCountInvariantPredicate(const FoldingSetNodeIDRef ID,
+                                 const SCEV *TripCountLoad,
+                                 const SCEV *TripCountInvariantLoad);
+
+  /// Implementation of the SCEVPredicate interface
+  bool implies(const SCEVPredicate *N, ScalarEvolution &SE) const override;
+  void print(raw_ostream &OS, unsigned Depth = 0) const override;
+  bool isAlwaysTrue() const override;
+
+  /// This predicate doesn't generate any runtime checks, and solely depends
+  /// upon the memory alias checks for the runtime check generation, so contributes
+  /// nothing to the check complexity
+  unsigned getComplexity() const override { return 0; }
+
+  const SCEV *getTripCountLoad() const { return TripCountLoad; }
+  const SCEV *getTripCountInvariantLoad() const { return TripCountInvariantLoad; }
+
+  static bool classof(const SCEVPredicate *P) {
+    return P->getKind() == P_TripCountInvariant;
+  }
+};
+
 /// This class represents an assumption made on an AddRec expression. Given an
 /// affine AddRec expression {a,+,b}, we assume that it has the nssw or nusw
 /// flags (defined below) in the first X iterations of the loop, where X is a
@@ -1122,6 +1153,11 @@ class ScalarEvolution {
   LLVM_ABI const SCEV *getPredicatedSymbolicMaxBackedgeTakenCount(
       const Loop *L, SmallVectorImpl<const SCEVPredicate *> &Predicates);
 
+  /// To compute the backedge-taken count under the assumption of TripCountInvariant predicate.
+  LLVM_ABI const SCEV *computeBackedgeTakenCountWithTripCountInvariants(
+      const Loop *L, ArrayRef<const SCEVTripCountInvariantPredicate *> Preds,
+      bool SymbolicMax = false);
+
   /// Return true if the backedge taken count is either the value returned by
   /// getConstantMaxBackedgeTakenCount or zero.
   LLVM_ABI bool isBackedgeTakenCountMaxOrZero(const Loop *L);
@@ -1522,6 +1558,9 @@ class ScalarEvolution {
   getWrapPredicate(const SCEVAddRecExpr *AR,
                    SCEVWrapPredicate::IncrementWrapFlags AddedFlags);
 
+  LLVM_ABI const SCEVPredicate *getTripCountInvariantPredicate(const SCEV *Load,
+                                                               const SCEV *InvariantLoad);
+
   /// Re-writes the SCEV according to the Predicates in \p A.
   LLVM_ABI const SCEV *rewriteUsingPredicate(const SCEV *S, const Loop *L,
                                              const SCEVPredicate &A);
@@ -1890,6 +1929,8 @@ class ScalarEvolution {
   /// function as they are computed.
   DenseMap<const Loop *, BackedgeTakenInfo> PredicatedBackedgeTakenCounts;
 
+  DenseMap<const Value *, const SCEV *> *ActiveTripCountInvariantSubst = nullptr;
+
   /// Loops whose backedge taken counts directly use this non-constant SCEV.
   DenseMap<const SCEV *, SmallPtrSet<PointerIntPair<const Loop *, 1, bool>, 4>>
       BECountUsers;
@@ -2655,6 +2696,17 @@ class PredicatedScalarEvolution {
   /// Adds a new predicate.
   LLVM_ABI void addPredicate(const SCEVPredicate &Pred);
 
+  LLVM_ABI void addTripCountInvariantPredicate(const SCEV *Load,
+                                               const SCEV *InvariantLoad);
+
+  LLVM_ABI bool hasTripCountInvariantPredicates() const {
+    return !TripCountInvariantPreds.empty();
+  }
+
+  LLVM_ABI ArrayRef<const SCEVTripCountInvariantPredicate *> getTripCountInvariantPredicates() const {
+    return TripCountInvariantPreds;
+  }
+
   /// Adds all predicates in \p Preds.
   LLVM_ABI void addPredicates(ArrayRef<const SCEVPredicate *> Preds);
 
@@ -2726,6 +2778,10 @@ class PredicatedScalarEvolution {
   /// The symbolic backedge taken count.
   const SCEV *SymbolicMaxBackedgeCount = nullptr;
 
+  SmallVector<const SCEVTripCountInvariantPredicate *, 2> TripCountInvariantPreds;
+
+  DenseMap<const Value *, const SCEV *> TripCountInvariantMap;
+
   /// The constant max trip count for the loop.
   std::optional<unsigned> SmallConstantMaxTripCount;
 };
diff --git a/llvm/lib/Analysis/LoopAccessAnalysis.cpp b/llvm/lib/Analysis/LoopAccessAnalysis.cpp
index 8d3c66c8fe321..aecb316fd162a 100644
--- a/llvm/lib/Analysis/LoopAccessAnalysis.cpp
+++ b/llvm/lib/Analysis/LoopAccessAnalysis.cpp
@@ -27,6 +27,7 @@
 #include "llvm/Analysis/AssumptionCache.h"
 #include "llvm/Analysis/LoopAnalysisManager.h"
 #include "llvm/Analysis/LoopInfo.h"
+#include "llvm/Analysis/Loads.h"
 #include "llvm/Analysis/LoopIterator.h"
 #include "llvm/Analysis/MemoryLocation.h"
 #include "llvm/Analysis/OptimizationRemarkEmitter.h"
@@ -3175,13 +3176,168 @@ void LoopAccessInfo::collectStridedAccess(Value *MemAccess) {
   SymbolicStrides[Ptr] = cast<SCEVUnknown>(StrideBase);
 }
 
+static bool isBoundLoadSafeToLoadAtCtx(Value *Ptr, Type *Ty, Align Alignment,
+                                       Instruction *CtxI, DominatorTree *DT,
+                                       AssumptionCache *AC) {
+  const DataLayout &DL = CtxI->getDataLayout();
+  for (DomTreeNode *Node = DT->getNode(CtxI->getParent()); Node;
+       Node = Node->getIDom()) {
+    BasicBlock *BB = Node->getBlock();
+    Instruction *ScanFrom =
+        (BB == CtxI->getParent()) ? CtxI : BB->getTerminator();
+    if (isSafeToLoadUnconditionally(Ptr, Ty, Alignment, DL, ScanFrom, AC, DT)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+static bool isSafeToHoistBoundLoad(Instruction *I,
+                                   const SmallPtrSetImpl<Value *> &ModifiedPtrs,
+                                   Instruction *CtxI, DominatorTree *DT,
+                                   AssumptionCache *AC) {
+  if (auto *LI = dyn_cast<LoadInst>(I)) {
+    if (!LI->isSimple()) {
+      return false;
+    }
+    Value *Ptr = LI->getPointerOperand();
+    if (ModifiedPtrs.count(Ptr)) {
+      return false;
+    }
+    if (!isBoundLoadSafeToLoadAtCtx(Ptr, LI->getType(), LI->getAlign(), CtxI, DT,
+                                   AC)) {
+      return false;
+    }
+    return true;
+  }
+
+  // Limiting down the kind of instructions we can hoist to avoid any unforeseen side effects.
+  if (I->isBinaryOp() || I->isUnaryOp() || I->isCast() || isa<GetElementPtrInst>(I)) {
+    return isSafeToSpeculativelyExecute(I, CtxI, AC, DT);
+  }
+  return false;
+}
+
+LLVM_ABI bool llvm::collectInvariantLoadsBoundChain(
+    Loop *L, ScalarEvolution *SE, DominatorTree *DT, AssumptionCache *AC,
+    SmallVectorImpl<Instruction *> &HoistedDeps,
+    SmallVectorImpl<LoadInst *> &BoundLoads) {
+  HoistedDeps.clear();
+  BoundLoads.clear();
+
+  BasicBlock *ExitingBB = L->getExitingBlock();
+  if (!ExitingBB || ExitingBB != L->getLoopLatch()) {
+    return false;
+  }
+  auto *ExitBranch = dyn_cast<CondBrInst>(ExitingBB->getTerminator());
+  if (!ExitBranch) {
+    return false;
+  }
+  auto *ExitCmp = dyn_cast<ICmpInst>(ExitBranch->getCondition());
+  if (!ExitCmp) {
+    return false;
+  }
+  PHINode *IndVar = L->getInductionVariable(*SE);
+  if (!IndVar) {
+    return false;
+  }
+  Value *StepInst = IndVar->getIncomingValueForBlock(L->getLoopLatch());
+  auto IsIVOrStep = [&](Value *V) { return V == IndVar || V == StepInst; };
+
+  Value *DynamicUpperBound = nullptr;
+  if (IsIVOrStep(ExitCmp->getOperand(0))) {
+    DynamicUpperBound = ExitCmp->getOperand(1);
+  } else if (IsIVOrStep(ExitCmp->getOperand(1))) {
+    DynamicUpperBound = ExitCmp->getOperand(0);
+  } else {
+    return false;
+  }
+
+  /// Anything written inside the loop is a "not-invariant" pointer; the bound
+  /// load cannot share a pointer with such writes.
+  /// This is to prune out cases like for (i = 0; i < *Len; ++i) { Len[i] = ..*.. }
+  SmallPtrSet<Value *, 16> ModifiedPtrs;
+  for (BasicBlock *BB : L->getBlocks()) {
+    for (Instruction &I : *BB) {
+      if (auto *SI = dyn_cast<StoreInst>(&I)) {
+        ModifiedPtrs.insert(SI->getPointerOperand());
+      }
+    }
+  }
+
+  BasicBlock *Preheader = L->getLoopPreheader();
+  if (!Preheader) {
+    return false;
+  }
+  Instruction *CtxI = Preheader->getTerminator();
+
+  // The bound must depend on at least one in-loop load (otherwise the loop
+  // would already be countable and this is unnecessary).
+  SmallVector<Instruction *, 16> Worklist;
+  SmallPtrSet<Instruction *, 16> VisitedForHoisting;
+  SmallPtrSet<Instruction *, 16> VisitedForChecking;
+
+  if (auto *I = dyn_cast<Instruction>(DynamicUpperBound)) {
+    Worklist.push_back(I);
+  }
+
+  while (!Worklist.empty()) {
+    Instruction *I = Worklist.back();
+
+    if (VisitedForHoisting.count(I)) {
+      Worklist.pop_back();
+      continue;
+    }
+
+    if (!L->contains(I->getParent())) {
+      Worklist.pop_back();
+      continue;
+    }
+
+    if (VisitedForChecking.insert(I).second) {
+      if (!isSafeToHoistBoundLoad(I, ModifiedPtrs, CtxI, DT, AC)) {
+        return false;
+      }
+
+      if (auto *LI = dyn_cast<LoadInst>(I)) {
+        BoundLoads.push_back(LI);
+      }
+
+      for (Use &U : I->operands()) {
+        if (auto *OpI = dyn_cast<Instruction>(U.get())) {
+          if (L->contains(OpI)) {
+            Worklist.push_back(OpI);
+          }
+        }
+      }
+
+      continue;
+    }
+
+    Worklist.pop_back();
+
+    VisitedForHoisting.insert(I);
+
+    HoistedDeps.push_back(I);
+  }
+
+  return !BoundLoads.empty() && !HoistedDeps.empty();
+}
+
 LoopAccessInfo::LoopAccessInfo(Loop *L, ScalarEvolution *SE,
                                const TargetTransformInfo *TTI,
                                const TargetLibraryInfo *TLI, AAResults *AA,
                                DominatorTree *DT, LoopInfo *LI,
-                               AssumptionCache *AC, bool AllowPartial)
+                               AssumptionCache *AC, bool AllowPartial,
+                               ArrayRef<const SCEVTripCountInvariantPredicate *> TripCountInvariantPreds)
     : PSE(std::make_unique<PredicatedScalarEvolution>(*SE, *L)),
-      PtrRtChecking(nullptr), TheLoop(L), AllowPartial(AllowPartial) {
+      PtrRtChecking(nullptr), TheLoop(L), AllowPartial(AllowPartial),
+      TripCountInvariantPreds(TripCountInvariantPreds) {
+
+  for (const SCEVTripCountInvariantPredicate *P : TripCountInvariantPreds) {
+    PSE->addTripCountInvariantPredicate(P->getTripCountLoad(), P->getTripCountInvariantLoad());
+  }
+
   unsigned MaxTargetVectorWidthInBits = std::numeric_limits<unsigned>::max();
   if (TTI && !TTI->enableScalableVectorization())
     // Scale the vector width by 2 as rough estimate to also consider
@@ -3254,16 +3410,31 @@ void LoopAccessInfo::print(raw_ostream &OS, unsigned Depth) const {
 
 const LoopAccessInfo &LoopAccessInfoManager::getInfo(Loop &L,
                                                      bool AllowPartial) {
+  return getInfo(L, AllowPartial, {});
+}
+
+const LoopAccessInfo &LoopAccessInfoManager::getInfo(Loop &L,
+                                                     bool AllowPartial,
+                                                     ArrayRef<const SCEVTripCountInvariantPredicate *> TripCountInvariantPreds) {
   const auto &[It, Inserted] = LoopAccessInfoMap.try_emplace(&L);
 
+  auto SamePreds = [&]() {
+    ArrayRef<const SCEVTripCountInvariantPredicate *> Cached =
+        It->second->getTripCountInvariantPreds();
+    if (Cached.size() != TripCountInvariantPreds.size())
+      return false;
+    return std::equal(Cached.begin(), Cached.end(), TripCountInvariantPreds.begin());
+  };
+
   // We need to create the LoopAccessInfo if either we don't already have one,
   // or if it was created with a different value of AllowPartial.
-  if (Inserted || It->second->hasAllowPartial() != AllowPartial)
+  if (Inserted || It->second->hasAllowPartial() != AllowPartial || !SamePreds())
     It->second = std::make_unique<LoopAccessInfo>(&L, &SE, TTI, TLI, &AA, &DT,
-                                                  &LI, AC, AllowPartial);
+                                                  &LI, AC, AllowPartial, TripCountInvariantPreds);
 
   return *It->second;
 }
+
 void LoopAccessInfoManager::clear() {
   // Collect LoopAccessInfo entries that may keep references to IR outside the
   // analyzed loop or SCEVs that may have been modified or invalidated. At the
diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index 134be6ac097e0..a204c46947001 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -8676,6 +8676,29 @@ const SCEV *ScalarEvolution::getPredicatedConstantMaxBackedgeTakenCount(
   return getPredicatedBackedgeTakenInfo(L).getConstantMax(this, &Preds);
 }
 
+const SCEV *ScalarEvolution::computeBackedgeTakenCountWithTripCountInvariants(
+    const Loop *L, ArrayRef<const SCEVTripCountInvariantPredicate *> Preds,
+    bool SymbolicMax) {
+  if (Preds.empty())
+    return SymbolicMax ? getSymbolicMaxBackedgeTakenCount(L)
+                       : getBackedgeTakenCount(L);
+
+  ValueToSCEVMapTy Subst;
+  for (const SCEVTripCountInvariantPredicate *P : Preds) {
+    const auto *V = dyn_cast<SCEVUnknown>(P->getTripCountLoad());
+    if (!V)
+      return getCouldNotCompute();
+    Subst[V->getValue()] = P->getTripCountInvariantLoad();
+  }
+
+  (void)getBackedgeTakenInfo(L);
+
+  SaveAndRestore<DenseMap<const Value *, const SCEV *> *> Active(
+      ActiveTripCountInvariantSubst, &Subst);
+  BackedgeTakenInfo BTI = computeBackedgeTakenCount(L, /*AllowPredicates=*/false);
+  return SymbolicMax ? BTI.getSymbolicMax(L, this) : BTI.getExact(L, this);
+}
+
 bool ScalarEvolution::isBackedgeTakenCountMaxOrZero(const Loop *L) {
   return getBackedgeTakenInfo(L).isConstantMaxOrZero(this);
 }
@@ -9464,6 +9487,15 @@ ScalarEvolution::ExitLimit ScalarEvolution::computeExitLimitFromICmp(
   const SCEV *LHS = getSCEV(ExitCond->getOperand(0));
   const SCEV *RHS = getSCEV(ExitCond->getOperand(1));
 
+  // If the TripCountInvariant predicate is active, then replace the LHS and RHS to their proper
+  // substition values as per the substitution map.
+  if (ActiveTripCountInvariantSubst && !ActiveTripCountInvariantSubst->empty()) {
+    auto &Subst = const_cast<ValueToSCEVMapTy &>(*ActiveTripCountInvariantSubst);
+
+    LHS = SCEVParameterRewriter::rewrite(LHS, *this, Subst);
+    RHS = SCEVParameterRewriter::rewrite(RHS, *this, Subst);
+  }
+
   ExitLimit EL = computeExitLimitFromICmp(L, Pred, LHS, RHS, ControlsOnlyExit,
                                           AllowPredicates);
   if (EL.hasAnyInfo())
@@ -15263,6 +15295,21 @@ const SCEVPredicate *ScalarEvolution::getWrapPredicate(
   return OF;
 }
 
+const SCEVPredicate *ScalarEvolution::getTripCountInvariantPredicate(const SCEV *Load,
+                                                                     const SCEV *InvariantLoad) {
+  FoldingSetNodeID ID;
+  ID.AddInteger(SCEVPredicate::P_TripCountInvariant);
+  ID.AddPointer(Load);
+  ID.AddPointer(InvariantLoad);
+  void *IP = nullptr;
+  if (const auto *S = UniquePreds.FindNodeOrInsertPos(ID, IP))
+    return S;
+  auto *TCI = new (SCEVAllocator)
+      SCEVTripCountInvariantPredicate(ID.Intern(SCEVAllocator), Load, InvariantLoad);
+  UniquePreds.InsertNode(TCI, IP);
+  return TCI;
+}
+
 namespace {
 
 class SCEVPredicateRewriter : public SCEVRewriteVisitor<SCEVPredicateRewriter> {
@@ -15470,6 +15517,29 @@ void SCEVComparePredicate::print(raw_ostream &OS, unsigned Depth) const {
 
 }
 
+SCEVTripCountInvariantPredicate::SCEVTripCountInvariantPredicate(const FoldingSetNodeIDRef ID,
+                                                                   const SCEV *TripCountLoad,
+                                                                   const SCEV *TripCountInvariantLoad)
+  : SCEVPredicate(ID, P_TripCountInvariant), TripCountLoad(TripCountLoad), TripCountInvariantLoad(TripCountInvariantLoad) {}
+
+bool SCEVTripCountInvariantPredicate::implies(const SCEVPredicate *N,
+                                              ScalarEvolution &SE) const {
+  const auto *Op = dyn_cast<SCEVTripCountInvariantPredicate>(N);
+  if (!Op)
+    return false;
+  return Op->TripCountLoad == TripCountLoad && Op->TripCountInvariantLoad == TripCountInvariantLoad;
+}
+
+bool SCEVTripCountInvariantPredicate::isAlwaysTrue() const {
+  return TripCountLoad == TripCountInvariantLoad;
+}
+
+void SCEVTripCountInvariantPredicate::print(raw_ostream &OS, unsigned Depth) const {
+  OS.indent(Depth) << "Trip count invariant predicate: " << *TripCountLoad 
+                              << " is invariant (== " << *TripCountInvariantLoad << ")\n"
+                              << "\n";
+}
+
 SCEVWrapPredicate::SCEVWrapPredicate(const FoldingSetNodeIDRef ID,
                                      const SCEVAddRecExpr *AR,
                                      IncrementWrapFlags Flags)
@@ -15684,6 +15754,11 @@ const SCEV *PredicatedScalarEvolution::getPredicatedSCEV(const SCEV *Expr) {
     Expr = Entry.second;
 
   const SCEV *NewSCEV = SE.rewriteUsingPredicate(Expr, &L, *Preds);
+
+  if (!TripCountInvariantMap.empty()) {
+    NewSCEV = SCEVParameterRewriter::rewrite(NewSCEV, SE, TripCountInvariantMap);
+  }
+
   Entry = {Generation, NewSCEV};
 
   return NewSCEV;
@@ -15691,21 +15766,33 @@ const SCEV *PredicatedScalarEvolution::getPredicatedSCEV(const SCEV *Expr) {
 
 const SCEV *PredicatedScalarEvolution::getBackedgeTakenCount() {
   if (!BackedgeCount) {
-    SmallVector<const SCEVPredicate *, 4> Preds;
-    BackedgeCount = SE.getPredicatedBackedgeTakenCount(&L, Preds);
-    for (const auto *P : Preds)
-      addPredicate(*P);
+    if (!TripCountInvariantPreds.empty()) {
+      BackedgeCount = SE.computeBackedgeTakenCountWithTripCountInvariants(&L, 
+        TripCountInvariantPreds, /*SymbolicMax=*/false);
+    }
+    else {
+      SmallVector<const SCEVPredicate *, 4> Preds;
+      BackedgeCount = SE.getPredicatedBackedgeTakenCount(&L, Preds);
+      for (const auto *P : Preds)
+        addPredicate(*P);
+    }
   }
   return BackedgeCount;
 }
 
 const SCEV *PredicatedScalarEvolution::getSymbolicMaxBackedgeTakenCount() {
   if (!SymbolicMaxBackedgeCount) {
-    SmallVector<const SCEVPredicate *, 4> Preds;
-    SymbolicMaxBackedgeCount =
-        SE.getPredicatedSymbolicMaxBackedgeTakenCount(&L, Preds);
-    for (const auto *P : Preds)
-      addPredicate(*P);
+    if (!TripCountInvariantPreds.empty()) {
+      SymbolicMaxBackedgeCount = SE.computeBackedgeTakenCountWithTripCountInvariants(&L, 
+        TripCountInvariantPreds, /*SymbolicMax=*/true);
+    }
+    else {
+      SmallVector<const SCEVPredicate *, 4> Preds;
+      SymbolicMaxBackedgeCount =
+          SE.getPredicatedSymbolicMaxBackedgeTakenCount(&L, Preds);
+      for (const auto *P : Preds)
+        addPredicate(*P);
+    }
   }
   return SymbolicMaxBackedgeCount;
 }
@@ -15730,6 +15817,23 @@ void PredicatedScalarEvolution::addPredicate(const SCEVPredicate &Pred) {
   updateGeneration();
 }
 
+void PredicatedScalarEvolution::addTripCountInvariantPredicate(const SCEV *Load, const SCEV *InvariantLoad) {
+  const auto *V = dyn_cast<SCEVUnknown>(Load);
+  assert(V && "mem-invariant Load must be a SCEVUnknown");
+
+  const auto *P = cast<SCEVTripCountInvariantPredicate>(
+      SE.getTripCountInvariantPredicate(Load, InvariantLoad));
+
+  if (TripCountInvariantMap.insert({V->getValue(), InvariantLoad}).second) {
+    TripCountInvariantPreds.push_back(P);
+    BackedgeCount = nullptr;
+    SymbolicMaxBackedgeCount = nullptr;
+    SmallConstantMaxTripCount.reset();
+  }
+
+  addPredicate(*P);
+}
+
 void PredicatedScalarEvolution::addPredicates(
     ArrayRef<const SCEVPredicate *> Preds) {
   for (const SCEVPredicate *P : Preds)
@@ -15787,7 +15891,9 @@ PredicatedScalarEvolution::PredicatedScalarEvolution(
     : RewriteMap(Init.RewriteMap), SE(Init.SE), L(Init.L),
       Preds(std::make_unique<SCEVUnionPredicate>(Init.Preds->getPredicates(),
                                                  SE)),
-      Generation(Init.Generation), BackedgeCount(Init.BackedgeCount) {}
+      Generation(Init.Generation), BackedgeCount(Init.BackedgeCount),
+      TripCountInvariantPreds(Init.TripCountInvariantPreds),
+      TripCountInvariantMap(Init.TripCountInvariantMap) {}
 
 void PredicatedScalarEvolution::print(raw_ostream &OS, unsigned Depth) const {
   // For each block.
diff --git a/llvm/lib/Transforms/Utils/ScalarEvolutionExpander.cpp b/llvm/lib/Transforms/Utils/ScalarEvolutionExpander.cpp
index 125571b18dddc..f46eb9aff16e3 100644
--- a/llvm/lib/Transforms/Utils/ScalarEvolutionExpander.cpp
+++ b/llvm/lib/Transforms/Utils/ScalarEvolutionExpander.cpp
@@ -2211,6 +2211,8 @@ Value *SCEVExpander::expandCodeForPredicate(const SCEVPredicate *Pred,
     auto *AddRecPred = cast<SCEVWrapPredicate>(Pred);
     return expandWrapPredicate(AddRecPred, IP);
   }
+  case SCEVPredicate::P_TripCountInvariant:
+    return ConstantInt::getFalse(IP->getContext());
   }
   llvm_unreachable("Unknown SCEV predicate type");
 }
diff --git a/llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp b/llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp
index 8d875b2b6e492..94e101297f68a 100644
--- a/llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp
+++ b/llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp
@@ -1158,7 +1158,8 @@ bool LoopVectorizationLegality::canVectorizeIndirectUnsafeDependences() {
 }
 
 bool LoopVectorizationLegality::canVectorizeMemory() {
-  LAI = &LAIs.getInfo(*TheLoop);
+  LAI = &LAIs.getInfo(*TheLoop, false,
+      PSE.getTripCountInvariantPredicates());
   const OptimizationRemarkAnalysis *LAR = LAI->getReport();
   if (LAR) {
     ORE->emit([&]() {
diff --git a/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp b/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
index 1929f52ae7d95..7dbd53a83904f 100644
--- a/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
+++ b/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
@@ -412,6 +412,12 @@ static cl::opt<bool> EnableEarlyExitVectorization(
     cl::desc(
         "Enable vectorization of early exit loops with uncountable exits."));
 
+static cl::opt<bool> EnableVectorizeLoadsAsBound(
+    "enable-vectorize-loads-as-bound", cl::init(false), cl::Hidden,
+    cl::desc("Enable vectorization of loops whose trip count is uncountable "
+             "only because their upper bound is a load with a loop-invariant "
+             "address (example: for (i = 0; i < *Len; ++i))."));
+
 static cl::opt<bool> EnableEarlyExitVectorizationWithSideEffects(
     "enable-early-exit-vectorization-with-side-effects", cl::init(false),
     cl::Hidden,
@@ -1630,16 +1636,29 @@ class GeneratedRTChecks {
     BasicBlock *LoopHeader = L->getHeader();
     BasicBlock *Preheader = L->getLoopPreheader();
 
+    SmallVector<const SCEVPredicate *, 4> SCEVPredsVec;
+    if (const auto *U = dyn_cast<SCEVUnionPredicate>(&UnionPred)) {
+      for (const SCEVPredicate *P : U->getPredicates()) {
+        if (!isa<SCEVTripCountInvariantPredicate>(P)) {
+          SCEVPredsVec.push_back(P);
+        }
+      }
+    }
+    else if (!isa<SCEVTripCountInvariantPredicate>(&UnionPred)) {
+      SCEVPredsVec.push_back(&UnionPred);
+    }
+    SCEVUnionPredicate FilteredPred(SCEVPredsVec, *PSE.getSE());
+
     // Use SplitBlock to create blocks for SCEV & memory runtime checks to
     // ensure the blocks are properly added to LoopInfo & DominatorTree. Those
     // may be used by SCEVExpander. The blocks will be un-linked from their
     // predecessors and removed from LI & DT at the end of the function.
-    if (!UnionPred.isAlwaysTrue()) {
+    if (!FilteredPred.isAlwaysTrue()) {
       SCEVCheckBlock = SplitBlock(Preheader, Preheader->getTerminator(), DT, LI,
                                   nullptr, "vector.scevcheck");
 
       SCEVCheckCond = SCEVExp.expandCodeForPredicate(
-          &UnionPred, SCEVCheckBlock->getTerminator());
+          &FilteredPred, SCEVCheckBlock->getTerminator());
       if (isa<Constant>(SCEVCheckCond)) {
         // Clean up directly after expanding the predicate to a constant, to
         // avoid further expansions re-using anything left over from SCEVExp.
@@ -7859,6 +7878,66 @@ static void connectEpilogueVectorLoop(VPlan &EpiPlan, Loop *L,
       Phi.eraseFromParent();
 }
 
+bool EnableLoadBoundVectorization(Loop *L, PredicatedScalarEvolution &PSE,
+                                  ScalarEvolution *SE, DominatorTree *DT,
+                                  AssumptionCache *AC) {
+  if (!L->isInnermost() || !L->isLoopSimplifyForm() ||
+    L->getNumBackEdges() != 1 || !L->getUniqueExitBlock()) {
+    return false;
+  }
+
+  if (!isa<SCEVCouldNotCompute>(SE->getBackedgeTakenCount(L))) {
+    return false;
+  }
+
+  SmallVector<Instruction *, 16> HoistedDeps;
+  SmallVector<LoadInst *, 4> BoundLoads;
+  if (!collectInvariantLoadsBoundChain(L, SE, DT, AC, HoistedDeps, BoundLoads)) {
+    return false;
+  }
+
+  BasicBlock *Preheader = L->getLoopPreheader();
+  Instruction *InsertPt = Preheader->getTerminator();
+
+  DenseMap<Value *, Value *> CloneMap;
+  for (Instruction *I : HoistedDeps) {
+    Instruction *Clone = I->clone();
+    Clone->setName(I->getName() + ".bound.pre");
+    for (Use &U : Clone->operands()) {
+      if (auto *OpI = dyn_cast<Instruction>(U.get())) {
+        if (L->contains(OpI)) {
+          U.set(CloneMap[OpI]);
+        }
+      }
+    }
+    Clone->insertBefore(InsertPt->getIterator());
+    CloneMap[I] = Clone;
+  }
+
+  bool Added = false;
+  for (LoadInst *LI : BoundLoads) {
+    auto It = CloneMap.find(LI);
+    if (It == CloneMap.end()) {
+      continue;
+    }
+    const SCEV *LoadSCEV = SE->getSCEV(LI);
+    const SCEV *InvSCEV = SE->getSCEV(It->second);
+    if (!isa<SCEVUnknown>(LoadSCEV)) {
+      continue;
+    }
+    PSE.addTripCountInvariantPredicate(LoadSCEV, InvSCEV);
+    Added = true;
+  }
+  if (!Added) {
+    return false;
+  }
+
+  LLVM_DEBUG(dbgs() << "LV: Assuming in-loop bound load(s) invariant to make '"
+                    << L->getHeader()->getName()
+                    << "' countable (discharged by memory alias check).\n");
+  return true;
+}
+
 bool LoopVectorizePass::processLoop(Loop *L) {
   assert((EnableVPlanNativePath || L->isInnermost()) &&
          "VPlan-native path is not enabled. Only process inner loops.");
@@ -7898,6 +7977,10 @@ bool LoopVectorizePass::processLoop(Loop *L) {
 
   PredicatedScalarEvolution PSE(*SE, *L);
 
+  if (EnableVectorizeLoadsAsBound && L->isInnermost()) {
+    EnableLoadBoundVectorization(L, PSE, SE, DT, AC);
+  }
+
   // Query this against the original loop and save it here because the profile
   // of the original loop header may change as the transformation happens.
   bool OptForSize = llvm::shouldOptimizeForSize(
diff --git a/llvm/test/Transforms/LoopVectorize/dynamic-bound-array-element.ll b/llvm/test/Transforms/LoopVectorize/dynamic-bound-array-element.ll
new file mode 100644
index 0000000000000..7d8db5c67f519
--- /dev/null
+++ b/llvm/test/Transforms/LoopVectorize/dynamic-bound-array-element.ll
@@ -0,0 +1,56 @@
+; RUN: opt < %s -passes=loop-vectorize -enable-vectorize-loads-as-bound -force-vector-width=4 -force-vector-interleave=1 -S | FileCheck %s
+;
+; Negative test: Loop bound loaded from an array element A[2], where A is also
+; written inside the loop. The load and store use different indices, so
+; pointer identity does not flag a conflict but then LoopAccessAnalysis 
+; finds an unresolvable (same-base) dependence between the A[2] read
+; and the A[i] write and refuses to vectorize
+;
+;   void foo(int *A, int *B, int *C, int *Len) {
+;     for (int i = 0; i < A[2]; i++)
+;       A[i] = B[i] + C[i];
+;   }
+;
+
+define dso_local void @foo(ptr noundef captures(none) %A, ptr noundef readonly captures(none) %B, ptr noundef readonly captures(none) %C, ptr noundef readnone captures(none) %Len) #0 {
+; CHECK-LABEL: @foo(
+; CHECK-NOT:   vector.body
+entry:
+  %arrayidx = getelementptr inbounds nuw i8, ptr %A, i64 8
+  %0 = load i32, ptr %arrayidx, align 4, !tbaa !5
+  %cmp11 = icmp sgt i32 %0, 0
+  br i1 %cmp11, label %for.body, label %for.cond.cleanup
+
+for.cond.cleanup:
+  ret void
+
+for.body:
+  %indvars.iv = phi i64 [ %indvars.iv.next, %for.body ], [ 0, %entry ]
+  %arrayidx1 = getelementptr inbounds nuw i32, ptr %B, i64 %indvars.iv
+  %1 = load i32, ptr %arrayidx1, align 4, !tbaa !5
+  %arrayidx3 = getelementptr inbounds nuw i32, ptr %C, i64 %indvars.iv
+  %2 = load i32, ptr %arrayidx3, align 4, !tbaa !5
+  %add = add nsw i32 %2, %1
+  %arrayidx5 = getelementptr inbounds nuw i32, ptr %A, i64 %indvars.iv
+  store i32 %add, ptr %arrayidx5, align 4, !tbaa !5
+  %indvars.iv.next = add nuw nsw i64 %indvars.iv, 1
+  %3 = load i32, ptr %arrayidx, align 4, !tbaa !5
+  %4 = sext i32 %3 to i64
+  %cmp = icmp slt i64 %indvars.iv.next, %4
+  br i1 %cmp, label %for.body, label %for.cond.cleanup, !llvm.loop !9
+}
+
+attributes #0 = { mustprogress nofree norecurse nosync nounwind memory(argmem: readwrite) uwtable }
+
+!0 = !{i32 1, !"wchar_size", i32 4}
+!1 = !{i32 8, !"PIC Level", i32 2}
+!2 = !{i32 7, !"PIE Level", i32 2}
+!3 = !{i32 7, !"uwtable", i32 2}
+!4 = !{!"clang version 21.1.8"}
+!5 = !{!6, !6, i64 0, i64 4}
+!6 = !{!7, i64 4, !"int"}
+!7 = !{!8, i64 1, !"omnipotent char"}
+!8 = !{!"Simple C++ TBAA"}
+!9 = distinct !{!9, !10, !11}
+!10 = !{!"llvm.loop.mustprogress"}
+!11 = !{!"llvm.loop.unroll.disable"}
diff --git a/llvm/test/Transforms/LoopVectorize/dynamic-bound-chained-loads.ll b/llvm/test/Transforms/LoopVectorize/dynamic-bound-chained-loads.ll
new file mode 100644
index 0000000000000..b9a7ad48547bf
--- /dev/null
+++ b/llvm/test/Transforms/LoopVectorize/dynamic-bound-chained-loads.ll
@@ -0,0 +1,60 @@
+; RUN: opt < %s -passes=loop-vectorize -enable-vectorize-loads-as-bound -force-vector-width=4 -force-vector-interleave=1 -S | FileCheck %s
+;
+; Negative test: the upper bound is **PtrLen (an indirect load).
+; LoopAccessAnalysis cannot compute SCEV bounds for the indirect 
+; load (load i32, ptr %ptr) because %ptr itself is loaded inside the loop. 
+; The loop will be rejected.
+;
+; C source:
+;   void foo(int *A, int *B, int *C, int **PtrLen) {
+;     for (int i = 0; i < **PtrLen; i++)
+;       A[i] = B[i] + C[i];
+;   }
+
+define dso_local void @foo(ptr noundef writeonly captures(none) %A, ptr noundef readonly captures(none) %B, ptr noundef readonly captures(none) %C, ptr noundef readonly captures(none) %PtrLen) #0 {
+; CHECK-LABEL: @foo(
+; CHECK-NOT:   vector.body
+; CHECK-NOT:   .bound.pre
+entry:
+  %ptr0 = load ptr, ptr %PtrLen, align 8, !tbaa !5
+  %val0 = load i32, ptr %ptr0, align 4, !tbaa !10
+  %cmp9 = icmp sgt i32 %val0, 0
+  br i1 %cmp9, label %for.body, label %for.cond.cleanup
+
+for.cond.cleanup:
+  ret void
+
+for.body:
+  %indvars.iv = phi i64 [ %indvars.iv.next, %for.body ], [ 0, %entry ]
+  %arrayidx = getelementptr inbounds nuw i32, ptr %B, i64 %indvars.iv
+  %1 = load i32, ptr %arrayidx, align 4, !tbaa !10
+  %arrayidx2 = getelementptr inbounds nuw i32, ptr %C, i64 %indvars.iv
+  %2 = load i32, ptr %arrayidx2, align 4, !tbaa !10
+  %add = add nsw i32 %2, %1
+  %arrayidx4 = getelementptr inbounds nuw i32, ptr %A, i64 %indvars.iv
+  store i32 %add, ptr %arrayidx4, align 4, !tbaa !10
+  %indvars.iv.next = add nuw nsw i64 %indvars.iv, 1
+  %ptr = load ptr, ptr %PtrLen, align 8, !tbaa !5
+  %val = load i32, ptr %ptr, align 4, !tbaa !10
+  %3 = sext i32 %val to i64
+  %cmp = icmp slt i64 %indvars.iv.next, %3
+  br i1 %cmp, label %for.body, label %for.cond.cleanup, !llvm.loop !12
+}
+
+attributes #0 = { mustprogress nofree norecurse nosync nounwind memory(argmem: readwrite) uwtable }
+
+!0 = !{i32 1, !"wchar_size", i32 4}
+!1 = !{i32 8, !"PIC Level", i32 2}
+!2 = !{i32 7, !"PIE Level", i32 2}
+!3 = !{i32 7, !"uwtable", i32 2}
+!4 = !{!"clang version 21.1.8"}
+!5 = !{!6, !6, i64 0, i64 8}
+!6 = !{!7, i64 8, !"p1 int"}
+!7 = !{!8, i64 8, !"any pointer"}
+!8 = !{!9, i64 1, !"omnipotent char"}
+!9 = !{!"Simple C++ TBAA"}
+!10 = !{!11, !11, i64 0, i64 4}
+!11 = !{!8, i64 4, !"int"}
+!12 = distinct !{!12, !13, !14}
+!13 = !{!"llvm.loop.mustprogress"}
+!14 = !{!"llvm.loop.unroll.disable"}
diff --git a/llvm/test/Transforms/LoopVectorize/dynamic-bound-fcmp-exit.ll b/llvm/test/Transforms/LoopVectorize/dynamic-bound-fcmp-exit.ll
new file mode 100644
index 0000000000000..d10ea005bf350
--- /dev/null
+++ b/llvm/test/Transforms/LoopVectorize/dynamic-bound-fcmp-exit.ll
@@ -0,0 +1,41 @@
+; RUN: opt < %s -passes=loop-vectorize -enable-vectorize-loads-as-bound -force-vector-width=4 -force-vector-interleave=1 -S | FileCheck %s
+;
+; Negative test: the exit condition is an fcmp (not icmp). 
+; SCEV cannot compute the trip count of a float-bounded loop, so the
+; pass enters the dynamic-bound path.  It then rejects because the
+; branch condition is an FCmpInst, not an ICmpInst.
+;
+;   void foo(float *A, float *B, float *Limit) {
+;     for (float x = 0.0f; x < *Limit; x += 1.0f)
+;       A[(int)x] = B[(int)x];
+;   }
+
+define dso_local void @foo(ptr noundef %A, ptr noundef readonly %B, ptr noundef readonly %Limit) #0 {
+; CHECK-LABEL: @foo(
+; CHECK-NOT:   vector.body
+; CHECK-NOT:   .bound.pre
+entry:
+  br label %for.body
+
+for.body:
+  %iv = phi i64 [ 0, %entry ], [ %iv.next, %for.body ]
+  %fiv = phi float [ 0.0, %entry ], [ %fiv.next, %for.body ]
+  %gep.b = getelementptr inbounds i32, ptr %B, i64 %iv
+  %b = load i32, ptr %gep.b, align 4
+  %gep.a = getelementptr inbounds i32, ptr %A, i64 %iv
+  store i32 %b, ptr %gep.a, align 4
+  %iv.next = add nuw nsw i64 %iv, 1
+  %fiv.next = fadd float %fiv, 1.0
+  %lim = load float, ptr %Limit, align 4
+  %cmp = fcmp olt float %fiv.next, %lim
+  br i1 %cmp, label %for.body, label %for.exit, !llvm.loop !0
+
+for.exit:
+  ret void
+}
+
+attributes #0 = { mustprogress nofree norecurse nosync nounwind memory(argmem: readwrite) uwtable }
+
+!0 = distinct !{!0, !1, !2}
+!1 = !{!"llvm.loop.mustprogress"}
+!2 = !{!"llvm.loop.unroll.disable"}
diff --git a/llvm/test/Transforms/LoopVectorize/dynamic-bound-lambda.ll b/llvm/test/Transforms/LoopVectorize/dynamic-bound-lambda.ll
new file mode 100644
index 0000000000000..e43a7bcb15738
--- /dev/null
+++ b/llvm/test/Transforms/LoopVectorize/dynamic-bound-lambda.ll
@@ -0,0 +1,147 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes=loop-vectorize -enable-vectorize-loads-as-bound -force-vector-width=4 -force-vector-interleave=1 -S | FileCheck %s
+;
+; Lambda with loop bound loaded from pointer (*len).
+;
+;   void foo(int *A, int *B, int *C, int *D, int *len) {
+;     auto func = [&]() {
+;       for (int i = 0; i < *len; i++)
+;         A[i] = B[i] * C[i];
+;     };
+;     func();
+;   }
+;
+
+define dso_local void @foo(ptr noundef writeonly captures(none) %A, ptr noundef readonly captures(none) %B, ptr noundef readonly captures(none) %C, ptr noundef readnone captures(none) %D, ptr noundef readonly captures(none) %len) #0 {
+;
+; CHECK-LABEL: define dso_local void @foo(
+; CHECK-SAME: ptr noundef writeonly captures(none) [[A:%.*]], ptr noundef readonly captures(none) [[B:%.*]], ptr noundef readonly captures(none) [[C:%.*]], ptr noundef readnone captures(none) [[D:%.*]], ptr noundef readonly captures(none) [[LEN:%.*]]) #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[TMP0:%.*]] = load i32, ptr [[LEN]], align 4, !tbaa [[TBAA0:![0-9]+]]
+; CHECK-NEXT:    [[CMP10_I:%.*]] = icmp sgt i32 [[TMP0]], 0
+; CHECK-NEXT:    br i1 [[CMP10_I]], label %[[FOR_BODY_I_PREHEADER:.*]], label %[[EXIT:.*]]
+; CHECK:       [[FOR_BODY_I_PREHEADER]]:
+; CHECK-NEXT:    [[DOTBOUND_PRE:%.*]] = load i32, ptr [[LEN]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT:    [[DOTBOUND_PRE1:%.*]] = sext i32 [[DOTBOUND_PRE]] to i64
+; CHECK-NEXT:    [[SMAX12:%.*]] = call i64 @llvm.smax.i64(i64 [[DOTBOUND_PRE1]], i64 1)
+; CHECK-NEXT:    [[MIN_ITERS_CHECK:%.*]] = icmp ult i64 [[SMAX12]], 4
+; CHECK-NEXT:    br i1 [[MIN_ITERS_CHECK]], label %[[SCALAR_PH:.*]], label %[[VECTOR_MEMCHECK:.*]]
+; CHECK:       [[VECTOR_MEMCHECK]]:
+; CHECK-NEXT:    [[SMAX:%.*]] = call i64 @llvm.smax.i64(i64 [[DOTBOUND_PRE1]], i64 1)
+; CHECK-NEXT:    [[TMP1:%.*]] = shl nuw nsw i64 [[SMAX]], 2
+; CHECK-NEXT:    [[SCEVGEP:%.*]] = getelementptr i8, ptr [[A]], i64 [[TMP1]]
+; CHECK-NEXT:    [[SCEVGEP2:%.*]] = getelementptr i8, ptr [[B]], i64 [[TMP1]]
+; CHECK-NEXT:    [[SCEVGEP3:%.*]] = getelementptr i8, ptr [[C]], i64 [[TMP1]]
+; CHECK-NEXT:    [[SCEVGEP4:%.*]] = getelementptr i8, ptr [[LEN]], i64 4
+; CHECK-NEXT:    [[BOUND0:%.*]] = icmp ult ptr [[A]], [[SCEVGEP2]]
+; CHECK-NEXT:    [[BOUND1:%.*]] = icmp ult ptr [[B]], [[SCEVGEP]]
+; CHECK-NEXT:    [[FOUND_CONFLICT:%.*]] = and i1 [[BOUND0]], [[BOUND1]]
+; CHECK-NEXT:    [[BOUND05:%.*]] = icmp ult ptr [[A]], [[SCEVGEP3]]
+; CHECK-NEXT:    [[BOUND16:%.*]] = icmp ult ptr [[C]], [[SCEVGEP]]
+; CHECK-NEXT:    [[FOUND_CONFLICT7:%.*]] = and i1 [[BOUND05]], [[BOUND16]]
+; CHECK-NEXT:    [[CONFLICT_RDX:%.*]] = or i1 [[FOUND_CONFLICT]], [[FOUND_CONFLICT7]]
+; CHECK-NEXT:    [[BOUND08:%.*]] = icmp ult ptr [[A]], [[SCEVGEP4]]
+; CHECK-NEXT:    [[BOUND19:%.*]] = icmp ult ptr [[LEN]], [[SCEVGEP]]
+; CHECK-NEXT:    [[FOUND_CONFLICT10:%.*]] = and i1 [[BOUND08]], [[BOUND19]]
+; CHECK-NEXT:    [[CONFLICT_RDX11:%.*]] = or i1 [[CONFLICT_RDX]], [[FOUND_CONFLICT10]]
+; CHECK-NEXT:    br i1 [[CONFLICT_RDX11]], label %[[SCALAR_PH]], label %[[VECTOR_PH:.*]]
+; CHECK:       [[VECTOR_PH]]:
+; CHECK-NEXT:    [[N_MOD_VF:%.*]] = urem i64 [[SMAX12]], 4
+; CHECK-NEXT:    [[N_VEC:%.*]] = sub i64 [[SMAX12]], [[N_MOD_VF]]
+; CHECK-NEXT:    br label %[[VECTOR_BODY:.*]]
+; CHECK:       [[VECTOR_BODY]]:
+; CHECK-NEXT:    [[INDEX:%.*]] = phi i64 [ 0, %[[VECTOR_PH]] ], [ [[INDEX_NEXT:%.*]], %[[VECTOR_BODY]] ]
+; CHECK-NEXT:    [[TMP2:%.*]] = getelementptr inbounds nuw i32, ptr [[B]], i64 [[INDEX]]
+; CHECK-NEXT:    [[WIDE_LOAD:%.*]] = load <4 x i32>, ptr [[TMP2]], align 4, !tbaa [[TBAA0]], !alias.scope [[META4:![0-9]+]]
+; CHECK-NEXT:    [[TMP3:%.*]] = getelementptr inbounds nuw i32, ptr [[C]], i64 [[INDEX]]
+; CHECK-NEXT:    [[WIDE_LOAD13:%.*]] = load <4 x i32>, ptr [[TMP3]], align 4, !tbaa [[TBAA0]], !alias.scope [[META7:![0-9]+]]
+; CHECK-NEXT:    [[TMP4:%.*]] = mul nsw <4 x i32> [[WIDE_LOAD13]], [[WIDE_LOAD]]
+; CHECK-NEXT:    [[TMP5:%.*]] = getelementptr inbounds nuw i32, ptr [[A]], i64 [[INDEX]]
+; CHECK-NEXT:    store <4 x i32> [[TMP4]], ptr [[TMP5]], align 4, !tbaa [[TBAA0]], !alias.scope [[META9:![0-9]+]], !noalias [[META11:![0-9]+]]
+; CHECK-NEXT:    [[INDEX_NEXT]] = add nuw i64 [[INDEX]], 4
+; CHECK-NEXT:    [[TMP6:%.*]] = icmp eq i64 [[INDEX_NEXT]], [[N_VEC]]
+; CHECK-NEXT:    br i1 [[TMP6]], label %[[MIDDLE_BLOCK:.*]], label %[[VECTOR_BODY]], !llvm.loop [[LOOP13:![0-9]+]]
+; CHECK:       [[MIDDLE_BLOCK]]:
+; CHECK-NEXT:    [[CMP_N:%.*]] = icmp eq i64 [[SMAX12]], [[N_VEC]]
+; CHECK-NEXT:    br i1 [[CMP_N]], label %[[EXIT_LOOPEXIT:.*]], label %[[SCALAR_PH]]
+; CHECK:       [[SCALAR_PH]]:
+; CHECK-NEXT:    [[BC_RESUME_VAL:%.*]] = phi i64 [ [[N_VEC]], %[[MIDDLE_BLOCK]] ], [ 0, %[[FOR_BODY_I_PREHEADER]] ], [ 0, %[[VECTOR_MEMCHECK]] ]
+; CHECK-NEXT:    br label %[[FOR_BODY_I:.*]]
+; CHECK:       [[FOR_BODY_I]]:
+; CHECK-NEXT:    [[INDVARS_IV_I:%.*]] = phi i64 [ [[INDVARS_IV_NEXT_I:%.*]], %[[FOR_BODY_I]] ], [ [[BC_RESUME_VAL]], %[[SCALAR_PH]] ]
+; CHECK-NEXT:    [[ARRAYIDX_I:%.*]] = getelementptr inbounds nuw i32, ptr [[B]], i64 [[INDVARS_IV_I]]
+; CHECK-NEXT:    [[TMP7:%.*]] = load i32, ptr [[ARRAYIDX_I]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT:    [[ARRAYIDX3_I:%.*]] = getelementptr inbounds nuw i32, ptr [[C]], i64 [[INDVARS_IV_I]]
+; CHECK-NEXT:    [[TMP8:%.*]] = load i32, ptr [[ARRAYIDX3_I]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT:    [[MUL_I:%.*]] = mul nsw i32 [[TMP8]], [[TMP7]]
+; CHECK-NEXT:    [[ARRAYIDX5_I:%.*]] = getelementptr inbounds nuw i32, ptr [[A]], i64 [[INDVARS_IV_I]]
+; CHECK-NEXT:    store i32 [[MUL_I]], ptr [[ARRAYIDX5_I]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT:    [[INDVARS_IV_NEXT_I]] = add nuw nsw i64 [[INDVARS_IV_I]], 1
+; CHECK-NEXT:    [[TMP9:%.*]] = load i32, ptr [[LEN]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT:    [[TMP10:%.*]] = sext i32 [[TMP9]] to i64
+; CHECK-NEXT:    [[CMP_I:%.*]] = icmp slt i64 [[INDVARS_IV_NEXT_I]], [[TMP10]]
+; CHECK-NEXT:    br i1 [[CMP_I]], label %[[FOR_BODY_I]], label %[[EXIT_LOOPEXIT]], !llvm.loop [[LOOP18:![0-9]+]]
+; CHECK:       [[EXIT_LOOPEXIT]]:
+; CHECK-NEXT:    br label %[[EXIT]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret void
+;
+entry:
+  %0 = load i32, ptr %len, align 4, !tbaa !5
+  %cmp10.i = icmp sgt i32 %0, 0
+  br i1 %cmp10.i, label %for.body.i, label %exit
+
+for.body.i:
+  %indvars.iv.i = phi i64 [ %indvars.iv.next.i, %for.body.i ], [ 0, %entry ]
+  %arrayidx.i = getelementptr inbounds nuw i32, ptr %B, i64 %indvars.iv.i
+  %1 = load i32, ptr %arrayidx.i, align 4, !tbaa !5
+  %arrayidx3.i = getelementptr inbounds nuw i32, ptr %C, i64 %indvars.iv.i
+  %2 = load i32, ptr %arrayidx3.i, align 4, !tbaa !5
+  %mul.i = mul nsw i32 %2, %1
+  %arrayidx5.i = getelementptr inbounds nuw i32, ptr %A, i64 %indvars.iv.i
+  store i32 %mul.i, ptr %arrayidx5.i, align 4, !tbaa !5
+  %indvars.iv.next.i = add nuw nsw i64 %indvars.iv.i, 1
+  %3 = load i32, ptr %len, align 4, !tbaa !5
+  %4 = sext i32 %3 to i64
+  %cmp.i = icmp slt i64 %indvars.iv.next.i, %4
+  br i1 %cmp.i, label %for.body.i, label %exit, !llvm.loop !9
+
+exit:
+  ret void
+}
+
+attributes #0 = { mustprogress nofree norecurse nosync nounwind memory(argmem: readwrite) uwtable }
+
+!0 = !{i32 1, !"wchar_size", i32 4}
+!1 = !{i32 8, !"PIC Level", i32 2}
+!2 = !{i32 7, !"PIE Level", i32 2}
+!3 = !{i32 7, !"uwtable", i32 2}
+!4 = !{!"clang version 21.1.8"}
+!5 = !{!6, !6, i64 0, i64 4}
+!6 = !{!7, i64 4, !"int"}
+!7 = !{!8, i64 1, !"omnipotent char"}
+!8 = !{!"Simple C++ TBAA"}
+!9 = distinct !{!9, !10, !11}
+!10 = !{!"llvm.loop.mustprogress"}
+!11 = !{!"llvm.loop.unroll.disable"}
+;.
+; CHECK: [[TBAA0]] = !{[[META1:![0-9]+]], [[META1]], i64 0, i64 4}
+; CHECK: [[META1]] = !{[[META2:![0-9]+]], i64 4, !"int"}
+; CHECK: [[META2]] = !{[[META3:![0-9]+]], i64 1, !"omnipotent char"}
+; CHECK: [[META3]] = !{!"Simple C++ TBAA"}
+; CHECK: [[META4]] = !{[[META5:![0-9]+]]}
+; CHECK: [[META5]] = distinct !{[[META5]], [[META6:![0-9]+]]}
+; CHECK: [[META6]] = distinct !{[[META6]], !"LVerDomain"}
+; CHECK: [[META7]] = !{[[META8:![0-9]+]]}
+; CHECK: [[META8]] = distinct !{[[META8]], [[META6]]}
+; CHECK: [[META9]] = !{[[META10:![0-9]+]]}
+; CHECK: [[META10]] = distinct !{[[META10]], [[META6]]}
+; CHECK: [[META11]] = !{[[META5]], [[META8]], [[META12:![0-9]+]]}
+; CHECK: [[META12]] = distinct !{[[META12]], [[META6]]}
+; CHECK: [[LOOP13]] = distinct !{[[LOOP13]], [[META14:![0-9]+]], [[META15:![0-9]+]], [[META16:![0-9]+]], [[META17:![0-9]+]]}
+; CHECK: [[META14]] = !{!"llvm.loop.mustprogress"}
+; CHECK: [[META15]] = !{!"llvm.loop.unroll.disable"}
+; CHECK: [[META16]] = !{!"llvm.loop.isvectorized", i32 1}
+; CHECK: [[META17]] = !{!"llvm.loop.unroll.runtime.disable"}
+; CHECK: [[LOOP18]] = distinct !{[[LOOP18]], [[META14]], [[META15]], [[META16]]}
+;.
diff --git a/llvm/test/Transforms/LoopVectorize/dynamic-bound-loop-varying.ll b/llvm/test/Transforms/LoopVectorize/dynamic-bound-loop-varying.ll
new file mode 100644
index 0000000000000..5ad94b333f144
--- /dev/null
+++ b/llvm/test/Transforms/LoopVectorize/dynamic-bound-loop-varying.ll
@@ -0,0 +1,54 @@
+; RUN: opt < %s -passes=loop-vectorize -enable-vectorize-loads-as-bound -force-vector-width=4 -force-vector-interleave=1 -S | FileCheck %s
+;
+; Negative test: the bound is Len[i], which depends on the
+; induction variable hence not invariant.  The dependency chain traces back to a loop header
+; PHI, so the loop will be rejected.
+;
+;   void foo(int *A, int *B, int *C, int *Len) {
+;     for (int i = 0; i < Len[i]; i++)
+;       A[i] = B[i] + C[i];
+;   }
+
+define dso_local void @foo(ptr noundef writeonly captures(none) %A, ptr noundef readonly captures(none) %B, ptr noundef readonly captures(none) %C, ptr noundef readonly captures(none) %Len) #0 {
+; CHECK-LABEL: @foo(
+; CHECK-NOT:   vector.body
+; CHECK-NOT:   .bound.pre
+entry:
+  %0 = load i32, ptr %Len, align 4, !tbaa !5
+  %cmp12 = icmp sgt i32 %0, 0
+  br i1 %cmp12, label %for.body, label %for.cond.cleanup
+
+for.cond.cleanup:
+  ret void
+
+for.body:
+  %indvars.iv = phi i64 [ %indvars.iv.next, %for.body ], [ 0, %entry ]
+  %arrayidx2 = getelementptr inbounds nuw i32, ptr %B, i64 %indvars.iv
+  %1 = load i32, ptr %arrayidx2, align 4, !tbaa !5
+  %arrayidx4 = getelementptr inbounds nuw i32, ptr %C, i64 %indvars.iv
+  %2 = load i32, ptr %arrayidx4, align 4, !tbaa !5
+  %add = add nsw i32 %2, %1
+  %arrayidx6 = getelementptr inbounds nuw i32, ptr %A, i64 %indvars.iv
+  store i32 %add, ptr %arrayidx6, align 4, !tbaa !5
+  %indvars.iv.next = add nuw nsw i64 %indvars.iv, 1
+  %arrayidx = getelementptr inbounds nuw i32, ptr %Len, i64 %indvars.iv.next
+  %3 = load i32, ptr %arrayidx, align 4, !tbaa !5
+  %4 = sext i32 %3 to i64
+  %cmp = icmp slt i64 %indvars.iv.next, %4
+  br i1 %cmp, label %for.body, label %for.cond.cleanup, !llvm.loop !9
+}
+
+attributes #0 = { mustprogress nofree norecurse nosync nounwind memory(argmem: readwrite) uwtable }
+
+!0 = !{i32 1, !"wchar_size", i32 4}
+!1 = !{i32 8, !"PIC Level", i32 2}
+!2 = !{i32 7, !"PIE Level", i32 2}
+!3 = !{i32 7, !"uwtable", i32 2}
+!4 = !{!"clang version 21.1.8"}
+!5 = !{!6, !6, i64 0, i64 4}
+!6 = !{!7, i64 4, !"int"}
+!7 = !{!8, i64 1, !"omnipotent char"}
+!8 = !{!"Simple C++ TBAA"}
+!9 = distinct !{!9, !10, !11}
+!10 = !{!"llvm.loop.mustprogress"}
+!11 = !{!"llvm.loop.unroll.disable"}
diff --git a/llvm/test/Transforms/LoopVectorize/dynamic-bound-simple.ll b/llvm/test/Transforms/LoopVectorize/dynamic-bound-simple.ll
new file mode 100644
index 0000000000000..c6e6d344e2fec
--- /dev/null
+++ b/llvm/test/Transforms/LoopVectorize/dynamic-bound-simple.ll
@@ -0,0 +1,144 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes=loop-vectorize -enable-vectorize-loads-as-bound -force-vector-width=4 -force-vector-interleave=1 -S | FileCheck %s
+;
+; Simple loop with loop bound loaded from a pointer (*Len).
+;
+;   void foo(int *A, int *B, int *C, int *Len) {
+;     for (int i = 0; i < *Len; i++)
+;       A[i] = B[i] + C[i];
+;   }
+;
+
+define dso_local void @foo(ptr noundef writeonly captures(none) %A, ptr noundef readonly captures(none) %B, ptr noundef readonly captures(none) %C, ptr noundef readonly captures(none) %Len) #0 {
+;
+; CHECK-LABEL: define dso_local void @foo(
+; CHECK-SAME: ptr noundef writeonly captures(none) [[A:%.*]], ptr noundef readonly captures(none) [[B:%.*]], ptr noundef readonly captures(none) [[C:%.*]], ptr noundef readonly captures(none) [[LEN:%.*]]) #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[TMP0:%.*]] = load i32, ptr [[LEN]], align 4, !tbaa [[TBAA0:![0-9]+]]
+; CHECK-NEXT:    [[CMP9:%.*]] = icmp sgt i32 [[TMP0]], 0
+; CHECK-NEXT:    br i1 [[CMP9]], label %[[FOR_BODY_PREHEADER:.*]], label %[[FOR_COND_CLEANUP:.*]]
+; CHECK:       [[FOR_BODY_PREHEADER]]:
+; CHECK-NEXT:    [[DOTBOUND_PRE:%.*]] = load i32, ptr [[LEN]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT:    [[DOTBOUND_PRE1:%.*]] = sext i32 [[DOTBOUND_PRE]] to i64
+; CHECK-NEXT:    [[SMAX12:%.*]] = call i64 @llvm.smax.i64(i64 [[DOTBOUND_PRE1]], i64 1)
+; CHECK-NEXT:    [[MIN_ITERS_CHECK:%.*]] = icmp ult i64 [[SMAX12]], 4
+; CHECK-NEXT:    br i1 [[MIN_ITERS_CHECK]], label %[[SCALAR_PH:.*]], label %[[VECTOR_MEMCHECK:.*]]
+; CHECK:       [[VECTOR_MEMCHECK]]:
+; CHECK-NEXT:    [[SMAX:%.*]] = call i64 @llvm.smax.i64(i64 [[DOTBOUND_PRE1]], i64 1)
+; CHECK-NEXT:    [[TMP1:%.*]] = shl nuw nsw i64 [[SMAX]], 2
+; CHECK-NEXT:    [[SCEVGEP:%.*]] = getelementptr i8, ptr [[A]], i64 [[TMP1]]
+; CHECK-NEXT:    [[SCEVGEP2:%.*]] = getelementptr i8, ptr [[B]], i64 [[TMP1]]
+; CHECK-NEXT:    [[SCEVGEP3:%.*]] = getelementptr i8, ptr [[C]], i64 [[TMP1]]
+; CHECK-NEXT:    [[SCEVGEP4:%.*]] = getelementptr i8, ptr [[LEN]], i64 4
+; CHECK-NEXT:    [[BOUND0:%.*]] = icmp ult ptr [[A]], [[SCEVGEP2]]
+; CHECK-NEXT:    [[BOUND1:%.*]] = icmp ult ptr [[B]], [[SCEVGEP]]
+; CHECK-NEXT:    [[FOUND_CONFLICT:%.*]] = and i1 [[BOUND0]], [[BOUND1]]
+; CHECK-NEXT:    [[BOUND05:%.*]] = icmp ult ptr [[A]], [[SCEVGEP3]]
+; CHECK-NEXT:    [[BOUND16:%.*]] = icmp ult ptr [[C]], [[SCEVGEP]]
+; CHECK-NEXT:    [[FOUND_CONFLICT7:%.*]] = and i1 [[BOUND05]], [[BOUND16]]
+; CHECK-NEXT:    [[CONFLICT_RDX:%.*]] = or i1 [[FOUND_CONFLICT]], [[FOUND_CONFLICT7]]
+; CHECK-NEXT:    [[BOUND08:%.*]] = icmp ult ptr [[A]], [[SCEVGEP4]]
+; CHECK-NEXT:    [[BOUND19:%.*]] = icmp ult ptr [[LEN]], [[SCEVGEP]]
+; CHECK-NEXT:    [[FOUND_CONFLICT10:%.*]] = and i1 [[BOUND08]], [[BOUND19]]
+; CHECK-NEXT:    [[CONFLICT_RDX11:%.*]] = or i1 [[CONFLICT_RDX]], [[FOUND_CONFLICT10]]
+; CHECK-NEXT:    br i1 [[CONFLICT_RDX11]], label %[[SCALAR_PH]], label %[[VECTOR_PH:.*]]
+; CHECK:       [[VECTOR_PH]]:
+; CHECK-NEXT:    [[N_MOD_VF:%.*]] = urem i64 [[SMAX12]], 4
+; CHECK-NEXT:    [[N_VEC:%.*]] = sub i64 [[SMAX12]], [[N_MOD_VF]]
+; CHECK-NEXT:    br label %[[VECTOR_BODY:.*]]
+; CHECK:       [[VECTOR_BODY]]:
+; CHECK-NEXT:    [[INDEX:%.*]] = phi i64 [ 0, %[[VECTOR_PH]] ], [ [[INDEX_NEXT:%.*]], %[[VECTOR_BODY]] ]
+; CHECK-NEXT:    [[TMP2:%.*]] = getelementptr inbounds nuw i32, ptr [[B]], i64 [[INDEX]]
+; CHECK-NEXT:    [[WIDE_LOAD:%.*]] = load <4 x i32>, ptr [[TMP2]], align 4, !tbaa [[TBAA0]], !alias.scope [[META4:![0-9]+]]
+; CHECK-NEXT:    [[TMP3:%.*]] = getelementptr inbounds nuw i32, ptr [[C]], i64 [[INDEX]]
+; CHECK-NEXT:    [[WIDE_LOAD13:%.*]] = load <4 x i32>, ptr [[TMP3]], align 4, !tbaa [[TBAA0]], !alias.scope [[META7:![0-9]+]]
+; CHECK-NEXT:    [[TMP4:%.*]] = add nsw <4 x i32> [[WIDE_LOAD13]], [[WIDE_LOAD]]
+; CHECK-NEXT:    [[TMP5:%.*]] = getelementptr inbounds nuw i32, ptr [[A]], i64 [[INDEX]]
+; CHECK-NEXT:    store <4 x i32> [[TMP4]], ptr [[TMP5]], align 4, !tbaa [[TBAA0]], !alias.scope [[META9:![0-9]+]], !noalias [[META11:![0-9]+]]
+; CHECK-NEXT:    [[INDEX_NEXT]] = add nuw i64 [[INDEX]], 4
+; CHECK-NEXT:    [[TMP6:%.*]] = icmp eq i64 [[INDEX_NEXT]], [[N_VEC]]
+; CHECK-NEXT:    br i1 [[TMP6]], label %[[MIDDLE_BLOCK:.*]], label %[[VECTOR_BODY]], !llvm.loop [[LOOP13:![0-9]+]]
+; CHECK:       [[MIDDLE_BLOCK]]:
+; CHECK-NEXT:    [[CMP_N:%.*]] = icmp eq i64 [[SMAX12]], [[N_VEC]]
+; CHECK-NEXT:    br i1 [[CMP_N]], label %[[FOR_COND_CLEANUP_LOOPEXIT:.*]], label %[[SCALAR_PH]]
+; CHECK:       [[SCALAR_PH]]:
+; CHECK-NEXT:    [[BC_RESUME_VAL:%.*]] = phi i64 [ [[N_VEC]], %[[MIDDLE_BLOCK]] ], [ 0, %[[FOR_BODY_PREHEADER]] ], [ 0, %[[VECTOR_MEMCHECK]] ]
+; CHECK-NEXT:    br label %[[FOR_BODY:.*]]
+; CHECK:       [[FOR_COND_CLEANUP_LOOPEXIT]]:
+; CHECK-NEXT:    br label %[[FOR_COND_CLEANUP]]
+; CHECK:       [[FOR_COND_CLEANUP]]:
+; CHECK-NEXT:    ret void
+; CHECK:       [[FOR_BODY]]:
+; CHECK-NEXT:    [[INDVARS_IV:%.*]] = phi i64 [ [[INDVARS_IV_NEXT:%.*]], %[[FOR_BODY]] ], [ [[BC_RESUME_VAL]], %[[SCALAR_PH]] ]
+; CHECK-NEXT:    [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i32, ptr [[B]], i64 [[INDVARS_IV]]
+; CHECK-NEXT:    [[TMP7:%.*]] = load i32, ptr [[ARRAYIDX]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT:    [[ARRAYIDX2:%.*]] = getelementptr inbounds nuw i32, ptr [[C]], i64 [[INDVARS_IV]]
+; CHECK-NEXT:    [[TMP8:%.*]] = load i32, ptr [[ARRAYIDX2]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT:    [[ADD:%.*]] = add nsw i32 [[TMP8]], [[TMP7]]
+; CHECK-NEXT:    [[ARRAYIDX4:%.*]] = getelementptr inbounds nuw i32, ptr [[A]], i64 [[INDVARS_IV]]
+; CHECK-NEXT:    store i32 [[ADD]], ptr [[ARRAYIDX4]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT:    [[INDVARS_IV_NEXT]] = add nuw nsw i64 [[INDVARS_IV]], 1
+; CHECK-NEXT:    [[TMP9:%.*]] = load i32, ptr [[LEN]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT:    [[TMP10:%.*]] = sext i32 [[TMP9]] to i64
+; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i64 [[INDVARS_IV_NEXT]], [[TMP10]]
+; CHECK-NEXT:    br i1 [[CMP]], label %[[FOR_BODY]], label %[[FOR_COND_CLEANUP_LOOPEXIT]], !llvm.loop [[LOOP18:![0-9]+]]
+;
+entry:
+  %0 = load i32, ptr %Len, align 4, !tbaa !5
+  %cmp9 = icmp sgt i32 %0, 0
+  br i1 %cmp9, label %for.body, label %for.cond.cleanup
+
+for.cond.cleanup:
+  ret void
+
+for.body:
+  %indvars.iv = phi i64 [ %indvars.iv.next, %for.body ], [ 0, %entry ]
+  %arrayidx = getelementptr inbounds nuw i32, ptr %B, i64 %indvars.iv
+  %1 = load i32, ptr %arrayidx, align 4, !tbaa !5
+  %arrayidx2 = getelementptr inbounds nuw i32, ptr %C, i64 %indvars.iv
+  %2 = load i32, ptr %arrayidx2, align 4, !tbaa !5
+  %add = add nsw i32 %2, %1
+  %arrayidx4 = getelementptr inbounds nuw i32, ptr %A, i64 %indvars.iv
+  store i32 %add, ptr %arrayidx4, align 4, !tbaa !5
+  %indvars.iv.next = add nuw nsw i64 %indvars.iv, 1
+  %3 = load i32, ptr %Len, align 4, !tbaa !5
+  %4 = sext i32 %3 to i64
+  %cmp = icmp slt i64 %indvars.iv.next, %4
+  br i1 %cmp, label %for.body, label %for.cond.cleanup, !llvm.loop !9
+}
+
+attributes #0 = { mustprogress nofree norecurse nosync nounwind memory(argmem: readwrite) uwtable }
+
+!0 = !{i32 1, !"wchar_size", i32 4}
+!1 = !{i32 8, !"PIC Level", i32 2}
+!2 = !{i32 7, !"PIE Level", i32 2}
+!3 = !{i32 7, !"uwtable", i32 2}
+!4 = !{!"clang version 21.1.8"}
+!5 = !{!6, !6, i64 0, i64 4}
+!6 = !{!7, i64 4, !"int"}
+!7 = !{!8, i64 1, !"omnipotent char"}
+!8 = !{!"Simple C++ TBAA"}
+!9 = distinct !{!9, !10, !11}
+!10 = !{!"llvm.loop.mustprogress"}
+!11 = !{!"llvm.loop.unroll.disable"}
+;.
+; CHECK: [[TBAA0]] = !{[[META1:![0-9]+]], [[META1]], i64 0, i64 4}
+; CHECK: [[META1]] = !{[[META2:![0-9]+]], i64 4, !"int"}
+; CHECK: [[META2]] = !{[[META3:![0-9]+]], i64 1, !"omnipotent char"}
+; CHECK: [[META3]] = !{!"Simple C++ TBAA"}
+; CHECK: [[META4]] = !{[[META5:![0-9]+]]}
+; CHECK: [[META5]] = distinct !{[[META5]], [[META6:![0-9]+]]}
+; CHECK: [[META6]] = distinct !{[[META6]], !"LVerDomain"}
+; CHECK: [[META7]] = !{[[META8:![0-9]+]]}
+; CHECK: [[META8]] = distinct !{[[META8]], [[META6]]}
+; CHECK: [[META9]] = !{[[META10:![0-9]+]]}
+; CHECK: [[META10]] = distinct !{[[META10]], [[META6]]}
+; CHECK: [[META11]] = !{[[META5]], [[META8]], [[META12:![0-9]+]]}
+; CHECK: [[META12]] = distinct !{[[META12]], [[META6]]}
+; CHECK: [[LOOP13]] = distinct !{[[LOOP13]], [[META14:![0-9]+]], [[META15:![0-9]+]], [[META16:![0-9]+]], [[META17:![0-9]+]]}
+; CHECK: [[META14]] = !{!"llvm.loop.mustprogress"}
+; CHECK: [[META15]] = !{!"llvm.loop.unroll.disable"}
+; CHECK: [[META16]] = !{!"llvm.loop.isvectorized", i32 1}
+; CHECK: [[META17]] = !{!"llvm.loop.unroll.runtime.disable"}
+; CHECK: [[LOOP18]] = distinct !{[[LOOP18]], [[META14]], [[META15]], [[META16]]}
+;.
diff --git a/llvm/test/Transforms/LoopVectorize/dynamic-bound-stored-pointer.ll b/llvm/test/Transforms/LoopVectorize/dynamic-bound-stored-pointer.ll
new file mode 100644
index 0000000000000..ecb087261e3af
--- /dev/null
+++ b/llvm/test/Transforms/LoopVectorize/dynamic-bound-stored-pointer.ll
@@ -0,0 +1,47 @@
+; RUN: opt < %s -passes=loop-vectorize -enable-vectorize-loads-as-bound -force-vector-width=4 -force-vector-interleave=1 -S | FileCheck %s
+;
+; The bound pointer %Len is also written to inside the loop body with a computed value, 
+; so the bound changes each iteration and cannot be hoisted.  The ModifiedPtrs check detects that the same
+; pointer is used as both a store target and the bound load's address.
+;
+;   void foo(int *A, int *B, int *C, int *Len) {
+;     for (int i = 0; i < *Len; i++) {
+;       A[i] = B[i] + C[i];
+;       *Len = B[i] + C[i];   // overwrite bound with computed value
+;     }
+;   }
+
+define dso_local void @foo(ptr noundef %A, ptr noundef readonly %B, ptr noundef readonly %C, ptr noundef %Len) #0 {
+; CHECK-LABEL: @foo(
+; CHECK-NOT:   vector.body
+; CHECK-NOT:   .bound.pre
+entry:
+  %len0 = load i32, ptr %Len, align 4
+  %cmp0 = icmp sgt i32 %len0, 0
+  br i1 %cmp0, label %for.body, label %for.cond.cleanup
+
+for.cond.cleanup:
+  ret void
+
+for.body:
+  %indvars.iv = phi i64 [ %indvars.iv.next, %for.body ], [ 0, %entry ]
+  %arrayidx = getelementptr inbounds nuw i32, ptr %B, i64 %indvars.iv
+  %0 = load i32, ptr %arrayidx, align 4
+  %arrayidx2 = getelementptr inbounds nuw i32, ptr %C, i64 %indvars.iv
+  %1 = load i32, ptr %arrayidx2, align 4
+  %add = add nsw i32 %1, %0
+  %arrayidx4 = getelementptr inbounds nuw i32, ptr %A, i64 %indvars.iv
+  store i32 %add, ptr %arrayidx4, align 4
+  store i32 %add, ptr %Len, align 4
+  %indvars.iv.next = add nuw nsw i64 %indvars.iv, 1
+  %len = load i32, ptr %Len, align 4
+  %ext = sext i32 %len to i64
+  %cmp = icmp slt i64 %indvars.iv.next, %ext
+  br i1 %cmp, label %for.body, label %for.cond.cleanup, !llvm.loop !0
+}
+
+attributes #0 = { mustprogress nofree norecurse nosync nounwind memory(argmem: readwrite) uwtable }
+
+!0 = distinct !{!0, !1, !2}
+!1 = !{!"llvm.loop.mustprogress"}
+!2 = !{!"llvm.loop.unroll.disable"}
diff --git a/llvm/test/Transforms/LoopVectorize/dynamic-bound-two-loads.ll b/llvm/test/Transforms/LoopVectorize/dynamic-bound-two-loads.ll
new file mode 100644
index 0000000000000..240f4f0143e95
--- /dev/null
+++ b/llvm/test/Transforms/LoopVectorize/dynamic-bound-two-loads.ll
@@ -0,0 +1,166 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes=loop-vectorize -enable-vectorize-loads-as-bound -force-vector-width=4 -force-vector-interleave=1 -S | FileCheck %s
+;
+; Loop bound is *Len1 + *Len2.  Two loads and an add are hoisted to the preheader for the dynamic bound.
+;
+;   void foo(int *A, int *B, int *C, int *Len1, int *Len2) {
+;     for (int i = 0; i < *Len1 + *Len2; i++)
+;       A[i] = B[i] + C[i];
+;   }
+;
+
+define dso_local void @foo(ptr noundef writeonly captures(none) %A, ptr noundef readonly captures(none) %B, ptr noundef readonly captures(none) %C, ptr noundef readonly captures(none) %Len1, ptr noundef readonly captures(none) %Len2) #0 {
+;
+; CHECK-LABEL: define dso_local void @foo(
+; CHECK-SAME: ptr noundef writeonly captures(none) [[A:%.*]], ptr noundef readonly captures(none) [[B:%.*]], ptr noundef readonly captures(none) [[C:%.*]], ptr noundef readonly captures(none) [[LEN1:%.*]], ptr noundef readonly captures(none) [[LEN2:%.*]]) #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[TMP0:%.*]] = load i32, ptr [[LEN1]], align 4, !tbaa [[TBAA0:![0-9]+]]
+; CHECK-NEXT:    [[TMP1:%.*]] = load i32, ptr [[LEN2]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT:    [[ADD10:%.*]] = add nsw i32 [[TMP1]], [[TMP0]]
+; CHECK-NEXT:    [[CMP11:%.*]] = icmp sgt i32 [[ADD10]], 0
+; CHECK-NEXT:    br i1 [[CMP11]], label %[[FOR_BODY_PREHEADER:.*]], label %[[FOR_COND_CLEANUP:.*]]
+; CHECK:       [[FOR_BODY_PREHEADER]]:
+; CHECK-NEXT:    [[DOTBOUND_PRE:%.*]] = load i32, ptr [[LEN1]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT:    [[DOTBOUND_PRE1:%.*]] = load i32, ptr [[LEN2]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT:    [[ADD_BOUND_PRE:%.*]] = add nsw i32 [[DOTBOUND_PRE1]], [[DOTBOUND_PRE]]
+; CHECK-NEXT:    [[DOTBOUND_PRE2:%.*]] = sext i32 [[ADD_BOUND_PRE]] to i64
+; CHECK-NEXT:    [[TMP2:%.*]] = sext i32 [[DOTBOUND_PRE1]] to i64
+; CHECK-NEXT:    [[TMP3:%.*]] = sext i32 [[DOTBOUND_PRE]] to i64
+; CHECK-NEXT:    [[TMP4:%.*]] = add i64 [[TMP2]], [[TMP3]]
+; CHECK-NEXT:    [[SMAX18:%.*]] = call i64 @llvm.smax.i64(i64 [[TMP4]], i64 1)
+; CHECK-NEXT:    [[MIN_ITERS_CHECK:%.*]] = icmp ult i64 [[SMAX18]], 4
+; CHECK-NEXT:    br i1 [[MIN_ITERS_CHECK]], label %[[SCALAR_PH:.*]], label %[[VECTOR_MEMCHECK:.*]]
+; CHECK:       [[VECTOR_MEMCHECK]]:
+; CHECK-NEXT:    [[TMP5:%.*]] = sext i32 [[DOTBOUND_PRE1]] to i64
+; CHECK-NEXT:    [[TMP6:%.*]] = sext i32 [[DOTBOUND_PRE]] to i64
+; CHECK-NEXT:    [[TMP7:%.*]] = add i64 [[TMP5]], [[TMP6]]
+; CHECK-NEXT:    [[SMAX:%.*]] = call i64 @llvm.smax.i64(i64 [[TMP7]], i64 1)
+; CHECK-NEXT:    [[TMP8:%.*]] = shl nuw nsw i64 [[SMAX]], 2
+; CHECK-NEXT:    [[SCEVGEP:%.*]] = getelementptr i8, ptr [[A]], i64 [[TMP8]]
+; CHECK-NEXT:    [[SCEVGEP3:%.*]] = getelementptr i8, ptr [[B]], i64 [[TMP8]]
+; CHECK-NEXT:    [[SCEVGEP4:%.*]] = getelementptr i8, ptr [[C]], i64 [[TMP8]]
+; CHECK-NEXT:    [[SCEVGEP5:%.*]] = getelementptr i8, ptr [[LEN1]], i64 4
+; CHECK-NEXT:    [[SCEVGEP6:%.*]] = getelementptr i8, ptr [[LEN2]], i64 4
+; CHECK-NEXT:    [[BOUND0:%.*]] = icmp ult ptr [[A]], [[SCEVGEP3]]
+; CHECK-NEXT:    [[BOUND1:%.*]] = icmp ult ptr [[B]], [[SCEVGEP]]
+; CHECK-NEXT:    [[FOUND_CONFLICT:%.*]] = and i1 [[BOUND0]], [[BOUND1]]
+; CHECK-NEXT:    [[BOUND07:%.*]] = icmp ult ptr [[A]], [[SCEVGEP4]]
+; CHECK-NEXT:    [[BOUND18:%.*]] = icmp ult ptr [[C]], [[SCEVGEP]]
+; CHECK-NEXT:    [[FOUND_CONFLICT9:%.*]] = and i1 [[BOUND07]], [[BOUND18]]
+; CHECK-NEXT:    [[CONFLICT_RDX:%.*]] = or i1 [[FOUND_CONFLICT]], [[FOUND_CONFLICT9]]
+; CHECK-NEXT:    [[BOUND010:%.*]] = icmp ult ptr [[A]], [[SCEVGEP5]]
+; CHECK-NEXT:    [[BOUND111:%.*]] = icmp ult ptr [[LEN1]], [[SCEVGEP]]
+; CHECK-NEXT:    [[FOUND_CONFLICT12:%.*]] = and i1 [[BOUND010]], [[BOUND111]]
+; CHECK-NEXT:    [[CONFLICT_RDX13:%.*]] = or i1 [[CONFLICT_RDX]], [[FOUND_CONFLICT12]]
+; CHECK-NEXT:    [[BOUND014:%.*]] = icmp ult ptr [[A]], [[SCEVGEP6]]
+; CHECK-NEXT:    [[BOUND115:%.*]] = icmp ult ptr [[LEN2]], [[SCEVGEP]]
+; CHECK-NEXT:    [[FOUND_CONFLICT16:%.*]] = and i1 [[BOUND014]], [[BOUND115]]
+; CHECK-NEXT:    [[CONFLICT_RDX17:%.*]] = or i1 [[CONFLICT_RDX13]], [[FOUND_CONFLICT16]]
+; CHECK-NEXT:    br i1 [[CONFLICT_RDX17]], label %[[SCALAR_PH]], label %[[VECTOR_PH:.*]]
+; CHECK:       [[VECTOR_PH]]:
+; CHECK-NEXT:    [[N_MOD_VF:%.*]] = urem i64 [[SMAX18]], 4
+; CHECK-NEXT:    [[N_VEC:%.*]] = sub i64 [[SMAX18]], [[N_MOD_VF]]
+; CHECK-NEXT:    br label %[[VECTOR_BODY:.*]]
+; CHECK:       [[VECTOR_BODY]]:
+; CHECK-NEXT:    [[INDEX:%.*]] = phi i64 [ 0, %[[VECTOR_PH]] ], [ [[INDEX_NEXT:%.*]], %[[VECTOR_BODY]] ]
+; CHECK-NEXT:    [[TMP9:%.*]] = getelementptr inbounds nuw i32, ptr [[B]], i64 [[INDEX]]
+; CHECK-NEXT:    [[WIDE_LOAD:%.*]] = load <4 x i32>, ptr [[TMP9]], align 4, !tbaa [[TBAA0]], !alias.scope [[META4:![0-9]+]]
+; CHECK-NEXT:    [[TMP10:%.*]] = getelementptr inbounds nuw i32, ptr [[C]], i64 [[INDEX]]
+; CHECK-NEXT:    [[WIDE_LOAD19:%.*]] = load <4 x i32>, ptr [[TMP10]], align 4, !tbaa [[TBAA0]], !alias.scope [[META7:![0-9]+]]
+; CHECK-NEXT:    [[TMP11:%.*]] = add nsw <4 x i32> [[WIDE_LOAD19]], [[WIDE_LOAD]]
+; CHECK-NEXT:    [[TMP12:%.*]] = getelementptr inbounds nuw i32, ptr [[A]], i64 [[INDEX]]
+; CHECK-NEXT:    store <4 x i32> [[TMP11]], ptr [[TMP12]], align 4, !tbaa [[TBAA0]], !alias.scope [[META9:![0-9]+]], !noalias [[META11:![0-9]+]]
+; CHECK-NEXT:    [[INDEX_NEXT]] = add nuw i64 [[INDEX]], 4
+; CHECK-NEXT:    [[TMP13:%.*]] = icmp eq i64 [[INDEX_NEXT]], [[N_VEC]]
+; CHECK-NEXT:    br i1 [[TMP13]], label %[[MIDDLE_BLOCK:.*]], label %[[VECTOR_BODY]], !llvm.loop [[LOOP14:![0-9]+]]
+; CHECK:       [[MIDDLE_BLOCK]]:
+; CHECK-NEXT:    [[CMP_N:%.*]] = icmp eq i64 [[SMAX18]], [[N_VEC]]
+; CHECK-NEXT:    br i1 [[CMP_N]], label %[[FOR_COND_CLEANUP_LOOPEXIT:.*]], label %[[SCALAR_PH]]
+; CHECK:       [[SCALAR_PH]]:
+; CHECK-NEXT:    [[BC_RESUME_VAL:%.*]] = phi i64 [ [[N_VEC]], %[[MIDDLE_BLOCK]] ], [ 0, %[[FOR_BODY_PREHEADER]] ], [ 0, %[[VECTOR_MEMCHECK]] ]
+; CHECK-NEXT:    br label %[[FOR_BODY:.*]]
+; CHECK:       [[FOR_COND_CLEANUP_LOOPEXIT]]:
+; CHECK-NEXT:    br label %[[FOR_COND_CLEANUP]]
+; CHECK:       [[FOR_COND_CLEANUP]]:
+; CHECK-NEXT:    ret void
+; CHECK:       [[FOR_BODY]]:
+; CHECK-NEXT:    [[INDVARS_IV:%.*]] = phi i64 [ [[INDVARS_IV_NEXT:%.*]], %[[FOR_BODY]] ], [ [[BC_RESUME_VAL]], %[[SCALAR_PH]] ]
+; CHECK-NEXT:    [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i32, ptr [[B]], i64 [[INDVARS_IV]]
+; CHECK-NEXT:    [[TMP14:%.*]] = load i32, ptr [[ARRAYIDX]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT:    [[ARRAYIDX2:%.*]] = getelementptr inbounds nuw i32, ptr [[C]], i64 [[INDVARS_IV]]
+; CHECK-NEXT:    [[TMP15:%.*]] = load i32, ptr [[ARRAYIDX2]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT:    [[ADD3:%.*]] = add nsw i32 [[TMP15]], [[TMP14]]
+; CHECK-NEXT:    [[ARRAYIDX5:%.*]] = getelementptr inbounds nuw i32, ptr [[A]], i64 [[INDVARS_IV]]
+; CHECK-NEXT:    store i32 [[ADD3]], ptr [[ARRAYIDX5]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT:    [[INDVARS_IV_NEXT]] = add nuw nsw i64 [[INDVARS_IV]], 1
+; CHECK-NEXT:    [[TMP16:%.*]] = load i32, ptr [[LEN1]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT:    [[TMP17:%.*]] = load i32, ptr [[LEN2]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT:    [[ADD:%.*]] = add nsw i32 [[TMP17]], [[TMP16]]
+; CHECK-NEXT:    [[TMP18:%.*]] = sext i32 [[ADD]] to i64
+; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i64 [[INDVARS_IV_NEXT]], [[TMP18]]
+; CHECK-NEXT:    br i1 [[CMP]], label %[[FOR_BODY]], label %[[FOR_COND_CLEANUP_LOOPEXIT]], !llvm.loop [[LOOP19:![0-9]+]]
+;
+entry:
+  %0 = load i32, ptr %Len1, align 4, !tbaa !5
+  %1 = load i32, ptr %Len2, align 4, !tbaa !5
+  %add10 = add nsw i32 %1, %0
+  %cmp11 = icmp sgt i32 %add10, 0
+  br i1 %cmp11, label %for.body, label %for.cond.cleanup
+
+for.cond.cleanup:
+  ret void
+
+for.body:
+  %indvars.iv = phi i64 [ %indvars.iv.next, %for.body ], [ 0, %entry ]
+  %arrayidx = getelementptr inbounds nuw i32, ptr %B, i64 %indvars.iv
+  %2 = load i32, ptr %arrayidx, align 4, !tbaa !5
+  %arrayidx2 = getelementptr inbounds nuw i32, ptr %C, i64 %indvars.iv
+  %3 = load i32, ptr %arrayidx2, align 4, !tbaa !5
+  %add3 = add nsw i32 %3, %2
+  %arrayidx5 = getelementptr inbounds nuw i32, ptr %A, i64 %indvars.iv
+  store i32 %add3, ptr %arrayidx5, align 4, !tbaa !5
+  %indvars.iv.next = add nuw nsw i64 %indvars.iv, 1
+  %4 = load i32, ptr %Len1, align 4, !tbaa !5
+  %5 = load i32, ptr %Len2, align 4, !tbaa !5
+  %add = add nsw i32 %5, %4
+  %6 = sext i32 %add to i64
+  %cmp = icmp slt i64 %indvars.iv.next, %6
+  br i1 %cmp, label %for.body, label %for.cond.cleanup, !llvm.loop !9
+}
+
+attributes #0 = { mustprogress nofree norecurse nosync nounwind memory(argmem: readwrite) uwtable }
+
+!0 = !{i32 1, !"wchar_size", i32 4}
+!1 = !{i32 8, !"PIC Level", i32 2}
+!2 = !{i32 7, !"PIE Level", i32 2}
+!3 = !{i32 7, !"uwtable", i32 2}
+!4 = !{!"clang version 21.1.8"}
+!5 = !{!6, !6, i64 0, i64 4}
+!6 = !{!7, i64 4, !"int"}
+!7 = !{!8, i64 1, !"omnipotent char"}
+!8 = !{!"Simple C++ TBAA"}
+!9 = distinct !{!9, !10, !11}
+!10 = !{!"llvm.loop.mustprogress"}
+!11 = !{!"llvm.loop.unroll.disable"}
+;.
+; CHECK: [[TBAA0]] = !{[[META1:![0-9]+]], [[META1]], i64 0, i64 4}
+; CHECK: [[META1]] = !{[[META2:![0-9]+]], i64 4, !"int"}
+; CHECK: [[META2]] = !{[[META3:![0-9]+]], i64 1, !"omnipotent char"}
+; CHECK: [[META3]] = !{!"Simple C++ TBAA"}
+; CHECK: [[META4]] = !{[[META5:![0-9]+]]}
+; CHECK: [[META5]] = distinct !{[[META5]], [[META6:![0-9]+]]}
+; CHECK: [[META6]] = distinct !{[[META6]], !"LVerDomain"}
+; CHECK: [[META7]] = !{[[META8:![0-9]+]]}
+; CHECK: [[META8]] = distinct !{[[META8]], [[META6]]}
+; CHECK: [[META9]] = !{[[META10:![0-9]+]]}
+; CHECK: [[META10]] = distinct !{[[META10]], [[META6]]}
+; CHECK: [[META11]] = !{[[META5]], [[META8]], [[META12:![0-9]+]], [[META13:![0-9]+]]}
+; CHECK: [[META12]] = distinct !{[[META12]], [[META6]]}
+; CHECK: [[META13]] = distinct !{[[META13]], [[META6]]}
+; CHECK: [[LOOP14]] = distinct !{[[LOOP14]], [[META15:![0-9]+]], [[META16:![0-9]+]], [[META17:![0-9]+]], [[META18:![0-9]+]]}
+; CHECK: [[META15]] = !{!"llvm.loop.mustprogress"}
+; CHECK: [[META16]] = !{!"llvm.loop.unroll.disable"}
+; CHECK: [[META17]] = !{!"llvm.loop.isvectorized", i32 1}
+; CHECK: [[META18]] = !{!"llvm.loop.unroll.runtime.disable"}
+; CHECK: [[LOOP19]] = distinct !{[[LOOP19]], [[META15]], [[META16]], [[META17]]}
+;.
diff --git a/llvm/test/Transforms/LoopVectorize/dynamic-bound-volatile-load.ll b/llvm/test/Transforms/LoopVectorize/dynamic-bound-volatile-load.ll
new file mode 100644
index 0000000000000..1c0495faa4ceb
--- /dev/null
+++ b/llvm/test/Transforms/LoopVectorize/dynamic-bound-volatile-load.ll
@@ -0,0 +1,43 @@
+; RUN: opt < %s -passes=loop-vectorize -enable-vectorize-loads-as-bound -force-vector-width=4 -force-vector-interleave=1 -S | FileCheck %s
+;
+; Negative test: the bound is loaded via a volatile (non-simple) load. The detector
+; rejects it (isSafeToHoistBoundLoad requires a simple load), so the loop will be rejected.
+;
+;   void foo(int *A, int *B, int *C, volatile int *Len) {
+;     for (int i = 0; i < *Len; i++)
+;       A[i] = B[i] + C[i];
+;   }
+
+define dso_local void @foo(ptr noundef writeonly %A, ptr noundef readonly %B, ptr noundef readonly %C, ptr noundef %Len) #0 {
+; CHECK-LABEL: @foo(
+; CHECK-NOT:   vector.body
+; CHECK-NOT:   .bound.pre
+entry:
+  %len0 = load volatile i32, ptr %Len, align 4
+  %cmp0 = icmp sgt i32 %len0, 0
+  br i1 %cmp0, label %for.body, label %for.cond.cleanup
+
+for.cond.cleanup:
+  ret void
+
+for.body:
+  %indvars.iv = phi i64 [ %indvars.iv.next, %for.body ], [ 0, %entry ]
+  %arrayidx = getelementptr inbounds nuw i32, ptr %B, i64 %indvars.iv
+  %0 = load i32, ptr %arrayidx, align 4
+  %arrayidx2 = getelementptr inbounds nuw i32, ptr %C, i64 %indvars.iv
+  %1 = load i32, ptr %arrayidx2, align 4
+  %add = add nsw i32 %1, %0
+  %arrayidx4 = getelementptr inbounds nuw i32, ptr %A, i64 %indvars.iv
+  store i32 %add, ptr %arrayidx4, align 4
+  %indvars.iv.next = add nuw nsw i64 %indvars.iv, 1
+  %len = load volatile i32, ptr %Len, align 4
+  %ext = sext i32 %len to i64
+  %cmp = icmp slt i64 %indvars.iv.next, %ext
+  br i1 %cmp, label %for.body, label %for.cond.cleanup, !llvm.loop !0
+}
+
+attributes #0 = { mustprogress nofree norecurse nosync nounwind memory(argmem: readwrite) uwtable }
+
+!0 = distinct !{!0, !1, !2}
+!1 = !{!"llvm.loop.mustprogress"}
+!2 = !{!"llvm.loop.unroll.disable"}

>From c6eedcf8f84e44fccdb6f2192016fc63a1e69495 Mon Sep 17 00:00:00 2001
From: Manish Srivastava <manish.srivastava at amd.com>
Date: Fri, 17 Jul 2026 11:30:51 +0530
Subject: [PATCH 2/2] Fix for the prechecks failures

---
 .../llvm/Analysis/LoopAccessAnalysis.h        | 36 ++++++-----
 llvm/include/llvm/Analysis/ScalarEvolution.h  | 37 ++++++-----
 llvm/lib/Analysis/LoopAccessAnalysis.cpp      | 47 ++++++++------
 llvm/lib/Analysis/ScalarEvolution.cpp         | 64 +++++++++++--------
 .../Vectorize/LoopVectorizationLegality.cpp   |  3 +-
 .../Transforms/Vectorize/LoopVectorize.cpp    |  8 +--
 6 files changed, 111 insertions(+), 84 deletions(-)

diff --git a/llvm/include/llvm/Analysis/LoopAccessAnalysis.h b/llvm/include/llvm/Analysis/LoopAccessAnalysis.h
index d84fd0e03a5e9..fd5bf6eea9cd1 100644
--- a/llvm/include/llvm/Analysis/LoopAccessAnalysis.h
+++ b/llvm/include/llvm/Analysis/LoopAccessAnalysis.h
@@ -697,7 +697,8 @@ class LoopAccessInfo {
                           const TargetLibraryInfo *TLI, AAResults *AA,
                           DominatorTree *DT, LoopInfo *LI, AssumptionCache *AC,
                           bool AllowPartial = false,
-                          ArrayRef<const SCEVTripCountInvariantPredicate *> TripCountInvariantPreds = {});
+                          ArrayRef<const SCEVTripCountInvariantPredicate *>
+                              TripCountInvariantPreds = {});
 
   /// Return true we can analyze the memory accesses in the loop and there are
   /// no memory dependence cycles. Note that for dependences between loads &
@@ -717,7 +718,8 @@ class LoopAccessInfo {
   /// could be analyzed.
   bool hasAllowPartial() const { return AllowPartial; }
 
-  ArrayRef<const SCEVTripCountInvariantPredicate *> getTripCountInvariantPreds() const {
+  ArrayRef<const SCEVTripCountInvariantPredicate *>
+  getTripCountInvariantPreds() const {
     return TripCountInvariantPreds;
   }
 
@@ -845,9 +847,10 @@ class LoopAccessInfo {
   /// memory accesses could be analyzed.
   bool AllowPartial;
 
-  /// Predicates for the loops inside the loop which are used to compute the tripcount and
-  /// which this analysis assumes to be invariant.
-  SmallVector<const SCEVTripCountInvariantPredicate *, 2> TripCountInvariantPreds;
+  /// Predicates for the loops inside the loop which are used to compute the
+  /// tripcount and which this analysis assumes to be invariant.
+  SmallVector<const SCEVTripCountInvariantPredicate *, 2>
+      TripCountInvariantPreds;
 
   unsigned NumLoads = 0;
   unsigned NumStores = 0;
@@ -990,14 +993,15 @@ LLVM_ABI std::pair<const SCEV *, const SCEV *> getStartAndEndForAccess(
     DominatorTree *DT, AssumptionCache *AC,
     std::optional<ScalarEvolution::LoopGuards> &LoopGuards);
 
-/// This function will detect whether the loop is uncountabel because the tripcount
-/// is based on a load.
-/// If yes, then it will build a dependency chain of instructions, and a list of loads
-/// which are used to compute the tripcount.
-LLVM_ABI bool collectInvariantLoadsBoundChain(
-    Loop *L, ScalarEvolution *SE, DominatorTree *DT, AssumptionCache *AC,
-    SmallVectorImpl<Instruction *> &HoistedDeps,
-    SmallVectorImpl<LoadInst *> &BoundLoads);
+/// This function will detect whether the loop is uncountabel because the
+/// tripcount is based on a load. If yes, then it will build a dependency chain
+/// of instructions, and a list of loads which are used to compute the
+/// tripcount.
+LLVM_ABI bool
+collectInvariantLoadsBoundChain(Loop *L, ScalarEvolution *SE, DominatorTree *DT,
+                                AssumptionCache *AC,
+                                SmallVectorImpl<Instruction *> &HoistedDeps,
+                                SmallVectorImpl<LoadInst *> &BoundLoads);
 
 class LoopAccessInfoManager {
   /// The cache.
@@ -1020,8 +1024,10 @@ class LoopAccessInfoManager {
 
   LLVM_ABI const LoopAccessInfo &getInfo(Loop &L, bool AllowPartial = false);
 
-  LLVM_ABI const LoopAccessInfo &getInfo(Loop &L, bool AllowPartial,
-        ArrayRef<const SCEVTripCountInvariantPredicate *> TripCountInvariantPreds);
+  LLVM_ABI const LoopAccessInfo &
+  getInfo(Loop &L, bool AllowPartial,
+          ArrayRef<const SCEVTripCountInvariantPredicate *>
+              TripCountInvariantPreds);
 
   LLVM_ABI void clear();
 
diff --git a/llvm/include/llvm/Analysis/ScalarEvolution.h b/llvm/include/llvm/Analysis/ScalarEvolution.h
index f39553d3013d2..15ea107d04625 100644
--- a/llvm/include/llvm/Analysis/ScalarEvolution.h
+++ b/llvm/include/llvm/Analysis/ScalarEvolution.h
@@ -464,18 +464,19 @@ class LLVM_ABI SCEVComparePredicate final : public SCEVPredicate {
   }
 };
 
-/// This class extends the SCEVPredicate class to represent the assumption that the trip count of a loop is invariant.
-/// This predicate doesn't lower any numeric run-time check but solely depends on the memory alias checks generated
-/// for the loop by LoopVectorizer.
-/// NOTE: Users of this predicate make sure to verify that the memory alias checks are generated for the loop.
+/// This class extends the SCEVPredicate class to represent the assumption that
+/// the trip count of a loop is invariant. This predicate doesn't lower any
+/// numeric run-time check but solely depends on the memory alias checks
+/// generated for the loop by LoopVectorizer. NOTE: Users of this predicate make
+/// sure to verify that the memory alias checks are generated for the loop.
 class LLVM_ABI SCEVTripCountInvariantPredicate final : public SCEVPredicate {
   const SCEV *TripCountLoad;
   const SCEV *TripCountInvariantLoad;
 
 public:
   SCEVTripCountInvariantPredicate(const FoldingSetNodeIDRef ID,
-                                 const SCEV *TripCountLoad,
-                                 const SCEV *TripCountInvariantLoad);
+                                  const SCEV *TripCountLoad,
+                                  const SCEV *TripCountInvariantLoad);
 
   /// Implementation of the SCEVPredicate interface
   bool implies(const SCEVPredicate *N, ScalarEvolution &SE) const override;
@@ -483,12 +484,14 @@ class LLVM_ABI SCEVTripCountInvariantPredicate final : public SCEVPredicate {
   bool isAlwaysTrue() const override;
 
   /// This predicate doesn't generate any runtime checks, and solely depends
-  /// upon the memory alias checks for the runtime check generation, so contributes
-  /// nothing to the check complexity
+  /// upon the memory alias checks for the runtime check generation, so
+  /// contributes nothing to the check complexity
   unsigned getComplexity() const override { return 0; }
 
   const SCEV *getTripCountLoad() const { return TripCountLoad; }
-  const SCEV *getTripCountInvariantLoad() const { return TripCountInvariantLoad; }
+  const SCEV *getTripCountInvariantLoad() const {
+    return TripCountInvariantLoad;
+  }
 
   static bool classof(const SCEVPredicate *P) {
     return P->getKind() == P_TripCountInvariant;
@@ -1153,7 +1156,8 @@ class ScalarEvolution {
   LLVM_ABI const SCEV *getPredicatedSymbolicMaxBackedgeTakenCount(
       const Loop *L, SmallVectorImpl<const SCEVPredicate *> &Predicates);
 
-  /// To compute the backedge-taken count under the assumption of TripCountInvariant predicate.
+  /// To compute the backedge-taken count under the assumption of
+  /// TripCountInvariant predicate.
   LLVM_ABI const SCEV *computeBackedgeTakenCountWithTripCountInvariants(
       const Loop *L, ArrayRef<const SCEVTripCountInvariantPredicate *> Preds,
       bool SymbolicMax = false);
@@ -1558,8 +1562,8 @@ class ScalarEvolution {
   getWrapPredicate(const SCEVAddRecExpr *AR,
                    SCEVWrapPredicate::IncrementWrapFlags AddedFlags);
 
-  LLVM_ABI const SCEVPredicate *getTripCountInvariantPredicate(const SCEV *Load,
-                                                               const SCEV *InvariantLoad);
+  LLVM_ABI const SCEVPredicate *
+  getTripCountInvariantPredicate(const SCEV *Load, const SCEV *InvariantLoad);
 
   /// Re-writes the SCEV according to the Predicates in \p A.
   LLVM_ABI const SCEV *rewriteUsingPredicate(const SCEV *S, const Loop *L,
@@ -1929,7 +1933,8 @@ class ScalarEvolution {
   /// function as they are computed.
   DenseMap<const Loop *, BackedgeTakenInfo> PredicatedBackedgeTakenCounts;
 
-  DenseMap<const Value *, const SCEV *> *ActiveTripCountInvariantSubst = nullptr;
+  DenseMap<const Value *, const SCEV *> *ActiveTripCountInvariantSubst =
+      nullptr;
 
   /// Loops whose backedge taken counts directly use this non-constant SCEV.
   DenseMap<const SCEV *, SmallPtrSet<PointerIntPair<const Loop *, 1, bool>, 4>>
@@ -2703,7 +2708,8 @@ class PredicatedScalarEvolution {
     return !TripCountInvariantPreds.empty();
   }
 
-  LLVM_ABI ArrayRef<const SCEVTripCountInvariantPredicate *> getTripCountInvariantPredicates() const {
+  LLVM_ABI ArrayRef<const SCEVTripCountInvariantPredicate *>
+  getTripCountInvariantPredicates() const {
     return TripCountInvariantPreds;
   }
 
@@ -2778,7 +2784,8 @@ class PredicatedScalarEvolution {
   /// The symbolic backedge taken count.
   const SCEV *SymbolicMaxBackedgeCount = nullptr;
 
-  SmallVector<const SCEVTripCountInvariantPredicate *, 2> TripCountInvariantPreds;
+  SmallVector<const SCEVTripCountInvariantPredicate *, 2>
+      TripCountInvariantPreds;
 
   DenseMap<const Value *, const SCEV *> TripCountInvariantMap;
 
diff --git a/llvm/lib/Analysis/LoopAccessAnalysis.cpp b/llvm/lib/Analysis/LoopAccessAnalysis.cpp
index aecb316fd162a..fe2ea5201b2d9 100644
--- a/llvm/lib/Analysis/LoopAccessAnalysis.cpp
+++ b/llvm/lib/Analysis/LoopAccessAnalysis.cpp
@@ -25,9 +25,9 @@
 #include "llvm/Analysis/AliasSetTracker.h"
 #include "llvm/Analysis/AssumeBundleQueries.h"
 #include "llvm/Analysis/AssumptionCache.h"
+#include "llvm/Analysis/Loads.h"
 #include "llvm/Analysis/LoopAnalysisManager.h"
 #include "llvm/Analysis/LoopInfo.h"
-#include "llvm/Analysis/Loads.h"
 #include "llvm/Analysis/LoopIterator.h"
 #include "llvm/Analysis/MemoryLocation.h"
 #include "llvm/Analysis/OptimizationRemarkEmitter.h"
@@ -3204,15 +3204,17 @@ static bool isSafeToHoistBoundLoad(Instruction *I,
     if (ModifiedPtrs.count(Ptr)) {
       return false;
     }
-    if (!isBoundLoadSafeToLoadAtCtx(Ptr, LI->getType(), LI->getAlign(), CtxI, DT,
-                                   AC)) {
+    if (!isBoundLoadSafeToLoadAtCtx(Ptr, LI->getType(), LI->getAlign(), CtxI,
+                                    DT, AC)) {
       return false;
     }
     return true;
   }
 
-  // Limiting down the kind of instructions we can hoist to avoid any unforeseen side effects.
-  if (I->isBinaryOp() || I->isUnaryOp() || I->isCast() || isa<GetElementPtrInst>(I)) {
+  // Limiting down the kind of instructions we can hoist to avoid any unforeseen
+  // side effects.
+  if (I->isBinaryOp() || I->isUnaryOp() || I->isCast() ||
+      isa<GetElementPtrInst>(I)) {
     return isSafeToSpeculativelyExecute(I, CtxI, AC, DT);
   }
   return false;
@@ -3255,7 +3257,8 @@ LLVM_ABI bool llvm::collectInvariantLoadsBoundChain(
 
   /// Anything written inside the loop is a "not-invariant" pointer; the bound
   /// load cannot share a pointer with such writes.
-  /// This is to prune out cases like for (i = 0; i < *Len; ++i) { Len[i] = ..*.. }
+  /// This is to prune out cases like for (i = 0; i < *Len; ++i) { Len[i] =
+  /// ..*.. }
   SmallPtrSet<Value *, 16> ModifiedPtrs;
   for (BasicBlock *BB : L->getBlocks()) {
     for (Instruction &I : *BB) {
@@ -3324,18 +3327,18 @@ LLVM_ABI bool llvm::collectInvariantLoadsBoundChain(
   return !BoundLoads.empty() && !HoistedDeps.empty();
 }
 
-LoopAccessInfo::LoopAccessInfo(Loop *L, ScalarEvolution *SE,
-                               const TargetTransformInfo *TTI,
-                               const TargetLibraryInfo *TLI, AAResults *AA,
-                               DominatorTree *DT, LoopInfo *LI,
-                               AssumptionCache *AC, bool AllowPartial,
-                               ArrayRef<const SCEVTripCountInvariantPredicate *> TripCountInvariantPreds)
+LoopAccessInfo::LoopAccessInfo(
+    Loop *L, ScalarEvolution *SE, const TargetTransformInfo *TTI,
+    const TargetLibraryInfo *TLI, AAResults *AA, DominatorTree *DT,
+    LoopInfo *LI, AssumptionCache *AC, bool AllowPartial,
+    ArrayRef<const SCEVTripCountInvariantPredicate *> TripCountInvariantPreds)
     : PSE(std::make_unique<PredicatedScalarEvolution>(*SE, *L)),
       PtrRtChecking(nullptr), TheLoop(L), AllowPartial(AllowPartial),
       TripCountInvariantPreds(TripCountInvariantPreds) {
 
   for (const SCEVTripCountInvariantPredicate *P : TripCountInvariantPreds) {
-    PSE->addTripCountInvariantPredicate(P->getTripCountLoad(), P->getTripCountInvariantLoad());
+    PSE->addTripCountInvariantPredicate(P->getTripCountLoad(),
+                                        P->getTripCountInvariantLoad());
   }
 
   unsigned MaxTargetVectorWidthInBits = std::numeric_limits<unsigned>::max();
@@ -3413,24 +3416,28 @@ const LoopAccessInfo &LoopAccessInfoManager::getInfo(Loop &L,
   return getInfo(L, AllowPartial, {});
 }
 
-const LoopAccessInfo &LoopAccessInfoManager::getInfo(Loop &L,
-                                                     bool AllowPartial,
-                                                     ArrayRef<const SCEVTripCountInvariantPredicate *> TripCountInvariantPreds) {
-  const auto &[It, Inserted] = LoopAccessInfoMap.try_emplace(&L);
+const LoopAccessInfo &LoopAccessInfoManager::getInfo(
+    Loop &L, bool AllowPartial,
+    ArrayRef<const SCEVTripCountInvariantPredicate *> TripCountInvariantPreds) {
+  auto Insertion = LoopAccessInfoMap.try_emplace(&L);
+  auto It = Insertion.first;
+  bool Inserted = Insertion.second;
 
   auto SamePreds = [&]() {
     ArrayRef<const SCEVTripCountInvariantPredicate *> Cached =
         It->second->getTripCountInvariantPreds();
     if (Cached.size() != TripCountInvariantPreds.size())
       return false;
-    return std::equal(Cached.begin(), Cached.end(), TripCountInvariantPreds.begin());
+    return std::equal(Cached.begin(), Cached.end(),
+                      TripCountInvariantPreds.begin());
   };
 
   // We need to create the LoopAccessInfo if either we don't already have one,
   // or if it was created with a different value of AllowPartial.
   if (Inserted || It->second->hasAllowPartial() != AllowPartial || !SamePreds())
-    It->second = std::make_unique<LoopAccessInfo>(&L, &SE, TTI, TLI, &AA, &DT,
-                                                  &LI, AC, AllowPartial, TripCountInvariantPreds);
+    It->second =
+        std::make_unique<LoopAccessInfo>(&L, &SE, TTI, TLI, &AA, &DT, &LI, AC,
+                                         AllowPartial, TripCountInvariantPreds);
 
   return *It->second;
 }
diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index a204c46947001..02926865366cf 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -8695,7 +8695,8 @@ const SCEV *ScalarEvolution::computeBackedgeTakenCountWithTripCountInvariants(
 
   SaveAndRestore<DenseMap<const Value *, const SCEV *> *> Active(
       ActiveTripCountInvariantSubst, &Subst);
-  BackedgeTakenInfo BTI = computeBackedgeTakenCount(L, /*AllowPredicates=*/false);
+  BackedgeTakenInfo BTI =
+      computeBackedgeTakenCount(L, /*AllowPredicates=*/false);
   return SymbolicMax ? BTI.getSymbolicMax(L, this) : BTI.getExact(L, this);
 }
 
@@ -9487,10 +9488,12 @@ ScalarEvolution::ExitLimit ScalarEvolution::computeExitLimitFromICmp(
   const SCEV *LHS = getSCEV(ExitCond->getOperand(0));
   const SCEV *RHS = getSCEV(ExitCond->getOperand(1));
 
-  // If the TripCountInvariant predicate is active, then replace the LHS and RHS to their proper
-  // substition values as per the substitution map.
-  if (ActiveTripCountInvariantSubst && !ActiveTripCountInvariantSubst->empty()) {
-    auto &Subst = const_cast<ValueToSCEVMapTy &>(*ActiveTripCountInvariantSubst);
+  // If the TripCountInvariant predicate is active, then replace the LHS and RHS
+  // to their proper substition values as per the substitution map.
+  if (ActiveTripCountInvariantSubst &&
+      !ActiveTripCountInvariantSubst->empty()) {
+    auto &Subst =
+        const_cast<ValueToSCEVMapTy &>(*ActiveTripCountInvariantSubst);
 
     LHS = SCEVParameterRewriter::rewrite(LHS, *this, Subst);
     RHS = SCEVParameterRewriter::rewrite(RHS, *this, Subst);
@@ -15295,8 +15298,9 @@ const SCEVPredicate *ScalarEvolution::getWrapPredicate(
   return OF;
 }
 
-const SCEVPredicate *ScalarEvolution::getTripCountInvariantPredicate(const SCEV *Load,
-                                                                     const SCEV *InvariantLoad) {
+const SCEVPredicate *
+ScalarEvolution::getTripCountInvariantPredicate(const SCEV *Load,
+                                                const SCEV *InvariantLoad) {
   FoldingSetNodeID ID;
   ID.AddInteger(SCEVPredicate::P_TripCountInvariant);
   ID.AddPointer(Load);
@@ -15304,8 +15308,8 @@ const SCEVPredicate *ScalarEvolution::getTripCountInvariantPredicate(const SCEV
   void *IP = nullptr;
   if (const auto *S = UniquePreds.FindNodeOrInsertPos(ID, IP))
     return S;
-  auto *TCI = new (SCEVAllocator)
-      SCEVTripCountInvariantPredicate(ID.Intern(SCEVAllocator), Load, InvariantLoad);
+  auto *TCI = new (SCEVAllocator) SCEVTripCountInvariantPredicate(
+      ID.Intern(SCEVAllocator), Load, InvariantLoad);
   UniquePreds.InsertNode(TCI, IP);
   return TCI;
 }
@@ -15517,27 +15521,30 @@ void SCEVComparePredicate::print(raw_ostream &OS, unsigned Depth) const {
 
 }
 
-SCEVTripCountInvariantPredicate::SCEVTripCountInvariantPredicate(const FoldingSetNodeIDRef ID,
-                                                                   const SCEV *TripCountLoad,
-                                                                   const SCEV *TripCountInvariantLoad)
-  : SCEVPredicate(ID, P_TripCountInvariant), TripCountLoad(TripCountLoad), TripCountInvariantLoad(TripCountInvariantLoad) {}
+SCEVTripCountInvariantPredicate::SCEVTripCountInvariantPredicate(
+    const FoldingSetNodeIDRef ID, const SCEV *TripCountLoad,
+    const SCEV *TripCountInvariantLoad)
+    : SCEVPredicate(ID, P_TripCountInvariant), TripCountLoad(TripCountLoad),
+      TripCountInvariantLoad(TripCountInvariantLoad) {}
 
 bool SCEVTripCountInvariantPredicate::implies(const SCEVPredicate *N,
                                               ScalarEvolution &SE) const {
   const auto *Op = dyn_cast<SCEVTripCountInvariantPredicate>(N);
   if (!Op)
     return false;
-  return Op->TripCountLoad == TripCountLoad && Op->TripCountInvariantLoad == TripCountInvariantLoad;
+  return Op->TripCountLoad == TripCountLoad &&
+         Op->TripCountInvariantLoad == TripCountInvariantLoad;
 }
 
 bool SCEVTripCountInvariantPredicate::isAlwaysTrue() const {
   return TripCountLoad == TripCountInvariantLoad;
 }
 
-void SCEVTripCountInvariantPredicate::print(raw_ostream &OS, unsigned Depth) const {
-  OS.indent(Depth) << "Trip count invariant predicate: " << *TripCountLoad 
-                              << " is invariant (== " << *TripCountInvariantLoad << ")\n"
-                              << "\n";
+void SCEVTripCountInvariantPredicate::print(raw_ostream &OS,
+                                            unsigned Depth) const {
+  OS.indent(Depth) << "Trip count invariant predicate: " << *TripCountLoad
+                   << " is invariant (== " << *TripCountInvariantLoad << ")\n"
+                   << "\n";
 }
 
 SCEVWrapPredicate::SCEVWrapPredicate(const FoldingSetNodeIDRef ID,
@@ -15756,7 +15763,8 @@ const SCEV *PredicatedScalarEvolution::getPredicatedSCEV(const SCEV *Expr) {
   const SCEV *NewSCEV = SE.rewriteUsingPredicate(Expr, &L, *Preds);
 
   if (!TripCountInvariantMap.empty()) {
-    NewSCEV = SCEVParameterRewriter::rewrite(NewSCEV, SE, TripCountInvariantMap);
+    NewSCEV =
+        SCEVParameterRewriter::rewrite(NewSCEV, SE, TripCountInvariantMap);
   }
 
   Entry = {Generation, NewSCEV};
@@ -15767,10 +15775,9 @@ const SCEV *PredicatedScalarEvolution::getPredicatedSCEV(const SCEV *Expr) {
 const SCEV *PredicatedScalarEvolution::getBackedgeTakenCount() {
   if (!BackedgeCount) {
     if (!TripCountInvariantPreds.empty()) {
-      BackedgeCount = SE.computeBackedgeTakenCountWithTripCountInvariants(&L, 
-        TripCountInvariantPreds, /*SymbolicMax=*/false);
-    }
-    else {
+      BackedgeCount = SE.computeBackedgeTakenCountWithTripCountInvariants(
+          &L, TripCountInvariantPreds, /*SymbolicMax=*/false);
+    } else {
       SmallVector<const SCEVPredicate *, 4> Preds;
       BackedgeCount = SE.getPredicatedBackedgeTakenCount(&L, Preds);
       for (const auto *P : Preds)
@@ -15783,10 +15790,10 @@ const SCEV *PredicatedScalarEvolution::getBackedgeTakenCount() {
 const SCEV *PredicatedScalarEvolution::getSymbolicMaxBackedgeTakenCount() {
   if (!SymbolicMaxBackedgeCount) {
     if (!TripCountInvariantPreds.empty()) {
-      SymbolicMaxBackedgeCount = SE.computeBackedgeTakenCountWithTripCountInvariants(&L, 
-        TripCountInvariantPreds, /*SymbolicMax=*/true);
-    }
-    else {
+      SymbolicMaxBackedgeCount =
+          SE.computeBackedgeTakenCountWithTripCountInvariants(
+              &L, TripCountInvariantPreds, /*SymbolicMax=*/true);
+    } else {
       SmallVector<const SCEVPredicate *, 4> Preds;
       SymbolicMaxBackedgeCount =
           SE.getPredicatedSymbolicMaxBackedgeTakenCount(&L, Preds);
@@ -15817,7 +15824,8 @@ void PredicatedScalarEvolution::addPredicate(const SCEVPredicate &Pred) {
   updateGeneration();
 }
 
-void PredicatedScalarEvolution::addTripCountInvariantPredicate(const SCEV *Load, const SCEV *InvariantLoad) {
+void PredicatedScalarEvolution::addTripCountInvariantPredicate(
+    const SCEV *Load, const SCEV *InvariantLoad) {
   const auto *V = dyn_cast<SCEVUnknown>(Load);
   assert(V && "mem-invariant Load must be a SCEVUnknown");
 
diff --git a/llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp b/llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp
index 94e101297f68a..ddae25fc17386 100644
--- a/llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp
+++ b/llvm/lib/Transforms/Vectorize/LoopVectorizationLegality.cpp
@@ -1158,8 +1158,7 @@ bool LoopVectorizationLegality::canVectorizeIndirectUnsafeDependences() {
 }
 
 bool LoopVectorizationLegality::canVectorizeMemory() {
-  LAI = &LAIs.getInfo(*TheLoop, false,
-      PSE.getTripCountInvariantPredicates());
+  LAI = &LAIs.getInfo(*TheLoop, false, PSE.getTripCountInvariantPredicates());
   const OptimizationRemarkAnalysis *LAR = LAI->getReport();
   if (LAR) {
     ORE->emit([&]() {
diff --git a/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp b/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
index 7dbd53a83904f..95a0cd21a7291 100644
--- a/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
+++ b/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
@@ -1643,8 +1643,7 @@ class GeneratedRTChecks {
           SCEVPredsVec.push_back(P);
         }
       }
-    }
-    else if (!isa<SCEVTripCountInvariantPredicate>(&UnionPred)) {
+    } else if (!isa<SCEVTripCountInvariantPredicate>(&UnionPred)) {
       SCEVPredsVec.push_back(&UnionPred);
     }
     SCEVUnionPredicate FilteredPred(SCEVPredsVec, *PSE.getSE());
@@ -7882,7 +7881,7 @@ bool EnableLoadBoundVectorization(Loop *L, PredicatedScalarEvolution &PSE,
                                   ScalarEvolution *SE, DominatorTree *DT,
                                   AssumptionCache *AC) {
   if (!L->isInnermost() || !L->isLoopSimplifyForm() ||
-    L->getNumBackEdges() != 1 || !L->getUniqueExitBlock()) {
+      L->getNumBackEdges() != 1 || !L->getUniqueExitBlock()) {
     return false;
   }
 
@@ -7892,7 +7891,8 @@ bool EnableLoadBoundVectorization(Loop *L, PredicatedScalarEvolution &PSE,
 
   SmallVector<Instruction *, 16> HoistedDeps;
   SmallVector<LoadInst *, 4> BoundLoads;
-  if (!collectInvariantLoadsBoundChain(L, SE, DT, AC, HoistedDeps, BoundLoads)) {
+  if (!collectInvariantLoadsBoundChain(L, SE, DT, AC, HoistedDeps,
+                                       BoundLoads)) {
     return false;
   }
 



More information about the llvm-commits mailing list