[llvm-dev] Improving SCEV's behavior around IR level no-wrap flags
Sanjoy Das via llvm-dev
llvm-dev at lists.llvm.org
Fri Sep 23 02:09:19 PDT 2016
Hi all,
This is about a project I've been prototyping on-and-off for a while
that has finally reached a point where I can claim it to be
"potentially viable". I'd like to gather some input from the
community before moving too far ahead.
# The problem
There is a representation issue within SCEV that prevents it from
fully using information from nsw/nuw flags present in the IR. This
isn't just a theoretical issue, e.g. today LLVM won't unroll this
loop:
void f(int x, long* arr) {
for (int i = x + 1; i < x + 3; i++)
arr[i] = 40;
}
since SCEV is unable to exploit the no-overflow on x+1 and x+3 to
prove that the loop only runs twice.
The fundamental problem here is that SCEV expressions are unique'd but
the nsw/nuw flags on SCEV expressions are not part of the key they're
unique'd by. Instead, nsw/nuw flags on SCEV expressions are expressed
by mutating the SCEV expressions in place.
This means "add %x, 1" and "add nsw %x, 1" both map to the _same_ SCEV
expression (that is, literally the same SCEV* object), and we can't
mutate the common SCEV expression to flag it as nsw since that will
incorrectly denote "add %x, 1" as nsw too.
In general, this means SCEV has to be very conservative about marking
SCEV expressions as no-wrap. In some cases (e.g. the loop above),
this ends up being excessively conservative.
One path forward is to have SCEV try to prove that if a certain
operation produces poison, the program definitely has undefined
behavior. This can let us mutate the corresponding SCEV objects to
pull the "nsw"-ness into SCEV. For instance, if we have
%x = load ...
%t = add i32 nsw %x, 1
%addr = gep(%array, %t)
store i32 0, %addr
%t2 = add i32 %x, 1
then transferring NSW to getSCEV(%t) is okay, since even though %t2
(which will be mapped to the same SCEV expression as %t) does not have
"nsw" on the instruction, we know adding 1 to %x cannot overflow since
the program would have UB otherwise.
Bjarke Roune has implemented some of this. However, this is difficult
to do for cases like the x+1 .. x+3 loop above without running a
control flow analysis over the entire function. And this approach
does not work in the presence of function calls or general control
flow, like
%x = load ...
%t = add i32 nsw %x, 1
call void @f()
%addr = gep(%array, %t)
store i32 0, %addr
or
%x = load ...
%t = add i32 nsw %x, 1
if (<condition>)
return;
%addr = gep(%array, %t)
store i32 0, %addr
since unless the side-effecting use of %t (the store) "strongly"[1]
post dominates the def of %x, there is no guaranteed undefined
behavior on a poisonous %t. Things are even more complex if %x is not
a load, but an expression SCEV an look through, like an add or a shift
by a constant.
*I think the current representation of nsw/nuw in SCEV expressions is
not congruent with LLVM's specification of poison values, and that is
blocking us from exploiting poison values as intended by LLVM's
design.*
# The proposed solution
Since poison values are, well, _values_, I propose we model them as
data within SCEV. We treat nsw/nuw flags as "operands" since they
contribute to the result of an SCEV expression just like normal inputs
to the expression.
This means we'd treat "add %x, %y" as a different SCEV expression than
"add nsw %x, %y", since the latter sometimes produces poison while the
former doesn't. The latter would be known to not overflow, and SCEV
would use that fact in the usual ways.
With this change SCEV expressions will be pointer equal less often,
and while relying on pointer equality for value equality will be
correct, it will be somewhat pessimistic; and we'll have to implement
and use some form of structural equality.
In other words, some places that do
SCEV *X = ...
SCEV *Y = ...
if (X == Y)
...
will have to be changed to do
SCEV *X = ...
SCEV *Y = ...
if (X->equals(Y))
...
This has potential for compile-time regressions. Hopefully they'll
all be addressable.
There are cases in which SCEV (via trip count analysis, say) can
_prove_ that a certain expression does not overflow. In those cases
we will mutate the SCEV expression to indicate no-wrap; since the
no-wrap flag is just a "cache" of a proof based on the structure of
the SCEV expression, and _does_ apply to all SCEV expressions with the
same shapes.
Concretely, we'll endow relevant SCEV expression types with two sets
distinct of flags:
- AxiomaticFlags: These flags follow from nsw/nuw annotations in the
IR. These will be part of the key the SCEV expression is unique'd
on.
- ComputedFlags: These flags are derived from the structure of the
SCEV expression, and they're *not* a part of the key the SCEV
expression is unique'd on.
For the purposes of consumption, there will be no difference between
AxiomaticFlags and ComputedFlags. Consumers will get a union of the
two when they ask for the set of flags that apply to a specific SCEV
expression.
ComputedFlags will, in general, depend on AxiomaticFlags. For
instance if AxiomaticFlags is "nsw" for, say, {1,+,1}, we can add
"nuw" to its ComputedFlags. There is no need to further distinguish
"{1,+,1}-axiomatic<nsw>" on the computed<nuw> dimension since
"{1,+,1}-axiomatic<nsw>" will always be computed<nuw>.
What do you think? Does the overall picture here make sense?
Alternate solutions are also more than welcome (especially if they're
easier to implement!).
Thanks,
-- Sanjoy
[1]: That is, it the store is guaranteed to execute once the load has
been issued.
More information about the llvm-dev
mailing list