Compiler projects using llvm
; 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(i8*)

declare fastcc void @f.resume(%f.frame* align 4 dereferenceable(4))
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 [4 x i8], align 4
; 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
}

; 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 i8* @f()

; 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)
  %2 = call token @llvm.coro.save(i8* %hdl)
  %3 = call i8 @llvm.coro.suspend(token %2, i1 false)
  switch i8 %3, label  %coro.ret [
    i8 0, label %final.suspend
    i8 1, label %cleanups
  ]

; CHECK-LABEL: final.suspend:
final.suspend:
; CHECK-NEXT: call fastcc void bitcast (void (%f.frame*)* @f.cleanup to void (i8*)*)(i8* %vFrame)
  %4 = call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 1)
  %5 = bitcast i8* %4 to void (i8*)*
  call fastcc void %5(i8* %hdl)
  %6 = call token @llvm.coro.save(i8* %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 bitcast (void (%f.frame*)* @f.cleanup to void (i8*)*)(i8* %vFrame)
  %8 = call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 1)
  %9 = bitcast i8* %8 to void (i8*)*
  call fastcc void %9(i8* %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 i8* null {
entry:
; CHECK: alloca [4 x i8], align 4
; CHECK-NOT: coro.begin
; CHECK-NOT: CustomAlloc
; CHECK: call void @may_throw()
  %hdl = call i8* @f()

  %0 = call token @llvm.coro.save(i8* %hdl)
; CHECK: invoke fastcc void bitcast (void (%f.frame*)* @f.resume to void (i8*)*)(i8* %vFrame)
  %1 = call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 0)
  %2 = bitcast i8* %1 to void (i8*)*
  invoke fastcc void %2(i8* %hdl)
    to label %invoke.cont1 unwind label %lpad

; CHECK-LABEL: invoke.cont1:
invoke.cont1:
  %3 = call i8 @llvm.coro.suspend(token %0, i1 false)
  switch i8 %3, label  %coro.ret [
    i8 0, label %final.ready
    i8 1, label %cleanups
  ]

; CHECK-LABEL: lpad:
lpad:
  %4 = landingpad { i8*, i32 }
          catch i8* null
; CHECK: call fastcc void bitcast (void (%f.frame*)* @f.cleanup to void (i8*)*)(i8* %vFrame)
  %5 = call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 1)
  %6 = bitcast i8* %5 to void (i8*)*
  call fastcc void %6(i8* %hdl)
  br label %final.suspend

; CHECK-LABEL: final.ready:
final.ready:
; CHECK-NEXT: call fastcc void bitcast (void (%f.frame*)* @f.cleanup to void (i8*)*)(i8* %vFrame)
  %7 = call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 1)
  %8 = bitcast i8* %7 to void (i8*)*
  call fastcc void %8(i8* %hdl)
  br label %final.suspend

; CHECK-LABEL: final.suspend:
final.suspend:
  %9 = call token @llvm.coro.save(i8* %hdl)
  %10 = call i8 @llvm.coro.suspend(token %9, i1 true)
  switch i8 %10, label  %coro.ret [
    i8 0, label %coro.ret
    i8 1, label %cleanups
  ]

; CHECK-LABEL: cleanups:
cleanups:
; CHECK-NEXT: call fastcc void bitcast (void (%f.frame*)* @f.cleanup to void (i8*)*)(i8* %vFrame)
  %11 = call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 1)
  %12 = bitcast i8* %11 to void (i8*)*
  call fastcc void %12(i8* %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 i8* @f()
; 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)
  %2 = call token @llvm.coro.save(i8* %hdl)
  %3 = call i8 @llvm.coro.suspend(token %2, i1 false)
  switch i8 %3, label  %coro.ret [
    i8 0, label %final.suspend
    i8 1, label %cleanups
  ]

; CHECK-LABEL: final.suspend:
final.suspend:
; CHECK-NEXT: call fastcc void bitcast (void (%f.frame*)* @f.cleanup to void (i8*)*)(i8* %vFrame)
  %4 = call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 1)
  %5 = bitcast i8* %4 to void (i8*)*
  call fastcc void %5(i8* %hdl)
  %6 = call token @llvm.coro.save(i8* %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 bitcast (void (%f.frame*)* @f.cleanup to void (i8*)*)(i8* %vFrame)
  %8 = call i8* @llvm.coro.subfn.addr(i8* %hdl, i8 1)
  %9 = bitcast i8* %8 to void (i8*)*
  call fastcc void %9(i8* %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 i8* @CustomAlloc(
  %hdl = call i8* @f()
; CHECK: tail call void @bar(
  tail call void @bar(i8* %hdl)
; CHECK: tail call void @bar(
  tail call void @bar(i8* null)
  br i1 %cond, label %if.then, label %if.else

; CHECK-LABEL: if.then:
if.then:
; CHECK: 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)
  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(i8* null)
  br i1 %cond, label %if.then, label %if.else

if.then:
; CHECK-NOT: CustomAlloc
; CHECK: call void @may_throw()
  %hdl = call i8* @f()
; CHECK: call void @bar(
  tail call void @bar(i8* %hdl)
; CHECK: 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.cleanup 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)
  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 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)
declare i8 @llvm.coro.suspend(token, i1)
declare token @llvm.coro.save(i8*)