diff --git a/llvm/lib/Transforms/Coroutines/CoroFrame.cpp b/llvm/lib/Transforms/Coroutines/CoroFrame.cpp --- a/llvm/lib/Transforms/Coroutines/CoroFrame.cpp +++ b/llvm/lib/Transforms/Coroutines/CoroFrame.cpp @@ -1104,6 +1104,7 @@ auto *FramePtr = cast(Builder.CreateBitCast(CB, FramePtrTy, "FramePtr")); DominatorTree DT(*CB->getFunction()); + SmallDenseMap 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 @@ -1209,6 +1210,21 @@ CurrentReload = Builder.CreateLoad( FrameTy->getElementType(FrameData.getFieldIndex(E.first)), GEP, E.first->getName() + Twine(".reload")); + + TinyPtrVector 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 @@ -1276,50 +1292,18 @@ SmallPtrSet SeenDbgBBs; TinyPtrVector 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(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) { @@ -2172,6 +2156,58 @@ } } +void coro::salvageDebugInfo( + SmallDenseMap &DbgPtrAllocaCache, + DbgDeclareInst *DDI, bool LoadFromFramePtr) { + Function *F = DDI->getFunction(); + IRBuilder<> Builder(F->getContext()); + auto InsertPt = F->getEntryBlock().getFirstInsertionPt(); + while (isa(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(Storage)) { + Storage = LdInst->getOperand(0); + } else if (auto *StInst = dyn_cast(Storage)) { + Storage = StInst->getOperand(0); + } else if (auto *GEPInst = dyn_cast(Storage)) { + Expr = llvm::salvageDebugInfoImpl(*GEPInst, Expr, + /*WithStackValue=*/false); + Storage = GEPInst->getOperand(0); + } else if (auto *BCInst = dyn_cast(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(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(Storage)) + DDI->moveAfter(InsertPt); +} + void coro::buildCoroutineFrame(Function &F, Shape &Shape) { eliminateSwiftError(F, Shape); diff --git a/llvm/lib/Transforms/Coroutines/CoroInternal.h b/llvm/lib/Transforms/Coroutines/CoroInternal.h --- a/llvm/lib/Transforms/Coroutines/CoroInternal.h +++ b/llvm/lib/Transforms/Coroutines/CoroInternal.h @@ -50,6 +50,11 @@ void replaceCoroFree(CoroIdInst *CoroId, bool Elide); void updateCallGraph(Function &Caller, ArrayRef 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 &DbgPtrAllocaCache, + DbgDeclareInst *DDI, bool LoadFromCoroFrame = false); // Keeps data and helper functions for lowering coroutine intrinsics. struct LowererBase { diff --git a/llvm/lib/Transforms/Coroutines/CoroSplit.cpp b/llvm/lib/Transforms/Coroutines/CoroSplit.cpp --- a/llvm/lib/Transforms/Coroutines/CoroSplit.cpp +++ b/llvm/lib/Transforms/Coroutines/CoroSplit.cpp @@ -158,6 +158,7 @@ void replaceCoroSuspends(); void replaceCoroEnds(); void replaceSwiftErrorOps(); + void salvageDebugInfo(); void handleFinalSuspend(); }; @@ -631,6 +632,39 @@ ::replaceSwiftErrorOps(*NewF, Shape, &VMap); } +void CoroCloner::salvageDebugInfo() { + SmallVector Worklist; + SmallDenseMap DbgPtrAllocaCache; + for (auto &BB : *NewF) + for (auto &I : BB) + if (auto *DDI = dyn_cast(&I)) + Worklist.push_back(DDI); + for (DbgDeclareInst *DDI : Worklist) { + // This is a heuristic that detects declares left by CoroFrame. + bool LoadFromFramePtr = !isa(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(DDI->getAddress())) { + // Count all non-debuginfo uses in reachable blocks. + unsigned Uses = 0; + for (auto *User : DDI->getAddress()->users()) + if (auto *I = dyn_cast(User)) + if (!isa(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 @@ -904,6 +938,9 @@ // 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) diff --git a/llvm/test/Transforms/Coroutines/coro-debug-frame-variable.ll b/llvm/test/Transforms/Coroutines/coro-debug-frame-variable.ll --- a/llvm/test/Transforms/Coroutines/coro-debug-frame-variable.ll +++ b/llvm/test/Transforms/Coroutines/coro-debug-frame-variable.ll @@ -37,22 +37,18 @@ ; 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) @@ -61,10 +57,10 @@ ; 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() { diff --git a/llvm/test/Transforms/Coroutines/coro-debug.ll b/llvm/test/Transforms/Coroutines/coro-debug.ll --- a/llvm/test/Transforms/Coroutines/coro-debug.ll +++ b/llvm/test/Transforms/Coroutines/coro-debug.ll @@ -130,15 +130,20 @@ ; 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"