[libc++] Step 2: Removing __always_inline__ in _LIBCPP_EXTERN_TEMPLATE_INLINE_VISIBILITY


This proposal is the second step towards removing uses of __always_inline__ to control what appears in libc++'s ABI. Step 1 of the proposal was to replace uses of __always_inline__ by Clang's internal_linkage attribute whenever available, through the introduction of a new _LIBCPP_HIDE_FROM_ABI macro. Step 1 was discussed at http://lists.llvm.org/pipermail/cfe-dev/2018-July/058419.html and is being reviewed in ⚙ D49240 [libc++] Introduce _LIBCPP_HIDE_FROM_ABI to replace _LIBCPP_INLINE_VISIBILITY.

Step 2 of the proposal is to get rid of __always_inline__ in the _LIBCPP_EXTERN_TEMPLATE_INLINE_VISIBILITY macro. First of all, let me explain my understanding of the situation regarding _LIBCPP_EXTERN_TEMPLATE_INLINE_VISIBILITY.

_LIBCPP_EXTERN_TEMPLATE_INLINE_VISIBILITY was introduced because some methods used to be provided in the dylib, but are now defined in the headers too. The idea is that whenever a program is using recent libc++ headers, calls to those methods can be inlined and we don't call into the dylib. However, the dylib still exports those methods (through an explicit instantiation + the fact that the methods have default visibility), which means that an older program where calls to those methods are not inlined will still be able to link against a new libc++. The important part here is that we really would rather hide those methods from the ABI, but we keep them in the ABI for backwards compatibility.

In light of this, I suggest we introduce a new macro called `_LIBCPP_HIDE_FROM_ABI_AFTER(abi_version)`, which hides or doesn't hide a symbol from the ABI based on the ABI version used to build the library. When we build libc++, we know what ABI version we're building for, so we can decide to hide those symbols from the ABI (or not). From the user side, we would always pretend that those symbols are hidden from the ABI such that no more calls to a specific function in the dylib are generated after we've decided that the function was not going to be part of the ABI in some future version. In pseudocode, we'd have something like this:

    // we're building the dylib, where we want to provide a symbol only when the ABI supports it.
    # define _LIBCPP_HIDE_FROM_ABI_AFTER(__abi_version) _LIBCPP_ABI_VERSION > __abi_version ? _LIBCPP_HIDE_FROM_ABI : __attribute__((visibility(“default”)))
    #else // we're in the user code, where we never take advantage of calls in the dylib that have been removed at some point

Note that we're reusing _LIBCPP_HIDE_FROM_ABI introduced in the first step of this proposal, which basically uses internal_linkage and hidden visibility. Then, we would decorate methods that are currently marked with _LIBCPP_EXTERN_TEMPLATE_INLINE_VISIBILITY with _LIBCPP_HIDE_FROM_ABI_AFTER(N), as appropriate. For example:

    template <class _CharT, class _Traits>
    class basic_istream {
        // ...
        inline _LIBCPP_HIDE_FROM_ABI_AFTER(1)
        basic_istream& operator>>(basic_ios<char_type, traits_type>&
                                  (*__pf)(basic_ios<char_type, traits_type>&));
        // ...

This would mean that the symbol is hidden from the ABI whenever we're building libc++ with an ABI after v1 (the latest stable ABI version), but it would be part of the ABI before that. How does this work? Well, there's an explicit instantiation of basic_istream in the dylib:

    template class basic_istream<char>;

Based on the ABI version used to build the dylib, operator>> above is either given hidden visibility or default visibility. If it's given default visibility, that symbol is part of the ABI, otherwise it's not part of the ABI. From the user side, however, operator>> is always marked with hidden visibility and (most importantly) internal linkage. This means that their code will never try to call this function from the dylib, which is the intent of the macro today.

Just to be clear, I'm not vouching for ABI breaks or ABI migrations, and in fact it would be an entirely valid option for vendors never to upgrade their ABI beyond v1 (today's stable ABI). However, since it appears that _LIBCPP_EXTERN_TEMPLATE_INLINE_VISIBILITY is solving an ABI migration problem, it makes sense to tackle the problem with an ABI migration tool, even if the migration never happens for most users.

I've implemented this proposal and verified that it doesn't change the ABI under the current ABI version. I then artificially bumped the ABI version and verified that exactly the symbols that are currently marked with _LIBCPP_EXTERN_TEMPLATE_INLINE_VISIBILITY become hidden from the ABI. I also ran all the tests in both cases and they pass.

Please let me know what you think. I'll be putting up a patch for review soon.


Just FYI, a review implementing this proposal has been opened here: https://reviews.llvm.org/D49914.