[llvm-branch-commits] [llvm] [ConstraintElim] Split signed min/max across their operands when checking a compare (PR #235161)
Jon Roelofs via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Fri Oct 23 17:03:20 PDT 2026
https://github.com/jroelofs created https://github.com/llvm/llvm-project/pull/235161
The existing constraint systems only record the bounds that hold for both
operands of a min/max (smax(a, b) >= a and >= b, smin(a, b) <= a and <= b). The
other direction is a disjunction, which cannot be recorded as a fact. When a
compare of signed decompositions involves a min/max result, split it into its
operands: the decomposition is monotonic in that result, so it is the max or min
of its values on each side, and the relation holds for all or for either side
respectively.
https://alive2.llvm.org/ce/z/zsGkJ-
---
<sub>Stack created with <a href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>
>From 90400189316861fc2de4ebd2753e67158a9ba5b1 Mon Sep 17 00:00:00 2001
From: Jon Roelofs <jonathan_roelofs at apple.com>
Date: Fri, 23 Oct 2026 15:46:58 -0700
Subject: [PATCH] [ConstraintElim] Split signed min/max across their operands
when checking a compare
The existing constraint systems only record the bounds that hold for both
operands of a min/max (smax(a, b) >= a and >= b, smin(a, b) <= a and <= b). The
other direction is a disjunction, which cannot be recorded as a fact. When a
compare of signed decompositions involves a min/max result, split it into its
operands: the decomposition is monotonic in that result, so it is the max or min
of its values on each side, and the relation holds for all or for either side
respectively.
https://alive2.llvm.org/ce/z/zsGkJ-
---
.../Scalar/ConstraintElimination.cpp | 111 ++++++
.../ConstraintElimination/minmax.ll | 369 +++++++++++++++++-
.../sadd-with-overflow.ll | 12 +-
3 files changed, 481 insertions(+), 11 deletions(-)
diff --git a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
index afca2f98e60808..4cffefb3bb58b2 100644
--- a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
@@ -2126,6 +2126,115 @@ static RowTy getDecompositionLinkRow(Value *V, const ConstraintTy &C,
return NewVariables.empty() ? Row : RowTy();
}
+/// Three-valued conjunction, where std::nullopt is "unknown".
+static std::optional<bool> kleeneAnd(std::optional<bool> A,
+ std::optional<bool> B) {
+ if (A == false || B == false)
+ return false;
+ if (A == true && B == true)
+ return true;
+ return std::nullopt;
+}
+
+/// Three-valued disjunction, where std::nullopt is "unknown".
+static std::optional<bool> kleeneOr(std::optional<bool> A,
+ std::optional<bool> B) {
+ if (A == true || B == true)
+ return true;
+ if (A == false && B == false)
+ return false;
+ return std::nullopt;
+}
+
+/// Checks the signed relation \p D \p Pred 0 against the signed system, where
+/// \p Pred is SLT or SLE. Returns true or false if the relation or its
+/// negation is implied, std::nullopt otherwise.
+///
+/// For the result of a signed min/max intrinsic the systems only contain the
+/// bounds that hold for both sides (smax(a, b) >= a and >= b, and smin(a, b) <=
+/// a and <= b). The other direction is a disjunction, which cannot be a fact.
+/// Handle it here by splitting a min/max result in \p D across its operands: \p
+/// D is monotonic in that result, so it is the max or the min of its values at
+/// each side, and the relation holds for all or for either of the sides
+/// respectively.
+static std::optional<bool>
+checkSignedDecompositionWithMinMax(CmpInst::Predicate Pred, Decomposition D,
+ ConstraintInfo &Info, unsigned Depth) {
+ assert((Pred == CmpInst::ICMP_SLT || Pred == CmpInst::ICMP_SLE) &&
+ "only < and <= are handled");
+ if (D.canonicalize())
+ return std::nullopt;
+
+ // Evaluate constants directly.
+ if (D.Vars.empty())
+ return Pred == CmpInst::ICMP_SLT ? D.Offset < 0 : D.Offset <= 0;
+
+ if (Depth != 0) {
+ for (const DecompEntry &E : D.Vars) {
+ auto *MM = dyn_cast<MinMaxIntrinsic>(E.Variable);
+ if (!MM || !MM->isSigned())
+ continue;
+ auto LHS = D.substitute(E.Variable, MM->getLHS(), Info);
+ auto RHS = D.substitute(E.Variable, MM->getRHS(), Info);
+ if (!LHS || !RHS)
+ return std::nullopt;
+ auto L = checkSignedDecompositionWithMinMax(Pred, *LHS, Info, Depth - 1);
+ auto R = checkSignedDecompositionWithMinMax(Pred, *RHS, Info, Depth - 1);
+ // D is an affine function of MM with slope E.Coefficient. If the slope
+ // is positive, then D(max(a, b)) = max(D(a), D(b)) and D(min(a, b)) =
+ // min(D(a), D(b)). If it is negative, max and min swap roles.
+ return (MM->getIntrinsicID() == Intrinsic::smax) == (E.Coefficient > 0)
+ ? kleeneAnd(L, R)
+ : kleeneOr(L, R);
+ }
+ }
+
+ // Build "D Pred 0" as a single constraint over known variables.
+ Decomposition Zero(int64_t(0));
+ SmallVector<Value *> NewVariables;
+ RowTy R = getRowForLessEqual(D, Zero, Info.getValue2Index(/*Signed=*/true),
+ NewVariables);
+ if (R.empty() || !NewVariables.empty())
+ return std::nullopt;
+
+ // "D < 0" is "D <= -1". R[0] is -D.Offset, and getRowForLessEqual gives up if
+ // that overflows, so R[0] > INT64_MIN and subtracting one cannot wrap.
+ if (Pred == CmpInst::ICMP_SLT) {
+ assert(R[0].Coefficient != std::numeric_limits<int64_t>::min() &&
+ "getRowForLessEqual rejects an offset that cannot be negated");
+ --R[0].Coefficient;
+ }
+
+ ConstraintTy C(std::move(R), Info.getValue2Index(/*Signed=*/true).size(),
+ /*IsSigned=*/true, /*IsEq=*/false, /*IsNe=*/false);
+ return C.isImpliedBy(Info.getCS(/*Signed=*/true));
+}
+
+/// Checks the signed relation \p A \p Pred \p B, splitting signed min/max
+/// results across their respective operands.
+static std::optional<bool> TryWithMinMaxDecomposition(CmpInst::Predicate Pred,
+ Value *A, Value *B,
+ ConstraintInfo &Info) {
+ if (!A->getType()->isIntegerTy())
+ return std::nullopt;
+
+ // Canonicalize to "<" and "<=".
+ if (Pred == CmpInst::ICMP_SGT || Pred == CmpInst::ICMP_SGE) {
+ Pred = CmpInst::getSwappedPredicate(Pred);
+ std::swap(A, B);
+ }
+
+ if (Pred != CmpInst::ICMP_SLT && Pred != CmpInst::ICMP_SLE)
+ return std::nullopt;
+
+ const DataLayout &DL = Info.getDataLayout();
+ Decomposition D = decompose(A, Info, /*IsSigned=*/true, DL);
+ if (D.sub(decompose(B, Info, /*IsSigned=*/true, DL)))
+ return std::nullopt;
+
+ return checkSignedDecompositionWithMinMax(Pred, D, Info, /*Depth=*/2);
+}
+
static std::optional<bool> checkCondition(CmpInst::Predicate Pred, Value *A,
Value *B, Instruction *CheckInst,
ConstraintInfo &Info) {
@@ -2188,6 +2297,8 @@ static std::optional<bool> checkCondition(CmpInst::Predicate Pred, Value *A,
return ImpliedCondition;
if (auto ImpliedCondition = TryWithLinkedDecomposition(R))
return ImpliedCondition;
+ if (auto ImpliedCondition = TryWithMinMaxDecomposition(Pred, A, B, Info))
+ return ImpliedCondition;
// For non-negative operands unsigned queries can also be checked against the
// signed system.
diff --git a/llvm/test/Transforms/ConstraintElimination/minmax.ll b/llvm/test/Transforms/ConstraintElimination/minmax.ll
index ffcc7a8bcf7e60..b207f568a25320 100644
--- a/llvm/test/Transforms/ConstraintElimination/minmax.ll
+++ b/llvm/test/Transforms/ConstraintElimination/minmax.ll
@@ -306,9 +306,7 @@ define i1 @smin_branchless(i32 %x, i32 %y) {
; CHECK-SAME: (i32 [[X:%.*]], i32 [[Y:%.*]]) {
; CHECK-NEXT: entry:
; CHECK-NEXT: [[MIN:%.*]] = call i32 @llvm.smin.i32(i32 [[X]], i32 [[Y]])
-; CHECK-NEXT: [[CMP1:%.*]] = icmp sle i32 [[MIN]], [[X]]
-; CHECK-NEXT: [[CMP2:%.*]] = icmp sgt i32 [[MIN]], [[X]]
-; CHECK-NEXT: [[RET:%.*]] = xor i1 [[CMP1]], [[CMP2]]
+; CHECK-NEXT: [[RET:%.*]] = xor i1 true, false
; CHECK-NEXT: ret i1 [[RET]]
;
entry:
@@ -722,3 +720,368 @@ declare i32 @llvm.smin.i32(i32, i32)
declare i32 @llvm.smax.i32(i32, i32)
declare i32 @llvm.umin.i32(i32, i32)
declare i32 @llvm.umax.i32(i32, i32)
+
+define i1 @smax_le_both_sides(i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: define i1 @smax_le_both_sides
+; CHECK-SAME: (i32 [[A:%.*]], i32 [[B:%.*]], i32 [[C:%.*]]) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[AC:%.*]] = icmp sle i32 [[A]], [[C]]
+; CHECK-NEXT: call void @llvm.assume(i1 [[AC]])
+; CHECK-NEXT: [[BC:%.*]] = icmp sle i32 [[B]], [[C]]
+; CHECK-NEXT: call void @llvm.assume(i1 [[BC]])
+; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smax.i32(i32 [[A]], i32 [[B]])
+; CHECK-NEXT: ret i1 true
+;
+entry:
+ %ac = icmp sle i32 %a, %c
+ call void @llvm.assume(i1 %ac)
+ %bc = icmp sle i32 %b, %c
+ call void @llvm.assume(i1 %bc)
+ %m = call i32 @llvm.smax.i32(i32 %a, i32 %b)
+ %r = icmp sle i32 %m, %c
+ ret i1 %r
+}
+
+define i1 @smax_gt_both_sides(i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: define i1 @smax_gt_both_sides
+; CHECK-SAME: (i32 [[A:%.*]], i32 [[B:%.*]], i32 [[C:%.*]]) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[AC:%.*]] = icmp sle i32 [[A]], [[C]]
+; CHECK-NEXT: call void @llvm.assume(i1 [[AC]])
+; CHECK-NEXT: [[BC:%.*]] = icmp sle i32 [[B]], [[C]]
+; CHECK-NEXT: call void @llvm.assume(i1 [[BC]])
+; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smax.i32(i32 [[A]], i32 [[B]])
+; CHECK-NEXT: ret i1 false
+;
+entry:
+ %ac = icmp sle i32 %a, %c
+ call void @llvm.assume(i1 %ac)
+ %bc = icmp sle i32 %b, %c
+ call void @llvm.assume(i1 %bc)
+ %m = call i32 @llvm.smax.i32(i32 %a, i32 %b)
+ %r = icmp sgt i32 %m, %c
+ ret i1 %r
+}
+
+define i1 @smax_ge_swapped_both_sides(i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: define i1 @smax_ge_swapped_both_sides
+; CHECK-SAME: (i32 [[A:%.*]], i32 [[B:%.*]], i32 [[C:%.*]]) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[AC:%.*]] = icmp sle i32 [[A]], [[C]]
+; CHECK-NEXT: call void @llvm.assume(i1 [[AC]])
+; CHECK-NEXT: [[BC:%.*]] = icmp sle i32 [[B]], [[C]]
+; CHECK-NEXT: call void @llvm.assume(i1 [[BC]])
+; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smax.i32(i32 [[A]], i32 [[B]])
+; CHECK-NEXT: ret i1 true
+;
+entry:
+ %ac = icmp sle i32 %a, %c
+ call void @llvm.assume(i1 %ac)
+ %bc = icmp sle i32 %b, %c
+ call void @llvm.assume(i1 %bc)
+ %m = call i32 @llvm.smax.i32(i32 %a, i32 %b)
+ %r = icmp sge i32 %c, %m
+ ret i1 %r
+}
+
+define i1 @smax_le_one_side(i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: define i1 @smax_le_one_side
+; CHECK-SAME: (i32 [[A:%.*]], i32 [[B:%.*]], i32 [[C:%.*]]) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[AC:%.*]] = icmp sle i32 [[A]], [[C]]
+; CHECK-NEXT: call void @llvm.assume(i1 [[AC]])
+; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smax.i32(i32 [[A]], i32 [[B]])
+; CHECK-NEXT: [[R:%.*]] = icmp sle i32 [[M]], [[C]]
+; CHECK-NEXT: ret i1 [[R]]
+;
+entry:
+ %ac = icmp sle i32 %a, %c
+ call void @llvm.assume(i1 %ac)
+ %m = call i32 @llvm.smax.i32(i32 %a, i32 %b)
+ %r = icmp sle i32 %m, %c
+ ret i1 %r
+}
+
+define i1 @smin_ge_both_sides(i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: define i1 @smin_ge_both_sides
+; CHECK-SAME: (i32 [[A:%.*]], i32 [[B:%.*]], i32 [[C:%.*]]) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[AC:%.*]] = icmp sge i32 [[A]], [[C]]
+; CHECK-NEXT: call void @llvm.assume(i1 [[AC]])
+; CHECK-NEXT: [[BC:%.*]] = icmp sge i32 [[B]], [[C]]
+; CHECK-NEXT: call void @llvm.assume(i1 [[BC]])
+; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smin.i32(i32 [[A]], i32 [[B]])
+; CHECK-NEXT: ret i1 true
+;
+entry:
+ %ac = icmp sge i32 %a, %c
+ call void @llvm.assume(i1 %ac)
+ %bc = icmp sge i32 %b, %c
+ call void @llvm.assume(i1 %bc)
+ %m = call i32 @llvm.smin.i32(i32 %a, i32 %b)
+ %r = icmp sge i32 %m, %c
+ ret i1 %r
+}
+
+define i1 @smin_le_swapped_both_sides(i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: define i1 @smin_le_swapped_both_sides
+; CHECK-SAME: (i32 [[A:%.*]], i32 [[B:%.*]], i32 [[C:%.*]]) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[AC:%.*]] = icmp sge i32 [[A]], [[C]]
+; CHECK-NEXT: call void @llvm.assume(i1 [[AC]])
+; CHECK-NEXT: [[BC:%.*]] = icmp sge i32 [[B]], [[C]]
+; CHECK-NEXT: call void @llvm.assume(i1 [[BC]])
+; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smin.i32(i32 [[A]], i32 [[B]])
+; CHECK-NEXT: ret i1 true
+;
+entry:
+ %ac = icmp sge i32 %a, %c
+ call void @llvm.assume(i1 %ac)
+ %bc = icmp sge i32 %b, %c
+ call void @llvm.assume(i1 %bc)
+ %m = call i32 @llvm.smin.i32(i32 %a, i32 %b)
+ %r = icmp sle i32 %c, %m
+ ret i1 %r
+}
+
+define i1 @smin_lt_both_sides(i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: define i1 @smin_lt_both_sides
+; CHECK-SAME: (i32 [[A:%.*]], i32 [[B:%.*]], i32 [[C:%.*]]) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[AC:%.*]] = icmp sge i32 [[A]], [[C]]
+; CHECK-NEXT: call void @llvm.assume(i1 [[AC]])
+; CHECK-NEXT: [[BC:%.*]] = icmp sge i32 [[B]], [[C]]
+; CHECK-NEXT: call void @llvm.assume(i1 [[BC]])
+; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smin.i32(i32 [[A]], i32 [[B]])
+; CHECK-NEXT: ret i1 false
+;
+entry:
+ %ac = icmp sge i32 %a, %c
+ call void @llvm.assume(i1 %ac)
+ %bc = icmp sge i32 %b, %c
+ call void @llvm.assume(i1 %bc)
+ %m = call i32 @llvm.smin.i32(i32 %a, i32 %b)
+ %r = icmp slt i32 %m, %c
+ ret i1 %r
+}
+
+define i1 @smin_ge_one_side(i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: define i1 @smin_ge_one_side
+; CHECK-SAME: (i32 [[A:%.*]], i32 [[B:%.*]], i32 [[C:%.*]]) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[AC:%.*]] = icmp sge i32 [[A]], [[C]]
+; CHECK-NEXT: call void @llvm.assume(i1 [[AC]])
+; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smin.i32(i32 [[A]], i32 [[B]])
+; CHECK-NEXT: [[R:%.*]] = icmp sge i32 [[M]], [[C]]
+; CHECK-NEXT: ret i1 [[R]]
+;
+entry:
+ %ac = icmp sge i32 %a, %c
+ call void @llvm.assume(i1 %ac)
+ %m = call i32 @llvm.smin.i32(i32 %a, i32 %b)
+ %r = icmp sge i32 %m, %c
+ ret i1 %r
+}
+
+define i1 @smax_ge_one_side(i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: define i1 @smax_ge_one_side
+; CHECK-SAME: (i32 [[A:%.*]], i32 [[B:%.*]], i32 [[C:%.*]]) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[AC:%.*]] = icmp sge i32 [[A]], [[C]]
+; CHECK-NEXT: call void @llvm.assume(i1 [[AC]])
+; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smax.i32(i32 [[A]], i32 [[B]])
+; CHECK-NEXT: ret i1 true
+;
+entry:
+ %ac = icmp sge i32 %a, %c
+ call void @llvm.assume(i1 %ac)
+ %m = call i32 @llvm.smax.i32(i32 %a, i32 %b)
+ %r = icmp sge i32 %m, %c
+ ret i1 %r
+}
+
+define i1 @smin_le_one_side(i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: define i1 @smin_le_one_side
+; CHECK-SAME: (i32 [[A:%.*]], i32 [[B:%.*]], i32 [[C:%.*]]) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[AC:%.*]] = icmp sle i32 [[A]], [[C]]
+; CHECK-NEXT: call void @llvm.assume(i1 [[AC]])
+; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smin.i32(i32 [[A]], i32 [[B]])
+; CHECK-NEXT: ret i1 true
+;
+entry:
+ %ac = icmp sle i32 %a, %c
+ call void @llvm.assume(i1 %ac)
+ %m = call i32 @llvm.smin.i32(i32 %a, i32 %b)
+ %r = icmp sle i32 %m, %c
+ ret i1 %r
+}
+
+define i1 @smax_ge_no_side(i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: define i1 @smax_ge_no_side
+; CHECK-SAME: (i32 [[A:%.*]], i32 [[B:%.*]], i32 [[C:%.*]]) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smax.i32(i32 [[A]], i32 [[B]])
+; CHECK-NEXT: [[R:%.*]] = icmp sge i32 [[M]], [[C]]
+; CHECK-NEXT: ret i1 [[R]]
+;
+entry:
+ %m = call i32 @llvm.smax.i32(i32 %a, i32 %b)
+ %r = icmp sge i32 %m, %c
+ ret i1 %r
+}
+
+define i1 @smax_le_with_other_terms(i32 %x, i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: define i1 @smax_le_with_other_terms
+; CHECK-SAME: (i32 [[X:%.*]], i32 [[A:%.*]], i32 [[B:%.*]], i32 [[C:%.*]]) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[XZ:%.*]] = icmp sle i32 [[X]], 0
+; CHECK-NEXT: call void @llvm.assume(i1 [[XZ]])
+; CHECK-NEXT: [[AC:%.*]] = icmp sle i32 [[A]], [[C]]
+; CHECK-NEXT: call void @llvm.assume(i1 [[AC]])
+; CHECK-NEXT: [[BC:%.*]] = icmp sle i32 [[B]], [[C]]
+; CHECK-NEXT: call void @llvm.assume(i1 [[BC]])
+; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smax.i32(i32 [[A]], i32 [[B]])
+; CHECK-NEXT: [[S:%.*]] = add nsw i32 [[X]], [[M]]
+; CHECK-NEXT: ret i1 true
+;
+entry:
+ %xz = icmp sle i32 %x, 0
+ call void @llvm.assume(i1 %xz)
+ %ac = icmp sle i32 %a, %c
+ call void @llvm.assume(i1 %ac)
+ %bc = icmp sle i32 %b, %c
+ call void @llvm.assume(i1 %bc)
+ %m = call i32 @llvm.smax.i32(i32 %a, i32 %b)
+ %s = add nsw i32 %x, %m
+ %r = icmp sle i32 %s, %c
+ ret i1 %r
+}
+
+define i1 @umax_le_both_sides(i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: define i1 @umax_le_both_sides
+; CHECK-SAME: (i32 [[A:%.*]], i32 [[B:%.*]], i32 [[C:%.*]]) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[AC:%.*]] = icmp ule i32 [[A]], [[C]]
+; CHECK-NEXT: call void @llvm.assume(i1 [[AC]])
+; CHECK-NEXT: [[BC:%.*]] = icmp ule i32 [[B]], [[C]]
+; CHECK-NEXT: call void @llvm.assume(i1 [[BC]])
+; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.umax.i32(i32 [[A]], i32 [[B]])
+; CHECK-NEXT: [[R:%.*]] = icmp sle i32 [[M]], [[C]]
+; CHECK-NEXT: ret i1 [[R]]
+;
+entry:
+ %ac = icmp ule i32 %a, %c
+ call void @llvm.assume(i1 %ac)
+ %bc = icmp ule i32 %b, %c
+ call void @llvm.assume(i1 %bc)
+ %m = call i32 @llvm.umax.i32(i32 %a, i32 %b)
+ %r = icmp sle i32 %m, %c
+ ret i1 %r
+}
+
+define i1 @smax_ge_second_side(i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: define i1 @smax_ge_second_side
+; CHECK-SAME: (i32 [[A:%.*]], i32 [[B:%.*]], i32 [[C:%.*]]) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[BC:%.*]] = icmp sge i32 [[B]], [[C]]
+; CHECK-NEXT: call void @llvm.assume(i1 [[BC]])
+; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smax.i32(i32 [[A]], i32 [[B]])
+; CHECK-NEXT: ret i1 true
+;
+entry:
+ %bc = icmp sge i32 %b, %c
+ call void @llvm.assume(i1 %bc)
+ %m = call i32 @llvm.smax.i32(i32 %a, i32 %b)
+ %r = icmp sge i32 %m, %c
+ ret i1 %r
+}
+
+define i1 @smin_of_smax_le(i32 %a, i32 %b, i32 %c, i32 %d) {
+; CHECK-LABEL: define i1 @smin_of_smax_le
+; CHECK-SAME: (i32 [[A:%.*]], i32 [[B:%.*]], i32 [[C:%.*]], i32 [[D:%.*]]) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[AD:%.*]] = icmp sle i32 [[A]], [[D]]
+; CHECK-NEXT: call void @llvm.assume(i1 [[AD]])
+; CHECK-NEXT: [[BD:%.*]] = icmp sle i32 [[B]], [[D]]
+; CHECK-NEXT: call void @llvm.assume(i1 [[BD]])
+; CHECK-NEXT: [[MX:%.*]] = call i32 @llvm.smax.i32(i32 [[A]], i32 [[B]])
+; CHECK-NEXT: [[MN:%.*]] = call i32 @llvm.smin.i32(i32 [[MX]], i32 [[C]])
+; CHECK-NEXT: ret i1 true
+;
+entry:
+ %ad = icmp sle i32 %a, %d
+ call void @llvm.assume(i1 %ad)
+ %bd = icmp sle i32 %b, %d
+ call void @llvm.assume(i1 %bd)
+ %mx = call i32 @llvm.smax.i32(i32 %a, i32 %b)
+ %mn = call i32 @llvm.smin.i32(i32 %mx, i32 %c)
+ %r = icmp sle i32 %mn, %d
+ ret i1 %r
+}
+
+define i1 @smax_three_levels_le(i32 %a, i32 %b, i32 %c, i32 %d, i32 %e) {
+; CHECK-LABEL: define i1 @smax_three_levels_le
+; CHECK-SAME: (i32 [[A:%.*]], i32 [[B:%.*]], i32 [[C:%.*]], i32 [[D:%.*]], i32 [[E:%.*]]) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[AE:%.*]] = icmp sle i32 [[A]], [[E]]
+; CHECK-NEXT: call void @llvm.assume(i1 [[AE]])
+; CHECK-NEXT: [[BE:%.*]] = icmp sle i32 [[B]], [[E]]
+; CHECK-NEXT: call void @llvm.assume(i1 [[BE]])
+; CHECK-NEXT: [[CE:%.*]] = icmp sle i32 [[C]], [[E]]
+; CHECK-NEXT: call void @llvm.assume(i1 [[CE]])
+; CHECK-NEXT: [[DE:%.*]] = icmp sle i32 [[D]], [[E]]
+; CHECK-NEXT: call void @llvm.assume(i1 [[DE]])
+; CHECK-NEXT: [[M1:%.*]] = call i32 @llvm.smax.i32(i32 [[A]], i32 [[B]])
+; CHECK-NEXT: [[M2:%.*]] = call i32 @llvm.smax.i32(i32 [[M1]], i32 [[C]])
+; CHECK-NEXT: [[M3:%.*]] = call i32 @llvm.smax.i32(i32 [[M2]], i32 [[D]])
+; CHECK-NEXT: [[R:%.*]] = icmp sle i32 [[M3]], [[E]]
+; CHECK-NEXT: ret i1 [[R]]
+;
+entry:
+ %ae = icmp sle i32 %a, %e
+ call void @llvm.assume(i1 %ae)
+ %be = icmp sle i32 %b, %e
+ call void @llvm.assume(i1 %be)
+ %ce = icmp sle i32 %c, %e
+ call void @llvm.assume(i1 %ce)
+ %de = icmp sle i32 %d, %e
+ call void @llvm.assume(i1 %de)
+ %m1 = call i32 @llvm.smax.i32(i32 %a, i32 %b)
+ %m2 = call i32 @llvm.smax.i32(i32 %m1, i32 %c)
+ %m3 = call i32 @llvm.smax.i32(i32 %m2, i32 %d)
+ %r = icmp sle i32 %m3, %e
+ ret i1 %r
+}
+
+define i1 @smax_substitute_overflow(i64 %x, i64 %z, i64 %y) {
+; CHECK-LABEL: define i1 @smax_substitute_overflow
+; CHECK-SAME: (i64 [[X:%.*]], i64 [[Z:%.*]], i64 [[Y:%.*]]) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[A:%.*]] = mul nsw i64 [[X]], 1099511627776
+; CHECK-NEXT: [[M:%.*]] = call i64 @llvm.smax.i64(i64 [[A]], i64 [[Z]])
+; CHECK-NEXT: [[D:%.*]] = mul nsw i64 [[M]], 1073741824
+; CHECK-NEXT: [[R:%.*]] = icmp slt i64 [[D]], [[Y]]
+; CHECK-NEXT: ret i1 [[R]]
+;
+entry:
+ %a = mul nsw i64 %x, 1099511627776
+ %m = call i64 @llvm.smax.i64(i64 %a, i64 %z)
+ %d = mul nsw i64 %m, 1073741824
+ %r = icmp slt i64 %d, %y
+ ret i1 %r
+}
+
+define i1 @slt_offset_overflow(i64 %x) {
+; CHECK-LABEL: define i1 @slt_offset_overflow
+; CHECK-SAME: (i64 [[X:%.*]]) {
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[A:%.*]] = add nsw i64 [[X]], 9223372036854775806
+; CHECK-NEXT: [[R:%.*]] = icmp slt i64 [[A]], -5
+; CHECK-NEXT: ret i1 [[R]]
+;
+entry:
+ %a = add nsw i64 %x, 9223372036854775806
+ %r = icmp slt i64 %a, -5
+ ret i1 %r
+}
diff --git a/llvm/test/Transforms/ConstraintElimination/sadd-with-overflow.ll b/llvm/test/Transforms/ConstraintElimination/sadd-with-overflow.ll
index 08dee4ca907156..7ffae1b1350a48 100644
--- a/llvm/test/Transforms/ConstraintElimination/sadd-with-overflow.ll
+++ b/llvm/test/Transforms/ConstraintElimination/sadd-with-overflow.ll
@@ -524,10 +524,8 @@ define void @sadd_smax_clamp(i8 %start, i8 %end) {
; CHECK-NEXT: [[M:%.*]] = call i8 @llvm.smax.i8(i8 [[X]], i8 0)
; CHECK-NEXT: [[TMP0:%.*]] = add nsw i8 [[START]], [[M]]
; CHECK-NEXT: call void @use(i1 false)
-; CHECK-NEXT: [[LT:%.*]] = icmp slt i8 [[TMP0]], [[START]]
-; CHECK-NEXT: call void @use(i1 [[LT]])
-; CHECK-NEXT: [[GT:%.*]] = icmp slt i8 [[END]], [[TMP0]]
-; CHECK-NEXT: call void @use(i1 [[GT]])
+; CHECK-NEXT: call void @use(i1 false)
+; CHECK-NEXT: call void @use(i1 false)
; CHECK-NEXT: ret void
;
entry:
@@ -556,8 +554,7 @@ define void @sadd_smax_clamp_unordered(i8 %start, i8 %end) {
; CHECK-NEXT: [[M:%.*]] = call i8 @llvm.smax.i8(i8 [[X]], i8 0)
; CHECK-NEXT: [[TMP0:%.*]] = add nsw i8 [[START]], [[M]]
; CHECK-NEXT: call void @use(i1 false)
-; CHECK-NEXT: [[LT:%.*]] = icmp slt i8 [[TMP0]], [[START]]
-; CHECK-NEXT: call void @use(i1 [[LT]])
+; CHECK-NEXT: call void @use(i1 false)
; CHECK-NEXT: [[GT:%.*]] = icmp slt i8 [[END]], [[TMP0]]
; CHECK-NEXT: call void @use(i1 [[GT]])
; CHECK-NEXT: ret void
@@ -634,8 +631,7 @@ define void @sadd_smax_clamp_samesign(i64 %v, i64 %start) {
; CHECK-NEXT: [[TMP0:%.*]] = add nsw i64 [[START]], [[M]]
; CHECK-NEXT: call void @use(i1 false)
; CHECK-NEXT: call void @use(i1 false)
-; CHECK-NEXT: [[GT:%.*]] = icmp slt i64 [[END]], [[TMP0]]
-; CHECK-NEXT: call void @use(i1 [[GT]])
+; CHECK-NEXT: call void @use(i1 false)
; CHECK-NEXT: ret void
;
entry:
More information about the llvm-branch-commits
mailing list