[llvm-dev] [RFC] How to manifest information in LLVM-IR, or, revisiting llvm.assume
Doerfert, Johannes via llvm-dev
llvm-dev at lists.llvm.org
Wed Dec 18 08:28:15 PST 2019
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)]
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.
We can easily teach the Attributor to follow these "pseudo-calls"
in order to derive information from them.
Thanks,
Johannes
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 228 bytes
Desc: not available
URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20191218/a9937832/attachment.sig>
More information about the llvm-dev
mailing list