Files
clang-p2996/clang/test/CodeGenCoroutines/coro-awaiter-noinline-suspend.cpp
Chuanqi Xu b32aa72afc Recommit [C++20] [Coroutines] Mark await_suspend as noinline if the awaiter is not empty
The original patch is incorrect since it marks too many calls to be
noinline. It shows that it is bad to do analysis in the frontend again.
This patch tries to mark the await_suspend function as noinlne only.

---

Close https://github.com/llvm/llvm-project/issues/56301
Close https://github.com/llvm/llvm-project/issues/64151
Close https://github.com/llvm/llvm-project/issues/65018

See the summary and the discussion of
https://reviews.llvm.org/D157070
to get the full context.

As @rjmccall pointed out, the key point of the root cause is that
currently we didn't implement the semantics for '@llvm.coro.save'
well ("after the await-ready returns false, the coroutine is considered
to be suspended ") well.
Since the semantics implies that we (the compiler) shouldn't write
the spills into the coroutine frame in the await_suspend. But now it is
possible due to some combinations of the optimizations so the semantics are
broken. And the inlining is the root optimization of such optimizations.
So in this patch, we tried to add the `noinline` attribute to the
await_suspend function.

This looks slightly problematic since the users are able to call the
await_suspend function standalone. This is limited by the
implementation. On the one hand, we don't want the workaround solution
(See the proposed solution later) to be too complex. On the other hand,
it is rare to call await_suspend standalone. Also it is not semantically
incorrect to do so since the inlining is not part of the C++ standard.

Also as an optimization, we don't add the `noinline` attribute to
the await_suspend function if the awaiter is an empty class. This should be
correct since the programmers can't access the local variables in
await_suspend if the awaiter is empty. I think this is necessary for
the performance since it is pretty common.

The long term solution is:

    call @llvm.coro.await_suspend(ptr %awaiter, ptr %handle,
                                  ptr @awaitSuspendFn)

Then it is much easier to perform the safety analysis in the middle
end. If it is safe to inline the call to awaitSuspend, we can replace it
in the CoroEarly pass. Otherwise we could replace it in the CoroSplit
pass.

Reviewed By: rjmccall

Differential Revision: https://reviews.llvm.org/D157833
2023-08-28 17:07:30 +08:00

169 lines
5.3 KiB
C++

// Tests that we can mark await-suspend as noinline correctly.
//
// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s \
// RUN: -O1 -disable-llvm-passes | FileCheck %s
#include "Inputs/coroutine.h"
struct Task {
struct promise_type {
struct FinalAwaiter {
bool await_ready() const noexcept { return false; }
template <typename PromiseType>
std::coroutine_handle<> await_suspend(std::coroutine_handle<PromiseType> h) noexcept {
return h.promise().continuation;
}
void await_resume() noexcept {}
};
Task get_return_object() noexcept {
return std::coroutine_handle<promise_type>::from_promise(*this);
}
std::suspend_always initial_suspend() noexcept { return {}; }
FinalAwaiter final_suspend() noexcept { return {}; }
void unhandled_exception() noexcept {}
void return_void() noexcept {}
std::coroutine_handle<> continuation;
};
Task(std::coroutine_handle<promise_type> handle);
~Task();
private:
std::coroutine_handle<promise_type> handle;
};
struct StatefulAwaiter {
int value;
bool await_ready() const noexcept { return false; }
template <typename PromiseType>
void await_suspend(std::coroutine_handle<PromiseType> h) noexcept {}
void await_resume() noexcept {}
};
typedef std::suspend_always NoStateAwaiter;
using AnotherStatefulAwaiter = StatefulAwaiter;
template <class T>
struct TemplatedAwaiter {
T value;
bool await_ready() const noexcept { return false; }
template <typename PromiseType>
void await_suspend(std::coroutine_handle<PromiseType> h) noexcept {}
void await_resume() noexcept {}
};
class Awaitable {};
StatefulAwaiter operator co_await(Awaitable) {
return StatefulAwaiter{};
}
StatefulAwaiter GlobalAwaiter;
class Awaitable2 {};
StatefulAwaiter& operator co_await(Awaitable2) {
return GlobalAwaiter;
}
struct AlwaysInlineStatefulAwaiter {
void* value;
bool await_ready() const noexcept { return false; }
template <typename PromiseType>
__attribute__((always_inline))
void await_suspend(std::coroutine_handle<PromiseType> h) noexcept {}
void await_resume() noexcept {}
};
Task testing() {
co_await std::suspend_always{};
co_await StatefulAwaiter{};
co_await AnotherStatefulAwaiter{};
// Test lvalue case.
StatefulAwaiter awaiter;
co_await awaiter;
// The explicit call to await_suspend is not considered suspended.
awaiter.await_suspend(std::coroutine_handle<void>::from_address(nullptr));
co_await TemplatedAwaiter<int>{};
TemplatedAwaiter<int> TemplatedAwaiterInstace;
co_await TemplatedAwaiterInstace;
co_await Awaitable{};
co_await Awaitable2{};
co_await AlwaysInlineStatefulAwaiter{};
}
struct AwaitTransformTask {
struct promise_type {
struct FinalAwaiter {
bool await_ready() const noexcept { return false; }
template <typename PromiseType>
std::coroutine_handle<> await_suspend(std::coroutine_handle<PromiseType> h) noexcept {
return h.promise().continuation;
}
void await_resume() noexcept {}
};
AwaitTransformTask get_return_object() noexcept {
return std::coroutine_handle<promise_type>::from_promise(*this);
}
std::suspend_always initial_suspend() noexcept { return {}; }
FinalAwaiter final_suspend() noexcept { return {}; }
void unhandled_exception() noexcept {}
void return_void() noexcept {}
template <typename Awaitable>
auto await_transform(Awaitable &&awaitable) {
return awaitable;
}
std::coroutine_handle<> continuation;
};
AwaitTransformTask(std::coroutine_handle<promise_type> handle);
~AwaitTransformTask();
private:
std::coroutine_handle<promise_type> handle;
};
struct awaitableWithGetAwaiter {
bool await_ready() const noexcept { return false; }
template <typename PromiseType>
void await_suspend(std::coroutine_handle<PromiseType> h) noexcept {}
void await_resume() noexcept {}
};
AwaitTransformTask testingWithAwaitTransform() {
co_await awaitableWithGetAwaiter{};
}
// CHECK: define{{.*}}@_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE{{.*}}#[[NORMAL_ATTR:[0-9]+]]
// CHECK: define{{.*}}@_ZN15StatefulAwaiter13await_suspendIN4Task12promise_typeEEEvSt16coroutine_handleIT_E{{.*}}#[[NOINLINE_ATTR:[0-9]+]]
// CHECK: define{{.*}}@_ZN15StatefulAwaiter13await_suspendIvEEvSt16coroutine_handleIT_E{{.*}}#[[NORMAL_ATTR]]
// CHECK: define{{.*}}@_ZN16TemplatedAwaiterIiE13await_suspendIN4Task12promise_typeEEEvSt16coroutine_handleIT_E{{.*}}#[[NOINLINE_ATTR]]
// CHECK: define{{.*}}@_ZN27AlwaysInlineStatefulAwaiter13await_suspendIN4Task12promise_typeEEEvSt16coroutine_handleIT_E{{.*}}#[[ALWAYS_INLINE_ATTR:[0-9]+]]
// CHECK: define{{.*}}@_ZN4Task12promise_type12FinalAwaiter13await_suspendIS0_EESt16coroutine_handleIvES3_IT_E{{.*}}#[[NORMAL_ATTR]]
// CHECK: define{{.*}}@_ZN23awaitableWithGetAwaiter13await_suspendIN18AwaitTransformTask12promise_typeEEEvSt16coroutine_handleIT_E{{.*}}#[[NORMAL_ATTR]]
// CHECK: define{{.*}}@_ZN18AwaitTransformTask12promise_type12FinalAwaiter13await_suspendIS0_EESt16coroutine_handleIvES3_IT_E{{.*}}#[[NORMAL_ATTR]]
// CHECK-NOT: attributes #[[NORMAL_ATTR]] = noinline
// CHECK: attributes #[[NOINLINE_ATTR]] = {{.*}}noinline
// CHECK-NOT: attributes #[[ALWAYS_INLINE_ATTR]] = {{.*}}noinline
// CHECK: attributes #[[ALWAYS_INLINE_ATTR]] = {{.*}}alwaysinline