Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
9fc3169
Elide suspension points via [[clang::coro_await_suspend_destroy]]
snarkmaster Aug 7, 2025
eb5557a
Fix CI
snarkmaster Aug 8, 2025
5d6a06d
Improve doc formatting
snarkmaster Aug 8, 2025
9fe0b17
Merge branch 'main' into coro_await_suspend_destroy
snarkmaster Aug 8, 2025
1dabfe6
Rework the AttrDocs.td addition based on feedback
snarkmaster Aug 9, 2025
62789ef
Split out the `libcxx/test` change into PR #152820
snarkmaster Aug 9, 2025
4835f37
Lift standard suspend flow to emitStandardAwaitSuspend; tweak comment.
snarkmaster Aug 9, 2025
3e84df1
Merge branch 'llvm:main' into coro_await_suspend_destroy
snarkmaster Aug 9, 2025
b359f5f
Merge branch 'coro_await_suspend_destroy' of github.com:snarkmaster/l…
snarkmaster Aug 9, 2025
4b7707d
Merge branch 'llvm:main' into coro_await_suspend_destroy
snarkmaster Aug 17, 2025
99703af
Elide suspension points via [[clang::coro_await_suspend_destroy]]
snarkmaster Aug 7, 2025
8bf453e
Fix CI
snarkmaster Aug 8, 2025
811501d
Improve doc formatting
snarkmaster Aug 8, 2025
63cf306
Rework the AttrDocs.td addition based on feedback
snarkmaster Aug 9, 2025
2b2748c
Split out the `libcxx/test` change into PR #152820
snarkmaster Aug 9, 2025
cf26f6b
Lift standard suspend flow to emitStandardAwaitSuspend; tweak comment.
snarkmaster Aug 9, 2025
f1e885c
Improvements in response to comments
snarkmaster Aug 17, 2025
4f4e815
Merge branch 'llvm:main' into coro_await_suspend_destroy
snarkmaster Aug 19, 2025
2b3a9b7
Merge branch 'coro_await_suspend_destroy' of github.com:snarkmaster/l…
snarkmaster Aug 19, 2025
72274d2
Fix bad merge & some doc backticks
snarkmaster Aug 19, 2025
543bf07
Remove another leftover file from bad merge
snarkmaster Aug 19, 2025
a09b1f8
Merge branch 'llvm:main' into coro_await_suspend_destroy
snarkmaster Aug 19, 2025
e6b6367
Address 2 more comments
snarkmaster Aug 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@ Removed Compiler Flags
Attribute Changes in Clang
--------------------------

- Introduced a new attribute ``[[clang::coro_await_suspend_destroy]]``. When
applied to a coroutine awaiter class, it causes suspensions into this awaiter
to use a new `await_suspend_destroy(Promise&)` method instead of the standard
`await_suspend(std::coroutine_handle<...>)`. The coroutine is then destroyed.
This improves code speed & size for "short-circuiting" coroutines.

Improvements to Clang's diagnostics
-----------------------------------
- Added a separate diagnostic group ``-Wfunction-effect-redeclarations``, for the more pedantic
Expand Down
8 changes: 8 additions & 0 deletions clang/include/clang/Basic/Attr.td
Original file line number Diff line number Diff line change
Expand Up @@ -1352,6 +1352,14 @@ def CoroAwaitElidableArgument : InheritableAttr {
let SimpleHandler = 1;
}

def CoroAwaitSuspendDestroy: InheritableAttr {
let Spellings = [Clang<"coro_await_suspend_destroy">];
let Subjects = SubjectList<[CXXRecord]>;
let LangOpts = [CPlusPlus];
let Documentation = [CoroAwaitSuspendDestroyDoc];
let SimpleHandler = 1;
}

// OSObject-based attributes.
def OSConsumed : InheritableParamAttr {
let Spellings = [Clang<"os_consumed">];
Expand Down
92 changes: 92 additions & 0 deletions clang/include/clang/Basic/AttrDocs.td
Original file line number Diff line number Diff line change
Expand Up @@ -9270,6 +9270,98 @@ Example:
}];
}

def CoroAwaitSuspendDestroyDoc : Documentation {
let Category = DocCatDecl;
let Content = [{

The ``[[clang::coro_await_suspend_destroy]]`` attribute may be applied to a C++
coroutine awaiter type. When this attribute is present, the awaiter must
implement ``void await_suspend_destroy(Promise&)``. If ``await_ready()``
returns ``false`` at a suspension point, ``await_suspend_destroy`` will be
called directly, bypassing the ``await_suspend(std::coroutine_handle<...>)``
method. The coroutine being suspended will then be immediately destroyed.

Logically, the new behavior is equivalent to this standard code:

.. code-block:: c++

void await_suspend_destroy(YourPromise&) { ... }
void await_suspend(auto handle) {
await_suspend_destroy(handle.promise());
handle.destroy();
}

This enables `await_suspend_destroy()` usage in portable awaiters — just add a
stub ``await_suspend()`` as above. Without ``coro_await_suspend_destroy``
support, the awaiter will behave nearly identically, with the only difference
being heap allocation instead of stack allocation for the coroutine frame.

This attribute exists to optimize short-circuiting coroutines—coroutines whose
suspend points are either (i) trivial (like ``std::suspend_never``), or (ii)
short-circuiting (like a ``co_await`` that can be expressed in regular control
flow as):

.. code-block:: c++

T val;
if (awaiter.await_ready()) {
val = awaiter.await_resume();
} else {
awaiter.await_suspend();
return /* value representing the "execution short-circuited" outcome */;
}

The benefits of this attribute are:

- **Avoid heap allocations for coro frames**: Allocating short-circuiting
coros on the stack makes code more predictable under memory pressure.
Without this attribute, LLVM cannot elide heap allocation even when all
awaiters are short-circuiting.

- **Performance**: Significantly faster execution and smaller code size.

- **Build time**: Faster compilation due to less IR being generated.

Marking your ``await_suspend_destroy`` method as ``noexcept`` can sometimes
further improve optimization.

Here is a toy example of a portable short-circuiting awaiter:

.. code-block:: c++

template <typename T>
struct [[clang::coro_await_suspend_destroy]] optional_awaitable {
std::optional<T> opt_;
bool await_ready() const noexcept { return opt_.has_value(); }
T await_resume() { return std::move(opt_).value(); }
void await_suspend_destroy(auto& promise) {
// Assume the return object of the outer coro defaults to "empty".
}
// Fallback for when `coro_await_suspend_destroy` is unavailable.
void await_suspend(auto handle) {
await_suspend_destroy(handle.promise());
handle.destroy();
}
};

If all suspension points use (i) trivial or (ii) short-circuiting awaiters,
then the coroutine optimizes more like a plain function, with 2 caveats:

- **Behavior:** The coroutine promise provides an implicit exception boundary
(as if wrapping the function in ``try {} catch { unhandled_exception(); }``).
This exception handling behavior is usually desirable in robust,
return-value-oriented programs that need short-circuiting coroutines.
Otherwise, the promise can always re-throw.

- **Speed:** As of 2025, there is still an optimization gap between a
realistic short-circuiting coro, and the equivalent (but much more verbose)
function. For a guesstimate, expect 4-5ns per call on x86. One idea for
improvement is to also elide trivial suspends like `std::suspend_never`, in
order to hit the `HasCoroSuspend` path in `CoroEarly.cpp`.

}];
}

def CountedByDocs : Documentation {
let Category = DocCatField;
let Content = [{
Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -12504,6 +12504,9 @@ def note_coroutine_promise_call_implicitly_required : Note<
def err_await_suspend_invalid_return_type : Error<
"return type of 'await_suspend' is required to be 'void' or 'bool' (have %0)"
>;
def err_await_suspend_destroy_invalid_return_type : Error<
"return type of 'await_suspend_destroy' is required to be 'void' (have %0)"
>;
def note_await_ready_no_bool_conversion : Note<
"return type of 'await_ready' is required to be contextually convertible to 'bool'"
>;
Expand Down
Loading