[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