Skip to content

Commit 27c8338

Browse files
authored
[libc++] Replace __compressed_pair with [[no_unique_address]] (#76756)
This significantly simplifies the code, improves compile times and improves the object layout of types using `__compressed_pair` in the unstable ABI. The only downside is that this is extremely ABI sensitive and pedantically breaks the ABI for empty final types, since the address of the subobject may change. The ABI of the whole object should not be affected. Fixes #91266 Fixes #93069
1 parent e16ec9b commit 27c8338

File tree

27 files changed

+636
-678
lines changed

27 files changed

+636
-678
lines changed

libcxx/docs/ReleaseNotes/20.rst

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ Implemented Papers
4343
- P2985R0: A type trait for detecting virtual base classes (`Github <https://github.com/llvm/llvm-project/issues/105432>`__)
4444
- ``std::jthread`` and ``<stop_token>`` are not guarded behind ``-fexperimental-library`` anymore
4545

46-
4746
Improvements and New Features
4847
-----------------------------
4948

@@ -53,6 +52,9 @@ Improvements and New Features
5352
- The ``_LIBCPP_ENABLE_CXX20_REMOVED_UNCAUGHT_EXCEPTION`` macro has been added to make ``std::uncaught_exception``
5453
available in C++20 and later modes.
5554

55+
- The internal structure ``__compressed_pair`` has been replaced with ``[[no_unique_address]]``, resulting in reduced
56+
compile times and smaller debug information as well as better code generation if optimizations are disabled.
57+
The Chromium project measured a 5% reduction in object file and debug information size.
5658

5759
Deprecations and Removals
5860
-------------------------
@@ -97,8 +99,16 @@ LLVM 21
9799
ABI Affecting Changes
98100
---------------------
99101

100-
- TODO
102+
- The internal structure ``__compressed_pair`` has been replaced with ``[[no_unique_address]]``. The ABI impact is:
101103

104+
- When using the Itanium ABI (most non-MSVC platforms), empty types are now placed at the beginning of the enclosing
105+
object instead of where the beginning of the ``__compressed_pair`` subobject was. This is only observable by
106+
checking the address of the empty allocator, equality comparator or hasher.
107+
- Additionally, using an overaligned empty type as an allocator, comparator or hasher in the associative containers
108+
(and only those containers) may result in the container's object object size and data layout changing beyond only
109+
the address of the empty member.
110+
- When using the MSVC ABI, this change results in some classes having a completely different memory layout, so this is
111+
a genuine ABI break. However, the library does not currently guarantee ABI stability on MSVC platforms.
102112

103113
Build System Changes
104114
--------------------

libcxx/include/__config

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
# define _LIBCPP_CONCAT_IMPL(_X, _Y) _X##_Y
3333
# define _LIBCPP_CONCAT(_X, _Y) _LIBCPP_CONCAT_IMPL(_X, _Y)
34+
# define _LIBCPP_CONCAT3(X, Y, Z) _LIBCPP_CONCAT(X, _LIBCPP_CONCAT(Y, Z))
3435

3536
# if __STDC_HOSTED__ == 0
3637
# define _LIBCPP_FREESTANDING
@@ -191,25 +192,6 @@ _LIBCPP_HARDENING_MODE_DEBUG
191192
# error "libc++ only supports C++03 with Clang-based compilers. Please enable C++11"
192193
# endif
193194

194-
// FIXME: ABI detection should be done via compiler builtin macros. This
195-
// is just a placeholder until Clang implements such macros. For now assume
196-
// that Windows compilers pretending to be MSVC++ target the Microsoft ABI,
197-
// and allow the user to explicitly specify the ABI to handle cases where this
198-
// heuristic falls short.
199-
# if defined(_LIBCPP_ABI_FORCE_ITANIUM) && defined(_LIBCPP_ABI_FORCE_MICROSOFT)
200-
# error "Only one of _LIBCPP_ABI_FORCE_ITANIUM and _LIBCPP_ABI_FORCE_MICROSOFT can be defined"
201-
# elif defined(_LIBCPP_ABI_FORCE_ITANIUM)
202-
# define _LIBCPP_ABI_ITANIUM
203-
# elif defined(_LIBCPP_ABI_FORCE_MICROSOFT)
204-
# define _LIBCPP_ABI_MICROSOFT
205-
# else
206-
# if defined(_WIN32) && defined(_MSC_VER)
207-
# define _LIBCPP_ABI_MICROSOFT
208-
# else
209-
# define _LIBCPP_ABI_ITANIUM
210-
# endif
211-
# endif
212-
213195
# if defined(_LIBCPP_ABI_MICROSOFT) && !defined(_LIBCPP_NO_VCRUNTIME)
214196
# define _LIBCPP_ABI_VCRUNTIME
215197
# endif

libcxx/include/__configuration/abi.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,25 @@
1818
# pragma GCC system_header
1919
#endif
2020

21+
// FIXME: ABI detection should be done via compiler builtin macros. This
22+
// is just a placeholder until Clang implements such macros. For now assume
23+
// that Windows compilers pretending to be MSVC++ target the Microsoft ABI,
24+
// and allow the user to explicitly specify the ABI to handle cases where this
25+
// heuristic falls short.
26+
#if defined(_LIBCPP_ABI_FORCE_ITANIUM) && defined(_LIBCPP_ABI_FORCE_MICROSOFT)
27+
# error "Only one of _LIBCPP_ABI_FORCE_ITANIUM and _LIBCPP_ABI_FORCE_MICROSOFT can be defined"
28+
#elif defined(_LIBCPP_ABI_FORCE_ITANIUM)
29+
# define _LIBCPP_ABI_ITANIUM
30+
#elif defined(_LIBCPP_ABI_FORCE_MICROSOFT)
31+
# define _LIBCPP_ABI_MICROSOFT
32+
#else
33+
# if defined(_WIN32) && defined(_MSC_VER)
34+
# define _LIBCPP_ABI_MICROSOFT
35+
# else
36+
# define _LIBCPP_ABI_ITANIUM
37+
# endif
38+
#endif
39+
2140
#if _LIBCPP_ABI_VERSION >= 2
2241
// Change short string representation so that string data starts at offset 0,
2342
// improving its alignment in some cases.
@@ -98,6 +117,13 @@
98117
// and WCHAR_MAX. This ABI setting determines whether we should instead track whether the fill
99118
// value has been initialized using a separate boolean, which changes the ABI.
100119
# define _LIBCPP_ABI_IOS_ALLOW_ARBITRARY_FILL_VALUE
120+
// Historically, libc++ used a type called `__compressed_pair` to reduce storage needs in cases of empty types (e.g. an
121+
// empty allocator in std::vector). We switched to using `[[no_unique_address]]`. However, for ABI compatibility reasons
122+
// we had to add artificial padding in a few places.
123+
//
124+
// This setting disables the addition of such artificial padding, leading to a more optimal
125+
// representation for several types.
126+
# define _LIBCPP_ABI_NO_COMPRESSED_PAIR_PADDING
101127
#elif _LIBCPP_ABI_VERSION == 1
102128
# if !(defined(_LIBCPP_OBJECT_FORMAT_COFF) || defined(_LIBCPP_OBJECT_FORMAT_XCOFF))
103129
// Enable compiling copies of now inline methods into the dylib to support
@@ -150,6 +176,11 @@
150176
// ABI impact: changes the iterator type of `vector` (except `vector<bool>`).
151177
// #define _LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR
152178

179+
// [[msvc::no_unique_address]] seems to mostly affect empty classes, so the padding scheme for Itanium doesn't work.
180+
#if defined(_LIBCPP_ABI_MICROSOFT) && !defined(_LIBCPP_ABI_NO_COMPRESSED_PAIR_PADDING)
181+
# define _LIBCPP_ABI_NO_COMPRESSED_PAIR_PADDING
182+
#endif
183+
153184
#if defined(_LIBCPP_COMPILER_CLANG_BASED)
154185
# if defined(__APPLE__)
155186
# if defined(__i386__) || defined(__x86_64__)

libcxx/include/__functional/function.h

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -143,45 +143,46 @@ class __default_alloc_func;
143143

144144
template <class _Fp, class _Ap, class _Rp, class... _ArgTypes>
145145
class __alloc_func<_Fp, _Ap, _Rp(_ArgTypes...)> {
146-
__compressed_pair<_Fp, _Ap> __f_;
146+
_LIBCPP_COMPRESSED_PAIR(_Fp, __func_, _Ap, __alloc_);
147147

148148
public:
149149
typedef _LIBCPP_NODEBUG _Fp _Target;
150150
typedef _LIBCPP_NODEBUG _Ap _Alloc;
151151

152-
_LIBCPP_HIDE_FROM_ABI const _Target& __target() const { return __f_.first(); }
152+
_LIBCPP_HIDE_FROM_ABI const _Target& __target() const { return __func_; }
153153

154154
// WIN32 APIs may define __allocator, so use __get_allocator instead.
155-
_LIBCPP_HIDE_FROM_ABI const _Alloc& __get_allocator() const { return __f_.second(); }
155+
_LIBCPP_HIDE_FROM_ABI const _Alloc& __get_allocator() const { return __alloc_; }
156156

157-
_LIBCPP_HIDE_FROM_ABI explicit __alloc_func(_Target&& __f)
158-
: __f_(piecewise_construct, std::forward_as_tuple(std::move(__f)), std::forward_as_tuple()) {}
157+
_LIBCPP_HIDE_FROM_ABI explicit __alloc_func(_Target&& __f) : __func_(std::move(__f)), __alloc_() {}
159158

160-
_LIBCPP_HIDE_FROM_ABI explicit __alloc_func(const _Target& __f, const _Alloc& __a)
161-
: __f_(piecewise_construct, std::forward_as_tuple(__f), std::forward_as_tuple(__a)) {}
159+
_LIBCPP_HIDE_FROM_ABI explicit __alloc_func(const _Target& __f, const _Alloc& __a) : __func_(__f), __alloc_(__a) {}
162160

163161
_LIBCPP_HIDE_FROM_ABI explicit __alloc_func(const _Target& __f, _Alloc&& __a)
164-
: __f_(piecewise_construct, std::forward_as_tuple(__f), std::forward_as_tuple(std::move(__a))) {}
162+
: __func_(__f), __alloc_(std::move(__a)) {}
165163

166164
_LIBCPP_HIDE_FROM_ABI explicit __alloc_func(_Target&& __f, _Alloc&& __a)
167-
: __f_(piecewise_construct, std::forward_as_tuple(std::move(__f)), std::forward_as_tuple(std::move(__a))) {}
165+
: __func_(std::move(__f)), __alloc_(std::move(__a)) {}
168166

169167
_LIBCPP_HIDE_FROM_ABI _Rp operator()(_ArgTypes&&... __arg) {
170168
typedef __invoke_void_return_wrapper<_Rp> _Invoker;
171-
return _Invoker::__call(__f_.first(), std::forward<_ArgTypes>(__arg)...);
169+
return _Invoker::__call(__func_, std::forward<_ArgTypes>(__arg)...);
172170
}
173171

174172
_LIBCPP_HIDE_FROM_ABI __alloc_func* __clone() const {
175173
typedef allocator_traits<_Alloc> __alloc_traits;
176174
typedef __rebind_alloc<__alloc_traits, __alloc_func> _AA;
177-
_AA __a(__f_.second());
175+
_AA __a(__alloc_);
178176
typedef __allocator_destructor<_AA> _Dp;
179177
unique_ptr<__alloc_func, _Dp> __hold(__a.allocate(1), _Dp(__a, 1));
180-
::new ((void*)__hold.get()) __alloc_func(__f_.first(), _Alloc(__a));
178+
::new ((void*)__hold.get()) __alloc_func(__func_, _Alloc(__a));
181179
return __hold.release();
182180
}
183181

184-
_LIBCPP_HIDE_FROM_ABI void destroy() _NOEXCEPT { __f_.~__compressed_pair<_Target, _Alloc>(); }
182+
_LIBCPP_HIDE_FROM_ABI void destroy() _NOEXCEPT {
183+
__func_.~_Fp();
184+
__alloc_.~_Alloc();
185+
}
185186

186187
_LIBCPP_HIDE_FROM_ABI static void __destroy_and_delete(__alloc_func* __f) {
187188
typedef allocator_traits<_Alloc> __alloc_traits;

0 commit comments

Comments
 (0)