The !invariant.group loads having pointer operand that dominates
the loop body can be hoisted, because we know the load will produce
the same value in every loop step.
Details
Diff Detail
- Build Status
Buildable 5248 Build 5248: arc lint + arc unit
Event Timeline
Comments inline.
lib/Transforms/Scalar/LICM.cpp | ||
---|---|---|
495 | You can use Loop::isLoopInvariant here. | |
571 | I'm not sure that that the langref lets you do this. What it allows for is %a = *ptr, !invariant.group %b = *ptr, !invariant.group to %a = *ptr, !invariant.group %b = %a which is slightly different from what you're doing here. So I'd recommend changing the langref wording to be more like what we have for !invariant.load. | |
877 | This bit does not look correct. Why can't these attributes be control dependent? | |
test/Transforms/LICM/hoist-invariant-group-load.ll | ||
40 | Please use -instnamer to name all the instructions. Otherwise editing the tests will be painful. |
test/Transforms/LICM/hoist-invariant-group-load.ll | ||
---|---|---|
40 | Also, if you can, i'd just use update_test_checks at this point. |
lib/Transforms/Scalar/LICM.cpp | ||
---|---|---|
571 | I am not sure how I can make it more clear in LangRef, but I am happy to change that if you have any ideas. Right now it says " The existence of the invariant.group metadata on the instruction tells the optimizer that every load and store to the same pointer operand within the same invariant group can be assumed to load or store the same value" so if my pointer operand doesn't change in any loop step, then I guess it works. But I agree that the docs shoud probably mention that it has to be executed etc. | |
877 | Good catch, I didn't think about it because this works for devirtualization. |
Hi Piotr,
Won't this patch allow a situation like this:
for (;;) { vtable = load ptr0, !invariant.group use(vtable) store new vtable to ptr0 ptr1 = barrier(ptr0) // ptr1 is not used, say }
to
for (;;) { store new vtable to ptr0 ptr1 = barrier(ptr0) // ptr1 is not used, say } vtable = load ptr0, !invariant.group use(vtable)
?
After this, the load of vtable will return the newer vtable, which seems problematic.
It would, but that would mean that either the !invariant.group metadata is invalid there, or the store is storing the same value as it is loaded (so it would probably have !invariant.group
there, but it doesn't matter).
If I would unroll this loop:
vtable.pre = load ptr0, !invariant.group use(vtable.pre) store new vtable to ptr0 ptr1 = barrier(ptr0) for (;;) { vtable = load ptr0, !invariant.group use(vtable) store new vtable to ptr0 ptr2 = barrier(ptr0) // ptr1 is not used, say }
Then you can clearly see that vtable.pre dominates vtable and it has the same pointer operand, so it means it has to load the same value.
Or maybe you are refering to the fact that invariant.group metadata would be preserved? If this is a case, then see my post on mailing list :)
Does this reasoning still hold if the loop has just one iteration? That is, say the loop was
for (i = 0; i < 1; i++) { vtable = load ptr0, !invariant.group use(vtable) store new vtable to ptr0 // S0 ptr1 = barrier(ptr0) // ptr1 is not used, say }
then (I claim that) the program does not have UB even if S0 is storing a new vtable. I think this is the kind of IR we'll get from
for (i = 0; i < 1; i++) { storage->f(); storage->~Foo(); new(storage) Bar; }
Or maybe you are refering to the fact that invariant.group metadata would be preserved? If this is a case, then see my post on mailing list :)
I thought your mailing list post was about speculating loads. Here we're not speculating anything -- we're only sinking. In any case, you do not need the !invariant.group metadata on the sunk load for the example above to "work".
So it looks like we can't sink based on invariant.group, but I guess hoisting is still possible, because
it would be only invalid if there would be a store before the load
for (i = 0; i < 1; i++) {
store new vtable to ptr0 // S0 vtable = load ptr0, !invariant.group use(vtable)
}
but then if store stores different value, that means it is UB. Is this right?
I thought your mailing list post was about speculating loads. Here we're not speculating anything -- we're only sinking. In any case, you do not need the !invariant.group metadata on the sunk load for the example above to "work".
The dereferenceable is usefull for speculative loads, but in tha last mail I showed the problem with other metadata:
If I will remove invariant.group md while hoisting from loop, then I probably won't be able to devirtualize it further. Consider:
void loop(A* a, int p) { for (int i = 0; i < p; i++) a->foo(): } void call() { A a; clobber(&a); // external function loop(&a, 15); } external void clobber(A *a);
if I will hoist vtable load of out the loop before inlining, then it won't be able to change it to A::foo(), because invariant.group will be removed.
If I will not hoist it, then it will be devirtualized after inlining, but for callsites of loop() that wasn't inlined it will be worse.
That means we need a way of specifing that one propery holds globally, which would mean that is not dependent on branch. for vtables and virtual function the invariant.group, invariant.load and !dereferenceable is a global property, and I can't loose this information.
Few problems that I have to address before it will make sense to hoist based on invariant.group
1a. We should preserve the metadata if we are not speculatively hoisting loads (if it is guaranteed to execute). This way we won't gonna loose invariant.group metadata that is crucial to preserve
1b. with 1a we should only hoist invariant.group instructions if we will preserve the metadata
- We have to find a way to preserve the metadata if we want to hoist speculatively - I proposed solution on mailing list, and there is also very close one here https://reviews.llvm.org/D18738
Here are the features that I needed:
https://reviews.llvm.org/D45150
https://reviews.llvm.org/D45151
You can use Loop::isLoopInvariant here.