Skip to content

Commit e23bd5e

Browse files
Improve DOT flow graph dumping (#52329)
1. Do not include EH and Loop regions in the graph for inlinees. The data required for them is not valid in the inlinee compiler. 2. Do not include Loop regions in phases starting with the rationalizer. The loop table is not maintained, and decays, but we don't ever mark it as invalid. This is an arbitrary point after which it seems to be unmaintained (and can lead to asserts when using it). 3. Add the text "(inlinee)" to the function name in inlinee graph output, to distinguish it. 4. Fix a bug where the block map was using incorrect block number count for inlinees. 5. Fix a region insert bug when inserting a parent region after a child region where they share the end block (but the parent start block is earlier than the child). This happens in some EH region tables. Added some comments about all the different forms of region that need to be handled. 6. Add a `Verify` function to validate the constructed region tree. 7. Stop adding removed loops to the output.
1 parent be93385 commit e23bd5e

File tree

1 file changed

+123
-20
lines changed

1 file changed

+123
-20
lines changed

src/coreclr/jit/fgdiagnostic.cpp

Lines changed: 123 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -617,9 +617,12 @@ bool Compiler::fgDumpFlowGraph(Phases phase)
617617

618618
#ifdef DEBUG
619619
const bool createDotFile = JitConfig.JitDumpFgDot() != 0;
620-
const bool includeEH = JitConfig.JitDumpFgEH() != 0;
621-
const bool includeLoops = JitConfig.JitDumpFgLoops() != 0;
622-
const bool constrained = JitConfig.JitDumpFgConstrained() != 0;
620+
const bool includeEH = (JitConfig.JitDumpFgEH() != 0) && !compIsForInlining();
621+
// The loop table is not well maintained after the optimization phases, but there is no single point at which
622+
// it is declared invalid. For now, refuse to add loop information starting at the rationalize phase, to
623+
// avoid asserts.
624+
const bool includeLoops = (JitConfig.JitDumpFgLoops() != 0) && !compIsForInlining() && (phase < PHASE_RATIONALIZE);
625+
const bool constrained = JitConfig.JitDumpFgConstrained() != 0;
623626
#else // !DEBUG
624627
const bool createDotFile = true;
625628
const bool includeEH = false;
@@ -633,6 +636,8 @@ bool Compiler::fgDumpFlowGraph(Phases phase)
633636
return false;
634637
}
635638

639+
JITDUMP("Dumping flow graph after phase %s\n", PhaseNames[phase]);
640+
636641
bool validWeights = fgHaveValidEdgeWeights;
637642
double weightDivisor = (double)BasicBlock::getCalledCount(this);
638643
const char* escapedString;
@@ -654,7 +659,8 @@ bool Compiler::fgDumpFlowGraph(Phases phase)
654659
if (createDotFile)
655660
{
656661
fprintf(fgxFile, "digraph FlowGraph {\n");
657-
fprintf(fgxFile, " graph [label = \"%s\\nafter\\n%s\"];\n", info.compMethodName, PhaseNames[phase]);
662+
fprintf(fgxFile, " graph [label = \"%s%s\\nafter\\n%s\"];\n", info.compMethodName,
663+
compIsForInlining() ? "\\n(inlinee)" : "", PhaseNames[phase]);
658664
fprintf(fgxFile, " node [shape = \"Box\"];\n");
659665
}
660666
else
@@ -712,12 +718,18 @@ bool Compiler::fgDumpFlowGraph(Phases phase)
712718
// to insert in the tree, to determine nesting. We'd like to use the bbNum to do this. However, we don't
713719
// want to renumber the blocks. So, create a mapping of bbNum to ordinal, and compare block order by
714720
// comparing the mapped ordinals instead.
715-
716-
unsigned blockOrdinal = 0;
717-
unsigned* blkMap = new (this, CMK_DebugOnly) unsigned[fgBBNumMax + 1];
718-
memset(blkMap, 0, sizeof(unsigned) * (fgBBNumMax + 1));
721+
//
722+
// For inlinees, the max block number of the inliner is used, so we need to allocate the block map based on
723+
// that size, even though it means allocating a block map possibly much bigger than what's required for just
724+
// the inlinee blocks.
725+
726+
unsigned blkMapSize = 1 + (compIsForInlining() ? impInlineInfo->InlinerCompiler->fgBBNumMax : fgBBNumMax);
727+
unsigned blockOrdinal = 1;
728+
unsigned* blkMap = new (this, CMK_DebugOnly) unsigned[blkMapSize];
729+
memset(blkMap, 0, sizeof(unsigned) * blkMapSize);
719730
for (BasicBlock* block = fgFirstBB; block != nullptr; block = block->bbNext)
720731
{
732+
assert(block->bbNum < blkMapSize);
721733
blkMap[block->bbNum] = blockOrdinal++;
722734
}
723735

@@ -1040,7 +1052,8 @@ bool Compiler::fgDumpFlowGraph(Phases phase)
10401052
};
10411053

10421054
public:
1043-
RegionGraph(Compiler* comp, unsigned* blkMap) : m_comp(comp), m_rgnRoot(nullptr), m_blkMap(blkMap)
1055+
RegionGraph(Compiler* comp, unsigned* blkMap, unsigned blkMapSize)
1056+
: m_comp(comp), m_rgnRoot(nullptr), m_blkMap(blkMap), m_blkMapSize(blkMapSize)
10441057
{
10451058
// Create a root region that encompasses the whole function.
10461059
m_rgnRoot =
@@ -1087,6 +1100,24 @@ bool Compiler::fgDumpFlowGraph(Phases phase)
10871100
unsigned childStartOrdinal = m_blkMap[child->m_bbStart->bbNum];
10881101
unsigned childEndOrdinal = m_blkMap[child->m_bbEnd->bbNum];
10891102

1103+
// Consider the following cases, where each "x" is a block in the range:
1104+
// xxxxxxx // current 'child' range; we're comparing against this
1105+
// xxxxxxx // (1) same range; could be considered child or parent
1106+
// xxxxxxxxx // (2) parent range, shares last block
1107+
// xxxxxxxxx // (3) parent range, shares first block
1108+
// xxxxxxxxxxx // (4) fully overlapping parent range
1109+
// xx // (5) non-overlapping preceding sibling range
1110+
// xx // (6) non-overlapping following sibling range
1111+
// xxx // (7) child range
1112+
// xxx // (8) child range, shares same start block
1113+
// x // (9) single-block child range, shares same start block
1114+
// xxx // (10) child range, shares same end block
1115+
// x // (11) single-block child range, shares same end block
1116+
// xxxxxxx // illegal: overlapping ranges
1117+
// xxx // illegal: overlapping ranges (shared child start block and new end block)
1118+
// xxxxxxx // illegal: overlapping ranges
1119+
// xxx // illegal: overlapping ranges (shared child end block and new start block)
1120+
10901121
// Assert the child is properly nested within the parent.
10911122
// Note that if regions have the same start and end, you can't tell which is nested within the
10921123
// other, though it shouldn't matter.
@@ -1095,21 +1126,19 @@ bool Compiler::fgDumpFlowGraph(Phases phase)
10951126
assert(childEndOrdinal <= curEndOrdinal);
10961127

10971128
// Should the new region be before this child?
1129+
// Case (5).
10981130
if (newEndOrdinal < childStartOrdinal)
10991131
{
11001132
// Insert before this child.
11011133
newRgn->m_rgnNext = child;
11021134
*lastChildPtr = newRgn;
11031135
break;
11041136
}
1105-
else if (newEndOrdinal <= childEndOrdinal)
1137+
else if ((newStartOrdinal >= childStartOrdinal) && (newEndOrdinal <= childEndOrdinal))
11061138
{
11071139
// Insert as a child of this child.
1108-
// Need to recurse to walk the child's children list to see where
1109-
// it belongs.
1110-
1111-
// It better be properly nested.
1112-
assert(newStartOrdinal >= childStartOrdinal);
1140+
// Need to recurse to walk the child's children list to see where it belongs.
1141+
// Case (1), (7), (8), (9), (10), (11).
11131142

11141143
curStartOrdinal = m_blkMap[child->m_bbStart->bbNum];
11151144
curEndOrdinal = m_blkMap[child->m_bbEnd->bbNum];
@@ -1122,6 +1151,8 @@ bool Compiler::fgDumpFlowGraph(Phases phase)
11221151
else if (newStartOrdinal <= childStartOrdinal)
11231152
{
11241153
// The new region is a parent of one or more of the existing children.
1154+
// Case (2), (3), (4).
1155+
11251156
// Find all the children it encompasses.
11261157
Region** lastEndChildPtr = &child->m_rgnNext;
11271158
Region* endChild = child->m_rgnNext;
@@ -1154,6 +1185,7 @@ bool Compiler::fgDumpFlowGraph(Phases phase)
11541185
}
11551186

11561187
// Else, look for next child.
1188+
// Case (6).
11571189

11581190
lastChildPtr = &child->m_rgnNext;
11591191
child = child->m_rgnNext;
@@ -1216,17 +1248,82 @@ bool Compiler::fgDumpFlowGraph(Phases phase)
12161248
//------------------------------------------------------------------------
12171249
// Dump: dump the entire region graph
12181250
//
1219-
// Arguments:
1220-
// stmt - the statement to dump;
1221-
// bbNum - the basic block number to dump.
1222-
//
12231251
void Dump()
12241252
{
12251253
printf("Region graph:\n");
12261254
DumpRegionNode(m_rgnRoot, 0);
12271255
printf("\n");
12281256
}
12291257

1258+
//------------------------------------------------------------------------
1259+
// VerifyNode: verify the region graph rooted at `rgn`.
1260+
//
1261+
// Arguments:
1262+
// rgn - the node (and its children) to check.
1263+
//
1264+
void Verify(Region* rgn)
1265+
{
1266+
// The region needs to be a non-overlapping parent to all its children.
1267+
// The children need to be non-overlapping, and in increasing order.
1268+
1269+
unsigned rgnStartOrdinal = m_blkMap[rgn->m_bbStart->bbNum];
1270+
unsigned rgnEndOrdinal = m_blkMap[rgn->m_bbEnd->bbNum];
1271+
assert(rgnStartOrdinal <= rgnEndOrdinal);
1272+
1273+
Region* child = rgn->m_rgnChild;
1274+
Region* lastChild = nullptr;
1275+
if (child != nullptr)
1276+
{
1277+
unsigned childStartOrdinal = m_blkMap[child->m_bbStart->bbNum];
1278+
unsigned childEndOrdinal = m_blkMap[child->m_bbEnd->bbNum];
1279+
assert(childStartOrdinal <= childEndOrdinal);
1280+
assert(rgnStartOrdinal <= childStartOrdinal);
1281+
1282+
while (true)
1283+
{
1284+
Verify(child);
1285+
1286+
lastChild = child;
1287+
unsigned lastChildStartOrdinal = childStartOrdinal;
1288+
unsigned lastChildEndOrdinal = childEndOrdinal;
1289+
1290+
child = child->m_rgnNext;
1291+
if (child == nullptr)
1292+
{
1293+
break;
1294+
}
1295+
1296+
childStartOrdinal = m_blkMap[child->m_bbStart->bbNum];
1297+
childEndOrdinal = m_blkMap[child->m_bbEnd->bbNum];
1298+
assert(childStartOrdinal <= childEndOrdinal);
1299+
1300+
// The children can't overlap; they can't share any blocks.
1301+
assert(lastChildEndOrdinal < childStartOrdinal);
1302+
}
1303+
1304+
// The parent region must fully include the last child.
1305+
assert(childEndOrdinal <= rgnEndOrdinal);
1306+
}
1307+
}
1308+
1309+
//------------------------------------------------------------------------
1310+
// Verify: verify the region graph satisfies proper nesting, and other legality rules.
1311+
//
1312+
void Verify()
1313+
{
1314+
assert(m_comp != nullptr);
1315+
assert(m_blkMap != nullptr);
1316+
for (unsigned i = 0; i < m_blkMapSize; i++)
1317+
{
1318+
assert(m_blkMap[i] < m_blkMapSize);
1319+
}
1320+
1321+
// The root region has no siblings.
1322+
assert(m_rgnRoot != nullptr);
1323+
assert(m_rgnRoot->m_rgnNext == nullptr);
1324+
Verify(m_rgnRoot);
1325+
}
1326+
12301327
#endif // DEBUG
12311328

12321329
//------------------------------------------------------------------------
@@ -1349,11 +1446,12 @@ bool Compiler::fgDumpFlowGraph(Phases phase)
13491446
Compiler* m_comp;
13501447
Region* m_rgnRoot;
13511448
unsigned* m_blkMap;
1449+
unsigned m_blkMapSize;
13521450
};
13531451

13541452
// Define the region graph object. We'll add regions to this, then output the graph.
13551453

1356-
RegionGraph rgnGraph(this, blkMap);
1454+
RegionGraph rgnGraph(this, blkMap, blkMapSize);
13571455

13581456
// Add the EH regions to the region graph. An EH region consists of a region for the
13591457
// `try`, a region for the handler, and, for filter/filter-handlers, a region for the
@@ -1405,13 +1503,18 @@ bool Compiler::fgDumpFlowGraph(Phases phase)
14051503
for (unsigned loopNum = 0; loopNum < optLoopCount; loopNum++)
14061504
{
14071505
const LoopDsc& loop = optLoopTable[loopNum];
1506+
if (loop.lpFlags & LPFLG_REMOVED)
1507+
{
1508+
continue;
1509+
}
14081510
sprintf_s(name, sizeof(name), FMT_LP, loopNum);
14091511
rgnGraph.Insert(name, RegionGraph::RegionType::Loop, loop.lpFirst, loop.lpBottom);
14101512
}
14111513
}
14121514

14131515
// All the regions have been added. Now, output them.
14141516
DBEXEC(verbose, rgnGraph.Dump());
1517+
INDEBUG(rgnGraph.Verify());
14151518
rgnGraph.Output(fgxFile);
14161519
}
14171520
}

0 commit comments

Comments
 (0)