#include "llvm/Transforms/Coroutines/CoroSplit.h"
#include "CoroInstr.h"
#include "CoroInternal.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/PriorityWorklist.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Analysis/CFG.h"
#include "llvm/Analysis/CallGraph.h"
#include "llvm/Analysis/ConstantFolding.h"
#include "llvm/Analysis/LazyCallGraph.h"
#include "llvm/Analysis/TargetTransformInfo.h"
#include "llvm/BinaryFormat/Dwarf.h"
#include "llvm/IR/Argument.h"
#include "llvm/IR/Attributes.h"
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/CFG.h"
#include "llvm/IR/CallingConv.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/DataLayout.h"
#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/Dominators.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/GlobalValue.h"
#include "llvm/IR/GlobalVariable.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/InstIterator.h"
#include "llvm/IR/InstrTypes.h"
#include "llvm/IR/Instruction.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Type.h"
#include "llvm/IR/Value.h"
#include "llvm/IR/Verifier.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/PrettyStackTrace.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Transforms/Scalar.h"
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
#include "llvm/Transforms/Utils/CallGraphUpdater.h"
#include "llvm/Transforms/Utils/Cloning.h"
#include "llvm/Transforms/Utils/Local.h"
#include "llvm/Transforms/Utils/ValueMapper.h"
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <initializer_list>
#include <iterator>
using namespace llvm;
#define DEBUG_TYPE "coro-split"
namespace {
class CoroCloner {
public:
enum class Kind {
SwitchResume,
SwitchUnwind,
SwitchCleanup,
Continuation,
Async,
};
private:
Function &OrigF;
Function *NewF;
const Twine &Suffix;
coro::Shape &Shape;
Kind FKind;
ValueToValueMapTy VMap;
IRBuilder<> Builder;
Value *NewFramePtr = nullptr;
AnyCoroSuspendInst *ActiveSuspend = nullptr;
public:
CoroCloner(Function &OrigF, const Twine &Suffix, coro::Shape &Shape,
Kind FKind)
: OrigF(OrigF), NewF(nullptr), Suffix(Suffix), Shape(Shape),
FKind(FKind), Builder(OrigF.getContext()) {
assert(Shape.ABI == coro::ABI::Switch);
}
CoroCloner(Function &OrigF, const Twine &Suffix, coro::Shape &Shape,
Function *NewF, AnyCoroSuspendInst *ActiveSuspend)
: OrigF(OrigF), NewF(NewF), Suffix(Suffix), Shape(Shape),
FKind(Shape.ABI == coro::ABI::Async ? Kind::Async : Kind::Continuation),
Builder(OrigF.getContext()), ActiveSuspend(ActiveSuspend) {
assert(Shape.ABI == coro::ABI::Retcon ||
Shape.ABI == coro::ABI::RetconOnce || Shape.ABI == coro::ABI::Async);
assert(NewF && "need existing function for continuation");
assert(ActiveSuspend && "need active suspend point for continuation");
}
Function *getFunction() const {
assert(NewF != nullptr && "declaration not yet set");
return NewF;
}
void create();
private:
bool isSwitchDestroyFunction() {
switch (FKind) {
case Kind::Async:
case Kind::Continuation:
case Kind::SwitchResume:
return false;
case Kind::SwitchUnwind:
case Kind::SwitchCleanup:
return true;
}
llvm_unreachable("Unknown CoroCloner::Kind enum");
}
void replaceEntryBlock();
Value *deriveNewFramePointer();
void replaceRetconOrAsyncSuspendUses();
void replaceCoroSuspends();
void replaceCoroEnds();
void replaceSwiftErrorOps();
void salvageDebugInfo();
void handleFinalSuspend();
};
}
static void maybeFreeRetconStorage(IRBuilder<> &Builder,
const coro::Shape &Shape, Value *FramePtr,
CallGraph *CG) {
assert(Shape.ABI == coro::ABI::Retcon ||
Shape.ABI == coro::ABI::RetconOnce);
if (Shape.RetconLowering.IsFrameInlineInStorage)
return;
Shape.emitDealloc(Builder, FramePtr, CG);
}
static bool replaceCoroEndAsync(AnyCoroEndInst *End) {
IRBuilder<> Builder(End);
auto *EndAsync = dyn_cast<CoroAsyncEndInst>(End);
if (!EndAsync) {
Builder.CreateRetVoid();
return true ;
}
auto *MustTailCallFunc = EndAsync->getMustTailCallFunction();
if (!MustTailCallFunc) {
Builder.CreateRetVoid();
return true ;
}
auto *CoroEndBlock = End->getParent();
auto *MustTailCallFuncBlock = CoroEndBlock->getSinglePredecessor();
assert(MustTailCallFuncBlock && "Must have a single predecessor block");
auto It = MustTailCallFuncBlock->getTerminator()->getIterator();
auto *MustTailCall = cast<CallInst>(&*std::prev(It));
CoroEndBlock->getInstList().splice(
End->getIterator(), MustTailCallFuncBlock->getInstList(), MustTailCall);
Builder.SetInsertPoint(End);
Builder.CreateRetVoid();
InlineFunctionInfo FnInfo;
auto *BB = End->getParent();
BB->splitBasicBlock(End);
BB->getTerminator()->eraseFromParent();
auto InlineRes = InlineFunction(*MustTailCall, FnInfo);
assert(InlineRes.isSuccess() && "Expected inlining to succeed");
(void)InlineRes;
return false;
}
static void replaceFallthroughCoroEnd(AnyCoroEndInst *End,
const coro::Shape &Shape, Value *FramePtr,
bool InResume, CallGraph *CG) {
IRBuilder<> Builder(End);
switch (Shape.ABI) {
case coro::ABI::Switch:
if (!InResume)
return;
Builder.CreateRetVoid();
break;
case coro::ABI::Async: {
bool CoroEndBlockNeedsCleanup = replaceCoroEndAsync(End);
if (!CoroEndBlockNeedsCleanup)
return;
break;
}
case coro::ABI::RetconOnce:
maybeFreeRetconStorage(Builder, Shape, FramePtr, CG);
Builder.CreateRetVoid();
break;
case coro::ABI::Retcon: {
maybeFreeRetconStorage(Builder, Shape, FramePtr, CG);
auto RetTy = Shape.getResumeFunctionType()->getReturnType();
auto RetStructTy = dyn_cast<StructType>(RetTy);
PointerType *ContinuationTy =
cast<PointerType>(RetStructTy ? RetStructTy->getElementType(0) : RetTy);
Value *ReturnValue = ConstantPointerNull::get(ContinuationTy);
if (RetStructTy) {
ReturnValue = Builder.CreateInsertValue(UndefValue::get(RetStructTy),
ReturnValue, 0);
}
Builder.CreateRet(ReturnValue);
break;
}
}
auto *BB = End->getParent();
BB->splitBasicBlock(End);
BB->getTerminator()->eraseFromParent();
}
static void markCoroutineAsDone(IRBuilder<> &Builder, const coro::Shape &Shape,
Value *FramePtr) {
assert(
Shape.ABI == coro::ABI::Switch &&
"markCoroutineAsDone is only supported for Switch-Resumed ABI for now.");
auto *GepIndex = Builder.CreateStructGEP(
Shape.FrameTy, FramePtr, coro::Shape::SwitchFieldIndex::Resume,
"ResumeFn.addr");
auto *NullPtr = ConstantPointerNull::get(cast<PointerType>(
Shape.FrameTy->getTypeAtIndex(coro::Shape::SwitchFieldIndex::Resume)));
Builder.CreateStore(NullPtr, GepIndex);
}
static void replaceUnwindCoroEnd(AnyCoroEndInst *End, const coro::Shape &Shape,
Value *FramePtr, bool InResume,
CallGraph *CG) {
IRBuilder<> Builder(End);
switch (Shape.ABI) {
case coro::ABI::Switch: {
markCoroutineAsDone(Builder, Shape, FramePtr);
if (!InResume)
return;
break;
}
case coro::ABI::Async:
break;
case coro::ABI::Retcon:
case coro::ABI::RetconOnce:
maybeFreeRetconStorage(Builder, Shape, FramePtr, CG);
break;
}
if (auto Bundle = End->getOperandBundle(LLVMContext::OB_funclet)) {
auto *FromPad = cast<CleanupPadInst>(Bundle->Inputs[0]);
auto *CleanupRet = Builder.CreateCleanupRet(FromPad, nullptr);
End->getParent()->splitBasicBlock(End);
CleanupRet->getParent()->getTerminator()->eraseFromParent();
}
}
static void replaceCoroEnd(AnyCoroEndInst *End, const coro::Shape &Shape,
Value *FramePtr, bool InResume, CallGraph *CG) {
if (End->isUnwind())
replaceUnwindCoroEnd(End, Shape, FramePtr, InResume, CG);
else
replaceFallthroughCoroEnd(End, Shape, FramePtr, InResume, CG);
auto &Context = End->getContext();
End->replaceAllUsesWith(InResume ? ConstantInt::getTrue(Context)
: ConstantInt::getFalse(Context));
End->eraseFromParent();
}
static void createResumeEntryBlock(Function &F, coro::Shape &Shape) {
assert(Shape.ABI == coro::ABI::Switch);
LLVMContext &C = F.getContext();
auto *NewEntry = BasicBlock::Create(C, "resume.entry", &F);
auto *UnreachBB = BasicBlock::Create(C, "unreachable", &F);
IRBuilder<> Builder(NewEntry);
auto *FramePtr = Shape.FramePtr;
auto *FrameTy = Shape.FrameTy;
auto *GepIndex = Builder.CreateStructGEP(
FrameTy, FramePtr, Shape.getSwitchIndexField(), "index.addr");
auto *Index = Builder.CreateLoad(Shape.getIndexType(), GepIndex, "index");
auto *Switch =
Builder.CreateSwitch(Index, UnreachBB, Shape.CoroSuspends.size());
Shape.SwitchLowering.ResumeSwitch = Switch;
size_t SuspendIndex = 0;
for (auto *AnyS : Shape.CoroSuspends) {
auto *S = cast<CoroSuspendInst>(AnyS);
ConstantInt *IndexVal = Shape.getIndex(SuspendIndex);
auto *Save = S->getCoroSave();
Builder.SetInsertPoint(Save);
if (S->isFinal()) {
markCoroutineAsDone(Builder, Shape, FramePtr);
} else {
auto *GepIndex = Builder.CreateStructGEP(
FrameTy, FramePtr, Shape.getSwitchIndexField(), "index.addr");
Builder.CreateStore(IndexVal, GepIndex);
}
Save->replaceAllUsesWith(ConstantTokenNone::get(C));
Save->eraseFromParent();
auto *SuspendBB = S->getParent();
auto *ResumeBB =
SuspendBB->splitBasicBlock(S, "resume." + Twine(SuspendIndex));
auto *LandingBB = ResumeBB->splitBasicBlock(
S->getNextNode(), ResumeBB->getName() + Twine(".landing"));
Switch->addCase(IndexVal, ResumeBB);
cast<BranchInst>(SuspendBB->getTerminator())->setSuccessor(0, LandingBB);
auto *PN = PHINode::Create(Builder.getInt8Ty(), 2, "", &LandingBB->front());
S->replaceAllUsesWith(PN);
PN->addIncoming(Builder.getInt8(-1), SuspendBB);
PN->addIncoming(S, ResumeBB);
++SuspendIndex;
}
Builder.SetInsertPoint(UnreachBB);
Builder.CreateUnreachable();
Shape.SwitchLowering.ResumeEntryBlock = NewEntry;
}
void CoroCloner::handleFinalSuspend() {
assert(Shape.ABI == coro::ABI::Switch &&
Shape.SwitchLowering.HasFinalSuspend);
auto *Switch = cast<SwitchInst>(VMap[Shape.SwitchLowering.ResumeSwitch]);
auto FinalCaseIt = std::prev(Switch->case_end());
BasicBlock *ResumeBB = FinalCaseIt->getCaseSuccessor();
Switch->removeCase(FinalCaseIt);
if (isSwitchDestroyFunction()) {
BasicBlock *OldSwitchBB = Switch->getParent();
auto *NewSwitchBB = OldSwitchBB->splitBasicBlock(Switch, "Switch");
Builder.SetInsertPoint(OldSwitchBB->getTerminator());
auto *GepIndex = Builder.CreateStructGEP(Shape.FrameTy, NewFramePtr,
coro::Shape::SwitchFieldIndex::Resume,
"ResumeFn.addr");
auto *Load = Builder.CreateLoad(Shape.getSwitchResumePointerType(),
GepIndex);
auto *Cond = Builder.CreateIsNull(Load);
Builder.CreateCondBr(Cond, ResumeBB, NewSwitchBB);
OldSwitchBB->getTerminator()->eraseFromParent();
}
}
static FunctionType *
getFunctionTypeFromAsyncSuspend(AnyCoroSuspendInst *Suspend) {
auto *AsyncSuspend = cast<CoroSuspendAsyncInst>(Suspend);
auto *StructTy = cast<StructType>(AsyncSuspend->getType());
auto &Context = Suspend->getParent()->getParent()->getContext();
auto *VoidTy = Type::getVoidTy(Context);
return FunctionType::get(VoidTy, StructTy->elements(), false);
}
static Function *createCloneDeclaration(Function &OrigF, coro::Shape &Shape,
const Twine &Suffix,
Module::iterator InsertBefore,
AnyCoroSuspendInst *ActiveSuspend) {
Module *M = OrigF.getParent();
auto *FnTy = (Shape.ABI != coro::ABI::Async)
? Shape.getResumeFunctionType()
: getFunctionTypeFromAsyncSuspend(ActiveSuspend);
Function *NewF =
Function::Create(FnTy, GlobalValue::LinkageTypes::InternalLinkage,
OrigF.getName() + Suffix);
if (Shape.ABI != coro::ABI::Async)
NewF->addParamAttr(0, Attribute::NonNull);
if (Shape.ABI != coro::ABI::Async)
NewF->addParamAttr(0, Attribute::NoAlias);
M->getFunctionList().insert(InsertBefore, NewF);
return NewF;
}
void CoroCloner::replaceRetconOrAsyncSuspendUses() {
assert(Shape.ABI == coro::ABI::Retcon || Shape.ABI == coro::ABI::RetconOnce ||
Shape.ABI == coro::ABI::Async);
auto NewS = VMap[ActiveSuspend];
if (NewS->use_empty()) return;
SmallVector<Value*, 8> Args;
bool IsAsyncABI = Shape.ABI == coro::ABI::Async;
for (auto I = IsAsyncABI ? NewF->arg_begin() : std::next(NewF->arg_begin()),
E = NewF->arg_end();
I != E; ++I)
Args.push_back(&*I);
if (!isa<StructType>(NewS->getType())) {
assert(Args.size() == 1);
NewS->replaceAllUsesWith(Args.front());
return;
}
for (Use &U : llvm::make_early_inc_range(NewS->uses())) {
auto *EVI = dyn_cast<ExtractValueInst>(U.getUser());
if (!EVI || EVI->getNumIndices() != 1)
continue;
EVI->replaceAllUsesWith(Args[EVI->getIndices().front()]);
EVI->eraseFromParent();
}
if (NewS->use_empty()) return;
Value *Agg = UndefValue::get(NewS->getType());
for (size_t I = 0, E = Args.size(); I != E; ++I)
Agg = Builder.CreateInsertValue(Agg, Args[I], I);
NewS->replaceAllUsesWith(Agg);
}
void CoroCloner::replaceCoroSuspends() {
Value *SuspendResult;
switch (Shape.ABI) {
case coro::ABI::Switch:
SuspendResult = Builder.getInt8(isSwitchDestroyFunction() ? 1 : 0);
break;
case coro::ABI::Async:
return;
case coro::ABI::RetconOnce:
case coro::ABI::Retcon:
return;
}
for (AnyCoroSuspendInst *CS : Shape.CoroSuspends) {
if (CS == ActiveSuspend) continue;
auto *MappedCS = cast<AnyCoroSuspendInst>(VMap[CS]);
MappedCS->replaceAllUsesWith(SuspendResult);
MappedCS->eraseFromParent();
}
}
void CoroCloner::replaceCoroEnds() {
for (AnyCoroEndInst *CE : Shape.CoroEnds) {
auto *NewCE = cast<AnyCoroEndInst>(VMap[CE]);
replaceCoroEnd(NewCE, Shape, NewFramePtr, true, nullptr);
}
}
static void replaceSwiftErrorOps(Function &F, coro::Shape &Shape,
ValueToValueMapTy *VMap) {
if (Shape.ABI == coro::ABI::Async && Shape.CoroSuspends.empty())
return;
Value *CachedSlot = nullptr;
auto getSwiftErrorSlot = [&](Type *ValueTy) -> Value * {
if (CachedSlot) {
assert(cast<PointerType>(CachedSlot->getType())
->isOpaqueOrPointeeTypeMatches(ValueTy) &&
"multiple swifterror slots in function with different types");
return CachedSlot;
}
for (auto &Arg : F.args()) {
if (Arg.isSwiftError()) {
CachedSlot = &Arg;
assert(cast<PointerType>(Arg.getType())
->isOpaqueOrPointeeTypeMatches(ValueTy) &&
"swifterror argument does not have expected type");
return &Arg;
}
}
IRBuilder<> Builder(F.getEntryBlock().getFirstNonPHIOrDbg());
auto Alloca = Builder.CreateAlloca(ValueTy);
Alloca->setSwiftError(true);
CachedSlot = Alloca;
return Alloca;
};
for (CallInst *Op : Shape.SwiftErrorOps) {
auto MappedOp = VMap ? cast<CallInst>((*VMap)[Op]) : Op;
IRBuilder<> Builder(MappedOp);
Value *MappedResult;
if (Op->arg_empty()) {
auto ValueTy = Op->getType();
auto Slot = getSwiftErrorSlot(ValueTy);
MappedResult = Builder.CreateLoad(ValueTy, Slot);
} else {
assert(Op->arg_size() == 1);
auto Value = MappedOp->getArgOperand(0);
auto ValueTy = Value->getType();
auto Slot = getSwiftErrorSlot(ValueTy);
Builder.CreateStore(Value, Slot);
MappedResult = Slot;
}
MappedOp->replaceAllUsesWith(MappedResult);
MappedOp->eraseFromParent();
}
if (VMap == nullptr) {
Shape.SwiftErrorOps.clear();
}
}
void CoroCloner::replaceSwiftErrorOps() {
::replaceSwiftErrorOps(*NewF, Shape, &VMap);
}
void CoroCloner::salvageDebugInfo() {
SmallVector<DbgVariableIntrinsic *, 8> Worklist;
SmallDenseMap<llvm::Value *, llvm::AllocaInst *, 4> DbgPtrAllocaCache;
for (auto &BB : *NewF)
for (auto &I : BB)
if (auto *DVI = dyn_cast<DbgVariableIntrinsic>(&I))
Worklist.push_back(DVI);
for (DbgVariableIntrinsic *DVI : Worklist)
coro::salvageDebugInfo(DbgPtrAllocaCache, DVI, Shape.OptimizeFrame);
DominatorTree DomTree(*NewF);
auto IsUnreachableBlock = [&](BasicBlock *BB) {
return !isPotentiallyReachable(&NewF->getEntryBlock(), BB, nullptr,
&DomTree);
};
for (DbgVariableIntrinsic *DVI : Worklist) {
if (IsUnreachableBlock(DVI->getParent()))
DVI->eraseFromParent();
else if (isa_and_nonnull<AllocaInst>(DVI->getVariableLocationOp(0))) {
unsigned Uses = 0;
for (auto *User : DVI->getVariableLocationOp(0)->users())
if (auto *I = dyn_cast<Instruction>(User))
if (!isa<AllocaInst>(I) && !IsUnreachableBlock(I->getParent()))
++Uses;
if (!Uses)
DVI->eraseFromParent();
}
}
}
void CoroCloner::replaceEntryBlock() {
auto *Entry = cast<BasicBlock>(VMap[Shape.AllocaSpillBlock]);
auto *OldEntry = &NewF->getEntryBlock();
Entry->setName("entry" + Suffix);
Entry->moveBefore(OldEntry);
Entry->getTerminator()->eraseFromParent();
assert(Entry->hasOneUse());
auto BranchToEntry = cast<BranchInst>(Entry->user_back());
assert(BranchToEntry->isUnconditional());
Builder.SetInsertPoint(BranchToEntry);
Builder.CreateUnreachable();
BranchToEntry->eraseFromParent();
Builder.SetInsertPoint(Entry);
switch (Shape.ABI) {
case coro::ABI::Switch: {
auto *SwitchBB =
cast<BasicBlock>(VMap[Shape.SwitchLowering.ResumeEntryBlock]);
Builder.CreateBr(SwitchBB);
break;
}
case coro::ABI::Async:
case coro::ABI::Retcon:
case coro::ABI::RetconOnce: {
assert((Shape.ABI == coro::ABI::Async &&
isa<CoroSuspendAsyncInst>(ActiveSuspend)) ||
((Shape.ABI == coro::ABI::Retcon ||
Shape.ABI == coro::ABI::RetconOnce) &&
isa<CoroSuspendRetconInst>(ActiveSuspend)));
auto *MappedCS = cast<AnyCoroSuspendInst>(VMap[ActiveSuspend]);
auto Branch = cast<BranchInst>(MappedCS->getNextNode());
assert(Branch->isUnconditional());
Builder.CreateBr(Branch->getSuccessor(0));
break;
}
}
Function *F = OldEntry->getParent();
DominatorTree DT{*F};
for (Instruction &I : llvm::make_early_inc_range(instructions(F))) {
auto *Alloca = dyn_cast<AllocaInst>(&I);
if (!Alloca || I.use_empty())
continue;
if (DT.isReachableFromEntry(I.getParent()) ||
!isa<ConstantInt>(Alloca->getArraySize()))
continue;
I.moveBefore(*Entry, Entry->getFirstInsertionPt());
}
}
Value *CoroCloner::deriveNewFramePointer() {
switch (Shape.ABI) {
case coro::ABI::Switch:
return &*NewF->arg_begin();
case coro::ABI::Async: {
auto *ActiveAsyncSuspend = cast<CoroSuspendAsyncInst>(ActiveSuspend);
auto ContextIdx = ActiveAsyncSuspend->getStorageArgumentIndex() & 0xff;
auto *CalleeContext = NewF->getArg(ContextIdx);
auto *FramePtrTy = Shape.FrameTy->getPointerTo();
auto *ProjectionFunc =
ActiveAsyncSuspend->getAsyncContextProjectionFunction();
auto DbgLoc =
cast<CoroSuspendAsyncInst>(VMap[ActiveSuspend])->getDebugLoc();
auto *CallerContext = Builder.CreateCall(ProjectionFunc->getFunctionType(),
ProjectionFunc, CalleeContext);
CallerContext->setCallingConv(ProjectionFunc->getCallingConv());
CallerContext->setDebugLoc(DbgLoc);
auto &Context = Builder.getContext();
auto *FramePtrAddr = Builder.CreateConstInBoundsGEP1_32(
Type::getInt8Ty(Context), CallerContext,
Shape.AsyncLowering.FrameOffset, "async.ctx.frameptr");
InlineFunctionInfo InlineInfo;
auto InlineRes = InlineFunction(*CallerContext, InlineInfo);
assert(InlineRes.isSuccess());
(void)InlineRes;
return Builder.CreateBitCast(FramePtrAddr, FramePtrTy);
}
case coro::ABI::Retcon:
case coro::ABI::RetconOnce: {
Argument *NewStorage = &*NewF->arg_begin();
auto FramePtrTy = Shape.FrameTy->getPointerTo();
if (Shape.RetconLowering.IsFrameInlineInStorage)
return Builder.CreateBitCast(NewStorage, FramePtrTy);
auto FramePtrPtr =
Builder.CreateBitCast(NewStorage, FramePtrTy->getPointerTo());
return Builder.CreateLoad(FramePtrTy, FramePtrPtr);
}
}
llvm_unreachable("bad ABI");
}
static void addFramePointerAttrs(AttributeList &Attrs, LLVMContext &Context,
unsigned ParamIndex,
uint64_t Size, Align Alignment) {
AttrBuilder ParamAttrs(Context);
ParamAttrs.addAttribute(Attribute::NonNull);
ParamAttrs.addAttribute(Attribute::NoAlias);
ParamAttrs.addAlignmentAttr(Alignment);
ParamAttrs.addDereferenceableAttr(Size);
Attrs = Attrs.addParamAttributes(Context, ParamIndex, ParamAttrs);
}
static void addAsyncContextAttrs(AttributeList &Attrs, LLVMContext &Context,
unsigned ParamIndex) {
AttrBuilder ParamAttrs(Context);
ParamAttrs.addAttribute(Attribute::SwiftAsync);
Attrs = Attrs.addParamAttributes(Context, ParamIndex, ParamAttrs);
}
static void addSwiftSelfAttrs(AttributeList &Attrs, LLVMContext &Context,
unsigned ParamIndex) {
AttrBuilder ParamAttrs(Context);
ParamAttrs.addAttribute(Attribute::SwiftSelf);
Attrs = Attrs.addParamAttributes(Context, ParamIndex, ParamAttrs);
}
void CoroCloner::create() {
if (!NewF) {
NewF = createCloneDeclaration(OrigF, Shape, Suffix,
OrigF.getParent()->end(), ActiveSuspend);
}
SmallVector<Instruction *> DummyArgs;
for (Argument &A : OrigF.args()) {
DummyArgs.push_back(new FreezeInst(UndefValue::get(A.getType())));
VMap[&A] = DummyArgs.back();
}
SmallVector<ReturnInst *, 4> Returns;
auto savedVisibility = NewF->getVisibility();
auto savedUnnamedAddr = NewF->getUnnamedAddr();
auto savedDLLStorageClass = NewF->getDLLStorageClass();
auto savedLinkage = NewF->getLinkage();
NewF->setLinkage(llvm::GlobalValue::ExternalLinkage);
CloneFunctionInto(NewF, &OrigF, VMap,
CloneFunctionChangeType::LocalChangesOnly, Returns);
auto &Context = NewF->getContext();
if (DISubprogram *SP = NewF->getSubprogram()) {
assert(SP != OrigF.getSubprogram() && SP->isDistinct());
if (ActiveSuspend)
if (auto DL = ActiveSuspend->getDebugLoc())
if (SP->getFile() == DL->getFile())
SP->setScopeLine(DL->getLine());
if (!SP->getDeclaration() && SP->getUnit() &&
SP->getUnit()->getSourceLanguage() == dwarf::DW_LANG_Swift)
SP->replaceLinkageName(MDString::get(Context, NewF->getName()));
}
NewF->setLinkage(savedLinkage);
NewF->setVisibility(savedVisibility);
NewF->setUnnamedAddr(savedUnnamedAddr);
NewF->setDLLStorageClass(savedDLLStorageClass);
if (Shape.ABI == coro::ABI::Switch &&
NewF->hasMetadata(LLVMContext::MD_func_sanitize))
NewF->eraseMetadata(LLVMContext::MD_func_sanitize);
auto OrigAttrs = NewF->getAttributes();
auto NewAttrs = AttributeList();
switch (Shape.ABI) {
case coro::ABI::Switch:
NewAttrs = NewAttrs.addFnAttributes(
Context, AttrBuilder(Context, OrigAttrs.getFnAttrs()));
addFramePointerAttrs(NewAttrs, Context, 0,
Shape.FrameSize, Shape.FrameAlign);
break;
case coro::ABI::Async: {
auto *ActiveAsyncSuspend = cast<CoroSuspendAsyncInst>(ActiveSuspend);
if (OrigF.hasParamAttribute(Shape.AsyncLowering.ContextArgNo,
Attribute::SwiftAsync)) {
uint32_t ArgAttributeIndices =
ActiveAsyncSuspend->getStorageArgumentIndex();
auto ContextArgIndex = ArgAttributeIndices & 0xff;
addAsyncContextAttrs(NewAttrs, Context, ContextArgIndex);
auto SwiftSelfIndex = ArgAttributeIndices >> 8;
if (SwiftSelfIndex)
addSwiftSelfAttrs(NewAttrs, Context, SwiftSelfIndex);
}
auto FnAttrs = OrigF.getAttributes().getFnAttrs();
NewAttrs = NewAttrs.addFnAttributes(Context, AttrBuilder(Context, FnAttrs));
break;
}
case coro::ABI::Retcon:
case coro::ABI::RetconOnce:
NewAttrs = Shape.RetconLowering.ResumePrototype->getAttributes();
addFramePointerAttrs(NewAttrs, Context, 0,
Shape.getRetconCoroId()->getStorageSize(),
Shape.getRetconCoroId()->getStorageAlignment());
break;
}
switch (Shape.ABI) {
case coro::ABI::Switch:
case coro::ABI::RetconOnce:
for (ReturnInst *Return : Returns)
changeToUnreachable(Return);
break;
case coro::ABI::Retcon:
break;
case coro::ABI::Async:
break;
}
NewF->setAttributes(NewAttrs);
NewF->setCallingConv(Shape.getResumeFunctionCC());
replaceEntryBlock();
Builder.SetInsertPoint(&NewF->getEntryBlock().front());
NewFramePtr = deriveNewFramePointer();
Value *OldFramePtr = VMap[Shape.FramePtr];
NewFramePtr->takeName(OldFramePtr);
OldFramePtr->replaceAllUsesWith(NewFramePtr);
auto *NewVFrame = Builder.CreateBitCast(
NewFramePtr, Type::getInt8PtrTy(Builder.getContext()), "vFrame");
Value *OldVFrame = cast<Value>(VMap[Shape.CoroBegin]);
if (OldVFrame != NewVFrame)
OldVFrame->replaceAllUsesWith(NewVFrame);
for (Instruction *DummyArg : DummyArgs) {
DummyArg->replaceAllUsesWith(UndefValue::get(DummyArg->getType()));
DummyArg->deleteValue();
}
switch (Shape.ABI) {
case coro::ABI::Switch:
if (Shape.SwitchLowering.HasFinalSuspend)
handleFinalSuspend();
break;
case coro::ABI::Async:
case coro::ABI::Retcon:
case coro::ABI::RetconOnce:
assert(ActiveSuspend != nullptr &&
"no active suspend when lowering a continuation-style coroutine");
replaceRetconOrAsyncSuspendUses();
break;
}
replaceCoroSuspends();
replaceSwiftErrorOps();
replaceCoroEnds();
salvageDebugInfo();
if (Shape.ABI == coro::ABI::Switch)
coro::replaceCoroFree(cast<CoroIdInst>(VMap[Shape.CoroBegin->getId()]),
FKind == CoroCloner::Kind::SwitchCleanup);
}
static Function *createClone(Function &F, const Twine &Suffix,
coro::Shape &Shape, CoroCloner::Kind FKind) {
CoroCloner Cloner(F, Suffix, Shape, FKind);
Cloner.create();
return Cloner.getFunction();
}
static void updateAsyncFuncPointerContextSize(coro::Shape &Shape) {
assert(Shape.ABI == coro::ABI::Async);
auto *FuncPtrStruct = cast<ConstantStruct>(
Shape.AsyncLowering.AsyncFuncPointer->getInitializer());
auto *OrigRelativeFunOffset = FuncPtrStruct->getOperand(0);
auto *OrigContextSize = FuncPtrStruct->getOperand(1);
auto *NewContextSize = ConstantInt::get(OrigContextSize->getType(),
Shape.AsyncLowering.ContextSize);
auto *NewFuncPtrStruct = ConstantStruct::get(
FuncPtrStruct->getType(), OrigRelativeFunOffset, NewContextSize);
Shape.AsyncLowering.AsyncFuncPointer->setInitializer(NewFuncPtrStruct);
}
static void replaceFrameSizeAndAlignment(coro::Shape &Shape) {
if (Shape.ABI == coro::ABI::Async)
updateAsyncFuncPointerContextSize(Shape);
for (CoroAlignInst *CA : Shape.CoroAligns) {
CA->replaceAllUsesWith(
ConstantInt::get(CA->getType(), Shape.FrameAlign.value()));
CA->eraseFromParent();
}
if (Shape.CoroSizes.empty())
return;
auto *SizeIntrin = Shape.CoroSizes.back();
Module *M = SizeIntrin->getModule();
const DataLayout &DL = M->getDataLayout();
auto Size = DL.getTypeAllocSize(Shape.FrameTy);
auto *SizeConstant = ConstantInt::get(SizeIntrin->getType(), Size);
for (CoroSizeInst *CS : Shape.CoroSizes) {
CS->replaceAllUsesWith(SizeConstant);
CS->eraseFromParent();
}
}
static void setCoroInfo(Function &F, coro::Shape &Shape,
ArrayRef<Function *> Fns) {
assert(Shape.ABI == coro::ABI::Switch);
SmallVector<Constant *, 4> Args(Fns.begin(), Fns.end());
assert(!Args.empty());
Function *Part = *Fns.begin();
Module *M = Part->getParent();
auto *ArrTy = ArrayType::get(Part->getType(), Args.size());
auto *ConstVal = ConstantArray::get(ArrTy, Args);
auto *GV = new GlobalVariable(*M, ConstVal->getType(), true,
GlobalVariable::PrivateLinkage, ConstVal,
F.getName() + Twine(".resumers"));
LLVMContext &C = F.getContext();
auto *BC = ConstantExpr::getPointerCast(GV, Type::getInt8PtrTy(C));
Shape.getSwitchCoroId()->setInfo(BC);
}
static void updateCoroFrame(coro::Shape &Shape, Function *ResumeFn,
Function *DestroyFn, Function *CleanupFn) {
assert(Shape.ABI == coro::ABI::Switch);
IRBuilder<> Builder(Shape.getInsertPtAfterFramePtr());
auto *ResumeAddr = Builder.CreateStructGEP(
Shape.FrameTy, Shape.FramePtr, coro::Shape::SwitchFieldIndex::Resume,
"resume.addr");
Builder.CreateStore(ResumeFn, ResumeAddr);
Value *DestroyOrCleanupFn = DestroyFn;
CoroIdInst *CoroId = Shape.getSwitchCoroId();
if (CoroAllocInst *CA = CoroId->getCoroAlloc()) {
DestroyOrCleanupFn = Builder.CreateSelect(CA, DestroyFn, CleanupFn);
}
auto *DestroyAddr = Builder.CreateStructGEP(
Shape.FrameTy, Shape.FramePtr, coro::Shape::SwitchFieldIndex::Destroy,
"destroy.addr");
Builder.CreateStore(DestroyOrCleanupFn, DestroyAddr);
}
static void postSplitCleanup(Function &F) {
removeUnreachableBlocks(F);
#ifndef NDEBUG
if (verifyFunction(F, &errs()))
report_fatal_error("Broken function");
#endif
}
static void
scanPHIsAndUpdateValueMap(Instruction *Prev, BasicBlock *NewBlock,
DenseMap<Value *, Value *> &ResolvedValues) {
auto *PrevBB = Prev->getParent();
for (PHINode &PN : NewBlock->phis()) {
auto V = PN.getIncomingValueForBlock(PrevBB);
auto VI = ResolvedValues.find(V);
if (VI != ResolvedValues.end())
V = VI->second;
ResolvedValues[&PN] = V;
}
}
static bool simplifyTerminatorLeadingToRet(Instruction *InitialInst) {
DenseMap<Value *, Value *> ResolvedValues;
BasicBlock *UnconditionalSucc = nullptr;
assert(InitialInst->getModule());
const DataLayout &DL = InitialInst->getModule()->getDataLayout();
auto GetFirstValidInstruction = [](Instruction *I) {
while (I) {
if (isa<BitCastInst>(I) || I->isDebugOrPseudoInst() ||
I->isLifetimeStartOrEnd())
I = I->getNextNode();
else if (isInstructionTriviallyDead(I))
I = &*I->eraseFromParent();
else
break;
}
return I;
};
auto TryResolveConstant = [&ResolvedValues](Value *V) {
auto It = ResolvedValues.find(V);
if (It != ResolvedValues.end())
V = It->second;
return dyn_cast<ConstantInt>(V);
};
Instruction *I = InitialInst;
while (I->isTerminator() || isa<CmpInst>(I)) {
if (isa<ReturnInst>(I)) {
if (I != InitialInst) {
if (UnconditionalSucc)
UnconditionalSucc->removePredecessor(InitialInst->getParent(), true);
ReplaceInstWithInst(InitialInst, I->clone());
}
return true;
}
if (auto *BR = dyn_cast<BranchInst>(I)) {
if (BR->isUnconditional()) {
BasicBlock *Succ = BR->getSuccessor(0);
if (I == InitialInst)
UnconditionalSucc = Succ;
scanPHIsAndUpdateValueMap(I, Succ, ResolvedValues);
I = GetFirstValidInstruction(Succ->getFirstNonPHIOrDbgOrLifetime());
continue;
}
BasicBlock *BB = BR->getParent();
if (ConstantFoldTerminator(BB, true)) {
I = BB->getTerminator();
continue;
}
} else if (auto *CondCmp = dyn_cast<CmpInst>(I)) {
auto *BR = dyn_cast<BranchInst>(
GetFirstValidInstruction(CondCmp->getNextNode()));
if (!BR || !BR->isConditional() || CondCmp != BR->getCondition())
return false;
ConstantInt *Cond0 = TryResolveConstant(CondCmp->getOperand(0));
auto *Cond1 = dyn_cast<ConstantInt>(CondCmp->getOperand(1));
if (!Cond0 || !Cond1)
return false;
auto *ConstResult =
dyn_cast_or_null<ConstantInt>(ConstantFoldCompareInstOperands(
CondCmp->getPredicate(), Cond0, Cond1, DL));
if (!ConstResult)
return false;
CondCmp->replaceAllUsesWith(ConstResult);
CondCmp->eraseFromParent();
I = BR;
continue;
} else if (auto *SI = dyn_cast<SwitchInst>(I)) {
ConstantInt *Cond = TryResolveConstant(SI->getCondition());
if (!Cond)
return false;
BasicBlock *BB = SI->findCaseValue(Cond)->getCaseSuccessor();
scanPHIsAndUpdateValueMap(I, BB, ResolvedValues);
I = GetFirstValidInstruction(BB->getFirstNonPHIOrDbgOrLifetime());
continue;
}
return false;
}
return false;
}
static bool shouldBeMustTail(const CallInst &CI, const Function &F) {
if (CI.isInlineAsm())
return false;
FunctionType *CalleeTy = CI.getFunctionType();
if (!CalleeTy->getReturnType()->isVoidTy() || (CalleeTy->getNumParams() != 1))
return false;
Type *CalleeParmTy = CalleeTy->getParamType(0);
if (!CalleeParmTy->isPointerTy() ||
(CalleeParmTy->getPointerAddressSpace() != 0))
return false;
if (CI.getCallingConv() != F.getCallingConv())
return false;
static const Attribute::AttrKind ABIAttrs[] = {
Attribute::StructRet, Attribute::ByVal, Attribute::InAlloca,
Attribute::Preallocated, Attribute::InReg, Attribute::Returned,
Attribute::SwiftSelf, Attribute::SwiftError};
AttributeList Attrs = CI.getAttributes();
for (auto AK : ABIAttrs)
if (Attrs.hasParamAttr(0, AK))
return false;
return true;
}
static void addMustTailToCoroResumes(Function &F) {
bool changed = false;
SmallVector<CallInst *, 4> Resumes;
for (auto &I : instructions(F))
if (auto *Call = dyn_cast<CallInst>(&I))
if (shouldBeMustTail(*Call, F))
Resumes.push_back(Call);
for (CallInst *Call : Resumes)
if (simplifyTerminatorLeadingToRet(Call->getNextNode())) {
Call->setTailCallKind(CallInst::TCK_MustTail);
changed = true;
}
if (changed)
removeUnreachableBlocks(F);
}
static void handleNoSuspendCoroutine(coro::Shape &Shape) {
auto *CoroBegin = Shape.CoroBegin;
auto *CoroId = CoroBegin->getId();
auto *AllocInst = CoroId->getCoroAlloc();
switch (Shape.ABI) {
case coro::ABI::Switch: {
auto SwitchId = cast<CoroIdInst>(CoroId);
coro::replaceCoroFree(SwitchId, AllocInst != nullptr);
if (AllocInst) {
IRBuilder<> Builder(AllocInst);
auto *Frame = Builder.CreateAlloca(Shape.FrameTy);
Frame->setAlignment(Shape.FrameAlign);
auto *VFrame = Builder.CreateBitCast(Frame, Builder.getInt8PtrTy());
AllocInst->replaceAllUsesWith(Builder.getFalse());
AllocInst->eraseFromParent();
CoroBegin->replaceAllUsesWith(VFrame);
} else {
CoroBegin->replaceAllUsesWith(CoroBegin->getMem());
}
break;
}
case coro::ABI::Async:
case coro::ABI::Retcon:
case coro::ABI::RetconOnce:
CoroBegin->replaceAllUsesWith(UndefValue::get(CoroBegin->getType()));
break;
}
CoroBegin->eraseFromParent();
}
static bool hasCallsInBlockBetween(Instruction *From, Instruction *To) {
for (Instruction *I = From; I != To; I = I->getNextNode()) {
if (isa<IntrinsicInst>(I))
continue;
if (isa<CallBase>(I))
return true;
}
return false;
}
static bool hasCallsInBlocksBetween(BasicBlock *SaveBB, BasicBlock *ResDesBB) {
SmallPtrSet<BasicBlock *, 8> Set;
SmallVector<BasicBlock *, 8> Worklist;
Set.insert(SaveBB);
Worklist.push_back(ResDesBB);
while (!Worklist.empty()) {
auto *BB = Worklist.pop_back_val();
Set.insert(BB);
for (auto *Pred : predecessors(BB))
if (!Set.contains(Pred))
Worklist.push_back(Pred);
}
Set.erase(SaveBB);
Set.erase(ResDesBB);
for (auto *BB : Set)
if (hasCallsInBlockBetween(BB->getFirstNonPHI(), nullptr))
return true;
return false;
}
static bool hasCallsBetween(Instruction *Save, Instruction *ResumeOrDestroy) {
auto *SaveBB = Save->getParent();
auto *ResumeOrDestroyBB = ResumeOrDestroy->getParent();
if (SaveBB == ResumeOrDestroyBB)
return hasCallsInBlockBetween(Save->getNextNode(), ResumeOrDestroy);
if (hasCallsInBlockBetween(Save->getNextNode(), nullptr))
return true;
if (hasCallsInBlockBetween(ResumeOrDestroyBB->getFirstNonPHI(),
ResumeOrDestroy))
return true;
if (hasCallsInBlocksBetween(SaveBB, ResumeOrDestroyBB))
return true;
return false;
}
static bool simplifySuspendPoint(CoroSuspendInst *Suspend,
CoroBeginInst *CoroBegin) {
Instruction *Prev = Suspend->getPrevNode();
if (!Prev) {
auto *Pred = Suspend->getParent()->getSinglePredecessor();
if (!Pred)
return false;
Prev = Pred->getTerminator();
}
CallBase *CB = dyn_cast<CallBase>(Prev);
if (!CB)
return false;
auto *Callee = CB->getCalledOperand()->stripPointerCasts();
auto *SubFn = dyn_cast<CoroSubFnInst>(Callee);
if (!SubFn)
return false;
if (SubFn->getFrame() != CoroBegin)
return false;
auto *Save = Suspend->getCoroSave();
if (hasCallsBetween(Save, CB))
return false;
Suspend->replaceAllUsesWith(SubFn->getRawIndex());
Suspend->eraseFromParent();
Save->eraseFromParent();
if (auto *Invoke = dyn_cast<InvokeInst>(CB)) {
BranchInst::Create(Invoke->getNormalDest(), Invoke);
}
auto *CalledValue = CB->getCalledOperand();
CB->eraseFromParent();
if (CalledValue != SubFn && CalledValue->user_empty())
if (auto *I = dyn_cast<Instruction>(CalledValue))
I->eraseFromParent();
if (SubFn->user_empty())
SubFn->eraseFromParent();
return true;
}
static void simplifySuspendPoints(coro::Shape &Shape) {
if (Shape.ABI != coro::ABI::Switch)
return;
auto &S = Shape.CoroSuspends;
size_t I = 0, N = S.size();
if (N == 0)
return;
while (true) {
auto SI = cast<CoroSuspendInst>(S[I]);
if (!SI->isFinal() && simplifySuspendPoint(SI, Shape.CoroBegin)) {
if (--N == I)
break;
std::swap(S[I], S[N]);
continue;
}
if (++I == N)
break;
}
S.resize(N);
}
static void splitSwitchCoroutine(Function &F, coro::Shape &Shape,
SmallVectorImpl<Function *> &Clones,
TargetTransformInfo &TTI) {
assert(Shape.ABI == coro::ABI::Switch);
createResumeEntryBlock(F, Shape);
auto ResumeClone = createClone(F, ".resume", Shape,
CoroCloner::Kind::SwitchResume);
auto DestroyClone = createClone(F, ".destroy", Shape,
CoroCloner::Kind::SwitchUnwind);
auto CleanupClone = createClone(F, ".cleanup", Shape,
CoroCloner::Kind::SwitchCleanup);
postSplitCleanup(*ResumeClone);
postSplitCleanup(*DestroyClone);
postSplitCleanup(*CleanupClone);
if (TTI.supportsTailCalls())
addMustTailToCoroResumes(*ResumeClone);
updateCoroFrame(Shape, ResumeClone, DestroyClone, CleanupClone);
assert(Clones.empty());
Clones.push_back(ResumeClone);
Clones.push_back(DestroyClone);
Clones.push_back(CleanupClone);
setCoroInfo(F, Shape, Clones);
}
static void replaceAsyncResumeFunction(CoroSuspendAsyncInst *Suspend,
Value *Continuation) {
auto *ResumeIntrinsic = Suspend->getResumeFunction();
auto &Context = Suspend->getParent()->getParent()->getContext();
auto *Int8PtrTy = Type::getInt8PtrTy(Context);
IRBuilder<> Builder(ResumeIntrinsic);
auto *Val = Builder.CreateBitOrPointerCast(Continuation, Int8PtrTy);
ResumeIntrinsic->replaceAllUsesWith(Val);
ResumeIntrinsic->eraseFromParent();
Suspend->setOperand(CoroSuspendAsyncInst::ResumeFunctionArg,
UndefValue::get(Int8PtrTy));
}
static void coerceArguments(IRBuilder<> &Builder, FunctionType *FnTy,
ArrayRef<Value *> FnArgs,
SmallVectorImpl<Value *> &CallArgs) {
size_t ArgIdx = 0;
for (auto paramTy : FnTy->params()) {
assert(ArgIdx < FnArgs.size());
if (paramTy != FnArgs[ArgIdx]->getType())
CallArgs.push_back(
Builder.CreateBitOrPointerCast(FnArgs[ArgIdx], paramTy));
else
CallArgs.push_back(FnArgs[ArgIdx]);
++ArgIdx;
}
}
CallInst *coro::createMustTailCall(DebugLoc Loc, Function *MustTailCallFn,
ArrayRef<Value *> Arguments,
IRBuilder<> &Builder) {
auto *FnTy = MustTailCallFn->getFunctionType();
SmallVector<Value *, 8> CallArgs;
coerceArguments(Builder, FnTy, Arguments, CallArgs);
auto *TailCall = Builder.CreateCall(FnTy, MustTailCallFn, CallArgs);
TailCall->setTailCallKind(CallInst::TCK_MustTail);
TailCall->setDebugLoc(Loc);
TailCall->setCallingConv(MustTailCallFn->getCallingConv());
return TailCall;
}
static void splitAsyncCoroutine(Function &F, coro::Shape &Shape,
SmallVectorImpl<Function *> &Clones) {
assert(Shape.ABI == coro::ABI::Async);
assert(Clones.empty());
F.removeFnAttr(Attribute::NoReturn);
F.removeRetAttr(Attribute::NoAlias);
F.removeRetAttr(Attribute::NonNull);
auto &Context = F.getContext();
auto *Int8PtrTy = Type::getInt8PtrTy(Context);
auto *Id = cast<CoroIdAsyncInst>(Shape.CoroBegin->getId());
IRBuilder<> Builder(Id);
auto *FramePtr = Id->getStorage();
FramePtr = Builder.CreateBitOrPointerCast(FramePtr, Int8PtrTy);
FramePtr = Builder.CreateConstInBoundsGEP1_32(
Type::getInt8Ty(Context), FramePtr, Shape.AsyncLowering.FrameOffset,
"async.ctx.frameptr");
{
TrackingVH<Value> Handle(Shape.FramePtr);
Shape.CoroBegin->replaceAllUsesWith(FramePtr);
Shape.FramePtr = Handle.getValPtr();
}
auto NextF = std::next(F.getIterator());
Clones.reserve(Shape.CoroSuspends.size());
for (size_t Idx = 0, End = Shape.CoroSuspends.size(); Idx != End; ++Idx) {
auto *Suspend = cast<CoroSuspendAsyncInst>(Shape.CoroSuspends[Idx]);
auto ResumeNameSuffix = ".resume.";
auto ProjectionFunctionName =
Suspend->getAsyncContextProjectionFunction()->getName();
bool UseSwiftMangling = false;
if (ProjectionFunctionName.equals("__swift_async_resume_project_context")) {
ResumeNameSuffix = "TQ";
UseSwiftMangling = true;
} else if (ProjectionFunctionName.equals(
"__swift_async_resume_get_context")) {
ResumeNameSuffix = "TY";
UseSwiftMangling = true;
}
auto *Continuation = createCloneDeclaration(
F, Shape,
UseSwiftMangling ? ResumeNameSuffix + Twine(Idx) + "_"
: ResumeNameSuffix + Twine(Idx),
NextF, Suspend);
Clones.push_back(Continuation);
auto *SuspendBB = Suspend->getParent();
auto *NewSuspendBB = SuspendBB->splitBasicBlock(Suspend);
auto *Branch = cast<BranchInst>(SuspendBB->getTerminator());
auto *ReturnBB =
BasicBlock::Create(F.getContext(), "coro.return", &F, NewSuspendBB);
Branch->setSuccessor(0, ReturnBB);
IRBuilder<> Builder(ReturnBB);
auto *Fn = Suspend->getMustTailCallFunction();
SmallVector<Value *, 8> Args(Suspend->args());
auto FnArgs = ArrayRef<Value *>(Args).drop_front(
CoroSuspendAsyncInst::MustTailCallFuncArg + 1);
auto *TailCall =
coro::createMustTailCall(Suspend->getDebugLoc(), Fn, FnArgs, Builder);
Builder.CreateRetVoid();
InlineFunctionInfo FnInfo;
auto InlineRes = InlineFunction(*TailCall, FnInfo);
assert(InlineRes.isSuccess() && "Expected inlining to succeed");
(void)InlineRes;
replaceAsyncResumeFunction(Suspend, Continuation);
}
assert(Clones.size() == Shape.CoroSuspends.size());
for (size_t Idx = 0, End = Shape.CoroSuspends.size(); Idx != End; ++Idx) {
auto *Suspend = Shape.CoroSuspends[Idx];
auto *Clone = Clones[Idx];
CoroCloner(F, "resume." + Twine(Idx), Shape, Clone, Suspend).create();
}
}
static void splitRetconCoroutine(Function &F, coro::Shape &Shape,
SmallVectorImpl<Function *> &Clones) {
assert(Shape.ABI == coro::ABI::Retcon ||
Shape.ABI == coro::ABI::RetconOnce);
assert(Clones.empty());
F.removeFnAttr(Attribute::NoReturn);
F.removeRetAttr(Attribute::NoAlias);
F.removeRetAttr(Attribute::NonNull);
auto *Id = cast<AnyCoroIdRetconInst>(Shape.CoroBegin->getId());
Value *RawFramePtr;
if (Shape.RetconLowering.IsFrameInlineInStorage) {
RawFramePtr = Id->getStorage();
} else {
IRBuilder<> Builder(Id);
const DataLayout &DL = F.getParent()->getDataLayout();
auto Size = DL.getTypeAllocSize(Shape.FrameTy);
RawFramePtr = Shape.emitAlloc(Builder, Builder.getInt64(Size), nullptr);
RawFramePtr =
Builder.CreateBitCast(RawFramePtr, Shape.CoroBegin->getType());
auto Dest = Builder.CreateBitCast(Id->getStorage(),
RawFramePtr->getType()->getPointerTo());
Builder.CreateStore(RawFramePtr, Dest);
}
{
TrackingVH<Value> Handle(Shape.FramePtr);
Shape.CoroBegin->replaceAllUsesWith(RawFramePtr);
Shape.FramePtr = Handle.getValPtr();
}
BasicBlock *ReturnBB = nullptr;
SmallVector<PHINode *, 4> ReturnPHIs;
auto NextF = std::next(F.getIterator());
Clones.reserve(Shape.CoroSuspends.size());
for (size_t i = 0, e = Shape.CoroSuspends.size(); i != e; ++i) {
auto Suspend = cast<CoroSuspendRetconInst>(Shape.CoroSuspends[i]);
auto Continuation =
createCloneDeclaration(F, Shape, ".resume." + Twine(i), NextF, nullptr);
Clones.push_back(Continuation);
auto SuspendBB = Suspend->getParent();
auto NewSuspendBB = SuspendBB->splitBasicBlock(Suspend);
auto Branch = cast<BranchInst>(SuspendBB->getTerminator());
if (!ReturnBB) {
ReturnBB = BasicBlock::Create(F.getContext(), "coro.return", &F,
NewSuspendBB);
Shape.RetconLowering.ReturnBlock = ReturnBB;
IRBuilder<> Builder(ReturnBB);
assert(ReturnPHIs.empty());
ReturnPHIs.push_back(Builder.CreatePHI(Continuation->getType(),
Shape.CoroSuspends.size()));
for (auto ResultTy : Shape.getRetconResultTypes())
ReturnPHIs.push_back(Builder.CreatePHI(ResultTy,
Shape.CoroSuspends.size()));
auto RetTy = F.getReturnType();
auto CastedContinuationTy =
(ReturnPHIs.size() == 1 ? RetTy : RetTy->getStructElementType(0));
auto *CastedContinuation =
Builder.CreateBitCast(ReturnPHIs[0], CastedContinuationTy);
Value *RetV;
if (ReturnPHIs.size() == 1) {
RetV = CastedContinuation;
} else {
RetV = UndefValue::get(RetTy);
RetV = Builder.CreateInsertValue(RetV, CastedContinuation, 0);
for (size_t I = 1, E = ReturnPHIs.size(); I != E; ++I)
RetV = Builder.CreateInsertValue(RetV, ReturnPHIs[I], I);
}
Builder.CreateRet(RetV);
}
Branch->setSuccessor(0, ReturnBB);
ReturnPHIs[0]->addIncoming(Continuation, SuspendBB);
size_t NextPHIIndex = 1;
for (auto &VUse : Suspend->value_operands())
ReturnPHIs[NextPHIIndex++]->addIncoming(&*VUse, SuspendBB);
assert(NextPHIIndex == ReturnPHIs.size());
}
assert(Clones.size() == Shape.CoroSuspends.size());
for (size_t i = 0, e = Shape.CoroSuspends.size(); i != e; ++i) {
auto Suspend = Shape.CoroSuspends[i];
auto Clone = Clones[i];
CoroCloner(F, "resume." + Twine(i), Shape, Clone, Suspend).create();
}
}
namespace {
class PrettyStackTraceFunction : public PrettyStackTraceEntry {
Function &F;
public:
PrettyStackTraceFunction(Function &F) : F(F) {}
void print(raw_ostream &OS) const override {
OS << "While splitting coroutine ";
F.printAsOperand(OS, false, F.getParent());
OS << "\n";
}
};
}
static coro::Shape splitCoroutine(Function &F,
SmallVectorImpl<Function *> &Clones,
TargetTransformInfo &TTI,
bool OptimizeFrame) {
PrettyStackTraceFunction prettyStackTrace(F);
removeUnreachableBlocks(F);
coro::Shape Shape(F, OptimizeFrame);
if (!Shape.CoroBegin)
return Shape;
simplifySuspendPoints(Shape);
buildCoroutineFrame(F, Shape);
replaceFrameSizeAndAlignment(Shape);
if (Shape.CoroSuspends.empty()) {
handleNoSuspendCoroutine(Shape);
} else {
switch (Shape.ABI) {
case coro::ABI::Switch:
splitSwitchCoroutine(F, Shape, Clones, TTI);
break;
case coro::ABI::Async:
splitAsyncCoroutine(F, Shape, Clones);
break;
case coro::ABI::Retcon:
case coro::ABI::RetconOnce:
splitRetconCoroutine(F, Shape, Clones);
break;
}
}
replaceSwiftErrorOps(F, Shape, nullptr);
SmallVector<DbgVariableIntrinsic *, 8> Worklist;
SmallDenseMap<llvm::Value *, llvm::AllocaInst *, 4> DbgPtrAllocaCache;
for (auto &BB : F) {
for (auto &I : BB) {
if (auto *DDI = dyn_cast<DbgDeclareInst>(&I)) {
Worklist.push_back(DDI);
continue;
}
if (auto *DDI = dyn_cast<DbgAddrIntrinsic>(&I)) {
Worklist.push_back(DDI);
continue;
}
}
}
for (auto *DDI : Worklist)
coro::salvageDebugInfo(DbgPtrAllocaCache, DDI, Shape.OptimizeFrame);
return Shape;
}
static void removeCoroEnds(const coro::Shape &Shape) {
for (auto End : Shape.CoroEnds) {
replaceCoroEnd(End, Shape, Shape.FramePtr, false, nullptr);
}
}
static void updateCallGraphAfterCoroutineSplit(
LazyCallGraph::Node &N, const coro::Shape &Shape,
const SmallVectorImpl<Function *> &Clones, LazyCallGraph::SCC &C,
LazyCallGraph &CG, CGSCCAnalysisManager &AM, CGSCCUpdateResult &UR,
FunctionAnalysisManager &FAM) {
if (!Shape.CoroBegin)
return;
if (Shape.ABI != coro::ABI::Switch)
removeCoroEnds(Shape);
else {
for (llvm::AnyCoroEndInst *End : Shape.CoroEnds) {
auto &Context = End->getContext();
End->replaceAllUsesWith(ConstantInt::getFalse(Context));
End->eraseFromParent();
}
}
if (!Clones.empty()) {
switch (Shape.ABI) {
case coro::ABI::Switch:
for (Function *Clone : Clones)
CG.addSplitFunction(N.getFunction(), *Clone);
break;
case coro::ABI::Async:
case coro::ABI::Retcon:
case coro::ABI::RetconOnce:
if (!Clones.empty())
CG.addSplitRefRecursiveFunctions(N.getFunction(), Clones);
break;
}
updateCGAndAnalysisManagerForCGSCCPass(CG, C, N, AM, UR, FAM);
}
postSplitCleanup(N.getFunction());
updateCGAndAnalysisManagerForFunctionPass(CG, C, N, AM, UR, FAM);
}
static void replacePrepare(CallInst *Prepare, LazyCallGraph &CG,
LazyCallGraph::SCC &C) {
auto CastFn = Prepare->getArgOperand(0); auto Fn = CastFn->stripPointerCasts();
for (Use &U : llvm::make_early_inc_range(Prepare->uses())) {
auto *Cast = dyn_cast<BitCastInst>(U.getUser());
if (!Cast || Cast->getType() != Fn->getType())
continue;
Cast->replaceAllUsesWith(Fn);
Cast->eraseFromParent();
}
Prepare->replaceAllUsesWith(CastFn);
Prepare->eraseFromParent();
while (auto *Cast = dyn_cast<BitCastInst>(CastFn)) {
if (!Cast->use_empty())
break;
CastFn = Cast->getOperand(0);
Cast->eraseFromParent();
}
}
static bool replaceAllPrepares(Function *PrepareFn, LazyCallGraph &CG,
LazyCallGraph::SCC &C) {
bool Changed = false;
for (Use &P : llvm::make_early_inc_range(PrepareFn->uses())) {
auto *Prepare = cast<CallInst>(P.getUser());
replacePrepare(Prepare, CG, C);
Changed = true;
}
return Changed;
}
static void addPrepareFunction(const Module &M,
SmallVectorImpl<Function *> &Fns,
StringRef Name) {
auto *PrepareFn = M.getFunction(Name);
if (PrepareFn && !PrepareFn->use_empty())
Fns.push_back(PrepareFn);
}
PreservedAnalyses CoroSplitPass::run(LazyCallGraph::SCC &C,
CGSCCAnalysisManager &AM,
LazyCallGraph &CG, CGSCCUpdateResult &UR) {
Module &M = *C.begin()->getFunction().getParent();
auto &FAM =
AM.getResult<FunctionAnalysisManagerCGSCCProxy>(C, CG).getManager();
SmallVector<Function *, 2> PrepareFns;
addPrepareFunction(M, PrepareFns, "llvm.coro.prepare.retcon");
addPrepareFunction(M, PrepareFns, "llvm.coro.prepare.async");
SmallVector<LazyCallGraph::Node *> Coroutines;
for (LazyCallGraph::Node &N : C)
if (N.getFunction().isPresplitCoroutine())
Coroutines.push_back(&N);
if (Coroutines.empty() && PrepareFns.empty())
return PreservedAnalyses::all();
if (Coroutines.empty()) {
for (auto *PrepareFn : PrepareFns) {
replaceAllPrepares(PrepareFn, CG, C);
}
}
for (LazyCallGraph::Node *N : Coroutines) {
Function &F = N->getFunction();
LLVM_DEBUG(dbgs() << "CoroSplit: Processing coroutine '" << F.getName()
<< "\n");
F.setSplittedCoroutine();
SmallVector<Function *, 4> Clones;
const coro::Shape Shape = splitCoroutine(
F, Clones, FAM.getResult<TargetIRAnalysis>(F), OptimizeFrame);
updateCallGraphAfterCoroutineSplit(*N, Shape, Clones, C, CG, AM, UR, FAM);
if (!Shape.CoroSuspends.empty()) {
UR.CWorklist.insert(&C);
for (Function *Clone : Clones)
UR.CWorklist.insert(CG.lookupSCC(CG.get(*Clone)));
}
}
if (!PrepareFns.empty()) {
for (auto *PrepareFn : PrepareFns) {
replaceAllPrepares(PrepareFn, CG, C);
}
}
return PreservedAnalyses::none();
}