Skip to content

Commit 5127e07

Browse files
authored
JIT: cross-block local assertion prop in morph (#94363)
During global morph, allow assertions to propagate to a block from the block's predecessors. Handle special cases where we can't allow this to happen: * block has preds that have not yet been morphed * block has no preds * block is specially flagged as one that might gain new preds during morph * block is an EH handler entry Contributes to #93246. When enabled, size the assertion table based on the number of locals, up to the tracked limit. Disabled by default; use 'DOTNET_JitEnableCrossBlockLocalAssertionProp=1` to enable.
1 parent b60f394 commit 5127e07

File tree

6 files changed

+160
-38
lines changed

6 files changed

+160
-38
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ jobs:
585585
- jitobjectstackallocation
586586
- jitphysicalpromotion_only
587587
- jitphysicalpromotion_full
588-
588+
- jitcrossblocklocalassertionprop
589589
${{ if in(parameters.testGroup, 'jit-cfg') }}:
590590
scenarios:
591591
- jitcfg

src/coreclr/jit/assertionprop.cpp

+68-22
Original file line numberDiff line numberDiff line change
@@ -541,36 +541,83 @@ void Compiler::optAssertionTraitsInit(AssertionIndex assertionCount)
541541

542542
void Compiler::optAssertionInit(bool isLocalProp)
543543
{
544-
// Use a function countFunc to determine a proper maximum assertion count for the
545-
// method being compiled. The function is linear to the IL size for small and
546-
// moderate methods. For large methods, considering throughput impact, we track no
547-
// more than 64 assertions.
548-
// Note this tracks at most only 256 assertions.
549-
static const AssertionIndex countFunc[] = {64, 128, 256, 64};
550-
static const unsigned lowerBound = 0;
551-
static const unsigned upperBound = ArrLen(countFunc) - 1;
552-
const unsigned codeSize = info.compILCodeSize / 512;
553-
optMaxAssertionCount = countFunc[isLocalProp ? lowerBound : min(upperBound, codeSize)];
554-
555-
optLocalAssertionProp = isLocalProp;
556-
optAssertionTabPrivate = new (this, CMK_AssertionProp) AssertionDsc[optMaxAssertionCount];
557544
assert(NO_ASSERTION_INDEX == 0);
545+
const unsigned maxTrackedLocals = (unsigned)JitConfig.JitMaxLocalsToTrack();
558546

559-
if (!isLocalProp)
547+
// We initialize differently for local prop / global prop
548+
//
549+
if (isLocalProp)
560550
{
551+
optLocalAssertionProp = true;
552+
optCrossBlockLocalAssertionProp = true;
553+
554+
// Disable via config
555+
//
556+
if (JitConfig.JitEnableCrossBlockLocalAssertionProp() == 0)
557+
{
558+
JITDUMP("Disabling cross-block assertion prop by config setting\n");
559+
optCrossBlockLocalAssertionProp = false;
560+
}
561+
562+
// Disable if too many locals
563+
//
564+
// The typical number of local assertions is roughly proportional
565+
// to the number of locals. So when we have huge numbers of locals,
566+
// just do within-block local assertion prop.
567+
//
568+
if (lvaCount > maxTrackedLocals)
569+
{
570+
JITDUMP("Disabling cross-block assertion prop: too many locals\n");
571+
optCrossBlockLocalAssertionProp = false;
572+
}
573+
574+
if (optCrossBlockLocalAssertionProp)
575+
{
576+
// We may need a fairly large table.
577+
// Allow for roughly one assertion per local, up to the tracked limit.
578+
// (empirical studies show about 0.6 asserions/local)
579+
//
580+
optMaxAssertionCount = (AssertionIndex)min(maxTrackedLocals, ((lvaCount / 64) + 1) * 64);
581+
}
582+
else
583+
{
584+
// The assertion table will be reset for each block, so it can be smaller.
585+
//
586+
optMaxAssertionCount = 64;
587+
}
588+
589+
// Local assertion prop keeps mappings from each local var to the assertions about that var.
590+
//
591+
optAssertionDep =
592+
new (this, CMK_AssertionProp) JitExpandArray<ASSERT_TP>(getAllocator(CMK_AssertionProp), max(1, lvaCount));
593+
}
594+
else
595+
{
596+
// General assertion prop.
597+
//
598+
optLocalAssertionProp = false;
599+
600+
// Use a function countFunc to determine a proper maximum assertion count for the
601+
// method being compiled. The function is linear to the IL size for small and
602+
// moderate methods. For large methods, considering throughput impact, we track no
603+
// more than 64 assertions.
604+
// Note this tracks at most only 256 assertions.
605+
//
606+
static const AssertionIndex countFunc[] = {64, 128, 256, 64};
607+
static const unsigned upperBound = ArrLen(countFunc) - 1;
608+
const unsigned codeSize = info.compILCodeSize / 512;
609+
optMaxAssertionCount = countFunc[min(upperBound, codeSize)];
610+
561611
optValueNumToAsserts =
562612
new (getAllocator(CMK_AssertionProp)) ValueNumToAssertsMap(getAllocator(CMK_AssertionProp));
563613
optComplementaryAssertionMap = new (this, CMK_AssertionProp)
564614
AssertionIndex[optMaxAssertionCount + 1](); // zero-inited (NO_ASSERTION_INDEX)
565615
}
566616

567-
if (optAssertionDep == nullptr)
568-
{
569-
optAssertionDep =
570-
new (this, CMK_AssertionProp) JitExpandArray<ASSERT_TP>(getAllocator(CMK_AssertionProp), max(1, lvaCount));
571-
}
617+
optAssertionTabPrivate = new (this, CMK_AssertionProp) AssertionDsc[optMaxAssertionCount];
572618

573619
optAssertionTraitsInit(optMaxAssertionCount);
620+
574621
optAssertionCount = 0;
575622
optAssertionOverflow = 0;
576623
optAssertionPropagated = false;
@@ -4457,12 +4504,11 @@ AssertionIndex Compiler::optAssertionIsNonNullInternal(GenTree* op,
44574504
// Find live assertions related to lclNum
44584505
//
44594506
unsigned const lclNum = op->AsLclVarCommon()->GetLclNum();
4460-
ASSERT_TP apDependent = GetAssertionDep(lclNum);
4461-
BitVecOps::IntersectionD(apTraits, apDependent, apLocal);
4507+
ASSERT_TP apDependent = BitVecOps::Intersection(apTraits, GetAssertionDep(lclNum), assertions);
44624508

44634509
// Scan those looking for a suitable assertion
44644510
//
4465-
BitVecOps::Iter iter(apTraits, assertions);
4511+
BitVecOps::Iter iter(apTraits, apDependent);
44664512
unsigned index = 0;
44674513
while (iter.NextElem(&index))
44684514
{

src/coreclr/jit/compiler.h

+1
Original file line numberDiff line numberDiff line change
@@ -7640,6 +7640,7 @@ class Compiler
76407640
AssertionDsc* optAssertionTabPrivate; // table that holds info about value assignments
76417641
AssertionIndex optAssertionCount; // total number of assertions in the assertion table
76427642
AssertionIndex optMaxAssertionCount;
7643+
bool optCrossBlockLocalAssertionProp;
76437644
unsigned optAssertionOverflow;
76447645
bool optCanPropLclVar;
76457646
bool optCanPropEqual;

src/coreclr/jit/jitconfigvalues.h

+3
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,9 @@ CONFIG_INTEGER(JitEnableHeadTailMerge, W("JitEnableHeadTailMerge"), 1)
653653
// Enable physical promotion
654654
CONFIG_INTEGER(JitEnablePhysicalPromotion, W("JitEnablePhysicalPromotion"), 1)
655655

656+
// Enable cross-block local assertion prop
657+
CONFIG_INTEGER(JitEnableCrossBlockLocalAssertionProp, W("JitEnableCrossBlockLocalAssertionProp"), 0)
658+
656659
#if defined(DEBUG)
657660
// JitFunctionFile: Name of a file that contains a list of functions. If the currently compiled function is in the
658661
// file, certain other JIT config variables will be active. If the currently compiled function is not in the file,

src/coreclr/jit/morph.cpp

+85-15
Original file line numberDiff line numberDiff line change
@@ -13779,10 +13779,73 @@ void Compiler::fgMorphBlock(BasicBlock* block)
1377913779

1378013780
if (optLocalAssertionProp)
1378113781
{
13782-
// For now, each block starts with an empty table, and no available assertions
13783-
//
13784-
optAssertionReset(0);
13785-
apLocal = BitVecOps::MakeEmpty(apTraits);
13782+
if (!optCrossBlockLocalAssertionProp)
13783+
{
13784+
// Each block starts with an empty table, and no available assertions
13785+
//
13786+
optAssertionReset(0);
13787+
apLocal = BitVecOps::MakeEmpty(apTraits);
13788+
}
13789+
else
13790+
{
13791+
// Determine if this block can leverage assertions from its pred blocks.
13792+
//
13793+
// Some blocks are ineligible.
13794+
//
13795+
bool canUsePredAssertions = ((block->bbFlags & BBF_CAN_ADD_PRED) == 0) && !bbIsHandlerBeg(block);
13796+
13797+
// Validate all preds have valid info
13798+
//
13799+
if (!canUsePredAssertions)
13800+
{
13801+
JITDUMP(FMT_BB " ineligible for cross-block\n", block->bbNum);
13802+
}
13803+
else
13804+
{
13805+
bool hasPredAssertions = false;
13806+
13807+
for (BasicBlock* const pred : block->PredBlocks())
13808+
{
13809+
// A smaller pred postorder number means the pred appears later in the postorder.
13810+
// An equal number means pred == block (block is a self-loop).
13811+
// Either way the assertion info is not available, and we must assume the worst.
13812+
//
13813+
if (pred->bbPostorderNum <= block->bbPostorderNum)
13814+
{
13815+
JITDUMP(FMT_BB " pred " FMT_BB " not processed; clearing assertions in\n", block->bbNum,
13816+
pred->bbNum);
13817+
break;
13818+
}
13819+
13820+
// Yes, pred assertions are available. If this is the first pred, copy.
13821+
// If this is a subsequent pred, intersect.
13822+
//
13823+
if (!hasPredAssertions)
13824+
{
13825+
apLocal = BitVecOps::MakeCopy(apTraits, pred->bbAssertionOut);
13826+
hasPredAssertions = true;
13827+
}
13828+
else
13829+
{
13830+
BitVecOps::IntersectionD(apTraits, apLocal, pred->bbAssertionOut);
13831+
}
13832+
}
13833+
13834+
if (!hasPredAssertions)
13835+
{
13836+
// Either no preds, or some preds w/o assertions.
13837+
//
13838+
canUsePredAssertions = false;
13839+
}
13840+
}
13841+
13842+
if (!canUsePredAssertions)
13843+
{
13844+
apLocal = BitVecOps::MakeEmpty(apTraits);
13845+
}
13846+
13847+
JITDUMPEXEC(optDumpAssertionIndices("Assertions in: ", apLocal));
13848+
}
1378613849
}
1378713850

1378813851
// Make the current basic block address available globally.
@@ -13800,6 +13863,14 @@ void Compiler::fgMorphBlock(BasicBlock* block)
1380013863
}
1380113864
}
1380213865

13866+
// Publish the live out state.
13867+
//
13868+
if (optCrossBlockLocalAssertionProp && (block->NumSucc() > 0))
13869+
{
13870+
assert(optLocalAssertionProp);
13871+
block->bbAssertionOut = BitVecOps::MakeCopy(apTraits, apLocal);
13872+
}
13873+
1380313874
compCurBB = nullptr;
1380413875
}
1380513876

@@ -13819,15 +13890,18 @@ PhaseStatus Compiler::fgMorphBlocks()
1381913890
//
1382013891
fgGlobalMorph = true;
1382113892

13822-
// Local assertion prop is enabled if we are optimized
13823-
//
13824-
optLocalAssertionProp = opts.OptimizationEnabled();
13825-
13826-
if (optLocalAssertionProp)
13893+
if (opts.OptimizationEnabled())
13894+
{
13895+
// Local assertion prop is enabled if we are optimizing.
13896+
//
13897+
optAssertionInit(/* isLocalProp*/ true);
13898+
}
13899+
else
1382713900
{
13828-
// Initialize for local assertion prop
13901+
// Not optimizing. No assertion prop.
1382913902
//
13830-
optAssertionInit(true);
13903+
optLocalAssertionProp = false;
13904+
optCrossBlockLocalAssertionProp = false;
1383113905
}
1383213906

1383313907
if (!compEnregLocals())
@@ -13854,10 +13928,6 @@ PhaseStatus Compiler::fgMorphBlocks()
1385413928
{
1385513929
// If we aren't optimizing, we just morph in normal bbNext order.
1385613930
//
13857-
// Note morph can add blocks downstream from the current block,
13858-
// and alter (but not null out) the current block's bbNext;
13859-
// this iterator ensures they all get visited.
13860-
//
1386113931
for (BasicBlock* block : Blocks())
1386213932
{
1386313933
fgMorphBlock(block);

src/tests/Common/testenvironment.proj

+2
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
RunningIlasmRoundTrip;
8181
DOTNET_JitSynthesizeCounts;
8282
DOTNET_JitCheckSynthesizedCounts
83+
DOTNET_JitDoCrossBlockLocalAssertionProp
8384
</DOTNETVariables>
8485
</PropertyGroup>
8586
<ItemGroup>
@@ -220,6 +221,7 @@
220221
<TestEnvironment Include="jitobjectstackallocation" JitObjectStackAllocation="1" TieredCompilation="0" />
221222
<TestEnvironment Include="jitphysicalpromotion_only" JitStressModeNames="STRESS_NO_OLD_PROMOTION" TieredCompilation="0" />
222223
<TestEnvironment Include="jitphysicalpromotion_full" JitStressModeNames="STRESS_PHYSICAL_PROMOTION_COST STRESS_NO_OLD_PROMOTION" TieredCompilation="0" />
224+
<TestEnvironment Include="jitcrossblocklocalassertionprop" JitEnableCrossBlockLocalAssertionProp="1" TieredCompilation="0" />
223225
<TestEnvironment Include="jitcfg" JitForceControlFlowGuard="1" />
224226
<TestEnvironment Include="jitcfg_dispatcher_always" JitForceControlFlowGuard="1" JitCFGUseDispatcher="1" />
225227
<TestEnvironment Include="jitcfg_dispatcher_never" JitForceControlFlowGuard="1" JitCFGUseDispatcher="0" />

0 commit comments

Comments
 (0)