diff --git a/eng/pipelines/common/templates/runtimes/run-test-job.yml b/eng/pipelines/common/templates/runtimes/run-test-job.yml index df3228f2764a86..a2e13dca489deb 100644 --- a/eng/pipelines/common/templates/runtimes/run-test-job.yml +++ b/eng/pipelines/common/templates/runtimes/run-test-job.yml @@ -585,7 +585,7 @@ jobs: - jitobjectstackallocation - jitphysicalpromotion_only - jitphysicalpromotion_full - + - jitcrossblocklocalassertionprop ${{ if in(parameters.testGroup, 'jit-cfg') }}: scenarios: - jitcfg diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index a633f7f962f117..0aa32b4767a80d 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -541,36 +541,83 @@ void Compiler::optAssertionTraitsInit(AssertionIndex assertionCount) void Compiler::optAssertionInit(bool isLocalProp) { - // Use a function countFunc to determine a proper maximum assertion count for the - // method being compiled. The function is linear to the IL size for small and - // moderate methods. For large methods, considering throughput impact, we track no - // more than 64 assertions. - // Note this tracks at most only 256 assertions. - static const AssertionIndex countFunc[] = {64, 128, 256, 64}; - static const unsigned lowerBound = 0; - static const unsigned upperBound = ArrLen(countFunc) - 1; - const unsigned codeSize = info.compILCodeSize / 512; - optMaxAssertionCount = countFunc[isLocalProp ? lowerBound : min(upperBound, codeSize)]; - - optLocalAssertionProp = isLocalProp; - optAssertionTabPrivate = new (this, CMK_AssertionProp) AssertionDsc[optMaxAssertionCount]; assert(NO_ASSERTION_INDEX == 0); + const unsigned maxTrackedLocals = (unsigned)JitConfig.JitMaxLocalsToTrack(); - if (!isLocalProp) + // We initialize differently for local prop / global prop + // + if (isLocalProp) { + optLocalAssertionProp = true; + optCrossBlockLocalAssertionProp = true; + + // Disable via config + // + if (JitConfig.JitEnableCrossBlockLocalAssertionProp() == 0) + { + JITDUMP("Disabling cross-block assertion prop by config setting\n"); + optCrossBlockLocalAssertionProp = false; + } + + // Disable if too many locals + // + // The typical number of local assertions is roughly proportional + // to the number of locals. So when we have huge numbers of locals, + // just do within-block local assertion prop. + // + if (lvaCount > maxTrackedLocals) + { + JITDUMP("Disabling cross-block assertion prop: too many locals\n"); + optCrossBlockLocalAssertionProp = false; + } + + if (optCrossBlockLocalAssertionProp) + { + // We may need a fairly large table. + // Allow for roughly one assertion per local, up to the tracked limit. + // (empirical studies show about 0.6 asserions/local) + // + optMaxAssertionCount = (AssertionIndex)min(maxTrackedLocals, ((lvaCount / 64) + 1) * 64); + } + else + { + // The assertion table will be reset for each block, so it can be smaller. + // + optMaxAssertionCount = 64; + } + + // Local assertion prop keeps mappings from each local var to the assertions about that var. + // + optAssertionDep = + new (this, CMK_AssertionProp) JitExpandArray(getAllocator(CMK_AssertionProp), max(1, lvaCount)); + } + else + { + // General assertion prop. + // + optLocalAssertionProp = false; + + // Use a function countFunc to determine a proper maximum assertion count for the + // method being compiled. The function is linear to the IL size for small and + // moderate methods. For large methods, considering throughput impact, we track no + // more than 64 assertions. + // Note this tracks at most only 256 assertions. + // + static const AssertionIndex countFunc[] = {64, 128, 256, 64}; + static const unsigned upperBound = ArrLen(countFunc) - 1; + const unsigned codeSize = info.compILCodeSize / 512; + optMaxAssertionCount = countFunc[min(upperBound, codeSize)]; + optValueNumToAsserts = new (getAllocator(CMK_AssertionProp)) ValueNumToAssertsMap(getAllocator(CMK_AssertionProp)); optComplementaryAssertionMap = new (this, CMK_AssertionProp) AssertionIndex[optMaxAssertionCount + 1](); // zero-inited (NO_ASSERTION_INDEX) } - if (optAssertionDep == nullptr) - { - optAssertionDep = - new (this, CMK_AssertionProp) JitExpandArray(getAllocator(CMK_AssertionProp), max(1, lvaCount)); - } + optAssertionTabPrivate = new (this, CMK_AssertionProp) AssertionDsc[optMaxAssertionCount]; optAssertionTraitsInit(optMaxAssertionCount); + optAssertionCount = 0; optAssertionOverflow = 0; optAssertionPropagated = false; @@ -4379,12 +4426,11 @@ AssertionIndex Compiler::optAssertionIsNonNullInternal(GenTree* op, // Find live assertions related to lclNum // unsigned const lclNum = op->AsLclVarCommon()->GetLclNum(); - ASSERT_TP apDependent = GetAssertionDep(lclNum); - BitVecOps::IntersectionD(apTraits, apDependent, apLocal); + ASSERT_TP apDependent = BitVecOps::Intersection(apTraits, GetAssertionDep(lclNum), assertions); // Scan those looking for a suitable assertion // - BitVecOps::Iter iter(apTraits, assertions); + BitVecOps::Iter iter(apTraits, apDependent); unsigned index = 0; while (iter.NextElem(&index)) { diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 77d2e0d35f3837..80740ebb53bb95 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -7640,6 +7640,7 @@ class Compiler AssertionDsc* optAssertionTabPrivate; // table that holds info about value assignments AssertionIndex optAssertionCount; // total number of assertions in the assertion table AssertionIndex optMaxAssertionCount; + bool optCrossBlockLocalAssertionProp; unsigned optAssertionOverflow; bool optCanPropLclVar; bool optCanPropEqual; diff --git a/src/coreclr/jit/jitconfigvalues.h b/src/coreclr/jit/jitconfigvalues.h index f6460feddec126..65afd348819e11 100644 --- a/src/coreclr/jit/jitconfigvalues.h +++ b/src/coreclr/jit/jitconfigvalues.h @@ -653,6 +653,9 @@ CONFIG_INTEGER(JitEnableHeadTailMerge, W("JitEnableHeadTailMerge"), 1) // Enable physical promotion CONFIG_INTEGER(JitEnablePhysicalPromotion, W("JitEnablePhysicalPromotion"), 1) +// Enable cross-block local assertion prop +CONFIG_INTEGER(JitEnableCrossBlockLocalAssertionProp, W("JitEnableCrossBlockLocalAssertionProp"), 0) + #if defined(DEBUG) // JitFunctionFile: Name of a file that contains a list of functions. If the currently compiled function is in the // file, certain other JIT config variables will be active. If the currently compiled function is not in the file, diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index ec70cb0025efac..275be7c9b17c23 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -13780,10 +13780,73 @@ void Compiler::fgMorphBlock(BasicBlock* block) if (optLocalAssertionProp) { - // For now, each block starts with an empty table, and no available assertions - // - optAssertionReset(0); - apLocal = BitVecOps::MakeEmpty(apTraits); + if (!optCrossBlockLocalAssertionProp) + { + // Each block starts with an empty table, and no available assertions + // + optAssertionReset(0); + apLocal = BitVecOps::MakeEmpty(apTraits); + } + else + { + // Determine if this block can leverage assertions from its pred blocks. + // + // Some blocks are ineligible. + // + bool canUsePredAssertions = ((block->bbFlags & BBF_CAN_ADD_PRED) == 0) && !bbIsHandlerBeg(block); + + // Validate all preds have valid info + // + if (!canUsePredAssertions) + { + JITDUMP(FMT_BB " ineligible for cross-block\n", block->bbNum); + } + else + { + bool hasPredAssertions = false; + + for (BasicBlock* const pred : block->PredBlocks()) + { + // A smaller pred postorder number means the pred appears later in the postorder. + // An equal number means pred == block (block is a self-loop). + // Either way the assertion info is not available, and we must assume the worst. + // + if (pred->bbPostorderNum <= block->bbPostorderNum) + { + JITDUMP(FMT_BB " pred " FMT_BB " not processed; clearing assertions in\n", block->bbNum, + pred->bbNum); + break; + } + + // Yes, pred assertions are available. If this is the first pred, copy. + // If this is a subsequent pred, intersect. + // + if (!hasPredAssertions) + { + apLocal = BitVecOps::MakeCopy(apTraits, pred->bbAssertionOut); + hasPredAssertions = true; + } + else + { + BitVecOps::IntersectionD(apTraits, apLocal, pred->bbAssertionOut); + } + } + + if (!hasPredAssertions) + { + // Either no preds, or some preds w/o assertions. + // + canUsePredAssertions = false; + } + } + + if (!canUsePredAssertions) + { + apLocal = BitVecOps::MakeEmpty(apTraits); + } + + JITDUMPEXEC(optDumpAssertionIndices("Assertions in: ", apLocal)); + } } // Make the current basic block address available globally. @@ -13801,6 +13864,14 @@ void Compiler::fgMorphBlock(BasicBlock* block) } } + // Publish the live out state. + // + if (optCrossBlockLocalAssertionProp && (block->NumSucc() > 0)) + { + assert(optLocalAssertionProp); + block->bbAssertionOut = BitVecOps::MakeCopy(apTraits, apLocal); + } + compCurBB = nullptr; } @@ -13820,15 +13891,18 @@ PhaseStatus Compiler::fgMorphBlocks() // fgGlobalMorph = true; - // Local assertion prop is enabled if we are optimized - // - optLocalAssertionProp = opts.OptimizationEnabled(); - - if (optLocalAssertionProp) + if (opts.OptimizationEnabled()) + { + // Local assertion prop is enabled if we are optimizing. + // + optAssertionInit(/* isLocalProp*/ true); + } + else { - // Initialize for local assertion prop + // Not optimizing. No assertion prop. // - optAssertionInit(true); + optLocalAssertionProp = false; + optCrossBlockLocalAssertionProp = false; } if (!compEnregLocals()) @@ -13855,10 +13929,6 @@ PhaseStatus Compiler::fgMorphBlocks() { // If we aren't optimizing, we just morph in normal bbNext order. // - // Note morph can add blocks downstream from the current block, - // and alter (but not null out) the current block's bbNext; - // this iterator ensures they all get visited. - // for (BasicBlock* block : Blocks()) { fgMorphBlock(block); diff --git a/src/tests/Common/testenvironment.proj b/src/tests/Common/testenvironment.proj index bf70dac353e882..1613582faf6ee7 100644 --- a/src/tests/Common/testenvironment.proj +++ b/src/tests/Common/testenvironment.proj @@ -80,6 +80,7 @@ RunningIlasmRoundTrip; DOTNET_JitSynthesizeCounts; DOTNET_JitCheckSynthesizedCounts + DOTNET_JitDoCrossBlockLocalAssertionProp @@ -220,6 +221,7 @@ +