[libcxx-commits] [libcxx] [libc++] Add move constructor & assignment to `exception_ptr` (PR #164281)

Adrian Vogelsgesang via libcxx-commits libcxx-commits at lists.llvm.org
Mon Oct 20 10:11:30 PDT 2025


https://github.com/vogelsgesang updated https://github.com/llvm/llvm-project/pull/164281

>From 47823b92f0e160893e34b339c92e1c455d69cbea Mon Sep 17 00:00:00 2001
From: Adrian Vogelsgesang <avogelsgesang at salesforce.com>
Date: Mon, 20 Oct 2025 13:47:44 +0000
Subject: [PATCH 1/2] [libc++] Add benchmark for `std::exception_ptr`

This commit adds benchmarks for `std::exception_ptr` to set a baseline
in preparation for follow-up optimizations.
---
 .../test/benchmarks/exception_ptr.bench.cpp   | 45 +++++++++++++++++++
 1 file changed, 45 insertions(+)

diff --git a/libcxx/test/benchmarks/exception_ptr.bench.cpp b/libcxx/test/benchmarks/exception_ptr.bench.cpp
index 7791c510b1eb6..1ff1d8b10d2b0 100644
--- a/libcxx/test/benchmarks/exception_ptr.bench.cpp
+++ b/libcxx/test/benchmarks/exception_ptr.bench.cpp
@@ -18,4 +18,49 @@ void bm_make_exception_ptr(benchmark::State& state) {
 }
 BENCHMARK(bm_make_exception_ptr)->ThreadRange(1, 8);
 
+static bool exception_ptr_moves_copies_swap(std::exception_ptr p1) {
+  // Taken from https://github.com/llvm/llvm-project/issues/44892
+  std::exception_ptr p2(p1);            // Copy constructor
+  std::exception_ptr p3(std::move(p2)); // Move constructor
+  p2 = std::move(p1);                   // Move assignment
+  p1 = p2;                              // Copy assignment
+  swap(p1, p2);                         // Swap
+  // Comparisons against nullptr. The overhead from creating temporary `exception_ptr`
+  // instances should be optimized out.
+  bool is_null  = p1 == nullptr && nullptr == p2;
+  bool is_equal = p1 == p2; // Comparison
+  return is_null && is_equal;
+}
+
+void bm_nonnull_exception_ptr(benchmark::State& state) {
+  std::exception_ptr excptr = std::make_exception_ptr(42);
+  for (auto _ : state) {
+    benchmark::DoNotOptimize(excptr);
+    benchmark::DoNotOptimize(exception_ptr_moves_copies_swap(excptr));
+  }
+}
+BENCHMARK(bm_nonnull_exception_ptr);
+
+void bm_null_exception_ptr(benchmark::State& state) {
+  std::exception_ptr excptr;
+  for (auto _ : state) {
+    // All of the `exception_ptr_noops` are no-ops but the optimizer
+    // cannot optimize them away, because the `DoNotOptimize` calls
+    // prevent the optimizer from doing so.
+    benchmark::DoNotOptimize(excptr);
+    benchmark::DoNotOptimize(exception_ptr_moves_copies_swap(excptr));
+  }
+}
+BENCHMARK(bm_null_exception_ptr);
+
+void bm_optimized_null_exception_ptr(benchmark::State& state) {
+  for (auto _ : state) {
+    // All of the `exception_ptr_noops` are no-ops because
+    // the exception_ptr is empty. Hence, the compiler should
+    // be able to optimize them very aggressively.
+    benchmark::DoNotOptimize(exception_ptr_moves_copies_swap(std::exception_ptr{nullptr}));
+  }
+}
+BENCHMARK(bm_optimized_null_exception_ptr);
+
 BENCHMARK_MAIN();

>From b595542f9770363431d3fff43ebaa0537ee7a63f Mon Sep 17 00:00:00 2001
From: Adrian Vogelsgesang <avogelsgesang at salesforce.com>
Date: Mon, 20 Oct 2025 16:07:33 +0000
Subject: [PATCH 2/2] [libc++] Add move constructor & assignment to
 `exception_ptr`

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.
---
 libcxx/include/__exception/exception_ptr.h       | 16 ++++++++++++++++
 .../support/runtime/exception_pointer_cxxabi.ipp |  4 ++++
 .../runtime/exception_pointer_glibcxx.ipp        |  6 ++++++
 .../runtime/exception_pointer_unimplemented.ipp  |  5 +++++
 4 files changed, 31 insertions(+)

diff --git a/libcxx/include/__exception/exception_ptr.h b/libcxx/include/__exception/exception_ptr.h
index 796fa924be121..38c8036112be4 100644
--- a/libcxx/include/__exception/exception_ptr.h
+++ b/libcxx/include/__exception/exception_ptr.h
@@ -60,6 +60,9 @@ _LIBCPP_BEGIN_UNVERSIONED_NAMESPACE_STD
 class _LIBCPP_EXPORTED_FROM_ABI exception_ptr {
   void* __ptr_;
 
+  static void __do_decrement_refcount(void* __ptr) _NOEXCEPT;
+  _LIBCPP_HIDE_FROM_ABI static void __decrement_refcount(void* __ptr) _NOEXCEPT { if (__ptr) __do_decrement_refcount(__ptr); }
+
   static exception_ptr __from_native_exception_pointer(void*) _NOEXCEPT;
 
   template <class _Ep>
@@ -75,7 +78,9 @@ 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&&) _NOEXCEPT;
   exception_ptr& operator=(const exception_ptr&) _NOEXCEPT;
+  _LIBCPP_HIDE_FROM_ABI exception_ptr& operator=(exception_ptr&&) _NOEXCEPT;
   ~exception_ptr() _NOEXCEPT;
 
   _LIBCPP_HIDE_FROM_ABI explicit operator bool() const _NOEXCEPT { return __ptr_ != nullptr; }
@@ -92,6 +97,17 @@ class _LIBCPP_EXPORTED_FROM_ABI exception_ptr {
   friend _LIBCPP_EXPORTED_FROM_ABI void rethrow_exception(exception_ptr);
 };
 
+_LIBCPP_HIDE_FROM_ABI inline exception_ptr::exception_ptr(exception_ptr&& __other) _NOEXCEPT : __ptr_(__other.__ptr_) {
+  __other.__ptr_ = nullptr;
+}
+
+_LIBCPP_HIDE_FROM_ABI inline exception_ptr& exception_ptr::operator=(exception_ptr&& __other) _NOEXCEPT {
+  __decrement_refcount(__ptr_);
+  __ptr_         = __other.__ptr_;
+  __other.__ptr_ = nullptr;
+  return *this;
+}
+
 #  if _LIBCPP_HAS_EXCEPTIONS
 #    if _LIBCPP_AVAILABILITY_HAS_INIT_PRIMARY_EXCEPTION
 template <class _Ep>
diff --git a/libcxx/src/support/runtime/exception_pointer_cxxabi.ipp b/libcxx/src/support/runtime/exception_pointer_cxxabi.ipp
index 8f5c2060bb06c..a10c06457a998 100644
--- a/libcxx/src/support/runtime/exception_pointer_cxxabi.ipp
+++ b/libcxx/src/support/runtime/exception_pointer_cxxabi.ipp
@@ -13,6 +13,10 @@
 
 namespace std {
 
+void exception_ptr::__do_decrement_refcount(void* __ptr) noexcept {
+  __cxa_decrement_exception_refcount(__ptr);
+}
+
 exception_ptr::~exception_ptr() noexcept { __cxa_decrement_exception_refcount(__ptr_); }
 
 exception_ptr::exception_ptr(const exception_ptr& other) noexcept : __ptr_(other.__ptr_) {
diff --git a/libcxx/src/support/runtime/exception_pointer_glibcxx.ipp b/libcxx/src/support/runtime/exception_pointer_glibcxx.ipp
index 174b44ce0e6f7..c10004ead4f7c 100644
--- a/libcxx/src/support/runtime/exception_pointer_glibcxx.ipp
+++ b/libcxx/src/support/runtime/exception_pointer_glibcxx.ipp
@@ -27,12 +27,18 @@ struct exception_ptr {
   exception_ptr(const exception_ptr&) noexcept;
   exception_ptr& operator=(const exception_ptr&) noexcept;
   ~exception_ptr() noexcept;
+
+  void _M_release() noexcept;
 };
 
 } // namespace __exception_ptr
 
 [[noreturn]] void rethrow_exception(__exception_ptr::exception_ptr);
 
+void exception_ptr::__do_decrement_refcount(void* __ptr) noexcept {
+  reinterpret_cast<__exception_ptr::exception_ptr*>(this)->_M_release();
+}
+
 exception_ptr::~exception_ptr() noexcept { reinterpret_cast<__exception_ptr::exception_ptr*>(this)->~exception_ptr(); }
 
 exception_ptr::exception_ptr(const exception_ptr& other) noexcept : __ptr_(other.__ptr_) {
diff --git a/libcxx/src/support/runtime/exception_pointer_unimplemented.ipp b/libcxx/src/support/runtime/exception_pointer_unimplemented.ipp
index 05a71ce34e5ac..fdf2076c64386 100644
--- a/libcxx/src/support/runtime/exception_pointer_unimplemented.ipp
+++ b/libcxx/src/support/runtime/exception_pointer_unimplemented.ipp
@@ -11,6 +11,11 @@
 
 namespace std {
 
+void exception_ptr::__do_decrement_refcount(void* __ptr) noexcept {
+#warning exception_ptr not yet implemented
+  __libcpp_verbose_abort("exception_ptr not yet implemented\n");
+}
+
 exception_ptr::~exception_ptr() noexcept {
 #warning exception_ptr not yet implemented
   __libcpp_verbose_abort("exception_ptr not yet implemented\n");



More information about the libcxx-commits mailing list