Skip to content

Use fastcall for synthetic coroutine frames on i686 with Clang < 23 - #2372

Open
veljjanoski wants to merge 1 commit into
NVIDIA:mainfrom
veljjanoski:fix-i686-coro-calling-convention
Open

veljjanoski wants to merge 1 commit into
NVIDIA:mainfrom
veljjanoski:fix-i686-coro-calling-convention

Conversation

@veljjanoski

Copy link
Copy Markdown
Contributor

Fixes #2113.

What

On 32-bit x86, code built with Clang before 23 crashes when it resumes a __synthetic_coro_frame (added in #1974). That covers every co_await that goes through connect_awaitable, for example sync_wait of an exec::task.

Why

Before LLVM 23, Clang lowers coroutine_handle::resume() and destroy() to a fastcc call through the frame's function pointers (llvm/llvm-project#198943 changed that to the C convention). On i686, fastcc passes the frame address in ECX. The functions stored in __synthetic_coro_frame used the default C convention and read their argument from the stack, so __resume got a garbage pointer. On x86-64 and ARM both conventions pass the first argument in the same register, so only i686 is affected.

How

  • A new macro in coroutine.hpp, STDEXEC_CORO_FRAME_CALLCONV, is __attribute__((__fastcall__)) for Clang < 23 on i686 and empty otherwise. fastcall also takes the first argument in ECX.
  • It is applied to __synthetic_coro_frame::__callback_fn_t, __noop_fn and __connect_await::__promise::__resume.
  • It can be predefined, for example as empty for a Clang that has the LLVM change backported.

The frames in the STDEXEC_MSVC_CORO_DESTROY_BUG_WORKAROUND block are only compiled by MSVC, where the macro is empty, so they are unchanged.

Verification

Linux x86-64 host (Docker, Debian 12), Clang 21.1 with libstdc++ 12, -m32, Debug, the same flags as the 32-bit CI job:

main (9d292a4) this PR
exec::task example from #2113, -O0 and -O2 segfault runs
test.stdexec (641 test cases) 4 fail (connect_awaitable) all pass
test.exec (454 test cases) 35 crash, 1 fails all pass

The preprocessed #2113 example is the same as on main, apart from whitespace, for Clang 21 x86-64, GCC 12 x86-64, GCC 12 -m32, and Clang 21 -m32 with -DSTDEXEC_CORO_FRAME_CALLCONV=. clang-format 21 is clean.

There is no new test, because the existing tests already fail on i686 with Clang < 23. The 32-bit CI job uses GCC 14, so CI never runs them with Clang. I can add a Clang 32-bit job here or in a follow-up if you want one.

Not tested: clang-cl on 32-bit Windows, Release builds of the test suites, the other CI configurations.

AI-usage disclosure: Claude (Anthropic) was used as assistant.

@copy-pr-bot

copy-pr-bot Bot commented Oct 27, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SEGV (stack-user-after-return) with simple exec::task in a stdexec::sync_wait (Linux 32-bit)

1 participant