diff options
Diffstat (limited to 'test/CodeGenCoroutines')
-rw-r--r-- | test/CodeGenCoroutines/Inputs/coroutine.h | 80 | ||||
-rw-r--r-- | test/CodeGenCoroutines/coro-alloc.cpp | 40 | ||||
-rw-r--r-- | test/CodeGenCoroutines/coro-await.cpp | 64 | ||||
-rw-r--r-- | test/CodeGenCoroutines/coro-builtins.c | 22 | ||||
-rw-r--r-- | test/CodeGenCoroutines/coro-cleanup.cpp | 67 | ||||
-rw-r--r-- | test/CodeGenCoroutines/coro-eh-cleanup.cpp | 16 | ||||
-rw-r--r-- | test/CodeGenCoroutines/coro-gro.cpp | 86 | ||||
-rw-r--r-- | test/CodeGenCoroutines/coro-lambda.cpp | 58 | ||||
-rw-r--r-- | test/CodeGenCoroutines/coro-params.cpp | 95 | ||||
-rw-r--r-- | test/CodeGenCoroutines/coro-promise-dtor.cpp | 47 | ||||
-rw-r--r-- | test/CodeGenCoroutines/coro-ret-void.cpp | 39 | ||||
-rw-r--r-- | test/CodeGenCoroutines/coro-unhandled-exception.cpp | 72 |
12 files changed, 634 insertions, 52 deletions
diff --git a/test/CodeGenCoroutines/Inputs/coroutine.h b/test/CodeGenCoroutines/Inputs/coroutine.h new file mode 100644 index 0000000000000..d58212b1d5280 --- /dev/null +++ b/test/CodeGenCoroutines/Inputs/coroutine.h @@ -0,0 +1,80 @@ +#pragma once + +namespace std { namespace experimental { inline namespace coroutines_v1 { + +template <typename R, typename...> struct coroutine_traits { + using promise_type = typename R::promise_type; +}; + +template <typename Promise = void> struct coroutine_handle; + +template <> struct coroutine_handle<void> { + static coroutine_handle from_address(void *addr) noexcept { + coroutine_handle me; + me.ptr = addr; + return me; + } + void operator()() { resume(); } + void *address() const { return ptr; } + void resume() const { __builtin_coro_resume(ptr); } + void destroy() const { __builtin_coro_destroy(ptr); } + bool done() const { return __builtin_coro_done(ptr); } + coroutine_handle &operator=(decltype(nullptr)) { + ptr = nullptr; + return *this; + } + coroutine_handle(decltype(nullptr)) : ptr(nullptr) {} + coroutine_handle() : ptr(nullptr) {} +// void reset() { ptr = nullptr; } // add to P0057? + explicit operator bool() const { return ptr; } + +protected: + void *ptr; +}; + +template <typename Promise> struct coroutine_handle : coroutine_handle<> { + using coroutine_handle<>::operator=; + + static coroutine_handle from_address(void *addr) noexcept { + coroutine_handle me; + me.ptr = addr; + return me; + } + + Promise &promise() const { + return *reinterpret_cast<Promise *>( + __builtin_coro_promise(ptr, alignof(Promise), false)); + } + static coroutine_handle from_promise(Promise &promise) { + coroutine_handle p; + p.ptr = __builtin_coro_promise(&promise, alignof(Promise), true); + return p; + } +}; + + template <typename _PromiseT> + bool operator==(coroutine_handle<_PromiseT> const& _Left, + coroutine_handle<_PromiseT> const& _Right) noexcept + { + return _Left.address() == _Right.address(); + } + + template <typename _PromiseT> + bool operator!=(coroutine_handle<_PromiseT> const& _Left, + coroutine_handle<_PromiseT> const& _Right) noexcept + { + return !(_Left == _Right); + } + +struct suspend_always { + bool await_ready() { return false; } + void await_suspend(coroutine_handle<>) {} + void await_resume() {} +}; +struct suspend_never { + bool await_ready() { return true; } + void await_suspend(coroutine_handle<>) {} + void await_resume() {} +}; + +}}} diff --git a/test/CodeGenCoroutines/coro-alloc.cpp b/test/CodeGenCoroutines/coro-alloc.cpp index 3a65736f6c03a..820201db357fd 100644 --- a/test/CodeGenCoroutines/coro-alloc.cpp +++ b/test/CodeGenCoroutines/coro-alloc.cpp @@ -56,12 +56,28 @@ struct std::experimental::coroutine_traits<void, global_new_delete_tag> { // CHECK-LABEL: f0( extern "C" void f0(global_new_delete_tag) { // CHECK: %[[ID:.+]] = call token @llvm.coro.id(i32 16 + // CHECK: %[[NeedAlloc:.+]] = call i1 @llvm.coro.alloc(token %[[ID]]) + // CHECK: br i1 %[[NeedAlloc]], label %[[AllocBB:.+]], label %[[InitBB:.+]] + + // CHECK: [[AllocBB]]: // CHECK: %[[SIZE:.+]] = call i64 @llvm.coro.size.i64() - // CHECK: call i8* @_Znwm(i64 %[[SIZE]]) + // CHECK: %[[MEM:.+]] = call i8* @_Znwm(i64 %[[SIZE]]) + // CHECK: br label %[[InitBB]] + + // CHECK: [[InitBB]]: + // CHECK: %[[PHI:.+]] = phi i8* [ null, %{{.+}} ], [ %call, %[[AllocBB]] ] + // CHECK: %[[FRAME:.+]] = call i8* @llvm.coro.begin(token %[[ID]], i8* %[[PHI]]) - // CHECK: %[[FRAME:.+]] = call i8* @llvm.coro.frame() // CHECK: %[[MEM:.+]] = call i8* @llvm.coro.free(token %[[ID]], i8* %[[FRAME]]) + // CHECK: %[[NeedDealloc:.+]] = icmp ne i8* %[[MEM]], null + // CHECK: br i1 %[[NeedDealloc]], label %[[FreeBB:.+]], label %[[Afterwards:.+]] + + // CHECK: [[FreeBB]]: // CHECK: call void @_ZdlPv(i8* %[[MEM]]) + // CHECK: br label %[[Afterwards]] + + // CHECK: [[Afterwards]]: + // CHECK: ret void co_return; } @@ -84,7 +100,7 @@ extern "C" void f1(promise_new_tag ) { // CHECK: %[[SIZE:.+]] = call i64 @llvm.coro.size.i64() // CHECK: call i8* @_ZNSt12experimental16coroutine_traitsIJv15promise_new_tagEE12promise_typenwEm(i64 %[[SIZE]]) - // CHECK: %[[FRAME:.+]] = call i8* @llvm.coro.frame() + // CHECK: %[[FRAME:.+]] = call i8* @llvm.coro.begin( // CHECK: %[[MEM:.+]] = call i8* @llvm.coro.free(token %[[ID]], i8* %[[FRAME]]) // CHECK: call void @_ZdlPv(i8* %[[MEM]]) co_return; @@ -109,7 +125,7 @@ extern "C" void f2(promise_delete_tag) { // CHECK: %[[SIZE:.+]] = call i64 @llvm.coro.size.i64() // CHECK: call i8* @_Znwm(i64 %[[SIZE]]) - // CHECK: %[[FRAME:.+]] = call i8* @llvm.coro.frame() + // CHECK: %[[FRAME:.+]] = call i8* @llvm.coro.begin( // CHECK: %[[MEM:.+]] = call i8* @llvm.coro.free(token %[[ID]], i8* %[[FRAME]]) // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJv18promise_delete_tagEE12promise_typedlEPv(i8* %[[MEM]]) co_return; @@ -134,7 +150,7 @@ extern "C" void f3(promise_sized_delete_tag) { // CHECK: %[[SIZE:.+]] = call i64 @llvm.coro.size.i64() // CHECK: call i8* @_Znwm(i64 %[[SIZE]]) - // CHECK: %[[FRAME:.+]] = call i8* @llvm.coro.frame() + // CHECK: %[[FRAME:.+]] = call i8* @llvm.coro.begin( // 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]]) @@ -156,6 +172,7 @@ struct std::experimental::coroutine_traits<int, promise_on_alloc_failure_tag> { // CHECK-LABEL: f4( extern "C" int f4(promise_on_alloc_failure_tag) { + // CHECK: %[[RetVal:.+]] = alloca i32 // CHECK: %[[ID:.+]] = call token @llvm.coro.id(i32 16 // CHECK: %[[SIZE:.+]] = call i64 @llvm.coro.size.i64() // CHECK: %[[MEM:.+]] = call i8* @_ZnwmRKSt9nothrow_t(i64 %[[SIZE]], %"struct.std::nothrow_t"* dereferenceable(1) @_ZStL7nothrow) @@ -163,7 +180,16 @@ extern "C" int f4(promise_on_alloc_failure_tag) { // 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]] + // CHECK: %[[FailRet:.+]] = call i32 @_ZNSt12experimental16coroutine_traitsIJi28promise_on_alloc_failure_tagEE12promise_type39get_return_object_on_allocation_failureEv( + // CHECK: store i32 %[[FailRet]], i32* %[[RetVal]] + // CHECK: br label %[[RetBB:.+]] + + // CHECK: [[OKBB]]: + // CHECK: %[[OkRet:.+]] = call i32 @_ZNSt12experimental16coroutine_traitsIJi28promise_on_alloc_failure_tagEE12promise_type17get_return_objectEv( + // CHECK: store i32 %[[OkRet]], i32* %[[RetVal]] + + // CHECK: [[RetBB]]: + // CHECK: %[[LoadRet:.+]] = load i32, i32* %[[RetVal]], align 4 + // CHECK: ret i32 %[[LoadRet]] co_return; } diff --git a/test/CodeGenCoroutines/coro-await.cpp b/test/CodeGenCoroutines/coro-await.cpp index b0fd0129de34a..1e2deaa8f59a7 100644 --- a/test/CodeGenCoroutines/coro-await.cpp +++ b/test/CodeGenCoroutines/coro-await.cpp @@ -21,6 +21,17 @@ struct coroutine_handle : coroutine_handle<> { } } +struct init_susp { + bool await_ready(); + void await_suspend(std::experimental::coroutine_handle<>); + void await_resume(); +}; +struct final_susp { + bool await_ready(); + void await_suspend(std::experimental::coroutine_handle<>); + void await_resume(); +}; + struct suspend_always { int stuff; bool await_ready(); @@ -32,14 +43,22 @@ template<> struct std::experimental::coroutine_traits<void> { struct promise_type { void get_return_object(); - suspend_always initial_suspend(); - suspend_always final_suspend(); + init_susp initial_suspend(); + final_susp final_suspend(); void return_void(); }; }; // CHECK-LABEL: f0( extern "C" void f0() { + // CHECK: %[[FRAME:.+]] = call i8* @llvm.coro.begin( + + // See if initial_suspend was issued: + // ---------------------------------- + // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJvEE12promise_type15initial_suspendEv( + // CHECK-NEXT: call zeroext i1 @_ZN9init_susp11await_readyEv(%struct.init_susp* + // CHECK: %[[INITSP_ID:.+]] = call token @llvm.coro.save( + // CHECK: call i8 @llvm.coro.suspend(token %[[INITSP_ID]], i1 false) co_await suspend_always{}; // See if we need to suspend: @@ -54,7 +73,6 @@ extern "C" void f0() { // --------------------------- // 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** %{{.+}} @@ -76,6 +94,13 @@ extern "C" void f0() { // -------------------------- // CHECK: [[READY_BB]]: // CHECK: call void @_ZN14suspend_always12await_resumeEv(%struct.suspend_always* %[[AWAITABLE]]) + + // See if final_suspend was issued: + // ---------------------------------- + // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJvEE12promise_type13final_suspendEv( + // CHECK-NEXT: call zeroext i1 @_ZN10final_susp11await_readyEv(%struct.final_susp* + // CHECK: %[[FINALSP_ID:.+]] = call token @llvm.coro.save( + // CHECK: call i8 @llvm.coro.suspend(token %[[FINALSP_ID]], i1 true) } struct suspend_maybe { @@ -91,8 +116,8 @@ template<> struct std::experimental::coroutine_traits<void,int> { struct promise_type { void get_return_object(); - suspend_always initial_suspend(); - suspend_always final_suspend(); + init_susp initial_suspend(); + final_susp final_suspend(); void return_void(); suspend_maybe yield_value(int); }; @@ -100,8 +125,9 @@ struct std::experimental::coroutine_traits<void,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: %[[FRAME:.+]] = call i8* @llvm.coro.begin( + co_yield 42; // 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: @@ -116,7 +142,6 @@ extern "C" void f1(int) { // --------------------------- // 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** %{{.+}} @@ -228,3 +253,28 @@ extern "C" void TestOpAwait() { // CHECK: call void @_ZN5MyAggawEv(%struct.MyAgg* % // CHECK: call void @_ZN11AggrAwaiter12await_resumeEv(%struct.Aggr* sret % } + +// CHECK-LABEL: EndlessLoop( +extern "C" void EndlessLoop() { + // CHECK: %[[FRAME:.+]] = call i8* @llvm.coro.begin( + + // See if initial_suspend was issued: + // ---------------------------------- + // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJvEE12promise_type15initial_suspendEv( + // CHECK-NEXT: call zeroext i1 @_ZN9init_susp11await_readyEv(%struct.init_susp* + + for (;;) + co_await suspend_always{}; + + // Verify that final_suspend was NOT issued: + // ---------------------------------- + // CHECK-NOT: call void @_ZNSt12experimental16coroutine_traitsIJvEE12promise_type13final_suspendEv( + // CHECK-NOT: call zeroext i1 @_ZN10final_susp11await_readyEv(%struct.final_susp* +} + +// Verifies that we don't crash when awaiting on an lvalue. +// CHECK-LABEL: @_Z11AwaitLValuev( +void AwaitLValue() { + suspend_always lval; + co_await lval; +} diff --git a/test/CodeGenCoroutines/coro-builtins.c b/test/CodeGenCoroutines/coro-builtins.c index d18d5e730f911..9ec2147642588 100644 --- a/test/CodeGenCoroutines/coro-builtins.c +++ b/test/CodeGenCoroutines/coro-builtins.c @@ -2,7 +2,7 @@ void *myAlloc(long long); -// CHECK-LABEL: f( +// CHECK-LABEL: f( void f(int n) { // CHECK: %n.addr = alloca i32 // CHECK: %n_copy = alloca i32 @@ -19,31 +19,25 @@ void f(int n) { // CHECK-NEXT: %[[SIZE:.+]] = call i64 @llvm.coro.size.i64() // CHECK-NEXT: %[[MEM:.+]] = call i8* @myAlloc(i64 %[[SIZE]]) - // CHECK-NEXT: %[[BEG:.+]] = call i8* @llvm.coro.begin(token %[[COROID]], i8* %[[MEM]]) + // CHECK-NEXT: %[[FRAME:.+]] = call i8* @llvm.coro.begin(token %[[COROID]], i8* %[[MEM]]) __builtin_coro_begin(myAlloc(__builtin_coro_size())); - // CHECK-NEXT: %[[FRAME1:.+]] = call i8* @llvm.coro.frame() - // CHECK-NEXT: call void @llvm.coro.resume(i8* %[[FRAME1]]) + // CHECK-NEXT: call void @llvm.coro.resume(i8* %[[FRAME]]) __builtin_coro_resume(__builtin_coro_frame()); - // CHECK-NEXT: %[[FRAME2:.+]] = call i8* @llvm.coro.frame() - // CHECK-NEXT: call void @llvm.coro.destroy(i8* %[[FRAME2]]) + // CHECK-NEXT: call void @llvm.coro.destroy(i8* %[[FRAME]]) __builtin_coro_destroy(__builtin_coro_frame()); - // CHECK-NEXT: %[[FRAME3:.+]] = call i8* @llvm.coro.frame() - // CHECK-NEXT: call i1 @llvm.coro.done(i8* %[[FRAME3]]) + // CHECK-NEXT: call i1 @llvm.coro.done(i8* %[[FRAME]]) __builtin_coro_done(__builtin_coro_frame()); - // CHECK-NEXT: %[[FRAME4:.+]] = call i8* @llvm.coro.frame() - // CHECK-NEXT: call i8* @llvm.coro.promise(i8* %[[FRAME4]], i32 48, i1 false) + // CHECK-NEXT: call i8* @llvm.coro.promise(i8* %[[FRAME]], i32 48, i1 false) __builtin_coro_promise(__builtin_coro_frame(), 48, 0); - // CHECK-NEXT: %[[FRAME5:.+]] = call i8* @llvm.coro.frame() - // CHECK-NEXT: call i8* @llvm.coro.free(token %[[COROID]], i8* %[[FRAME5]]) + // CHECK-NEXT: call i8* @llvm.coro.free(token %[[COROID]], i8* %[[FRAME]]) __builtin_coro_free(__builtin_coro_frame()); - // CHECK-NEXT: %[[FRAME6:.+]] = call i8* @llvm.coro.frame() - // CHECK-NEXT: call i1 @llvm.coro.end(i8* %[[FRAME6]], i1 false) + // CHECK-NEXT: call i1 @llvm.coro.end(i8* %[[FRAME]], 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 index 7f6f35cfe26c4..ba1246979e522 100644 --- a/test/CodeGenCoroutines/coro-cleanup.cpp +++ b/test/CodeGenCoroutines/coro-cleanup.cpp @@ -6,38 +6,38 @@ template <typename... T> struct coroutine_traits; template <class Promise = void> struct coroutine_handle { coroutine_handle() = default; - static coroutine_handle from_address(void *) { return {}; } + static coroutine_handle from_address(void *) noexcept; }; template <> struct coroutine_handle<void> { - static coroutine_handle from_address(void *) { return {}; } + static coroutine_handle from_address(void *) noexcept; coroutine_handle() = default; template <class PromiseType> - coroutine_handle(coroutine_handle<PromiseType>) {} + coroutine_handle(coroutine_handle<PromiseType>) noexcept; }; } struct suspend_always { - bool await_ready(); - void await_suspend(std::experimental::coroutine_handle<>); - void await_resume(); + bool await_ready() noexcept; + void await_suspend(std::experimental::coroutine_handle<>) noexcept; + void await_resume() noexcept; }; 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(); + void get_return_object() noexcept; + suspend_always initial_suspend() noexcept; + suspend_always final_suspend() noexcept; + void return_void() noexcept; promise_type(); ~promise_type(); - void unhandled_exception(); + void unhandled_exception() noexcept; }; }; struct Cleanup { ~Cleanup(); }; void may_throw(); -// CHECK: define void @_Z1fv( +// CHECK-LABEL: define void @_Z1fv( void f() { // CHECK: call i8* @_Znwm(i64 @@ -46,25 +46,40 @@ void f() { // CHECK: invoke void @_ZNSt12experimental16coroutine_traitsIJvEE12promise_typeC1Ev( // CHECK-NEXT: to label %{{.+}} unwind label %[[DeallocPad:.+]] + // CHECK: [[DeallocPad]]: + // CHECK-NEXT: landingpad + // CHECK-NEXT: cleanup + // CHECK: br label %[[Dealloc:.+]] + 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-NEXT: to label %{{.+}} unwind label %[[CatchPad:.+]] - // CHECK: [[DeallocPad]]: - // CHECK-NEXT: landingpad - // CHECK-NEXT: cleanup - // CHECK: br label %[[Dealloc:.+]] + // CHECK: [[CatchPad]]: + // CHECK-NEXT: landingpad + // CHECK-NEXT: catch i8* null + // CHECK: call void @_ZN7CleanupD1Ev( + // CHECK: br label %[[Catch:.+]] - // CHECK: [[PromDtorPad]]: - // CHECK-NEXT: landingpad - // CHECK-NEXT: cleanup - // CHECK: call void @_ZN7CleanupD1Ev(%struct.Cleanup* + // CHECK: [[Catch]]: + // CHECK: call i8* @__cxa_begin_catch( + // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJvEE12promise_type19unhandled_exceptionEv( + // CHECK: invoke void @__cxa_end_catch() + // CHECK-NEXT: to label %[[Cont:.+]] unwind + + // CHECK: [[Cont]]: + // CHECK-NEXT: br label %[[Cont2:.+]] + // CHECK: [[Cont2]]: + // CHECK-NEXT: br label %[[Cleanup:.+]] + + // CHECK: [[Cleanup]]: // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJvEE12promise_typeD1Ev( - // CHECK: br label %[[Dealloc]] + // CHECK: %[[Mem0:.+]] = call i8* @llvm.coro.free( + // CHECK: call void @_ZdlPv(i8* %[[Mem0]] // CHECK: [[Dealloc]]: // CHECK: %[[Mem:.+]] = call i8* @llvm.coro.free( @@ -72,3 +87,11 @@ void f() { co_return; } + +// CHECK-LABEL: define void @_Z1gv( +void g() { + for (;;) + co_await suspend_always{}; + // Since this is the endless loop there should be no fallthrough handler (call to 'return_void'). + // CHECK-NOT: call void @_ZNSt12experimental16coroutine_traitsIJvEE12promise_type11return_voidEv +} diff --git a/test/CodeGenCoroutines/coro-eh-cleanup.cpp b/test/CodeGenCoroutines/coro-eh-cleanup.cpp index 578d57fbab28a..5a3ecd1da4949 100644 --- a/test/CodeGenCoroutines/coro-eh-cleanup.cpp +++ b/test/CodeGenCoroutines/coro-eh-cleanup.cpp @@ -51,7 +51,13 @@ coro_t f() { // CHECK: [[EHCLEANUP]]: // CHECK: %[[INNERPAD:.+]] = cleanuppad within none [] // CHECK: call void @"\01??_DCleanup@@QEAAXXZ"( -// CHECK: cleanupret from %[[INNERPAD]] unwind label %[[COROENDBB:.+]] +// CHECK: cleanupret from %{{.+}} unwind label %[[CATCHDISPATCH:.+]] + +// CHECK: [[CATCHDISPATCH]]: +// CHECK: catchswitch within none [label %[[CATCHPAD:.+]]] unwind label %[[COROENDBB:.+]] +// CHECK: [[CATCHPAD]]: +// CHECK: call void @"\01?unhandled_exception@promise_type@coro_t@@QEAAXXZ" + // CHECK: [[COROENDBB]]: // CHECK-NEXT: %[[CLPAD:.+]] = cleanuppad within none // CHECK-NEXT: call i1 @llvm.coro.end(i8* null, i1 true) [ "funclet"(token %[[CLPAD]]) ] @@ -62,8 +68,14 @@ coro_t f() { // CHECK-LPAD: to label %[[CONT:.+]] unwind label %[[EHCLEANUP:.+]] // CHECK-LPAD: [[EHCLEANUP]]: // CHECK-LPAD: landingpad { i8*, i32 } -// CHECK-LPAD: cleanup +// CHECK-LPAD: catch // CHECK-LPAD: call void @_ZN7CleanupD1Ev( +// CHECK-LPAD: call i8* @__cxa_begin_catch +// CHECK-LPAD: call void @_ZN6coro_t12promise_type19unhandled_exceptionEv +// CHECK-LPAD: invoke void @__cxa_end_catch() +// CHECK-LPAD: to label %{{.+}} unwind label %[[UNWINDBB:.+]] + +// CHECK-LPAD: [[UNWINDBB]]: // CHECK-LPAD: %[[I1RESUME:.+]] = call i1 @llvm.coro.end(i8* null, i1 true) // CHECK-LPAD: br i1 %[[I1RESUME]], label %[[EHRESUME:.+]], label // CHECK-LPAD: [[EHRESUME]]: diff --git a/test/CodeGenCoroutines/coro-gro.cpp b/test/CodeGenCoroutines/coro-gro.cpp new file mode 100644 index 0000000000000..95bf2069722a2 --- /dev/null +++ b/test/CodeGenCoroutines/coro-gro.cpp @@ -0,0 +1,86 @@ +// Verifies lifetime of __gro local variable +// 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 -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 *) noexcept; +}; +template <> struct coroutine_handle<void> { + static coroutine_handle from_address(void *) noexcept; + coroutine_handle() = default; + template <class PromiseType> + coroutine_handle(coroutine_handle<PromiseType>) noexcept; +}; +} + +struct suspend_always { + bool await_ready() noexcept; + void await_suspend(std::experimental::coroutine_handle<>) noexcept; + void await_resume() noexcept; +}; + +struct GroType { + ~GroType(); + operator int() noexcept; +}; + +template <> struct std::experimental::coroutine_traits<int> { + struct promise_type { + GroType get_return_object() noexcept; + suspend_always initial_suspend() noexcept; + suspend_always final_suspend() noexcept; + void return_void() noexcept; + promise_type(); + ~promise_type(); + void unhandled_exception() noexcept; + }; +}; + +struct Cleanup { ~Cleanup(); }; +void doSomething() noexcept; + +// CHECK: define i32 @_Z1fv( +int f() { + // CHECK: %[[RetVal:.+]] = alloca i32 + // CHECK: %[[GroActive:.+]] = alloca i1 + + // CHECK: %[[Size:.+]] = call i64 @llvm.coro.size.i64() + // CHECK: call i8* @_Znwm(i64 %[[Size]]) + // CHECK: store i1 false, i1* %[[GroActive]] + // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJiEE12promise_typeC1Ev( + // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJiEE12promise_type17get_return_objectEv( + // CHECK: store i1 true, i1* %[[GroActive]] + + Cleanup cleanup; + doSomething(); + co_return; + + // CHECK: call void @_Z11doSomethingv( + // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJiEE12promise_type11return_voidEv( + // CHECK: call void @_ZN7CleanupD1Ev( + + // Destroy promise and free the memory. + + // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJiEE12promise_typeD1Ev( + // CHECK: %[[Mem:.+]] = call i8* @llvm.coro.free( + // CHECK: call void @_ZdlPv(i8* %[[Mem]]) + + // Initialize retval from Gro and destroy Gro + + // CHECK: %[[Conv:.+]] = call i32 @_ZN7GroTypecviEv( + // CHECK: store i32 %[[Conv]], i32* %[[RetVal]] + // CHECK: %[[IsActive:.+]] = load i1, i1* %[[GroActive]] + // CHECK: br i1 %[[IsActive]], label %[[CleanupGro:.+]], label %[[Done:.+]] + + // CHECK: [[CleanupGro]]: + // CHECK: call void @_ZN7GroTypeD1Ev( + // CHECK: br label %[[Done]] + + // CHECK: [[Done]]: + // CHECK: %[[LoadRet:.+]] = load i32, i32* %[[RetVal]] + // CHECK: ret i32 %[[LoadRet]] +} diff --git a/test/CodeGenCoroutines/coro-lambda.cpp b/test/CodeGenCoroutines/coro-lambda.cpp new file mode 100644 index 0000000000000..cd3256dc07eff --- /dev/null +++ b/test/CodeGenCoroutines/coro-lambda.cpp @@ -0,0 +1,58 @@ +// Verify that we synthesized the coroutine for a lambda inside of a function template. +// 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 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 Task { + struct promise_type { + Task get_return_object(); + void return_void() {} + suspend_always initial_suspend() noexcept; + suspend_always final_suspend() noexcept; + void unhandled_exception() noexcept; + }; +}; + +template <typename _AwrT> auto SyncAwait(_AwrT &&A) { + if (!A.await_ready()) { + auto AwaitAsync = [&]() -> Task { + try { (void)(co_await A); } catch (...) {} + }; + Task t = AwaitAsync(); + } + return A.await_resume(); +} + +void f() { + suspend_always test; + SyncAwait(test); +} + +// Verify that we synthesized the coroutine for a lambda inside SyncAwait +// CHECK-LABEL: define linkonce_odr void @_ZZ9SyncAwaitIR14suspend_alwaysEDaOT_ENKUlvE_clEv( +// CHECK: alloca %"struct.Task::promise_type" +// CHECK: call token @llvm.coro.id( +// CHECK: call i8 @llvm.coro.suspend( +// CHECK: call i1 @llvm.coro.end( diff --git a/test/CodeGenCoroutines/coro-params.cpp b/test/CodeGenCoroutines/coro-params.cpp new file mode 100644 index 0000000000000..c88e503a651c0 --- /dev/null +++ b/test/CodeGenCoroutines/coro-params.cpp @@ -0,0 +1,95 @@ +// Verifies that parameters are copied with move constructors +// Verifies that parameter copies are destroyed +// Vefifies that parameter copies are used in the body of the coroutine +// RUN: %clang_cc1 -std=c++1z -fcoroutines-ts -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s -disable-llvm-passes -fexceptions | 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 *) noexcept; +}; +template <> struct coroutine_handle<void> { + static coroutine_handle from_address(void *) noexcept; + coroutine_handle() = default; + template <class PromiseType> + coroutine_handle(coroutine_handle<PromiseType>) noexcept; +}; +} + +struct suspend_always { + bool await_ready() noexcept; + void await_suspend(std::experimental::coroutine_handle<>) noexcept; + void await_resume() noexcept; +}; + +template <typename... Args> struct std::experimental::coroutine_traits<void, Args...> { + struct promise_type { + void get_return_object() noexcept; + suspend_always initial_suspend() noexcept; + suspend_always final_suspend() noexcept; + void return_void() noexcept; + promise_type(); + ~promise_type() noexcept; + void unhandled_exception() noexcept; + }; +}; + +// TODO: Not supported yet +struct CopyOnly { + int val; + CopyOnly(const CopyOnly&) noexcept; + CopyOnly(CopyOnly&&) = delete; + ~CopyOnly(); +}; + +struct MoveOnly { + int val; + MoveOnly(const MoveOnly&) = delete; + MoveOnly(MoveOnly&&) noexcept; + ~MoveOnly(); +}; + +struct MoveAndCopy { + int val; + MoveAndCopy(const MoveAndCopy&)noexcept; + MoveAndCopy(MoveAndCopy&&) noexcept; + ~MoveAndCopy(); +}; + +void consume(int,int,int) noexcept; + +// TODO: Add support for CopyOnly params +// CHECK: define void @_Z1fi8MoveOnly11MoveAndCopy(i32 %val, %struct.MoveOnly* %[[MoParam:.+]], %struct.MoveAndCopy* %[[McParam:.+]]) #0 personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8* +void f(int val, MoveOnly moParam, MoveAndCopy mcParam) { + // CHECK: %[[MoCopy:.+]] = alloca %struct.MoveOnly + // CHECK: %[[McCopy:.+]] = alloca %struct.MoveAndCopy + // CHECK: store i32 %val, i32* %[[ValAddr:.+]] + + // CHECK: call i8* @llvm.coro.begin( + // CHECK-NEXT: call void @_ZN8MoveOnlyC1EOS_(%struct.MoveOnly* %[[MoCopy]], %struct.MoveOnly* dereferenceable(4) %[[MoParam]]) + // CHECK-NEXT: call void @_ZN11MoveAndCopyC1EOS_(%struct.MoveAndCopy* %[[McCopy]], %struct.MoveAndCopy* dereferenceable(4) %[[McParam]]) # + // CHECK-NEXT: invoke void @_ZNSt12experimental16coroutine_traitsIJvi8MoveOnly11MoveAndCopyEE12promise_typeC1Ev( + + // CHECK: call void @_ZN14suspend_always12await_resumeEv( + // CHECK: %[[IntParam:.+]] = load i32, i32* %val.addr + // CHECK: %[[MoGep:.+]] = getelementptr inbounds %struct.MoveOnly, %struct.MoveOnly* %[[MoCopy]], i32 0, i32 0 + // CHECK: %[[MoVal:.+]] = load i32, i32* %[[MoGep]] + // CHECK: %[[McGep:.+]] = getelementptr inbounds %struct.MoveAndCopy, %struct.MoveAndCopy* %[[McCopy]], i32 0, i32 0 + // CHECK: %[[McVal:.+]] = load i32, i32* %[[McGep]] + // CHECK: call void @_Z7consumeiii(i32 %[[IntParam]], i32 %[[MoVal]], i32 %[[McVal]]) + + consume(val, moParam.val, mcParam.val); + co_return; + + // Skip to final suspend: + // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJvi8MoveOnly11MoveAndCopyEE12promise_type13final_suspendEv( + // CHECK: call void @_ZN14suspend_always12await_resumeEv( + + // Destroy promise, then parameter copies: + // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJvi8MoveOnly11MoveAndCopyEE12promise_typeD1Ev(%"struct.std::experimental::coroutine_traits<void, int, MoveOnly, MoveAndCopy>::promise_type"* %__promise) #2 + // CHECK-NEXT: call void @_ZN11MoveAndCopyD1Ev(%struct.MoveAndCopy* %[[McCopy]]) + // CHECK-NEXT: call void @_ZN8MoveOnlyD1Ev(%struct.MoveOnly* %[[MoCopy]] + // CHECK-NEXT: call i8* @llvm.coro.free( +} diff --git a/test/CodeGenCoroutines/coro-promise-dtor.cpp b/test/CodeGenCoroutines/coro-promise-dtor.cpp new file mode 100644 index 0000000000000..4142cebe6132f --- /dev/null +++ b/test/CodeGenCoroutines/coro-promise-dtor.cpp @@ -0,0 +1,47 @@ +// RUN: %clang_cc1 -std=c++14 -fcoroutines-ts -triple=x86_64-pc-windows-msvc18.0.0 -emit-llvm -o - %s -fexceptions -fcxx-exceptions -disable-llvm-passes | FileCheck %s +// -triple=x86_64-unknown-linux-gnu + +#include "Inputs/coroutine.h" + +namespace coro = std::experimental::coroutines_v1; + +struct coro_t { + void* p; + ~coro_t(); + struct promise_type { + coro_t get_return_object(); + coro::suspend_never initial_suspend(); + coro::suspend_never final_suspend(); + void return_void(); + promise_type(); + ~promise_type(); + void unhandled_exception(); + }; +}; + +struct Cleanup { ~Cleanup(); }; +void may_throw(); + +coro_t f() { + Cleanup cleanup; + may_throw(); + co_return; +} + +// CHECK-LABEL: define void @"\01?f@@YA?AUcoro_t@@XZ"( +// CHECK: %gro.active = alloca i1 +// CHECK: store i1 false, i1* %gro.active + +// CHECK: invoke %"struct.coro_t::promise_type"* @"\01??0promise_type@coro_t@@QEAA@XZ"( +// CHECK: invoke void @"\01?get_return_object@promise_type@coro_t@@QEAA?AU2@XZ"( +// CHECK: store i1 true, i1* %gro.active + +// CHECK: %[[IS_ACTIVE:.+]] = load i1, i1* %gro.active +// CHECK: br i1 %[[IS_ACTIVE]], label %[[CLEANUP1:.+]], label + +// CHECK: [[CLEANUP1]]: +// CHECK: %[[NRVO:.+]] = load i1, i1* %nrvo +// CHECK: br i1 %[[NRVO]], label %{{.+}}, label %[[DTOR:.+]] + +// CHECK: [[DTOR]]: +// CHECK: call void @"\01??_Dcoro_t@@QEAAXXZ"( diff --git a/test/CodeGenCoroutines/coro-ret-void.cpp b/test/CodeGenCoroutines/coro-ret-void.cpp new file mode 100644 index 0000000000000..6b07f6ea1d642 --- /dev/null +++ b/test/CodeGenCoroutines/coro-ret-void.cpp @@ -0,0 +1,39 @@ +// RUN: %clang_cc1 -std=c++14 -fcoroutines-ts -triple=x86_64-unknown-linux-gnu -emit-llvm %s -o - -disable-llvm-passes | FileCheck %s + +#include "Inputs/coroutine.h" + +namespace coro = std::experimental::coroutines_v1; + +struct coro1 { + struct promise_type { + coro1 get_return_object(); + coro::suspend_never initial_suspend(); + coro::suspend_never final_suspend(); + void return_void(); + }; +}; + +coro1 f() { + co_await coro::suspend_never{}; +} + +// CHECK-LABEL: define void @_Z1fv( +// CHECK: call void @_ZNSt12experimental13coroutines_v113suspend_never12await_resumeEv(%"struct.std::experimental::coroutines_v1::suspend_never"* +// CHECK: call void @_ZN5coro112promise_type11return_voidEv(%"struct.coro1::promise_type"* %__promise) + +struct coro2 { + struct promise_type { + coro2 get_return_object(); + coro::suspend_never initial_suspend(); + coro::suspend_never final_suspend(); + void return_value(int); + }; +}; + +coro2 g() { + co_return 42; +} + +// CHECK-LABEL: define void @_Z1gv( +// CHECK: call void @_ZNSt12experimental13coroutines_v113suspend_never12await_resumeEv(%"struct.std::experimental::coroutines_v1::suspend_never"* +// CHECK: call void @_ZN5coro212promise_type12return_valueEi(%"struct.coro2::promise_type"* %__promise, i32 42) diff --git a/test/CodeGenCoroutines/coro-unhandled-exception.cpp b/test/CodeGenCoroutines/coro-unhandled-exception.cpp new file mode 100644 index 0000000000000..e26a51861d9f2 --- /dev/null +++ b/test/CodeGenCoroutines/coro-unhandled-exception.cpp @@ -0,0 +1,72 @@ +// RUN: %clang_cc1 -std=c++14 -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++14 -fcoroutines-ts -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s -fexceptions -fcxx-exceptions -disable-llvm-passes | FileCheck --check-prefix=CHECK-LPAD %s + +#include "Inputs/coroutine.h" + +namespace coro = std::experimental::coroutines_v1; + +namespace std { + using exception_ptr = int; + exception_ptr current_exception(); +} + +struct coro_t { + struct promise_type { + coro_t get_return_object() { + coro::coroutine_handle<promise_type>{}; + return {}; + } + coro::suspend_never initial_suspend() { return {}; } + coro::suspend_never final_suspend() { return {}; } + void return_void(){} + 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 %{{.+}} unwind label %[[EHCLEANUP:.+]] +// CHECK: [[EHCLEANUP]]: +// CHECK: %[[INNERPAD:.+]] = cleanuppad within none [] +// CHECK: call void @"\01??_DCleanup@@QEAAXXZ"( +// CHECK: cleanupret from %[[INNERPAD]] unwind label %[[CATCHSW:.+]] +// CHECK: [[CATCHSW]]: +// CHECK: %[[CATCHSWTOK:.+]] = catchswitch within none [label %[[CATCH:.+]]] unwind label +// CHECK: [[CATCH]]: +// CHECK: %[[CATCHTOK:.+]] = catchpad within [[CATCHSWTOK:.+]] +// CHECK: call void @"\01?unhandled_exception@promise_type@coro_t@@QEAAXXZ" +// CHECK: catchret from %[[CATCHTOK]] to label %[[CATCHRETDEST:.+]] +// CHECK: [[CATCHRETDEST]]: +// CHECK-NEXT: br label %[[TRYCONT:.+]] +// CHECK: [[TRYCONT]]: +// CHECK-NEXT: br label %[[COROFIN:.+]] +// CHECK: [[COROFIN]]: +// CHECK-NEXT: invoke void @"\01?final_suspend@promise_type@coro_t@@QEAA?AUsuspend_never@coroutines_v1@experimental@std@@XZ"( + +// CHECK-LPAD: @_Z1fv( +// CHECK-LPAD: invoke void @_Z9may_throwv() +// CHECK-LPAD: to label %[[CONT:.+]] unwind label %[[CLEANUP:.+]] +// CHECK-LPAD: [[CLEANUP]]: +// CHECK-LPAD: call void @_ZN7CleanupD1Ev(%struct.Cleanup* %x) #2 +// CHECK-LPAD: br label %[[CATCH:.+]] + +// CHECK-LPAD: [[CATCH]]: +// CHECK-LPAD: call i8* @__cxa_begin_catch +// CHECK-LPAD: call void @_ZN6coro_t12promise_type19unhandled_exceptionEv(%"struct.coro_t::promise_type"* %__promise) #2 +// CHECK-LPAD: invoke void @__cxa_end_catch() +// CHECK-LPAD-NEXT: to label %[[CATCHRETDEST:.+]] unwind label +// CHECK-LPAD: [[CATCHRETDEST]]: +// CHECK-LPAD-NEXT: br label %[[TRYCONT:.+]] +// CHECK-LPAD: [[TRYCONT]]: +// CHECK-LPAD-NEXT: br label %[[COROFIN:.+]] +// CHECK-LPAD: [[COROFIN]]: +// CHECK-LPAD-NEXT: invoke void @_ZN6coro_t12promise_type13final_suspendEv( |