[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