aboutsummaryrefslogtreecommitdiff
path: root/test/Transforms/Coroutines
diff options
context:
space:
mode:
authorDimitry Andric <dim@FreeBSD.org>2017-01-02 19:17:04 +0000
committerDimitry Andric <dim@FreeBSD.org>2017-01-02 19:17:04 +0000
commitb915e9e0fc85ba6f398b3fab0db6a81a8913af94 (patch)
tree98b8f811c7aff2547cab8642daf372d6c59502fb /test/Transforms/Coroutines
parent6421cca32f69ac849537a3cff78c352195e99f1b (diff)
Notes
Diffstat (limited to 'test/Transforms/Coroutines')
-rw-r--r--test/Transforms/Coroutines/ArgAddr.ll67
-rw-r--r--test/Transforms/Coroutines/coro-cleanup.ll18
-rw-r--r--test/Transforms/Coroutines/coro-early.ll41
-rw-r--r--test/Transforms/Coroutines/coro-elide.ll112
-rw-r--r--test/Transforms/Coroutines/coro-heap-elide.ll127
-rw-r--r--test/Transforms/Coroutines/coro-split-00.ll79
-rw-r--r--test/Transforms/Coroutines/coro-split-01.ll56
-rw-r--r--test/Transforms/Coroutines/coro-split-02.ll54
-rw-r--r--test/Transforms/Coroutines/coro-split-dbg.ll119
-rw-r--r--test/Transforms/Coroutines/ex0.ll59
-rw-r--r--test/Transforms/Coroutines/ex1.ll54
-rw-r--r--test/Transforms/Coroutines/ex2.ll63
-rw-r--r--test/Transforms/Coroutines/ex3.ll60
-rw-r--r--test/Transforms/Coroutines/ex4.ll71
-rw-r--r--test/Transforms/Coroutines/ex5.ll73
-rw-r--r--test/Transforms/Coroutines/no-suspend.ll189
-rw-r--r--test/Transforms/Coroutines/phi-coro-end.ll48
-rw-r--r--test/Transforms/Coroutines/restart-trigger.ll43
-rw-r--r--test/Transforms/Coroutines/smoketest.ll25
19 files changed, 1358 insertions, 0 deletions
diff --git a/test/Transforms/Coroutines/ArgAddr.ll b/test/Transforms/Coroutines/ArgAddr.ll
new file mode 100644
index 000000000000..4bedb510cd9e
--- /dev/null
+++ b/test/Transforms/Coroutines/ArgAddr.ll
@@ -0,0 +1,67 @@
+; Need to move users of allocas that were moved into the coroutine frame after
+; coro.begin.
+; RUN: opt < %s -O2 -enable-coroutines -S | FileCheck %s
+
+define nonnull i8* @f(i32 %n) {
+entry:
+ %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null);
+ %n.addr = alloca i32
+ store i32 %n, i32* %n.addr ; this needs to go after coro.begin
+ %0 = tail call i32 @llvm.coro.size.i32()
+ %call = tail call i8* @malloc(i32 %0)
+ %1 = tail call noalias nonnull i8* @llvm.coro.begin(token %id, i8* %call)
+ %2 = bitcast i32* %n.addr to i8*
+ call void @ctor(i8* %2)
+ br label %for.cond
+
+for.cond:
+ %3 = load i32, i32* %n.addr
+ %dec = add nsw i32 %3, -1
+ store i32 %dec, i32* %n.addr
+ call void @print(i32 %3)
+ %4 = call i8 @llvm.coro.suspend(token none, i1 false)
+ %conv = sext i8 %4 to i32
+ switch i32 %conv, label %coro_Suspend [
+ i32 0, label %for.cond
+ i32 1, label %coro_Cleanup
+ ]
+
+coro_Cleanup:
+ %5 = call i8* @llvm.coro.free(token %id, i8* nonnull %1)
+ call void @free(i8* %5)
+ br label %coro_Suspend
+
+coro_Suspend:
+ call void @llvm.coro.end(i8* null, i1 false)
+ ret i8* %1
+}
+
+; CHECK-LABEL: @main
+define i32 @main() {
+entry:
+ %hdl = call i8* @f(i32 4)
+ call void @llvm.coro.resume(i8* %hdl)
+ call void @llvm.coro.resume(i8* %hdl)
+ call void @llvm.coro.destroy(i8* %hdl)
+ ret i32 0
+; CHECK: call void @ctor
+; CHECK-NEXT: call void @print(i32 4)
+; CHECK-NEXT: call void @print(i32 3)
+; CHECK-NEXT: call void @print(i32 2)
+; CHECK: ret i32 0
+}
+
+declare i8* @malloc(i32)
+declare void @free(i8*)
+declare void @print(i32)
+declare void @ctor(i8* nocapture readonly)
+
+declare token @llvm.coro.id(i32, i8*, i8*, i8*)
+declare i32 @llvm.coro.size.i32()
+declare i8* @llvm.coro.begin(token, i8*)
+declare i8 @llvm.coro.suspend(token, i1)
+declare i8* @llvm.coro.free(token, i8*)
+declare void @llvm.coro.end(i8*, i1)
+
+declare void @llvm.coro.resume(i8*)
+declare void @llvm.coro.destroy(i8*)
diff --git a/test/Transforms/Coroutines/coro-cleanup.ll b/test/Transforms/Coroutines/coro-cleanup.ll
new file mode 100644
index 000000000000..02a0ef2911bc
--- /dev/null
+++ b/test/Transforms/Coroutines/coro-cleanup.ll
@@ -0,0 +1,18 @@
+; Make sure that all library helper coro intrinsics are lowered.
+; RUN: opt < %s -O0 -enable-coroutines -S | FileCheck %s
+
+; CHECK-LABEL: @uses_library_support_coro_intrinsics(
+; CHECK-NOT: @llvm.coro
+; CHECK: ret void
+define void @uses_library_support_coro_intrinsics(i8* %hdl) {
+entry:
+ call void @llvm.coro.resume(i8* %hdl)
+ call void @llvm.coro.destroy(i8* %hdl)
+ call i1 @llvm.coro.done(i8* %hdl)
+ ret void
+}
+
+declare void @llvm.coro.resume(i8*)
+declare void @llvm.coro.destroy(i8*)
+declare i1 @llvm.coro.done(i8*)
+
diff --git a/test/Transforms/Coroutines/coro-early.ll b/test/Transforms/Coroutines/coro-early.ll
new file mode 100644
index 000000000000..7b8b74e2e221
--- /dev/null
+++ b/test/Transforms/Coroutines/coro-early.ll
@@ -0,0 +1,41 @@
+; Tests that CoroEarly pass correctly lowers coro.resume and coro.destroy
+; intrinsics.
+; RUN: opt < %s -S -coro-early | FileCheck %s
+
+; CHECK-LABEL: @callResume(
+define void @callResume(i8* %hdl) {
+; CHECK-NEXT: entry
+entry:
+; CHECK-NEXT: %0 = call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 0)
+; CHECK-NEXT: %1 = bitcast i8* %0 to void (i8*)*
+; CHECK-NEXT: call fastcc void %1(i8* %hdl)
+ call void @llvm.coro.resume(i8* %hdl)
+
+; CHECK-NEXT: %2 = call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 1)
+; CHECK-NEXT: %3 = bitcast i8* %2 to void (i8*)*
+; CHECK-NEXT: call fastcc void %3(i8* %hdl)
+ call void @llvm.coro.destroy(i8* %hdl)
+
+ ret void
+; CHECK-NEXT: ret void
+}
+
+; CHECK-LABEL: @eh(
+define void @eh(i8* %hdl) personality i8* null {
+; CHECK-NEXT: entry
+entry:
+; CHECK-NEXT: %0 = call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 0)
+; CHECK-NEXT: %1 = bitcast i8* %0 to void (i8*)*
+; CHECK-NEXT: invoke fastcc void %1(i8* %hdl)
+ invoke void @llvm.coro.resume(i8* %hdl)
+ to label %cont unwind label %ehcleanup
+cont:
+ ret void
+
+ehcleanup:
+ %0 = cleanuppad within none []
+ cleanupret from %0 unwind to caller
+}
+
+declare void @llvm.coro.resume(i8*)
+declare void @llvm.coro.destroy(i8*)
diff --git a/test/Transforms/Coroutines/coro-elide.ll b/test/Transforms/Coroutines/coro-elide.ll
new file mode 100644
index 000000000000..371d7f1b9401
--- /dev/null
+++ b/test/Transforms/Coroutines/coro-elide.ll
@@ -0,0 +1,112 @@
+; Tests that the coro.destroy and coro.resume are devirtualized where possible,
+; SCC pipeline restarts and inlines the direct calls.
+; RUN: opt < %s -S -inline -coro-elide -dce | FileCheck %s
+
+declare void @print(i32) nounwind
+
+; resume part of the coroutine
+define fastcc void @f.resume(i8*) {
+ tail call void @print(i32 0)
+ ret void
+}
+
+; destroy part of the coroutine
+define fastcc void @f.destroy(i8*) {
+ tail call void @print(i32 1)
+ ret void
+}
+
+@f.resumers = internal constant [2 x void (i8*)*] [void (i8*)* @f.resume,
+ void (i8*)* @f.destroy]
+
+; a coroutine start function
+define i8* @f() {
+entry:
+ %id = call token @llvm.coro.id(i32 0, i8* null,
+ i8* bitcast (i8*()* @f to i8*),
+ i8* bitcast ([2 x void (i8*)*]* @f.resumers to i8*))
+ %hdl = call i8* @llvm.coro.begin(token %id, i8* null)
+ ret i8* %hdl
+}
+
+; CHECK-LABEL: @callResume(
+define void @callResume() {
+entry:
+ %hdl = call i8* @f()
+
+; CHECK: call void @print(i32 0)
+ %0 = call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 0)
+ %1 = bitcast i8* %0 to void (i8*)*
+ call fastcc void %1(i8* %hdl)
+
+; CHECK-NEXT: call void @print(i32 1)
+ %2 = call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 1)
+ %3 = bitcast i8* %2 to void (i8*)*
+ call fastcc void %3(i8* %hdl)
+
+; CHECK-NEXT: ret void
+ ret void
+}
+
+; CHECK-LABEL: @eh(
+define void @eh() personality i8* null {
+entry:
+ %hdl = call i8* @f()
+
+; CHECK: call void @print(i32 0)
+ %0 = call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 0)
+ %1 = bitcast i8* %0 to void (i8*)*
+ invoke void %1(i8* %hdl)
+ to label %cont unwind label %ehcleanup
+cont:
+ ret void
+
+ehcleanup:
+ %tok = cleanuppad within none []
+ cleanupret from %tok unwind to caller
+}
+
+; CHECK-LABEL: @no_devirt_info_null(
+; no devirtualization here, since coro.begin info parameter is null
+define void @no_devirt_info_null() {
+entry:
+ %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
+ %hdl = call i8* @llvm.coro.begin(token %id, i8* null)
+
+; CHECK: call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 0)
+ %0 = call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 0)
+ %1 = bitcast i8* %0 to void (i8*)*
+ call fastcc void %1(i8* %hdl)
+
+; CHECK: call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 1)
+ %2 = call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 1)
+ %3 = bitcast i8* %2 to void (i8*)*
+ call fastcc void %3(i8* %hdl)
+
+; CHECK: ret void
+ ret void
+}
+
+; CHECK-LABEL: @no_devirt_no_begin(
+; no devirtualization here, since coro.begin is not visible
+define void @no_devirt_no_begin(i8* %hdl) {
+entry:
+
+; CHECK: call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 0)
+ %0 = call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 0)
+ %1 = bitcast i8* %0 to void (i8*)*
+ call fastcc void %1(i8* %hdl)
+
+; CHECK: call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 1)
+ %2 = call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 1)
+ %3 = bitcast i8* %2 to void (i8*)*
+ call fastcc void %3(i8* %hdl)
+
+; CHECK: ret void
+ ret void
+}
+
+declare token @llvm.coro.id(i32, i8*, i8*, i8*)
+declare i8* @llvm.coro.begin(token, i8*)
+declare i8* @llvm.coro.frame()
+declare i8* @llvm.coro.subfn.addr(i8*, i8)
diff --git a/test/Transforms/Coroutines/coro-heap-elide.ll b/test/Transforms/Coroutines/coro-heap-elide.ll
new file mode 100644
index 000000000000..839f844fde83
--- /dev/null
+++ b/test/Transforms/Coroutines/coro-heap-elide.ll
@@ -0,0 +1,127 @@
+; Tests that the dynamic allocation and deallocation of the coroutine frame is
+; elided and any tail calls referencing the coroutine frame has the tail
+; call attribute removed.
+; RUN: opt < %s -S -inline -coro-elide -instsimplify -simplifycfg | FileCheck %s
+
+declare void @print(i32) nounwind
+
+%f.frame = type {i32}
+
+declare void @bar(i8*)
+
+declare fastcc void @f.resume(%f.frame*)
+declare fastcc void @f.destroy(%f.frame*)
+declare fastcc void @f.cleanup(%f.frame*)
+
+declare void @may_throw()
+declare i8* @CustomAlloc(i32)
+declare void @CustomFree(i8*)
+
+@f.resumers = internal constant [3 x void (%f.frame*)*]
+ [void (%f.frame*)* @f.resume, void (%f.frame*)* @f.destroy, void (%f.frame*)* @f.cleanup]
+
+; a coroutine start function
+define i8* @f() personality i8* null {
+entry:
+ %id = call token @llvm.coro.id(i32 0, i8* null,
+ i8* bitcast (i8*()* @f to i8*),
+ i8* bitcast ([3 x void (%f.frame*)*]* @f.resumers to i8*))
+ %need.dyn.alloc = call i1 @llvm.coro.alloc(token %id)
+ br i1 %need.dyn.alloc, label %dyn.alloc, label %coro.begin
+dyn.alloc:
+ %alloc = call i8* @CustomAlloc(i32 4)
+ br label %coro.begin
+coro.begin:
+ %phi = phi i8* [ null, %entry ], [ %alloc, %dyn.alloc ]
+ %hdl = call i8* @llvm.coro.begin(token %id, i8* %phi)
+ invoke void @may_throw()
+ to label %ret unwind label %ehcleanup
+ret:
+ ret i8* %hdl
+
+ehcleanup:
+ %tok = cleanuppad within none []
+ %mem = call i8* @llvm.coro.free(token %id, i8* %hdl)
+ %need.dyn.free = icmp ne i8* %mem, null
+ br i1 %need.dyn.free, label %dyn.free, label %if.end
+dyn.free:
+ call void @CustomFree(i8* %mem)
+ br label %if.end
+if.end:
+ cleanupret from %tok unwind to caller
+}
+
+; CHECK-LABEL: @callResume(
+define void @callResume() {
+entry:
+; CHECK: alloca %f.frame
+; CHECK-NOT: coro.begin
+; CHECK-NOT: CustomAlloc
+; CHECK: call void @may_throw()
+ %hdl = call i8* @f()
+
+; Need to remove 'tail' from the first call to @bar
+; CHECK-NOT: tail call void @bar(
+; CHECK: call void @bar(
+ tail call void @bar(i8* %hdl)
+; CHECK: tail call void @bar(
+ tail call void @bar(i8* null)
+
+; CHECK-NEXT: call fastcc void bitcast (void (%f.frame*)* @f.resume to void (i8*)*)(i8* %vFrame)
+ %0 = call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 0)
+ %1 = bitcast i8* %0 to void (i8*)*
+ call fastcc void %1(i8* %hdl)
+
+; CHECK-NEXT: call fastcc void bitcast (void (%f.frame*)* @f.cleanup to void (i8*)*)(i8* %vFrame)
+ %2 = call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 1)
+ %3 = bitcast i8* %2 to void (i8*)*
+ call fastcc void %3(i8* %hdl)
+
+; CHECK-NEXT: ret void
+ ret void
+}
+
+; a coroutine start function (cannot elide heap alloc, due to second argument to
+; coro.begin not pointint to coro.alloc)
+define i8* @f_no_elision() personality i8* null {
+entry:
+ %id = call token @llvm.coro.id(i32 0, i8* null,
+ i8* bitcast (i8*()* @f_no_elision to i8*),
+ i8* bitcast ([3 x void (%f.frame*)*]* @f.resumers to i8*))
+ %alloc = call i8* @CustomAlloc(i32 4)
+ %hdl = call i8* @llvm.coro.begin(token %id, i8* %alloc)
+ ret i8* %hdl
+}
+
+; CHECK-LABEL: @callResume_no_elision(
+define void @callResume_no_elision() {
+entry:
+; CHECK: call i8* @CustomAlloc(
+ %hdl = call i8* @f_no_elision()
+
+; Tail call should remain tail calls
+; CHECK: tail call void @bar(
+ tail call void @bar(i8* %hdl)
+; CHECK: tail call void @bar(
+ tail call void @bar(i8* null)
+
+; CHECK-NEXT: call fastcc void bitcast (void (%f.frame*)* @f.resume to void (i8*)*)(i8*
+ %0 = call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 0)
+ %1 = bitcast i8* %0 to void (i8*)*
+ call fastcc void %1(i8* %hdl)
+
+; CHECK-NEXT: call fastcc void bitcast (void (%f.frame*)* @f.destroy to void (i8*)*)(i8*
+ %2 = call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 1)
+ %3 = bitcast i8* %2 to void (i8*)*
+ call fastcc void %3(i8* %hdl)
+
+; CHECK-NEXT: ret void
+ ret void
+}
+
+declare token @llvm.coro.id(i32, i8*, i8*, i8*)
+declare i1 @llvm.coro.alloc(token)
+declare i8* @llvm.coro.free(token, i8*)
+declare i8* @llvm.coro.begin(token, i8*)
+declare i8* @llvm.coro.frame(token)
+declare i8* @llvm.coro.subfn.addr(i8*, i8)
diff --git a/test/Transforms/Coroutines/coro-split-00.ll b/test/Transforms/Coroutines/coro-split-00.ll
new file mode 100644
index 000000000000..12aec27b2fe6
--- /dev/null
+++ b/test/Transforms/Coroutines/coro-split-00.ll
@@ -0,0 +1,79 @@
+; Tests that coro-split pass splits the coroutine into f, f.resume and f.destroy
+; RUN: opt < %s -coro-split -S | FileCheck %s
+
+define i8* @f() "coroutine.presplit"="1" {
+entry:
+ %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
+ %need.alloc = call i1 @llvm.coro.alloc(token %id)
+ br i1 %need.alloc, label %dyn.alloc, label %begin
+
+dyn.alloc:
+ %size = call i32 @llvm.coro.size.i32()
+ %alloc = call i8* @malloc(i32 %size)
+ br label %begin
+
+begin:
+ %phi = phi i8* [ null, %entry ], [ %alloc, %dyn.alloc ]
+ %hdl = call i8* @llvm.coro.begin(token %id, i8* %phi)
+ call void @print(i32 0)
+ %0 = call i8 @llvm.coro.suspend(token none, i1 false)
+ switch i8 %0, label %suspend [i8 0, label %resume
+ i8 1, label %cleanup]
+resume:
+ call void @print(i32 1)
+ br label %cleanup
+
+cleanup:
+ %mem = call i8* @llvm.coro.free(token %id, i8* %hdl)
+ call void @free(i8* %mem)
+ br label %suspend
+suspend:
+ call void @llvm.coro.end(i8* %hdl, i1 0)
+ ret i8* %hdl
+}
+
+; CHECK-LABEL: @f(
+; CHECK: call i8* @malloc
+; CHECK: @llvm.coro.begin(token %id, i8* %phi)
+; CHECK: store void (%f.Frame*)* @f.resume, void (%f.Frame*)** %resume.addr
+; CHECK: %[[SEL:.+]] = select i1 %need.alloc, void (%f.Frame*)* @f.destroy, void (%f.Frame*)* @f.cleanup
+; CHECK: store void (%f.Frame*)* %[[SEL]], void (%f.Frame*)** %destroy.addr
+; CHECK: call void @print(i32 0)
+; CHECK-NOT: call void @print(i32 1)
+; CHECK-NOT: call void @free(
+; CHECK: ret i8* %hdl
+
+; CHECK-LABEL: @f.resume(
+; CHECK-NOT: call i8* @malloc
+; CHECK-NOT: call void @print(i32 0)
+; CHECK: call void @print(i32 1)
+; CHECK-NOT: call void @print(i32 0)
+; CHECK: call void @free(
+; CHECK: ret void
+
+; CHECK-LABEL: @f.destroy(
+; CHECK-NOT: call i8* @malloc
+; CHECK-NOT: call void @print(
+; CHECK: call void @free(
+; CHECK: ret void
+
+; CHECK-LABEL: @f.cleanup(
+; CHECK-NOT: call i8* @malloc
+; CHECK-NOT: call void @print(
+; CHECK-NOT: call void @free(
+; CHECK: ret void
+
+declare i8* @llvm.coro.free(token, i8*)
+declare i32 @llvm.coro.size.i32()
+declare i8 @llvm.coro.suspend(token, i1)
+declare void @llvm.coro.resume(i8*)
+declare void @llvm.coro.destroy(i8*)
+
+declare token @llvm.coro.id(i32, i8*, i8*, i8*)
+declare i1 @llvm.coro.alloc(token)
+declare i8* @llvm.coro.begin(token, i8*)
+declare void @llvm.coro.end(i8*, i1)
+
+declare noalias i8* @malloc(i32)
+declare void @print(i32)
+declare void @free(i8*)
diff --git a/test/Transforms/Coroutines/coro-split-01.ll b/test/Transforms/Coroutines/coro-split-01.ll
new file mode 100644
index 000000000000..2b5801f7ddd1
--- /dev/null
+++ b/test/Transforms/Coroutines/coro-split-01.ll
@@ -0,0 +1,56 @@
+; Tests that a coroutine is split, inlined into the caller and devirtualized.
+; RUN: opt < %s -S -enable-coroutines -O2 | FileCheck %s
+
+define i8* @f() {
+entry:
+ %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
+ %need.dyn.alloc = call i1 @llvm.coro.alloc(token %id)
+ br i1 %need.dyn.alloc, label %dyn.alloc, label %coro.begin
+dyn.alloc:
+ %size = call i32 @llvm.coro.size.i32()
+ %alloc = call i8* @malloc(i32 %size)
+ br label %coro.begin
+coro.begin:
+ %phi = phi i8* [ null, %entry ], [ %alloc, %dyn.alloc ]
+ %hdl = call i8* @llvm.coro.begin(token %id, i8* %phi)
+ call void @print(i32 0)
+ %0 = call i8 @llvm.coro.suspend(token none, i1 false)
+ switch i8 %0, label %suspend [i8 0, label %resume
+ i8 1, label %cleanup]
+resume:
+ call void @print(i32 1)
+ br label %cleanup
+
+cleanup:
+ %mem = call i8* @llvm.coro.free(token %id, i8* %hdl)
+ call void @free(i8* %mem)
+ br label %suspend
+suspend:
+ call void @llvm.coro.end(i8* %hdl, i1 0)
+ ret i8* %hdl
+}
+define i32 @main() {
+entry:
+ %hdl = call i8* @f()
+ call void @llvm.coro.resume(i8* %hdl)
+ ret i32 0
+; CHECK-LABEL: @main(
+; CHECK: call void @print(i32 0)
+; CHECK: call void @print(i32 1)
+; CHECK: ret i32 0
+}
+
+declare i8* @llvm.coro.free(token, i8*)
+declare i32 @llvm.coro.size.i32()
+declare i8 @llvm.coro.suspend(token, i1)
+declare void @llvm.coro.resume(i8*)
+declare void @llvm.coro.destroy(i8*)
+
+declare token @llvm.coro.id(i32, i8*, i8*, i8*)
+declare i1 @llvm.coro.alloc(token)
+declare i8* @llvm.coro.begin(token, i8*)
+declare void @llvm.coro.end(i8*, i1)
+
+declare noalias i8* @malloc(i32)
+declare void @print(i32)
+declare void @free(i8*)
diff --git a/test/Transforms/Coroutines/coro-split-02.ll b/test/Transforms/Coroutines/coro-split-02.ll
new file mode 100644
index 000000000000..2326f77f1987
--- /dev/null
+++ b/test/Transforms/Coroutines/coro-split-02.ll
@@ -0,0 +1,54 @@
+; Tests that coro-split can handle the case when a code after coro.suspend uses
+; a value produces between coro.save and coro.suspend (%Result.i19)
+; RUN: opt < %s -coro-split -S | FileCheck %s
+
+%"struct.std::coroutine_handle" = type { i8* }
+%"struct.std::coroutine_handle.0" = type { %"struct.std::coroutine_handle" }
+%"struct.lean_future<int>::Awaiter" = type { i32, %"struct.std::coroutine_handle.0" }
+
+declare i8* @malloc(i64)
+declare void @print(i32)
+
+define void @a() "coroutine.presplit"="1" {
+entry:
+ %ref.tmp7 = alloca %"struct.lean_future<int>::Awaiter", align 8
+ %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
+ %alloc = call i8* @malloc(i64 16) #3
+ %vFrame = call noalias nonnull i8* @llvm.coro.begin(token %id, i8* %alloc)
+
+ %save = call token @llvm.coro.save(i8* null)
+ %Result.i19 = getelementptr inbounds %"struct.lean_future<int>::Awaiter", %"struct.lean_future<int>::Awaiter"* %ref.tmp7, i64 0, i32 0
+ %suspend = call i8 @llvm.coro.suspend(token %save, i1 false)
+ switch i8 %suspend, label %exit [
+ i8 0, label %await.ready
+ i8 1, label %exit
+ ]
+await.ready:
+ %val = load i32, i32* %Result.i19
+ call void @print(i32 %val)
+ br label %exit
+exit:
+ call void @llvm.coro.end(i8* null, i1 false)
+ ret void
+}
+
+; CHECK-LABEL: @a.resume(
+; CHECK: getelementptr inbounds %a.Frame
+; CHECK-NEXT: getelementptr inbounds %"struct.lean_future<int>::Awaiter"
+; CHECK-NEXT: %val = load i32, i32* %Result
+; CHECK-NEXT: call void @print(i32 %val)
+; CHECK-NEXT: ret void
+
+declare token @llvm.coro.id(i32, i8* readnone, i8* nocapture readonly, i8*)
+declare i1 @llvm.coro.alloc(token) #3
+declare noalias nonnull i8* @"\01??2@YAPEAX_K@Z"(i64) local_unnamed_addr
+declare i64 @llvm.coro.size.i64() #5
+declare i8* @llvm.coro.begin(token, i8* writeonly) #3
+declare void @"\01?puts@@YAXZZ"(...)
+declare token @llvm.coro.save(i8*) #3
+declare i8* @llvm.coro.frame() #5
+declare i8 @llvm.coro.suspend(token, i1) #3
+declare void @"\01??3@YAXPEAX@Z"(i8*) local_unnamed_addr #10
+declare i8* @llvm.coro.free(token, i8* nocapture readonly) #2
+declare void @llvm.coro.end(i8*, i1) #3
+
diff --git a/test/Transforms/Coroutines/coro-split-dbg.ll b/test/Transforms/Coroutines/coro-split-dbg.ll
new file mode 100644
index 000000000000..293622c40ebd
--- /dev/null
+++ b/test/Transforms/Coroutines/coro-split-dbg.ll
@@ -0,0 +1,119 @@
+; Make sure that coro-split correctly deals with debug information.
+; The test here is simply that it does not result in bad IR that will crash opt.
+; RUN: opt < %s -coro-split -disable-output
+source_filename = "coro.c"
+target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+; Function Attrs: nounwind readnone
+declare void @llvm.dbg.declare(metadata, metadata, metadata) #1
+
+declare void @bar(...) local_unnamed_addr #2
+
+; Function Attrs: nounwind uwtable
+define i8* @f() #3 !dbg !16 {
+entry:
+ %0 = tail call token @llvm.coro.id(i32 0, i8* null, i8* bitcast (i8* ()* @f to i8*), i8* null), !dbg !26
+ %1 = tail call i64 @llvm.coro.size.i64(), !dbg !26
+ %call = tail call i8* @malloc(i64 %1), !dbg !26
+ %2 = tail call i8* @llvm.coro.begin(token %0, i8* %call) #9, !dbg !26
+ tail call void @llvm.dbg.value(metadata i8* %2, i64 0, metadata !21, metadata !12), !dbg !26
+ br label %for.cond, !dbg !27
+
+for.cond: ; preds = %for.cond, %entry
+ tail call void @llvm.dbg.value(metadata i32 undef, i64 0, metadata !22, metadata !12), !dbg !28
+ tail call void @llvm.dbg.value(metadata i32 undef, i64 0, metadata !11, metadata !12) #7, !dbg !29
+ tail call void (...) @bar() #7, !dbg !33
+ %3 = tail call token @llvm.coro.save(i8* null), !dbg !34
+ %4 = tail call i8 @llvm.coro.suspend(token %3, i1 false), !dbg !34
+ %conv = sext i8 %4 to i32, !dbg !34
+ switch i32 %conv, label %coro_Suspend [
+ i32 0, label %for.cond
+ i32 1, label %coro_Cleanup
+ ], !dbg !34
+
+coro_Cleanup: ; preds = %for.cond
+ %5 = tail call i8* @llvm.coro.free(token %0, i8* %2), !dbg !35
+ tail call void @free(i8* nonnull %5), !dbg !36
+ br label %coro_Suspend, !dbg !36
+
+coro_Suspend: ; preds = %for.cond, %if.then, %coro_Cleanup
+ tail call void @llvm.coro.end(i8* null, i1 false) #9, !dbg !38
+ ret i8* %2, !dbg !39
+}
+
+; Function Attrs: argmemonly nounwind
+declare void @llvm.lifetime.start(i64, i8* nocapture) #4
+
+; Function Attrs: argmemonly nounwind readonly
+declare token @llvm.coro.id(i32, i8* readnone, i8* nocapture readonly, i8*) #5
+
+; Function Attrs: nounwind
+declare noalias i8* @malloc(i64) local_unnamed_addr #6
+declare i64 @llvm.coro.size.i64() #1
+declare i8* @llvm.coro.begin(token, i8* writeonly) #7
+declare token @llvm.coro.save(i8*) #7
+declare i8 @llvm.coro.suspend(token, i1) #7
+declare void @llvm.lifetime.end(i64, i8* nocapture) #4
+declare i8* @llvm.coro.free(token, i8* nocapture readonly) #5
+declare void @free(i8* nocapture) local_unnamed_addr #6
+declare void @llvm.coro.end(i8*, i1) #7
+declare i8* @llvm.coro.subfn.addr(i8* nocapture readonly, i8) #5
+
+declare void @llvm.dbg.value(metadata, i64, metadata, metadata) #1
+
+attributes #0 = { nounwind uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #1 = { nounwind readnone }
+attributes #2 = { "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #3 = { nounwind uwtable "coroutine.presplit"="1" "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #4 = { argmemonly nounwind }
+attributes #5 = { argmemonly nounwind readonly }
+attributes #6 = { nounwind "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #7 = { nounwind }
+attributes #8 = { alwaysinline nounwind }
+attributes #9 = { noduplicate }
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3, !4}
+!llvm.ident = !{!5}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 4.0.0 (http://llvm.org/git/clang.git 6c405f93921ac99ff5b8521bb1b3df4449deede4) (http://llvm.org/git/llvm.git 6e6d3247102a0f87ce14e906dcf6a3a5ed3faa65)", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2)
+!1 = !DIFile(filename: "coro.c", directory: "/home/gor/build/bin")
+!2 = !{}
+!3 = !{i32 2, !"Dwarf Version", i32 4}
+!4 = !{i32 2, !"Debug Info Version", i32 3}
+!5 = !{!"clang version 4.0.0 (http://llvm.org/git/clang.git 6c405f93921ac99ff5b8521bb1b3df4449deede4) (http://llvm.org/git/llvm.git 6e6d3247102a0f87ce14e906dcf6a3a5ed3faa65)"}
+!6 = distinct !DISubprogram(name: "print", scope: !1, file: !1, line: 6, type: !7, isLocal: false, isDefinition: true, scopeLine: 6, flags: DIFlagPrototyped, isOptimized: true, unit: !0, variables: !10)
+!7 = !DISubroutineType(types: !8)
+!8 = !{null, !9}
+!9 = !DIBasicType(name: "int", size: 32, align: 32, encoding: DW_ATE_signed)
+!10 = !{!11}
+!11 = !DILocalVariable(name: "v", arg: 1, scope: !6, file: !1, line: 6, type: !9)
+!12 = !DIExpression()
+!13 = !DILocation(line: 6, column: 16, scope: !6)
+!14 = !DILocation(line: 6, column: 19, scope: !6)
+!15 = !DILocation(line: 6, column: 25, scope: !6)
+!16 = distinct !DISubprogram(name: "f", scope: !1, file: !1, line: 8, type: !17, isLocal: false, isDefinition: true, scopeLine: 8, isOptimized: true, unit: !0, variables: !20)
+!17 = !DISubroutineType(types: !18)
+!18 = !{!19}
+!19 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: null, size: 64, align: 64)
+!20 = !{!21, !22, !24}
+!21 = !DILocalVariable(name: "coro_hdl", scope: !16, file: !1, line: 9, type: !19)
+!22 = !DILocalVariable(name: "i", scope: !23, file: !1, line: 11, type: !9)
+!23 = distinct !DILexicalBlock(scope: !16, file: !1, line: 11, column: 3)
+!24 = !DILocalVariable(name: "coro_mem", scope: !25, file: !1, line: 16, type: !19)
+!25 = distinct !DILexicalBlock(scope: !16, file: !1, line: 16, column: 3)
+!26 = !DILocation(line: 9, column: 3, scope: !16)
+!27 = !DILocation(line: 11, column: 8, scope: !23)
+!28 = !DILocation(line: 11, column: 12, scope: !23)
+!29 = !DILocation(line: 6, column: 16, scope: !6, inlinedAt: !30)
+!30 = distinct !DILocation(line: 12, column: 5, scope: !31)
+!31 = distinct !DILexicalBlock(scope: !32, file: !1, line: 11, column: 25)
+!32 = distinct !DILexicalBlock(scope: !23, file: !1, line: 11, column: 3)
+!33 = !DILocation(line: 6, column: 19, scope: !6, inlinedAt: !30)
+!34 = !DILocation(line: 13, column: 5, scope: !31)
+!35 = !DILocation(line: 16, column: 3, scope: !25)
+!36 = !DILocation(line: 16, column: 3, scope: !37)
+!37 = distinct !DILexicalBlock(scope: !25, file: !1, line: 16, column: 3)
+!38 = !DILocation(line: 16, column: 3, scope: !16)
+!39 = !DILocation(line: 17, column: 1, scope: !16)
diff --git a/test/Transforms/Coroutines/ex0.ll b/test/Transforms/Coroutines/ex0.ll
new file mode 100644
index 000000000000..d4a9f941d838
--- /dev/null
+++ b/test/Transforms/Coroutines/ex0.ll
@@ -0,0 +1,59 @@
+; First example from Doc/Coroutines.rst (two block loop)
+; RUN: opt < %s -enable-coroutines -O2 -S | FileCheck %s
+
+define i8* @f(i32 %n) {
+entry:
+ %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
+ %size = call i32 @llvm.coro.size.i32()
+ %alloc = call i8* @malloc(i32 %size)
+ %hdl = call i8* @llvm.coro.begin(token %id, i8* %alloc)
+ br label %loop
+
+loop:
+ %n.val = phi i32 [ %n, %entry ], [ %inc, %resume ]
+ call void @print(i32 %n.val)
+ %0 = call i8 @llvm.coro.suspend(token none, i1 false)
+ switch i8 %0, label %suspend [i8 0, label %resume
+ i8 1, label %cleanup]
+resume:
+ %inc = add i32 %n.val, 1
+ br label %loop
+
+cleanup:
+ %mem = call i8* @llvm.coro.free(token %id, i8* %hdl)
+ call void @free(i8* %mem)
+ br label %suspend
+suspend:
+ call void @llvm.coro.end(i8* %hdl, i1 0)
+ ret i8* %hdl
+}
+
+; CHECK-LABEL: @main(
+define i32 @main() {
+entry:
+ %hdl = call i8* @f(i32 4)
+ call void @llvm.coro.resume(i8* %hdl)
+ call void @llvm.coro.resume(i8* %hdl)
+ call void @llvm.coro.destroy(i8* %hdl)
+ ret i32 0
+; CHECK: entry:
+; CHECK: call void @print(i32 4)
+; CHECK: call void @print(i32 5)
+; CHECK: call void @print(i32 6)
+; CHECK: ret i32 0
+}
+
+declare token @llvm.coro.id(i32, i8*, i8*, i8*)
+declare i8* @llvm.coro.alloc(token)
+declare i8* @llvm.coro.free(token, i8*)
+declare i32 @llvm.coro.size.i32()
+declare i8 @llvm.coro.suspend(token, i1)
+declare void @llvm.coro.resume(i8*)
+declare void @llvm.coro.destroy(i8*)
+
+declare i8* @llvm.coro.begin(token, i8*)
+declare void @llvm.coro.end(i8*, i1)
+
+declare noalias i8* @malloc(i32)
+declare void @print(i32)
+declare void @free(i8*)
diff --git a/test/Transforms/Coroutines/ex1.ll b/test/Transforms/Coroutines/ex1.ll
new file mode 100644
index 000000000000..86ac75b13404
--- /dev/null
+++ b/test/Transforms/Coroutines/ex1.ll
@@ -0,0 +1,54 @@
+; First example from Doc/Coroutines.rst (one block loop)
+; RUN: opt < %s -O2 -enable-coroutines -S | FileCheck %s
+
+define i8* @f(i32 %n) {
+entry:
+ %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
+ %size = call i32 @llvm.coro.size.i32()
+ %alloc = call i8* @malloc(i32 %size)
+ %hdl = call noalias i8* @llvm.coro.begin(token %id, i8* %alloc)
+ br label %loop
+loop:
+ %n.val = phi i32 [ %n, %entry ], [ %inc, %loop ]
+ %inc = add nsw i32 %n.val, 1
+ call void @print(i32 %n.val)
+ %0 = call i8 @llvm.coro.suspend(token none, i1 false)
+ switch i8 %0, label %suspend [i8 0, label %loop
+ i8 1, label %cleanup]
+cleanup:
+ %mem = call i8* @llvm.coro.free(token %id, i8* %hdl)
+ call void @free(i8* %mem)
+ br label %suspend
+suspend:
+ call void @llvm.coro.end(i8* %hdl, i1 false)
+ ret i8* %hdl
+}
+
+; CHECK-LABEL: @main(
+define i32 @main() {
+entry:
+ %hdl = call i8* @f(i32 4)
+ call void @llvm.coro.resume(i8* %hdl)
+ call void @llvm.coro.resume(i8* %hdl)
+ call void @llvm.coro.destroy(i8* %hdl)
+ ret i32 0
+; CHECK-NEXT: entry:
+; CHECK: call void @print(i32 4)
+; CHECK: call void @print(i32 5)
+; CHECK: call void @print(i32 6)
+; CHECK: ret i32 0
+}
+
+declare i8* @malloc(i32)
+declare void @free(i8*)
+declare void @print(i32)
+
+declare token @llvm.coro.id(i32, i8*, i8*, i8*)
+declare i32 @llvm.coro.size.i32()
+declare i8* @llvm.coro.begin(token, i8*)
+declare i8 @llvm.coro.suspend(token, i1)
+declare i8* @llvm.coro.free(token, i8*)
+declare void @llvm.coro.end(i8*, i1)
+
+declare void @llvm.coro.resume(i8*)
+declare void @llvm.coro.destroy(i8*)
diff --git a/test/Transforms/Coroutines/ex2.ll b/test/Transforms/Coroutines/ex2.ll
new file mode 100644
index 000000000000..8681e4cecc80
--- /dev/null
+++ b/test/Transforms/Coroutines/ex2.ll
@@ -0,0 +1,63 @@
+; Second example from Doc/Coroutines.rst (custom alloc and free functions)
+; RUN: opt < %s -O2 -enable-coroutines -S | FileCheck %s
+
+define i8* @f(i32 %n) {
+entry:
+ %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
+ %need.dyn.alloc = call i1 @llvm.coro.alloc(token %id)
+ br i1 %need.dyn.alloc, label %dyn.alloc, label %coro.begin
+dyn.alloc:
+ %size = call i32 @llvm.coro.size.i32()
+ %alloc = call i8* @CustomAlloc(i32 %size)
+ br label %coro.begin
+coro.begin:
+ %phi = phi i8* [ null, %entry ], [ %alloc, %dyn.alloc ]
+ %hdl = call noalias i8* @llvm.coro.begin(token %id, i8* %phi)
+ br label %loop
+loop:
+ %n.val = phi i32 [ %n, %coro.begin ], [ %inc, %loop ]
+ %inc = add nsw i32 %n.val, 1
+ call void @print(i32 %n.val)
+ %0 = call i8 @llvm.coro.suspend(token none, i1 false)
+ switch i8 %0, label %suspend [i8 0, label %loop
+ i8 1, label %cleanup]
+cleanup:
+ %mem = call i8* @llvm.coro.free(token %id, i8* %hdl)
+ %need.dyn.free = icmp ne i8* %mem, null
+ br i1 %need.dyn.free, label %dyn.free, label %suspend
+dyn.free:
+ call void @CustomFree(i8* %mem)
+ br label %suspend
+suspend:
+ call void @llvm.coro.end(i8* %hdl, i1 false)
+ ret i8* %hdl
+}
+
+; CHECK-LABEL: @main
+define i32 @main() {
+entry:
+ %hdl = call i8* @f(i32 4)
+ call void @llvm.coro.resume(i8* %hdl)
+ call void @llvm.coro.resume(i8* %hdl)
+ call void @llvm.coro.destroy(i8* %hdl)
+ ret i32 0
+; CHECK: call void @print(i32 4)
+; CHECK-NEXT: call void @print(i32 5)
+; CHECK-NEXT: call void @print(i32 6)
+; CHECK-NEXT: ret i32 0
+}
+
+declare i8* @CustomAlloc(i32)
+declare void @CustomFree(i8*)
+declare void @print(i32)
+
+declare token @llvm.coro.id(i32, i8*, i8*, i8*)
+declare i1 @llvm.coro.alloc(token)
+declare i32 @llvm.coro.size.i32()
+declare i8* @llvm.coro.begin(token, i8*)
+declare i8 @llvm.coro.suspend(token, i1)
+declare i8* @llvm.coro.free(token, i8*)
+declare void @llvm.coro.end(i8*, i1)
+
+declare void @llvm.coro.resume(i8*)
+declare void @llvm.coro.destroy(i8*)
diff --git a/test/Transforms/Coroutines/ex3.ll b/test/Transforms/Coroutines/ex3.ll
new file mode 100644
index 000000000000..13289c8e974a
--- /dev/null
+++ b/test/Transforms/Coroutines/ex3.ll
@@ -0,0 +1,60 @@
+; Third example from Doc/Coroutines.rst (two suspend points)
+; RUN: opt < %s -O2 -enable-coroutines -S | FileCheck %s
+
+define i8* @f(i32 %n) {
+entry:
+ %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
+ %size = call i32 @llvm.coro.size.i32()
+ %alloc = call i8* @malloc(i32 %size)
+ %hdl = call noalias i8* @llvm.coro.begin(token %id, i8* %alloc)
+ br label %loop
+loop:
+ %n.val = phi i32 [ %n, %entry ], [ %inc, %loop.resume ]
+ call void @print(i32 %n.val) #4
+ %0 = call i8 @llvm.coro.suspend(token none, i1 false)
+ switch i8 %0, label %suspend [i8 0, label %loop.resume
+ i8 1, label %cleanup]
+loop.resume:
+ %inc = add nsw i32 %n.val, 1
+ %sub = xor i32 %n.val, -1
+ call void @print(i32 %sub)
+ %1 = call i8 @llvm.coro.suspend(token none, i1 false)
+ switch i8 %1, label %suspend [i8 0, label %loop
+ i8 1, label %cleanup]
+cleanup:
+ %mem = call i8* @llvm.coro.free(token %id, i8* %hdl)
+ call void @free(i8* %mem)
+ br label %suspend
+suspend:
+ call void @llvm.coro.end(i8* %hdl, i1 false)
+ ret i8* %hdl
+}
+
+; CHECK-LABEL: @main
+define i32 @main() {
+entry:
+ %hdl = call i8* @f(i32 4)
+ call void @llvm.coro.resume(i8* %hdl)
+ call void @llvm.coro.resume(i8* %hdl)
+ call void @llvm.coro.destroy(i8* %hdl)
+ ret i32 0
+; CHECK: call void @print(i32 4)
+; CHECK-NEXT: call void @print(i32 -5)
+; CHECK-NEXT: call void @print(i32 5)
+; CHECK: ret i32 0
+}
+
+declare i8* @malloc(i32)
+declare void @free(i8*)
+declare void @print(i32)
+
+declare token @llvm.coro.id(i32, i8*, i8*, i8*)
+declare i1 @llvm.coro.alloc(token)
+declare i32 @llvm.coro.size.i32()
+declare i8* @llvm.coro.begin(token, i8*)
+declare i8 @llvm.coro.suspend(token, i1)
+declare i8* @llvm.coro.free(token, i8*)
+declare void @llvm.coro.end(i8*, i1)
+
+declare void @llvm.coro.resume(i8*)
+declare void @llvm.coro.destroy(i8*)
diff --git a/test/Transforms/Coroutines/ex4.ll b/test/Transforms/Coroutines/ex4.ll
new file mode 100644
index 000000000000..ce896ad7ee41
--- /dev/null
+++ b/test/Transforms/Coroutines/ex4.ll
@@ -0,0 +1,71 @@
+; Fourth example from Doc/Coroutines.rst (coroutine promise)
+; RUN: opt < %s -O2 -enable-coroutines -S | FileCheck %s
+
+define i8* @f(i32 %n) {
+entry:
+ %promise = alloca i32
+ %pv = bitcast i32* %promise to i8*
+ %id = call token @llvm.coro.id(i32 0, i8* %pv, i8* null, i8* null)
+ %need.dyn.alloc = call i1 @llvm.coro.alloc(token %id)
+ br i1 %need.dyn.alloc, label %dyn.alloc, label %coro.begin
+dyn.alloc:
+ %size = call i32 @llvm.coro.size.i32()
+ %alloc = call i8* @malloc(i32 %size)
+ br label %coro.begin
+coro.begin:
+ %phi = phi i8* [ null, %entry ], [ %alloc, %dyn.alloc ]
+ %hdl = call noalias i8* @llvm.coro.begin(token %id, i8* %phi)
+ br label %loop
+loop:
+ %n.val = phi i32 [ %n, %coro.begin ], [ %inc, %loop ]
+ %inc = add nsw i32 %n.val, 1
+ store i32 %n.val, i32* %promise
+ %0 = call i8 @llvm.coro.suspend(token none, i1 false)
+ switch i8 %0, label %suspend [i8 0, label %loop
+ i8 1, label %cleanup]
+cleanup:
+ %mem = call i8* @llvm.coro.free(token %id, i8* %hdl)
+ call void @free(i8* %mem)
+ br label %suspend
+suspend:
+ call void @llvm.coro.end(i8* %hdl, i1 false)
+ ret i8* %hdl
+}
+
+; CHECK-LABEL: @main
+define i32 @main() {
+entry:
+ %hdl = call i8* @f(i32 4)
+ %promise.addr.raw = call i8* @llvm.coro.promise(i8* %hdl, i32 4, i1 false)
+ %promise.addr = bitcast i8* %promise.addr.raw to i32*
+ %val0 = load i32, i32* %promise.addr
+ call void @print(i32 %val0)
+ call void @llvm.coro.resume(i8* %hdl)
+ %val1 = load i32, i32* %promise.addr
+ call void @print(i32 %val1)
+ call void @llvm.coro.resume(i8* %hdl)
+ %val2 = load i32, i32* %promise.addr
+ call void @print(i32 %val2)
+ call void @llvm.coro.destroy(i8* %hdl)
+ ret i32 0
+; CHECK: call void @print(i32 4)
+; CHECK-NEXT: call void @print(i32 5)
+; CHECK-NEXT: call void @print(i32 6)
+; CHECK: ret i32 0
+}
+
+declare i8* @llvm.coro.promise(i8*, i32, i1)
+declare i8* @malloc(i32)
+declare void @free(i8*)
+declare void @print(i32)
+
+declare token @llvm.coro.id(i32, i8*, i8*, i8*)
+declare i1 @llvm.coro.alloc(token)
+declare i32 @llvm.coro.size.i32()
+declare i8* @llvm.coro.begin(token, i8*)
+declare i8 @llvm.coro.suspend(token, i1)
+declare i8* @llvm.coro.free(token, i8*)
+declare void @llvm.coro.end(i8*, i1)
+
+declare void @llvm.coro.resume(i8*)
+declare void @llvm.coro.destroy(i8*)
diff --git a/test/Transforms/Coroutines/ex5.ll b/test/Transforms/Coroutines/ex5.ll
new file mode 100644
index 000000000000..c9772825f250
--- /dev/null
+++ b/test/Transforms/Coroutines/ex5.ll
@@ -0,0 +1,73 @@
+; Fifth example from Doc/Coroutines.rst (final suspend)
+; RUN: opt < %s -O2 -enable-coroutines -S | FileCheck %s
+
+define i8* @f(i32 %n) {
+entry:
+ %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
+ %size = call i32 @llvm.coro.size.i32()
+ %alloc = call i8* @malloc(i32 %size)
+ %hdl = call noalias i8* @llvm.coro.begin(token %id, i8* %alloc)
+ br label %while.cond
+while.cond:
+ %n.val = phi i32 [ %n, %entry ], [ %dec, %while.body ]
+ %cmp = icmp sgt i32 %n.val, 0
+ br i1 %cmp, label %while.body, label %while.end
+
+while.body:
+ %dec = add nsw i32 %n.val, -1
+ call void @print(i32 %n.val) #4
+ %s = call i8 @llvm.coro.suspend(token none, i1 false)
+ switch i8 %s, label %suspend [i8 0, label %while.cond
+ i8 1, label %cleanup]
+while.end:
+ %s.final = call i8 @llvm.coro.suspend(token none, i1 true)
+ switch i8 %s.final, label %suspend [i8 0, label %trap
+ i8 1, label %cleanup]
+trap:
+ call void @llvm.trap()
+ unreachable
+cleanup:
+ %mem = call i8* @llvm.coro.free(token %id, i8* %hdl)
+ call void @free(i8* %mem)
+ br label %suspend
+suspend:
+ call void @llvm.coro.end(i8* %hdl, i1 false)
+ ret i8* %hdl
+}
+
+declare noalias i8* @malloc(i32)
+declare void @print(i32)
+declare void @llvm.trap()
+declare void @free(i8* nocapture)
+
+declare token @llvm.coro.id( i32, i8*, i8*, i8*)
+declare i32 @llvm.coro.size.i32()
+declare i8* @llvm.coro.begin(token, i8*)
+declare token @llvm.coro.save(i8*)
+declare i8 @llvm.coro.suspend(token, i1)
+declare i8* @llvm.coro.free(token, i8*)
+declare void @llvm.coro.end(i8*, i1)
+
+; CHECK-LABEL: @main
+define i32 @main() {
+entry:
+ %hdl = call i8* @f(i32 4)
+ br label %while
+while:
+ call void @llvm.coro.resume(i8* %hdl)
+ %done = call i1 @llvm.coro.done(i8* %hdl)
+ br i1 %done, label %end, label %while
+end:
+ call void @llvm.coro.destroy(i8* %hdl)
+ ret i32 0
+
+; CHECK: call void @print(i32 4)
+; CHECK: call void @print(i32 3)
+; CHECK: call void @print(i32 2)
+; CHECK: call void @print(i32 1)
+; CHECK: ret i32 0
+}
+
+declare i1 @llvm.coro.done(i8*)
+declare void @llvm.coro.resume(i8*)
+declare void @llvm.coro.destroy(i8*)
diff --git a/test/Transforms/Coroutines/no-suspend.ll b/test/Transforms/Coroutines/no-suspend.ll
new file mode 100644
index 000000000000..d219495de6cc
--- /dev/null
+++ b/test/Transforms/Coroutines/no-suspend.ll
@@ -0,0 +1,189 @@
+; Test no suspend coroutines
+; RUN: opt < %s -O2 -enable-coroutines -S | FileCheck %s
+
+; Coroutine with no-suspends will turn into:
+;
+; CHECK-LABEL: define void @no_suspends(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: call void @print(i32 %n)
+; CHECK-NEXT: ret void
+;
+define void @no_suspends(i32 %n) {
+entry:
+ %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
+ %need.dyn.alloc = call i1 @llvm.coro.alloc(token %id)
+ br i1 %need.dyn.alloc, label %dyn.alloc, label %coro.begin
+dyn.alloc:
+ %size = call i32 @llvm.coro.size.i32()
+ %alloc = call i8* @malloc(i32 %size)
+ br label %coro.begin
+coro.begin:
+ %phi = phi i8* [ null, %entry ], [ %alloc, %dyn.alloc ]
+ %hdl = call noalias i8* @llvm.coro.begin(token %id, i8* %phi)
+ br label %body
+body:
+ call void @print(i32 %n)
+ br label %cleanup
+cleanup:
+ %mem = call i8* @llvm.coro.free(token %id, i8* %hdl)
+ %need.dyn.free = icmp ne i8* %mem, null
+ br i1 %need.dyn.free, label %dyn.free, label %suspend
+dyn.free:
+ call void @free(i8* %mem)
+ br label %suspend
+suspend:
+ call void @llvm.coro.end(i8* %hdl, i1 false)
+ ret void
+}
+
+; SimplifySuspendPoint will detect that coro.resume resumes itself and will
+; replace suspend with a jump to %resume label turning it into no-suspend
+; coroutine.
+;
+; CHECK-LABEL: define void @simplify_resume(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: call void @print(i32 0)
+; CHECK-NEXT: ret void
+;
+define void @simplify_resume() {
+entry:
+ %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
+ %need.dyn.alloc = call i1 @llvm.coro.alloc(token %id)
+ br i1 %need.dyn.alloc, label %dyn.alloc, label %coro.begin
+dyn.alloc:
+ %size = call i32 @llvm.coro.size.i32()
+ %alloc = call i8* @malloc(i32 %size)
+ br label %coro.begin
+coro.begin:
+ %phi = phi i8* [ null, %entry ], [ %alloc, %dyn.alloc ]
+ %hdl = call noalias i8* @llvm.coro.begin(token %id, i8* %phi)
+ br label %body
+body:
+ %save = call token @llvm.coro.save(i8* %hdl)
+ call void @llvm.coro.resume(i8* %hdl)
+ %0 = call i8 @llvm.coro.suspend(token %save, i1 false)
+ switch i8 %0, label %suspend [i8 0, label %resume
+ i8 1, label %pre.cleanup]
+resume:
+ call void @print(i32 0)
+ br label %cleanup
+
+pre.cleanup:
+ call void @print(i32 1)
+ br label %cleanup
+
+cleanup:
+ %mem = call i8* @llvm.coro.free(token %id, i8* %hdl)
+ call void @free(i8* %mem)
+ br label %suspend
+suspend:
+ call void @llvm.coro.end(i8* %hdl, i1 false)
+ ret void
+}
+
+; SimplifySuspendPoint will detect that coroutine destroys itself and will
+; replace suspend with a jump to %cleanup label turning it into no-suspend
+; coroutine.
+;
+; CHECK-LABEL: define void @simplify_destroy(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: call void @print(i32 1)
+; CHECK-NEXT: ret void
+;
+define void @simplify_destroy() {
+entry:
+ %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
+ %need.dyn.alloc = call i1 @llvm.coro.alloc(token %id)
+ br i1 %need.dyn.alloc, label %dyn.alloc, label %coro.begin
+dyn.alloc:
+ %size = call i32 @llvm.coro.size.i32()
+ %alloc = call i8* @malloc(i32 %size)
+ br label %coro.begin
+coro.begin:
+ %phi = phi i8* [ null, %entry ], [ %alloc, %dyn.alloc ]
+ %hdl = call noalias i8* @llvm.coro.begin(token %id, i8* %phi)
+ br label %body
+body:
+ %save = call token @llvm.coro.save(i8* %hdl)
+ call void @llvm.coro.destroy(i8* %hdl)
+ %0 = call i8 @llvm.coro.suspend(token %save, i1 false)
+ switch i8 %0, label %suspend [i8 0, label %resume
+ i8 1, label %pre.cleanup]
+resume:
+ call void @print(i32 0)
+ br label %cleanup
+
+pre.cleanup:
+ call void @print(i32 1)
+ br label %cleanup
+
+cleanup:
+ %mem = call i8* @llvm.coro.free(token %id, i8* %hdl)
+ call void @free(i8* %mem)
+ br label %suspend
+suspend:
+ call void @llvm.coro.end(i8* %hdl, i1 false)
+ ret void
+}
+
+; SimplifySuspendPoint won't be able to simplify if it detects that there are
+; other calls between coro.save and coro.suspend. They potentially can call
+; resume or destroy, so we should not simplify this suspend point.
+;
+; CHECK-LABEL: define void @cannot_simplify(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: call i8* @malloc
+
+define void @cannot_simplify() {
+entry:
+ %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
+ %need.dyn.alloc = call i1 @llvm.coro.alloc(token %id)
+ br i1 %need.dyn.alloc, label %dyn.alloc, label %coro.begin
+dyn.alloc:
+ %size = call i32 @llvm.coro.size.i32()
+ %alloc = call i8* @malloc(i32 %size)
+ br label %coro.begin
+coro.begin:
+ %phi = phi i8* [ null, %entry ], [ %alloc, %dyn.alloc ]
+ %hdl = call noalias i8* @llvm.coro.begin(token %id, i8* %phi)
+ br label %body
+body:
+ %save = call token @llvm.coro.save(i8* %hdl)
+ call void @foo()
+ call void @llvm.coro.destroy(i8* %hdl)
+ %0 = call i8 @llvm.coro.suspend(token %save, i1 false)
+ switch i8 %0, label %suspend [i8 0, label %resume
+ i8 1, label %pre.cleanup]
+resume:
+ call void @print(i32 0)
+ br label %cleanup
+
+pre.cleanup:
+ call void @print(i32 1)
+ br label %cleanup
+
+cleanup:
+ %mem = call i8* @llvm.coro.free(token %id, i8* %hdl)
+ call void @free(i8* %mem)
+ br label %suspend
+suspend:
+ call void @llvm.coro.end(i8* %hdl, i1 false)
+ ret void
+}
+
+declare i8* @malloc(i32)
+declare void @free(i8*)
+declare void @print(i32)
+declare void @foo()
+
+declare token @llvm.coro.id(i32, i8*, i8*, i8*)
+declare i1 @llvm.coro.alloc(token)
+declare i32 @llvm.coro.size.i32()
+declare i8* @llvm.coro.begin(token, i8*)
+declare token @llvm.coro.save(i8* %hdl)
+declare i8 @llvm.coro.suspend(token, i1)
+declare i8* @llvm.coro.free(token, i8*)
+declare void @llvm.coro.end(i8*, i1)
+
+declare void @llvm.coro.resume(i8*)
+declare void @llvm.coro.destroy(i8*)
diff --git a/test/Transforms/Coroutines/phi-coro-end.ll b/test/Transforms/Coroutines/phi-coro-end.ll
new file mode 100644
index 000000000000..e2529412e72c
--- /dev/null
+++ b/test/Transforms/Coroutines/phi-coro-end.ll
@@ -0,0 +1,48 @@
+; Verify that we correctly handle suspend when the coro.end block contains phi
+; RUN: opt < %s -O2 -enable-coroutines -S | FileCheck %s
+
+define i8* @f(i32 %n) {
+entry:
+ %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
+ %size = call i32 @llvm.coro.size.i32()
+ %alloc = call i8* @malloc(i32 %size)
+ %hdl = call i8* @llvm.coro.begin(token %id, i8* %alloc)
+ %0 = call i8 @llvm.coro.suspend(token none, i1 false)
+ switch i8 %0, label %suspend [i8 0, label %cleanup i8 1, label %cleanup]
+
+cleanup:
+ %mem = call i8* @llvm.coro.free(token %id, i8* %hdl)
+ call void @free(i8* %mem)
+ br label %suspend
+
+suspend:
+ %r = phi i32 [%n, %entry], [1, %cleanup]
+ call void @llvm.coro.end(i8* %hdl, i1 false)
+ call void @print(i32 %r)
+ ret i8* %hdl
+}
+
+; CHECK-LABEL: @main
+define i32 @main() {
+entry:
+ %hdl = call i8* @f(i32 4)
+ call void @llvm.coro.resume(i8* %hdl)
+ ret i32 0
+;CHECK: call void @print(i32 4)
+;CHECK: ret i32 0
+}
+
+declare i8* @llvm.coro.alloc()
+declare i32 @llvm.coro.size.i32()
+declare i8* @llvm.coro.free(token, i8*)
+declare i8 @llvm.coro.suspend(token, i1)
+declare void @llvm.coro.resume(i8*)
+declare void @llvm.coro.destroy(i8*)
+
+declare token @llvm.coro.id(i32, i8*, i8*, i8*)
+declare i8* @llvm.coro.begin(token, i8*)
+declare void @llvm.coro.end(i8*, i1)
+
+declare noalias i8* @malloc(i32)
+declare void @print(i32)
+declare void @free(i8*)
diff --git a/test/Transforms/Coroutines/restart-trigger.ll b/test/Transforms/Coroutines/restart-trigger.ll
new file mode 100644
index 000000000000..2240f8fa6323
--- /dev/null
+++ b/test/Transforms/Coroutines/restart-trigger.ll
@@ -0,0 +1,43 @@
+; Verifies that restart trigger forces IPO pipelines restart and the same
+; coroutine is looked at by CoroSplit pass twice.
+; REQUIRES: asserts
+; RUN: opt < %s -S -O0 -enable-coroutines -debug-only=coro-split 2>&1 | FileCheck %s
+; RUN: opt < %s -S -O1 -enable-coroutines -debug-only=coro-split 2>&1 | FileCheck %s
+
+; CHECK: CoroSplit: Processing coroutine 'f' state: 0
+; CHECK-NEXT: CoroSplit: Processing coroutine 'f' state: 1
+
+define void @f() {
+ %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
+ %size = call i32 @llvm.coro.size.i32()
+ %alloc = call i8* @malloc(i32 %size)
+ %hdl = call i8* @llvm.coro.begin(token %id, i8* %alloc)
+ call void @print(i32 0)
+ %s1 = call i8 @llvm.coro.suspend(token none, i1 false)
+ switch i8 %s1, label %suspend [i8 0, label %resume
+ i8 1, label %cleanup]
+resume:
+ call void @print(i32 1)
+ br label %cleanup
+
+cleanup:
+ %mem = call i8* @llvm.coro.free(token %id, i8* %hdl)
+ call void @free(i8* %mem)
+ br label %suspend
+suspend:
+ call void @llvm.coro.end(i8* %hdl, i1 0)
+ ret void
+}
+
+declare token @llvm.coro.id(i32, i8*, i8*, i8*)
+declare i8* @llvm.coro.begin(token, i8*)
+declare i8* @llvm.coro.free(token, i8*)
+declare i32 @llvm.coro.size.i32()
+declare i8 @llvm.coro.suspend(token, i1)
+declare void @llvm.coro.resume(i8*)
+declare void @llvm.coro.destroy(i8*)
+declare void @llvm.coro.end(i8*, i1)
+
+declare noalias i8* @malloc(i32)
+declare void @print(i32)
+declare void @free(i8*)
diff --git a/test/Transforms/Coroutines/smoketest.ll b/test/Transforms/Coroutines/smoketest.ll
new file mode 100644
index 000000000000..925e45be7c9a
--- /dev/null
+++ b/test/Transforms/Coroutines/smoketest.ll
@@ -0,0 +1,25 @@
+; Test that all coroutine passes run in the correct order at all optimization
+; levels and -enable-coroutines adds coroutine passes to the pipeline.
+;
+; RUN: opt < %s -disable-output -enable-coroutines -debug-pass=Arguments -O0 2>&1 | FileCheck %s
+; RUN: opt < %s -disable-output -enable-coroutines -debug-pass=Arguments -O1 2>&1 | FileCheck %s
+; RUN: opt < %s -disable-output -enable-coroutines -debug-pass=Arguments -O2 2>&1 | FileCheck %s
+; RUN: opt < %s -disable-output -enable-coroutines -debug-pass=Arguments -O3 2>&1 | FileCheck %s
+; RUN: opt < %s -disable-output -enable-coroutines -debug-pass=Arguments \
+; RUN: -coro-early -coro-split -coro-elide -coro-cleanup 2>&1 | FileCheck %s
+; RUN: opt < %s -disable-output -debug-pass=Arguments 2>&1 \
+; RUN: | FileCheck %s -check-prefix=NOCORO
+
+; CHECK: coro-early
+; CHECK: coro-split
+; CHECK: coro-elide
+; CHECK: coro-cleanup
+
+; NOCORO-NOT: coro-early
+; NOCORO-NOT: coro-split
+; NOCORO-NOT: coro-elide
+; NOCORO-NOT: coro-cleanup
+
+define void @foo() {
+ ret void
+}