From cda192b7115417d27d5719193ce39c3bf9348999 Mon Sep 17 00:00:00 2001 From: Egor Bogatov Date: Sat, 24 Oct 2026 12:15:00 +0200 Subject: [PATCH 1/2] JIT: Size the global assertion table by the actual number of assertions Instead of sizing the global assertion table (and the bit vectors) upfront via a heuristic, grow the table on demand during assertion gen (up to 512) and size the bit vectors by the actual number of assertions afterwards. This avoids dropping assertions in methods the heuristic underpredicted, and avoids larger than needed bit vectors in methods it overpredicted. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e6dfca0e-42e2-45de-99de-222d54fe6937 --- src/coreclr/jit/assertionprop.cpp | 44 +++++++++++++++++++++++-------- src/coreclr/jit/compiler.h | 1 + 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index d451f3c5d05d10..9862f0edb39be0 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -929,6 +929,8 @@ void Compiler::optAssertionInit(bool isLocalProp) optMaxAssertionCount = 64; } + optAssertionTabCapacity = optMaxAssertionCount; + // Local assertion prop keeps mappings from each local var to the assertions about that var. // optAssertionDep = @@ -947,21 +949,17 @@ void Compiler::optAssertionInit(bool isLocalProp) optLocalAssertionProp = false; optCrossBlockLocalAssertionProp = false; - // Heuristic for sizing the assertion table. - // - // The weighting of basicBlocks vs locals reflects their relative contribution observed empirically. - // Validated against 1,115,046 compiled methods: - // - 94.6% of methods stay at the floor of 64 (only 1.9% actually need more). - // - Underpredicts for 481 methods (0.043%), with a worst-case deficit of 127. - // - Only 0.4% of methods hit the 256 cap. - optMaxAssertionCount = (AssertionIndex)max(64, min(256, (int)(lvaTrackedCount + 3 * fgBBcount + 48) >> 2)); + // The table grows on demand and the bit vectors are sized by the actual number + // of assertions once all of them are created (see optAssertionPropMain). + optMaxAssertionCount = 512; + optAssertionTabCapacity = 64; optComplementaryAssertionMap = new (this, CMK_AssertionProp) - AssertionIndex[optMaxAssertionCount + 1](); // zero-inited (NO_ASSERTION_INDEX) + AssertionIndex[optAssertionTabCapacity + 1](); // zero-inited (NO_ASSERTION_INDEX) } - optAssertionTabPrivate = new (this, CMK_AssertionProp) AssertionDsc[optMaxAssertionCount]; - optAssertionTraitsInit(optMaxAssertionCount); + optAssertionTabPrivate = new (this, CMK_AssertionProp) AssertionDsc[optAssertionTabCapacity]; + optAssertionTraitsInit(optAssertionTabCapacity); optAssertionCount = 0; optAssertionOverflow = 0; @@ -1622,6 +1620,27 @@ AssertionIndex Compiler::optAddAssertion(const AssertionDsc& newAssertion) return NO_ASSERTION_INDEX; } + if (optAssertionCount == optAssertionTabCapacity) + { + // Only global assertion prop grows the table, see optAssertionInit. + assert(!optLocalAssertionProp); + const AssertionIndex newCapacity = + (AssertionIndex)min(2u * optAssertionTabCapacity, (unsigned)optMaxAssertionCount); + + AssertionDsc* newTab = new (this, CMK_AssertionProp) AssertionDsc[newCapacity]; + for (unsigned i = 0; i < optAssertionCount; i++) + { + newTab[i] = optAssertionTabPrivate[i]; + } + optAssertionTabPrivate = newTab; + + AssertionIndex* newMap = new (this, CMK_AssertionProp) AssertionIndex[newCapacity + 1](); + memcpy(newMap, optComplementaryAssertionMap, (optAssertionTabCapacity + 1) * sizeof(AssertionIndex)); + optComplementaryAssertionMap = newMap; + + optAssertionTabCapacity = newCapacity; + } + optAssertionTabPrivate[optAssertionCount] = newAssertion; optAssertionCount++; @@ -6781,6 +6800,9 @@ PhaseStatus Compiler::optAssertionPropMain() madeChanges |= optCreateJumpTableImpliedAssertions(switchBlock); } + // All assertions are created, size the bit vectors accordingly. + optAssertionTraitsInit(max(optAssertionCount, (AssertionIndex)1)); + if (optAssertionCount == 0) { // Zero out the bbAssertionIn values, as these can be referenced in RangeCheck::MergeAssertion diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 0fd9d6f06c3d1c..0109eb08aa6b73 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -9310,6 +9310,7 @@ class Compiler VNSet* optAssertionVNsMap = nullptr; AssertionIndex optAssertionCount = 0; // total number of assertions in the assertion table AssertionIndex optMaxAssertionCount; + AssertionIndex optAssertionTabCapacity; bool optCrossBlockLocalAssertionProp; unsigned optAssertionOverflow; bool optCanPropLclVar; From c93ce0702101b0bb91dd22edbda18660814d2bb2 Mon Sep 17 00:00:00 2001 From: Egor Bogatov Date: Sat, 24 Oct 2026 21:16:33 +0200 Subject: [PATCH 2/2] JIT: Skip unrelated constant assertions in range analysis Assertions against integer constants can only constrain their left-hand VN. Reject unrelated ones before evaluating the remaining range-analysis cases. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e6dfca0e-42e2-45de-99de-222d54fe6937 --- src/coreclr/jit/rangecheck.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/coreclr/jit/rangecheck.cpp b/src/coreclr/jit/rangecheck.cpp index b55c8f96229369..1c5aa898063201 100644 --- a/src/coreclr/jit/rangecheck.cpp +++ b/src/coreclr/jit/rangecheck.cpp @@ -1307,6 +1307,11 @@ void RangeCheck::MergeEdgeAssertionsWorker(Compiler* comp const Compiler::AssertionDsc& curAssertion = comp->optGetAssertion(assertionIndex); + if (curAssertion.GetOp2().KindIs(Compiler::O2K_CONST_INT) && (curAssertion.GetOp1().GetVN() != normalLclVN)) + { + continue; + } + Limit limit(Limit::keUndef); genTreeOps cmpOper = GT_NONE; bool isUnsigned = false;