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
169 lines
5.3 KiB
C++
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
|