Skip to content

Apple Clang miscompiles coroutine exception handling at -O2 with default visibility #194

@mvandeberg

Description

@mvandeberg

Summary

Two when_any tests fail on Apple Clang Release builds (-O2 and above) with default symbol visibility. The compiler-generated coroutine state machine silently drops exceptions thrown in the coroutine body, causing the run_async error handler to never fire.

Failing tests:

  • testEmptyVectorThrowswhen_any.cpp:1348
  • testEmptyRangeThrowswhen_any.cpp:2157

Both verify that when_any(empty_range) delivers std::invalid_argument through the run_async error handler.

Environment

  • Apple Clang 17.0.0 (clang-1700.0.13.3), arm64-apple-darwin25.3.0
  • Failure boundary: passes at -O1, fails at -O2 and above
  • -DNDEBUG and debug symbols have no effect

Root cause

Assembly diff between -O2 (failing) and -O2 -fvisibility=hidden (passing) shows the difference: with default visibility, template instantiation symbols (weak external linkage) are accessed through the GOT (@GOTPAGE/@GOTPAGEOFF). With hidden visibility, they use direct references (@PAGE/@PAGEOFF). The coroutine optimizer generates incorrect exception tables when these function references are GOT-indirect.

The bug is in compiler-generated code (the coroutine resume function's implicit try/catch that routes to unhandled_exception()), not in user-written code. This was confirmed by applying __attribute__((optnone)) to invoke_impl — the handler dispatch function — which had no effect, proving the exception never reaches it.

Approaches tried (all failed)

Approach Result
__attribute__((optnone)) on invoke_impl FAIL
__attribute__((noinline)) on exception() / unhandled_exception() FAIL
volatile bool has_ep_ FAIL
Cache exception_ptr locally FAIL
asm volatile memory barrier on function pointer FAIL
co_await noop IoAwaitable before throw FAIL
[[noreturn]] __attribute__((noinline)) throw wrapper FAIL

Current workaround

Build with -fvisibility=hidden on Apple Clang. This eliminates GOT indirection for weak template symbols and produces correct exception routing at all optimization levels.

Related upstream issues

  • LLVM #61900 — incorrect code for coroutines when unhandled_exception() exits via exception
  • LLVM #105595 — runtime crash at -O2 in coroutines with co_await
  • CWG2935 / PR #177628 — coroutine startup exception safety

TODO

  • When a new Apple Clang version ships, test with default visibility at -O2 to check if the upstream fix has landed. Reproduce with: cmake -DCMAKE_CXX_FLAGS="-O2 -DNDEBUG" .. && cmake --build . && ctest
  • If fixed upstream, remove the -fvisibility=hidden workaround from CI and document the minimum Apple Clang version

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions