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

Conversation

phyBrackets
Copy link
Member

Implementing #105234

@phyBrackets phyBrackets requested a review from a team as a code owner September 4, 2024 09:34
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Sep 4, 2024
@llvmbot
Copy link
Collaborator

llvmbot commented Sep 4, 2024

@llvm/pr-subscribers-libcxx

Author: Shivam (phyBrackets)

Changes

Implementing #105234


Full diff: https://github.com/llvm/llvm-project/pull/107206.diff

5 Files Affected:

  • (modified) libcxx/include/CMakeLists.txt (+2)
  • (added) libcxx/include/__memory/start_lifetime_as.h (+50)
  • (added) libcxx/include/__memory/start_lifetime_as_array.h (+47)
  • (modified) libcxx/include/memory (+2)
  • (added) libcxx/test/std/utilities/memory/default.allocator/start_lifetime_as.pass.cpp (+127)
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 32579272858a8e..629742cbaee764 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -542,6 +542,8 @@ set(files
   __memory/ranges_uninitialized_algorithms.h
   __memory/raw_storage_iterator.h
   __memory/shared_ptr.h
+  __memory/start_lifetime_as_array.h
+  __memory/start_lifetime_as.h
   __memory/swap_allocator.h
   __memory/temp_value.h
   __memory/temporary_buffer.h
diff --git a/libcxx/include/__memory/start_lifetime_as.h b/libcxx/include/__memory/start_lifetime_as.h
new file mode 100644
index 00000000000000..f661a709c06c42
--- /dev/null
+++ b/libcxx/include/__memory/start_lifetime_as.h
@@ -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 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 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 start_lifetime_as<_Tp>(const_cast<void*>(__p));
+}
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___MEMORY_START_LIFETIME_AS_H
diff --git a/libcxx/include/__memory/start_lifetime_as_array.h b/libcxx/include/__memory/start_lifetime_as_array.h
new file mode 100644
index 00000000000000..5b227870f7ed6c
--- /dev/null
+++ b/libcxx/include/__memory/start_lifetime_as_array.h
@@ -0,0 +1,47 @@
+// -*- 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);
+}
+
+template <class _Tp>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR const _Tp* start_lifetime_as_array(const void* __p, size_t __n) _NOEXCEPT {
+  return 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 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 start_lifetime_as_array<_Tp>(const_cast<void*>(__p), __n);
+}
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___MEMORY_START_LIFETIME_AS_ARRAY_H
diff --git a/libcxx/include/memory b/libcxx/include/memory
index b940a32c3ebe6c..3162bbb2b906a9 100644
--- a/libcxx/include/memory
+++ b/libcxx/include/memory
@@ -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>
diff --git a/libcxx/test/std/utilities/memory/default.allocator/start_lifetime_as.pass.cpp b/libcxx/test/std/utilities/memory/default.allocator/start_lifetime_as.pass.cpp
new file mode 100644
index 00000000000000..e483516e77b46d
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/default.allocator/start_lifetime_as.pass.cpp
@@ -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;
+}
\ No newline at end of file


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.

@Zingam
Copy link
Contributor

Zingam commented Sep 4, 2024

Implementing #105234

"implementing" is not a valid GitHub "keyword", please check: https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue. You should use e.g. Closes #105234.

Also if I recall correctly you'll need this PR to be merged first: #106870

@cor3ntin
Copy link
Contributor

cor3ntin commented Sep 4, 2024

This requires compiler support to inject aliasing barriers.
Compiler support, in turns, requires llvm support.

There was an attempt here to support the non-array case https://reviews.llvm.org/D147020 @davidtgoldblatt @sam-mccall @ilya-biryukov

(See also an attempt to hijack launder-semantics for that, which we think is insufficient #82776 )

Given that all of this is only observable in complex use cases, i think it's prudent not to try to implement something that would not provide the same guarantees as the standard

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants