On Thu, 28 Aug 2025, Jakub Jelinek wrote:

> Hi!
> 
> As I can't think of how the middle-end would treat
> __builtin_start_lifetime_as other than a blackbox and probably would
> need to be implemented as such inline asm in RTL, this patch
> just implements it using inline asm in the library.
> If not anything else, it can serve as fallback before we and/or clang
> get some builtin for it.
> 
> Right now the inline asms pretend (potential) read from the whole
> memory region and make optimizers forget where the return value points to.
> If the optimizers don't know where it points to, I think that should be
> good enough, but I'm a little bit afraid of possibly future optimizations
> trying to optimize
>   q->c = 1;
>   q->d = 2;
>   auto p = std::start_lifetime_as<S>(q);
>   if (p == reinterpret_cast<decltype (p)>(q))
>     return p->a + p->b;
> that because of the guarding condition or perhaps assertion we could
> simply use the q pointer in MEM_REFs with S type and be surprised by TBAA.
> Though if it is a must-alias case, then we should be fine as well.
> Though guess that would be the same case with a builtin.
> Or shall we also pretend the inline asm modifies (or could modify) the
> memory?

You definitely cannot move loads across std::start_lifetime_as<S>
given they might be UB (wrt TBAA) on one of the "sides".  So yes,
it needs to appear as a store as well.  But, since it isn't an
actual store in the end, it may not appear as a kill.

As said in the other mail the best thing we have is an actual store
(that we also code generate, if we can't elide it).  With your
approach some intended uses of std::start_lifetime_as<> will not
be optimized (what _are_ the intended use cases?)

Richard.

> 
> 2025-08-28  Jakub Jelinek  <[email protected]>
> 
>       * include/bits/version.def: Implement C++23 P2590R2 - Explicit
>       lifetime management.
>       (start_lifetime_as): New.
>       * include/bits/version.h: Regenerate.
>       * include/std/memory (std::start_lifetime_as,
>       std::start_lifetime_as_array): New function templates.
>       * testsuite/std/memory/start_lifetime_as/start_lifetime_as.cc: New test.
> 
> --- libstdc++-v3/include/bits/version.def.jj  2025-08-28 17:06:44.190031458 
> +0200
> +++ libstdc++-v3/include/bits/version.def     2025-08-28 18:44:00.947678928 
> +0200
> @@ -2100,6 +2100,14 @@ ftms = {
>    };
>  };
>  
> +ftms = {
> +  name = start_lifetime_as;
> +  values = {
> +    v =  202207;
> +    cxxmin = 23;
> +  };
> +};
> +
>  // Standard test specifications.
>  stds[97] = ">= 199711L";
>  stds[03] = ">= 199711L";
> --- libstdc++-v3/include/bits/version.h.jj    2025-08-28 17:06:49.160883915 
> +0200
> +++ libstdc++-v3/include/bits/version.h       2025-08-28 18:44:08.431550790 
> +0200
> @@ -2353,4 +2353,14 @@
>  #endif /* !defined(__cpp_lib_is_implicit_lifetime) && 
> defined(__glibcxx_want_is_implicit_lifetime) */
>  #undef __glibcxx_want_is_implicit_lifetime
>  
> +#if !defined(__cpp_lib_start_lifetime_as)
> +# if (__cplusplus >= 202100L)
> +#  define __glibcxx_start_lifetime_as 202207L
> +#  if defined(__glibcxx_want_all) || 
> defined(__glibcxx_want_start_lifetime_as)
> +#   define __cpp_lib_start_lifetime_as 202207L
> +#  endif
> +# endif
> +#endif /* !defined(__cpp_lib_start_lifetime_as) && 
> defined(__glibcxx_want_start_lifetime_as) */
> +#undef __glibcxx_want_start_lifetime_as
> +
>  #undef __glibcxx_want_all
> --- libstdc++-v3/include/std/memory.jj        2025-08-23 15:00:05.208775420 
> +0200
> +++ libstdc++-v3/include/std/memory   2025-08-28 19:47:26.101011256 +0200
> @@ -120,6 +120,7 @@
>  #define __glibcxx_want_shared_ptr_arrays
>  #define __glibcxx_want_shared_ptr_weak_type
>  #define __glibcxx_want_smart_ptr_for_overwrite
> +#define __glibcxx_want_start_lifetime_as
>  #define __glibcxx_want_to_address
>  #define __glibcxx_want_transparent_operators
>  #define __glibcxx_want_smart_ptr_owner_equality
> @@ -172,6 +173,117 @@ _GLIBCXX_END_NAMESPACE_VERSION
>  } // namespace
>  #endif // C++11 to C++20
>  
> +#if __cpp_lib_start_lifetime_as >= 202207L // C++ >= 23
> +namespace std _GLIBCXX_VISIBILITY(default)
> +{
> +_GLIBCXX_BEGIN_NAMESPACE_VERSION
> +  template<typename _Tp>
> +    [[__gnu__::__always_inline__]]
> +    inline _Tp*
> +    start_lifetime_as(void* __p) noexcept
> +    {
> +#if __cpp_lib_is_implicit_lifetime >= 202302L
> +      static_assert(is_implicit_lifetime_v<_Tp>);
> +#endif
> +      auto __q = reinterpret_cast<_Tp*>(__p);
> +      __asm__ __volatile__("" : "=g" (__q) : "0" (__q), "m" (*__q));
> +      return __q;
> +    }
> +
> +  template<typename _Tp>
> +    [[__gnu__::__always_inline__]]
> +    inline const _Tp*
> +    start_lifetime_as(const void* __p) noexcept
> +    {
> +#if __cpp_lib_is_implicit_lifetime >= 202302L
> +      static_assert(is_implicit_lifetime_v<_Tp>);
> +#endif
> +      auto __q = reinterpret_cast<const _Tp*>(__p);
> +      __asm__ __volatile__("" : "=g" (__q) : "0" (__q), "m" (*__q));
> +      return __q;
> +    }
> +
> +  template<typename _Tp>
> +    [[__gnu__::__always_inline__]]
> +    inline volatile _Tp*
> +    start_lifetime_as(volatile void* __p) noexcept
> +    {
> +#if __cpp_lib_is_implicit_lifetime >= 202302L
> +      static_assert(is_implicit_lifetime_v<_Tp>);
> +#endif
> +      auto __q = reinterpret_cast<volatile _Tp*>(__p);
> +      __asm__ __volatile__("" : "=g" (__q) : "0" (__q), "m" (*__q));
> +      return __q;
> +    }
> +
> +  template<typename _Tp>
> +    [[__gnu__::__always_inline__]]
> +    inline const volatile _Tp*
> +    start_lifetime_as(const volatile void* __p) noexcept
> +    {
> +#if __cpp_lib_is_implicit_lifetime >= 202302L
> +      static_assert(is_implicit_lifetime_v<_Tp>);
> +#endif
> +      auto __q = reinterpret_cast<const volatile _Tp*>(__p);
> +      __asm__ __volatile__("" : "=g" (__q) : "0" (__q), "m" (*__q));
> +      return __q;
> +    }
> +
> +  template<typename _Tp>
> +    [[__gnu__::__always_inline__]]
> +    inline _Tp*
> +    start_lifetime_as_array(void* __p, size_t __n) noexcept
> +    {
> +      auto __q = reinterpret_cast<_Tp*>(__p);
> +      if (!__n)
> +     return __q;
> +      auto __r = (__extension__ reinterpret_cast<_Tp(*)[__n]>(__p));
> +      __asm__ __volatile__("" : "=g" (__q) : "0" (__q), "m" (*__r));
> +      return __q;
> +    }
> +
> +  template<typename _Tp>
> +    [[__gnu__::__always_inline__]]
> +    inline const _Tp*
> +    start_lifetime_as_array(const void* __p, size_t __n) noexcept
> +    {
> +      auto __q = reinterpret_cast<const _Tp*>(__p);
> +      if (!__n)
> +     return __q;
> +      auto __r = (__extension__ reinterpret_cast<const _Tp(*)[__n]>(__p));
> +      __asm__ __volatile__("" : "=g" (__q) : "0" (__q), "m" (*__r));
> +      return __q;
> +    }
> +
> +  template<typename _Tp>
> +    [[__gnu__::__always_inline__]]
> +    inline volatile _Tp*
> +    start_lifetime_as_array(volatile void* __p, size_t __n) noexcept
> +    {
> +      auto __q = reinterpret_cast<volatile _Tp*>(__p);
> +      if (!__n)
> +     return __q;
> +      auto __r = (__extension__ reinterpret_cast<volatile _Tp(*)[__n]>(__p));
> +      __asm__ __volatile__("" : "=g" (__q) : "0" (__q), "m" (*__r));
> +      return __q;
> +    }
> +
> +  template<typename _Tp>
> +    [[__gnu__::__always_inline__]]
> +    inline const volatile _Tp*
> +    start_lifetime_as_array(const volatile void* __p, size_t __n) noexcept
> +    {
> +      auto __q = reinterpret_cast<const volatile _Tp*>(__p);
> +      if (!__n)
> +     return __q;
> +      auto __r = (__extension__ reinterpret_cast<const volatile 
> _Tp(*)[__n]>(__p));
> +      __asm__ __volatile__("" : "=g" (__q) : "0" (__q), "m" (*__r));
> +      return __q;
> +    }
> +_GLIBCXX_END_NAMESPACE_VERSION
> +} // namespace
> +#endif
> +
>  #ifdef __cpp_lib_parallel_algorithm // C++ >= 17 && HOSTED
>  // Parallel STL algorithms
>  # if _PSTL_EXECUTION_POLICIES_DEFINED
> --- 
> libstdc++-v3/testsuite/std/memory/start_lifetime_as/start_lifetime_as.cc.jj   
>     2025-08-28 19:29:12.865001523 +0200
> +++ libstdc++-v3/testsuite/std/memory/start_lifetime_as/start_lifetime_as.cc  
> 2025-08-28 19:56:44.975864728 +0200
> @@ -0,0 +1,68 @@
> +// { dg-do run { target c++23 } }
> +
> +#include <bit>
> +#include <memory>
> +
> +#include <testsuite_hooks.h>
> +#include <testsuite_allocator.h>
> +
> +struct S { int a; int b; };
> +struct T { long long c; };
> +
> +void
> +test01()
> +{
> +  if (sizeof(S) != sizeof(T))
> +    return;
> +
> +  union U { unsigned char a[sizeof(S)]; S b; T c; } u;
> +  u.a[0] = 1;
> +  T v = std::bit_cast<T> (S{1, 2});
> +  union V { unsigned char a[3 * sizeof(S)]; S b[3]; T c[3]; } w;
> +  T x = std::bit_cast<T> (S{3, 4});
> +  T y = std::bit_cast<T> (S{5, 6});
> +  S* d = std::start_lifetime_as<S>(reinterpret_cast<void*>(&u.a));
> +  d->a = 1;
> +  d->b = 2;
> +  T* e = std::start_lifetime_as<T>(reinterpret_cast<void*>(d));
> +  VERIFY( e->c == v.c );
> +  const T* f = std::start_lifetime_as<T>(reinterpret_cast<const void*>(d));
> +  VERIFY( f->c == v.c );
> +  volatile T* g = std::start_lifetime_as<T>(reinterpret_cast<volatile 
> void*>(d));
> +  VERIFY( g->c == v.c );
> +  const volatile T* h = std::start_lifetime_as<T>(reinterpret_cast<const 
> volatile void*>(d));
> +  VERIFY( h->c == v.c );
> +  S* i = std::start_lifetime_as_array<S>(reinterpret_cast<void*>(&w.a), 3);
> +  i[0].a = 1;
> +  i[0].b = 2;
> +  i[1].a = 3;
> +  i[1].b = 4;
> +  i[2].a = 5;
> +  i[2].b = 6;
> +  T* j = std::start_lifetime_as_array<T>(reinterpret_cast<void*>(i), 3);
> +  VERIFY( j[0].c == v.c && j[1].c == x.c && j[2].c == y.c );
> +  const T* k = std::start_lifetime_as_array<T>(reinterpret_cast<const 
> void*>(i), 3);
> +  VERIFY( k[0].c == v.c && k[1].c == x.c && k[2].c == y.c );
> +  volatile T* l = std::start_lifetime_as_array<T>(reinterpret_cast<volatile 
> void*>(i), 3);
> +  VERIFY( l[0].c == v.c && l[1].c == x.c && l[2].c == y.c );
> +  const volatile T* m = 
> std::start_lifetime_as_array<T>(reinterpret_cast<const volatile void*>(i), 3);
> +  VERIFY( m[0].c == v.c && m[1].c == x.c && m[2].c == y.c );
> +  T* n = std::start_lifetime_as_array<T>(static_cast<void*>(nullptr), 0);
> +  VERIFY( n == nullptr );
> +  const T* o = std::start_lifetime_as_array<T>(static_cast<const 
> void*>(nullptr), 0);
> +  VERIFY( o == nullptr );
> +  volatile T* p = std::start_lifetime_as_array<T>(static_cast<volatile 
> void*>(nullptr), 0);
> +  VERIFY( p == nullptr );
> +  const volatile T* q = std::start_lifetime_as_array<T>(static_cast<const 
> volatile void*>(nullptr), 0);
> +  VERIFY( q == nullptr );
> +  VERIFY( std::start_lifetime_as_array<T>(reinterpret_cast<void*>(&w.a), 0) 
> == &w.c[0] );
> +  VERIFY( std::start_lifetime_as_array<T>(reinterpret_cast<const 
> void*>(&w.a), 0) == static_cast<const T*>(&w.c[0]) );
> +  VERIFY( std::start_lifetime_as_array<T>(reinterpret_cast<volatile 
> void*>(&w.a), 0) == static_cast<volatile T*>(&w.c[0]) );
> +  VERIFY( std::start_lifetime_as_array<T>(reinterpret_cast<const volatile 
> void*>(&w.a), 0) == static_cast<const volatile T*>(&w.c[0]) );
> +}
> +
> +int
> +main()
> +{
> +  test01();
> +}
> 
>       Jakub
> 
> 

-- 
Richard Biener <[email protected]>
SUSE Software Solutions Germany GmbH,
Frankenstrasse 146, 90461 Nuernberg, Germany;
GF: Ivo Totev, Andrew McDonald, Werner Knoblich; (HRB 36809, AG Nuernberg)

Reply via email to