|
| 1 | +// Tests that we can mark await-suspend as noinline correctly. |
| 2 | +// |
| 3 | +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s \ |
| 4 | +// RUN: -disable-llvm-passes | FileCheck %s |
| 5 | + |
| 6 | +#include "Inputs/coroutine.h" |
| 7 | + |
| 8 | +struct Task { |
| 9 | + struct promise_type { |
| 10 | + struct FinalAwaiter { |
| 11 | + bool await_ready() const noexcept { return false; } |
| 12 | + template <typename PromiseType> |
| 13 | + std::coroutine_handle<> await_suspend(std::coroutine_handle<PromiseType> h) noexcept { |
| 14 | + return h.promise().continuation; |
| 15 | + } |
| 16 | + void await_resume() noexcept {} |
| 17 | + }; |
| 18 | + |
| 19 | + Task get_return_object() noexcept { |
| 20 | + return std::coroutine_handle<promise_type>::from_promise(*this); |
| 21 | + } |
| 22 | + |
| 23 | + std::suspend_always initial_suspend() noexcept { return {}; } |
| 24 | + FinalAwaiter final_suspend() noexcept { return {}; } |
| 25 | + void unhandled_exception() noexcept {} |
| 26 | + void return_void() noexcept {} |
| 27 | + |
| 28 | + std::coroutine_handle<> continuation; |
| 29 | + }; |
| 30 | + |
| 31 | + Task(std::coroutine_handle<promise_type> handle); |
| 32 | + ~Task(); |
| 33 | + |
| 34 | +private: |
| 35 | + std::coroutine_handle<promise_type> handle; |
| 36 | +}; |
| 37 | + |
| 38 | +struct StatefulAwaiter { |
| 39 | + int value; |
| 40 | + bool await_ready() const noexcept { return false; } |
| 41 | + template <typename PromiseType> |
| 42 | + void await_suspend(std::coroutine_handle<PromiseType> h) noexcept {} |
| 43 | + void await_resume() noexcept {} |
| 44 | +}; |
| 45 | + |
| 46 | +typedef std::suspend_always NoStateAwaiter; |
| 47 | +using AnotherStatefulAwaiter = StatefulAwaiter; |
| 48 | + |
| 49 | +template <class T> |
| 50 | +struct TemplatedAwaiter { |
| 51 | + T value; |
| 52 | + bool await_ready() const noexcept { return false; } |
| 53 | + template <typename PromiseType> |
| 54 | + void await_suspend(std::coroutine_handle<PromiseType> h) noexcept {} |
| 55 | + void await_resume() noexcept {} |
| 56 | +}; |
| 57 | + |
| 58 | + |
| 59 | +class Awaitable {}; |
| 60 | +StatefulAwaiter operator co_await(Awaitable) { |
| 61 | + return StatefulAwaiter{}; |
| 62 | +} |
| 63 | + |
| 64 | +StatefulAwaiter GlobalAwaiter; |
| 65 | +class Awaitable2 {}; |
| 66 | +StatefulAwaiter& operator co_await(Awaitable2) { |
| 67 | + return GlobalAwaiter; |
| 68 | +} |
| 69 | + |
| 70 | +Task testing() { |
| 71 | + co_await std::suspend_always{}; |
| 72 | + co_await StatefulAwaiter{}; |
| 73 | + co_await AnotherStatefulAwaiter{}; |
| 74 | + |
| 75 | + // Test lvalue case. |
| 76 | + StatefulAwaiter awaiter; |
| 77 | + co_await awaiter; |
| 78 | + |
| 79 | + // The explicit call to await_suspend is not considered suspended. |
| 80 | + awaiter.await_suspend(std::coroutine_handle<void>::from_address(nullptr)); |
| 81 | + |
| 82 | + co_await TemplatedAwaiter<int>{}; |
| 83 | + TemplatedAwaiter<int> TemplatedAwaiterInstace; |
| 84 | + co_await TemplatedAwaiterInstace; |
| 85 | + |
| 86 | + co_await Awaitable{}; |
| 87 | + co_await Awaitable2{}; |
| 88 | +} |
| 89 | + |
| 90 | +// CHECK-LABEL: @_Z7testingv |
| 91 | + |
| 92 | +// Check `co_await __promise__.initial_suspend();` Since it returns std::suspend_always, |
| 93 | +// which is an empty class, we shouldn't generate optimization blocker for it. |
| 94 | +// CHECK: call token @llvm.coro.save |
| 95 | +// CHECK: call void @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE{{.*}}#[[NORMAL_ATTR:[0-9]+]] |
| 96 | + |
| 97 | +// Check the `co_await std::suspend_always{};` expression. We shouldn't emit the optimization |
| 98 | +// blocker for it since it is an empty class. |
| 99 | +// CHECK: call token @llvm.coro.save |
| 100 | +// CHECK: call void @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE{{.*}}#[[NORMAL_ATTR]] |
| 101 | + |
| 102 | +// Check `co_await StatefulAwaiter{};`. We need to emit the optimization blocker since |
| 103 | +// the awaiter is not empty. |
| 104 | +// CHECK: call token @llvm.coro.save |
| 105 | +// CHECK: call void @_ZN15StatefulAwaiter13await_suspendIN4Task12promise_typeEEEvSt16coroutine_handleIT_E{{.*}}#[[NOINLINE_ATTR:[0-9]+]] |
| 106 | + |
| 107 | +// Check `co_await AnotherStatefulAwaiter{};` to make sure that we can handle TypedefTypes. |
| 108 | +// CHECK: call token @llvm.coro.save |
| 109 | +// CHECK: call void @_ZN15StatefulAwaiter13await_suspendIN4Task12promise_typeEEEvSt16coroutine_handleIT_E{{.*}}#[[NOINLINE_ATTR]] |
| 110 | + |
| 111 | +// Check `co_await awaiter;` to make sure we can handle lvalue cases. |
| 112 | +// CHECK: call token @llvm.coro.save |
| 113 | +// CHECK: call void @_ZN15StatefulAwaiter13await_suspendIN4Task12promise_typeEEEvSt16coroutine_handleIT_E{{.*}}#[[NOINLINE_ATTR]] |
| 114 | + |
| 115 | +// Check `awaiter.await_suspend(...)` to make sure the explicit call the await_suspend won't be marked as noinline |
| 116 | +// CHECK: call void @_ZN15StatefulAwaiter13await_suspendIvEEvSt16coroutine_handleIT_E{{.*}}#[[NORMAL_ATTR]] |
| 117 | + |
| 118 | +// Check `co_await TemplatedAwaiter<int>{};` to make sure we can handle specialized template |
| 119 | +// type. |
| 120 | +// CHECK: call token @llvm.coro.save |
| 121 | +// CHECK: call void @_ZN16TemplatedAwaiterIiE13await_suspendIN4Task12promise_typeEEEvSt16coroutine_handleIT_E{{.*}}#[[NOINLINE_ATTR]] |
| 122 | + |
| 123 | +// Check `co_await TemplatedAwaiterInstace;` to make sure we can handle the lvalue from |
| 124 | +// specialized template type. |
| 125 | +// CHECK: call token @llvm.coro.save |
| 126 | +// CHECK: call void @_ZN16TemplatedAwaiterIiE13await_suspendIN4Task12promise_typeEEEvSt16coroutine_handleIT_E{{.*}}#[[NOINLINE_ATTR]] |
| 127 | + |
| 128 | +// Check `co_await Awaitable{};` to make sure we can handle awaiter returned by |
| 129 | +// `operator co_await`; |
| 130 | +// CHECK: call token @llvm.coro.save |
| 131 | +// CHECK: call void @_ZN15StatefulAwaiter13await_suspendIN4Task12promise_typeEEEvSt16coroutine_handleIT_E{{.*}}#[[NOINLINE_ATTR]] |
| 132 | + |
| 133 | +// Check `co_await Awaitable2{};` to make sure we can handle awaiter returned by |
| 134 | +// `operator co_await` which returns a reference; |
| 135 | +// CHECK: call token @llvm.coro.save |
| 136 | +// CHECK: call void @_ZN15StatefulAwaiter13await_suspendIN4Task12promise_typeEEEvSt16coroutine_handleIT_E{{.*}}#[[NOINLINE_ATTR]] |
| 137 | + |
| 138 | +// Check `co_await __promise__.final_suspend();`. We don't emit an blocker here since it is |
| 139 | +// empty. |
| 140 | +// CHECK: call token @llvm.coro.save |
| 141 | +// CHECK: call ptr @_ZN4Task12promise_type12FinalAwaiter13await_suspendIS0_EESt16coroutine_handleIvES3_IT_E{{.*}}#[[NORMAL_ATTR]] |
| 142 | + |
| 143 | +struct AwaitTransformTask { |
| 144 | + struct promise_type { |
| 145 | + struct FinalAwaiter { |
| 146 | + bool await_ready() const noexcept { return false; } |
| 147 | + template <typename PromiseType> |
| 148 | + std::coroutine_handle<> await_suspend(std::coroutine_handle<PromiseType> h) noexcept { |
| 149 | + return h.promise().continuation; |
| 150 | + } |
| 151 | + void await_resume() noexcept {} |
| 152 | + }; |
| 153 | + |
| 154 | + AwaitTransformTask get_return_object() noexcept { |
| 155 | + return std::coroutine_handle<promise_type>::from_promise(*this); |
| 156 | + } |
| 157 | + |
| 158 | + std::suspend_always initial_suspend() noexcept { return {}; } |
| 159 | + FinalAwaiter final_suspend() noexcept { return {}; } |
| 160 | + void unhandled_exception() noexcept {} |
| 161 | + void return_void() noexcept {} |
| 162 | + |
| 163 | + template <typename Awaitable> |
| 164 | + auto await_transform(Awaitable &&awaitable) { |
| 165 | + return awaitable; |
| 166 | + } |
| 167 | + |
| 168 | + std::coroutine_handle<> continuation; |
| 169 | + }; |
| 170 | + |
| 171 | + AwaitTransformTask(std::coroutine_handle<promise_type> handle); |
| 172 | + ~AwaitTransformTask(); |
| 173 | + |
| 174 | +private: |
| 175 | + std::coroutine_handle<promise_type> handle; |
| 176 | +}; |
| 177 | + |
| 178 | +struct awaitableWithGetAwaiter { |
| 179 | + bool await_ready() const noexcept { return false; } |
| 180 | + template <typename PromiseType> |
| 181 | + void await_suspend(std::coroutine_handle<PromiseType> h) noexcept {} |
| 182 | + void await_resume() noexcept {} |
| 183 | +}; |
| 184 | + |
| 185 | +AwaitTransformTask testingWithAwaitTransform() { |
| 186 | + co_await awaitableWithGetAwaiter{}; |
| 187 | +} |
| 188 | + |
| 189 | +// CHECK-LABEL: @_Z25testingWithAwaitTransformv |
| 190 | + |
| 191 | +// Init suspend |
| 192 | +// CHECK: call token @llvm.coro.save |
| 193 | +// CHECK-NOT: call void @llvm.coro.opt.blocker( |
| 194 | +// CHECK: call void @_ZNSt14suspend_always13await_suspendESt16coroutine_handleIvE{{.*}}#[[NORMAL_ATTR]] |
| 195 | + |
| 196 | +// Check `co_await awaitableWithGetAwaiter{};`. |
| 197 | +// CHECK: call token @llvm.coro.save |
| 198 | +// CHECK-NOT: call void @llvm.coro.opt.blocker( |
| 199 | +// Check call void @_ZN23awaitableWithGetAwaiter13await_suspendIN18AwaitTransformTask12promise_typeEEEvSt16coroutine_handleIT_E{{.*}}#[[NORMAL_ATTR]] |
| 200 | + |
| 201 | +// Final suspend |
| 202 | +// CHECK: call token @llvm.coro.save |
| 203 | +// CHECK-NOT: call void @llvm.coro.opt.blocker( |
| 204 | +// CHECK: call ptr @_ZN18AwaitTransformTask12promise_type12FinalAwaiter13await_suspendIS0_EESt16coroutine_handleIvES3_IT_E{{.*}}#[[NORMAL_ATTR]] |
| 205 | + |
| 206 | +// CHECK-NOT: attributes #[[NORMAL_ATTR]] = noinline |
| 207 | +// CHECK: attributes #[[NOINLINE_ATTR]] = {{.*}}noinline |
0 commit comments