diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index be3a8cc7af8a3f..40379581737f1e 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -959,8 +959,17 @@ void Compiler::optAssertionInit(bool isLocalProp) optLocalAssertionProp = isLocalProp; optAssertionTabPrivate = new (this, CMK_AssertionProp) AssertionDsc[optMaxAssertionCount]; + + for (int i = 0; i < optMaxAssertionCount; i++) + { + optAssertionTabPrivate[i].vnBased = !isLocalProp; + } + optComplementaryAssertionMap = new (this, CMK_AssertionProp) AssertionIndex[optMaxAssertionCount + 1](); // zero-inited (NO_ASSERTION_INDEX) + + optAssertionDscMap = new (getAllocator()) AssertionDscMap(getAllocator()); + assert(NO_ASSERTION_INDEX == 0); if (!isLocalProp) @@ -1338,7 +1347,7 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, assert(op1 != nullptr); assert(!helperCallArgs || (op2 != nullptr)); - AssertionDsc assertion = {OAK_INVALID}; + AssertionDsc assertion(optLocalAssertionProp); assert(assertion.assertionKind == OAK_INVALID); if (op1->OperIs(GT_BOUNDS_CHECK)) @@ -1957,6 +1966,7 @@ bool Compiler::optAssertionVnInvolvesNan(AssertionDsc* assertion) */ AssertionIndex Compiler::optAddAssertion(AssertionDsc* newAssertion) { + RECORD_ASSERTION_STATS(addAssertionCallCount++); noway_assert(newAssertion->assertionKind != OAK_INVALID); // Even though the propagation step takes care of NaN, just a check @@ -1967,21 +1977,74 @@ AssertionIndex Compiler::optAddAssertion(AssertionDsc* newAssertion) return NO_ASSERTION_INDEX; } + AssertionIndex fastAnswer = NO_ASSERTION_INDEX; + AssertionIndex slowAnswer = NO_ASSERTION_INDEX; +#ifdef DEBUG + bool found = false; + // Check if exists already, so we can skip adding new one. Search backwards. for (AssertionIndex index = optAssertionCount; index >= 1; index--) { AssertionDsc* curAssertion = optGetAssertion(index); - if (curAssertion->Equals(newAssertion, !optLocalAssertionProp)) + if (curAssertion->Equals(*newAssertion)) { - return index; + found = true; + slowAnswer = index; + RECORD_ASSERTION_STATS(addAssertionMatchCount++); + RECORD_ASSERTION_STATS(addAssertionIter += (optAssertionCount - index + 1)); + break; } } + if (!found) + { + RECORD_ASSERTION_STATS(addAssertionMissedCount++); + RECORD_ASSERTION_STATS(addAssertionMissedIter += optAssertionCount); + } +#endif + // Check if we are within max count. if (optAssertionCount >= optMaxAssertionCount) { - return NO_ASSERTION_INDEX; + // If we don't want to add more assertions, just see if `newAssertion` already + // exists, and return the corresponding assertionIndex. + if (optAssertionDscMap->Lookup(*newAssertion, &fastAnswer)) + { + assert(slowAnswer == fastAnswer); + return fastAnswer; + } + else + { +#ifdef DEBUG + if (found) + { + JITDUMP("HashCode= %u not found in map.\n", AssertionDscKeyFuncs::GetHashCode(*newAssertion)); + assert(false); + } +#endif + return NO_ASSERTION_INDEX; + } + } + + // Check if newAssertion already exists and return the corresponding assertionIndex + // Otherwise, set it in the map. + if (optAssertionDscMap->Set(*newAssertion, optAssertionCount + 1, AssertionDscMap::SetKind::SkipIfExist, + &fastAnswer)) + { + assert(slowAnswer == fastAnswer); + return fastAnswer; } +#ifdef DEBUG + else + { + if (found) + { + JITDUMP("HashCode=%u was not found in map and we added it.\n", + AssertionDscKeyFuncs::GetHashCode(*newAssertion)); + assert(false); + } + } +#endif optAssertionTabPrivate[optAssertionCount] = *newAssertion; optAssertionCount++; @@ -2221,7 +2284,8 @@ AssertionIndex Compiler::optAssertionGenCast(GenTreeCast* cast) return NO_ASSERTION_INDEX; } - AssertionDsc assertion = {OAK_SUBRANGE}; + AssertionDsc assertion(optLocalAssertionProp); + assertion.assertionKind = OAK_SUBRANGE; assertion.op1.kind = O1K_LCLVAR; assertion.op1.vn = vnStore->VNConservativeNormalValue(lclVar->gtVNPair); assertion.op1.lcl.lclNum = lclVar->GetLclNum(); @@ -2278,9 +2342,10 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) GenTree* op1 = relop->gtGetOp1(); GenTree* op2 = relop->gtGetOp2(); - ValueNum op1VN = vnStore->VNConservativeNormalValue(op1->gtVNPair); - ValueNum op2VN = vnStore->VNConservativeNormalValue(op2->gtVNPair); - ValueNum relopVN = vnStore->VNConservativeNormalValue(relop->gtVNPair); + ValueNum op1VN = vnStore->VNConservativeNormalValue(op1->gtVNPair); + ValueNum op2VN = vnStore->VNConservativeNormalValue(op2->gtVNPair); + ValueNum relopVN = vnStore->VNConservativeNormalValue(relop->gtVNPair); + AssertionDsc dsc(optLocalAssertionProp); bool hasTestAgainstZero = (relop->gtOper == GT_EQ || relop->gtOper == GT_NE) && (op2VN == vnStore->VNZeroForType(op2->TypeGet())); @@ -2291,7 +2356,6 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) // Assertion: "i < bnd +/- k == 0" if (hasTestAgainstZero && vnStore->IsVNCompareCheckedBoundArith(op1VN)) { - AssertionDsc dsc; dsc.assertionKind = relop->gtOper == GT_EQ ? OAK_EQUAL : OAK_NOT_EQUAL; dsc.op1.kind = O1K_BOUND_OPER_BND; dsc.op1.vn = op1VN; @@ -2308,7 +2372,6 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) // Assertion: "i < bnd +/- k != 0" else if (vnStore->IsVNCompareCheckedBoundArith(relopVN)) { - AssertionDsc dsc; dsc.assertionKind = OAK_NOT_EQUAL; dsc.op1.kind = O1K_BOUND_OPER_BND; dsc.op1.vn = relopVN; @@ -2325,7 +2388,6 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) // Assertion: "i < bnd == false" else if (hasTestAgainstZero && vnStore->IsVNCompareCheckedBound(op1VN)) { - AssertionDsc dsc; dsc.assertionKind = relop->gtOper == GT_EQ ? OAK_EQUAL : OAK_NOT_EQUAL; dsc.op1.kind = O1K_BOUND_LOOP_BND; dsc.op1.vn = op1VN; @@ -2342,7 +2404,6 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) // Assertion: "i < bnd != 0" else if (vnStore->IsVNCompareCheckedBound(relopVN)) { - AssertionDsc dsc; dsc.assertionKind = OAK_NOT_EQUAL; dsc.op1.kind = O1K_BOUND_LOOP_BND; dsc.op1.vn = relopVN; @@ -2362,7 +2423,6 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) assert((unsignedCompareBnd.cmpOper == VNF_LT_UN) || (unsignedCompareBnd.cmpOper == VNF_GE_UN)); assert(vnStore->IsVNCheckedBound(unsignedCompareBnd.vnBound)); - AssertionDsc dsc; dsc.assertionKind = OAK_NO_THROW; dsc.op1.kind = O1K_ARR_BND; dsc.op1.vn = relopVN; @@ -2385,7 +2445,6 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) // Assertion: "i < 100 == false" else if (hasTestAgainstZero && vnStore->IsVNConstantBound(op1VN)) { - AssertionDsc dsc; dsc.assertionKind = relop->gtOper == GT_EQ ? OAK_EQUAL : OAK_NOT_EQUAL; dsc.op1.kind = O1K_CONSTANT_LOOP_BND; dsc.op1.vn = op1VN; @@ -2402,7 +2461,6 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) // Assertion: "i < 100 != 0" else if (vnStore->IsVNConstantBound(relopVN)) { - AssertionDsc dsc; dsc.assertionKind = OAK_NOT_EQUAL; dsc.op1.kind = O1K_CONSTANT_LOOP_BND; dsc.op1.vn = relopVN; @@ -2416,7 +2474,6 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) } else if (vnStore->IsVNConstantBoundUnsigned(relopVN)) { - AssertionDsc dsc; dsc.assertionKind = OAK_NOT_EQUAL; dsc.op1.kind = O1K_CONSTANT_LOOP_BND_UN; dsc.op1.vn = relopVN; @@ -2498,7 +2555,7 @@ AssertionInfo Compiler::optAssertionGenJtrue(GenTree* tree) int con = vnStore->ConstantValue(op2VN); if (con >= 0) { - AssertionDsc dsc; + AssertionDsc dsc(optLocalAssertionProp); // For arr.Length != 0, we know that 0 is a valid index // For arr.Length == con, we know that con - 1 is the greatest valid index @@ -2800,7 +2857,7 @@ AssertionIndex Compiler::optFindComplementary(AssertionIndex assertIndex) { // Make sure assertion kinds are complementary and op1, op2 kinds match. AssertionDsc* curAssertion = optGetAssertion(index); - if (curAssertion->Complementary(inputAssertion, !optLocalAssertionProp)) + if (curAssertion->Complementary(*inputAssertion)) { optMapComplementary(assertIndex, index); return index; @@ -2831,6 +2888,8 @@ AssertionIndex Compiler::optAssertionIsSubrange(GenTree* tree, IntegralRange ran return NO_ASSERTION_INDEX; } + RECORD_ASSERTION_STATS(subRangeCallCount++); + for (AssertionIndex index = 1; index <= optAssertionCount; index++) { AssertionDsc* curAssertion = optGetAssertion(index); @@ -2850,11 +2909,17 @@ AssertionIndex Compiler::optAssertionIsSubrange(GenTree* tree, IntegralRange ran if (range.Contains(curAssertion->op2.u2)) { + RECORD_ASSERTION_STATS(subRangeMatchCount++); + RECORD_ASSERTION_STATS(subRangeIter += index); return index; } } } + RECORD_ASSERTION_STATS(subRangeMissedCount++); + RECORD_ASSERTION_STATS(subRangeMissedIter += + (optLocalAssertionProp ? optAssertionCount : (BitVecOps::Count(apTraits, assertions)))); + return NO_ASSERTION_INDEX; } @@ -2873,6 +2938,9 @@ AssertionIndex Compiler::optAssertionIsSubtype(GenTree* tree, GenTree* methodTab { return NO_ASSERTION_INDEX; } + + RECORD_ASSERTION_STATS(subTypeCallCount++); + for (AssertionIndex index = 1; index <= optAssertionCount; index++) { if (!optLocalAssertionProp && !BitVecOps::IsMember(apTraits, assertions, index - 1)) @@ -2916,9 +2984,15 @@ AssertionIndex Compiler::optAssertionIsSubtype(GenTree* tree, GenTree* methodTab if (curAssertion->op2.u1.iconVal == methodTableVal) { + RECORD_ASSERTION_STATS(subTypeMatchCount++); + RECORD_ASSERTION_STATS(subTypeIter += index); return index; } } + + RECORD_ASSERTION_STATS(subTypeMissedCount++); + RECORD_ASSERTION_STATS(subTypeMissedIter += + (optLocalAssertionProp ? optAssertionCount : (BitVecOps::Count(apTraits, assertions)))); return NO_ASSERTION_INDEX; } @@ -3597,10 +3671,15 @@ GenTree* Compiler::optAssertionProp_LclVar(ASSERT_VALARG_TP assertions, GenTreeL return nullptr; } + RECORD_ASSERTION_STATS(propLclVarCallCount++); + BitVecOps::Iter iter(apTraits, assertions); unsigned index = 0; + RECORD_ASSERTION_STATS(unsigned iterCount = 0); while (iter.NextElem(&index)) { + RECORD_ASSERTION_STATS(iterCount++); + AssertionIndex assertionIndex = GetAssertionIndex(index); if (assertionIndex > optAssertionCount) { @@ -3626,6 +3705,8 @@ GenTree* Compiler::optAssertionProp_LclVar(ASSERT_VALARG_TP assertions, GenTreeL GenTree* newTree = optCopyAssertionProp(curAssertion, tree, stmt DEBUGARG(assertionIndex)); if (newTree != nullptr) { + RECORD_ASSERTION_STATS(propLclVarMatchCount++); + RECORD_ASSERTION_STATS(propLclVarIter += iterCount); return newTree; } } @@ -3634,7 +3715,6 @@ GenTree* Compiler::optAssertionProp_LclVar(ASSERT_VALARG_TP assertions, GenTreeL } // There are no constant assertions for structs. - // if (varTypeIsStruct(tree)) { continue; @@ -3656,18 +3736,26 @@ GenTree* Compiler::optAssertionProp_LclVar(ASSERT_VALARG_TP assertions, GenTreeL // If local assertion prop, just perform constant prop. if (optLocalAssertionProp) { + RECORD_ASSERTION_STATS(propLclVarMatchCount++); + RECORD_ASSERTION_STATS(propLclVarIter += iterCount); return optConstantAssertionProp(curAssertion, tree, stmt DEBUGARG(assertionIndex)); } // If global assertion, perform constant propagation only if the VN's match. if (curAssertion->op1.vn == vnStore->VNConservativeNormalValue(tree->gtVNPair)) { + RECORD_ASSERTION_STATS(propLclVarMatchCount++); + RECORD_ASSERTION_STATS(propLclVarIter += iterCount); return optConstantAssertionProp(curAssertion, tree, stmt DEBUGARG(assertionIndex)); } } } } + RECORD_ASSERTION_STATS(propLclVarMissedCount++); + RECORD_ASSERTION_STATS(propLclVarMissedIter += + (optLocalAssertionProp ? optAssertionCount : (BitVecOps::Count(apTraits, assertions)))); + return nullptr; } @@ -3749,6 +3837,8 @@ AssertionIndex Compiler::optLocalAssertionIsEqualOrNotEqual( return NO_ASSERTION_INDEX; } + RECORD_ASSERTION_STATS(equalOrNotEquaCallCount++); + for (AssertionIndex index = 1; index <= optAssertionCount; ++index) { AssertionDsc* curAssertion = optGetAssertion(index); @@ -3767,11 +3857,18 @@ AssertionIndex Compiler::optLocalAssertionIsEqualOrNotEqual( if (constantIsEqual || assertionIsEqual) { + RECORD_ASSERTION_STATS(equalOrNotEquaMatchCount++); + RECORD_ASSERTION_STATS(equalOrNotEquaIter += index); return index; } } } } + + RECORD_ASSERTION_STATS(equalOrNotEquaMissedCount++); + RECORD_ASSERTION_STATS(equalOrNotEquaMissedIter += + (optLocalAssertionProp ? optAssertionCount : (BitVecOps::Count(apTraits, assertions)))); + return NO_ASSERTION_INDEX; } @@ -3799,10 +3896,17 @@ AssertionIndex Compiler::optGlobalAssertionIsEqualOrNotEqual(ASSERT_VALARG_TP as { return NO_ASSERTION_INDEX; } + + RECORD_ASSERTION_STATS(propEqualOrNotCallCount++); + BitVecOps::Iter iter(apTraits, assertions); unsigned index = 0; + RECORD_ASSERTION_STATS(unsigned iterCount = 0); + while (iter.NextElem(&index)) { + RECORD_ASSERTION_STATS(iterCount++); + AssertionIndex assertionIndex = GetAssertionIndex(index); if (assertionIndex > optAssertionCount) { @@ -3817,6 +3921,8 @@ AssertionIndex Compiler::optGlobalAssertionIsEqualOrNotEqual(ASSERT_VALARG_TP as if ((curAssertion->op1.vn == vnStore->VNConservativeNormalValue(op1->gtVNPair)) && (curAssertion->op2.vn == vnStore->VNConservativeNormalValue(op2->gtVNPair))) { + RECORD_ASSERTION_STATS(propEqualOrNotMatchCount++); + RECORD_ASSERTION_STATS(propEqualOrNotIter += iterCount); return assertionIndex; } @@ -3832,11 +3938,17 @@ AssertionIndex Compiler::optGlobalAssertionIsEqualOrNotEqual(ASSERT_VALARG_TP as if ((curAssertion->op1.vn == vnStore->VNConservativeNormalValue(indirAddr->gtVNPair)) && (curAssertion->op2.vn == vnStore->VNConservativeNormalValue(op2->gtVNPair))) { + RECORD_ASSERTION_STATS(propEqualOrNotMatchCount++); + RECORD_ASSERTION_STATS(propEqualOrNotIter += iterCount); return assertionIndex; } } } } + + RECORD_ASSERTION_STATS(propEqualOrNotMissedCount++); + RECORD_ASSERTION_STATS(propEqualOrNotMissedIter += BitVecOps::Count(apTraits, assertions)); + return NO_ASSERTION_INDEX; } @@ -3852,10 +3964,16 @@ AssertionIndex Compiler::optGlobalAssertionIsEqualOrNotEqualZero(ASSERT_VALARG_T { return NO_ASSERTION_INDEX; } + + RECORD_ASSERTION_STATS(propEqualZeroCallCount++); + BitVecOps::Iter iter(apTraits, assertions); unsigned index = 0; + RECORD_ASSERTION_STATS(unsigned iterCount = 0); + while (iter.NextElem(&index)) { + RECORD_ASSERTION_STATS(iterCount++); AssertionIndex assertionIndex = GetAssertionIndex(index); if (assertionIndex > optAssertionCount) { @@ -3870,9 +3988,16 @@ AssertionIndex Compiler::optGlobalAssertionIsEqualOrNotEqualZero(ASSERT_VALARG_T if ((curAssertion->op1.vn == vnStore->VNConservativeNormalValue(op1->gtVNPair)) && (curAssertion->op2.vn == vnStore->VNZeroForType(op1->TypeGet()))) { + RECORD_ASSERTION_STATS(propEqualZeroMatchCount++); + RECORD_ASSERTION_STATS(propEqualZeroIter += iterCount); + return assertionIndex; } } + + RECORD_ASSERTION_STATS(propEqualZeroMissedCount++); + RECORD_ASSERTION_STATS(propEqualZeroMissedIter += BitVecOps::Count(apTraits, assertions)); + return NO_ASSERTION_INDEX; } @@ -4495,6 +4620,7 @@ AssertionIndex Compiler::optAssertionIsNonNullInternal(GenTree* op, return NO_ASSERTION_INDEX; } + RECORD_ASSERTION_STATS(noNullCallCount++); // Look at both the top-level vn, and // the vn we get by stripping off any constant adds. // @@ -4523,8 +4649,11 @@ AssertionIndex Compiler::optAssertionIsNonNullInternal(GenTree* op, // BitVecOps::Iter iter(apTraits, assertions); unsigned index = 0; + RECORD_ASSERTION_STATS(unsigned iterCount = 0); + while (iter.NextElem(&index)) { + RECORD_ASSERTION_STATS(iterCount++); AssertionIndex assertionIndex = GetAssertionIndex(index); if (assertionIndex > optAssertionCount) { @@ -4550,11 +4679,18 @@ AssertionIndex Compiler::optAssertionIsNonNullInternal(GenTree* op, *pVnBased = true; #endif + RECORD_ASSERTION_STATS(noNullMatchCount++); + RECORD_ASSERTION_STATS(noNullIter += iterCount); return assertionIndex; } + + RECORD_ASSERTION_STATS(noNullMissedCount++); + RECORD_ASSERTION_STATS(noNullMissedIter += BitVecOps::Count(apTraits, assertions)); } else { + RECORD_ASSERTION_STATS(noNullCallCount++); + unsigned lclNum = op->AsLclVarCommon()->GetLclNum(); // Check each assertion to find if we have a variable == or != null assertion. for (AssertionIndex index = 1; index <= optAssertionCount; index++) @@ -4565,10 +4701,18 @@ AssertionIndex Compiler::optAssertionIsNonNullInternal(GenTree* op, (curAssertion->op2.kind == O2K_CONST_INT) && // op2 (curAssertion->op1.lcl.lclNum == lclNum) && (curAssertion->op2.u1.iconVal == 0)) { +#ifdef DEBUG + RECORD_ASSERTION_STATS(noNullMatchCount++); + RECORD_ASSERTION_STATS(noNullIter += index); +#endif return index; } } + + RECORD_ASSERTION_STATS(noNullMissedCount++); + RECORD_ASSERTION_STATS(noNullMissedIter += optAssertionCount); } + return NO_ASSERTION_INDEX; } /***************************************************************************** @@ -4703,10 +4847,16 @@ GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, GenTree } #endif // FEATURE_ENABLE_NO_RANGE_CHECKS + RECORD_ASSERTION_STATS(propBndChkCallCount++); + BitVecOps::Iter iter(apTraits, assertions); unsigned index = 0; + RECORD_ASSERTION_STATS(unsigned iterCount = 0); + while (iter.NextElem(&index)) { + RECORD_ASSERTION_STATS(iterCount++); + AssertionIndex assertionIndex = GetAssertionIndex(index); if (assertionIndex > optAssertionCount) { @@ -4799,6 +4949,8 @@ GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, GenTree #endif if (arrBndsChk == stmt->GetRootNode()) { + RECORD_ASSERTION_STATS(propBndChkMatchCount++); + RECORD_ASSERTION_STATS(propBndChkIter += iterCount); // We have a top-level bounds check node. // This can happen when trees are broken up due to inlining. // optRemoveStandaloneRangeCheck will return the modified tree (side effects or a no-op). @@ -4814,6 +4966,9 @@ GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, GenTree return nullptr; } + RECORD_ASSERTION_STATS(propBndChkMissedCount++); + RECORD_ASSERTION_STATS(propBndChkMissedIter += BitVecOps::Count(apTraits, assertions)); + return nullptr; } @@ -5057,14 +5212,14 @@ void Compiler::optImpliedByTypeOfAssertions(ASSERT_TP& activeAssertions) // Search the assertion table for a non-null assertion on op1 that matches chkAssertion for (AssertionIndex impIndex = 1; impIndex <= optAssertionCount; impIndex++) { - AssertionDsc* impAssertion = optGetAssertion(impIndex); - // The impAssertion must be different from the chkAssertion if (impIndex == chkAssertionIndex) { continue; } + AssertionDsc* impAssertion = optGetAssertion(impIndex); + // impAssertion must be a Non Null assertion on lclNum if ((impAssertion->assertionKind != OAK_NOT_EQUAL) || ((impAssertion->op1.kind != O1K_LCLVAR) && (impAssertion->op1.kind != O1K_VALUE_NUMBER)) || diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index 5745dc191f8f60..6a6737943686f4 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -1674,6 +1674,15 @@ void Compiler::compShutdown() } #endif // LOOP_HOIST_STATS +#if TRACK_ASSERTION_STATS +#ifdef DEBUG // Always display assertion stats in retail + if (JitConfig.DisplayAssertionPropStats() != 0) +#endif // DEBUG + { + PrintAggregateAssertionStats(jitstdout); + } +#endif + #if TRACK_ENREG_STATS if (JitConfig.JitEnregStats() != 0) { @@ -6036,6 +6045,10 @@ void Compiler::compCompileFinish() AddLoopHoistStats(); #endif // LOOP_HOIST_STATS +#if TRACK_ASSERTION_STATS + AddAssertionStats(); +#endif // TRACK_ASSERTION_STATS + #if MEASURE_NODE_SIZE genTreeNcntHist.record(static_cast(genNodeSizeStatsPerFunc.genTreeNodeCnt)); genTreeNsizHist.record(static_cast(genNodeSizeStatsPerFunc.genTreeNodeSize)); @@ -6448,6 +6461,27 @@ int Compiler::compCompileHelper(CORINFO_MODULE_HANDLE classPtr, compHasBackwardJump = false; compHasBackwardJumpInHandler = false; +#if TRACK_ASSERTION_STATS + +#define ASSERTION_STATS_ZERO(name) \ + name##Iter = 0; \ + name##MatchCount = 0; \ + name##MissedIter = 0; \ + name##MissedCount = 0; \ + name##CallCount = 0; + + ASSERTION_STATS_ZERO(addAssertion) + ASSERTION_STATS_ZERO(subRange) + ASSERTION_STATS_ZERO(subType) + ASSERTION_STATS_ZERO(equalOrNotEqua) + ASSERTION_STATS_ZERO(noNull) + ASSERTION_STATS_ZERO(propLclVar) + ASSERTION_STATS_ZERO(propEqualOrNot) + ASSERTION_STATS_ZERO(propEqualZero) + ASSERTION_STATS_ZERO(propBndChk) + +#endif // TRACK_ASSERTION_STATS + #ifdef DEBUG compCurBB = nullptr; lvaTable = nullptr; @@ -8611,8 +8645,8 @@ void JitTimer::PrintCsvHeader() fprintf(s_csvFile, "\"GC Info Bytes\","); fprintf(s_csvFile, "\"Total Bytes Allocated\","); fprintf(s_csvFile, "\"Total Cycles\","); - fprintf(s_csvFile, "\"CPS\"\n"); - + fprintf(s_csvFile, "\"CPS\""); + fprintf(s_csvFile, "\n"); fflush(s_csvFile); } } @@ -8694,8 +8728,8 @@ void JitTimer::PrintCsvMethodStats(Compiler* comp) fprintf(s_csvFile, "%Iu,", comp->compInfoBlkSize); fprintf(s_csvFile, "%Iu,", comp->compGetArenaAllocator()->getTotalBytesAllocated()); fprintf(s_csvFile, "%I64u,", m_info.m_totalCycles); - fprintf(s_csvFile, "%f\n", CachedCyclesPerSecond()); - + fprintf(s_csvFile, "%f", CachedCyclesPerSecond()); + fprintf(s_csvFile, "\n"); fflush(s_csvFile); } @@ -8782,6 +8816,83 @@ void Compiler::PrintPerMethodLoopHoistStats() } #endif // LOOP_HOIST_STATS +#if TRACK_ASSERTION_STATS +// Static fields. +CritSecObject Compiler::s_assertionStatsLock; // Default constructor. + +#define ASSERTION_STATS_DEF_VAR(name) \ + unsigned Compiler::s_##name##Iter = 0; \ + unsigned Compiler::s_##name##MatchCount = 0; \ + unsigned Compiler::s_##name##MissedIter = 0; \ + unsigned Compiler::s_##name##MissedCount = 0; \ + unsigned Compiler::s_##name##CallCount = 0; + +ASSERTION_STATS_DEF_VAR(addAssertion) +ASSERTION_STATS_DEF_VAR(subRange) +ASSERTION_STATS_DEF_VAR(subType) +ASSERTION_STATS_DEF_VAR(equalOrNotEqua) +ASSERTION_STATS_DEF_VAR(noNull) +ASSERTION_STATS_DEF_VAR(propLclVar) +ASSERTION_STATS_DEF_VAR(propEqualOrNot) +ASSERTION_STATS_DEF_VAR(propEqualZero) +ASSERTION_STATS_DEF_VAR(propBndChk) + +#undef ASSERTION_STATS_DEF_VAR + +// static +void Compiler::PrintAggregateAssertionStats(FILE* f) +{ + fprintf(f, "\n"); + fprintf(f, "---------------------------------------------------\n"); + fprintf(f, "Assertion stats\n"); + fprintf(f, "---------------------------------------------------\n"); + +#define ASSERTION_TITLE(name, value) \ + fprintf(f, "---" name "---\n"); \ + fprintf(f, name "Match: %u\n", s_##value##MatchCount); \ + fprintf(f, name "Iter: %u\n", s_##value##Iter); \ + fprintf(f, name "MissedIter: %u\n", s_##value##MissedIter); \ + fprintf(f, name "MissedCount: %u\n", s_##value##MissedCount); \ + fprintf(f, name "CallCount: %u\n", s_##value##CallCount); \ + fprintf(f, "\n"); + + ASSERTION_TITLE("AddAssertion", addAssertion) + ASSERTION_TITLE("SubType", subType) + ASSERTION_TITLE("SubRange", subRange) + ASSERTION_TITLE("EqualOrNotEqual", equalOrNotEqua) + ASSERTION_TITLE("NoNull", noNull) + ASSERTION_TITLE("PropLclVar", propLclVar) + ASSERTION_TITLE("PropEqualOrNot", propEqualOrNot) + ASSERTION_TITLE("PropEqualZero", propEqualZero) + ASSERTION_TITLE("PropBndChk", propBndChk) +} + +void Compiler::AddAssertionStats() +{ + CritSecHolder statsLock(s_assertionStatsLock); + +#define INCREMENT_STATS(name) \ + s_##name##Iter += name##Iter; \ + s_##name##MatchCount += name##MatchCount; \ + s_##name##MissedIter += name##MissedIter; \ + s_##name##MissedCount += name##MissedCount; \ + s_##name##CallCount += name##CallCount; + + INCREMENT_STATS(addAssertion) + INCREMENT_STATS(subRange) + INCREMENT_STATS(subType) + INCREMENT_STATS(equalOrNotEqua) + INCREMENT_STATS(noNull) + INCREMENT_STATS(propLclVar) + INCREMENT_STATS(propEqualOrNot) + INCREMENT_STATS(propEqualZero) + INCREMENT_STATS(propBndChk) + +#undef INCREMENT_STATS +} + +#endif + //------------------------------------------------------------------------ // RecordStateAtEndOfInlining: capture timing data (if enabled) after // inlining as completed. diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index f397d4bcd9e883..c4bddc3eae5781 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -6992,7 +6992,7 @@ class Compiler BitVecTraits* apTraits; ASSERT_TP apFull; - enum optAssertionKind + enum optAssertionKind : unsigned short { OAK_INVALID, OAK_EQUAL, @@ -7033,6 +7033,7 @@ class Compiler struct AssertionDsc { optAssertionKind assertionKind; + bool vnBased; struct SsaVar { unsigned lclNum; // assigned to or property of this local var number @@ -7125,27 +7126,26 @@ class Compiler return false; } - bool HasSameOp1(AssertionDsc* that, bool vnBased) + bool HasSameOp1(const AssertionDsc& that) const { - if (op1.kind != that->op1.kind) + if (op1.kind != that.op1.kind) { return false; } else if (op1.kind == O1K_ARR_BND) { assert(vnBased); - return (op1.bnd.vnIdx == that->op1.bnd.vnIdx) && (op1.bnd.vnLen == that->op1.bnd.vnLen); + return (op1.bnd.vnIdx == that.op1.bnd.vnIdx) && (op1.bnd.vnLen == that.op1.bnd.vnLen); } else { - return ((vnBased && (op1.vn == that->op1.vn)) || - (!vnBased && (op1.lcl.lclNum == that->op1.lcl.lclNum))); + return ((vnBased && (op1.vn == that.op1.vn)) || (!vnBased && (op1.lcl.lclNum == that.op1.lcl.lclNum))); } } - bool HasSameOp2(AssertionDsc* that, bool vnBased) + bool HasSameOp2(const AssertionDsc& that) const { - if (op2.kind != that->op2.kind) + if (op2.kind != that.op2.kind) { return false; } @@ -7154,24 +7154,24 @@ class Compiler { case O2K_IND_CNS_INT: case O2K_CONST_INT: - return ((op2.u1.iconVal == that->op2.u1.iconVal) && (op2.u1.iconFlags == that->op2.u1.iconFlags)); + return ((op2.u1.iconVal == that.op2.u1.iconVal) && (op2.u1.iconFlags == that.op2.u1.iconFlags)); case O2K_CONST_LONG: - return (op2.lconVal == that->op2.lconVal); + return (op2.lconVal == that.op2.lconVal); case O2K_CONST_DOUBLE: // exact match because of positive and negative zero. - return (memcmp(&op2.dconVal, &that->op2.dconVal, sizeof(double)) == 0); + return (memcmp(&op2.dconVal, &that.op2.dconVal, sizeof(double)) == 0); case O2K_ZEROOBJ: return true; case O2K_LCLVAR_COPY: - return (op2.lcl.lclNum == that->op2.lcl.lclNum) && - (!vnBased || (op2.lcl.ssaNum == that->op2.lcl.ssaNum)); + return (op2.lcl.lclNum == that.op2.lcl.lclNum) && + (!vnBased || (op2.lcl.ssaNum == that.op2.lcl.ssaNum)); case O2K_SUBRANGE: - return op2.u2.Equals(that->op2.u2); + return op2.u2.Equals(that.op2.u2); case O2K_INVALID: // we will return false @@ -7185,28 +7185,88 @@ class Compiler return false; } - bool Complementary(AssertionDsc* that, bool vnBased) + bool Complementary(const AssertionDsc& that) { - return ComplementaryKind(assertionKind, that->assertionKind) && HasSameOp1(that, vnBased) && - HasSameOp2(that, vnBased); + return ComplementaryKind(assertionKind, that.assertionKind) && HasSameOp1(that) && HasSameOp2(that); } - bool Equals(AssertionDsc* that, bool vnBased) + bool Equals(const AssertionDsc& that) const { - if (assertionKind != that->assertionKind) + if (assertionKind != that.assertionKind) { return false; } else if (assertionKind == OAK_NO_THROW) { assert(op2.kind == O2K_INVALID); - return HasSameOp1(that, vnBased); + return HasSameOp1(that); } else { - return HasSameOp1(that, vnBased) && HasSameOp2(that, vnBased); + return HasSameOp1(that) && HasSameOp2(that); } } + + public: + unsigned GetHashCode() const + { + assert(assertionKind != OAK_INVALID); + unsigned op2Valid = op2.kind ^ O2K_INVALID; + unsigned op1Valid = op1.kind ^ O1K_INVALID; + unsigned hashCode = 0; + + if (vnBased) + { + if (op2Valid) + { + /* op2.lcl's lclNum combined 9-bits will take care of 9-bits of other members + of union like u1.iconVal, lconVal, dconVal. + */ + hashCode |= ((op2.lcl.lclNum & 0x1FF) << 23) | /* 31 ~ 23 : op2.lcl.lclNum (low 9-bits) */ + (op2.kind << 18); /* 22 ~ 18 : op2Kind (5-bits) */ + } + + if (op1.kind == O1K_ARR_BND) + { + hashCode |= ((op1.lcl.lclNum & 0x7FF) << 7) | /* 17 ~ 07 : op1.lcl.lclNum (low 11-bits) */ + (op1.kind << 3); /* 06 ~ 03 : op1Kind (4-bits) */ + } + else if (op1Valid) + { + hashCode |= ((op1.vn & 0x7FF) << 7) | /* 17 ~ 07 : op1.vn (low 11-bits) */ + (op1.kind << 3); /* 06 ~ 03 : op1Kind (4-bits) */ + } + + return hashCode | (assertionKind); /* 02 ~ 00 : assertionKind (3-bits)*/ + } + else + { + if (op2Valid) + { + /* op2.lcl's lclNum combined 15-bits will take care of 15-bits of other members + of union like u1.iconVal, lconVal, dconVal. + */ + hashCode |= ((op2.lcl.lclNum & 0x7FFF) << 17) | /* 31 ~ 17 : op2.lcl.lclNum (low 15-bits) */ + (op2.kind << 12); /* 16 ~ 12 : op2Kind (5-bits) */ + } + + if (op1Valid) + { + hashCode |= ((op1.lcl.lclNum & 0x1F) << 7) | /* 11 ~ 07 : op1.lcl.lclNum (low 5-bits) */ + (op1.kind << 3); /* 06 ~ 03 : op1Kind (4-bits) */ + } + + return hashCode | (assertionKind); /* 02 ~ 00 : assertionKind (3-bits)*/ + } + } + + AssertionDsc(bool isLocalProp = false) + { + assertionKind = OAK_INVALID; + op1 = AssertionDscOp1(); + op2 = AssertionDscOp2(); + vnBased = !isLocalProp; + } }; protected: @@ -7228,6 +7288,22 @@ class Compiler AssertionIndex optAssertionCount; // total number of assertions in the assertion table AssertionIndex optMaxAssertionCount; + struct AssertionDscKeyFuncs + { + static bool Equals(const AssertionDsc& x, const AssertionDsc& y) + { + return x.Equals(y); + } + + static unsigned GetHashCode(const AssertionDsc& dsc) + { + return dsc.GetHashCode(); + } + }; + + typedef JitHashTable AssertionDscMap; + AssertionDscMap* optAssertionDscMap; + public: void optVnNonNullPropCurStmt(BasicBlock* block, Statement* stmt, GenTree* tree); fgWalkResult optVNConstantPropCurStmt(BasicBlock* block, Statement* stmt, GenTree* tree); @@ -9943,6 +10019,45 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX static void PrintAggregateLoopHoistStats(FILE* f); #endif // LOOP_HOIST_STATS +#if TRACK_ASSERTION_STATS + + static CritSecObject s_assertionStatsLock; // This lock protects the data structures below. + +#define ASSERTION_STATS_DECL_VAR(name) \ + unsigned name##Iter = 0; \ + unsigned name##MatchCount = 0; \ + unsigned name##MissedIter = 0; \ + unsigned name##MissedCount = 0; \ + unsigned name##CallCount = 0; \ + static unsigned s_##name##Iter; \ + static unsigned s_##name##MatchCount; \ + static unsigned s_##name##MissedIter; \ + static unsigned s_##name##MissedCount; \ + static unsigned s_##name##CallCount; + + ASSERTION_STATS_DECL_VAR(addAssertion) + ASSERTION_STATS_DECL_VAR(subRange) + ASSERTION_STATS_DECL_VAR(subType) + ASSERTION_STATS_DECL_VAR(equalOrNotEqua) + ASSERTION_STATS_DECL_VAR(noNull) + ASSERTION_STATS_DECL_VAR(propLclVar) + ASSERTION_STATS_DECL_VAR(propEqualOrNot) + ASSERTION_STATS_DECL_VAR(propEqualZero) + ASSERTION_STATS_DECL_VAR(propBndChk) + +#undef ASSERTION_STATS_DECL_VAR + +#define RECORD_ASSERTION_STATS(x) x + + void AddAssertionStats(); + static void PrintAggregateAssertionStats(FILE* f); + +#else + +#define RECORD_ASSERTION_STATS(x) + +#endif // TRACK_ASSERTION_STATS + #if TRACK_ENREG_STATS class EnregisterStats { diff --git a/src/coreclr/jit/compiler.hpp b/src/coreclr/jit/compiler.hpp index ae3da0f8b0d9c9..04323d1cb6c65e 100644 --- a/src/coreclr/jit/compiler.hpp +++ b/src/coreclr/jit/compiler.hpp @@ -3249,12 +3249,23 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX inline void Compiler::optAssertionReset(AssertionIndex limit) { PREFAST_ASSUME(optAssertionCount <= optMaxAssertionCount); + if (limit == 0) + { + optAssertionDscMap->RemoveAll(); + } while (optAssertionCount > limit) { AssertionIndex index = optAssertionCount; AssertionDsc* curAssertion = optGetAssertion(index); + + if (limit > 0) + { + optAssertionDscMap->Remove(*curAssertion); + } + optAssertionCount--; + unsigned lclNum = curAssertion->op1.lcl.lclNum; assert(lclNum < lvaCount); BitVecOps::RemoveElemD(apTraits, GetAssertionDep(lclNum), index - 1); @@ -3276,7 +3287,10 @@ inline void Compiler::optAssertionReset(AssertionIndex limit) { AssertionIndex index = ++optAssertionCount; AssertionDsc* curAssertion = optGetAssertion(index); - unsigned lclNum = curAssertion->op1.lcl.lclNum; + + optAssertionDscMap->Set(*curAssertion, optAssertionCount); + + unsigned lclNum = curAssertion->op1.lcl.lclNum; BitVecOps::AddElemD(apTraits, GetAssertionDep(lclNum), index - 1); // @@ -3335,6 +3349,7 @@ inline void Compiler::optAssertionRemove(AssertionIndex index) BitVecOps::RemoveElemD(apTraits, GetAssertionDep(lclNum), index - 1); } + optAssertionDscMap->Remove(*curAssertion); optAssertionCount--; } else diff --git a/src/coreclr/jit/jit.h b/src/coreclr/jit/jit.h index f229cae217031c..efd7df2a3ee3dc 100644 --- a/src/coreclr/jit/jit.h +++ b/src/coreclr/jit/jit.h @@ -438,14 +438,16 @@ class GlobalJitOptions #define VERBOSE_VERIFY 0 // Dump additional information when verifying code. Useful to debug verification bugs. #ifdef DEBUG -#define MEASURE_MEM_ALLOC 1 // Collect memory allocation stats. -#define LOOP_HOIST_STATS 1 // Collect loop hoisting stats. -#define TRACK_LSRA_STATS 1 // Collect LSRA stats -#define TRACK_ENREG_STATS 1 // Collect enregistration stats +#define MEASURE_MEM_ALLOC 1 // Collect memory allocation stats. +#define LOOP_HOIST_STATS 1 // Collect loop hoisting stats. +#define TRACK_LSRA_STATS 1 // Collect LSRA stats +#define TRACK_ENREG_STATS 1 // Collect enregistration stats +#define TRACK_ASSERTION_STATS 1 // Collect assertion prop stats #else -#define MEASURE_MEM_ALLOC 0 // You can set this to 1 to get memory stats in retail, as well -#define LOOP_HOIST_STATS 0 // You can set this to 1 to get loop hoist stats in retail, as well -#define TRACK_LSRA_STATS 0 // You can set this to 1 to get LSRA stats in retail, as well +#define MEASURE_MEM_ALLOC 0 // You can set this to 1 to get memory stats in retail, as well +#define LOOP_HOIST_STATS 0 // You can set this to 1 to get loop hoist stats in retail, as well +#define TRACK_LSRA_STATS 0 // You can set this to 1 to get LSRA stats in retail, as well +#define TRACK_ASSERTION_STATS 0 // You can set this to 1 to get assertion prop stats in retail, as well #endif // Timing calls to clr.dll is only available under certain conditions. diff --git a/src/coreclr/jit/jitconfigvalues.h b/src/coreclr/jit/jitconfigvalues.h index 893cdbb51831fb..243bb3b92f4fbf 100644 --- a/src/coreclr/jit/jitconfigvalues.h +++ b/src/coreclr/jit/jitconfigvalues.h @@ -25,7 +25,8 @@ CONFIG_INTEGER(DebugBreakOnVerificationFailure, W("DebugBreakOnVerificationFailu CONFIG_INTEGER(DiffableDasm, W("JitDiffableDasm"), 0) // Make the disassembly diff-able CONFIG_INTEGER(JitDasmWithAddress, W("JitDasmWithAddress"), 0) // Print the process address next to each instruction of // the disassembly -CONFIG_INTEGER(DisplayLoopHoistStats, W("JitLoopHoistStats"), 0) // Display JIT loop hoisting statistics +CONFIG_INTEGER(DisplayLoopHoistStats, W("JitLoopHoistStats"), 0) // Display JIT loop hoisting statistics +CONFIG_INTEGER(DisplayAssertionPropStats, W("JitAssertionPropStats"), 0) // Display JIT assertion statistics CONFIG_INTEGER(DisplayLsraStats, W("JitLsraStats"), 0) // Display JIT Linear Scan Register Allocator statistics // If set to "1", display the stats in textual format. // If set to "2", display the stats in csv format. diff --git a/src/coreclr/jit/jithashtable.h b/src/coreclr/jit/jithashtable.h index 85186dfb508e66..6fa43510d0526e 100644 --- a/src/coreclr/jit/jithashtable.h +++ b/src/coreclr/jit/jithashtable.h @@ -204,10 +204,13 @@ class JitHashTable // v - the value // kind - Normal, we are not allowed to overwrite // Overwrite, we are allowed to overwrite + // SkipIfExist, skip if value exists. // currently only used by CHK/DBG builds in an assert. + // pVal - If SkipIfExist is passed, returns the value that + // was already present in the table. Otherwise, unchanged. // // Return Value: - // `true` if the key exists and was overwritten, + // `true` if the key exists and was overwritten/skipped. // `false` otherwise. // // Notes: @@ -217,10 +220,14 @@ class JitHashTable enum SetKind { None, - Overwrite + Overwrite, + + // Skip setting if it exists. Useful to find node just once instead of + // Lookup() and Set + SkipIfExist }; - bool Set(Key k, Value v, SetKind kind = None) + bool Set(Key k, Value v, SetKind kind = None, Value* pVal = nullptr) { CheckGrowth(); @@ -235,8 +242,19 @@ class JitHashTable } if (pN != nullptr) { - assert(kind == Overwrite); - pN->m_val = v; + if (kind == SkipIfExist) + { + assert(KeyFuncs::Equals(k, pN->m_key)); + if (pVal != nullptr) + { + *pVal = pN->m_val; + } + } + else + { + assert(kind == Overwrite); + pN->m_val = v; + } return true; } else