public inbox for gcc-bugs@sourceware.org
help / color / mirror / Atom feed
* [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
@ 2020-05-26 20:45 andrew2085 at gmail dot com
  2020-05-27  8:04 ` [Bug c++/95349] " rguenth at gcc dot gnu.org
                   ` (51 more replies)
  0 siblings, 52 replies; 53+ messages in thread
From: andrew2085 at gmail dot com @ 2020-05-26 20:45 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

            Bug ID: 95349
           Summary: Using std::launder(p) produces unexpected behavior
                    where (p) produces expected behavior
           Product: gcc
           Version: 10.1.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: andrew2085 at gmail dot com
  Target Milestone: ---

Created attachment 48609
  --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=48609&action=edit
preprocessed ii file

All of these f1/f2/f3 functions should do the same thing, essentially the same
thing as std::start_lifetime_as. Since the return value of placement new is
used, std::launder is not necessary here, but using it anyway produces
unexpected behavior when it should do nothing at all. Every version of gcc and
every system I've tried has produced the same result.

Here's the original source for reference: https://godbolt.org/z/zdQsfV

gcc version: 10.1.0 (All other versions tested produce same behavior)

system: Arch Linux x86_64

compile command: g++ -save-temps -std=c++17 -O2 -fno-stack-protector
-fstrict-aliasing main.cpp && ./a.out

configure options: g++ -v
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-pc-linux-gnu/10.1.0/lto-wrapper
Target: x86_64-pc-linux-gnu
Configured with: /build/gcc/src/gcc/configure --prefix=/usr --libdir=/usr/lib
--libexecdir=/usr/lib --mandir=/usr/share/man --infodir=/usr/share/info
--with-bugurl=https://bugs.archlinux.org/
--enable-languages=c,c++,ada,fortran,go,lto,objc,obj-c++,d --with-isl
--with-linker-hash-style=gnu --with-system-zlib --enable-__cxa_atexit
--enable-cet=auto --enable-checking=release --enable-clocale=gnu
--enable-default-pie --enable-default-ssp --enable-gnu-indirect-function
--enable-gnu-unique-object --enable-install-libiberty --enable-linker-build-id
--enable-lto --enable-multilib --enable-plugin --enable-shared
--enable-threads=posix --disable-libssp --disable-libstdcxx-pch
--disable-libunwind-exceptions --disable-werror
gdc_include_dir=/usr/include/dlang/gdc
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 10.1.0 (GCC)

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
@ 2020-05-27  8:04 ` rguenth at gcc dot gnu.org
  2020-05-27  9:14 ` redi at gcc dot gnu.org
                   ` (50 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: rguenth at gcc dot gnu.org @ 2020-05-27  8:04 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

Richard Biener <rguenth at gcc dot gnu.org> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |rguenth at gcc dot gnu.org
           Keywords|                            |alias, wrong-code

--- Comment #1 from Richard Biener <rguenth at gcc dot gnu.org> ---
I think std::launder merely acts as optimization barrier here and without we
manage to propagate the constant.  We still "miscompile" things dependent on
what exactly the C++ standard says.  When seeing

std::uint64_t* s3(double* p) {
    unsigned char storage[sizeof(std::uint64_t)];
    std::memcpy(storage, p, sizeof(storage));
    auto t = new(p) std::uint64_t;
    std::memcpy(t, storage, sizeof(storage));
    return t;
}

the placement new has no effect (it's just passing through a pointer) and
memcpy has C semantics, transfering the active dynamic type of 'p'
through 'storage' back to 'p'.

std::uint64_t f3() {
    double d = 3.14159;
    return *s3(&d);
}

... which is still 'double'.  Which you then access via an lvalue of type
uint64_t which invokes undefined behavior.  So in GCCs implementation
reading of relevant standards you need -fno-strict-aliasing and your program
is not conforming.

So what goes on is that GCC optimizes s3 to just { return (uint64t *)p; } which
makes f3 effectively do

      double d = 3.14159;
      return *(uint64_t *)&d;

which arguably is bogus.  Without the std::launder we are nice to the
user and "optimize" the above to return the correct value.  With
std::launder we cannot do this since it breaks the pointer flow and
we'll DSE the initialization of 'd' because it is not used (due to the
undefinedness in case the load would alias it).

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
  2020-05-27  8:04 ` [Bug c++/95349] " rguenth at gcc dot gnu.org
@ 2020-05-27  9:14 ` redi at gcc dot gnu.org
  2020-05-27  9:40 ` rguenther at suse dot de
                   ` (49 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: redi at gcc dot gnu.org @ 2020-05-27  9:14 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #2 from Jonathan Wakely <redi at gcc dot gnu.org> ---
Using 

  auto t = new(p) std::uint64_t;
  std::memcpy(t, std::launder(storage), sizeof(storage));
  return t;

also prevents GCC from propagating the dynamic type of p to t.

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
  2020-05-27  8:04 ` [Bug c++/95349] " rguenth at gcc dot gnu.org
  2020-05-27  9:14 ` redi at gcc dot gnu.org
@ 2020-05-27  9:40 ` rguenther at suse dot de
  2020-05-27 11:05 ` redi at gcc dot gnu.org
                   ` (48 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: rguenther at suse dot de @ 2020-05-27  9:40 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #3 from rguenther at suse dot de <rguenther at suse dot de> ---
On Wed, 27 May 2020, redi at gcc dot gnu.org wrote:

> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349
> 
> --- Comment #2 from Jonathan Wakely <redi at gcc dot gnu.org> ---
> Using 
> 
>   auto t = new(p) std::uint64_t;
>   std::memcpy(t, std::launder(storage), sizeof(storage));
>   return t;
> 
> also prevents GCC from propagating the dynamic type of p to t.

So the language lawyer question is whether the testcase is valid
or not and what std::launder makes a difference semantics wise
(the dynamic type is still transfered, just the compiler no
longer knows which one it is).

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (2 preceding siblings ...)
  2020-05-27  9:40 ` rguenther at suse dot de
@ 2020-05-27 11:05 ` redi at gcc dot gnu.org
  2020-05-27 14:45 ` andrew2085 at gmail dot com
                   ` (47 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: redi at gcc dot gnu.org @ 2020-05-27 11:05 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #4 from Jonathan Wakely <redi at gcc dot gnu.org> ---
I don't know the answer, and I don't know why it's useful to try this anyway.

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (3 preceding siblings ...)
  2020-05-27 11:05 ` redi at gcc dot gnu.org
@ 2020-05-27 14:45 ` andrew2085 at gmail dot com
  2020-05-27 15:07 ` redi at gcc dot gnu.org
                   ` (46 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: andrew2085 at gmail dot com @ 2020-05-27 14:45 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #5 from Andrew Downing <andrew2085 at gmail dot com> ---
(In reply to Richard Biener from comment #1)
> I think std::launder merely acts as optimization barrier here and without we
> manage to propagate the constant.  We still "miscompile" things dependent on
> what exactly the C++ standard says.  When seeing
> 
> std::uint64_t* s3(double* p) {
>     unsigned char storage[sizeof(std::uint64_t)];
>     std::memcpy(storage, p, sizeof(storage));
>     auto t = new(p) std::uint64_t;
>     std::memcpy(t, storage, sizeof(storage));
>     return t;
> }
> 
> the placement new has no effect (it's just passing through a pointer) and
> memcpy has C semantics, transfering the active dynamic type of 'p'
> through 'storage' back to 'p'.
> 
> std::uint64_t f3() {
>     double d = 3.14159;
>     return *s3(&d);
> }
> 
> ... which is still 'double'.  Which you then access via an lvalue of type
> uint64_t which invokes undefined behavior.  So in GCCs implementation
> reading of relevant standards you need -fno-strict-aliasing and your program
> is not conforming.
> 
> So what goes on is that GCC optimizes s3 to just { return (uint64t *)p; }
> which makes f3 effectively do
> 
>       double d = 3.14159;
>       return *(uint64_t *)&d;
> 
> which arguably is bogus.  Without the std::launder we are nice to the
> user and "optimize" the above to return the correct value.  With
> std::launder we cannot do this since it breaks the pointer flow and
> we'll DSE the initialization of 'd' because it is not used (due to the
> undefinedness in case the load would alias it).


The placement new (in each of s1/s2/s3) shouldn't do nothing though. That line
should create a std::uint64_t object in the storage of the double in each of
f1/f2/f3, and end the lifetime of the double. Physically it does nothing, but
in the C++ object model, it ends the lifetime of the double, and begins the
lifetime of a std::uint64_t. Since the default trivial constructor is used no
initialization is done, and the std::uint64_t has indeterminate value
(according to the standard). The next line copies the object representation of
the double, which was saved to separate storage, to the std::uint64_t. That is
allowed with std::memcpy because they are both trivially copyable types.

Operations that implicitly create objects only do so if it would give the
program defined behavior. If the first std::memcpy implicitly creates a double
object in storage, no harm done, but if the second std::memcpy implicitly ends
the lifetime of the std::uint64_t pointed to by 't' and begins the lifetime of
a double in it's place that wouldn't give the program defined behavior because
't' is being returned as a std::uint64_t* and is later dereferenced. Also, I'm
not sure if operations that implicitly create objects in storage are allowed to
do so if an object has already explicitly created in that storage (from new).

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (4 preceding siblings ...)
  2020-05-27 14:45 ` andrew2085 at gmail dot com
@ 2020-05-27 15:07 ` redi at gcc dot gnu.org
  2020-05-27 15:19 ` andrew2085 at gmail dot com
                   ` (45 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: redi at gcc dot gnu.org @ 2020-05-27 15:07 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #6 from Jonathan Wakely <redi at gcc dot gnu.org> ---
(In reply to Andrew Downing from comment #5)
> Also, I'm not sure if operations that implicitly create
> objects in storage are allowed to do so if an object has already explicitly
> created in that storage (from new).

The lifetime of the object created with new ends as soon as the storage is
reused for another object. But I'm not sure if copying new bytes to it does
reuse the storage or not.

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (5 preceding siblings ...)
  2020-05-27 15:07 ` redi at gcc dot gnu.org
@ 2020-05-27 15:19 ` andrew2085 at gmail dot com
  2020-05-27 16:01 ` andrew2085 at gmail dot com
                   ` (44 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: andrew2085 at gmail dot com @ 2020-05-27 15:19 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #7 from Andrew Downing <andrew2085 at gmail dot com> ---
(In reply to Jonathan Wakely from comment #6)
> (In reply to Andrew Downing from comment #5)
> > Also, I'm not sure if operations that implicitly create
> > objects in storage are allowed to do so if an object has already explicitly
> > created in that storage (from new).
> 
> The lifetime of the object created with new ends as soon as the storage is
> reused for another object. But I'm not sure if copying new bytes to it does
> reuse the storage or not.

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0593r6.html

"We could specify that implicit object creation happens automatically at any
program point that relies on an object existing."

I don't believe operations that implicitly create objects are supposed to do so
if an object has already implicitly or explicitly been created in that storage,
unless that object is a char/unsigned char/std::byte array, since reading out
those back out is always valid anyway.

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (6 preceding siblings ...)
  2020-05-27 15:19 ` andrew2085 at gmail dot com
@ 2020-05-27 16:01 ` andrew2085 at gmail dot com
  2020-05-29 10:59 ` ed at catmur dot uk
                   ` (43 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: andrew2085 at gmail dot com @ 2020-05-27 16:01 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #8 from Andrew Downing <andrew2085 at gmail dot com> ---
>From the C standard:
If a value is copied into an object having no declared type using memcpy or
memmove, or is copied as an array of character type, then the effective type of
the modified object for that access and for subsequent accesses that do not
modify the value is the effective type of the object from which the value is
copied, if it has one. For all other accesses to an object having no declared
type, the effective type of the object is simply the type of the lvalue used
for the access.

So even using C semantics the effective type of storage and *t should not be
changed, because they already have a declared type.

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (7 preceding siblings ...)
  2020-05-27 16:01 ` andrew2085 at gmail dot com
@ 2020-05-29 10:59 ` ed at catmur dot uk
  2020-05-29 11:23 ` rguenth at gcc dot gnu.org
                   ` (42 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: ed at catmur dot uk @ 2020-05-29 10:59 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #9 from Ed Catmur <ed at catmur dot uk> ---
(In reply to Jonathan Wakely from comment #4)
> I don't know the answer, and I don't know why it's useful to try this anyway.

If I'm reading P0593 correctly (I may not be), this would be a valid
implementation of start_lifetime_as:

template<class T>
inline T* start_lifetime_as(void* p) {
    std::byte storage[sizeof(T)];
    std::memcpy(storage, p, sizeof(T));
    auto q = new (p) std::byte[sizeof(T)];
    std::memcpy(q, storage, sizeof(T));
    auto t = reinterpret_cast<T*>(q);
    return std::launder(t);
}

But this has the same issue: https://godbolt.org/z/YYtciP

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (8 preceding siblings ...)
  2020-05-29 10:59 ` ed at catmur dot uk
@ 2020-05-29 11:23 ` rguenth at gcc dot gnu.org
  2020-05-29 11:32 ` rguenth at gcc dot gnu.org
                   ` (41 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: rguenth at gcc dot gnu.org @ 2020-05-29 11:23 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #10 from Richard Biener <rguenth at gcc dot gnu.org> ---
(In reply to Andrew Downing from comment #8)
> From the C standard:
> If a value is copied into an object having no declared type using memcpy or
> memmove, or is copied as an array of character type, then the effective type
> of the modified object for that access and for subsequent accesses that do
> not modify the value is the effective type of the object from which the
> value is copied, if it has one. For all other accesses to an object having
> no declared type, the effective type of the object is simply the type of the
> lvalue used for the access.
> 
> So even using C semantics the effective type of storage and *t should not be
> changed, because they already have a declared type.

But in your testcases 't' is a pointer and the declared object is not visible.
So the only thing an implementation can do is take advantage of the declared
type for optimization when it is visible.

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (9 preceding siblings ...)
  2020-05-29 11:23 ` rguenth at gcc dot gnu.org
@ 2020-05-29 11:32 ` rguenth at gcc dot gnu.org
  2020-05-29 13:53 ` ed at catmur dot uk
                   ` (40 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: rguenth at gcc dot gnu.org @ 2020-05-29 11:32 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #11 from Richard Biener <rguenth at gcc dot gnu.org> ---
(In reply to Ed Catmur from comment #9)
> (In reply to Jonathan Wakely from comment #4)
> > I don't know the answer, and I don't know why it's useful to try this anyway.
> 
> If I'm reading P0593 correctly (I may not be), this would be a valid
> implementation of start_lifetime_as:
> 
> template<class T>
> inline T* start_lifetime_as(void* p) {
>     std::byte storage[sizeof(T)];
>     std::memcpy(storage, p, sizeof(T));
>     auto q = new (p) std::byte[sizeof(T)];
>     std::memcpy(q, storage, sizeof(T));
>     auto t = reinterpret_cast<T*>(q);
>     return std::launder(t);
> }
> 
> But this has the same issue: https://godbolt.org/z/YYtciP

I think there is no way to pun the dynamic type of an object without altering
its current storage representation.  You can do punning via a union but
that wouldn't change its effective type.

Note that for C++ types you can apply memcpy to the placement new is not
needed since object re-use terminates lifetime of the previous object and
starts lifetime of a new one.  This means that your example can be
simplified to

template<class T>
inline T* start_lifetime_as(void* p) {
 return reinterpret_cast<T*>(q);
}

easily showing why that cannot be the intention.

Note that while your example performs memcpy dances you are probably
after a solution that elides all generated code?

Note that I do not belive making your examples work as you intend is
possible in an actual implementation without sacrifying all
type-based alias analysis.

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (10 preceding siblings ...)
  2020-05-29 11:32 ` rguenth at gcc dot gnu.org
@ 2020-05-29 13:53 ` ed at catmur dot uk
  2020-05-29 14:15 ` redi at gcc dot gnu.org
                   ` (39 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: ed at catmur dot uk @ 2020-05-29 13:53 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #12 from Ed Catmur <ed at catmur dot uk> ---
(In reply to Richard Biener from comment #11)
> Note that for C++ types you can apply memcpy to the placement new is not
> needed since object re-use terminates lifetime of the previous object and
> starts lifetime of a new one.

Under P0593R6 it has the effect of implicitly creating objects on demand.
Effectively it is supposed to "curse" the double and "bless" the subsequent
uint64_t. Invoking P0593 may be jumping the gun since it's still in LWG, but
Richard (Smith) wants it retroactively applied to C++20 IS as a DR, and that
could still happen.

> Note that while your example performs memcpy dances you are probably
> after a solution that elides all generated code?

Sure, I assume that memcpy of anything smaller than a page will be elided :)

> Note that I do not belive making your examples work as you intend is
> possible in an actual implementation without sacrifying all
> type-based alias analysis.

Ouch. You might be asked to if and when P0593 goes in (again, assuming I've
understood it correctly). Would it be appropriate to find out what Ville
thinks?

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (11 preceding siblings ...)
  2020-05-29 13:53 ` ed at catmur dot uk
@ 2020-05-29 14:15 ` redi at gcc dot gnu.org
  2020-05-29 14:24 ` rguenther at suse dot de
                   ` (38 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: redi at gcc dot gnu.org @ 2020-05-29 14:15 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

Jonathan Wakely <redi at gcc dot gnu.org> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |richard-gccbugzilla@metafoo
                   |                            |.co.uk

--- Comment #13 from Jonathan Wakely <redi at gcc dot gnu.org> ---
(In reply to Ed Catmur from comment #12)
It's Richard's paper now, not Ville's, so I've CC'd him.

It's unclear whether std::start_lifetime_as is expected to be implementable in
C++ as a normal library function. If there's compiler support then it can do
things the sample implementations shows here can't do.

And start_lifetime_as isn't planned to be treated as a DR for C++20.

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (12 preceding siblings ...)
  2020-05-29 14:15 ` redi at gcc dot gnu.org
@ 2020-05-29 14:24 ` rguenther at suse dot de
  2020-05-29 15:05 ` andrew2085 at gmail dot com
                   ` (37 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: rguenther at suse dot de @ 2020-05-29 14:24 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #14 from rguenther at suse dot de <rguenther at suse dot de> ---
On Fri, 29 May 2020, ed at catmur dot uk wrote:

> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349
> 
> --- Comment #12 from Ed Catmur <ed at catmur dot uk> ---
> (In reply to Richard Biener from comment #11)
> > Note that for C++ types you can apply memcpy to the placement new is not
> > needed since object re-use terminates lifetime of the previous object and
> > starts lifetime of a new one.
> 
> Under P0593R6 it has the effect of implicitly creating objects on demand.
> Effectively it is supposed to "curse" the double and "bless" the subsequent
> uint64_t. Invoking P0593 may be jumping the gun since it's still in LWG, but
> Richard (Smith) wants it retroactively applied to C++20 IS as a DR, and that
> could still happen.

I believe such "curse"/"bless" operation cannot be implemented without
overhead(*) and thus I would not recommend to make it apply to all
placement new operations.

> > Note that while your example performs memcpy dances you are probably
> > after a solution that elides all generated code?
> 
> Sure, I assume that memcpy of anything smaller than a page will be elided :)

(*) then no longer.

> > Note that I do not belive making your examples work as you intend is
> > possible in an actual implementation without sacrifying all
> > type-based alias analysis.
> 
> Ouch. You might be asked to if and when P0593 goes in (again, assuming I've
> understood it correctly). Would it be appropriate to find out what Ville
> thinks?

Definitely.

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (13 preceding siblings ...)
  2020-05-29 14:24 ` rguenther at suse dot de
@ 2020-05-29 15:05 ` andrew2085 at gmail dot com
  2020-05-29 18:07 ` richard-gccbugzilla at metafoo dot co.uk
                   ` (36 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: andrew2085 at gmail dot com @ 2020-05-29 15:05 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #15 from Andrew Downing <andrew2085 at gmail dot com> ---
(In reply to Richard Biener from comment #10)
> (In reply to Andrew Downing from comment #8)
> > From the C standard:
> > If a value is copied into an object having no declared type using memcpy or
> > memmove, or is copied as an array of character type, then the effective type
> > of the modified object for that access and for subsequent accesses that do
> > not modify the value is the effective type of the object from which the
> > value is copied, if it has one. For all other accesses to an object having
> > no declared type, the effective type of the object is simply the type of the
> > lvalue used for the access.
> > 
> > So even using C semantics the effective type of storage and *t should not be
> > changed, because they already have a declared type.
> 
> But in your testcases 't' is a pointer and the declared object is not
> visible.
> So the only thing an implementation can do is take advantage of the declared
> type for optimization when it is visible.

That's not exactly relevant in this case though since this is C++. In C++ new
has special semantics. Regardless of the type of the variable being assigned
to, or the declared type of the object that the pointer argument points to, it
starts the lifetime of an object of the specified type in the storage, and ends
the lifetime of whatever is there. There is no equivalent of this in C. In C
you can change the effective type of an object with allocated storage duration
since it has no declared type, but you can not change the declared type of an
object with automatic storage duration since it has a declared type. double d;
has a declared type;

>Note that for C++ types you can apply memcpy to the placement new is not
>needed since object re-use terminates lifetime of the previous object and
>starts lifetime of a new one.  This means that your example can be
>simplified to

memcpy is needed because starting the lifetime of a new object in the storage
of an existing object does not re-use the old objects representation. If the
default trivial constructor is used the standard explicitly states that the
value of the new object is indeterminate and accessing the value of the object
will result in undefined behavior. This is why in p0593r6 section 3.8 they
mention copying the object representation to another location and then copying
it back after placement new. There is no way in C++ to pun in C like you can
with a union with no intermediate steps.

Yes I expect all these operations to be elided away. They are simply there
because the standard says they have to be. In other compilers they may be
required to ensure well defined behavior. This isn't code that I'm actually
using anywhere I was just reading p0593r6 and tested their described
implementation of std::start_lifetime_as and found this strange behavior. There
is an underlying bug here, and if it's popping up in this example, it will pop
up somewhere else eventually.

This is all kind of besides the point anyway though, because gcc is handling
everything ok except for std::launder. std::launder is only supposed to be an
optimization barrier, but it's causing the opposite problem. Here, std::launder
is preventing an optimization that shouldn't be taking place from NOT taking
place. I've been looking at gcc's code for a while and have gotten as far as
seeing that the use of std::launder is preventing dse_classify_store() in
tree-ssa-dse.c from seeing the relationship between double d = 3.14159; and _6
here.

// this is right before the tree-dse3 pass (I disabled some passes to prevent
constant propagation)
f1 ()
{
  long unsigned int u;
  double d;
  long unsigned int * _6;

  <bb 2> [local count: 1073741824]:

  // this line is removed by tree-dse3
  d = 3.14158999999999988261834005243144929409027099609375e+0;

  _6 = .LAUNDER (&d);
  u_3 = MEM[(uint64_t *)_6];
  d ={v} {CLOBBER};
  return u_3;

}

If the implementation of std::launder in gcc simply disallows optimization
passes from seeing through it, I think that is a mistake. std::launder being an
optimization barrier means disallowing checks that enable an optimization from
seeing through it as well as allowing checks that disable an optimization from
seeing through it.

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (14 preceding siblings ...)
  2020-05-29 15:05 ` andrew2085 at gmail dot com
@ 2020-05-29 18:07 ` richard-gccbugzilla at metafoo dot co.uk
  2020-05-29 21:00 ` andrew2085 at gmail dot com
                   ` (35 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: richard-gccbugzilla at metafoo dot co.uk @ 2020-05-29 18:07 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #16 from Richard Smith <richard-gccbugzilla at metafoo dot co.uk> ---
Per p0593, memcpy implicitly creates objects (of any implicit lifetime type) in
the destination. It does not propagate the objects in the source memory to the
destination memory, and can therefore be used to perform a bit cast. (This is
different from C, where memcpy either preserves or copies the effective type
depending on whether the destination has a declared type.)

The s3 function in comment#1 looks correct to me (with or without the launder).
Optimizing it to { return (uint64t *)p; } is incorrect, because it loses the
erasure of dynamic type information that p0593 requires from memcpy in C++.

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (15 preceding siblings ...)
  2020-05-29 18:07 ` richard-gccbugzilla at metafoo dot co.uk
@ 2020-05-29 21:00 ` andrew2085 at gmail dot com
  2020-05-29 21:50 ` richard-gccbugzilla at metafoo dot co.uk
                   ` (34 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: andrew2085 at gmail dot com @ 2020-05-29 21:00 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #17 from Andrew Downing <andrew2085 at gmail dot com> ---
Also none of the behavior described in p0593 is required for this C++ program
to be well defined. All objects that are required to exists here are created
explicitly. It's not relying on the implicit creation of any objects. This is
valid C++17 code.

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (16 preceding siblings ...)
  2020-05-29 21:00 ` andrew2085 at gmail dot com
@ 2020-05-29 21:50 ` richard-gccbugzilla at metafoo dot co.uk
  2020-05-29 23:13 ` andrew2085 at gmail dot com
                   ` (33 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: richard-gccbugzilla at metafoo dot co.uk @ 2020-05-29 21:50 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #18 from Richard Smith <richard-gccbugzilla at metafoo dot co.uk> ---
(In reply to Andrew Downing from comment #17)
> Also none of the behavior described in p0593 is required for this C++
> program to be well defined. All objects that are required to exists here are
> created explicitly. It's not relying on the implicit creation of any
> objects. This is valid C++17 code.

I agree, for what it's worth. I think the only thing that might suggest
otherwise is the wording in the C standard that says that memcpy copies the
effective type, but that doesn't mean anything in C++ (and it's also specified
in the language section of C, not the library section, so isn't part of the
wording that C++ incorporates by reference).

C++ doesn't have any wording that says what value an object has after you
memcpy the representation of a value of a different type over it, but there
isn't any provision for memcpy to change the dynamic type of the object prior
to P0593 (and after P0593, memcpy is only allowed to change the dynamic type if
doing so makes the program's behavior more defined).

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (17 preceding siblings ...)
  2020-05-29 21:50 ` richard-gccbugzilla at metafoo dot co.uk
@ 2020-05-29 23:13 ` andrew2085 at gmail dot com
  2020-05-29 23:25 ` richard-gccbugzilla at metafoo dot co.uk
                   ` (32 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: andrew2085 at gmail dot com @ 2020-05-29 23:13 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #19 from Andrew Downing <andrew2085 at gmail dot com> ---
Not that it would make a difference in this particular situation, but is the
intent of P0593R6 to only allow implicitly creating an object in the relevant
storage location where one hasn't already been implicitly or explicitly
created? e.g. could the first memcpy implicitly create a double object in
storage? Doing so would result in the same behavior in this situation, I'm not
sure if that would be considered more defined. I'm also not sure if there could
be other situations where implicitly creating a new object where another object
exists would result in more defined, but unintended behavior.

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (18 preceding siblings ...)
  2020-05-29 23:13 ` andrew2085 at gmail dot com
@ 2020-05-29 23:25 ` richard-gccbugzilla at metafoo dot co.uk
  2020-06-02 12:09 ` rguenth at gcc dot gnu.org
                   ` (31 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: richard-gccbugzilla at metafoo dot co.uk @ 2020-05-29 23:25 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #20 from Richard Smith <richard-gccbugzilla at metafoo dot co.uk> ---
(In reply to Andrew Downing from comment #19)
> Not that it would make a difference in this particular situation, but is the
> intent of P0593R6 to only allow implicitly creating an object in the
> relevant storage location where one hasn't already been implicitly or
> explicitly created?

No, the new objects are allowed to replace existing objects. For example, this
implementation would also be correct:

std::uint64_t* s3(double* p) {
    std::memmove(p, p, sizeof(double));
    return std::launder(reinterpret_cast<std::uint64_t*>(p));
}

... on the basis that it has defined behavior if the memmove implicitly creates
an 'uint64_t' object in the underlying storage after it (notionally) copies the
contents elsewhere and before it (notionally) copies the contents back again.
(The 'launder' is necessary in order to form a pointer to the
implicitly-created uint64_t object, because p doesn't point to that object.)

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (19 preceding siblings ...)
  2020-05-29 23:25 ` richard-gccbugzilla at metafoo dot co.uk
@ 2020-06-02 12:09 ` rguenth at gcc dot gnu.org
  2020-06-02 12:20 ` rguenth at gcc dot gnu.org
                   ` (30 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: rguenth at gcc dot gnu.org @ 2020-06-02 12:09 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #21 from Richard Biener <rguenth at gcc dot gnu.org> ---
(In reply to Andrew Downing from comment #15)
> This is all kind of besides the point anyway though, because gcc is handling
> everything ok except for std::launder. std::launder is only supposed to be
> an optimization barrier, but it's causing the opposite problem. Here,
> std::launder is preventing an optimization that shouldn't be taking place
> from NOT taking place. I've been looking at gcc's code for a while and have
> gotten as far as seeing that the use of std::launder is preventing
> dse_classify_store() in tree-ssa-dse.c from seeing the relationship between
> double d = 3.14159; and _6 here.
> 
> // this is right before the tree-dse3 pass (I disabled some passes to
> prevent constant propagation)
> f1 ()
> {
>   long unsigned int u;
>   double d;
>   long unsigned int * _6;
> 
>   <bb 2> [local count: 1073741824]:
> 
>   // this line is removed by tree-dse3
>   d = 3.14158999999999988261834005243144929409027099609375e+0;
> 
>   _6 = .LAUNDER (&d);
>   u_3 = MEM[(uint64_t *)_6];
>   d ={v} {CLOBBER};
>   return u_3;
> 
> }
> 
> If the implementation of std::launder in gcc simply disallows optimization
> passes from seeing through it, I think that is a mistake. std::launder being
> an optimization barrier means disallowing checks that enable an optimization
> from seeing through it as well as allowing checks that disable an
> optimization from seeing through it.

Note that when you elide .LAUNDER nothing really changes in GCC other than
when seeing these kind of must alias of a definition and a use GCC chooses
to do-what-I-mean and allow type-punning.  So I don't think the .LAUNDER
implementation has an issue - of course I never understood the point
of std::launder in the first place, but ...

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (20 preceding siblings ...)
  2020-06-02 12:09 ` rguenth at gcc dot gnu.org
@ 2020-06-02 12:20 ` rguenth at gcc dot gnu.org
  2020-06-02 16:00 ` andrew2085 at gmail dot com
                   ` (29 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: rguenth at gcc dot gnu.org @ 2020-06-02 12:20 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #22 from Richard Biener <rguenth at gcc dot gnu.org> ---
(In reply to Richard Smith from comment #20)
> (In reply to Andrew Downing from comment #19)
> > Not that it would make a difference in this particular situation, but is the
> > intent of P0593R6 to only allow implicitly creating an object in the
> > relevant storage location where one hasn't already been implicitly or
> > explicitly created?
> 
> No, the new objects are allowed to replace existing objects. For example,
> this implementation would also be correct:
> 
> std::uint64_t* s3(double* p) {
>     std::memmove(p, p, sizeof(double));
>     return std::launder(reinterpret_cast<std::uint64_t*>(p));
> }
> 
> ... on the basis that it has defined behavior if the memmove implicitly
> creates an 'uint64_t' object in the underlying storage after it (notionally)
> copies the contents elsewhere and before it (notionally) copies the contents
> back again. (The 'launder' is necessary in order to form a pointer to the
> implicitly-created uint64_t object, because p doesn't point to that object.)

Note that in GCCs view if there's a memcpy in the IL the memcpy destination
has indetermine type and accesses using any effective type lvalue are
well-defined.

The issue with the testcase at hand is that GCC elides the memcpy
(and the temporary object) completely - which was desired by the testcase
author.  But that loses this "barrier" from the IL.

Note that GCC both implements the C and the C++ language and performs
inter-CU optimization across language barriers and thus we need to find
common grounds of semantics - such as memcpy.  So for GCC the argument
"this is C++, we don't care for C semantics" isn't productive.

Note GCC also elides memcpy and memmove with identical source/destination
even though this technically has barrier semantics.

So IMHO std::start_lifetime_as would need first-class compiler support
and be appropriately represented in the IL.  Which also means it will
have a non-zero overhead but less overhead than for example keeping
a memmove (p, p, N) in the IL.

Which leads me towards the suggestion to work on memcpy semantics since
IMHO the C semantics provide the reason for what GCC does to the testcase
and I find it extremely odd (and bad for general optimization that doesn't
try to emulate std::start_lifetime_in_as) to require different semantics.

Note we're already allowing dynamic type changes of objects with a declared
type in C since that's also used in practice.  Note we're already pessimizing
C++ for re-using parts of objects.

So IMHO no GCC bug to see here.  And yes, GCC doesn't seem to be able to
implement std::start_lifetime_as without larger overhead at the moment.

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (21 preceding siblings ...)
  2020-06-02 12:20 ` rguenth at gcc dot gnu.org
@ 2020-06-02 16:00 ` andrew2085 at gmail dot com
  2020-06-02 16:23 ` rguenther at suse dot de
                   ` (28 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: andrew2085 at gmail dot com @ 2020-06-02 16:00 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #23 from Andrew Downing <andrew2085 at gmail dot com> ---
But gcc already can implement std::start_lifetime_as with no overhead.
https://godbolt.org/z/YdoEcH

My intent wasn't to draw attention to std::start_lifetime_as in this bug
report, I only mentioned it as the reason I came up with the original code. My
main focus was intended to be std::launder, which when used in this situation,
breaks something when it should do nothing at all. My thought was that if it
breaks something in this situation, it may break something in other situations
too. Possibly where it is actually required.

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (22 preceding siblings ...)
  2020-06-02 16:00 ` andrew2085 at gmail dot com
@ 2020-06-02 16:23 ` rguenther at suse dot de
  2020-06-02 16:34 ` andrew2085 at gmail dot com
                   ` (27 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: rguenther at suse dot de @ 2020-06-02 16:23 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #24 from rguenther at suse dot de <rguenther at suse dot de> ---
On Tue, 2 Jun 2020, andrew2085 at gmail dot com wrote:

> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349
> 
> --- Comment #23 from Andrew Downing <andrew2085 at gmail dot com> ---
> But gcc already can implement std::start_lifetime_as with no overhead.
> https://godbolt.org/z/YdoEcH

But it's "correct" only because your testcase is very simple and thus
GCCs knowledge is complete, DTRT here.

> My intent wasn't to draw attention to std::start_lifetime_as in this bug
> report, I only mentioned it as the reason I came up with the original code. My
> main focus was intended to be std::launder, which when used in this situation,
> breaks something when it should do nothing at all. My thought was that if it
> breaks something in this situation, it may break something in other situations
> too. Possibly where it is actually required.

It really depends on what the requirements on std::launder are.  AFAIK
it's an optimization barrier for the pointer.

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (23 preceding siblings ...)
  2020-06-02 16:23 ` rguenther at suse dot de
@ 2020-06-02 16:34 ` andrew2085 at gmail dot com
  2020-06-02 16:37 ` andrew2085 at gmail dot com
                   ` (26 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: andrew2085 at gmail dot com @ 2020-06-02 16:34 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #25 from Andrew Downing <andrew2085 at gmail dot com> ---
Do you know how to change that example so that gcc's knowledge is incomplete
and it not longer does the correct thing?

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (24 preceding siblings ...)
  2020-06-02 16:34 ` andrew2085 at gmail dot com
@ 2020-06-02 16:37 ` andrew2085 at gmail dot com
  2020-06-02 17:54 ` rguenther at suse dot de
                   ` (25 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: andrew2085 at gmail dot com @ 2020-06-02 16:37 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #26 from Andrew Downing <andrew2085 at gmail dot com> ---
I mean without modifying the definition of start_lifetime_as

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (25 preceding siblings ...)
  2020-06-02 16:37 ` andrew2085 at gmail dot com
@ 2020-06-02 17:54 ` rguenther at suse dot de
  2020-06-02 18:43 ` andrew2085 at gmail dot com
                   ` (24 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: rguenther at suse dot de @ 2020-06-02 17:54 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #27 from rguenther at suse dot de <rguenther at suse dot de> ---
On June 2, 2020 6:34:12 PM GMT+02:00, andrew2085 at gmail dot com
<gcc-bugzilla@gcc.gnu.org> wrote:
>https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349
>
>--- Comment #25 from Andrew Downing <andrew2085 at gmail dot com> ---
>Do you know how to change that example so that gcc's knowledge is
>incomplete
>and it not longer does the correct thing?

Add std::launder ;) or, for example, conditionally assign another pointer
incoming to the function before dereferencing it (untested).

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (26 preceding siblings ...)
  2020-06-02 17:54 ` rguenther at suse dot de
@ 2020-06-02 18:43 ` andrew2085 at gmail dot com
  2020-06-02 20:53 ` andrew2085 at gmail dot com
                   ` (23 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: andrew2085 at gmail dot com @ 2020-06-02 18:43 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #28 from Andrew Downing <andrew2085 at gmail dot com> ---
Hey that's cheating, but yea the second part did it.

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (27 preceding siblings ...)
  2020-06-02 18:43 ` andrew2085 at gmail dot com
@ 2020-06-02 20:53 ` andrew2085 at gmail dot com
  2020-06-03  6:52 ` rguenth at gcc dot gnu.org
                   ` (22 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: andrew2085 at gmail dot com @ 2020-06-02 20:53 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #29 from Andrew Downing <andrew2085 at gmail dot com> ---
So I think this sort of equivalent example in C shows what's going wrong in the
C++ example. https://godbolt.org/z/ZMz4Cp

gcc knows that if the object mem points to is modified inside pun() its
effective type will change to the type of the value that is assigned because
the object mem points to has no declared type. If the argument to pun has a
declared type, the code doesn't work, like in the c++ example.

So for this c++ example https://godbolt.org/z/NeAJ5d could a solution be for
gcc to treat placement new as if it were a modifying access and as if it's
parameter had no declared type. So it would change the effective type of d in
f1 to uint64_t, or at least insert IL instructions to simulate that?

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (28 preceding siblings ...)
  2020-06-02 20:53 ` andrew2085 at gmail dot com
@ 2020-06-03  6:52 ` rguenth at gcc dot gnu.org
  2020-06-04  0:27 ` andrew2085 at gmail dot com
                   ` (21 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: rguenth at gcc dot gnu.org @ 2020-06-03  6:52 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #30 from Richard Biener <rguenth at gcc dot gnu.org> ---
(In reply to Andrew Downing from comment #29)
> So I think this sort of equivalent example in C shows what's going wrong in
> the C++ example. https://godbolt.org/z/ZMz4Cp
> 
> gcc knows that if the object mem points to is modified inside pun() its
> effective type will change to the type of the value that is assigned because
> the object mem points to has no declared type. If the argument to pun has a
> declared type, the code doesn't work, like in the c++ example.

As said earlier the issue is that pun() is completely elided and GCC
doesn't see anything else than a simple pointer cast of its argument
at the caller side (after inlining).

> So for this c++ example https://godbolt.org/z/NeAJ5d could a solution be for
> gcc to treat placement new as if it were a modifying access and as if it's
> parameter had no declared type. So it would change the effective type of d
> in f1 to uint64_t, or at least insert IL instructions to simulate that?

The main issue with placement new is that it is not necessary to use
placement new!  In C++, for POD (or some bigger set of) types you can
simply start using storage in a new type, no need for a placement new.
This is why GCC treats _every_ _store_ as possibly altering the dynamic
type of the stored to object.  So everything is fine - until all stores
[possibly altering the dynamic type] are optimized away.

So with your argument we'd have to insert extra magic instructions at
_every_ store and we'd have to keep those (while we could elide the
actual stores).  While in the high-level IL this might be feasible
things get tricky in RTL land where we'd have the choice to either
not do TBAA anymore or also represent these "fake" memory state
affecting instructions.

In the end I'd rather not venture there but indeed that removing of
stores has proven an issue in the past (PR93946 for example).

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (29 preceding siblings ...)
  2020-06-03  6:52 ` rguenth at gcc dot gnu.org
@ 2020-06-04  0:27 ` andrew2085 at gmail dot com
  2020-06-04  6:14 ` rguenther at suse dot de
                   ` (20 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: andrew2085 at gmail dot com @ 2020-06-04  0:27 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #31 from Andrew Downing <andrew2085 at gmail dot com> ---
What would you say is the solution here? There's a disconnect between what the
c++ standard says should work, and what actually works.

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (30 preceding siblings ...)
  2020-06-04  0:27 ` andrew2085 at gmail dot com
@ 2020-06-04  6:14 ` rguenther at suse dot de
  2020-06-04 16:05 ` andrew2085 at gmail dot com
                   ` (19 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: rguenther at suse dot de @ 2020-06-04  6:14 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #32 from rguenther at suse dot de <rguenther at suse dot de> ---
On Thu, 4 Jun 2020, andrew2085 at gmail dot com wrote:

> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349
> 
> --- Comment #31 from Andrew Downing <andrew2085 at gmail dot com> ---
> What would you say is the solution here? There's a disconnect between what the
> c++ standard says should work, and what actually works.

I think C++ standards people must come to realize that designing how
TBAA works in a compiler isn't something that can be turned around
every now and then so changing requirements in the standard every
now and then does not work.

I think people will have to live with the reality of existing 
implementations.  Because massive changes like this cannot be
brought to older releases nor can I give any estimate on what
future release of GCC might "support" this "feature" of the
standard.  There's the workaround of disabling type-based
alias anaysis via -fno-strict-aliasing of course.  Mind it took
GCC about 4 to 5 major releases to get placement new work
correctly.  Well, to the reading of the C++11 standard.  So
expect us to be ready with C++20 in about 5 years.

The message to the standards people should also be that C++ does
not live in isolation and modern technology like link-time
optimization has to cope with input from multiple source languages
which means that compilers intermediate language has to cope
with all of them and do optimizations expected by people using
different languages.

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (31 preceding siblings ...)
  2020-06-04  6:14 ` rguenther at suse dot de
@ 2020-06-04 16:05 ` andrew2085 at gmail dot com
  2020-06-05  6:52 ` rguenth at gcc dot gnu.org
                   ` (18 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: andrew2085 at gmail dot com @ 2020-06-04 16:05 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #33 from Andrew Downing <andrew2085 at gmail dot com> ---
Those are all perfectly good arguments, but the problem ended up not having
anything to do with std::launder or new implicit object creation rules or
anything else introduced in the most recent standards right? This should be
well defined in c++11 and on https://godbolt.org/z/w5FoZN. It compiles
correctly until gcc 5.1, and in all versions of the other major compilers I've
tried that will actually compile on godbolt.org. As far as I can tell, it
should also be well defined in c++98 and on if you use different types and
check the size and alignment some other way.

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (32 preceding siblings ...)
  2020-06-04 16:05 ` andrew2085 at gmail dot com
@ 2020-06-05  6:52 ` rguenth at gcc dot gnu.org
  2020-06-05 14:30 ` andrew2085 at gmail dot com
                   ` (17 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: rguenth at gcc dot gnu.org @ 2020-06-05  6:52 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #34 from Richard Biener <rguenth at gcc dot gnu.org> ---
(In reply to Andrew Downing from comment #33)
> Those are all perfectly good arguments, but the problem ended up not having
> anything to do with std::launder or new implicit object creation rules or
> anything else introduced in the most recent standards right? This should be
> well defined in c++11 and on https://godbolt.org/z/w5FoZN. It compiles
> correctly until gcc 5.1, and in all versions of the other major compilers
> I've tried that will actually compile on godbolt.org. As far as I can tell,
> it should also be well defined in c++98 and on if you use different types
> and check the size and alignment some other way.

GCC assumes that memcpy transfers the dynamic type as it does in C which makes
the testcase invalid without the new implicit object creation rules (which
then must rely on that memcpy behavior).  Citing your new example here:

#include <new>
#include <cstring>
#include <cstdlib>
#include <cstdint>

static_assert(sizeof(double) == sizeof(std::uint64_t), "");
static_assert(alignof(double) == alignof(std::uint64_t), "");

std::uint64_t *pun(void *p) {
    char storage[sizeof(double)];
    std::memcpy(storage, p, sizeof(storage));
    std::uint64_t *u = new(p) std::uint64_t;
    std::memcpy(u,storage, sizeof(storage));
    return u;
}

std::uint64_t f1(std::uint64_t *maybe) {
    double d = 3.14159;
    std::uint64_t *u = pun(&d);

    if(rand() == 0) {
        u = maybe;
    }

    return *u;
}

In particular in C++14 3.9/4 refers to footnote 44 which says "The intent is
that the memory model of C++ is compatible with that of ISO/IEC 9899
Programming Language C" which is a laudable goal.

But I see that information about the memory model and the impact of language
features on it is scattered across many places in the C++ standard and some
wordings sound contradictory.  And I always fail to remember where all the
relevant points were.  But I did a thorough research of C and C++ standards
when implementing what GCC does in the GCC 5 timeframe (where it was C++11
and C++14 draft state IIRC).

IMHO you cannot elide memcpy if it implicitely creates an object of a type
that is only determined later by (the first?) access.  It would also be
an extremely bad choice of semantics.  Making it so that memcpy does not
alter the type of the destination object is bad as well if you consider
re-use of allocated storage where the last access to the old object is
visible which determines the dynamic type.  So what C specifies is the
only viable semantics for memcpy.  C has the additional restriction
of declared objects which does not apply to C++ and thus GCC does not use
that for C either.

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (33 preceding siblings ...)
  2020-06-05  6:52 ` rguenth at gcc dot gnu.org
@ 2020-06-05 14:30 ` andrew2085 at gmail dot com
  2020-06-15  9:29 ` rguenth at gcc dot gnu.org
                   ` (16 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: andrew2085 at gmail dot com @ 2020-06-05 14:30 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #35 from Andrew Downing <andrew2085 at gmail dot com> ---
I agree that the new implicit object creation rules sound very difficult to
implement correctly especially because the behavior in C is different. I'm
curious to see how that will all play out.

In this situation though, if we use the C rules for what memcpy does C17 6.5/6
https://web.archive.org/web/20181230041359if_/http://www.open-std.org/jtc1/sc22/wg14/www/abq/c17_updated_proposed_fdis.pdf#section.6.5,
the effective type shouldn't be changed. The declared type of both objects is
known to the compiler. In the first memcpy the declared type of the object is
unsigned char[8], in the second memcpy the declared type of the object is
double. Placement new changes the effective type to std::uint64_t, but that
doesn't change the behavior of memcpy. Footnote 88 says "Allocated objects have
no declared type.". I believe calling a function defined in another TU that
returns a pointer also has to be considered to return a pointer to an object
with no declared type, because the object's declaration isn't visible. In this
situation though, the declared types are visible, and so a modifying access, or
memcpy, or memmove shouldn't change the effective type.

If gcc is changing the effective type with every memcpy no matter what, that
would be the wrong thing to do right? Especially since you're saying that it's
the reason that this example isn't being compiled correctly.

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (34 preceding siblings ...)
  2020-06-05 14:30 ` andrew2085 at gmail dot com
@ 2020-06-15  9:29 ` rguenth at gcc dot gnu.org
  2020-06-15 21:45 ` richard-gccbugzilla at metafoo dot co.uk
                   ` (15 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: rguenth at gcc dot gnu.org @ 2020-06-15  9:29 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #36 from Richard Biener <rguenth at gcc dot gnu.org> ---
(In reply to Andrew Downing from comment #35)
> I agree that the new implicit object creation rules sound very difficult to
> implement correctly especially because the behavior in C is different. I'm
> curious to see how that will all play out.
> 
> In this situation though, if we use the C rules for what memcpy does C17
> 6.5/6
> https://web.archive.org/web/20181230041359if_/http://www.open-std.org/jtc1/
> sc22/wg14/www/abq/c17_updated_proposed_fdis.pdf#section.6.5, the effective
> type shouldn't be changed. The declared type of both objects is known to the
> compiler. In the first memcpy the declared type of the object is unsigned
> char[8], in the second memcpy the declared type of the object is double.

The issue I have with this special handling of objects with a declared type
is that the standard assumes universal knowledge and disregards that
compilers are imperfect.  It's also not clear what it means for

 int *p;
 int x;
 if (<condition>)
   p = &x;
 else
   p = malloc (4);
 memcpy (p, q, 4);

there is a single memcpy call and the standard says that both the dynamic
type transfers (from q) and that it does not (to x).

> Placement new changes the effective type to std::uint64_t, but that doesn't
> change the behavior of memcpy. Footnote 88 says "Allocated objects have no
> declared type.". I believe calling a function defined in another TU that
> returns a pointer also has to be considered to return a pointer to an object
> with no declared type, because the object's declaration isn't visible. In
> this situation though, the declared types are visible, and so a modifying
> access, or memcpy, or memmove shouldn't change the effective type.

Note the C++ standard makes the placement new optional.  Do you say that
your example is incorrect with the placement new elided?  Note you say
the declared types are visible - but at least 'd' is in another function
(compilers are imperfect) and is accessed via a pointer here.  IIRC C++
does not have this "special-casing" of objects with declared types
(it doesn't have this memcpy wording at all I think).  [there's more
"interesting" bits of all this dynamic type frobbing in PR79671 when
partial objects are re-used]

> If gcc is changing the effective type with every memcpy no matter what, that
> would be the wrong thing to do right? Especially since you're saying that
> it's the reason that this example isn't being compiled correctly.

As you say above the C rule that memcpy does not change the dynamic type
of an object with a declared type can only be fulfilled by being conservative.
This is why GCC interprets memcpy as TBAA barrier but it uses the
transfer of dynamic type as means to be able to elide a memcpy roundtrip
as seen in your testcase.  If the memcpy roundtrip has to be considered
a side-effect then I see no easy way to preserve that without preserving
the actual memcpy operation [without changing the intermediate representation
of GCC, that is].  I'd be interested in a C testcase that we get wrong,
even when relying on that special casing of objects with a declared type.
It should then be possible to construct a C++ variant that expects exactly
the opposite behavior.

The main issue I see is that this differing expectations of C and C++ are
impossible to get correct at the same time.

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (35 preceding siblings ...)
  2020-06-15  9:29 ` rguenth at gcc dot gnu.org
@ 2020-06-15 21:45 ` richard-gccbugzilla at metafoo dot co.uk
  2020-06-16  3:27 ` andrew2085 at gmail dot com
                   ` (14 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: richard-gccbugzilla at metafoo dot co.uk @ 2020-06-15 21:45 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #37 from Richard Smith <richard-gccbugzilla at metafoo dot co.uk> ---
(In reply to Richard Biener from comment #36)
> The main issue I see is that this differing expectations of C and C++ are
> impossible to get correct at the same time.

That is a rather bold claim. I think you can satisfy both rule sets by using
the C++ rule even in C. It is conservatively correct to discard the effective /
dynamic type when you see a memcpy, and the C++ semantics require you to do so.

The C semantics also appear to require the same thing, if you cannot track the
destination back to either an object with a declared type or to a heap
allocation; as described in comment#35, GCC gets this wrong and presumably
miscompiles C code in some cases as a result.

It seems to me that all you're allowed to do within the bounds of conformance
is:

#1 if you can track the destination back to an object with declared type in C
code, then use its type as the effective type of the result

#2 if you can track the destination back to a heap allocation in C code, then
copy the effective type from source to destination

#3 otherwise (in either C or C++) erase the effective type of the destination

(#1 and #3 will presumably result in memcpy being replaced by some operation
that updates the effective type, rather than being eliminated entirely.)

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (36 preceding siblings ...)
  2020-06-15 21:45 ` richard-gccbugzilla at metafoo dot co.uk
@ 2020-06-16  3:27 ` andrew2085 at gmail dot com
  2020-06-16  6:50 ` rguenther at suse dot de
                   ` (13 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: andrew2085 at gmail dot com @ 2020-06-16  3:27 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #38 from Andrew Downing <andrew2085 at gmail dot com> ---
>  int *p;
>  int x;
>  if (<condition>)
>    p = &x;
>  else
>    p = malloc (4);
>  memcpy (p, q, 4);
> 
> there is a single memcpy call and the standard says that both the dynamic
> type transfers (from q) and that it does not (to x).

I would say just that, that it both does and doesn't transfer the effective
type. Meaning that you need to be conservative during optimization and consider
p to alias both int and whatever type q is.

> Note the C++ standard makes the placement new optional.  Do you say that
> your example is incorrect with the placement new elided?

I'm not sure what you mean about the first part about it being optional. It
depends what you mean by elided. I wouldn't expect any code to be generated for
it either way, but I would expect the compiler to now consider the object at
that address as having a different type regardless.

If we pretend for a second that GCC is using pre C++20 rules for memcpy and not
messing with the effective/dynamic type for the destination, then isn't this
example still not going to work if GCC is treating placement new in this case
as doing nothing? In f1 after s1 is called, d is still a double, and u is a
pointer to uint64_t, so pointing u at d and accessing *u is still going to be
UB right? I would expect d = 3.14159 to still be optimized out, because why
would a store to a double affect a load from a uint64_t?

I can't see how this could work in every situation unless the compiler keeps
track of the type of d changing from double -> uint64_t, so it knows that
stores to it when it was a double could affect loads from a uint64_t after it's
type changed to uint64_t. In a more complex scenario where placement new was
used conditionally with many different types, the compiler would have to
consider pointers to any of those types as aliasing d afterwards. I can think
of some situations where the compiler would have a very hard time proving that
the address of some object didn't make it's way to a placement new somewhere
else in the program. This does seem very difficult to do correctly without
being very conservative and disabling a lot of optimizations, or having pretty
advanced static analysis.

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (37 preceding siblings ...)
  2020-06-16  3:27 ` andrew2085 at gmail dot com
@ 2020-06-16  6:50 ` rguenther at suse dot de
  2020-06-16  6:57 ` rguenther at suse dot de
                   ` (12 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: rguenther at suse dot de @ 2020-06-16  6:50 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #39 from rguenther at suse dot de <rguenther at suse dot de> ---
On Tue, 16 Jun 2020, andrew2085 at gmail dot com wrote:

> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349
> 
> --- Comment #38 from Andrew Downing <andrew2085 at gmail dot com> ---
> >  int *p;
> >  int x;
> >  if (<condition>)
> >    p = &x;
> >  else
> >    p = malloc (4);
> >  memcpy (p, q, 4);
> > 
> > there is a single memcpy call and the standard says that both the dynamic
> > type transfers (from q) and that it does not (to x).
> 
> I would say just that, that it both does and doesn't transfer the effective
> type. Meaning that you need to be conservative during optimization and consider
> p to alias both int and whatever type q is.
> 
> > Note the C++ standard makes the placement new optional.  Do you say that
> > your example is incorrect with the placement new elided?
> 
> I'm not sure what you mean about the first part about it being optional. It

Somewhere the C++ standard says (or said in some "old" version) that
the lifetime of an object ends when "... or the storage is re-used".
Likewise lifetime of an object starts "when storage with the proper
alignment and size ... is obtained".  Back in time when I designed
the way GCC currently works to satisfy placement new and friends I
concluded the safe thing to do is to treat every memory write
as changing the dynamic type of the memory location (because we have
to assume it is re-use of storage).

Thus for types without a non-trivial ctor/dtor you do not need to use
placement new.  So take your example and remove the placement new.
Does that change its semantics?

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (38 preceding siblings ...)
  2020-06-16  6:50 ` rguenther at suse dot de
@ 2020-06-16  6:57 ` rguenther at suse dot de
  2020-06-16 13:56 ` andrew2085 at gmail dot com
                   ` (11 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: rguenther at suse dot de @ 2020-06-16  6:57 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #40 from rguenther at suse dot de <rguenther at suse dot de> ---
On Mon, 15 Jun 2020, richard-gccbugzilla at metafoo dot co.uk wrote:

> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349
> 
> --- Comment #37 from Richard Smith <richard-gccbugzilla at metafoo dot co.uk> ---
> (In reply to Richard Biener from comment #36)
> > The main issue I see is that this differing expectations of C and C++ are
> > impossible to get correct at the same time.
> 
> That is a rather bold claim. I think you can satisfy both rule sets by using
> the C++ rule even in C. It is conservatively correct to discard the effective /
> dynamic type when you see a memcpy, and the C++ semantics require you to do so.
> 
> The C semantics also appear to require the same thing, if you cannot track the
> destination back to either an object with a declared type or to a heap
> allocation; as described in comment#35, GCC gets this wrong and presumably
> miscompiles C code in some cases as a result.

I very much would like to see such an example!

The current GCC rule is quite simple - every store to memory alters
the dynamic type of the stored to object to that of the store.  Up to now
we've had more success with that model than any other we tried before.

> It seems to me that all you're allowed to do within the bounds of conformance
> is:
> 
> #1 if you can track the destination back to an object with declared type in C
> code, then use its type as the effective type of the result
> 
> #2 if you can track the destination back to a heap allocation in C code, then
> copy the effective type from source to destination
> 
> #3 otherwise (in either C or C++) erase the effective type of the destination
> 
> (#1 and #3 will presumably result in memcpy being replaced by some operation
> that updates the effective type, rather than being eliminated entirely.)

Indeed.  Such "operation that updates the effective type" would have come
handy in a few cases already, but we do not have it right now and given
past experience with variants of it (bad one, obviously) I'm not too
keen of re-introducing it.

That GCC elideds the memcpy roundtrip is (in the above model where every
store alters the dynamic type) a bug - the memcpy internally actually
discards the dynamic type info.  The option of having to preserve
that roundtrip isn't very appealing though.  We've went to that way
for cases where we now cannot remove a "redundant" store (a store
with the same bit pattern but different effective type).

Bottom line is I wouldn't hold my breath getting this fixed on the
GCC side.  Like with the partial object re-use and aggregate assignment
case the C++ FE will have the option disabling type-based alias-analysis
completely for some objects (but I can't see how that helps with the
case referenced here).

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (39 preceding siblings ...)
  2020-06-16  6:57 ` rguenther at suse dot de
@ 2020-06-16 13:56 ` andrew2085 at gmail dot com
  2022-01-11 12:43 ` rguenth at gcc dot gnu.org
                   ` (10 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: andrew2085 at gmail dot com @ 2020-06-16 13:56 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #41 from Andrew Downing <andrew2085 at gmail dot com> ---
> Thus for types without a non-trivial ctor/dtor you do not need to use
> placement new.  So take your example and remove the placement new.
> Does that change its semantics?

These are C++17 rules.

4.5/1) An object is created by a definition, by a new-expression, when
implicitly changing the active member of a union, or when a temporary object is
created.

6.8/1) The lifetime of an object of type T begins when: storage with the proper
alignment and size for type T is obtained, and if the object has non-vacuous
initialization, its initialization is complete.

double d;

My interpretation of the above rules would be that only a double object is
created in the storage for d because T in 6.8/1 is set to double by the
definition of d. According to these rules the only way to change the dynamic
type of the object in d's storage would be with placement new (pre C++20).
memcpy only overwrites the object representation. It doesn't affect it's type
or lifetime.

If you remove the placement new from my example, the program has undefined
behavior because it later accesses the double object with a uint64_t pointer.
With placement new in place, it accesses a uint64_t object in d's storage with
a uint64_t pointer.

In C++20 the placement new wouldn't be required because in addition to the
things above that create objects, you also have operations that implicitly
create objects of which memcpy is one. The rules are different than C's though.
The only objects that are or are not created are ones that would give the
program defined behavior. So in f1 where the uint64_t* pointing to d is
accessed, the compiler would have to make the second memcpy create a uint64_t
object in d's storage before copying the bytes to give the subsequent access
through a uint64_t* defined behavior. If you used placement new anyway, then
the second memcpy wouldn't have to create an object because the program would
already have defined behavior.

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (40 preceding siblings ...)
  2020-06-16 13:56 ` andrew2085 at gmail dot com
@ 2022-01-11 12:43 ` rguenth at gcc dot gnu.org
  2022-01-11 12:48 ` rguenth at gcc dot gnu.org
                   ` (9 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: rguenth at gcc dot gnu.org @ 2022-01-11 12:43 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #42 from Richard Biener <rguenth at gcc dot gnu.org> ---
See PR101641 for an interesting case where eliding a round-trip causes
wrong-code generation.  It's union related so might not apply 1:1 to C++.

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (41 preceding siblings ...)
  2022-01-11 12:43 ` rguenth at gcc dot gnu.org
@ 2022-01-11 12:48 ` rguenth at gcc dot gnu.org
  2022-11-14  4:53 ` andrew2085 at gmail dot com
                   ` (8 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: rguenth at gcc dot gnu.org @ 2022-01-11 12:48 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #43 from Richard Biener <rguenth at gcc dot gnu.org> ---
(In reply to Andrew Downing from comment #41)
> > Thus for types without a non-trivial ctor/dtor you do not need to use
> > placement new.  So take your example and remove the placement new.
> > Does that change its semantics?
> 
> These are C++17 rules.
> 
> 4.5/1) An object is created by a definition, by a new-expression, when
> implicitly changing the active member of a union, or when a temporary object
> is created.
> 
> 6.8/1) The lifetime of an object of type T begins when: storage with the
> proper alignment and size for type T is obtained, and if the object has
> non-vacuous initialization, its initialization is complete.
> 
> double d;
> 
> My interpretation of the above rules would be that only a double object is
> created in the storage for d because T in 6.8/1 is set to double by the
> definition of d. According to these rules the only way to change the dynamic
> type of the object in d's storage would be with placement new (pre C++20).
> memcpy only overwrites the object representation. It doesn't affect it's
> type or lifetime.

What would

  *(long *)&d = 1;

do?  My reading of earlier standards say it starts lifetime of a new object
of type long (the storage of 'd' gets reused).  Following that stmt a read
like

  foo (d);

invokes undefined behavior (it accesses the storage of effective type long
via an effective type of double).  The same example with placement new
would be

  *(new (&d) long) = 1;

and I'm arguing the placement new is not required to start the lifetime
of an object of type long in the storage of 'd'.

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (42 preceding siblings ...)
  2022-01-11 12:48 ` rguenth at gcc dot gnu.org
@ 2022-11-14  4:53 ` andrew2085 at gmail dot com
  2024-06-03  8:02 ` Christopher.Nerz at de dot bosch.com
                   ` (7 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: andrew2085 at gmail dot com @ 2022-11-14  4:53 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #44 from Andrew Downing <andrew2085 at gmail dot com> ---
(In reply to Richard Biener from comment #43)
> (In reply to Andrew Downing from comment #41)
> > > Thus for types without a non-trivial ctor/dtor you do not need to use
> > > placement new.  So take your example and remove the placement new.
> > > Does that change its semantics?
> > 
> > These are C++17 rules.
> > 
> > 4.5/1) An object is created by a definition, by a new-expression, when
> > implicitly changing the active member of a union, or when a temporary object
> > is created.
> > 
> > 6.8/1) The lifetime of an object of type T begins when: storage with the
> > proper alignment and size for type T is obtained, and if the object has
> > non-vacuous initialization, its initialization is complete.
> > 
> > double d;
> > 
> > My interpretation of the above rules would be that only a double object is
> > created in the storage for d because T in 6.8/1 is set to double by the
> > definition of d. According to these rules the only way to change the dynamic
> > type of the object in d's storage would be with placement new (pre C++20).
> > memcpy only overwrites the object representation. It doesn't affect it's
> > type or lifetime.
> 
> What would
> 
>   *(long *)&d = 1;
> 
> do?  My reading of earlier standards say it starts lifetime of a new object
> of type long (the storage of 'd' gets reused).  Following that stmt a read
> like
> 
>   foo (d);
> 
> invokes undefined behavior (it accesses the storage of effective type long
> via an effective type of double).  The same example with placement new
> would be
> 
>   *(new (&d) long) = 1;
> 
> and I'm arguing the placement new is not required to start the lifetime
> of an object of type long in the storage of 'd'.

It's been a while since I've though about this stuff.

double d;
*(long *)&d = 1;

That would be lead to undefined behavior because it's breaking the strict
aliasing rules.

*(new (&d) long) = 1;

That would be ok because new creates a long object in the storage of d before
dereferencing.

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (43 preceding siblings ...)
  2022-11-14  4:53 ` andrew2085 at gmail dot com
@ 2024-06-03  8:02 ` Christopher.Nerz at de dot bosch.com
  2024-06-03  8:51 ` rguenth at gcc dot gnu.org
                   ` (6 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: Christopher.Nerz at de dot bosch.com @ 2024-06-03  8:02 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

Christopher Nerz <Christopher.Nerz at de dot bosch.com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |Christopher.Nerz at de dot bosch.c
                   |                            |om

--- Comment #45 from Christopher Nerz <Christopher.Nerz at de dot bosch.com> ---
This is a critical bug which renders gcc unusable for safety relevant systems
using expected/variant or simple ipc.

You can get the same buggy behavior with far simpler code:
https://godbolt.org/z/1WTnnYceM


#include <cstdint>
#include <memory>

bool check()
{
    // Just to prove that it is not a problem with alignment etc.
    static_assert(alignof(double) == alignof(std::uint64_t));
    static_assert(sizeof(double) == sizeof(std::uint64_t));

    alignas(8) std::byte buffer[8]; // some buffer
    new (buffer) double{1}; // some completely trivial data
    // reuse memory -> double ends lifetime, uint64 starts lifetime
    std::uint64_t * res = new (buffer) std::uint64_t;
    // *res is allowed to be used as it is the correct pointer returned by new
    // *res == 0x3ff0000000000000 // and gives correct value
    // The very definition of std::launder says that it is suppose to be used
as:
    return (*res == *std::launder(reinterpret_cast<std::uint64_t*>(buffer)));
}

int main(int argc, char **argv) {
    return check(); // gives false with activatred O2 (true with O0)
}


We get the same behavior when initialisating the memory at our version of
"std::uint64_t * res = new (buffer) std::uint64_t;", but were unable to give a
minimal example for that behavior.

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (44 preceding siblings ...)
  2024-06-03  8:02 ` Christopher.Nerz at de dot bosch.com
@ 2024-06-03  8:51 ` rguenth at gcc dot gnu.org
  2024-06-03  9:13 ` Christopher.Nerz at de dot bosch.com
                   ` (5 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: rguenth at gcc dot gnu.org @ 2024-06-03  8:51 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

Richard Biener <rguenth at gcc dot gnu.org> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |jason at gcc dot gnu.org
           See Also|                            |https://gcc.gnu.org/bugzill
                   |                            |a/show_bug.cgi?id=101641

--- Comment #46 from Richard Biener <rguenth at gcc dot gnu.org> ---
(In reply to Christopher Nerz from comment #45)
> This is a critical bug which renders gcc unusable for safety relevant
> systems using expected/variant or simple ipc.
> 
> You can get the same buggy behavior with far simpler code:
> https://godbolt.org/z/1WTnnYceM
> 
> 
> #include <cstdint>
> #include <memory>
> 
> bool check()
> {
>     // Just to prove that it is not a problem with alignment etc.
>     static_assert(alignof(double) == alignof(std::uint64_t));
>     static_assert(sizeof(double) == sizeof(std::uint64_t));
> 
>     alignas(8) std::byte buffer[8]; // some buffer
>     new (buffer) double{1}; // some completely trivial data
>     // reuse memory -> double ends lifetime, uint64 starts lifetime
>     std::uint64_t * res = new (buffer) std::uint64_t;
>     // *res is allowed to be used as it is the correct pointer returned by
> new
>     // *res == 0x3ff0000000000000 // and gives correct value
>     // The very definition of std::launder says that it is suppose to be
> used as:
>     return (*res == *std::launder(reinterpret_cast<std::uint64_t*>(buffer)));
> }
> 
> int main(int argc, char **argv) {
>     return check(); // gives false with activatred O2 (true with O0)
> }
> 
> 
> We get the same behavior when initialisating the memory at our version of
> "std::uint64_t * res = new (buffer) std::uint64_t;", but were unable to give
> a minimal example for that behavior.

For this case we end up with an indetermined value for 'buffer' read as
uint64_t but that indetermined value is different from the one read after
.LAUNDER.  A somewhat early IL is

  MEM[(double *)&buffer] = 1.0e+0;
  _1 = MEM[(uint64_t *)&buffer];
  _12 = .LAUNDER (&buffer);
  _3 = *_12;
  _13 = _1 == _3;

we then re-interpret 1.0e+0 as uint64_t and then remove the store as dead
because there's no valid use - the *_12 load is done as uint64_t.
The effect is that the later load reads from uninitialized stack.

Note that .LAUNDER only constitutes a data dependence between the &buffer
and _12 pointer _values_ but there's no dependence of the memory contents
pointed to - .LAUNDER is ECF_NOVOPS.  That makes the compiler forget
what _12 points to but it doesn't make later uint64 loads valid from
*_12 from an earlier store to double.

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (45 preceding siblings ...)
  2024-06-03  8:51 ` rguenth at gcc dot gnu.org
@ 2024-06-03  9:13 ` Christopher.Nerz at de dot bosch.com
  2024-06-03  9:23 ` rguenth at gcc dot gnu.org
                   ` (4 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: Christopher.Nerz at de dot bosch.com @ 2024-06-03  9:13 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #47 from Christopher Nerz <Christopher.Nerz at de dot bosch.com> ---
But shouldn't both give the same value?
The return of the new and the std::launder(...) point to the same object and
are both equal read-operations! It is imho not predictable that they behave
differently.

Note that I could construct the same behavior when creating a std::byte array
(within a complex structure) in which a (trivially copy, move, default
constructible (and destructable)) object is created via new, then the
surrounding complex structure is copied and then the copied byte array read as
this kind of object. So rougly

```
struct data { std::byte mem[8]; }

data d1;
new (d1.mem) long{5};
data d2 = std::move(d1);
*std::launder<long*>(reinterpret_cast<long*>(d2.mem));
```

not literally that code (that one is working), but conceptionally the same
thing.
I have to check whether NRVO in our code removes the move...

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (46 preceding siblings ...)
  2024-06-03  9:13 ` Christopher.Nerz at de dot bosch.com
@ 2024-06-03  9:23 ` rguenth at gcc dot gnu.org
  2024-06-03 10:26 ` Christopher.Nerz at de dot bosch.com
                   ` (3 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: rguenth at gcc dot gnu.org @ 2024-06-03  9:23 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #48 from Richard Biener <rguenth at gcc dot gnu.org> ---
(In reply to Christopher Nerz from comment #47)
> But shouldn't both give the same value?

I'm not sure what the standard says to this.  Does std::launder(...)
sanitize earlier "undefined behavior"?  For example failing to initialize
an object?

> The return of the new and the std::launder(...) point to the same object and
> are both equal read-operations! It is imho not predictable that they behave
> differently.

One load we can optimize to a constant, the other not (because of .LAUNDER).

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (47 preceding siblings ...)
  2024-06-03  9:23 ` rguenth at gcc dot gnu.org
@ 2024-06-03 10:26 ` Christopher.Nerz at de dot bosch.com
  2024-06-03 11:19 ` rguenther at suse dot de
                   ` (2 subsequent siblings)
  51 siblings, 0 replies; 53+ messages in thread
From: Christopher.Nerz at de dot bosch.com @ 2024-06-03 10:26 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #49 from Christopher Nerz <Christopher.Nerz at de dot bosch.com> ---
Ah, misunderstood and therefore forgot optimization to a constant.

In the current code example, we have the problem that the (second)
initialization does not initialize with a value (ergo undefined behaviour due
to uninitialized variable although it is created in an initialized memory).
In the more complex code - which I am trying to simplify, but so far get the
behaviour only in quite special situations which are hard to reduce to an
example - we have that the buffer is explicitly initialized via `new T{}`
(calling an inline initialization, i.e. not a trivial default constructor, but
T is trivially copy & move constructible), then the buffer is copied (as
std::byte-array) and then std::launder is applied to the resulting byte-array.
To my understanding [basic.types.general].$3

> For two distinct objects obj1 and obj2 of trivially copyable type T, where neither obj1 nor obj2 is a potentially-overlapping subobject, if the underlying bytes (6.7.1) making up obj1 are copied into obj2, obj2 shall subsequently hold the same value as obj1.

guarantees that the copied buffer can be used as `T` via
`*std::launder<T*>(second_buffer)`. Clang, MSVC agree, gcc does not.

Again: Still trying to construct the minimal example for that behavior :-/

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (48 preceding siblings ...)
  2024-06-03 10:26 ` Christopher.Nerz at de dot bosch.com
@ 2024-06-03 11:19 ` rguenther at suse dot de
  2024-06-03 15:53 ` redi at gcc dot gnu.org
  2024-06-03 16:00 ` redi at gcc dot gnu.org
  51 siblings, 0 replies; 53+ messages in thread
From: rguenther at suse dot de @ 2024-06-03 11:19 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #50 from rguenther at suse dot de <rguenther at suse dot de> ---
On Mon, 3 Jun 2024, Christopher.Nerz at de dot bosch.com wrote:

> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349
> 
> --- Comment #49 from Christopher Nerz <Christopher.Nerz at de dot bosch.com> ---
> Ah, misunderstood and therefore forgot optimization to a constant.
> 
> In the current code example, we have the problem that the (second)
> initialization does not initialize with a value (ergo undefined behaviour due
> to uninitialized variable although it is created in an initialized memory).
> In the more complex code - which I am trying to simplify, but so far get the
> behaviour only in quite special situations which are hard to reduce to an
> example - we have that the buffer is explicitly initialized via `new T{}`
> (calling an inline initialization, i.e. not a trivial default constructor, but
> T is trivially copy & move constructible), then the buffer is copied (as
> std::byte-array) and then std::launder is applied to the resulting byte-array.
> To my understanding [basic.types.general].$3
> 
> > For two distinct objects obj1 and obj2 of trivially copyable type T, where neither obj1 nor obj2 is a potentially-overlapping subobject, if the underlying bytes (6.7.1) making up obj1 are copied into obj2, obj2 shall subsequently hold the same value as obj1.
> 
> guarantees that the copied buffer can be used as `T` via
> `*std::launder<T*>(second_buffer)`. Clang, MSVC agree, gcc does not.
> 
> Again: Still trying to construct the minimal example for that behavior :-/

Hmm, copying as std::byte-array should make the store use alias-set zero
so a followup load as T should be OK.  Unless "then the buffer is copied 
(as std::byte-array)" is really doing something more advanced with regard
to type-based aliasing rules (thus not a plain memcpy).

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (49 preceding siblings ...)
  2024-06-03 11:19 ` rguenther at suse dot de
@ 2024-06-03 15:53 ` redi at gcc dot gnu.org
  2024-06-03 16:00 ` redi at gcc dot gnu.org
  51 siblings, 0 replies; 53+ messages in thread
From: redi at gcc dot gnu.org @ 2024-06-03 15:53 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #51 from Jonathan Wakely <redi at gcc dot gnu.org> ---
(In reply to Richard Biener from comment #48)
> (In reply to Christopher Nerz from comment #47)
> > But shouldn't both give the same value?
> 
> I'm not sure what the standard says to this.  Does std::launder(...)
> sanitize earlier "undefined behavior"?  For example failing to initialize
> an object?

No.

The example appears to be trying to use std::launder as std::start_lifetime_as,
but they're not the same.

^ permalink raw reply	[flat|nested] 53+ messages in thread

* [Bug c++/95349] Using std::launder(p) produces unexpected behavior where (p) produces expected behavior
  2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
                   ` (50 preceding siblings ...)
  2024-06-03 15:53 ` redi at gcc dot gnu.org
@ 2024-06-03 16:00 ` redi at gcc dot gnu.org
  51 siblings, 0 replies; 53+ messages in thread
From: redi at gcc dot gnu.org @ 2024-06-03 16:00 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95349

--- Comment #52 from Jonathan Wakely <redi at gcc dot gnu.org> ---
(In reply to Christopher Nerz from comment #45)
> This is a critical bug which renders gcc unusable for safety relevant
> systems using expected/variant or simple ipc.

I don't think your example demonstrates that.

>     alignas(8) std::byte buffer[8]; // some buffer
>     new (buffer) double{1}; // some completely trivial data
>     // reuse memory -> double ends lifetime, uint64 starts lifetime
>     std::uint64_t * res = new (buffer) std::uint64_t;

This starts the lifetime of a new object, but it has indeterminate value.

>     // *res is allowed to be used as it is the correct pointer returned by
> new

The pointer does point to the new object, but derefencing it causes a read of
an indeterminate value, which is undefined behaviour.

>     // *res == 0x3ff0000000000000 // and gives correct value
>     // The very definition of std::launder says that it is suppose to be
> used as:
>     return (*res == *std::launder(reinterpret_cast<std::uint64_t*>(buffer)));

It looks like what you're actually trying to do is:

    alignas(8) std::byte buffer[8];
    new (buffer) double{1};
    std::uint64_t* res = std::start_lifetime_as<std::uint64_t>(buffer);
    return *res == 0x3ff0000000000000;


This is not what std::launder is for.

^ permalink raw reply	[flat|nested] 53+ messages in thread

end of thread, other threads:[~2024-06-03 16:00 UTC | newest]

Thread overview: 53+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-05-26 20:45 [Bug c++/95349] New: Using std::launder(p) produces unexpected behavior where (p) produces expected behavior andrew2085 at gmail dot com
2020-05-27  8:04 ` [Bug c++/95349] " rguenth at gcc dot gnu.org
2020-05-27  9:14 ` redi at gcc dot gnu.org
2020-05-27  9:40 ` rguenther at suse dot de
2020-05-27 11:05 ` redi at gcc dot gnu.org
2020-05-27 14:45 ` andrew2085 at gmail dot com
2020-05-27 15:07 ` redi at gcc dot gnu.org
2020-05-27 15:19 ` andrew2085 at gmail dot com
2020-05-27 16:01 ` andrew2085 at gmail dot com
2020-05-29 10:59 ` ed at catmur dot uk
2020-05-29 11:23 ` rguenth at gcc dot gnu.org
2020-05-29 11:32 ` rguenth at gcc dot gnu.org
2020-05-29 13:53 ` ed at catmur dot uk
2020-05-29 14:15 ` redi at gcc dot gnu.org
2020-05-29 14:24 ` rguenther at suse dot de
2020-05-29 15:05 ` andrew2085 at gmail dot com
2020-05-29 18:07 ` richard-gccbugzilla at metafoo dot co.uk
2020-05-29 21:00 ` andrew2085 at gmail dot com
2020-05-29 21:50 ` richard-gccbugzilla at metafoo dot co.uk
2020-05-29 23:13 ` andrew2085 at gmail dot com
2020-05-29 23:25 ` richard-gccbugzilla at metafoo dot co.uk
2020-06-02 12:09 ` rguenth at gcc dot gnu.org
2020-06-02 12:20 ` rguenth at gcc dot gnu.org
2020-06-02 16:00 ` andrew2085 at gmail dot com
2020-06-02 16:23 ` rguenther at suse dot de
2020-06-02 16:34 ` andrew2085 at gmail dot com
2020-06-02 16:37 ` andrew2085 at gmail dot com
2020-06-02 17:54 ` rguenther at suse dot de
2020-06-02 18:43 ` andrew2085 at gmail dot com
2020-06-02 20:53 ` andrew2085 at gmail dot com
2020-06-03  6:52 ` rguenth at gcc dot gnu.org
2020-06-04  0:27 ` andrew2085 at gmail dot com
2020-06-04  6:14 ` rguenther at suse dot de
2020-06-04 16:05 ` andrew2085 at gmail dot com
2020-06-05  6:52 ` rguenth at gcc dot gnu.org
2020-06-05 14:30 ` andrew2085 at gmail dot com
2020-06-15  9:29 ` rguenth at gcc dot gnu.org
2020-06-15 21:45 ` richard-gccbugzilla at metafoo dot co.uk
2020-06-16  3:27 ` andrew2085 at gmail dot com
2020-06-16  6:50 ` rguenther at suse dot de
2020-06-16  6:57 ` rguenther at suse dot de
2020-06-16 13:56 ` andrew2085 at gmail dot com
2022-01-11 12:43 ` rguenth at gcc dot gnu.org
2022-01-11 12:48 ` rguenth at gcc dot gnu.org
2022-11-14  4:53 ` andrew2085 at gmail dot com
2024-06-03  8:02 ` Christopher.Nerz at de dot bosch.com
2024-06-03  8:51 ` rguenth at gcc dot gnu.org
2024-06-03  9:13 ` Christopher.Nerz at de dot bosch.com
2024-06-03  9:23 ` rguenth at gcc dot gnu.org
2024-06-03 10:26 ` Christopher.Nerz at de dot bosch.com
2024-06-03 11:19 ` rguenther at suse dot de
2024-06-03 15:53 ` redi at gcc dot gnu.org
2024-06-03 16:00 ` redi at gcc dot gnu.org

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).