[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