[llvm] [InstCombine] Simplify and/or by replacing operands with constants (PR #77231)
via llvm-commits
llvm-commits at lists.llvm.org
Sun Jan 7 01:52:38 PST 2024
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-transforms
Author: Yingwei Zheng (dtcxzyw)
<details>
<summary>Changes</summary>
This patch tries to simplify `X | Y` by replacing occurrences of `Y` in `X` with 0. Similarly, it tries to simplify `X & Y` by replacing occurrences of `Y` in `X` with -1.
See also https://github.com/llvm/llvm-project/pull/76572#pullrequestreview-1800286376.
Alive2: https://alive2.llvm.org/ce/z/cNjDTR
Note:
1. I am not sure that it is correct to decompose `(A ^ B) | Y` into `((A | Y) ^ (B | Y)) | Y` if `Y` may be an undef (See the alive2 proof).
2. As the current implementation is too conservative in the one-use checks, I cannot remove other existing hard-coded simplifications if they involves more than two instructions (e.g, `A & ~(A ^ B) --> A & B`).
Fixes #<!-- -->76554.
It is an alternative to #<!-- -->76572.
---
Full diff: https://github.com/llvm/llvm-project/pull/77231.diff
4 Files Affected:
- (modified) llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp (+65-11)
- (modified) llvm/test/Transforms/InstCombine/and-or-icmps.ll (+5-9)
- (modified) llvm/test/Transforms/InstCombine/and-or-not.ll (+3-11)
- (modified) llvm/test/Transforms/InstCombine/or.ll (+111)
``````````diff
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index c03f50d75814d8..dc350b29bc20b5 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -2159,6 +2159,61 @@ Instruction *InstCombinerImpl::foldBinOpOfDisplacedShifts(BinaryOperator &I) {
return BinaryOperator::Create(ShiftOp, NewC, ShAmt);
}
+// Try to simplify X | Y by replacing occurrences of Y in X with 0.
+// Similarly, simplify X & Y by replacing occurrences of Y in X with -1.
+static Value *simplifyAndOrWithOpReplaced(Value *X, Value *Y, bool IsAnd,
+ InstCombinerImpl &IC,
+ unsigned Depth = 0) {
+ if (isa<Constant>(X) || X == Y)
+ return nullptr;
+
+ auto RecursivelyReplaceUses = [&](Instruction::BinaryOps Opcode, Value *Op0,
+ Value *Op1) -> Value * {
+ if (Depth == 2)
+ return nullptr;
+
+ // TODO: Relax the one-use constraint to clean up existing hard-coded
+ // simplifications.
+ if (!X->hasOneUse())
+ return nullptr;
+ Value *NewOp0 = simplifyAndOrWithOpReplaced(Op0, Y, IsAnd, IC, Depth + 1);
+ Value *NewOp1 = simplifyAndOrWithOpReplaced(Op1, Y, IsAnd, IC, Depth + 1);
+ if (!NewOp0 && !NewOp1)
+ return nullptr;
+ return IC.Builder.CreateBinOp(Opcode, NewOp0 ? NewOp0 : Op0,
+ NewOp1 ? NewOp1 : Op1);
+ };
+
+ Value *Op0, *Op1;
+ if (match(X, m_And(m_Value(Op0), m_Value(Op1)))) {
+ if (Op0 == Y || Op1 == Y)
+ return IsAnd ? (Op0 == Y ? Op1 : Op0)
+ : Constant::getNullValue(X->getType());
+ return RecursivelyReplaceUses(Instruction::And, Op0, Op1);
+ } else if (match(X, m_Or(m_Value(Op0), m_Value(Op1)))) {
+ if (Op0 == Y || Op1 == Y)
+ return IsAnd ? Constant::getAllOnesValue(X->getType())
+ : (Op0 == Y ? Op1 : Op0);
+ return RecursivelyReplaceUses(Instruction::Or, Op0, Op1);
+ } else if (match(X, m_Xor(m_Value(Op0), m_Value(Op1)))) {
+ if (Op0 == Y || Op1 == Y) {
+ Value *V = Op0 == Y ? Op1 : Op0;
+ if (IsAnd) {
+ if (Value *NotV =
+ simplifyXorInst(V, Constant::getAllOnesValue(V->getType()),
+ IC.getSimplifyQuery()))
+ return NotV;
+ if (X->hasOneUse())
+ return IC.Builder.CreateNot(V);
+ } else
+ return V;
+ }
+ // FIXME: Is it correct to decompose xor if Y may be undef?
+ return RecursivelyReplaceUses(Instruction::Xor, Op0, Op1);
+ }
+ return nullptr;
+}
+
// FIXME: We use commutative matchers (m_c_*) for some, but not all, matches
// here. We should standardize that construct where it is needed or choose some
// other way to ensure that commutated variants of patterns are not missed.
@@ -2488,13 +2543,6 @@ Instruction *InstCombinerImpl::visitAnd(BinaryOperator &I) {
{
Value *A, *B, *C;
- // A & (A ^ B) --> A & ~B
- if (match(Op1, m_OneUse(m_c_Xor(m_Specific(Op0), m_Value(B)))))
- return BinaryOperator::CreateAnd(Op0, Builder.CreateNot(B));
- // (A ^ B) & A --> A & ~B
- if (match(Op0, m_OneUse(m_c_Xor(m_Specific(Op1), m_Value(B)))))
- return BinaryOperator::CreateAnd(Op1, Builder.CreateNot(B));
-
// A & ~(A ^ B) --> A & B
if (match(Op1, m_Not(m_c_Xor(m_Specific(Op0), m_Value(B)))))
return BinaryOperator::CreateAnd(Op0, B);
@@ -2688,6 +2736,11 @@ Instruction *InstCombinerImpl::visitAnd(BinaryOperator &I) {
if (Instruction *Res = foldBinOpOfDisplacedShifts(I))
return Res;
+ if (Value *V = simplifyAndOrWithOpReplaced(Op0, Op1, /*IsAnd*/ true, *this))
+ return BinaryOperator::CreateAnd(Op1, V);
+ if (Value *V = simplifyAndOrWithOpReplaced(Op1, Op0, /*IsAnd*/ true, *this))
+ return BinaryOperator::CreateAnd(Op0, V);
+
return nullptr;
}
@@ -3399,10 +3452,6 @@ Instruction *InstCombinerImpl::visitOr(BinaryOperator &I) {
return BinaryOperator::CreateMul(X, IncrementY);
}
- // X | (X ^ Y) --> X | Y (4 commuted patterns)
- if (match(&I, m_c_Or(m_Value(X), m_c_Xor(m_Deferred(X), m_Value(Y)))))
- return BinaryOperator::CreateOr(X, Y);
-
// (A & C) | (B & D)
Value *A, *B, *C, *D;
if (match(Op0, m_And(m_Value(A), m_Value(C))) &&
@@ -3884,6 +3933,11 @@ Instruction *InstCombinerImpl::visitOr(BinaryOperator &I) {
return BinaryOperator::CreateAnd(X, ConstantInt::get(Ty, *C1 | *C2));
}
+ if (Value *V = simplifyAndOrWithOpReplaced(Op0, Op1, /*IsAnd*/ false, *this))
+ return BinaryOperator::CreateOr(Op1, V);
+ if (Value *V = simplifyAndOrWithOpReplaced(Op1, Op0, /*IsAnd*/ false, *this))
+ return BinaryOperator::CreateOr(Op0, V);
+
return nullptr;
}
diff --git a/llvm/test/Transforms/InstCombine/and-or-icmps.ll b/llvm/test/Transforms/InstCombine/and-or-icmps.ll
index 91ecf24760259b..b43a65d5b8e3b2 100644
--- a/llvm/test/Transforms/InstCombine/and-or-icmps.ll
+++ b/llvm/test/Transforms/InstCombine/and-or-icmps.ll
@@ -369,16 +369,12 @@ define void @simplify_before_foldAndOfICmps(ptr %p) {
; CHECK-NEXT: [[TMP1:%.*]] = icmp eq i16 [[L7]], -1
; CHECK-NEXT: [[B11:%.*]] = zext i1 [[TMP1]] to i16
; CHECK-NEXT: [[C10:%.*]] = icmp ugt i16 [[L7]], [[B11]]
-; CHECK-NEXT: [[C5:%.*]] = icmp slt i16 [[L7]], 1
; CHECK-NEXT: [[C7:%.*]] = icmp slt i16 [[L7]], 0
-; CHECK-NEXT: [[B15:%.*]] = xor i1 [[C7]], [[C10]]
-; CHECK-NEXT: [[C6:%.*]] = xor i1 [[B15]], true
-; CHECK-NEXT: [[TMP2:%.*]] = and i1 [[C5]], [[C6]]
-; CHECK-NEXT: [[C3:%.*]] = and i1 [[TMP2]], [[C10]]
-; CHECK-NEXT: [[TMP3:%.*]] = xor i1 [[C10]], true
-; CHECK-NEXT: [[C18:%.*]] = or i1 [[C7]], [[TMP3]]
-; CHECK-NEXT: [[TMP4:%.*]] = sext i1 [[C3]] to i64
-; CHECK-NEXT: [[G26:%.*]] = getelementptr i1, ptr null, i64 [[TMP4]]
+; CHECK-NEXT: [[C3:%.*]] = and i1 [[C10]], [[C7]]
+; CHECK-NEXT: [[TMP2:%.*]] = xor i1 [[C10]], true
+; CHECK-NEXT: [[C18:%.*]] = or i1 [[C7]], [[TMP2]]
+; CHECK-NEXT: [[TMP3:%.*]] = sext i1 [[C3]] to i64
+; CHECK-NEXT: [[G26:%.*]] = getelementptr i1, ptr null, i64 [[TMP3]]
; CHECK-NEXT: store i16 [[L7]], ptr [[P:%.*]], align 2
; CHECK-NEXT: store i1 [[C18]], ptr [[P]], align 1
; CHECK-NEXT: store ptr [[G26]], ptr [[P]], align 8
diff --git a/llvm/test/Transforms/InstCombine/and-or-not.ll b/llvm/test/Transforms/InstCombine/and-or-not.ll
index ca093eba1b5688..2e351c30ea1f7b 100644
--- a/llvm/test/Transforms/InstCombine/and-or-not.ll
+++ b/llvm/test/Transforms/InstCombine/and-or-not.ll
@@ -761,11 +761,7 @@ define i4 @simplify_and_common_op_use1(i4 %x, i4 %y, i4 %z) {
define i4 @simplify_and_common_op_use2(i4 %x, i4 %y, i4 %z) {
; CHECK-LABEL: @simplify_and_common_op_use2(
; CHECK-NEXT: call void @use(i4 [[Y:%.*]])
-; CHECK-NEXT: [[TMP1:%.*]] = or i4 [[X:%.*]], [[Z:%.*]]
-; CHECK-NEXT: [[XYZ:%.*]] = or i4 [[TMP1]], [[Y]]
-; CHECK-NEXT: [[NOT_XYZ:%.*]] = xor i4 [[XYZ]], -1
-; CHECK-NEXT: [[R:%.*]] = and i4 [[NOT_XYZ]], [[X]]
-; CHECK-NEXT: ret i4 [[R]]
+; CHECK-NEXT: ret i4 0
;
%xy = or i4 %y, %x
call void @use(i4 %y)
@@ -779,12 +775,8 @@ define i4 @simplify_and_common_op_use2(i4 %x, i4 %y, i4 %z) {
define i4 @simplify_and_common_op_use3(i4 %x, i4 %y, i4 %z) {
; CHECK-LABEL: @simplify_and_common_op_use3(
-; CHECK-NEXT: [[XY:%.*]] = or i4 [[X:%.*]], [[Y:%.*]]
-; CHECK-NEXT: [[XYZ:%.*]] = or i4 [[XY]], [[Z:%.*]]
-; CHECK-NEXT: call void @use(i4 [[Z]])
-; CHECK-NEXT: [[NOT_XYZ:%.*]] = xor i4 [[XYZ]], -1
-; CHECK-NEXT: [[R:%.*]] = and i4 [[NOT_XYZ]], [[X]]
-; CHECK-NEXT: ret i4 [[R]]
+; CHECK-NEXT: call void @use(i4 [[Z:%.*]])
+; CHECK-NEXT: ret i4 0
;
%xy = or i4 %x, %y
%xyz = or i4 %xy, %z
diff --git a/llvm/test/Transforms/InstCombine/or.ll b/llvm/test/Transforms/InstCombine/or.ll
index 573a11599141a7..0450255135f028 100644
--- a/llvm/test/Transforms/InstCombine/or.ll
+++ b/llvm/test/Transforms/InstCombine/or.ll
@@ -1777,3 +1777,114 @@ if.then:
if.else:
ret i32 0
}
+
+; Tests from PR76554
+define i32 @test_or_and_xor_constant(i32 %x, i32 %y) {
+; CHECK-LABEL: @test_or_and_xor_constant(
+; CHECK-NEXT: [[A1:%.*]] = or i32 [[X:%.*]], [[Y:%.*]]
+; CHECK-NEXT: [[D:%.*]] = and i32 [[A1]], -2147483648
+; CHECK-NEXT: ret i32 [[D]]
+;
+ %a = and i32 %x, -2147483648
+ %b = xor i32 %a, -2147483648
+ %c = and i32 %b, %y
+ %d = or i32 %c, %a
+ ret i32 %d
+}
+
+define i32 @test_or_and_xor(i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: @test_or_and_xor(
+; CHECK-NEXT: [[TMP1:%.*]] = and i32 [[B:%.*]], [[C:%.*]]
+; CHECK-NEXT: [[OR:%.*]] = or i32 [[TMP1]], [[A:%.*]]
+; CHECK-NEXT: ret i32 [[OR]]
+;
+ %xor = xor i32 %a, %b
+ %and = and i32 %xor, %c
+ %or = or i32 %and, %a
+ ret i32 %or
+}
+
+define i32 @test_or_and_xor_commuted1(i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: @test_or_and_xor_commuted1(
+; CHECK-NEXT: [[TMP1:%.*]] = and i32 [[B:%.*]], [[C:%.*]]
+; CHECK-NEXT: [[OR:%.*]] = or i32 [[TMP1]], [[A:%.*]]
+; CHECK-NEXT: ret i32 [[OR]]
+;
+ %xor = xor i32 %b, %a
+ %and = and i32 %xor, %c
+ %or = or i32 %and, %a
+ ret i32 %or
+}
+
+define i32 @test_or_and_xor_commuted2(i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: @test_or_and_xor_commuted2(
+; CHECK-NEXT: [[CC:%.*]] = mul i32 [[C:%.*]], [[C]]
+; CHECK-NEXT: [[TMP1:%.*]] = and i32 [[CC]], [[B:%.*]]
+; CHECK-NEXT: [[OR:%.*]] = or i32 [[TMP1]], [[A:%.*]]
+; CHECK-NEXT: ret i32 [[OR]]
+;
+ %cc = mul i32 %c, %c
+ %xor = xor i32 %a, %b
+ %and = and i32 %cc, %xor
+ %or = or i32 %and, %a
+ ret i32 %or
+}
+
+define i32 @test_or_and_xor_commuted3(i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: @test_or_and_xor_commuted3(
+; CHECK-NEXT: [[AA:%.*]] = mul i32 [[A:%.*]], [[A]]
+; CHECK-NEXT: [[TMP1:%.*]] = and i32 [[B:%.*]], [[C:%.*]]
+; CHECK-NEXT: [[OR:%.*]] = or i32 [[AA]], [[TMP1]]
+; CHECK-NEXT: ret i32 [[OR]]
+;
+ %aa = mul i32 %a, %a
+ %xor = xor i32 %aa, %b
+ %and = and i32 %xor, %c
+ %or = or i32 %aa, %and
+ ret i32 %or
+}
+
+define i32 @test_or_and_xor_multiuse1(i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: @test_or_and_xor_multiuse1(
+; CHECK-NEXT: [[XOR:%.*]] = xor i32 [[A:%.*]], [[B:%.*]]
+; CHECK-NEXT: call void @use(i32 [[XOR]])
+; CHECK-NEXT: [[TMP1:%.*]] = and i32 [[B]], [[C:%.*]]
+; CHECK-NEXT: [[OR:%.*]] = or i32 [[TMP1]], [[A]]
+; CHECK-NEXT: ret i32 [[OR]]
+;
+ %xor = xor i32 %a, %b
+ call void @use(i32 %xor)
+ %and = and i32 %xor, %c
+ %or = or i32 %and, %a
+ ret i32 %or
+}
+
+; Negative tests
+
+define i32 @test_or_and_xor_mismatched_op(i32 %a, i32 %b, i32 %c, i32 %d) {
+; CHECK-LABEL: @test_or_and_xor_mismatched_op(
+; CHECK-NEXT: [[XOR:%.*]] = xor i32 [[A:%.*]], [[B:%.*]]
+; CHECK-NEXT: [[AND:%.*]] = and i32 [[XOR]], [[C:%.*]]
+; CHECK-NEXT: [[OR:%.*]] = or i32 [[AND]], [[D:%.*]]
+; CHECK-NEXT: ret i32 [[OR]]
+;
+ %xor = xor i32 %a, %b
+ %and = and i32 %xor, %c
+ %or = or i32 %and, %d
+ ret i32 %or
+}
+
+define i32 @test_or_and_xor_multiuse2(i32 %a, i32 %b, i32 %c) {
+; CHECK-LABEL: @test_or_and_xor_multiuse2(
+; CHECK-NEXT: [[XOR:%.*]] = xor i32 [[A:%.*]], [[B:%.*]]
+; CHECK-NEXT: [[AND:%.*]] = and i32 [[XOR]], [[C:%.*]]
+; CHECK-NEXT: call void @use(i32 [[AND]])
+; CHECK-NEXT: [[OR:%.*]] = or i32 [[AND]], [[A]]
+; CHECK-NEXT: ret i32 [[OR]]
+;
+ %xor = xor i32 %a, %b
+ %and = and i32 %xor, %c
+ call void @use(i32 %and)
+ %or = or i32 %and, %a
+ ret i32 %or
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/77231
More information about the llvm-commits
mailing list