; 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*)