[libcxx-commits] [clang] [libcxx] Elide suspension points via [[clang::coro_await_suspend_destroy]] (PR #152623)
via libcxx-commits
libcxx-commits at lists.llvm.org
Tue Aug 19 21:54:42 PDT 2025
================
@@ -9363,6 +9363,126 @@ Example:
}];
}
+def CoroAwaitSuspendDestroyDoc : Documentation {
+ let Category = DocCatFunction;
+ let Content = [{
+
+The ``[[clang::coro_await_suspend_destroy]]`` attribute applies to an
+``await_suspend(std::coroutine_handle<Promise>)`` member function of a
+coroutine awaiter. When applied, suspensions into the awaiter use an optimized
+call path that bypasses standard suspend intrinsics, and immediately destroys
+the suspending coro.
+
+Instead of calling the annotated ``await_suspend()``, the coroutine calls
+``await_suspend_destroy(Promise&)`` and immediately destroys the coroutine.
+
+Although it is not called, it is strongly recommended that `await_suspend()`
+contain the following portability stub. The stub ensures the awaiter behaves
+equivalently without `coro_await_suspend_destroy` support, and makes the
+control flow clear to readers unfamiliar with the attribute:
+
+.. code-block:: c++
+
+ void await_suspend_destroy(Promise&) { /* actual implementation*/ }
+ [[clang::coro_await_suspend_destroy]]
+ void await_suspend(std::coroutine_handle<Promise> handle) {
+ // Stub to preserve behavior when the attribute is not supported
+ await_suspend_destroy(handle.promise());
+ handle.destroy();
+ }
----------------
snarkmaster wrote:
Okay, sounds like I need to make it very explicit. Let me put my lawyer hat on! Let me know if the following is good enough, to replace the lines you highlighted?
---
Here is the formal contract for this attribute.
The attribute is considered *active* when **both** of these are true:
- The compiler supports it -- i.e. the macro `__has_cpp_attribute(clang::coro_await_suspend_destroy)` expands to a nonzero integer.
- The `await_suspend` overload applicable to the current coroutine's promise type is annotated with `[[clang::coro_await_suspend_destroy]]`.
If the attribute is **inactive**, then the compiler will follow the C++ standard suspension behavior. When `await_ready()` returns `false`:
- First, the coroutine is suspended -- the compiler saves the coroutine state and creates a handle.
- Then, `await_suspend` is invoked with the handle.
- Note: With an inactive attribute, `await_suspend_destroy(Promise&)` may be defined, but is not part of the compiler's protocol.
If the attribute is **active**, the compiler will follow this non-standard protocol whenever `await_ready()` returns `false`:
- First, `await_suspend_destroy` is invoked with a mutable reference to awaiting coroutine's promise.
- Then, the coroutine is immediately destroyed, as if on `co_return ...;` but without invoking either `return_void()` or `return_value()`.
- Notes:
* The coroutine is **not** suspended, and a handle is **not** created.
* The applicable `await_suspend` is **not** called. It must still be declared, since the compiler looks for the attribute on this special member, but a definition is optional. NB: Before providing a definition, read the note on portability below.
**Portability note:** It is strongly recommended to write your code in a way that does **not** rely on support for this attribute. Fortunately, the attribute's contract is designed so that portability does not require conditional compilation.
Suppose you have the following standard `await_suspend`:
```cpp
void await_suspend(std::coroutine_handle<MyPromise>& h) {
record_suspension_via_promise(h.promise());
h.destroy();
}
```
Without loss of portability, you can replace it by `await_suspend_destroy`, plus a fallback `await_suspend`. Depending on the compiler, either one may be the entry point, but the behavior will be the same -- except for the speed, size, and allocation-elision benefits of the attribute.
```cpp
// Entry point when `clang::coro_await_suspend_destroy` is supported
void await_suspend_destroy(MyPromise& p) {
record_suspension_via_promise(p);
}
// Entry point when `clang::coro_await_suspend_destroy` is not supported.
// Emits no code when `clang::coro_await_suspend_destroy` is supported.
void await_suspend(std::coroutine_handle<MyPromise>& h) {
await_suspend_destroy(h.promise());
h.destroy();
}
```
The "standard" and "replacement" forms are equivalent because the fallback `await_suspend` replicates the attribute's contract, when the attribute is not supported by the compiler.
**Warning:** Even if you only use Clang, do not neglect to add the portability stub -- LLVM reserves the right to remove support for this attribute in a later major release.
https://github.com/llvm/llvm-project/pull/152623
More information about the libcxx-commits
mailing list