-
Notifications
You must be signed in to change notification settings - Fork 14.6k
[InstCombine] Canoncalize complex boolean expressions into ~((y | z) ^ x) #149530
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
base: main
Are you sure you want to change the base?
Conversation
@llvm/pr-subscribers-llvm-transforms Author: (yafet-a) ChangesFixes #97044 Optimizations added: Results :
Full diff: https://github.com/llvm/llvm-project/pull/149530.diff 2 Files Affected:
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index 3beda6bc5ba38..5d9bee7b30fad 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -2139,6 +2139,44 @@ static Instruction *foldComplexAndOrPatterns(BinaryOperator &I,
X);
}
+ // ((~Z) & ((X & Y) | (~X & ~Y))) ^ (Z & ((X & Y) | (X & ~Y))) -> ~((Y | Z) ^ X)
+ {
+ {
+ Value *X, *Y, *Z;
+ Value *SomethingOrZ, *ZAndX;
+
+ if (match(&I, m_c_Xor(m_Value(SomethingOrZ), m_Value(ZAndX))) &&
+ match(ZAndX, m_And(m_Value(Z), m_Value(X))) &&
+ match(SomethingOrZ, m_Or(m_Value(), m_Specific(Z)))) {
+ Value *Something;
+ if (match(SomethingOrZ, m_Or(m_Value(Something), m_Specific(Z))) &&
+ match(Something, m_Xor(m_Specific(X), m_Value(Y)))) {
+ Value *YOrZ = Builder.CreateOr(Y, Z);
+ Value *YOrZXorX = Builder.CreateXor(YOrZ, X);
+ return BinaryOperator::CreateNot(YOrZXorX);
+ }
+ }
+ }
+
+ // ((X & Y) | (~X & ~Y)) ^ (Z & (((X & Y) | (~X & ~Y)) ^ ((X & Y) | (X & ~Y)))) -> ~((Y | Z) ^ X)
+ if (match(Op1, m_AllOnes())) {
+ Value *X, *Y, *Z;
+ Value *XorWithY;
+ if (match(Op0, m_Xor(m_Value(XorWithY), m_Value(Y)))) {
+ Value *ZAndNotY;
+ if (match(XorWithY, m_Xor(m_Value(X), m_Value(ZAndNotY)))) {
+ Value *NotY;
+ if (match(ZAndNotY, m_And(m_Value(Z), m_Value(NotY))) &&
+ match(NotY, m_Not(m_Specific(Y)))) {
+ Value *YOrZ = Builder.CreateOr(Y, Z);
+ Value *YOrZXorX = Builder.CreateXor(YOrZ, X);
+ return BinaryOperator::CreateNot(YOrZXorX);
+ }
+ }
+ }
+ }
+ }
+
return nullptr;
}
@@ -3780,6 +3818,18 @@ Instruction *InstCombinerImpl::visitOr(BinaryOperator &I) {
return replaceInstUsesWith(I, V);
Value *Op0 = I.getOperand(0), *Op1 = I.getOperand(1);
+
+ // ((X & Y & ~Z) | (X & ~Y & Z) | (~X & ~Y &~Z) | (X & Y &Z)) -> ~((Y | Z) ^ X)
+ {
+ Value *X, *Y, *Z;
+ Value *Term1, *Term2, *XAndYAndZ;
+ if (match(&I, m_Or(m_Or(m_Value(Term1), m_Value(Term2)), m_Value(XAndYAndZ))) &&
+ match(XAndYAndZ, m_And(m_And(m_Value(X), m_Value(Y)), m_Value(Z)))) {
+ Value *YOrZ = Builder.CreateOr(Y, Z);
+ Value *YOrZXorX = Builder.CreateXor(YOrZ, X);
+ return BinaryOperator::CreateNot(YOrZXorX);
+ }
+ }
Type *Ty = I.getType();
if (Ty->isIntOrIntVectorTy(1)) {
if (auto *SI0 = dyn_cast<SelectInst>(Op0)) {
diff --git a/llvm/test/Transforms/InstCombine/pr97044.ll b/llvm/test/Transforms/InstCombine/pr97044.ll
new file mode 100644
index 0000000000000..4e45f88956d89
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/pr97044.ll
@@ -0,0 +1,94 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+; Tests for GitHub issue #97044 - Boolean instruction canonicalization
+; All expressions should optimise to the same canonical form: ~((y | z) ^ x)
+
+define i32 @test0_4way_or(i32 %x, i32 %y, i32 %z) {
+; CHECK-LABEL: @test0_4way_or(
+; CHECK-NEXT: [[TMP1:%.*]] = or i32 [[Y:%.*]], [[Z:%.*]]
+; CHECK-NEXT: [[TMP2:%.*]] = xor i32 [[TMP1]], [[X:%.*]]
+; CHECK-NEXT: [[TMP3:%.*]] = xor i32 [[TMP2]], -1
+; CHECK-NEXT: ret i32 [[TMP3]]
+;
+ %not = xor i32 %z, -1
+ %and = and i32 %y, %not
+ %and1 = and i32 %and, %x
+ %not2 = xor i32 %y, -1
+ %and3 = and i32 %x, %not2
+ %and4 = and i32 %and3, %z
+ %or = or i32 %and1, %and4
+ %not5 = xor i32 %x, -1
+ %not6 = xor i32 %y, -1
+ %and7 = and i32 %not5, %not6
+ %not8 = xor i32 %z, -1
+ %and9 = and i32 %and7, %not8
+ %or10 = or i32 %or, %and9
+ %and11 = and i32 %x, %y
+ %and12 = and i32 %and11, %z
+ %or13 = or i32 %or10, %and12
+ ret i32 %or13
+}
+
+define i32 @test1_xor_pattern(i32 %x, i32 %y, i32 %z) {
+; CHECK-LABEL: @test1_xor_pattern(
+; CHECK-NEXT: [[TMP2:%.*]] = xor i32 [[TMP1:%.*]], [[X:%.*]]
+; CHECK-NEXT: [[AND4_DEMORGAN:%.*]] = or i32 [[TMP2]], [[Z:%.*]]
+; CHECK-NEXT: [[AND8:%.*]] = and i32 [[Z]], [[TMP1]]
+; CHECK-NEXT: [[TMP4:%.*]] = xor i32 [[AND4_DEMORGAN]], -1
+; CHECK-NEXT: [[TMP3:%.*]] = or i32 [[AND8]], [[TMP4]]
+; CHECK-NEXT: ret i32 [[TMP3]]
+;
+ %not = xor i32 %z, -1
+ %and = and i32 %x, %y
+ %not1 = xor i32 %x, -1
+ %not2 = xor i32 %y, -1
+ %and3 = and i32 %not1, %not2
+ %or = or i32 %and, %and3
+ %and4 = and i32 %not, %or
+ %and5 = and i32 %x, %y
+ %and6 = and i32 %x, %not2
+ %or7 = or i32 %and5, %and6
+ %and8 = and i32 %z, %or7
+ %xor = xor i32 %and4, %and8
+ ret i32 %xor
+}
+
+define i32 @test2_nested_xor(i32 %x, i32 %y, i32 %z) {
+; CHECK-LABEL: @test2_nested_xor(
+; CHECK-NEXT: [[TMP3:%.*]] = xor i32 [[TMP2:%.*]], -1
+; CHECK-NEXT: [[AND8:%.*]] = and i32 [[Z:%.*]], [[TMP3]]
+; CHECK-NEXT: [[TMP1:%.*]] = xor i32 [[X:%.*]], [[AND8]]
+; CHECK-NEXT: ret i32 [[TMP1]]
+;
+ %and = and i32 %x, %y
+ %not = xor i32 %x, -1
+ %not1 = xor i32 %y, -1
+ %and2 = and i32 %not, %not1
+ %or = or i32 %and, %and2
+ %and3 = and i32 %x, %y
+ %not4 = xor i32 %y, -1
+ %and5 = and i32 %x, %not4
+ %or6 = or i32 %and3, %and5
+ %xor = xor i32 %or, %or6
+ %not7 = xor i32 %y, -1
+ %and8 = and i32 %z, %not7
+ %and9 = and i32 %xor, %and8
+ %xor10 = xor i32 %or, %and9
+ %xor11 = xor i32 %xor10, %y
+ %xor12 = xor i32 %xor11, -1
+ ret i32 %xor12
+}
+
+define i32 @test3_already_optimal(i32 %x, i32 %y, i32 %z) {
+; CHECK-LABEL: @test3_already_optimal(
+; CHECK-NEXT: [[OR:%.*]] = or i32 [[Y:%.*]], [[Z:%.*]]
+; CHECK-NEXT: [[XOR:%.*]] = xor i32 [[OR]], [[X:%.*]]
+; CHECK-NEXT: [[NOT:%.*]] = xor i32 [[XOR]], -1
+; CHECK-NEXT: ret i32 [[NOT]]
+;
+ %or = or i32 %y, %z
+ %xor = xor i32 %or, %x
+ %not = xor i32 %xor, -1
+ ret i32 %not
+}
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
7c960cb
to
eb53480
Compare
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.
We don't accept complex patterns without real-world usefulness: https://llvm.org/docs/InstCombineContributorGuide.html#real-world-usefulness
Currently, the original case can be simplified into a smaller expression: https://godbolt.org/z/En3za8Gjz. Please check if we can fold it into the target pattern through a simpler transformation (2-3 instructions). Then you need to demonstrate its real-world usefulness.
Reducing the size of an expression is not the business of optimizing compilers. Instead, it is widely used by decompilers. See also https://docs.hex-rays.com/user-guide/decompiler/goomba.
60f76f9
to
9aef3f0
Compare
9aef3f0
to
a95e12b
Compare
a95e12b
to
db43e0a
Compare
db43e0a
to
02807e3
Compare
Pull Request: Response to Changes Requested and Discussion in #97044Design Change: Pattern Matching → Truth Table ApproachBefore:
After:
Key Changes
Testing & Validation
Future ExtensibilityFramework can support all 256 possible 3-variable boolean functions. For now may be better to see where this goes and incrementally enable addition of new cases with real-world justification. |
Fixes #97044
Optimizations added:
((x&y&~z) | (x&~y&z) | (~x&~y&~z) | (x&y&z))
→~((y | z) ^ x)
((~z) & ((x&y) | (~x&~y))) ^ (z & ((x&y) | (x&~y)))
→~((y | z) ^ x)
((x&y) | (~x&~y)) ^ (z & (((x&y) | (~x&~y)) ^ ((x&y) | (x&~y))))
→~((y | z) ^ x)
Tests Pre-Commited in order to view the effect of the canocalization.
I have also provided Alive2 Proofs here - https://alive2.llvm.org/ce/z/RwhZzS
Results :