Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
1 change: 1 addition & 0 deletions libcxx/include/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,7 @@ set(files
__type_traits/is_reference.h
__type_traits/is_reference_wrapper.h
__type_traits/is_referenceable.h
__type_traits/is_replaceable.h
__type_traits/is_same.h
__type_traits/is_scalar.h
__type_traits/is_signed.h
Expand Down
5 changes: 4 additions & 1 deletion libcxx/include/__exception/exception_ptr.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,11 @@ class _LIBCPP_EXPORTED_FROM_ABI exception_ptr {
friend _LIBCPP_HIDE_FROM_ABI exception_ptr make_exception_ptr(_Ep) _NOEXCEPT;

public:
// exception_ptr is basically a COW string.
// exception_ptr is basically a COW string so it is trivially relocatable.
// However, it's not replaceable because destroying and move-constructing could cause
// the underlying refcount to hit 0 if we're self-assigning.
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure I understand this. If we're self-assigning it's a no-op and shouldn't potentially delete the underlying resource.

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 semantics of __is_replaceable mean that move-assignment is equivalent to destroy followed by a move construction. However, you can't replace a move-assignment by a destroy + move-construct for std::exception_ptr, since if both the source and destination are the same object, the destroy + move-construction will result in the refcount hitting 0 (after destruction) whereas the move-assignment wouldn't have.

This actually makes me realize: you can never do destroy + move-construct if the source and the destination are the same object. This has nothing to do with the presence of a refcount in the object. How is __is_replaceable supposed to work in that case? Aren't all types non-replaceable, then?

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think you have to consider the same memory location case. You can't relocate an object into the same place it is already in, since you can't relocate from an object that is already destroyed (which you just did by relocating into the same memory location).

Copy link
Member Author

Choose a reason for hiding this comment

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

That makes me wonder how useful __is_replaceable is as a trait outside of relocation use cases. It might not be.

In order to make progress for the time being, I will add documentation clarifying that __is_replaceable doesn't apply if you're doing a self-assignment. And then I will revisit whether these types can be replaceable or not based on that new point of view.

using __trivially_relocatable _LIBCPP_NODEBUG = exception_ptr;
using __replaceable _LIBCPP_NODEBUG = void;

_LIBCPP_HIDE_FROM_ABI exception_ptr() _NOEXCEPT : __ptr_() {}
_LIBCPP_HIDE_FROM_ABI exception_ptr(nullptr_t) _NOEXCEPT : __ptr_() {}
Expand Down
3 changes: 3 additions & 0 deletions libcxx/include/__expected/expected.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <__type_traits/is_nothrow_assignable.h>
#include <__type_traits/is_nothrow_constructible.h>
#include <__type_traits/is_reference.h>
#include <__type_traits/is_replaceable.h>
#include <__type_traits/is_same.h>
#include <__type_traits/is_swappable.h>
#include <__type_traits/is_trivially_constructible.h>
Expand Down Expand Up @@ -470,6 +471,8 @@ class expected : private __expected_base<_Tp, _Err> {
__conditional_t<__libcpp_is_trivially_relocatable<_Tp>::value && __libcpp_is_trivially_relocatable<_Err>::value,
expected,
void>;
using __replaceable _LIBCPP_NODEBUG =
__conditional_t<__is_replaceable_v<_Tp> && __is_replaceable_v<_Err>, expected, void>;

template <class _Up>
using rebind = expected<_Up, error_type>;
Expand Down
5 changes: 4 additions & 1 deletion libcxx/include/__locale
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,11 @@ _LIBCPP_HIDE_FROM_ABI const _Facet& use_facet(const locale&);

class _LIBCPP_EXPORTED_FROM_ABI locale {
public:
// locale is essentially a shared_ptr that doesn't support weak_ptrs and never got a move constructor.
// locale is essentially a shared_ptr that doesn't support weak_ptrs and never got a move constructor,
// so it is trivially relocatable. However, it is not replaceable because self-assignment must prevent
// the refcount from hitting 0.
using __trivially_relocatable _LIBCPP_NODEBUG = locale;
using __replaceable _LIBCPP_NODEBUG = void;

// types:
class _LIBCPP_EXPORTED_FROM_ABI facet;
Expand Down
7 changes: 7 additions & 0 deletions libcxx/include/__memory/shared_ptr.h
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,11 @@ class _LIBCPP_SHARED_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS shared_ptr {

// A shared_ptr contains only two raw pointers which point to the heap and move constructing already doesn't require
// any bookkeeping, so it's always trivially relocatable.
//
// However, it's not replaceable because of self-assignment, which must prevent the refcount from
// hitting 0.
using __trivially_relocatable _LIBCPP_NODEBUG = shared_ptr;
using __replaceable _LIBCPP_NODEBUG = void;

private:
element_type* __ptr_;
Expand Down Expand Up @@ -1212,7 +1216,10 @@ class _LIBCPP_SHARED_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS weak_ptr {

// A weak_ptr contains only two raw pointers which point to the heap and move constructing already doesn't require
// any bookkeeping, so it's always trivially relocatable.
//
// However, it's not replaceable because we must preserve a non-zero refcount through self-assignment.
using __trivially_relocatable _LIBCPP_NODEBUG = weak_ptr;
using __replaceable _LIBCPP_NODEBUG = void;

private:
element_type* __ptr_;
Expand Down
5 changes: 5 additions & 0 deletions libcxx/include/__memory/unique_ptr.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include <__type_traits/is_function.h>
#include <__type_traits/is_pointer.h>
#include <__type_traits/is_reference.h>
#include <__type_traits/is_replaceable.h>
#include <__type_traits/is_same.h>
#include <__type_traits/is_swappable.h>
#include <__type_traits/is_trivially_relocatable.h>
Expand Down Expand Up @@ -144,6 +145,8 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr {
__libcpp_is_trivially_relocatable<pointer>::value && __libcpp_is_trivially_relocatable<deleter_type>::value,
unique_ptr,
void>;
using __replaceable _LIBCPP_NODEBUG =
__conditional_t<__is_replaceable_v<pointer> && __is_replaceable_v<deleter_type>, unique_ptr, void>;

private:
_LIBCPP_COMPRESSED_PAIR(pointer, __ptr_, deleter_type, __deleter_);
Expand Down Expand Up @@ -410,6 +413,8 @@ class _LIBCPP_UNIQUE_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS unique_ptr<_Tp[], _Dp>
__libcpp_is_trivially_relocatable<pointer>::value && __libcpp_is_trivially_relocatable<deleter_type>::value,
unique_ptr,
void>;
using __replaceable _LIBCPP_NODEBUG =
__conditional_t<__is_replaceable_v<pointer> && __is_replaceable_v<deleter_type>, unique_ptr, void>;

private:
template <class _Up, class _OtherDeleter>
Expand Down
5 changes: 5 additions & 0 deletions libcxx/include/__split_buffer
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <__type_traits/integral_constant.h>
#include <__type_traits/is_nothrow_assignable.h>
#include <__type_traits/is_nothrow_constructible.h>
#include <__type_traits/is_replaceable.h>
#include <__type_traits/is_swappable.h>
#include <__type_traits/is_trivially_destructible.h>
#include <__type_traits/is_trivially_relocatable.h>
Expand Down Expand Up @@ -72,6 +73,10 @@ public:
__libcpp_is_trivially_relocatable<pointer>::value && __libcpp_is_trivially_relocatable<allocator_type>::value,
__split_buffer,
void>;
using __replaceable _LIBCPP_NODEBUG =
__conditional_t<__is_replaceable_v<pointer> && __container_allocator_is_replaceable<__alloc_traits>::value,
__split_buffer,
void>;

pointer __first_;
pointer __begin_;
Expand Down
62 changes: 62 additions & 0 deletions libcxx/include/__type_traits/is_replaceable.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//===----------------------------------------------------------------------===//
//
// 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___TYPE_TRAITS_IS_REPLACEABLE_H
#define _LIBCPP___TYPE_TRAITS_IS_REPLACEABLE_H

#include <__config>
#include <__type_traits/enable_if.h>
#include <__type_traits/integral_constant.h>
#include <__type_traits/is_same.h>
#include <__type_traits/is_trivially_copyable.h>

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

_LIBCPP_BEGIN_NAMESPACE_STD

// A type is replaceable if `x = std::move(y)` is equivalent to:
//
// std::destroy_at(&x)
// std::construct_at(&x, std::move(y))
//
// This allows turning a move-assignment into a sequence of destroy + move-construct, which
// is often more efficient. This is especially relevant when the move-construct is in fact
// part of a trivial relocation from somewhere else, in which case there is a huge win.
//
// Note that this requires language support in order to be really effective, but we
// currently emulate the base template with something very conservative.
template <class _Tp, class = void>
struct __is_replaceable : is_trivially_copyable<_Tp> {};

template <class _Tp>
struct __is_replaceable<_Tp, __enable_if_t<is_same<_Tp, typename _Tp::__replaceable>::value> > : true_type {};

template <class _Tp>
inline const bool __is_replaceable_v = __is_replaceable<_Tp>::value;

// Determines whether an allocator member of a container is replaceable.
//
// We take into account whether the allocator is propagated on assignments. If the allocator
// always compares equal, then it doesn't matter whether we propagate it or not on assignments,
// the result will be the same and we can just as much move-construct it instead.
//
// If the allocator does not always compare equal, we check whether it propagates on assignment
// and it is replaceable.
template <class _AllocatorTraits>
struct __container_allocator_is_replaceable
: integral_constant<bool,
_AllocatorTraits::is_always_equal::value ||
(_AllocatorTraits::propagate_on_container_move_assignment::value &&
_AllocatorTraits::propagate_on_container_copy_assignment::value &&
__is_replaceable_v<typename _AllocatorTraits::allocator_type>)> {};

_LIBCPP_END_NAMESPACE_STD

#endif // _LIBCPP___TYPE_TRAITS_IS_REPLACEABLE_H
3 changes: 3 additions & 0 deletions libcxx/include/__utility/pair.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
#include <__type_traits/is_implicitly_default_constructible.h>
#include <__type_traits/is_nothrow_assignable.h>
#include <__type_traits/is_nothrow_constructible.h>
#include <__type_traits/is_replaceable.h>
#include <__type_traits/is_same.h>
#include <__type_traits/is_swappable.h>
#include <__type_traits/is_trivially_relocatable.h>
#include <__type_traits/nat.h>
Expand Down Expand Up @@ -72,6 +74,7 @@ struct _LIBCPP_TEMPLATE_VIS pair
__conditional_t<__libcpp_is_trivially_relocatable<_T1>::value && __libcpp_is_trivially_relocatable<_T2>::value,
pair,
void>;
using __replaceable _LIBCPP_NODEBUG = __conditional_t<__is_replaceable_v<_T1> && __is_replaceable_v<_T2>, pair, void>;

_LIBCPP_HIDE_FROM_ABI pair(pair const&) = default;
_LIBCPP_HIDE_FROM_ABI pair(pair&&) = default;
Expand Down
5 changes: 5 additions & 0 deletions libcxx/include/__vector/vector.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
#include <__type_traits/is_nothrow_assignable.h>
#include <__type_traits/is_nothrow_constructible.h>
#include <__type_traits/is_pointer.h>
#include <__type_traits/is_replaceable.h>
#include <__type_traits/is_same.h>
#include <__type_traits/is_trivially_relocatable.h>
#include <__type_traits/type_identity.h>
Expand Down Expand Up @@ -120,6 +121,10 @@ class _LIBCPP_TEMPLATE_VIS vector {
__libcpp_is_trivially_relocatable<pointer>::value && __libcpp_is_trivially_relocatable<allocator_type>::value,
vector,
void>;
using __replaceable _LIBCPP_NODEBUG =
__conditional_t<__is_replaceable_v<pointer> && __container_allocator_is_replaceable<__alloc_traits>::value,
vector,
void>;

static_assert(__check_valid_allocator<allocator_type>::value, "");
static_assert(is_same<typename allocator_type::value_type, value_type>::value,
Expand Down
2 changes: 2 additions & 0 deletions libcxx/include/array
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ template <size_t I, class T, size_t N> const T&& get(const array<T, N>&&) noexce
# include <__type_traits/is_const.h>
# include <__type_traits/is_constructible.h>
# include <__type_traits/is_nothrow_constructible.h>
# include <__type_traits/is_replaceable.h>
# include <__type_traits/is_same.h>
# include <__type_traits/is_swappable.h>
# include <__type_traits/is_trivially_relocatable.h>
Expand Down Expand Up @@ -175,6 +176,7 @@ template <class _Tp, size_t _Size>
struct _LIBCPP_TEMPLATE_VIS array {
using __trivially_relocatable _LIBCPP_NODEBUG =
__conditional_t<__libcpp_is_trivially_relocatable<_Tp>::value, array, void>;
using __replaceable _LIBCPP_NODEBUG = __conditional_t<__is_replaceable_v<_Tp>, array, void>;

// types:
using __self _LIBCPP_NODEBUG = array;
Expand Down
5 changes: 5 additions & 0 deletions libcxx/include/deque
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ template <class T, class Allocator, class Predicate>
# include <__type_traits/is_convertible.h>
# include <__type_traits/is_nothrow_assignable.h>
# include <__type_traits/is_nothrow_constructible.h>
# include <__type_traits/is_replaceable.h>
# include <__type_traits/is_same.h>
# include <__type_traits/is_swappable.h>
# include <__type_traits/is_trivially_relocatable.h>
Expand Down Expand Up @@ -530,6 +531,10 @@ public:
__libcpp_is_trivially_relocatable<__map>::value && __libcpp_is_trivially_relocatable<allocator_type>::value,
deque,
void>;
using __replaceable _LIBCPP_NODEBUG =
__conditional_t<__is_replaceable_v<__map> && __container_allocator_is_replaceable<__alloc_traits>::value,
deque,
void>;

static_assert(is_nothrow_default_constructible<allocator_type>::value ==
is_nothrow_default_constructible<__pointer_allocator>::value,
Expand Down
4 changes: 4 additions & 0 deletions libcxx/include/module.modulemap
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,10 @@ module std_core [system] {
header "__type_traits/is_referenceable.h"
export std_core.type_traits.integral_constant
}
module is_replaceable {
header "__type_traits/is_replaceable.h"
export std_core.type_traits.integral_constant
}
module is_same {
header "__type_traits/is_same.h"
export std_core.type_traits.integral_constant
Expand Down
2 changes: 2 additions & 0 deletions libcxx/include/optional
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ namespace std {
# include <__type_traits/is_nothrow_constructible.h>
# include <__type_traits/is_object.h>
# include <__type_traits/is_reference.h>
# include <__type_traits/is_replaceable.h>
# include <__type_traits/is_same.h>
# include <__type_traits/is_scalar.h>
# include <__type_traits/is_swappable.h>
Expand Down Expand Up @@ -590,6 +591,7 @@ public:

using __trivially_relocatable _LIBCPP_NODEBUG =
conditional_t<__libcpp_is_trivially_relocatable<_Tp>::value, optional, void>;
using __replaceable _LIBCPP_NODEBUG = conditional_t<__is_replaceable_v<_Tp>, optional, void>;

private:
// Disable the reference extension using this static assert.
Expand Down
8 changes: 8 additions & 0 deletions libcxx/include/string
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,7 @@ basic_string<char32_t> operator""s( const char32_t *str, size_t len );
# include <__type_traits/is_convertible.h>
# include <__type_traits/is_nothrow_assignable.h>
# include <__type_traits/is_nothrow_constructible.h>
# include <__type_traits/is_replaceable.h>
# include <__type_traits/is_same.h>
# include <__type_traits/is_standard_layout.h>
# include <__type_traits/is_trivial.h>
Expand Down Expand Up @@ -754,13 +755,20 @@ public:
// external memory. In such cases, the destructor is responsible for unpoisoning
// the memory to avoid triggering false positives.
// Therefore it's crucial to ensure the destructor is called.
//
// However, it is replaceable since implementing move-assignment as a destroy + move-construct
// will maintain the right ASAN state.
using __trivially_relocatable = void;
# else
using __trivially_relocatable _LIBCPP_NODEBUG = __conditional_t<
__libcpp_is_trivially_relocatable<allocator_type>::value && __libcpp_is_trivially_relocatable<pointer>::value,
basic_string,
void>;
# endif
using __replaceable _LIBCPP_NODEBUG =
__conditional_t<__is_replaceable_v<pointer> && __container_allocator_is_replaceable<__alloc_traits>::value,
basic_string,
void>;

# if _LIBCPP_HAS_ASAN && _LIBCPP_INSTRUMENTED_WITH_ASAN
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 pointer __asan_volatile_wrapper(pointer const& __ptr) const {
Expand Down
2 changes: 2 additions & 0 deletions libcxx/include/tuple
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ template <class... Types>
# include <__type_traits/is_nothrow_assignable.h>
# include <__type_traits/is_nothrow_constructible.h>
# include <__type_traits/is_reference.h>
# include <__type_traits/is_replaceable.h>
# include <__type_traits/is_same.h>
# include <__type_traits/is_swappable.h>
# include <__type_traits/is_trivially_relocatable.h>
Expand Down Expand Up @@ -555,6 +556,7 @@ class _LIBCPP_TEMPLATE_VIS _LIBCPP_NO_SPECIALIZATIONS tuple {
public:
using __trivially_relocatable _LIBCPP_NODEBUG =
__conditional_t<_And<__libcpp_is_trivially_relocatable<_Tp>...>::value, tuple, void>;
using __replaceable _LIBCPP_NODEBUG = __conditional_t<_And<__is_replaceable<_Tp>...>::value, tuple, void>;

// [tuple.cnstr]

Expand Down
2 changes: 2 additions & 0 deletions libcxx/include/variant
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ namespace std {
# include <__type_traits/is_nothrow_assignable.h>
# include <__type_traits/is_nothrow_constructible.h>
# include <__type_traits/is_reference.h>
# include <__type_traits/is_replaceable.h>
# include <__type_traits/is_same.h>
# include <__type_traits/is_swappable.h>
# include <__type_traits/is_trivially_assignable.h>
Expand Down Expand Up @@ -1176,6 +1177,7 @@ class _LIBCPP_TEMPLATE_VIS _LIBCPP_DECLSPEC_EMPTY_BASES _LIBCPP_NO_SPECIALIZATIO
public:
using __trivially_relocatable _LIBCPP_NODEBUG =
conditional_t<_And<__libcpp_is_trivially_relocatable<_Types>...>::value, variant, void>;
using __replaceable _LIBCPP_NODEBUG = conditional_t<_And<__is_replaceable<_Types>...>::value, variant, void>;

template <bool _Dummy = true,
enable_if_t<__dependent_type<is_default_constructible<__first_type>, _Dummy>::value, int> = 0>
Expand Down
Loading