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. 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. Jakub