https://github.com/chfast created https://github.com/llvm/llvm-project/pull/234580
A musttail call to a noreturn function (`__attribute__((noreturn))`, `_Noreturn`, `exit()`, `longjmp()`, ...) gets `unreachable` after it instead of `ret`, which fails the verifier with "musttail call must precede a ret". Release builds skip the verifier, so -O2 crashes with "failed to perform tail call elimination on a call site marked musttail" and -O0 silently emits a regular call. Sema only rejects `[[noreturn]]` callees (#77727), so all other spellings get through. Implicit musttail calls produce the same invalid IR: `swiftasynccall`, and the `-fptrauth-calls` thunk for a pointer to a noreturn virtual member function. The latter crashes even at -O0, without any `musttail` in the source. The noreturn path in `EmitCall` runs before the musttail one. This patch skips it for musttail calls, so they get a `ret` like any other musttail call. As a side effect, the musttail checks there (e.g. the noexcept mismatch error) now apply to noreturn callees too. This matches GCC, which accepts musttail calls to noreturn functions, `[[noreturn]]` ones included: https://godbolt.org/z/q8ajhMh15 With CodeGen handling it, the Sema error for `[[noreturn]]` callees is no longer needed. I'll lift it in a follow-up PR. Inspired by rnk's comment in #191198 asking whether the thunk code emitting a `ret` after the musttail call is dead: https://github.com/llvm/llvm-project/pull/191198#discussion_r3414988237. Only noreturn callees reach it. Fixes #60162. Fixes #120088. Fixes #120637. From 43d83c3b07896ac39b68c564b8e99b03e4e6a245 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= <[email protected]> Date: Thu, 22 Oct 2026 12:46:01 +0200 Subject: [PATCH] [clang][CodeGen] Emit return after musttail call to noreturn function A musttail call to a noreturn function (`__attribute__((noreturn))`, `_Noreturn`, `exit()`, `longjmp()`, ...) gets `unreachable` after it instead of `ret`, which fails the verifier with "musttail call must precede a ret". Release builds skip the verifier, so -O2 crashes with "failed to perform tail call elimination on a call site marked musttail" and -O0 silently emits a regular call. Sema only rejects `[[noreturn]]` callees (#77727), so all other spellings get through. Implicit musttail calls produce the same invalid IR: `swiftasynccall`, and the `-fptrauth-calls` thunk for a pointer to a noreturn virtual member function. The latter crashes even at -O0, without any `musttail` in the source. The noreturn path in `EmitCall` runs before the musttail one. This patch skips it for musttail calls, so they get a `ret` like any other musttail call. As a side effect, the musttail checks there (e.g. the noexcept mismatch error) now apply to noreturn callees too. This matches GCC, which accepts musttail calls to noreturn functions, `[[noreturn]]` ones included: https://godbolt.org/z/q8ajhMh15 With CodeGen handling it, the Sema error for `[[noreturn]]` callees is no longer needed. I'll lift it in a follow-up PR. Inspired by rnk's comment in #191198 asking whether the thunk code emitting a `ret` after the musttail call is dead: https://github.com/llvm/llvm-project/pull/191198#discussion_r3414988237. Only noreturn callees reach it. Fixes #60162. Fixes #120088. Fixes #120637. --- clang/docs/ReleaseNotes.md | 5 ++++ clang/lib/CodeGen/CGCall.cpp | 5 ++-- clang/test/CodeGen/musttail-noreturn.c | 29 +++++++++++++++++++ .../ptrauth-member-function-pointer.cpp | 14 +++++++++ 4 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 clang/test/CodeGen/musttail-noreturn.c diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 7fe9977a979c0d..61bb291008439d 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -873,6 +873,11 @@ documentation](CIR/index.md) for more information regarding ClangIR's status and `alias` are now correctly diagnosed as definitions when followed by an out-of-line definition. (#GH204762) +- Fixed a crash on a `[[clang::musttail]]` call to a noreturn function, e.g. + one declared with `__attribute__((noreturn))`, and on taking the address of + a noreturn virtual member function with `-fptrauth-calls`. (#GH60162, + #GH120088, #GH120637) + #### Bug Fixes to C++ Support - Instantiate constexpr functions as needed before they are evaluated. (#GH73232) (#GH35052) (#GH100897) diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp index 9b8b0b52b66cb5..27b7d2d3e15f9a 100644 --- a/clang/lib/CodeGen/CGCall.cpp +++ b/clang/lib/CodeGen/CGCall.cpp @@ -6611,8 +6611,9 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo, // If the call doesn't return, finish the basic block and clear the // insertion point; this allows the rest of IRGen to discard - // unreachable code. - if (CI->doesNotReturn()) { + // unreachable code. A musttail call must be followed by a return, so it is + // handled below even if the callee doesn't return. + if (CI->doesNotReturn() && !IsMustTail) { if (NeedSRetLifetimeEnd) PopCleanupBlock(); diff --git a/clang/test/CodeGen/musttail-noreturn.c b/clang/test/CodeGen/musttail-noreturn.c new file mode 100644 index 00000000000000..676ad4d051b525 --- /dev/null +++ b/clang/test/CodeGen/musttail-noreturn.c @@ -0,0 +1,29 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c23 -emit-llvm %s -o - | FileCheck %s + +// A musttail call to a noreturn function must still be followed by a return. + +__attribute__((noreturn)) void gnu_noreturn(int); +typedef void noreturn_fn(int) __attribute__((noreturn)); +extern noreturn_fn *noreturn_ptr; + +// CHECK-LABEL: define {{.*}} @test_gnu( +// CHECK: musttail call void @gnu_noreturn( +// CHECK-NEXT: ret void +void test_gnu(int x) { [[clang::musttail]] return gnu_noreturn(x); } + +// CHECK-LABEL: define {{.*}} @test_ptr( +// CHECK: musttail call void %{{.*}}( +// CHECK-NEXT: ret void +void test_ptr(int x) { [[clang::musttail]] return noreturn_ptr(x); } + +// swiftasynccall makes the call musttail implicitly. +__attribute__((swiftasynccall, noreturn)) void +async_noreturn(char *__attribute__((swift_async_context)) ctx); + +// CHECK-LABEL: define {{.*}} @test_swiftasync( +// CHECK: musttail call swifttailcc void @async_noreturn( +// CHECK-NEXT: ret void +__attribute__((swiftasynccall)) void +test_swiftasync(char *__attribute__((swift_async_context)) ctx) { + return async_noreturn(ctx); +} diff --git a/clang/test/CodeGenCXX/ptrauth-member-function-pointer.cpp b/clang/test/CodeGenCXX/ptrauth-member-function-pointer.cpp index cbfe9e625e7bdb..ddf8a2af5125f1 100644 --- a/clang/test/CodeGenCXX/ptrauth-member-function-pointer.cpp +++ b/clang/test/CodeGenCXX/ptrauth-member-function-pointer.cpp @@ -501,3 +501,17 @@ void test2() { } } + +namespace testNoreturn { + +// CHECK: define linkonce_odr hidden void @_ZN12testNoreturn1S16virtual_noreturnEv_vfpthunk_( +// CHECK: musttail call void +// CHECK-NEXT: ret void + +struct S { + __attribute__((noreturn)) virtual void virtual_noreturn(); +}; + +void (S::*mfptr)() = &S::virtual_noreturn; + +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
