This is an archive of the discontinued LLVM Phabricator instance.

[InstSimplify] Fold logic And to Zero
ClosedPublic

Authored by MehrHeidar on Dec 14 2021, 1:08 PM.

Details

Summary

Add following fold opportunity:

((A | B) ^ A ) & ((A | B) ^ B) --> 0

https://alive2.llvm.org/ce/z/z38w2w

Diff Detail

Event Timeline

MehrHeidar created this revision.Dec 14 2021, 1:08 PM
MehrHeidar requested review of this revision.Dec 14 2021, 1:08 PM
Herald added a project: Restricted Project. · View Herald TranscriptDec 14 2021, 1:08 PM
MehrHeidar edited the summary of this revision. (Show Details)Dec 14 2021, 1:11 PM
MehrHeidar added reviewers: foad, spatel.
MehrHeidar added a reviewer: rampitec.
rampitec added inline comments.Dec 14 2021, 2:21 PM
llvm/lib/Analysis/InstructionSimplify.cpp
2176

Could you please use X and Y in the comment, same as in the match?

2177

Commuted 'or' does not do anything, it will always match as just 'm_Or'.

2180

You could use 'm_CombineOr' for the LHS to select either deferred X or deferred Y. Then you do not need second expression.

llvm/test/Transforms/InstSimplify/and.ll
168

'or' is more complex than the argument, the xor will be commuted.
See InstCombiner::getComplexity() for the details. Search for "thwart complexity-based canonicalization" in the llvm/test/Transforms/InstCombine directory for test coverage that works around it.

spatel added inline comments.Dec 15 2021, 5:07 AM
llvm/test/Transforms/InstSimplify/and.ll
168

This is instsimplify, so we don't have to worry about the pass itself altering the input (this is shown in the baseline CHECK lines).

rampitec added inline comments.Dec 15 2021, 9:20 AM
llvm/test/Transforms/InstSimplify/and.ll
168

Ok, thanks.

Address review comments.

MehrHeidar marked 5 inline comments as done.Dec 15 2021, 12:51 PM
MehrHeidar added inline comments.
llvm/lib/Analysis/InstructionSimplify.cpp
2180

I replaced the comments and changed the usage of A/B with X/Y.
Also, I tried to use the idea for usage of m_CombineOr to remove the second expression.

llvm/test/Transforms/InstSimplify/and.ll
168

I think the test coverage looks fine, based on @spatel comment,

Quoted Text

Right?

168

I think according to the comment the test coverage is fine.

This is instsimplify, so we don't have to worry about the pass itself altering the input (this is shown in the baseline CHECK lines).

Right?

rampitec added inline comments.Dec 15 2021, 1:15 PM
llvm/lib/Analysis/InstructionSimplify.cpp
2180

Second match shall not use m_CombineOr. Now you would incorrectly match ((X | Y) ^ X ) & ((X | Y) ^ X). Speaking of which it deserves a negative test.

llvm/test/Transforms/InstSimplify/and.ll
168

Yes, but there are 2 more commutes: (a ^ (a | b)) & ((a | b) ^ b) and ((a | b) ^ b) & (a ^ (a | b))

Add negative test.

MehrHeidar marked an inline comment as done.Dec 15 2021, 2:15 PM
MehrHeidar added inline comments.
llvm/lib/Analysis/InstructionSimplify.cpp
2180

If I don't put the m_CombineOr on the second match, we won't be able to catch all commutes. I added a negative test now to see it won't incorrectly match your example.

I am not so confident with the current code too and prefer my first version of this patch. What do you think @rampitec ?

rampitec added inline comments.Dec 15 2021, 2:16 PM
llvm/test/Transforms/InstSimplify/and.ll
187

It shall not be '0'. Comment is misleading.

189

This is not commute. This is a wrong operand.

191

And in fact the test does not catch it. I think 'and' was simply dropped earlier. Maybe a duplicated 'or' will expose it or maybe not.

rampitec added inline comments.Dec 15 2021, 2:17 PM
llvm/lib/Analysis/InstructionSimplify.cpp
2180

m_CombineOr is not about commute. At the point of second match you already know your X and Y exactly.

MehrHeidar marked an inline comment as done.

Revert to first version and add two test cases

MehrHeidar marked 2 inline comments as done.Dec 16 2021, 2:14 PM
MehrHeidar added inline comments.
llvm/lib/Analysis/InstructionSimplify.cpp
2180

As I mentioned before, not using m_CombineOr doesn't allow me to catch all versions, so I reverted back to first version that I had.

llvm/test/Transforms/InstSimplify/and.ll
168

Right, thanks!
Added two more commutes.

191

Agree! I only added the previous test for the sake of your previous comment, I was not going to commit that=)
Thank you! Changed the code so it's not a concern anymore.

MehrHeidar marked 2 inline comments as done.Dec 20 2021, 10:24 AM
MehrHeidar added inline comments.
llvm/lib/Analysis/InstructionSimplify.cpp
2180

@rampitec Would you please take a look at the patch again and check whether it's ok or not?
Thanks

rampitec added inline comments.Dec 20 2021, 11:53 AM
llvm/lib/Analysis/InstructionSimplify.cpp
2177

I believe commuted Or is not needed here.

2180

I still do not see why m_CombineOr does not work, can you give an example?

MehrHeidar added inline comments.Dec 20 2021, 1:04 PM
llvm/lib/Analysis/InstructionSimplify.cpp
2180

Sure, if I change the pattern match to be

match(Op0, m_c_Xor(m_c_Or(m_Value(X), m_Value(Y)), m_CombineOr(m_Deferred(X), m_Deferred(Y))) &&
      match(Op1, m_c_Xor(m_c_Or(m_Specific(X), m_Specific(Y)), m_Specific(X)))
     return Constant::getNullValue(Op0->getType());

I can't match this case ((X | Y) ^ X ) & ((X | Y) ^ Y) ;
If I change it to

match(Op0, m_c_Xor(m_c_Or(m_Value(X), m_Value(Y)), m_CombineOr(m_Deferred(X), m_Deferred(Y))) &&
      match(Op1, m_c_Xor(m_c_Or(m_Specific(X), m_Specific(Y)), m_Specific(Y)))
     return Constant::getNullValue(Op0->getType());

I can't match this case ((X | Y) ^ Y ) & ((X | Y) ^ X) ; And, if I use m_CombineOr on second match it's going to incorrectly match ((X | Y) ^ X ) & ((X | Y) ^ X) this pattern.

So, I couldn't fully cover all cases.

MehrHeidar marked an inline comment as done.Dec 20 2021, 1:04 PM

There are still 2 comments around tests.

llvm/lib/Analysis/InstructionSimplify.cpp
2180

OK, I am not sure why but let it be.

The code seems more complicated than necessary (and the tests less thorough than necessary).
We know that the 'or' instruction is a shared op, so let's match that first and then match it again as a specific value?
If I'm seeing it correctly, that means there are 8 potential commutes:

BinaryOperator *Or;
if (match(Op0, m_c_Xor(m_Value(X), m_BinOp(Or))))
  if (match(Or, m_c_Or(m_Specific(X), m_Value(Y))) &&
      match(Op1, m_c_Xor(m_Specific(Or), m_Specific(Y))))
    return Constant::getNullValue(Op0->getType());

The code seems more complicated than necessary (and the tests less thorough than necessary).
We know that the 'or' instruction is a shared op, so let's match that first and then match it again as a specific value?
If I'm seeing it correctly, that means there are 8 potential commutes:

BinaryOperator *Or;
if (match(Op0, m_c_Xor(m_Value(X), m_BinOp(Or))))
  if (match(Or, m_c_Or(m_Specific(X), m_Value(Y))) &&
      match(Op1, m_c_Xor(m_Specific(Or), m_Specific(Y))))
    return Constant::getNullValue(Op0->getType());

On 2nd thought, that might not be complicated enough. :)
I think this could match a "wrong" binop first (for example Y is an add instruction) and then fail to match the pattern (so we could use even more tests...).
We probably need to use m_CombineAnd to get this to accurately match Op0 and capture the 'or' in one shot:

BinaryOperator *Or;
if (match(Op0, m_c_Xor(m_Value(X),
                       m_CombineAnd(m_BinOp(Or),
                                    m_c_Or(m_Deferred(X), m_Value(Y))))) &&
    match(Op1, m_c_Xor(m_Specific(Or), m_Specific(Y))))
  return Constant::getNullValue(Op0->getType());

The code seems more complicated than necessary (and the tests less thorough than necessary).
We know that the 'or' instruction is a shared op, so let's match that first and then match it again as a specific value?
If I'm seeing it correctly, that means there are 8 potential commutes:

BinaryOperator *Or;
if (match(Op0, m_c_Xor(m_Value(X), m_BinOp(Or))))
  if (match(Or, m_c_Or(m_Specific(X), m_Value(Y))) &&
      match(Op1, m_c_Xor(m_Specific(Or), m_Specific(Y))))
    return Constant::getNullValue(Op0->getType());

On 2nd thought, that might not be complicated enough. :)
I think this could match a "wrong" binop first (for example Y is an add instruction) and then fail to match the pattern (so we could use even more tests...).
We probably need to use m_CombineAnd to get this to accurately match Op0 and capture the 'or' in one shot:

BinaryOperator *Or;
if (match(Op0, m_c_Xor(m_Value(X),
                       m_CombineAnd(m_BinOp(Or),
                                    m_c_Or(m_Deferred(X), m_Value(Y))))) &&
    match(Op1, m_c_Xor(m_Specific(Or), m_Specific(Y))))
  return Constant::getNullValue(Op0->getType());

The code seems more complicated than necessary (and the tests less thorough than necessary).
We know that the 'or' instruction is a shared op, so let's match that first and then match it again as a specific value?
If I'm seeing it correctly, that means there are 8 potential commutes:

BinaryOperator *Or;
if (match(Op0, m_c_Xor(m_Value(X), m_BinOp(Or))))
  if (match(Or, m_c_Or(m_Specific(X), m_Value(Y))) &&
      match(Op1, m_c_Xor(m_Specific(Or), m_Specific(Y))))
    return Constant::getNullValue(Op0->getType());

On 2nd thought, that might not be complicated enough. :)
I think this could match a "wrong" binop first (for example Y is an add instruction) and then fail to match the pattern (so we could use even more tests...).
We probably need to use m_CombineAnd to get this to accurately match Op0 and capture the 'or' in one shot:

BinaryOperator *Or;
if (match(Op0, m_c_Xor(m_Value(X),
                       m_CombineAnd(m_BinOp(Or),
                                    m_c_Or(m_Deferred(X), m_Value(Y))))) &&
    match(Op1, m_c_Xor(m_Specific(Or), m_Specific(Y))))
  return Constant::getNullValue(Op0->getType());

Thank you!
But, this one is not complicated enough to catch this pattern : ) -->(Y ^ (Y | X) ) & ((X | Y) ^ X) --> 0

%or1 = or i32 %y, %x
 %or2 = or i32 %x, %y
 %xor1 = xor i32 %y, %or1
 %xor2 = xor i32 %or2, %x
 %and = and i32 %xor1, %xor2
 ret i32 %and

But, this one is not complicated enough to catch this pattern : ) -->(Y ^ (Y | X) ) & ((X | Y) ^ X) --> 0

%or1 = or i32 %y, %x
 %or2 = or i32 %x, %y
 %xor1 = xor i32 %y, %or1
 %xor2 = xor i32 %or2, %x
 %and = and i32 %xor1, %xor2
 ret i32 %and

Unless we have some evidence that this pattern can escape a typical -O1 pipeline, I don't think we need to care about it. %or1 and %or2 should be CSE'd independently of instsimplify. In other words, I think the positive tests only need to include a single 'or' instruction. If you want to include a test with different 'or' instructions to show the limits of instsimplify, that's fine.

MehrHeidar marked 3 inline comments as done.

Adding more tests and revising the code for pattern match.

Fix name issue in test cases.

MehrHeidar marked 3 inline comments as done.Dec 21 2021, 8:21 AM
MehrHeidar added inline comments.
llvm/lib/Analysis/InstructionSimplify.cpp
2180

Changed the code according to @spatel recommendation.

llvm/test/Transforms/InstSimplify/and.ll
191

These comments were related to a test that I added temporary; so they are not valid anymore.

MehrHeidar marked an inline comment as done.Dec 21 2021, 8:21 AM

But, this one is not complicated enough to catch this pattern : ) -->(Y ^ (Y | X) ) & ((X | Y) ^ X) --> 0

%or1 = or i32 %y, %x
 %or2 = or i32 %x, %y
 %xor1 = xor i32 %y, %or1
 %xor2 = xor i32 %or2, %x
 %and = and i32 %xor1, %xor2
 ret i32 %and

Unless we have some evidence that this pattern can escape a typical -O1 pipeline, I don't think we need to care about it. %or1 and %or2 should be CSE'd independently of instsimplify. In other words, I think the positive tests only need to include a single 'or' instruction. If you want to include a test with different 'or' instructions to show the limits of instsimplify, that's fine.

Thank you for explaining the reason behind this. I checked the test case and it did get simplified and we ended up with a single or; So I updated the code and test cases.

spatel accepted this revision.Dec 21 2021, 2:08 PM

LGTM - but please wait to push until @rampitec has another look.

This revision is now accepted and ready to land.Dec 21 2021, 2:08 PM

LGTM - but please wait to push until @rampitec has another look.

Sure, Thank you!

rampitec added inline comments.Dec 21 2021, 2:37 PM
llvm/lib/Analysis/InstructionSimplify.cpp
2179

It may probably fail to match if X is an 'or' itself. Just an independent 'or'. If I am not mistaken in this case it will not be important what to record as X and what as Y for LHS. Looks like yet another limitation. But I guess we have a lot of such cases anyway, so LGTM.

rampitec accepted this revision.Dec 21 2021, 3:01 PM
This revision was landed with ongoing or failed builds.Dec 23 2021, 7:03 AM
This revision was automatically updated to reflect the committed changes.