Skip to content

[libc++] Make sure that we use the fast platform wait for all eligible types in atomic_wait #146145

Closed

Description

@ldionne

While discussing about #109290 with @huixie90 and Apple folks, we noticed that we didn't use the underlying platform's API (e.g. __ulock_wait on Apple, futex on Linux) even in cases where we could. This is caused by the brittle way in which we distinguish between the platform-native contention state and other states (e.g. here):

_LIBCPP_EXPORTED_FROM_ABI void __cxx_atomic_notify_one(__cxx_atomic_contention_t const volatile* __location) noexcept; // fast
_LIBCPP_EXPORTED_FROM_ABI void __cxx_atomic_notify_one(void const volatile* __location) noexcept; // slow

We basically forward only atomics which are exactly of the right type to the fast path, and everything else gets the slow path. For example, on Apple __cxx_atomic_contention_t is int64_t. That means that if we try to wait on a std::atomic<uint64_t>, we'll end up in the slow path even though we could clearly use the underlying platform's wait mechanism, which is faster.

To resolve this, I would suggest re-thinking the ABI boundary of the synchronization library and basically forwarding (from the headers) any type that has a natural alignment and sizeof(T) == {32,64} to the underlying platform API. At the end of the day, we basically hand over a bag of bytes to the underlying platform wait, the value and the type of these bytes doesn't matter.

Activity

  1. added
    libc++libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.
    on Jun 27, 2025
  2. huixie90 commented on Jun 29, 2025

    @huixie90
    Member

    I am not sure how we can get ABI stability. right now, we effectively have these 4 abi functions

    void wait(int64_t*);
    void wait(void*);
    void notify(int64_t*);
    void notify(void*);

    imagine we add some new ABI boundary functions which can handle int32_t

    void wait_new_api(in32_t*);
    void notify_new_api(in32_t*);

    and we keep the original functions unchanged.

    Now imagine user has two TU, A is compiled with the old libc++ and B is compiled with the new libc++.
    A is calling atomic<int32_t>::wait, which ends up calling void notify(void*); in the dylib, which will wait on the global contention table
    B is calling atomic<int32_t>::notify, which ends up calling void notify_new_api(in32_t*), which will notify the atomic itself.
    So A and B won't work together properly because they don't operation on the same memory location.

    to fix this, somehow we need to make sure the old void notify(void*); to do the right thing for int32_t, which is not trivial i think

  3. ldionne commented on Jul 4, 2025

    @ldionne
    MemberAuthor

    Hmm, I think you're right. This is unlike most other similar situations because we have two APIs (wait and notify) and their implementation needs to be in sync in case they are called from different TUs because they use shared global state. This is pretty nasty.

    The optimization does seem worth it to me though, I wonder how frequent/likely it is for people to be calling wait and notify from different TUs compiled against different versions of the library. That's probably rare. For now, I would suggest that we implement this new API but guard it behind an ABI macro and we can think about ways to roll it out without any ABI macro.

  4. ldionne commented on Jul 4, 2025

    @ldionne
    MemberAuthor

    One strategy that we could potentially use is to make the changes we want to the headers:

    // add this
    void wait_new_api(in32_t*);
    void notify_new_api(in32_t*);

    but then don't actually take advantage of that new information inside the dylib, unless an ABI macro is set. That allows us to make the changes to the headers unconditionally and manage our risk with deploying this improvement by only setting an ABI macro when building the dylib.

  5. huixie90 commented on Dec 14, 2025

    @huixie90
    Member

    This is fixed by
    #161086

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

libc++libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions