[PATCH] libstdc++: Implement C++23 P2590R2 - Explicit lifetime management
Tomasz Kaminski
tkaminsk@redhat.com
Fri Aug 29 06:48:02 GMT 2025
Hi,
>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))));
The memove performs implicit object creation (
https://eel.is/c++draft/c.strings#cstring.syn-3),
so it does create (amongst other) objects of type T, in the provided memory
region. However,
it does not produce a pointer to a suitable create object
https://eel.is/c++draft/intro.object#12,
and we have all possible objects created.
Now, the launder now helps to address both:
* The precondition https://eel.is/c++draft/ptr.launder#2, filtres all
created objects to one
of type similar to T, to make behavior defined
* We get proper address to that pointer
We
Regards,
Tomasz
On Thu, Aug 28, 2025 at 8:12 PM Jakub Jelinek <jakub@redhat.com> 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?
>
> 2025-08-28 Jakub Jelinek <jakub@redhat.com>
>
> * 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
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20250829/59f44149/attachment-0001.htm>
More information about the Libstdc++
mailing list