This is the mail archive of the libstdc++@gcc.gnu.org mailing list for the libstdc++ project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: C++17 std::launder and aliasing


On 24/10/16 11:44 +0200, Richard Biener wrote:
On Mon, 24 Oct 2016, Jonathan Wakely wrote:

On 24/10/16 10:18 +0200, Richard Biener wrote:
> On Mon, 24 Oct 2016, Ville Voutilainen wrote:
>
> > On 24 October 2016 at 10:41, Richard Biener <rguenther@suse.de> wrote:
> > >> Seems that testcase is devirtualization related.
> > >> With -O3 -fno-devirtualize we return 3 (but still no calls).
> > >> With asm ("" : "+g" (p)); before return p; in launder we don't
> > devirtualize
> > >> it any more and return 3 in the end.  Is the testcase only valid
> > >> with std::launder and not valid otherwise (I hope so, otherwise we are
> > in
> > >> big trouble with devirtualization)?
> > >
> > > I believe the testcase shows a bug in devirtualization.  (stpuid
> > > godbolt.org having no way to textually extract the source easily)
> > >
> > > Please file a GCC bugreport.  GCC shouldn't miscompile this even
> > > without std::launder.
> >
> >
> > Note that A::f() can be in a different translation unit, so we still
> > need the optimization
> > barrier in some cases, even if in this particular case as written we
> > wouldn't.
>
> Well, if it is valid to construct a new object in place of *this in
> a virtual function call on this then GCC has an issue regardless of
> std::launder.  So, for
>
> {
>   A a;
>   A::f ();
> }
>
> where does the _FE_ get the knowledge what destructor to call?  IIRC
> we "devirtualize" destructor calls in this case as well.
>
> struct B {
>    virtual ~B();
> };
> struct A : B {
>    virtual ~A();
>    void foo ();
> };
>
> int foo ()
> {
>  A a;
>  a.foo();
> }
>
>
> foo ends up calling A::~A() with GCC 6.
>
> How do I launder a here?

This example has undefined behaviour even if you add std::launder. The
original example doesn't.

The difference is 3.8 [basic.life] paragraph 9, which says that if the
program ends the lifetime of the automatic object 'a' then it must
ensure that another object of the same type occupies the same storage
when the implicit destructor runs. So the original example is OK
because it ends the lifetime of the original object by creating a B at
the same location, but then creates another A there, so the implicit
destructor call to A::~A() is valid, because there is an A at that
location.

Ok, but in the original example if either f () would throw
before switching back to A then the testcase would invoke
undefined behavior again?

Yes. If there isn't an object of type A (and not a sub-object of some
more derived type) at that location it's UB.

I don't think we have a bug, or a defect, in the absence of
std::launder. Without std::launder our devirtualization is OK.

Again, std::launder(&a) is not the same as &a, so if we simply fold
std::launder(&a) to &a we allow devirtualization through the pointer
as though it was still a pointer to the original object &a, and that's
what std::launder is supposed to prevent.

Note that std::launder () then has to be implemented as clobbering
memory (the vtable pointer) as otherwise the devirt machiner won't see it.
This means that std::launder () will pessimize optimizations (well,
we can try to special-case it in the alias machinery to only consider
clobbering its argument directly pointed to memory and not keeping
its argument pointed to memory live).

It's OK for std::launder to pessimize optimizations. That's what it's
for, after all :-)



Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]