Skip to content

Commit c4e3ef6

Browse files
committed
JIT: profile synthesis consistency checking and more
Add the ability to verify that the profile counts produced by synthesis are self-consistent, and optionally to assert if they're not. Disable checking if we know profile flow will be inconsistent (because of irreducible loops and/or capped cyclic probabilities). Consistently ignore the likely flow out of handlers. Generally we model handlers as isolated subgraphs that do not contribute flow to the main flow graph. This is generally acceptable. The one caveat is for flow into finallies. The plan here is to fix the weights for finallies up in a subsequent pass via simple scaling once callfinallies are introduced. Flow weights out of finallies will be ignored as the callfinally block will be modeled as always flowing to its pair tail. Also add the ability to propagate the synthesized counts into the live profile data. This is mainly to facilitate using the MIBC comparison tools we have to assess how closely the synthesiszed data comes to actual PGO data. Finally, enable the new synthesized plus checked modes in a few of our PGO pipelines. Contributes to dotnet#82964.
1 parent 73b2a0f commit c4e3ef6

File tree

10 files changed

+224
-52
lines changed

10 files changed

+224
-52
lines changed

eng/pipelines/common/templates/runtimes/run-test-job.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,7 @@ jobs:
541541
- fullpgo_random_gdv_methodprofiling_only
542542
- fullpgo_random_gdv_edge
543543
- fullpgo_methodprofiling_always_optimized
544+
- syntheticpgo
544545
${{ if in(parameters.testGroup, 'gc-longrunning') }}:
545546
longRunningGcTests: true
546547
scenarios:

eng/pipelines/libraries/run-test-job.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,3 +200,4 @@ jobs:
200200
- fullpgo_random_gdv_edge
201201
- jitosr_stress
202202
- jitosr_stress_random
203+
- syntheticpgo

src/coreclr/jit/compiler.h

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1577,6 +1577,53 @@ inline constexpr FlowGraphUpdates operator&(FlowGraphUpdates a, FlowGraphUpdates
15771577
return (FlowGraphUpdates)((unsigned int)a & (unsigned int)b);
15781578
}
15791579

1580+
// Profile checking options
1581+
//
1582+
// clang-format off
1583+
enum class ProfileChecks : unsigned int
1584+
{
1585+
CHECK_NONE = 0,
1586+
CHECK_CLASSIC = 1 << 0, // check "classic" jit weights
1587+
CHECK_LIKELY = 1 << 1, // check likelihood based weights
1588+
RAISE_ASSERT = 1 << 2, // assert on check failure
1589+
CHECK_ALL_BLOCKS = 1 << 3, // check blocks even if bbHasProfileWeight is false
1590+
};
1591+
1592+
inline constexpr ProfileChecks operator ~(ProfileChecks a)
1593+
{
1594+
return (ProfileChecks)(~(unsigned int)a);
1595+
}
1596+
1597+
inline constexpr ProfileChecks operator |(ProfileChecks a, ProfileChecks b)
1598+
{
1599+
return (ProfileChecks)((unsigned int)a | (unsigned int)b);
1600+
}
1601+
1602+
inline constexpr ProfileChecks operator &(ProfileChecks a, ProfileChecks b)
1603+
{
1604+
return (ProfileChecks)((unsigned int)a & (unsigned int)b);
1605+
}
1606+
1607+
inline ProfileChecks& operator |=(ProfileChecks& a, ProfileChecks b)
1608+
{
1609+
return a = (ProfileChecks)((unsigned int)a | (unsigned int)b);
1610+
}
1611+
1612+
inline ProfileChecks& operator &=(ProfileChecks& a, ProfileChecks b)
1613+
{
1614+
return a = (ProfileChecks)((unsigned int)a & (unsigned int)b);
1615+
}
1616+
1617+
inline ProfileChecks& operator ^=(ProfileChecks& a, ProfileChecks b)
1618+
{
1619+
return a = (ProfileChecks)((unsigned int)a ^ (unsigned int)b);
1620+
}
1621+
1622+
inline bool hasFlag(const ProfileChecks& flagSet, const ProfileChecks& flag)
1623+
{
1624+
return ((flagSet & flag) == flag);
1625+
}
1626+
15801627
//---------------------------------------------------------------
15811628
// Compilation time.
15821629
//
@@ -5516,8 +5563,9 @@ class Compiler
55165563
void fgDebugCheckFlagsHelper(GenTree* tree, GenTreeFlags actualFlags, GenTreeFlags expectedFlags);
55175564
void fgDebugCheckTryFinallyExits();
55185565
void fgDebugCheckProfileWeights();
5519-
bool fgDebugCheckIncomingProfileData(BasicBlock* block);
5520-
bool fgDebugCheckOutgoingProfileData(BasicBlock* block);
5566+
void fgDebugCheckProfileWeights(ProfileChecks checks);
5567+
bool fgDebugCheckIncomingProfileData(BasicBlock* block, ProfileChecks checks);
5568+
bool fgDebugCheckOutgoingProfileData(BasicBlock* block, ProfileChecks checks);
55215569

55225570
#endif // DEBUG
55235571

src/coreclr/jit/fgdiagnostic.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -957,7 +957,7 @@ bool Compiler::fgDumpFlowGraph(Phases phase, PhasePosition pos)
957957
}
958958

959959
// "Raw" Profile weight
960-
if (block->hasProfileWeight())
960+
if (block->hasProfileWeight() || (JitConfig.JitSynthesizeCounts() > 0))
961961
{
962962
fprintf(fgxFile, "\\n\\n%7.2f", ((double)block->getBBWeight(this)) / BB_UNITY_WEIGHT);
963963
}

src/coreclr/jit/fgprofile.cpp

Lines changed: 115 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,26 @@ void BlockCountInstrumentor::Instrument(BasicBlock* block, Schema& schema, uint8
596596
(entry.InstrumentationKind == ICorJitInfo::PgoInstrumentationKind::BasicBlockLongCount));
597597
uint8_t* addrOfCurrentExecutionCount = entry.Offset + profileMemory;
598598

599+
#ifdef DEBUG
600+
if (JitConfig.JitPropagateSynthesizedCountsToProfileData() > 0)
601+
{
602+
// Write the current synthesized count as the profile data
603+
//
604+
weight_t blockWeight = block->bbWeight;
605+
606+
if (entry.InstrumentationKind == ICorJitInfo::PgoInstrumentationKind::EdgeIntCount)
607+
{
608+
*((uint32_t*)addrOfCurrentExecutionCount) = (uint32_t)blockWeight;
609+
}
610+
else
611+
{
612+
*((uint64_t*)addrOfCurrentExecutionCount) = (uint64_t)blockWeight;
613+
}
614+
615+
return;
616+
}
617+
#endif
618+
599619
var_types typ =
600620
entry.InstrumentationKind == ICorJitInfo::PgoInstrumentationKind::BasicBlockIntCount ? TYP_INT : TYP_LONG;
601621

@@ -1742,6 +1762,32 @@ void EfficientEdgeCountInstrumentor::Instrument(BasicBlock* block, Schema& schem
17421762

17431763
uint8_t* addrOfCurrentExecutionCount = profileMemory + entry.Offset;
17441764

1765+
#ifdef DEBUG
1766+
if (JitConfig.JitPropagateSynthesizedCountsToProfileData() > 0)
1767+
{
1768+
// Write the current synthesized count as the profile data
1769+
//
1770+
// Todo: handle pseudo edges!
1771+
FlowEdge* const edge = m_comp->fgGetPredForBlock(source, target);
1772+
1773+
if (edge != nullptr)
1774+
{
1775+
weight_t edgeWeight = edge->getLikelyWeight();
1776+
1777+
if (entry.InstrumentationKind == ICorJitInfo::PgoInstrumentationKind::EdgeIntCount)
1778+
{
1779+
*((uint32_t*)addrOfCurrentExecutionCount) = (uint32_t)edgeWeight;
1780+
}
1781+
else
1782+
{
1783+
*((uint64_t*)addrOfCurrentExecutionCount) = (uint64_t)edgeWeight;
1784+
}
1785+
}
1786+
1787+
return;
1788+
}
1789+
#endif
1790+
17451791
// Determine where to place the probe.
17461792
//
17471793
BasicBlock* instrumentedBlock = nullptr;
@@ -2428,7 +2474,7 @@ PhaseStatus Compiler::fgIncorporateProfileData()
24282474
}
24292475

24302476
#ifdef DEBUG
2431-
// Optionally just run synthesis
2477+
// Optionally run synthesis
24322478
//
24332479
if ((JitConfig.JitSynthesizeCounts() > 0) && !compIsForInlining())
24342480
{
@@ -2439,6 +2485,17 @@ PhaseStatus Compiler::fgIncorporateProfileData()
24392485
return PhaseStatus::MODIFIED_EVERYTHING;
24402486
}
24412487
}
2488+
2489+
// Or run synthesis and save the data out as the actual profile data
2490+
//
2491+
if (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_BBINSTR) &&
2492+
(JitConfig.JitPropagateSynthesizedCountsToProfileData() > 0) && !compIsForInlining())
2493+
{
2494+
JITDUMP("Synthesizing profile data and writing it out as the actual profile data\n");
2495+
ProfileSynthesis::Run(this, ProfileSynthesisOption::AssignLikelihoods);
2496+
fgPgoHaveWeights = false;
2497+
return PhaseStatus::MODIFIED_EVERYTHING;
2498+
}
24422499
#endif
24432500

24442501
// Do we have profile data?
@@ -4845,15 +4902,8 @@ bool Compiler::fgProfileWeightsConsistent(weight_t weight1, weight_t weight2)
48454902
// (or nearly so)
48464903
//
48474904
// Notes:
4848-
// For each profiled block, check that the flow of counts into
4849-
// the block matches the flow of counts out of the block.
4850-
//
4851-
// We ignore EH flow as we don't have explicit edges and generally
4852-
// we expect EH edge counts to be small, so errors from ignoring
4853-
// them should be rare.
4854-
//
4855-
// There's no point checking until we've built pred lists, as
4856-
// we can't easily reason about consistency without them.
4905+
// Does nothing, if profile checks are disabled, or there are
4906+
// no profile weights or pred lists.
48574907
//
48584908
void Compiler::fgDebugCheckProfileWeights()
48594909
{
@@ -4865,13 +4915,37 @@ void Compiler::fgDebugCheckProfileWeights()
48654915
return;
48664916
}
48674917

4918+
fgDebugCheckProfileWeights((ProfileChecks)JitConfig.JitProfileChecks());
4919+
}
4920+
4921+
//------------------------------------------------------------------------
4922+
// fgDebugCheckProfileWeights: verify profile weights are self-consistent
4923+
// (or nearly so)
4924+
//
4925+
// Arguments:
4926+
// checks - checker options
4927+
//
4928+
// Notes:
4929+
// For each profiled block, check that the flow of counts into
4930+
// the block matches the flow of counts out of the block.
4931+
//
4932+
// We ignore EH flow as we don't have explicit edges and generally
4933+
// we expect EH edge counts to be small, so errors from ignoring
4934+
// them should be rare.
4935+
//
4936+
// There's no point checking until we've built pred lists, as
4937+
// we can't easily reason about consistency without them.
4938+
//
4939+
void Compiler::fgDebugCheckProfileWeights(ProfileChecks checks)
4940+
{
48684941
// We can check classic (min/max, late computed) weights
48694942
// and/or
48704943
// new likelyhood based weights.
48714944
//
4872-
const bool verifyClassicWeights = fgEdgeWeightsComputed && (JitConfig.JitProfileChecks() & 0x1) == 0x1;
4873-
const bool verifyLikelyWeights = (JitConfig.JitProfileChecks() & 0x2) == 0x2;
4874-
const bool assertOnFailure = (JitConfig.JitProfileChecks() & 0x4) == 0x4;
4945+
const bool verifyClassicWeights = fgEdgeWeightsComputed && hasFlag(checks, ProfileChecks::CHECK_CLASSIC);
4946+
const bool verifyLikelyWeights = hasFlag(checks, ProfileChecks::CHECK_LIKELY);
4947+
const bool assertOnFailure = hasFlag(checks, ProfileChecks::RAISE_ASSERT);
4948+
const bool checkAllBlocks = hasFlag(checks, ProfileChecks::CHECK_ALL_BLOCKS);
48754949

48764950
if (!(verifyClassicWeights || verifyLikelyWeights))
48774951
{
@@ -4891,7 +4965,7 @@ void Compiler::fgDebugCheckProfileWeights()
48914965
//
48924966
for (BasicBlock* const block : Blocks())
48934967
{
4894-
if (!block->hasProfileWeight())
4968+
if (!block->hasProfileWeight() && !checkAllBlocks)
48954969
{
48964970
unprofiledBlocks++;
48974971
continue;
@@ -4929,8 +5003,11 @@ void Compiler::fgDebugCheckProfileWeights()
49295003
//
49305004
if (block->KindIs(BBJ_RETURN, BBJ_THROW))
49315005
{
4932-
exitWeight += blockWeight;
4933-
exitProfiled = !opts.IsOSR();
5006+
if (BasicBlock::sameHndRegion(block, fgFirstBB))
5007+
{
5008+
exitWeight += blockWeight;
5009+
exitProfiled = !opts.IsOSR();
5010+
}
49345011
verifyOutgoing = false;
49355012
}
49365013

@@ -4969,12 +5046,12 @@ void Compiler::fgDebugCheckProfileWeights()
49695046

49705047
if (verifyIncoming)
49715048
{
4972-
incomingConsistent = fgDebugCheckIncomingProfileData(block);
5049+
incomingConsistent = fgDebugCheckIncomingProfileData(block, checks);
49735050
}
49745051

49755052
if (verifyOutgoing)
49765053
{
4977-
outgoingConsistent = fgDebugCheckOutgoingProfileData(block);
5054+
outgoingConsistent = fgDebugCheckOutgoingProfileData(block, checks);
49785055
}
49795056

49805057
if (!incomingConsistent || !outgoingConsistent)
@@ -4987,7 +5064,13 @@ void Compiler::fgDebugCheckProfileWeights()
49875064
//
49885065
if (entryProfiled && exitProfiled)
49895066
{
4990-
if (!fgProfileWeightsConsistent(entryWeight, exitWeight))
5067+
// Note these may not agree, if fgEntryBB is a loop header.
5068+
//
5069+
if (fgFirstBB->bbRefs > 1)
5070+
{
5071+
JITDUMP(" Method entry " FMT_BB " is loop head, can't check entry/exit balance\n");
5072+
}
5073+
else if (!fgProfileWeightsConsistent(entryWeight, exitWeight))
49915074
{
49925075
problemBlocks++;
49935076
JITDUMP(" Method entry " FMT_WT " method exit " FMT_WT " weight mismatch\n", entryWeight, exitWeight);
@@ -5025,18 +5108,19 @@ void Compiler::fgDebugCheckProfileWeights()
50255108
// block matches the profile weight of the block.
50265109
//
50275110
// Arguments:
5028-
// block - block to check
5111+
// block - block to check
5112+
// checks - checker options
50295113
//
50305114
// Returns:
50315115
// true if counts consistent or checking disabled, false otherwise.
50325116
//
50335117
// Notes:
50345118
// Only useful to call on blocks with predecessors.
50355119
//
5036-
bool Compiler::fgDebugCheckIncomingProfileData(BasicBlock* block)
5120+
bool Compiler::fgDebugCheckIncomingProfileData(BasicBlock* block, ProfileChecks checks)
50375121
{
5038-
const bool verifyClassicWeights = fgEdgeWeightsComputed && (JitConfig.JitProfileChecks() & 0x1) == 0x1;
5039-
const bool verifyLikelyWeights = (JitConfig.JitProfileChecks() & 0x2) == 0x2;
5122+
const bool verifyClassicWeights = fgEdgeWeightsComputed && hasFlag(checks, ProfileChecks::CHECK_CLASSIC);
5123+
const bool verifyLikelyWeights = hasFlag(checks, ProfileChecks::CHECK_LIKELY);
50405124

50415125
if (!(verifyClassicWeights || verifyLikelyWeights))
50425126
{
@@ -5056,7 +5140,10 @@ bool Compiler::fgDebugCheckIncomingProfileData(BasicBlock* block)
50565140
incomingWeightMax += predEdge->edgeWeightMax();
50575141
if (predEdge->hasLikelihood())
50585142
{
5059-
incomingLikelyWeight += predEdge->getLikelyWeight();
5143+
if (BasicBlock::sameHndRegion(block, predEdge->getSourceBlock()))
5144+
{
5145+
incomingLikelyWeight += predEdge->getLikelyWeight();
5146+
}
50605147
}
50615148
else
50625149
{
@@ -5122,17 +5209,18 @@ bool Compiler::fgDebugCheckIncomingProfileData(BasicBlock* block)
51225209
//
51235210
// Arguments:
51245211
// block - block to check
5212+
// checks - checker options
51255213
//
51265214
// Returns:
51275215
// true if counts consistent or checking disabled, false otherwise.
51285216
//
51295217
// Notes:
51305218
// Only useful to call on blocks with successors.
51315219
//
5132-
bool Compiler::fgDebugCheckOutgoingProfileData(BasicBlock* block)
5220+
bool Compiler::fgDebugCheckOutgoingProfileData(BasicBlock* block, ProfileChecks checks)
51335221
{
5134-
const bool verifyClassicWeights = fgEdgeWeightsComputed && (JitConfig.JitProfileChecks() & 0x1) == 0x1;
5135-
const bool verifyLikelyWeights = (JitConfig.JitProfileChecks() & 0x2) == 0x2;
5222+
const bool verifyClassicWeights = fgEdgeWeightsComputed && hasFlag(checks, ProfileChecks::CHECK_CLASSIC);
5223+
const bool verifyLikelyWeights = hasFlag(checks, ProfileChecks::CHECK_LIKELY);
51365224

51375225
if (!(verifyClassicWeights || verifyLikelyWeights))
51385226
{

0 commit comments

Comments
 (0)