On Fri, Aug 29, 2025 at 9:40 AM Richard Biener <rguent...@suse.de> wrote:

> On Fri, 29 Aug 2025, Tomasz Kaminski wrote:
>
> > On Fri, Aug 29, 2025 at 9:03 AM Jakub Jelinek <ja...@redhat.com> wrote:
> >
> > > On Fri, Aug 29, 2025 at 08:48:02AM +0200, Tomasz Kaminski wrote:
> > > > >From recent discussions, my understand was that this function can be
> > > > implemented purely in library as:
> > > > return launder(static_cast<T*>(memmove(p, p, sizeof(T))));
> > >
> > > It can't, we optimize memmove(p,p,whatever) away pretty much instantly,
> >
> > so all it ends up with is __builtin_launder (and later comparison against
> > > the original pointer with the launder return can get us back to the
> > > original
> > > pointer).
> > >
> > memove has the power to implicitly create objects, so the calls to need
> need
> > to force optimizers to also forget about where p was pointing to, in the
> > same manner
> > as it starts for its lifetime as.
>
> I don't think memmove can do such thing, not portably across C and C++
> at least.  It transfers the effective type of storage, so it's really
> a no-op TBAA-wise, according to the C standard at least.
>
It is required to implicitly create objects per C++ standard, same as
start_lifetime_as do
so if it is not in C++ mode, that would mean non-conformance:
   https://eel.is/c++draft/c.strings#cstring.syn-3
But maybe we are speaking about something else. As far I understand the
implicit object
creation only relax strict aliasing rules, so you interpret char buffer as
int, if you memmoved/
memcopied an int representation to it before.

>
> >
> > > And, if we didn't, then it wouldn't work for the const case and would
> > > create
> > > data races where there shouldn't be one otherwise.
> > > Or is
> > > struct S { int a[2]; };
> > > struct T { long long b; };
> > > const struct S s[2] = { { 1, 2 }, { 3, 4 } };
> > > long long
> > > foo ()
> > > {
> > >   auto p = std::start_lifetime_as_array<T> (reinterpret_cast<const
> void *>
> > > (&s[0]), 2);
> > >   return p[0].b + p[0].c;
> > > }
> > > instead for some reason?
> > >
> > I think modifies a const object, at starting the lifetime of a new
> object,
> > reuses storage,
> > and then destroys the old object. But, I agree you can construct valid
> > cases where it should
> > work, like:
> > union {
> >   const S s;
> > } u;
> > U* u = start_lifetime_as<const S>(&u.s);
> > So yes, memove is not really an option.
> >
> > >
> > > Or multiple threads doing start_lifetime_as on the same block of memory
> > > concurrently?
> > >
> > I think it is unclear if that is causing a data race, but I agree that it
> > would be
> > better for it to not create a data race if threads are only reading from
> > memory.
> >
> > > My understanding is that it is as if it is a store but not really.
>
> For the middle-end it needs to appear to be a store, but if it isn't
> really then it may not appear as a true store.  But my hunch is that
> people will expect std::start_lifetime_as<T> to _not_ be sth like
> a full optimization barrier.
>
> But yeah, if std::start_lifetime_as<T> is a thing for 'const' storage
> (huh - how can you start a new object lifetime in something immutable?)
> then actual stores are not going to work.  But then you need to hide
> the const-ness from the middle-end because aliasing will happily say
> it isn't a store then and move loads across causing TBAA issues again.
>
> So, is std::start_lifetime_as<T> a thing on readonly objects?
>
> Richard.

Reply via email to