Handling invariant.groups with equality + marking it as experimental

Hi folks,
I would like to ask for some help with handling invariant.group metadata for pointer comparison. Currently, we have a problem with devirtualization
of this code:

void compare() {
    A* a = new A;
    a->foo();
    A* b = new(a) B;
    if (a == b) {
        b->foo();
    }
}

Now because it is legal to replace b with an in the branch the vtable load will use old pointer operand with !invariant.group, which will lead to devirtualization to A::foo().
I tried to fix that in the frontend by introducing barriers for dynamic objects comparison, like:

if (barrier(a) == barrier(b))

Unfortunately, it didn’t work out for all of the cases. Example by Hubert Tong

struct A {
  virtual void foo() { printf("%s\n", __PRETTY_FUNCTION__); }
  int m;
  int *zip() { return &m; }
};

struct B : A {
  virtual void foo() { printf("%s\n", __PRETTY_FUNCTION__); }
};

int main(void) {
  A *ap = new A;
  ap->foo();
  int *const apz = ap->zip();
  B *bp = new (ap) B;
  if (apz == bp->zip()) {
    bp->foo();
  }
}

Here we don't compare vptrs, but pointers with small offset like:
a+4 == b+4

And because we can transform it to a == b, it breaks.

I started thinking about handling it in the LLVM instead, and my first idea was as folows:
Because the invariant.group is associated with the pointer value, preserving the invariant.group information when changing pointer seems invalid.
If we replace one pointer with another, based on the comparision, we could strip all of the invariant.group information from it.
This idea is good enough to solve the above code, but it not gonna work for all the cases, like passing the pointer to a function or returning it - 
by doing later inlining we could get the same situation and we would not know that we should strip invariant.group metadata.

Other idea is following:
We can replace the pointer by another pointer after barrier like:

if (a == b)
  use(b)
=>
if (a == b)
  use(barrier(a))

This would be probablly correct, but I am not sure if it is a good solution, because It might actually make it worse from optimization point of view.
I am also not sure what should be the semantics of the barrier(constant) - should we constantfold it to constant? I am not sure if constant pointer 
can carry any invariant.group information - I am pretty sure that **null** and **undef** does not carry it ([https://reviews.llvm.org/D32423](https://reviews.llvm.org/D32423)).

The third solution I am thinking of is just not to propagate the new value to every dominated use - 
especially to instructions like calls, return or load/store with invariant.group information. 
To validate this idea I need some help from GVN wizzards - would it break any important optimizations, etc?
Please help.



Also, Sanjoy proposed to mark invariant.group features as experimental, so we will not be afraid to break behavior of frontends that already use it. 
Right now I am pretty sure that clang is the only one that curently uses it (and not by default).
Is anyone ok with it?


Best
Piotr

Hi folks,
I really need help to move forward, so any comments will be appreciated.

Piotr

Hi Piotr,

Also, Sanjoy proposed to mark invariant.group features as experimental, so
we will not be afraid to break behavior of frontends that already use it.

Right now I am pretty sure that clang is the only one that curently uses it
(and not by default).

Firstly, yes, I think we should definitely make the barrier stuff
experimental. I don't think it has been ironed out enough for
generally use.

Secondly, at this point I'm wondering if the barrier/invariant.group
is the right abstraction at all. Are there other designs you
considered early on that we can revisit in the light of these issues?

-- Sanjoy

Hi Piotr,

> Also, Sanjoy proposed to mark invariant.group features as experimental,
so
> we will not be afraid to break behavior of frontends that already use it.
>
> Right now I am pretty sure that clang is the only one that curently uses
it
> (and not by default).
Firstly, yes, I think we should definitely make the barrier stuff
experimental. I don't think it has been ironed out enough for
generally use.

Oh right, I will post patch that will mention it in the LangRef this week
and see if there will be any objections.

Secondly, at this point I'm wondering if the barrier/invariant.group

is the right abstraction at all. Are there other designs you
considered early on that we can revisit in the light of these issues?

-- Sanjoy

I don't think there were any other ideas for propagation of the
"invartianess" of the vptr -
@Richard or @Nick, did you have any other ideas before my internship?

I am open to hear any other ideas for the model - even if they would be not
checked it might be a good inspiration.
Right now the only problem with the current model for devirtualization
(that we have seen) is the propagation
of the pointers from comparision. It is also the problem that we knew
almost from the beginning.
@Sanjoy, maybe you have some ideas how the semantics of the
!invariant.groups with propagation
of the pointers could be specified in a way that it would not brake the
testcases?

Piotr

Hi folks,
friendly ping about this issue.

Piotr