auto *FramePtr =
cast<Instruction>(Builder.CreateBitCast(CB, FramePtrTy, "FramePtr"));
DominatorTree DT(*CB->getFunction());
+ SmallDenseMap<llvm::Value *, llvm::AllocaInst *, 4> DbgPtrAllocaCache;
// Create a GEP with the given index into the coroutine frame for the original
// value Orig. Appends an extra 0 index for array-allocas, preserving the
CurrentReload = Builder.CreateLoad(
FrameTy->getElementType(FrameData.getFieldIndex(E.first)), GEP,
E.first->getName() + Twine(".reload"));
+
+ TinyPtrVector<DbgDeclareInst *> DIs = FindDbgDeclareUses(Def);
+ for (DbgDeclareInst *DDI : DIs) {
+ bool AllowUnresolved = false;
+ // This dbg.declare is preserved for all coro-split function
+ // fragments. It will be unreachable in the main function, and
+ // processed by coro::salvageDebugInfo() by CoroCloner.
+ DIBuilder(*CurrentBlock->getParent()->getParent(), AllowUnresolved)
+ .insertDeclare(CurrentReload, DDI->getVariable(),
+ DDI->getExpression(), DDI->getDebugLoc(),
+ &*Builder.GetInsertPoint());
+ // This dbg.declare is for the main function entry point. It
+ // will be deleted in all coro-split functions.
+ coro::salvageDebugInfo(DbgPtrAllocaCache, DDI);
+ }
}
// If we have a single edge PHINode, remove it and replace it with a
SmallPtrSet<BasicBlock *, 4> SeenDbgBBs;
TinyPtrVector<DbgDeclareInst *> DIs = FindDbgDeclareUses(Alloca);
- DIBuilder DIB(*Alloca->getModule(), /*AllowUnresolved*/ false);
- Instruction *FirstDbgDecl = nullptr;
-
- if (!DIs.empty()) {
- FirstDbgDecl = DIB.insertDeclare(G, DIs.front()->getVariable(),
- DIs.front()->getExpression(),
- DIs.front()->getDebugLoc(), DIs.front());
- SeenDbgBBs.insert(DIs.front()->getParent());
- }
+ if (!DIs.empty())
+ DIBuilder(*Alloca->getModule(),
+ /*AllowUnresolved*/ false)
+ .insertDeclare(G, DIs.front()->getVariable(),
+ DIs.front()->getExpression(),
+ DIs.front()->getDebugLoc(), DIs.front());
for (auto *DI : FindDbgDeclareUses(Alloca))
DI->eraseFromParent();
replaceDbgUsesWithUndef(Alloca);
- for (Instruction *I : UsersToUpdate) {
+ for (Instruction *I : UsersToUpdate)
I->replaceUsesOfWith(Alloca, G);
-
- // After cloning, transformations might not guarantee that all uses
- // of this alloca are dominated by the already existing dbg.declare's,
- // compromising the debug quality. Instead of writing another
- // transformation to patch each clone, go ahead and early populate
- // basic blocks that use such allocas with more debug info.
- if (SeenDbgBBs.count(I->getParent()))
- continue;
-
- // If there isn't a prior dbg.declare for this alloca, it probably
- // means the state hasn't changed prior to one of the relevant suspend
- // point for this frame access.
- if (!FirstDbgDecl)
- continue;
-
- // These instructions are all dominated by the alloca, insert the
- // dbg.value in the beginning of the BB to enhance debugging
- // experience and allow values to be inspected as early as possible.
- // Prefer dbg.value over dbg.declare since it better sets expectations
- // that control flow can be later changed by other passes.
- auto *DI = cast<DbgDeclareInst>(FirstDbgDecl);
- BasicBlock *CurrentBlock = I->getParent();
- auto *DerefExpr =
- DIExpression::append(DI->getExpression(), dwarf::DW_OP_deref);
- DIB.insertDbgValueIntrinsic(G, DI->getVariable(), DerefExpr,
- DI->getDebugLoc(),
- &*CurrentBlock->getFirstInsertionPt());
- SeenDbgBBs.insert(CurrentBlock);
- }
}
Builder.SetInsertPoint(FramePtr->getNextNode());
for (const auto &A : FrameData.Allocas) {
}
}
+void coro::salvageDebugInfo(
+ SmallDenseMap<llvm::Value *, llvm::AllocaInst *, 4> &DbgPtrAllocaCache,
+ DbgDeclareInst *DDI, bool LoadFromFramePtr) {
+ Function *F = DDI->getFunction();
+ IRBuilder<> Builder(F->getContext());
+ auto InsertPt = F->getEntryBlock().getFirstInsertionPt();
+ while (isa<IntrinsicInst>(InsertPt))
+ ++InsertPt;
+ Builder.SetInsertPoint(&F->getEntryBlock(), InsertPt);
+ DIExpression *Expr = DDI->getExpression();
+ // Follow the pointer arithmetic all the way to the incoming
+ // function argument and convert into a DIExpression.
+ Value *Storage = DDI->getAddress();
+ while (Storage) {
+ if (auto *LdInst = dyn_cast<LoadInst>(Storage)) {
+ Storage = LdInst->getOperand(0);
+ } else if (auto *StInst = dyn_cast<StoreInst>(Storage)) {
+ Storage = StInst->getOperand(0);
+ } else if (auto *GEPInst = dyn_cast<GetElementPtrInst>(Storage)) {
+ Expr = llvm::salvageDebugInfoImpl(*GEPInst, Expr,
+ /*WithStackValue=*/false);
+ Storage = GEPInst->getOperand(0);
+ } else if (auto *BCInst = dyn_cast<llvm::BitCastInst>(Storage))
+ Storage = BCInst->getOperand(0);
+ else
+ break;
+ }
+ // Store a pointer to the coroutine frame object in an alloca so it
+ // is available throughout the function when producing unoptimized
+ // code. Extending the lifetime this way is correct because the
+ // variable has been declared by a dbg.declare intrinsic.
+ if (auto Arg = dyn_cast_or_null<llvm::Argument>(Storage)) {
+ auto &Cached = DbgPtrAllocaCache[Storage];
+ if (!Cached) {
+ Cached = Builder.CreateAlloca(Storage->getType(), 0, nullptr,
+ Arg->getName() + ".debug");
+ Builder.CreateStore(Storage, Cached);
+ }
+ Storage = Cached;
+ }
+ // The FramePtr object adds one extra layer of indirection that
+ // needs to be unwrapped.
+ if (LoadFromFramePtr)
+ Expr = DIExpression::prepend(Expr, DIExpression::DerefBefore);
+ auto &VMContext = DDI->getFunction()->getContext();
+ DDI->setOperand(
+ 0, MetadataAsValue::get(VMContext, ValueAsMetadata::get(Storage)));
+ DDI->setOperand(2, MetadataAsValue::get(VMContext, Expr));
+ if (auto *InsertPt = dyn_cast_or_null<Instruction>(Storage))
+ DDI->moveAfter(InsertPt);
+}
+
void coro::buildCoroutineFrame(Function &F, Shape &Shape) {
eliminateSwiftError(F, Shape);
void replaceCoroFree(CoroIdInst *CoroId, bool Elide);
void updateCallGraph(Function &Caller, ArrayRef<Function *> Funcs,
CallGraph &CG, CallGraphSCC &SCC);
+/// Recover a dbg.declare prepared by the frontend and emit an alloca
+/// holding a pointer to the coroutine frame.
+void salvageDebugInfo(
+ SmallDenseMap<llvm::Value *, llvm::AllocaInst *, 4> &DbgPtrAllocaCache,
+ DbgDeclareInst *DDI, bool LoadFromCoroFrame = false);
// Keeps data and helper functions for lowering coroutine intrinsics.
struct LowererBase {
void replaceCoroSuspends();
void replaceCoroEnds();
void replaceSwiftErrorOps();
+ void salvageDebugInfo();
void handleFinalSuspend();
};
::replaceSwiftErrorOps(*NewF, Shape, &VMap);
}
+void CoroCloner::salvageDebugInfo() {
+ SmallVector<DbgDeclareInst *, 8> Worklist;
+ SmallDenseMap<llvm::Value *, llvm::AllocaInst *, 4> DbgPtrAllocaCache;
+ for (auto &BB : *NewF)
+ for (auto &I : BB)
+ if (auto *DDI = dyn_cast<DbgDeclareInst>(&I))
+ Worklist.push_back(DDI);
+ for (DbgDeclareInst *DDI : Worklist) {
+ // This is a heuristic that detects declares left by CoroFrame.
+ bool LoadFromFramePtr = !isa<AllocaInst>(DDI->getAddress());
+ coro::salvageDebugInfo(DbgPtrAllocaCache, DDI, LoadFromFramePtr);
+ }
+ // Remove all salvaged dbg.declare intrinsics that became
+ // either unreachable or stale due to the CoroSplit transformation.
+ auto IsUnreachableBlock = [&](BasicBlock *BB) {
+ return BB->hasNPredecessors(0) && BB != &NewF->getEntryBlock();
+ };
+ for (DbgDeclareInst *DDI : Worklist) {
+ if (IsUnreachableBlock(DDI->getParent()))
+ DDI->eraseFromParent();
+ else if (auto *Alloca = dyn_cast_or_null<AllocaInst>(DDI->getAddress())) {
+ // Count all non-debuginfo uses in reachable blocks.
+ unsigned Uses = 0;
+ for (auto *User : DDI->getAddress()->users())
+ if (auto *I = dyn_cast<Instruction>(User))
+ if (!isa<AllocaInst>(I) && !IsUnreachableBlock(I->getParent()))
+ ++Uses;
+ if (!Uses)
+ DDI->eraseFromParent();
+ }
+ }
+}
+
void CoroCloner::replaceEntryBlock() {
// In the original function, the AllocaSpillBlock is a block immediately
// following the allocation of the frame object which defines GEPs for
// Remove coro.end intrinsics.
replaceCoroEnds();
+ // Salvage debug info that points into the coroutine frame.
+ salvageDebugInfo();
+
// Eliminate coro.free from the clones, replacing it with 'null' in cleanup,
// to suppress deallocation code.
if (Shape.ABI == coro::ABI::Switch)
; CHECK: call void @llvm.dbg.declare(metadata i32* [[IGEP]], metadata ![[IVAR:[0-9]+]], metadata !DIExpression()), !dbg ![[IDBGLOC:[0-9]+]]
; CHECK: call void @llvm.dbg.declare(metadata [10 x i32]* [[XGEP]], metadata ![[XVAR:[0-9]+]], metadata !DIExpression()), !dbg ![[IDBGLOC]]
; CHECK: await.ready:
-; CHECK: call void @llvm.dbg.value(metadata [10 x i32]* [[XGEP]], metadata ![[XVAR]], metadata !DIExpression(DW_OP_deref)), !dbg ![[IDBGLOC]]
-; CHECK: call void @llvm.dbg.value(metadata i32* [[IGEP]], metadata ![[IVAR]], metadata !DIExpression(DW_OP_deref)), !dbg ![[IDBGLOC]]
; CHECK: call void @llvm.dbg.declare(metadata i32* %j, metadata ![[JVAR:[0-9]+]], metadata !DIExpression()), !dbg ![[JDBGLOC:[0-9]+]]
;
; CHECK-LABEL: define internal fastcc void @f.resume({{.*}}) {
; CHECK: entry.resume:
-; CHECK: %j = alloca i32, align 4
-; CHECK: [[IGEP_RESUME:%.+]] = getelementptr inbounds %f.Frame, %f.Frame* %FramePtr, i32 0, i32 4
-; CHECK: [[XGEP_RESUME:%.+]] = getelementptr inbounds %f.Frame, %f.Frame* %FramePtr, i32 0, i32 6
+; CHECK-NEXT: %[[DBG_PTR:.*]] = alloca %f.Frame*
+; CHECK-NEXT: call void @llvm.dbg.declare(metadata %f.Frame** %[[DBG_PTR]], metadata ![[XVAR_RESUME:[0-9]+]], metadata !DIExpression(DW_OP_deref, DW_OP_plus_uconst, 32)), !dbg
+; CHECK-NEXT: call void @llvm.dbg.declare(metadata %f.Frame** %[[DBG_PTR]], metadata ![[IVAR_RESUME:[0-9]+]], metadata !DIExpression(DW_OP_deref, DW_OP_plus_uconst, 20)), !dbg ![[IDBGLOC_RESUME:[0-9]+]]
+; CHECK-NEXT: store %f.Frame* {{.*}}, %f.Frame** %[[DBG_PTR]]
+; CHECK: %[[J:.*]] = alloca i32, align 4
+; CHECK-NEXT: call void @llvm.dbg.declare(metadata i32* %[[J]], metadata ![[JVAR_RESUME:[0-9]+]], metadata !DIExpression()), !dbg ![[JDBGLOC_RESUME:[0-9]+]]
; CHECK: init.ready:
-; CHECK: call void @llvm.dbg.declare(metadata i32* [[IGEP_RESUME]], metadata ![[IVAR_RESUME:[0-9]+]], metadata !DIExpression()), !dbg ![[IDBGLOC_RESUME:[0-9]+]]
-; CHECK: call void @llvm.dbg.declare(metadata [10 x i32]* [[XGEP_RESUME]], metadata ![[XVAR_RESUME:[0-9]+]], metadata !DIExpression()), !dbg ![[IDBGLOC_RESUME]]
; CHECK: await.ready:
-; CHECK: call void @llvm.dbg.value(metadata [10 x i32]* [[XGEP_RESUME]], metadata ![[XVAR_RESUME]], metadata !DIExpression(DW_OP_deref)), !dbg ![[IDBGLOC_RESUME]]
-; CHECK: call void @llvm.dbg.value(metadata i32* [[IGEP_RESUME]], metadata ![[IVAR_RESUME]], metadata !DIExpression(DW_OP_deref)), !dbg ![[IDBGLOC_RESUME]]
-; CHECK: call void @llvm.dbg.declare(metadata i32* %j, metadata ![[JVAR_RESUME:[0-9]+]], metadata !DIExpression()), !dbg ![[JDBGLOC_RESUME:[0-9]+]]
;
; CHECK: ![[IVAR]] = !DILocalVariable(name: "i"
; CHECK: ![[SCOPE:[0-9]+]] = distinct !DILexicalBlock(scope: !8, file: !1, line: 23, column: 12)
; CHECK: ![[JVAR]] = !DILocalVariable(name: "j"
; CHECK: ![[JDBGLOC]] = !DILocation(line: 32, column: 7, scope: ![[SCOPE]])
-; CHECK: ![[IVAR_RESUME]] = !DILocalVariable(name: "i"
-; CHECK: ![[RESUME_SCOPE:[0-9]+]] = distinct !DILexicalBlock(scope: !8, file: !1, line: 23, column: 12)
-; CHECK: ![[IDBGLOC_RESUME]] = !DILocation(line: 24, column: 7, scope: ![[RESUME_SCOPE]])
; CHECK: ![[XVAR_RESUME]] = !DILocalVariable(name: "x"
+; CHECK: ![[IDBGLOC_RESUME]] = !DILocation(line: 24, column: 7, scope: ![[RESUME_SCOPE:[0-9]+]])
+; CHECK: ![[RESUME_SCOPE]] = distinct !DILexicalBlock(scope: !8, file: !1, line: 23, column: 12)
+; CHECK: ![[IVAR_RESUME]] = !DILocalVariable(name: "i"
; CHECK: ![[JVAR_RESUME]] = !DILocalVariable(name: "j"
; CHECK: ![[JDBGLOC_RESUME]] = !DILocation(line: 32, column: 7, scope: ![[RESUME_SCOPE]])
define void @f() {
; CHECK: define i8* @f(i32 %x) #0 !dbg ![[ORIG:[0-9]+]]
; CHECK: define internal fastcc void @f.resume(%f.Frame* noalias nonnull align 8 dereferenceable(32) %FramePtr) #0 !dbg ![[RESUME:[0-9]+]]
; CHECK: entry.resume:
+; CHECK: %[[DBG_PTR:.*]] = alloca %f.Frame*
+; CHECK: call void @llvm.dbg.declare(metadata %f.Frame** %[[DBG_PTR]], metadata ![[RESUME_COROHDL:[0-9]+]], metadata !DIExpression(DW_OP_deref, DW_OP_plus_uconst,
+; CHECK: call void @llvm.dbg.declare(metadata %f.Frame** %[[DBG_PTR]], metadata ![[RESUME_X:[0-9]+]], metadata !DIExpression(DW_OP_deref, DW_OP_plus_uconst,
+; CHECK: store %f.Frame* {{.*}}, %f.Frame** %[[DBG_PTR]]
+; CHECK-NOT: alloca %struct.test*
; CHECK: call void @coro.devirt.trigger(i8* null)
-; CHECK: call void @llvm.dbg.declare(metadata i32* %x.addr.reload.addr, metadata ![[RESUME_VAR:[0-9]+]]
; CHECK: define internal fastcc void @f.destroy(%f.Frame* noalias nonnull align 8 dereferenceable(32) %FramePtr) #0 !dbg ![[DESTROY:[0-9]+]]
; CHECK: define internal fastcc void @f.cleanup(%f.Frame* noalias nonnull align 8 dereferenceable(32) %FramePtr) #0 !dbg ![[CLEANUP:[0-9]+]]
; CHECK: ![[ORIG]] = distinct !DISubprogram(name: "f", linkageName: "flink"
; CHECK: ![[RESUME]] = distinct !DISubprogram(name: "f", linkageName: "flink"
-; CHECK: ![[RESUME_VAR]] = !DILocalVariable(name: "x", arg: 1, scope: ![[RESUME]]
+; CHECK: ![[RESUME_COROHDL]] = !DILocalVariable(name: "coro_hdl", scope: ![[RESUME]]
+; CHECK: ![[RESUME_X]] = !DILocalVariable(name: "x", arg: 1, scope: ![[RESUME]]
; CHECK: ![[DESTROY]] = distinct !DISubprogram(name: "f", linkageName: "flink"