Files
clang-p2996/clang/test/CodeGenCoroutines/pr56329.cpp
fpasserby f786881340 [coroutine] Implement llvm.coro.await.suspend intrinsic (#79712)
Implement `llvm.coro.await.suspend` intrinsics, to deal with performance
regression after prohibiting `.await_suspend` inlining, as suggested in
#64945.
Actually, there are three new intrinsics, which directly correspond to
each of three forms of `await_suspend`:
```
void llvm.coro.await.suspend.void(ptr %awaiter, ptr %frame, ptr @wrapperFunction)
i1 llvm.coro.await.suspend.bool(ptr %awaiter, ptr %frame, ptr @wrapperFunction)
ptr llvm.coro.await.suspend.handle(ptr %awaiter, ptr %frame, ptr @wrapperFunction)
```
There are three different versions instead of one, because in `bool`
case it's result is used for resuming via a branch, and in
`coroutine_handle` case exceptions from `await_suspend` are handled in
the coroutine, and exceptions from the subsequent `.resume()` are
propagated to the caller.

Await-suspend block is simplified down to intrinsic calls only, for
example for symmetric transfer:
```
%id = call token @llvm.coro.save(ptr null)
%handle = call ptr @llvm.coro.await.suspend.handle(ptr %awaiter, ptr %frame, ptr @wrapperFunction)
call void @llvm.coro.resume(%handle)
%result = call i8 @llvm.coro.suspend(token %id, i1 false)
switch i8 %result, ...
```
All await-suspend logic is moved out into a wrapper function, generated
for each suspension point.
The signature of the function is `<type> wrapperFunction(ptr %awaiter,
ptr %frame)` where `<type>` is one of `void` `i1` or `ptr`, depending on
the return type of `await_suspend`.
Intrinsic calls are lowered during `CoroSplit` pass, right after the
split.

Because I'm new to LLVM, I'm not sure if the helper function generation,
calls to them and lowering are implemented in the right way, especially
with regard to various metadata and attributes, i. e. for TBAA. All
things that seemed questionable are marked with `FIXME` comments.

There is another detail: in case of symmetric transfer raw pointer to
the frame of coroutine, that should be resumed, is returned from the
helper function and a direct call to `@llvm.coro.resume` is generated.
C++ standard demands, that `.resume()` method is evaluated. Not sure how
important is this, because code has been generated in the same way
before, sans helper function.
2024-03-11 10:00:00 +08:00

124 lines
3.3 KiB
C++

// Test for PR56919. Tests the we won't contain the resumption of final suspend point.
//
// RUN: %clang_cc1 -triple %itanium_abi_triple -std=c++20 %s -O3 -S -emit-llvm -o - | FileCheck %s
// This test is expected to fail on PowerPC.
// XFAIL: target=powerpc{{.*}}
#include "Inputs/coroutine.h"
void _exit(int status) __attribute__ ((__noreturn__));
class Promise;
// An object that can be co_awaited, but we always resume immediately from
// await_suspend.
struct ResumeFromAwaitSuspend{};
struct Task {
using promise_type = Promise;
Promise& promise;
};
struct Promise {
static std::coroutine_handle<Promise> GetHandle(Promise& promise) {
return std::coroutine_handle<Promise>::from_promise(promise);
}
void unhandled_exception() {}
Task get_return_object() { return Task{*this}; }
void return_void() {}
// Always suspend before starting the coroutine body. We actually run the body
// when we are co_awaited.
std::suspend_always initial_suspend() { return {}; }
// We support awaiting tasks. We do so by configuring them to resume us when
// they are finished, and then resuming them from their initial suspend.
auto await_transform(Task&& task) {
struct Awaiter {
bool await_ready() { return false; }
std::coroutine_handle<> await_suspend(
const std::coroutine_handle<> handle) {
// Tell the child to resume the parent once it finishes.
child.resume_at_final_suspend = GetHandle(parent);
// Run the child.
return GetHandle(child);
}
void await_resume() {
// The child is now at its final suspend point, and can be destroyed.
return GetHandle(child).destroy();
}
Promise& parent;
Promise& child;
};
return Awaiter{
.parent = *this,
.child = task.promise,
};
}
// Make evaluation of `co_await ResumeFromAwaitSuspend{}` go through the
// await_suspend path, but cause it to resume immediately by returning our own
// handle to resume.
auto await_transform(ResumeFromAwaitSuspend) {
struct Awaiter {
bool await_ready() { return false; }
std::coroutine_handle<> await_suspend(const std::coroutine_handle<> h) {
return h;
}
void await_resume() {}
};
return Awaiter{};
}
// Always suspend at the final suspend point, transferring control back to our
// caller. We expect never to be resumed from the final suspend.
auto final_suspend() noexcept {
struct FinalSuspendAwaitable final {
bool await_ready() noexcept { return false; }
std::coroutine_handle<> await_suspend(std::coroutine_handle<>) noexcept {
return promise.resume_at_final_suspend;
}
void await_resume() noexcept {
_exit(1);
}
Promise& promise;
};
return FinalSuspendAwaitable{.promise = *this};
}
// The handle we will resume once we hit final suspend.
std::coroutine_handle<> resume_at_final_suspend;
};
Task Inner();
Task Outer() {
co_await ResumeFromAwaitSuspend();
co_await Inner();
}
// CHECK: define{{.*}}@_Z5Outerv.resume(
// CHECK-NOT: }
// CHECK-NOT: _exit
// CHECK: musttail call
// CHECK: musttail call
// CHECK: musttail call
// CHECK-NEXT: ret void
// CHECK-EMPTY:
// CHECK-NEXT: unreachable:
// CHECK-NEXT: unreachable
// CHECK-NEXT: }