in src/coreclr/jit/fgdiagnostic.cpp [699:1695]
bool Compiler::fgDumpFlowGraph(Phases phase, PhasePosition pos)
{
bool result = false;
bool dontClose = false;
#ifdef DEBUG
const bool createDotFile = JitConfig.JitDumpFgDot() != 0;
const bool includeEH = (JitConfig.JitDumpFgEH() != 0) && !compIsForInlining();
const bool includeLoops = (JitConfig.JitDumpFgLoops() != 0) && !compIsForInlining();
const bool constrained = JitConfig.JitDumpFgConstrained() != 0;
const bool useBlockId = JitConfig.JitDumpFgBlockID() != 0;
const bool displayBlockFlags = JitConfig.JitDumpFgBlockFlags() != 0;
#else // !DEBUG
const bool createDotFile = true;
const bool includeEH = false;
const bool includeLoops = false;
const bool constrained = true;
const bool useBlockId = false;
const bool displayBlockFlags = false;
#endif // !DEBUG
FILE* fgxFile = fgOpenFlowGraphFile(&dontClose, phase, pos, createDotFile ? "dot" : "fgx");
if (fgxFile == nullptr)
{
return false;
}
JITDUMP("Writing out flow graph %s phase %s\n", (pos == PhasePosition::PrePhase) ? "before" : "after",
PhaseNames[phase]);
double weightDivisor = (double)BasicBlock::getCalledCount(this);
const char* escapedString;
const char* regionString = "NONE";
if (info.compMethodInfo->regionKind == CORINFO_REGION_HOT)
{
regionString = "HOT";
}
else if (info.compMethodInfo->regionKind == CORINFO_REGION_COLD)
{
regionString = "COLD";
}
else if (info.compMethodInfo->regionKind == CORINFO_REGION_JIT)
{
regionString = "JIT";
}
if (createDotFile)
{
fprintf(fgxFile, "digraph FlowGraph {\n");
fprintf(fgxFile, " graph [label = \"%s%s\\n%s\\n%s\"];\n", info.compMethodName,
compIsForInlining() ? "\\n(inlinee)" : "", (pos == PhasePosition::PrePhase) ? "before" : "after",
PhaseNames[phase]);
fprintf(fgxFile, " node [shape = \"Box\"];\n");
}
else
{
fprintf(fgxFile, "<method");
escapedString = fgProcessEscapes(info.compFullName, s_EscapeMapping);
fprintf(fgxFile, "\n name=\"%s\"", escapedString);
escapedString = fgProcessEscapes(info.compClassName, s_EscapeMapping);
fprintf(fgxFile, "\n className=\"%s\"", escapedString);
escapedString = fgProcessEscapes(info.compMethodName, s_EscapeMapping);
fprintf(fgxFile, "\n methodName=\"%s\"", escapedString);
fprintf(fgxFile, "\n ngenRegion=\"%s\"", regionString);
fprintf(fgxFile, "\n bytesOfIL=\"%d\"", info.compILCodeSize);
fprintf(fgxFile, "\n localVarCount=\"%d\"", lvaCount);
if (fgHaveProfileWeights())
{
fprintf(fgxFile, "\n calledCount=\"%f\"", fgCalledCount);
fprintf(fgxFile, "\n profileData=\"true\"");
}
if (compHndBBtabCount > 0)
{
fprintf(fgxFile, "\n hasEHRegions=\"true\"");
}
if (fgHasLoops)
{
fprintf(fgxFile, "\n hasLoops=\"true\"");
}
if (fgFirstColdBlock != nullptr)
{
fprintf(fgxFile, "\n firstColdBlock=\"%d\"", fgFirstColdBlock->bbNum);
}
fprintf(fgxFile, ">");
fprintf(fgxFile, "\n <blocks");
fprintf(fgxFile, "\n blockCount=\"%d\"", fgBBcount);
fprintf(fgxFile, ">");
}
// In some cases, we want to change the display based on whether an edge is lexically backwards, forwards,
// or lexical successor. Also, for the region tree, using the lexical order is useful for determining where
// to insert in the tree, to determine nesting. We'd like to use the bbNum to do this. However, we don't
// want to renumber the blocks. So, create a mapping of bbNum to ordinal, and compare block order by
// comparing the mapped ordinals instead.
//
// For inlinees, the max block number of the inliner is used, so we need to allocate the block map based on
// that size, even though it means allocating a block map possibly much bigger than what's required for just
// the inlinee blocks.
unsigned blkMapSize = 1 + fgBBNumMax;
unsigned blockOrdinal = 1;
unsigned* blkMap = new (this, CMK_DebugOnly) unsigned[blkMapSize];
memset(blkMap, 0, sizeof(unsigned) * blkMapSize);
for (BasicBlock* const block : Blocks())
{
assert(block->bbNum < blkMapSize);
blkMap[block->bbNum] = blockOrdinal++;
}
static const char* kindImage[] = {"EHFINALLYRET", "EHFILTERRET", "EHCATCHRET", "THROW", "RETURN", "NONE",
"ALWAYS", "LEAVE", "CALLFINALLY", "COND", "SWITCH"};
BasicBlock* block;
for (block = fgFirstBB, blockOrdinal = 1; block != nullptr; block = block->Next(), blockOrdinal++)
{
if (createDotFile)
{
fprintf(fgxFile, " " FMT_BB " [label = \"", block->bbNum);
if (useBlockId)
{
fprintf(fgxFile, "%s", block->dspToString());
}
else
{
fprintf(fgxFile, FMT_BB, block->bbNum);
}
if (displayBlockFlags)
{
// Don't display the `[` `]` unless we're going to display something.
const bool isTryEntryBlock = bbIsTryBeg(block);
if (isTryEntryBlock ||
block->HasAnyFlag(BBF_FUNCLET_BEG | BBF_RUN_RARELY | BBF_LOOP_HEAD | BBF_LOOP_ALIGN))
{
// Display a very few, useful, block flags
fprintf(fgxFile, " [");
if (isTryEntryBlock)
{
fprintf(fgxFile, "T");
}
if (block->HasFlag(BBF_FUNCLET_BEG))
{
fprintf(fgxFile, "F");
}
if (block->HasFlag(BBF_RUN_RARELY))
{
fprintf(fgxFile, "R");
}
if (block->HasFlag(BBF_LOOP_HEAD))
{
fprintf(fgxFile, "L");
}
if (block->HasFlag(BBF_LOOP_ALIGN))
{
fprintf(fgxFile, "A");
}
fprintf(fgxFile, "]");
}
}
// Optionally show GC Heap Mem SSA state and Memory Phis
//
if ((JitConfig.JitDumpFgMemorySsa() != 0) && (fgSsaPassesCompleted > 0))
{
fprintf(fgxFile, "\\n");
MemoryKind k = MemoryKind::GcHeap;
const unsigned ssaIn = block->bbMemorySsaNumIn[k];
const unsigned ssaOut = block->bbMemorySsaNumOut[k];
if (ssaIn != SsaConfig::RESERVED_SSA_NUM)
{
ValueNum vnIn = GetMemoryPerSsaData(ssaIn)->m_vnPair.GetLiberal();
BasicBlock::MemoryPhiArg* memPhi = block->bbMemorySsaPhiFunc[k];
if ((memPhi != nullptr) && (memPhi != BasicBlock::EmptyMemoryPhiDef))
{
fprintf(fgxFile, "MI %d " FMT_VN " = PHI(", ssaIn, vnIn);
bool first = true;
for (; memPhi != nullptr; memPhi = memPhi->m_nextArg)
{
ValueNum phiVN = GetMemoryPerSsaData(memPhi->GetSsaNum())->m_vnPair.GetLiberal();
fprintf(fgxFile, "%s%d " FMT_VN, first ? "" : ",", memPhi->m_ssaNum, phiVN);
first = false;
}
fprintf(fgxFile, ")");
}
else
{
ValueNum vn = GetMemoryPerSsaData(block->bbMemorySsaNumIn[k])->m_vnPair.GetLiberal();
fprintf(fgxFile, "MI %d " FMT_VN, block->bbMemorySsaNumIn[k], vn);
}
fprintf(fgxFile, "\\n");
if (block->bbMemoryHavoc != 0)
{
fprintf(fgxFile, "** HAVOC **\\n");
}
ValueNum vnOut = GetMemoryPerSsaData(ssaOut)->m_vnPair.GetLiberal();
fprintf(fgxFile, "MO %d " FMT_VN, ssaOut, vnOut);
}
}
if (block->KindIs(BBJ_COND))
{
fprintf(fgxFile, "\\n");
// Include a line with the basics of the branch condition, if possible.
// Find the loop termination test at the bottom of the loop.
Statement* condStmt = block->lastStmt();
if (condStmt != nullptr)
{
GenTree* const condTree = condStmt->GetRootNode();
noway_assert(condTree->gtOper == GT_JTRUE);
GenTree* const compareTree = condTree->AsOp()->gtOp1;
fgDumpTree(fgxFile, compareTree);
}
}
// "Raw" Profile weight
if (block->hasProfileWeight() || (JitConfig.JitSynthesizeCounts() > 0))
{
fprintf(fgxFile, "\\n\\n%7.2f", ((double)block->getBBWeight(this)) / BB_UNITY_WEIGHT);
}
// end of block label
fprintf(fgxFile, "\"");
// other node attributes
//
if (block == fgFirstBB)
{
fprintf(fgxFile, ", shape = \"house\"");
}
else if (block->KindIs(BBJ_RETURN))
{
fprintf(fgxFile, ", shape = \"invhouse\"");
}
else if (block->KindIs(BBJ_THROW))
{
fprintf(fgxFile, ", shape = \"trapezium\"");
}
else if (block->HasFlag(BBF_INTERNAL))
{
fprintf(fgxFile, ", shape = \"note\"");
}
fprintf(fgxFile, "];\n");
}
else
{
fprintf(fgxFile, "\n <block");
fprintf(fgxFile, "\n id=\"%d\"", block->bbNum);
fprintf(fgxFile, "\n ordinal=\"%d\"", blockOrdinal);
fprintf(fgxFile, "\n jumpKind=\"%s\"", kindImage[block->GetKind()]);
if (block->hasTryIndex())
{
fprintf(fgxFile, "\n inTry=\"%s\"", "true");
}
if (block->hasHndIndex())
{
fprintf(fgxFile, "\n inHandler=\"%s\"", "true");
}
if ((fgFirstBB->hasProfileWeight()) && !block->HasFlag(BBF_COLD))
{
fprintf(fgxFile, "\n hot=\"true\"");
}
if (block->HasFlag(BBF_HAS_NEWOBJ))
{
fprintf(fgxFile, "\n callsNew=\"true\"");
}
if (block->HasFlag(BBF_LOOP_HEAD))
{
fprintf(fgxFile, "\n loopHead=\"true\"");
}
const char* rootTreeOpName = "n/a";
if (block->IsLIR() || (block->lastStmt() != nullptr))
{
if (block->lastNode() != nullptr)
{
rootTreeOpName = GenTree::OpName(block->lastNode()->OperGet());
}
}
fprintf(fgxFile, "\n weight=");
fprintfDouble(fgxFile, ((double)block->bbWeight) / weightDivisor);
// fgGetCodeEstimate() will assert if the costs have not yet been initialized.
// fprintf(fgxFile, "\n codeEstimate=\"%d\"", fgGetCodeEstimate(block));
fprintf(fgxFile, "\n startOffset=\"%d\"", block->bbCodeOffs);
fprintf(fgxFile, "\n rootTreeOp=\"%s\"", rootTreeOpName);
fprintf(fgxFile, "\n endOffset=\"%d\"", block->bbCodeOffsEnd);
fprintf(fgxFile, ">");
fprintf(fgxFile, "\n </block>");
}
}
if (!createDotFile)
{
fprintf(fgxFile, "\n </blocks>");
fprintf(fgxFile, "\n <edges");
fprintf(fgxFile, "\n edgeCount=\"%d\"", fgEdgeCount);
fprintf(fgxFile, ">");
}
if (fgPredsComputed)
{
unsigned edgeNum = 1;
BasicBlock* bTarget;
for (bTarget = fgFirstBB; bTarget != nullptr; bTarget = bTarget->Next())
{
double targetWeightDivisor;
if (bTarget->bbWeight == BB_ZERO_WEIGHT)
{
targetWeightDivisor = 1.0;
}
else
{
targetWeightDivisor = (double)bTarget->bbWeight;
}
for (FlowEdge* const edge : bTarget->PredEdges())
{
BasicBlock* bSource = edge->getSourceBlock();
double sourceWeightDivisor;
if (bSource->bbWeight == BB_ZERO_WEIGHT)
{
sourceWeightDivisor = 1.0;
}
else
{
sourceWeightDivisor = (double)bSource->bbWeight;
}
if (createDotFile)
{
fprintf(fgxFile, " " FMT_BB " -> " FMT_BB, bSource->bbNum, bTarget->bbNum);
const char* sep = "";
if (blkMap[bSource->bbNum] > blkMap[bTarget->bbNum])
{
// Lexical backedge
fprintf(fgxFile, " [color=green");
sep = ", ";
}
else if ((blkMap[bSource->bbNum] + 1) == blkMap[bTarget->bbNum])
{
// Lexical successor
fprintf(fgxFile, " [color=blue, weight=20");
sep = ", ";
}
else
{
fprintf(fgxFile, " [");
}
const weight_t edgeWeight = edge->getLikelyWeight();
fprintf(fgxFile, "%slabel=\"%7.2f\"", sep, (double)edgeWeight / weightDivisor);
fprintf(fgxFile, "];\n");
}
else
{
fprintf(fgxFile, "\n <edge");
fprintf(fgxFile, "\n id=\"%d\"", edgeNum);
fprintf(fgxFile, "\n source=\"%d\"", bSource->bbNum);
fprintf(fgxFile, "\n target=\"%d\"", bTarget->bbNum);
if (bSource->KindIs(BBJ_SWITCH))
{
if (edge->getDupCount() >= 2)
{
fprintf(fgxFile, "\n switchCases=\"%d\"", edge->getDupCount());
}
if (bSource->GetSwitchTargets()->getDefault()->getDestinationBlock() == bTarget)
{
fprintf(fgxFile, "\n switchDefault=\"true\"");
}
}
const weight_t edgeWeight = edge->getLikelyWeight();
fprintf(fgxFile, "\n weight=");
fprintfDouble(fgxFile, ((double)edgeWeight) / weightDivisor);
if (edgeWeight > 0)
{
if (edgeWeight < bSource->bbWeight)
{
fprintf(fgxFile, "\n out=");
fprintfDouble(fgxFile, ((double)edgeWeight) / sourceWeightDivisor);
}
if (edgeWeight < bTarget->bbWeight)
{
fprintf(fgxFile, "\n in=");
fprintfDouble(fgxFile, ((double)edgeWeight) / targetWeightDivisor);
}
}
}
if (!createDotFile)
{
fprintf(fgxFile, ">");
fprintf(fgxFile, "\n </edge>");
}
++edgeNum;
}
}
}
// For dot, show edges w/o pred lists, and add invisible bbNext links.
// Also, add EH and/or loop regions as "cluster" subgraphs, if requested.
//
if (createDotFile)
{
for (BasicBlock* const bSource : Blocks())
{
if (constrained)
{
// Invisible edge for bbNext chain
//
if (!bSource->IsLast())
{
fprintf(fgxFile, " " FMT_BB " -> " FMT_BB " [style=\"invis\", weight=25];\n", bSource->bbNum,
bSource->Next()->bbNum);
}
}
if (fgPredsComputed)
{
// Already emitted pred edges above.
//
continue;
}
// Emit successor edges
//
for (BasicBlock* const bTarget : bSource->Succs())
{
fprintf(fgxFile, " " FMT_BB " -> " FMT_BB, bSource->bbNum, bTarget->bbNum);
if (blkMap[bSource->bbNum] > blkMap[bTarget->bbNum])
{
// Lexical backedge
fprintf(fgxFile, " [color=green]\n");
}
else if ((blkMap[bSource->bbNum] + 1) == blkMap[bTarget->bbNum])
{
// Lexical successor
fprintf(fgxFile, " [color=blue]\n");
}
else
{
fprintf(fgxFile, ";\n");
}
}
}
if (includeEH && (compHndBBtabCount > 0))
{
// Generate something like:
// subgraph cluster_0 {
// label = "xxx";
// color = yyy;
// bb; bb;
// subgraph {
// label = "aaa";
// color = bbb;
// bb; bb...
// }
// ...
// }
//
// Thus, the subgraphs need to be nested to show the region nesting.
//
// The EH table is in order, top-to-bottom, most nested to least nested where
// there is a parent/child relationship.
//
// Build a region tree, collecting all the regions we want to display,
// and then walk it to emit the regions.
// RegionGraph: represent non-overlapping, possibly nested, block ranges in the flow graph.
class RegionGraph
{
public:
enum class RegionType
{
Root,
EH,
};
private:
struct Region
{
Region(RegionType rgnType, const char* rgnName, BasicBlock* bbStart, BasicBlock* bbEnd)
: m_rgnNext(nullptr)
, m_rgnChild(nullptr)
, m_rgnType(rgnType)
, m_bbStart(bbStart)
, m_bbEnd(bbEnd)
{
strcpy_s(m_rgnName, sizeof(m_rgnName), rgnName);
}
Region* m_rgnNext;
Region* m_rgnChild;
RegionType m_rgnType;
char m_rgnName[30];
BasicBlock* m_bbStart;
BasicBlock* m_bbEnd;
};
public:
RegionGraph(Compiler* comp, unsigned* blkMap, unsigned blkMapSize)
: m_comp(comp)
, m_rgnRoot(nullptr)
, m_blkMap(blkMap)
, m_blkMapSize(blkMapSize)
{
// Create a root region that encompasses the whole function.
m_rgnRoot =
new (m_comp, CMK_DebugOnly) Region(RegionType::Root, "Root", comp->fgFirstBB, comp->fgLastBB);
}
//------------------------------------------------------------------------
// Insert: Insert a region [start..end] (inclusive) into the graph.
//
// Arguments:
// name - the textual label to use for the region
// rgnType - the region type
// start - start block of the region
// end - last block of the region
//
void Insert(const char* name, RegionType rgnType, BasicBlock* start, BasicBlock* end)
{
JITDUMP("Insert region: %s, type: %s, start: " FMT_BB ", end: " FMT_BB "\n", name,
GetRegionType(rgnType), start->bbNum, end->bbNum);
assert(start != nullptr);
assert(end != nullptr);
Region* newRgn = new (m_comp, CMK_DebugOnly) Region(rgnType, name, start, end);
unsigned newStartOrdinal = m_blkMap[start->bbNum];
unsigned newEndOrdinal = m_blkMap[end->bbNum];
Region* curRgn = m_rgnRoot;
unsigned curStartOrdinal = m_blkMap[curRgn->m_bbStart->bbNum];
unsigned curEndOrdinal = m_blkMap[curRgn->m_bbEnd->bbNum];
// A range can be a single block, but there can be no overlap between ranges.
assert(newStartOrdinal <= newEndOrdinal);
assert(curStartOrdinal <= curEndOrdinal);
assert(newStartOrdinal >= curStartOrdinal);
assert(newEndOrdinal <= curEndOrdinal);
// We know the new region will be part of the current region. Should it be a direct
// child, or put within one of the existing children?
Region** lastChildPtr = &curRgn->m_rgnChild;
Region* child = curRgn->m_rgnChild;
while (child != nullptr)
{
unsigned childStartOrdinal = m_blkMap[child->m_bbStart->bbNum];
unsigned childEndOrdinal = m_blkMap[child->m_bbEnd->bbNum];
// Consider the following cases, where each "x" is a block in the range:
// xxxxxxx // current 'child' range; we're comparing against this
// xxxxxxx // (1) same range; could be considered child or parent
// xxxxxxxxx // (2) parent range, shares last block
// xxxxxxxxx // (3) parent range, shares first block
// xxxxxxxxxxx // (4) fully overlapping parent range
// xx // (5) non-overlapping preceding sibling range
// xx // (6) non-overlapping following sibling range
// xxx // (7) child range
// xxx // (8) child range, shares same start block
// x // (9) single-block child range, shares same start block
// xxx // (10) child range, shares same end block
// x // (11) single-block child range, shares same end block
// xxxxxxx // illegal: overlapping ranges
// xxx // illegal: overlapping ranges (shared child start block and new end block)
// xxxxxxx // illegal: overlapping ranges
// xxx // illegal: overlapping ranges (shared child end block and new start block)
// Assert the child is properly nested within the parent.
// Note that if regions have the same start and end, you can't tell which is nested within the
// other, though it shouldn't matter.
assert(childStartOrdinal <= childEndOrdinal);
assert(curStartOrdinal <= childStartOrdinal);
assert(childEndOrdinal <= curEndOrdinal);
// Should the new region be before this child?
// Case (5).
if (newEndOrdinal < childStartOrdinal)
{
// Insert before this child.
newRgn->m_rgnNext = child;
*lastChildPtr = newRgn;
break;
}
else if ((newStartOrdinal >= childStartOrdinal) && (newEndOrdinal <= childEndOrdinal))
{
// Insert as a child of this child.
// Need to recurse to walk the child's children list to see where it belongs.
// Case (1), (7), (8), (9), (10), (11).
curStartOrdinal = m_blkMap[child->m_bbStart->bbNum];
curEndOrdinal = m_blkMap[child->m_bbEnd->bbNum];
lastChildPtr = &child->m_rgnChild;
child = child->m_rgnChild;
continue;
}
else if (newStartOrdinal <= childStartOrdinal)
{
// The new region is a parent of one or more of the existing children.
// Case (2), (3), (4).
// Find all the children it encompasses.
Region** lastEndChildPtr = &child->m_rgnNext;
Region* endChild = child->m_rgnNext;
while (endChild != nullptr)
{
unsigned endChildStartOrdinal = m_blkMap[endChild->m_bbStart->bbNum];
unsigned endChildEndOrdinal = m_blkMap[endChild->m_bbEnd->bbNum];
assert(endChildStartOrdinal <= endChildEndOrdinal);
if (newEndOrdinal < endChildStartOrdinal)
{
// Found the range
break;
}
lastEndChildPtr = &endChild->m_rgnNext;
endChild = endChild->m_rgnNext;
}
// The range is [child..endChild previous]. If endChild is nullptr, then
// the range is to the end of the parent. Move these all to be
// children of newRgn, and put newRgn in where `child` is.
newRgn->m_rgnNext = endChild;
*lastChildPtr = newRgn;
newRgn->m_rgnChild = child;
*lastEndChildPtr = nullptr;
break;
}
// Else, look for next child.
// Case (6).
lastChildPtr = &child->m_rgnNext;
child = child->m_rgnNext;
}
if (child == nullptr)
{
// Insert as the last child (could be the only child).
*lastChildPtr = newRgn;
}
}
#ifdef DEBUG
const unsigned dumpIndentIncrement = 2; // How much to indent each nested level.
//------------------------------------------------------------------------
// GetRegionType: get a textual name for the region type, to be used in dumps.
//
// Arguments:
// rgnType - the region type
//
static const char* GetRegionType(RegionType rgnType)
{
switch (rgnType)
{
case RegionType::Root:
return "Root";
case RegionType::EH:
return "EH";
default:
return "UNKNOWN";
}
}
//------------------------------------------------------------------------
// DumpRegionNode: Region graph dump helper to dump a region node at the given indent,
// and recursive dump its children.
//
// Arguments:
// rgn - the region to dump
// indent - number of leading characters to indent all output
//
void DumpRegionNode(Region* rgn, unsigned indent) const
{
printf("%*s======\n", indent, "");
printf("%*sType: %s\n", indent, "", GetRegionType(rgn->m_rgnType));
printf("%*sName: %s\n", indent, "", rgn->m_rgnName);
printf("%*sRange: " FMT_BB ".." FMT_BB "\n", indent, "", rgn->m_bbStart->bbNum,
rgn->m_bbEnd->bbNum);
for (Region* child = rgn->m_rgnChild; child != nullptr; child = child->m_rgnNext)
{
DumpRegionNode(child, indent + dumpIndentIncrement);
}
}
//------------------------------------------------------------------------
// Dump: dump the entire region graph
//
void Dump()
{
printf("Region graph:\n");
DumpRegionNode(m_rgnRoot, 0);
printf("\n");
}
//------------------------------------------------------------------------
// VerifyNode: verify the region graph rooted at `rgn`.
//
// Arguments:
// rgn - the node (and its children) to check.
//
void Verify(Region* rgn)
{
// The region needs to be a non-overlapping parent to all its children.
// The children need to be non-overlapping, and in increasing order.
unsigned rgnStartOrdinal = m_blkMap[rgn->m_bbStart->bbNum];
unsigned rgnEndOrdinal = m_blkMap[rgn->m_bbEnd->bbNum];
assert(rgnStartOrdinal <= rgnEndOrdinal);
Region* child = rgn->m_rgnChild;
Region* lastChild = nullptr;
if (child != nullptr)
{
unsigned childStartOrdinal = m_blkMap[child->m_bbStart->bbNum];
unsigned childEndOrdinal = m_blkMap[child->m_bbEnd->bbNum];
assert(childStartOrdinal <= childEndOrdinal);
assert(rgnStartOrdinal <= childStartOrdinal);
while (true)
{
Verify(child);
lastChild = child;
unsigned lastChildStartOrdinal = childStartOrdinal;
unsigned lastChildEndOrdinal = childEndOrdinal;
child = child->m_rgnNext;
if (child == nullptr)
{
break;
}
childStartOrdinal = m_blkMap[child->m_bbStart->bbNum];
childEndOrdinal = m_blkMap[child->m_bbEnd->bbNum];
assert(childStartOrdinal <= childEndOrdinal);
// The children can't overlap; they can't share any blocks.
assert(lastChildEndOrdinal < childStartOrdinal);
}
// The parent region must fully include the last child.
assert(childEndOrdinal <= rgnEndOrdinal);
}
}
//------------------------------------------------------------------------
// Verify: verify the region graph satisfies proper nesting, and other legality rules.
//
void Verify()
{
assert(m_comp != nullptr);
assert(m_blkMap != nullptr);
for (unsigned i = 0; i < m_blkMapSize; i++)
{
assert(m_blkMap[i] < m_blkMapSize);
}
// The root region has no siblings.
assert(m_rgnRoot != nullptr);
assert(m_rgnRoot->m_rgnNext == nullptr);
Verify(m_rgnRoot);
}
#endif // DEBUG
//------------------------------------------------------------------------
// Output: output the region graph to the .dot file
//
// Arguments:
// file - the file to write output to.
//
void Output(FILE* file)
{
unsigned clusterNum = 0;
// Output the regions; don't output the top (root) region that represents the whole function.
for (Region* child = m_rgnRoot->m_rgnChild; child != nullptr; child = child->m_rgnNext)
{
OutputRegion(file, clusterNum, child, 4);
}
fprintf(file, "\n");
}
private:
//------------------------------------------------------------------------
// GetColorForRegion: get a color name to use for a region
//
// Arguments:
// rgn - the region for which we need a color
//
static const char* GetColorForRegion(Region* rgn)
{
RegionType rgnType = rgn->m_rgnType;
switch (rgnType)
{
case RegionType::EH:
return "red";
default:
return "black";
}
}
//------------------------------------------------------------------------
// OutputRegion: helper function to output a region and its nested children
// to the .dot file.
//
// Arguments:
// file - the file to write output to.
// clusterNum - the number of this dot "cluster". This is updated as we
// create new clusters.
// rgn - the region to output.
// indent - the current indent level, in characters.
//
void OutputRegion(FILE* file, unsigned& clusterNum, Region* rgn, unsigned indent)
{
fprintf(file, "%*ssubgraph cluster_%u {\n", indent, "", clusterNum);
indent += 4;
fprintf(file, "%*slabel = \"%s\";\n", indent, "", rgn->m_rgnName);
fprintf(file, "%*scolor = %s;\n", indent, "", GetColorForRegion(rgn));
clusterNum++;
bool needIndent = true;
BasicBlock* bbCur = rgn->m_bbStart;
BasicBlock* bbEnd = rgn->m_bbEnd->Next();
Region* child = rgn->m_rgnChild;
BasicBlock* childCurBB = (child == nullptr) ? nullptr : child->m_bbStart;
// Count the children and assert we output all of them.
unsigned totalChildren = 0;
unsigned childCount = 0;
for (Region* tmpChild = child; tmpChild != nullptr; tmpChild = tmpChild->m_rgnNext)
{
totalChildren++;
}
while (bbCur != bbEnd)
{
// Output from bbCur to current child first block.
while ((bbCur != childCurBB) && (bbCur != bbEnd))
{
fprintf(file, "%*s" FMT_BB ";", needIndent ? indent : 0, "", bbCur->bbNum);
needIndent = false;
bbCur = bbCur->Next();
}
if (bbCur == bbEnd)
{
// We're done at this level.
break;
}
else
{
assert(bbCur != nullptr); // Or else we should also have `bbCur == bbEnd`
assert(child != nullptr);
// If there is a child, output that child.
if (!needIndent)
{
// We've printed some basic blocks, so put the subgraph on a new line.
fprintf(file, "\n");
}
OutputRegion(file, clusterNum, child, indent);
needIndent = true;
childCount++;
bbCur = child->m_bbEnd->Next(); // Next, output blocks after this child.
child = child->m_rgnNext; // Move to the next child, if any.
childCurBB = (child == nullptr) ? nullptr : child->m_bbStart;
}
}
// Put the end brace on its own line and leave the cursor at the beginning of the line for the
// parent.
indent -= 4;
fprintf(file, "\n%*s}\n", indent, "");
assert(childCount == totalChildren);
}
Compiler* m_comp;
Region* m_rgnRoot;
unsigned* m_blkMap;
unsigned m_blkMapSize;
};
// Define the region graph object. We'll add regions to this, then output the graph.
RegionGraph rgnGraph(this, blkMap, blkMapSize);
// Add the EH regions to the region graph. An EH region consists of a region for the
// `try`, a region for the handler, and, for filter/filter-handlers, a region for the
// `filter` as well.
if (includeEH)
{
char name[30];
unsigned XTnum;
EHblkDsc* ehDsc;
for (XTnum = 0, ehDsc = compHndBBtab; XTnum < compHndBBtabCount; XTnum++, ehDsc++)
{
sprintf_s(name, sizeof(name), "EH#%u try", XTnum);
rgnGraph.Insert(name, RegionGraph::RegionType::EH, ehDsc->ebdTryBeg, ehDsc->ebdTryLast);
const char* handlerType = "";
switch (ehDsc->ebdHandlerType)
{
case EH_HANDLER_CATCH:
handlerType = "catch";
break;
case EH_HANDLER_FILTER:
handlerType = "filter-hnd";
break;
case EH_HANDLER_FAULT:
handlerType = "fault";
break;
case EH_HANDLER_FINALLY:
handlerType = "finally";
break;
case EH_HANDLER_FAULT_WAS_FINALLY:
handlerType = "fault-was-finally";
break;
}
sprintf_s(name, sizeof(name), "EH#%u %s", XTnum, handlerType);
rgnGraph.Insert(name, RegionGraph::RegionType::EH, ehDsc->ebdHndBeg, ehDsc->ebdHndLast);
if (ehDsc->HasFilter())
{
sprintf_s(name, sizeof(name), "EH#%u filter", XTnum);
rgnGraph.Insert(name, RegionGraph::RegionType::EH, ehDsc->ebdFilter, ehDsc->ebdHndBeg->Prev());
}
}
}
// All the regions have been added. Now, output them.
DBEXEC(verbose, rgnGraph.Dump());
INDEBUG(rgnGraph.Verify());
rgnGraph.Output(fgxFile);
}
if (includeLoops && (m_loops != nullptr))
{
fgDumpFlowGraphLoops(fgxFile);
}
}
if (createDotFile)
{
fprintf(fgxFile, "}\n");
}
else
{
fprintf(fgxFile, "\n </edges>");
fprintf(fgxFile, "\n</method>\n");
}
if (dontClose)
{
// fgxFile is jitstdout or stderr
fprintf(fgxFile, "\n");
}
else
{
fclose(fgxFile);
}
return result;
}