C++17 std::launder and aliasing

Jonathan Wakely jwakely@redhat.com
Mon Oct 24 09:49:00 GMT 2016


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




More information about the Libstdc++ mailing list