libstdc++: Implement C++23 P2590R2 - Explicit lifetime management [PR106658]

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 and write to 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.

2025-09-18  Jakub Jelinek  <jakub@redhat.com>

	PR c++/106658
	* 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.
	* src/c++23/std.cc.in (std::start_lifetime_as,
	std::start_lifetime_as_array): Export.
	* testsuite/std/memory/start_lifetime_as/start_lifetime_as.cc: New test.
This commit is contained in:
Jakub Jelinek
2025-09-18 07:44:54 +02:00
committed by Jakub Jelinek
parent dd713d0f3f
commit c1e1691b95
5 changed files with 245 additions and 0 deletions

View File

@@ -2117,6 +2117,14 @@ ftms = {
}; };
}; };
ftms = {
name = start_lifetime_as;
values = {
v = 202207;
cxxmin = 23;
};
};
// Standard test specifications. // Standard test specifications.
stds[97] = ">= 199711L"; stds[97] = ">= 199711L";
stds[03] = ">= 199711L"; stds[03] = ">= 199711L";

View File

@@ -2372,4 +2372,14 @@
#endif /* !defined(__cpp_lib_constexpr_exceptions) && defined(__glibcxx_want_constexpr_exceptions) */ #endif /* !defined(__cpp_lib_constexpr_exceptions) && defined(__glibcxx_want_constexpr_exceptions) */
#undef __glibcxx_want_constexpr_exceptions #undef __glibcxx_want_constexpr_exceptions
#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 #undef __glibcxx_want_all

View File

@@ -120,6 +120,7 @@
#define __glibcxx_want_shared_ptr_arrays #define __glibcxx_want_shared_ptr_arrays
#define __glibcxx_want_shared_ptr_weak_type #define __glibcxx_want_shared_ptr_weak_type
#define __glibcxx_want_smart_ptr_for_overwrite #define __glibcxx_want_smart_ptr_for_overwrite
#define __glibcxx_want_start_lifetime_as
#define __glibcxx_want_to_address #define __glibcxx_want_to_address
#define __glibcxx_want_transparent_operators #define __glibcxx_want_transparent_operators
#define __glibcxx_want_smart_ptr_owner_equality #define __glibcxx_want_smart_ptr_owner_equality
@@ -172,6 +173,134 @@ _GLIBCXX_END_NAMESPACE_VERSION
} // namespace } // namespace
#endif // C++11 to C++20 #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), "=m" (*__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);
auto __r = reinterpret_cast<_Tp*>(const_cast<void*>(__p));
__asm__ __volatile__("" : "=g" (__q), "=m" (*__r)
: "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);
auto __r = reinterpret_cast<_Tp*>(const_cast<void*>(__p));
__asm__ __volatile__("" : "=g" (__q), "=m" (*__r)
: "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);
auto __r = reinterpret_cast<_Tp*>(const_cast<void*>(__p));
__asm__ __volatile__("" : "=g" (__q), "=m" (*__r)
: "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), "=m" (*__r)
: "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));
auto __s = (__extension__
reinterpret_cast<_Tp(*)[__n]>(const_cast<void*>(__p)));
__asm__ __volatile__("" : "=g" (__q), "=m" (*__s)
: "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));
auto __s = (__extension__
reinterpret_cast<_Tp(*)[__n]>(const_cast<void*>(__p)));
__asm__ __volatile__("" : "=g" (__q), "=m" (*__s)
: "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));
auto __s = (__extension__
reinterpret_cast<_Tp(*)[__n]>(const_cast<void*>(__p)));
__asm__ __volatile__("" : "=g" (__q), "=m" (*__s)
: "0" (__q), "m" (*__r));
return __q;
}
_GLIBCXX_END_NAMESPACE_VERSION
} // namespace
#endif
#ifdef __cpp_lib_parallel_algorithm // C++ >= 17 && HOSTED #ifdef __cpp_lib_parallel_algorithm // C++ >= 17 && HOSTED
// Parallel STL algorithms // Parallel STL algorithms
# if _PSTL_EXECUTION_POLICIES_DEFINED # if _PSTL_EXECUTION_POLICIES_DEFINED

View File

@@ -2004,6 +2004,10 @@ export namespace std
using std::owner_equal; using std::owner_equal;
using std::owner_hash; using std::owner_hash;
#endif #endif
#if __cpp_lib_start_lifetime_as
using std::start_lifetime_as;
using std::start_lifetime_as_array;
#endif
} }
// 20.4 <memory_resource> // 20.4 <memory_resource>

View File

@@ -0,0 +1,94 @@
// { 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; };
template<typename S, typename T>
void
test01()
{
}
template<typename S, typename T>
requires (sizeof(S) == sizeof(T))
void
test01()
{
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]) );
static const S r[] = { { 5, 6 }, { 3, 4 } };
const T* s = std::start_lifetime_as<T>(&r[1]);
VERIFY( s->c == x.c );
const T* t = std::start_lifetime_as_array<T>(&r[0], 2);
VERIFY( t[0].c == y.c && t[1].c == x.c );
}
int
main()
{
test01<S, T>();
}