// 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 struct [[clang::coro_await_elidable]] Task { struct promise_type { struct FinalAwaiter { bool await_ready() const noexcept { return false; } template std::coroutine_handle<> await_suspend(std::coroutine_handle

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::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 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 continuation) noexcept {} T await_resume() noexcept { return task->handle.promise().value; } Task *task; }; auto operator co_await() { return Awaiter{this}; } private: std::coroutine_handle handle; }; // CHECK-LABEL: define{{.*}} @_Z6calleev{{.*}} { Task callee() { co_return 1; } // CHECK-LABEL: define{{.*}} @_Z8elidablev{{.*}} { Task 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 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 addTasks([[clang::coro_await_elidable_argument]] Task &&t1, Task &&t2) { int i1 = co_await t1; int i2 = co_await t2; co_return i1 + i2; } // CHECK-LABEL: define{{.*}} @_Z10returnSamei{{.*}} { Task returnSame(int i) { co_return i; } // CHECK-LABEL: define{{.*}} @_Z21elidableWithMustAwaitv{{.*}} { Task 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 Task sumAll([[clang::coro_await_elidable_argument]] Args && ... tasks); // CHECK-LABEL: define{{.*}} @_Z16elidableWithPackv{{.*}} { Task 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 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)); } // CHECK: attributes #[[ELIDE_SAFE]] = { coro_elide_safe }