Skip to content

Commit 3fddf2f

Browse files
committed
[Coroutines] Allocas used after @coro.end should not go in the coro frame
Because the coro frame may not exist anymore at that point. See the bug for a motivating example. Fixes #127499
1 parent 6d86a8a commit 3fddf2f

File tree

4 files changed

+117
-11
lines changed

4 files changed

+117
-11
lines changed

llvm/lib/Transforms/Coroutines/SpillUtils.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,16 @@ struct AllocaUseVisitor : PtrUseVisitor<AllocaUseVisitor> {
162162

163163
void visit(Instruction &I) {
164164
Users.insert(&I);
165+
166+
for (const Instruction *CoroEnd : CoroShape.CoroEnds) {
167+
if (DT.dominates(CoroEnd, &I)) {
168+
// Data accessed after coro.end (such as the return object and callee
169+
// destroyed parameters) should always go on the stack.
170+
ShouldLiveOnFrame = false;
171+
return;
172+
}
173+
}
174+
165175
Base::visit(I);
166176
// If the pointer is escaped prior to CoroBegin, we have to assume it would
167177
// be written into before CoroBegin as well.
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
; RUN: opt < %s -passes='cgscc(coro-split),simplifycfg,early-cse' -S | FileCheck %s
2+
3+
; Inspired by coro-param-copy.ll and the following:
4+
;
5+
; $ clang++ -std=c++20 -S -emit-llvm -g0 -fno-exceptions \
6+
; -mllvm -disable-llvm-optzns -fno-discard-value-names a.cc -o -
7+
;
8+
; #include <coroutine>
9+
;
10+
; class BasicCoroutine {
11+
; public:
12+
; struct Promise {
13+
; BasicCoroutine get_return_object();
14+
; void unhandled_exception() noexcept;
15+
; void return_void() noexcept;
16+
; std::suspend_never initial_suspend() noexcept;
17+
; std::suspend_never final_suspend() noexcept;
18+
; };
19+
; using promise_type = Promise;
20+
; };
21+
;
22+
; struct [[clang::trivial_abi]] Trivial {
23+
; Trivial(int x) : x(x) {}
24+
; ~Trivial();
25+
; int x;
26+
; };
27+
;
28+
; BasicCoroutine coro(Trivial t) {
29+
; co_return;
30+
; }
31+
;
32+
;
33+
; Check that even though %x.local may escape via use() in the beginning of @f,
34+
; it is not put in the corountine frame, since %x.local is used after
35+
; @llvm.coro.end, at which point the coroutine frame may have been deallocated.
36+
;
37+
; In the program above, a move constructor (or just memcpy) is invoked to copy
38+
; t to a coroutine-local alloca. At the end of the function, t's destructor is
39+
; called because of trivial_abi. At that point, t must not have been stored in
40+
; the coro frame.
41+
42+
; The frame should not contain an i64.
43+
; CHECK: %f.Frame = type { ptr, ptr, i1 }
44+
45+
; Check that we have both uses of %x.local (and they're not using the frame).
46+
; CHECK-LABEL: define ptr @f(i64 %x)
47+
; CHECK: call void @use(ptr %x.local)
48+
; CHECK: call void @use(ptr %x.local)
49+
50+
51+
define ptr @f(i64 %x) presplitcoroutine {
52+
entry:
53+
%x.local = alloca i64
54+
store i64 %x, ptr %x.local
55+
br label %coro.alloc
56+
57+
coro.alloc:
58+
%id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null)
59+
%size = call i32 @llvm.coro.size.i32()
60+
%alloc = call ptr @myAlloc(i32 %size)
61+
%hdl = call ptr @llvm.coro.begin(token %id, ptr %alloc)
62+
call void @use(ptr %x.local)
63+
%0 = call i8 @llvm.coro.suspend(token none, i1 false)
64+
switch i8 %0, label %suspend [i8 0, label %resume
65+
i8 1, label %cleanup]
66+
resume:
67+
br label %cleanup
68+
69+
cleanup:
70+
%mem = call ptr @llvm.coro.free(token %id, ptr %hdl)
71+
call void @free(ptr %mem)
72+
br label %suspend
73+
74+
suspend:
75+
call i1 @llvm.coro.end(ptr %hdl, i1 0, token none)
76+
call void @use(ptr %x.local) ; It better not be on the frame, that's gone.
77+
ret ptr %hdl
78+
}
79+
80+
declare ptr @llvm.coro.free(token, ptr)
81+
declare i32 @llvm.coro.size.i32()
82+
declare i8 @llvm.coro.suspend(token, i1)
83+
declare void @llvm.coro.resume(ptr)
84+
declare void @llvm.coro.destroy(ptr)
85+
86+
declare token @llvm.coro.id(i32, ptr, ptr, ptr)
87+
declare i1 @llvm.coro.alloc(token)
88+
declare ptr @llvm.coro.begin(token, ptr)
89+
declare i1 @llvm.coro.end(ptr, i1, token)
90+
91+
92+
declare noalias ptr @myAlloc(i32)
93+
declare void @use(ptr)
94+
declare void @free(ptr)

llvm/test/Transforms/Coroutines/coro-debug.ll

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ indirect.dest:
6464
br label %coro_Cleanup
6565

6666
coro_Cleanup: ; preds = %sw.epilog, %sw.bb1
67-
%5 = load ptr, ptr %coro_hdl, align 8, !dbg !24
67+
%5 = load ptr, ptr %coro_hdl, align 8, !dbg !24 ; XXX: Here %coro_hdl is used between a coro.suspend and coro.end, which seems wrong?
6868
%6 = call ptr @llvm.coro.free(token %0, ptr %5), !dbg !24
6969
call void @free(ptr %6), !dbg !24
7070
call void @llvm.dbg.declare(metadata i32 %asm_res, metadata !32, metadata !13), !dbg !16
@@ -172,14 +172,14 @@ attributes #7 = { noduplicate }
172172
!32 = !DILocalVariable(name: "inline_asm", scope: !6, file: !7, line: 55, type: !11)
173173

174174
; CHECK: define ptr @f(i32 %x) #0 personality i32 0 !dbg ![[ORIG:[0-9]+]]
175-
; CHECK: define internal fastcc void @f.resume(ptr noundef nonnull align 8 dereferenceable(40) %0) #0 personality i32 0 !dbg ![[RESUME:[0-9]+]]
175+
; CHECK: define internal fastcc void @f.resume(ptr noundef nonnull align 8 dereferenceable(32) %0) #0 personality i32 0 !dbg ![[RESUME:[0-9]+]]
176176
; CHECK: entry.resume:
177177
; CHECK: %[[DBG_PTR:.*]] = alloca ptr
178-
; CHECK: #dbg_declare(ptr %[[DBG_PTR]], ![[RESUME_COROHDL:[0-9]+]], !DIExpression(DW_OP_deref, DW_OP_plus_uconst,
179178
; CHECK: #dbg_declare(ptr %[[DBG_PTR]], ![[RESUME_X:[0-9]+]], !DIExpression(DW_OP_deref, DW_OP_plus_uconst, [[EXPR_TAIL:.*]])
180179
; CHECK: #dbg_declare(ptr %[[DBG_PTR]], ![[RESUME_DIRECT:[0-9]+]], !DIExpression(DW_OP_deref, DW_OP_plus_uconst, [[EXPR_TAIL]])
181180
; CHECK: store ptr {{.*}}, ptr %[[DBG_PTR]]
182-
; CHECK-NOT: alloca ptr
181+
; CHECK: alloca ptr
182+
; CHECK: #dbg_declare(ptr %[[CORO_HDL:.*]], ![[RESUME_COROHDL:[0-9]+]], !DIExpression()
183183
; CHECK: #dbg_declare(i8 0, ![[RESUME_CONST:[0-9]+]], !DIExpression(DW_OP_LLVM_convert, 8, DW_ATE_signed, DW_OP_LLVM_convert, 32, DW_ATE_signed),
184184
; Note that keeping the undef value here could be acceptable, too.
185185
; CHECK-NOT: #dbg_declare(ptr undef, !{{[0-9]+}}, !DIExpression(),
@@ -195,15 +195,15 @@ attributes #7 = { noduplicate }
195195
; CHECK: [[DEFAULT_DEST]]:
196196
; CHECK-NOT: {{.*}}:
197197
; CHECK: #dbg_declare(i32 %[[CALLBR_RES]]
198-
; CHECK: define internal fastcc void @f.destroy(ptr noundef nonnull align 8 dereferenceable(40) %0) #0 personality i32 0 !dbg ![[DESTROY:[0-9]+]]
199-
; CHECK: define internal fastcc void @f.cleanup(ptr noundef nonnull align 8 dereferenceable(40) %0) #0 personality i32 0 !dbg ![[CLEANUP:[0-9]+]]
198+
; CHECK: define internal fastcc void @f.destroy(ptr noundef nonnull align 8 dereferenceable(32) %0) #0 personality i32 0 !dbg ![[DESTROY:[0-9]+]]
199+
; CHECK: define internal fastcc void @f.cleanup(ptr noundef nonnull align 8 dereferenceable(32) %0) #0 personality i32 0 !dbg ![[CLEANUP:[0-9]+]]
200200

201201
; CHECK: ![[ORIG]] = distinct !DISubprogram(name: "f", linkageName: "flink"
202202

203203
; CHECK: ![[RESUME]] = distinct !DISubprogram(name: "f", linkageName: "flink"
204-
; CHECK: ![[RESUME_COROHDL]] = !DILocalVariable(name: "coro_hdl", scope: ![[RESUME]]
205204
; CHECK: ![[RESUME_X]] = !DILocalVariable(name: "x", arg: 1, scope: ![[RESUME]]
206205
; CHECK: ![[RESUME_DIRECT]] = !DILocalVariable(name: "direct_mem", scope: ![[RESUME]]
206+
; CHECK: ![[RESUME_COROHDL]] = !DILocalVariable(name: "coro_hdl", scope: ![[RESUME]]
207207
; CHECK: ![[RESUME_CONST]] = !DILocalVariable(name: "direct_const", scope: ![[RESUME]]
208208
; CHECK: ![[RESUME_DIRECT_VALUE]] = !DILocalVariable(name: "direct_value", scope: ![[RESUME]]
209209

llvm/test/Transforms/Coroutines/coro-lifetime-end.ll

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,16 +50,18 @@ exit:
5050
define void @LifetimeEndAfterCoroEnd() presplitcoroutine {
5151
; CHECK-LABEL: define void @LifetimeEndAfterCoroEnd() {
5252
; CHECK-NEXT: entry:
53+
; CHECK-NEXT: [[TESTVAL:%.*]] = alloca
5354
; CHECK-NEXT: [[ID:%.*]] = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr @LifetimeEndAfterCoroEnd.resumers)
5455
; CHECK-NEXT: [[ALLOC:%.*]] = call ptr @malloc(i64 16)
5556
; CHECK-NEXT: [[VFRAME:%.*]] = call noalias nonnull ptr @llvm.coro.begin(token [[ID]], ptr [[ALLOC]])
5657
; CHECK-NEXT: store ptr @LifetimeEndAfterCoroEnd.resume, ptr [[VFRAME]], align 8
5758
; CHECK-NEXT: [[DESTROY_ADDR:%.*]] = getelementptr inbounds nuw [[LIFETIMEENDAFTERCOROEND_FRAME:%.*]], ptr [[VFRAME]], i32 0, i32 1
5859
; CHECK-NEXT: store ptr @LifetimeEndAfterCoroEnd.destroy, ptr [[DESTROY_ADDR]], align 8
59-
; CHECK-NEXT: [[INDEX_ADDR1:%.*]] = getelementptr inbounds [[LIFETIMEENDAFTERCOROEND_FRAME]], ptr [[VFRAME]], i32 0, i32 2
60-
; CHECK-NEXT: call void @consume.i8.array(ptr [[INDEX_ADDR1]])
61-
; CHECK-NEXT: [[INDEX_ADDR2:%.*]] = getelementptr inbounds nuw [[LIFETIMEENDAFTERCOROEND_FRAME]], ptr [[VFRAME]], i32 0, i32 3
62-
; CHECK-NEXT: store i1 false, ptr [[INDEX_ADDR2]], align 1
60+
; CHECK-NEXT: call void @llvm.lifetime.start.p0(i64 100, ptr [[TESTVAL]])
61+
; CHECK-NEXT: call void @consume.i8.array(ptr [[TESTVAL]])
62+
; CHECK-NEXT: [[INDEX_ADDR1:%.*]] = getelementptr inbounds nuw [[LIFETIMEENDAFTERCOROEND_FRAME]], ptr [[VFRAME]], i32 0, i32 2
63+
; CHECK-NEXT: store i1 false, ptr [[INDEX_ADDR1]], align 1
64+
; CHECK-NEXT: call void @llvm.lifetime.end.p0(i64 100, ptr [[TESTVAL]])
6365
; CHECK-NEXT: ret void
6466
;
6567
entry:

0 commit comments

Comments
 (0)