Skip to content

Commit 62a5dde

Browse files
committed
Add intrinsics for doing frame-bound dynamic allocations within a coroutine.
These rely on having an allocator provided to the coroutine and thus, for now, only work in retcon lowerings. llvm-svn: 368791
1 parent 137b50f commit 62a5dde

File tree

5 files changed

+464
-4
lines changed

5 files changed

+464
-4
lines changed

llvm/include/llvm/IR/Intrinsics.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,10 @@ def int_coro_suspend : Intrinsic<[llvm_i8_ty], [llvm_token_ty, llvm_i1_ty], []>;
985985
def int_coro_suspend_retcon : Intrinsic<[llvm_any_ty], [llvm_vararg_ty], []>;
986986
def int_coro_prepare_retcon : Intrinsic<[llvm_ptr_ty], [llvm_ptr_ty],
987987
[IntrNoMem]>;
988+
def int_coro_alloca_alloc : Intrinsic<[llvm_token_ty],
989+
[llvm_anyint_ty, llvm_i32_ty], []>;
990+
def int_coro_alloca_get : Intrinsic<[llvm_ptr_ty], [llvm_token_ty], []>;
991+
def int_coro_alloca_free : Intrinsic<[], [llvm_token_ty], []>;
988992

989993
def int_coro_param : Intrinsic<[llvm_i1_ty], [llvm_ptr_ty, llvm_ptr_ty],
990994
[IntrNoMem, ReadNone<0>, ReadNone<1>]>;

llvm/lib/Transforms/Coroutines/CoroFrame.cpp

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -960,6 +960,155 @@ static void splitAround(Instruction *I, const Twine &Name) {
960960
splitBlockIfNotFirst(I->getNextNode(), "After" + Name);
961961
}
962962

963+
static bool isSuspendBlock(BasicBlock *BB) {
964+
return isa<AnyCoroSuspendInst>(BB->front());
965+
}
966+
967+
typedef SmallPtrSet<BasicBlock*, 8> VisitedBlocksSet;
968+
969+
/// Does control flow starting at the given block ever reach a suspend
970+
/// instruction before reaching a block in VisitedOrFreeBBs?
971+
static bool isSuspendReachableFrom(BasicBlock *From,
972+
VisitedBlocksSet &VisitedOrFreeBBs) {
973+
// Eagerly try to add this block to the visited set. If it's already
974+
// there, stop recursing; this path doesn't reach a suspend before
975+
// either looping or reaching a freeing block.
976+
if (!VisitedOrFreeBBs.insert(From).second)
977+
return false;
978+
979+
// We assume that we'll already have split suspends into their own blocks.
980+
if (isSuspendBlock(From))
981+
return true;
982+
983+
// Recurse on the successors.
984+
for (auto Succ : successors(From)) {
985+
if (isSuspendReachableFrom(Succ, VisitedOrFreeBBs))
986+
return true;
987+
}
988+
989+
return false;
990+
}
991+
992+
/// Is the given alloca "local", i.e. bounded in lifetime to not cross a
993+
/// suspend point?
994+
static bool isLocalAlloca(CoroAllocaAllocInst *AI) {
995+
// Seed the visited set with all the basic blocks containing a free
996+
// so that we won't pass them up.
997+
VisitedBlocksSet VisitedOrFreeBBs;
998+
for (auto User : AI->users()) {
999+
if (auto FI = dyn_cast<CoroAllocaFreeInst>(User))
1000+
VisitedOrFreeBBs.insert(FI->getParent());
1001+
}
1002+
1003+
return !isSuspendReachableFrom(AI->getParent(), VisitedOrFreeBBs);
1004+
}
1005+
1006+
/// After we split the coroutine, will the given basic block be along
1007+
/// an obvious exit path for the resumption function?
1008+
static bool willLeaveFunctionImmediatelyAfter(BasicBlock *BB,
1009+
unsigned depth = 3) {
1010+
// If we've bottomed out our depth count, stop searching and assume
1011+
// that the path might loop back.
1012+
if (depth == 0) return false;
1013+
1014+
// If this is a suspend block, we're about to exit the resumption function.
1015+
if (isSuspendBlock(BB)) return true;
1016+
1017+
// Recurse into the successors.
1018+
for (auto Succ : successors(BB)) {
1019+
if (!willLeaveFunctionImmediatelyAfter(Succ, depth - 1))
1020+
return false;
1021+
}
1022+
1023+
// If none of the successors leads back in a loop, we're on an exit/abort.
1024+
return true;
1025+
}
1026+
1027+
static bool localAllocaNeedsStackSave(CoroAllocaAllocInst *AI) {
1028+
// Look for a free that isn't sufficiently obviously followed by
1029+
// either a suspend or a termination, i.e. something that will leave
1030+
// the coro resumption frame.
1031+
for (auto U : AI->users()) {
1032+
auto FI = dyn_cast<CoroAllocaFreeInst>(U);
1033+
if (!FI) continue;
1034+
1035+
if (!willLeaveFunctionImmediatelyAfter(FI->getParent()))
1036+
return true;
1037+
}
1038+
1039+
// If we never found one, we don't need a stack save.
1040+
return false;
1041+
}
1042+
1043+
/// Turn each of the given local allocas into a normal (dynamic) alloca
1044+
/// instruction.
1045+
static void lowerLocalAllocas(ArrayRef<CoroAllocaAllocInst*> LocalAllocas) {
1046+
for (auto AI : LocalAllocas) {
1047+
auto M = AI->getModule();
1048+
IRBuilder<> Builder(AI);
1049+
1050+
// Save the stack depth. Try to avoid doing this if the stackrestore
1051+
// is going to immediately precede a return or something.
1052+
Value *StackSave = nullptr;
1053+
if (localAllocaNeedsStackSave(AI))
1054+
StackSave = Builder.CreateCall(
1055+
Intrinsic::getDeclaration(M, Intrinsic::stacksave));
1056+
1057+
// Allocate memory.
1058+
auto Alloca = Builder.CreateAlloca(Builder.getInt8Ty(), AI->getSize());
1059+
Alloca->setAlignment(AI->getAlignment());
1060+
1061+
for (auto U : AI->users()) {
1062+
// Replace gets with the allocation.
1063+
if (isa<CoroAllocaGetInst>(U)) {
1064+
U->replaceAllUsesWith(Alloca);
1065+
1066+
// Replace frees with stackrestores. This is safe because
1067+
// alloca.alloc is required to obey a stack discipline, although we
1068+
// don't enforce that structurally.
1069+
} else {
1070+
auto FI = cast<CoroAllocaFreeInst>(U);
1071+
if (StackSave) {
1072+
Builder.SetInsertPoint(FI);
1073+
Builder.CreateCall(
1074+
Intrinsic::getDeclaration(M, Intrinsic::stackrestore),
1075+
StackSave);
1076+
}
1077+
}
1078+
cast<Instruction>(U)->eraseFromParent();
1079+
}
1080+
1081+
AI->eraseFromParent();
1082+
}
1083+
}
1084+
1085+
/// Turn the given coro.alloca.alloc call into a dynamic allocation.
1086+
/// This happens during the all-instructions iteration, so it must not
1087+
/// delete the call.
1088+
static Instruction *lowerNonLocalAlloca(CoroAllocaAllocInst *AI,
1089+
coro::Shape &Shape,
1090+
SmallVectorImpl<Instruction*> &DeadInsts) {
1091+
IRBuilder<> Builder(AI);
1092+
auto Alloc = Shape.emitAlloc(Builder, AI->getSize(), nullptr);
1093+
1094+
for (User *U : AI->users()) {
1095+
if (isa<CoroAllocaGetInst>(U)) {
1096+
U->replaceAllUsesWith(Alloc);
1097+
} else {
1098+
auto FI = cast<CoroAllocaFreeInst>(U);
1099+
Builder.SetInsertPoint(FI);
1100+
Shape.emitDealloc(Builder, Alloc, nullptr);
1101+
}
1102+
DeadInsts.push_back(cast<Instruction>(U));
1103+
}
1104+
1105+
// Push this on last so that it gets deleted after all the others.
1106+
DeadInsts.push_back(AI);
1107+
1108+
// Return the new allocation value so that we can check for needed spills.
1109+
return cast<Instruction>(Alloc);
1110+
}
1111+
9631112
void coro::buildCoroutineFrame(Function &F, Shape &Shape) {
9641113
// Lower coro.dbg.declare to coro.dbg.value, since we are going to rewrite
9651114
// access to local variables.
@@ -992,6 +1141,8 @@ void coro::buildCoroutineFrame(Function &F, Shape &Shape) {
9921141

9931142
IRBuilder<> Builder(F.getContext());
9941143
SpillInfo Spills;
1144+
SmallVector<CoroAllocaAllocInst*, 4> LocalAllocas;
1145+
SmallVector<Instruction*, 4> DeadInstructions;
9951146

9961147
for (int Repeat = 0; Repeat < 4; ++Repeat) {
9971148
// See if there are materializable instructions across suspend points.
@@ -1021,12 +1172,35 @@ void coro::buildCoroutineFrame(Function &F, Shape &Shape) {
10211172
// of the Coroutine Frame.
10221173
if (isCoroutineStructureIntrinsic(I) || &I == Shape.CoroBegin)
10231174
continue;
1175+
10241176
// The Coroutine Promise always included into coroutine frame, no need to
10251177
// check for suspend crossing.
10261178
if (Shape.ABI == coro::ABI::Switch &&
10271179
Shape.SwitchLowering.PromiseAlloca == &I)
10281180
continue;
10291181

1182+
// Handle alloca.alloc specially here.
1183+
if (auto AI = dyn_cast<CoroAllocaAllocInst>(&I)) {
1184+
// Check whether the alloca's lifetime is bounded by suspend points.
1185+
if (isLocalAlloca(AI)) {
1186+
LocalAllocas.push_back(AI);
1187+
continue;
1188+
}
1189+
1190+
// If not, do a quick rewrite of the alloca and then add spills of
1191+
// the rewritten value. The rewrite doesn't invalidate anything in
1192+
// Spills because the other alloca intrinsics have no other operands
1193+
// besides AI, and it doesn't invalidate the iteration because we delay
1194+
// erasing AI.
1195+
auto Alloc = lowerNonLocalAlloca(AI, Shape, DeadInstructions);
1196+
1197+
for (User *U : Alloc->users()) {
1198+
if (Checker.isDefinitionAcrossSuspend(*Alloc, U))
1199+
Spills.emplace_back(Alloc, U);
1200+
}
1201+
continue;
1202+
}
1203+
10301204
for (User *U : I.users())
10311205
if (Checker.isDefinitionAcrossSuspend(I, U)) {
10321206
// We cannot spill a token.
@@ -1040,4 +1214,8 @@ void coro::buildCoroutineFrame(Function &F, Shape &Shape) {
10401214
moveSpillUsesAfterCoroBegin(F, Spills, Shape.CoroBegin);
10411215
Shape.FrameTy = buildFrameType(F, Shape, Spills);
10421216
Shape.FramePtr = insertSpills(Spills, Shape);
1217+
lowerLocalAllocas(LocalAllocas);
1218+
1219+
for (auto I : DeadInstructions)
1220+
I->eraseFromParent();
10431221
}

llvm/lib/Transforms/Coroutines/CoroInstr.h

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,60 @@ class LLVM_LIBRARY_VISIBILITY CoroEndInst : public IntrinsicInst {
456456
}
457457
};
458458

459+
/// This represents the llvm.coro.alloca.alloc instruction.
460+
class LLVM_LIBRARY_VISIBILITY CoroAllocaAllocInst : public IntrinsicInst {
461+
enum { SizeArg, AlignArg };
462+
public:
463+
Value *getSize() const {
464+
return getArgOperand(SizeArg);
465+
}
466+
unsigned getAlignment() const {
467+
return cast<ConstantInt>(getArgOperand(AlignArg))->getZExtValue();
468+
}
469+
470+
// Methods to support type inquiry through isa, cast, and dyn_cast:
471+
static bool classof(const IntrinsicInst *I) {
472+
return I->getIntrinsicID() == Intrinsic::coro_alloca_alloc;
473+
}
474+
static bool classof(const Value *V) {
475+
return isa<IntrinsicInst>(V) && classof(cast<IntrinsicInst>(V));
476+
}
477+
};
478+
479+
/// This represents the llvm.coro.alloca.get instruction.
480+
class LLVM_LIBRARY_VISIBILITY CoroAllocaGetInst : public IntrinsicInst {
481+
enum { AllocArg };
482+
public:
483+
CoroAllocaAllocInst *getAlloc() const {
484+
return cast<CoroAllocaAllocInst>(getArgOperand(AllocArg));
485+
}
486+
487+
// Methods to support type inquiry through isa, cast, and dyn_cast:
488+
static bool classof(const IntrinsicInst *I) {
489+
return I->getIntrinsicID() == Intrinsic::coro_alloca_get;
490+
}
491+
static bool classof(const Value *V) {
492+
return isa<IntrinsicInst>(V) && classof(cast<IntrinsicInst>(V));
493+
}
494+
};
495+
496+
/// This represents the llvm.coro.alloca.free instruction.
497+
class LLVM_LIBRARY_VISIBILITY CoroAllocaFreeInst : public IntrinsicInst {
498+
enum { AllocArg };
499+
public:
500+
CoroAllocaAllocInst *getAlloc() const {
501+
return cast<CoroAllocaAllocInst>(getArgOperand(AllocArg));
502+
}
503+
504+
// Methods to support type inquiry through isa, cast, and dyn_cast:
505+
static bool classof(const IntrinsicInst *I) {
506+
return I->getIntrinsicID() == Intrinsic::coro_alloca_free;
507+
}
508+
static bool classof(const Value *V) {
509+
return isa<IntrinsicInst>(V) && classof(cast<IntrinsicInst>(V));
510+
}
511+
};
512+
459513
} // End namespace llvm.
460514

461515
#endif

llvm/lib/Transforms/Coroutines/Coroutines.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -424,10 +424,15 @@ void coro::Shape::buildFrom(Function &F) {
424424

425425
// Check that the result type of the suspend matches the resume types.
426426
Type *SResultTy = Suspend->getType();
427-
ArrayRef<Type*> SuspendResultTys =
428-
(isa<StructType>(SResultTy)
429-
? cast<StructType>(SResultTy)->elements()
430-
: SResultTy); // forms an ArrayRef using SResultTy, be careful
427+
ArrayRef<Type*> SuspendResultTys;
428+
if (SResultTy->isVoidTy()) {
429+
// leave as empty array
430+
} else if (auto SResultStructTy = dyn_cast<StructType>(SResultTy)) {
431+
SuspendResultTys = SResultStructTy->elements();
432+
} else {
433+
// forms an ArrayRef using SResultTy, be careful
434+
SuspendResultTys = SResultTy;
435+
}
431436
if (SuspendResultTys.size() != ResumeTys.size()) {
432437
#ifndef NDEBUG
433438
Suspend->dump();

0 commit comments

Comments
 (0)