Skip to content

Commit 4db3531

Browse files
authored
Add support for dumping and using precise debug info (#61735)
* In the JIT, add support for dumping the precise debug info out through an environment variable `DOTNET_JitDumpPreciseDebugInfoFile` in a simple JSON format. This is a stopgap until we expose the extra information through ETW events. * In dotnet-pgo, add an argument --precise-debug-info-file which can point to the file produced by the JIT. When used, dotnet-pgo will get native<->IL mappings from this file instead of through ETW events. * In dotnet-pgo, add support for attributing samples to inlinees when that information is present. This changes the attribution process a bit: previously, we would group all LBR data/samples and then construct the profile from all the data. We now do it in a more streaming way where there is a SampleCorrelator that can handle individual LBR records and individual samples. * In dotnet-pgo, add an argument --dump-worst-overlap-graphs-to which can be used in the compare-mibc command to dump out a .dot file containing the flow graph of the methods with the worst overlap measures, and showing the relative weight count on each basic block and edge for the two profiles being compared. This is particular useful to find out where we are producing incorrect debug mappings, by comparing spgo.mibc and instrumented.mibc files.
1 parent 80ca504 commit 4db3531

17 files changed

+1147
-449
lines changed

src/coreclr/jit/codegen.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -556,11 +556,16 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
556556
void genIPmappingListDisp();
557557
#endif // DEBUG
558558

559-
IPmappingDsc* genCreateIPMapping(IPmappingDscKind kind, const DebugInfo& di, bool isLabel);
560559
void genIPmappingAdd(IPmappingDscKind kind, const DebugInfo& di, bool isLabel);
561560
void genIPmappingAddToFront(IPmappingDscKind kind, const DebugInfo& di, bool isLabel);
562561
void genIPmappingGen();
563562

563+
#ifdef DEBUG
564+
void genDumpPreciseDebugInfo();
565+
void genDumpPreciseDebugInfoInlineTree(FILE* file, InlineContext* context, bool* first);
566+
void genAddPreciseIPMappingHere(const DebugInfo& di);
567+
#endif
568+
564569
void genEnsureCodeEmitted(const DebugInfo& di);
565570

566571
//-------------------------------------------------------------------------

src/coreclr/jit/codegencommon.cpp

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ CodeGen::CodeGen(Compiler* theCompiler) : CodeGenInterface(theCompiler)
127127
compiler->genIPmappingLast = nullptr;
128128
compiler->genCallSite2DebugInfoMap = nullptr;
129129

130+
compiler->genPreciseIPMappingsHead = nullptr;
131+
compiler->genPreciseIPMappingsTail = nullptr;
132+
130133
/* Assume that we not fully interruptible */
131134

132135
SetInterruptible(false);
@@ -2446,6 +2449,8 @@ void CodeGen::genEmitUnwindDebugGCandEH()
24462449

24472450
genIPmappingGen();
24482451

2452+
INDEBUG(genDumpPreciseDebugInfo());
2453+
24492454
/* Finalize the Local Var info in terms of generated code */
24502455

24512456
genSetScopeInfo();
@@ -10801,6 +10806,98 @@ void CodeGen::genIPmappingGen()
1080110806
compiler->eeSetLIdone();
1080210807
}
1080310808

10809+
#ifdef DEBUG
10810+
void CodeGen::genDumpPreciseDebugInfoInlineTree(FILE* file, InlineContext* context, bool* first)
10811+
{
10812+
if (context->GetSibling() != nullptr)
10813+
{
10814+
genDumpPreciseDebugInfoInlineTree(file, context->GetSibling(), first);
10815+
}
10816+
10817+
if (context->IsSuccess())
10818+
{
10819+
if (!*first)
10820+
{
10821+
fprintf(file, ",");
10822+
}
10823+
10824+
*first = false;
10825+
10826+
fprintf(file, "{\"Ordinal\":%u,", context->GetOrdinal());
10827+
fprintf(file, "\"MethodID\":%lld,", (INT64)context->GetCallee());
10828+
const char* className;
10829+
const char* methodName = compiler->eeGetMethodName(context->GetCallee(), &className);
10830+
fprintf(file, "\"MethodName\":\"%s\",", methodName);
10831+
fprintf(file, "\"Inlinees\":[");
10832+
if (context->GetChild() != nullptr)
10833+
{
10834+
bool childFirst = true;
10835+
genDumpPreciseDebugInfoInlineTree(file, context->GetChild(), &childFirst);
10836+
}
10837+
fprintf(file, "]}");
10838+
}
10839+
}
10840+
10841+
void CodeGen::genDumpPreciseDebugInfo()
10842+
{
10843+
if (JitConfig.JitDumpPreciseDebugInfoFile() == nullptr)
10844+
return;
10845+
10846+
static CritSecObject s_critSect;
10847+
CritSecHolder holder(s_critSect);
10848+
10849+
FILE* file = _wfopen(JitConfig.JitDumpPreciseDebugInfoFile(), W("a"));
10850+
if (file == nullptr)
10851+
return;
10852+
10853+
// MethodID in ETW events are the method handles.
10854+
fprintf(file, "{\"MethodID\":%lld,", (INT64)compiler->info.compMethodHnd);
10855+
// Print inline tree.
10856+
fprintf(file, "\"InlineTree\":");
10857+
10858+
bool first = true;
10859+
genDumpPreciseDebugInfoInlineTree(file, compiler->compInlineContext, &first);
10860+
fprintf(file, ",\"Mappings\":[");
10861+
first = true;
10862+
for (PreciseIPMapping* mapping = compiler->genPreciseIPMappingsHead; mapping != nullptr; mapping = mapping->next)
10863+
{
10864+
if (!first)
10865+
{
10866+
fprintf(file, ",");
10867+
}
10868+
10869+
first = false;
10870+
10871+
fprintf(file, "{\"NativeOffset\":%u,\"InlineContext\":%u,\"ILOffset\":%u}",
10872+
mapping->nativeLoc.CodeOffset(GetEmitter()), mapping->debugInfo.GetInlineContext()->GetOrdinal(),
10873+
mapping->debugInfo.GetLocation().GetOffset());
10874+
}
10875+
10876+
fprintf(file, "]}\n");
10877+
10878+
fclose(file);
10879+
}
10880+
10881+
void CodeGen::genAddPreciseIPMappingHere(const DebugInfo& di)
10882+
{
10883+
PreciseIPMapping* mapping = new (compiler, CMK_DebugInfo) PreciseIPMapping;
10884+
mapping->next = nullptr;
10885+
mapping->nativeLoc.CaptureLocation(GetEmitter());
10886+
mapping->debugInfo = di;
10887+
10888+
if (compiler->genPreciseIPMappingsTail != nullptr)
10889+
{
10890+
compiler->genPreciseIPMappingsTail->next = mapping;
10891+
}
10892+
else
10893+
{
10894+
compiler->genPreciseIPMappingsHead = mapping;
10895+
}
10896+
10897+
compiler->genPreciseIPMappingsTail = mapping;
10898+
}
10899+
#endif
10900+
1080410901
/*============================================================================
1080510902
*
1080610903
* These are empty stubs to help the late dis-assembler to compile

src/coreclr/jit/codegenlinear.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,13 @@ void CodeGen::genCodeForBBlist()
441441
genIPmappingAdd(IPmappingDscKind::Normal, currentDI, firstMapping);
442442
firstMapping = false;
443443
}
444+
444445
#ifdef DEBUG
446+
if ((JitConfig.JitDumpPreciseDebugInfoFile() != nullptr) && ilOffset->gtStmtDI.IsValid())
447+
{
448+
genAddPreciseIPMappingHere(ilOffset->gtStmtDI);
449+
}
450+
445451
assert(ilOffset->gtStmtLastILoffs <= compiler->info.compILCodeSize ||
446452
ilOffset->gtStmtLastILoffs == BAD_IL_OFFSET);
447453

src/coreclr/jit/compiler.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2534,6 +2534,13 @@ struct IPmappingDsc
25342534
bool ipmdIsLabel; // Can this code be a branch label?
25352535
};
25362536

2537+
struct PreciseIPMapping
2538+
{
2539+
PreciseIPMapping* next;
2540+
emitLocation nativeLoc;
2541+
DebugInfo debugInfo;
2542+
};
2543+
25372544
/*
25382545
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
25392546
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
@@ -8269,6 +8276,9 @@ class Compiler
82698276
IPmappingDsc* genIPmappingList;
82708277
IPmappingDsc* genIPmappingLast;
82718278

8279+
PreciseIPMapping* genPreciseIPMappingsHead;
8280+
PreciseIPMapping* genPreciseIPMappingsTail;
8281+
82728282
// Managed RetVal - A side hash table meant to record the mapping from a
82738283
// GT_CALL node to its debug info. This info is used to emit sequence points
82748284
// that can be used by debugger to determine the native offset at which the

src/coreclr/jit/inline.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,18 @@ class InlineContext
726726
return m_Parent;
727727
}
728728

729+
// Get the sibling context.
730+
InlineContext* GetSibling() const
731+
{
732+
return m_Sibling;
733+
}
734+
735+
// Get the first child context.
736+
InlineContext* GetChild() const
737+
{
738+
return m_Child;
739+
}
740+
729741
// Get the code pointer for this context.
730742
const BYTE* GetCode() const
731743
{
@@ -806,7 +818,6 @@ class InlineContext
806818
private:
807819
InlineContext(InlineStrategy* strategy);
808820

809-
private:
810821
InlineStrategy* m_InlineStrategy; // overall strategy
811822
InlineContext* m_Parent; // logical caller (parent)
812823
InlineContext* m_Child; // first child

src/coreclr/jit/jitconfigvalues.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,8 @@ CONFIG_INTEGER(JitDumpFgConstrained, W("JitDumpFgConstrained"), 1) // 0 == don't
232232
CONFIG_INTEGER(JitDumpFgBlockID, W("JitDumpFgBlockID"), 0) // 0 == display block with bbNum; 1 == display with both
233233
// bbNum and bbID
234234

235+
CONFIG_STRING(JitDumpPreciseDebugInfoFile, W("JitDumpPreciseDebugInfoFile"))
236+
235237
CONFIG_STRING(JitLateDisasmTo, W("JITLateDisasmTo"))
236238
CONFIG_STRING(JitRange, W("JitRange"))
237239
CONFIG_STRING(JitStressModeNames, W("JitStressModeNames")) // Internal Jit stress mode: stress using the given set of

src/coreclr/tools/Common/TypeSystem/IL/FlowGraph.cs

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -88,47 +88,6 @@ public IEnumerable<BasicBlock> LookupRange(int ilOffsetStart, int ilOffsetEnd)
8888
yield return BasicBlocks[i];
8989
}
9090

91-
internal string Dump(Func<BasicBlock, string> getNodeAnnot, Func<(BasicBlock, BasicBlock), string> getEdgeAnnot)
92-
{
93-
var sb = new StringBuilder();
94-
sb.AppendLine("digraph G {");
95-
sb.AppendLine(" forcelabels=true;");
96-
sb.AppendLine();
97-
Dictionary<long, int> bbToIndex = new Dictionary<long, int>();
98-
for (int i = 0; i < BasicBlocks.Count; i++)
99-
bbToIndex.Add(BasicBlocks[i].Start, i);
100-
101-
foreach (BasicBlock bb in BasicBlocks)
102-
{
103-
string label = $"[{bb.Start:x}..{bb.Start + bb.Size:x})\\n{getNodeAnnot(bb)}";
104-
sb.AppendLine($" BB{bbToIndex[bb.Start]} [label=\"{label}\"];");
105-
}
106-
107-
sb.AppendLine();
108-
109-
foreach (BasicBlock bb in BasicBlocks)
110-
{
111-
foreach (BasicBlock tar in bb.Targets)
112-
{
113-
string label = getEdgeAnnot((bb, tar));
114-
string postfix = string.IsNullOrEmpty(label) ? "" : $" [label=\"{label}\"]";
115-
sb.AppendLine($" BB{bbToIndex[bb.Start]} -> BB{bbToIndex[tar.Start]}{postfix};");
116-
}
117-
}
118-
119-
// Write ranks with BFS.
120-
List<BasicBlock> curRank = new List<BasicBlock> { BasicBlocks.Single(bb => bb.Start == 0) };
121-
HashSet<BasicBlock> seen = new HashSet<BasicBlock>(curRank);
122-
while (curRank.Count > 0)
123-
{
124-
sb.AppendLine($" {{rank = same; {string.Concat(curRank.Select(bb => $"BB{bbToIndex[bb.Start]}; "))}}}");
125-
curRank = curRank.SelectMany(bb => bb.Targets).Where(seen.Add).ToList();
126-
}
127-
128-
sb.AppendLine("}");
129-
return sb.ToString();
130-
}
131-
13291
public static FlowGraph Create(MethodIL il)
13392
{
13493
HashSet<int> bbStarts = GetBasicBlockStarts(il);

src/coreclr/tools/dotnet-pgo/CommandLineOptions.cs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ internal class CommandLineOptions
1818

1919
public FileInfo TraceFile;
2020
public FileInfo OutputFileName;
21+
public FileInfo PreciseDebugInfoFile;
2122
public int? Pid;
2223
public string ProcessName;
2324
public PgoFileType? FileType;
@@ -29,8 +30,7 @@ internal class CommandLineOptions
2930
public bool ValidateOutputFile;
3031
public bool GenerateCallGraph;
3132
public bool Spgo;
32-
public bool SpgoIncludeBlockCounts;
33-
public bool SpgoIncludeEdgeCounts;
33+
public bool IncludeFullGraphs;
3434
public int SpgoMinSamples = 50;
3535
public bool VerboseWarnings;
3636
public jittraceoptions JitTraceOptions;
@@ -45,6 +45,8 @@ internal class CommandLineOptions
4545
public bool DumpMibc = false;
4646
public FileInfo InputFileToDump;
4747
public List<FileInfo> CompareMibc;
48+
public DirectoryInfo DumpWorstOverlapGraphsTo;
49+
public int DumpWorstOverlapGraphs = -1;
4850
public bool InheritTimestamp;
4951

5052
public string[] HelpArgs = Array.Empty<string>();
@@ -196,13 +198,15 @@ void HelpOption()
196198
CommonOptions();
197199
CompressedOption();
198200

201+
string preciseDebugInfoFile = null;
202+
syntax.DefineOption(name: "precise-debug-info-file", ref preciseDebugInfoFile, "Name of file of newline separated JSON objects containing precise debug info");
203+
if (preciseDebugInfoFile != null)
204+
PreciseDebugInfoFile = new FileInfo(preciseDebugInfoFile);
205+
199206
syntax.DefineOption(name: "spgo", value: ref Spgo, help: "Base profile on samples in the input. Uses last branch records if available and otherwise raw IP samples.", requireValue: false);
200-
syntax.DefineOption(name: "spgo-with-block-counts", value: ref SpgoIncludeBlockCounts, help: "Include block counts in the written .mibc file. If neither this nor spgo-with-edge-counts are specified, then defaults to true.", requireValue: false);
201-
syntax.DefineOption(name: "spgo-with-edge-counts", value: ref SpgoIncludeEdgeCounts, help: "Include edge counts in the written .mibc file.", requireValue: false);
202207
syntax.DefineOption(name: "spgo-min-samples", value: ref SpgoMinSamples, help: $"The minimum number of total samples a function must have before generating profile data for it with SPGO. Default: {SpgoMinSamples}", requireValue: false);
203208

204-
if (!SpgoIncludeBlockCounts && !SpgoIncludeEdgeCounts)
205-
SpgoIncludeBlockCounts = true;
209+
syntax.DefineOption(name: "include-full-graphs", value: ref IncludeFullGraphs, help: "Include all blocks and edges in the written .mibc file, regardless of profile counts", requireValue: false);
206210

207211
HelpOption();
208212
}
@@ -305,6 +309,12 @@ void HelpOption()
305309
CompareMibc = DefineFileOptionList(name: "i|input", help: "The input .mibc files to be compared. Specify as --input file1.mibc --input file2.mibc");
306310
if (CompareMibc.Count != 2)
307311
Help = true;
312+
313+
syntax.DefineOption(name: "dump-worst-overlap-graphs", value: ref DumpWorstOverlapGraphs, help: "Number of graphs to dump to .dot format in dump-worst-overlap-graphs-to directory");
314+
string dumpWorstOverlapGraphsTo = null;
315+
syntax.DefineOption(name: "dump-worst-overlap-graphs-to", value: ref dumpWorstOverlapGraphsTo, help: "Number of graphs to dump to .dot format in dump-worst-overlap-graphs-to directory");
316+
if (dumpWorstOverlapGraphsTo != null)
317+
DumpWorstOverlapGraphsTo = new DirectoryInfo(dumpWorstOverlapGraphsTo);
308318
}
309319

310320
if (syntax.ActiveCommand == null)

0 commit comments

Comments
 (0)