summaryrefslogtreecommitdiff
path: root/test/CodeGenCoroutines
diff options
context:
space:
mode:
Diffstat (limited to 'test/CodeGenCoroutines')
-rw-r--r--test/CodeGenCoroutines/coro-alloc.cpp60
-rw-r--r--test/CodeGenCoroutines/coro-await.cpp230
-rw-r--r--test/CodeGenCoroutines/coro-builtins.c2
-rw-r--r--test/CodeGenCoroutines/coro-cleanup.cpp74
-rw-r--r--test/CodeGenCoroutines/coro-eh-cleanup.cpp74
-rw-r--r--test/CodeGenCoroutines/coro-return.cpp59
6 files changed, 489 insertions, 10 deletions
diff --git a/test/CodeGenCoroutines/coro-alloc.cpp b/test/CodeGenCoroutines/coro-alloc.cpp
index 7dbbdd31be085..f0a600eabe9a2 100644
--- a/test/CodeGenCoroutines/coro-alloc.cpp
+++ b/test/CodeGenCoroutines/coro-alloc.cpp
@@ -4,12 +4,27 @@ namespace std {
namespace experimental {
template <typename... T>
struct coroutine_traits; // expected-note {{declared here}}
+
+template <class Promise = void>
+struct coroutine_handle {
+ coroutine_handle() = default;
+ static coroutine_handle from_address(void *) { return {}; }
+};
+
+template <>
+struct coroutine_handle<void> {
+ static coroutine_handle from_address(void *) { return {}; }
+ coroutine_handle() = default;
+ template <class PromiseType>
+ coroutine_handle(coroutine_handle<PromiseType>) {}
+};
+
}
}
struct suspend_always {
bool await_ready() { return false; }
- void await_suspend() {}
+ void await_suspend(std::experimental::coroutine_handle<>) {}
void await_resume() {}
};
@@ -25,7 +40,7 @@ struct std::experimental::coroutine_traits<void, global_new_delete_tag> {
};
};
-// CHECK-LABEL: f0(
+// CHECK-LABEL: f0(
extern "C" void f0(global_new_delete_tag) {
// CHECK: %[[ID:.+]] = call token @llvm.coro.id(i32 16
// CHECK: %[[SIZE:.+]] = call i64 @llvm.coro.size.i64()
@@ -34,7 +49,7 @@ extern "C" void f0(global_new_delete_tag) {
// CHECK: %[[FRAME:.+]] = call i8* @llvm.coro.frame()
// CHECK: %[[MEM:.+]] = call i8* @llvm.coro.free(token %[[ID]], i8* %[[FRAME]])
// CHECK: call void @_ZdlPv(i8* %[[MEM]])
- co_await suspend_always{};
+ co_return;
}
struct promise_new_tag {};
@@ -50,7 +65,7 @@ struct std::experimental::coroutine_traits<void, promise_new_tag> {
};
};
-// CHECK-LABEL: f1(
+// CHECK-LABEL: f1(
extern "C" void f1(promise_new_tag ) {
// CHECK: %[[ID:.+]] = call token @llvm.coro.id(i32 16
// CHECK: %[[SIZE:.+]] = call i64 @llvm.coro.size.i64()
@@ -59,7 +74,7 @@ extern "C" void f1(promise_new_tag ) {
// CHECK: %[[FRAME:.+]] = call i8* @llvm.coro.frame()
// CHECK: %[[MEM:.+]] = call i8* @llvm.coro.free(token %[[ID]], i8* %[[FRAME]])
// CHECK: call void @_ZdlPv(i8* %[[MEM]])
- co_await suspend_always{};
+ co_return;
}
struct promise_delete_tag {};
@@ -75,7 +90,7 @@ struct std::experimental::coroutine_traits<void, promise_delete_tag> {
};
};
-// CHECK-LABEL: f2(
+// CHECK-LABEL: f2(
extern "C" void f2(promise_delete_tag) {
// CHECK: %[[ID:.+]] = call token @llvm.coro.id(i32 16
// CHECK: %[[SIZE:.+]] = call i64 @llvm.coro.size.i64()
@@ -84,7 +99,7 @@ extern "C" void f2(promise_delete_tag) {
// CHECK: %[[FRAME:.+]] = call i8* @llvm.coro.frame()
// CHECK: %[[MEM:.+]] = call i8* @llvm.coro.free(token %[[ID]], i8* %[[FRAME]])
// CHECK: call void @_ZNSt12experimental16coroutine_traitsIJv18promise_delete_tagEE12promise_typedlEPv(i8* %[[MEM]])
- co_await suspend_always{};
+ co_return;
}
struct promise_sized_delete_tag {};
@@ -100,7 +115,7 @@ struct std::experimental::coroutine_traits<void, promise_sized_delete_tag> {
};
};
-// CHECK-LABEL: f3(
+// CHECK-LABEL: f3(
extern "C" void f3(promise_sized_delete_tag) {
// CHECK: %[[ID:.+]] = call token @llvm.coro.id(i32 16
// CHECK: %[[SIZE:.+]] = call i64 @llvm.coro.size.i64()
@@ -110,5 +125,32 @@ extern "C" void f3(promise_sized_delete_tag) {
// CHECK: %[[MEM:.+]] = call i8* @llvm.coro.free(token %[[ID]], i8* %[[FRAME]])
// CHECK: %[[SIZE2:.+]] = call i64 @llvm.coro.size.i64()
// CHECK: call void @_ZNSt12experimental16coroutine_traitsIJv24promise_sized_delete_tagEE12promise_typedlEPvm(i8* %[[MEM]], i64 %[[SIZE2]])
- co_await suspend_always{};
+ co_return;
+}
+
+struct promise_on_alloc_failure_tag {};
+
+template<>
+struct std::experimental::coroutine_traits<int, promise_on_alloc_failure_tag> {
+ struct promise_type {
+ int get_return_object() {}
+ suspend_always initial_suspend() { return {}; }
+ suspend_always final_suspend() { return {}; }
+ void return_void() {}
+ static int get_return_object_on_allocation_failure() { return -1; }
+ };
+};
+
+// CHECK-LABEL: f4(
+extern "C" int f4(promise_on_alloc_failure_tag) {
+ // CHECK: %[[ID:.+]] = call token @llvm.coro.id(i32 16
+ // CHECK: %[[SIZE:.+]] = call i64 @llvm.coro.size.i64()
+ // CHECK: %[[MEM:.+]] = call i8* @_Znwm(i64 %[[SIZE]])
+ // CHECK: %[[OK:.+]] = icmp ne i8* %[[MEM]], null
+ // CHECK: br i1 %[[OK]], label %[[OKBB:.+]], label %[[ERRBB:.+]]
+
+ // CHECK: [[ERRBB]]:
+ // CHECK: %[[RETVAL:.+]] = call i32 @_ZNSt12experimental16coroutine_traitsIJi28promise_on_alloc_failure_tagEE12promise_type39get_return_object_on_allocation_failureEv(
+ // CHECK: ret i32 %[[RETVAL]]
+ co_return;
}
diff --git a/test/CodeGenCoroutines/coro-await.cpp b/test/CodeGenCoroutines/coro-await.cpp
new file mode 100644
index 0000000000000..b0fd0129de34a
--- /dev/null
+++ b/test/CodeGenCoroutines/coro-await.cpp
@@ -0,0 +1,230 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fcoroutines-ts -std=c++14 -emit-llvm %s -o - -disable-llvm-passes | FileCheck %s
+
+namespace std {
+namespace experimental {
+template <typename... T>
+struct coroutine_traits;
+
+template <typename Promise = void> struct coroutine_handle;
+
+template <>
+struct coroutine_handle<void> {
+ void *ptr;
+ static coroutine_handle from_address(void *);
+};
+
+template <typename Promise>
+struct coroutine_handle : coroutine_handle<> {
+ static coroutine_handle from_address(void *);
+};
+
+}
+}
+
+struct suspend_always {
+ int stuff;
+ bool await_ready();
+ void await_suspend(std::experimental::coroutine_handle<>);
+ void await_resume();
+};
+
+template<>
+struct std::experimental::coroutine_traits<void> {
+ struct promise_type {
+ void get_return_object();
+ suspend_always initial_suspend();
+ suspend_always final_suspend();
+ void return_void();
+ };
+};
+
+// CHECK-LABEL: f0(
+extern "C" void f0() {
+
+ co_await suspend_always{};
+ // See if we need to suspend:
+ // --------------------------
+ // CHECK: %[[READY:.+]] = call zeroext i1 @_ZN14suspend_always11await_readyEv(%struct.suspend_always* %[[AWAITABLE:.+]])
+ // CHECK: br i1 %[[READY]], label %[[READY_BB:.+]], label %[[SUSPEND_BB:.+]]
+
+ // If we are suspending:
+ // ---------------------
+ // CHECK: [[SUSPEND_BB]]:
+ // CHECK: %[[SUSPEND_ID:.+]] = call token @llvm.coro.save(
+ // ---------------------------
+ // Build the coroutine handle and pass it to await_suspend
+ // ---------------------------
+ // CHECK: %[[FRAME:.+]] = call i8* @llvm.coro.frame()
+ // CHECK: call i8* @_ZNSt12experimental16coroutine_handleINS_16coroutine_traitsIJvEE12promise_typeEE12from_addressEPv(i8* %[[FRAME]])
+ // ... many lines of code to coerce coroutine_handle into an i8* scalar
+ // CHECK: %[[CH:.+]] = load i8*, i8** %{{.+}}
+ // CHECK: call void @_ZN14suspend_always13await_suspendENSt12experimental16coroutine_handleIvEE(%struct.suspend_always* %[[AWAITABLE]], i8* %[[CH]])
+ // -------------------------
+ // Generate a suspend point:
+ // -------------------------
+ // CHECK: %[[OUTCOME:.+]] = call i8 @llvm.coro.suspend(token %[[SUSPEND_ID]], i1 false)
+ // CHECK: switch i8 %[[OUTCOME]], label %[[RET_BB:.+]] [
+ // CHECK: i8 0, label %[[READY_BB]]
+ // CHECK: i8 1, label %[[CLEANUP_BB:.+]]
+ // CHECK: ]
+
+ // Cleanup code goes here:
+ // -----------------------
+ // CHECK: [[CLEANUP_BB]]:
+
+ // When coroutine is resumed, call await_resume
+ // --------------------------
+ // CHECK: [[READY_BB]]:
+ // CHECK: call void @_ZN14suspend_always12await_resumeEv(%struct.suspend_always* %[[AWAITABLE]])
+}
+
+struct suspend_maybe {
+ float stuff;
+ ~suspend_maybe();
+ bool await_ready();
+ bool await_suspend(std::experimental::coroutine_handle<>);
+ void await_resume();
+};
+
+
+template<>
+struct std::experimental::coroutine_traits<void,int> {
+ struct promise_type {
+ void get_return_object();
+ suspend_always initial_suspend();
+ suspend_always final_suspend();
+ void return_void();
+ suspend_maybe yield_value(int);
+ };
+};
+
+// CHECK-LABEL: f1(
+extern "C" void f1(int) {
+ co_yield 42;
+ // CHECK: %[[PROMISE:.+]] = alloca %"struct.std::experimental::coroutine_traits<void, int>::promise_type"
+ // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJviEE12promise_type11yield_valueEi(%struct.suspend_maybe* sret %[[AWAITER:.+]], %"struct.std::experimental::coroutine_traits<void, int>::promise_type"* %[[PROMISE]], i32 42)
+
+ // See if we need to suspend:
+ // --------------------------
+ // CHECK: %[[READY:.+]] = call zeroext i1 @_ZN13suspend_maybe11await_readyEv(%struct.suspend_maybe* %[[AWAITABLE]])
+ // CHECK: br i1 %[[READY]], label %[[READY_BB:.+]], label %[[SUSPEND_BB:.+]]
+
+ // If we are suspending:
+ // ---------------------
+ // CHECK: [[SUSPEND_BB]]:
+ // CHECK: %[[SUSPEND_ID:.+]] = call token @llvm.coro.save(
+ // ---------------------------
+ // Build the coroutine handle and pass it to await_suspend
+ // ---------------------------
+ // CHECK: %[[FRAME:.+]] = call i8* @llvm.coro.frame()
+ // CHECK: call i8* @_ZNSt12experimental16coroutine_handleINS_16coroutine_traitsIJviEE12promise_typeEE12from_addressEPv(i8* %[[FRAME]])
+ // ... many lines of code to coerce coroutine_handle into an i8* scalar
+ // CHECK: %[[CH:.+]] = load i8*, i8** %{{.+}}
+ // CHECK: %[[YES:.+]] = call zeroext i1 @_ZN13suspend_maybe13await_suspendENSt12experimental16coroutine_handleIvEE(%struct.suspend_maybe* %[[AWAITABLE]], i8* %[[CH]])
+ // -------------------------------------------
+ // See if await_suspend decided not to suspend
+ // -------------------------------------------
+ // CHECK: br i1 %[[YES]], label %[[SUSPEND_PLEASE:.+]], label %[[READY_BB]]
+
+ // CHECK: [[SUSPEND_PLEASE]]:
+ // CHECK: call i8 @llvm.coro.suspend(token %[[SUSPEND_ID]], i1 false)
+
+ // CHECK: [[READY_BB]]:
+ // CHECK: call void @_ZN13suspend_maybe12await_resumeEv(%struct.suspend_maybe* %[[AWAITABLE]])
+}
+
+struct ComplexAwaiter {
+ template <typename F> void await_suspend(F);
+ bool await_ready();
+ _Complex float await_resume();
+};
+extern "C" void UseComplex(_Complex float);
+
+// CHECK-LABEL: @TestComplex(
+extern "C" void TestComplex() {
+ UseComplex(co_await ComplexAwaiter{});
+ // CHECK: call <2 x float> @_ZN14ComplexAwaiter12await_resumeEv(%struct.ComplexAwaiter*
+ // CHECK: call void @UseComplex(<2 x float> %{{.+}})
+
+ co_await ComplexAwaiter{};
+ // CHECK: call <2 x float> @_ZN14ComplexAwaiter12await_resumeEv(%struct.ComplexAwaiter*
+
+ _Complex float Val = co_await ComplexAwaiter{};
+ // CHECK: call <2 x float> @_ZN14ComplexAwaiter12await_resumeEv(%struct.ComplexAwaiter*
+}
+
+struct Aggr { int X, Y, Z; ~Aggr(); };
+struct AggrAwaiter {
+ template <typename F> void await_suspend(F);
+ bool await_ready();
+ Aggr await_resume();
+};
+
+extern "C" void Whatever();
+extern "C" void UseAggr(Aggr&&);
+
+// FIXME: Once the cleanup code is in, add testing that destructors for Aggr
+// are invoked properly on the cleanup branches.
+
+// CHECK-LABEL: @TestAggr(
+extern "C" void TestAggr() {
+ UseAggr(co_await AggrAwaiter{});
+ Whatever();
+ // CHECK: call void @_ZN11AggrAwaiter12await_resumeEv(%struct.Aggr* sret %[[AwaitResume:.+]],
+ // CHECK: call void @UseAggr(%struct.Aggr* dereferenceable(12) %[[AwaitResume]])
+ // CHECK: call void @_ZN4AggrD1Ev(%struct.Aggr* %[[AwaitResume]])
+ // CHECK: call void @Whatever()
+
+ co_await AggrAwaiter{};
+ Whatever();
+ // CHECK: call void @_ZN11AggrAwaiter12await_resumeEv(%struct.Aggr* sret %[[AwaitResume2:.+]],
+ // CHECK: call void @_ZN4AggrD1Ev(%struct.Aggr* %[[AwaitResume2]])
+ // CHECK: call void @Whatever()
+
+ Aggr Val = co_await AggrAwaiter{};
+ Whatever();
+ // CHECK: call void @_ZN11AggrAwaiter12await_resumeEv(%struct.Aggr* sret %[[AwaitResume3:.+]],
+ // CHECK: call void @Whatever()
+ // CHECK: call void @_ZN4AggrD1Ev(%struct.Aggr* %[[AwaitResume3]])
+}
+
+struct ScalarAwaiter {
+ template <typename F> void await_suspend(F);
+ bool await_ready();
+ int await_resume();
+};
+
+extern "C" void UseScalar(int);
+
+// CHECK-LABEL: @TestScalar(
+extern "C" void TestScalar() {
+ UseScalar(co_await ScalarAwaiter{});
+ // CHECK: %[[Result:.+]] = call i32 @_ZN13ScalarAwaiter12await_resumeEv(%struct.ScalarAwaiter*
+ // CHECK: call void @UseScalar(i32 %[[Result]])
+
+ int Val = co_await ScalarAwaiter{};
+ // CHECK: %[[Result2:.+]] = call i32 @_ZN13ScalarAwaiter12await_resumeEv(%struct.ScalarAwaiter*
+ // CHECK: store i32 %[[Result2]], i32* %Val
+
+ co_await ScalarAwaiter{};
+ // CHECK: call i32 @_ZN13ScalarAwaiter12await_resumeEv(%struct.ScalarAwaiter*
+}
+
+// Test operator co_await codegen.
+enum class MyInt: int {};
+ScalarAwaiter operator co_await(MyInt);
+
+struct MyAgg {
+ AggrAwaiter operator co_await();
+};
+
+// CHECK-LABEL: @TestOpAwait(
+extern "C" void TestOpAwait() {
+ co_await MyInt(42);
+ // CHECK: call void @_Zaw5MyInt(i32 42)
+ // CHECK: call i32 @_ZN13ScalarAwaiter12await_resumeEv(%struct.ScalarAwaiter* %
+
+ co_await MyAgg{};
+ // CHECK: call void @_ZN5MyAggawEv(%struct.MyAgg* %
+ // CHECK: call void @_ZN11AggrAwaiter12await_resumeEv(%struct.Aggr* sret %
+}
diff --git a/test/CodeGenCoroutines/coro-builtins.c b/test/CodeGenCoroutines/coro-builtins.c
index 5c3e6c3a6bdff..d18d5e730f911 100644
--- a/test/CodeGenCoroutines/coro-builtins.c
+++ b/test/CodeGenCoroutines/coro-builtins.c
@@ -43,7 +43,7 @@ void f(int n) {
__builtin_coro_free(__builtin_coro_frame());
// CHECK-NEXT: %[[FRAME6:.+]] = call i8* @llvm.coro.frame()
- // CHECK-NEXT: call void @llvm.coro.end(i8* %[[FRAME6]], i1 false)
+ // CHECK-NEXT: call i1 @llvm.coro.end(i8* %[[FRAME6]], i1 false)
__builtin_coro_end(__builtin_coro_frame(), 0);
// CHECK-NEXT: call i8 @llvm.coro.suspend(token none, i1 true)
diff --git a/test/CodeGenCoroutines/coro-cleanup.cpp b/test/CodeGenCoroutines/coro-cleanup.cpp
new file mode 100644
index 0000000000000..7f6f35cfe26c4
--- /dev/null
+++ b/test/CodeGenCoroutines/coro-cleanup.cpp
@@ -0,0 +1,74 @@
+// Verify that coroutine promise and allocated memory are freed up on exception.
+// RUN: %clang_cc1 -std=c++1z -fcoroutines-ts -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s -fexceptions -fcxx-exceptions -disable-llvm-passes | FileCheck %s
+
+namespace std::experimental {
+template <typename... T> struct coroutine_traits;
+
+template <class Promise = void> struct coroutine_handle {
+ coroutine_handle() = default;
+ static coroutine_handle from_address(void *) { return {}; }
+};
+template <> struct coroutine_handle<void> {
+ static coroutine_handle from_address(void *) { return {}; }
+ coroutine_handle() = default;
+ template <class PromiseType>
+ coroutine_handle(coroutine_handle<PromiseType>) {}
+};
+}
+
+struct suspend_always {
+ bool await_ready();
+ void await_suspend(std::experimental::coroutine_handle<>);
+ void await_resume();
+};
+
+template <> struct std::experimental::coroutine_traits<void> {
+ struct promise_type {
+ void get_return_object();
+ suspend_always initial_suspend();
+ suspend_always final_suspend();
+ void return_void();
+ promise_type();
+ ~promise_type();
+ void unhandled_exception();
+ };
+};
+
+struct Cleanup { ~Cleanup(); };
+void may_throw();
+
+// CHECK: define void @_Z1fv(
+void f() {
+ // CHECK: call i8* @_Znwm(i64
+
+ // If promise constructor throws, check that we free the memory.
+
+ // CHECK: invoke void @_ZNSt12experimental16coroutine_traitsIJvEE12promise_typeC1Ev(
+ // CHECK-NEXT: to label %{{.+}} unwind label %[[DeallocPad:.+]]
+
+ Cleanup cleanup;
+ may_throw();
+
+ // if may_throw throws, check that we destroy the promise and free the memory.
+
+ // CHECK: invoke void @_Z9may_throwv(
+ // CHECK-NEXT: to label %{{.+}} unwind label %[[PromDtorPad:.+]]
+
+ // CHECK: [[DeallocPad]]:
+ // CHECK-NEXT: landingpad
+ // CHECK-NEXT: cleanup
+ // CHECK: br label %[[Dealloc:.+]]
+
+ // CHECK: [[PromDtorPad]]:
+ // CHECK-NEXT: landingpad
+ // CHECK-NEXT: cleanup
+ // CHECK: call void @_ZN7CleanupD1Ev(%struct.Cleanup*
+ // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJvEE12promise_typeD1Ev(
+ // CHECK: br label %[[Dealloc]]
+
+ // CHECK: [[Dealloc]]:
+ // CHECK: %[[Mem:.+]] = call i8* @llvm.coro.free(
+ // CHECK: call void @_ZdlPv(i8* %[[Mem]])
+
+ co_return;
+}
diff --git a/test/CodeGenCoroutines/coro-eh-cleanup.cpp b/test/CodeGenCoroutines/coro-eh-cleanup.cpp
new file mode 100644
index 0000000000000..578d57fbab28a
--- /dev/null
+++ b/test/CodeGenCoroutines/coro-eh-cleanup.cpp
@@ -0,0 +1,74 @@
+// RUN: %clang_cc1 -std=c++1z -fcoroutines-ts -triple=x86_64-pc-windows-msvc18.0.0 -emit-llvm %s -o - -fexceptions -fcxx-exceptions -disable-llvm-passes | FileCheck %s
+// RUN: %clang_cc1 -std=c++1z -fcoroutines-ts -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s -fexceptions -fcxx-exceptions -disable-llvm-passes | FileCheck --check-prefix=CHECK-LPAD %s
+
+namespace std::experimental {
+template <typename R, typename... T> struct coroutine_traits {
+ using promise_type = typename R::promise_type;
+};
+
+template <class Promise = void> struct coroutine_handle;
+
+template <> struct coroutine_handle<void> {
+ static coroutine_handle from_address(void *) noexcept;
+ coroutine_handle() = default;
+ template <class PromiseType>
+ coroutine_handle(coroutine_handle<PromiseType>) noexcept;
+};
+template <class Promise> struct coroutine_handle: coroutine_handle<void> {
+ coroutine_handle() = default;
+ static coroutine_handle from_address(void *) noexcept;
+};
+}
+
+struct suspend_always {
+ bool await_ready() noexcept;
+ void await_suspend(std::experimental::coroutine_handle<>) noexcept;
+ void await_resume() noexcept;
+};
+
+struct coro_t {
+ struct promise_type {
+ coro_t get_return_object() noexcept;
+ suspend_always initial_suspend() noexcept;
+ suspend_always final_suspend() noexcept;
+ void return_void() noexcept;
+ void unhandled_exception() noexcept;
+ };
+};
+
+struct Cleanup { ~Cleanup(); };
+void may_throw();
+
+coro_t f() {
+ Cleanup x;
+ may_throw();
+ co_return;
+}
+
+// CHECK: @"\01?f@@YA?AUcoro_t@@XZ"(
+// CHECK: invoke void @"\01?may_throw@@YAXXZ"()
+// CHECK: to label %[[CONT:.+]] unwind label %[[EHCLEANUP:.+]]
+// CHECK: [[EHCLEANUP]]:
+// CHECK: %[[INNERPAD:.+]] = cleanuppad within none []
+// CHECK: call void @"\01??_DCleanup@@QEAAXXZ"(
+// CHECK: cleanupret from %[[INNERPAD]] unwind label %[[COROENDBB:.+]]
+// CHECK: [[COROENDBB]]:
+// CHECK-NEXT: %[[CLPAD:.+]] = cleanuppad within none
+// CHECK-NEXT: call i1 @llvm.coro.end(i8* null, i1 true) [ "funclet"(token %[[CLPAD]]) ]
+// CHECK-NEXT: cleanupret from %[[CLPAD]] unwind label
+
+// CHECK-LPAD: @_Z1fv(
+// CHECK-LPAD: invoke void @_Z9may_throwv()
+// CHECK-LPAD: to label %[[CONT:.+]] unwind label %[[EHCLEANUP:.+]]
+// CHECK-LPAD: [[EHCLEANUP]]:
+// CHECK-LPAD: landingpad { i8*, i32 }
+// CHECK-LPAD: cleanup
+// CHECK-LPAD: call void @_ZN7CleanupD1Ev(
+// CHECK-LPAD: %[[I1RESUME:.+]] = call i1 @llvm.coro.end(i8* null, i1 true)
+// CHECK-LPAD: br i1 %[[I1RESUME]], label %[[EHRESUME:.+]], label
+// CHECK-LPAD: [[EHRESUME]]:
+// CHECK-LPAD-NEXT: %[[exn:.+]] = load i8*, i8** %exn.slot, align 8
+// CHECK-LPAD-NEXT: %[[sel:.+]] = load i32, i32* %ehselector.slot, align 4
+// CHECK-LPAD-NEXT: %[[val1:.+]] = insertvalue { i8*, i32 } undef, i8* %[[exn]], 0
+// CHECK-LPAD-NEXT: %[[val2:.+]] = insertvalue { i8*, i32 } %[[val1]], i32 %[[sel]], 1
+// CHECK-LPAD-NEXT: resume { i8*, i32 } %[[val2]]
diff --git a/test/CodeGenCoroutines/coro-return.cpp b/test/CodeGenCoroutines/coro-return.cpp
new file mode 100644
index 0000000000000..7577725909d0f
--- /dev/null
+++ b/test/CodeGenCoroutines/coro-return.cpp
@@ -0,0 +1,59 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fcoroutines-ts -std=c++1z -emit-llvm %s -o - -disable-llvm-passes | FileCheck %s
+
+namespace std::experimental {
+template <typename... T> struct coroutine_traits;
+
+template <class Promise = void> struct coroutine_handle {
+ coroutine_handle() = default;
+ static coroutine_handle from_address(void *) { return {}; }
+};
+template <> struct coroutine_handle<void> {
+ static coroutine_handle from_address(void *) { return {}; }
+ coroutine_handle() = default;
+ template <class PromiseType>
+ coroutine_handle(coroutine_handle<PromiseType>) {}
+};
+}
+
+struct suspend_always {
+ bool await_ready();
+ void await_suspend(std::experimental::coroutine_handle<>);
+ void await_resume();
+};
+
+template <> struct std::experimental::coroutine_traits<void> {
+ struct promise_type {
+ void get_return_object();
+ suspend_always initial_suspend();
+ suspend_always final_suspend();
+ void return_void();
+ };
+};
+
+// CHECK-LABEL: f0(
+extern "C" void f0() {
+ // CHECK: %__promise = alloca %"struct.std::experimental::coroutine_traits<void>::promise_type"
+ // CHECK: %call = call i8* @_Znwm(
+ // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJvEE12promise_type11return_voidEv(%"struct.std::experimental::coroutine_traits<void>::promise_type"* %__promise)
+ // CHECK: call void @_ZdlPv
+ co_return;
+}
+
+template<>
+struct std::experimental::coroutine_traits<int> {
+ struct promise_type {
+ int get_return_object();
+ suspend_always initial_suspend();
+ suspend_always final_suspend();
+ void return_value(int);
+ };
+};
+
+// CHECK-LABEL: f1(
+extern "C" int f1() {
+ // CHECK: %__promise = alloca %"struct.std::experimental::coroutine_traits<int>::promise_type"
+ // CHECK: %call = call i8* @_Znwm(
+ // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJiEE12promise_type12return_valueEi(%"struct.std::experimental::coroutine_traits<int>::promise_type"* %__promise, i32 42)
+ // CHECK: call void @_ZdlPv
+ co_return 42;
+}