This is an archive of the discontinued LLVM Phabricator instance.

[ValueTracking] Take signedness into account for and/or ranges
AbandonedPublic

Authored by nikic on Mar 21 2019, 2:22 PM.

Details

Reviewers
lebedev.ri
spatel
Summary

While trying to extend D59386 to signed add/sub I ran into quite a few issues ... unfortunately our range handling is quite biased in favor of unsigned ranges right now and it'll be necessary to thread preferred signedness through various APIs to get reasonable results.

This is the first step in this direction. computeConstantRange() gets a preferred signedness flag, which is respected for and/or range calculations. InstSimplify calls computeConstantRange() with the signedness of the predicate. This allows us to simplify signed icmps of and/or in InstSimplify.

Diff Detail

Event Timeline

nikic created this revision.Mar 21 2019, 2:22 PM

Ok, i have to ask.
Is there some alternative way that does not require passing ForSigned all the way?

  • Operate on a single range, and reinterpret it somehow when we know the sign matters?
  • Always track two ranges, and use the one we need?
nikic added a comment.Mar 23 2019, 6:56 AM

Ok, i have to ask.
Is there some alternative way that does not require passing ForSigned all the way?

  • Operate on a single range, and reinterpret it somehow when we know the sign matters?
  • Always track two ranges, and use the one we need?

Good question! Let me start with what we can't do: It's not possible to have a single range and reinterpret it for signed and unsigned purposes. Taking the example of known bits 0b..00, we can either interpret this is an unsigned range [0b0000, 0b1100] or a signed range [0b1000, 0b0100]. Here is a drawing of the unsigned (u) and signed (s) ranges, as well as the values (x) which are actually possible:

0b0000  u s x
0b0001  u s
0b0010  u s
0b0011  u s
0b0100  u s x
0b0101  u
0b0110  u
0b0111  u
0b1000  u s x
0b1001  u s
0b1010  u s
0b1011  u s
0b1100  u s x
0b1101    s
0b1110    s
0b1111    s

As you can see, both ranges cover all the x's, but in different ways: One without wrapping the unsigned domain, the other without wrapping the signed domain. Once we've arrived at this representation, we can no longer switch between them, because we have lost the information that the sign bit can be arbitrary.

There are a couple of different ways we can deal with this:

  1. As you have already suggested, we can store both signed and unsigned ranges and just use whichever will be more convenient in the end. This would work, but it has rather high costs. We'd have to store two ranges everywhere and perform all calculations on two ranges. In many cases both ranges would end up being the same -- while you can interpret a known bits result without fixed sign bit in two ways, there's no two ways to interpret a urem result.
  1. Pass a preferred signedness flag, which is what is done here. This is already common practice in some other parts of the codebase that deal with ranges, e.g. SCEV passes a SignHint through the range-based functionality. A flag allows us to choose an appropriate range when beneficial, but all calculations are still performed on only one range. (As a sidenote: While SCEV just passes this flag through, for recursive operations you don't necessarily want to do that. If you are computing a umax range for a signed context, you will still want to compute the ranges of the umax operands as unsigned. This is something that has to be decided per-operation, there is no general signedness context for the whole calculation.)
  1. Some kind of hybrid representation that is not based on pure ranges. For example, we could store that the first N bits are undetermined and only store the range for the lower BW-N bits. If N>0 this would allow recovering both the signed and unsigned ranges for the case above. I'm not sure how general this would be, as it wouldn't be able to handle cases where the signed and unsigned ranges are not the same after truncation. Of course, this would also require rewriting the entire ConstantRange machinery from scratch :)
lebedev.ri requested changes to this revision.Mar 27 2019, 2:45 PM
lebedev.ri added inline comments.
llvm/test/Transforms/InstSimplify/icmp-constant.ll
495–503

This is a miscompile, i believe.
https://rise4fun.com/Alive/R6k

----------------------------------------
Optimization: 1

ERROR: Mismatch in values of i1 %z

Example:
%x i8 = 0x80 (128, -128)
%y i8 = 0x80 (128, -128)
Source value: 0x0 (0)
Target value: 0x1 (1, -1)
This revision now requires changes to proceed.Mar 27 2019, 2:45 PM
nikic marked an inline comment as done.Mar 27 2019, 3:04 PM
nikic added inline comments.
llvm/test/Transforms/InstSimplify/icmp-constant.ll
495–503

Wow, thanks for catching that! What I did with the lower bound for and here doesn't make sense, it should just be SignedMin, independent of the constant.

I'm wondering if I should maybe move these calculations to ConstantRange, so they can be independently (and exhaustively) tested. The calculations here are quite specific though, in that they compute the range when combining a full range and a single constant. Something like ConstantRange::forBinaryAndWithConst() and friends?

lebedev.ri added inline comments.Mar 27 2019, 3:15 PM
llvm/test/Transforms/InstSimplify/icmp-constant.ll
495–503

I'm not quite sure how to best expose that (ConstantRange::forBinaryAndWithConst()
is a reasonable intermediate solution), but i *really* *really* like the idea of
separating them into small functions with exhaustive test coverage.

nikic abandoned this revision.May 10 2019, 1:26 PM

I'm going to drop this for now -- the original motivation here went away with the signed intersection support. These changes still make sense to improve icmp instsimplify, but are also not super valuable in that instcombine can handle it.