[llvm-dev] [RFC] How to manifest information in LLVM-IR, or, revisiting llvm.assume

John McCall via llvm-dev llvm-dev at lists.llvm.org
Wed Dec 18 11:54:21 PST 2019


On 18 Dec 2019, at 14:18, Doerfert, Johannes wrote:
> Hi John,
>
> Is it correct to assume that you are in favor of
>   - changing llvm.assume to be more expressive
>   - operand bundle uses, especially w/o outlining
>   - outlining, if we can show it plays well with transformations, e.g.
>     the binding to the code is "weak"

Yes, this is all correct.  I’m excited that we’re looking into 
making this
less useless.

> I inlined more comments below.
>
> On 12/18, John McCall wrote:
>> On 18 Dec 2019, at 11:28, Doerfert, Johannes wrote:
>>
>>> On 12/18, John McCall wrote:
>>>> On 16 Dec 2019, at 18:16, Doerfert, Johannes via llvm-dev wrote:
>>>>> Abstract:
>>>>>
>>>>> It is often hard or impossible to encode complex, e.g., 
>>>>> non-boolean,
>>>>> information in an `llvm.assume(i1)`. This RFC describes various 
>>>>> problems
>>>>> we have right now and provides alternative design ideas.
>>>>>
>>>>>
>>>>>
>>>>> Some Existing Problems:
>>>>>
>>>>> A) The boolean requirement.
>>>>>   The current `llvm.assume(i1)` expects a boolean that is known to 
>>>>> hold
>>>>>   true at runtime (once the `llvm.assume` call is reached). 
>>>>> However,
>>>>>   forming this boolean for "arbitrary" information is hard or even
>>>>>   impossible. Alignment, which is an easy case, already requires 3 
>>>>> extra
>>>>>   instructions, one of which is a `ptrtoint` and therefore not 
>>>>> really
>>>>>   optimizer friendly. Dereferenceability, is even scarier. Thus, 
>>>>> we are
>>>>>   currently limited to (almost) boolean information when we want 
>>>>> to
>>>>>   manifest information in the IR (which happens due to inlining or 
>>>>> code
>>>>>   motion, see https://reviews.llvm.org/D69477 for an example).
>>>>>
>>>>> B) The one-use checks.
>>>>>   Various pattern matching passes check the number of uses of a 
>>>>> value.
>>>>>   However, while `llvm.assume` is eventually eliminated by the 
>>>>> backend
>>>>>   it will still increase the use count of the operand. I doubt we 
>>>>> are
>>>>>   able to not increase the use count at all (and keep everything 
>>>>> else
>>>>>   working), but what we can do is make sure the uses in 
>>>>> "assumptions"
>>>>>   are easy to spot, thus filter. This is not the case right now 
>>>>> because
>>>>>   of the additional instructions we need to make the values 
>>>>> boolean.
>>>>>   Even if you have `__builtin_assume(ptr);` the `ptr` use will not 
>>>>> be
>>>>>   the `llvm.assume` call but a `icmp`.
>>>>>
>>>>> C) The side-effect avoidance.
>>>>>   `__assume`, `__builtin_assume`, `__builtin_assume_aligned`, and 
>>>>> OpenMP
>>>>>   `omp assume` are all defined to not evaluate their argument, 
>>>>> thus to
>>>>>   not cause the side effects that the evaluation of the argument 
>>>>> would
>>>>>   otherwise imply. The way we implement this restriction is by not
>>>>>   emitting the argument expression in IR if it might cause a side
>>>>>   effect. We warn the user if that happens. While this is 
>>>>> generally
>>>>>   speaking fine, it would be interesting to lift the 
>>>>> *implementation*
>>>>>   restriction. One benefit would be that we could implement 
>>>>> `assert`
>>>>>   through `__builtin_assume` properly.
>>>>>
>>>>> D) The singleton ranges.
>>>>>   An `llvm.assume` will only provide information for a single 
>>>>> program
>>>>>   point not a range. Even if the beginning and the end of a range 
>>>>> have
>>>>>   an `llvm.assume`, there are cases where the information will not 
>>>>> be
>>>>>   as good as a proper range assumption. OpenMP 5.1 introduces such
>>>>>   range assumptions but other situations would benefit from them 
>>>>> as
>>>>>   well. Take for example function attributes and inlining. Since 
>>>>> we know
>>>>>   they hold for the entire function and not only when it is 
>>>>> entered we
>>>>>   could encode the information over the entire range of the 
>>>>> inlined
>>>>>   code.
>>>>>
>>>>>
>>>>> Some Site Notes:
>>>>>
>>>>> - It seems of little use that we interleave the code for the 
>>>>> assumed
>>>>>   expression with the user code. Having the `llvm.assume` allows 
>>>>> us to
>>>>>   tie information to a program point, beyond that we just clutter 
>>>>> the
>>>>>   function with instructions that we later remove anyway.
>>>>>
>>>>> - Reconstructing information from the pattern of instructions that 
>>>>> feed
>>>>>   into the `llvm.assume` is also not optimal, especially since we 
>>>>> do
>>>>>   not need to "optimize" these instructions anyway.
>>>>>
>>>>> - The current (=arbitrary) side-effects of `llvm.assume` are too 
>>>>> strong.
>>>>>   We have `inaccessiblemem_or_argmemonly` and we might be able to 
>>>>> come
>>>>>   up with something even more specialized for this, e.g.,
>>>>>   `control_dependences_only` to indicate that there are only 
>>>>> control
>>>>>   dependences.
>>>>
>>>> This is all well put; I think you’ve covered the major 
>>>> weaknesses.
>>>>
>>>>
>>>>> Some Design Ideas:
>>>>>
>>>>> 1) Use named operand bundles to encode information.
>>>>>    If we want to encode property XYZ for a value %V holds at a 
>>>>> certain
>>>>>    program point and the property is dependent on %N we could 
>>>>> encode
>>>>>    that as:
>>>>>      `llvm.assume() ["XYZ"(%V, %N)]`
>>>>>    There are various benefits, including:
>>>>>      - It is freely extensible.
>>>>>      - The property is directly tied to the value. Thus, no need 
>>>>> for
>>>>>        encoding patterns that introduce extra instructions and 
>>>>> uses and
>>>>>        which we need to preserve and decode later.
>>>>>      - Allows dynamic properties even when corresponding 
>>>>> attributes do
>>>>>        not, e.g., `llvm.assume() ["align"(%arg_ptr, %N)]` is fine 
>>>>> and
>>>>>        once `%N` becomes a constant, or we determine a lower 
>>>>> bound, we
>>>>>        can introduce the `align(C)` attribute for `%arg_ptr`.
>>>>>
>>>>> 2) Outline assumption expression code (with side-effects).
>>>>>   If we potentially have side-effects, or we simply have a 
>>>>> non-trivial
>>>>>   expression that requires to be lowered into instructions, we can
>>>>>   outline the assumption expression code and tie it to the
>>>>>   `llvm.assume` via another operand bundle property. It could look
>>>>>   something like this:
>>>>>     `__builtin_assume(foo(i) == bar(j));`
>>>>>   will cause us to generate
>>>>>     ```
>>>>>     /// Must return true!
>>>>>     static bool llvm.assume.expression_#27(int i, int j) {
>>>>>       return foo(i) == bar(j);
>>>>>     }
>>>>>     ```
>>>>>   and a `llvm.assume` call like this:
>>>>>     `llvm.assume() ["assume_fn"(@llvm.assume.expression_#27, %i, 
>>>>> %j))]
>>>>>   So we generate the expression in a new function which we (only) 
>>>>> tie to
>>>>>   the `llvm.assume()` through the "assume_fn" operand bundle. This 
>>>>> will
>>>>>   make sure we do not accidentally evaluate the code, or assume it 
>>>>> is
>>>>>   evaluated and produced side-effects. We can still optimize the 
>>>>> code
>>>>>   and use the information that we learn from it at the 
>>>>> `llvm.assume`
>>>>>   site though.
>>>>
>>>> I think outlining is abstractly very promising, but I’m worried 
>>>> about
>>>> it impacting existing optimizations:
>>>>
>>>> - It’s won’t be obvious at all from the IR that the predicate 
>>>> function
>>>>   is dead code.  It would be a shame if we ended up emitting the 
>>>> predicate
>>>>   function, or some global only it references, because we didn’t 
>>>> delete
>>>>   all the llvm.assume calls early enough to recognize that it was 
>>>> dead.
>>>
>>> So, we already "delete" llvm.assume and its operands in the 
>>> backends.
>>> Having a dedicated middle-end pass to do that which also deletes the
>>> associated "assume_fn" doesn't seem hard or complicated to set up.
>>>
>>> Worst case (which I really don't think would ever happen), we emit 
>>> the
>>> functions which are internal and never referenced. The linker should
>>> strip them.
>>>
>>>
>>>
>>>> - Anything passed to the predicate function will by default look 
>>>> like it
>>>>   escapes.  This is particularly true if the predicate takes local
>>>>   variables by references, which is the easiest and most 
>>>> straightforwardly
>>>>   correct way for frontends to emit these predicates.  So this will 
>>>> block
>>>>   basic memory analyses (including mem2reg!) unless they’re 
>>>> taught to
>>>>   remove or rewrite assumptions.
>>>
>>> Partially true and we already have that problem though.
>>>
>>> Mem2reg, and others, might need to know about llvm.assume uses but I
>>> fail to see why they need to rewrite much (in the short therm). The
>>> frontend generated code would naturally look like this:
>>>
>>> %ptr.addr = alloca i32*
>>> store %ptr, %ptr.addr
>>> ...
>>> %ptr.val = load %ptr.addr
>>> llvm.assume() ["align"(%ptr.val)]
>>
>> I disagree; the natural way to generate this code in frontends will
>> actually be to take the variable by reference.  We can, of course, 
>> make
>> frontends smart enough to take the variable by value if it’s
>> obviously only loaded from in the expressions, but if the optimizers
>> still aren’t generally aware of the intrinsic, that will just mean
>> that assumptions pessimize slightly more abstracted code.
>>
>> For example, if I had this:
>>
>> ```
>>   Clang::QualType type = …;
>>   __builtin_assume(!type.hasLocalQualifiers());
>> ```
>>
>> At a high level, I want to be able to apply mem2reg to the value of
>> this `QualType`; but at a low level, this method call takes `type`
>> by reference, and so the predicate function will take it by reference
>> as well.
>
> At some point we need to realize code is only used in an assumption in
> order to actually outline. There is no question about that. Where it
> happens is a good question though. I could write a pass to do that in
> the IR, in order to test the idea.

You mean for things like loads that are just passed to the intrinsic?
I agree, although I think other people who’ve worked with 
`llvm.assume`
have noticed that the presence of the load can change optimization in
ways that are hard to eliminate, e.g. if a load gets hoisted because
it’s “done twice”.

> Since we don't have this code I won't speculate about it now. What I
> will say instead is that we have code to modify the IR in order to 
> pass
> an argument by reference instead of by value. I am more than happy to
> make it aware of llvm.assume operand bundle uses and I am very much
> certain the amount of code needed to transform these from pass by
> reference to pass by value is negligible.

Okay.

>>> Mem2reg should kick in just fine even if %ptr now has a "unknown" 
>>> use.
>>> But that "unknown" use is much less problematic than what we have 
>>> now
>>> because the user is the `llvm.assume` call and not some `ptrtoint` 
>>> which
>>> is used two steps later in an `llvm.assume.
>>>
>>> If you feel I missed a problem here, please let me know.
>>>
>>>
>>>
>>>> Unfortunately, I don’t have a great counter-proposal that isn’t 
>>>> a
>>>> major project.
>>>>
>>>> (The major project is to make the predicates sub-functions within
>>>> the caller.  This doesn’t fix all the problems, and sub-functions
>>>> introduce a host of new issues, but they do have the benefit of
>>>> making the analysis much more obviously intra-procedural.)
>>>
>>> I don't think inter-procedural reasoning is a problem or bad. 
>>> Especially
>>> here with internal functions that have a single use, it is really 
>>> not
>>> hard to make the connection.
>>
>> It’s certainly not a problem *in theory*.  *In theory* every
>> intraprocedural analysis can be taught to go interprocedural
>> into a predicate.
>
> We might have different expectations how assumes are used or where our
> analyses/transformation are heading. Not every pass needs to look at
> assumes at the end of the day. Most information that we use can 
> already,
> or should be described through an attribute and we have a working way 
> to
> deduce those interprocedurally. The future, I hope, is dominated by
> interprocedural analysis and optimization. Given how far we have come
> this summer alone, and what was said on the IPO panel at LLVM'dev, I 
> am
> confident we'll get there.

I suspect this will always be a practical point of tension, and I
did not come away from that panel feeling like there was anything
other than a fairly hand-wavey hope that somehow we’d get there.

John.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20191218/2e7e4a93/attachment.html>


More information about the llvm-dev mailing list