Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[libc++] P2590R2: Explicit lifetime management #107206

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions libcxx/include/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,8 @@ set(files
__memory/ranges_uninitialized_algorithms.h
__memory/raw_storage_iterator.h
__memory/shared_ptr.h
__memory/start_lifetime_as.h
__memory/start_lifetime_as_array.h
__memory/swap_allocator.h
__memory/temp_value.h
__memory/temporary_buffer.h
Expand Down
50 changes: 50 additions & 0 deletions libcxx/include/__memory/start_lifetime_as.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// -*- C++ -*-
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef _LIBCPP___MEMORY_START_LIFETIME_AS_H
#define _LIBCPP___MEMORY_START_LIFETIME_AS_H

#include <__config>
#include <__type_traits/is_array.h>
#include <__type_traits/remove_all_extents.h>

#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
#endif

_LIBCPP_BEGIN_NAMESPACE_STD

template <class _Tp>
struct _LIBCPP_TEMPLATE_VIS __start_lifetime_as_impl {
_LIBCPP_HIDE_FROM_ABI static _LIBCPP_CONSTEXPR _Tp* __call(void* __p) _NOEXCEPT { return static_cast<_Tp*>(__p); }
};

template <class _Tp>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR _Tp* start_lifetime_as(void* __p) _NOEXCEPT {
return __start_lifetime_as_impl<_Tp>::__call(__p);
}

template <class _Tp>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR const _Tp* start_lifetime_as(const void* __p) _NOEXCEPT {
return std::start_lifetime_as<_Tp>(const_cast<void*>(__p));
}

template <class _Tp>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR volatile _Tp* start_lifetime_as(volatile void* __p) _NOEXCEPT {
return std::start_lifetime_as<_Tp>(const_cast<void*>(__p));
}

template <class _Tp>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR const volatile _Tp* start_lifetime_as(const volatile void* __p) _NOEXCEPT {
return std::start_lifetime_as<_Tp>(const_cast<void*>(__p));
}

_LIBCPP_END_NAMESPACE_STD

#endif // _LIBCPP___MEMORY_START_LIFETIME_AS_H
48 changes: 48 additions & 0 deletions libcxx/include/__memory/start_lifetime_as_array.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// -*- C++ -*-
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef _LIBCPP___MEMORY_START_LIFETIME_AS_ARRAY_H
#define _LIBCPP___MEMORY_START_LIFETIME_AS_ARRAY_H

#include <__config>
#include <__memory/start_lifetime_as.h>
#include <cstddef>

#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
#endif

_LIBCPP_BEGIN_NAMESPACE_STD

template <class _Tp>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR _Tp*
start_lifetime_as_array(void* __p, [[__maybe_unused__]] size_t __n) _NOEXCEPT {
return static_cast<_Tp*>(__p);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No built-in used here. Could you explain why this is considered to work?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function itself doesn't actually do anything at runtime. It's a no-op in terms of generated code. It happens at the compiler level. When the compiler sees a call to this function, it treats it as a signal that it should consider the lifetime of an array of _Tp objects to have begun at the memory location pointed to by __p , even though no constructors have been called. Or is it intended something else? I am not sure, Could you explain?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the compiler sees a call to this function,

Currently, I'm not seeing anything in the source code of Clang or GCC that recognizes start_lifetime_as(_array). So I can't believe this approach will automatically work,

Or is it intended something else? I am not sure, Could you explain?

I guess an intrinsic like __builitin_start_lifetime_as (or __builtin_tbaa_barrier?) is expected.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah Okay, that make sense, is it worth it if we currently define a macro to check if the compiler supports the necessary intrinsics?

If supported, it declares the intrinsics and implements start_lifetime_as and start_lifetime_as_array using these intrinsics. If not supported, it falls back to the current implementation (which is essentially a no-op).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's a bad idea, since people expect things to work as advertised that exist. IOW, these functions should only exist if we can actually implement them.

}

template <class _Tp>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR const _Tp* start_lifetime_as_array(const void* __p, size_t __n) _NOEXCEPT {
return std::start_lifetime_as_array<_Tp>(const_cast<void*>(__p), __n);
}

template <class _Tp>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR volatile _Tp*
start_lifetime_as_array(volatile void* __p, size_t __n) _NOEXCEPT {
return std::start_lifetime_as_array<_Tp>(const_cast<void*>(__p), __n);
}

template <class _Tp>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR const volatile _Tp*
start_lifetime_as_array(const volatile void* __p, size_t __n) _NOEXCEPT {
return std::start_lifetime_as_array<_Tp>(const_cast<void*>(__p), __n);
}

_LIBCPP_END_NAMESPACE_STD

#endif // _LIBCPP___MEMORY_START_LIFETIME_AS_ARRAY_H
2 changes: 2 additions & 0 deletions libcxx/include/memory
Original file line number Diff line number Diff line change
Expand Up @@ -946,6 +946,8 @@ template<class Pointer = void, class Smart, class... Args>
#include <__memory/pointer_traits.h>
#include <__memory/raw_storage_iterator.h>
#include <__memory/shared_ptr.h>
#include <__memory/start_lifetime_as.h>
#include <__memory/start_lifetime_as_array.h>
#include <__memory/temporary_buffer.h>
#include <__memory/uninitialized_algorithms.h>
#include <__memory/unique_ptr.h>
Expand Down
2 changes: 2 additions & 0 deletions libcxx/include/module.modulemap
Original file line number Diff line number Diff line change
Expand Up @@ -1543,6 +1543,8 @@ module std_private_memory_shared_ptr [system] {
header "__memory/shared_ptr.h"
export std_private_memory_uninitialized_algorithms
}
module std_private_memory_start_lifetime_as_array [system] { header "__memory/start_lifetime_as_array.h" }
module std_private_memory_start_lifetime_as [system] { header "__memory/start_lifetime_as.h" }
module std_private_memory_swap_allocator [system] { header "__memory/swap_allocator.h" }
module std_private_memory_temp_value [system] { header "__memory/temp_value.h" }
module std_private_memory_temporary_buffer [system] {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// UNSUPPORTED: c++03, c++11, c++14, c++17

// <memory>

// template <class T>
// T* start_lifetime_as(void* p) noexcept;
//
// template <class T>
// const T* start_lifetime_as(const void* p) noexcept;
//
// template <class T>
// volatile T* start_lifetime_as(volatile void* p) noexcept;
//
// template <class T>
// const volatile T* start_lifetime_as(const volatile void* p) noexcept;
//
// template <class T>
// T* start_lifetime_as_array(void* p, size_t n) noexcept;
//
// template <class T>
// const T* start_lifetime_as_array(const void* p, size_t n) noexcept;
//
// template <class T>
// volatile T* start_lifetime_as_array(volatile void* p, size_t n) noexcept;
//
// template <class T>
// const volatile T* start_lifetime_as_array(const volatile void* p, size_t n) noexcept;

#include <memory>
#include <type_traits>
#include <cassert>

#include "test_macros.h"

struct S {
int x;
double y;
};

template <class T>
void test_start_lifetime_as() {
alignas(T) char buffer[sizeof(T)];

{
T* ptr = std::start_lifetime_as<T>(buffer);
ASSERT_SAME_TYPE(decltype(ptr), T*);
ASSERT_NOEXCEPT(std::start_lifetime_as<T>(buffer));
assert(ptr == reinterpret_cast<T*>(buffer));
}

{
const T* cptr = std::start_lifetime_as<T>(static_cast<const void*>(buffer));
ASSERT_SAME_TYPE(decltype(cptr), const T*);
ASSERT_NOEXCEPT(std::start_lifetime_as<T>(static_cast<const void*>(buffer)));
assert(cptr == reinterpret_cast<const T*>(buffer));
}

{
volatile T* vptr = std::start_lifetime_as<T>(static_cast<volatile void*>(buffer));
ASSERT_SAME_TYPE(decltype(vptr), volatile T*);
ASSERT_NOEXCEPT(std::start_lifetime_as<T>(static_cast<volatile void*>(buffer)));
assert(vptr == reinterpret_cast<volatile T*>(buffer));
}

{
const volatile T* cvptr = std::start_lifetime_as<T>(static_cast<const volatile void*>(buffer));
ASSERT_SAME_TYPE(decltype(cvptr), const volatile T*);
ASSERT_NOEXCEPT(std::start_lifetime_as<T>(static_cast<const volatile void*>(buffer)));
assert(cvptr == reinterpret_cast<const volatile T*>(buffer));
}
}

template <class T>
void test_start_lifetime_as_array() {
constexpr size_t count = 5;
alignas(T) char buffer[sizeof(T) * count];

{
T* ptr = std::start_lifetime_as_array<T>(buffer, count);
ASSERT_SAME_TYPE(decltype(ptr), T*);
ASSERT_NOEXCEPT(std::start_lifetime_as_array<T>(buffer, count));
assert(ptr == reinterpret_cast<T*>(buffer));
}

{
const T* cptr = std::start_lifetime_as_array<T>(static_cast<const void*>(buffer), count);
ASSERT_SAME_TYPE(decltype(cptr), const T*);
ASSERT_NOEXCEPT(std::start_lifetime_as_array<T>(static_cast<const void*>(buffer), count));
assert(cptr == reinterpret_cast<const T*>(buffer));
}

{
volatile T* vptr = std::start_lifetime_as_array<T>(static_cast<volatile void*>(buffer), count);
ASSERT_SAME_TYPE(decltype(vptr), volatile T*);
ASSERT_NOEXCEPT(std::start_lifetime_as_array<T>(static_cast<volatile void*>(buffer), count));
assert(vptr == reinterpret_cast<volatile T*>(buffer));
}

{
const volatile T* cvptr = std::start_lifetime_as_array<T>(static_cast<const volatile void*>(buffer), count);
ASSERT_SAME_TYPE(decltype(cvptr), const volatile T*);
ASSERT_NOEXCEPT(std::start_lifetime_as_array<T>(static_cast<const volatile void*>(buffer), count));
assert(cvptr == reinterpret_cast<const volatile T*>(buffer));
}
}

int main(int, char**) {
test_start_lifetime_as<char>();
test_start_lifetime_as<int>();
test_start_lifetime_as<double>();
test_start_lifetime_as<S>();

test_start_lifetime_as_array<char>();
test_start_lifetime_as_array<int>();
test_start_lifetime_as_array<double>();
test_start_lifetime_as_array<S>();

return 0;
}
Loading