[llvm] [InstCombine] Decompose icmp over min/max intrinsics when compared with a constant (PR #182461)

via llvm-commits llvm-commits at lists.llvm.org
Fri Feb 20 01:46:19 PST 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: Nathiyaa Sengodan (Nathiyaa-Sengodan)

<details>
<summary>Changes</summary>

 Fold icmp pred min/max(X, Y), C (where C is a constant) into individual comparisons combined with and/or:

  min(X, Y) pred C --> (X pred C) and (Y pred C) when pred is >, >=
  min(X, Y) pred C --> (X pred C) or  (Y pred C) when pred is <, <=
  max(X, Y) pred C --> (X pred C) or  (Y pred C) when pred is >, >=
  max(X, Y) pred C --> (X pred C) and (Y pred C) when pred is <, <=

 This eliminates the min/max intrinsic and exposes simple constant comparisons that enable further optimization by downstream passes.
 
 The decomposition is restricted to cases where:
- The comparison operand Z is a constant
- The min/max has one use
- The predicate is non-equality

Example:

Before :
%v = call i8 @<!-- -->llvm.smin.i8(i8 %x, i8 %y)
%cmp = icmp sgt i8 %v, 5

After this fold:
%cmp1 = icmp sgt i8 %x, 5
%cmp2 = icmp sgt i8 %y, 5
%res = and i1 %cmp1, %cmp2

Fixes [#<!-- -->167059](https://github.com/llvm/llvm-project/issues/167059)

---
Full diff: https://github.com/llvm/llvm-project/pull/182461.diff


3 Files Affected:

- (modified) llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp (+19-1) 
- (modified) llvm/test/Transforms/InstCombine/min-positive.ll (+6-4) 
- (modified) llvm/test/Transforms/InstCombine/minmax-intrinsics.ll (+75) 


``````````diff
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index bce609275ffa9..4c0052973ede2 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -5717,8 +5717,26 @@ Instruction *InstCombinerImpl::foldICmpWithMinMax(Instruction &I,
   Pred = Pred.dropSameSign();
   auto CmpXZ = IsCondKnownTrue(simplifyICmpInst(Pred, X, Z, Q));
   auto CmpYZ = IsCondKnownTrue(simplifyICmpInst(Pred, Y, Z, Q));
-  if (!CmpXZ.has_value() && !CmpYZ.has_value())
+
+  if (!CmpXZ.has_value() && !CmpYZ.has_value()) {
+    // General decomposition of icmp over min/max intrinsic:
+    // min(X, Y) pred Z --> (X pred Z) and (Y pred Z) when pred is >, >=
+    // min(X, Y) pred Z --> (X pred Z)  or (Y pred Z) when pred is <, <=
+    // max(X, Y) pred Z --> (X pred Z)  or (Y pred Z) when pred is >, >=
+    // max(X, Y) pred Z --> (X pred Z) and (Y pred Z) when pred is <, <=
+    if (MinMax->hasOneUse() && !ICmpInst::isEquality(Pred) &&
+        isa<Constant>(Z)) {
+      bool IsSame =
+          MinMax->getPredicate() == ICmpInst::getStrictPredicate(Pred);
+      Value *CmpX = Builder.CreateICmp(Pred, X, Z);
+      Value *CmpY = Builder.CreateICmp(Pred, Y, Z);
+      if (IsSame)
+        return BinaryOperator::CreateOr(CmpX, CmpY);
+      return BinaryOperator::CreateAnd(CmpX, CmpY);
+    }
     return nullptr;
+  }
+
   if (!CmpXZ.has_value()) {
     std::swap(X, Y);
     std::swap(CmpXZ, CmpYZ);
diff --git a/llvm/test/Transforms/InstCombine/min-positive.ll b/llvm/test/Transforms/InstCombine/min-positive.ll
index db73974b7bf6a..3724eec5c6917 100644
--- a/llvm/test/Transforms/InstCombine/min-positive.ll
+++ b/llvm/test/Transforms/InstCombine/min-positive.ll
@@ -84,8 +84,9 @@ define <2 x i1> @smin_commute_vec_poison_elts(<2 x i32> %x, <2 x i32> %other) {
 define i1 @maybe_not_positive(i32 %other) {
 ; CHECK-LABEL: @maybe_not_positive(
 ; CHECK-NEXT:    [[POSITIVE:%.*]] = load i32, ptr @g, align 4, !range [[RNG0:![0-9]+]]
-; CHECK-NEXT:    [[SEL:%.*]] = call i32 @llvm.smin.i32(i32 [[POSITIVE]], i32 [[OTHER:%.*]])
-; CHECK-NEXT:    [[TEST:%.*]] = icmp sgt i32 [[SEL]], 0
+; CHECK-NEXT:    [[CMP1:%.*]] = icmp ne i32 [[POSITIVE]], 0
+; CHECK-NEXT:    [[CMP2:%.*]] = icmp sgt i32 [[OTHER:%.*]], 0
+; CHECK-NEXT:    [[TEST:%.*]] = and i1 [[CMP1]], [[CMP2]]
 ; CHECK-NEXT:    ret i1 [[TEST]]
 ;
   %positive = load i32, ptr @g, !range !{i32 0, i32 2048}
@@ -98,8 +99,9 @@ define i1 @maybe_not_positive(i32 %other) {
 define <2 x i1> @maybe_not_positive_vec(<2 x i32> %x, <2 x i32> %other) {
 ; CHECK-LABEL: @maybe_not_positive_vec(
 ; CHECK-NEXT:    [[NOTNEG:%.*]] = and <2 x i32> [[X:%.*]], splat (i32 7)
-; CHECK-NEXT:    [[SEL:%.*]] = call <2 x i32> @llvm.smin.v2i32(<2 x i32> [[NOTNEG]], <2 x i32> [[OTHER:%.*]])
-; CHECK-NEXT:    [[TEST:%.*]] = icmp sgt <2 x i32> [[SEL]], zeroinitializer
+; CHECK-NEXT:    [[CMP1:%.*]] = icmp ne <2 x i32> [[NOTNEG]], zeroinitializer
+; CHECK-NEXT:    [[CMP2:%.*]] = icmp sgt <2 x i32> [[OTHER:%.*]], zeroinitializer
+; CHECK-NEXT:    [[TEST:%.*]] = and <2 x i1> [[CMP1]], [[CMP2]]
 ; CHECK-NEXT:    ret <2 x i1> [[TEST]]
 ;
   %notneg = and <2 x i32> %x, <i32 7, i32 7>
diff --git a/llvm/test/Transforms/InstCombine/minmax-intrinsics.ll b/llvm/test/Transforms/InstCombine/minmax-intrinsics.ll
index 52bc3636be359..639728a6e90c0 100644
--- a/llvm/test/Transforms/InstCombine/minmax-intrinsics.ll
+++ b/llvm/test/Transforms/InstCombine/minmax-intrinsics.ll
@@ -2718,3 +2718,78 @@ define i8 @test_smin_and_multiuse(i8 %x, i8 %y) {
   %res = call i8 @llvm.smin.i8(i8 %x1, i8 %y1)
   ret i8 %res
 }
+
+define i1 @smin_sgt_decompose(i8 %x, i8 %y) {
+; CHECK-LABEL: @smin_sgt_decompose(
+; CHECK-NEXT:    [[CMP1:%.*]] = icmp sgt i8 [[X:%.*]], 5
+; CHECK-NEXT:    [[CMP2:%.*]] = icmp sgt i8 [[Y:%.*]], 5
+; CHECK-NEXT:    [[RES:%.*]] = and i1 [[CMP1]], [[CMP2]]
+; CHECK-NEXT:    ret i1 [[RES]]
+;
+  %v = call i8 @llvm.smin.i8(i8 %x, i8 %y)
+  %cmp = icmp sgt i8 %v, 5
+  ret i1 %cmp
+}
+
+define i1 @smax_sgt_decompose(i8 %x, i8 %y) {
+; CHECK-LABEL: @smax_sgt_decompose(
+; CHECK-NEXT:    [[CMP1:%.*]] = icmp sgt i8 [[X:%.*]], 5
+; CHECK-NEXT:    [[CMP2:%.*]] = icmp sgt i8 [[Y:%.*]], 5
+; CHECK-NEXT:    [[RES:%.*]] = or i1 [[CMP1]], [[CMP2]]
+; CHECK-NEXT:    ret i1 [[RES]]
+;
+  %v = call i8 @llvm.smax.i8(i8 %x, i8 %y)
+  %cmp = icmp sgt i8 %v, 5
+  ret i1 %cmp
+}
+
+define i1 @umin_ult_decompose(i8 %x, i8 %y) {
+; CHECK-LABEL: @umin_ult_decompose(
+; CHECK-NEXT:    [[CMP1:%.*]] = icmp ult i8 [[X:%.*]], 5
+; CHECK-NEXT:    [[CMP2:%.*]] = icmp ult i8 [[Y:%.*]], 5
+; CHECK-NEXT:    [[RES:%.*]] = or i1 [[CMP1]], [[CMP2]]
+; CHECK-NEXT:    ret i1 [[RES]]
+;
+  %v = call i8 @llvm.umin.i8(i8 %x, i8 %y)
+  %cmp = icmp ult i8 %v, 5
+  ret i1 %cmp
+}
+
+; smin(sub nsw Y X, sub nsw C X) sgt 0 --> (X slt C) and (Y sgt X)
+define i1 @smin_of_sub_nsw_sgt_zero_decompose(i16 %arg0, i16 %arg1) {
+; CHECK-LABEL: @smin_of_sub_nsw_sgt_zero_decompose(
+; CHECK-NEXT:    [[CMP1:%.*]] = icmp slt i16 [[ARG0:%.*]], 32
+; CHECK-NEXT:    [[CMP2:%.*]] = icmp sgt i16 [[ARG1:%.*]], [[ARG0]]
+; CHECK-NEXT:    [[RES:%.*]] = and i1 [[CMP1]], [[CMP2]]
+; CHECK-NEXT:    ret i1 [[RES]]
+;
+  %v0 = sub nsw i16 %arg1, %arg0
+  %v1 = sub nsw i16 32, %arg0
+  %v2 = call i16 @llvm.smin.i16(i16 %v1, i16 %v0)
+  %v3 = icmp sgt i16 %v2, 0
+  ret i1 %v3
+}
+
+define i1 @smin_sgt_decompose_multiuse(i8 %x, i8 %y) {
+; CHECK-LABEL: @smin_sgt_decompose_multiuse(
+; CHECK-NEXT:    [[V:%.*]] = call i8 @llvm.smin.i8(i8 [[X:%.*]], i8 [[Y:%.*]])
+; CHECK-NEXT:    call void @use(i8 [[V]])
+; CHECK-NEXT:    [[CMP:%.*]] = icmp sgt i8 [[V]], 5
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %v = call i8 @llvm.smin.i8(i8 %x, i8 %y)
+  call void @use(i8 %v)
+  %cmp = icmp sgt i8 %v, 5
+  ret i1 %cmp
+}
+
+define i1 @smax_sgt_variable(i8 %x, i8 %y, i8 %z) {
+; CHECK-LABEL: @smax_sgt_variable(
+; CHECK-NEXT:    [[V:%.*]] = call i8 @llvm.smax.i8(i8 [[X:%.*]], i8 [[Y:%.*]])
+; CHECK-NEXT:    [[CMP:%.*]] = icmp sgt i8 [[V]], [[Z:%.*]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %v = call i8 @llvm.smax.i8(i8 %x, i8 %y)
+  %cmp = icmp sgt i8 %v, %z
+  ret i1 %cmp
+}
\ No newline at end of file

``````````

</details>


https://github.com/llvm/llvm-project/pull/182461


More information about the llvm-commits mailing list