; 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 \
; RUN: -passes='cgscc(inline,function(coro-elide,instsimplify,simplifycfg))' \
; RUN: -aa-pipeline='basic-aa' | FileCheck %s
declare void @print(i32) nounwind
%f.frame = type {i32}
declare void @bar(ptr)
declare fastcc void @f.resume(ptr align 4 dereferenceable(4))
declare fastcc void @f.destroy(ptr)
declare fastcc void @f.cleanup(ptr)
declare void @may_throw()
declare ptr @CustomAlloc(i32)
declare void @CustomFree(ptr)
@f.resumers = internal constant [3 x ptr]
[ptr @f.resume, ptr @f.destroy, ptr @f.cleanup]
; a coroutine start function
define ptr @f() personality ptr null {
entry:
%id = call token @llvm.coro.id(i32 0, ptr null,
ptr @f,
ptr @f.resumers)
%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 ptr @CustomAlloc(i32 4)
br label %coro.begin
coro.begin:
%phi = phi ptr [ null, %entry ], [ %alloc, %dyn.alloc ]
%hdl = call ptr @llvm.coro.begin(token %id, ptr %phi)
invoke void @may_throw()
to label %ret unwind label %ehcleanup
ret:
ret ptr %hdl
ehcleanup:
%tok = cleanuppad within none []
%mem = call ptr @llvm.coro.free(token %id, ptr %hdl)
%need.dyn.free = icmp ne ptr %mem, null
br i1 %need.dyn.free, label %dyn.free, label %if.end
dyn.free:
call void @CustomFree(ptr %mem)
br label %if.end
if.end:
cleanupret from %tok unwind to caller
}
; CHECK-LABEL: @callResume(
define void @callResume() {
entry:
; CHECK: alloca [4 x i8], align 4
; CHECK-NOT: coro.begin
; CHECK-NOT: CustomAlloc
; CHECK: call void @may_throw()
%hdl = call ptr @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(ptr %hdl)
; CHECK: tail call void @bar(
tail call void @bar(ptr null)
; CHECK-NEXT: call fastcc void @f.resume(ptr %0)
%0 = call ptr @llvm.coro.subfn.addr(ptr %hdl, i8 0)
call fastcc void %0(ptr %hdl)
; CHECK-NEXT: call fastcc void @f.cleanup(ptr %0)
%1 = call ptr @llvm.coro.subfn.addr(ptr %hdl, i8 1)
call fastcc void %1(ptr %hdl)
; CHECK-NEXT: ret void
ret void
}
; CHECK-LABEL: @callResume_with_coro_suspend_1(
define void @callResume_with_coro_suspend_1() {
entry:
; CHECK: alloca [4 x i8], align 4
; CHECK-NOT: coro.begin
; CHECK-NOT: CustomAlloc
; CHECK: call void @may_throw()
%hdl = call ptr @f()
; CHECK-NEXT: call fastcc void @f.resume(ptr %0)
%0 = call ptr @llvm.coro.subfn.addr(ptr %hdl, i8 0)
call fastcc void %0(ptr %hdl)
%1 = call token @llvm.coro.save(ptr %hdl)
%2 = call i8 @llvm.coro.suspend(token %1, i1 false)
switch i8 %2, label %coro.ret [
i8 0, label %final.suspend
i8 1, label %cleanups
]
; CHECK-LABEL: final.suspend:
final.suspend:
; CHECK-NEXT: call fastcc void @f.cleanup(ptr %0)
%3 = call ptr @llvm.coro.subfn.addr(ptr %hdl, i8 1)
call fastcc void %3(ptr %hdl)
%4 = call token @llvm.coro.save(ptr %hdl)
%5 = call i8 @llvm.coro.suspend(token %4, i1 true)
switch i8 %5, label %coro.ret [
i8 0, label %coro.ret
i8 1, label %cleanups
]
; CHECK-LABEL: cleanups:
cleanups:
; CHECK-NEXT: call fastcc void @f.cleanup(ptr %0)
%6 = call ptr @llvm.coro.subfn.addr(ptr %hdl, i8 1)
call fastcc void %6(ptr %hdl)
br label %coro.ret
; CHECK-LABEL: coro.ret:
coro.ret:
; CHECK-NEXT: ret void
ret void
}
; CHECK-LABEL: @callResume_with_coro_suspend_2(
define void @callResume_with_coro_suspend_2() personality ptr null {
entry:
; CHECK: alloca [4 x i8], align 4
; CHECK-NOT: coro.begin
; CHECK-NOT: CustomAlloc
; CHECK: call void @may_throw()
%hdl = call ptr @f()
%0 = call token @llvm.coro.save(ptr %hdl)
; CHECK: invoke fastcc void @f.resume(ptr %0)
%1 = call ptr @llvm.coro.subfn.addr(ptr %hdl, i8 0)
invoke fastcc void %1(ptr %hdl)
to label %invoke.cont1 unwind label %lpad
; CHECK-LABEL: invoke.cont1:
invoke.cont1:
%2 = call i8 @llvm.coro.suspend(token %0, i1 false)
switch i8 %2, label %coro.ret [
i8 0, label %final.ready
i8 1, label %cleanups
]
; CHECK-LABEL: lpad:
lpad:
%3 = landingpad { ptr, i32 }
catch ptr null
; CHECK: call fastcc void @f.cleanup(ptr %0)
%4 = call ptr @llvm.coro.subfn.addr(ptr %hdl, i8 1)
call fastcc void %4(ptr %hdl)
br label %final.suspend
; CHECK-LABEL: final.ready:
final.ready:
; CHECK-NEXT: call fastcc void @f.cleanup(ptr %0)
%5 = call ptr @llvm.coro.subfn.addr(ptr %hdl, i8 1)
call fastcc void %5(ptr %hdl)
br label %final.suspend
; CHECK-LABEL: final.suspend:
final.suspend:
%6 = call token @llvm.coro.save(ptr %hdl)
%7 = call i8 @llvm.coro.suspend(token %6, i1 true)
switch i8 %7, label %coro.ret [
i8 0, label %coro.ret
i8 1, label %cleanups
]
; CHECK-LABEL: cleanups:
cleanups:
; CHECK-NEXT: call fastcc void @f.cleanup(ptr %0)
%8 = call ptr @llvm.coro.subfn.addr(ptr %hdl, i8 1)
call fastcc void %8(ptr %hdl)
br label %coro.ret
; CHECK-LABEL: coro.ret:
coro.ret:
; CHECK-NEXT: ret void
ret void
}
; CHECK-LABEL: @callResume_with_coro_suspend_3(
define void @callResume_with_coro_suspend_3(i8 %cond) {
entry:
; CHECK: alloca [4 x i8], align 4
switch i8 %cond, label %coro.ret [
i8 0, label %init.suspend
i8 1, label %coro.ret
]
init.suspend:
; CHECK-NOT: llvm.coro.begin
; CHECK-NOT: CustomAlloc
; CHECK: call void @may_throw()
%hdl = call ptr @f()
; CHECK-NEXT: call fastcc void @f.resume(ptr %0)
%0 = call ptr @llvm.coro.subfn.addr(ptr %hdl, i8 0)
call fastcc void %0(ptr %hdl)
%1 = call token @llvm.coro.save(ptr %hdl)
%2 = call i8 @llvm.coro.suspend(token %1, i1 false)
switch i8 %2, label %coro.ret [
i8 0, label %final.suspend
i8 1, label %cleanups
]
; CHECK-LABEL: final.suspend:
final.suspend:
; CHECK-NEXT: call fastcc void @f.cleanup(ptr %0)
%3 = call ptr @llvm.coro.subfn.addr(ptr %hdl, i8 1)
call fastcc void %3(ptr %hdl)
%4 = call token @llvm.coro.save(ptr %hdl)
%5 = call i8 @llvm.coro.suspend(token %4, i1 true)
switch i8 %5, label %coro.ret [
i8 0, label %coro.ret
i8 1, label %cleanups
]
; CHECK-LABEL: cleanups:
cleanups:
; CHECK-NEXT: call fastcc void @f.cleanup(ptr %0)
%6 = call ptr @llvm.coro.subfn.addr(ptr %hdl, i8 1)
call fastcc void %6(ptr %hdl)
br label %coro.ret
; CHECK-LABEL: coro.ret:
coro.ret:
; CHECK-NEXT: ret void
ret void
}
; CHECK-LABEL: @callResume_PR34897_no_elision(
define void @callResume_PR34897_no_elision(i1 %cond) {
; CHECK-LABEL: entry:
entry:
; CHECK: call ptr @CustomAlloc(
%hdl = call ptr @f()
; CHECK: tail call void @bar(
tail call void @bar(ptr %hdl)
; CHECK: tail call void @bar(
tail call void @bar(ptr null)
br i1 %cond, label %if.then, label %if.else
; CHECK-LABEL: if.then:
if.then:
; CHECK: call fastcc void @f.resume(ptr
%0 = call ptr @llvm.coro.subfn.addr(ptr %hdl, i8 0)
call fastcc void %0(ptr %hdl)
; CHECK-NEXT: call fastcc void @f.destroy(ptr
%1 = call ptr @llvm.coro.subfn.addr(ptr %hdl, i8 1)
call fastcc void %1(ptr %hdl)
br label %return
if.else:
br label %return
; CHECK-LABEL: return:
return:
; CHECK: ret void
ret void
}
; CHECK-LABEL: @callResume_PR34897_elision(
define void @callResume_PR34897_elision(i1 %cond) {
; CHECK-LABEL: entry:
entry:
; CHECK: alloca [4 x i8], align 4
; CHECK: tail call void @bar(
tail call void @bar(ptr null)
br i1 %cond, label %if.then, label %if.else
if.then:
; CHECK-NOT: CustomAlloc
; CHECK: call void @may_throw()
%hdl = call ptr @f()
; CHECK: call void @bar(
tail call void @bar(ptr %hdl)
; CHECK: call fastcc void @f.resume(ptr
%0 = call ptr @llvm.coro.subfn.addr(ptr %hdl, i8 0)
call fastcc void %0(ptr %hdl)
; CHECK-NEXT: call fastcc void @f.cleanup(ptr
%1 = call ptr @llvm.coro.subfn.addr(ptr %hdl, i8 1)
call fastcc void %1(ptr %hdl)
br label %return
if.else:
br label %return
; CHECK-LABEL: return:
return:
; CHECK: 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 ptr @f_no_elision() personality ptr null {
entry:
%id = call token @llvm.coro.id(i32 0, ptr null,
ptr @f_no_elision,
ptr @f.resumers)
%alloc = call ptr @CustomAlloc(i32 4)
%hdl = call ptr @llvm.coro.begin(token %id, ptr %alloc)
ret ptr %hdl
}
; CHECK-LABEL: @callResume_no_elision(
define void @callResume_no_elision() {
entry:
; CHECK: call ptr @CustomAlloc(
%hdl = call ptr @f_no_elision()
; Tail call should remain tail calls
; CHECK: tail call void @bar(
tail call void @bar(ptr %hdl)
; CHECK: tail call void @bar(
tail call void @bar(ptr null)
; CHECK-NEXT: call fastcc void @f.resume(ptr
%0 = call ptr @llvm.coro.subfn.addr(ptr %hdl, i8 0)
call fastcc void %0(ptr %hdl)
; CHECK-NEXT: call fastcc void @f.destroy(ptr
%1 = call ptr @llvm.coro.subfn.addr(ptr %hdl, i8 1)
call fastcc void %1(ptr %hdl)
; CHECK-NEXT: ret void
ret void
}
declare token @llvm.coro.id(i32, ptr, ptr, ptr)
declare i1 @llvm.coro.alloc(token)
declare ptr @llvm.coro.free(token, ptr)
declare ptr @llvm.coro.begin(token, ptr)
declare ptr @llvm.coro.frame(token)
declare ptr @llvm.coro.subfn.addr(ptr, i8)
declare i8 @llvm.coro.suspend(token, i1)
declare token @llvm.coro.save(ptr)