Skip to content

Commit f02b661

Browse files
authored
[libc++] Add move constructor & assignment to exception_ptr (#164281)
This commit adds move constructor, 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. `bm_exception_ptr_move_*` benefits 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 `__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. `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
1 parent dccced2 commit f02b661

File tree

6 files changed

+152
-1
lines changed

6 files changed

+152
-1
lines changed

libcxx/include/__exception/exception_ptr.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,18 @@
1616
#include <__memory/construct_at.h>
1717
#include <__type_traits/decay.h>
1818
#include <__type_traits/is_pointer.h>
19+
#include <__utility/move.h>
20+
#include <__utility/swap.h>
1921
#include <cstdlib>
2022
#include <typeinfo>
2123

2224
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
2325
# pragma GCC system_header
2426
#endif
2527

28+
_LIBCPP_PUSH_MACROS
29+
#include <__undef_macros>
30+
2631
#ifndef _LIBCPP_ABI_MICROSOFT
2732

2833
# if _LIBCPP_AVAILABILITY_HAS_INIT_PRIMARY_EXCEPTION
@@ -57,6 +62,8 @@ _LIBCPP_BEGIN_UNVERSIONED_NAMESPACE_STD
5762

5863
#ifndef _LIBCPP_ABI_MICROSOFT
5964

65+
inline _LIBCPP_HIDE_FROM_ABI void swap(exception_ptr& __x, exception_ptr& __y) _NOEXCEPT;
66+
6067
class _LIBCPP_EXPORTED_FROM_ABI exception_ptr {
6168
void* __ptr_;
6269

@@ -75,7 +82,15 @@ class _LIBCPP_EXPORTED_FROM_ABI exception_ptr {
7582
_LIBCPP_HIDE_FROM_ABI exception_ptr(nullptr_t) _NOEXCEPT : __ptr_() {}
7683

7784
exception_ptr(const exception_ptr&) _NOEXCEPT;
85+
_LIBCPP_HIDE_FROM_ABI exception_ptr(exception_ptr&& __other) _NOEXCEPT : __ptr_(__other.__ptr_) {
86+
__other.__ptr_ = nullptr;
87+
}
7888
exception_ptr& operator=(const exception_ptr&) _NOEXCEPT;
89+
_LIBCPP_HIDE_FROM_ABI exception_ptr& operator=(exception_ptr&& __other) _NOEXCEPT {
90+
exception_ptr __tmp(std::move(__other));
91+
std::swap(__tmp, *this);
92+
return *this;
93+
}
7994
~exception_ptr() _NOEXCEPT;
8095

8196
_LIBCPP_HIDE_FROM_ABI explicit operator bool() const _NOEXCEPT { return __ptr_ != nullptr; }
@@ -88,10 +103,16 @@ class _LIBCPP_EXPORTED_FROM_ABI exception_ptr {
88103
return !(__x == __y);
89104
}
90105

106+
friend _LIBCPP_HIDE_FROM_ABI void swap(exception_ptr& __x, exception_ptr& __y) _NOEXCEPT;
107+
91108
friend _LIBCPP_EXPORTED_FROM_ABI exception_ptr current_exception() _NOEXCEPT;
92109
friend _LIBCPP_EXPORTED_FROM_ABI void rethrow_exception(exception_ptr);
93110
};
94111

112+
inline _LIBCPP_HIDE_FROM_ABI void swap(exception_ptr& __x, exception_ptr& __y) _NOEXCEPT {
113+
std::swap(__x.__ptr_, __y.__ptr_);
114+
}
115+
95116
# if _LIBCPP_HAS_EXCEPTIONS
96117
# if _LIBCPP_AVAILABILITY_HAS_INIT_PRIMARY_EXCEPTION
97118
template <class _Ep>
@@ -201,4 +222,6 @@ _LIBCPP_HIDE_FROM_ABI exception_ptr make_exception_ptr(_Ep __e) _NOEXCEPT {
201222
#endif // _LIBCPP_ABI_MICROSOFT
202223
_LIBCPP_END_UNVERSIONED_NAMESPACE_STD
203224

225+
_LIBCPP_POP_MACROS
226+
204227
#endif // _LIBCPP___EXCEPTION_EXCEPTION_PTR_H

libcxx/modules/std/exception.inc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export namespace std {
1818
using std::rethrow_exception;
1919
using std::rethrow_if_nested;
2020
using std::set_terminate;
21+
using std::swap;
2122
using std::terminate;
2223
using std::terminate_handler;
2324
using std::throw_with_nested;

libcxx/test/std/language.support/support.exception/propagation/exception_ptr.pass.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
#include <exception>
1616
#include <cassert>
17-
#include <type_traits>
1817

1918
#include "test_macros.h"
2019

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// UNSUPPORTED: no-exceptions, c++03
10+
11+
// <exception>
12+
13+
// typedef unspecified exception_ptr;
14+
15+
// Test the move assignment of exception_ptr
16+
17+
#include <exception>
18+
#include <utility>
19+
#include <cassert>
20+
21+
#include "test_macros.h"
22+
23+
int main(int, char**) {
24+
std::exception_ptr p = std::make_exception_ptr(42);
25+
std::exception_ptr p2{p};
26+
assert(p2 == p);
27+
// Under test: the move assignment
28+
std::exception_ptr p3;
29+
p3 = std::move(p2);
30+
assert(p3 == p);
31+
// `p2` was moved from. In libc++ it will be nullptr, but
32+
// this is not guaranteed by the standard.
33+
#if defined(_LIBCPP_VERSION) && !defined(_LIBCPP_ABI_MICROSOFT)
34+
assert(p2 == nullptr);
35+
assert(p2 == nullptr);
36+
#endif
37+
38+
try {
39+
std::rethrow_exception(p3);
40+
} catch (int e) {
41+
assert(e == 42);
42+
}
43+
44+
return 0;
45+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// UNSUPPORTED: no-exceptions, c++03
10+
11+
// <exception>
12+
13+
// typedef unspecified exception_ptr;
14+
15+
// Test the move constructor of exception_ptr
16+
17+
#include <exception>
18+
#include <utility>
19+
#include <cassert>
20+
21+
#include "test_macros.h"
22+
23+
int main(int, char**) {
24+
std::exception_ptr p = std::make_exception_ptr(42);
25+
std::exception_ptr p2{p};
26+
assert(p2 == p);
27+
// Under test: The move constructor
28+
std::exception_ptr p3{std::move(p2)};
29+
assert(p3 == p);
30+
// `p2` was moved from. In libc++ it will be nullptr, but
31+
// this is not guaranteed by the standard.
32+
#if defined(_LIBCPP_VERSION) && !defined(_LIBCPP_ABI_MICROSOFT)
33+
assert(p2 == nullptr);
34+
#endif
35+
36+
try {
37+
std::rethrow_exception(p3);
38+
} catch (int e) {
39+
assert(e == 42);
40+
}
41+
42+
return 0;
43+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// UNSUPPORTED: no-exceptions
10+
11+
// <exception>
12+
13+
// typedef unspecified exception_ptr;
14+
15+
// Test swapping of exception_ptr
16+
17+
#include <exception>
18+
#include <utility>
19+
#include <cassert>
20+
21+
#include "test_macros.h"
22+
23+
int main(int, char**) {
24+
std::exception_ptr p21 = std::make_exception_ptr(42);
25+
std::exception_ptr p42 = std::make_exception_ptr(21);
26+
std::swap(p42, p21);
27+
28+
try {
29+
std::rethrow_exception(p21);
30+
} catch (int e) {
31+
assert(e == 21);
32+
}
33+
try {
34+
std::rethrow_exception(p42);
35+
} catch (int e) {
36+
assert(e == 42);
37+
}
38+
39+
return 0;
40+
}

0 commit comments

Comments
 (0)