[Coroutines] Part15c: Fix coro-split to correctly handle definitions between coro.save and coro.suspend
Summary:
In the case below, %Result.i19 is defined between coro.save and coro.suspend and used after coro.suspend. We need to correctly place such a value into the coroutine frame.
```
%save = call token @llvm.coro.save(i8* null)
%Result.i19 = getelementptr inbounds %"struct.lean_future<int>::Awaiter", %"struct.lean_future<int>::Awaiter"* %ref.tmp7, i64 0, i32 0
%suspend = call i8 @llvm.coro.suspend(token %save, i1 false)
switch i8 %suspend, label %exit [
i8 0, label %await.ready
i8 1, label %exit
]
await.ready:
%val = load i32, i32* %Result.i19
```
Reviewers: majnemer
Subscribers: llvm-commits, mehdi_amini
Differential Revision: https://reviews.llvm.org/D24418
llvm-svn: 282902
This commit is contained in:
@@ -171,19 +171,22 @@ SuspendCrossingInfo::SuspendCrossingInfo(Function &F, coro::Shape &Shape)
|
||||
for (auto *CE : Shape.CoroEnds)
|
||||
getBlockData(CE->getParent()).End = true;
|
||||
|
||||
// Mark all suspend blocks and indicate that kill everything they consume.
|
||||
// Note, that crossing coro.save is used to indicate suspend, as any code
|
||||
// Mark all suspend blocks and indicate that they kill everything they
|
||||
// consume. Note, that crossing coro.save also requires a spill, as any code
|
||||
// between coro.save and coro.suspend may resume the coroutine and all of the
|
||||
// state needs to be saved by that time.
|
||||
for (CoroSuspendInst *CSI : Shape.CoroSuspends) {
|
||||
CoroSaveInst *const CoroSave = CSI->getCoroSave();
|
||||
BasicBlock *const CoroSaveBB = CoroSave->getParent();
|
||||
auto &B = getBlockData(CoroSaveBB);
|
||||
auto markSuspendBlock = [&](IntrinsicInst* BarrierInst) {
|
||||
BasicBlock *SuspendBlock = BarrierInst->getParent();
|
||||
auto &B = getBlockData(SuspendBlock);
|
||||
B.Suspend = true;
|
||||
B.Kills |= B.Consumes;
|
||||
};
|
||||
for (CoroSuspendInst *CSI : Shape.CoroSuspends) {
|
||||
markSuspendBlock(CSI);
|
||||
markSuspendBlock(CSI->getCoroSave());
|
||||
}
|
||||
|
||||
// Iterate propagating consumes and kills until they stop changing
|
||||
// Iterate propagating consumes and kills until they stop changing.
|
||||
int Iteration = 0;
|
||||
(void)Iteration;
|
||||
|
||||
@@ -533,6 +536,13 @@ static bool materializable(Instruction &V) {
|
||||
isa<BinaryOperator>(&V) || isa<CmpInst>(&V) || isa<SelectInst>(&V);
|
||||
}
|
||||
|
||||
// Check for structural coroutine intrinsics that should not be spilled into
|
||||
// the coroutine frame.
|
||||
static bool isCoroutineStructureIntrinsic(Instruction &I) {
|
||||
return isa<CoroIdInst>(&I) || isa<CoroBeginInst>(&I) ||
|
||||
isa<CoroSaveInst>(&I) || isa<CoroSuspendInst>(&I);
|
||||
}
|
||||
|
||||
// For every use of the value that is across suspend point, recreate that value
|
||||
// after a suspend point.
|
||||
static void rewriteMaterializableInstructions(IRBuilder<> &IRB,
|
||||
@@ -647,10 +657,13 @@ void coro::buildCoroutineFrame(Function &F, Shape &Shape) {
|
||||
Shape.CoroBegin->getId()->clearPromise();
|
||||
}
|
||||
|
||||
// Make sure that all coro.saves and the fallthrough coro.end are in their
|
||||
// own block to simplify the logic of building up SuspendCrossing data.
|
||||
for (CoroSuspendInst *CSI : Shape.CoroSuspends)
|
||||
// Make sure that all coro.save, coro.suspend and the fallthrough coro.end
|
||||
// intrinsics are in their own blocks to simplify the logic of building up
|
||||
// SuspendCrossing data.
|
||||
for (CoroSuspendInst *CSI : Shape.CoroSuspends) {
|
||||
splitAround(CSI->getCoroSave(), "CoroSave");
|
||||
splitAround(CSI, "CoroSuspend");
|
||||
}
|
||||
|
||||
// Put fallthrough CoroEnd into its own block. Note: Shape::buildFrom places
|
||||
// the fallthrough coro.end as the first element of CoroEnds array.
|
||||
@@ -686,18 +699,9 @@ void coro::buildCoroutineFrame(Function &F, Shape &Shape) {
|
||||
Spills.emplace_back(&A, U);
|
||||
|
||||
for (Instruction &I : instructions(F)) {
|
||||
// token returned by CoroSave is an artifact of how we build save/suspend
|
||||
// pairs and should not be part of the Coroutine Frame
|
||||
if (isa<CoroSaveInst>(&I))
|
||||
continue;
|
||||
// CoroBeginInst returns a handle to a coroutine which is passed as a sole
|
||||
// parameter to .resume and .cleanup parts and should not go into coroutine
|
||||
// frame.
|
||||
if (isa<CoroBeginInst>(&I))
|
||||
continue;
|
||||
// A token returned CoroIdInst is used to tie together structural intrinsics
|
||||
// in a coroutine. It should not be saved to the coroutine frame.
|
||||
if (isa<CoroIdInst>(&I))
|
||||
// Values returned from coroutine structure intrinsics should not be part
|
||||
// of the Coroutine Frame.
|
||||
if (isCoroutineStructureIntrinsic(I))
|
||||
continue;
|
||||
// The Coroutine Promise always included into coroutine frame, no need to
|
||||
// check for suspend crossing.
|
||||
|
||||
54
llvm/test/Transforms/Coroutines/coro-split-02.ll
Normal file
54
llvm/test/Transforms/Coroutines/coro-split-02.ll
Normal file
@@ -0,0 +1,54 @@
|
||||
; Tests that coro-split can handle the case when a code after coro.suspend uses
|
||||
; a value produces between coro.save and coro.suspend (%Result.i19)
|
||||
; RUN: opt < %s -coro-split -S | FileCheck %s
|
||||
|
||||
%"struct.std::coroutine_handle" = type { i8* }
|
||||
%"struct.std::coroutine_handle.0" = type { %"struct.std::coroutine_handle" }
|
||||
%"struct.lean_future<int>::Awaiter" = type { i32, %"struct.std::coroutine_handle.0" }
|
||||
|
||||
declare i8* @malloc(i64)
|
||||
declare void @print(i32)
|
||||
|
||||
define void @a() "coroutine.presplit"="1" {
|
||||
entry:
|
||||
%ref.tmp7 = alloca %"struct.lean_future<int>::Awaiter", align 8
|
||||
%id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null)
|
||||
%alloc = call i8* @malloc(i64 16) #3
|
||||
%vFrame = call noalias nonnull i8* @llvm.coro.begin(token %id, i8* %alloc)
|
||||
|
||||
%save = call token @llvm.coro.save(i8* null)
|
||||
%Result.i19 = getelementptr inbounds %"struct.lean_future<int>::Awaiter", %"struct.lean_future<int>::Awaiter"* %ref.tmp7, i64 0, i32 0
|
||||
%suspend = call i8 @llvm.coro.suspend(token %save, i1 false)
|
||||
switch i8 %suspend, label %exit [
|
||||
i8 0, label %await.ready
|
||||
i8 1, label %exit
|
||||
]
|
||||
await.ready:
|
||||
%val = load i32, i32* %Result.i19
|
||||
call void @print(i32 %val)
|
||||
br label %exit
|
||||
exit:
|
||||
call void @llvm.coro.end(i8* null, i1 false)
|
||||
ret void
|
||||
}
|
||||
|
||||
; CHECK-LABEL: @a.resume(
|
||||
; CHECK: getelementptr inbounds %a.Frame
|
||||
; CHECK-NEXT: getelementptr inbounds %"struct.lean_future<int>::Awaiter"
|
||||
; CHECK-NEXT: %val = load i32, i32* %Result
|
||||
; CHECK-NEXT: call void @print(i32 %val)
|
||||
; CHECK-NEXT: ret void
|
||||
|
||||
declare token @llvm.coro.id(i32, i8* readnone, i8* nocapture readonly, i8*)
|
||||
declare i1 @llvm.coro.alloc(token) #3
|
||||
declare noalias nonnull i8* @"\01??2@YAPEAX_K@Z"(i64) local_unnamed_addr
|
||||
declare i64 @llvm.coro.size.i64() #5
|
||||
declare i8* @llvm.coro.begin(token, i8* writeonly) #3
|
||||
declare void @"\01?puts@@YAXZZ"(...)
|
||||
declare token @llvm.coro.save(i8*) #3
|
||||
declare i8* @llvm.coro.frame() #5
|
||||
declare i8 @llvm.coro.suspend(token, i1) #3
|
||||
declare void @"\01??3@YAXPEAX@Z"(i8*) local_unnamed_addr #10
|
||||
declare i8* @llvm.coro.free(token, i8* nocapture readonly) #2
|
||||
declare void @llvm.coro.end(i8*, i1) #3
|
||||
|
||||
Reference in New Issue
Block a user