[llvm] [ValueTracking] Infer min/max compares from bounded operand guards (PR #227205)
Akash Manna via llvm-commits
llvm-commits at lists.llvm.org
Tue Sep 29 00:10:02 PDT 2026
https://github.com/akash-manna-sky created https://github.com/llvm/llvm-project/pull/227205
Fixes #207131
For `if (a >= 0 && a <= 15 && b >= 0 && b <= 15) { if (min(a, b) > 15) use(); }` the call is dead, but it survives `-O3`. SimplifyCFG merges the inner check into the guard and InstCombine turns the guard into `(a | b) u< 16`, so we end up with `and (icmp ult (or b, a), 16), (icmp sgt (smin a, b), 15)`. `isImpliedCondition` couldn't prove that the first compare rules out the second: it didn't know that a bound on `a | b` also bounds `a` and `b`, and it didn't look through the `smin` at all. Nothing to do with `computeConstantRange` ignoring dominating conditions, by the way — there's no dominating branch left at that point, and `f_range_assume` only folds because the assume keeps SimplifyCFG from merging the check into the guard.
`isImpliedCondICmps` now carries a guard's upper bound over to anything `isTruePredicate` knows is no larger than the guarded value (and a lower bound to anything no smaller), e.g. `a u<= a | b`, so `(a | b) u< 16` also gives `a u< 16`. A compare of `min/max(X, Y)` is now decided through its operands: the result is always `X` or `Y`, so if the guard gives `X pred Z` and `Y pred Z` the same answer, the min/max compare gets it too, and when the compare goes the same way as the min/max, one operand is enough — `smin(X, Y) s> Z` is false as soon as `X s> Z` is. That proves `smin(a, b) s> 15` false under the guard, and the dead branch folds away.
>From 82f038f0995b487b4ade71ae90c4555dd7746595 Mon Sep 17 00:00:00 2001
From: Akash Manna <akash.manna.mymail at gmail.com>
Date: Tue, 29 Sep 2026 12:33:59 +0530
Subject: [PATCH 1/2] [ValueTracking] Add tests for #207131 (NFC)
A guard like (a | b) u< 16 bounds both operands of smin(a, b), but
isImpliedCondition cannot use that, so an `and` of the guard and a
compare of the min/max is not simplified. Add InstSimplify tests for it
and PhaseOrdering tests with the three functions from the issue.
---
.../InstSimplify/and-or-implied-cond.ll | 238 ++++++++++++++++++
.../Transforms/PhaseOrdering/cmp-logic.ll | 197 ++++++++++++++-
2 files changed, 433 insertions(+), 2 deletions(-)
diff --git a/llvm/test/Transforms/InstSimplify/and-or-implied-cond.ll b/llvm/test/Transforms/InstSimplify/and-or-implied-cond.ll
index 440499d1bfd30..83e39b52d6235 100644
--- a/llvm/test/Transforms/InstSimplify/and-or-implied-cond.ll
+++ b/llvm/test/Transforms/InstSimplify/and-or-implied-cond.ll
@@ -398,4 +398,242 @@ define i1 @neg_or_trunc_implied(i8 %x, i8 %y) {
ret i1 %and
}
+define i1 @pr207131(i32 %a, i32 %b) {
+; CHECK-LABEL: @pr207131(
+; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smin.i32(i32 [[A:%.*]], i32 [[B:%.*]])
+; CHECK-NEXT: [[OR:%.*]] = or i32 [[B]], [[A]]
+; CHECK-NEXT: [[C1:%.*]] = icmp ult i32 [[OR]], 16
+; CHECK-NEXT: [[C2:%.*]] = icmp sgt i32 [[M]], 15
+; CHECK-NEXT: [[AND:%.*]] = and i1 [[C1]], [[C2]]
+; CHECK-NEXT: ret i1 [[AND]]
+;
+ %m = call i32 @llvm.smin.i32(i32 %a, i32 %b)
+ %or = or i32 %b, %a
+ %c1 = icmp ult i32 %or, 16
+ %c2 = icmp sgt i32 %m, 15
+ %and = and i1 %c1, %c2
+ ret i1 %and
+}
+
+define i1 @and_smin_of_bounded_or_commuted(i32 %a, i32 %b) {
+; CHECK-LABEL: @and_smin_of_bounded_or_commuted(
+; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smin.i32(i32 [[A:%.*]], i32 [[B:%.*]])
+; CHECK-NEXT: [[OR:%.*]] = or i32 [[B]], [[A]]
+; CHECK-NEXT: [[C1:%.*]] = icmp ult i32 [[OR]], 16
+; CHECK-NEXT: [[C2:%.*]] = icmp slt i32 15, [[M]]
+; CHECK-NEXT: [[AND:%.*]] = and i1 [[C1]], [[C2]]
+; CHECK-NEXT: ret i1 [[AND]]
+;
+ %m = call i32 @llvm.smin.i32(i32 %a, i32 %b)
+ %or = or i32 %b, %a
+ %c1 = icmp ult i32 %or, 16
+ %c2 = icmp slt i32 15, %m
+ %and = and i1 %c1, %c2
+ ret i1 %and
+}
+
+define i1 @and_smax_of_bounded_or(i32 %a, i32 %b) {
+; CHECK-LABEL: @and_smax_of_bounded_or(
+; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smax.i32(i32 [[A:%.*]], i32 [[B:%.*]])
+; CHECK-NEXT: [[OR:%.*]] = or i32 [[B]], [[A]]
+; CHECK-NEXT: [[C1:%.*]] = icmp ult i32 [[OR]], 16
+; CHECK-NEXT: [[C2:%.*]] = icmp sgt i32 [[M]], 15
+; CHECK-NEXT: [[AND:%.*]] = and i1 [[C1]], [[C2]]
+; CHECK-NEXT: ret i1 [[AND]]
+;
+ %m = call i32 @llvm.smax.i32(i32 %a, i32 %b)
+ %or = or i32 %b, %a
+ %c1 = icmp ult i32 %or, 16
+ %c2 = icmp sgt i32 %m, 15
+ %and = and i1 %c1, %c2
+ ret i1 %and
+}
+
+define i1 @and_smax_of_bounded_or_implied(i32 %a, i32 %b) {
+; CHECK-LABEL: @and_smax_of_bounded_or_implied(
+; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smax.i32(i32 [[A:%.*]], i32 [[B:%.*]])
+; CHECK-NEXT: [[OR:%.*]] = or i32 [[B]], [[A]]
+; CHECK-NEXT: [[C1:%.*]] = icmp ult i32 [[OR]], 16
+; CHECK-NEXT: [[C2:%.*]] = icmp slt i32 [[M]], 16
+; CHECK-NEXT: [[AND:%.*]] = and i1 [[C1]], [[C2]]
+; CHECK-NEXT: ret i1 [[AND]]
+;
+ %m = call i32 @llvm.smax.i32(i32 %a, i32 %b)
+ %or = or i32 %b, %a
+ %c1 = icmp ult i32 %or, 16
+ %c2 = icmp slt i32 %m, 16
+ %and = and i1 %c1, %c2
+ ret i1 %and
+}
+
+define i1 @and_smin_of_bounded_or_neg(i32 %a, i32 %b) {
+; CHECK-LABEL: @and_smin_of_bounded_or_neg(
+; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smin.i32(i32 [[A:%.*]], i32 [[B:%.*]])
+; CHECK-NEXT: [[OR:%.*]] = or i32 [[B]], [[A]]
+; CHECK-NEXT: [[C1:%.*]] = icmp ult i32 [[OR]], 17
+; CHECK-NEXT: [[C2:%.*]] = icmp sgt i32 [[M]], 15
+; CHECK-NEXT: [[AND:%.*]] = and i1 [[C1]], [[C2]]
+; CHECK-NEXT: ret i1 [[AND]]
+;
+ %m = call i32 @llvm.smin.i32(i32 %a, i32 %b)
+ %or = or i32 %b, %a
+ %c1 = icmp ult i32 %or, 17
+ %c2 = icmp sgt i32 %m, 15
+ %and = and i1 %c1, %c2
+ ret i1 %and
+}
+
+define i1 @and_smin_one_operand_bounded(i32 %a, i32 %b) {
+; CHECK-LABEL: @and_smin_one_operand_bounded(
+; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smin.i32(i32 [[A:%.*]], i32 [[B:%.*]])
+; CHECK-NEXT: [[C1:%.*]] = icmp ult i32 [[A]], 16
+; CHECK-NEXT: [[C2:%.*]] = icmp sgt i32 [[M]], 15
+; CHECK-NEXT: [[AND:%.*]] = and i1 [[C1]], [[C2]]
+; CHECK-NEXT: ret i1 [[AND]]
+;
+ %m = call i32 @llvm.smin.i32(i32 %a, i32 %b)
+ %c1 = icmp ult i32 %a, 16
+ %c2 = icmp sgt i32 %m, 15
+ %and = and i1 %c1, %c2
+ ret i1 %and
+}
+
+define i1 @and_smax_one_operand_bounded_neg(i32 %a, i32 %b) {
+; CHECK-LABEL: @and_smax_one_operand_bounded_neg(
+; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smax.i32(i32 [[A:%.*]], i32 [[B:%.*]])
+; CHECK-NEXT: [[C1:%.*]] = icmp ult i32 [[A]], 16
+; CHECK-NEXT: [[C2:%.*]] = icmp sgt i32 [[M]], 15
+; CHECK-NEXT: [[AND:%.*]] = and i1 [[C1]], [[C2]]
+; CHECK-NEXT: ret i1 [[AND]]
+;
+ %m = call i32 @llvm.smax.i32(i32 %a, i32 %b)
+ %c1 = icmp ult i32 %a, 16
+ %c2 = icmp sgt i32 %m, 15
+ %and = and i1 %c1, %c2
+ ret i1 %and
+}
+
+define i1 @and_smin_signed_bound(i32 %a, i32 %b) {
+; CHECK-LABEL: @and_smin_signed_bound(
+; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smin.i32(i32 [[A:%.*]], i32 [[B:%.*]])
+; CHECK-NEXT: [[C1:%.*]] = icmp slt i32 [[A]], 16
+; CHECK-NEXT: [[C2:%.*]] = icmp sgt i32 [[M]], 15
+; CHECK-NEXT: [[AND:%.*]] = and i1 [[C1]], [[C2]]
+; CHECK-NEXT: ret i1 [[AND]]
+;
+ %m = call i32 @llvm.smin.i32(i32 %a, i32 %b)
+ %c1 = icmp slt i32 %a, 16
+ %c2 = icmp sgt i32 %m, 15
+ %and = and i1 %c1, %c2
+ ret i1 %and
+}
+
+define i1 @and_smin_of_bounded_or_eq(i32 %a, i32 %b) {
+; CHECK-LABEL: @and_smin_of_bounded_or_eq(
+; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smin.i32(i32 [[A:%.*]], i32 [[B:%.*]])
+; CHECK-NEXT: [[OR:%.*]] = or i32 [[B]], [[A]]
+; CHECK-NEXT: [[C1:%.*]] = icmp ult i32 [[OR]], 16
+; CHECK-NEXT: [[C2:%.*]] = icmp eq i32 [[M]], 16
+; CHECK-NEXT: [[AND:%.*]] = and i1 [[C1]], [[C2]]
+; CHECK-NEXT: ret i1 [[AND]]
+;
+ %m = call i32 @llvm.smin.i32(i32 %a, i32 %b)
+ %or = or i32 %b, %a
+ %c1 = icmp ult i32 %or, 16
+ %c2 = icmp eq i32 %m, 16
+ %and = and i1 %c1, %c2
+ ret i1 %and
+}
+
+define i1 @and_smin_of_bounded_or_eq_neg(i32 %a, i32 %b) {
+; CHECK-LABEL: @and_smin_of_bounded_or_eq_neg(
+; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smin.i32(i32 [[A:%.*]], i32 [[B:%.*]])
+; CHECK-NEXT: [[OR:%.*]] = or i32 [[B]], [[A]]
+; CHECK-NEXT: [[C1:%.*]] = icmp ult i32 [[OR]], 16
+; CHECK-NEXT: [[C2:%.*]] = icmp eq i32 [[M]], 15
+; CHECK-NEXT: [[AND:%.*]] = and i1 [[C1]], [[C2]]
+; CHECK-NEXT: ret i1 [[AND]]
+;
+ %m = call i32 @llvm.smin.i32(i32 %a, i32 %b)
+ %or = or i32 %b, %a
+ %c1 = icmp ult i32 %or, 16
+ %c2 = icmp eq i32 %m, 15
+ %and = and i1 %c1, %c2
+ ret i1 %and
+}
+
+define i1 @and_smin_of_bounded_or_ucmp(i32 %a, i32 %b) {
+; CHECK-LABEL: @and_smin_of_bounded_or_ucmp(
+; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smin.i32(i32 [[A:%.*]], i32 [[B:%.*]])
+; CHECK-NEXT: [[OR:%.*]] = or i32 [[B]], [[A]]
+; CHECK-NEXT: [[C1:%.*]] = icmp ult i32 [[OR]], 16
+; CHECK-NEXT: [[C2:%.*]] = icmp ugt i32 [[M]], 15
+; CHECK-NEXT: [[AND:%.*]] = and i1 [[C1]], [[C2]]
+; CHECK-NEXT: ret i1 [[AND]]
+;
+ %m = call i32 @llvm.smin.i32(i32 %a, i32 %b)
+ %or = or i32 %b, %a
+ %c1 = icmp ult i32 %or, 16
+ %c2 = icmp ugt i32 %m, 15
+ %and = and i1 %c1, %c2
+ ret i1 %and
+}
+
+define i1 @and_bounded_or_operand(i32 %a, i32 %b) {
+; CHECK-LABEL: @and_bounded_or_operand(
+; CHECK-NEXT: [[OR:%.*]] = or i32 [[B:%.*]], [[A:%.*]]
+; CHECK-NEXT: [[C1:%.*]] = icmp ult i32 [[OR]], 16
+; CHECK-NEXT: [[C2:%.*]] = icmp sgt i32 [[A]], 15
+; CHECK-NEXT: [[AND:%.*]] = and i1 [[C1]], [[C2]]
+; CHECK-NEXT: ret i1 [[AND]]
+;
+ %or = or i32 %b, %a
+ %c1 = icmp ult i32 %or, 16
+ %c2 = icmp sgt i32 %a, 15
+ %and = and i1 %c1, %c2
+ ret i1 %and
+}
+
+define i1 @or_umax_of_bounded_or(i32 %a, i32 %b) {
+; CHECK-LABEL: @or_umax_of_bounded_or(
+; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.umax.i32(i32 [[A:%.*]], i32 [[B:%.*]])
+; CHECK-NEXT: [[OR:%.*]] = or i32 [[B]], [[A]]
+; CHECK-NEXT: [[C1:%.*]] = icmp uge i32 [[OR]], 16
+; CHECK-NEXT: [[C2:%.*]] = icmp ult i32 [[M]], 16
+; CHECK-NEXT: [[RES:%.*]] = or i1 [[C1]], [[C2]]
+; CHECK-NEXT: ret i1 [[RES]]
+;
+ %m = call i32 @llvm.umax.i32(i32 %a, i32 %b)
+ %or = or i32 %b, %a
+ %c1 = icmp uge i32 %or, 16
+ %c2 = icmp ult i32 %m, 16
+ %res = or i1 %c1, %c2
+ ret i1 %res
+}
+
+define i1 @smin_of_bounded_or_dom_cond(i32 %a, i32 %b) {
+; CHECK-LABEL: @smin_of_bounded_or_dom_cond(
+; CHECK-NEXT: [[OR:%.*]] = or i32 [[B:%.*]], [[A:%.*]]
+; CHECK-NEXT: [[C1:%.*]] = icmp ult i32 [[OR]], 16
+; CHECK-NEXT: br i1 [[C1]], label [[THEN:%.*]], label [[ELSE:%.*]]
+; CHECK: then:
+; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smin.i32(i32 [[A]], i32 [[B]])
+; CHECK-NEXT: [[C2:%.*]] = icmp sgt i32 [[M]], 15
+; CHECK-NEXT: ret i1 [[C2]]
+; CHECK: else:
+; CHECK-NEXT: ret i1 true
+;
+ %or = or i32 %b, %a
+ %c1 = icmp ult i32 %or, 16
+ br i1 %c1, label %then, label %else
+
+then:
+ %m = call i32 @llvm.smin.i32(i32 %a, i32 %b)
+ %c2 = icmp sgt i32 %m, 15
+ ret i1 %c2
+
+else:
+ ret i1 true
+}
+
declare i1 @llvm.is.constant.i1(i1)
diff --git a/llvm/test/Transforms/PhaseOrdering/cmp-logic.ll b/llvm/test/Transforms/PhaseOrdering/cmp-logic.ll
index 4efac572b634c..f3c5404158d65 100644
--- a/llvm/test/Transforms/PhaseOrdering/cmp-logic.ll
+++ b/llvm/test/Transforms/PhaseOrdering/cmp-logic.ll
@@ -1,6 +1,6 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
-; RUN: opt -passes='default<O1>' -S < %s | FileCheck %s --check-prefixes=CHECK,O1
-; RUN: opt -passes='default<O2>' -S < %s | FileCheck %s --check-prefixes=CHECK,O2
+; RUN: opt -passes="default<O1>" -S < %s | FileCheck %s --check-prefixes=CHECK,O1
+; RUN: opt -passes="default<O2>" -S < %s | FileCheck %s --check-prefixes=CHECK,O2
target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
@@ -156,3 +156,196 @@ if.end:
%1 = load i32, ptr @c, align 4
ret i32 %1
}
+
+declare void @use()
+
+define void @PR207131(i32 noundef %a, i32 noundef %b) {
+; CHECK-LABEL: @PR207131(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[COND:%.*]] = tail call i32 @llvm.smin.i32(i32 [[A:%.*]], i32 [[B:%.*]])
+; CHECK-NEXT: [[TMP0:%.*]] = or i32 [[B]], [[A]]
+; CHECK-NEXT: [[OR_COND2:%.*]] = icmp ult i32 [[TMP0]], 16
+; CHECK-NEXT: [[CMP7:%.*]] = icmp sgt i32 [[COND]], 15
+; CHECK-NEXT: [[OR_COND3:%.*]] = and i1 [[OR_COND2]], [[CMP7]]
+; CHECK-NEXT: br i1 [[OR_COND3]], label [[IF_THEN8:%.*]], label [[IF_END9:%.*]]
+; CHECK: if.then8:
+; CHECK-NEXT: tail call void @use()
+; CHECK-NEXT: br label [[IF_END9]]
+; CHECK: if.end9:
+; CHECK-NEXT: ret void
+;
+entry:
+ %a.addr = alloca i32, align 4
+ %b.addr = alloca i32, align 4
+ %m = alloca i32, align 4
+ store i32 %a, ptr %a.addr, align 4
+ store i32 %b, ptr %b.addr, align 4
+ %0 = load i32, ptr %a.addr, align 4
+ %1 = load i32, ptr %b.addr, align 4
+ %cmp = icmp slt i32 %0, %1
+ br i1 %cmp, label %cond.true, label %cond.false
+
+cond.true:
+ %2 = load i32, ptr %a.addr, align 4
+ br label %cond.end
+
+cond.false:
+ %3 = load i32, ptr %b.addr, align 4
+ br label %cond.end
+
+cond.end:
+ %cond = phi i32 [ %2, %cond.true ], [ %3, %cond.false ]
+ store i32 %cond, ptr %m, align 4
+ %4 = load i32, ptr %a.addr, align 4
+ %cmp1 = icmp sge i32 %4, 0
+ br i1 %cmp1, label %land.lhs.true, label %if.end9
+
+land.lhs.true:
+ %5 = load i32, ptr %a.addr, align 4
+ %cmp2 = icmp sle i32 %5, 15
+ br i1 %cmp2, label %land.lhs.true3, label %if.end9
+
+land.lhs.true3:
+ %6 = load i32, ptr %b.addr, align 4
+ %cmp4 = icmp sge i32 %6, 0
+ br i1 %cmp4, label %land.lhs.true5, label %if.end9
+
+land.lhs.true5:
+ %7 = load i32, ptr %b.addr, align 4
+ %cmp6 = icmp sle i32 %7, 15
+ br i1 %cmp6, label %if.then, label %if.end9
+
+if.then:
+ %8 = load i32, ptr %m, align 4
+ %cmp7 = icmp sgt i32 %8, 15
+ br i1 %cmp7, label %if.then8, label %if.end
+
+if.then8:
+ call void @use()
+ br label %if.end
+
+if.end:
+ br label %if.end9
+
+if.end9:
+ ret void
+}
+
+define void @PR207131_mask(i32 noundef %a, i32 noundef %b) {
+; CHECK-LABEL: @PR207131_mask(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: ret void
+;
+entry:
+ %a.addr = alloca i32, align 4
+ %b.addr = alloca i32, align 4
+ %m = alloca i32, align 4
+ store i32 %a, ptr %a.addr, align 4
+ store i32 %b, ptr %b.addr, align 4
+ %0 = load i32, ptr %a.addr, align 4
+ %1 = load i32, ptr %b.addr, align 4
+ %cmp = icmp slt i32 %0, %1
+ br i1 %cmp, label %cond.true, label %cond.false
+
+cond.true:
+ %2 = load i32, ptr %a.addr, align 4
+ br label %cond.end
+
+cond.false:
+ %3 = load i32, ptr %b.addr, align 4
+ br label %cond.end
+
+cond.end:
+ %cond = phi i32 [ %2, %cond.true ], [ %3, %cond.false ]
+ store i32 %cond, ptr %m, align 4
+ %4 = load i32, ptr %a.addr, align 4
+ %and = and i32 %4, -16
+ %cmp1 = icmp eq i32 %and, 0
+ br i1 %cmp1, label %land.lhs.true, label %if.end6
+
+land.lhs.true:
+ %5 = load i32, ptr %b.addr, align 4
+ %and2 = and i32 %5, -16
+ %cmp3 = icmp eq i32 %and2, 0
+ br i1 %cmp3, label %if.then, label %if.end6
+
+if.then:
+ %6 = load i32, ptr %m, align 4
+ %cmp4 = icmp sgt i32 %6, 15
+ br i1 %cmp4, label %if.then5, label %if.end
+
+if.then5:
+ call void @use()
+ br label %if.end
+
+if.end:
+ br label %if.end6
+
+if.end6:
+ ret void
+}
+
+define void @PR207131_assume(i32 noundef %a, i32 noundef %b) {
+; CHECK-LABEL: @PR207131_assume(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: ret void
+;
+entry:
+ %a.addr = alloca i32, align 4
+ %b.addr = alloca i32, align 4
+ %m = alloca i32, align 4
+ store i32 %a, ptr %a.addr, align 4
+ store i32 %b, ptr %b.addr, align 4
+ %0 = load i32, ptr %a.addr, align 4
+ %1 = load i32, ptr %b.addr, align 4
+ %cmp = icmp slt i32 %0, %1
+ br i1 %cmp, label %cond.true, label %cond.false
+
+cond.true:
+ %2 = load i32, ptr %a.addr, align 4
+ br label %cond.end
+
+cond.false:
+ %3 = load i32, ptr %b.addr, align 4
+ br label %cond.end
+
+cond.end:
+ %cond = phi i32 [ %2, %cond.true ], [ %3, %cond.false ]
+ store i32 %cond, ptr %m, align 4
+ %4 = load i32, ptr %a.addr, align 4
+ %cmp1 = icmp sge i32 %4, 0
+ br i1 %cmp1, label %land.lhs.true, label %if.end10
+
+land.lhs.true:
+ %5 = load i32, ptr %a.addr, align 4
+ %cmp2 = icmp sle i32 %5, 15
+ br i1 %cmp2, label %land.lhs.true3, label %if.end10
+
+land.lhs.true3:
+ %6 = load i32, ptr %b.addr, align 4
+ %cmp4 = icmp sge i32 %6, 0
+ br i1 %cmp4, label %land.lhs.true5, label %if.end10
+
+land.lhs.true5:
+ %7 = load i32, ptr %b.addr, align 4
+ %cmp6 = icmp sle i32 %7, 15
+ br i1 %cmp6, label %if.then, label %if.end10
+
+if.then:
+ %8 = load i32, ptr %a.addr, align 4
+ %cmp7 = icmp sle i32 %8, 15
+ call void @llvm.assume(i1 %cmp7)
+ %9 = load i32, ptr %m, align 4
+ %cmp8 = icmp sgt i32 %9, 15
+ br i1 %cmp8, label %if.then9, label %if.end
+
+if.then9:
+ call void @use()
+ br label %if.end
+
+if.end:
+ br label %if.end10
+
+if.end10:
+ ret void
+}
>From 3c72ad33b3228a4c39647e98e04f221585954197 Mon Sep 17 00:00:00 2001
From: Akash Manna <akash.manna.mymail at gmail.com>
Date: Tue, 29 Sep 2026 12:37:55 +0530
Subject: [PATCH 2/2] [ValueTracking] Infer min/max compares from bounded
operand guards
For `if (a >= 0 && a <= 15 && b >= 0 && b <= 15) if (min(a, b) > 15)`,
SimplifyCFG merges the inner test into the guard and InstCombine turns
the guard into (a | b) u< 16, leaving
`and (icmp ult (or b, a), 16), (icmp sgt (smin a, b), 15)`.
isImpliedCondition could not show that the guard makes the compare
false: it did not know that a bound on a | b also bounds a, and it did
not look through the smin.
If isTruePredicate orders the compared value against the guard's value
in the direction of the guard, e.g. a u<= a | b for (a | b) u< 16, the
guard holds for that value too, so retry the implication with it. And
since min/max(X, Y) is X or Y, decide a compare of it from the compares
of X and Y: both agreeing is enough, and for an ordered compare one of
them may be, e.g. smin(X, Y) s> Z is X s> Z && Y s> Z.
Fixes #207131
---
llvm/lib/Analysis/ValueTracking.cpp | 69 ++++++++++++++++--
.../InstSimplify/and-or-implied-cond.ll | 71 ++++---------------
.../Transforms/PhaseOrdering/cmp-logic.ll | 10 ---
3 files changed, 75 insertions(+), 75 deletions(-)
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index dd5f6fc7ad16a..48c0300903b17 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -9968,6 +9968,20 @@ isImpliedCondICmps(CmpPredicate LPred, const Value *L0, const Value *L1,
if (!LHSIsTrue)
LPred = ICmpInst::getInverseCmpPredicate(LPred);
+ // E.g. (X | Y) u< C implies X u< C, since X u<= X | Y.
+ if (L0 != R0) {
+ CmpInst::Predicate Pred = LPred.dropSameSign();
+ CmpInst::Predicate LEPred =
+ ICmpInst::isSigned(Pred) ? ICmpInst::ICMP_SLE : ICmpInst::ICMP_ULE;
+ if (((ICmpInst::isLT(Pred) || ICmpInst::isLE(Pred)) &&
+ isTruePredicate(LEPred, R0, L0)) ||
+ ((ICmpInst::isGT(Pred) || ICmpInst::isGE(Pred)) &&
+ isTruePredicate(LEPred, L0, R0)))
+ if (std::optional<bool> Implied = isImpliedCondICmps(
+ Pred, R0, L1, RPred, R0, R1, DL, /*LHSIsTrue=*/true))
+ return Implied;
+ }
+
// We can have non-canonical operands, so try to normalize any common operand
// to L0/R0.
if (L0 == R1) {
@@ -10178,6 +10192,41 @@ isImpliedCondAndOr(const Instruction *LHS, CmpPredicate RHSPred,
return std::nullopt;
}
+/// min/max(X, Y) is X or Y. For an ordered compare one operand may suffice,
+/// e.g. smin(X, Y) s> Z is X s> Z && Y s> Z.
+static std::optional<bool> isImpliedCondMinMax(const Value *LHS,
+ CmpPredicate RPred,
+ const Value *R0, const Value *R1,
+ const DataLayout &DL,
+ bool LHSIsTrue, unsigned Depth) {
+ const auto *MinMax = dyn_cast<MinMaxIntrinsic>(R0);
+ if (!MinMax) {
+ MinMax = dyn_cast<MinMaxIntrinsic>(R1);
+ if (!MinMax)
+ return std::nullopt;
+ R1 = R0;
+ RPred = ICmpInst::getSwappedCmpPredicate(RPred);
+ }
+
+ CmpInst::Predicate Pred = RPred.dropSameSign();
+ CmpInst::Predicate StrictPred = ICmpInst::getStrictPredicate(Pred);
+ bool IsAnd =
+ StrictPred == ICmpInst::getSwappedPredicate(MinMax->getPredicate());
+ bool IsOr = StrictPred == MinMax->getPredicate();
+
+ std::optional<bool> ImpliedX = isImpliedCondition(
+ LHS, Pred, MinMax->getLHS(), R1, DL, LHSIsTrue, Depth + 1);
+ if ((IsAnd && ImpliedX == false) || (IsOr && ImpliedX == true))
+ return ImpliedX;
+ std::optional<bool> ImpliedY = isImpliedCondition(
+ LHS, Pred, MinMax->getRHS(), R1, DL, LHSIsTrue, Depth + 1);
+ if ((IsAnd && ImpliedY == false) || (IsOr && ImpliedY == true))
+ return ImpliedY;
+ if (ImpliedX && ImpliedX == ImpliedY)
+ return ImpliedX;
+ return std::nullopt;
+}
+
std::optional<bool>
llvm::isImpliedCondition(const Value *LHS, CmpPredicate RHSPred,
const Value *RHSOp0, const Value *RHSOp1,
@@ -10202,9 +10251,13 @@ llvm::isImpliedCondition(const Value *LHS, CmpPredicate RHSPred,
if (RHSOp0->getType()->getScalarType()->isIntOrPtrTy()) {
CmpPredicate LHSPred;
Value *LHSOp0, *LHSOp1;
- if (match(LHS, m_ICmpLike(LHSPred, m_Value(LHSOp0), m_Value(LHSOp1))))
- return isImpliedCondICmps(LHSPred, LHSOp0, LHSOp1, RHSPred, RHSOp0,
- RHSOp1, DL, LHSIsTrue);
+ if (match(LHS, m_ICmpLike(LHSPred, m_Value(LHSOp0), m_Value(LHSOp1)))) {
+ if (std::optional<bool> Implied = isImpliedCondICmps(
+ LHSPred, LHSOp0, LHSOp1, RHSPred, RHSOp0, RHSOp1, DL, LHSIsTrue))
+ return Implied;
+ return isImpliedCondMinMax(LHS, RHSPred, RHSOp0, RHSOp1, DL, LHSIsTrue,
+ Depth);
+ }
} else {
assert(RHSOp0->getType()->isFPOrFPVectorTy() &&
"Expected floating point type only!");
@@ -10220,9 +10273,13 @@ llvm::isImpliedCondition(const Value *LHS, CmpPredicate RHSPred,
if (const Instruction *LHSI = dyn_cast<Instruction>(LHS)) {
if ((LHSI->getOpcode() == Instruction::And ||
LHSI->getOpcode() == Instruction::Or ||
- LHSI->getOpcode() == Instruction::Select))
- return isImpliedCondAndOr(LHSI, RHSPred, RHSOp0, RHSOp1, DL, LHSIsTrue,
- Depth);
+ LHSI->getOpcode() == Instruction::Select)) {
+ if (std::optional<bool> Implied = isImpliedCondAndOr(
+ LHSI, RHSPred, RHSOp0, RHSOp1, DL, LHSIsTrue, Depth))
+ return Implied;
+ return isImpliedCondMinMax(LHS, RHSPred, RHSOp0, RHSOp1, DL, LHSIsTrue,
+ Depth);
+ }
}
return std::nullopt;
}
diff --git a/llvm/test/Transforms/InstSimplify/and-or-implied-cond.ll b/llvm/test/Transforms/InstSimplify/and-or-implied-cond.ll
index 83e39b52d6235..932c465f685eb 100644
--- a/llvm/test/Transforms/InstSimplify/and-or-implied-cond.ll
+++ b/llvm/test/Transforms/InstSimplify/and-or-implied-cond.ll
@@ -400,12 +400,7 @@ define i1 @neg_or_trunc_implied(i8 %x, i8 %y) {
define i1 @pr207131(i32 %a, i32 %b) {
; CHECK-LABEL: @pr207131(
-; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smin.i32(i32 [[A:%.*]], i32 [[B:%.*]])
-; CHECK-NEXT: [[OR:%.*]] = or i32 [[B]], [[A]]
-; CHECK-NEXT: [[C1:%.*]] = icmp ult i32 [[OR]], 16
-; CHECK-NEXT: [[C2:%.*]] = icmp sgt i32 [[M]], 15
-; CHECK-NEXT: [[AND:%.*]] = and i1 [[C1]], [[C2]]
-; CHECK-NEXT: ret i1 [[AND]]
+; CHECK-NEXT: ret i1 false
;
%m = call i32 @llvm.smin.i32(i32 %a, i32 %b)
%or = or i32 %b, %a
@@ -417,12 +412,7 @@ define i1 @pr207131(i32 %a, i32 %b) {
define i1 @and_smin_of_bounded_or_commuted(i32 %a, i32 %b) {
; CHECK-LABEL: @and_smin_of_bounded_or_commuted(
-; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smin.i32(i32 [[A:%.*]], i32 [[B:%.*]])
-; CHECK-NEXT: [[OR:%.*]] = or i32 [[B]], [[A]]
-; CHECK-NEXT: [[C1:%.*]] = icmp ult i32 [[OR]], 16
-; CHECK-NEXT: [[C2:%.*]] = icmp slt i32 15, [[M]]
-; CHECK-NEXT: [[AND:%.*]] = and i1 [[C1]], [[C2]]
-; CHECK-NEXT: ret i1 [[AND]]
+; CHECK-NEXT: ret i1 false
;
%m = call i32 @llvm.smin.i32(i32 %a, i32 %b)
%or = or i32 %b, %a
@@ -434,12 +424,7 @@ define i1 @and_smin_of_bounded_or_commuted(i32 %a, i32 %b) {
define i1 @and_smax_of_bounded_or(i32 %a, i32 %b) {
; CHECK-LABEL: @and_smax_of_bounded_or(
-; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smax.i32(i32 [[A:%.*]], i32 [[B:%.*]])
-; CHECK-NEXT: [[OR:%.*]] = or i32 [[B]], [[A]]
-; CHECK-NEXT: [[C1:%.*]] = icmp ult i32 [[OR]], 16
-; CHECK-NEXT: [[C2:%.*]] = icmp sgt i32 [[M]], 15
-; CHECK-NEXT: [[AND:%.*]] = and i1 [[C1]], [[C2]]
-; CHECK-NEXT: ret i1 [[AND]]
+; CHECK-NEXT: ret i1 false
;
%m = call i32 @llvm.smax.i32(i32 %a, i32 %b)
%or = or i32 %b, %a
@@ -451,12 +436,9 @@ define i1 @and_smax_of_bounded_or(i32 %a, i32 %b) {
define i1 @and_smax_of_bounded_or_implied(i32 %a, i32 %b) {
; CHECK-LABEL: @and_smax_of_bounded_or_implied(
-; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smax.i32(i32 [[A:%.*]], i32 [[B:%.*]])
-; CHECK-NEXT: [[OR:%.*]] = or i32 [[B]], [[A]]
+; CHECK-NEXT: [[OR:%.*]] = or i32 [[B:%.*]], [[A:%.*]]
; CHECK-NEXT: [[C1:%.*]] = icmp ult i32 [[OR]], 16
-; CHECK-NEXT: [[C2:%.*]] = icmp slt i32 [[M]], 16
-; CHECK-NEXT: [[AND:%.*]] = and i1 [[C1]], [[C2]]
-; CHECK-NEXT: ret i1 [[AND]]
+; CHECK-NEXT: ret i1 [[C1]]
;
%m = call i32 @llvm.smax.i32(i32 %a, i32 %b)
%or = or i32 %b, %a
@@ -485,11 +467,7 @@ define i1 @and_smin_of_bounded_or_neg(i32 %a, i32 %b) {
define i1 @and_smin_one_operand_bounded(i32 %a, i32 %b) {
; CHECK-LABEL: @and_smin_one_operand_bounded(
-; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smin.i32(i32 [[A:%.*]], i32 [[B:%.*]])
-; CHECK-NEXT: [[C1:%.*]] = icmp ult i32 [[A]], 16
-; CHECK-NEXT: [[C2:%.*]] = icmp sgt i32 [[M]], 15
-; CHECK-NEXT: [[AND:%.*]] = and i1 [[C1]], [[C2]]
-; CHECK-NEXT: ret i1 [[AND]]
+; CHECK-NEXT: ret i1 false
;
%m = call i32 @llvm.smin.i32(i32 %a, i32 %b)
%c1 = icmp ult i32 %a, 16
@@ -515,11 +493,7 @@ define i1 @and_smax_one_operand_bounded_neg(i32 %a, i32 %b) {
define i1 @and_smin_signed_bound(i32 %a, i32 %b) {
; CHECK-LABEL: @and_smin_signed_bound(
-; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smin.i32(i32 [[A:%.*]], i32 [[B:%.*]])
-; CHECK-NEXT: [[C1:%.*]] = icmp slt i32 [[A]], 16
-; CHECK-NEXT: [[C2:%.*]] = icmp sgt i32 [[M]], 15
-; CHECK-NEXT: [[AND:%.*]] = and i1 [[C1]], [[C2]]
-; CHECK-NEXT: ret i1 [[AND]]
+; CHECK-NEXT: ret i1 false
;
%m = call i32 @llvm.smin.i32(i32 %a, i32 %b)
%c1 = icmp slt i32 %a, 16
@@ -530,12 +504,7 @@ define i1 @and_smin_signed_bound(i32 %a, i32 %b) {
define i1 @and_smin_of_bounded_or_eq(i32 %a, i32 %b) {
; CHECK-LABEL: @and_smin_of_bounded_or_eq(
-; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smin.i32(i32 [[A:%.*]], i32 [[B:%.*]])
-; CHECK-NEXT: [[OR:%.*]] = or i32 [[B]], [[A]]
-; CHECK-NEXT: [[C1:%.*]] = icmp ult i32 [[OR]], 16
-; CHECK-NEXT: [[C2:%.*]] = icmp eq i32 [[M]], 16
-; CHECK-NEXT: [[AND:%.*]] = and i1 [[C1]], [[C2]]
-; CHECK-NEXT: ret i1 [[AND]]
+; CHECK-NEXT: ret i1 false
;
%m = call i32 @llvm.smin.i32(i32 %a, i32 %b)
%or = or i32 %b, %a
@@ -564,12 +533,7 @@ define i1 @and_smin_of_bounded_or_eq_neg(i32 %a, i32 %b) {
define i1 @and_smin_of_bounded_or_ucmp(i32 %a, i32 %b) {
; CHECK-LABEL: @and_smin_of_bounded_or_ucmp(
-; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smin.i32(i32 [[A:%.*]], i32 [[B:%.*]])
-; CHECK-NEXT: [[OR:%.*]] = or i32 [[B]], [[A]]
-; CHECK-NEXT: [[C1:%.*]] = icmp ult i32 [[OR]], 16
-; CHECK-NEXT: [[C2:%.*]] = icmp ugt i32 [[M]], 15
-; CHECK-NEXT: [[AND:%.*]] = and i1 [[C1]], [[C2]]
-; CHECK-NEXT: ret i1 [[AND]]
+; CHECK-NEXT: ret i1 false
;
%m = call i32 @llvm.smin.i32(i32 %a, i32 %b)
%or = or i32 %b, %a
@@ -581,11 +545,7 @@ define i1 @and_smin_of_bounded_or_ucmp(i32 %a, i32 %b) {
define i1 @and_bounded_or_operand(i32 %a, i32 %b) {
; CHECK-LABEL: @and_bounded_or_operand(
-; CHECK-NEXT: [[OR:%.*]] = or i32 [[B:%.*]], [[A:%.*]]
-; CHECK-NEXT: [[C1:%.*]] = icmp ult i32 [[OR]], 16
-; CHECK-NEXT: [[C2:%.*]] = icmp sgt i32 [[A]], 15
-; CHECK-NEXT: [[AND:%.*]] = and i1 [[C1]], [[C2]]
-; CHECK-NEXT: ret i1 [[AND]]
+; CHECK-NEXT: ret i1 false
;
%or = or i32 %b, %a
%c1 = icmp ult i32 %or, 16
@@ -596,12 +556,7 @@ define i1 @and_bounded_or_operand(i32 %a, i32 %b) {
define i1 @or_umax_of_bounded_or(i32 %a, i32 %b) {
; CHECK-LABEL: @or_umax_of_bounded_or(
-; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.umax.i32(i32 [[A:%.*]], i32 [[B:%.*]])
-; CHECK-NEXT: [[OR:%.*]] = or i32 [[B]], [[A]]
-; CHECK-NEXT: [[C1:%.*]] = icmp uge i32 [[OR]], 16
-; CHECK-NEXT: [[C2:%.*]] = icmp ult i32 [[M]], 16
-; CHECK-NEXT: [[RES:%.*]] = or i1 [[C1]], [[C2]]
-; CHECK-NEXT: ret i1 [[RES]]
+; CHECK-NEXT: ret i1 true
;
%m = call i32 @llvm.umax.i32(i32 %a, i32 %b)
%or = or i32 %b, %a
@@ -617,9 +572,7 @@ define i1 @smin_of_bounded_or_dom_cond(i32 %a, i32 %b) {
; CHECK-NEXT: [[C1:%.*]] = icmp ult i32 [[OR]], 16
; CHECK-NEXT: br i1 [[C1]], label [[THEN:%.*]], label [[ELSE:%.*]]
; CHECK: then:
-; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smin.i32(i32 [[A]], i32 [[B]])
-; CHECK-NEXT: [[C2:%.*]] = icmp sgt i32 [[M]], 15
-; CHECK-NEXT: ret i1 [[C2]]
+; CHECK-NEXT: ret i1 false
; CHECK: else:
; CHECK-NEXT: ret i1 true
;
diff --git a/llvm/test/Transforms/PhaseOrdering/cmp-logic.ll b/llvm/test/Transforms/PhaseOrdering/cmp-logic.ll
index f3c5404158d65..8eaa86ce7d7c1 100644
--- a/llvm/test/Transforms/PhaseOrdering/cmp-logic.ll
+++ b/llvm/test/Transforms/PhaseOrdering/cmp-logic.ll
@@ -162,16 +162,6 @@ declare void @use()
define void @PR207131(i32 noundef %a, i32 noundef %b) {
; CHECK-LABEL: @PR207131(
; CHECK-NEXT: entry:
-; CHECK-NEXT: [[COND:%.*]] = tail call i32 @llvm.smin.i32(i32 [[A:%.*]], i32 [[B:%.*]])
-; CHECK-NEXT: [[TMP0:%.*]] = or i32 [[B]], [[A]]
-; CHECK-NEXT: [[OR_COND2:%.*]] = icmp ult i32 [[TMP0]], 16
-; CHECK-NEXT: [[CMP7:%.*]] = icmp sgt i32 [[COND]], 15
-; CHECK-NEXT: [[OR_COND3:%.*]] = and i1 [[OR_COND2]], [[CMP7]]
-; CHECK-NEXT: br i1 [[OR_COND3]], label [[IF_THEN8:%.*]], label [[IF_END9:%.*]]
-; CHECK: if.then8:
-; CHECK-NEXT: tail call void @use()
-; CHECK-NEXT: br label [[IF_END9]]
-; CHECK: if.end9:
; CHECK-NEXT: ret void
;
entry:
More information about the llvm-commits
mailing list