diff options
| author | Dimitry Andric <dim@FreeBSD.org> | 2017-01-02 19:17:04 +0000 |
|---|---|---|
| committer | Dimitry Andric <dim@FreeBSD.org> | 2017-01-02 19:17:04 +0000 |
| commit | b915e9e0fc85ba6f398b3fab0db6a81a8913af94 (patch) | |
| tree | 98b8f811c7aff2547cab8642daf372d6c59502fb /test/Transforms/Coroutines | |
| parent | 6421cca32f69ac849537a3cff78c352195e99f1b (diff) | |
Notes
Diffstat (limited to 'test/Transforms/Coroutines')
| -rw-r--r-- | test/Transforms/Coroutines/ArgAddr.ll | 67 | ||||
| -rw-r--r-- | test/Transforms/Coroutines/coro-cleanup.ll | 18 | ||||
| -rw-r--r-- | test/Transforms/Coroutines/coro-early.ll | 41 | ||||
| -rw-r--r-- | test/Transforms/Coroutines/coro-elide.ll | 112 | ||||
| -rw-r--r-- | test/Transforms/Coroutines/coro-heap-elide.ll | 127 | ||||
| -rw-r--r-- | test/Transforms/Coroutines/coro-split-00.ll | 79 | ||||
| -rw-r--r-- | test/Transforms/Coroutines/coro-split-01.ll | 56 | ||||
| -rw-r--r-- | test/Transforms/Coroutines/coro-split-02.ll | 54 | ||||
| -rw-r--r-- | test/Transforms/Coroutines/coro-split-dbg.ll | 119 | ||||
| -rw-r--r-- | test/Transforms/Coroutines/ex0.ll | 59 | ||||
| -rw-r--r-- | test/Transforms/Coroutines/ex1.ll | 54 | ||||
| -rw-r--r-- | test/Transforms/Coroutines/ex2.ll | 63 | ||||
| -rw-r--r-- | test/Transforms/Coroutines/ex3.ll | 60 | ||||
| -rw-r--r-- | test/Transforms/Coroutines/ex4.ll | 71 | ||||
| -rw-r--r-- | test/Transforms/Coroutines/ex5.ll | 73 | ||||
| -rw-r--r-- | test/Transforms/Coroutines/no-suspend.ll | 189 | ||||
| -rw-r--r-- | test/Transforms/Coroutines/phi-coro-end.ll | 48 | ||||
| -rw-r--r-- | test/Transforms/Coroutines/restart-trigger.ll | 43 | ||||
| -rw-r--r-- | test/Transforms/Coroutines/smoketest.ll | 25 |
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 +} |
