Skip to content

Commit 9edc5ee

Browse files
committed
Don't use lifetime.start based alloca localization for ABI.Async/ABI.Retcon
Infinite loops can lead to an IR representation where the lifetime.end intrinsice is missing. The code to do lifetime based optimization then fails to see that an address escapes (is life) accross a supspend. Eventually, we could detect such situations and disable it under more narrow circumstances. For now, do the correct thing. rdar://83635953 Differential Revision: https://reviews.llvm.org/D110949
1 parent 5c40704 commit 9edc5ee

File tree

2 files changed

+105
-4
lines changed

2 files changed

+105
-4
lines changed

llvm/lib/Transforms/Coroutines/CoroFrame.cpp

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1240,8 +1240,10 @@ namespace {
12401240
struct AllocaUseVisitor : PtrUseVisitor<AllocaUseVisitor> {
12411241
using Base = PtrUseVisitor<AllocaUseVisitor>;
12421242
AllocaUseVisitor(const DataLayout &DL, const DominatorTree &DT,
1243-
const CoroBeginInst &CB, const SuspendCrossingInfo &Checker)
1244-
: PtrUseVisitor(DL), DT(DT), CoroBegin(CB), Checker(Checker) {}
1243+
const CoroBeginInst &CB, const SuspendCrossingInfo &Checker,
1244+
bool ShouldUseLifetimeStartInfo)
1245+
: PtrUseVisitor(DL), DT(DT), CoroBegin(CB), Checker(Checker),
1246+
ShouldUseLifetimeStartInfo(ShouldUseLifetimeStartInfo) {}
12451247

12461248
void visit(Instruction &I) {
12471249
Users.insert(&I);
@@ -1389,6 +1391,7 @@ struct AllocaUseVisitor : PtrUseVisitor<AllocaUseVisitor> {
13891391
SmallPtrSet<Instruction *, 4> Users{};
13901392
SmallPtrSet<IntrinsicInst *, 2> LifetimeStarts{};
13911393
bool MayWriteBeforeCoroBegin{false};
1394+
bool ShouldUseLifetimeStartInfo{true};
13921395

13931396
mutable llvm::Optional<bool> ShouldLiveOnFrame{};
13941397

@@ -1397,7 +1400,7 @@ struct AllocaUseVisitor : PtrUseVisitor<AllocaUseVisitor> {
13971400
// more precise. We look at every pair of lifetime.start intrinsic and
13981401
// every basic block that uses the pointer to see if they cross suspension
13991402
// points. The uses cover both direct uses as well as indirect uses.
1400-
if (!LifetimeStarts.empty()) {
1403+
if (ShouldUseLifetimeStartInfo && !LifetimeStarts.empty()) {
14011404
for (auto *I : Users)
14021405
for (auto *S : LifetimeStarts)
14031406
if (Checker.isDefinitionAcrossSuspend(*S, I))
@@ -2489,8 +2492,15 @@ static void collectFrameAllocas(Function &F, coro::Shape &Shape,
24892492
continue;
24902493
}
24912494
DominatorTree DT(F);
2495+
// The code that uses lifetime.start intrinsic does not work for functions
2496+
// with loops without exit. Disable it on ABIs we know to generate such
2497+
// code.
2498+
bool ShouldUseLifetimeStartInfo =
2499+
(Shape.ABI != coro::ABI::Async && Shape.ABI != coro::ABI::Retcon &&
2500+
Shape.ABI != coro::ABI::RetconOnce);
24922501
AllocaUseVisitor Visitor{F.getParent()->getDataLayout(), DT,
2493-
*Shape.CoroBegin, Checker};
2502+
*Shape.CoroBegin, Checker,
2503+
ShouldUseLifetimeStartInfo};
24942504
Visitor.visitPtr(*AI);
24952505
if (!Visitor.getShouldLiveOnFrame())
24962506
continue;
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
; RUN: opt < %s -enable-coroutines -passes='default<O0>' -S | FileCheck --check-prefixes=CHECK %s
2+
; RUN: opt < %s -enable-coroutines -O0 -S | FileCheck --check-prefixes=CHECK %s
3+
4+
target datalayout = "p:64:64:64"
5+
6+
%async.task = type { i64 }
7+
%async.actor = type { i64 }
8+
%async.fp = type <{ i32, i32 }>
9+
10+
%async.ctxt = type { i8*, void (i8*, %async.task*, %async.actor*)* }
11+
12+
; The async callee.
13+
@my_other_async_function_fp = external global <{ i32, i32 }>
14+
declare void @my_other_async_function(i8* %async.ctxt)
15+
16+
@my_async_function_fp = constant <{ i32, i32 }>
17+
<{ i32 trunc ( ; Relative pointer to async function
18+
i64 sub (
19+
i64 ptrtoint (void (i8*)* @my_async_function to i64),
20+
i64 ptrtoint (i32* getelementptr inbounds (<{ i32, i32 }>, <{ i32, i32 }>* @my_async_function_fp, i32 0, i32 1) to i64)
21+
)
22+
to i32),
23+
i32 128 ; Initial async context size without space for frame
24+
}>
25+
26+
define swiftcc void @my_other_async_function_fp.apply(i8* %fnPtr, i8* %async.ctxt) {
27+
%callee = bitcast i8* %fnPtr to void(i8*)*
28+
tail call swiftcc void %callee(i8* %async.ctxt)
29+
ret void
30+
}
31+
32+
declare void @escape(i64*)
33+
declare void @store_resume(i8*)
34+
define i8* @resume_context_projection(i8* %ctxt) {
35+
entry:
36+
%resume_ctxt_addr = bitcast i8* %ctxt to i8**
37+
%resume_ctxt = load i8*, i8** %resume_ctxt_addr, align 8
38+
ret i8* %resume_ctxt
39+
}
40+
41+
; The address of alloca escapes but the analysis based on lifetimes fails to see
42+
; that it can't localize this alloca.
43+
; CHECK: define swiftcc void @my_async_function(i8* swiftasync %async.ctxt) {
44+
; CHECK: entry:
45+
; CHECK-NOT: ret
46+
; CHECK-NOT: [[ESCAPED_ADDR:%.*]] = alloca i64, align 8
47+
; CHECK: ret
48+
define swiftcc void @my_async_function(i8* swiftasync %async.ctxt) {
49+
entry:
50+
%escaped_addr = alloca i64
51+
52+
%id = call token @llvm.coro.id.async(i32 128, i32 16, i32 0,
53+
i8* bitcast (<{i32, i32}>* @my_async_function_fp to i8*))
54+
%hdl = call i8* @llvm.coro.begin(token %id, i8* null)
55+
%ltb = bitcast i64* %escaped_addr to i8*
56+
call void @llvm.lifetime.start.p0i8(i64 4, i8* %ltb)
57+
call void @escape(i64* %escaped_addr)
58+
br label %callblock
59+
60+
61+
callblock:
62+
63+
%callee_context = call i8* @context_alloc()
64+
65+
%resume.func_ptr = call i8* @llvm.coro.async.resume()
66+
call void @store_resume(i8* %resume.func_ptr)
67+
%resume_proj_fun = bitcast i8*(i8*)* @resume_context_projection to i8*
68+
%callee = bitcast void(i8*)* @asyncSuspend to i8*
69+
%res = call {i8*, i8*, i8*} (i32, i8*, i8*, ...) @llvm.coro.suspend.async(i32 0,
70+
i8* %resume.func_ptr,
71+
i8* %resume_proj_fun,
72+
void (i8*, i8*)* @my_other_async_function_fp.apply,
73+
i8* %callee, i8* %callee_context)
74+
br label %callblock
75+
}
76+
77+
declare { i8*, i8*, i8*, i8* } @llvm.coro.suspend.async.sl_p0i8p0i8p0i8p0i8s(i32, i8*, i8*, ...)
78+
declare i8* @llvm.coro.prepare.async(i8*)
79+
declare token @llvm.coro.id.async(i32, i32, i32, i8*)
80+
declare i8* @llvm.coro.begin(token, i8*)
81+
declare i1 @llvm.coro.end.async(i8*, i1, ...)
82+
declare i1 @llvm.coro.end(i8*, i1)
83+
declare {i8*, i8*, i8*} @llvm.coro.suspend.async(i32, i8*, i8*, ...)
84+
declare i8* @context_alloc()
85+
declare void @llvm.coro.async.context.dealloc(i8*)
86+
declare swiftcc void @asyncSuspend(i8*)
87+
declare i8* @llvm.coro.async.resume()
88+
declare void @llvm.coro.async.size.replace(i8*, i8*)
89+
declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) #0
90+
declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture) #0
91+
attributes #0 = { argmemonly nofree nosync nounwind willreturn }

0 commit comments

Comments
 (0)