Closing https://github.com/llvm/llvm-project/issues/56329 The problem happens when we try to simplify the suspend points. We might break the assumption that the final suspend lives in the last slot of Shape.CoroSuspends. This patch tries to main the assumption and fixes the problem.
118 lines
3.1 KiB
C++
118 lines
3.1 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
|
|
|
|
#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-NEXT: ret void
|
|
// CHEKC-NEXT: }
|