This is an archive of the discontinued LLVM Phabricator instance.

[InstSimplify] Simplify icmp between Shl instructions of the same value
ClosedPublic

Authored by MattDevereau on Jan 25 2023, 6:55 AM.

Details

Summary

[InstSimplify] Simplify icmp between Shl instructions of the same value

define i1 @compare_vscales() {
  %vscale = call i64 @llvm.vscale.i64()
  %vscalex2 = shl nuw nsw i64 %vscale, 1
  %vscalex4 = shl nuw nsw i64 %vscale, 2
  %cmp = icmp ult i64 %vscalex2, %vscalex4
  ret i1 %cmp
}

This IR is currently emitted by LLVM. This icmp is redundant as this snippet
can be simplified to true or false as both operands originate from the same
@llvm.vscale.i64() call.

Proof of concept:
https://alive2.llvm.org/ce/z/KCrE8j
https://alive2.llvm.org/ce/z/wH8vBN

Diff Detail

Event Timeline

MattDevereau created this revision.Jan 25 2023, 6:55 AM
Herald added a project: Restricted Project. · View Herald TranscriptJan 25 2023, 6:55 AM
MattDevereau requested review of this revision.Jan 25 2023, 6:55 AM
Herald added a project: Restricted Project. · View Herald TranscriptJan 25 2023, 6:55 AM
MattDevereau edited the summary of this revision. (Show Details)Jan 25 2023, 6:55 AM
sdesmalen added inline comments.Jan 25 2023, 7:05 AM
llvm/lib/Analysis/InstructionSimplify.cpp
3652

Does it matter that this is value is vscale ?

I would expect that:

  • (X << C1) < (X << C2) == true for any value X for any C1 < C2
  • (X << C1) <= (X << C2) == true for any value X for any C1 <= C2
  • (X << C1) > (X << C2) == true for any value X for any C1 > C2
  • ...
MattDevereau added inline comments.Jan 25 2023, 7:19 AM
llvm/lib/Analysis/InstructionSimplify.cpp
3652

Very true, I think I got a bit too tunnel visioned on the original test case. I will replace the vscale checks with m_Value for LHS and m_Deferred for RHS, and remove any references to vscale in names

dmgreen added inline comments.
llvm/lib/Analysis/InstructionSimplify.cpp
3652

Make sure you check the value isn't 0, and it can be good to provide alive proofs.

MattDevereau added inline comments.Jan 25 2023, 7:31 AM
llvm/lib/Analysis/InstructionSimplify.cpp
3652

Noted, thank you

MattDevereau added inline comments.Jan 25 2023, 8:55 AM
llvm/lib/Analysis/InstructionSimplify.cpp
3652

https://alive2.llvm.org/ce/z/UAsDSn This alive test shows the transformation is not valid for the case where value is 0, however
I'm not sure how to write a test to assert the not-zero 0 check here, something simple like

define i1 @compare_zeroes_foo() {
; CHECK-LABEL: @compare_zeroes_foo(
; CHECK-NEXT:    ret i1 false
;
  %shl1 = shl nuw nsw i64 0, 1
  %shl2 = shl nuw nsw i64 0, 2
  %cmp = icmp ult i64 %shl1, %shl2
  ret i1 %cmp
}

Has already optimized away the shifts by the time it reaches my added function simplifyICmpLShiftedValues, and when LHS and RHS are both ConstantInt 0 this will be optimized to return i1 false by ConstantFoldCompareInstOperands above. I'm not sure if a simple check such as

  ConstantInt *IntLHS, *IntRHS;
  Value* V;
  if (!match(LHS, m_Shl(m_Value(V), m_ConstantInt(IntLHS))) ||
      !match(RHS, m_Shl(m_Deferred(V), m_ConstantInt(IntRHS))) ||
      LHS->getType() != RHS->getType())
    return nullptr;

  if(auto ConstantIntV = dyn_cast<ConstantInt>(V)){
    if(ConstantIntV->isZero())
      return nullptr;
  }
...

Is sufficient or even necessary here.

To generalize the pattern, you probably want to use isKnownNonZero() to check that the shifted value always has some bits set. That function appears to already know that the result of a vscale call is always non zero.
The simplest regression test to verify that would include an or with constant to set some bit(s).

The larger shift needs to have "nuw" (don't shift out any set bits) to guarantee that an unsigned comparison holds; "nuw" and "nsw" are needed to reduce a signed comparison?
Here's a generalized proof for 'ugt':
https://alive2.llvm.org/ce/z/Q6J6qn
You'd need to adapt that for each predicate to make sure we have the right constraints on the transform.

nikic added a subscriber: nikic.Jan 30 2023, 3:59 AM
nikic added inline comments.
llvm/lib/Analysis/InstructionSimplify.cpp
3654

Prefer m_APInt

3658

Use cast<OverflowingBinaryOperator>, otherwise this might crash for constant expressions.

3692

There is an ICmpInst::compare() helper that takes a predicate and APInts -- this code can probably be simplified based on that?

3962

Should probably be called from simplifyICmpWithBinOp. Also, it looks like a very similar fold already exists there: https://github.com/llvm/llvm-project/blob/bde5d31e96f556fed30bf9120cc6ff05a2116b64/llvm/lib/Analysis/InstructionSimplify.cpp#L3426-L3436 This seems to be the same, just on different operands of the shift. Possibly that could serve as inspiration on how to implement this more compactly.

MattDevereau retitled this revision from [InstSimplify] Simplify icmp between Left-Shifted VScale Calls to [InstSimplify] Simplify icmp between Shl instructions of the same value.
MattDevereau edited the summary of this revision. (Show Details)
MattDevereau marked 3 inline comments as done.Jan 31 2023, 6:00 AM
MattDevereau added inline comments.
llvm/lib/Analysis/InstructionSimplify.cpp
3692

ICmpInst::compare() works perfectly, thank you

3962

I've moved this to be inside simplifyICmpWithBinOp and close to where you suggested. However this required checking the 0th operands of the binops for equality instead of comparing the 1st operands of the binops like where you suggested did.

nikic added inline comments.Jan 31 2023, 6:08 AM
llvm/lib/Analysis/InstructionSimplify.cpp
3264

I don't think the types can differ.

3273

These checks should go through Q.IIQ.

3281

I believe we could return simplifyICmpInst(Pred, ShiftAmountL, ShiftAmountR, Q, MaxRecurse -1) here, without the limitation to constant shift amounts. The only disadvantage to that would be that we would have to require the nuw/nsw flags on both shifts, rather than only on the largest one (as we wouldn't know which one is the largest is without further effort).

This would allow handling comparisons between shl %v, %s and %shl %v, (add nuw %s, 1) or similar.

MattDevereau marked 2 inline comments as done.
MattDevereau marked an inline comment as done.Feb 1 2023, 8:22 AM
MattDevereau added inline comments.
llvm/lib/Analysis/InstructionSimplify.cpp
3273

I've made them use Q.IIQ now, and I've moved the checks a level higher, since similar code in simplifyICmpWithBinOp exists for when the shift amounts are equal for an ICmp with two shl operands there.

3281

Hopefully I've understood what you mean here and implemented this correctly. I inverted the logic of the matches and replaced return getFalse(getCompareTy(LHS)); with your suggestion which works ok, however returning your suggestion for any non match caused several test failures elsewhere and resulted in some miscompiles

sdesmalen added inline comments.Feb 14 2023, 7:58 AM
llvm/lib/Analysis/InstructionSimplify.cpp
3258

This function isn't really needed, see my other comment further down for details.

3423

It might be useful to bail out here if this part of the condition is false, such that you can have two indented blocks below:

if (!MaxRecurse || !LBO || !RBO || LBO->getOpcode() != RBO->getOpcode())
  return nullptr;

if (LBO->getOperand(0) == RBO->getOperand(0)) {
  ..
}

if (LBO->getOperand(1) == RBO->getOperand(1)) {
  ..
}

return nullptr;

That avoids having to indent the existing block (LBO->getOperand(1) == RBO->getOperand(1) ) below.

3436

Rather than calling simplifyICmpWithShl here, you can just compare the shift-amounts directly, i.e.

if (Value *V = simplifyICmpInst(Pred, LBO->getOperand(1),
                               RBO->getOperand(1), Q, MaxRecurse - 1))
 return V;

You only need to add an extra check to ensure that LBO->getOperand(0) is known non-zero.

MattDevereau marked an inline comment as done.

@sdesmalen Thanks, I've put in your comments, the patch looks a lot more compact now.

Can you please include the relevant alive2 proofs for the transform in the patch description? Should be something along the lines of https://alive2.llvm.org/ce/z/KCrE8j and https://alive2.llvm.org/ce/z/wH8vBN I think. Notably, a signed predicate requires *both* nuw and nsw. I don't think this is correctly enforced right now?

Can you please also pre-commit the tests?

sdesmalen added inline comments.Feb 15 2023, 12:55 AM
llvm/test/Transforms/InstSimplify/compare.ll
2918

nit: this name is a bit confusing, I'm not sure what you mean with 'nuw_nsw_smallest_shift' ?

2933

Can you just use %x and %y instead of @llvm.vscale here?

It would be good also to have at least one positive test with @llvm.vscale(), could you please add one?

MattDevereau added inline comments.Feb 15 2023, 1:10 AM
llvm/test/Transforms/InstSimplify/compare.ll
2918

The idea here was that based on https://alive2.llvm.org/ce/z/YCbQCH, only the larger shift value needed to have nuw/(nuw && nsw) flags on it for the combine to be valid. However it's probably easier and more versatile to do the more generic combine (https://alive2.llvm.org/ce/z/tSyv_m), so this test can be replaced with something more appropriate

2933

Sure, I will add this and move the tests into a different patch and pre-commit them

MattDevereau marked 2 inline comments as done.
MattDevereau edited the summary of this revision. (Show Details)

@nikic I've precommited the tests, and added your alive examples to the description. I've changed the flag condition to if (!NUW || (ICmpInst::isSigned(Pred) && !NSW)to assert NUW for unsigned and NUW and NSW for signed. I've added two tests neg_icmp_lshr_known_non_zero_slt_no_nuw and neg_icmp_lshr_known_non_zero_ult_no_nuw which assert the correct flag conditions.

nikic accepted this revision.Feb 15 2023, 6:58 AM

LGTM

llvm/test/Transforms/InstSimplify/compare.ll
2886

Based on test name, this was supposed to be ult?

This revision is now accepted and ready to land.Feb 15 2023, 6:58 AM
sdesmalen accepted this revision.Feb 15 2023, 7:12 AM
MattDevereau added a comment.EditedFeb 15 2023, 7:45 AM
This comment has been deleted.
llvm/test/Transforms/InstSimplify/compare.ll
2886

Correct, I will update this before pushing, thank you.