[llvm] [ConstraintElim] Bound non-increasing header IVs by start value. (PR #217921)
Florian Hahn via llvm-commits
llvm-commits at lists.llvm.org
Sat Aug 22 06:43:43 PDT 2026
https://github.com/fhahn updated https://github.com/llvm/llvm-project/pull/217921
>From d956708ec13a4cd57ae25771a759451957b91a9a Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Fri, 21 Aug 2026 14:45:23 +0100
Subject: [PATCH 1/3] [ConstraintElim] Add more tests
---
.../monotonic-int-phis-decrement.ll | 214 ++++++++++++++++++
.../constraint-eliminiation-interactions.ll | 60 +++++
2 files changed, 274 insertions(+)
diff --git a/llvm/test/Transforms/ConstraintElimination/monotonic-int-phis-decrement.ll b/llvm/test/Transforms/ConstraintElimination/monotonic-int-phis-decrement.ll
index b1895c0881df8..06bd70da4335a 100644
--- a/llvm/test/Transforms/ConstraintElimination/monotonic-int-phis-decrement.ll
+++ b/llvm/test/Transforms/ConstraintElimination/monotonic-int-phis-decrement.ll
@@ -419,3 +419,217 @@ loop.latch:
exit:
ret void
}
+
+; The upper bound does not depend on the step being exactly -1.
+define void @add_rec_decreasing_nsw_step_minus_2(i8 %start, i8 %b) {
+; CHECK-LABEL: define void @add_rec_decreasing_nsw_step_minus_2(
+; CHECK-SAME: i8 [[START:%.*]], i8 [[B:%.*]]) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT: br label [[LOOP_HEADER:%.*]]
+; CHECK: loop.header:
+; CHECK-NEXT: [[K_0:%.*]] = phi i8 [ [[START]], [[ENTRY:%.*]] ], [ [[K_DEC:%.*]], [[LOOP_LATCH:%.*]] ]
+; CHECK-NEXT: [[CMP2_NOT:%.*]] = icmp eq i8 [[K_0]], [[B]]
+; CHECK-NEXT: br i1 [[CMP2_NOT]], label [[EXIT:%.*]], label [[LOOP_LATCH]]
+; CHECK: loop.latch:
+; CHECK-NEXT: [[S:%.*]] = icmp sle i8 [[K_0]], [[START]]
+; CHECK-NEXT: call void @use(i1 [[S]])
+; CHECK-NEXT: [[K_DEC]] = add nsw i8 [[K_0]], -2
+; CHECK-NEXT: br label [[LOOP_HEADER]]
+; CHECK: exit:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop.header
+
+loop.header:
+ %k.0 = phi i8 [ %start, %entry ], [ %k.dec, %loop.latch ]
+ %cmp2.not = icmp eq i8 %k.0, %b
+ br i1 %cmp2.not, label %exit, label %loop.latch
+
+loop.latch:
+ %s = icmp sle i8 %k.0, %start
+ call void @use(i1 %s)
+ %k.dec = add nsw i8 %k.0, -2
+ br label %loop.header
+
+exit:
+ ret void
+}
+
+define void @add_rec_decreasing_nsw_compare_in_header(i8 %start, i8 %b) {
+; CHECK-LABEL: define void @add_rec_decreasing_nsw_compare_in_header(
+; CHECK-SAME: i8 [[START:%.*]], i8 [[B:%.*]]) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT: br label [[LOOP_HEADER:%.*]]
+; CHECK: loop.header:
+; CHECK-NEXT: [[K_0:%.*]] = phi i8 [ [[START]], [[ENTRY:%.*]] ], [ [[K_DEC:%.*]], [[LOOP_LATCH:%.*]] ]
+; CHECK-NEXT: [[S:%.*]] = icmp sle i8 [[K_0]], [[START]]
+; CHECK-NEXT: call void @use(i1 [[S]])
+; CHECK-NEXT: [[CMP2_NOT:%.*]] = icmp eq i8 [[K_0]], [[B]]
+; CHECK-NEXT: br i1 [[CMP2_NOT]], label [[EXIT:%.*]], label [[LOOP_LATCH]]
+; CHECK: loop.latch:
+; CHECK-NEXT: [[K_DEC]] = add nsw i8 [[K_0]], -1
+; CHECK-NEXT: br label [[LOOP_HEADER]]
+; CHECK: exit:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop.header
+
+loop.header:
+ %k.0 = phi i8 [ %start, %entry ], [ %k.dec, %loop.latch ]
+ %s = icmp sle i8 %k.0, %start
+ call void @use(i1 %s)
+ %cmp2.not = icmp eq i8 %k.0, %b
+ br i1 %cmp2.not, label %exit, label %loop.latch
+
+loop.latch:
+ %k.dec = add nsw i8 %k.0, -1
+ br label %loop.header
+
+exit:
+ ret void
+}
+
+define void @add_rec_decreasing_nsw_second_phi(i8 %start, i8 %start2, i8 %b) {
+; CHECK-LABEL: define void @add_rec_decreasing_nsw_second_phi(
+; CHECK-SAME: i8 [[START:%.*]], i8 [[START2:%.*]], i8 [[B:%.*]]) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT: br label [[LOOP_HEADER:%.*]]
+; CHECK: loop.header:
+; CHECK-NEXT: [[K_0:%.*]] = phi i8 [ [[START]], [[ENTRY:%.*]] ], [ [[K_DEC:%.*]], [[LOOP_LATCH:%.*]] ]
+; CHECK-NEXT: [[J_0:%.*]] = phi i8 [ [[START2]], [[ENTRY]] ], [ [[J_DEC:%.*]], [[LOOP_LATCH]] ]
+; CHECK-NEXT: [[CMP2_NOT:%.*]] = icmp eq i8 [[K_0]], [[B]]
+; CHECK-NEXT: br i1 [[CMP2_NOT]], label [[EXIT:%.*]], label [[LOOP_LATCH]]
+; CHECK: loop.latch:
+; CHECK-NEXT: [[S:%.*]] = icmp sle i8 [[J_0]], [[START2]]
+; CHECK-NEXT: call void @use(i1 [[S]])
+; CHECK-NEXT: [[K_DEC]] = add nsw i8 [[K_0]], -1
+; CHECK-NEXT: [[J_DEC]] = add nsw i8 [[J_0]], -3
+; CHECK-NEXT: br label [[LOOP_HEADER]]
+; CHECK: exit:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop.header
+
+loop.header:
+ %k.0 = phi i8 [ %start, %entry ], [ %k.dec, %loop.latch ]
+ %j.0 = phi i8 [ %start2, %entry ], [ %j.dec, %loop.latch ]
+ %cmp2.not = icmp eq i8 %k.0, %b
+ br i1 %cmp2.not, label %exit, label %loop.latch
+
+loop.latch:
+ %s = icmp sle i8 %j.0, %start2
+ call void @use(i1 %s)
+ %k.dec = add nsw i8 %k.0, -1
+ %j.dec = add nsw i8 %j.0, -3
+ br label %loop.header
+
+exit:
+ ret void
+}
+
+define void @add_rec_decreasing_nsw_slt_exit(i8 %start, i8 %b) {
+; CHECK-LABEL: define void @add_rec_decreasing_nsw_slt_exit(
+; CHECK-SAME: i8 [[START:%.*]], i8 [[B:%.*]]) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT: br label [[LOOP_HEADER:%.*]]
+; CHECK: loop.header:
+; CHECK-NEXT: [[K_0:%.*]] = phi i8 [ [[START]], [[ENTRY:%.*]] ], [ [[K_DEC:%.*]], [[LOOP_LATCH:%.*]] ]
+; CHECK-NEXT: [[EC:%.*]] = icmp slt i8 [[K_0]], [[B]]
+; CHECK-NEXT: br i1 [[EC]], label [[EXIT:%.*]], label [[LOOP_LATCH]]
+; CHECK: loop.latch:
+; CHECK-NEXT: [[S:%.*]] = icmp sle i8 [[K_0]], [[START]]
+; CHECK-NEXT: call void @use(i1 [[S]])
+; CHECK-NEXT: [[K_DEC]] = add nsw i8 [[K_0]], -1
+; CHECK-NEXT: br label [[LOOP_HEADER]]
+; CHECK: exit:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop.header
+
+loop.header:
+ %k.0 = phi i8 [ %start, %entry ], [ %k.dec, %loop.latch ]
+ %ec = icmp slt i8 %k.0, %b
+ br i1 %ec, label %exit, label %loop.latch
+
+loop.latch:
+ %s = icmp sle i8 %k.0, %start
+ call void @use(i1 %s)
+ %k.dec = add nsw i8 %k.0, -1
+ br label %loop.header
+
+exit:
+ ret void
+}
+
+define void @add_rec_decreasing_no_wrap_flags_wraps_past_start() {
+; CHECK-LABEL: define void @add_rec_decreasing_no_wrap_flags_wraps_past_start() {
+; CHECK-NEXT: entry:
+; CHECK-NEXT: br label [[LOOP_HEADER:%.*]]
+; CHECK: loop.header:
+; CHECK-NEXT: [[K_0:%.*]] = phi i8 [ 5, [[ENTRY:%.*]] ], [ [[K_DEC:%.*]], [[LOOP_LATCH:%.*]] ]
+; CHECK-NEXT: [[S:%.*]] = icmp sle i8 [[K_0]], 5
+; CHECK-NEXT: call void @use(i1 [[S]])
+; CHECK-NEXT: br label [[LOOP_LATCH]]
+; CHECK: loop.latch:
+; CHECK-NEXT: [[K_DEC]] = add i8 [[K_0]], -1
+; CHECK-NEXT: [[EC:%.*]] = icmp eq i8 [[K_DEC]], 5
+; CHECK-NEXT: br i1 [[EC]], label [[EXIT:%.*]], label [[LOOP_HEADER]]
+; CHECK: exit:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop.header
+
+loop.header:
+ %k.0 = phi i8 [ 5, %entry ], [ %k.dec, %loop.latch ]
+ %s = icmp sle i8 %k.0, 5
+ call void @use(i1 %s)
+ br label %loop.latch
+
+loop.latch:
+ %k.dec = add i8 %k.0, -1
+ %ec = icmp eq i8 %k.dec, 5
+ br i1 %ec, label %exit, label %loop.header
+
+exit:
+ ret void
+}
+
+define void @zero_start_nuw_no_upper_bound(i8 %b) {
+; CHECK-LABEL: define void @zero_start_nuw_no_upper_bound(
+; CHECK-SAME: i8 [[B:%.*]]) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT: br label [[LOOP_HEADER:%.*]]
+; CHECK: loop.header:
+; CHECK-NEXT: [[IV:%.*]] = phi i8 [ 0, [[ENTRY:%.*]] ], [ [[IV_NEXT:%.*]], [[LOOP_LATCH:%.*]] ]
+; CHECK-NEXT: [[S:%.*]] = icmp sle i8 [[IV]], 0
+; CHECK-NEXT: call void @use(i1 [[S]])
+; CHECK-NEXT: [[EC:%.*]] = icmp eq i8 [[IV]], [[B]]
+; CHECK-NEXT: br i1 [[EC]], label [[EXIT:%.*]], label [[LOOP_LATCH]]
+; CHECK: loop.latch:
+; CHECK-NEXT: [[IV_NEXT]] = add nuw i8 [[IV]], 1
+; CHECK-NEXT: br label [[LOOP_HEADER]]
+; CHECK: exit:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop.header
+
+loop.header:
+ %iv = phi i8 [ 0, %entry ], [ %iv.next, %loop.latch ]
+ %s = icmp sle i8 %iv, 0
+ call void @use(i1 %s)
+ %ec = icmp eq i8 %iv, %b
+ br i1 %ec, label %exit, label %loop.latch
+
+loop.latch:
+ %iv.next = add nuw i8 %iv, 1
+ br label %loop.header
+
+exit:
+ ret void
+}
diff --git a/llvm/test/Transforms/PhaseOrdering/constraint-eliminiation-interactions.ll b/llvm/test/Transforms/PhaseOrdering/constraint-eliminiation-interactions.ll
index ce7485ba98cfb..27ec5f662d3a7 100644
--- a/llvm/test/Transforms/PhaseOrdering/constraint-eliminiation-interactions.ll
+++ b/llvm/test/Transforms/PhaseOrdering/constraint-eliminiation-interactions.ll
@@ -1,6 +1,8 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
; RUN: opt -O3 -S %s | FileCheck %s
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+
; SCEV can infer tighter wrap flags for %iv, which in turn can be used by
; ConstraintElimination.
define void @sum_of_induction_and_guard(i8 %n, i8 %j) mustprogress {
@@ -159,3 +161,61 @@ else:
%res = phi i64 [ %v, %cont ], [ 0, %entry ]
ret i64 %res
}
+
+define void @shl_flags(i32 %n) {
+; CHECK-LABEL: define void @shl_flags(
+; CHECK-SAME: i32 [[N:%.*]]) local_unnamed_addr {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[START:%.*]] = lshr i32 [[N]], 1
+; CHECK-NEXT: [[TMP0:%.*]] = lshr i32 [[N]], 1
+; CHECK-NEXT: [[TMP2:%.*]] = zext nneg i32 [[TMP0]] to i64
+; CHECK-NEXT: [[TMP1:%.*]] = zext nneg i32 [[START]] to i64
+; CHECK-NEXT: br label %[[LOOP_HEADER:.*]]
+; CHECK: [[LOOP_HEADER]]:
+; CHECK-NEXT: [[EXT:%.*]] = phi i64 [ [[INDVARS_IV_NEXT:%.*]], %[[LOOP_LATCH:.*]] ], [ [[TMP2]], %[[ENTRY]] ]
+; CHECK-NEXT: [[GUARD:%.*]] = icmp sgt i64 [[EXT]], [[TMP1]]
+; CHECK-NEXT: br i1 [[GUARD]], label %[[LOOP_LATCH]], label %[[BODY:.*]]
+; CHECK: [[BODY]]:
+; CHECK-NEXT: [[TMP3:%.*]] = trunc nuw i64 [[EXT]] to i32
+; CHECK-NEXT: [[SHL:%.*]] = shl nuw i32 [[TMP3]], 1
+; CHECK-NEXT: [[POS:%.*]] = icmp sgt i32 [[SHL]], 0
+; CHECK-NEXT: br i1 [[POS]], label %[[CALL_BB:.*]], label %[[EXIT:.*]]
+; CHECK: [[CALL_BB]]:
+; CHECK-NEXT: [[V:%.*]] = tail call i32 @load(i64 [[EXT]])
+; CHECK-NEXT: br label %[[LOOP_LATCH]]
+; CHECK: [[LOOP_LATCH]]:
+; CHECK-NEXT: [[DONE:%.*]] = icmp eq i64 [[EXT]], 0
+; CHECK-NEXT: [[INDVARS_IV_NEXT]] = add nsw i64 [[EXT]], -1
+; CHECK-NEXT: br i1 [[DONE]], label %[[EXIT]], label %[[LOOP_HEADER]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ %start = lshr i32 %n, 1
+ br label %loop.header
+
+loop.header:
+ %iv = phi i32 [ %start, %entry ], [ %iv.next, %loop.latch ]
+ %guard = icmp slt i32 %start, %iv
+ br i1 %guard, label %loop.latch, label %body
+
+body:
+ %shl = shl i32 %iv, 1
+ %pos = icmp sgt i32 %shl, 0
+ br i1 %pos, label %call.bb, label %exit
+
+call.bb:
+ %ext = zext i32 %iv to i64
+ %v = call i32 @load(i64 %ext)
+ br label %loop.latch
+
+loop.latch:
+ %iv.next = add i32 %iv, -1
+ %done = icmp eq i32 %iv, 0
+ br i1 %done, label %exit, label %loop.header
+
+exit:
+ ret void
+}
+
+declare i32 @load(i64)
>From 4eb6708bfb5ba81e7593ef06a1c0038725bea2c3 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Thu, 20 Aug 2026 16:32:18 +0100
Subject: [PATCH 2/3] [ConstraintElim] Bound non-increasing header IVs by start
value.
Generalize addLowerBoundsForHeaderInductions to also support bounding
decrementing IVs: if the IV gets decremented w/o wapping in the signed
sense, it will never exceed the start value and we can add PN s>=
StartValue.
This helps to remove a number of branches in real-world workloads:
https://github.com/dtcxzyw/llvm-opt-benchmark-nightly/pull/984
Note that in a few instances (found 2), the removed branches result in
flags dropped. We should be able to recover those (and more) by using
info from ConstrantElimination to strengthen flags.
Alive2 Proof: https://alive2.llvm.org/ce/z/7t_xHX
---
.../Scalar/ConstraintElimination.cpp | 100 +++++++++++-------
...nduction-condition-in-loop-exit-postinc.ll | 6 +-
.../monotonic-int-phis-decrement.ll | 18 ++--
.../constraint-eliminiation-interactions.ll | 15 +--
4 files changed, 73 insertions(+), 66 deletions(-)
diff --git a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
index 6e8552b04b4eb..ccdc51d67ac60 100644
--- a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
@@ -172,6 +172,17 @@ struct FactOrCheck {
bool isConditionFact() const { return Ty == EntryTy::ConditionFact; }
};
+/// The senses in which an induction phi is monotonic, together with the
+/// direction it moves in.
+struct MonotonicInfo {
+ /// True if the phi steps by a negative constant.
+ bool Decreasing = false;
+ /// True if the phi is monotonic in the unsigned sense.
+ bool Unsigned = false;
+ /// True if the phi is monotonic in the signed sense.
+ bool Signed = false;
+};
+
/// Keep state required to build worklist.
struct State {
DominatorTree &DT;
@@ -188,16 +199,16 @@ struct State {
void addInfoFor(BasicBlock &BB);
/// If \p BB is a loop header, bound each induction phi in it by its start
- /// value, if it is non-decreasing.
- void addLowerBoundsForHeaderInductions(BasicBlock &BB);
+ /// value.
+ void addBoundsForHeaderInductions(BasicBlock &BB);
/// Try to add facts for loop inductions (AddRecs) in EQ/NE compares
/// controlling the loop header.
void addInfoForInductions(BasicBlock &BB);
- /// Returns the senses, {unsigned, signed}, in which stepping the induction
- /// phi \p PN, starting at \p Start, to \p Step cannot decrease it.
- std::pair<bool, bool> getNonDecreasingInfo(PHINode &PN, Value *Step);
+ /// Returns the direction the induction phi \p PN with backedge value \p Step
+ /// moves in, and the senses in which it is monotonic in that direction.
+ MonotonicInfo getMonotonicityInfo(PHINode &PN, Value *Step);
/// Returns true if we can add a known condition from BB to its successor
/// block Succ.
@@ -986,40 +997,46 @@ getStartAndBackedgeValue(const PHINode &PN, const BasicBlock *LoopPred) {
return {PN.getIncomingValue(StartIdx), PN.getIncomingValue(1 - StartIdx)};
}
-std::pair<bool, bool> State::getNonDecreasingInfo(PHINode &PN, Value *Step) {
- bool Unsigned = false, Signed = false;
+MonotonicInfo State::getMonotonicityInfo(PHINode &PN, Value *Step) {
+ MonotonicInfo Info;
const APInt *StepOffset = nullptr;
if (match(Step, m_c_Add(m_Specific(&PN), m_APInt(StepOffset)))) {
- if (StepOffset->isNegative())
- return {false, false};
+ Info.Decreasing = StepOffset->isNegative();
const auto *Add = cast<OverflowingBinaryOperator>(Step);
- Unsigned = Add->hasNoUnsignedWrap();
- Signed = Add->hasNoSignedWrap();
+ Info.Unsigned = !Info.Decreasing && Add->hasNoUnsignedWrap();
+ Info.Signed = Add->hasNoSignedWrap();
} else if (const auto *GEP = dyn_cast<GEPOperator>(Step)) {
+ // TODO: Handle the non-increasing direction, which needs a nusw GEP with a
+ // negative constant offset.
const DataLayout &DL = PN.getDataLayout();
APInt GEPOffset(DL.getIndexTypeSizeInBits(GEP->getType()), 0);
- Unsigned = GEP->getPointerOperand() == &PN &&
- (GEP->hasNoUnsignedWrap() ||
- ((GEP->hasNoUnsignedSignedWrap() &&
- GEP->accumulateConstantOffset(DL, GEPOffset) &&
- !GEPOffset.isNegative())));
+ Info.Unsigned = GEP->getPointerOperand() == &PN &&
+ (GEP->hasNoUnsignedWrap() ||
+ ((GEP->hasNoUnsignedSignedWrap() &&
+ GEP->accumulateConstantOffset(DL, GEPOffset) &&
+ !GEPOffset.isNegative())));
}
// Forming the SCEV of a phi is expensive, so only consult it for a PN + C
// step whose no-wrap flags prove nothing.
- if (Unsigned || Signed || !StepOffset)
- return {Unsigned, Signed};
+ if (Info.Unsigned || Info.Signed || !StepOffset)
+ return Info;
const auto *AR = dyn_cast<SCEVAddRecExpr>(SE.getSCEV(&PN));
if (!AR)
- return {false, false};
- return {SE.getMonotonicPredicateType(AR, CmpInst::ICMP_UGT) ==
- ScalarEvolution::MonotonicallyIncreasing,
- SE.getMonotonicPredicateType(AR, CmpInst::ICMP_SGT) ==
- ScalarEvolution::MonotonicallyIncreasing};
+ return Info;
+ ScalarEvolution::MonotonicPredicateType Expected =
+ Info.Decreasing ? ScalarEvolution::MonotonicallyDecreasing
+ : ScalarEvolution::MonotonicallyIncreasing;
+ auto IsMonotonic = [&](CmpInst::Predicate Pred) {
+ return SE.getMonotonicPredicateType(AR, Pred) == Expected;
+ };
+ Info.Signed = IsMonotonic(CmpInst::ICMP_SGT);
+ Info.Unsigned = !Info.Decreasing && IsMonotonic(CmpInst::ICMP_UGT);
+ return Info;
}
-void State::addLowerBoundsForHeaderInductions(BasicBlock &BB) {
+void State::addBoundsForHeaderInductions(BasicBlock &BB) {
Loop *L = LI.getLoopFor(&BB);
if (!L || L->getHeader() != &BB)
return;
@@ -1036,17 +1053,22 @@ void State::addLowerBoundsForHeaderInductions(BasicBlock &BB) {
if (!Start)
continue;
- auto [Unsigned, Signed] = getNonDecreasingInfo(PN, Step);
+ MonotonicInfo Info = getMonotonicityInfo(PN, Step);
// Every variable in the unsigned system already has a `V >= 0` row, so a
// zero start value would just duplicate it.
if (match(Start, m_Zero()))
- Unsigned = false;
- if (!Unsigned && !Signed)
+ Info.Unsigned = false;
+ if (!Info.Unsigned && !Info.Signed)
continue;
- CmpPredicate Pred(Unsigned ? CmpInst::ICMP_UGE : CmpInst::ICMP_SGE,
- /*HasSameSign=*/Unsigned && Signed);
- WorkList.push_back(FactOrCheck::getConditionFact(DTN, Pred, &PN, Start));
+ // A non-decreasing induction cannot step below its start value, and a
+ // non-increasing one cannot step above it.
+ Value *LHS = &PN, *RHS = Start;
+ if (Info.Decreasing)
+ std::swap(LHS, RHS);
+ CmpPredicate Pred(Info.Unsigned ? CmpInst::ICMP_UGE : CmpInst::ICMP_SGE,
+ /*HasSameSign=*/Info.Unsigned && Info.Signed);
+ WorkList.push_back(FactOrCheck::getConditionFact(DTN, Pred, LHS, RHS));
}
}
@@ -1122,6 +1144,8 @@ void State::addInfoForInductions(BasicBlock &BB) {
if (IncStep && (*IncStep != *StepOffset || StepOffset->isNegative()))
return;
+ MonotonicInfo Info = getMonotonicityInfo(*PN, Backedge);
+
// Handle negative steps.
if (StepOffset->isNegative()) {
// TODO: Extend to allow steps > -1.
@@ -1134,9 +1158,10 @@ void State::addInfoForInductions(BasicBlock &BB) {
WorkList.push_back(FactOrCheck::getConditionFact(
DTN, CmpInst::ICMP_UGE, StartValue, PN,
ConditionTy(CmpInst::ICMP_ULE, B, StartValue)));
- WorkList.push_back(FactOrCheck::getConditionFact(
- DTN, CmpInst::ICMP_SGE, StartValue, PN,
- ConditionTy(CmpInst::ICMP_SLE, B, StartValue)));
+ if (!(Info.Decreasing && Info.Signed))
+ WorkList.push_back(FactOrCheck::getConditionFact(
+ DTN, CmpInst::ICMP_SGE, StartValue, PN,
+ ConditionTy(CmpInst::ICMP_SLE, B, StartValue)));
// Add PN > B conditional on B <= StartValue which guarantees that the loop
// exits when reaching B with a step of -1.
WorkList.push_back(FactOrCheck::getConditionFact(
@@ -1148,9 +1173,6 @@ void State::addInfoForInductions(BasicBlock &BB) {
return;
}
- auto [MonotonicallyIncreasingUnsigned, MonotonicallyIncreasingSigned] =
- getNonDecreasingInfo(*PN, Backedge);
-
// Make sure AR either steps by 1 or that the value we compare against is a
// GEP based on the same start value and all offsets are a multiple of the
// step size, to guarantee that the induction will reach the value.
@@ -1186,10 +1208,10 @@ void State::addInfoForInductions(BasicBlock &BB) {
// restrictions on B and the step above.
ConditionTy StartBeforeBoundULE = {CmpInst::ICMP_ULE, LowerBound, B};
ConditionTy StartBeforeBoundSLE = {CmpInst::ICMP_SLE, LowerBound, B};
- if (!MonotonicallyIncreasingUnsigned && LowerBoundNUW)
+ if (!Info.Unsigned && LowerBoundNUW)
WorkList.push_back(FactOrCheck::getConditionFact(
DTN, CmpInst::ICMP_UGE, PN, StartValue, StartBeforeBoundULE));
- if (!MonotonicallyIncreasingSigned && LowerBoundNSW)
+ if (!Info.Signed && LowerBoundNSW)
WorkList.push_back(FactOrCheck::getConditionFact(
DTN, CmpInst::ICMP_SGE, PN, StartValue, StartBeforeBoundSLE));
@@ -1264,7 +1286,7 @@ static bool getConstraintFromMemoryAccess(GetElementPtrInst &GEP,
}
void State::addInfoFor(BasicBlock &BB) {
- addLowerBoundsForHeaderInductions(BB);
+ addBoundsForHeaderInductions(BB);
addInfoForInductions(BB);
auto &DL = BB.getDataLayout();
diff --git a/llvm/test/Transforms/ConstraintElimination/induction-condition-in-loop-exit-postinc.ll b/llvm/test/Transforms/ConstraintElimination/induction-condition-in-loop-exit-postinc.ll
index 06336f5f3218a..e5e4dab8fcec9 100644
--- a/llvm/test/Transforms/ConstraintElimination/induction-condition-in-loop-exit-postinc.ll
+++ b/llvm/test/Transforms/ConstraintElimination/induction-condition-in-loop-exit-postinc.ll
@@ -640,8 +640,7 @@ define void @header_postdec_negative_step_folds() {
; CHECK-NEXT: call void @use(i1 [[ULE]])
; CHECK-NEXT: [[SGT:%.*]] = icmp sgt i8 [[IV]], 2
; CHECK-NEXT: call void @use(i1 [[SGT]])
-; CHECK-NEXT: [[SLE:%.*]] = icmp sle i8 [[IV]], 5
-; CHECK-NEXT: call void @use(i1 [[SLE]])
+; CHECK-NEXT: call void @use(i1 true)
; CHECK-NEXT: br label %[[LOOP]]
; CHECK: [[EXIT]]:
; CHECK-NEXT: ret void
@@ -685,8 +684,7 @@ define void @latch_postdec_negative_step_folds() {
; CHECK-NEXT: call void @use(i1 [[ULE]])
; CHECK-NEXT: [[SGT:%.*]] = icmp sgt i8 [[IV]], 2
; CHECK-NEXT: call void @use(i1 [[SGT]])
-; CHECK-NEXT: [[SLE:%.*]] = icmp sle i8 [[IV]], 5
-; CHECK-NEXT: call void @use(i1 [[SLE]])
+; CHECK-NEXT: call void @use(i1 true)
; CHECK-NEXT: br label %[[LOOP_LATCH]]
; CHECK: [[LOOP_LATCH]]:
; CHECK-NEXT: [[IV_NEXT]] = add i8 [[IV]], -1
diff --git a/llvm/test/Transforms/ConstraintElimination/monotonic-int-phis-decrement.ll b/llvm/test/Transforms/ConstraintElimination/monotonic-int-phis-decrement.ll
index 06bd70da4335a..f2e4b42fc477a 100644
--- a/llvm/test/Transforms/ConstraintElimination/monotonic-int-phis-decrement.ll
+++ b/llvm/test/Transforms/ConstraintElimination/monotonic-int-phis-decrement.ll
@@ -269,8 +269,7 @@ define void @add_rec_decreasing_nsw_signed_monotonic(i8 %start, i8 %b) {
; CHECK-NEXT: [[CMP2_NOT:%.*]] = icmp eq i8 [[K_0]], [[B]]
; CHECK-NEXT: br i1 [[CMP2_NOT]], label [[EXIT:%.*]], label [[LOOP_LATCH]]
; CHECK: loop.latch:
-; CHECK-NEXT: [[S:%.*]] = icmp sle i8 [[K_0]], [[START]]
-; CHECK-NEXT: call void @use(i1 [[S]])
+; CHECK-NEXT: call void @use(i1 true)
; CHECK-NEXT: [[U:%.*]] = icmp ule i8 [[K_0]], [[START]]
; CHECK-NEXT: call void @use(i1 [[U]])
; CHECK-NEXT: [[K_DEC]] = add nsw i8 [[K_0]], -1
@@ -388,8 +387,7 @@ define void @add_rec_decreasing_nsw_from_scev(i8 %b) {
; CHECK-NEXT: [[CMP2_NOT:%.*]] = icmp eq i8 [[K_0]], [[B]]
; CHECK-NEXT: br i1 [[CMP2_NOT]], label [[EXIT:%.*]], label [[LOOP_BODY:%.*]]
; CHECK: loop.body:
-; CHECK-NEXT: [[S:%.*]] = icmp sle i8 [[K_0]], 100
-; CHECK-NEXT: call void @use(i1 [[S]])
+; CHECK-NEXT: call void @use(i1 true)
; CHECK-NEXT: [[POSITIVE:%.*]] = icmp sgt i8 [[K_0]], 0
; CHECK-NEXT: br i1 [[POSITIVE]], label [[LOOP_LATCH]], label [[EXIT]]
; CHECK: loop.latch:
@@ -431,8 +429,7 @@ define void @add_rec_decreasing_nsw_step_minus_2(i8 %start, i8 %b) {
; CHECK-NEXT: [[CMP2_NOT:%.*]] = icmp eq i8 [[K_0]], [[B]]
; CHECK-NEXT: br i1 [[CMP2_NOT]], label [[EXIT:%.*]], label [[LOOP_LATCH]]
; CHECK: loop.latch:
-; CHECK-NEXT: [[S:%.*]] = icmp sle i8 [[K_0]], [[START]]
-; CHECK-NEXT: call void @use(i1 [[S]])
+; CHECK-NEXT: call void @use(i1 true)
; CHECK-NEXT: [[K_DEC]] = add nsw i8 [[K_0]], -2
; CHECK-NEXT: br label [[LOOP_HEADER]]
; CHECK: exit:
@@ -463,8 +460,7 @@ define void @add_rec_decreasing_nsw_compare_in_header(i8 %start, i8 %b) {
; CHECK-NEXT: br label [[LOOP_HEADER:%.*]]
; CHECK: loop.header:
; CHECK-NEXT: [[K_0:%.*]] = phi i8 [ [[START]], [[ENTRY:%.*]] ], [ [[K_DEC:%.*]], [[LOOP_LATCH:%.*]] ]
-; CHECK-NEXT: [[S:%.*]] = icmp sle i8 [[K_0]], [[START]]
-; CHECK-NEXT: call void @use(i1 [[S]])
+; CHECK-NEXT: call void @use(i1 true)
; CHECK-NEXT: [[CMP2_NOT:%.*]] = icmp eq i8 [[K_0]], [[B]]
; CHECK-NEXT: br i1 [[CMP2_NOT]], label [[EXIT:%.*]], label [[LOOP_LATCH]]
; CHECK: loop.latch:
@@ -502,8 +498,7 @@ define void @add_rec_decreasing_nsw_second_phi(i8 %start, i8 %start2, i8 %b) {
; CHECK-NEXT: [[CMP2_NOT:%.*]] = icmp eq i8 [[K_0]], [[B]]
; CHECK-NEXT: br i1 [[CMP2_NOT]], label [[EXIT:%.*]], label [[LOOP_LATCH]]
; CHECK: loop.latch:
-; CHECK-NEXT: [[S:%.*]] = icmp sle i8 [[J_0]], [[START2]]
-; CHECK-NEXT: call void @use(i1 [[S]])
+; CHECK-NEXT: call void @use(i1 true)
; CHECK-NEXT: [[K_DEC]] = add nsw i8 [[K_0]], -1
; CHECK-NEXT: [[J_DEC]] = add nsw i8 [[J_0]], -3
; CHECK-NEXT: br label [[LOOP_HEADER]]
@@ -540,8 +535,7 @@ define void @add_rec_decreasing_nsw_slt_exit(i8 %start, i8 %b) {
; CHECK-NEXT: [[EC:%.*]] = icmp slt i8 [[K_0]], [[B]]
; CHECK-NEXT: br i1 [[EC]], label [[EXIT:%.*]], label [[LOOP_LATCH]]
; CHECK: loop.latch:
-; CHECK-NEXT: [[S:%.*]] = icmp sle i8 [[K_0]], [[START]]
-; CHECK-NEXT: call void @use(i1 [[S]])
+; CHECK-NEXT: call void @use(i1 true)
; CHECK-NEXT: [[K_DEC]] = add nsw i8 [[K_0]], -1
; CHECK-NEXT: br label [[LOOP_HEADER]]
; CHECK: exit:
diff --git a/llvm/test/Transforms/PhaseOrdering/constraint-eliminiation-interactions.ll b/llvm/test/Transforms/PhaseOrdering/constraint-eliminiation-interactions.ll
index 27ec5f662d3a7..f710f66ae413f 100644
--- a/llvm/test/Transforms/PhaseOrdering/constraint-eliminiation-interactions.ll
+++ b/llvm/test/Transforms/PhaseOrdering/constraint-eliminiation-interactions.ll
@@ -166,24 +166,17 @@ define void @shl_flags(i32 %n) {
; CHECK-LABEL: define void @shl_flags(
; CHECK-SAME: i32 [[N:%.*]]) local_unnamed_addr {
; CHECK-NEXT: [[ENTRY:.*]]:
-; CHECK-NEXT: [[START:%.*]] = lshr i32 [[N]], 1
; CHECK-NEXT: [[TMP0:%.*]] = lshr i32 [[N]], 1
; CHECK-NEXT: [[TMP2:%.*]] = zext nneg i32 [[TMP0]] to i64
-; CHECK-NEXT: [[TMP1:%.*]] = zext nneg i32 [[START]] to i64
; CHECK-NEXT: br label %[[LOOP_HEADER:.*]]
; CHECK: [[LOOP_HEADER]]:
-; CHECK-NEXT: [[EXT:%.*]] = phi i64 [ [[INDVARS_IV_NEXT:%.*]], %[[LOOP_LATCH:.*]] ], [ [[TMP2]], %[[ENTRY]] ]
-; CHECK-NEXT: [[GUARD:%.*]] = icmp sgt i64 [[EXT]], [[TMP1]]
-; CHECK-NEXT: br i1 [[GUARD]], label %[[LOOP_LATCH]], label %[[BODY:.*]]
-; CHECK: [[BODY]]:
-; CHECK-NEXT: [[TMP3:%.*]] = trunc nuw i64 [[EXT]] to i32
-; CHECK-NEXT: [[SHL:%.*]] = shl nuw i32 [[TMP3]], 1
+; CHECK-NEXT: [[EXT:%.*]] = phi i64 [ [[INDVARS_IV_NEXT:%.*]], %[[CALL_BB:.*]] ], [ [[TMP2]], %[[ENTRY]] ]
+; CHECK-NEXT: [[IV:%.*]] = trunc nuw i64 [[EXT]] to i32
+; CHECK-NEXT: [[SHL:%.*]] = shl i32 [[IV]], 1
; CHECK-NEXT: [[POS:%.*]] = icmp sgt i32 [[SHL]], 0
-; CHECK-NEXT: br i1 [[POS]], label %[[CALL_BB:.*]], label %[[EXIT:.*]]
+; CHECK-NEXT: br i1 [[POS]], label %[[CALL_BB]], label %[[EXIT:.*]]
; CHECK: [[CALL_BB]]:
; CHECK-NEXT: [[V:%.*]] = tail call i32 @load(i64 [[EXT]])
-; CHECK-NEXT: br label %[[LOOP_LATCH]]
-; CHECK: [[LOOP_LATCH]]:
; CHECK-NEXT: [[DONE:%.*]] = icmp eq i64 [[EXT]], 0
; CHECK-NEXT: [[INDVARS_IV_NEXT]] = add nsw i64 [[EXT]], -1
; CHECK-NEXT: br i1 [[DONE]], label %[[EXIT]], label %[[LOOP_HEADER]]
>From e736d57797649e0f45e7556727b20be0121862fc Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Sat, 22 Aug 2026 14:39:55 +0100
Subject: [PATCH 3/3] !fixup add TODO + tests for more negative code paths
---
.../monotonic-int-phis-decrement.ll | 80 +++++++++++++++++++
.../constraint-eliminiation-interactions.ll | 1 +
2 files changed, 81 insertions(+)
diff --git a/llvm/test/Transforms/ConstraintElimination/monotonic-int-phis-decrement.ll b/llvm/test/Transforms/ConstraintElimination/monotonic-int-phis-decrement.ll
index f2e4b42fc477a..32d38acb8bac9 100644
--- a/llvm/test/Transforms/ConstraintElimination/monotonic-int-phis-decrement.ll
+++ b/llvm/test/Transforms/ConstraintElimination/monotonic-int-phis-decrement.ll
@@ -375,6 +375,86 @@ exit:
ret void
}
+; Same as above, but the loop is guarded by %b <=s %start, which guarantees the
+; loop exits before the decrement wraps in the signed sense.
+define void @add_rec_decreasing_no_wrap_flags_signed_precond(i8 %start, i8 %b) {
+; CHECK-LABEL: define void @add_rec_decreasing_no_wrap_flags_signed_precond(
+; CHECK-SAME: i8 [[START:%.*]], i8 [[B:%.*]]) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[PRECOND:%.*]] = icmp sle i8 [[B]], [[START]]
+; CHECK-NEXT: br i1 [[PRECOND]], label [[LOOP_HEADER:%.*]], label [[EXIT:%.*]]
+; CHECK: loop.header:
+; CHECK-NEXT: [[K_0:%.*]] = phi i8 [ [[START]], [[ENTRY:%.*]] ], [ [[K_DEC:%.*]], [[LOOP_LATCH:%.*]] ]
+; CHECK-NEXT: [[CMP2_NOT:%.*]] = icmp eq i8 [[K_0]], [[B]]
+; CHECK-NEXT: br i1 [[CMP2_NOT]], label [[EXIT]], label [[LOOP_LATCH]]
+; CHECK: loop.latch:
+; CHECK-NEXT: call void @use(i1 true)
+; CHECK-NEXT: [[U:%.*]] = icmp ule i8 [[K_0]], [[START]]
+; CHECK-NEXT: call void @use(i1 [[U]])
+; CHECK-NEXT: [[K_DEC]] = add i8 [[K_0]], -1
+; CHECK-NEXT: br label [[LOOP_HEADER]]
+; CHECK: exit:
+; CHECK-NEXT: ret void
+;
+entry:
+ %precond = icmp sle i8 %b, %start
+ br i1 %precond, label %loop.header, label %exit
+
+loop.header:
+ %k.0 = phi i8 [ %start, %entry ], [ %k.dec, %loop.latch ]
+ %cmp2.not = icmp eq i8 %k.0, %b
+ br i1 %cmp2.not, label %exit, label %loop.latch
+
+loop.latch:
+ %s = icmp sle i8 %k.0, %start
+ call void @use(i1 %s)
+ %u = icmp ule i8 %k.0, %start
+ call void @use(i1 %u)
+ %k.dec = add i8 %k.0, -1
+ br label %loop.header
+
+exit:
+ ret void
+}
+
+; Same as above, but the step is a `sub` and thus not visible as `add %k.0, C`
+; in the IR; the direction of the induction comes from SCEV only.
+define void @add_rec_decreasing_sub_step_signed_precond(i8 %start, i8 %b) {
+; CHECK-LABEL: define void @add_rec_decreasing_sub_step_signed_precond(
+; CHECK-SAME: i8 [[START:%.*]], i8 [[B:%.*]]) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[PRECOND:%.*]] = icmp sle i8 [[B]], [[START]]
+; CHECK-NEXT: br i1 [[PRECOND]], label [[LOOP_HEADER:%.*]], label [[EXIT:%.*]]
+; CHECK: loop.header:
+; CHECK-NEXT: [[K_0:%.*]] = phi i8 [ [[START]], [[ENTRY:%.*]] ], [ [[K_DEC:%.*]], [[LOOP_LATCH:%.*]] ]
+; CHECK-NEXT: [[CMP2_NOT:%.*]] = icmp eq i8 [[K_0]], [[B]]
+; CHECK-NEXT: br i1 [[CMP2_NOT]], label [[EXIT]], label [[LOOP_LATCH]]
+; CHECK: loop.latch:
+; CHECK-NEXT: call void @use(i1 true)
+; CHECK-NEXT: [[K_DEC]] = sub i8 [[K_0]], 1
+; CHECK-NEXT: br label [[LOOP_HEADER]]
+; CHECK: exit:
+; CHECK-NEXT: ret void
+;
+entry:
+ %precond = icmp sle i8 %b, %start
+ br i1 %precond, label %loop.header, label %exit
+
+loop.header:
+ %k.0 = phi i8 [ %start, %entry ], [ %k.dec, %loop.latch ]
+ %cmp2.not = icmp eq i8 %k.0, %b
+ br i1 %cmp2.not, label %exit, label %loop.latch
+
+loop.latch:
+ %s = icmp sle i8 %k.0, %start
+ call void @use(i1 %s)
+ %k.dec = sub i8 %k.0, 1
+ br label %loop.header
+
+exit:
+ ret void
+}
+
; The decrement has no wrap flags in the IR, but SCEV can infer nsw for the
; induction from the guarded backedge.
define void @add_rec_decreasing_nsw_from_scev(i8 %b) {
diff --git a/llvm/test/Transforms/PhaseOrdering/constraint-eliminiation-interactions.ll b/llvm/test/Transforms/PhaseOrdering/constraint-eliminiation-interactions.ll
index f710f66ae413f..4b5e0b278f350 100644
--- a/llvm/test/Transforms/PhaseOrdering/constraint-eliminiation-interactions.ll
+++ b/llvm/test/Transforms/PhaseOrdering/constraint-eliminiation-interactions.ll
@@ -162,6 +162,7 @@ else:
ret i64 %res
}
+; TODO: We should be able to preserve shl nuw.
define void @shl_flags(i32 %n) {
; CHECK-LABEL: define void @shl_flags(
; CHECK-SAME: i32 [[N:%.*]]) local_unnamed_addr {
More information about the llvm-commits
mailing list