Skip to content

Commit 0554541

Browse files
committed
Salvage debug info for function arguments in coro-split funclets.
This patch improves the availability for variables stored in the coroutine frame by emitting an alloca to hold the pointer to the frame object and rewriting dbg.declare intrinsics to point inside the frame object using salvaged DIExpressions. Finally, a new alloca is created in the funclet to hold the FramePtr pointer to ensure that it is available throughout the entire function at -O0. This path also effectively reverts D90772. The testcase updates highlight nicely how every removed CHECK for a dbg.value is preceded by a new CHECK for a dbg.declare. Thanks to JunMa, Yifeng, and Bruno for their thoughtful reviews! Differential Revision: https://reviews.llvm.org/D93497 rdar://71866936
1 parent 8afabff commit 0554541

File tree

5 files changed

+133
-54
lines changed

5 files changed

+133
-54
lines changed

llvm/lib/Transforms/Coroutines/CoroFrame.cpp

Lines changed: 75 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,6 +1104,7 @@ static Instruction *insertSpills(const FrameDataInfo &FrameData,
11041104
auto *FramePtr =
11051105
cast<Instruction>(Builder.CreateBitCast(CB, FramePtrTy, "FramePtr"));
11061106
DominatorTree DT(*CB->getFunction());
1107+
SmallDenseMap<llvm::Value *, llvm::AllocaInst *, 4> DbgPtrAllocaCache;
11071108

11081109
// Create a GEP with the given index into the coroutine frame for the original
11091110
// value Orig. Appends an extra 0 index for array-allocas, preserving the
@@ -1209,6 +1210,21 @@ static Instruction *insertSpills(const FrameDataInfo &FrameData,
12091210
CurrentReload = Builder.CreateLoad(
12101211
FrameTy->getElementType(FrameData.getFieldIndex(E.first)), GEP,
12111212
E.first->getName() + Twine(".reload"));
1213+
1214+
TinyPtrVector<DbgDeclareInst *> DIs = FindDbgDeclareUses(Def);
1215+
for (DbgDeclareInst *DDI : DIs) {
1216+
bool AllowUnresolved = false;
1217+
// This dbg.declare is preserved for all coro-split function
1218+
// fragments. It will be unreachable in the main function, and
1219+
// processed by coro::salvageDebugInfo() by CoroCloner.
1220+
DIBuilder(*CurrentBlock->getParent()->getParent(), AllowUnresolved)
1221+
.insertDeclare(CurrentReload, DDI->getVariable(),
1222+
DDI->getExpression(), DDI->getDebugLoc(),
1223+
&*Builder.GetInsertPoint());
1224+
// This dbg.declare is for the main function entry point. It
1225+
// will be deleted in all coro-split functions.
1226+
coro::salvageDebugInfo(DbgPtrAllocaCache, DDI);
1227+
}
12121228
}
12131229

12141230
// If we have a single edge PHINode, remove it and replace it with a
@@ -1276,50 +1292,18 @@ static Instruction *insertSpills(const FrameDataInfo &FrameData,
12761292

12771293
SmallPtrSet<BasicBlock *, 4> SeenDbgBBs;
12781294
TinyPtrVector<DbgDeclareInst *> DIs = FindDbgDeclareUses(Alloca);
1279-
DIBuilder DIB(*Alloca->getModule(), /*AllowUnresolved*/ false);
1280-
Instruction *FirstDbgDecl = nullptr;
1281-
1282-
if (!DIs.empty()) {
1283-
FirstDbgDecl = DIB.insertDeclare(G, DIs.front()->getVariable(),
1284-
DIs.front()->getExpression(),
1285-
DIs.front()->getDebugLoc(), DIs.front());
1286-
SeenDbgBBs.insert(DIs.front()->getParent());
1287-
}
1295+
if (!DIs.empty())
1296+
DIBuilder(*Alloca->getModule(),
1297+
/*AllowUnresolved*/ false)
1298+
.insertDeclare(G, DIs.front()->getVariable(),
1299+
DIs.front()->getExpression(),
1300+
DIs.front()->getDebugLoc(), DIs.front());
12881301
for (auto *DI : FindDbgDeclareUses(Alloca))
12891302
DI->eraseFromParent();
12901303
replaceDbgUsesWithUndef(Alloca);
12911304

1292-
for (Instruction *I : UsersToUpdate) {
1305+
for (Instruction *I : UsersToUpdate)
12931306
I->replaceUsesOfWith(Alloca, G);
1294-
1295-
// After cloning, transformations might not guarantee that all uses
1296-
// of this alloca are dominated by the already existing dbg.declare's,
1297-
// compromising the debug quality. Instead of writing another
1298-
// transformation to patch each clone, go ahead and early populate
1299-
// basic blocks that use such allocas with more debug info.
1300-
if (SeenDbgBBs.count(I->getParent()))
1301-
continue;
1302-
1303-
// If there isn't a prior dbg.declare for this alloca, it probably
1304-
// means the state hasn't changed prior to one of the relevant suspend
1305-
// point for this frame access.
1306-
if (!FirstDbgDecl)
1307-
continue;
1308-
1309-
// These instructions are all dominated by the alloca, insert the
1310-
// dbg.value in the beginning of the BB to enhance debugging
1311-
// experience and allow values to be inspected as early as possible.
1312-
// Prefer dbg.value over dbg.declare since it better sets expectations
1313-
// that control flow can be later changed by other passes.
1314-
auto *DI = cast<DbgDeclareInst>(FirstDbgDecl);
1315-
BasicBlock *CurrentBlock = I->getParent();
1316-
auto *DerefExpr =
1317-
DIExpression::append(DI->getExpression(), dwarf::DW_OP_deref);
1318-
DIB.insertDbgValueIntrinsic(G, DI->getVariable(), DerefExpr,
1319-
DI->getDebugLoc(),
1320-
&*CurrentBlock->getFirstInsertionPt());
1321-
SeenDbgBBs.insert(CurrentBlock);
1322-
}
13231307
}
13241308
Builder.SetInsertPoint(FramePtr->getNextNode());
13251309
for (const auto &A : FrameData.Allocas) {
@@ -2172,6 +2156,58 @@ static void collectFrameAllocas(Function &F, coro::Shape &Shape,
21722156
}
21732157
}
21742158

2159+
void coro::salvageDebugInfo(
2160+
SmallDenseMap<llvm::Value *, llvm::AllocaInst *, 4> &DbgPtrAllocaCache,
2161+
DbgDeclareInst *DDI, bool LoadFromFramePtr) {
2162+
Function *F = DDI->getFunction();
2163+
IRBuilder<> Builder(F->getContext());
2164+
auto InsertPt = F->getEntryBlock().getFirstInsertionPt();
2165+
while (isa<IntrinsicInst>(InsertPt))
2166+
++InsertPt;
2167+
Builder.SetInsertPoint(&F->getEntryBlock(), InsertPt);
2168+
DIExpression *Expr = DDI->getExpression();
2169+
// Follow the pointer arithmetic all the way to the incoming
2170+
// function argument and convert into a DIExpression.
2171+
Value *Storage = DDI->getAddress();
2172+
while (Storage) {
2173+
if (auto *LdInst = dyn_cast<LoadInst>(Storage)) {
2174+
Storage = LdInst->getOperand(0);
2175+
} else if (auto *StInst = dyn_cast<StoreInst>(Storage)) {
2176+
Storage = StInst->getOperand(0);
2177+
} else if (auto *GEPInst = dyn_cast<GetElementPtrInst>(Storage)) {
2178+
Expr = llvm::salvageDebugInfoImpl(*GEPInst, Expr,
2179+
/*WithStackValue=*/false);
2180+
Storage = GEPInst->getOperand(0);
2181+
} else if (auto *BCInst = dyn_cast<llvm::BitCastInst>(Storage))
2182+
Storage = BCInst->getOperand(0);
2183+
else
2184+
break;
2185+
}
2186+
// Store a pointer to the coroutine frame object in an alloca so it
2187+
// is available throughout the function when producing unoptimized
2188+
// code. Extending the lifetime this way is correct because the
2189+
// variable has been declared by a dbg.declare intrinsic.
2190+
if (auto Arg = dyn_cast_or_null<llvm::Argument>(Storage)) {
2191+
auto &Cached = DbgPtrAllocaCache[Storage];
2192+
if (!Cached) {
2193+
Cached = Builder.CreateAlloca(Storage->getType(), 0, nullptr,
2194+
Arg->getName() + ".debug");
2195+
Builder.CreateStore(Storage, Cached);
2196+
}
2197+
Storage = Cached;
2198+
}
2199+
// The FramePtr object adds one extra layer of indirection that
2200+
// needs to be unwrapped.
2201+
if (LoadFromFramePtr)
2202+
Expr = DIExpression::prepend(Expr, DIExpression::DerefBefore);
2203+
auto &VMContext = DDI->getFunction()->getContext();
2204+
DDI->setOperand(
2205+
0, MetadataAsValue::get(VMContext, ValueAsMetadata::get(Storage)));
2206+
DDI->setOperand(2, MetadataAsValue::get(VMContext, Expr));
2207+
if (auto *InsertPt = dyn_cast_or_null<Instruction>(Storage))
2208+
DDI->moveAfter(InsertPt);
2209+
}
2210+
21752211
void coro::buildCoroutineFrame(Function &F, Shape &Shape) {
21762212
eliminateSwiftError(F, Shape);
21772213

llvm/lib/Transforms/Coroutines/CoroInternal.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ bool declaresIntrinsics(const Module &M,
5050
void replaceCoroFree(CoroIdInst *CoroId, bool Elide);
5151
void updateCallGraph(Function &Caller, ArrayRef<Function *> Funcs,
5252
CallGraph &CG, CallGraphSCC &SCC);
53+
/// Recover a dbg.declare prepared by the frontend and emit an alloca
54+
/// holding a pointer to the coroutine frame.
55+
void salvageDebugInfo(
56+
SmallDenseMap<llvm::Value *, llvm::AllocaInst *, 4> &DbgPtrAllocaCache,
57+
DbgDeclareInst *DDI, bool LoadFromCoroFrame = false);
5358

5459
// Keeps data and helper functions for lowering coroutine intrinsics.
5560
struct LowererBase {

llvm/lib/Transforms/Coroutines/CoroSplit.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ class CoroCloner {
158158
void replaceCoroSuspends();
159159
void replaceCoroEnds();
160160
void replaceSwiftErrorOps();
161+
void salvageDebugInfo();
161162
void handleFinalSuspend();
162163
};
163164

@@ -631,6 +632,39 @@ void CoroCloner::replaceSwiftErrorOps() {
631632
::replaceSwiftErrorOps(*NewF, Shape, &VMap);
632633
}
633634

635+
void CoroCloner::salvageDebugInfo() {
636+
SmallVector<DbgDeclareInst *, 8> Worklist;
637+
SmallDenseMap<llvm::Value *, llvm::AllocaInst *, 4> DbgPtrAllocaCache;
638+
for (auto &BB : *NewF)
639+
for (auto &I : BB)
640+
if (auto *DDI = dyn_cast<DbgDeclareInst>(&I))
641+
Worklist.push_back(DDI);
642+
for (DbgDeclareInst *DDI : Worklist) {
643+
// This is a heuristic that detects declares left by CoroFrame.
644+
bool LoadFromFramePtr = !isa<AllocaInst>(DDI->getAddress());
645+
coro::salvageDebugInfo(DbgPtrAllocaCache, DDI, LoadFromFramePtr);
646+
}
647+
// Remove all salvaged dbg.declare intrinsics that became
648+
// either unreachable or stale due to the CoroSplit transformation.
649+
auto IsUnreachableBlock = [&](BasicBlock *BB) {
650+
return BB->hasNPredecessors(0) && BB != &NewF->getEntryBlock();
651+
};
652+
for (DbgDeclareInst *DDI : Worklist) {
653+
if (IsUnreachableBlock(DDI->getParent()))
654+
DDI->eraseFromParent();
655+
else if (auto *Alloca = dyn_cast_or_null<AllocaInst>(DDI->getAddress())) {
656+
// Count all non-debuginfo uses in reachable blocks.
657+
unsigned Uses = 0;
658+
for (auto *User : DDI->getAddress()->users())
659+
if (auto *I = dyn_cast<Instruction>(User))
660+
if (!isa<AllocaInst>(I) && !IsUnreachableBlock(I->getParent()))
661+
++Uses;
662+
if (!Uses)
663+
DDI->eraseFromParent();
664+
}
665+
}
666+
}
667+
634668
void CoroCloner::replaceEntryBlock() {
635669
// In the original function, the AllocaSpillBlock is a block immediately
636670
// following the allocation of the frame object which defines GEPs for
@@ -904,6 +938,9 @@ void CoroCloner::create() {
904938
// Remove coro.end intrinsics.
905939
replaceCoroEnds();
906940

941+
// Salvage debug info that points into the coroutine frame.
942+
salvageDebugInfo();
943+
907944
// Eliminate coro.free from the clones, replacing it with 'null' in cleanup,
908945
// to suppress deallocation code.
909946
if (Shape.ABI == coro::ABI::Switch)

llvm/test/Transforms/Coroutines/coro-debug-frame-variable.ll

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -37,22 +37,18 @@
3737
; CHECK: call void @llvm.dbg.declare(metadata i32* [[IGEP]], metadata ![[IVAR:[0-9]+]], metadata !DIExpression()), !dbg ![[IDBGLOC:[0-9]+]]
3838
; CHECK: call void @llvm.dbg.declare(metadata [10 x i32]* [[XGEP]], metadata ![[XVAR:[0-9]+]], metadata !DIExpression()), !dbg ![[IDBGLOC]]
3939
; CHECK: await.ready:
40-
; CHECK: call void @llvm.dbg.value(metadata [10 x i32]* [[XGEP]], metadata ![[XVAR]], metadata !DIExpression(DW_OP_deref)), !dbg ![[IDBGLOC]]
41-
; CHECK: call void @llvm.dbg.value(metadata i32* [[IGEP]], metadata ![[IVAR]], metadata !DIExpression(DW_OP_deref)), !dbg ![[IDBGLOC]]
4240
; CHECK: call void @llvm.dbg.declare(metadata i32* %j, metadata ![[JVAR:[0-9]+]], metadata !DIExpression()), !dbg ![[JDBGLOC:[0-9]+]]
4341
;
4442
; CHECK-LABEL: define internal fastcc void @f.resume({{.*}}) {
4543
; CHECK: entry.resume:
46-
; CHECK: %j = alloca i32, align 4
47-
; CHECK: [[IGEP_RESUME:%.+]] = getelementptr inbounds %f.Frame, %f.Frame* %FramePtr, i32 0, i32 4
48-
; CHECK: [[XGEP_RESUME:%.+]] = getelementptr inbounds %f.Frame, %f.Frame* %FramePtr, i32 0, i32 6
44+
; CHECK-NEXT: %[[DBG_PTR:.*]] = alloca %f.Frame*
45+
; 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
46+
; 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]+]]
47+
; CHECK-NEXT: store %f.Frame* {{.*}}, %f.Frame** %[[DBG_PTR]]
48+
; CHECK: %[[J:.*]] = alloca i32, align 4
49+
; CHECK-NEXT: call void @llvm.dbg.declare(metadata i32* %[[J]], metadata ![[JVAR_RESUME:[0-9]+]], metadata !DIExpression()), !dbg ![[JDBGLOC_RESUME:[0-9]+]]
4950
; CHECK: init.ready:
50-
; CHECK: call void @llvm.dbg.declare(metadata i32* [[IGEP_RESUME]], metadata ![[IVAR_RESUME:[0-9]+]], metadata !DIExpression()), !dbg ![[IDBGLOC_RESUME:[0-9]+]]
51-
; CHECK: call void @llvm.dbg.declare(metadata [10 x i32]* [[XGEP_RESUME]], metadata ![[XVAR_RESUME:[0-9]+]], metadata !DIExpression()), !dbg ![[IDBGLOC_RESUME]]
5251
; CHECK: await.ready:
53-
; CHECK: call void @llvm.dbg.value(metadata [10 x i32]* [[XGEP_RESUME]], metadata ![[XVAR_RESUME]], metadata !DIExpression(DW_OP_deref)), !dbg ![[IDBGLOC_RESUME]]
54-
; CHECK: call void @llvm.dbg.value(metadata i32* [[IGEP_RESUME]], metadata ![[IVAR_RESUME]], metadata !DIExpression(DW_OP_deref)), !dbg ![[IDBGLOC_RESUME]]
55-
; CHECK: call void @llvm.dbg.declare(metadata i32* %j, metadata ![[JVAR_RESUME:[0-9]+]], metadata !DIExpression()), !dbg ![[JDBGLOC_RESUME:[0-9]+]]
5652
;
5753
; CHECK: ![[IVAR]] = !DILocalVariable(name: "i"
5854
; CHECK: ![[SCOPE:[0-9]+]] = distinct !DILexicalBlock(scope: !8, file: !1, line: 23, column: 12)
@@ -61,10 +57,10 @@
6157
; CHECK: ![[JVAR]] = !DILocalVariable(name: "j"
6258
; CHECK: ![[JDBGLOC]] = !DILocation(line: 32, column: 7, scope: ![[SCOPE]])
6359

64-
; CHECK: ![[IVAR_RESUME]] = !DILocalVariable(name: "i"
65-
; CHECK: ![[RESUME_SCOPE:[0-9]+]] = distinct !DILexicalBlock(scope: !8, file: !1, line: 23, column: 12)
66-
; CHECK: ![[IDBGLOC_RESUME]] = !DILocation(line: 24, column: 7, scope: ![[RESUME_SCOPE]])
6760
; CHECK: ![[XVAR_RESUME]] = !DILocalVariable(name: "x"
61+
; CHECK: ![[IDBGLOC_RESUME]] = !DILocation(line: 24, column: 7, scope: ![[RESUME_SCOPE:[0-9]+]])
62+
; CHECK: ![[RESUME_SCOPE]] = distinct !DILexicalBlock(scope: !8, file: !1, line: 23, column: 12)
63+
; CHECK: ![[IVAR_RESUME]] = !DILocalVariable(name: "i"
6864
; CHECK: ![[JVAR_RESUME]] = !DILocalVariable(name: "j"
6965
; CHECK: ![[JDBGLOC_RESUME]] = !DILocation(line: 32, column: 7, scope: ![[RESUME_SCOPE]])
7066
define void @f() {

llvm/test/Transforms/Coroutines/coro-debug.ll

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,15 +130,20 @@ attributes #7 = { noduplicate }
130130
; CHECK: define i8* @f(i32 %x) #0 !dbg ![[ORIG:[0-9]+]]
131131
; CHECK: define internal fastcc void @f.resume(%f.Frame* noalias nonnull align 8 dereferenceable(32) %FramePtr) #0 !dbg ![[RESUME:[0-9]+]]
132132
; CHECK: entry.resume:
133+
; CHECK: %[[DBG_PTR:.*]] = alloca %f.Frame*
134+
; 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,
135+
; 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,
136+
; CHECK: store %f.Frame* {{.*}}, %f.Frame** %[[DBG_PTR]]
137+
; CHECK-NOT: alloca %struct.test*
133138
; CHECK: call void @coro.devirt.trigger(i8* null)
134-
; CHECK: call void @llvm.dbg.declare(metadata i32* %x.addr.reload.addr, metadata ![[RESUME_VAR:[0-9]+]]
135139
; CHECK: define internal fastcc void @f.destroy(%f.Frame* noalias nonnull align 8 dereferenceable(32) %FramePtr) #0 !dbg ![[DESTROY:[0-9]+]]
136140
; CHECK: define internal fastcc void @f.cleanup(%f.Frame* noalias nonnull align 8 dereferenceable(32) %FramePtr) #0 !dbg ![[CLEANUP:[0-9]+]]
137141

138142
; CHECK: ![[ORIG]] = distinct !DISubprogram(name: "f", linkageName: "flink"
139143

140144
; CHECK: ![[RESUME]] = distinct !DISubprogram(name: "f", linkageName: "flink"
141-
; CHECK: ![[RESUME_VAR]] = !DILocalVariable(name: "x", arg: 1, scope: ![[RESUME]]
145+
; CHECK: ![[RESUME_COROHDL]] = !DILocalVariable(name: "coro_hdl", scope: ![[RESUME]]
146+
; CHECK: ![[RESUME_X]] = !DILocalVariable(name: "x", arg: 1, scope: ![[RESUME]]
142147

143148
; CHECK: ![[DESTROY]] = distinct !DISubprogram(name: "f", linkageName: "flink"
144149

0 commit comments

Comments
 (0)