| // This file tests the coro_await_elidable attribute semantics. | |
| // RUN: %clang_cc1 -triple=x86_64-unknown-linux-gnu -std=c++20 -disable-llvm-passes -emit-llvm %s -o - | FileCheck %s | |
| #include "Inputs/coroutine.h" | |
| #include "Inputs/utility.h" | |
| template <typename T> | |
| struct [[clang::coro_await_elidable]] Task { | |
| struct promise_type { | |
| struct FinalAwaiter { | |
| bool await_ready() const noexcept { return false; } | |
| template <typename P> | |
| std::coroutine_handle<> await_suspend(std::coroutine_handle<P> coro) noexcept { | |
| if (!coro) | |
| return std::noop_coroutine(); | |
| return coro.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_value(T x) noexcept { | |
| value = x; | |
| } | |
| std::coroutine_handle<> continuation; | |
| T value; | |
| }; | |
| Task(std::coroutine_handle<promise_type> handle) : handle(handle) {} | |
| ~Task() { | |
| if (handle) | |
| handle.destroy(); | |
| } | |
| struct Awaiter { | |
| Awaiter(Task *t) : task(t) {} | |
| bool await_ready() const noexcept { return false; } | |
| void await_suspend(std::coroutine_handle<void> continuation) noexcept {} | |
| T await_resume() noexcept { | |
| return task->handle.promise().value; | |
| } | |
| Task *task; | |
| }; | |
| auto operator co_await() { | |
| return Awaiter{this}; | |
| } | |
| private: | |
| std::coroutine_handle<promise_type> handle; | |
| }; | |
| // CHECK-LABEL: define{{.*}} @_Z6calleev{{.*}} { | |
| Task<int> callee() { | |
| co_return 1; | |
| } | |
| // CHECK-LABEL: define{{.*}} @_Z8elidablev{{.*}} { | |
| Task<int> elidable() { | |
| // CHECK: %[[TASK_OBJ:.+]] = alloca %struct.Task | |
| // CHECK: call void @_Z6calleev(ptr dead_on_unwind writable sret(%struct.Task) align 8 %[[TASK_OBJ]]) #[[ELIDE_SAFE:.+]] | |
| co_return co_await callee(); | |
| } | |
| // CHECK-LABEL: define{{.*}} @_Z11nonelidablev{{.*}} { | |
| Task<int> nonelidable() { | |
| // CHECK: %[[TASK_OBJ:.+]] = alloca %struct.Task | |
| auto t = callee(); | |
| // Because we aren't co_awaiting a prvalue, we cannot elide here. | |
| // CHECK: call void @_Z6calleev(ptr dead_on_unwind writable sret(%struct.Task) align 8 %[[TASK_OBJ]]) | |
| // CHECK-NOT: #[[ELIDE_SAFE]] | |
| co_await t; | |
| co_await std::move(t); | |
| co_return 1; | |
| } | |
| // CHECK-LABEL: define{{.*}} @_Z8addTasksO4TaskIiES1_{{.*}} { | |
| Task<int> addTasks([[clang::coro_await_elidable_argument]] Task<int> &&t1, Task<int> &&t2) { | |
| int i1 = co_await t1; | |
| int i2 = co_await t2; | |
| co_return i1 + i2; | |
| } | |
| // CHECK-LABEL: define{{.*}} @_Z10returnSamei{{.*}} { | |
| Task<int> returnSame(int i) { | |
| co_return i; | |
| } | |
| // CHECK-LABEL: define{{.*}} @_Z21elidableWithMustAwaitv{{.*}} { | |
| Task<int> elidableWithMustAwait() { | |
| // CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 2) #[[ELIDE_SAFE]] | |
| // CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 3){{$}} | |
| co_return co_await addTasks(returnSame(2), returnSame(3)); | |
| } | |
| template <typename... Args> | |
| Task<int> sumAll([[clang::coro_await_elidable_argument]] Args && ... tasks); | |
| // CHECK-LABEL: define{{.*}} @_Z16elidableWithPackv{{.*}} { | |
| Task<int> elidableWithPack() { | |
| // CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 1){{$}} | |
| // CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 2) #[[ELIDE_SAFE]] | |
| // CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 3) #[[ELIDE_SAFE]] | |
| auto t = returnSame(1); | |
| co_return co_await sumAll(t, returnSame(2), returnSame(3)); | |
| } | |
| // CHECK-LABEL: define{{.*}} @_Z25elidableWithPackRecursivev{{.*}} { | |
| Task<int> elidableWithPackRecursive() { | |
| // CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 1) #[[ELIDE_SAFE]] | |
| // CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 2){{$}} | |
| // CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 3) #[[ELIDE_SAFE]] | |
| co_return co_await sumAll(addTasks(returnSame(1), returnSame(2)), returnSame(3)); | |
| } | |
| // Test that parenthesized expressions don't break HALO | |
| // CHECK-LABEL: define{{.*}} @_Z14withParenthesev{{.*}} { | |
| Task<int> withParenthese() { | |
| // CHECK: call void @_Z6calleev(ptr {{.*}}) #[[ELIDE_SAFE]] | |
| co_return co_await (callee()); | |
| } | |
| // Test nested parentheses | |
| // CHECK-LABEL: define{{.*}} @_Z20withNestedParenthesev{{.*}} { | |
| Task<int> withNestedParenthese() { | |
| // CHECK: call void @_Z6calleev(ptr {{.*}}) #[[ELIDE_SAFE]] | |
| co_return co_await ((callee())); | |
| } | |
| // Test parentheses with elidable argument | |
| // CHECK-LABEL: define{{.*}} @_Z21withParenArgsElidablev{{.*}} { | |
| Task<int> withParenArgsElidable() { | |
| // CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 2) #[[ELIDE_SAFE]] | |
| // CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 3){{$}} | |
| co_return co_await (addTasks(returnSame(2), returnSame(3))); | |
| } | |
| // Test parentheses around elidable argument expressions | |
| // CHECK-LABEL: define{{.*}} @_Z24withParenInsideArgsFirstv{{.*}} { | |
| Task<int> withParenInsideArgsFirst() { | |
| // Argument wrapped in parens should still be elidable | |
| // CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 4) #[[ELIDE_SAFE]] | |
| // CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 5){{$}} | |
| co_return co_await addTasks((returnSame(4)), returnSame(5)); | |
| } | |
| // CHECK-LABEL: define{{.*}} @_Z25withParenInsideArgsSecondv{{.*}} { | |
| Task<int> withParenInsideArgsSecond() { | |
| // Both arguments wrapped in parens, first should still be elidable | |
| // CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 6) #[[ELIDE_SAFE]] | |
| // CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 7){{$}} | |
| co_return co_await addTasks((returnSame(6)), (returnSame(7))); | |
| } | |
| // Test operator overloading scenario (like the `operator|` case) | |
| Task<int> operator|(int, [[clang::coro_await_elidable_argument]] Task<int> &&t) { | |
| co_return co_await t; | |
| } | |
| // CHECK-LABEL: define{{.*}} @_Z15withOperatorOldv{{.*}} { | |
| Task<int> withOperatorOld() { | |
| // CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 8) #[[ELIDE_SAFE]] | |
| co_return co_await (0 | returnSame(8)); | |
| } | |
| // CHECK: attributes #[[ELIDE_SAFE]] = { coro_elide_safe } |