Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JIT: initial version of a profile checker #42481

Merged
merged 1 commit into from
Sep 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions src/coreclr/src/jit/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4537,7 +4537,11 @@ void Compiler::compCompile(void** methodCodePtr, ULONG* methodCodeSize, JitFlags

// Compute bbNum, bbRefs and bbPreds
//
// This is the first time full (not cheap) preds will be computed
// This is the first time full (not cheap) preds will be computed.
// And, if we have profile data, we can now check integrity.
//
// From this point on the flowgraph information such as bbNum,
// bbRefs or bbPreds has to be kept updated.
//
auto computePredsPhase = [this]() {
JITDUMP("\nRenumbering the basic blocks for fgComputePred\n");
Expand All @@ -4564,9 +4568,6 @@ void Compiler::compCompile(void** methodCodePtr, ULONG* methodCodeSize, JitFlags
DoPhase(this, PHASE_EARLY_UPDATE_FLOW_GRAPH, earlyUpdateFlowGraphPhase);
}

// From this point on the flowgraph information such as bbNum,
// bbRefs or bbPreds has to be kept updated
//
// Promote struct locals
//
auto promoteStructsPhase = [this]() {
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/src/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -5271,6 +5271,7 @@ class Compiler
void fgDebugCheckFlags(GenTree* tree);
void fgDebugCheckFlagsHelper(GenTree* tree, unsigned treeFlags, unsigned chkFlags);
void fgDebugCheckTryFinallyExits();
void fgDebugCheckProfileData();
#endif

static GenTree* fgGetFirstNode(GenTree* tree);
Expand Down
246 changes: 245 additions & 1 deletion src/coreclr/src/jit/flowgraph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26181,7 +26181,7 @@ PhaseStatus Compiler::fgMergeFinallyChains()
{
JITDUMP("Method had mergeable try-finallys and some callfinally merges were performed.\n");

#if DEBUG
#ifdef DEBUG
if (verbose)
{
printf("\n*************** After fgMergeFinallyChains()\n");
Expand Down Expand Up @@ -26718,3 +26718,247 @@ void Compiler::fgTailMergeThrowsJumpToHelper(BasicBlock* predBlock,
// Note there is now a jump to the canonical block
canonicalBlock->bbFlags |= (BBF_JMP_TARGET | BBF_HAS_LABEL);
}

#ifdef DEBUG

//------------------------------------------------------------------------
// fgDebugCheckProfileData: verify profile data is self-consistent
// (or nearly so)
//
// Notes:
// For each profiled block, check that the flow of counts into
// the block matches the flow of counts out of the block.
//
// We ignore EH flow as we don't have explicit edges and generally
// we expect EH edge counts to be small, so errors from ignoring
// them should be rare.
//
void Compiler::fgDebugCheckProfileData()
{
// We can't check before we have pred lists built.
//
assert(fgComputePredsDone);

JITDUMP("Checking Profile Data\n");
unsigned problemBlocks = 0;
unsigned unprofiledBlocks = 0;
unsigned profiledBlocks = 0;
bool entryProfiled = false;
bool exitProfiled = false;
BasicBlock::weight_t entryWeight = 0;
BasicBlock::weight_t exitWeight = 0;

// Verify each profiled block.
//
for (BasicBlock* block = fgFirstBB; block != nullptr; block = block->bbNext)
{
if (!block->hasProfileWeight())
{
unprofiledBlocks++;
continue;
}

// There is some profile data to check.
//
profiledBlocks++;

// Currently using raw counts. Consider using normalized counts instead?
//
BasicBlock::weight_t blockWeight = block->bbWeight;

bool verifyIncoming = true;
bool verifyOutgoing = true;

// First, look for blocks that require special treatment.

// Entry blocks
//
if (block == fgFirstBB)
{
entryWeight += blockWeight;
entryProfiled = true;
verifyIncoming = false;
}

// Exit blocks
//
if ((block->bbJumpKind == BBJ_RETURN) || (block->bbJumpKind == BBJ_THROW))
{
exitWeight += blockWeight;
exitProfiled = true;
verifyOutgoing = false;
}

// Handler entries
//
if (block->hasEHBoundaryIn())
{
verifyIncoming = false;
}

// Handler exits
//
if (block->hasEHBoundaryOut())
{
verifyOutgoing = false;
}

// We generally expect that the incoming flow, block weight and outgoing
// flow should all match.
//
// But we have two edge counts... so for now we simply check if the block
// count falls within the [min,max] range.
//
if (verifyIncoming)
{
BasicBlock::weight_t incomingWeightMin = 0;
BasicBlock::weight_t incomingWeightMax = 0;
bool foundPreds = false;

for (flowList* predEdge = block->bbPreds; predEdge != nullptr; predEdge = predEdge->flNext)
{
incomingWeightMin += predEdge->edgeWeightMin();
incomingWeightMax += predEdge->edgeWeightMax();
foundPreds = true;
}

if (!foundPreds)
{
// Might need to tone this down as we could see unreachable blocks?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would seem that the seriousness of this as a "problem" would depend on whether this is estimated or measured profile data - if the latter, it would be interesting to report if there was non-zero weight (though it could happen if the data were stale).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may see nonzero weight blocks become unreachable/dead even with accurate profile data, because at times we have to make guesses as to how profile data ends up distributed when we duplicate code. You can often see this post-inlining when there is strongly caller-correlated behavior.

I have a bunch of notes written up about "profile maintenance" that I haven't shared widely yet, but the upshot is that at times the profile will become inconsistent and fixing it up is potentially a global (intraprocedural) problem. So we may let things get out of balance for stretches and then reconstitute before key dependent phases. More on this soon.

problemBlocks++;
JITDUMP(" " FMT_BB " - expected to see predecessors\n", block->bbNum);
}
else
{
if (incomingWeightMin > incomingWeightMax)
{
problemBlocks++;
JITDUMP(" " FMT_BB " - incoming min %d > incoming max %d\n", block->bbNum, incomingWeightMin,
incomingWeightMax);
}
else if (blockWeight < incomingWeightMin)
{
problemBlocks++;
JITDUMP(" " FMT_BB " - block weight %d < incoming min %d\n", block->bbNum, blockWeight,
incomingWeightMin);
}
else if (blockWeight > incomingWeightMax)
{
problemBlocks++;
JITDUMP(" " FMT_BB " - block weight %d > incoming max %d\n", block->bbNum, blockWeight,
incomingWeightMax);
}
}
}

if (verifyOutgoing)
{
const unsigned numSuccs = block->NumSucc();

if (numSuccs == 0)
{
problemBlocks++;
JITDUMP(" " FMT_BB " - expected to see successors\n", block->bbNum);
}
else
{
BasicBlock::weight_t outgoingWeightMin = 0;
BasicBlock::weight_t outgoingWeightMax = 0;

// Walking successor edges is a bit wonky. Seems like it should be easier.
// Note this can also fail to enumerate all the edges, if we have a multigraph
//
int missingEdges = 0;

for (unsigned i = 0; i < numSuccs; i++)
{
BasicBlock* succBlock = block->GetSucc(i);
flowList* succEdge = nullptr;

for (flowList* edge = succBlock->bbPreds; edge != nullptr; edge = edge->flNext)
{
if (edge->flBlock == block)
{
succEdge = edge;
break;
}
}

if (succEdge == nullptr)
{
missingEdges++;
JITDUMP(" " FMT_BB " can't find successor edge to " FMT_BB "\n", block->bbNum,
succBlock->bbNum);
}
else
{
outgoingWeightMin += succEdge->edgeWeightMin();
outgoingWeightMax += succEdge->edgeWeightMax();
}
}

if (missingEdges > 0)
{
JITDUMP(" " FMT_BB " - missing %d successor edges\n", block->bbNum, missingEdges);
problemBlocks++;
}
if (outgoingWeightMin > outgoingWeightMax)
{
problemBlocks++;
JITDUMP(" " FMT_BB " - outgoing min %d > outgoing max %d\n", block->bbNum, outgoingWeightMin,
outgoingWeightMax);
}
else if (blockWeight < outgoingWeightMin)
{
problemBlocks++;
JITDUMP(" " FMT_BB " - block weight %d < outgoing min %d\n", block->bbNum, blockWeight,
outgoingWeightMin);
}
else if (blockWeight > outgoingWeightMax)
{
problemBlocks++;
JITDUMP(" " FMT_BB " - block weight %d > outgoing max %d\n", block->bbNum, blockWeight,
outgoingWeightMax);
}
}
}
}

// Verify overall input-output balance.
//
if (entryProfiled && exitProfiled)
{
if (entryWeight != exitWeight)
{
problemBlocks++;
JITDUMP(" Entry %d exit %d mismatch\n", entryWeight, exitWeight);
}
}

// Sum up what we discovered.
//
if (problemBlocks == 0)
{
if (profiledBlocks == 0)
{
JITDUMP("No blocks were profiled, so nothing to check\n");
}
else
{
JITDUMP("Profile is self-consistent (%d profiled blocks, %d unprofiled)\n", profiledBlocks,
unprofiledBlocks);
}
}
else
{
JITDUMP("Profile is NOT self-consistent, found %d problems (%d profiled blocks, %d unprofiled)\n",
problemBlocks, profiledBlocks, unprofiledBlocks);

if (JitConfig.JitProfileChecks() == 2)
{
assert(!"Inconsistent profile");
}
}
}

#endif // DEBUG
1 change: 1 addition & 0 deletions src/coreclr/src/jit/jitconfigvalues.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ CONFIG_INTEGER(JitPInvokeCheckEnabled, W("JITPInvokeCheckEnabled"), 0)
CONFIG_INTEGER(JitPInvokeEnabled, W("JITPInvokeEnabled"), 1)
CONFIG_METHODSET(JitPrintInlinedMethods, W("JitPrintInlinedMethods"))
CONFIG_METHODSET(JitPrintDevirtualizedMethods, W("JitPrintDevirtualizedMethods"))
CONFIG_INTEGER(JitProfileChecks, W("JitProfileChecks"), 0) // 1 enable in dumps, 2 assert if issues found
CONFIG_INTEGER(JitRequired, W("JITRequired"), -1)
CONFIG_INTEGER(JitRoundFloat, W("JITRoundFloat"), DEFAULT_ROUND_LEVEL)
CONFIG_INTEGER(JitStackAllocToLocalSize, W("JitStackAllocToLocalSize"), DEFAULT_MAX_LOCALLOC_TO_LOCAL_SIZE)
Expand Down
14 changes: 14 additions & 0 deletions src/coreclr/src/jit/phase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,20 @@ void Phase::PostPhase(PhaseStatus status)
}
}

// Optionally check profile data, if we have any.
//
// There's no point checking until we've built pred lists, as
// we can't easily reason about consistency without them.
//
// Bypass the "doPostPhase" filter until we're sure all
// phases that mess with profile counts set their phase status
// appropriately.
//
if ((JitConfig.JitProfileChecks() > 0) && comp->fgHaveProfileData() && comp->fgComputePredsDone)
{
comp->fgDebugCheckProfileData();
}

#endif // DEBUG

comp->EndPhase(m_phase);
Expand Down