libc++’s ABI Guarantees

libc++ provides multiple types of ABI guarantees. These include stability of the layout of structs, the linking of TUs built against different versions and configurations of the library, and more. This document describes what guarantees libc++ provides in these different areas as well as what options exist for vendors to affect these guarantees.

Note that all of the guarantees listed below come with an asterisk that there may be circumstances where we deem it worth it to break that guarantee. These breaks are communicated to vendors by CCing #libcxx-vendors on GitHub. If you are a vendor, please ask to be added to that group to be notified about changes that potentially affect you.

ABI flags

All the ABI flags listed below can be added to the __config_site header by the vendor to opt in to an ABI breaking change. These flags should never be set by the user. When porting libc++ to a new platform, vendord should consider which flags to enable, assuming that ABI stability is relevant to them. Please contact the libc++ team on Discord or through other means to be able to make an informed decision on which flags make sense to enable, and to avoid enabling flags which may not be stable. Flags can be enabled via the LIBCXX_ABI_DEFINES CMake option.

Stability of the Layout of Structs

The layout of any user-observable struct is kept stable across versions of the library and any user-facing options documented here. There are a lot of structs that have internal names, but are none the less observable by users; for example through public aliases to these types or because they affect the layout of other types.

There are multiple ABI flags which affect the layout of certain structs:

_LIBCPP_ABI_ALTERNATE_STRING_LAYOUT

This changes the internal layout of basic_string to move the section that is used for the internal buffer to the front, making it eight byte aligned instead of being unaligned, improving the performance of some operations significantly.

_LIBCPP_ABI_NO_ITERATOR_BASES

This removes the iterator base class from back_insert_iterator, front_insert_iterator, insert_iterator, istream_iterator, ostream_iterator, ostreambuf_iterator, reverse_iterator, and raw_storage_iterator. This doesn’t directly affect the layout of these types in most cases, but may result in more padding being used when they are used in combination, for example reverse_iterator<reverse_iterator<T>>.

_LIBCPP_ABI_VARIANT_INDEX_TYPE_OPTIMIZATION

This changes the index type used inside variant to the smallest required type to reduce the datasize of variants in most cases.

_LIBCPP_ABI_OPTIMIZED_FUNCTION

This significantly restructures how function is written to provide better performance, but is currently not ABI stable.

_LIBCPP_ABI_NO_RANDOM_DEVICE_COMPATIBILITY_LAYOUT

This changes the layout of random_device to only holds state with an implementation that gets entropy from a file (see _LIBCPP_USING_DEV_RANDOM). When switching from this implementation to another one on a platform that has already shipped random_device, one needs to retain the same object layout to remain ABI compatible. This flag removes these workarounds for platforms that don’t care about ABI compatibility.

_LIBCPP_ABI_NO_COMPRESSED_PAIR_PADDING

This removes artificial padding from _LIBCPP_COMPRESSED_PAIR and _LIBCPP_COMPRESSED_TRIPLE.

These macros are used inside the associative and unordered containers, deque, forward_list, future, list, basic_string, function, shared_ptr, unique_ptr, and vector to stay ABI compatible with the legacy __compressed_pair type. __compressed_pair had historically been used to reduce storage requirements in the case of empty types, but has been replaced by [[no_unique_address]]. [[no_unique_address]] is significantly lighter in terms of compile time and debug information, and also improves the layout of structs further. However, to keep ABI stability, the additional improvements in layout had to be reverted by introducing artificial padding. This flag removes that artificial padding.

_LIBCPP_ABI_IOS_ALLOW_ARBITRARY_FILL_VALUE

basic_ios uses WEOF to indicate that the fill value is uninitialized. However, on platforms where the size of char_type is equal to or greater than the size of int_type and char_type is unsigned, char_traits<char_type>::eq_int_type() cannot distinguish between WEOF and WCHAR_MAX. This flag changes basic_ios to instead track whether the fill value has been initialized using a separate boolean.

Linking TUs which have been compiled against different releases of libc++

libc++ supports linking TUs which have been compiled against different releases of libc++ by marking symbols with hidden visibility and changing the mangling of header-only functions in every release.

Linking TUs which have been compiled with different flags affecting code gen

There are a lot of compiler (and library) flags which change the code generated for functions. This includes flags like -O1, which are guaranteed by the compiler to not change the observable behaviour of a correct program, as well as flags like -fexceptions, which do change the observable behaviour. libc++ allows linking of TUs which have been compiled whith specific flags only and makes no guarantees for any of the flags not listed below.

The flags allowed (in any combination) are: - -f[no-]exceptions - -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE{_FAST,_EXTENSIVE,_DEBUG,_NONE}

Note that this does not provide any guarantees about user-defined functions, but only that the libc++ functions linked behave as the flags say.

Availability of symbols in the built library (both static and shared)

In general, libc++ does not make any guarantees about forwards-compatibility. That is, a TU compiled against new headers may not work with an older library. Vendors who require such support can leverage availability markup. On the other hand, backwards compatibility is generally guaranteed.

There are multiple ABI flags that change the symbols exported from the built library:

_LIBCPP_ABI_DO_NOT_EXPORT_BASIC_STRING_COMMON

This removes __basic_string_common<true>::__throw_length_error() and __basic_string_common<true>::__throw_out_of_range(). These symbols have been used by basic_string in the past, but are not referenced from the headers anymore.

_LIBCPP_ABI_DO_NOT_EXPORT_VECTOR_BASE_COMMON

This removes __vector_base_common<true>::__throw_length_error() and __vector_base_common<true>::__throw_out_of_range(). These symbols have been used by vector in the past, but are not referenced from the headers anymore.

_LIBCPP_ABI_DO_NOT_EXPORT_TO_CHARS_BASE_10

This removes __itoa::__u32toa() and __iota::__u64toa. These symbols have been used by to_chars in the past, but are not referenced from the headers anymore.

_LIBCPP_ABI_STRING_OPTIMIZED_EXTERNAL_INSTANTIATION

This replaces the symbols that are exported for basic_string to avoid exporting functions which are likely to be inlined as well as explicitly moving paths to the built library which are slow, improving fast-path inlining of multiple functions. This flag is currently unstable.

Stability of the traits of a type

Whether a particular trait of a type is kept stable depends heavily on the type in question and the trait. The most important trait of a type to keep stable is the triviality for the purpose of calls, since that directly affects the function call ABI. Which types are considered non-trivial for the purpose of calls is defined in the Itanium ABI. is_trivially_copyable should also be kept stable usually, since many programs depend on this trait for their own layouting. This isn’t as rigid as the previous requirement though.

There are multiple ABI flags that change traits of a struct:

_LIBCPP_ABI_ENABLE_UNIQUE_PTR_TRIVIAL_ABI

This flag adds [[clang::trivial_abi]] to unique_ptr, which makes it trivial for the purpose of calls.

_LIBCPP_ABI_ENABLE_SHARED_PTR_TRIVIAL_ABI

This flag adds [[clang::trivial_abi]] to shared_ptr, which makes it trivial for the purpose of calls.

Types that public aliases reference

There are a lot of aliases that reference types with library internal names. For example, containers contain an iterator alias to a type with a library internal name. These have to always reference the same type, since the mangling of user-defined function overloads would change otherwise. A notable exception to this are the alias templates to type traits. There doesn’t seem to be anybody who relies on these names staying the same, so it is OK to change what these aliases actually reference.

There are multiple ABI flags which change which type an alias references:

_LIBCPP_ABI_INCOMPLETE_TYPES_IN_DEQUE

This changes deque::iterator to avoid requiring complete types for deque.

_LIBCPP_ABI_FIX_UNORDERED_CONTAINER_SIZE_TYPE

This changes the unordered container’s size_types aliases.

_LIBCPP_ABI_USE_WRAP_ITER_IN_STD_ARRAY and _LIBCPP_ABI_USE_WRAP_ITER_IN_STD_STRING_VIEW

This changes the iterator and const_iterator of array and string_view respectively to reference __wrap_iter instead, which makes it less likely for users to depend on non-portable implementation details. This is especially useful because enabling bounded iterators hardening requires code not to make these assumptions.

_LIBCPP_ABI_BOUNDED_ITERATORS, _LIBCPP_ABI_BOUNDED_ITERATORS_IN_STRING, _LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR, and _LIBCPP_ABI_BOUNDED_ITERATORS_IN_STD_ARRAY

These flags change the iterator member of various classes to reference hardened iterators instead. See the hardening documentation for more details.

Meaning of values

The meaning of specific values can usually not be changed, since programs compiled against older versions of the headers may check for these values. These specific values don’t have to be hard-coded, but can also depend on user input.

There are multiple ABI flags that change the meaning of particular values:

_LIBCPP_ABI_REGEX_CONSTANTS_NONZERO

This changes the value of regex_constants::syntax_option-type::ECMAScript to be standards-conforming.

_LIBCPP_ABI_FIX_CITYHASH_IMPLEMENTATION

This flag fixes the implementation of CityHash used for hash<fundamental-type>. The incorrect implementation of CityHash has the problem that it drops some bits on the floor. Fixing the implementation changes the hash of values, resulting in an ABI break.

inline namespaces

Inline namespaces which contain types that are observable by the user need to be kept the same, since they affect mangling. Almost all of libc++’s symbols are inside an inline namespace. By default that namespace is __1, but can be changed by the vendor by setting LIBCXX_ABI_NAMESPACE during CMake configuration. There is also _LIBCPP_ABI_NO_FILESYSTEM_INLINE_NAMESPACE to remove the __fs namespace from surrounding the filesystem namespace. This shortens the mangling of the filesystem symbols a bit.