This is an archive of the discontinued LLVM Phabricator instance.

Hoisting invariant.group in LICM
AbandonedPublic

Authored by Prazek on Mar 31 2017, 9:05 AM.

Details

Reviewers
chandlerc
sanjoy
Summary

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.

Event Timeline

Prazek created this revision.Mar 31 2017, 9:05 AM
Prazek updated this revision to Diff 93662.Mar 31 2017, 9:13 AM

small updates

Prazek updated this revision to Diff 93664.Mar 31 2017, 9:15 AM

add newline

Prazek added a subscriber: amharc.Mar 31 2017, 9:52 AM
sanjoy requested changes to this revision.Mar 31 2017, 6:59 PM

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.

This revision now requires changes to proceed.Mar 31 2017, 6:59 PM
dberlin added inline comments.
test/Transforms/LICM/hoist-invariant-group-load.ll
40

Also, if you can, i'd just use update_test_checks at this point.

Prazek added inline comments.Apr 1 2017, 8:26 AM
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.
The other interpretation is that unrolling the loop one time and then optimizing load in the loop based on invariant.group is exactly the same.

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.
This means that we need a way to say that given property holds globally. See mailing list

Prazek updated this revision to Diff 93750.Apr 1 2017, 9:45 AM
Prazek edited edge metadata.

Fixed test

Prazek marked 2 inline comments as done.Apr 1 2017, 11:56 AM
sanjoy added a comment.Apr 2 2017, 1:38 PM

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.

Prazek added a comment.Apr 2 2017, 2:03 PM

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 :)

sanjoy added a comment.Apr 2 2017, 4:27 PM

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.

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".

Prazek added a comment.Apr 3 2017, 2:34 AM

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)

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?

Prazek added a comment.Apr 3 2017, 3:03 AM

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.

Prazek planned changes to this revision.Apr 14 2017, 7:18 AM

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

  1. 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