[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