Skip to content

Commit 9f77c26

Browse files
authored
[CoroEarly] Hide promise alloca for later passes (llvm#139243)
Currently coroutine promises are modeled as allocas. This is problematic because other middle-end passes will assume promise dead after coroutine suspend, leading to misoptimizations. I propose the front ends remain free to emit and use allocas to model coro promise. At CoroEarly, we will replace all uses of promise alloca with `coro.promise`. Non coroutine passes should only access promise through `coro.promise`. Then at CoroSplit, we will lower `coro.promise` back to promise alloca again. So that it will be correctly collected into coro frame. Note that we do not have to bother maintainers of other middle-end passes. Fix llvm#105595
1 parent d779b8f commit 9f77c26

File tree

5 files changed

+96
-14
lines changed

5 files changed

+96
-14
lines changed

llvm/docs/Coroutines.rst

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2121,10 +2121,11 @@ Coroutine Transformation Passes
21212121
===============================
21222122
CoroEarly
21232123
---------
2124-
The pass CoroEarly lowers coroutine intrinsics that hide the details of the
2125-
structure of the coroutine frame, but, otherwise not needed to be preserved to
2126-
help later coroutine passes. This pass lowers `coro.frame`_, `coro.done`_,
2127-
and `coro.promise`_ intrinsics.
2124+
The CoroEarly pass ensures later middle end passes correctly interpret coroutine
2125+
semantics and lowers coroutine intrinsics that not needed to be preserved to
2126+
help later coroutine passes. This pass lowers `coro.promise`_, `coro.frame`_ and
2127+
`coro.done`_ intrinsics. Afterwards, it replace uses of promise alloca with
2128+
`coro.promise`_ intrinsic.
21282129

21292130
.. _CoroSplit:
21302131

llvm/include/llvm/Transforms/Coroutines/CoroShape.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,17 @@ struct Shape {
7979

8080
// Scan the function and collect the above intrinsics for later processing
8181
void analyze(Function &F, SmallVectorImpl<CoroFrameInst *> &CoroFrames,
82-
SmallVectorImpl<CoroSaveInst *> &UnusedCoroSaves);
82+
SmallVectorImpl<CoroSaveInst *> &UnusedCoroSaves,
83+
CoroPromiseInst *&CoroPromise);
8384
// If for some reason, we were not able to find coro.begin, bailout.
8485
void invalidateCoroutine(Function &F,
8586
SmallVectorImpl<CoroFrameInst *> &CoroFrames);
8687
// Perform ABI related initial transformation
8788
void initABI();
8889
// Remove orphaned and unnecessary intrinsics
8990
void cleanCoroutine(SmallVectorImpl<CoroFrameInst *> &CoroFrames,
90-
SmallVectorImpl<CoroSaveInst *> &UnusedCoroSaves);
91+
SmallVectorImpl<CoroSaveInst *> &UnusedCoroSaves,
92+
CoroPromiseInst *CoroPromise);
9193

9294
// Field indexes for special fields in the switch lowering.
9395
struct SwitchFieldIndex {
@@ -265,13 +267,14 @@ struct Shape {
265267
explicit Shape(Function &F) {
266268
SmallVector<CoroFrameInst *, 8> CoroFrames;
267269
SmallVector<CoroSaveInst *, 2> UnusedCoroSaves;
270+
CoroPromiseInst *CoroPromise = nullptr;
268271

269-
analyze(F, CoroFrames, UnusedCoroSaves);
272+
analyze(F, CoroFrames, UnusedCoroSaves, CoroPromise);
270273
if (!CoroBegin) {
271274
invalidateCoroutine(F, CoroFrames);
272275
return;
273276
}
274-
cleanCoroutine(CoroFrames, UnusedCoroSaves);
277+
cleanCoroutine(CoroFrames, UnusedCoroSaves, CoroPromise);
275278
}
276279
};
277280

llvm/lib/Transforms/Coroutines/CoroEarly.cpp

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class Lowerer : public coro::LowererBase {
3030
void lowerCoroPromise(CoroPromiseInst *Intrin);
3131
void lowerCoroDone(IntrinsicInst *II);
3232
void lowerCoroNoop(IntrinsicInst *II);
33+
void hidePromiseAlloca(CoroIdInst *CoroId, CoroBeginInst *CoroBegin);
3334

3435
public:
3536
Lowerer(Module &M)
@@ -153,6 +154,28 @@ void Lowerer::lowerCoroNoop(IntrinsicInst *II) {
153154
II->eraseFromParent();
154155
}
155156

157+
// Later middle-end passes will assume promise alloca dead after coroutine
158+
// suspend, leading to misoptimizations. We hide promise alloca using
159+
// coro.promise and will lower it back to alloca at CoroSplit.
160+
void Lowerer::hidePromiseAlloca(CoroIdInst *CoroId, CoroBeginInst *CoroBegin) {
161+
auto *PA = CoroId->getPromise();
162+
if (!PA || !CoroBegin)
163+
return;
164+
Builder.SetInsertPoint(*CoroBegin->getInsertionPointAfterDef());
165+
166+
auto *Alignment = Builder.getInt32(PA->getAlign().value());
167+
auto *FromPromise = Builder.getInt1(false);
168+
SmallVector<Value *, 3> Arg{CoroBegin, Alignment, FromPromise};
169+
auto *PI = Builder.CreateIntrinsic(
170+
Builder.getPtrTy(), Intrinsic::coro_promise, Arg, {}, "promise.addr");
171+
PI->setCannotDuplicate();
172+
PA->replaceUsesWithIf(PI, [CoroId](Use &U) {
173+
bool IsBitcast = U == U.getUser()->stripPointerCasts();
174+
bool IsCoroId = U.getUser() == CoroId;
175+
return !IsBitcast && !IsCoroId;
176+
});
177+
}
178+
156179
// Prior to CoroSplit, calls to coro.begin needs to be marked as NoDuplicate,
157180
// as CoroSplit assumes there is exactly one coro.begin. After CoroSplit,
158181
// NoDuplicate attribute will be removed from coro.begin otherwise, it will
@@ -165,6 +188,7 @@ static void setCannotDuplicate(CoroIdInst *CoroId) {
165188

166189
void Lowerer::lowerEarlyIntrinsics(Function &F) {
167190
CoroIdInst *CoroId = nullptr;
191+
CoroBeginInst *CoroBegin = nullptr;
168192
SmallVector<CoroFreeInst *, 4> CoroFrees;
169193
bool HasCoroSuspend = false;
170194
for (Instruction &I : llvm::make_early_inc_range(instructions(F))) {
@@ -175,6 +199,13 @@ void Lowerer::lowerEarlyIntrinsics(Function &F) {
175199
switch (CB->getIntrinsicID()) {
176200
default:
177201
continue;
202+
case Intrinsic::coro_begin:
203+
case Intrinsic::coro_begin_custom_abi:
204+
if (CoroBegin)
205+
report_fatal_error(
206+
"coroutine should have exactly one defining @llvm.coro.begin");
207+
CoroBegin = cast<CoroBeginInst>(&I);
208+
break;
178209
case Intrinsic::coro_free:
179210
CoroFrees.push_back(cast<CoroFreeInst>(&I));
180211
break;
@@ -227,13 +258,16 @@ void Lowerer::lowerEarlyIntrinsics(Function &F) {
227258
}
228259
}
229260

230-
// Make sure that all CoroFree reference the coro.id intrinsic.
231-
// Token type is not exposed through coroutine C/C++ builtins to plain C, so
232-
// we allow specifying none and fixing it up here.
233-
if (CoroId)
261+
if (CoroId) {
262+
// Make sure that all CoroFree reference the coro.id intrinsic.
263+
// Token type is not exposed through coroutine C/C++ builtins to plain C, so
264+
// we allow specifying none and fixing it up here.
234265
for (CoroFreeInst *CF : CoroFrees)
235266
CF->setArgOperand(0, CoroId);
236267

268+
hidePromiseAlloca(CoroId, CoroBegin);
269+
}
270+
237271
// Coroutine suspention could potentially lead to any argument modified
238272
// outside of the function, hence arguments should not have noalias
239273
// attributes.

llvm/lib/Transforms/Coroutines/Coroutines.cpp

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,8 @@ static CoroSaveInst *createCoroSave(CoroBeginInst *CoroBegin,
192192
// Collect "interesting" coroutine intrinsics.
193193
void coro::Shape::analyze(Function &F,
194194
SmallVectorImpl<CoroFrameInst *> &CoroFrames,
195-
SmallVectorImpl<CoroSaveInst *> &UnusedCoroSaves) {
195+
SmallVectorImpl<CoroSaveInst *> &UnusedCoroSaves,
196+
CoroPromiseInst *&CoroPromise) {
196197
clear();
197198

198199
bool HasFinalSuspend = false;
@@ -286,6 +287,11 @@ void coro::Shape::analyze(Function &F,
286287
}
287288
}
288289
break;
290+
case Intrinsic::coro_promise:
291+
assert(CoroPromise == nullptr &&
292+
"CoroEarly must ensure coro.promise unique");
293+
CoroPromise = cast<CoroPromiseInst>(II);
294+
break;
289295
}
290296
}
291297
}
@@ -477,7 +483,7 @@ void coro::AnyRetconABI::init() {
477483

478484
void coro::Shape::cleanCoroutine(
479485
SmallVectorImpl<CoroFrameInst *> &CoroFrames,
480-
SmallVectorImpl<CoroSaveInst *> &UnusedCoroSaves) {
486+
SmallVectorImpl<CoroSaveInst *> &UnusedCoroSaves, CoroPromiseInst *PI) {
481487
// The coro.frame intrinsic is always lowered to the result of coro.begin.
482488
for (CoroFrameInst *CF : CoroFrames) {
483489
CF->replaceAllUsesWith(CoroBegin);
@@ -489,6 +495,13 @@ void coro::Shape::cleanCoroutine(
489495
for (CoroSaveInst *CoroSave : UnusedCoroSaves)
490496
CoroSave->eraseFromParent();
491497
UnusedCoroSaves.clear();
498+
499+
if (PI) {
500+
PI->replaceAllUsesWith(PI->isFromPromise()
501+
? cast<Value>(CoroBegin)
502+
: cast<Value>(getPromiseAlloca()));
503+
PI->eraseFromParent();
504+
}
492505
}
493506

494507
static void propagateCallAttrsFromCallee(CallInst *Call, Function *Callee) {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
; Test that store-load operation that crosses suspension point will not be eliminated by DSE
2+
; Coro result conversion function that attempts to modify promise shall produce this pattern
3+
; RUN: opt < %s -passes='coro-early,dse' -S | FileCheck %s
4+
5+
define void @fn() presplitcoroutine {
6+
%__promise = alloca ptr, align 8
7+
%id = call token @llvm.coro.id(i32 16, ptr %__promise, ptr @fn, ptr null)
8+
%hdl = call ptr @llvm.coro.begin(token %id, ptr null)
9+
; CHECK: %promise.addr = call ptr @llvm.coro.promise(ptr %hdl, i32 8, i1 false)
10+
%save = call token @llvm.coro.save(ptr null)
11+
%sp = call i8 @llvm.coro.suspend(token %save, i1 false)
12+
%flag = icmp ule i8 %sp, 1
13+
br i1 %flag, label %resume, label %suspend
14+
15+
resume:
16+
; CHECK: call void @use_value(ptr %promise.addr)
17+
call void @use_value(ptr %__promise)
18+
br label %suspend
19+
20+
suspend:
21+
; load value when resume
22+
; CHECK: %null = load ptr, ptr %promise.addr, align 8
23+
%null = load ptr, ptr %__promise, align 8
24+
call void @use_value(ptr %null)
25+
; store value when suspend
26+
; CHECK: store ptr null, ptr %promise.addr, align 8
27+
store ptr null, ptr %__promise, align 8
28+
ret void
29+
}
30+
31+
declare void @use_value(ptr)

0 commit comments

Comments
 (0)