[LLVMdev] Optimization hints for "constant" loads
Philip Reames
listmail at philipreames.com
Tue Oct 21 16:03:33 PDT 2014
On 10/21/2014 01:44 PM, Andrew Trick wrote:
>> On Oct 21, 2014, at 11:46 AM, Sanjoy Das <sanjoy at playingwithpointers.com> wrote:
>>
>> Thank you for the explanation, I think I misunderstood your solution
>> initially. Is it accurate to say: by making the definition of the
>> source pointer of an !invariant load control (or data) dependent on
>> some initialization event, you can effectively separate the
>> initialization event (where the location is not invariant) and the
>> rest of the execution (where the location is invariant).
>>
>> This approach looks similar to Ruby's "freeze" method:
>> http://ruby-doc.org/core-2.1.3/Object.html#method-i-freeze
>>
>> What is the aliasing relationship between a non-invariant load from
>> the result of a safe_cast and a load from (or store to) the argument
>> we pass to it? Is it sensible to make them MustAlias? I'm trying to
>> think if a frontend needs to be careful about using the return value
>> of check_cast only for !invariant loads (or some other restriction),
>> or is it legal (or desirable) to mix and match both kinds of loads
>> from both kinds of pointers.
> %p1 = ...
> store %v0, %p1
> %p2 = llvm.safe_cast %p1
> store %v1, %p1
> store %v2, %p2
> %v3 = load %p2, !invariant
>
> The load can be moved above both stores (%v1 and %v2). AFAIK, the invariant metadata tag essentially tells alias analysis not to even try, just assume no aliasing. So the semantics effectively guarantee that %v0==%v1==%v3==%v3. The IR is still “legal” in that it is verifiable, but it is nonsense.
>
> Note that it is also totally valid for an alias analysis to return that they MustAlias. That would allow forwarding from store %v0 to the load. This would have to be implemented as a special case, since the invariant tag normally bypasses pointer analysis. I admit this is weird, but I’m don’t know of any problems it will cause. This can already happen with TBAA, and will be a problem with any metadata that can be abused by the frontend or language design. A difficultly with this approach is that the frontend has the burden for emitting guards anywhere the “invariant” data could be modified.
>
> How do you imagine mixing and matching invariant and non-invariant? I showed some examples of doing that safely, but I’m not sure how it’s relevant to you.
>
> -Andy
Ok, I'm catching up on this thread. Let me summarize a couple of things
I noticed while reading.
Sanjoy made a good point. We don't actually need a new variant of
"invariant.start". Simply using an invariant.start with no uses gives
us a notion of an invariant region with no end. (Since the result
doesn't escape, there can be no end hidden inside a function call.)
This seems like a simple notion to exploit and is strict step forward
from where we are, without a new intrinsic.
The notions of "invariant over some range" and "safe to speculate over
some range" are related, but distinct. However, to get the most power
out of the former, you often need the later.
Andy seems to be arguing in favour of data flow mostly because it's
easier for the optimizer. Unless I'm misreading, there's nothing
preventing us from using control flow for the same patterns. For example:
p1 = ...
*p1 = 5
p2 = llvm.safe_cast p1
p3 = select is_constant p2, p1
load p3
Is isomorphic with:
p1 = ...
*p1 = 5
if is_constant:
invariant.start(p1)
load p1
Andy, is this correct? Or am I missing something?
(p.s. You guys seem to be throwing around the terms "!invariant" and
"invariant load" without clearly defining them. I'm unsure if you mean
the same things.)
I suspect this is going to be a topic we should discuss in person at the
developers conference or over a beer at a social. It's a bit
complicated to keep track of in email.
My belief is that a path sensitive invariant indicator for a memory
location is probably sufficient for most use cases. In particular, the
initialization and use logic is usually not in the same compilation for
the cases I care about. I care that such cases are *possible*, but I'm
less concerned about them being *easy*. I'm also having real trouble
find examples of cases where Andy's "conditionally invariant data" is
useful. Given that we *have an existing* mechanism which only needs a
slightly tweak for "endless" invariant regions, I'm tempted to stick
with what we have.
The only change (clarification?) I'd make to the current scheme is to
specify that a location is both invariant *and dereferenceable* at the
point it is listed in an invariant.start. This enables speculative
loading up to that construct. I think this is actually what the current
intrinsic is trying for from the documentation.
(Hm.. it does make it hard to rearrange calls to invariant.start
though... Thoughts?)
I suspect that for the Array.length case, I'd probably do something like
this:
arr = function_that_returns_array();
invariant.start(arr.length)
... arbitrary control flow ...
length = arr.length
... arbitrary control flow ...
length2 = arr.length
My reasoning here is that if something returns an array (in this
compilation) I want the invariant fact to be visible. If that factory
function gets inlined, I'd end up with:
arr = new Array;
arr.length = X;
invariant.start(arr.length)
... arbitrary control flow ...
length = arr.length
... arbitrary control flow ...
length2 = arr.length
Both cases seem nearly ideal here. I know I can lift the loads as far
as the function which returns the base pointer. I know I can value
forward loads (either with or without lifting). I know I can value
forward the store to the load (as I normally would).
In terms of aliasing, it seems like having invariant.start read from
it's arguments memory is sufficient. That prevents reorderings of
stores to that location before the address to after the
invariant.start. Due to conservative aliasing, that might be somewhat
harmful, but I don't really see a way to avoid this... (Or is this the
point Andy's trying to solve with the data flow?) I don't have a sense
for how painful this is. Try it and see?
I could see a useful extension for a function attribute (type?, TBAA?)
for describing the return value's invariant fields (*at the point of the
return*), but that is more minor goodness for IR size then clearly
necessary.
Philip
More information about the llvm-dev
mailing list