This is an archive of the discontinued LLVM Phabricator instance.

[NARY-REASSOCIATE] Fix infinite recursion optimizing min\max
ClosedPublic

Authored by ebrevnov on Oct 19 2021, 3:15 AM.

Details

Summary

To guarantee convergence of the algorithm each optimization step should decrease number of instructions when IR is modified. This property is not held in this test case. The problem is that SCEV Expander may do "unexpected" reassociation what results in creation of new min/max chains and introduction of extra instructions. As a result on each step we indefinitely optimize back and forth.

The solution is to restrict SCEV Expander to perform uncontrolled reassociations by means of "Unknown" expressions.

Diff Detail

Event Timeline

ebrevnov created this revision.Oct 19 2021, 3:15 AM
ebrevnov requested review of this revision.Oct 19 2021, 3:15 AM
Herald added a project: Restricted Project. · View Herald TranscriptOct 19 2021, 3:15 AM
spatel added reviewers: lebedev.ri, nikic.EditedOct 19 2021, 6:17 AM

I've never looked at this pass closely before, so another reviewer should comment too.
It may be independent of this patch, but I find the use of PatternMatch internals ("bind_ty", "smax_pred_ty", etc) confusing - why is that necessary?
Is this code going to stop being useful when we canonicalize to min/max intrinsics ( D98152 ) - it is specifically matching cmp/sel idioms only?

llvm/lib/Transforms/Scalar/NaryReassociate.cpp
615

This code comment doesn't match the code?

nikic accepted this revision.Oct 19 2021, 9:36 AM

The fix itself (using SCEVUnknown) looks good to me, though I agree that the whole implementation is pretty odd and could be cleaned up in a followup change.

llvm/lib/Transforms/Scalar/NaryReassociate.cpp
615

Something that also confuses me about this code is why it performs a loop with two iterations over j, and then has completely separate behavior for them. Wouldn't this whole loop be equivalent to the following?

if (BExpr != CExpr) {
  std::swap(BExpr, CExpr);
  std::swap(B, C);
}
if (AExpr != CExpr) {
  std::swap(AExpr, CExpr);
  std::swap(A, C);
}

Or if we allow some redundant swaps just:

std::swap(BExpr, CExpr);
std::swap(B, C);
std::swap(AExpr, CExpr);
std::swap(A, C);

Which reorders A B C into B C A.

Though as all of these variables are under your control I think you could just directly match them into the right variables rather than swapping them after the fact.

Am I missing something here?

642

You can pass these directly, no need to create SmallVector for an ArrayRef.

This revision is now accepted and ready to land.Oct 19 2021, 9:36 AM
ebrevnov added inline comments.Oct 19 2021, 10:53 PM
llvm/lib/Transforms/Scalar/NaryReassociate.cpp
615

This code comment doesn't match the code?

It does matches the code but not easy to follow. The trick is that pointers named A, B, and C refers to different expressions on each iteration. For example, after the first swap B points to what C used to point.... I agree all that looks confusing ... I will try to restructure the code to make it simpler for understanding.

615

Something that also confuses me about this code is why it performs a loop with two iterations over j, and then has completely separate behavior for them. Wouldn't this whole loop be equivalent to the following?

if (BExpr != CExpr) {
  std::swap(BExpr, CExpr);
  std::swap(B, C);
}
if (AExpr != CExpr) {
  std::swap(AExpr, CExpr);
  std::swap(A, C);
}

Or if we allow some redundant swaps just:

std::swap(BExpr, CExpr);
std::swap(B, C);
std::swap(AExpr, CExpr);
std::swap(A, C);

Which reorders A B C into B C A.

Though as all of these variables are under your control I think you could just directly match them into the right variables rather than swapping them after the fact.

Am I missing something here?

No, this is not semantically equivalent code. The idea is to try two combinations: (AopC)opB and then (BopC)opA. That's why the loop of two iterations.
I agree all that look confusing...I will try to restructure the code to make it simpler for understanding.

642

You can pass these directly, no need to create SmallVector for an ArrayRef.

Thanks. Will fix.

nikic added inline comments.Oct 20 2021, 12:37 AM
llvm/lib/Transforms/Scalar/NaryReassociate.cpp
615

No, this is not semantically equivalent code. The idea is to try two combinations: (AopC)opB and then (BopC)opA. That's why the loop of two iterations.

Uh yeah, I misread the code. For some reason I thought the j loop ends after the swaps, but there's more code after it :)

Maybe a clearer way to do this is to have a helper function with the actual logic, and then call it twice with arguments A, C, B and B, C, A?

ebrevnov added inline comments.Oct 20 2021, 3:07 AM
llvm/lib/Transforms/Scalar/NaryReassociate.cpp
615

Maybe a clearer way to do this is to have a helper function with the actual logic, and then call it twice with arguments A, C, B and B, C, A?

That's exactly how I was going to restructure the code. Here it is https://reviews.llvm.org/D112128