Skip to content

Conversation

@vogelsgesang
Copy link
Member

@vogelsgesang vogelsgesang commented Oct 20, 2025

This commit adds move constructor and move assignment and swap
to exception_ptr. Adding those operators allows us to avoid
unnecessary calls to __cxa_{inc,dec}rement_refcount.

Performance results (from libc++'s CI):

Benchmark                               Baseline    Candidate    Difference    % Difference
------------------------------------  ----------  -----------  ------------  --------------
bm_exception_ptr_copy_assign_nonnull        9.77         9.94          0.18           1.79%
bm_exception_ptr_copy_assign_null          10.29        10.65          0.35           3.42%
bm_exception_ptr_copy_ctor_nonnull          7.02         7.01         -0.01          -0.13%
bm_exception_ptr_copy_ctor_null            10.54        10.60          0.06           0.56%
bm_exception_ptr_move_assign_nonnull       16.92        13.76         -3.16         -18.70%
bm_exception_ptr_move_assign_null          10.61        10.76          0.14           1.36%
bm_exception_ptr_move_ctor_nonnull         13.31        10.25         -3.06         -23.02%
bm_exception_ptr_move_ctor_null            10.28         7.30         -2.98         -28.95%
bm_exception_ptr_swap_nonnull              19.22         0.63        -18.59         -96.74%
bm_exception_ptr_swap_null                 20.02         7.79        -12.23         -61.07%

As expected, the bm_exception_ptr_copy_* benchmarks are not influenced by this change.
The bm_exception_ptr_move_* benefit between 18% and 30%.
The bm_exception_ptr_swap_* tests show the biggest improvements since multiple calls to the copy constructor are replaced by a simple pointer swap.

While bm_exception_ptr_move_assign_null did not show a regression in the CI measurements, local measurements showed a regression from 3.98 to 4.71, i.e. by 18%. This is due to the additional __tmp inside operator=. The destructor of the original __other is a no-op after the move because __other.__ptr will be a nullptr. However, the compiler does not realize this, since the destructor is not inlined and is lacking a fast-path. As such, the swap-based implementation leads to an additional destructor call. The bm_exception_ptr_move_assign_nonnull still benefits because the swap-based move constructor avoids unnecessary _cxa{in,de}crement_refcount calls. As soon as we inline the destructor, this regression should disappear again.

Works towards #44892

@github-actions
Copy link

github-actions bot commented Oct 20, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

Copy link
Contributor

@philnik777 philnik777 left a comment

Choose a reason for hiding this comment

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

How about we instead add a swap and implement operator= as swap(exception_ptr())? That would avoid having to introduce any new symbols, at least in this patch.

@vogelsgesang vogelsgesang force-pushed the avogelsgesang-exceptionptr-move branch from b595542 to 154c286 Compare October 21, 2025 12:18
Copy link
Contributor

@philnik777 philnik777 left a comment

Choose a reason for hiding this comment

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

Basically LGTM, just some nits.

@vogelsgesang
Copy link
Member Author

I updated this commit to use implement the move-assignment using swap now:

_LIBCPP_HIDE_FROM_ABI inline exception_ptr& exception_ptr::operator=(exception_ptr&& __other) _NOEXCEPT {
  exception_ptr __tmp(std::move(__other));
  swap(__tmp, *this);
  return *this;
}

Is that what you had in mind?

Benchmark results (original implementation)

Benchmark                                         Baseline    Candidate    Difference    % Difference
----------------------------------------------  ----------  -----------  ------------  --------------
bm_exception_ptr_copy_assign_nonnull                 12.36        12.62          0.26            2.13
bm_exception_ptr_copy_assign_null                     3.97         4.06          0.09            2.23
bm_exception_ptr_copy_ctor_nonnull                   11.67        11.97          0.30            2.59
bm_exception_ptr_copy_ctor_null                       4.77         4.86          0.09            1.80
bm_exception_ptr_move_assign_nonnull                 24.07        14.28         -9.78          -40.65
bm_exception_ptr_move_assign_null                     3.98         2.42         -1.56          -39.17
bm_exception_ptr_move_copy_swap_nonnull              51.74        40.15        -11.59          -22.40
bm_exception_ptr_move_copy_swap_null                 32.63        23.07         -9.56          -29.30
bm_exception_ptr_move_copy_swap_null_optimized       30.50        21.15         -9.35          -30.66
bm_exception_ptr_move_ctor_nonnull                   23.44        14.38         -9.06          -38.65
bm_exception_ptr_move_ctor_null                       4.67         2.63         -2.05          -43.78
bm_exception_ptr_swap_nonnull                        33.51         2.64        -30.88          -92.13
bm_exception_ptr_swap_null                            7.88         2.38         -5.50          -69.82

Benchmark results (swap-based implementation)

Benchmark                                         Baseline    Candidate    Difference    % Difference
----------------------------------------------  ----------  -----------  ------------  --------------
bm_exception_ptr_copy_assign_nonnull                 12.36        12.85          0.49            3.95
bm_exception_ptr_copy_assign_null                     3.97         4.04          0.07            1.70
bm_exception_ptr_copy_ctor_nonnull                   11.67        12.15          0.49            4.17
bm_exception_ptr_copy_ctor_null                       4.77         4.80          0.03            0.61
bm_exception_ptr_move_assign_nonnull                 24.07        15.04         -9.02          -37.49
bm_exception_ptr_move_assign_null                     3.98         4.71          0.73           18.40
bm_exception_ptr_move_copy_swap_nonnull              51.74        39.24        -12.49          -24.15
bm_exception_ptr_move_copy_swap_null                 32.63        21.80        -10.83          -33.19
bm_exception_ptr_move_copy_swap_null_optimized       30.50        19.88        -10.63          -34.84
bm_exception_ptr_move_ctor_nonnull                   23.44        14.46         -8.98          -38.30
bm_exception_ptr_move_ctor_null                       4.67         2.58         -2.09          -44.81
bm_exception_ptr_swap_nonnull                        33.51         1.16        -32.36          -96.55
bm_exception_ptr_swap_null                            7.88         1.18         -6.70          -85.04

Interpretation of benchmark results

I consider the bm_exception_ptr_copy_* regressions to be noise.
For bm_exception_ptr_move_ctor_*, both variants perform the same.
The main difference is in bm_exception_ptr_move_assign_*, as expected.

In particular bm_exception_ptr_move_assign_null even regresses compared to main. This is probably due to the additional __tmp inside operator=. The destructor of the original __other is a no-op after the move because __other.__ptr will be a nullptr. However, the compiler does not realize this, since the destructor is not inlined and is lacking a fast-path. As such, the swap-based implementation leads to an additional destructor call.

The bm_exception_ptr_move_assign_nonnull still benefits because the swap-based move constructor avoids unnecessary __cxa_{in,de}crement_refcount calls.

As soon as we inline the destructor, this regression should disappear again. As such, I think we can live with that temporary regression - WDYT?

@philnik777
Copy link
Contributor

I updated this commit to use implement the move-assignment using swap now:

_LIBCPP_HIDE_FROM_ABI inline exception_ptr& exception_ptr::operator=(exception_ptr&& __other) _NOEXCEPT {
  exception_ptr __tmp(std::move(__other));
  swap(__tmp, *this);
  return *this;
}

Is that what you had in mind?

Yes, exactly.

Benchmark results (original implementation)

Benchmark                                         Baseline    Candidate    Difference    % Difference
----------------------------------------------  ----------  -----------  ------------  --------------
bm_exception_ptr_copy_assign_nonnull                 12.36        12.62          0.26            2.13
bm_exception_ptr_copy_assign_null                     3.97         4.06          0.09            2.23
bm_exception_ptr_copy_ctor_nonnull                   11.67        11.97          0.30            2.59
bm_exception_ptr_copy_ctor_null                       4.77         4.86          0.09            1.80
bm_exception_ptr_move_assign_nonnull                 24.07        14.28         -9.78          -40.65
bm_exception_ptr_move_assign_null                     3.98         2.42         -1.56          -39.17
bm_exception_ptr_move_copy_swap_nonnull              51.74        40.15        -11.59          -22.40
bm_exception_ptr_move_copy_swap_null                 32.63        23.07         -9.56          -29.30
bm_exception_ptr_move_copy_swap_null_optimized       30.50        21.15         -9.35          -30.66
bm_exception_ptr_move_ctor_nonnull                   23.44        14.38         -9.06          -38.65
bm_exception_ptr_move_ctor_null                       4.67         2.63         -2.05          -43.78
bm_exception_ptr_swap_nonnull                        33.51         2.64        -30.88          -92.13
bm_exception_ptr_swap_null                            7.88         2.38         -5.50          -69.82

Benchmark results (swap-based implementation)

Benchmark                                         Baseline    Candidate    Difference    % Difference
----------------------------------------------  ----------  -----------  ------------  --------------
bm_exception_ptr_copy_assign_nonnull                 12.36        12.85          0.49            3.95
bm_exception_ptr_copy_assign_null                     3.97         4.04          0.07            1.70
bm_exception_ptr_copy_ctor_nonnull                   11.67        12.15          0.49            4.17
bm_exception_ptr_copy_ctor_null                       4.77         4.80          0.03            0.61
bm_exception_ptr_move_assign_nonnull                 24.07        15.04         -9.02          -37.49
bm_exception_ptr_move_assign_null                     3.98         4.71          0.73           18.40
bm_exception_ptr_move_copy_swap_nonnull              51.74        39.24        -12.49          -24.15
bm_exception_ptr_move_copy_swap_null                 32.63        21.80        -10.83          -33.19
bm_exception_ptr_move_copy_swap_null_optimized       30.50        19.88        -10.63          -34.84
bm_exception_ptr_move_ctor_nonnull                   23.44        14.46         -8.98          -38.30
bm_exception_ptr_move_ctor_null                       4.67         2.58         -2.09          -44.81
bm_exception_ptr_swap_nonnull                        33.51         1.16        -32.36          -96.55
bm_exception_ptr_swap_null                            7.88         1.18         -6.70          -85.04

Interpretation of benchmark results

I consider the bm_exception_ptr_copy_* regressions to be noise. For bm_exception_ptr_move_ctor_*, both variants perform the same. The main difference is in bm_exception_ptr_move_assign_*, as expected.

In particular bm_exception_ptr_move_assign_null even regresses compared to main. This is probably due to the additional __tmp inside operator=. The destructor of the original __other is a no-op after the move because __other.__ptr will be a nullptr. However, the compiler does not realize this, since the destructor is not inlined and is lacking a fast-path. As such, the swap-based implementation leads to an additional destructor call.

The bm_exception_ptr_move_assign_nonnull still benefits because the swap-based move constructor avoids unnecessary __cxa_{in,de}crement_refcount calls.

As soon as we inline the destructor, this regression should disappear again. As such, I think we can live with that temporary regression - WDYT?

Yeah, I think that's fine.

Also, please update the commit message to mention that we introduce swap as well.

@vogelsgesang
Copy link
Member Author

vogelsgesang commented Oct 21, 2025

Yeah, I think that's fine.

👍 then I think we have high-level alignment on this PR. The next step will be to polish it for final review.

Also, please update the commit message to mention that we introduce swap as well.

I will do so after #164278 shipped, so I won't have to repeatedly rebase this PR and redo the measurements anymore

One more question:
The build currently fails due to #define move SYSTEM_RESERVED_NAME.
Should I simply add _LIBCPP_PUSH_MACROS #include <__undef_macros>? Or is there some other recommended solution?

@philnik777
Copy link
Contributor

Yeah, I think that's fine.

👍 then I think we have high-level alignment on this PR. The next step will be to polish it for final review.

Also, please update the commit message to mention that we introduce swap as well.

I will do so after #164278 shipped, so I won't have to repeatedly rebase this PR and redo the measurements anymore

One more question: The build currently fails due to #define move SYSTEM_RESERVED_NAME. Should I simply add _LIBCPP_PUSH_MACROS #include <__undef_macros>? Or is there some other recommended solution?

Yeah, that should fix the CI.

This commit adds move constructor and move assignment to
`exception_ptr`. Adding those operators allows us to avoid unnecessary
calls to `__cxa_{inc,dec}rement_refcount`.

Performance results:

```
Benchmark                          Baseline    Candidate    Difference    % Difference
-------------------------------  ----------  -----------  ------------  --------------
bm_nonnull_exception_ptr              52.22        40.92        -11.31          -21.65
bm_null_exception_ptr                 31.41        23.29         -8.12          -25.85
bm_optimized_null_exception_ptr       28.69        20.50         -8.19          -28.55
```

This commit does not add a `swap` specialization. Thanks to the added
move-assignment, we already save a couple of increments/decrements also
in the default `swap` implementation. The default `swap` is still not
perfect, as it calls the desctructor on `tmp`. As soon as we also
inlined the `~exception_ptr` destructor fast-path for `__ptr ==
nullptr`, the optimizer should be able to optimize the default `swap`
just as well as a specialized `swap`, though.
@vogelsgesang vogelsgesang force-pushed the avogelsgesang-exceptionptr-move branch from 154c286 to 0fcaeee Compare October 27, 2025 20:10
@vogelsgesang vogelsgesang force-pushed the avogelsgesang-exceptionptr-move branch from 0fcaeee to b106dc9 Compare October 27, 2025 20:16
@vogelsgesang
Copy link
Member Author

vogelsgesang commented Oct 27, 2025

/libcxx-bot benchmark libcxx/test/benchmarks/exception_ptr.bench.cpp

Benchmark results:
Benchmark                               Baseline    Candidate    Difference    % Difference
------------------------------------  ----------  -----------  ------------  --------------
bm_exception_ptr_copy_assign_nonnull        9.77         9.94          0.18           1.79%
bm_exception_ptr_copy_assign_null          10.29        10.65          0.35           3.42%
bm_exception_ptr_copy_ctor_nonnull          7.02         7.01         -0.01          -0.13%
bm_exception_ptr_copy_ctor_null            10.54        10.60          0.06           0.56%
bm_exception_ptr_move_assign_nonnull       16.92        13.76         -3.16         -18.70%
bm_exception_ptr_move_assign_null          10.61        10.76          0.14           1.36%
bm_exception_ptr_move_ctor_nonnull         13.31        10.25         -3.06         -23.02%
bm_exception_ptr_move_ctor_null            10.28         7.30         -2.98         -28.95%
bm_exception_ptr_swap_nonnull              19.22         0.63        -18.59         -96.74%
bm_exception_ptr_swap_null                 20.02         7.79        -12.23         -61.07%
bm_make_exception_ptr/threads:1            32.30        32.44          0.14           0.43%
bm_make_exception_ptr/threads:2            16.17        16.34          0.17           1.07%
bm_make_exception_ptr/threads:4             8.32         8.40          0.07           0.90%
bm_make_exception_ptr/threads:8             4.19         4.18         -0.00          -0.11%
Geomean                                    12.00         8.34         -3.65         -30.46%

Copy link
Contributor

@philnik777 philnik777 left a comment

Choose a reason for hiding this comment

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

Could you add some basic tests for the new functions? I'm pretty sure everything should be testable portably to some extent.

Copy link
Contributor

@philnik777 philnik777 left a comment

Choose a reason for hiding this comment

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

Except for also improving the MSVC version this looks basically good to me.

@vogelsgesang vogelsgesang marked this pull request as ready for review October 31, 2025 13:42
@vogelsgesang vogelsgesang requested a review from a team as a code owner October 31, 2025 13:42
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Oct 31, 2025
@llvmbot
Copy link
Member

llvmbot commented Oct 31, 2025

@llvm/pr-subscribers-libcxx

Author: Adrian Vogelsgesang (vogelsgesang)

Changes

This commit adds move constructor and move assignment and swap
to exception_ptr. Adding those operators allows us to avoid
unnecessary calls to __cxa_{inc,dec}rement_refcount.

Performance results:

Benchmark                               Baseline    Candidate    Difference    % Difference
------------------------------------  ----------  -----------  ------------  --------------
bm_exception_ptr_copy_assign_nonnull        9.77         9.94          0.18           1.79%
bm_exception_ptr_copy_assign_null          10.29        10.65          0.35           3.42%
bm_exception_ptr_copy_ctor_nonnull          7.02         7.01         -0.01          -0.13%
bm_exception_ptr_copy_ctor_null            10.54        10.60          0.06           0.56%
bm_exception_ptr_move_assign_nonnull       16.92        13.76         -3.16         -18.70%
bm_exception_ptr_move_assign_null          10.61        10.76          0.14           1.36%
bm_exception_ptr_move_ctor_nonnull         13.31        10.25         -3.06         -23.02%
bm_exception_ptr_move_ctor_null            10.28         7.30         -2.98         -28.95%
bm_exception_ptr_swap_nonnull              19.22         0.63        -18.59         -96.74%
bm_exception_ptr_swap_null                 20.02         7.79        -12.23         -61.07%

As expected, the bm_exception_ptr_copy_* benchmarks are not influenced by this change.
The bm_exception_ptr_move_* benefit between 18% and 30%.
The bm_exception_ptr_swap_* tests show the biggest improvements since multiple calls to the copy constructor are replaced by a simple pointer swap.

While bm_exception_ptr_move_assign_null did not show a regression in the CI measurements, local measurements showed a regression from 3.98 to 4.71, i.e. by 18%. This is due to the additional __tmp inside operator=. The destructor of the original __other is a no-op after the move because __other.__ptr will be a nullptr. However, the compiler does not realize this, since the destructor is not inlined and is lacking a fast-path. As such, the swap-based implementation leads to an additional destructor call. The bm_exception_ptr_move_assign_nonnull still benefits because the swap-based move constructor avoids unnecessary _cxa{in,de}crement_refcount calls. As soon as we inline the destructor, this regression should disappear again.


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

5 Files Affected:

  • (modified) libcxx/include/__exception/exception_ptr.h (+20)
  • (modified) libcxx/test/std/language.support/support.exception/propagation/exception_ptr.pass.cpp (-1)
  • (added) libcxx/test/std/language.support/support.exception/propagation/exception_ptr_move_assignment.cpp (+45)
  • (added) libcxx/test/std/language.support/support.exception/propagation/exception_ptr_move_ctr.cpp (+43)
  • (added) libcxx/test/std/language.support/support.exception/propagation/exception_ptr_swap.cpp (+40)
diff --git a/libcxx/include/__exception/exception_ptr.h b/libcxx/include/__exception/exception_ptr.h
index 796fa924be121..e096d8e1dfd49 100644
--- a/libcxx/include/__exception/exception_ptr.h
+++ b/libcxx/include/__exception/exception_ptr.h
@@ -16,6 +16,7 @@
 #include <__memory/construct_at.h>
 #include <__type_traits/decay.h>
 #include <__type_traits/is_pointer.h>
+#include <__utility/move.h>
 #include <cstdlib>
 #include <typeinfo>
 
@@ -23,6 +24,9 @@
 #  pragma GCC system_header
 #endif
 
+_LIBCPP_PUSH_MACROS
+#include <__undef_macros>
+
 #ifndef _LIBCPP_ABI_MICROSOFT
 
 #  if _LIBCPP_AVAILABILITY_HAS_INIT_PRIMARY_EXCEPTION
@@ -75,7 +79,15 @@ class _LIBCPP_EXPORTED_FROM_ABI exception_ptr {
   _LIBCPP_HIDE_FROM_ABI exception_ptr(nullptr_t) _NOEXCEPT : __ptr_() {}
 
   exception_ptr(const exception_ptr&) _NOEXCEPT;
+  _LIBCPP_HIDE_FROM_ABI exception_ptr(exception_ptr&& __other) _NOEXCEPT : __ptr_(__other.__ptr_) {
+    __other.__ptr_ = nullptr;
+  }
   exception_ptr& operator=(const exception_ptr&) _NOEXCEPT;
+  _LIBCPP_HIDE_FROM_ABI exception_ptr& operator=(exception_ptr&& __other) _NOEXCEPT {
+    exception_ptr __tmp(std::move(__other));
+    std::swap(__tmp, *this);
+    return *this;
+  }
   ~exception_ptr() _NOEXCEPT;
 
   _LIBCPP_HIDE_FROM_ABI explicit operator bool() const _NOEXCEPT { return __ptr_ != nullptr; }
@@ -88,10 +100,16 @@ class _LIBCPP_EXPORTED_FROM_ABI exception_ptr {
     return !(__x == __y);
   }
 
+  friend _LIBCPP_HIDE_FROM_ABI void swap(exception_ptr& __x, exception_ptr& __y) _NOEXCEPT;
+
   friend _LIBCPP_EXPORTED_FROM_ABI exception_ptr current_exception() _NOEXCEPT;
   friend _LIBCPP_EXPORTED_FROM_ABI void rethrow_exception(exception_ptr);
 };
 
+inline _LIBCPP_HIDE_FROM_ABI void swap(exception_ptr& __x, exception_ptr& __y) _NOEXCEPT {
+  swap(__x.__ptr_, __y.__ptr_);
+}
+
 #  if _LIBCPP_HAS_EXCEPTIONS
 #    if _LIBCPP_AVAILABILITY_HAS_INIT_PRIMARY_EXCEPTION
 template <class _Ep>
@@ -201,4 +219,6 @@ _LIBCPP_HIDE_FROM_ABI exception_ptr make_exception_ptr(_Ep __e) _NOEXCEPT {
 #endif // _LIBCPP_ABI_MICROSOFT
 _LIBCPP_END_UNVERSIONED_NAMESPACE_STD
 
+_LIBCPP_POP_MACROS
+
 #endif // _LIBCPP___EXCEPTION_EXCEPTION_PTR_H
diff --git a/libcxx/test/std/language.support/support.exception/propagation/exception_ptr.pass.cpp b/libcxx/test/std/language.support/support.exception/propagation/exception_ptr.pass.cpp
index 0aded33e660d5..7e25d40dc8a7d 100644
--- a/libcxx/test/std/language.support/support.exception/propagation/exception_ptr.pass.cpp
+++ b/libcxx/test/std/language.support/support.exception/propagation/exception_ptr.pass.cpp
@@ -14,7 +14,6 @@
 
 #include <exception>
 #include <cassert>
-#include <type_traits>
 
 #include "test_macros.h"
 
diff --git a/libcxx/test/std/language.support/support.exception/propagation/exception_ptr_move_assignment.cpp b/libcxx/test/std/language.support/support.exception/propagation/exception_ptr_move_assignment.cpp
new file mode 100644
index 0000000000000..3f041fc367dfd
--- /dev/null
+++ b/libcxx/test/std/language.support/support.exception/propagation/exception_ptr_move_assignment.cpp
@@ -0,0 +1,45 @@
+//===----------------------------------------------------------------------===//
+//
+// 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: no-exceptions
+
+// <exception>
+
+// typedef unspecified exception_ptr;
+
+// Test the move assignment of exception_ptr
+
+#include <exception>
+#include <utility>
+#include <cassert>
+
+#include "test_macros.h"
+
+int main(int, char**) {
+  std::exception_ptr p = std::make_exception_ptr(42);
+  std::exception_ptr p2{p};
+  assert(p2 == p);
+  // Under test: the move assignment
+  std::exception_ptr p3;
+  p3 = std::move(p2);
+  assert(p3 == p);
+// `p2` was moved from. In libc++ it will be nullptr, but
+// this is not guaranteed by the standard.
+#if defined(_LIBCPP_VERSION) && !defined(_LIBCPP_ABI_MICROSOFT)
+  assert(p2 == nullptr);
+  assert(p2 == nullptr);
+#endif
+
+  try {
+    std::rethrow_exception(p3);
+  } catch (int e) {
+    assert(e == 42);
+  }
+
+  return 0;
+}
\ No newline at end of file
diff --git a/libcxx/test/std/language.support/support.exception/propagation/exception_ptr_move_ctr.cpp b/libcxx/test/std/language.support/support.exception/propagation/exception_ptr_move_ctr.cpp
new file mode 100644
index 0000000000000..37421e86a3d3f
--- /dev/null
+++ b/libcxx/test/std/language.support/support.exception/propagation/exception_ptr_move_ctr.cpp
@@ -0,0 +1,43 @@
+//===----------------------------------------------------------------------===//
+//
+// 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: no-exceptions
+
+// <exception>
+
+// typedef unspecified exception_ptr;
+
+// Test the move constructor of exception_ptr
+
+#include <exception>
+#include <utility>
+#include <cassert>
+
+#include "test_macros.h"
+
+int main(int, char**) {
+  std::exception_ptr p = std::make_exception_ptr(42);
+  std::exception_ptr p2{p};
+  assert(p2 == p);
+  // Under test: The move constructor
+  std::exception_ptr p3{std::move(p2)};
+  assert(p3 == p);
+// `p2` was moved from. In libc++ it will be nullptr, but
+// this is not guaranteed by the standard.
+#if defined(_LIBCPP_VERSION) && !defined(_LIBCPP_ABI_MICROSOFT)
+  assert(p2 == nullptr);
+#endif
+
+  try {
+    std::rethrow_exception(p3);
+  } catch (int e) {
+    assert(e == 42);
+  }
+
+  return 0;
+}
\ No newline at end of file
diff --git a/libcxx/test/std/language.support/support.exception/propagation/exception_ptr_swap.cpp b/libcxx/test/std/language.support/support.exception/propagation/exception_ptr_swap.cpp
new file mode 100644
index 0000000000000..82b4713bed538
--- /dev/null
+++ b/libcxx/test/std/language.support/support.exception/propagation/exception_ptr_swap.cpp
@@ -0,0 +1,40 @@
+//===----------------------------------------------------------------------===//
+//
+// 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: no-exceptions
+
+// <exception>
+
+// typedef unspecified exception_ptr;
+
+// Test swapping of exception_ptr
+
+#include <exception>
+#include <utility>
+#include <cassert>
+
+#include "test_macros.h"
+
+int main(int, char**) {
+  std::exception_ptr p21 = std::make_exception_ptr(42);
+  std::exception_ptr p42 = std::make_exception_ptr(21);
+  std::swap(p42, p21);
+
+  try {
+    std::rethrow_exception(p21);
+  } catch (int e) {
+    assert(e == 21);
+  }
+  try {
+    std::rethrow_exception(p42);
+  } catch (int e) {
+    assert(e == 42);
+  }
+
+  return 0;
+}

@vogelsgesang vogelsgesang force-pushed the avogelsgesang-exceptionptr-move branch from 08b0a14 to ee4694c Compare October 31, 2025 14:13
Copy link
Contributor

@philnik777 philnik777 left a comment

Choose a reason for hiding this comment

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

LGTM % nit. I think you need to update the numbers in the commit message though.

@vogelsgesang
Copy link
Member Author

I think you need to update the numbers in the commit message though.

I am planning to use the PR description as my commit message. Afaict, the performance numbers in in the PR description should be up-to-date by now. Note that I used the "official CI" numbers (measured by your CI and posted to this comment) instead of my local measurements.

Or did I overlook something?

@philnik777
Copy link
Contributor

I think you need to update the numbers in the commit message though.

I am planning to use the PR description as my commit message. Afaict, the performance numbers in in the PR description should be up-to-date by now. Note that I used the "official CI" numbers (measured by your CI and posted to this comment) instead of my local measurements.

Or did I overlook something?

Ah, sorry, I thought you'd go for your numbers. The CI isn't super well tested, so I tend to prefer local numbers. But these look close enough to yours that it's probably fine. Never mind then.

Copy link
Member Author

@vogelsgesang vogelsgesang left a comment

Choose a reason for hiding this comment

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

@philnik777 Thanks for your approval. I had to do a couple more changes to (hopefully) get the CI green.

In particular on the two marked changes I am not 100% sure

using std::rethrow_exception;
using std::rethrow_if_nested;
using std::set_terminate;
using std::swap;
Copy link
Member Author

@vogelsgesang vogelsgesang Oct 31, 2025

Choose a reason for hiding this comment

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

I had to add std::swap to this list since otherwise
the test cases libcxx/module_std.gen.py/module_std.sh.cpp and libcxx/module_std_compat.gen.py/module_std_compat.sh.cpp failed.

I don't quite understand the modules build, though. Is this correct?

(Note that we now have both a std::swap and a std::_LIBCPP_ABI_NAMESPACE::swap because exception_ptr is in the unversioned namespace while the normal swap is in the versioned namespace. Not sure if that matters. I hope not 🤞 )

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 quite understand how these tests work either. I'm happy if the CI is happy here.

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 CI was indeed green 🙂
I merged this (after removing the documentation update from this PR)
Moving on to #165909

@vogelsgesang vogelsgesang merged commit f02b661 into llvm:main Nov 3, 2025
16 of 26 checks passed
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. performance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants