Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[InstSimplify] Fold X < Y ? (X + zext(X < Y)) <= Y : false to X < Y #118579

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

veera-sivarajan
Copy link
Contributor

This PR simplifies:
X > Y ? (X + zext(X > Y)) >= Y : false to X > Y
X < Y ? (X + zext(X < Y)) <= Y : false to X < Y

Proof: https://alive2.llvm.org/ce/z/JFcDzM

This helps improving codegen for Rust's loop over inclusive range with variable upper bound in rust-lang/rust#102462.

@llvmbot
Copy link
Member

llvmbot commented Dec 4, 2024

@llvm/pr-subscribers-llvm-analysis

Author: Veera (veera-sivarajan)

Changes

This PR simplifies:
X &gt; Y ? (X + zext(X &gt; Y)) &gt;= Y : false to X &gt; Y
X &lt; Y ? (X + zext(X &lt; Y)) &lt;= Y : false to X &lt; Y

Proof: https://alive2.llvm.org/ce/z/JFcDzM

This helps improving codegen for Rust's loop over inclusive range with variable upper bound in rust-lang/rust#102462.


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

2 Files Affected:

  • (modified) llvm/lib/Analysis/InstructionSimplify.cpp (+42)
  • (added) llvm/test/Transforms/InstSimplify/select-icmp-relational.ll (+423)
diff --git a/llvm/lib/Analysis/InstructionSimplify.cpp b/llvm/lib/Analysis/InstructionSimplify.cpp
index 01b0a089aab718..8e4731651ea59f 100644
--- a/llvm/lib/Analysis/InstructionSimplify.cpp
+++ b/llvm/lib/Analysis/InstructionSimplify.cpp
@@ -4639,6 +4639,45 @@ static Value *simplifySelectWithEquivalence(Value *CmpLHS, Value *CmpRHS,
   return nullptr;
 }
 
+/// Simplifies:
+/// `X > Y ? (X + zext(X > Y)) >= Y : false` to `X > Y`
+/// `X < Y ? (X + zext(X < Y)) <= Y : false` to `X < Y`
+static Value *simplifySelectWithStrictICmp(Value *CondVal, Value *TVal,
+                                           Value *FVal,
+                                           const SimplifyQuery &Q) {
+  if (!match(FVal, m_Zero()))
+    return nullptr;
+
+  ICmpInst::Predicate Pred;
+  Value *CmpLHS, *CmpRHS;
+
+  if (!match(CondVal, m_ICmp(Pred, m_Value(CmpLHS), m_Value(CmpRHS))))
+    return nullptr;
+
+  if (!CmpInst::isStrictPredicate(Pred))
+    return nullptr;
+
+  ICmpInst::Predicate NonStrictPred = ICmpInst::getNonStrictPredicate(Pred);
+  BinaryOperator *BinOp;
+
+  if (!match(TVal,
+             m_SpecificICmp(NonStrictPred, m_BinOp(BinOp), m_Specific(CmpRHS))))
+    return nullptr;
+
+  // This fold works for GT only when it does not wrap.
+  if (Pred == ICmpInst::ICMP_UGT && !Q.IIQ.hasNoUnsignedWrap(BinOp))
+    return nullptr;
+
+  if (Pred == ICmpInst::ICMP_SGT && !Q.IIQ.hasNoSignedWrap(BinOp))
+    return nullptr;
+
+  if (!match(BinOp, m_c_BinOp(Instruction::Add, m_Specific(CmpLHS),
+                              m_ZExt(m_Specific(CondVal)))))
+    return nullptr;
+
+  return CondVal;
+}
+
 /// Try to simplify a select instruction when its condition operand is an
 /// integer comparison.
 static Value *simplifySelectWithICmpCond(Value *CondVal, Value *TrueVal,
@@ -4761,6 +4800,9 @@ static Value *simplifySelectWithICmpCond(Value *CondVal, Value *TrueVal,
     }
   }
 
+  if (Value *V = simplifySelectWithStrictICmp(CondVal, TrueVal, FalseVal, Q))
+    return V;
+
   return nullptr;
 }
 
diff --git a/llvm/test/Transforms/InstSimplify/select-icmp-relational.ll b/llvm/test/Transforms/InstSimplify/select-icmp-relational.ll
new file mode 100644
index 00000000000000..c2f120469c6b59
--- /dev/null
+++ b/llvm/test/Transforms/InstSimplify/select-icmp-relational.ll
@@ -0,0 +1,423 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+define i1 @ult_ule(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @ult_ule(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ult i8 [[X]], [[Y]]
+; CHECK-NEXT:    ret i1 [[C1]]
+;
+  %c1 = icmp ult i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add nuw i8 %x, %z
+  %c2 = icmp ule i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @ult_ule_no_flags(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @ult_ule_no_flags(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ult i8 [[X]], [[Y]]
+; CHECK-NEXT:    ret i1 [[C1]]
+;
+  %c1 = icmp ult i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add i8 %x, %z
+  %c2 = icmp ule i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @slt_sle(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @slt_sle(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp slt i8 [[X]], [[Y]]
+; CHECK-NEXT:    ret i1 [[C1]]
+;
+  %c1 = icmp slt i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add nsw i8 %x, %z
+  %c2 = icmp sle i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @slt_sle_no_flags(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @slt_sle_no_flags(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp slt i8 [[X]], [[Y]]
+; CHECK-NEXT:    ret i1 [[C1]]
+;
+  %c1 = icmp slt i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add i8 %x, %z
+  %c2 = icmp sle i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @ugt_uge(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @ugt_uge(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ugt i8 [[X]], [[Y]]
+; CHECK-NEXT:    ret i1 [[C1]]
+;
+  %c1 = icmp ugt i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add nuw i8 %x, %z
+  %c2 = icmp uge i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @sgt_sge(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @sgt_sge(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp sgt i8 [[X]], [[Y]]
+; CHECK-NEXT:    ret i1 [[C1]]
+;
+  %c1 = icmp sgt i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add nsw i8 %x, %z
+  %c2 = icmp sge i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define <2 x i1> @ult_ule_splat_vector(<2 x i8> %x, <2 x i8> %y) {
+; CHECK-LABEL: define <2 x i1> @ult_ule_splat_vector(
+; CHECK-SAME: <2 x i8> [[X:%.*]], <2 x i8> [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ult <2 x i8> [[X]], [[Y]]
+; CHECK-NEXT:    ret <2 x i1> [[C1]]
+;
+  %c1 = icmp ult <2 x i8> %x, %y
+  %z = zext <2 x i1> %c1 to <2 x i8>
+  %add = add nuw <2 x i8> %x, %z
+  %c2 = icmp ule <2 x i8> %add, %y
+  %and = select <2 x i1> %c1, <2 x i1> %c2, <2 x i1> <i1 false, i1 false>
+  ret <2 x i1> %and
+}
+
+define <2 x i1> @ult_ule_vector_with_poison(<2 x i8> %x, <2 x i8> %y) {
+; CHECK-LABEL: define <2 x i1> @ult_ule_vector_with_poison(
+; CHECK-SAME: <2 x i8> [[X:%.*]], <2 x i8> [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ult <2 x i8> [[X]], [[Y]]
+; CHECK-NEXT:    ret <2 x i1> [[C1]]
+;
+  %c1 = icmp ult <2 x i8> %x, %y
+  %z = zext <2 x i1> %c1 to <2 x i8>
+  %add = add nuw <2 x i8> %x, %z
+  %c2 = icmp ule <2 x i8> %add, %y
+  %and = select <2 x i1> %c1, <2 x i1> %c2, <2 x i1> <i1 false, i1 poison>
+  ret <2 x i1> %and
+}
+
+define i1 @ugt_uge_with_poison(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @ugt_uge_with_poison(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    ret i1 false
+;
+  %c1 = icmp ugt i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add nuw i8 %x, poison
+  %c2 = icmp uge i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+declare void @use(i8)
+declare void @use_bit(i1)
+
+define i1 @ult_ule_multi_use(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @ult_ule_multi_use(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ult i8 [[X]], [[Y]]
+; CHECK-NEXT:    call void @use_bit(i1 [[C1]])
+; CHECK-NEXT:    [[Z:%.*]] = zext i1 [[C1]] to i8
+; CHECK-NEXT:    [[ADD:%.*]] = add nuw i8 [[X]], [[Z]]
+; CHECK-NEXT:    [[C2:%.*]] = icmp ule i8 [[ADD]], [[Y]]
+; CHECK-NEXT:    call void @use_bit(i1 [[C2]])
+; CHECK-NEXT:    ret i1 [[C1]]
+;
+  %c1 = icmp ult i8 %x, %y
+  call void @use_bit(i1 %c1)
+  %z = zext i1 %c1 to i8
+  %add = add nuw i8 %x, %z
+  %c2 = icmp ule i8 %add, %y
+  call void @use_bit(i1 %c2)
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @ult_ule_multi_use_add(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @ult_ule_multi_use_add(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ult i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[Z:%.*]] = zext i1 [[C1]] to i8
+; CHECK-NEXT:    [[ADD:%.*]] = add nuw i8 [[X]], [[Z]]
+; CHECK-NEXT:    call void @use(i8 [[ADD]])
+; CHECK-NEXT:    ret i1 [[C1]]
+;
+  %c1 = icmp ult i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add nuw i8 %x, %z
+  call void @use(i8 %add)
+  %c2 = icmp ule i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @ult_ule_commuted_binop(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @ult_ule_commuted_binop(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ult i8 [[X]], [[Y]]
+; CHECK-NEXT:    ret i1 [[C1]]
+;
+  %c1 = icmp ult i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add nuw i8 %z, %x
+  %c2 = icmp ule i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @ult_sext_sub_ule(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @ult_sext_sub_ule(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ult i8 [[X]], [[Y]]
+; CHECK-NEXT:    ret i1 [[C1]]
+;
+  %c1 = icmp ult i8 %x, %y
+  %z = sext i1 %c1 to i8
+  %add = sub nuw i8 %x, %z
+  %c2 = icmp ule i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @ugt_uge_const_fold_false(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @ugt_uge_const_fold_false(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ugt i8 [[X]], [[Y]]
+; CHECK-NEXT:    ret i1 [[C1]]
+;
+  %c1 = icmp ugt i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add nuw i8 %x, %z
+  %c2 = icmp uge i8 %add, %y
+  %c3 = icmp ult i8 10, 1
+  %and = select i1 %c1, i1 %c2, i1 %c3
+  ret i1 %and
+}
+
+define void @rust_inlclusive_noop(i8 noundef %n) unnamed_addr {
+; CHECK-LABEL: define void @rust_inlclusive_noop(
+; CHECK-SAME: i8 noundef [[N:%.*]]) unnamed_addr {
+; CHECK-NEXT:  [[START:.*]]:
+; CHECK-NEXT:    br label %[[BB2_I:.*]]
+; CHECK:       [[BB2_I]]:
+; CHECK-NEXT:    [[ITER_SROA_0_07:%.*]] = phi i8 [ 0, %[[START]] ], [ [[SPEC_SELECT5:%.*]], %[[BB2_I]] ]
+; CHECK-NEXT:    [[_0_I3_I:%.*]] = icmp ult i8 [[ITER_SROA_0_07]], [[N]]
+; CHECK-NEXT:    [[_0_I4_I:%.*]] = zext i1 [[_0_I3_I]] to i8
+; CHECK-NEXT:    [[SPEC_SELECT5]] = add nuw i8 [[ITER_SROA_0_07]], [[_0_I4_I]]
+; CHECK-NEXT:    br i1 [[_0_I3_I]], label %[[BB2_I]], label %[[THEEND:.*]]
+; CHECK:       [[THEEND]]:
+; CHECK-NEXT:    ret void
+;
+start:
+  br label %bb2.i
+
+bb2.i:
+  %iter.sroa.0.07 = phi i8 [ 0, %start ], [ %spec.select5, %bb2.i ]
+  %_0.i3.i = icmp ult i8 %iter.sroa.0.07, %n
+  %_0.i4.i = zext i1 %_0.i3.i to i8
+  %spec.select5 = add nuw i8 %iter.sroa.0.07, %_0.i4.i
+  %_0.i.not.i = icmp ule i8 %spec.select5, %n
+  %or.cond.not = select i1 %_0.i3.i, i1 %_0.i.not.i, i1 false
+  br i1 %or.cond.not, label %bb2.i, label %theend
+
+theend:
+  ret void
+}
+
+define i1 @ule_ule_negative(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @ule_ule_negative(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ule i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[Z:%.*]] = zext i1 [[C1]] to i8
+; CHECK-NEXT:    [[ADD:%.*]] = add nuw i8 [[X]], [[Z]]
+; CHECK-NEXT:    [[C2:%.*]] = icmp ule i8 [[ADD]], [[Y]]
+; CHECK-NEXT:    [[AND:%.*]] = select i1 [[C1]], i1 [[C2]], i1 false
+; CHECK-NEXT:    ret i1 [[AND]]
+;
+  %c1 = icmp ule i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add nuw i8 %x, %z
+  %c2 = icmp ule i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @ugt_ugt_negative(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @ugt_ugt_negative(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ugt i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[Z:%.*]] = zext i1 [[C1]] to i8
+; CHECK-NEXT:    [[ADD:%.*]] = add nuw i8 [[X]], [[Z]]
+; CHECK-NEXT:    [[C2:%.*]] = icmp ugt i8 [[ADD]], [[Y]]
+; CHECK-NEXT:    [[AND:%.*]] = select i1 [[C1]], i1 [[C2]], i1 false
+; CHECK-NEXT:    ret i1 [[AND]]
+;
+  %c1 = icmp ugt i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add nuw i8 %x, %z
+  %c2 = icmp ugt i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @ugt_sext_sub_uge_negative(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @ugt_sext_sub_uge_negative(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ugt i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[Z_NEG:%.*]] = zext i1 [[C1]] to i8
+; CHECK-NEXT:    [[ADD:%.*]] = add i8 [[X]], [[Z_NEG]]
+; CHECK-NEXT:    [[C2:%.*]] = icmp uge i8 [[ADD]], [[Y]]
+; CHECK-NEXT:    [[AND:%.*]] = select i1 [[C1]], i1 [[C2]], i1 false
+; CHECK-NEXT:    ret i1 [[AND]]
+;
+  %c1 = icmp ugt i8 %x, %y
+  %z = sext i1 %c1 to i8
+  %add = sub nuw i8 %x, %z
+  %c2 = icmp uge i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @false_value_is_true_negative(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @false_value_is_true_negative(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ugt i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[Z:%.*]] = zext i1 [[C1]] to i8
+; CHECK-NEXT:    [[ADD:%.*]] = add nuw i8 [[X]], [[Z]]
+; CHECK-NEXT:    [[C2:%.*]] = icmp uge i8 [[ADD]], [[Y]]
+; CHECK-NEXT:    [[NOT_C1:%.*]] = xor i1 [[C1]], true
+; CHECK-NEXT:    [[AND:%.*]] = select i1 [[NOT_C1]], i1 true, i1 [[C2]]
+; CHECK-NEXT:    ret i1 [[AND]]
+;
+  %c1 = icmp ugt i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add nuw i8 %x, %z
+  %c2 = icmp uge i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 true
+  ret i1 %and
+}
+
+define i1 @non_specific_operands_negative(i8 %x, i8 %y, i8 %a, i8 %b) {
+; CHECK-LABEL: define i1 @non_specific_operands_negative(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]], i8 [[A:%.*]], i8 [[B:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ult i8 [[A]], [[B]]
+; CHECK-NEXT:    [[Z:%.*]] = zext i1 [[C1]] to i8
+; CHECK-NEXT:    [[ADD:%.*]] = add nuw i8 [[X]], [[Z]]
+; CHECK-NEXT:    [[C2:%.*]] = icmp ule i8 [[ADD]], [[Y]]
+; CHECK-NEXT:    [[AND:%.*]] = select i1 [[C1]], i1 [[C2]], i1 false
+; CHECK-NEXT:    ret i1 [[AND]]
+;
+  %c1 = icmp ult i8 %a, %b
+  %z = zext i1 %c1 to i8
+  %add = add nuw i8 %x, %z
+  %c2 = icmp ule i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @sgt_nuw_sge_negative(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @sgt_nuw_sge_negative(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp sgt i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[Z:%.*]] = zext i1 [[C1]] to i8
+; CHECK-NEXT:    [[ADD:%.*]] = add nuw i8 [[X]], [[Z]]
+; CHECK-NEXT:    [[C2:%.*]] = icmp sge i8 [[ADD]], [[Y]]
+; CHECK-NEXT:    [[AND:%.*]] = select i1 [[C1]], i1 [[C2]], i1 false
+; CHECK-NEXT:    ret i1 [[AND]]
+;
+  %c1 = icmp sgt i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add nuw i8 %x, %z
+  %c2 = icmp sge i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @ugt_nsw_uge_negative(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @ugt_nsw_uge_negative(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ugt i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[Z:%.*]] = zext i1 [[C1]] to i8
+; CHECK-NEXT:    [[ADD:%.*]] = add nsw i8 [[X]], [[Z]]
+; CHECK-NEXT:    [[C2:%.*]] = icmp uge i8 [[ADD]], [[Y]]
+; CHECK-NEXT:    [[AND:%.*]] = select i1 [[C1]], i1 [[C2]], i1 false
+; CHECK-NEXT:    ret i1 [[AND]]
+;
+  %c1 = icmp ugt i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add nsw i8 %x, %z
+  %c2 = icmp uge i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @non_strict_predicate_negative(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @non_strict_predicate_negative(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp eq i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[Z:%.*]] = zext i1 [[C1]] to i8
+; CHECK-NEXT:    [[ADD:%.*]] = add nsw i8 [[X]], [[Z]]
+; CHECK-NEXT:    [[C2:%.*]] = icmp uge i8 [[ADD]], [[Y]]
+; CHECK-NEXT:    [[AND:%.*]] = select i1 [[C1]], i1 [[C2]], i1 false
+; CHECK-NEXT:    ret i1 [[AND]]
+;
+  %c1 = icmp eq i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add nsw i8 %x, %z
+  %c2 = icmp uge i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @ugt_uge_no_flags_negative(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @ugt_uge_no_flags_negative(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ugt i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[Z:%.*]] = zext i1 [[C1]] to i8
+; CHECK-NEXT:    [[ADD:%.*]] = add i8 [[X]], [[Z]]
+; CHECK-NEXT:    [[C2:%.*]] = icmp uge i8 [[ADD]], [[Y]]
+; CHECK-NEXT:    [[AND:%.*]] = select i1 [[C1]], i1 [[C2]], i1 false
+; CHECK-NEXT:    ret i1 [[AND]]
+;
+  %c1 = icmp ugt i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add i8 %x, %z
+  %c2 = icmp uge i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @sgt_sge_no_flags_negative(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @sgt_sge_no_flags_negative(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp sgt i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[Z:%.*]] = zext i1 [[C1]] to i8
+; CHECK-NEXT:    [[ADD:%.*]] = add i8 [[X]], [[Z]]
+; CHECK-NEXT:    [[C2:%.*]] = icmp sge i8 [[ADD]], [[Y]]
+; CHECK-NEXT:    [[AND:%.*]] = select i1 [[C1]], i1 [[C2]], i1 false
+; CHECK-NEXT:    ret i1 [[AND]]
+;
+  %c1 = icmp sgt i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add i8 %x, %z
+  %c2 = icmp sge i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}

@llvmbot
Copy link
Member

llvmbot commented Dec 4, 2024

@llvm/pr-subscribers-llvm-transforms

Author: Veera (veera-sivarajan)

Changes

This PR simplifies:
X &gt; Y ? (X + zext(X &gt; Y)) &gt;= Y : false to X &gt; Y
X &lt; Y ? (X + zext(X &lt; Y)) &lt;= Y : false to X &lt; Y

Proof: https://alive2.llvm.org/ce/z/JFcDzM

This helps improving codegen for Rust's loop over inclusive range with variable upper bound in rust-lang/rust#102462.


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

2 Files Affected:

  • (modified) llvm/lib/Analysis/InstructionSimplify.cpp (+42)
  • (added) llvm/test/Transforms/InstSimplify/select-icmp-relational.ll (+423)
diff --git a/llvm/lib/Analysis/InstructionSimplify.cpp b/llvm/lib/Analysis/InstructionSimplify.cpp
index 01b0a089aab718..8e4731651ea59f 100644
--- a/llvm/lib/Analysis/InstructionSimplify.cpp
+++ b/llvm/lib/Analysis/InstructionSimplify.cpp
@@ -4639,6 +4639,45 @@ static Value *simplifySelectWithEquivalence(Value *CmpLHS, Value *CmpRHS,
   return nullptr;
 }
 
+/// Simplifies:
+/// `X > Y ? (X + zext(X > Y)) >= Y : false` to `X > Y`
+/// `X < Y ? (X + zext(X < Y)) <= Y : false` to `X < Y`
+static Value *simplifySelectWithStrictICmp(Value *CondVal, Value *TVal,
+                                           Value *FVal,
+                                           const SimplifyQuery &Q) {
+  if (!match(FVal, m_Zero()))
+    return nullptr;
+
+  ICmpInst::Predicate Pred;
+  Value *CmpLHS, *CmpRHS;
+
+  if (!match(CondVal, m_ICmp(Pred, m_Value(CmpLHS), m_Value(CmpRHS))))
+    return nullptr;
+
+  if (!CmpInst::isStrictPredicate(Pred))
+    return nullptr;
+
+  ICmpInst::Predicate NonStrictPred = ICmpInst::getNonStrictPredicate(Pred);
+  BinaryOperator *BinOp;
+
+  if (!match(TVal,
+             m_SpecificICmp(NonStrictPred, m_BinOp(BinOp), m_Specific(CmpRHS))))
+    return nullptr;
+
+  // This fold works for GT only when it does not wrap.
+  if (Pred == ICmpInst::ICMP_UGT && !Q.IIQ.hasNoUnsignedWrap(BinOp))
+    return nullptr;
+
+  if (Pred == ICmpInst::ICMP_SGT && !Q.IIQ.hasNoSignedWrap(BinOp))
+    return nullptr;
+
+  if (!match(BinOp, m_c_BinOp(Instruction::Add, m_Specific(CmpLHS),
+                              m_ZExt(m_Specific(CondVal)))))
+    return nullptr;
+
+  return CondVal;
+}
+
 /// Try to simplify a select instruction when its condition operand is an
 /// integer comparison.
 static Value *simplifySelectWithICmpCond(Value *CondVal, Value *TrueVal,
@@ -4761,6 +4800,9 @@ static Value *simplifySelectWithICmpCond(Value *CondVal, Value *TrueVal,
     }
   }
 
+  if (Value *V = simplifySelectWithStrictICmp(CondVal, TrueVal, FalseVal, Q))
+    return V;
+
   return nullptr;
 }
 
diff --git a/llvm/test/Transforms/InstSimplify/select-icmp-relational.ll b/llvm/test/Transforms/InstSimplify/select-icmp-relational.ll
new file mode 100644
index 00000000000000..c2f120469c6b59
--- /dev/null
+++ b/llvm/test/Transforms/InstSimplify/select-icmp-relational.ll
@@ -0,0 +1,423 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+define i1 @ult_ule(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @ult_ule(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ult i8 [[X]], [[Y]]
+; CHECK-NEXT:    ret i1 [[C1]]
+;
+  %c1 = icmp ult i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add nuw i8 %x, %z
+  %c2 = icmp ule i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @ult_ule_no_flags(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @ult_ule_no_flags(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ult i8 [[X]], [[Y]]
+; CHECK-NEXT:    ret i1 [[C1]]
+;
+  %c1 = icmp ult i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add i8 %x, %z
+  %c2 = icmp ule i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @slt_sle(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @slt_sle(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp slt i8 [[X]], [[Y]]
+; CHECK-NEXT:    ret i1 [[C1]]
+;
+  %c1 = icmp slt i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add nsw i8 %x, %z
+  %c2 = icmp sle i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @slt_sle_no_flags(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @slt_sle_no_flags(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp slt i8 [[X]], [[Y]]
+; CHECK-NEXT:    ret i1 [[C1]]
+;
+  %c1 = icmp slt i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add i8 %x, %z
+  %c2 = icmp sle i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @ugt_uge(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @ugt_uge(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ugt i8 [[X]], [[Y]]
+; CHECK-NEXT:    ret i1 [[C1]]
+;
+  %c1 = icmp ugt i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add nuw i8 %x, %z
+  %c2 = icmp uge i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @sgt_sge(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @sgt_sge(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp sgt i8 [[X]], [[Y]]
+; CHECK-NEXT:    ret i1 [[C1]]
+;
+  %c1 = icmp sgt i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add nsw i8 %x, %z
+  %c2 = icmp sge i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define <2 x i1> @ult_ule_splat_vector(<2 x i8> %x, <2 x i8> %y) {
+; CHECK-LABEL: define <2 x i1> @ult_ule_splat_vector(
+; CHECK-SAME: <2 x i8> [[X:%.*]], <2 x i8> [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ult <2 x i8> [[X]], [[Y]]
+; CHECK-NEXT:    ret <2 x i1> [[C1]]
+;
+  %c1 = icmp ult <2 x i8> %x, %y
+  %z = zext <2 x i1> %c1 to <2 x i8>
+  %add = add nuw <2 x i8> %x, %z
+  %c2 = icmp ule <2 x i8> %add, %y
+  %and = select <2 x i1> %c1, <2 x i1> %c2, <2 x i1> <i1 false, i1 false>
+  ret <2 x i1> %and
+}
+
+define <2 x i1> @ult_ule_vector_with_poison(<2 x i8> %x, <2 x i8> %y) {
+; CHECK-LABEL: define <2 x i1> @ult_ule_vector_with_poison(
+; CHECK-SAME: <2 x i8> [[X:%.*]], <2 x i8> [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ult <2 x i8> [[X]], [[Y]]
+; CHECK-NEXT:    ret <2 x i1> [[C1]]
+;
+  %c1 = icmp ult <2 x i8> %x, %y
+  %z = zext <2 x i1> %c1 to <2 x i8>
+  %add = add nuw <2 x i8> %x, %z
+  %c2 = icmp ule <2 x i8> %add, %y
+  %and = select <2 x i1> %c1, <2 x i1> %c2, <2 x i1> <i1 false, i1 poison>
+  ret <2 x i1> %and
+}
+
+define i1 @ugt_uge_with_poison(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @ugt_uge_with_poison(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    ret i1 false
+;
+  %c1 = icmp ugt i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add nuw i8 %x, poison
+  %c2 = icmp uge i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+declare void @use(i8)
+declare void @use_bit(i1)
+
+define i1 @ult_ule_multi_use(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @ult_ule_multi_use(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ult i8 [[X]], [[Y]]
+; CHECK-NEXT:    call void @use_bit(i1 [[C1]])
+; CHECK-NEXT:    [[Z:%.*]] = zext i1 [[C1]] to i8
+; CHECK-NEXT:    [[ADD:%.*]] = add nuw i8 [[X]], [[Z]]
+; CHECK-NEXT:    [[C2:%.*]] = icmp ule i8 [[ADD]], [[Y]]
+; CHECK-NEXT:    call void @use_bit(i1 [[C2]])
+; CHECK-NEXT:    ret i1 [[C1]]
+;
+  %c1 = icmp ult i8 %x, %y
+  call void @use_bit(i1 %c1)
+  %z = zext i1 %c1 to i8
+  %add = add nuw i8 %x, %z
+  %c2 = icmp ule i8 %add, %y
+  call void @use_bit(i1 %c2)
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @ult_ule_multi_use_add(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @ult_ule_multi_use_add(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ult i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[Z:%.*]] = zext i1 [[C1]] to i8
+; CHECK-NEXT:    [[ADD:%.*]] = add nuw i8 [[X]], [[Z]]
+; CHECK-NEXT:    call void @use(i8 [[ADD]])
+; CHECK-NEXT:    ret i1 [[C1]]
+;
+  %c1 = icmp ult i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add nuw i8 %x, %z
+  call void @use(i8 %add)
+  %c2 = icmp ule i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @ult_ule_commuted_binop(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @ult_ule_commuted_binop(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ult i8 [[X]], [[Y]]
+; CHECK-NEXT:    ret i1 [[C1]]
+;
+  %c1 = icmp ult i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add nuw i8 %z, %x
+  %c2 = icmp ule i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @ult_sext_sub_ule(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @ult_sext_sub_ule(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ult i8 [[X]], [[Y]]
+; CHECK-NEXT:    ret i1 [[C1]]
+;
+  %c1 = icmp ult i8 %x, %y
+  %z = sext i1 %c1 to i8
+  %add = sub nuw i8 %x, %z
+  %c2 = icmp ule i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @ugt_uge_const_fold_false(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @ugt_uge_const_fold_false(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ugt i8 [[X]], [[Y]]
+; CHECK-NEXT:    ret i1 [[C1]]
+;
+  %c1 = icmp ugt i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add nuw i8 %x, %z
+  %c2 = icmp uge i8 %add, %y
+  %c3 = icmp ult i8 10, 1
+  %and = select i1 %c1, i1 %c2, i1 %c3
+  ret i1 %and
+}
+
+define void @rust_inlclusive_noop(i8 noundef %n) unnamed_addr {
+; CHECK-LABEL: define void @rust_inlclusive_noop(
+; CHECK-SAME: i8 noundef [[N:%.*]]) unnamed_addr {
+; CHECK-NEXT:  [[START:.*]]:
+; CHECK-NEXT:    br label %[[BB2_I:.*]]
+; CHECK:       [[BB2_I]]:
+; CHECK-NEXT:    [[ITER_SROA_0_07:%.*]] = phi i8 [ 0, %[[START]] ], [ [[SPEC_SELECT5:%.*]], %[[BB2_I]] ]
+; CHECK-NEXT:    [[_0_I3_I:%.*]] = icmp ult i8 [[ITER_SROA_0_07]], [[N]]
+; CHECK-NEXT:    [[_0_I4_I:%.*]] = zext i1 [[_0_I3_I]] to i8
+; CHECK-NEXT:    [[SPEC_SELECT5]] = add nuw i8 [[ITER_SROA_0_07]], [[_0_I4_I]]
+; CHECK-NEXT:    br i1 [[_0_I3_I]], label %[[BB2_I]], label %[[THEEND:.*]]
+; CHECK:       [[THEEND]]:
+; CHECK-NEXT:    ret void
+;
+start:
+  br label %bb2.i
+
+bb2.i:
+  %iter.sroa.0.07 = phi i8 [ 0, %start ], [ %spec.select5, %bb2.i ]
+  %_0.i3.i = icmp ult i8 %iter.sroa.0.07, %n
+  %_0.i4.i = zext i1 %_0.i3.i to i8
+  %spec.select5 = add nuw i8 %iter.sroa.0.07, %_0.i4.i
+  %_0.i.not.i = icmp ule i8 %spec.select5, %n
+  %or.cond.not = select i1 %_0.i3.i, i1 %_0.i.not.i, i1 false
+  br i1 %or.cond.not, label %bb2.i, label %theend
+
+theend:
+  ret void
+}
+
+define i1 @ule_ule_negative(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @ule_ule_negative(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ule i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[Z:%.*]] = zext i1 [[C1]] to i8
+; CHECK-NEXT:    [[ADD:%.*]] = add nuw i8 [[X]], [[Z]]
+; CHECK-NEXT:    [[C2:%.*]] = icmp ule i8 [[ADD]], [[Y]]
+; CHECK-NEXT:    [[AND:%.*]] = select i1 [[C1]], i1 [[C2]], i1 false
+; CHECK-NEXT:    ret i1 [[AND]]
+;
+  %c1 = icmp ule i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add nuw i8 %x, %z
+  %c2 = icmp ule i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @ugt_ugt_negative(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @ugt_ugt_negative(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ugt i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[Z:%.*]] = zext i1 [[C1]] to i8
+; CHECK-NEXT:    [[ADD:%.*]] = add nuw i8 [[X]], [[Z]]
+; CHECK-NEXT:    [[C2:%.*]] = icmp ugt i8 [[ADD]], [[Y]]
+; CHECK-NEXT:    [[AND:%.*]] = select i1 [[C1]], i1 [[C2]], i1 false
+; CHECK-NEXT:    ret i1 [[AND]]
+;
+  %c1 = icmp ugt i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add nuw i8 %x, %z
+  %c2 = icmp ugt i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @ugt_sext_sub_uge_negative(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @ugt_sext_sub_uge_negative(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ugt i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[Z_NEG:%.*]] = zext i1 [[C1]] to i8
+; CHECK-NEXT:    [[ADD:%.*]] = add i8 [[X]], [[Z_NEG]]
+; CHECK-NEXT:    [[C2:%.*]] = icmp uge i8 [[ADD]], [[Y]]
+; CHECK-NEXT:    [[AND:%.*]] = select i1 [[C1]], i1 [[C2]], i1 false
+; CHECK-NEXT:    ret i1 [[AND]]
+;
+  %c1 = icmp ugt i8 %x, %y
+  %z = sext i1 %c1 to i8
+  %add = sub nuw i8 %x, %z
+  %c2 = icmp uge i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @false_value_is_true_negative(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @false_value_is_true_negative(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ugt i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[Z:%.*]] = zext i1 [[C1]] to i8
+; CHECK-NEXT:    [[ADD:%.*]] = add nuw i8 [[X]], [[Z]]
+; CHECK-NEXT:    [[C2:%.*]] = icmp uge i8 [[ADD]], [[Y]]
+; CHECK-NEXT:    [[NOT_C1:%.*]] = xor i1 [[C1]], true
+; CHECK-NEXT:    [[AND:%.*]] = select i1 [[NOT_C1]], i1 true, i1 [[C2]]
+; CHECK-NEXT:    ret i1 [[AND]]
+;
+  %c1 = icmp ugt i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add nuw i8 %x, %z
+  %c2 = icmp uge i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 true
+  ret i1 %and
+}
+
+define i1 @non_specific_operands_negative(i8 %x, i8 %y, i8 %a, i8 %b) {
+; CHECK-LABEL: define i1 @non_specific_operands_negative(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]], i8 [[A:%.*]], i8 [[B:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ult i8 [[A]], [[B]]
+; CHECK-NEXT:    [[Z:%.*]] = zext i1 [[C1]] to i8
+; CHECK-NEXT:    [[ADD:%.*]] = add nuw i8 [[X]], [[Z]]
+; CHECK-NEXT:    [[C2:%.*]] = icmp ule i8 [[ADD]], [[Y]]
+; CHECK-NEXT:    [[AND:%.*]] = select i1 [[C1]], i1 [[C2]], i1 false
+; CHECK-NEXT:    ret i1 [[AND]]
+;
+  %c1 = icmp ult i8 %a, %b
+  %z = zext i1 %c1 to i8
+  %add = add nuw i8 %x, %z
+  %c2 = icmp ule i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @sgt_nuw_sge_negative(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @sgt_nuw_sge_negative(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp sgt i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[Z:%.*]] = zext i1 [[C1]] to i8
+; CHECK-NEXT:    [[ADD:%.*]] = add nuw i8 [[X]], [[Z]]
+; CHECK-NEXT:    [[C2:%.*]] = icmp sge i8 [[ADD]], [[Y]]
+; CHECK-NEXT:    [[AND:%.*]] = select i1 [[C1]], i1 [[C2]], i1 false
+; CHECK-NEXT:    ret i1 [[AND]]
+;
+  %c1 = icmp sgt i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add nuw i8 %x, %z
+  %c2 = icmp sge i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @ugt_nsw_uge_negative(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @ugt_nsw_uge_negative(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ugt i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[Z:%.*]] = zext i1 [[C1]] to i8
+; CHECK-NEXT:    [[ADD:%.*]] = add nsw i8 [[X]], [[Z]]
+; CHECK-NEXT:    [[C2:%.*]] = icmp uge i8 [[ADD]], [[Y]]
+; CHECK-NEXT:    [[AND:%.*]] = select i1 [[C1]], i1 [[C2]], i1 false
+; CHECK-NEXT:    ret i1 [[AND]]
+;
+  %c1 = icmp ugt i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add nsw i8 %x, %z
+  %c2 = icmp uge i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @non_strict_predicate_negative(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @non_strict_predicate_negative(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp eq i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[Z:%.*]] = zext i1 [[C1]] to i8
+; CHECK-NEXT:    [[ADD:%.*]] = add nsw i8 [[X]], [[Z]]
+; CHECK-NEXT:    [[C2:%.*]] = icmp uge i8 [[ADD]], [[Y]]
+; CHECK-NEXT:    [[AND:%.*]] = select i1 [[C1]], i1 [[C2]], i1 false
+; CHECK-NEXT:    ret i1 [[AND]]
+;
+  %c1 = icmp eq i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add nsw i8 %x, %z
+  %c2 = icmp uge i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @ugt_uge_no_flags_negative(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @ugt_uge_no_flags_negative(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp ugt i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[Z:%.*]] = zext i1 [[C1]] to i8
+; CHECK-NEXT:    [[ADD:%.*]] = add i8 [[X]], [[Z]]
+; CHECK-NEXT:    [[C2:%.*]] = icmp uge i8 [[ADD]], [[Y]]
+; CHECK-NEXT:    [[AND:%.*]] = select i1 [[C1]], i1 [[C2]], i1 false
+; CHECK-NEXT:    ret i1 [[AND]]
+;
+  %c1 = icmp ugt i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add i8 %x, %z
+  %c2 = icmp uge i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}
+
+define i1 @sgt_sge_no_flags_negative(i8 %x, i8 %y) {
+; CHECK-LABEL: define i1 @sgt_sge_no_flags_negative(
+; CHECK-SAME: i8 [[X:%.*]], i8 [[Y:%.*]]) {
+; CHECK-NEXT:    [[C1:%.*]] = icmp sgt i8 [[X]], [[Y]]
+; CHECK-NEXT:    [[Z:%.*]] = zext i1 [[C1]] to i8
+; CHECK-NEXT:    [[ADD:%.*]] = add i8 [[X]], [[Z]]
+; CHECK-NEXT:    [[C2:%.*]] = icmp sge i8 [[ADD]], [[Y]]
+; CHECK-NEXT:    [[AND:%.*]] = select i1 [[C1]], i1 [[C2]], i1 false
+; CHECK-NEXT:    ret i1 [[AND]]
+;
+  %c1 = icmp sgt i8 %x, %y
+  %z = zext i1 %c1 to i8
+  %add = add i8 %x, %z
+  %c2 = icmp sge i8 %add, %y
+  %and = select i1 %c1, i1 %c2, i1 false
+  ret i1 %and
+}

Copy link
Member

@dtcxzyw dtcxzyw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two main issues:

  1. Logical and/or expressions are always canonicalized into bitwise versions. These pattern should be simplified by simplifyAndInst/simplifyOrInst: https://alive2.llvm.org/ce/z/kedJdF
  2. We already implemented similar folds in simplifyAndInst/simplifyOrInst:
    if (Op0->getType()->isIntOrIntVectorTy(1)) {
    if (std::optional<bool> Implied = isImpliedCondition(Op0, Op1, Q.DL)) {
    // If Op0 is true implies Op1 is true, then Op0 is a subset of Op1.
    if (*Implied == true)
    return Op0;
    // If Op0 is true implies Op1 is false, then they are not true together.
    if (*Implied == false)
    return ConstantInt::getFalse(Op0->getType());
    }
    if (std::optional<bool> Implied = isImpliedCondition(Op1, Op0, Q.DL)) {
    // If Op1 is true implies Op0 is true, then Op1 is a subset of Op0.
    if (*Implied)
    return Op1;
    // If Op1 is true implies Op0 is false, then they are not true together.
    if (!*Implied)
    return ConstantInt::getFalse(Op1->getType());
    }
    }
    . Therefore, we should support this pattern in isImpliedCondICmps.

Some suggestions:

  1. Add more tests.
  2. Make sure that isImpliedPoison((X + zext(X < Y)) <= Y, X < Y) returns true
  3. Add support for this pattern in isImpliedCondICmps.

As an alternative, we can replace the inner X < Y with true in InstCombine (via replaceInInstruction). Then InstCombine will fold this expression into X < Y:

X < Y ? (X +nuw zext(X < Y)) <= Y : false -->
X < Y ? (X +nuw 1) <= Y : false -->
X < Y ? X < Y : false -->
X < Y

@nikic
Copy link
Contributor

nikic commented Dec 4, 2024

As an alternative, we can replace the inner X < Y with true in InstCombine (via replaceInInstruction). Then InstCombine will fold this expression into X < Y:

I don't think this would cover the motivating case, where the add is multi-use.

@veera-sivarajan
Copy link
Contributor Author

Make sure that isImpliedPoison((X + zext(X < Y)) <= Y, X < Y) returns true

The motivating case for this optimization is:

  %c1 = icmp ult i8 %x, %y
  %z.neg = zext i1 %c1 to i8
  %add = add nuw i8 %x, %z.neg
  %c2 = icmp ule i8 %add, %y
  %and = select i1 %c1, i1 %c2, i1 false
  ret i1 %and

In this case, the call to impliesPoison(y, x) will look like impliesPoison(%c2, %c1). This will return false as %c2 can be poison (due to nuw in %add) when %c1 is true.

This is because impliesPoison(y, x), by definition, cannot prove poison safety when y = poison and x = true even though transforming the select to and is correct1.

So I don't know how to make impliesPoison(y, x) return true for the optimizing case. Can I hard code a generic version of the motivating case in impliesPoisonOrCond()?

Footnotes

  1. https://aqjune.github.io/posts/2021-10-4.the-select-story.html

@dtcxzyw
Copy link
Member

dtcxzyw commented Dec 15, 2024

Can I hard code a generic version of the motivating case in impliesPoisonOrCond()?

Sure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants