On 8/28/25 9:52 AM, Jakub Jelinek wrote:
On Thu, Aug 28, 2025 at 02:08:12PM +0200, Jason Merrill wrote:
So, do you think for P2590R2 we want a purely library implementation which
does nothing (well, define the needed templates)?
Or do you prefer some builtin which will do nothing initially but might be
changed to do something when needed?
The first version actually clears the array, not sure if that is desirable
(especially for the const, volatile and const volatile overloads).

It seems undesirable: "The object representation
of a is the contents of the storage prior to the call to start_lifetime_as."
https://eel.is/c++draft/obj.lifetime#3

So if we want to represent this somehow, it needs a different representation
than the clobbers I used for placement new.

Ah, if it actually must preserve previous bitwise content, then not sure how to
represent it in the IL, probably needs to be some builtin which is kept in
the IL maybe until final.
For malloc etc. as well as placement new we generally don't need anything in
the IL for stores, they are expected to possibly change the dynamic type.
So say
long
foo (void *p)
{
   long ret = 0;
   long long *q = (long long *) p;
   *q = 42;
   ret = *q;
   double *r = (double *) p;
   *r = 42.0;
   ret += *r;
   return ret;
}
is ok, the storage has since *q = 42; long long dynamic type and the
*r = 42.0; store changes it to double.
But the way I read std::start_lifetime_as, it works as __builtin_bit_cast
or type puning through unions.  Except for that we really require all
accesses to be done through the unions in that case, but with
struct S { int a, b; };
struct T { long long c; };
long long
foo (void *p)
{
   S *q = std::start_lifetime_as <S> (p);
   q->a = 1;
   q->b = 2;
   T *r = std::start_lifetime_as <T> (p);
   return r->c;
}
if we implement std::start_lifetime_as just as reinterpret_cast of the
pointer type, then the IL will see 2 stores using S alias set (the ints
in there) and then read using T alias set (the long long in there).
One option would be to implement it using __builtin_launder though,
but IFN_LAUNDER is ECF_NOVOPS, so I think doesn't make it clear to the
middle-end that we actually do (or might) use all the stores prior to it.
Say
long long
foo (void *p)
{
   S s;
   s.a = 1;
   s.b = 2;
   T *r = std::start_lifetime_as <T> ((void *) &s);
   return r->c;
}
if there is just
   T *r = __builtin_launder (reinterpret_cast <T *> (void *) &s);
will I think happily DSE the s.a and s.b stores, while the r pointer can
(and does) point to the same location, the load is using a different alias
set.
Furthermore, IFN_LAUNDER is optimized away during RTL expansion, but even
RTL uses TBAA heavily.

And we don't want launder to break TBAA, it presumes that there's already an object of that type at that address.

So I think we need to treat it like an IFN with pointer and size arguments
which to the IL acts as if it did (or could do)
void *
__builtin_start_lifetime_as (void *p, size_t s)
{
   char buf[s];
   __builtin_memcpy (buf, p, s);
   __builtin_memcpy (p, buf, s);
   return p;
}
or so.  But we need to preserve it even during RTL.

Makes sense.

Jason

Reply via email to