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.

Reply via email to