-
Notifications
You must be signed in to change notification settings - Fork 10.8k
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
[InstCombine] Simplify and/or by replacing operands with constants #77231
Conversation
@llvm/pr-subscribers-llvm-transforms Author: Yingwei Zheng (dtcxzyw) ChangesThis patch tries to simplify
Fixes #76554. Full diff: https://github.com/llvm/llvm-project/pull/77231.diff 4 Files Affected:
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
+}
|
Could you check whether this patch simplifies (or partially) the example like https://alive2.llvm.org/ce/z/7MHnzm? It's a piece of unreduced real-world IR. It seems to be covered by the |
Is it extracted from |
Optimized result:
|
9d655c6
to
4980f1e
Compare
Thanks! It's extracted from https://github.com/qemu/qemu/blob/7425b6277f12e82952cede1f531bfc689bf77fb1/hw/virtio/vhost-shadow-virtqueue.c#L27. It seems that it has been optimized partially now. |
4980f1e
to
ca75191
Compare
I don't really understand how this is related to your patch.
I think there are quite a few patterns you could drop that are currently missing one-use checks. For example llvm-project/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp Lines 3543 to 3545 in 3c246ef
But it's fine to do this in separate step, with extra test coverage for multi-use if necessary. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks!
ca75191
to
e484974
Compare
This patch removes some bitwise folds that fail to check the one-use constraint on the operands. See also the comments #77231 (comment).
This patch tries to simplify
X | Y
by replacing occurrences ofY
inX
with 0. Similarly, it tries to simplifyX & Y
by replacing occurrences ofY
inX
with -1.See also #76572 (review).
Alive2: https://alive2.llvm.org/ce/z/cNjDTR
Note:
(A ^ B) | Y
into((A | Y) ^ (B | Y)) | Y
ifY
may be an undef (See the alive2 proof).A & ~(A ^ B) --> A & B
).Compile-time impact: http://llvm-compile-time-tracker.com/compare.php?from=a085402ef54379758e6c996dbaedfcb92ad222b5&to=9d655c6685865ffce0ad336fed81228f3071bd03&stat=instructions%3Au
Fixes #76554.
It is an alternative to #76572.