llvm-capstone/clang/test/CodeGenCoroutines/coro-only-destroy-when-complete.cpp
Chuanqi Xu b7b5907b56
[Coroutines] Introduce [[clang::coro_only_destroy_when_complete]] (#71014)
Close https://github.com/llvm/llvm-project/issues/56980.

This patch tries to introduce a light-weight optimization attribute for
coroutines which are guaranteed to only be destroyed after it reached
the final suspend.

The rationale behind the patch is simple. See the example:

```C++
A foo() {
  dtor d;
  co_await something();
  dtor d1;
  co_await something();
  dtor d2;
  co_return 43;
}
```

Generally the generated .destroy function may be:

```C++
void foo.destroy(foo.Frame *frame) {
  switch(frame->suspend_index()) {
    case 1:
      frame->d.~dtor();
      break;
    case 2:
      frame->d.~dtor();
      frame->d1.~dtor();
      break;
    case 3:
      frame->d.~dtor();
      frame->d1.~dtor();
      frame->d2.~dtor();
      break;
    default: // coroutine completed or haven't started
      break;
  }

  frame->promise.~promise_type();
  delete frame;
}
```

Since the compiler need to be ready for all the cases that the coroutine
may be destroyed in a valid state.

However, from the user's perspective, we can understand that certain
coroutine types may only be destroyed after it reached to the final
suspend point. And we need a method to teach the compiler about this.
Then this is the patch. After the compiler recognized that the
coroutines can only be destroyed after complete, it can optimize the
above example to:

```C++
void foo.destroy(foo.Frame *frame) {
  frame->promise.~promise_type();
  delete frame;
}
```

I spent a lot of time experimenting and experiencing this in the
downstream. The numbers are really good. In a real-world coroutine-heavy
workload, the size of the build dir (including .o files) reduces 14%.
And the size of final libraries (excluding the .o files) reduces 8% in
Debug mode and 1% in Release mode.
2023-11-09 14:42:07 +08:00

60 lines
1.3 KiB
C++

// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 \
// RUN: -disable-llvm-passes -emit-llvm %s -o - | FileCheck %s
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 \
// RUN: -O3 -emit-llvm %s -o - | FileCheck %s --check-prefix=CHECK-O
#include "Inputs/coroutine.h"
using namespace std;
struct A;
struct A_promise_type {
A get_return_object();
suspend_always initial_suspend();
suspend_always final_suspend() noexcept;
void return_value(int);
void unhandled_exception();
std::coroutine_handle<> handle;
};
struct Awaitable{
bool await_ready();
int await_resume();
template <typename F>
void await_suspend(F);
};
Awaitable something();
struct dtor {
dtor();
~dtor();
};
struct [[clang::coro_only_destroy_when_complete]] A {
using promise_type = A_promise_type;
A();
A(std::coroutine_handle<>);
~A();
std::coroutine_handle<promise_type> handle;
};
A foo() {
dtor d;
co_await something();
dtor d1;
co_await something();
dtor d2;
co_return 43;
}
// CHECK: define{{.*}}@_Z3foov({{.*}}) #[[ATTR_NUM:[0-9]+]]
// CHECK: attributes #[[ATTR_NUM]] = {{.*}}coro_only_destroy_when_complete
// CHECK-O: define{{.*}}@_Z3foov.destroy
// CHECK-O: {{^.*}}:
// CHECK-O-NOT: br
// CHECK-O: ret void