This is an archive of the discontinued LLVM Phabricator instance.

[SCEVExpander] Be more conservative about poison flags when reusing instructions
AbandonedPublic

Authored by reames on Oct 27 2021, 10:45 AM.

Details

Summary

Noticed while reviewing D112389. Unfortunately, I don't have a good test case, just noticed during code review.

The basic problem we have is that we're trying to reuse an instruction which is mapped to some SCEV. Since we can have multiple such instructions (potentially with different flags), this is analogous to our need to drop flags when performing CSE. A trivial implementation would simply drop flags on any instruction we decided to reuse, and that would be correct.

However, we tackle the problem a bit differently. If we can prove that *all* instructions which map to the SCEV could validly have the flags on the instruction, then we can simply reuse I with those flags in place. The proof of all instructions depends on the defining scope notion and how we define what flags are valid on a SCEV to start with.

In practice, this fixes two conceptual problems with the previous code: 1) a binop could have been canonicalized into a form where there was no binop left, or 2) the inbounds GEP case which was simply unhandled.

Diff Detail

Event Timeline

reames created this revision.Oct 27 2021, 10:45 AM
reames requested review of this revision.Oct 27 2021, 10:45 AM
Herald added a project: Restricted Project. · View Herald TranscriptOct 27 2021, 10:45 AM
reames planned changes to this revision.Oct 27 2021, 11:51 AM

Don't bother to review just yet. In the process of doing a rebase over a landed change, and noticed a bug in the inbounds handling here (swapped conditional).

nikic added inline comments.Oct 27 2021, 12:32 PM
llvm/lib/Transforms/Utils/ScalarEvolutionExpander.cpp
1855

This comment confuses me. We cannot have used the flags on the binop unless they always hold (via poison UB reasoning). The conclusion is still correct in that we simply don't know whether the flags are always valid or not.

However, the problem goes beyond this. Just because you have some OBO and some SCEV with nowrap flags, does not mean that these flags have the same meaning. For example, consider

%a = add %x, 2
%b = add nuw %a, -1

in a context where %b is unused and %x has known range [0,-2]. The SCEV for %b is going to be %x +<nuw> 1 after folding the adds together and using range-based flag strengthening. However, the add nuw %a, -1 is clearly going to unsigned wrap for all values of %x other than -2. The problem here is that while both the IR instruction and the SCEV have the nuw flag, they also have different operands. One is %a - 1, the other is %x + 1.

The "proper" way to determine that the wrap flags really hold is to do the ext(x + y) == ext(x) + ext(y) dance, but that's really expensive and I'm not sure it's desirable to created these kinds of expression in the expander (especially as they have side-effects on nowrap flags).

reames added a comment.EditedOct 27 2021, 12:54 PM

Realized after some further thought, that the entire approach here is potentially unsound.

Counter example:
%x = add nsw nuw %a, %b
call maythrow()
%y = mul nuw 32 %x, 1

Both the current code and my proposed fix would use the flags inferred from %x, to allow reuse of ^y. In this particular case, that happens to be a valid result, but the reasoning is highly suspect. The basic problem is that the flags on the SCEV may apply to a different operation type than the instruction. Mapping e.g. mul to add or vice versa is not obviously correct.

I'm going to give this one a bit more thought to see if there's a better approach.

Edit: I'd missed Nikita's comment, this basically just says the same thing less well explained than his.