[llvm] Extending LoopVersioningLICM to handle cases where loopbound is invarint (PR #192794)
Manish Srivastava via llvm-commits
llvm-commits at lists.llvm.org
Sun Apr 19 23:55:00 PDT 2026
https://github.com/mk-srivastava updated https://github.com/llvm/llvm-project/pull/192794
>From 98cfc12f1e9a97a1084f5465abe63e6262869878 Mon Sep 17 00:00:00 2001
From: Manish Srivastava <manish.srivastava at amd.com>
Date: Fri, 17 Apr 2026 12:37:43 +0530
Subject: [PATCH] Extending LoopVersioningLICM to handle cases where loopbound
is invariant
---
.../llvm/Transforms/Utils/LoopVersioning.h | 7 +-
.../Transforms/Scalar/LoopVersioningLICM.cpp | 299 +++++++++++++++++-
llvm/lib/Transforms/Utils/LoopVersioning.cpp | 44 ++-
.../dynamic-bound-array-element.ll | 139 ++++++++
.../dynamic-bound-chained-loads.ll | 86 +++++
.../dynamic-bound-fcmp-exit.ll | 57 ++++
.../dynamic-bound-lambda.ll | 142 +++++++++
.../dynamic-bound-loop-varying.ll | 79 +++++
.../dynamic-bound-simple.ll | 139 ++++++++
.../dynamic-bound-stored-pointer.ll | 72 +++++
.../dynamic-bound-two-loads.ll | 158 +++++++++
.../dynamic-bound-volatile-load.ll | 67 ++++
12 files changed, 1262 insertions(+), 27 deletions(-)
create mode 100644 llvm/test/Transforms/LoopVersioningLICM/dynamic-bound-array-element.ll
create mode 100644 llvm/test/Transforms/LoopVersioningLICM/dynamic-bound-chained-loads.ll
create mode 100644 llvm/test/Transforms/LoopVersioningLICM/dynamic-bound-fcmp-exit.ll
create mode 100644 llvm/test/Transforms/LoopVersioningLICM/dynamic-bound-lambda.ll
create mode 100644 llvm/test/Transforms/LoopVersioningLICM/dynamic-bound-loop-varying.ll
create mode 100644 llvm/test/Transforms/LoopVersioningLICM/dynamic-bound-simple.ll
create mode 100644 llvm/test/Transforms/LoopVersioningLICM/dynamic-bound-stored-pointer.ll
create mode 100644 llvm/test/Transforms/LoopVersioningLICM/dynamic-bound-two-loads.ll
create mode 100644 llvm/test/Transforms/LoopVersioningLICM/dynamic-bound-volatile-load.ll
diff --git a/llvm/include/llvm/Transforms/Utils/LoopVersioning.h b/llvm/include/llvm/Transforms/Utils/LoopVersioning.h
index ea4fe27c90f5c..ec07fb9259b6a 100644
--- a/llvm/include/llvm/Transforms/Utils/LoopVersioning.h
+++ b/llvm/include/llvm/Transforms/Utils/LoopVersioning.h
@@ -61,11 +61,14 @@ class LoopVersioning {
/// analyze L
/// if versioning is necessary version L
/// transform L
- void versionLoop() { versionLoop(findDefsUsedOutsideOfLoop(VersionedLoop)); }
+ void versionLoop(Instruction *UserRuntimeCheck = nullptr) { versionLoop(findDefsUsedOutsideOfLoop(VersionedLoop), UserRuntimeCheck); }
/// Same but if the client has already precomputed the set of values
/// used outside the loop, this API will allows passing that.
- void versionLoop(const SmallVectorImpl<Instruction *> &DefsUsedOutside);
+ /// \p UserMemRuntimeCheck allows the caller to supply a pre-built runtime
+ /// check instruction instead of having LoopVersioning generate one.
+ void versionLoop(const SmallVectorImpl<Instruction *> &DefsUsedOutside,
+ Instruction *UserMemRuntimeCheck = nullptr);
/// Returns the versioned loop. Control flows here if pointers in the
/// loop don't alias (i.e. all memchecks passed). (This loop is actually the
diff --git a/llvm/lib/Transforms/Scalar/LoopVersioningLICM.cpp b/llvm/lib/Transforms/Scalar/LoopVersioningLICM.cpp
index 3aed643ee8065..caf16643f85c7 100644
--- a/llvm/lib/Transforms/Scalar/LoopVersioningLICM.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopVersioningLICM.cpp
@@ -70,19 +70,23 @@
#include "llvm/Analysis/LoopPass.h"
#include "llvm/Analysis/OptimizationRemarkEmitter.h"
#include "llvm/Analysis/ScalarEvolution.h"
+#include "llvm/Analysis/ScalarEvolutionExpressions.h"
#include "llvm/IR/Dominators.h"
#include "llvm/IR/Instruction.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/MDBuilder.h"
#include "llvm/IR/Metadata.h"
+#include "llvm/IR/Module.h"
#include "llvm/IR/Value.h"
+#include "llvm/IR/Verifier.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Transforms/Utils/LoopUtils.h"
#include "llvm/Transforms/Utils/LoopVersioning.h"
+#include "llvm/Transforms/Utils/ScalarEvolutionExpander.h"
#include <cassert>
using namespace llvm;
@@ -116,8 +120,8 @@ struct LoopVersioningLICM {
LoopVersioningLICM(AliasAnalysis *AA, ScalarEvolution *SE,
OptimizationRemarkEmitter *ORE,
LoopAccessInfoManager &LAIs, LoopInfo &LI,
- Loop *CurLoop)
- : AA(AA), SE(SE), LAIs(LAIs), LI(LI), CurLoop(CurLoop),
+ AssumptionCache *AC, Loop *CurLoop)
+ : AA(AA), SE(SE), LAIs(LAIs), LI(LI), AC(AC), CurLoop(CurLoop),
LoopDepthThreshold(LVLoopDepthThreshold),
InvariantThreshold(LVInvarThreshold), ORE(ORE) {}
@@ -138,9 +142,17 @@ struct LoopVersioningLICM {
LoopInfo &LI;
+ AssumptionCache *AC;
+
// The current loop we are working on.
Loop *CurLoop;
+ // Dynamic bound marker.
+ bool IsDynamicBound = false;
+
+ // Hoistable dependencies
+ SmallVector<Instruction *, 16> HoistedDependencies;
+
// Maximum loop nest threshold
unsigned LoopDepthThreshold;
@@ -156,6 +168,9 @@ struct LoopVersioningLICM {
// Read only loop marker.
bool IsReadOnlyLoop = true;
+ // Placeholder instruction for inserting runtime checks in the preheader.
+ Instruction *PlaceholderForRuntimeChecks = nullptr;
+
// OptimizationRemarkEmitter
OptimizationRemarkEmitter *ORE;
@@ -165,6 +180,10 @@ struct LoopVersioningLICM {
bool legalLoopMemoryAccesses();
bool isLoopAlreadyVisited();
bool instructionSafeForVersioning(Instruction *I);
+ bool isSafeToHoistBoundDep(Instruction *I,
+ const SmallPtrSetImpl<Value *> &ModifiedPtrs);
+ Instruction *insertRuntimeCheckPlaceholder(Loop *L);
+ void generateAndAddRuntimeChecks(DominatorTree *DT);
};
} // end anonymous namespace
@@ -213,12 +232,165 @@ bool LoopVersioningLICM::legalLoopStructure() {
// to generate the bound checks.
const SCEV *ExitCount = SE->getBackedgeTakenCount(CurLoop);
if (isa<SCEVCouldNotCompute>(ExitCount)) {
+ IsDynamicBound = true;
LLVM_DEBUG(dbgs() << " loop does not have trip count\n");
- return false;
+ LLVM_DEBUG(dbgs() << " checking the possibility of dynamic trip count\n");
+
+ HoistedDependencies.clear();
+
+ // Check if the loop trip count is dynamic
+ BasicBlock *ExitBlock = CurLoop->getExitingBlock();
+ assert(ExitBlock && "Exiting block guaranteed by earlier getExitingBlock() check");
+ CondBrInst *ExitBranch = dyn_cast<CondBrInst>(ExitBlock->getTerminator());
+ if (!ExitBranch) {
+ LLVM_DEBUG(dbgs() << " loop exit condition is not a conditional branch\n");
+ return false;
+ }
+ ICmpInst *ExitCmp = dyn_cast<ICmpInst>(ExitBranch->getCondition());
+ if (!ExitCmp) {
+ LLVM_DEBUG(dbgs() << " loop exit condition is not an icmp instruction\n");
+ return false;
+ }
+
+ PHINode *IndVar = CurLoop->getInductionVariable(*SE);
+ if (!IndVar) {
+ LLVM_DEBUG(dbgs() << " unable to find loop induction variable\n");
+ return false;
+ }
+ LLVM_DEBUG(dbgs() << " Induction variable: " << *IndVar << "\n");
+
+ Value *StepInst = IndVar->getIncomingValueForBlock(CurLoop->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 {
+ LLVM_DEBUG(dbgs() << " unable to identify dynamic bound operand\n");
+ return false;
+ }
+ LLVM_DEBUG(dbgs() << " Dynamic upper bound: " << *DynamicUpperBound
+ << "\n");
+
+ SmallVector<Instruction *, 16> Worklist;
+
+ SmallPtrSet<Value *, 16> ModifiedPtrs;
+ for (BasicBlock *BB : CurLoop->getBlocks()) {
+ for (Instruction &I : *BB) {
+ if (StoreInst *SI = dyn_cast<StoreInst>(&I))
+ ModifiedPtrs.insert(SI->getPointerOperand());
+ }
+ }
+
+ if (Instruction *I = dyn_cast<Instruction>(DynamicUpperBound)) {
+ Worklist.push_back(I);
+ }
+
+ SmallPtrSet<Instruction *, 16> Visited;
+
+ while(!Worklist.empty()) {
+ Instruction *I = Worklist.pop_back_val();
+ if (!Visited.insert(I).second)
+ continue;
+
+ if (!CurLoop->contains(I->getParent()))
+ continue;
+
+ if (!isSafeToHoistBoundDep(I, ModifiedPtrs))
+ return false;
+
+ for (Use &U : I->operands()) {
+ if (Instruction *OpI = dyn_cast<Instruction>(U.get())) {
+ if (CurLoop->contains(OpI)) {
+ Worklist.push_back(OpI);
+ }
+ }
+ }
+
+ HoistedDependencies.push_back(I);
+ }
+ return true;
}
return true;
}
+/// Check if an instruction in the dynamic bound dependency chain is safe to
+/// hoist. Only side-effect-free arithmetic, bitwise, cast, comparison, GEP,
+/// and simple load instructions are allowed.
+bool LoopVersioningLICM::isSafeToHoistBoundDep(
+ Instruction *I, const SmallPtrSetImpl<Value *> &ModifiedPtrs) {
+ switch (I->getOpcode()) {
+ case Instruction::Load: {
+ LoadInst *LI = cast<LoadInst>(I);
+ if (!LI->isSimple()) {
+ LLVM_DEBUG(dbgs() << " Found a non-simple load, as a dependency"
+ " of exit condition\n");
+ return false;
+ }
+ Value *Ptr = LI->getPointerOperand();
+ if (ModifiedPtrs.count(Ptr)) {
+ LLVM_DEBUG(dbgs() << " Bound candidate is invalid - pointer is"
+ " stored to within loop: " << *LI << "\n");
+ return false;
+ }
+ return true;
+ }
+ // Integer arithmetic
+ case Instruction::Add:
+ case Instruction::Sub:
+ case Instruction::Mul:
+ case Instruction::UDiv:
+ case Instruction::SDiv:
+ case Instruction::URem:
+ case Instruction::SRem:
+ // Floating-point arithmetic
+ case Instruction::FAdd:
+ case Instruction::FSub:
+ case Instruction::FMul:
+ case Instruction::FDiv:
+ case Instruction::FRem:
+ case Instruction::FNeg:
+ // Bitwise operations
+ case Instruction::Shl:
+ case Instruction::LShr:
+ case Instruction::AShr:
+ case Instruction::And:
+ case Instruction::Or:
+ case Instruction::Xor:
+ // Integer casts
+ case Instruction::SExt:
+ case Instruction::ZExt:
+ case Instruction::Trunc:
+ // FP casts
+ case Instruction::FPExt:
+ case Instruction::FPTrunc:
+ case Instruction::FPToSI:
+ case Instruction::FPToUI:
+ case Instruction::SIToFP:
+ case Instruction::UIToFP:
+ // Pointer / bitcast
+ case Instruction::BitCast:
+ case Instruction::IntToPtr:
+ case Instruction::PtrToInt:
+ case Instruction::GetElementPtr:
+ // Comparisons
+ case Instruction::ICmp:
+ case Instruction::FCmp:
+ return true;
+ default:
+ LLVM_DEBUG(dbgs() << " Unsupported instruction in bound dependency"
+ " chain: " << *I << "\n");
+ return false;
+ }
+}
+
/// Check memory accesses in loop and confirms it's good for
/// LoopVersioningLICM.
bool LoopVersioningLICM::legalLoopMemoryAccesses() {
@@ -413,7 +585,7 @@ bool LoopVersioningLICM::legalLoopInstructions() {
LLVM_DEBUG(dbgs() << " Found a read-only loop!\n");
return false;
}
- // Profitability check:
+ // Profitablity check:
// Check invariant threshold, should be in limit.
if (InvariantCounter * 100 < InvariantThreshold * LoadAndStoreCounter) {
LLVM_DEBUG(
@@ -507,32 +679,140 @@ bool LoopVersioningLICM::isLegalForVersioning() {
return true;
}
+Instruction *LoopVersioningLICM::insertRuntimeCheckPlaceholder(Loop *L) {
+ // Preheader for our checks
+ BasicBlock *Preheader = L->getLoopPreheader();
+
+ // Determine the insertion point.
+ Instruction *InsertPt = Preheader->getTerminator();
+ LLVMContext &Context = CurLoop->getHeader()->getParent()->getContext();
+ // Create the comparison.
+ Instruction *Placeholder = nullptr;
+ Placeholder = ICmpInst::Create(Instruction::ICmp, ICmpInst::ICMP_EQ,
+ ConstantInt::get(Type::getInt32Ty(Context), 1),
+ ConstantInt::get(Type::getInt32Ty(Context), 1),
+ "runtime.check.placeholder", InsertPt->getIterator());
+
+ return Placeholder;
+}
+
+void LoopVersioningLICM::generateAndAddRuntimeChecks(DominatorTree *DT) {
+ BasicBlock *RuntimeCheckBB = PlaceholderForRuntimeChecks->getParent();
+
+ const auto &RtPtrChecking = *LAI->getRuntimePointerChecking();
+
+ SCEVExpander Exp1(*RtPtrChecking.getSE(), "induction");
+
+ auto RPC = LAI->getRuntimePointerChecking();
+
+ Value *MemRuntimeChecks = llvm::addRuntimeChecks(PlaceholderForRuntimeChecks,
+ CurLoop, RPC->getChecks(), Exp1);
+
+ SCEVExpander Exp2(*SE, "scev.check");
+
+ const SCEVPredicate &Preds(LAI->getPSE().getPredicate());
+ Value *SCEVRuntimeChecks = Exp2.expandCodeForPredicate(&Preds, PlaceholderForRuntimeChecks);
+
+ IRBuilder<InstSimplifyFolder> Builder(RuntimeCheckBB->getContext(),
+ InstSimplifyFolder(RuntimeCheckBB->getModule()->getDataLayout()));
+
+ Value *RuntimeCheck = nullptr;
+ if (MemRuntimeChecks && SCEVRuntimeChecks) {
+ Builder.SetInsertPoint(PlaceholderForRuntimeChecks);
+ RuntimeCheck = Builder.CreateOr(MemRuntimeChecks, SCEVRuntimeChecks, "lver.safe");
+ } else {
+ RuntimeCheck = MemRuntimeChecks ? MemRuntimeChecks : SCEVRuntimeChecks;
+ }
+
+ RuntimeCheckBB->setName(CurLoop->getHeader()->getName() + ".lver.check");
+
+ CondBrInst *BI = dyn_cast<CondBrInst>(RuntimeCheckBB->getTerminator());
+ assert(BI && "Expected conditional branch in runtime check block");
+
+ assert(RuntimeCheck && "called even though we don't need "
+ "any runtime checks");
+
+ PlaceholderForRuntimeChecks->replaceAllUsesWith(RuntimeCheck);
+ PlaceholderForRuntimeChecks->eraseFromParent();
+}
+
bool LoopVersioningLICM::run(DominatorTree *DT) {
// Do not do the transformation if disabled by metadata.
if (hasLICMVersioningTransformation(CurLoop) & TM_Disable)
return false;
-
bool Changed = false;
-
// Check feasiblity of LoopVersioningLICM.
// If versioning found to be feasible and beneficial then proceed
// else simply return, by cleaning up memory.
if (isLegalForVersioning()) {
+ PlaceholderForRuntimeChecks = nullptr;
+ if (IsDynamicBound) {
+ PlaceholderForRuntimeChecks = insertRuntimeCheckPlaceholder(CurLoop);
+ if (!PlaceholderForRuntimeChecks) {
+ LLVM_DEBUG(dbgs() << "No runtime check created\n");
+ return false;
+ }
+ }
// Do loop versioning.
// Create memcheck for memory accessed inside loop.
// Clone original loop, and set blocks properly.
LoopVersioning LVer(*LAI, LAI->getRuntimePointerChecking()->getChecks(),
CurLoop, &LI, DT, SE);
- LVer.versionLoop();
+ LVer.versionLoop(PlaceholderForRuntimeChecks);
+
+ // Add the extra checks for dynamic bounds
+ if (IsDynamicBound) {
+ for (auto p = HoistedDependencies.rbegin(); p != HoistedDependencies.rend(); ++p) {
+ Instruction *I = *p;
+ if (isa<LoadInst>(I)) {
+ // Clone the load to the preheader instead of moving it. The original
+ // dead load is intentionally kept in the loop body so that the subsequent LAA recomputation sees
+ // the load and generates runtime pointer checks for its address range. The cleanup passes will erase it later.
+ LoadInst *LI = cast<LoadInst>(I);
+ LoadInst *NewLI = new LoadInst(
+ LI->getType(), LI->getPointerOperand(), LI->getName() + ".hoisted",
+ false, LI->getAlign(), LI->getOrdering(), LI->getSyncScopeID(),
+ PlaceholderForRuntimeChecks->getIterator()
+ );
+
+ NewLI->copyMetadata(*I);
+ I->replaceAllUsesWith(NewLI);
+ } else {
+ I->moveBefore(PlaceholderForRuntimeChecks->getIterator());
+ }
+ }
+
+// #ifdef EXPENSIVE_CHECKS
+ assert(!verifyFunction(*LVer.getVersionedLoop()->getHeader()->getParent(), &dbgs())
+ && "Verification failure after dynamic bounds");
+// #endif
+
+ SE->forgetLoop(CurLoop);
+ // Recompute LoopAccessInfo after hoisting the dynamic bound
+ LoopAccessInfoManager FreshLAIs(*SE, *AA, *DT, LI, nullptr, nullptr, AC);
+ LAI = &FreshLAIs.getInfo(*CurLoop);
+
+ generateAndAddRuntimeChecks(DT);
+
+// #ifdef EXPENSIVE_CHECKS
+ assert(!verifyFunction(*LVer.getVersionedLoop()->getHeader()->getParent(), &dbgs())
+ && "Verification failure after runtime checks");
+// #endif
+
+ // LAI isn't used anymore, so clear it to avoid dangling reference
+ LAI = nullptr;
+ }
// Set Loop Versioning metaData for original loop.
addStringMetadataToLoop(LVer.getNonVersionedLoop(), LICMVersioningMetaData);
// Set Loop Versioning metaData for version loop.
addStringMetadataToLoop(LVer.getVersionedLoop(), LICMVersioningMetaData);
+
// Set "llvm.mem.parallel_loop_access" metaData to versioned loop.
// FIXME: "llvm.mem.parallel_loop_access" annotates memory access
// instructions, not loops.
addStringMetadataToLoop(LVer.getVersionedLoop(),
"llvm.mem.parallel_loop_access");
+
// Update version loop with aggressive aliasing assumption.
LVer.annotateLoopWithNoAlias();
Changed = true;
@@ -540,6 +820,8 @@ bool LoopVersioningLICM::run(DominatorTree *DT) {
return Changed;
}
+namespace llvm {
+
PreservedAnalyses LoopVersioningLICMPass::run(Loop &L, LoopAnalysisManager &AM,
LoopStandardAnalysisResults &LAR,
LPMUpdater &U) {
@@ -550,7 +832,8 @@ PreservedAnalyses LoopVersioningLICMPass::run(Loop &L, LoopAnalysisManager &AM,
OptimizationRemarkEmitter ORE(F);
LoopAccessInfoManager LAIs(*SE, *AA, *DT, LAR.LI, nullptr, nullptr, &LAR.AC);
- if (!LoopVersioningLICM(AA, SE, &ORE, LAIs, LAR.LI, &L).run(DT))
+ if (!LoopVersioningLICM(AA, SE, &ORE, LAIs, LAR.LI, &LAR.AC, &L).run(DT))
return PreservedAnalyses::all();
return getLoopPassPreservedAnalyses();
}
+} // namespace llvm
diff --git a/llvm/lib/Transforms/Utils/LoopVersioning.cpp b/llvm/lib/Transforms/Utils/LoopVersioning.cpp
index b90466a8c49cf..6696e3c6a6b0e 100644
--- a/llvm/lib/Transforms/Utils/LoopVersioning.cpp
+++ b/llvm/lib/Transforms/Utils/LoopVersioning.cpp
@@ -48,7 +48,7 @@ LoopVersioning::LoopVersioning(const LoopAccessInfo &LAI,
LAI(LAI), LI(LI), DT(DT), SE(SE) {}
void LoopVersioning::versionLoop(
- const SmallVectorImpl<Instruction *> &DefsUsedOutside) {
+ const SmallVectorImpl<Instruction *> &DefsUsedOutside, Instruction *UserRuntimeCheck) {
assert(VersionedLoop->getUniqueExitBlock() && "No single exit block");
assert(VersionedLoop->isLoopSimplifyForm() &&
"Loop is not in loop-simplify form");
@@ -59,27 +59,37 @@ void LoopVersioning::versionLoop(
// Add the memcheck in the original preheader (this is empty initially).
BasicBlock *RuntimeCheckBB = VersionedLoop->getLoopPreheader();
- const auto &RtPtrChecking = *LAI.getRuntimePointerChecking();
-
- SCEVExpander Exp2(*RtPtrChecking.getSE(), "induction");
- MemRuntimeCheck = addRuntimeChecks(RuntimeCheckBB->getTerminator(),
- VersionedLoop, AliasChecks, Exp2);
- SCEVExpander Exp(*SE, "scev.check");
- SCEVRuntimeCheck =
- Exp.expandCodeForPredicate(&Preds, RuntimeCheckBB->getTerminator());
+ if (!UserRuntimeCheck) {
+ const auto &RtPtrChecking = *LAI.getRuntimePointerChecking();
+
+ SCEVExpander Exp2(*RtPtrChecking.getSE(), "induction");
+ MemRuntimeCheck = addRuntimeChecks(RuntimeCheckBB->getTerminator(),
+ VersionedLoop, AliasChecks, Exp2);
+ SCEVExpander Exp(*SE, "scev.check");
+ SCEVRuntimeCheck =
+ Exp.expandCodeForPredicate(&Preds, RuntimeCheckBB->getTerminator());
+
+ IRBuilder<InstSimplifyFolder> Builder(
+ RuntimeCheckBB->getContext(),
+ InstSimplifyFolder(RuntimeCheckBB->getDataLayout()));
+ if (MemRuntimeCheck && SCEVRuntimeCheck) {
+ Builder.SetInsertPoint(RuntimeCheckBB->getTerminator());
+ RuntimeCheck =
+ Builder.CreateOr(MemRuntimeCheck, SCEVRuntimeCheck, "lver.safe");
+ } else
+ RuntimeCheck = MemRuntimeCheck ? MemRuntimeCheck : SCEVRuntimeCheck;
+
+ Exp.eraseDeadInstructions(SCEVRuntimeCheck);
+ }
+ else {
+ RuntimeCheck = UserRuntimeCheck;
+ RuntimeCheckBB = UserRuntimeCheck->getParent();
+ }
IRBuilder<InstSimplifyFolder> Builder(
RuntimeCheckBB->getContext(),
InstSimplifyFolder(RuntimeCheckBB->getDataLayout()));
- if (MemRuntimeCheck && SCEVRuntimeCheck) {
- Builder.SetInsertPoint(RuntimeCheckBB->getTerminator());
- RuntimeCheck =
- Builder.CreateOr(MemRuntimeCheck, SCEVRuntimeCheck, "lver.safe");
- } else
- RuntimeCheck = MemRuntimeCheck ? MemRuntimeCheck : SCEVRuntimeCheck;
-
- Exp.eraseDeadInstructions(SCEVRuntimeCheck);
assert(RuntimeCheck && "called even though we don't need "
"any runtime checks");
diff --git a/llvm/test/Transforms/LoopVersioningLICM/dynamic-bound-array-element.ll b/llvm/test/Transforms/LoopVersioningLICM/dynamic-bound-array-element.ll
new file mode 100644
index 0000000000000..bb844c2dee94e
--- /dev/null
+++ b/llvm/test/Transforms/LoopVersioningLICM/dynamic-bound-array-element.ll
@@ -0,0 +1,139 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt < %s -S -passes='loop-versioning-licm' | FileCheck %s
+;
+; TEST10: 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 runtime checks will
+; guard correctness.
+; Verify the load is hoisted and runtime checks are generated.
+;
+; 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: define dso_local void @foo(
+; CHECK-SAME: ptr noundef captures(none) [[A:%.*]], ptr noundef readonly captures(none) [[B:%.*]], ptr noundef readonly captures(none) [[C:%.*]], ptr noundef readnone captures(none) [[LEN:%.*]]) #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i8, ptr [[A]], i64 8
+; CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[ARRAYIDX]], align 4, !tbaa [[TBAA0:![0-9]+]]
+; CHECK-NEXT: [[CMP11:%.*]] = icmp sgt i32 [[TMP0]], 0
+; CHECK-NEXT: br i1 [[CMP11]], label %[[FOR_BODY_LVER_CHECK:.*]], label %[[FOR_COND_CLEANUP:.*]]
+; CHECK: [[FOR_BODY_LVER_CHECK]]:
+; CHECK-NEXT: [[DOTHOISTED:%.*]] = load i32, ptr [[ARRAYIDX]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT: [[TMP1:%.*]] = sext i32 [[DOTHOISTED]] to i64
+; CHECK-NEXT: [[TMP2:%.*]] = shl nsw i64 [[TMP1]], 2
+; CHECK-NEXT: [[SCEVGEP:%.*]] = getelementptr i8, ptr [[A]], i64 [[TMP2]]
+; CHECK-NEXT: [[SCEVGEP2:%.*]] = getelementptr i8, ptr [[B]], i64 [[TMP2]]
+; CHECK-NEXT: [[SCEVGEP3:%.*]] = getelementptr i8, ptr [[C]], i64 [[TMP2]]
+; 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: [[BOUND04:%.*]] = icmp ult ptr [[A]], [[SCEVGEP3]]
+; CHECK-NEXT: [[BOUND15:%.*]] = icmp ult ptr [[C]], [[SCEVGEP]]
+; CHECK-NEXT: [[FOUND_CONFLICT6:%.*]] = and i1 [[BOUND04]], [[BOUND15]]
+; CHECK-NEXT: [[CONFLICT_RDX:%.*]] = or i1 [[FOUND_CONFLICT]], [[FOUND_CONFLICT6]]
+; CHECK-NEXT: br i1 [[CONFLICT_RDX]], label %[[FOR_BODY_PH_LVER_ORIG:.*]], label %[[FOR_BODY_PH:.*]]
+; CHECK: [[FOR_BODY_PH_LVER_ORIG]]:
+; CHECK-NEXT: br label %[[FOR_BODY_LVER_ORIG:.*]]
+; CHECK: [[FOR_BODY_LVER_ORIG]]:
+; CHECK-NEXT: [[INDVARS_IV_LVER_ORIG:%.*]] = phi i64 [ [[INDVARS_IV_NEXT_LVER_ORIG:%.*]], %[[FOR_BODY_LVER_ORIG]] ], [ 0, %[[FOR_BODY_PH_LVER_ORIG]] ]
+; CHECK-NEXT: [[ARRAYIDX1_LVER_ORIG:%.*]] = getelementptr inbounds nuw i32, ptr [[B]], i64 [[INDVARS_IV_LVER_ORIG]]
+; CHECK-NEXT: [[TMP3:%.*]] = load i32, ptr [[ARRAYIDX1_LVER_ORIG]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT: [[ARRAYIDX3_LVER_ORIG:%.*]] = getelementptr inbounds nuw i32, ptr [[C]], i64 [[INDVARS_IV_LVER_ORIG]]
+; CHECK-NEXT: [[TMP4:%.*]] = load i32, ptr [[ARRAYIDX3_LVER_ORIG]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT: [[ADD_LVER_ORIG:%.*]] = add nsw i32 [[TMP4]], [[TMP3]]
+; CHECK-NEXT: [[ARRAYIDX5_LVER_ORIG:%.*]] = getelementptr inbounds nuw i32, ptr [[A]], i64 [[INDVARS_IV_LVER_ORIG]]
+; CHECK-NEXT: store i32 [[ADD_LVER_ORIG]], ptr [[ARRAYIDX5_LVER_ORIG]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT: [[INDVARS_IV_NEXT_LVER_ORIG]] = add nuw nsw i64 [[INDVARS_IV_LVER_ORIG]], 1
+; CHECK-NEXT: [[TMP5:%.*]] = load i32, ptr [[ARRAYIDX]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT: [[TMP6:%.*]] = sext i32 [[TMP5]] to i64
+; CHECK-NEXT: [[CMP_LVER_ORIG:%.*]] = icmp slt i64 [[INDVARS_IV_NEXT_LVER_ORIG]], [[TMP6]]
+; CHECK-NEXT: br i1 [[CMP_LVER_ORIG]], label %[[FOR_BODY_LVER_ORIG]], label %[[FOR_COND_CLEANUP_LOOPEXIT_LOOPEXIT:.*]], !llvm.loop [[LOOP4:![0-9]+]]
+; CHECK: [[FOR_BODY_PH]]:
+; CHECK-NEXT: br label %[[FOR_BODY:.*]]
+; CHECK: [[FOR_COND_CLEANUP_LOOPEXIT_LOOPEXIT]]:
+; CHECK-NEXT: br label %[[FOR_COND_CLEANUP_LOOPEXIT:.*]]
+; CHECK: [[FOR_COND_CLEANUP_LOOPEXIT_LOOPEXIT1:.*]]:
+; CHECK-NEXT: br label %[[FOR_COND_CLEANUP_LOOPEXIT]]
+; 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]] ], [ 0, %[[FOR_BODY_PH]] ]
+; CHECK-NEXT: [[ARRAYIDX1:%.*]] = getelementptr inbounds nuw i32, ptr [[B]], i64 [[INDVARS_IV]]
+; CHECK-NEXT: [[TMP7:%.*]] = load i32, ptr [[ARRAYIDX1]], align 4, !tbaa [[TBAA0]], !alias.scope [[META8:![0-9]+]]
+; CHECK-NEXT: [[ARRAYIDX3:%.*]] = getelementptr inbounds nuw i32, ptr [[C]], i64 [[INDVARS_IV]]
+; CHECK-NEXT: [[TMP8:%.*]] = load i32, ptr [[ARRAYIDX3]], align 4, !tbaa [[TBAA0]], !alias.scope [[META11:![0-9]+]]
+; CHECK-NEXT: [[ADD:%.*]] = add nsw i32 [[TMP8]], [[TMP7]]
+; CHECK-NEXT: [[ARRAYIDX5:%.*]] = getelementptr inbounds nuw i32, ptr [[A]], i64 [[INDVARS_IV]]
+; CHECK-NEXT: store i32 [[ADD]], ptr [[ARRAYIDX5]], align 4, !tbaa [[TBAA0]], !alias.scope [[META13:![0-9]+]], !noalias [[META15:![0-9]+]]
+; CHECK-NEXT: [[INDVARS_IV_NEXT]] = add nuw nsw i64 [[INDVARS_IV]], 1
+; CHECK-NEXT: [[TMP9:%.*]] = load i32, ptr [[ARRAYIDX]], align 4, !tbaa [[TBAA0]], !alias.scope [[META16:![0-9]+]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i64 [[INDVARS_IV_NEXT]], [[TMP1]]
+; CHECK-NEXT: br i1 [[CMP]], label %[[FOR_BODY]], label %[[FOR_COND_CLEANUP_LOOPEXIT_LOOPEXIT1]], !llvm.loop [[LOOP18:![0-9]+]]
+;
+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"}
+;.
+; 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: [[LOOP4]] = distinct !{[[LOOP4]], [[META5:![0-9]+]], [[META6:![0-9]+]], [[META7:![0-9]+]]}
+; CHECK: [[META5]] = !{!"llvm.loop.mustprogress"}
+; CHECK: [[META6]] = !{!"llvm.loop.unroll.disable"}
+; CHECK: [[META7]] = !{!"llvm.loop.licm_versioning.disable", i32 0}
+; CHECK: [[META8]] = !{[[META9:![0-9]+]]}
+; CHECK: [[META9]] = distinct !{[[META9]], [[META10:![0-9]+]]}
+; CHECK: [[META10]] = distinct !{[[META10]], !"LVerDomain"}
+; CHECK: [[META11]] = !{[[META12:![0-9]+]]}
+; CHECK: [[META12]] = distinct !{[[META12]], [[META10]]}
+; CHECK: [[META13]] = !{[[META14:![0-9]+]]}
+; CHECK: [[META14]] = distinct !{[[META14]], [[META10]]}
+; CHECK: [[META15]] = !{[[META9]], [[META12]]}
+; CHECK: [[META16]] = !{[[META17:![0-9]+]]}
+; CHECK: [[META17]] = distinct !{[[META17]], [[META10]]}
+; CHECK: [[LOOP18]] = distinct !{[[LOOP18]], [[META5]], [[META6]], [[META7]], [[META19:![0-9]+]]}
+; CHECK: [[META19]] = !{!"llvm.mem.parallel_loop_access", i32 0}
+;.
diff --git a/llvm/test/Transforms/LoopVersioningLICM/dynamic-bound-chained-loads.ll b/llvm/test/Transforms/LoopVersioningLICM/dynamic-bound-chained-loads.ll
new file mode 100644
index 0000000000000..dbff9edccbf59
--- /dev/null
+++ b/llvm/test/Transforms/LoopVersioningLICM/dynamic-bound-chained-loads.ll
@@ -0,0 +1,86 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -S -passes='loop-versioning-licm' -licm-versioning-invariant-threshold=15 | FileCheck %s
+;
+; Negative test: the upper bound is **PtrLen (a chained / indirect load).
+; The worklist correctly traces both loads, but LoopAccessAnalysis cannot
+; compute SCEV bounds for the indirect load (load i32, ptr %ptr) because
+; %ptr itself is loaded inside the loop. The pass must reject the loop.
+;
+; 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-NEXT: entry:
+; CHECK-NEXT: [[PTR0:%.*]] = load ptr, ptr [[PTRLEN:%.*]], align 8, !tbaa [[TBAA0:![0-9]+]]
+; CHECK-NEXT: [[VAL0:%.*]] = load i32, ptr [[PTR0]], align 4, !tbaa [[TBAA5:![0-9]+]]
+; CHECK-NEXT: [[CMP9:%.*]] = icmp sgt i32 [[VAL0]], 0
+; CHECK-NEXT: br i1 [[CMP9]], label [[FOR_BODY_PREHEADER:%.*]], label [[FOR_COND_CLEANUP:%.*]]
+; CHECK: for.body.preheader:
+; 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]] ], [ 0, [[FOR_BODY_PREHEADER]] ]
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i32, ptr [[B:%.*]], i64 [[INDVARS_IV]]
+; CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[ARRAYIDX]], align 4, !tbaa [[TBAA5]]
+; CHECK-NEXT: [[ARRAYIDX2:%.*]] = getelementptr inbounds nuw i32, ptr [[C:%.*]], i64 [[INDVARS_IV]]
+; CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[ARRAYIDX2]], align 4, !tbaa [[TBAA5]]
+; CHECK-NEXT: [[ADD:%.*]] = add nsw i32 [[TMP1]], [[TMP0]]
+; CHECK-NEXT: [[ARRAYIDX4:%.*]] = getelementptr inbounds nuw i32, ptr [[A:%.*]], i64 [[INDVARS_IV]]
+; CHECK-NEXT: store i32 [[ADD]], ptr [[ARRAYIDX4]], align 4, !tbaa [[TBAA5]]
+; CHECK-NEXT: [[INDVARS_IV_NEXT]] = add nuw nsw i64 [[INDVARS_IV]], 1
+; CHECK-NEXT: [[PTR:%.*]] = load ptr, ptr [[PTRLEN]], align 8, !tbaa [[TBAA0]]
+; CHECK-NEXT: [[VAL:%.*]] = load i32, ptr [[PTR]], align 4, !tbaa [[TBAA5]]
+; CHECK-NEXT: [[TMP2:%.*]] = sext i32 [[VAL]] to i64
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i64 [[INDVARS_IV_NEXT]], [[TMP2]]
+; CHECK-NEXT: br i1 [[CMP]], label [[FOR_BODY]], label [[FOR_COND_CLEANUP_LOOPEXIT:%.*]], !llvm.loop [[LOOP7:![0-9]+]]
+;
+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/LoopVersioningLICM/dynamic-bound-fcmp-exit.ll b/llvm/test/Transforms/LoopVersioningLICM/dynamic-bound-fcmp-exit.ll
new file mode 100644
index 0000000000000..0424258284f30
--- /dev/null
+++ b/llvm/test/Transforms/LoopVersioningLICM/dynamic-bound-fcmp-exit.ll
@@ -0,0 +1,57 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -S -passes='loop-versioning-licm' | FileCheck %s
+;
+; LoopVersioningLICM shouldn't kick in at all if 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-NEXT: entry:
+; CHECK-NEXT: br label [[FOR_BODY:%.*]]
+; CHECK: for.body:
+; CHECK-NEXT: [[IV:%.*]] = phi i64 [ 0, [[ENTRY:%.*]] ], [ [[IV_NEXT:%.*]], [[FOR_BODY]] ]
+; CHECK-NEXT: [[FIV:%.*]] = phi float [ 0.000000e+00, [[ENTRY]] ], [ [[FIV_NEXT:%.*]], [[FOR_BODY]] ]
+; CHECK-NEXT: [[GEP_B:%.*]] = getelementptr inbounds i32, ptr [[B:%.*]], i64 [[IV]]
+; CHECK-NEXT: [[B:%.*]] = load i32, ptr [[GEP_B]], align 4
+; CHECK-NEXT: [[GEP_A:%.*]] = getelementptr inbounds i32, ptr [[A:%.*]], i64 [[IV]]
+; CHECK-NEXT: store i32 [[B]], ptr [[GEP_A]], align 4
+; CHECK-NEXT: [[IV_NEXT]] = add nuw nsw i64 [[IV]], 1
+; CHECK-NEXT: [[FIV_NEXT]] = fadd float [[FIV]], 1.000000e+00
+; CHECK-NEXT: [[LIM:%.*]] = load float, ptr [[LIMIT:%.*]], align 4
+; CHECK-NEXT: [[CMP:%.*]] = fcmp olt float [[FIV_NEXT]], [[LIM]]
+; CHECK-NEXT: br i1 [[CMP]], label [[FOR_BODY]], label [[FOR_EXIT:%.*]], !llvm.loop [[LOOP0:![0-9]+]]
+; CHECK: for.exit:
+; CHECK-NEXT: ret void
+;
+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/LoopVersioningLICM/dynamic-bound-lambda.ll b/llvm/test/Transforms/LoopVersioningLICM/dynamic-bound-lambda.ll
new file mode 100644
index 0000000000000..60bbe5a49ef71
--- /dev/null
+++ b/llvm/test/Transforms/LoopVersioningLICM/dynamic-bound-lambda.ll
@@ -0,0 +1,142 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt < %s -S -passes='loop-versioning-licm' | FileCheck %s
+;
+; Lambda with loop bound loaded from pointer (*len).
+; Verify the load is hoisted and runtime checks are generated.
+;
+; 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_LVER_CHECK:.*]], label %[[EXIT:.*]]
+; CHECK: [[FOR_BODY_I_LVER_CHECK]]:
+; CHECK-NEXT: [[DOTHOISTED:%.*]] = load i32, ptr [[LEN]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT: [[TMP1:%.*]] = sext i32 [[DOTHOISTED]] to i64
+; CHECK-NEXT: [[TMP2:%.*]] = shl nsw i64 [[TMP1]], 2
+; CHECK-NEXT: [[SCEVGEP:%.*]] = getelementptr i8, ptr [[A]], i64 [[TMP2]]
+; CHECK-NEXT: [[SCEVGEP2:%.*]] = getelementptr i8, ptr [[B]], i64 [[TMP2]]
+; CHECK-NEXT: [[SCEVGEP3:%.*]] = getelementptr i8, ptr [[C]], i64 [[TMP2]]
+; 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 %[[FOR_BODY_I_PH_LVER_ORIG:.*]], label %[[FOR_BODY_I_PH:.*]]
+; CHECK: [[FOR_BODY_I_PH_LVER_ORIG]]:
+; CHECK-NEXT: br label %[[FOR_BODY_I_LVER_ORIG:.*]]
+; CHECK: [[FOR_BODY_I_LVER_ORIG]]:
+; CHECK-NEXT: [[INDVARS_IV_I_LVER_ORIG:%.*]] = phi i64 [ [[INDVARS_IV_NEXT_I_LVER_ORIG:%.*]], %[[FOR_BODY_I_LVER_ORIG]] ], [ 0, %[[FOR_BODY_I_PH_LVER_ORIG]] ]
+; CHECK-NEXT: [[ARRAYIDX_I_LVER_ORIG:%.*]] = getelementptr inbounds nuw i32, ptr [[B]], i64 [[INDVARS_IV_I_LVER_ORIG]]
+; CHECK-NEXT: [[TMP3:%.*]] = load i32, ptr [[ARRAYIDX_I_LVER_ORIG]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT: [[ARRAYIDX3_I_LVER_ORIG:%.*]] = getelementptr inbounds nuw i32, ptr [[C]], i64 [[INDVARS_IV_I_LVER_ORIG]]
+; CHECK-NEXT: [[TMP4:%.*]] = load i32, ptr [[ARRAYIDX3_I_LVER_ORIG]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT: [[MUL_I_LVER_ORIG:%.*]] = mul nsw i32 [[TMP4]], [[TMP3]]
+; CHECK-NEXT: [[ARRAYIDX5_I_LVER_ORIG:%.*]] = getelementptr inbounds nuw i32, ptr [[A]], i64 [[INDVARS_IV_I_LVER_ORIG]]
+; CHECK-NEXT: store i32 [[MUL_I_LVER_ORIG]], ptr [[ARRAYIDX5_I_LVER_ORIG]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT: [[INDVARS_IV_NEXT_I_LVER_ORIG]] = add nuw nsw i64 [[INDVARS_IV_I_LVER_ORIG]], 1
+; CHECK-NEXT: [[TMP5:%.*]] = load i32, ptr [[LEN]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT: [[TMP6:%.*]] = sext i32 [[TMP5]] to i64
+; CHECK-NEXT: [[CMP_I_LVER_ORIG:%.*]] = icmp slt i64 [[INDVARS_IV_NEXT_I_LVER_ORIG]], [[TMP6]]
+; CHECK-NEXT: br i1 [[CMP_I_LVER_ORIG]], label %[[FOR_BODY_I_LVER_ORIG]], label %[[EXIT_LOOPEXIT_LOOPEXIT:.*]], !llvm.loop [[LOOP4:![0-9]+]]
+; CHECK: [[FOR_BODY_I_PH]]:
+; 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]] ], [ 0, %[[FOR_BODY_I_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]], !alias.scope [[META8:![0-9]+]]
+; 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]], !alias.scope [[META11:![0-9]+]]
+; 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]], !alias.scope [[META13:![0-9]+]], !noalias [[META15:![0-9]+]]
+; 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]], !alias.scope [[META17:![0-9]+]]
+; CHECK-NEXT: [[CMP_I:%.*]] = icmp slt i64 [[INDVARS_IV_NEXT_I]], [[TMP1]]
+; CHECK-NEXT: br i1 [[CMP_I]], label %[[FOR_BODY_I]], label %[[EXIT_LOOPEXIT_LOOPEXIT1:.*]], !llvm.loop [[LOOP18:![0-9]+]]
+; CHECK: [[EXIT_LOOPEXIT_LOOPEXIT]]:
+; CHECK-NEXT: br label %[[EXIT_LOOPEXIT:.*]]
+; CHECK: [[EXIT_LOOPEXIT_LOOPEXIT1]]:
+; CHECK-NEXT: br label %[[EXIT_LOOPEXIT]]
+; 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: [[LOOP4]] = distinct !{[[LOOP4]], [[META5:![0-9]+]], [[META6:![0-9]+]], [[META7:![0-9]+]]}
+; CHECK: [[META5]] = !{!"llvm.loop.mustprogress"}
+; CHECK: [[META6]] = !{!"llvm.loop.unroll.disable"}
+; CHECK: [[META7]] = !{!"llvm.loop.licm_versioning.disable", i32 0}
+; CHECK: [[META8]] = !{[[META9:![0-9]+]]}
+; CHECK: [[META9]] = distinct !{[[META9]], [[META10:![0-9]+]]}
+; CHECK: [[META10]] = distinct !{[[META10]], !"LVerDomain"}
+; CHECK: [[META11]] = !{[[META12:![0-9]+]]}
+; CHECK: [[META12]] = distinct !{[[META12]], [[META10]]}
+; CHECK: [[META13]] = !{[[META14:![0-9]+]]}
+; CHECK: [[META14]] = distinct !{[[META14]], [[META10]]}
+; CHECK: [[META15]] = !{[[META9]], [[META12]], [[META16:![0-9]+]]}
+; CHECK: [[META16]] = distinct !{[[META16]], [[META10]]}
+; CHECK: [[META17]] = !{[[META16]]}
+; CHECK: [[LOOP18]] = distinct !{[[LOOP18]], [[META5]], [[META6]], [[META7]], [[META19:![0-9]+]]}
+; CHECK: [[META19]] = !{!"llvm.mem.parallel_loop_access", i32 0}
+;.
diff --git a/llvm/test/Transforms/LoopVersioningLICM/dynamic-bound-loop-varying.ll b/llvm/test/Transforms/LoopVersioningLICM/dynamic-bound-loop-varying.ll
new file mode 100644
index 0000000000000..30d155a84e0bd
--- /dev/null
+++ b/llvm/test/Transforms/LoopVersioningLICM/dynamic-bound-loop-varying.ll
@@ -0,0 +1,79 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -S -passes='loop-versioning-licm' | FileCheck %s
+;
+; LoopVersioningLICM shouldn't kick in at all if 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 pass will reject this loop instead of versioning it.
+;
+; 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-NEXT: entry:
+; CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[LEN:%.*]], align 4, !tbaa [[TBAA0:![0-9]+]]
+; CHECK-NEXT: [[CMP12:%.*]] = icmp sgt i32 [[TMP0]], 0
+; CHECK-NEXT: br i1 [[CMP12]], label [[FOR_BODY_PREHEADER:%.*]], label [[FOR_COND_CLEANUP:%.*]]
+; CHECK: for.body.preheader:
+; 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]] ], [ 0, [[FOR_BODY_PREHEADER]] ]
+; CHECK-NEXT: [[ARRAYIDX2:%.*]] = getelementptr inbounds nuw i32, ptr [[B:%.*]], i64 [[INDVARS_IV]]
+; CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[ARRAYIDX2]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT: [[ARRAYIDX4:%.*]] = getelementptr inbounds nuw i32, ptr [[C:%.*]], i64 [[INDVARS_IV]]
+; CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[ARRAYIDX4]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT: [[ADD:%.*]] = add nsw i32 [[TMP2]], [[TMP1]]
+; CHECK-NEXT: [[ARRAYIDX6:%.*]] = getelementptr inbounds nuw i32, ptr [[A:%.*]], i64 [[INDVARS_IV]]
+; CHECK-NEXT: store i32 [[ADD]], ptr [[ARRAYIDX6]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT: [[INDVARS_IV_NEXT]] = add nuw nsw i64 [[INDVARS_IV]], 1
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i32, ptr [[LEN]], i64 [[INDVARS_IV_NEXT]]
+; CHECK-NEXT: [[TMP3:%.*]] = load i32, ptr [[ARRAYIDX]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT: [[TMP4:%.*]] = sext i32 [[TMP3]] to i64
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i64 [[INDVARS_IV_NEXT]], [[TMP4]]
+; CHECK-NEXT: br i1 [[CMP]], label [[FOR_BODY]], label [[FOR_COND_CLEANUP_LOOPEXIT:%.*]], !llvm.loop [[LOOP4:![0-9]+]]
+;
+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/LoopVersioningLICM/dynamic-bound-simple.ll b/llvm/test/Transforms/LoopVersioningLICM/dynamic-bound-simple.ll
new file mode 100644
index 0000000000000..6f73c1013c047
--- /dev/null
+++ b/llvm/test/Transforms/LoopVersioningLICM/dynamic-bound-simple.ll
@@ -0,0 +1,139 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt < %s -S -passes='loop-versioning-licm' | FileCheck %s
+;
+; Simple loop with loop bound loaded from a pointer (*Len).
+; Verify the load is hoisted and runtime checks are generated.
+;
+; 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_LVER_CHECK:.*]], label %[[FOR_COND_CLEANUP:.*]]
+; CHECK: [[FOR_BODY_LVER_CHECK]]:
+; CHECK-NEXT: [[DOTHOISTED:%.*]] = load i32, ptr [[LEN]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT: [[TMP1:%.*]] = sext i32 [[DOTHOISTED]] to i64
+; CHECK-NEXT: [[TMP2:%.*]] = shl nsw i64 [[TMP1]], 2
+; CHECK-NEXT: [[SCEVGEP:%.*]] = getelementptr i8, ptr [[A]], i64 [[TMP2]]
+; CHECK-NEXT: [[SCEVGEP2:%.*]] = getelementptr i8, ptr [[B]], i64 [[TMP2]]
+; CHECK-NEXT: [[SCEVGEP3:%.*]] = getelementptr i8, ptr [[C]], i64 [[TMP2]]
+; 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 %[[FOR_BODY_PH_LVER_ORIG:.*]], label %[[FOR_BODY_PH:.*]]
+; CHECK: [[FOR_BODY_PH_LVER_ORIG]]:
+; CHECK-NEXT: br label %[[FOR_BODY_LVER_ORIG:.*]]
+; CHECK: [[FOR_BODY_LVER_ORIG]]:
+; CHECK-NEXT: [[INDVARS_IV_LVER_ORIG:%.*]] = phi i64 [ [[INDVARS_IV_NEXT_LVER_ORIG:%.*]], %[[FOR_BODY_LVER_ORIG]] ], [ 0, %[[FOR_BODY_PH_LVER_ORIG]] ]
+; CHECK-NEXT: [[ARRAYIDX_LVER_ORIG:%.*]] = getelementptr inbounds nuw i32, ptr [[B]], i64 [[INDVARS_IV_LVER_ORIG]]
+; CHECK-NEXT: [[TMP3:%.*]] = load i32, ptr [[ARRAYIDX_LVER_ORIG]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT: [[ARRAYIDX2_LVER_ORIG:%.*]] = getelementptr inbounds nuw i32, ptr [[C]], i64 [[INDVARS_IV_LVER_ORIG]]
+; CHECK-NEXT: [[TMP4:%.*]] = load i32, ptr [[ARRAYIDX2_LVER_ORIG]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT: [[ADD_LVER_ORIG:%.*]] = add nsw i32 [[TMP4]], [[TMP3]]
+; CHECK-NEXT: [[ARRAYIDX4_LVER_ORIG:%.*]] = getelementptr inbounds nuw i32, ptr [[A]], i64 [[INDVARS_IV_LVER_ORIG]]
+; CHECK-NEXT: store i32 [[ADD_LVER_ORIG]], ptr [[ARRAYIDX4_LVER_ORIG]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT: [[INDVARS_IV_NEXT_LVER_ORIG]] = add nuw nsw i64 [[INDVARS_IV_LVER_ORIG]], 1
+; CHECK-NEXT: [[TMP5:%.*]] = load i32, ptr [[LEN]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT: [[TMP6:%.*]] = sext i32 [[TMP5]] to i64
+; CHECK-NEXT: [[CMP_LVER_ORIG:%.*]] = icmp slt i64 [[INDVARS_IV_NEXT_LVER_ORIG]], [[TMP6]]
+; CHECK-NEXT: br i1 [[CMP_LVER_ORIG]], label %[[FOR_BODY_LVER_ORIG]], label %[[FOR_COND_CLEANUP_LOOPEXIT_LOOPEXIT:.*]], !llvm.loop [[LOOP4:![0-9]+]]
+; CHECK: [[FOR_BODY_PH]]:
+; CHECK-NEXT: br label %[[FOR_BODY:.*]]
+; CHECK: [[FOR_COND_CLEANUP_LOOPEXIT_LOOPEXIT]]:
+; CHECK-NEXT: br label %[[FOR_COND_CLEANUP_LOOPEXIT:.*]]
+; CHECK: [[FOR_COND_CLEANUP_LOOPEXIT_LOOPEXIT1:.*]]:
+; CHECK-NEXT: br label %[[FOR_COND_CLEANUP_LOOPEXIT]]
+; 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]] ], [ 0, %[[FOR_BODY_PH]] ]
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i32, ptr [[B]], i64 [[INDVARS_IV]]
+; CHECK-NEXT: [[TMP7:%.*]] = load i32, ptr [[ARRAYIDX]], align 4, !tbaa [[TBAA0]], !alias.scope [[META8:![0-9]+]]
+; CHECK-NEXT: [[ARRAYIDX2:%.*]] = getelementptr inbounds nuw i32, ptr [[C]], i64 [[INDVARS_IV]]
+; CHECK-NEXT: [[TMP8:%.*]] = load i32, ptr [[ARRAYIDX2]], align 4, !tbaa [[TBAA0]], !alias.scope [[META11:![0-9]+]]
+; 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]], !alias.scope [[META13:![0-9]+]], !noalias [[META15:![0-9]+]]
+; CHECK-NEXT: [[INDVARS_IV_NEXT]] = add nuw nsw i64 [[INDVARS_IV]], 1
+; CHECK-NEXT: [[TMP9:%.*]] = load i32, ptr [[LEN]], align 4, !tbaa [[TBAA0]], !alias.scope [[META17:![0-9]+]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i64 [[INDVARS_IV_NEXT]], [[TMP1]]
+; CHECK-NEXT: br i1 [[CMP]], label %[[FOR_BODY]], label %[[FOR_COND_CLEANUP_LOOPEXIT_LOOPEXIT1]], !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: [[LOOP4]] = distinct !{[[LOOP4]], [[META5:![0-9]+]], [[META6:![0-9]+]], [[META7:![0-9]+]]}
+; CHECK: [[META5]] = !{!"llvm.loop.mustprogress"}
+; CHECK: [[META6]] = !{!"llvm.loop.unroll.disable"}
+; CHECK: [[META7]] = !{!"llvm.loop.licm_versioning.disable", i32 0}
+; CHECK: [[META8]] = !{[[META9:![0-9]+]]}
+; CHECK: [[META9]] = distinct !{[[META9]], [[META10:![0-9]+]]}
+; CHECK: [[META10]] = distinct !{[[META10]], !"LVerDomain"}
+; CHECK: [[META11]] = !{[[META12:![0-9]+]]}
+; CHECK: [[META12]] = distinct !{[[META12]], [[META10]]}
+; CHECK: [[META13]] = !{[[META14:![0-9]+]]}
+; CHECK: [[META14]] = distinct !{[[META14]], [[META10]]}
+; CHECK: [[META15]] = !{[[META9]], [[META12]], [[META16:![0-9]+]]}
+; CHECK: [[META16]] = distinct !{[[META16]], [[META10]]}
+; CHECK: [[META17]] = !{[[META16]]}
+; CHECK: [[LOOP18]] = distinct !{[[LOOP18]], [[META5]], [[META6]], [[META7]], [[META19:![0-9]+]]}
+; CHECK: [[META19]] = !{!"llvm.mem.parallel_loop_access", i32 0}
+;.
diff --git a/llvm/test/Transforms/LoopVersioningLICM/dynamic-bound-stored-pointer.ll b/llvm/test/Transforms/LoopVersioningLICM/dynamic-bound-stored-pointer.ll
new file mode 100644
index 0000000000000..8d1366d14fbef
--- /dev/null
+++ b/llvm/test/Transforms/LoopVersioningLICM/dynamic-bound-stored-pointer.ll
@@ -0,0 +1,72 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -S -passes='loop-versioning-licm' | 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-NEXT: entry:
+; CHECK-NEXT: [[LEN0:%.*]] = load i32, ptr [[LEN:%.*]], align 4
+; CHECK-NEXT: [[CMP0:%.*]] = icmp sgt i32 [[LEN0]], 0
+; CHECK-NEXT: br i1 [[CMP0]], label [[FOR_BODY_PREHEADER:%.*]], label [[FOR_COND_CLEANUP:%.*]]
+; CHECK: for.body.preheader:
+; 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]] ], [ 0, [[FOR_BODY_PREHEADER]] ]
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i32, ptr [[B:%.*]], i64 [[INDVARS_IV]]
+; CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[ARRAYIDX]], align 4
+; CHECK-NEXT: [[ARRAYIDX2:%.*]] = getelementptr inbounds nuw i32, ptr [[C:%.*]], i64 [[INDVARS_IV]]
+; CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[ARRAYIDX2]], align 4
+; CHECK-NEXT: [[ADD:%.*]] = add nsw i32 [[TMP1]], [[TMP0]]
+; CHECK-NEXT: [[ARRAYIDX4:%.*]] = getelementptr inbounds nuw i32, ptr [[A:%.*]], i64 [[INDVARS_IV]]
+; CHECK-NEXT: store i32 [[ADD]], ptr [[ARRAYIDX4]], align 4
+; CHECK-NEXT: store i32 [[ADD]], ptr [[LEN]], align 4
+; CHECK-NEXT: [[INDVARS_IV_NEXT]] = add nuw nsw i64 [[INDVARS_IV]], 1
+; CHECK-NEXT: [[LEN_RELOAD:%.*]] = load i32, ptr [[LEN]], align 4
+; CHECK-NEXT: [[EXT:%.*]] = sext i32 [[LEN_RELOAD]] to i64
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i64 [[INDVARS_IV_NEXT]], [[EXT]]
+; CHECK-NEXT: br i1 [[CMP]], label [[FOR_BODY]], label [[FOR_COND_CLEANUP_LOOPEXIT:%.*]], !llvm.loop [[LOOP0:![0-9]+]]
+;
+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/LoopVersioningLICM/dynamic-bound-two-loads.ll b/llvm/test/Transforms/LoopVersioningLICM/dynamic-bound-two-loads.ll
new file mode 100644
index 0000000000000..e07aa60a9aa33
--- /dev/null
+++ b/llvm/test/Transforms/LoopVersioningLICM/dynamic-bound-two-loads.ll
@@ -0,0 +1,158 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt < %s -S -passes='loop-versioning-licm' | FileCheck %s
+;
+; Loop bound is *Len1 + *Len2. Two loads and an add are hoisted to the preheader for the dynamic bound.
+; hoisted to the preheader for the dynamic bound.
+; Verify both loads are hoisted, the add is hoisted, and runtime checks are generated.
+;
+; 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_LVER_CHECK:.*]], label %[[FOR_COND_CLEANUP:.*]]
+; CHECK: [[FOR_BODY_LVER_CHECK]]:
+; CHECK-NEXT: [[DOTHOISTED:%.*]] = load i32, ptr [[LEN2]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT: [[DOTHOISTED2:%.*]] = load i32, ptr [[LEN1]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT: [[ADD:%.*]] = add i32 [[DOTHOISTED]], [[DOTHOISTED2]]
+; CHECK-NEXT: [[TMP2:%.*]] = sext i32 [[ADD]] to i64
+; CHECK-NEXT: [[TMP3:%.*]] = shl nsw i64 [[TMP2]], 2
+; CHECK-NEXT: [[SCEVGEP:%.*]] = getelementptr i8, ptr [[A]], i64 [[TMP3]]
+; CHECK-NEXT: [[SCEVGEP3:%.*]] = getelementptr i8, ptr [[B]], i64 [[TMP3]]
+; CHECK-NEXT: [[SCEVGEP4:%.*]] = getelementptr i8, ptr [[C]], i64 [[TMP3]]
+; 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 %[[FOR_BODY_PH_LVER_ORIG:.*]], label %[[FOR_BODY_PH:.*]]
+; CHECK: [[FOR_BODY_PH_LVER_ORIG]]:
+; CHECK-NEXT: br label %[[FOR_BODY_LVER_ORIG:.*]]
+; CHECK: [[FOR_BODY_LVER_ORIG]]:
+; CHECK-NEXT: [[INDVARS_IV_LVER_ORIG:%.*]] = phi i64 [ [[INDVARS_IV_NEXT_LVER_ORIG:%.*]], %[[FOR_BODY_LVER_ORIG]] ], [ 0, %[[FOR_BODY_PH_LVER_ORIG]] ]
+; CHECK-NEXT: [[ARRAYIDX_LVER_ORIG:%.*]] = getelementptr inbounds nuw i32, ptr [[B]], i64 [[INDVARS_IV_LVER_ORIG]]
+; CHECK-NEXT: [[TMP4:%.*]] = load i32, ptr [[ARRAYIDX_LVER_ORIG]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT: [[ARRAYIDX2_LVER_ORIG:%.*]] = getelementptr inbounds nuw i32, ptr [[C]], i64 [[INDVARS_IV_LVER_ORIG]]
+; CHECK-NEXT: [[TMP5:%.*]] = load i32, ptr [[ARRAYIDX2_LVER_ORIG]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT: [[ADD3_LVER_ORIG:%.*]] = add nsw i32 [[TMP5]], [[TMP4]]
+; CHECK-NEXT: [[ARRAYIDX5_LVER_ORIG:%.*]] = getelementptr inbounds nuw i32, ptr [[A]], i64 [[INDVARS_IV_LVER_ORIG]]
+; CHECK-NEXT: store i32 [[ADD3_LVER_ORIG]], ptr [[ARRAYIDX5_LVER_ORIG]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT: [[INDVARS_IV_NEXT_LVER_ORIG]] = add nuw nsw i64 [[INDVARS_IV_LVER_ORIG]], 1
+; CHECK-NEXT: [[TMP6:%.*]] = load i32, ptr [[LEN1]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT: [[TMP7:%.*]] = load i32, ptr [[LEN2]], align 4, !tbaa [[TBAA0]]
+; CHECK-NEXT: [[ADD_LVER_ORIG:%.*]] = add nsw i32 [[TMP7]], [[TMP6]]
+; CHECK-NEXT: [[TMP8:%.*]] = sext i32 [[ADD_LVER_ORIG]] to i64
+; CHECK-NEXT: [[CMP_LVER_ORIG:%.*]] = icmp slt i64 [[INDVARS_IV_NEXT_LVER_ORIG]], [[TMP8]]
+; CHECK-NEXT: br i1 [[CMP_LVER_ORIG]], label %[[FOR_BODY_LVER_ORIG]], label %[[FOR_COND_CLEANUP_LOOPEXIT_LOOPEXIT:.*]], !llvm.loop [[LOOP4:![0-9]+]]
+; CHECK: [[FOR_BODY_PH]]:
+; CHECK-NEXT: br label %[[FOR_BODY:.*]]
+; CHECK: [[FOR_COND_CLEANUP_LOOPEXIT_LOOPEXIT]]:
+; CHECK-NEXT: br label %[[FOR_COND_CLEANUP_LOOPEXIT:.*]]
+; CHECK: [[FOR_COND_CLEANUP_LOOPEXIT_LOOPEXIT1:.*]]:
+; CHECK-NEXT: br label %[[FOR_COND_CLEANUP_LOOPEXIT]]
+; 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]] ], [ 0, %[[FOR_BODY_PH]] ]
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i32, ptr [[B]], i64 [[INDVARS_IV]]
+; CHECK-NEXT: [[TMP9:%.*]] = load i32, ptr [[ARRAYIDX]], align 4, !tbaa [[TBAA0]], !alias.scope [[META8:![0-9]+]]
+; CHECK-NEXT: [[ARRAYIDX2:%.*]] = getelementptr inbounds nuw i32, ptr [[C]], i64 [[INDVARS_IV]]
+; CHECK-NEXT: [[TMP10:%.*]] = load i32, ptr [[ARRAYIDX2]], align 4, !tbaa [[TBAA0]], !alias.scope [[META11:![0-9]+]]
+; CHECK-NEXT: [[ADD3:%.*]] = add nsw i32 [[TMP10]], [[TMP9]]
+; CHECK-NEXT: [[ARRAYIDX5:%.*]] = getelementptr inbounds nuw i32, ptr [[A]], i64 [[INDVARS_IV]]
+; CHECK-NEXT: store i32 [[ADD3]], ptr [[ARRAYIDX5]], align 4, !tbaa [[TBAA0]], !alias.scope [[META13:![0-9]+]], !noalias [[META15:![0-9]+]]
+; CHECK-NEXT: [[INDVARS_IV_NEXT]] = add nuw nsw i64 [[INDVARS_IV]], 1
+; CHECK-NEXT: [[TMP11:%.*]] = load i32, ptr [[LEN1]], align 4, !tbaa [[TBAA0]], !alias.scope [[META18:![0-9]+]]
+; CHECK-NEXT: [[TMP12:%.*]] = load i32, ptr [[LEN2]], align 4, !tbaa [[TBAA0]], !alias.scope [[META19:![0-9]+]]
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i64 [[INDVARS_IV_NEXT]], [[TMP2]]
+; CHECK-NEXT: br i1 [[CMP]], label %[[FOR_BODY]], label %[[FOR_COND_CLEANUP_LOOPEXIT_LOOPEXIT1]], !llvm.loop [[LOOP20:![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: [[LOOP4]] = distinct !{[[LOOP4]], [[META5:![0-9]+]], [[META6:![0-9]+]], [[META7:![0-9]+]]}
+; CHECK: [[META5]] = !{!"llvm.loop.mustprogress"}
+; CHECK: [[META6]] = !{!"llvm.loop.unroll.disable"}
+; CHECK: [[META7]] = !{!"llvm.loop.licm_versioning.disable", i32 0}
+; CHECK: [[META8]] = !{[[META9:![0-9]+]]}
+; CHECK: [[META9]] = distinct !{[[META9]], [[META10:![0-9]+]]}
+; CHECK: [[META10]] = distinct !{[[META10]], !"LVerDomain"}
+; CHECK: [[META11]] = !{[[META12:![0-9]+]]}
+; CHECK: [[META12]] = distinct !{[[META12]], [[META10]]}
+; CHECK: [[META13]] = !{[[META14:![0-9]+]]}
+; CHECK: [[META14]] = distinct !{[[META14]], [[META10]]}
+; CHECK: [[META15]] = !{[[META9]], [[META12]], [[META16:![0-9]+]], [[META17:![0-9]+]]}
+; CHECK: [[META16]] = distinct !{[[META16]], [[META10]]}
+; CHECK: [[META17]] = distinct !{[[META17]], [[META10]]}
+; CHECK: [[META18]] = !{[[META16]]}
+; CHECK: [[META19]] = !{[[META17]]}
+; CHECK: [[LOOP20]] = distinct !{[[LOOP20]], [[META5]], [[META6]], [[META7]], [[META21:![0-9]+]]}
+; CHECK: [[META21]] = !{!"llvm.mem.parallel_loop_access", i32 0}
+;.
diff --git a/llvm/test/Transforms/LoopVersioningLICM/dynamic-bound-volatile-load.ll b/llvm/test/Transforms/LoopVersioningLICM/dynamic-bound-volatile-load.ll
new file mode 100644
index 0000000000000..2b53fef5db62e
--- /dev/null
+++ b/llvm/test/Transforms/LoopVersioningLICM/dynamic-bound-volatile-load.ll
@@ -0,0 +1,67 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -S -passes='loop-versioning-licm' | FileCheck %s
+;
+; LoopVersioningLICM shouldn't kick in at all if the bound is loaded via a volatile (non-simple) load.
+; The pass rejects the loop because volatile loads cannot be hoisted.
+;
+; 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-NEXT: entry:
+; CHECK-NEXT: [[LEN0:%.*]] = load volatile i32, ptr [[LEN:%.*]], align 4
+; CHECK-NEXT: [[CMP0:%.*]] = icmp sgt i32 [[LEN0]], 0
+; CHECK-NEXT: br i1 [[CMP0]], label [[FOR_BODY_PREHEADER:%.*]], label [[FOR_COND_CLEANUP:%.*]]
+; CHECK: for.body.preheader:
+; 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]] ], [ 0, [[FOR_BODY_PREHEADER]] ]
+; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i32, ptr [[B:%.*]], i64 [[INDVARS_IV]]
+; CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[ARRAYIDX]], align 4
+; CHECK-NEXT: [[ARRAYIDX2:%.*]] = getelementptr inbounds nuw i32, ptr [[C:%.*]], i64 [[INDVARS_IV]]
+; CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[ARRAYIDX2]], align 4
+; CHECK-NEXT: [[ADD:%.*]] = add nsw i32 [[TMP1]], [[TMP0]]
+; CHECK-NEXT: [[ARRAYIDX4:%.*]] = getelementptr inbounds nuw i32, ptr [[A:%.*]], i64 [[INDVARS_IV]]
+; CHECK-NEXT: store i32 [[ADD]], ptr [[ARRAYIDX4]], align 4
+; CHECK-NEXT: [[INDVARS_IV_NEXT]] = add nuw nsw i64 [[INDVARS_IV]], 1
+; CHECK-NEXT: [[LEN_RELOAD:%.*]] = load volatile i32, ptr [[LEN]], align 4
+; CHECK-NEXT: [[EXT:%.*]] = sext i32 [[LEN_RELOAD]] to i64
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i64 [[INDVARS_IV_NEXT]], [[EXT]]
+; CHECK-NEXT: br i1 [[CMP]], label [[FOR_BODY]], label [[FOR_COND_CLEANUP_LOOPEXIT:%.*]], !llvm.loop [[LOOP0:![0-9]+]]
+;
+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"}
More information about the llvm-commits
mailing list