[llvm] [InstCombine] Fold icmp eq/ne (add (select (icmp X, C), TrueC, FalseC), X), 0 (PR #196027)

Muhammed Shiyas N via llvm-commits llvm-commits at lists.llvm.org
Wed May 6 02:07:19 PDT 2026


https://github.com/Shiyas-N created https://github.com/llvm/llvm-project/pull/196027

Fixes #167079

Fold:
  icmp eq/ne (add (select (icmp slt/sgt/ult/ugt X, CondC), TrueC, FalseC), X), 0

Into:
  eq: (X == -TrueC) || (X == -FalseC)
  ne: (X != -TrueC) && (X != -FalseC)

The pattern originates as `sub X, select(cond, TrueC, FalseC)` which
InstCombine canonicalizes to add form before this fold is reached.

The fold is valid when each negated constant lands on the correct side
of CondC relative to the inner predicate:

  SLT:  -TrueC s<  CondC  AND  -FalseC s>= CondC
  SGT:  -TrueC s>  CondC  AND  -FalseC s<= CondC
  ULT:  -TrueC u<  CondC  AND  -FalseC u>= CondC
  UGT:  -TrueC u>  CondC  AND  -FalseC u<= CondC

This ensures each root (-TrueC, -FalseC) is reached only by the select
branch that produces zero when added to X. The two cases are mutually
exclusive under these conditions. The ne case follows by De Morgan duality.

OneUse is required on the add (correctness — it is being removed) and
on the select (profitability — fold adds 3 instructions and removes 2,
so it is a loss if the select is used elsewhere).

Alive2 proof (all 8 cases, SLT/SGT/ULT/UGT × eq/ne):
https://alive2.llvm.org/ce/z/q8qHiZ

>From 24a9e4615845b3f290ec83a1ddaaa980f92f68d5 Mon Sep 17 00:00:00 2001
From: Shiyas-N <muhammedshiyasn811 at gmail.com>
Date: Mon, 4 May 2026 17:42:34 +0000
Subject: [PATCH 1/2] [InstCombine] Add baseline test for icmp eq/ne of
 add(select, X) with zero

Add test cases covering patterns of the form:
  icmp eq/ne (add (select (icmp X, C), TrueC, FalseC), X), 0
and the commuted variant.

These tests capture the current behavior prior to introducing the
fold for the missed optimization described in #167079.
---
 llvm/test/Transforms/InstCombine/icmp-add.ll | 347 +++++++++++++++++++
 1 file changed, 347 insertions(+)

diff --git a/llvm/test/Transforms/InstCombine/icmp-add.ll b/llvm/test/Transforms/InstCombine/icmp-add.ll
index 1b66a50c26e59..1dd172af15898 100644
--- a/llvm/test/Transforms/InstCombine/icmp-add.ll
+++ b/llvm/test/Transforms/InstCombine/icmp-add.ll
@@ -3602,3 +3602,350 @@ entry:
   %result = select i1 %eq, i1 %v1_lt_v3, i1 %less_than
   ret i1 %result
 }
+
+; PR 167079
+
+define i1 @icmp_add_select_slt_eq(i32 %x) {
+; CHECK-LABEL: @icmp_add_select_slt_eq(
+; CHECK-NEXT:    [[COND:%.*]] = icmp slt i32 [[X:%.*]], 15
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i32 -10, i32 -20
+; CHECK-NEXT:    [[ADD:%.*]] = sub i32 0, [[X]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[SEL]], [[ADD]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %cond = icmp slt i32 %x, 15
+  %sel = select i1 %cond, i32 -10, i32 -20
+  %add = add i32 %sel, %x
+  %cmp = icmp eq i32 %add, 0
+  ret i1 %cmp
+}
+
+; ne outer predicate produces and instead of or
+define i1 @icmp_add_select_slt_ne(i32 %x) {
+; CHECK-LABEL: @icmp_add_select_slt_ne(
+; CHECK-NEXT:    [[COND:%.*]] = icmp slt i32 [[X:%.*]], 15
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i32 -10, i32 -20
+; CHECK-NEXT:    [[ADD:%.*]] = sub i32 0, [[X]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ne i32 [[SEL]], [[ADD]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %cond = icmp slt i32 %x, 15
+  %sel = select i1 %cond, i32 -10, i32 -20
+  %add = add i32 %sel, %x
+  %cmp = icmp ne i32 %add, 0
+  ret i1 %cmp
+}
+
+; sgt inner predicate
+define i1 @icmp_add_select_sgt_eq(i32 %x) {
+; CHECK-LABEL: @icmp_add_select_sgt_eq(
+; CHECK-NEXT:    [[COND:%.*]] = icmp sgt i32 [[X:%.*]], 15
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i32 -20, i32 -10
+; CHECK-NEXT:    [[ADD:%.*]] = sub i32 0, [[X]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[SEL]], [[ADD]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %cond = icmp sgt i32 %x, 15
+  %sel = select i1 %cond, i32 -20, i32 -10
+  %add = add i32 %sel, %x
+  %cmp = icmp eq i32 %add, 0
+  ret i1 %cmp
+}
+
+; ult inner predicate
+define i1 @icmp_add_select_ult_eq(i32 %x) {
+; CHECK-LABEL: @icmp_add_select_ult_eq(
+; CHECK-NEXT:    [[COND:%.*]] = icmp ult i32 [[X:%.*]], 15
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i32 -10, i32 -20
+; CHECK-NEXT:    [[ADD:%.*]] = sub i32 0, [[X]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[SEL]], [[ADD]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %cond = icmp ult i32 %x, 15
+  %sel = select i1 %cond, i32 -10, i32 -20
+  %add = add i32 %sel, %x
+  %cmp = icmp eq i32 %add, 0
+  ret i1 %cmp
+}
+
+; ugt inner predicate
+define i1 @icmp_add_select_ugt_eq(i32 %x) {
+; CHECK-LABEL: @icmp_add_select_ugt_eq(
+; CHECK-NEXT:    [[COND:%.*]] = icmp ugt i32 [[X:%.*]], 15
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i32 -20, i32 -10
+; CHECK-NEXT:    [[ADD:%.*]] = sub i32 0, [[X]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[SEL]], [[ADD]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %cond = icmp ugt i32 %x, 15
+  %sel = select i1 %cond, i32 -20, i32 -10
+  %add = add i32 %sel, %x
+  %cmp = icmp eq i32 %add, 0
+  ret i1 %cmp
+}
+
+; Commuted add operands (tests m_c_Add)
+
+define i1 @icmp_add_select_slt_eq_commute(i32 %x) {
+; CHECK-LABEL: @icmp_add_select_slt_eq_commute(
+; CHECK-NEXT:    [[COND:%.*]] = icmp slt i32 [[X:%.*]], 15
+; CHECK-NEXT:    [[SEL_NEG:%.*]] = select i1 [[COND]], i32 10, i32 20
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[X]], [[SEL_NEG]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %cond = icmp slt i32 %x, 15
+  %sel = select i1 %cond, i32 -10, i32 -20
+  %add = add i32 %x, %sel
+  %cmp = icmp eq i32 %add, 0
+  ret i1 %cmp
+}
+
+; i8 Type coverage
+
+define i1 @icmp_add_select_slt_eq_i8(i8 %x) {
+; CHECK-LABEL: @icmp_add_select_slt_eq_i8(
+; CHECK-NEXT:    [[COND:%.*]] = icmp slt i8 [[X:%.*]], 15
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i8 -10, i8 -20
+; CHECK-NEXT:    [[ADD:%.*]] = sub i8 0, [[X]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i8 [[SEL]], [[ADD]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %cond = icmp slt i8 %x, 15
+  %sel = select i1 %cond, i8 -10, i8 -20
+  %add = add i8 %sel, %x
+  %cmp = icmp eq i8 %add, 0
+  ret i1 %cmp
+}
+
+; Vector — splat constants fold identically to scalars
+define <2 x i1> @icmp_add_select_slt_eq_vec(<2 x i32> %x) {
+; CHECK-LABEL: @icmp_add_select_slt_eq_vec(
+; CHECK-NEXT:    [[COND:%.*]] = icmp slt <2 x i32> [[X:%.*]], splat (i32 15)
+; CHECK-NEXT:    [[SEL:%.*]] = select <2 x i1> [[COND]], <2 x i32> splat (i32 -10), <2 x i32> splat (i32 -20)
+; CHECK-NEXT:    [[ADD:%.*]] = sub <2 x i32> zeroinitializer, [[X]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq <2 x i32> [[SEL]], [[ADD]]
+; CHECK-NEXT:    ret <2 x i1> [[CMP]]
+;
+  %cond = icmp slt <2 x i32> %x, splat (i32 15)
+  %sel = select <2 x i1> %cond, <2 x i32> splat (i32 -10), <2 x i32> splat (i32 -20)
+  %add = add <2 x i32> %sel, %x
+  %cmp = icmp eq <2 x i32> %add, zeroinitializer
+  ret <2 x i1> %cmp
+}
+
+; Edge case — INT_MIN in true-arm; negation wraps but fold still correct
+define i1 @icmp_add_select_slt_eq_intmin_root(i32 %x) {
+; CHECK-LABEL: @icmp_add_select_slt_eq_intmin_root(
+; CHECK-NEXT:    [[COND:%.*]] = icmp slt i32 [[X:%.*]], 1
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i32 -2147483648, i32 -20
+; CHECK-NEXT:    [[ADD:%.*]] = sub i32 0, [[X]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[SEL]], [[ADD]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %cond = icmp slt i32 %x, 1
+  %sel = select i1 %cond, i32 -2147483648, i32 -20
+  %add = add i32 %sel, %x
+  %cmp = icmp eq i32 %add, 0
+  ret i1 %cmp
+}
+
+; Flags — nsw on the add does not inhibit the fold
+; (nuw would make the eq-zero fold to false by a separate rule, not tested here)
+
+define i1 @icmp_add_select_slt_eq_nsw(i32 %x) {
+; CHECK-LABEL: @icmp_add_select_slt_eq_nsw(
+; CHECK-NEXT:    [[COND:%.*]] = icmp slt i32 [[X:%.*]], 15
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i32 -10, i32 -20
+; CHECK-NEXT:    [[ADD:%.*]] = sub i32 0, [[X]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[SEL]], [[ADD]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %cond = icmp slt i32 %x, 15
+  %sel = select i1 %cond, i32 -10, i32 -20
+  %add = add nsw i32 %sel, %x
+  %cmp = icmp eq i32 %add, 0
+  ret i1 %cmp
+}
+
+; Sub canonicalization — sub(x, sel) is canonicalized to add before the fold
+
+define i1 @icmp_sub_select_slt_eq(i32 %x) {
+; CHECK-LABEL: @icmp_sub_select_slt_eq(
+; CHECK-NEXT:    [[COND:%.*]] = icmp slt i32 [[X:%.*]], 1
+; CHECK-NEXT:    [[SEL_NEG:%.*]] = select i1 [[COND]], i32 1, i32 -1
+; CHECK-NEXT:    [[SUB:%.*]] = sub i32 0, [[X]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[SEL_NEG]], [[SUB]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %cond = icmp slt i32 %x, 1
+  %sel = select i1 %cond, i32 -1, i32 1
+  %sub = sub i32 %x, %sel
+  %cmp = icmp eq i32 %sub, 0
+  ret i1 %cmp
+}
+
+; sub(sel, x) is not commutative with sub(x, sel) — no fold
+define i1 @icmp_sub_select_slt_eq_wrong_order_no_fold(i32 %x) {
+; CHECK-LABEL: @icmp_sub_select_slt_eq_wrong_order_no_fold(
+; CHECK-NEXT:    [[COND:%.*]] = icmp slt i32 [[X:%.*]], 1
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i32 -1, i32 1
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[SEL]], [[X]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %cond = icmp slt i32 %x, 1
+  %sel = select i1 %cond, i32 -1, i32 1
+  %sub = sub i32 %sel, %x
+  %cmp = icmp eq i32 %sub, 0
+  ret i1 %cmp
+}
+
+; Negative tests
+
+; outer icmp predicate is not eq or ne
+define i1 @icmp_add_select_neg_outer_not_equality(i32 %x) {
+; CHECK-LABEL: @icmp_add_select_neg_outer_not_equality(
+; CHECK-NEXT:    [[COND:%.*]] = icmp slt i32 [[X:%.*]], 15
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i32 -10, i32 -20
+; CHECK-NEXT:    [[ADD:%.*]] = add i32 [[SEL]], [[X]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i32 [[ADD]], 0
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %cond = icmp slt i32 %x, 15
+  %sel = select i1 %cond, i32 -10, i32 -20
+  %add = add i32 %sel, %x
+  %cmp = icmp slt i32 %add, 0
+  ret i1 %cmp
+}
+
+; rhs of outer icmp is not zero
+define i1 @icmp_add_select_neg_rhs_nonzero(i32 %x) {
+; CHECK-LABEL: @icmp_add_select_neg_rhs_nonzero(
+; CHECK-NEXT:    [[COND:%.*]] = icmp slt i32 [[X:%.*]], 15
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i32 -10, i32 -20
+; CHECK-NEXT:    [[ADD:%.*]] = add i32 [[SEL]], [[X]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[ADD]], 1
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %cond = icmp slt i32 %x, 15
+  %sel = select i1 %cond, i32 -10, i32 -20
+  %add = add i32 %sel, %x
+  %cmp = icmp eq i32 %add, 1
+  ret i1 %cmp
+}
+
+; inner icmp predicate is eq — not a relational comparison, no partition
+define i1 @icmp_add_select_neg_inner_eq(i32 %x) {
+; CHECK-LABEL: @icmp_add_select_neg_inner_eq(
+; CHECK-NEXT:    [[COND:%.*]] = icmp eq i32 [[X:%.*]], 15
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i32 -10, i32 -20
+; CHECK-NEXT:    [[ADD:%.*]] = sub i32 0, [[X]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[SEL]], [[ADD]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %cond = icmp eq i32 %x, 15
+  %sel = select i1 %cond, i32 -10, i32 -20
+  %add = add i32 %sel, %x
+  %cmp = icmp eq i32 %add, 0
+  ret i1 %cmp
+}
+
+; add and inner icmp use different SSA values — m_Deferred fails
+define i1 @icmp_add_select_neg_different_vars(i32 %x, i32 %y) {
+; CHECK-LABEL: @icmp_add_select_neg_different_vars(
+; CHECK-NEXT:    [[COND:%.*]] = icmp slt i32 [[X:%.*]], 15
+; CHECK-NEXT:    [[SEL_NEG:%.*]] = select i1 [[COND]], i32 10, i32 20
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[Y:%.*]], [[SEL_NEG]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %cond = icmp slt i32 %x, 15
+  %sel = select i1 %cond, i32 -10, i32 -20
+  %add = add i32 %y, %sel
+  %cmp = icmp eq i32 %add, 0
+  ret i1 %cmp
+}
+
+; CondC equals TrueRoot — strict comparison fails to partition
+define i1 @icmp_add_select_neg_slt_cond_eq_trueroot(i32 %x) {
+; CHECK-LABEL: @icmp_add_select_neg_slt_cond_eq_trueroot(
+; CHECK-NEXT:    [[COND:%.*]] = icmp slt i32 [[X:%.*]], 10
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i32 -10, i32 -20
+; CHECK-NEXT:    [[ADD:%.*]] = sub i32 0, [[X]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[SEL]], [[ADD]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %cond = icmp slt i32 %x, 10
+  %sel = select i1 %cond, i32 -10, i32 -20
+  %add = add i32 %sel, %x
+  %cmp = icmp eq i32 %add, 0
+  ret i1 %cmp
+}
+
+; CondC is above both roots — both roots satisfy the predicate, partition fails
+define i1 @icmp_add_select_neg_slt_cond_above_roots(i32 %x) {
+; CHECK-LABEL: @icmp_add_select_neg_slt_cond_above_roots(
+; CHECK-NEXT:    [[COND:%.*]] = icmp slt i32 [[X:%.*]], 21
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i32 -10, i32 -20
+; CHECK-NEXT:    [[ADD:%.*]] = sub i32 0, [[X]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[SEL]], [[ADD]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %cond = icmp slt i32 %x, 21
+  %sel = select i1 %cond, i32 -10, i32 -20
+  %add = add i32 %sel, %x
+  %cmp = icmp eq i32 %add, 0
+  ret i1 %cmp
+}
+
+; FalseRoot is INT_MIN — its negation wraps, partition check fails
+define i1 @icmp_add_select_neg_slt_intmin_false_root(i32 %x) {
+; CHECK-LABEL: @icmp_add_select_neg_slt_intmin_false_root(
+; CHECK-NEXT:    [[COND:%.*]] = icmp slt i32 [[X:%.*]], 15
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i32 -10, i32 -2147483648
+; CHECK-NEXT:    [[ADD:%.*]] = sub i32 0, [[X]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[SEL]], [[ADD]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %cond = icmp slt i32 %x, 15
+  %sel = select i1 %cond, i32 -10, i32 -2147483648
+  %add = add i32 %sel, %x
+  %cmp = icmp eq i32 %add, 0
+  ret i1 %cmp
+}
+
+; Multi-use tests
+
+; add result has an extra use
+define i1 @icmp_add_select_neg_add_multiuse(i32 %x, ptr %p) {
+; CHECK-LABEL: @icmp_add_select_neg_add_multiuse(
+; CHECK-NEXT:    [[COND:%.*]] = icmp slt i32 [[X:%.*]], 15
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i32 -10, i32 -20
+; CHECK-NEXT:    [[ADD:%.*]] = add i32 [[SEL]], [[X]]
+; CHECK-NEXT:    store i32 [[ADD]], ptr [[P:%.*]], align 4
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[ADD]], 0
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %cond = icmp slt i32 %x, 15
+  %sel = select i1 %cond, i32 -10, i32 -20
+  %add = add i32 %sel, %x
+  store i32 %add, ptr %p
+  %cmp = icmp eq i32 %add, 0
+  ret i1 %cmp
+}
+
+; select result has an extra use
+define i32 @icmp_add_select_neg_select_multiuse(i32 %x) {
+; CHECK-LABEL: @icmp_add_select_neg_select_multiuse(
+; CHECK-NEXT:    [[COND:%.*]] = icmp slt i32 [[X:%.*]], 15
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i32 -10, i32 -20
+; CHECK-NEXT:    [[ADD:%.*]] = sub i32 0, [[X]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[SEL]], [[ADD]]
+; CHECK-NEXT:    [[RET:%.*]] = select i1 [[CMP]], i32 [[SEL]], i32 0
+; CHECK-NEXT:    ret i32 [[RET]]
+;
+  %cond = icmp slt i32 %x, 15
+  %sel = select i1 %cond, i32 -10, i32 -20
+  %add = add i32 %sel, %x
+  %cmp = icmp eq i32 %add, 0
+  %ret = select i1 %cmp, i32 %sel, i32 0
+  ret i32 %ret
+}

>From a57aac3406bdb845b0a6e61b2a5b0cb7540f6822 Mon Sep 17 00:00:00 2001
From: Shiyas-N <muhammedshiyasn811 at gmail.com>
Date: Mon, 4 May 2026 18:23:08 +0000
Subject: [PATCH 2/2] [InstCombine] Fold icmp eq/ne of add(select, X) with zero

Match patterns of the form:
  icmp eq/ne (add (select (icmp X, C), TrueC, FalseC), X), 0
and the commuted variant.

When the roots -TrueC and -FalseC are consistent with the select
condition, rewrite as:
  eq : (X == -TrueC) || (X == -FalseC)
  ne : (X != -TrueC) && (X != -FalseC)

This eliminates the add and select, replacing them with direct
comparisons on X.

The transform is applied only when:
  InnerPred(-TrueC,  C) == true
  InnerPred(-FalseC, C) == false
ensuring each root corresponds to the select arm that would
produce it.

Handles signed and unsigned predicates (slt/sgt/ult/ugt).
---
 .../InstCombine/InstCombineCompares.cpp       | 82 +++++++++++++++++++
 llvm/test/Transforms/InstCombine/icmp-add.ll  | 76 ++++++++---------
 2 files changed, 115 insertions(+), 43 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index 0b1f683e10408..dfe465f944ca4 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -1198,6 +1198,85 @@ Instruction *InstCombinerImpl::foldSignBitTest(ICmpInst &I) {
                           X, ConstantInt::getNullValue(XTy));
 }
 
+/// Handles:
+///   icmp eq/ne (add select((icmp slt/sgt/ult/ugt X, CondC), TrueC, FalseC),
+///               X), 0
+///   icmp eq/ne (add X,
+///               select((icmp slt/sgt/ult/ugt X, CondC), TrueC,FalseC)), 0
+///
+/// Fold into (when roots are consistent with select arms):
+///   eq : (X == -TrueC) || (X == -FalseC)
+///   ne : (X != -TrueC) && (X != -FalseC)
+///
+/// Caller guarantees: RHS of icmp is zero.
+static Instruction *foldICmpAddSelectZero(ICmpInst &Cmp, InstCombinerImpl &IC) {
+  CmpPredicate Pred = Cmp.getPredicate();
+  if (!ICmpInst::isEquality(Pred))
+    return nullptr;
+
+  Value *X;
+  const APInt *CondC, *TrueC, *FalseC;
+  CmpPredicate InnerPred;
+
+  // Match: add (select (icmp X, CondC), TrueC, FalseC), X — or commuted form.
+  // The outer OneUse guards the add (we're replacing it).
+  // The inner OneUse on the select is a profitability guard: this fold adds
+  // 3 instructions and removes 2, so it isn't profitable if the select is
+  // reused elsewhere.
+  if (!match(Cmp.getOperand(0),
+             m_OneUse(m_c_Add(m_OneUse(m_Select(
+                                  m_ICmp(InnerPred, m_Value(X), m_APInt(CondC)),
+                                  m_APInt(TrueC), m_APInt(FalseC))),
+                              m_Deferred(X)))))
+    return nullptr;
+
+  APInt TrueRoot = -(*TrueC);
+  APInt FalseRoot = -(*FalseC);
+
+  // The fold is valid only if each root matches the select arm that would
+  // produce it:
+  //   InnerPred(TrueRoot,  CondC) == true
+  //   InnerPred(FalseRoot, CondC) == false
+  //
+  // (sle/sge/ule/uge are canonicalized to slt/sgt/ult/ugt before reaching
+  // here.)
+  switch (InnerPred) {
+  case ICmpInst::ICMP_SLT:
+    if (!TrueRoot.slt(*CondC) || FalseRoot.slt(*CondC))
+      return nullptr;
+    break;
+  case ICmpInst::ICMP_SGT:
+    if (!TrueRoot.sgt(*CondC) || FalseRoot.sgt(*CondC))
+      return nullptr;
+    break;
+  case ICmpInst::ICMP_ULT:
+    if (!TrueRoot.ult(*CondC) || FalseRoot.ult(*CondC))
+      return nullptr;
+    break;
+  case ICmpInst::ICMP_UGT:
+    if (!TrueRoot.ugt(*CondC) || FalseRoot.ugt(*CondC))
+      return nullptr;
+    break;
+  default:
+    return nullptr;
+  }
+
+  InstCombiner::BuilderTy &Builder = IC.Builder;
+  Type *Ty = X->getType();
+  Constant *TrueConst = ConstantInt::get(Ty, TrueRoot);
+  Constant *FalseConst = ConstantInt::get(Ty, FalseRoot);
+
+  // eq -> (X == TrueRoot) || (X == FalseRoot)
+  // ne -> (X != TrueRoot) && (X != FalseRoot)
+  bool IsEq = Pred == ICmpInst::ICMP_EQ;
+  Value *CmpA = Builder.CreateICmp(IsEq ? CmpInst::ICMP_EQ : CmpInst::ICMP_NE,
+                                   X, TrueConst);
+  Value *CmpB = Builder.CreateICmp(IsEq ? CmpInst::ICMP_EQ : CmpInst::ICMP_NE,
+                                   X, FalseConst);
+  return IsEq ? BinaryOperator::CreateOr(CmpA, CmpB)
+              : BinaryOperator::CreateAnd(CmpA, CmpB);
+}
+
 // Handle  icmp pred X, 0
 Instruction *InstCombinerImpl::foldICmpWithZero(ICmpInst &Cmp) {
   CmpInst::Predicate Pred = Cmp.getPredicate();
@@ -1215,6 +1294,9 @@ Instruction *InstCombinerImpl::foldICmpWithZero(ICmpInst &Cmp) {
     }
   }
 
+  if (Instruction *New = foldICmpAddSelectZero(Cmp, *this))
+    return New;
+
   if (Instruction *New = foldIRemByPowerOfTwoToBitTest(Cmp))
     return New;
 
diff --git a/llvm/test/Transforms/InstCombine/icmp-add.ll b/llvm/test/Transforms/InstCombine/icmp-add.ll
index 1dd172af15898..c229a1c5856ac 100644
--- a/llvm/test/Transforms/InstCombine/icmp-add.ll
+++ b/llvm/test/Transforms/InstCombine/icmp-add.ll
@@ -3607,10 +3607,9 @@ entry:
 
 define i1 @icmp_add_select_slt_eq(i32 %x) {
 ; CHECK-LABEL: @icmp_add_select_slt_eq(
-; CHECK-NEXT:    [[COND:%.*]] = icmp slt i32 [[X:%.*]], 15
-; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i32 -10, i32 -20
-; CHECK-NEXT:    [[ADD:%.*]] = sub i32 0, [[X]]
-; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[SEL]], [[ADD]]
+; CHECK-NEXT:    [[TMP1:%.*]] = icmp eq i32 [[X:%.*]], 10
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp eq i32 [[X]], 20
+; CHECK-NEXT:    [[CMP:%.*]] = or i1 [[TMP1]], [[TMP2]]
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %cond = icmp slt i32 %x, 15
@@ -3623,10 +3622,9 @@ define i1 @icmp_add_select_slt_eq(i32 %x) {
 ; ne outer predicate produces and instead of or
 define i1 @icmp_add_select_slt_ne(i32 %x) {
 ; CHECK-LABEL: @icmp_add_select_slt_ne(
-; CHECK-NEXT:    [[COND:%.*]] = icmp slt i32 [[X:%.*]], 15
-; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i32 -10, i32 -20
-; CHECK-NEXT:    [[ADD:%.*]] = sub i32 0, [[X]]
-; CHECK-NEXT:    [[CMP:%.*]] = icmp ne i32 [[SEL]], [[ADD]]
+; CHECK-NEXT:    [[TMP1:%.*]] = icmp ne i32 [[X:%.*]], 10
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp ne i32 [[X]], 20
+; CHECK-NEXT:    [[CMP:%.*]] = and i1 [[TMP1]], [[TMP2]]
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %cond = icmp slt i32 %x, 15
@@ -3639,10 +3637,9 @@ define i1 @icmp_add_select_slt_ne(i32 %x) {
 ; sgt inner predicate
 define i1 @icmp_add_select_sgt_eq(i32 %x) {
 ; CHECK-LABEL: @icmp_add_select_sgt_eq(
-; CHECK-NEXT:    [[COND:%.*]] = icmp sgt i32 [[X:%.*]], 15
-; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i32 -20, i32 -10
-; CHECK-NEXT:    [[ADD:%.*]] = sub i32 0, [[X]]
-; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[SEL]], [[ADD]]
+; CHECK-NEXT:    [[TMP1:%.*]] = icmp eq i32 [[X:%.*]], 20
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp eq i32 [[X]], 10
+; CHECK-NEXT:    [[CMP:%.*]] = or i1 [[TMP1]], [[TMP2]]
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %cond = icmp sgt i32 %x, 15
@@ -3655,10 +3652,9 @@ define i1 @icmp_add_select_sgt_eq(i32 %x) {
 ; ult inner predicate
 define i1 @icmp_add_select_ult_eq(i32 %x) {
 ; CHECK-LABEL: @icmp_add_select_ult_eq(
-; CHECK-NEXT:    [[COND:%.*]] = icmp ult i32 [[X:%.*]], 15
-; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i32 -10, i32 -20
-; CHECK-NEXT:    [[ADD:%.*]] = sub i32 0, [[X]]
-; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[SEL]], [[ADD]]
+; CHECK-NEXT:    [[TMP1:%.*]] = icmp eq i32 [[X:%.*]], 10
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp eq i32 [[X]], 20
+; CHECK-NEXT:    [[CMP:%.*]] = or i1 [[TMP1]], [[TMP2]]
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %cond = icmp ult i32 %x, 15
@@ -3671,10 +3667,9 @@ define i1 @icmp_add_select_ult_eq(i32 %x) {
 ; ugt inner predicate
 define i1 @icmp_add_select_ugt_eq(i32 %x) {
 ; CHECK-LABEL: @icmp_add_select_ugt_eq(
-; CHECK-NEXT:    [[COND:%.*]] = icmp ugt i32 [[X:%.*]], 15
-; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i32 -20, i32 -10
-; CHECK-NEXT:    [[ADD:%.*]] = sub i32 0, [[X]]
-; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[SEL]], [[ADD]]
+; CHECK-NEXT:    [[TMP1:%.*]] = icmp eq i32 [[X:%.*]], 20
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp eq i32 [[X]], 10
+; CHECK-NEXT:    [[CMP:%.*]] = or i1 [[TMP1]], [[TMP2]]
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %cond = icmp ugt i32 %x, 15
@@ -3688,9 +3683,9 @@ define i1 @icmp_add_select_ugt_eq(i32 %x) {
 
 define i1 @icmp_add_select_slt_eq_commute(i32 %x) {
 ; CHECK-LABEL: @icmp_add_select_slt_eq_commute(
-; CHECK-NEXT:    [[COND:%.*]] = icmp slt i32 [[X:%.*]], 15
-; CHECK-NEXT:    [[SEL_NEG:%.*]] = select i1 [[COND]], i32 10, i32 20
-; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[X]], [[SEL_NEG]]
+; CHECK-NEXT:    [[TMP1:%.*]] = icmp eq i32 [[X:%.*]], 10
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp eq i32 [[X]], 20
+; CHECK-NEXT:    [[CMP:%.*]] = or i1 [[TMP1]], [[TMP2]]
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %cond = icmp slt i32 %x, 15
@@ -3704,10 +3699,9 @@ define i1 @icmp_add_select_slt_eq_commute(i32 %x) {
 
 define i1 @icmp_add_select_slt_eq_i8(i8 %x) {
 ; CHECK-LABEL: @icmp_add_select_slt_eq_i8(
-; CHECK-NEXT:    [[COND:%.*]] = icmp slt i8 [[X:%.*]], 15
-; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i8 -10, i8 -20
-; CHECK-NEXT:    [[ADD:%.*]] = sub i8 0, [[X]]
-; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i8 [[SEL]], [[ADD]]
+; CHECK-NEXT:    [[TMP1:%.*]] = icmp eq i8 [[X:%.*]], 10
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp eq i8 [[X]], 20
+; CHECK-NEXT:    [[CMP:%.*]] = or i1 [[TMP1]], [[TMP2]]
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %cond = icmp slt i8 %x, 15
@@ -3720,10 +3714,9 @@ define i1 @icmp_add_select_slt_eq_i8(i8 %x) {
 ; Vector — splat constants fold identically to scalars
 define <2 x i1> @icmp_add_select_slt_eq_vec(<2 x i32> %x) {
 ; CHECK-LABEL: @icmp_add_select_slt_eq_vec(
-; CHECK-NEXT:    [[COND:%.*]] = icmp slt <2 x i32> [[X:%.*]], splat (i32 15)
-; CHECK-NEXT:    [[SEL:%.*]] = select <2 x i1> [[COND]], <2 x i32> splat (i32 -10), <2 x i32> splat (i32 -20)
-; CHECK-NEXT:    [[ADD:%.*]] = sub <2 x i32> zeroinitializer, [[X]]
-; CHECK-NEXT:    [[CMP:%.*]] = icmp eq <2 x i32> [[SEL]], [[ADD]]
+; CHECK-NEXT:    [[TMP1:%.*]] = icmp eq <2 x i32> [[X:%.*]], splat (i32 10)
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp eq <2 x i32> [[X]], splat (i32 20)
+; CHECK-NEXT:    [[CMP:%.*]] = or <2 x i1> [[TMP1]], [[TMP2]]
 ; CHECK-NEXT:    ret <2 x i1> [[CMP]]
 ;
   %cond = icmp slt <2 x i32> %x, splat (i32 15)
@@ -3736,10 +3729,9 @@ define <2 x i1> @icmp_add_select_slt_eq_vec(<2 x i32> %x) {
 ; Edge case — INT_MIN in true-arm; negation wraps but fold still correct
 define i1 @icmp_add_select_slt_eq_intmin_root(i32 %x) {
 ; CHECK-LABEL: @icmp_add_select_slt_eq_intmin_root(
-; CHECK-NEXT:    [[COND:%.*]] = icmp slt i32 [[X:%.*]], 1
-; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i32 -2147483648, i32 -20
-; CHECK-NEXT:    [[ADD:%.*]] = sub i32 0, [[X]]
-; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[SEL]], [[ADD]]
+; CHECK-NEXT:    [[TMP1:%.*]] = icmp eq i32 [[X:%.*]], -2147483648
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp eq i32 [[X]], 20
+; CHECK-NEXT:    [[CMP:%.*]] = or i1 [[TMP1]], [[TMP2]]
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %cond = icmp slt i32 %x, 1
@@ -3754,10 +3746,9 @@ define i1 @icmp_add_select_slt_eq_intmin_root(i32 %x) {
 
 define i1 @icmp_add_select_slt_eq_nsw(i32 %x) {
 ; CHECK-LABEL: @icmp_add_select_slt_eq_nsw(
-; CHECK-NEXT:    [[COND:%.*]] = icmp slt i32 [[X:%.*]], 15
-; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[COND]], i32 -10, i32 -20
-; CHECK-NEXT:    [[ADD:%.*]] = sub i32 0, [[X]]
-; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[SEL]], [[ADD]]
+; CHECK-NEXT:    [[TMP1:%.*]] = icmp eq i32 [[X:%.*]], 10
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp eq i32 [[X]], 20
+; CHECK-NEXT:    [[CMP:%.*]] = or i1 [[TMP1]], [[TMP2]]
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %cond = icmp slt i32 %x, 15
@@ -3771,10 +3762,9 @@ define i1 @icmp_add_select_slt_eq_nsw(i32 %x) {
 
 define i1 @icmp_sub_select_slt_eq(i32 %x) {
 ; CHECK-LABEL: @icmp_sub_select_slt_eq(
-; CHECK-NEXT:    [[COND:%.*]] = icmp slt i32 [[X:%.*]], 1
-; CHECK-NEXT:    [[SEL_NEG:%.*]] = select i1 [[COND]], i32 1, i32 -1
-; CHECK-NEXT:    [[SUB:%.*]] = sub i32 0, [[X]]
-; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[SEL_NEG]], [[SUB]]
+; CHECK-NEXT:    [[TMP1:%.*]] = icmp eq i32 [[X:%.*]], -1
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp eq i32 [[X]], 1
+; CHECK-NEXT:    [[CMP:%.*]] = or i1 [[TMP1]], [[TMP2]]
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %cond = icmp slt i32 %x, 1



More information about the llvm-commits mailing list