[PatternMatch] Stabilize the matching order of commutative matchers

Summary:
Currently, we
1. match `LHS` matcher to the `first` operand of binary operator,
2. and then match `RHS` matcher to the `second` operand of binary operator.
If that does not match, we swap the `LHS` and `RHS` matchers:
1. match `RHS` matcher to the `first` operand of binary operator,
2. and then match `LHS` matcher to the `second` operand of binary operator.

This works ok.
But it complicates writing of commutative matchers, where one would like to match
(`m_Value()`) the value on one side, and use (`m_Specific()`) it on the other side.

This is additionally complicated by the fact that `m_Specific()` stores the `Value *`,
not `Value **`, so it won't work at all out of the box.

The last problem is trivially solved by adding a new `m_c_Specific()` that stores the
`Value **`, not `Value *`. I'm choosing to add a new matcher, not change the existing
one because i guess all the current users are ok with existing behavior,
and this additional pointer indirection may have performance drawbacks.
Also, i'm storing pointer, not reference, because for some mysterious-to-me reason
it did not work with the reference.

The first one appears trivial, too.
Currently, we
1. match `LHS` matcher to the `first` operand of binary operator,
2. and then match `RHS` matcher to the `second` operand of binary operator.
If that does not match, we swap the ~~`LHS` and `RHS` matchers~~ **operands**:
1. match ~~`RHS`~~ **`LHS`** matcher to the ~~`first`~~ **`second`** operand of binary operator,
2. and then match ~~`LHS`~~ **`RHS`** matcher to the ~~`second`~ **`first`** operand of binary operator.

Surprisingly, `$ ninja check-llvm` still passes with this.
But i expect the bots will disagree..

The motivational unittest is included.
I'd like to use this in D45664.

Reviewers: spatel, craig.topper, arsenm, RKSimon

Reviewed By: craig.topper

Subscribers: xbolva00, wdng, llvm-commits

Differential Revision: https://reviews.llvm.org/D45828

llvm-svn: 331085
This commit is contained in:
Roman Lebedev 2018-04-27 21:23:20 +00:00
parent 8ee7d01dcf
commit 6959b8e76f
7 changed files with 108 additions and 54 deletions

View File

@ -489,6 +489,22 @@ struct specificval_ty {
/// Match if we have a specific specified value.
inline specificval_ty m_Specific(const Value *V) { return V; }
/// Stores a reference to the Value *, not the Value * itself,
/// thus can be used in commutative matchers.
template <typename Class> struct deferredval_ty {
Class *const &Val;
deferredval_ty(Class *const &V) : Val(V) {}
template <typename ITy> bool match(ITy *const V) { return V == Val; }
};
/// A commutative-friendly version of m_Specific().
inline deferredval_ty<Value> m_Deferred(Value *const &V) { return V; }
inline deferredval_ty<const Value> m_Deferred(const Value *const &V) {
return V;
}
/// Match a specified floating point value or vector of all elements of
/// that value.
struct specific_fpval {
@ -562,13 +578,15 @@ struct AnyBinaryOp_match {
LHS_t L;
RHS_t R;
// The evaluation order is always stable, regardless of Commutability.
// The LHS is always matched first.
AnyBinaryOp_match(const LHS_t &LHS, const RHS_t &RHS) : L(LHS), R(RHS) {}
template <typename OpTy> bool match(OpTy *V) {
if (auto *I = dyn_cast<BinaryOperator>(V))
return (L.match(I->getOperand(0)) && R.match(I->getOperand(1))) ||
(Commutable && R.match(I->getOperand(0)) &&
L.match(I->getOperand(1)));
(Commutable && L.match(I->getOperand(1)) &&
R.match(I->getOperand(0)));
return false;
}
};
@ -588,20 +606,22 @@ struct BinaryOp_match {
LHS_t L;
RHS_t R;
// The evaluation order is always stable, regardless of Commutability.
// The LHS is always matched first.
BinaryOp_match(const LHS_t &LHS, const RHS_t &RHS) : L(LHS), R(RHS) {}
template <typename OpTy> bool match(OpTy *V) {
if (V->getValueID() == Value::InstructionVal + Opcode) {
auto *I = cast<BinaryOperator>(V);
return (L.match(I->getOperand(0)) && R.match(I->getOperand(1))) ||
(Commutable && R.match(I->getOperand(0)) &&
L.match(I->getOperand(1)));
(Commutable && L.match(I->getOperand(1)) &&
R.match(I->getOperand(0)));
}
if (auto *CE = dyn_cast<ConstantExpr>(V))
return CE->getOpcode() == Opcode &&
((L.match(CE->getOperand(0)) && R.match(CE->getOperand(1))) ||
(Commutable && R.match(CE->getOperand(0)) &&
L.match(CE->getOperand(1))));
(Commutable && L.match(CE->getOperand(1)) &&
R.match(CE->getOperand(0))));
return false;
}
};
@ -926,14 +946,16 @@ struct CmpClass_match {
LHS_t L;
RHS_t R;
// The evaluation order is always stable, regardless of Commutability.
// The LHS is always matched first.
CmpClass_match(PredicateTy &Pred, const LHS_t &LHS, const RHS_t &RHS)
: Predicate(Pred), L(LHS), R(RHS) {}
template <typename OpTy> bool match(OpTy *V) {
if (auto *I = dyn_cast<Class>(V))
if ((L.match(I->getOperand(0)) && R.match(I->getOperand(1))) ||
(Commutable && R.match(I->getOperand(0)) &&
L.match(I->getOperand(1)))) {
(Commutable && L.match(I->getOperand(1)) &&
R.match(I->getOperand(0)))) {
Predicate = I->getPredicate();
return true;
}
@ -1251,6 +1273,8 @@ struct MaxMin_match {
LHS_t L;
RHS_t R;
// The evaluation order is always stable, regardless of Commutability.
// The LHS is always matched first.
MaxMin_match(const LHS_t &LHS, const RHS_t &RHS) : L(LHS), R(RHS) {}
template <typename OpTy> bool match(OpTy *V) {
@ -1277,7 +1301,7 @@ struct MaxMin_match {
return false;
// It does! Bind the operands.
return (L.match(LHS) && R.match(RHS)) ||
(Commutable && R.match(LHS) && L.match(RHS));
(Commutable && L.match(RHS) && R.match(LHS));
}
};

View File

@ -982,12 +982,9 @@ static void computeKnownBitsFromOperator(const Operator *I, KnownBits &Known,
// matching the form add(x, add(x, y)) where y is odd.
// TODO: This could be generalized to clearing any bit set in y where the
// following bit is known to be unset in y.
Value *Y = nullptr;
Value *X = nullptr, *Y = nullptr;
if (!Known.Zero[0] && !Known.One[0] &&
(match(I->getOperand(0), m_Add(m_Specific(I->getOperand(1)),
m_Value(Y))) ||
match(I->getOperand(1), m_Add(m_Specific(I->getOperand(0)),
m_Value(Y))))) {
match(I, m_c_BinOp(m_Value(X), m_Add(m_Deferred(X), m_Value(Y))))) {
Known2.resetAll();
computeKnownBits(Y, Known2, Depth + 1, Q);
if (Known2.countMinTrailingOnes() > 0)

View File

@ -1363,26 +1363,15 @@ Instruction *InstCombiner::visitAdd(BinaryOperator &I) {
}
// (add (xor A, B) (and A, B)) --> (or A, B)
if (match(LHS, m_Xor(m_Value(A), m_Value(B))) &&
match(RHS, m_c_And(m_Specific(A), m_Specific(B))))
return BinaryOperator::CreateOr(A, B);
// (add (and A, B) (xor A, B)) --> (or A, B)
if (match(RHS, m_Xor(m_Value(A), m_Value(B))) &&
match(LHS, m_c_And(m_Specific(A), m_Specific(B))))
if (match(&I, m_c_BinOp(m_Xor(m_Value(A), m_Value(B)),
m_c_And(m_Deferred(A), m_Deferred(B)))))
return BinaryOperator::CreateOr(A, B);
// (add (or A, B) (and A, B)) --> (add A, B)
if (match(LHS, m_Or(m_Value(A), m_Value(B))) &&
match(RHS, m_c_And(m_Specific(A), m_Specific(B)))) {
I.setOperand(0, A);
I.setOperand(1, B);
return &I;
}
// (add (and A, B) (or A, B)) --> (add A, B)
if (match(RHS, m_Or(m_Value(A), m_Value(B))) &&
match(LHS, m_c_And(m_Specific(A), m_Specific(B)))) {
if (match(&I, m_c_BinOp(m_Or(m_Value(A), m_Value(B)),
m_c_And(m_Deferred(A), m_Deferred(B))))) {
I.setOperand(0, A);
I.setOperand(1, B);
return &I;

View File

@ -1288,8 +1288,8 @@ static Instruction *foldAndToXor(BinaryOperator &I,
// Operand complexity canonicalization guarantees that the 'or' is Op0.
// (A | B) & ~(A & B) --> A ^ B
// (A | B) & ~(B & A) --> A ^ B
if (match(Op0, m_Or(m_Value(A), m_Value(B))) &&
match(Op1, m_Not(m_c_And(m_Specific(A), m_Specific(B)))))
if (match(&I, m_BinOp(m_Or(m_Value(A), m_Value(B)),
m_Not(m_c_And(m_Deferred(A), m_Deferred(B))))))
return BinaryOperator::CreateXor(A, B);
// (A | ~B) & (~A | B) --> ~(A ^ B)
@ -1297,8 +1297,8 @@ static Instruction *foldAndToXor(BinaryOperator &I,
// (~B | A) & (~A | B) --> ~(A ^ B)
// (~B | A) & (B | ~A) --> ~(A ^ B)
if (Op0->hasOneUse() || Op1->hasOneUse())
if (match(Op0, m_c_Or(m_Value(A), m_Not(m_Value(B)))) &&
match(Op1, m_c_Or(m_Not(m_Specific(A)), m_Specific(B))))
if (match(&I, m_BinOp(m_c_Or(m_Value(A), m_Not(m_Value(B))),
m_c_Or(m_Not(m_Deferred(A)), m_Deferred(B)))))
return BinaryOperator::CreateNot(Builder.CreateXor(A, B));
return nullptr;
@ -2294,10 +2294,8 @@ static Instruction *foldXorToXor(BinaryOperator &I,
// (A & B) ^ (B | A) -> A ^ B
// (A | B) ^ (A & B) -> A ^ B
// (A | B) ^ (B & A) -> A ^ B
if ((match(Op0, m_And(m_Value(A), m_Value(B))) &&
match(Op1, m_c_Or(m_Specific(A), m_Specific(B)))) ||
(match(Op0, m_Or(m_Value(A), m_Value(B))) &&
match(Op1, m_c_And(m_Specific(A), m_Specific(B))))) {
if (match(&I, m_c_Xor(m_And(m_Value(A), m_Value(B)),
m_c_Or(m_Deferred(A), m_Deferred(B))))) {
I.setOperand(0, A);
I.setOperand(1, B);
return &I;
@ -2307,10 +2305,8 @@ static Instruction *foldXorToXor(BinaryOperator &I,
// (~B | A) ^ (~A | B) -> A ^ B
// (~A | B) ^ (A | ~B) -> A ^ B
// (B | ~A) ^ (A | ~B) -> A ^ B
if ((match(Op0, m_Or(m_Value(A), m_Not(m_Value(B)))) &&
match(Op1, m_c_Or(m_Not(m_Specific(A)), m_Specific(B)))) ||
(match(Op0, m_Or(m_Not(m_Value(A)), m_Value(B))) &&
match(Op1, m_c_Or(m_Specific(A), m_Not(m_Specific(B)))))) {
if (match(&I, m_Xor(m_c_Or(m_Value(A), m_Not(m_Value(B))),
m_c_Or(m_Not(m_Deferred(A)), m_Deferred(B))))) {
I.setOperand(0, A);
I.setOperand(1, B);
return &I;
@ -2320,10 +2316,8 @@ static Instruction *foldXorToXor(BinaryOperator &I,
// (~B & A) ^ (~A & B) -> A ^ B
// (~A & B) ^ (A & ~B) -> A ^ B
// (B & ~A) ^ (A & ~B) -> A ^ B
if ((match(Op0, m_And(m_Value(A), m_Not(m_Value(B)))) &&
match(Op1, m_c_And(m_Not(m_Specific(A)), m_Specific(B)))) ||
(match(Op0, m_And(m_Not(m_Value(A)), m_Value(B))) &&
match(Op1, m_c_And(m_Specific(A), m_Not(m_Specific(B)))))) {
if (match(&I, m_Xor(m_c_And(m_Value(A), m_Not(m_Value(B))),
m_c_And(m_Not(m_Deferred(A)), m_Deferred(B))))) {
I.setOperand(0, A);
I.setOperand(1, B);
return &I;

View File

@ -333,7 +333,7 @@ define i32 @xor_to_xor3(i32 %a, i32 %b) {
define i32 @xor_to_xor4(i32 %a, i32 %b) {
; CHECK-LABEL: @xor_to_xor4(
; CHECK-NEXT: [[XOR:%.*]] = xor i32 [[A:%.*]], [[B:%.*]]
; CHECK-NEXT: [[XOR:%.*]] = xor i32 [[B:%.*]], [[A:%.*]]
; CHECK-NEXT: ret i32 [[XOR]]
;
%or = or i32 %a, %b
@ -389,7 +389,7 @@ define i32 @xor_to_xor7(float %fa, float %fb) {
; CHECK-LABEL: @xor_to_xor7(
; CHECK-NEXT: [[A:%.*]] = fptosi float [[FA:%.*]] to i32
; CHECK-NEXT: [[B:%.*]] = fptosi float [[FB:%.*]] to i32
; CHECK-NEXT: [[XOR:%.*]] = xor i32 [[A]], [[B]]
; CHECK-NEXT: [[XOR:%.*]] = xor i32 [[B]], [[A]]
; CHECK-NEXT: ret i32 [[XOR]]
;
%a = fptosi float %fa to i32
@ -408,7 +408,7 @@ define i32 @xor_to_xor8(float %fa, float %fb) {
; CHECK-LABEL: @xor_to_xor8(
; CHECK-NEXT: [[A:%.*]] = fptosi float [[FA:%.*]] to i32
; CHECK-NEXT: [[B:%.*]] = fptosi float [[FB:%.*]] to i32
; CHECK-NEXT: [[XOR:%.*]] = xor i32 [[A]], [[B]]
; CHECK-NEXT: [[XOR:%.*]] = xor i32 [[B]], [[A]]
; CHECK-NEXT: ret i32 [[XOR]]
;
%a = fptosi float %fa to i32
@ -465,7 +465,7 @@ define i32 @xor_to_xor11(float %fa, float %fb) {
; CHECK-LABEL: @xor_to_xor11(
; CHECK-NEXT: [[A:%.*]] = fptosi float [[FA:%.*]] to i32
; CHECK-NEXT: [[B:%.*]] = fptosi float [[FB:%.*]] to i32
; CHECK-NEXT: [[XOR:%.*]] = xor i32 [[A]], [[B]]
; CHECK-NEXT: [[XOR:%.*]] = xor i32 [[B]], [[A]]
; CHECK-NEXT: ret i32 [[XOR]]
;
%a = fptosi float %fa to i32
@ -484,7 +484,7 @@ define i32 @xor_to_xor12(float %fa, float %fb) {
; CHECK-LABEL: @xor_to_xor12(
; CHECK-NEXT: [[A:%.*]] = fptosi float [[FA:%.*]] to i32
; CHECK-NEXT: [[B:%.*]] = fptosi float [[FB:%.*]] to i32
; CHECK-NEXT: [[XOR:%.*]] = xor i32 [[A]], [[B]]
; CHECK-NEXT: [[XOR:%.*]] = xor i32 [[B]], [[A]]
; CHECK-NEXT: ret i32 [[XOR]]
;
%a = fptosi float %fa to i32

View File

@ -188,7 +188,7 @@ define i32 @test13(i32 %x, i32 %y) {
; ((x | ~y) ^ (~x | y)) -> x ^ y
define i32 @test14(i32 %x, i32 %y) {
; CHECK-LABEL: @test14(
; CHECK-NEXT: [[XOR:%.*]] = xor i32 [[Y:%.*]], [[X:%.*]]
; CHECK-NEXT: [[XOR:%.*]] = xor i32 [[X:%.*]], [[Y:%.*]]
; CHECK-NEXT: ret i32 [[XOR]]
;
%noty = xor i32 %y, -1
@ -201,7 +201,7 @@ define i32 @test14(i32 %x, i32 %y) {
define i32 @test14_commuted(i32 %x, i32 %y) {
; CHECK-LABEL: @test14_commuted(
; CHECK-NEXT: [[XOR:%.*]] = xor i32 [[Y:%.*]], [[X:%.*]]
; CHECK-NEXT: [[XOR:%.*]] = xor i32 [[X:%.*]], [[Y:%.*]]
; CHECK-NEXT: ret i32 [[XOR]]
;
%noty = xor i32 %y, -1
@ -215,7 +215,7 @@ define i32 @test14_commuted(i32 %x, i32 %y) {
; ((x & ~y) ^ (~x & y)) -> x ^ y
define i32 @test15(i32 %x, i32 %y) {
; CHECK-LABEL: @test15(
; CHECK-NEXT: [[XOR:%.*]] = xor i32 [[Y:%.*]], [[X:%.*]]
; CHECK-NEXT: [[XOR:%.*]] = xor i32 [[X:%.*]], [[Y:%.*]]
; CHECK-NEXT: ret i32 [[XOR]]
;
%noty = xor i32 %y, -1
@ -228,7 +228,7 @@ define i32 @test15(i32 %x, i32 %y) {
define i32 @test15_commuted(i32 %x, i32 %y) {
; CHECK-LABEL: @test15_commuted(
; CHECK-NEXT: [[XOR:%.*]] = xor i32 [[Y:%.*]], [[X:%.*]]
; CHECK-NEXT: [[XOR:%.*]] = xor i32 [[X:%.*]], [[Y:%.*]]
; CHECK-NEXT: ret i32 [[XOR]]
;
%noty = xor i32 %y, -1

View File

@ -65,6 +65,56 @@ TEST_F(PatternMatchTest, OneUse) {
EXPECT_FALSE(m_OneUse(m_Value()).match(Leaf));
}
TEST_F(PatternMatchTest, CommutativeDeferredValue) {
Value *X = IRB.getInt32(1);
Value *Y = IRB.getInt32(2);
{
Value *tX = X;
EXPECT_TRUE(match(X, m_Deferred(tX)));
EXPECT_FALSE(match(Y, m_Deferred(tX)));
}
{
const Value *tX = X;
EXPECT_TRUE(match(X, m_Deferred(tX)));
EXPECT_FALSE(match(Y, m_Deferred(tX)));
}
{
Value *const tX = X;
EXPECT_TRUE(match(X, m_Deferred(tX)));
EXPECT_FALSE(match(Y, m_Deferred(tX)));
}
{
const Value *const tX = X;
EXPECT_TRUE(match(X, m_Deferred(tX)));
EXPECT_FALSE(match(Y, m_Deferred(tX)));
}
{
Value *tX = nullptr;
EXPECT_TRUE(match(IRB.CreateAnd(X, X), m_And(m_Value(tX), m_Deferred(tX))));
EXPECT_EQ(tX, X);
}
{
Value *tX = nullptr;
EXPECT_FALSE(
match(IRB.CreateAnd(X, Y), m_c_And(m_Value(tX), m_Deferred(tX))));
}
auto checkMatch = [X, Y](Value *Pattern) {
Value *tX = nullptr, *tY = nullptr;
EXPECT_TRUE(match(
Pattern, m_c_And(m_Value(tX), m_c_And(m_Deferred(tX), m_Value(tY)))));
EXPECT_EQ(tX, X);
EXPECT_EQ(tY, Y);
};
checkMatch(IRB.CreateAnd(X, IRB.CreateAnd(X, Y)));
checkMatch(IRB.CreateAnd(X, IRB.CreateAnd(Y, X)));
checkMatch(IRB.CreateAnd(IRB.CreateAnd(X, Y), X));
checkMatch(IRB.CreateAnd(IRB.CreateAnd(Y, X), X));
}
TEST_F(PatternMatchTest, FloatingPointOrderedMin) {
Type *FltTy = IRB.getFloatTy();
Value *L = ConstantFP::get(FltTy, 1.0);