Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[libc++] Vendor communication: upcoming ABI breaks in std::expected and parts of <ranges> #70820

Closed
ldionne opened this issue Oct 31, 2023 · 2 comments
Labels
ABI Application Binary Interface libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.

Comments

@ldionne
Copy link
Member

ldionne commented Oct 31, 2023

Hi @llvm/libcxx-vendors,

This issue is not a traditional issue. It is a communication to vendors about upcoming ABI breaks in std::expected and some parts of <ranges>. It was recently brought to our attention in #70494 (see the associated PR for more discussion) that there is a problem with some of the library's usage of [[no_unique_address]], which can lead to objects overlapping with padding bytes of other objects and their contents being overwritten unexpectedly.

We are aware of this issue in std::expected and in std::__movable_box, which is used to implement several views in <ranges>. Fixing this will require breaking the ABI for the types involved. We introduced std::expected and __movable_box in LLVM 16 so this has been shipping for two releases. We are aiming to fix these issues in LLVM 18.

Typically, we do not expect code to expose a lot of <ranges> views at ABI boundaries, so we believe the __movable_box ABI break to have a limited fallout. However, std::expected is a vocabulary type and is intended to appear at all kinds of API (and ABI) boundaries, in particular function result types. That ABI break is more likely to cause actual issues in the wild.

At this moment, we want to make vendors aware of these upcoming changes so they can plan ahead and perhaps limit the blast radius in case they haven't shipped LLVM 16/17 yet. One possibility would be to disable std::expected in their downstream release or keep it experimental behind -fexperimental-library. We will update this thread as we gain more clarity on the exact nature of the upcoming ABI break (e.g. which types are impacted and under what circumstances).

FAQ

Q: Are you going to allow vendors to opt into the old/new ABI for these types?
A: No. The current ABI has correctness issues which are serious enough that nobody should opt into the old ABI. This isn't a performance improvement or a slight conformance fix.

Q: Are you going to take this opportunity to enable other ABI breaks, effectively moving to "ABI v2"?
A: No. The intent of these changes is to keep the ABI break as narrow as possible with the hopes that the actual fallout will be limited. <ranges> is seldom used at ABI boundaries. std::expected shipped only recently, requires C++23 and we expect that few users will have had the time to depend on it in their ABI.

Q: Is it possible to use some techniques to try and diagnose when folks have an ABI mismatch after shipping that ABI break?
A: Yes, to some extent. We will use ABI tags to influence the mangling of functions that contain a type that undergoes an ABI break. This means that trying to link two units using/providing a function whose signature contains e.g. std::expected will fail at link time since the mangled name of the function will be different between LLVM 17 and LLVM 18. However, this technique has limitations. For example, if a std::expected is contained in a user-defined class, the user-defined class will not get an ABI tag despite its layout potentially changing between LLVM 17 and LLVM 18.

@ldionne ldionne added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Oct 31, 2023
@ldionne
Copy link
Member Author

ldionne commented Nov 1, 2023

Dropping a few links here for cross-reference purposes

@ldionne ldionne added the ABI Application Binary Interface label Dec 1, 2023
@ldionne ldionne added this to the LLVM 18.0.X Release milestone Jan 16, 2024
ldionne added a commit that referenced this issue Jan 22, 2024
Currently std::expected can have some padding bytes in its tail due to
[[no_unique_address]]. Those padding bytes can be used by other objects.
For example, in the current implementation:

  sizeof(std::expected<std::optional<int>, bool>) == 
    sizeof(std::expected<std::expected<std::optional<int>, bool>, bool>)

As a result, the data layout of an
  std::expected<std::expected<std::optional<int>, bool>, bool> 
can look like this:

              +-- optional "has value" flag
              |        +--padding
  /---int---\ |        |
  00 00 00 00 01 00 00 00
                |  |
                |  +- "outer" expected "has value" flag
                |
                +- expected "has value" flag

This is problematic because `emplace()`ing the "inner" expected can not
only overwrite the "inner" expected "has value" flag (issue #68552) but
also the tail padding where other objects might live.

This patch fixes the problem by ensuring that std::expected has no tail
padding, which is achieved by conditional usage of [[no_unique_address]]
based on the tail padding that this would create.

This is an ABI breaking change because the following property changes:

  sizeof(std::expected<std::optional<int>, bool>) <
    sizeof(std::expected<std::expected<std::optional<int>, bool>, bool>)

Before the change, this relation didn't hold. After the change, the relation
does hold, which means that the size of std::expected in these cases increases
after this patch. The data layout will change in the following cases where
tail padding can be reused by other objects:

  class foo : std::expected<std::optional<int>, bool> {
    bool b;
  };

or using [[no_unique_address]]:

  struct foo {
    [[no_unique_address]] std::expected<std::optional<int>, bool> e;
    bool b;
  };

The vendor communication is handled in #70820.
Fixes: #70494

Co-authored-by: philnik777 <nikolasklauser@berlin.de>
Co-authored-by: Louis Dionne <ldionne.2@gmail.com>
@ldionne
Copy link
Member Author

ldionne commented Jan 22, 2024

All the changes related to this event have now been merged:

Effect on <ranges>

The following components from <ranges> have an ABI break: take_while_view, filter_view, single_view, drop_while_view, repeat_view, transform_view, and chunk_by_view. We added an ABI tag to these classes such that trying to link programs using different versions of these views will produce a linker error.

Effect on std::expected

Some user-defined types containing a std::expected<T, E> member will have an ABI break. However, such ABI break requires a fairly uncommon set of conditions:

  • A type equivalent to union {T ; E} needs to have more than one byte of padding available.
  • The std::expected<T, E> member must have been in a situation where its padding bytes were previously reused by another object, which can happen in a few cases (this is probably not exhaustive):
    • It is a member with [[no_unique_address]] applied to it, and it is followed by another data member, or
    • It is a member with [[no_unique_address]] applied to it, and it is the last member of the user-defined type,
      and that user-defined type is used in ways that its padding bytes can be reused, or
    • It is inherited from

Overall, we expect (and hope) the breakage is not widespread due to the rarity of these conditions. Furthermore, under these conditions the code would have previously been broken due to the bug we're fixing.

We did not add an ABI tag to std::expected because the breakage only arises for types that contain a std::expected as a member, and adding an ABI tag in that case doesn't help. Adding an ABI tag would have simply caused a lot of spurious link-time breakage for cases where the ABI didn't actually change.

I will close this issue now, if vendors have questions please feel free to ask them here and perhaps ping me to make sure I see the notification.

@ldionne ldionne closed this as completed Jan 22, 2024
@github-project-automation github-project-automation bot moved this to Needs Triage in LLVM Release Status Feb 6, 2024
@tstellar tstellar moved this from Needs Triage to Done in LLVM Release Status Feb 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ABI Application Binary Interface libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.
Projects
Development

No branches or pull requests

1 participant