[llvm] [GVN] Support rnflow pattern matching and transform (PR #162259)
Madhur Amilkanthwar via llvm-commits
llvm-commits at lists.llvm.org
Mon May 18 06:23:34 PDT 2026
https://github.com/madhur13490 updated https://github.com/llvm/llvm-project/pull/162259
>From 568f755365c514a8c3d7eb176389d61e9866362f Mon Sep 17 00:00:00 2001
From: Madhur Amilkanthwar <madhura at nvidia.com>
Date: Mon, 18 May 2026 06:20:48 -0700
Subject: [PATCH] [GVN] Support rnflow pattern matching and transform
---
llvm/include/llvm/Transforms/Scalar/GVN.h | 9 +
llvm/lib/Transforms/Scalar/GVN.cpp | 434 +++++++
.../Transforms/GVN/PRE/gvn-min-pre-basic.ll | 236 ++++
.../GVN/PRE/gvn-min-pre-negatives.ll | 1007 +++++++++++++++++
.../GVN/PRE/gvn-min-pre-predicates.ll | 304 +++++
5 files changed, 1990 insertions(+)
create mode 100644 llvm/test/Transforms/GVN/PRE/gvn-min-pre-basic.ll
create mode 100644 llvm/test/Transforms/GVN/PRE/gvn-min-pre-negatives.ll
create mode 100644 llvm/test/Transforms/GVN/PRE/gvn-min-pre-predicates.ll
diff --git a/llvm/include/llvm/Transforms/Scalar/GVN.h b/llvm/include/llvm/Transforms/Scalar/GVN.h
index 806e969211e7a..062d4893ebee4 100644
--- a/llvm/include/llvm/Transforms/Scalar/GVN.h
+++ b/llvm/include/llvm/Transforms/Scalar/GVN.h
@@ -45,6 +45,8 @@ class FunctionPass;
class GetElementPtrInst;
class ImplicitControlFlowTracking;
class LoadInst;
+class Loop;
+class SelectInst;
class LoopInfo;
class MemDepResult;
class MemoryAccess;
@@ -421,6 +423,13 @@ class GVNPass : public OptionalPassInfoMixin<GVNPass> {
void addDeadBlock(BasicBlock *BB);
void assignValNumForDeadCode();
void assignBlockRPONumber(Function &F);
+
+ bool recognizeMinFindingSelectPattern(SelectInst *Select);
+ bool transformMinFindingSelectPattern(Loop *L, Type *LoadType,
+ BasicBlock *Preheader, BasicBlock *BB,
+ CmpInst *Comparison, SelectInst *Select,
+ Value *BasePtr, PHINode *IndexValPhi,
+ Value *OffsetVal);
};
/// Create a legacy GVN pass.
diff --git a/llvm/lib/Transforms/Scalar/GVN.cpp b/llvm/lib/Transforms/Scalar/GVN.cpp
index c069c1600a79b..0fa18e86b2bab 100644
--- a/llvm/lib/Transforms/Scalar/GVN.cpp
+++ b/llvm/lib/Transforms/Scalar/GVN.cpp
@@ -72,6 +72,7 @@
#include "llvm/Transforms/Utils/AssumeBundleBuilder.h"
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
#include "llvm/Transforms/Utils/Local.h"
+#include "llvm/Transforms/Utils/LoopUtils.h"
#include "llvm/Transforms/Utils/SSAUpdater.h"
#include "llvm/Transforms/Utils/VNCoercion.h"
#include <algorithm>
@@ -138,6 +139,10 @@ static cl::opt<uint32_t> MaxNumInsnsPerBlock(
cl::desc("Max number of instructions to scan in each basic block in GVN "
"(default = 100)"));
+static cl::opt<bool> GVNEnableMinFindingSelectPattern(
+ "gvn-enable-min-finding-select-pattern", cl::init(false), cl::Hidden,
+ cl::desc("Enable GVN transformation for minimum finding select pattern"));
+
struct llvm::GVNPass::Expression {
uint32_t Opcode;
bool Commutative = false;
@@ -2747,6 +2752,11 @@ bool GVNPass::processInstruction(Instruction *I) {
}
return Changed;
}
+ if (SelectInst *Select = dyn_cast<SelectInst>(I)) {
+ if (GVNEnableMinFindingSelectPattern &&
+ recognizeMinFindingSelectPattern(Select))
+ return true;
+ }
// Instructions with void type don't return a value, so there's
// no point in trying to find redundancies in them.
@@ -3333,6 +3343,430 @@ void GVNPass::assignValNumForDeadCode() {
}
}
+/// Return true if no instruction in the loop may write the load's
+/// memory location after the load executes. The transform memoizes the
+/// load's value across iterations, so any post-load mod in any loop
+/// block becomes a backedge clobber that invalidates the memoization.
+/// Scan every loop block; on the load's own block start from the next
+/// instruction, otherwise from the block's start.
+static bool canHoistLoadWithAA(Loop *L, LoadInst *Load, AAResults *AA) {
+ MemoryLocation Loc = MemoryLocation::get(Load);
+ for (BasicBlock *BB : L->blocks()) {
+ BasicBlock::iterator It =
+ BB == Load->getParent() ? std::next(Load->getIterator()) : BB->begin();
+ for (auto E = BB->end(); It != E; ++It) {
+ Instruction &I = *It;
+ if (!I.mayWriteToMemory())
+ continue;
+ if (isModSet(AA->getModRefInfo(&I, Loc))) {
+ LLVM_DEBUG(dbgs() << "GVN (minindx): Cannot hoist - clobbered in "
+ "loop by "
+ << I << "\n");
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+/// Hoist the chain of operations for the second load to preheader.
+/// In this transformation, we hoist the redundant load to the preheader,
+/// caching the first value of the iteration. This value is used to compare with
+/// the current value of the iteration and update the minimum value.
+/// The comparison is done in the loop body using the new select instruction.
+///
+/// *** Before transformation ***
+///
+/// preheader:
+/// ...
+/// loop:
+/// ...
+/// ...
+/// %val.first = load <TYPE>, ptr %ptr.first.load, align 4
+/// %min.idx.ext = sext i32 %min.idx to i64
+/// %ptr.<TYPE>.min = getelementptr <TYPE>, ptr %0, i64 %min.idx.ext
+/// %ptr.second.load = getelementptr i8, ptr %ptr.<TYPE>.min, i64 -4
+/// %val.current.min = load <TYPE>, ptr %ptr.second.load, align 4
+/// ...
+/// ...
+/// br i1 %cond, label %loop, label %exit
+///
+/// We capture <TYPE> as a part of pattern matching and then later
+/// use it in the transformation.
+///
+/// *** After transformation ***
+///
+/// preheader:
+/// %min.idx.ext = sext i32 %min.idx.ext to i64
+/// %hoist_gep1 = getelementptr <TYPE>, ptr %0, i64 %min.idx.ext
+/// %hoist_gep2 = getelementptr i8, ptr %hoist_gep1, i64 -4
+/// %hoisted_load = load <TYPE>, ptr %hoist_gep2, align 4
+/// br label %loop
+///
+/// loop:
+/// %val.first = load <TYPE>, ptr %ptr.first.load, align 4
+/// ...
+/// (new) %val.current.min = select i1 %cond, <TYPE> %hoisted_load, <TYPE>
+/// %val.current.min
+/// ...
+/// ...
+/// br i1 %cond, label %loop, label %exit
+bool GVNPass::transformMinFindingSelectPattern(
+ Loop *L, Type *LoadType, BasicBlock *Preheader, BasicBlock *BB,
+ CmpInst *Comparison, SelectInst *Select, Value *BasePtr,
+ PHINode *IndexValPhi, Value *OffsetVal) {
+
+ assert(BasePtr && "BasePtr is null");
+ assert(OffsetVal && "OffsetVal is null");
+ assert(IndexValPhi && "IndexValPhi is null");
+
+ // The recognizer canonicalizes the matched (hoistable) load to operand(1)
+ // of the compare. Capture both operands now, before we mutate the compare
+ // below.
+ Value *LHS = Comparison->getOperand(0);
+ Value *LoadVal = Comparison->getOperand(1);
+
+ // The transform memoizes the hoistable load's value across iterations, so
+ // any in-loop store or call that may write the load's location is a
+ // backedge clobber that invalidates the cache. With MSSA, use sink-mode
+ // invalidation (walks every MemoryDef in the loop with phi-translation).
+ // Without MSSA, walk the loop's instructions with AA.
+ auto *HoistableLoad = cast<LoadInst>(LoadVal);
+ bool CanHoist;
+ if (MSSAU) {
+ MemorySSA &MSSA = *MSSAU->getMemorySSA();
+ SinkAndHoistLICMFlags Flags(/*IsSink=*/true, *L, MSSA);
+ CanHoist = canHoistLoad(*HoistableLoad, VN.getAliasAnalysis(), DT, L, MSSA,
+ /*TargetExecutesOncePerLoop=*/true, Flags, ORE);
+ } else {
+ CanHoist = canHoistLoadWithAA(L, HoistableLoad, VN.getAliasAnalysis());
+ }
+ if (!CanHoist) {
+ LLVM_DEBUG(dbgs() << "GVN: Cannot hoist - may be clobbered by some "
+ "instruction in the loop.\n");
+ return false;
+ }
+
+ IRBuilder<> Builder(Preheader->getTerminator());
+ Value *InitialMinIndex = IndexValPhi->getIncomingValueForBlock(Preheader);
+
+ // Insert PHI node at the top of this block.
+ // This PHI node will be used to memoize the current minimum value so far.
+ PHINode *KnownMinPhi = PHINode::Create(LoadType, 2, "known_min", BB->begin());
+
+ // Hoist the load and build the necessary operations.
+ // 1. hoist_0 = sext i32 1 to i64
+ Value *HoistedSExt =
+ Builder.CreateSExt(InitialMinIndex, Builder.getInt64Ty(), "hoist_sext");
+
+ // 2. hoist_gep1 = getelementptr float, ptr BasePtr, i64 HoistedSExt
+ Value *HoistedGEP1 =
+ Builder.CreateGEP(LoadType, BasePtr, HoistedSExt, "hoist_gep1");
+
+ // 3. hoist_gep2 = getelementptr i8, ptr HoistedGEP1, i64 OffsetVal
+ Value *HoistedGEP2 = Builder.CreateGEP(Builder.getInt8Ty(), HoistedGEP1,
+ OffsetVal, "hoist_gep2");
+
+ // 4. hoisted_load = load float, ptr HoistedGEP2
+ // Preserve alignment and metadata from the original load; IRBuilder's
+ // natural alignment may exceed what the user declared, which would
+ // fault on strict-alignment targets at runtime.
+ LoadInst *OrigLoad = cast<LoadInst>(LoadVal);
+ LoadInst *NewLoad = Builder.CreateLoad(LoadType, HoistedGEP2, "hoisted_load");
+ NewLoad->setAlignment(OrigLoad->getAlign());
+ copyMetadataForLoad(*NewLoad, *OrigLoad);
+
+ // Update MemorySSA before erasing the original load.
+ if (MSSAU) {
+ auto *OrigUse =
+ MSSAU->getMemorySSA()->getMemoryAccess(dyn_cast<Instruction>(LoadVal));
+ if (OrigUse) {
+ MemoryAccess *DefiningAccess = OrigUse->getDefiningAccess();
+ MSSAU->createMemoryAccessInBB(NewLoad, DefiningAccess, Preheader,
+ MemorySSA::BeforeTerminator);
+ MSSAU->removeMemoryAccess(OrigUse);
+ }
+ }
+
+ // Invalidate MD cache for the loaded pointer; we added a new load and removed
+ // the old one (removeInstruction handles removing the old load from MD).
+ if (MD)
+ MD->invalidateCachedPointerInfo(NewLoad->getPointerOperand());
+
+ // Let the new load now take the place of the old load.
+ LoadVal->replaceAllUsesWith(NewLoad);
+ Instruction *LoadInst = dyn_cast<Instruction>(LoadVal);
+ if (uint32_t ValNo = VN.lookup(LoadInst, false))
+ LeaderTable.erase(ValNo, LoadInst, LoadInst->getParent());
+ removeInstruction(LoadInst);
+
+ // Comparison should now compare the current value and the newly inserted
+ // PHI node.
+ Comparison->setOperand(1, KnownMinPhi);
+
+ // Create new select instruction for selecting the minimum value.
+ IRBuilder<> SelectBuilder(BB->getTerminator());
+ SelectInst *CurrentMinSelect = dyn_cast<SelectInst>(
+ SelectBuilder.CreateSelect(Comparison, LHS, KnownMinPhi, "current_min"));
+
+ // Populate the newly created PHI node
+ // with (hoisted) NewLoad from the preheader and CurrentMinSelect.
+ KnownMinPhi->addIncoming(NewLoad, Preheader);
+ KnownMinPhi->addIncoming(CurrentMinSelect, BB);
+ LLVM_DEBUG(
+ dbgs() << "GVN: Transformed the code for minimum finding pattern.\n");
+ return true;
+}
+
+/// We are looking for the following pattern:
+/// loop:
+/// ...
+/// ...
+/// %min.idx = phi i32 [ %initial_min_idx, %entry ], [ %min.idx.next, %loop ]
+/// ...
+/// %val.first = load <TYPE>, ptr %ptr.first.load, align 4
+/// %min.idx.ext = sext i32 %min.idx to i64
+/// %ptr.<TYPE>.min = getelementptr <TYPE>, ptr %0, i64 %min.idx.ext
+/// %ptr.second.load = getelementptr i8, ptr %ptr.<TYPE>.min, i64 -4
+/// %val.current.min = load <TYPE>, ptr %ptr.second.load, align 4
+/// %cmp = <CMP_INST> <TYPE> %val.first, %val.current.min
+/// ...
+/// %min.idx.next = select i1 %cmp, ..., i32 %min.idx
+/// ...
+/// ...
+/// br i1 ..., label %loop, ...
+///
+/// Any compare predicate is accepted, and %val.current.min may appear on
+/// either side of the compare. The transform memoizes one operand of the
+/// compare in a loop-carried PHI, which is sound regardless of whether the
+/// pattern computes a min, a max, or an equality check.
+bool GVNPass::recognizeMinFindingSelectPattern(SelectInst *Select) {
+ BasicBlock *BB = Select->getParent();
+
+ // Phase 1: Do trivial checks: loop / select / compare structural checks.
+ // Confirm BB is in a loop with a preheader, the select's condition is a
+ // compare, and both compare operands are loads of the same scalar type.
+ // On success: L, Preheader, Comparison, LoadType are populated.
+
+ // If the block is not in a loop, bail out.
+ Loop *L = LI->getLoopFor(BB);
+ if (!L)
+ return false;
+
+ // If preheader of the loop is not found, bail out.
+ BasicBlock *Preheader = L->getLoopPreheader();
+ if (!Preheader) {
+ LLVM_DEBUG(dbgs() << "GVN (minindx): Could not find loop preheader.\n");
+ return false;
+ }
+
+ // The transform plants a 2-input PHI in BB with incomings from Preheader
+ // and BB, so it is well-formed only for a single-block self-loop where
+ // BB is both header and latch.
+ if (BB != L->getHeader() || BB != L->getLoopLatch()) {
+ LLVM_DEBUG(dbgs() << "GVN (minindx): select's block is not a single-block "
+ "self-loop (header == latch).\n");
+ return false;
+ }
+
+ Value *Condition = Select->getCondition();
+ CmpInst *Comparison = dyn_cast<CmpInst>(Condition);
+ if (!Comparison) {
+ LLVM_DEBUG(dbgs() << "GVN (minindx): Condition is not a comparison.\n");
+ return false;
+ }
+
+ // Both compare operands must be loads of the same type. The predicate
+ // itself is irrelevant: the rewrite memoizes one operand of the compare
+ // across iterations, and that is sound for any predicate.
+ Value *Op0 = Comparison->getOperand(0);
+ Value *Op1 = Comparison->getOperand(1);
+ if (!isa<LoadInst>(Op0) || !isa<LoadInst>(Op1)) {
+ LLVM_DEBUG(dbgs() << "GVN (minindx): Not both operands are loads.\n");
+ return false;
+ }
+ if (Op0->getType() != Op1->getType()) {
+ LLVM_DEBUG(
+ dbgs() << "GVN (minindx): Not both loads are of the same type.\n");
+ return false;
+ }
+ // The transform memoizes the hoistable load's value across iterations,
+ // dropping per-iteration freshness against concurrent writes. That is
+ // unsound for any non-simple (atomic or volatile) load.
+ if (!cast<LoadInst>(Op0)->isSimple() || !cast<LoadInst>(Op1)->isSimple()) {
+ LLVM_DEBUG(dbgs() << "GVN (minindx): atomic/volatile load operand.\n");
+ return false;
+ }
+ Type *LoadType = Op0->getType();
+
+ // Phase 2: Match the hoistable (RHS) load and canonicalize the compare.
+ // The rewrite needs one of the loads to be a typed GEP indexed by a
+ // sext'd phi (the recurrence of the candidate min-index). Try Op1 first
+ // to preserve the IR shape; if only Op0 matches, swapOperands() flips
+ // the predicate and moves the matched load to operand(1).
+ // On success: TypedGEP, RHSOffsetAP, IndexValPhi, Base are populated.
+ GetElementPtrInst *TypedGEP = nullptr;
+ APInt RHSOffsetAP;
+ PHINode *IndexValPhi = nullptr;
+ auto MatchHoistableLoad = [&](Value *LoadOp) -> bool {
+ Value *InnerGEP;
+ const APInt *OffsetAPInt;
+ if (!match(LoadOp,
+ m_Load(m_PtrAdd(m_Value(InnerGEP), m_APInt(OffsetAPInt)))))
+ return false;
+ auto *GEP = dyn_cast<GetElementPtrInst>(InnerGEP);
+ if (!GEP || GEP->getSourceElementType() != LoadType)
+ return false;
+ auto *SEInst = dyn_cast<SExtInst>(GEP->getOperand(1));
+ if (!SEInst)
+ return false;
+ if (!SEInst->getType()->isIntegerTy(64) ||
+ !SEInst->getOperand(0)->getType()->isIntegerTy(32))
+ return false;
+ auto *Phi = dyn_cast<PHINode>(SEInst->getOperand(0));
+ if (!Phi)
+ return false;
+ // The transform uses IndexValPhi->getIncomingValueForBlock(Preheader)
+ // and Phi nodes only have incoming entries for their parent's
+ // predecessors, so the Phi must live in BB (which has Preheader as a
+ // predecessor by Gate-3).
+ if (Phi->getParent() != BB)
+ return false;
+ TypedGEP = GEP;
+ RHSOffsetAP = *OffsetAPInt;
+ IndexValPhi = Phi;
+ return true;
+ };
+
+ if (!MatchHoistableLoad(Op1)) {
+ if (!MatchHoistableLoad(Op0)) {
+ LLVM_DEBUG(dbgs() << "GVN (minindx): No operand matches hoistable load "
+ "pattern.\n");
+ return false;
+ }
+ Comparison->swapOperands();
+ }
+ Value *Base = TypedGEP->getPointerOperand();
+
+ // RAUW with the hoisted value would corrupt any extra in-loop use of
+ // the per-iteration load; require the load to feed only the compare.
+ auto *HoistableLoad = cast<LoadInst>(Comparison->getOperand(1));
+ if (!HoistableLoad->hasOneUse()) {
+ LLVM_DEBUG(dbgs() << "GVN (minindx): hoistable load has extra uses.\n");
+ return false;
+ }
+
+ // The transform builds a preheader GEP from Base; Base must dominate
+ // the preheader (i.e. be loop-invariant) for the result to verify.
+ if (!L->isLoopInvariant(Base)) {
+ LLVM_DEBUG(dbgs() << "GVN (minindx): Base is not loop-invariant.\n");
+ return false;
+ }
+
+ // Phase 3: Match the LHS load shape.
+ // Extract %inner and an optional byte offset C1 from the LHS load, then
+ // verify %inner is a typed `getelementptr T, Base, IV` sharing the same
+ // base pointer as the RHS load and using a single index.
+ // On success: LHSGEP, LHSIV, C1AP are populated.
+ Value *LHS = Comparison->getOperand(0);
+ Value *LHSInnerGEP;
+ const APInt *C1AP = nullptr;
+ // Accept either of these two shapes for the LHS load:
+ // 1) load T, ptr (getelementptr i8, %inner, C1) -- byte offset C1 present
+ // 2) load T, ptr %inner -- no byte offset, C1 = 0
+ // %inner is the typed `getelementptr T, base, IV` checked just below.
+ if (!match(LHS, m_Load(m_PtrAdd(m_Value(LHSInnerGEP), m_APInt(C1AP)))) &&
+ !match(LHS, m_Load(m_Value(LHSInnerGEP)))) {
+ LLVM_DEBUG(dbgs() << "GVN (minindx): LHS load shape unsupported.\n");
+ return false;
+ }
+ // %inner (captured above) must be a typed `getelementptr T, base, IV`:
+ // - element type T matches the load type,
+ // - base pointer is the same array as the hoistable load,
+ // - exactly one index (IV).
+ auto *LHSGEP = dyn_cast<GetElementPtrInst>(LHSInnerGEP);
+ if (!LHSGEP || LHSGEP->getSourceElementType() != LoadType ||
+ LHSGEP->getPointerOperand() != Base || LHSGEP->getNumIndices() != 1) {
+ LLVM_DEBUG(dbgs() << "GVN (minindx): LHS GEP shape unsupported.\n");
+ return false;
+ }
+ Value *LHSIV = LHSGEP->getOperand(1);
+
+ // Phase 4: Match the new-index arm of the Select.
+ // One of the Select operands must be trunc(add LHSIV, KStep) with a
+ // constant KStep -- this is the IV update produced when the compare
+ // selects the new minimum index.
+ // On success: KStepAP is populated.
+ const APInt *KStepAP = nullptr;
+ auto MatchNewIdx = [&](Value *SelOp) {
+ Value *IVNext;
+ return match(SelOp, m_Trunc(m_Value(IVNext))) &&
+ match(IVNext, m_Add(m_Specific(LHSIV), m_APInt(KStepAP)));
+ };
+ // The transform plants `select Cmp, LHS, KnownMin`, which assumes the
+ // new index is on the Select's true arm; reject the mirrored polarity.
+ Value *OtherArm = nullptr;
+ if (MatchNewIdx(Select->getTrueValue())) {
+ OtherArm = Select->getFalseValue();
+ } else {
+ LLVM_DEBUG(dbgs() << "GVN (minindx): select's new-index arm is not the "
+ "true arm.\n");
+ return false;
+ }
+
+ // The transform's "cmp-false keeps known_min" assumes the Select's
+ // non-new-index arm is IndexValPhi itself.
+ if (OtherArm != IndexValPhi) {
+ LLVM_DEBUG(dbgs() << "GVN (minindx): select's keep arm is not "
+ "IndexValPhi.\n");
+ return false;
+ }
+
+ // The algebraic gate below assumes next iter's IndexValPhi takes the
+ // Select's result; enforce that here.
+ if (IndexValPhi->getIncomingValueForBlock(BB) != Select) {
+ LLVM_DEBUG(dbgs() << "GVN (minindx): IndexValPhi back-edge is not the "
+ "select.\n");
+ return false;
+ }
+
+ // Phase 6: Algebraic soundness gate.
+ // The transform is sound only when, on the cmp-true arm, the next
+ // iteration's hoistable load reads from the same address this
+ // iteration's LHS load read. Otherwise propagating the LHS value as
+ // known_min would change the value seen next iteration.
+ //
+ // Both load addresses look like `Base + sizeof(T) * <iv> + <byte off>`.
+ // With C1 / C2 the byte offsets on the LHS / hoistable GEPs and KStep
+ // the addend in `iv.next = add iv, KStep`, the address-equality reduces
+ // to:
+ // C1 - C2 == sizeof(T) * KStep.
+ // Evaluate that in pointer-index width.
+ const DataLayout &DL = Select->getDataLayout();
+ unsigned PtrBits = DL.getIndexTypeSizeInBits(Base->getType());
+ APInt C1 = C1AP ? C1AP->sextOrTrunc(PtrBits) : APInt::getZero(PtrBits);
+ APInt C2 = RHSOffsetAP.sextOrTrunc(PtrBits);
+ APInt KStep = KStepAP->sextOrTrunc(PtrBits);
+ APInt ElemBytes(PtrBits, DL.getTypeAllocSize(LoadType).getFixedValue());
+ APInt LHSDelta = C1 - C2, Stride = ElemBytes * KStep;
+ if (LHSDelta != Stride) {
+ LLVM_DEBUG(dbgs() << "GVN (minindx): offset/step relationship violated. "
+ << "C1=" << C1 << " C2=" << C2 << " KStep=" << KStep
+ << " sizeof(T)=" << ElemBytes
+ << "; require C1 - C2 == sizeof(T) * KStep, got "
+ << LHSDelta << " != " << Stride << ".\n");
+ return false;
+ }
+
+ LLVM_DEBUG(dbgs() << "GVN: Found minimum finding pattern in Block: "
+ << Select->getParent()->getName() << ".\n");
+
+ Value *OffsetVal = ConstantInt::get(Select->getContext(), RHSOffsetAP);
+ return transformMinFindingSelectPattern(L, LoadType, Preheader, BB,
+ Comparison, Select, Base, IndexValPhi,
+ OffsetVal);
+}
+
class llvm::gvn::GVNLegacyPass : public FunctionPass {
public:
static char ID; // Pass identification, replacement for typeid.
diff --git a/llvm/test/Transforms/GVN/PRE/gvn-min-pre-basic.ll b/llvm/test/Transforms/GVN/PRE/gvn-min-pre-basic.ll
new file mode 100644
index 0000000000000..1d222d3e2e809
--- /dev/null
+++ b/llvm/test/Transforms/GVN/PRE/gvn-min-pre-basic.ll
@@ -0,0 +1,236 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -gvn-enable-min-finding-select-pattern -passes=gvn -S < %s | FileCheck %s
+
+; Test the minimum finding pattern.
+; The following test case is extracted from rnflow app in Polyhedron benchmark suite.
+define void @test_gvn_min_pattern(ptr %0, i32 %initial_min_idx) {
+; CHECK-LABEL: define void @test_gvn_min_pattern(
+; CHECK-SAME: ptr [[TMP0:%.*]], i32 [[INITIAL_MIN_IDX:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[HOIST_SEXT:%.*]] = sext i32 [[INITIAL_MIN_IDX]] to i64
+; CHECK-NEXT: [[HOIST_GEP1:%.*]] = getelementptr float, ptr [[TMP0]], i64 [[HOIST_SEXT]]
+; CHECK-NEXT: [[HOIST_GEP2:%.*]] = getelementptr i8, ptr [[HOIST_GEP1]], i64 -4
+; CHECK-NEXT: [[HOISTED_LOAD:%.*]] = load float, ptr [[HOIST_GEP2]], align 4
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[KNOWN_MIN:%.*]] = phi float [ [[HOISTED_LOAD]], %[[ENTRY]] ], [ [[CURRENT_MIN:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[INDVARS_IV_I:%.*]] = phi i64 [ 1, %[[ENTRY]] ], [ [[INDVARS_IV_NEXT_I:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[LOOP_COUNTER:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[LOOP_COUNTER_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[MIN_IDX:%.*]] = phi i32 [ [[INITIAL_MIN_IDX]], %[[ENTRY]] ], [ [[MIN_IDX_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[INDVARS_IV_NEXT_I]] = add nsw i64 [[INDVARS_IV_I]], -1
+; CHECK-NEXT: [[PTR_FLOAT_IV:%.*]] = getelementptr float, ptr [[TMP0]], i64 [[INDVARS_IV_I]]
+; CHECK-NEXT: [[PTR_FIRST_LOAD:%.*]] = getelementptr i8, ptr [[PTR_FLOAT_IV]], i64 -8
+; CHECK-NEXT: [[VAL_FIRST:%.*]] = load float, ptr [[PTR_FIRST_LOAD]], align 4
+; CHECK-NEXT: [[MIN_IDX_EXT:%.*]] = sext i32 [[MIN_IDX]] to i64
+; CHECK-NEXT: [[PTR_FLOAT_MIN:%.*]] = getelementptr float, ptr [[TMP0]], i64 [[MIN_IDX_EXT]]
+; CHECK-NEXT: [[PTR_SECOND_LOAD:%.*]] = getelementptr i8, ptr [[PTR_FLOAT_MIN]], i64 -4
+; CHECK-NEXT: [[CMP:%.*]] = fcmp contract olt float [[VAL_FIRST]], [[KNOWN_MIN]]
+; CHECK-NEXT: [[NEXT_IDX_TRUNC:%.*]] = trunc nsw i64 [[INDVARS_IV_NEXT_I]] to i32
+; CHECK-NEXT: [[MIN_IDX_NEXT]] = select i1 [[CMP]], i32 [[NEXT_IDX_TRUNC]], i32 [[MIN_IDX]]
+; CHECK-NEXT: [[LOOP_COUNTER_NEXT]] = add nsw i64 [[LOOP_COUNTER]], -1
+; CHECK-NEXT: [[LOOP_CONTINUE:%.*]] = icmp samesign ugt i64 [[LOOP_COUNTER]], 1
+; CHECK-NEXT: [[CURRENT_MIN]] = select i1 [[CMP]], float [[VAL_FIRST]], float [[KNOWN_MIN]]
+; CHECK-NEXT: br i1 [[LOOP_CONTINUE]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop: ; preds = %loop, %entry
+ %indvars.iv.i = phi i64 [ 1, %entry ], [ %indvars.iv.next.i, %loop ]
+ %loop.counter = phi i64 [ 0, %entry ], [ %loop.counter.next, %loop ]
+ %min.idx = phi i32 [ %initial_min_idx, %entry ], [ %min.idx.next, %loop ]
+ %indvars.iv.next.i = add nsw i64 %indvars.iv.i, -1
+ %ptr.float.iv = getelementptr float, ptr %0, i64 %indvars.iv.i
+ %ptr.first.load = getelementptr i8, ptr %ptr.float.iv, i64 -8
+ %val.first = load float, ptr %ptr.first.load, align 4
+ %min.idx.ext = sext i32 %min.idx to i64
+ %ptr.float.min = getelementptr float, ptr %0, i64 %min.idx.ext
+ %ptr.second.load = getelementptr i8, ptr %ptr.float.min, i64 -4
+ %val.current.min = load float, ptr %ptr.second.load, align 4
+ %cmp = fcmp contract olt float %val.first, %val.current.min
+ %next.idx.trunc = trunc nsw i64 %indvars.iv.next.i to i32
+ %min.idx.next = select i1 %cmp, i32 %next.idx.trunc, i32 %min.idx
+ %loop.counter.next = add nsw i64 %loop.counter, -1
+ %loop.continue = icmp samesign ugt i64 %loop.counter, 1
+ br i1 %loop.continue, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+; Positive test: Minimum finding pattern with i32 loads.
+define void @test_gvn_min_pattern_i32(ptr %arr, i32 %initial_min_idx) {
+; CHECK-LABEL: define void @test_gvn_min_pattern_i32(
+; CHECK-SAME: ptr [[ARR:%.*]], i32 [[INITIAL_MIN_IDX:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[HOIST_SEXT:%.*]] = sext i32 [[INITIAL_MIN_IDX]] to i64
+; CHECK-NEXT: [[HOIST_GEP1:%.*]] = getelementptr i32, ptr [[ARR]], i64 [[HOIST_SEXT]]
+; CHECK-NEXT: [[HOIST_GEP2:%.*]] = getelementptr i8, ptr [[HOIST_GEP1]], i64 -4
+; CHECK-NEXT: [[HOISTED_LOAD:%.*]] = load i32, ptr [[HOIST_GEP2]], align 4
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[KNOWN_MIN:%.*]] = phi i32 [ [[HOISTED_LOAD]], %[[ENTRY]] ], [ [[CURRENT_MIN:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[INDVARS_IV_I:%.*]] = phi i64 [ 1, %[[ENTRY]] ], [ [[INDVARS_IV_NEXT_I:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[LOOP_COUNTER:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[LOOP_COUNTER_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[MIN_IDX:%.*]] = phi i32 [ [[INITIAL_MIN_IDX]], %[[ENTRY]] ], [ [[MIN_IDX_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[INDVARS_IV_NEXT_I]] = add nsw i64 [[INDVARS_IV_I]], -1
+; CHECK-NEXT: [[PTR_I32_IV:%.*]] = getelementptr i32, ptr [[ARR]], i64 [[INDVARS_IV_I]]
+; CHECK-NEXT: [[PTR_FIRST_LOAD:%.*]] = getelementptr i8, ptr [[PTR_I32_IV]], i64 -8
+; CHECK-NEXT: [[VAL_FIRST:%.*]] = load i32, ptr [[PTR_FIRST_LOAD]], align 4
+; CHECK-NEXT: [[MIN_IDX_EXT:%.*]] = sext i32 [[MIN_IDX]] to i64
+; CHECK-NEXT: [[PTR_I32_MIN:%.*]] = getelementptr i32, ptr [[ARR]], i64 [[MIN_IDX_EXT]]
+; CHECK-NEXT: [[PTR_SECOND_LOAD:%.*]] = getelementptr i8, ptr [[PTR_I32_MIN]], i64 -4
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i32 [[VAL_FIRST]], [[KNOWN_MIN]]
+; CHECK-NEXT: [[NEXT_IDX_TRUNC:%.*]] = trunc nsw i64 [[INDVARS_IV_NEXT_I]] to i32
+; CHECK-NEXT: [[MIN_IDX_NEXT]] = select i1 [[CMP]], i32 [[NEXT_IDX_TRUNC]], i32 [[MIN_IDX]]
+; CHECK-NEXT: [[LOOP_COUNTER_NEXT]] = add nsw i64 [[LOOP_COUNTER]], -1
+; CHECK-NEXT: [[LOOP_CONTINUE:%.*]] = icmp samesign ugt i64 [[LOOP_COUNTER]], 1
+; CHECK-NEXT: [[CURRENT_MIN]] = select i1 [[CMP]], i32 [[VAL_FIRST]], i32 [[KNOWN_MIN]]
+; CHECK-NEXT: br i1 [[LOOP_CONTINUE]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop: ; preds = %loop, %entry
+ %indvars.iv.i = phi i64 [ 1, %entry ], [ %indvars.iv.next.i, %loop ]
+ %loop.counter = phi i64 [ 0, %entry ], [ %loop.counter.next, %loop ]
+ %min.idx = phi i32 [ %initial_min_idx, %entry ], [ %min.idx.next, %loop ]
+ %indvars.iv.next.i = add nsw i64 %indvars.iv.i, -1
+ %ptr.i32.iv = getelementptr i32, ptr %arr, i64 %indvars.iv.i
+ %ptr.first.load = getelementptr i8, ptr %ptr.i32.iv, i64 -8
+ %val.first = load i32, ptr %ptr.first.load, align 4
+ %min.idx.ext = sext i32 %min.idx to i64
+ %ptr.i32.min = getelementptr i32, ptr %arr, i64 %min.idx.ext
+ %ptr.second.load = getelementptr i8, ptr %ptr.i32.min, i64 -4
+ %val.current.min = load i32, ptr %ptr.second.load, align 4
+ %cmp = icmp slt i32 %val.first, %val.current.min
+ %next.idx.trunc = trunc nsw i64 %indvars.iv.next.i to i32
+ %min.idx.next = select i1 %cmp, i32 %next.idx.trunc, i32 %min.idx
+ %loop.counter.next = add nsw i64 %loop.counter, -1
+ %loop.continue = icmp samesign ugt i64 %loop.counter, 1
+ br i1 %loop.continue, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+; Positive test: Minimum finding pattern with i64 loads.
+define void @test_gvn_min_pattern_i64(ptr %0, i32 %initial_min_idx) {
+; CHECK-LABEL: define void @test_gvn_min_pattern_i64(
+; CHECK-SAME: ptr [[TMP0:%.*]], i32 [[INITIAL_MIN_IDX:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[HOIST_SEXT:%.*]] = sext i32 [[INITIAL_MIN_IDX]] to i64
+; CHECK-NEXT: [[HOIST_GEP1:%.*]] = getelementptr i64, ptr [[TMP0]], i64 [[HOIST_SEXT]]
+; CHECK-NEXT: [[HOIST_GEP2:%.*]] = getelementptr i8, ptr [[HOIST_GEP1]], i64 -8
+; CHECK-NEXT: [[HOISTED_LOAD:%.*]] = load i64, ptr [[HOIST_GEP2]], align 8
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[KNOWN_MIN:%.*]] = phi i64 [ [[HOISTED_LOAD]], %[[ENTRY]] ], [ [[CURRENT_MIN:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[INDVARS_IV_I:%.*]] = phi i64 [ 1, %[[ENTRY]] ], [ [[INDVARS_IV_NEXT_I:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[LOOP_COUNTER:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[LOOP_COUNTER_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[MIN_IDX:%.*]] = phi i32 [ [[INITIAL_MIN_IDX]], %[[ENTRY]] ], [ [[MIN_IDX_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[INDVARS_IV_NEXT_I]] = add nsw i64 [[INDVARS_IV_I]], -1
+; CHECK-NEXT: [[PTR_I64_IV:%.*]] = getelementptr i64, ptr [[TMP0]], i64 [[INDVARS_IV_I]]
+; CHECK-NEXT: [[PTR_FIRST_LOAD:%.*]] = getelementptr i8, ptr [[PTR_I64_IV]], i64 -16
+; CHECK-NEXT: [[VAL_FIRST:%.*]] = load i64, ptr [[PTR_FIRST_LOAD]], align 8
+; CHECK-NEXT: [[MIN_IDX_EXT:%.*]] = sext i32 [[MIN_IDX]] to i64
+; CHECK-NEXT: [[PTR_I64_MIN:%.*]] = getelementptr i64, ptr [[TMP0]], i64 [[MIN_IDX_EXT]]
+; CHECK-NEXT: [[PTR_SECOND_LOAD:%.*]] = getelementptr i8, ptr [[PTR_I64_MIN]], i64 -8
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i64 [[VAL_FIRST]], [[KNOWN_MIN]]
+; CHECK-NEXT: [[NEXT_IDX_TRUNC:%.*]] = trunc nsw i64 [[INDVARS_IV_NEXT_I]] to i32
+; CHECK-NEXT: [[MIN_IDX_NEXT]] = select i1 [[CMP]], i32 [[NEXT_IDX_TRUNC]], i32 [[MIN_IDX]]
+; CHECK-NEXT: [[LOOP_COUNTER_NEXT]] = add nsw i64 [[LOOP_COUNTER]], -1
+; CHECK-NEXT: [[LOOP_CONTINUE:%.*]] = icmp samesign ugt i64 [[LOOP_COUNTER]], 1
+; CHECK-NEXT: [[CURRENT_MIN]] = select i1 [[CMP]], i64 [[VAL_FIRST]], i64 [[KNOWN_MIN]]
+; CHECK-NEXT: br i1 [[LOOP_CONTINUE]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop: ; preds = %loop, %entry
+ %indvars.iv.i = phi i64 [ 1, %entry ], [ %indvars.iv.next.i, %loop ]
+ %loop.counter = phi i64 [ 0, %entry ], [ %loop.counter.next, %loop ]
+ %min.idx = phi i32 [ %initial_min_idx, %entry ], [ %min.idx.next, %loop ]
+ %indvars.iv.next.i = add nsw i64 %indvars.iv.i, -1
+ %ptr.i64.iv = getelementptr i64, ptr %0, i64 %indvars.iv.i
+ %ptr.first.load = getelementptr i8, ptr %ptr.i64.iv, i64 -16
+ %val.first = load i64, ptr %ptr.first.load, align 8
+ %min.idx.ext = sext i32 %min.idx to i64
+ %ptr.i64.min = getelementptr i64, ptr %0, i64 %min.idx.ext
+ %ptr.second.load = getelementptr i8, ptr %ptr.i64.min, i64 -8
+ %val.current.min = load i64, ptr %ptr.second.load, align 8
+ %cmp = icmp slt i64 %val.first, %val.current.min
+ %next.idx.trunc = trunc nsw i64 %indvars.iv.next.i to i32
+ %min.idx.next = select i1 %cmp, i32 %next.idx.trunc, i32 %min.idx
+ %loop.counter.next = add nsw i64 %loop.counter, -1
+ %loop.continue = icmp samesign ugt i64 %loop.counter, 1
+ br i1 %loop.continue, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+; Positive test: the hoisted load preserves the user-declared
+; alignment of the original load (align 1 on a float load).
+define void @test_gvn_min_pattern_preserves_align(ptr %0, i32 %initial_min_idx) {
+; CHECK-LABEL: define void @test_gvn_min_pattern_preserves_align(
+; CHECK-SAME: ptr [[TMP0:%.*]], i32 [[INITIAL_MIN_IDX:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[HOIST_SEXT:%.*]] = sext i32 [[INITIAL_MIN_IDX]] to i64
+; CHECK-NEXT: [[HOIST_GEP1:%.*]] = getelementptr float, ptr [[TMP0]], i64 [[HOIST_SEXT]]
+; CHECK-NEXT: [[HOIST_GEP2:%.*]] = getelementptr i8, ptr [[HOIST_GEP1]], i64 -4
+; CHECK-NEXT: [[HOISTED_LOAD:%.*]] = load float, ptr [[HOIST_GEP2]], align 1
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[KNOWN_MIN:%.*]] = phi float [ [[HOISTED_LOAD]], %[[ENTRY]] ], [ [[CURRENT_MIN:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[LOOP_COUNTER:%.*]] = phi i64 [ 3, %[[ENTRY]] ], [ [[LOOP_COUNTER_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[INDVARS_IV_I:%.*]] = phi i64 [ 3, %[[ENTRY]] ], [ [[INDVARS_IV_NEXT_I:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[MIN_IDX:%.*]] = phi i32 [ [[INITIAL_MIN_IDX]], %[[ENTRY]] ], [ [[MIN_IDX_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[INDVARS_IV_NEXT_I]] = add nsw i64 [[INDVARS_IV_I]], -1
+; CHECK-NEXT: [[PTR_IV:%.*]] = getelementptr float, ptr [[TMP0]], i64 [[INDVARS_IV_I]]
+; CHECK-NEXT: [[PTR_FIRST_LOAD:%.*]] = getelementptr i8, ptr [[PTR_IV]], i64 -8
+; CHECK-NEXT: [[VAL_FIRST:%.*]] = load float, ptr [[PTR_FIRST_LOAD]], align 1
+; CHECK-NEXT: [[MIN_IDX_EXT:%.*]] = sext i32 [[MIN_IDX]] to i64
+; CHECK-NEXT: [[PTR_TYPED:%.*]] = getelementptr float, ptr [[TMP0]], i64 [[MIN_IDX_EXT]]
+; CHECK-NEXT: [[PTR_SECOND_LOAD:%.*]] = getelementptr i8, ptr [[PTR_TYPED]], i64 -4
+; CHECK-NEXT: [[CMP:%.*]] = fcmp olt float [[VAL_FIRST]], [[KNOWN_MIN]]
+; CHECK-NEXT: [[NEXT_IDX_TRUNC:%.*]] = trunc nsw i64 [[INDVARS_IV_NEXT_I]] to i32
+; CHECK-NEXT: [[MIN_IDX_NEXT]] = select i1 [[CMP]], i32 [[NEXT_IDX_TRUNC]], i32 [[MIN_IDX]]
+; CHECK-NEXT: [[LOOP_COUNTER_NEXT]] = add nsw i64 [[LOOP_COUNTER]], -1
+; CHECK-NEXT: [[LOOP_CONTINUE:%.*]] = icmp samesign ugt i64 [[LOOP_COUNTER]], 1
+; CHECK-NEXT: [[CURRENT_MIN]] = select i1 [[CMP]], float [[VAL_FIRST]], float [[KNOWN_MIN]]
+; CHECK-NEXT: br i1 [[LOOP_CONTINUE]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %loop.counter = phi i64 [ 3, %entry ], [ %loop.counter.next, %loop ]
+ %indvars.iv.i = phi i64 [ 3, %entry ], [ %indvars.iv.next.i, %loop ]
+ %min.idx = phi i32 [ %initial_min_idx, %entry ], [ %min.idx.next, %loop ]
+ %indvars.iv.next.i = add nsw i64 %indvars.iv.i, -1
+ %ptr.iv = getelementptr float, ptr %0, i64 %indvars.iv.i
+ %ptr.first.load = getelementptr i8, ptr %ptr.iv, i64 -8
+ %val.first = load float, ptr %ptr.first.load, align 1
+ %min.idx.ext = sext i32 %min.idx to i64
+ %ptr.typed = getelementptr float, ptr %0, i64 %min.idx.ext
+ %ptr.second.load = getelementptr i8, ptr %ptr.typed, i64 -4
+ %val.current.min = load float, ptr %ptr.second.load, align 1
+ %cmp = fcmp olt float %val.first, %val.current.min
+ %next.idx.trunc = trunc nsw i64 %indvars.iv.next.i to i32
+ %min.idx.next = select i1 %cmp, i32 %next.idx.trunc, i32 %min.idx
+ %loop.counter.next = add nsw i64 %loop.counter, -1
+ %loop.continue = icmp samesign ugt i64 %loop.counter, 1
+ br i1 %loop.continue, label %loop, label %exit
+
+exit:
+ ret void
+}
diff --git a/llvm/test/Transforms/GVN/PRE/gvn-min-pre-negatives.ll b/llvm/test/Transforms/GVN/PRE/gvn-min-pre-negatives.ll
new file mode 100644
index 0000000000000..257957cb9d9f3
--- /dev/null
+++ b/llvm/test/Transforms/GVN/PRE/gvn-min-pre-negatives.ll
@@ -0,0 +1,1007 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -gvn-enable-min-finding-select-pattern -passes=gvn -S < %s | FileCheck %s
+
+declare void @external_function(ptr)
+
+; Negative test: Select not in a loop.
+define void @test_not_in_loop(ptr %arr) {
+; CHECK-LABEL: define void @test_not_in_loop(
+; CHECK-SAME: ptr [[ARR:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[LOAD1:%.*]] = load float, ptr [[ARR]], align 4
+; CHECK-NEXT: [[GEP2:%.*]] = getelementptr float, ptr [[ARR]], i64 1
+; CHECK-NEXT: [[LOAD2:%.*]] = load float, ptr [[GEP2]], align 4
+; CHECK-NEXT: [[CMP:%.*]] = fcmp olt float [[LOAD1]], [[LOAD2]]
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], float [[LOAD1]], float [[LOAD2]]
+; CHECK-NEXT: ret void
+;
+entry:
+ %gep1 = getelementptr float, ptr %arr, i32 0
+ %load1 = load float, ptr %gep1
+ %sext = sext i32 1 to i64
+ %gep2 = getelementptr float, ptr %arr, i64 %sext
+ %gep3 = getelementptr i8, ptr %gep2, i64 0
+ %load2 = load float, ptr %gep3
+ %cmp = fcmp olt float %load1, %load2
+ %sel = select i1 %cmp, float %load1, float %load2
+ ret void
+}
+
+; Negative test: Loop without preheader (multiple entry points).
+define void @test_no_preheader(ptr %arr, i32 %n, i1 %cond) {
+; CHECK-LABEL: define void @test_no_preheader(
+; CHECK-SAME: ptr [[ARR:%.*]], i32 [[N:%.*]], i1 [[COND:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br i1 [[COND]], label %[[LOOP:.*]], label %[[OTHER_ENTRY:.*]]
+; CHECK: [[OTHER_ENTRY]]:
+; CHECK-NEXT: br label %[[LOOP]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[I:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ 0, %[[OTHER_ENTRY]] ], [ [[I_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[GEP1:%.*]] = getelementptr float, ptr [[ARR]], i32 [[I]]
+; CHECK-NEXT: [[SEXT:%.*]] = sext i32 [[I]] to i64
+; CHECK-NEXT: [[GEP2:%.*]] = getelementptr float, ptr [[ARR]], i64 [[SEXT]]
+; CHECK-NEXT: [[I_NEXT]] = add i32 [[I]], 1
+; CHECK-NEXT: [[EXIT:%.*]] = icmp slt i32 [[I_NEXT]], [[N]]
+; CHECK-NEXT: br i1 [[EXIT]], label %[[LOOP]], label %[[EXIT_BLOCK:.*]]
+; CHECK: [[EXIT_BLOCK]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br i1 %cond, label %loop, label %other_entry
+
+other_entry:
+ br label %loop
+
+loop:
+ %i = phi i32 [ 0, %entry ], [ 0, %other_entry ], [ %i.next, %loop ]
+ %gep1 = getelementptr float, ptr %arr, i32 %i
+ %load1 = load float, ptr %gep1
+ %sext = sext i32 %i to i64
+ %gep2 = getelementptr float, ptr %arr, i64 %sext
+ %gep3 = getelementptr i8, ptr %gep2, i64 0
+ %load2 = load float, ptr %gep3
+ %cmp = fcmp olt float %load1, %load2
+ %sel = select i1 %cmp, float %load1, float %load2
+ %i.next = add i32 %i, 1
+ %exit = icmp slt i32 %i.next, %n
+ br i1 %exit, label %loop, label %exit_block
+
+exit_block:
+ ret void
+}
+
+; Negative test: Condition is not a CmpInst.
+define void @test_condition_not_cmp(ptr %arr, i32 %n, i1 %bool) {
+; CHECK-LABEL: define void @test_condition_not_cmp(
+; CHECK-SAME: ptr [[ARR:%.*]], i32 [[N:%.*]], i1 [[BOOL:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[I:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[I_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[GEP1:%.*]] = getelementptr float, ptr [[ARR]], i32 [[I]]
+; CHECK-NEXT: [[SEXT:%.*]] = sext i32 [[I]] to i64
+; CHECK-NEXT: [[GEP2:%.*]] = getelementptr float, ptr [[ARR]], i64 [[SEXT]]
+; CHECK-NEXT: [[I_NEXT]] = add i32 [[I]], 1
+; CHECK-NEXT: [[EXIT:%.*]] = icmp slt i32 [[I_NEXT]], [[N]]
+; CHECK-NEXT: br i1 [[EXIT]], label %[[LOOP]], label %[[EXIT_BLOCK:.*]]
+; CHECK: [[EXIT_BLOCK]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %i = phi i32 [ 0, %entry ], [ %i.next, %loop ]
+ %gep1 = getelementptr float, ptr %arr, i32 %i
+ %load1 = load float, ptr %gep1
+ %sext = sext i32 %i to i64
+ %gep2 = getelementptr float, ptr %arr, i64 %sext
+ %gep3 = getelementptr i8, ptr %gep2, i64 0
+ %load2 = load float, ptr %gep3
+ %sel = select i1 %bool, float %load1, float %load2
+ %i.next = add i32 %i, 1
+ %exit = icmp slt i32 %i.next, %n
+ br i1 %exit, label %loop, label %exit_block
+
+exit_block:
+ ret void
+}
+
+; Negative test: Not both operands are loads (constant RHS).
+define void @test_not_both_loads(ptr %arr, i32 %n) {
+; CHECK-LABEL: define void @test_not_both_loads(
+; CHECK-SAME: ptr [[ARR:%.*]], i32 [[N:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[I:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[I_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[GEP1:%.*]] = getelementptr float, ptr [[ARR]], i32 [[I]]
+; CHECK-NEXT: [[LOAD1:%.*]] = load float, ptr [[GEP1]], align 4
+; CHECK-NEXT: [[CMP:%.*]] = fcmp olt float [[LOAD1]], 0.000000e+00
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], float [[LOAD1]], float 0.000000e+00
+; CHECK-NEXT: [[I_NEXT]] = add i32 [[I]], 1
+; CHECK-NEXT: [[EXIT:%.*]] = icmp slt i32 [[I_NEXT]], [[N]]
+; CHECK-NEXT: br i1 [[EXIT]], label %[[LOOP]], label %[[EXIT_BLOCK:.*]]
+; CHECK: [[EXIT_BLOCK]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %i = phi i32 [ 0, %entry ], [ %i.next, %loop ]
+ %gep1 = getelementptr float, ptr %arr, i32 %i
+ %load1 = load float, ptr %gep1
+ %cmp = fcmp olt float %load1, 0.0
+ %sel = select i1 %cmp, float %load1, float 0.0
+ %i.next = add i32 %i, 1
+ %exit = icmp slt i32 %i.next, %n
+ br i1 %exit, label %loop, label %exit_block
+
+exit_block:
+ ret void
+}
+
+; Negative test: Load doesn't match GEP(GEP(...))nested pattern.
+define void @test_wrong_gep_pattern(ptr %arr, i32 %n) {
+; CHECK-LABEL: define void @test_wrong_gep_pattern(
+; CHECK-SAME: ptr [[ARR:%.*]], i32 [[N:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[I:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[I_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[GEP1:%.*]] = getelementptr float, ptr [[ARR]], i32 [[I]]
+; CHECK-NEXT: [[I_NEXT]] = add i32 [[I]], 1
+; CHECK-NEXT: [[EXIT:%.*]] = icmp slt i32 [[I_NEXT]], [[N]]
+; CHECK-NEXT: br i1 [[EXIT]], label %[[LOOP]], label %[[EXIT_BLOCK:.*]]
+; CHECK: [[EXIT_BLOCK]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %i = phi i32 [ 0, %entry ], [ %i.next, %loop ]
+ %gep1 = getelementptr float, ptr %arr, i32 %i
+ %load1 = load float, ptr %gep1
+ ; Simple GEP, not nested GEP(GEP(...))
+ %gep2 = getelementptr float, ptr %arr, i32 %i
+ %load2 = load float, ptr %gep2
+ %cmp = fcmp olt float %load1, %load2
+ %sel = select i1 %cmp, float %load1, float %load2
+ %i.next = add i32 %i, 1
+ %exit = icmp slt i32 %i.next, %n
+ br i1 %exit, label %loop, label %exit_block
+
+exit_block:
+ ret void
+}
+
+; Negative test: ZExt instead of SExt.
+define void @test_no_sext(ptr %arr, i32 %n) {
+; CHECK-LABEL: define void @test_no_sext(
+; CHECK-SAME: ptr [[ARR:%.*]], i32 [[N:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[I:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[I_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[GEP1:%.*]] = getelementptr float, ptr [[ARR]], i32 [[I]]
+; CHECK-NEXT: [[LOAD1:%.*]] = load float, ptr [[GEP1]], align 4
+; CHECK-NEXT: [[ZEXT:%.*]] = zext i32 [[I]] to i64
+; CHECK-NEXT: [[GEP2:%.*]] = getelementptr float, ptr [[ARR]], i64 [[ZEXT]]
+; CHECK-NEXT: [[LOAD2:%.*]] = load float, ptr [[GEP2]], align 4
+; CHECK-NEXT: [[CMP:%.*]] = fcmp olt float [[LOAD1]], [[LOAD2]]
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], float [[LOAD1]], float [[LOAD2]]
+; CHECK-NEXT: [[I_NEXT]] = add i32 [[I]], 1
+; CHECK-NEXT: [[EXIT:%.*]] = icmp slt i32 [[I_NEXT]], [[N]]
+; CHECK-NEXT: br i1 [[EXIT]], label %[[LOOP]], label %[[EXIT_BLOCK:.*]]
+; CHECK: [[EXIT_BLOCK]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %i = phi i32 [ 0, %entry ], [ %i.next, %loop ]
+ %gep1 = getelementptr float, ptr %arr, i32 %i
+ %load1 = load float, ptr %gep1
+ %zext = zext i32 %i to i64
+ %gep2 = getelementptr float, ptr %arr, i64 %zext
+ %gep3 = getelementptr i8, ptr %gep2, i64 0
+ %load2 = load float, ptr %gep3
+ %cmp = fcmp olt float %load1, %load2
+ %sel = select i1 %cmp, float %load1, float %load2
+ %i.next = add i32 %i, 1
+ %exit = icmp slt i32 %i.next, %n
+ br i1 %exit, label %loop, label %exit_block
+
+exit_block:
+ ret void
+}
+
+; Negative test: SExt with wrong types (i16->i32 instead of i32->i64).
+define void @test_wrong_sext_types(ptr %arr, i32 %n) {
+; CHECK-LABEL: define void @test_wrong_sext_types(
+; CHECK-SAME: ptr [[ARR:%.*]], i32 [[N:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[I:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[I_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[GEP1:%.*]] = getelementptr float, ptr [[ARR]], i32 [[I]]
+; CHECK-NEXT: [[LOAD1:%.*]] = load float, ptr [[GEP1]], align 4
+; CHECK-NEXT: [[I16:%.*]] = trunc i32 [[I]] to i16
+; CHECK-NEXT: [[SEXT:%.*]] = sext i16 [[I16]] to i32
+; CHECK-NEXT: [[GEP2:%.*]] = getelementptr float, ptr [[ARR]], i32 [[SEXT]]
+; CHECK-NEXT: [[LOAD2:%.*]] = load float, ptr [[GEP2]], align 4
+; CHECK-NEXT: [[CMP:%.*]] = fcmp olt float [[LOAD1]], [[LOAD2]]
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], float [[LOAD1]], float [[LOAD2]]
+; CHECK-NEXT: [[I_NEXT]] = add i32 [[I]], 1
+; CHECK-NEXT: [[EXIT:%.*]] = icmp slt i32 [[I_NEXT]], [[N]]
+; CHECK-NEXT: br i1 [[EXIT]], label %[[LOOP]], label %[[EXIT_BLOCK:.*]]
+; CHECK: [[EXIT_BLOCK]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %i = phi i32 [ 0, %entry ], [ %i.next, %loop ]
+ %gep1 = getelementptr float, ptr %arr, i32 %i
+ %load1 = load float, ptr %gep1
+ %i16 = trunc i32 %i to i16
+ %sext = sext i16 %i16 to i32
+ %gep2 = getelementptr float, ptr %arr, i32 %sext
+ %gep3 = getelementptr i8, ptr %gep2, i32 0
+ %load2 = load float, ptr %gep3
+ %cmp = fcmp olt float %load1, %load2
+ %sel = select i1 %cmp, float %load1, float %load2
+ %i.next = add i32 %i, 1
+ %exit = icmp slt i32 %i.next, %n
+ br i1 %exit, label %loop, label %exit_block
+
+exit_block:
+ ret void
+}
+
+; Negative test: IndexVal is a constant, not a PHI node.
+define void @test_index_is_constant(ptr %arr, i32 %n) {
+; CHECK-LABEL: define void @test_index_is_constant(
+; CHECK-SAME: ptr [[ARR:%.*]], i32 [[N:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[GEP2_PHI_TRANS_INSERT:%.*]] = getelementptr float, ptr [[ARR]], i64 5
+; CHECK-NEXT: [[LOAD2_PRE:%.*]] = load float, ptr [[GEP2_PHI_TRANS_INSERT]], align 4
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[I:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[I_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[GEP1:%.*]] = getelementptr float, ptr [[ARR]], i32 [[I]]
+; CHECK-NEXT: [[LOAD1:%.*]] = load float, ptr [[GEP1]], align 4
+; CHECK-NEXT: [[CMP:%.*]] = fcmp olt float [[LOAD1]], [[LOAD2_PRE]]
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], float [[LOAD1]], float [[LOAD2_PRE]]
+; CHECK-NEXT: [[I_NEXT]] = add i32 [[I]], 1
+; CHECK-NEXT: [[EXIT:%.*]] = icmp slt i32 [[I_NEXT]], [[N]]
+; CHECK-NEXT: br i1 [[EXIT]], label %[[LOOP]], label %[[EXIT_BLOCK:.*]]
+; CHECK: [[EXIT_BLOCK]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %i = phi i32 [ 0, %entry ], [ %i.next, %loop ]
+
+ %gep1 = getelementptr float, ptr %arr, i32 %i
+ %load1 = load float, ptr %gep1
+ ; IndexVal is a constant (5), not a PHI node!
+ %sext = sext i32 5 to i64
+ %gep2 = getelementptr float, ptr %arr, i64 %sext
+ %gep3 = getelementptr i8, ptr %gep2, i64 0
+ %load2 = load float, ptr %gep3
+ %cmp = fcmp olt float %load1, %load2
+ %sel = select i1 %cmp, float %load1, float %load2
+ %i.next = add i32 %i, 1
+ %exit = icmp slt i32 %i.next, %n
+ br i1 %exit, label %loop, label %exit_block
+
+exit_block:
+ ret void
+}
+
+; Negative test: Initial min index value is not 1.
+define void @test_initial_index_not_one(ptr %arr, i32 %n) {
+; CHECK-LABEL: define void @test_initial_index_not_one(
+; CHECK-SAME: ptr [[ARR:%.*]], i32 [[N:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[I:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[I_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[MIN_IDX:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[MIN_IDX_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[GEP1:%.*]] = getelementptr float, ptr [[ARR]], i32 [[I]]
+; CHECK-NEXT: [[LOAD1:%.*]] = load float, ptr [[GEP1]], align 4
+; CHECK-NEXT: [[SEXT:%.*]] = sext i32 [[MIN_IDX]] to i64
+; CHECK-NEXT: [[GEP2:%.*]] = getelementptr float, ptr [[ARR]], i64 [[SEXT]]
+; CHECK-NEXT: [[LOAD2:%.*]] = load float, ptr [[GEP2]], align 4
+; CHECK-NEXT: [[CMP:%.*]] = fcmp olt float [[LOAD1]], [[LOAD2]]
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], float [[LOAD1]], float [[LOAD2]]
+; CHECK-NEXT: [[MIN_IDX_NEXT]] = select i1 [[CMP]], i32 [[I]], i32 [[MIN_IDX]]
+; CHECK-NEXT: [[I_NEXT]] = add i32 [[I]], 1
+; CHECK-NEXT: [[EXIT:%.*]] = icmp slt i32 [[I_NEXT]], [[N]]
+; CHECK-NEXT: br i1 [[EXIT]], label %[[LOOP]], label %[[EXIT_BLOCK:.*]]
+; CHECK: [[EXIT_BLOCK]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %i = phi i32 [ 0, %entry ], [ %i.next, %loop ]
+ ; Initial value is 0, not 1!
+ %min.idx = phi i32 [ 0, %entry ], [ %min.idx.next, %loop ]
+
+ %gep1 = getelementptr float, ptr %arr, i32 %i
+ %load1 = load float, ptr %gep1
+ %sext = sext i32 %min.idx to i64
+ %gep2 = getelementptr float, ptr %arr, i64 %sext
+ %gep3 = getelementptr i8, ptr %gep2, i64 0
+ %load2 = load float, ptr %gep3
+ %cmp = fcmp olt float %load1, %load2
+ %sel = select i1 %cmp, float %load1, float %load2
+ %min.idx.next = select i1 %cmp, i32 %i, i32 %min.idx
+ %i.next = add i32 %i, 1
+ %exit = icmp slt i32 %i.next, %n
+ br i1 %exit, label %loop, label %exit_block
+
+exit_block:
+ ret void
+}
+
+; Negative test: Pattern recognized but hoisting not safe due to aliasing store in loop.
+define void @test_aliasing_store_in_loop(ptr %arr, i32 %n) {
+; CHECK-LABEL: define void @test_aliasing_store_in_loop(
+; CHECK-SAME: ptr [[ARR:%.*]], i32 [[N:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[I:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[I_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[MIN_IDX:%.*]] = phi i32 [ 1, %[[ENTRY]] ], [ [[MIN_IDX_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[STORE_GEP:%.*]] = getelementptr float, ptr [[ARR]], i32 [[I]]
+; CHECK-NEXT: store float 0.000000e+00, ptr [[STORE_GEP]], align 4
+; CHECK-NEXT: [[SEXT:%.*]] = sext i32 [[MIN_IDX]] to i64
+; CHECK-NEXT: [[GEP2:%.*]] = getelementptr float, ptr [[ARR]], i64 [[SEXT]]
+; CHECK-NEXT: [[LOAD2:%.*]] = load float, ptr [[GEP2]], align 4
+; CHECK-NEXT: [[CMP:%.*]] = fcmp olt float 0.000000e+00, [[LOAD2]]
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], float 0.000000e+00, float [[LOAD2]]
+; CHECK-NEXT: [[MIN_IDX_NEXT]] = select i1 [[CMP]], i32 [[I]], i32 [[MIN_IDX]]
+; CHECK-NEXT: [[I_NEXT]] = add i32 [[I]], 1
+; CHECK-NEXT: [[EXIT:%.*]] = icmp slt i32 [[I_NEXT]], [[N]]
+; CHECK-NEXT: br i1 [[EXIT]], label %[[LOOP]], label %[[EXIT_BLOCK:.*]]
+; CHECK: [[EXIT_BLOCK]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %i = phi i32 [ 0, %entry ], [ %i.next, %loop ]
+ %min.idx = phi i32 [ 1, %entry ], [ %min.idx.next, %loop ]
+
+ ; This store aliases with the loads, preventing hoisting.
+ %store.gep = getelementptr float, ptr %arr, i32 %i
+ store float 0.0, ptr %store.gep
+
+ %gep1 = getelementptr float, ptr %arr, i32 %i
+ %load1 = load float, ptr %gep1
+ %sext = sext i32 %min.idx to i64
+ %gep2 = getelementptr float, ptr %arr, i64 %sext
+ %gep3 = getelementptr i8, ptr %gep2, i64 0
+ %load2 = load float, ptr %gep3
+ %cmp = fcmp olt float %load1, %load2
+ %sel = select i1 %cmp, float %load1, float %load2
+ %min.idx.next = select i1 %cmp, i32 %i, i32 %min.idx
+ %i.next = add i32 %i, 1
+ %exit = icmp slt i32 %i.next, %n
+ br i1 %exit, label %loop, label %exit_block
+
+exit_block:
+ ret void
+}
+
+; Negative test: Pattern recognized but BasePtr is not loop invariant.
+define void @test_non_invariant_base_ptr(ptr %arr, i32 %n) {
+; CHECK-LABEL: define void @test_non_invariant_base_ptr(
+; CHECK-SAME: ptr [[ARR:%.*]], i32 [[N:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[I:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[I_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[MIN_IDX:%.*]] = phi i32 [ 1, %[[ENTRY]] ], [ [[MIN_IDX_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[BASE_PTR:%.*]] = phi ptr [ [[ARR]], %[[ENTRY]] ], [ [[BASE_PTR_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[GEP1:%.*]] = getelementptr float, ptr [[BASE_PTR]], i32 [[I]]
+; CHECK-NEXT: [[LOAD1:%.*]] = load float, ptr [[GEP1]], align 4
+; CHECK-NEXT: [[SEXT:%.*]] = sext i32 [[MIN_IDX]] to i64
+; CHECK-NEXT: [[GEP2:%.*]] = getelementptr float, ptr [[BASE_PTR]], i64 [[SEXT]]
+; CHECK-NEXT: [[LOAD2:%.*]] = load float, ptr [[GEP2]], align 4
+; CHECK-NEXT: [[CMP:%.*]] = fcmp olt float [[LOAD1]], [[LOAD2]]
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], float [[LOAD1]], float [[LOAD2]]
+; CHECK-NEXT: [[MIN_IDX_NEXT]] = select i1 [[CMP]], i32 [[I]], i32 [[MIN_IDX]]
+; CHECK-NEXT: [[BASE_PTR_NEXT]] = getelementptr float, ptr [[BASE_PTR]], i32 1
+; CHECK-NEXT: [[I_NEXT]] = add i32 [[I]], 1
+; CHECK-NEXT: [[EXIT:%.*]] = icmp slt i32 [[I_NEXT]], [[N]]
+; CHECK-NEXT: br i1 [[EXIT]], label %[[LOOP]], label %[[EXIT_BLOCK:.*]]
+; CHECK: [[EXIT_BLOCK]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %i = phi i32 [ 0, %entry ], [ %i.next, %loop ]
+ %min.idx = phi i32 [ 1, %entry ], [ %min.idx.next, %loop ]
+ ; Base pointer changes each iteration - not loop invariant!
+ %base.ptr = phi ptr [ %arr, %entry ], [ %base.ptr.next, %loop ]
+
+ %gep1 = getelementptr float, ptr %base.ptr, i32 %i
+ %load1 = load float, ptr %gep1
+ %sext = sext i32 %min.idx to i64
+ %gep2 = getelementptr float, ptr %base.ptr, i64 %sext
+ %gep3 = getelementptr i8, ptr %gep2, i64 0
+ %load2 = load float, ptr %gep3
+ %cmp = fcmp olt float %load1, %load2
+ %sel = select i1 %cmp, float %load1, float %load2
+ %min.idx.next = select i1 %cmp, i32 %i, i32 %min.idx
+ %base.ptr.next = getelementptr float, ptr %base.ptr, i32 1
+ %i.next = add i32 %i, 1
+ %exit = icmp slt i32 %i.next, %n
+ br i1 %exit, label %loop, label %exit_block
+
+exit_block:
+ ret void
+}
+
+; Negative test: Pattern recognized but may-alias call in loop.
+define void @test_may_alias_call_in_loop(ptr %arr, i32 %n) {
+; CHECK-LABEL: define void @test_may_alias_call_in_loop(
+; CHECK-SAME: ptr [[ARR:%.*]], i32 [[N:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[I:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[I_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[MIN_IDX:%.*]] = phi i32 [ 1, %[[ENTRY]] ], [ [[MIN_IDX_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: call void @external_function(ptr [[ARR]])
+; CHECK-NEXT: [[GEP1:%.*]] = getelementptr float, ptr [[ARR]], i32 [[I]]
+; CHECK-NEXT: [[LOAD1:%.*]] = load float, ptr [[GEP1]], align 4
+; CHECK-NEXT: [[SEXT:%.*]] = sext i32 [[MIN_IDX]] to i64
+; CHECK-NEXT: [[GEP2:%.*]] = getelementptr float, ptr [[ARR]], i64 [[SEXT]]
+; CHECK-NEXT: [[LOAD2:%.*]] = load float, ptr [[GEP2]], align 4
+; CHECK-NEXT: [[CMP:%.*]] = fcmp olt float [[LOAD1]], [[LOAD2]]
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], float [[LOAD1]], float [[LOAD2]]
+; CHECK-NEXT: [[MIN_IDX_NEXT]] = select i1 [[CMP]], i32 [[I]], i32 [[MIN_IDX]]
+; CHECK-NEXT: [[I_NEXT]] = add i32 [[I]], 1
+; CHECK-NEXT: [[EXIT:%.*]] = icmp slt i32 [[I_NEXT]], [[N]]
+; CHECK-NEXT: br i1 [[EXIT]], label %[[LOOP]], label %[[EXIT_BLOCK:.*]]
+; CHECK: [[EXIT_BLOCK]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %i = phi i32 [ 0, %entry ], [ %i.next, %loop ]
+ %min.idx = phi i32 [ 1, %entry ], [ %min.idx.next, %loop ]
+
+ ; External function may modify memory.
+ call void @external_function(ptr %arr)
+
+ %gep1 = getelementptr float, ptr %arr, i32 %i
+ %load1 = load float, ptr %gep1
+ %sext = sext i32 %min.idx to i64
+ %gep2 = getelementptr float, ptr %arr, i64 %sext
+ %gep3 = getelementptr i8, ptr %gep2, i64 0
+ %load2 = load float, ptr %gep3
+ %cmp = fcmp olt float %load1, %load2
+ %sel = select i1 %cmp, float %load1, float %load2
+ %min.idx.next = select i1 %cmp, i32 %i, i32 %min.idx
+ %i.next = add i32 %i, 1
+ %exit = icmp slt i32 %i.next, %n
+ br i1 %exit, label %loop, label %exit_block
+
+exit_block:
+ ret void
+}
+
+; Negative test: byte offsets on the two load addresses do not satisfy
+; bytes_offset(val.first.gep) - bytes_offset(val.min.gep)
+; == sizeof(LoadType) * step_of_iv_next
+; Here val.first has no i8-offset (C1 = 0), val.min has -4 (C2 = -4),
+; sizeof(float) = 4, and step = -1, so the required equality
+; (C1 - C2 == sizeof(T) * step) becomes 4 == -4 -- false. Transforming
+; would propagate this iter's val.first as next iter's known_min, but
+; next iter's val.min reads a different address. Recognizer must bail.
+define i32 @test_gvn_min_pattern_offset_mismatch(ptr %base) {
+; CHECK-LABEL: define i32 @test_gvn_min_pattern_offset_mismatch(
+; CHECK-SAME: ptr [[BASE:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i64 [ 3, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[CTR:%.*]] = phi i64 [ 3, %[[ENTRY]] ], [ [[CTR_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[MIN_IDX:%.*]] = phi i32 [ 1, %[[ENTRY]] ], [ [[MIN_IDX_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[IV_NEXT]] = add nsw i64 [[IV]], -1
+; CHECK-NEXT: [[PTR_IV:%.*]] = getelementptr float, ptr [[BASE]], i64 [[IV]]
+; CHECK-NEXT: [[VAL_FIRST:%.*]] = load float, ptr [[PTR_IV]], align 4
+; CHECK-NEXT: [[MIN_IDX_EXT:%.*]] = sext i32 [[MIN_IDX]] to i64
+; CHECK-NEXT: [[GEP_TYPED:%.*]] = getelementptr float, ptr [[BASE]], i64 [[MIN_IDX_EXT]]
+; CHECK-NEXT: [[GEP_OFFSET:%.*]] = getelementptr i8, ptr [[GEP_TYPED]], i64 -4
+; CHECK-NEXT: [[VAL_MIN:%.*]] = load float, ptr [[GEP_OFFSET]], align 4
+; CHECK-NEXT: [[CMP:%.*]] = fcmp olt float [[VAL_FIRST]], [[VAL_MIN]]
+; CHECK-NEXT: [[NEW_IDX:%.*]] = trunc nsw i64 [[IV_NEXT]] to i32
+; CHECK-NEXT: [[MIN_IDX_NEXT]] = select i1 [[CMP]], i32 [[NEW_IDX]], i32 [[MIN_IDX]]
+; CHECK-NEXT: [[CTR_NEXT]] = add nsw i64 [[CTR]], -1
+; CHECK-NEXT: [[COND:%.*]] = icmp samesign ugt i64 [[CTR]], 1
+; CHECK-NEXT: br i1 [[COND]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret i32 [[MIN_IDX_NEXT]]
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i64 [ 3, %entry ], [ %iv.next, %loop ]
+ %ctr = phi i64 [ 3, %entry ], [ %ctr.next, %loop ]
+ %min.idx = phi i32 [ 1, %entry ], [ %min.idx.next, %loop ]
+ %iv.next = add nsw i64 %iv, -1
+ %ptr.iv = getelementptr float, ptr %base, i64 %iv
+ %val.first = load float, ptr %ptr.iv, align 4
+ %min.idx.ext = sext i32 %min.idx to i64
+ %gep.typed = getelementptr float, ptr %base, i64 %min.idx.ext
+ %gep.offset = getelementptr i8, ptr %gep.typed, i64 -4
+ %val.min = load float, ptr %gep.offset, align 4
+ %cmp = fcmp olt float %val.first, %val.min
+ %new.idx = trunc nsw i64 %iv.next to i32
+ %min.idx.next = select i1 %cmp, i32 %new.idx, i32 %min.idx
+ %ctr.next = add nsw i64 %ctr, -1
+ %cond = icmp samesign ugt i64 %ctr, 1
+ br i1 %cond, label %loop, label %exit
+
+exit:
+ ret i32 %min.idx.next
+}
+
+; Negative test: select is in an inner block, not header-is-latch. Transform
+; must not fire.
+define void @test_select_block_not_header_is_latch(ptr %arr, i32 %initial_min_idx, i1 %go) {
+; CHECK-LABEL: define void @test_select_block_not_header_is_latch(
+; CHECK-SAME: ptr [[ARR:%.*]], i32 [[INITIAL_MIN_IDX:%.*]], i1 [[GO:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[HEADER:.*]]
+; CHECK: [[HEADER]]:
+; CHECK-NEXT: [[INDVARS_IV_I:%.*]] = phi i64 [ 1, %[[ENTRY]] ], [ [[INDVARS_IV_NEXT_I:%.*]], %[[BODY:.*]] ]
+; CHECK-NEXT: [[LOOP_COUNTER:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[LOOP_COUNTER_NEXT:%.*]], %[[BODY]] ]
+; CHECK-NEXT: [[MIN_IDX:%.*]] = phi i32 [ [[INITIAL_MIN_IDX]], %[[ENTRY]] ], [ [[MIN_IDX_NEXT:%.*]], %[[BODY]] ]
+; CHECK-NEXT: br i1 [[GO]], label %[[BODY]], label %[[EXIT:.*]]
+; CHECK: [[BODY]]:
+; CHECK-NEXT: [[INDVARS_IV_NEXT_I]] = add nsw i64 [[INDVARS_IV_I]], -1
+; CHECK-NEXT: [[PTR_I32_IV:%.*]] = getelementptr i32, ptr [[ARR]], i64 [[INDVARS_IV_I]]
+; CHECK-NEXT: [[PTR_FIRST_LOAD:%.*]] = getelementptr i8, ptr [[PTR_I32_IV]], i64 -8
+; CHECK-NEXT: [[VAL_FIRST:%.*]] = load i32, ptr [[PTR_FIRST_LOAD]], align 4
+; CHECK-NEXT: [[MIN_IDX_EXT:%.*]] = sext i32 [[MIN_IDX]] to i64
+; CHECK-NEXT: [[PTR_I32_MIN:%.*]] = getelementptr i32, ptr [[ARR]], i64 [[MIN_IDX_EXT]]
+; CHECK-NEXT: [[PTR_SECOND_LOAD:%.*]] = getelementptr i8, ptr [[PTR_I32_MIN]], i64 -4
+; CHECK-NEXT: [[VAL_CURRENT_MIN:%.*]] = load i32, ptr [[PTR_SECOND_LOAD]], align 4
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i32 [[VAL_FIRST]], [[VAL_CURRENT_MIN]]
+; CHECK-NEXT: [[NEXT_IDX_TRUNC:%.*]] = trunc nsw i64 [[INDVARS_IV_NEXT_I]] to i32
+; CHECK-NEXT: [[MIN_IDX_NEXT]] = select i1 [[CMP]], i32 [[NEXT_IDX_TRUNC]], i32 [[MIN_IDX]]
+; CHECK-NEXT: [[LOOP_COUNTER_NEXT]] = add nsw i64 [[LOOP_COUNTER]], -1
+; CHECK-NEXT: [[LOOP_CONTINUE:%.*]] = icmp samesign ugt i64 [[LOOP_COUNTER]], 1
+; CHECK-NEXT: br i1 [[LOOP_CONTINUE]], label %[[HEADER]], label %[[EXIT]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %header
+
+header:
+ %indvars.iv.i = phi i64 [ 1, %entry ], [ %indvars.iv.next.i, %latch ]
+ %loop.counter = phi i64 [ 0, %entry ], [ %loop.counter.next, %latch ]
+ %min.idx = phi i32 [ %initial_min_idx, %entry ], [ %min.idx.next, %latch ]
+ br i1 %go, label %body, label %exit
+
+body:
+ %indvars.iv.next.i = add nsw i64 %indvars.iv.i, -1
+ %ptr.i32.iv = getelementptr i32, ptr %arr, i64 %indvars.iv.i
+ %ptr.first.load = getelementptr i8, ptr %ptr.i32.iv, i64 -8
+ %val.first = load i32, ptr %ptr.first.load, align 4
+ %min.idx.ext = sext i32 %min.idx to i64
+ %ptr.i32.min = getelementptr i32, ptr %arr, i64 %min.idx.ext
+ %ptr.second.load = getelementptr i8, ptr %ptr.i32.min, i64 -4
+ %val.current.min = load i32, ptr %ptr.second.load, align 4
+ %cmp = icmp slt i32 %val.first, %val.current.min
+ %next.idx.trunc = trunc nsw i64 %indvars.iv.next.i to i32
+ %min.idx.next = select i1 %cmp, i32 %next.idx.trunc, i32 %min.idx
+ br label %latch
+
+latch:
+ %loop.counter.next = add nsw i64 %loop.counter, -1
+ %loop.continue = icmp samesign ugt i64 %loop.counter, 1
+ br i1 %loop.continue, label %header, label %exit
+
+exit:
+ ret void
+}
+
+; Negative test: hoistable load has an extra use in the loop.
+define { i32, float } @test_hoistable_load_has_extra_use(ptr %arr) {
+; CHECK-LABEL: define { i32, float } @test_hoistable_load_has_extra_use(
+; CHECK-SAME: ptr [[ARR:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i64 [ 5, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[CTR:%.*]] = phi i64 [ 4, %[[ENTRY]] ], [ [[CTR_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[MIN_IDX:%.*]] = phi i32 [ 1, %[[ENTRY]] ], [ [[MIN_IDX_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[SUM:%.*]] = phi float [ 0.000000e+00, %[[ENTRY]] ], [ [[SUM_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[IV_NEXT]] = add nsw i64 [[IV]], -1
+; CHECK-NEXT: [[PTR_IV:%.*]] = getelementptr float, ptr [[ARR]], i64 [[IV]]
+; CHECK-NEXT: [[PTR_FIRST:%.*]] = getelementptr i8, ptr [[PTR_IV]], i64 -8
+; CHECK-NEXT: [[VAL_FIRST:%.*]] = load float, ptr [[PTR_FIRST]], align 4
+; CHECK-NEXT: [[MIN_IDX_EXT:%.*]] = sext i32 [[MIN_IDX]] to i64
+; CHECK-NEXT: [[GEP_TYPED:%.*]] = getelementptr float, ptr [[ARR]], i64 [[MIN_IDX_EXT]]
+; CHECK-NEXT: [[GEP_OFFSET:%.*]] = getelementptr i8, ptr [[GEP_TYPED]], i64 -4
+; CHECK-NEXT: [[VAL_MIN:%.*]] = load float, ptr [[GEP_OFFSET]], align 4
+; CHECK-NEXT: [[CMP:%.*]] = fcmp olt float [[VAL_FIRST]], [[VAL_MIN]]
+; CHECK-NEXT: [[NEW_IDX:%.*]] = trunc nsw i64 [[IV_NEXT]] to i32
+; CHECK-NEXT: [[MIN_IDX_NEXT]] = select i1 [[CMP]], i32 [[NEW_IDX]], i32 [[MIN_IDX]]
+; CHECK-NEXT: [[SUM_NEXT]] = fadd float [[SUM]], [[VAL_MIN]]
+; CHECK-NEXT: [[CTR_NEXT]] = add nsw i64 [[CTR]], -1
+; CHECK-NEXT: [[COND:%.*]] = icmp samesign ugt i64 [[CTR]], 1
+; CHECK-NEXT: br i1 [[COND]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[R0:%.*]] = insertvalue { i32, float } poison, i32 [[MIN_IDX_NEXT]], 0
+; CHECK-NEXT: [[R1:%.*]] = insertvalue { i32, float } [[R0]], float [[SUM_NEXT]], 1
+; CHECK-NEXT: ret { i32, float } [[R1]]
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i64 [ 5, %entry ], [ %iv.next, %loop ]
+ %ctr = phi i64 [ 4, %entry ], [ %ctr.next, %loop ]
+ %min.idx = phi i32 [ 1, %entry ], [ %min.idx.next, %loop ]
+ %sum = phi float [ 0.0, %entry ], [ %sum.next, %loop ]
+ %iv.next = add nsw i64 %iv, -1
+ %ptr.iv = getelementptr float, ptr %arr, i64 %iv
+ %ptr.first = getelementptr i8, ptr %ptr.iv, i64 -8
+ %val.first = load float, ptr %ptr.first, align 4
+ %min.idx.ext = sext i32 %min.idx to i64
+ %gep.typed = getelementptr float, ptr %arr, i64 %min.idx.ext
+ %gep.offset = getelementptr i8, ptr %gep.typed, i64 -4
+ %val.min = load float, ptr %gep.offset, align 4
+ %cmp = fcmp olt float %val.first, %val.min
+ %new.idx = trunc nsw i64 %iv.next to i32
+ %min.idx.next = select i1 %cmp, i32 %new.idx, i32 %min.idx
+ %sum.next = fadd float %sum, %val.min
+ %ctr.next = add nsw i64 %ctr, -1
+ %cond = icmp samesign ugt i64 %ctr, 1
+ br i1 %cond, label %loop, label %exit
+
+exit:
+ %r0 = insertvalue { i32, float } poison, i32 %min.idx.next, 0
+ %r1 = insertvalue { i32, float } %r0, float %sum.next, 1
+ ret { i32, float } %r1
+}
+
+; Negative test: IndexValPhi's loop back-edge is not the Select.
+define void @test_indexvalphi_backedge_not_select(ptr %arr, ptr %sink) {
+; CHECK-LABEL: define void @test_indexvalphi_backedge_not_select(
+; CHECK-SAME: ptr [[ARR:%.*]], ptr [[SINK:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i64 [ 5, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[CTR:%.*]] = phi i64 [ 4, %[[ENTRY]] ], [ [[CTR_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[IV_NEXT]] = add nsw i64 [[IV]], -1
+; CHECK-NEXT: [[PTR_IV:%.*]] = getelementptr float, ptr [[ARR]], i64 [[IV]]
+; CHECK-NEXT: [[PTR_FIRST:%.*]] = getelementptr i8, ptr [[PTR_IV]], i64 -8
+; CHECK-NEXT: [[VAL_FIRST:%.*]] = load float, ptr [[PTR_FIRST]], align 4
+; CHECK-NEXT: [[GEP_TYPED:%.*]] = getelementptr float, ptr [[ARR]], i64 1
+; CHECK-NEXT: [[GEP_OFFSET:%.*]] = getelementptr i8, ptr [[GEP_TYPED]], i64 -4
+; CHECK-NEXT: [[VAL_MIN:%.*]] = load float, ptr [[GEP_OFFSET]], align 4
+; CHECK-NEXT: [[CMP:%.*]] = fcmp olt float [[VAL_FIRST]], [[VAL_MIN]]
+; CHECK-NEXT: [[NEW_IDX:%.*]] = trunc nsw i64 [[IV_NEXT]] to i32
+; CHECK-NEXT: [[MIN_IDX_NEXT:%.*]] = select i1 [[CMP]], i32 [[NEW_IDX]], i32 1
+; CHECK-NEXT: [[SINK_IV:%.*]] = getelementptr i32, ptr [[SINK]], i64 [[CTR]]
+; CHECK-NEXT: store i32 [[MIN_IDX_NEXT]], ptr [[SINK_IV]], align 4
+; CHECK-NEXT: [[CTR_NEXT]] = add nsw i64 [[CTR]], -1
+; CHECK-NEXT: [[COND:%.*]] = icmp samesign ugt i64 [[CTR]], 1
+; CHECK-NEXT: br i1 [[COND]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i64 [ 5, %entry ], [ %iv.next, %loop ]
+ %ctr = phi i64 [ 4, %entry ], [ %ctr.next, %loop ]
+ %min.idx = phi i32 [ 1, %entry ], [ %min.idx.alt, %loop ]
+ %iv.next = add nsw i64 %iv, -1
+ %ptr.iv = getelementptr float, ptr %arr, i64 %iv
+ %ptr.first = getelementptr i8, ptr %ptr.iv, i64 -8
+ %val.first = load float, ptr %ptr.first, align 4
+ %min.idx.ext = sext i32 %min.idx to i64
+ %gep.typed = getelementptr float, ptr %arr, i64 %min.idx.ext
+ %gep.offset = getelementptr i8, ptr %gep.typed, i64 -4
+ %val.min = load float, ptr %gep.offset, align 4
+ %cmp = fcmp olt float %val.first, %val.min
+ %new.idx = trunc nsw i64 %iv.next to i32
+ %min.idx.next = select i1 %cmp, i32 %new.idx, i32 %min.idx
+ %min.idx.alt = add nsw i32 %min.idx, 0
+ %sink.iv = getelementptr i32, ptr %sink, i64 %ctr
+ store i32 %min.idx.next, ptr %sink.iv, align 4
+ %ctr.next = add nsw i64 %ctr, -1
+ %cond = icmp samesign ugt i64 %ctr, 1
+ br i1 %cond, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+; Negative test: Base pointer is loop-variant; hoisting GEP into the
+; preheader would use a value not dominating the preheader.
+define i32 @test_base_not_loop_invariant(ptr %arr) {
+; CHECK-LABEL: define i32 @test_base_not_loop_invariant(
+; CHECK-SAME: ptr [[ARR:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i64 [ 3, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[CTR:%.*]] = phi i64 [ 3, %[[ENTRY]] ], [ [[CTR_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[MIN_IDX:%.*]] = phi i32 [ 1, %[[ENTRY]] ], [ [[MIN_IDX_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[BASE_PTR:%.*]] = phi ptr [ [[ARR]], %[[ENTRY]] ], [ [[BASE_PTR_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[IV_NEXT]] = add nsw i64 [[IV]], -1
+; CHECK-NEXT: [[PTR_IV:%.*]] = getelementptr float, ptr [[BASE_PTR]], i64 [[IV]]
+; CHECK-NEXT: [[PTR_FIRST:%.*]] = getelementptr i8, ptr [[PTR_IV]], i64 -8
+; CHECK-NEXT: [[VAL_FIRST:%.*]] = load float, ptr [[PTR_FIRST]], align 4
+; CHECK-NEXT: [[MIN_IDX_EXT:%.*]] = sext i32 [[MIN_IDX]] to i64
+; CHECK-NEXT: [[GEP_TYPED:%.*]] = getelementptr float, ptr [[BASE_PTR]], i64 [[MIN_IDX_EXT]]
+; CHECK-NEXT: [[GEP_OFFSET:%.*]] = getelementptr i8, ptr [[GEP_TYPED]], i64 -4
+; CHECK-NEXT: [[VAL_MIN:%.*]] = load float, ptr [[GEP_OFFSET]], align 4
+; CHECK-NEXT: [[CMP:%.*]] = fcmp olt float [[VAL_FIRST]], [[VAL_MIN]]
+; CHECK-NEXT: [[NEW_IDX:%.*]] = trunc nsw i64 [[IV_NEXT]] to i32
+; CHECK-NEXT: [[MIN_IDX_NEXT]] = select i1 [[CMP]], i32 [[NEW_IDX]], i32 [[MIN_IDX]]
+; CHECK-NEXT: [[BASE_PTR_NEXT]] = getelementptr float, ptr [[BASE_PTR]], i64 4
+; CHECK-NEXT: [[CTR_NEXT]] = add nsw i64 [[CTR]], -1
+; CHECK-NEXT: [[COND:%.*]] = icmp samesign ugt i64 [[CTR]], 1
+; CHECK-NEXT: br i1 [[COND]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret i32 [[MIN_IDX_NEXT]]
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i64 [ 3, %entry ], [ %iv.next, %loop ]
+ %ctr = phi i64 [ 3, %entry ], [ %ctr.next, %loop ]
+ %min.idx = phi i32 [ 1, %entry ], [ %min.idx.next, %loop ]
+ %base.ptr = phi ptr [ %arr, %entry ], [ %base.ptr.next, %loop ]
+ %iv.next = add nsw i64 %iv, -1
+ %ptr.iv = getelementptr float, ptr %base.ptr, i64 %iv
+ %ptr.first = getelementptr i8, ptr %ptr.iv, i64 -8
+ %val.first = load float, ptr %ptr.first, align 4
+ %min.idx.ext = sext i32 %min.idx to i64
+ %gep.typed = getelementptr float, ptr %base.ptr, i64 %min.idx.ext
+ %gep.offset = getelementptr i8, ptr %gep.typed, i64 -4
+ %val.min = load float, ptr %gep.offset, align 4
+ %cmp = fcmp olt float %val.first, %val.min
+ %new.idx = trunc nsw i64 %iv.next to i32
+ %min.idx.next = select i1 %cmp, i32 %new.idx, i32 %min.idx
+ %base.ptr.next = getelementptr float, ptr %base.ptr, i64 4
+ %ctr.next = add nsw i64 %ctr, -1
+ %cond = icmp samesign ugt i64 %ctr, 1
+ br i1 %cond, label %loop, label %exit
+
+exit:
+ ret i32 %min.idx.next
+}
+
+; Negative test: IndexValPhi is defined in an outer-loop block, not the
+; inner loop containing the select; the preheader-lookup would assert.
+define i32 @test_indexvalphi_in_outer_loop(ptr %a) {
+; CHECK-LABEL: define i32 @test_indexvalphi_in_outer_loop(
+; CHECK-SAME: ptr [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[OUTER:.*]]
+; CHECK: [[OUTER]]:
+; CHECK-NEXT: [[IDX_OUTER:%.*]] = phi i32 [ 1, %[[ENTRY]] ], [ [[IDX_NEXT:%.*]], %[[LOOP:.*]] ]
+; CHECK-NEXT: [[IDX_EXT_PHI_TRANS_INSERT:%.*]] = sext i32 [[IDX_OUTER]] to i64
+; CHECK-NEXT: [[RHS_GEP1_PHI_TRANS_INSERT:%.*]] = getelementptr i32, ptr [[A]], i64 [[IDX_EXT_PHI_TRANS_INSERT]]
+; CHECK-NEXT: [[RHS_GEP2_PHI_TRANS_INSERT:%.*]] = getelementptr i8, ptr [[RHS_GEP1_PHI_TRANS_INSERT]], i64 -4
+; CHECK-NEXT: [[RHS_PRE:%.*]] = load i32, ptr [[RHS_GEP2_PHI_TRANS_INSERT]], align 4
+; CHECK-NEXT: br label %[[LOOP]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i64 [ 1, %[[OUTER]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[CNT:%.*]] = phi i32 [ 0, %[[OUTER]] ], [ [[CNT_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[LHS_GEP:%.*]] = getelementptr i32, ptr [[A]], i64 [[IV]]
+; CHECK-NEXT: [[LHS:%.*]] = load i32, ptr [[LHS_GEP]], align 4
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i32 [[LHS]], [[RHS_PRE]]
+; CHECK-NEXT: [[IV_NEXT]] = add i64 [[IV]], 1
+; CHECK-NEXT: [[NEWIDX:%.*]] = trunc i64 [[IV_NEXT]] to i32
+; CHECK-NEXT: [[IDX_NEXT]] = select i1 [[CMP]], i32 [[NEWIDX]], i32 [[IDX_OUTER]]
+; CHECK-NEXT: [[CNT_NEXT]] = add i32 [[CNT]], 1
+; CHECK-NEXT: [[CONT:%.*]] = icmp ult i32 [[CNT]], 1
+; CHECK-NEXT: br i1 [[CONT]], label %[[LOOP]], label %[[OUTER]]
+;
+entry:
+ br label %outer
+outer:
+ %idx.outer = phi i32 [ 1, %entry ], [ %idx.next, %loop ]
+ br label %loop
+loop:
+ %iv = phi i64 [ 1, %outer ], [ %iv.next, %loop ]
+ %cnt = phi i32 [ 0, %outer ], [ %cnt.next, %loop ]
+ %lhs.gep = getelementptr i32, ptr %a, i64 %iv
+ %lhs = load i32, ptr %lhs.gep, align 4
+ %idx.ext = sext i32 %idx.outer to i64
+ %rhs.gep1 = getelementptr i32, ptr %a, i64 %idx.ext
+ %rhs.gep2 = getelementptr i8, ptr %rhs.gep1, i64 -4
+ %rhs = load i32, ptr %rhs.gep2, align 4
+ %cmp = icmp slt i32 %lhs, %rhs
+ %iv.next = add i64 %iv, 1
+ %newidx = trunc i64 %iv.next to i32
+ %idx.next = select i1 %cmp, i32 %newidx, i32 %idx.outer
+ %cnt.next = add i32 %cnt, 1
+ %cont = icmp ult i32 %cnt, 1
+ br i1 %cont, label %loop, label %outer
+}
+
+; Negative test: a post-load store in the loop body aliases the
+; hoistable load's location on the next iteration. Memoizing the
+; value across iterations would observe a stale value.
+define i32 @test_backedge_store_clobbers_hoistable_load(ptr %arr) {
+; CHECK-LABEL: define i32 @test_backedge_store_clobbers_hoistable_load(
+; CHECK-SAME: ptr [[ARR:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i64 [ 1, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[CNT:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[CNT_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[MIN_IDX:%.*]] = phi i32 [ 1, %[[ENTRY]] ], [ [[MIN_IDX_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[PTR_CUR:%.*]] = getelementptr i32, ptr [[ARR]], i64 [[IV]]
+; CHECK-NEXT: [[VAL_FIRST:%.*]] = load i32, ptr [[PTR_CUR]], align 4
+; CHECK-NEXT: [[MIN_EXT:%.*]] = sext i32 [[MIN_IDX]] to i64
+; CHECK-NEXT: [[PTR_MIN_BASE:%.*]] = getelementptr i32, ptr [[ARR]], i64 [[MIN_EXT]]
+; CHECK-NEXT: [[PTR_SECOND:%.*]] = getelementptr i8, ptr [[PTR_MIN_BASE]], i64 -4
+; CHECK-NEXT: [[VAL_CURRENT_MIN:%.*]] = load i32, ptr [[PTR_SECOND]], align 4
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i32 [[VAL_FIRST]], [[VAL_CURRENT_MIN]]
+; CHECK-NEXT: [[IV_NEXT]] = add i64 [[IV]], 1
+; CHECK-NEXT: [[NEXT_IDX_TRUNC:%.*]] = trunc i64 [[IV_NEXT]] to i32
+; CHECK-NEXT: [[MIN_IDX_NEXT]] = select i1 [[CMP]], i32 [[NEXT_IDX_TRUNC]], i32 [[MIN_IDX]]
+; CHECK-NEXT: store i32 0, ptr [[PTR_CUR]], align 4
+; CHECK-NEXT: [[CNT_NEXT]] = add i64 [[CNT]], 1
+; CHECK-NEXT: [[CONT:%.*]] = icmp ult i64 [[CNT_NEXT]], 2
+; CHECK-NEXT: br i1 [[CONT]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret i32 [[MIN_IDX_NEXT]]
+;
+entry:
+ br label %loop
+loop:
+ %iv = phi i64 [ 1, %entry ], [ %iv.next, %loop ]
+ %cnt = phi i64 [ 0, %entry ], [ %cnt.next, %loop ]
+ %min.idx = phi i32 [ 1, %entry ], [ %min.idx.next, %loop ]
+ %ptr.cur = getelementptr i32, ptr %arr, i64 %iv
+ %val.first = load i32, ptr %ptr.cur, align 4
+ %min.ext = sext i32 %min.idx to i64
+ %ptr.min.base = getelementptr i32, ptr %arr, i64 %min.ext
+ %ptr.second = getelementptr i8, ptr %ptr.min.base, i64 -4
+ %val.current.min = load i32, ptr %ptr.second, align 4
+ %cmp = icmp slt i32 %val.first, %val.current.min
+ %iv.next = add i64 %iv, 1
+ %next.idx.trunc = trunc i64 %iv.next to i32
+ %min.idx.next = select i1 %cmp, i32 %next.idx.trunc, i32 %min.idx
+ store i32 0, ptr %ptr.cur, align 4
+ %cnt.next = add i64 %cnt, 1
+ %cont = icmp ult i64 %cnt.next, 2
+ br i1 %cont, label %loop, label %exit
+exit:
+ ret i32 %min.idx.next
+}
+
+; Negative test: hoistable load is unordered-atomic. Memoizing across
+; iterations would drop the per-iteration atomic semantics.
+define i32 @test_atomic_hoistable_load(ptr %a) {
+; CHECK-LABEL: define i32 @test_atomic_hoistable_load(
+; CHECK-SAME: ptr [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i64 [ 3, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[CTR:%.*]] = phi i64 [ 3, %[[ENTRY]] ], [ [[CTR_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[IDX:%.*]] = phi i32 [ 1, %[[ENTRY]] ], [ [[IDX_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[IV_NEXT]] = add nsw i64 [[IV]], -1
+; CHECK-NEXT: [[LHS_GEP:%.*]] = getelementptr i32, ptr [[A]], i64 [[IV]]
+; CHECK-NEXT: [[LHS:%.*]] = load i32, ptr [[LHS_GEP]], align 4
+; CHECK-NEXT: [[IDX_EXT:%.*]] = sext i32 [[IDX]] to i64
+; CHECK-NEXT: [[RHS_GEP1:%.*]] = getelementptr i32, ptr [[A]], i64 [[IDX_EXT]]
+; CHECK-NEXT: [[RHS_GEP2:%.*]] = getelementptr i8, ptr [[RHS_GEP1]], i64 -4
+; CHECK-NEXT: [[RHS:%.*]] = load atomic i32, ptr [[RHS_GEP2]] unordered, align 4
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i32 [[LHS]], [[RHS]]
+; CHECK-NEXT: [[NEWIDX:%.*]] = trunc nsw i64 [[IV_NEXT]] to i32
+; CHECK-NEXT: [[IDX_NEXT]] = select i1 [[CMP]], i32 [[NEWIDX]], i32 [[IDX]]
+; CHECK-NEXT: [[CTR_NEXT]] = add nsw i64 [[CTR]], -1
+; CHECK-NEXT: [[CONT:%.*]] = icmp samesign ugt i64 [[CTR]], 1
+; CHECK-NEXT: br i1 [[CONT]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret i32 [[IDX_NEXT]]
+;
+entry:
+ br label %loop
+loop:
+ %iv = phi i64 [ 3, %entry ], [ %iv.next, %loop ]
+ %ctr = phi i64 [ 3, %entry ], [ %ctr.next, %loop ]
+ %idx = phi i32 [ 1, %entry ], [ %idx.next, %loop ]
+ %iv.next = add nsw i64 %iv, -1
+ %lhs.gep = getelementptr i32, ptr %a, i64 %iv
+ %lhs = load i32, ptr %lhs.gep, align 4
+ %idx.ext = sext i32 %idx to i64
+ %rhs.gep1 = getelementptr i32, ptr %a, i64 %idx.ext
+ %rhs.gep2 = getelementptr i8, ptr %rhs.gep1, i64 -4
+ %rhs = load atomic i32, ptr %rhs.gep2 unordered, align 4
+ %cmp = icmp slt i32 %lhs, %rhs
+ %newidx = trunc nsw i64 %iv.next to i32
+ %idx.next = select i1 %cmp, i32 %newidx, i32 %idx
+ %ctr.next = add nsw i64 %ctr, -1
+ %cont = icmp samesign ugt i64 %ctr, 1
+ br i1 %cont, label %loop, label %exit
+exit:
+ ret i32 %idx.next
+}
+
+; Negative test: select's new-index sits on the false arm. The transform
+; plants `select Cmp, LHS, KnownMin`, so this polarity is unsupported.
+define i32 @test_new_index_on_false_arm(ptr %a) {
+; CHECK-LABEL: define i32 @test_new_index_on_false_arm(
+; CHECK-SAME: ptr [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i64 [ 1, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[CNT:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[CNT_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[IDX:%.*]] = phi i32 [ 1, %[[ENTRY]] ], [ [[IDX_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[LHS_GEP:%.*]] = getelementptr i32, ptr [[A]], i64 [[IV]]
+; CHECK-NEXT: [[LHS:%.*]] = load i32, ptr [[LHS_GEP]], align 4
+; CHECK-NEXT: [[IDX_EXT:%.*]] = sext i32 [[IDX]] to i64
+; CHECK-NEXT: [[RHS_GEP1:%.*]] = getelementptr i32, ptr [[A]], i64 [[IDX_EXT]]
+; CHECK-NEXT: [[RHS_GEP2:%.*]] = getelementptr i8, ptr [[RHS_GEP1]], i64 -4
+; CHECK-NEXT: [[RHS:%.*]] = load i32, ptr [[RHS_GEP2]], align 4
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i32 [[LHS]], [[RHS]]
+; CHECK-NEXT: [[IV_NEXT]] = add i64 [[IV]], 1
+; CHECK-NEXT: [[NEWIDX:%.*]] = trunc i64 [[IV_NEXT]] to i32
+; CHECK-NEXT: [[IDX_NEXT]] = select i1 [[CMP]], i32 [[IDX]], i32 [[NEWIDX]]
+; CHECK-NEXT: [[CNT_NEXT]] = add i32 [[CNT]], 1
+; CHECK-NEXT: [[CONT:%.*]] = icmp ult i32 [[CNT]], 1
+; CHECK-NEXT: br i1 [[CONT]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret i32 [[IDX_NEXT]]
+;
+entry:
+ br label %loop
+loop:
+ %iv = phi i64 [ 1, %entry ], [ %iv.next, %loop ]
+ %cnt = phi i32 [ 0, %entry ], [ %cnt.next, %loop ]
+ %idx = phi i32 [ 1, %entry ], [ %idx.next, %loop ]
+ %lhs.gep = getelementptr i32, ptr %a, i64 %iv
+ %lhs = load i32, ptr %lhs.gep, align 4
+ %idx.ext = sext i32 %idx to i64
+ %rhs.gep1 = getelementptr i32, ptr %a, i64 %idx.ext
+ %rhs.gep2 = getelementptr i8, ptr %rhs.gep1, i64 -4
+ %rhs = load i32, ptr %rhs.gep2, align 4
+ %cmp = icmp slt i32 %lhs, %rhs
+ %iv.next = add i64 %iv, 1
+ %newidx = trunc i64 %iv.next to i32
+ %idx.next = select i1 %cmp, i32 %idx, i32 %newidx
+ %cnt.next = add i32 %cnt, 1
+ %cont = icmp ult i32 %cnt, 1
+ br i1 %cont, label %loop, label %exit
+exit:
+ ret i32 %idx.next
+}
diff --git a/llvm/test/Transforms/GVN/PRE/gvn-min-pre-predicates.ll b/llvm/test/Transforms/GVN/PRE/gvn-min-pre-predicates.ll
new file mode 100644
index 0000000000000..f5fe1e66ace20
--- /dev/null
+++ b/llvm/test/Transforms/GVN/PRE/gvn-min-pre-predicates.ll
@@ -0,0 +1,304 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -gvn-enable-min-finding-select-pattern -passes=gvn -S < %s | FileCheck %s
+
+; Positive test: non-LT predicate on the compare. The matched (hoistable)
+; load is on the RHS, so no operand swap is needed. Exercises that the
+; recognizer no longer rejects on predicate alone.
+define void @test_gvn_pattern_gt_predicate(ptr %0, i32 %initial_min_idx) {
+; CHECK-LABEL: define void @test_gvn_pattern_gt_predicate(
+; CHECK-SAME: ptr [[TMP0:%.*]], i32 [[INITIAL_MIN_IDX:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[HOIST_SEXT:%.*]] = sext i32 [[INITIAL_MIN_IDX]] to i64
+; CHECK-NEXT: [[HOIST_GEP1:%.*]] = getelementptr float, ptr [[TMP0]], i64 [[HOIST_SEXT]]
+; CHECK-NEXT: [[HOIST_GEP2:%.*]] = getelementptr i8, ptr [[HOIST_GEP1]], i64 -4
+; CHECK-NEXT: [[HOISTED_LOAD:%.*]] = load float, ptr [[HOIST_GEP2]], align 4
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[KNOWN_MIN:%.*]] = phi float [ [[HOISTED_LOAD]], %[[ENTRY]] ], [ [[CURRENT_MIN:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[INDVARS_IV_I:%.*]] = phi i64 [ 1, %[[ENTRY]] ], [ [[INDVARS_IV_NEXT_I:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[LOOP_COUNTER:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[LOOP_COUNTER_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[MIN_IDX:%.*]] = phi i32 [ [[INITIAL_MIN_IDX]], %[[ENTRY]] ], [ [[MIN_IDX_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[INDVARS_IV_NEXT_I]] = add nsw i64 [[INDVARS_IV_I]], -1
+; CHECK-NEXT: [[PTR_FLOAT_IV:%.*]] = getelementptr float, ptr [[TMP0]], i64 [[INDVARS_IV_I]]
+; CHECK-NEXT: [[PTR_FIRST_LOAD:%.*]] = getelementptr i8, ptr [[PTR_FLOAT_IV]], i64 -8
+; CHECK-NEXT: [[VAL_FIRST:%.*]] = load float, ptr [[PTR_FIRST_LOAD]], align 4
+; CHECK-NEXT: [[MIN_IDX_EXT:%.*]] = sext i32 [[MIN_IDX]] to i64
+; CHECK-NEXT: [[PTR_FLOAT_MIN:%.*]] = getelementptr float, ptr [[TMP0]], i64 [[MIN_IDX_EXT]]
+; CHECK-NEXT: [[PTR_SECOND_LOAD:%.*]] = getelementptr i8, ptr [[PTR_FLOAT_MIN]], i64 -4
+; CHECK-NEXT: [[CMP:%.*]] = fcmp contract ogt float [[VAL_FIRST]], [[KNOWN_MIN]]
+; CHECK-NEXT: [[NEXT_IDX_TRUNC:%.*]] = trunc nsw i64 [[INDVARS_IV_NEXT_I]] to i32
+; CHECK-NEXT: [[MIN_IDX_NEXT]] = select i1 [[CMP]], i32 [[NEXT_IDX_TRUNC]], i32 [[MIN_IDX]]
+; CHECK-NEXT: [[LOOP_COUNTER_NEXT]] = add nsw i64 [[LOOP_COUNTER]], -1
+; CHECK-NEXT: [[LOOP_CONTINUE:%.*]] = icmp samesign ugt i64 [[LOOP_COUNTER]], 1
+; CHECK-NEXT: [[CURRENT_MIN]] = select i1 [[CMP]], float [[VAL_FIRST]], float [[KNOWN_MIN]]
+; CHECK-NEXT: br i1 [[LOOP_CONTINUE]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %indvars.iv.i = phi i64 [ 1, %entry ], [ %indvars.iv.next.i, %loop ]
+ %loop.counter = phi i64 [ 0, %entry ], [ %loop.counter.next, %loop ]
+ %min.idx = phi i32 [ %initial_min_idx, %entry ], [ %min.idx.next, %loop ]
+ %indvars.iv.next.i = add nsw i64 %indvars.iv.i, -1
+ %ptr.float.iv = getelementptr float, ptr %0, i64 %indvars.iv.i
+ %ptr.first.load = getelementptr i8, ptr %ptr.float.iv, i64 -8
+ %val.first = load float, ptr %ptr.first.load, align 4
+ %min.idx.ext = sext i32 %min.idx to i64
+ %ptr.float.min = getelementptr float, ptr %0, i64 %min.idx.ext
+ %ptr.second.load = getelementptr i8, ptr %ptr.float.min, i64 -4
+ %val.current.min = load float, ptr %ptr.second.load, align 4
+ %cmp = fcmp contract ogt float %val.first, %val.current.min
+ %next.idx.trunc = trunc nsw i64 %indvars.iv.next.i to i32
+ %min.idx.next = select i1 %cmp, i32 %next.idx.trunc, i32 %min.idx
+ %loop.counter.next = add nsw i64 %loop.counter, -1
+ %loop.continue = icmp samesign ugt i64 %loop.counter, 1
+ br i1 %loop.continue, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+; Positive test: matched (hoistable) load is the LHS of the compare. The
+; recognizer must swap the compare's operands so the existing transform
+; (which expects the matched load at operand(1)) keeps working. The
+; user-written select keeps the new-index on its true arm.
+define void @test_gvn_pattern_matched_load_on_lhs(ptr %0, i32 %initial_min_idx) {
+; CHECK-LABEL: define void @test_gvn_pattern_matched_load_on_lhs(
+; CHECK-SAME: ptr [[TMP0:%.*]], i32 [[INITIAL_MIN_IDX:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[HOIST_SEXT:%.*]] = sext i32 [[INITIAL_MIN_IDX]] to i64
+; CHECK-NEXT: [[HOIST_GEP1:%.*]] = getelementptr float, ptr [[TMP0]], i64 [[HOIST_SEXT]]
+; CHECK-NEXT: [[HOIST_GEP2:%.*]] = getelementptr i8, ptr [[HOIST_GEP1]], i64 -4
+; CHECK-NEXT: [[HOISTED_LOAD:%.*]] = load float, ptr [[HOIST_GEP2]], align 4
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[KNOWN_MIN:%.*]] = phi float [ [[HOISTED_LOAD]], %[[ENTRY]] ], [ [[CURRENT_MIN:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[INDVARS_IV_I:%.*]] = phi i64 [ 1, %[[ENTRY]] ], [ [[INDVARS_IV_NEXT_I:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[LOOP_COUNTER:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[LOOP_COUNTER_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[MIN_IDX:%.*]] = phi i32 [ [[INITIAL_MIN_IDX]], %[[ENTRY]] ], [ [[MIN_IDX_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[INDVARS_IV_NEXT_I]] = add nsw i64 [[INDVARS_IV_I]], -1
+; CHECK-NEXT: [[PTR_FLOAT_IV:%.*]] = getelementptr float, ptr [[TMP0]], i64 [[INDVARS_IV_I]]
+; CHECK-NEXT: [[PTR_FIRST_LOAD:%.*]] = getelementptr i8, ptr [[PTR_FLOAT_IV]], i64 -8
+; CHECK-NEXT: [[VAL_FIRST:%.*]] = load float, ptr [[PTR_FIRST_LOAD]], align 4
+; CHECK-NEXT: [[MIN_IDX_EXT:%.*]] = sext i32 [[MIN_IDX]] to i64
+; CHECK-NEXT: [[PTR_FLOAT_MIN:%.*]] = getelementptr float, ptr [[TMP0]], i64 [[MIN_IDX_EXT]]
+; CHECK-NEXT: [[PTR_SECOND_LOAD:%.*]] = getelementptr i8, ptr [[PTR_FLOAT_MIN]], i64 -4
+; CHECK-NEXT: [[CMP:%.*]] = fcmp contract olt float [[VAL_FIRST]], [[KNOWN_MIN]]
+; CHECK-NEXT: [[NEXT_IDX_TRUNC:%.*]] = trunc nsw i64 [[INDVARS_IV_NEXT_I]] to i32
+; CHECK-NEXT: [[MIN_IDX_NEXT]] = select i1 [[CMP]], i32 [[NEXT_IDX_TRUNC]], i32 [[MIN_IDX]]
+; CHECK-NEXT: [[LOOP_COUNTER_NEXT]] = add nsw i64 [[LOOP_COUNTER]], -1
+; CHECK-NEXT: [[LOOP_CONTINUE:%.*]] = icmp samesign ugt i64 [[LOOP_COUNTER]], 1
+; CHECK-NEXT: [[CURRENT_MIN]] = select i1 [[CMP]], float [[VAL_FIRST]], float [[KNOWN_MIN]]
+; CHECK-NEXT: br i1 [[LOOP_CONTINUE]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %indvars.iv.i = phi i64 [ 1, %entry ], [ %indvars.iv.next.i, %loop ]
+ %loop.counter = phi i64 [ 0, %entry ], [ %loop.counter.next, %loop ]
+ %min.idx = phi i32 [ %initial_min_idx, %entry ], [ %min.idx.next, %loop ]
+ %indvars.iv.next.i = add nsw i64 %indvars.iv.i, -1
+ %ptr.float.iv = getelementptr float, ptr %0, i64 %indvars.iv.i
+ %ptr.first.load = getelementptr i8, ptr %ptr.float.iv, i64 -8
+ %val.first = load float, ptr %ptr.first.load, align 4
+ %min.idx.ext = sext i32 %min.idx to i64
+ %ptr.float.min = getelementptr float, ptr %0, i64 %min.idx.ext
+ %ptr.second.load = getelementptr i8, ptr %ptr.float.min, i64 -4
+ %val.current.min = load float, ptr %ptr.second.load, align 4
+ %cmp = fcmp contract ogt float %val.current.min, %val.first
+ %next.idx.trunc = trunc nsw i64 %indvars.iv.next.i to i32
+ %min.idx.next = select i1 %cmp, i32 %next.idx.trunc, i32 %min.idx
+ %loop.counter.next = add nsw i64 %loop.counter, -1
+ %loop.continue = icmp samesign ugt i64 %loop.counter, 1
+ br i1 %loop.continue, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+; Positive test: max-finding loop with `icmp sgt` (signed greater-than).
+; The recognizer is predicate-agnostic: the rewrite preserves the original
+; predicate, so transforming a max-finding loop is sound for the same
+; algebraic reason the min-finding loop is.
+define void @test_gvn_pattern_icmp_sgt(ptr %arr, i32 %initial_min_idx) {
+; CHECK-LABEL: define void @test_gvn_pattern_icmp_sgt(
+; CHECK-SAME: ptr [[ARR:%.*]], i32 [[INITIAL_MIN_IDX:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[HOIST_SEXT:%.*]] = sext i32 [[INITIAL_MIN_IDX]] to i64
+; CHECK-NEXT: [[HOIST_GEP1:%.*]] = getelementptr i32, ptr [[ARR]], i64 [[HOIST_SEXT]]
+; CHECK-NEXT: [[HOIST_GEP2:%.*]] = getelementptr i8, ptr [[HOIST_GEP1]], i64 -4
+; CHECK-NEXT: [[HOISTED_LOAD:%.*]] = load i32, ptr [[HOIST_GEP2]], align 4
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[KNOWN_MIN:%.*]] = phi i32 [ [[HOISTED_LOAD]], %[[ENTRY]] ], [ [[CURRENT_MIN:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[INDVARS_IV_I:%.*]] = phi i64 [ 1, %[[ENTRY]] ], [ [[INDVARS_IV_NEXT_I:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[LOOP_COUNTER:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[LOOP_COUNTER_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[MIN_IDX:%.*]] = phi i32 [ [[INITIAL_MIN_IDX]], %[[ENTRY]] ], [ [[MIN_IDX_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[INDVARS_IV_NEXT_I]] = add nsw i64 [[INDVARS_IV_I]], -1
+; CHECK-NEXT: [[PTR_I32_IV:%.*]] = getelementptr i32, ptr [[ARR]], i64 [[INDVARS_IV_I]]
+; CHECK-NEXT: [[PTR_FIRST_LOAD:%.*]] = getelementptr i8, ptr [[PTR_I32_IV]], i64 -8
+; CHECK-NEXT: [[VAL_FIRST:%.*]] = load i32, ptr [[PTR_FIRST_LOAD]], align 4
+; CHECK-NEXT: [[MIN_IDX_EXT:%.*]] = sext i32 [[MIN_IDX]] to i64
+; CHECK-NEXT: [[PTR_I32_MIN:%.*]] = getelementptr i32, ptr [[ARR]], i64 [[MIN_IDX_EXT]]
+; CHECK-NEXT: [[PTR_SECOND_LOAD:%.*]] = getelementptr i8, ptr [[PTR_I32_MIN]], i64 -4
+; CHECK-NEXT: [[CMP:%.*]] = icmp sgt i32 [[VAL_FIRST]], [[KNOWN_MIN]]
+; CHECK-NEXT: [[NEXT_IDX_TRUNC:%.*]] = trunc nsw i64 [[INDVARS_IV_NEXT_I]] to i32
+; CHECK-NEXT: [[MIN_IDX_NEXT]] = select i1 [[CMP]], i32 [[NEXT_IDX_TRUNC]], i32 [[MIN_IDX]]
+; CHECK-NEXT: [[LOOP_COUNTER_NEXT]] = add nsw i64 [[LOOP_COUNTER]], -1
+; CHECK-NEXT: [[LOOP_CONTINUE:%.*]] = icmp samesign ugt i64 [[LOOP_COUNTER]], 1
+; CHECK-NEXT: [[CURRENT_MIN]] = select i1 [[CMP]], i32 [[VAL_FIRST]], i32 [[KNOWN_MIN]]
+; CHECK-NEXT: br i1 [[LOOP_CONTINUE]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %indvars.iv.i = phi i64 [ 1, %entry ], [ %indvars.iv.next.i, %loop ]
+ %loop.counter = phi i64 [ 0, %entry ], [ %loop.counter.next, %loop ]
+ %min.idx = phi i32 [ %initial_min_idx, %entry ], [ %min.idx.next, %loop ]
+ %indvars.iv.next.i = add nsw i64 %indvars.iv.i, -1
+ %ptr.i32.iv = getelementptr i32, ptr %arr, i64 %indvars.iv.i
+ %ptr.first.load = getelementptr i8, ptr %ptr.i32.iv, i64 -8
+ %val.first = load i32, ptr %ptr.first.load, align 4
+ %min.idx.ext = sext i32 %min.idx to i64
+ %ptr.i32.min = getelementptr i32, ptr %arr, i64 %min.idx.ext
+ %ptr.second.load = getelementptr i8, ptr %ptr.i32.min, i64 -4
+ %val.current.min = load i32, ptr %ptr.second.load, align 4
+ %cmp = icmp sgt i32 %val.first, %val.current.min
+ %next.idx.trunc = trunc nsw i64 %indvars.iv.next.i to i32
+ %min.idx.next = select i1 %cmp, i32 %next.idx.trunc, i32 %min.idx
+ %loop.counter.next = add nsw i64 %loop.counter, -1
+ %loop.continue = icmp samesign ugt i64 %loop.counter, 1
+ br i1 %loop.continue, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+; Positive test: unsigned-min loop with `icmp ult`. Documents that the
+; recognizer accepts unsigned predicates as well as signed.
+define void @test_gvn_pattern_icmp_ult(ptr %arr, i32 %initial_min_idx) {
+; CHECK-LABEL: define void @test_gvn_pattern_icmp_ult(
+; CHECK-SAME: ptr [[ARR:%.*]], i32 [[INITIAL_MIN_IDX:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[HOIST_SEXT:%.*]] = sext i32 [[INITIAL_MIN_IDX]] to i64
+; CHECK-NEXT: [[HOIST_GEP1:%.*]] = getelementptr i32, ptr [[ARR]], i64 [[HOIST_SEXT]]
+; CHECK-NEXT: [[HOIST_GEP2:%.*]] = getelementptr i8, ptr [[HOIST_GEP1]], i64 -4
+; CHECK-NEXT: [[HOISTED_LOAD:%.*]] = load i32, ptr [[HOIST_GEP2]], align 4
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[KNOWN_MIN:%.*]] = phi i32 [ [[HOISTED_LOAD]], %[[ENTRY]] ], [ [[CURRENT_MIN:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[INDVARS_IV_I:%.*]] = phi i64 [ 1, %[[ENTRY]] ], [ [[INDVARS_IV_NEXT_I:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[LOOP_COUNTER:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[LOOP_COUNTER_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[MIN_IDX:%.*]] = phi i32 [ [[INITIAL_MIN_IDX]], %[[ENTRY]] ], [ [[MIN_IDX_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[INDVARS_IV_NEXT_I]] = add nsw i64 [[INDVARS_IV_I]], -1
+; CHECK-NEXT: [[PTR_I32_IV:%.*]] = getelementptr i32, ptr [[ARR]], i64 [[INDVARS_IV_I]]
+; CHECK-NEXT: [[PTR_FIRST_LOAD:%.*]] = getelementptr i8, ptr [[PTR_I32_IV]], i64 -8
+; CHECK-NEXT: [[VAL_FIRST:%.*]] = load i32, ptr [[PTR_FIRST_LOAD]], align 4
+; CHECK-NEXT: [[MIN_IDX_EXT:%.*]] = sext i32 [[MIN_IDX]] to i64
+; CHECK-NEXT: [[PTR_I32_MIN:%.*]] = getelementptr i32, ptr [[ARR]], i64 [[MIN_IDX_EXT]]
+; CHECK-NEXT: [[PTR_SECOND_LOAD:%.*]] = getelementptr i8, ptr [[PTR_I32_MIN]], i64 -4
+; CHECK-NEXT: [[CMP:%.*]] = icmp ult i32 [[VAL_FIRST]], [[KNOWN_MIN]]
+; CHECK-NEXT: [[NEXT_IDX_TRUNC:%.*]] = trunc nsw i64 [[INDVARS_IV_NEXT_I]] to i32
+; CHECK-NEXT: [[MIN_IDX_NEXT]] = select i1 [[CMP]], i32 [[NEXT_IDX_TRUNC]], i32 [[MIN_IDX]]
+; CHECK-NEXT: [[LOOP_COUNTER_NEXT]] = add nsw i64 [[LOOP_COUNTER]], -1
+; CHECK-NEXT: [[LOOP_CONTINUE:%.*]] = icmp samesign ugt i64 [[LOOP_COUNTER]], 1
+; CHECK-NEXT: [[CURRENT_MIN]] = select i1 [[CMP]], i32 [[VAL_FIRST]], i32 [[KNOWN_MIN]]
+; CHECK-NEXT: br i1 [[LOOP_CONTINUE]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %indvars.iv.i = phi i64 [ 1, %entry ], [ %indvars.iv.next.i, %loop ]
+ %loop.counter = phi i64 [ 0, %entry ], [ %loop.counter.next, %loop ]
+ %min.idx = phi i32 [ %initial_min_idx, %entry ], [ %min.idx.next, %loop ]
+ %indvars.iv.next.i = add nsw i64 %indvars.iv.i, -1
+ %ptr.i32.iv = getelementptr i32, ptr %arr, i64 %indvars.iv.i
+ %ptr.first.load = getelementptr i8, ptr %ptr.i32.iv, i64 -8
+ %val.first = load i32, ptr %ptr.first.load, align 4
+ %min.idx.ext = sext i32 %min.idx to i64
+ %ptr.i32.min = getelementptr i32, ptr %arr, i64 %min.idx.ext
+ %ptr.second.load = getelementptr i8, ptr %ptr.i32.min, i64 -4
+ %val.current.min = load i32, ptr %ptr.second.load, align 4
+ %cmp = icmp ult i32 %val.first, %val.current.min
+ %next.idx.trunc = trunc nsw i64 %indvars.iv.next.i to i32
+ %min.idx.next = select i1 %cmp, i32 %next.idx.trunc, i32 %min.idx
+ %loop.counter.next = add nsw i64 %loop.counter, -1
+ %loop.continue = icmp samesign ugt i64 %loop.counter, 1
+ br i1 %loop.continue, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+; Positive test: equality predicate. Degenerate but semantically valid -
+; the rewrite is sound for any predicate, including eq. Documents the
+; predicate-agnostic claim end-to-end. After the rewrite, downstream GVN
+; folds the loop-carried phi (it never changes value when cmp is eq), so
+; only the hoisted load survives - which still proves the in-loop val.min
+; load was eliminated.
+define void @test_gvn_pattern_icmp_eq(ptr %arr, i32 %initial_min_idx) {
+; CHECK-LABEL: define void @test_gvn_pattern_icmp_eq(
+; CHECK-SAME: ptr [[ARR:%.*]], i32 [[INITIAL_MIN_IDX:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[HOIST_SEXT:%.*]] = sext i32 [[INITIAL_MIN_IDX]] to i64
+; CHECK-NEXT: [[HOIST_GEP1:%.*]] = getelementptr i32, ptr [[ARR]], i64 [[HOIST_SEXT]]
+; CHECK-NEXT: [[HOIST_GEP2:%.*]] = getelementptr i8, ptr [[HOIST_GEP1]], i64 -4
+; CHECK-NEXT: [[HOISTED_LOAD:%.*]] = load i32, ptr [[HOIST_GEP2]], align 4
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[INDVARS_IV_I:%.*]] = phi i64 [ 1, %[[ENTRY]] ], [ [[INDVARS_IV_NEXT_I:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[LOOP_COUNTER:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[LOOP_COUNTER_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[MIN_IDX:%.*]] = phi i32 [ [[INITIAL_MIN_IDX]], %[[ENTRY]] ], [ [[MIN_IDX_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT: [[INDVARS_IV_NEXT_I]] = add nsw i64 [[INDVARS_IV_I]], -1
+; CHECK-NEXT: [[PTR_I32_IV:%.*]] = getelementptr i32, ptr [[ARR]], i64 [[INDVARS_IV_I]]
+; CHECK-NEXT: [[PTR_FIRST_LOAD:%.*]] = getelementptr i8, ptr [[PTR_I32_IV]], i64 -8
+; CHECK-NEXT: [[VAL_FIRST:%.*]] = load i32, ptr [[PTR_FIRST_LOAD]], align 4
+; CHECK-NEXT: [[MIN_IDX_EXT:%.*]] = sext i32 [[MIN_IDX]] to i64
+; CHECK-NEXT: [[PTR_I32_MIN:%.*]] = getelementptr i32, ptr [[ARR]], i64 [[MIN_IDX_EXT]]
+; CHECK-NEXT: [[PTR_SECOND_LOAD:%.*]] = getelementptr i8, ptr [[PTR_I32_MIN]], i64 -4
+; CHECK-NEXT: [[CMP:%.*]] = icmp eq i32 [[VAL_FIRST]], [[HOISTED_LOAD]]
+; CHECK-NEXT: [[NEXT_IDX_TRUNC:%.*]] = trunc nsw i64 [[INDVARS_IV_NEXT_I]] to i32
+; CHECK-NEXT: [[MIN_IDX_NEXT]] = select i1 [[CMP]], i32 [[NEXT_IDX_TRUNC]], i32 [[MIN_IDX]]
+; CHECK-NEXT: [[LOOP_COUNTER_NEXT]] = add nsw i64 [[LOOP_COUNTER]], -1
+; CHECK-NEXT: [[LOOP_CONTINUE:%.*]] = icmp samesign ugt i64 [[LOOP_COUNTER]], 1
+; CHECK-NEXT: br i1 [[LOOP_CONTINUE]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %indvars.iv.i = phi i64 [ 1, %entry ], [ %indvars.iv.next.i, %loop ]
+ %loop.counter = phi i64 [ 0, %entry ], [ %loop.counter.next, %loop ]
+ %min.idx = phi i32 [ %initial_min_idx, %entry ], [ %min.idx.next, %loop ]
+ %indvars.iv.next.i = add nsw i64 %indvars.iv.i, -1
+ %ptr.i32.iv = getelementptr i32, ptr %arr, i64 %indvars.iv.i
+ %ptr.first.load = getelementptr i8, ptr %ptr.i32.iv, i64 -8
+ %val.first = load i32, ptr %ptr.first.load, align 4
+ %min.idx.ext = sext i32 %min.idx to i64
+ %ptr.i32.min = getelementptr i32, ptr %arr, i64 %min.idx.ext
+ %ptr.second.load = getelementptr i8, ptr %ptr.i32.min, i64 -4
+ %val.current.min = load i32, ptr %ptr.second.load, align 4
+ %cmp = icmp eq i32 %val.first, %val.current.min
+ %next.idx.trunc = trunc nsw i64 %indvars.iv.next.i to i32
+ %min.idx.next = select i1 %cmp, i32 %next.idx.trunc, i32 %min.idx
+ %loop.counter.next = add nsw i64 %loop.counter, -1
+ %loop.continue = icmp samesign ugt i64 %loop.counter, 1
+ br i1 %loop.continue, label %loop, label %exit
+
+exit:
+ ret void
+}
More information about the llvm-commits
mailing list