Skip to content

Commit 6f5287b

Browse files
committed
JIT: enable complementary jtrue assertions for cross-block local ap
Now that we can propagate assertions across block boundaries we can generate assertions for true/false branch conditions and propagate them along the corresponding edges. Contributes to dotnet#93246.
1 parent 3e66124 commit 6f5287b

File tree

3 files changed

+163
-58
lines changed

3 files changed

+163
-58
lines changed

src/coreclr/jit/assertionprop.cpp

+66-48
Original file line numberDiff line numberDiff line change
@@ -590,12 +590,19 @@ void Compiler::optAssertionInit(bool isLocalProp)
590590
//
591591
optAssertionDep =
592592
new (this, CMK_AssertionProp) JitExpandArray<ASSERT_TP>(getAllocator(CMK_AssertionProp), max(1, lvaCount));
593+
594+
if (optCrossBlockLocalAssertionProp)
595+
{
596+
optComplementaryAssertionMap = new (this, CMK_AssertionProp)
597+
AssertionIndex[optMaxAssertionCount + 1](); // zero-inited (NO_ASSERTION_INDEX)
598+
}
593599
}
594600
else
595601
{
596602
// General assertion prop.
597603
//
598-
optLocalAssertionProp = false;
604+
optLocalAssertionProp = false;
605+
optCrossBlockLocalAssertionProp = false;
599606

600607
// Use a function countFunc to determine a proper maximum assertion count for the
601608
// method being compiled. The function is linear to the IL size for small and
@@ -615,7 +622,6 @@ void Compiler::optAssertionInit(bool isLocalProp)
615622
}
616623

617624
optAssertionTabPrivate = new (this, CMK_AssertionProp) AssertionDsc[optMaxAssertionCount];
618-
619625
optAssertionTraitsInit(optMaxAssertionCount);
620626

621627
optAssertionCount = 0;
@@ -1641,7 +1647,8 @@ AssertionIndex Compiler::optAddAssertion(AssertionDsc* newAssertion)
16411647
//
16421648
if (optLocalAssertionProp)
16431649
{
1644-
assert(newAssertion->op1.kind == O1K_LCLVAR);
1650+
assert((newAssertion->op1.kind == O1K_LCLVAR) || (newAssertion->op1.kind == O1K_SUBTYPE) ||
1651+
(newAssertion->op1.kind == O1K_EXACT_TYPE));
16451652

16461653
unsigned lclNum = newAssertion->op1.lcl.lclNum;
16471654
BitVecOps::Iter iter(apTraits, GetAssertionDep(lclNum));
@@ -1702,7 +1709,8 @@ AssertionIndex Compiler::optAddAssertion(AssertionDsc* newAssertion)
17021709
// Assertion mask bits are [index + 1].
17031710
if (optLocalAssertionProp)
17041711
{
1705-
assert(newAssertion->op1.kind == O1K_LCLVAR);
1712+
assert((newAssertion->op1.kind == O1K_LCLVAR) || (newAssertion->op1.kind == O1K_SUBTYPE) ||
1713+
(newAssertion->op1.kind == O1K_EXACT_TYPE));
17061714

17071715
// Mark the variables this index depends on
17081716
unsigned lclNum = newAssertion->op1.lcl.lclNum;
@@ -1971,6 +1979,13 @@ AssertionIndex Compiler::optCreateJtrueAssertions(GenTree* op1
19711979

19721980
AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree)
19731981
{
1982+
// These assertions are VN based, so not relevant for local prop
1983+
//
1984+
if (optLocalAssertionProp)
1985+
{
1986+
return NO_ASSERTION_INDEX;
1987+
}
1988+
19741989
GenTree* relop = tree->gtGetOp1();
19751990
if (!relop->OperIsCompare())
19761991
{
@@ -2138,13 +2153,7 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree)
21382153
*/
21392154
AssertionInfo Compiler::optAssertionGenJtrue(GenTree* tree)
21402155
{
2141-
// Only create assertions for JTRUE when we are in the global phase
2142-
if (optLocalAssertionProp)
2143-
{
2144-
return NO_ASSERTION_INDEX;
2145-
}
2146-
2147-
GenTree* relop = tree->AsOp()->gtOp1;
2156+
GenTree* const relop = tree->AsOp()->gtOp1;
21482157
if (!relop->OperIsCompare())
21492158
{
21502159
return NO_ASSERTION_INDEX;
@@ -2158,6 +2167,11 @@ AssertionInfo Compiler::optAssertionGenJtrue(GenTree* tree)
21582167
return info;
21592168
}
21602169

2170+
if (optLocalAssertionProp && !optCrossBlockLocalAssertionProp)
2171+
{
2172+
return NO_ASSERTION_INDEX;
2173+
}
2174+
21612175
// Find assertion kind.
21622176
switch (relop->gtOper)
21632177
{
@@ -2185,53 +2199,57 @@ AssertionInfo Compiler::optAssertionGenJtrue(GenTree* tree)
21852199
std::swap(op1, op2);
21862200
}
21872201

2188-
ValueNum op1VN = vnStore->VNConservativeNormalValue(op1->gtVNPair);
2189-
ValueNum op2VN = vnStore->VNConservativeNormalValue(op2->gtVNPair);
21902202
// If op1 is lcl and op2 is const or lcl, create assertion.
21912203
if ((op1->gtOper == GT_LCL_VAR) && (op2->OperIsConst() || (op2->gtOper == GT_LCL_VAR))) // Fix for Dev10 851483
21922204
{
21932205
return optCreateJtrueAssertions(op1, op2, assertionKind);
21942206
}
2195-
else if (vnStore->IsVNCheckedBound(op1VN) && vnStore->IsVNInt32Constant(op2VN))
2207+
else if (!optLocalAssertionProp)
21962208
{
2197-
assert(relop->OperIs(GT_EQ, GT_NE));
2209+
ValueNum op1VN = vnStore->VNConservativeNormalValue(op1->gtVNPair);
2210+
ValueNum op2VN = vnStore->VNConservativeNormalValue(op2->gtVNPair);
21982211

2199-
int con = vnStore->ConstantValue<int>(op2VN);
2200-
if (con >= 0)
2212+
if (vnStore->IsVNCheckedBound(op1VN) && vnStore->IsVNInt32Constant(op2VN))
22012213
{
2202-
AssertionDsc dsc;
2214+
assert(relop->OperIs(GT_EQ, GT_NE));
22032215

2204-
// For arr.Length != 0, we know that 0 is a valid index
2205-
// For arr.Length == con, we know that con - 1 is the greatest valid index
2206-
if (con == 0)
2216+
int con = vnStore->ConstantValue<int>(op2VN);
2217+
if (con >= 0)
22072218
{
2208-
dsc.assertionKind = OAK_NOT_EQUAL;
2209-
dsc.op1.bnd.vnIdx = vnStore->VNForIntCon(0);
2210-
}
2211-
else
2212-
{
2213-
dsc.assertionKind = OAK_EQUAL;
2214-
dsc.op1.bnd.vnIdx = vnStore->VNForIntCon(con - 1);
2215-
}
2219+
AssertionDsc dsc;
22162220

2217-
dsc.op1.vn = op1VN;
2218-
dsc.op1.kind = O1K_ARR_BND;
2219-
dsc.op1.bnd.vnLen = op1VN;
2220-
dsc.op2.vn = vnStore->VNConservativeNormalValue(op2->gtVNPair);
2221-
dsc.op2.kind = O2K_CONST_INT;
2222-
dsc.op2.u1.iconVal = 0;
2223-
dsc.op2.SetIconFlag(GTF_EMPTY);
2224-
2225-
// when con is not zero, create an assertion on the arr.Length == con edge
2226-
// when con is zero, create an assertion on the arr.Length != 0 edge
2227-
AssertionIndex index = optAddAssertion(&dsc);
2228-
if (relop->OperIs(GT_NE) != (con == 0))
2229-
{
2230-
return AssertionInfo::ForNextEdge(index);
2231-
}
2232-
else
2233-
{
2234-
return index;
2221+
// For arr.Length != 0, we know that 0 is a valid index
2222+
// For arr.Length == con, we know that con - 1 is the greatest valid index
2223+
if (con == 0)
2224+
{
2225+
dsc.assertionKind = OAK_NOT_EQUAL;
2226+
dsc.op1.bnd.vnIdx = vnStore->VNForIntCon(0);
2227+
}
2228+
else
2229+
{
2230+
dsc.assertionKind = OAK_EQUAL;
2231+
dsc.op1.bnd.vnIdx = vnStore->VNForIntCon(con - 1);
2232+
}
2233+
2234+
dsc.op1.vn = op1VN;
2235+
dsc.op1.kind = O1K_ARR_BND;
2236+
dsc.op1.bnd.vnLen = op1VN;
2237+
dsc.op2.vn = vnStore->VNConservativeNormalValue(op2->gtVNPair);
2238+
dsc.op2.kind = O2K_CONST_INT;
2239+
dsc.op2.u1.iconVal = 0;
2240+
dsc.op2.SetIconFlag(GTF_EMPTY);
2241+
2242+
// when con is not zero, create an assertion on the arr.Length == con edge
2243+
// when con is zero, create an assertion on the arr.Length != 0 edge
2244+
AssertionIndex index = optAddAssertion(&dsc);
2245+
if (relop->OperIs(GT_NE) != (con == 0))
2246+
{
2247+
return AssertionInfo::ForNextEdge(index);
2248+
}
2249+
else
2250+
{
2251+
return index;
2252+
}
22352253
}
22362254
}
22372255
}
@@ -2260,7 +2278,7 @@ AssertionInfo Compiler::optAssertionGenJtrue(GenTree* tree)
22602278
return NO_ASSERTION_INDEX;
22612279
}
22622280

2263-
GenTreeCall* call = op1->AsCall();
2281+
GenTreeCall* const call = op1->AsCall();
22642282

22652283
// Note CORINFO_HELP_READYTORUN_ISINSTANCEOF does not have the same argument pattern.
22662284
// In particular, it is not possible to deduce what class is being tested from its args.

src/coreclr/jit/compiler.h

+1
Original file line numberDiff line numberDiff line change
@@ -7356,6 +7356,7 @@ class Compiler
73567356
BitVecTraits* apTraits;
73577357
ASSERT_TP apFull;
73587358
ASSERT_TP apLocal;
7359+
ASSERT_TP apLocalIfTrue;
73597360

73607361
enum optAssertionKind
73617362
{

src/coreclr/jit/morph.cpp

+96-10
Original file line numberDiff line numberDiff line change
@@ -12903,22 +12903,21 @@ void Compiler::fgAssertionGen(GenTree* tree)
1290312903
INDEBUG(unsigned oldAssertionCount = optAssertionCount;);
1290412904
optAssertionGen(tree);
1290512905

12906-
if (tree->GeneratesAssertion())
12907-
{
12908-
AssertionIndex const apIndex = tree->GetAssertionInfo().GetAssertionIndex();
12909-
unsigned const bvIndex = apIndex - 1;
12910-
12906+
// Helper to note when an existing assertion has been
12907+
// brought back to life.
12908+
//
12909+
auto announce = [&](AssertionIndex apIndex, const char* condition) {
1291112910
#ifdef DEBUG
1291212911
if (verbose)
1291312912
{
1291412913
if (oldAssertionCount == optAssertionCount)
1291512914
{
12916-
if (!BitVecOps::IsMember(apTraits, apLocal, bvIndex))
12915+
if (!BitVecOps::IsMember(apTraits, apLocal, apIndex - 1))
1291712916
{
1291812917
// This tree resurrected an existing assertion.
1291912918
// We call that out here since assertion prop won't.
1292012919
//
12921-
printf("GenTreeNode creates assertion:\n");
12920+
printf("GenTreeNode creates %sassertion:\n", condition);
1292212921
gtDispTree(tree, nullptr, nullptr, true);
1292312922
printf("In " FMT_BB " New Local ", compCurBB->bbNum);
1292412923
optPrintAssertion(optGetAssertion(apIndex), apIndex);
@@ -12935,7 +12934,69 @@ void Compiler::fgAssertionGen(GenTree* tree)
1293512934
}
1293612935
}
1293712936
#endif
12937+
};
12938+
12939+
// For BBJ_COND nodes, we have two assertion out BVs.
12940+
// apLocal will be stored on bbAssertionOut and be used for false successors.
12941+
// apLocalIfTrue will be stored on bbAssertionGen and be used for true successors.
12942+
//
12943+
const bool doCondUpdates = tree->OperIs(GT_JTRUE) && compCurBB->KindIs(BBJ_COND) && (compCurBB->NumSucc() == 2);
12944+
12945+
// Intialize apLocalIfTrue if we might look for it later,
12946+
// even if it ends up identical to apLocal.
12947+
//
12948+
if (doCondUpdates)
12949+
{
12950+
apLocalIfTrue = BitVecOps::MakeCopy(apTraits, apLocal);
12951+
}
12952+
12953+
if (!tree->GeneratesAssertion())
12954+
{
12955+
return;
12956+
}
12957+
12958+
AssertionInfo info = tree->GetAssertionInfo();
12959+
12960+
if (doCondUpdates)
12961+
{
12962+
// Update apLocal and apIfTrue with suitable assertions
12963+
// from the JTRUE
12964+
//
12965+
assert(optCrossBlockLocalAssertionProp);
12966+
12967+
AssertionIndex ifFalseAssertionIndex;
12968+
AssertionIndex ifTrueAssertionIndex;
12969+
12970+
if (info.IsNextEdgeAssertion())
12971+
{
12972+
ifFalseAssertionIndex = info.GetAssertionIndex();
12973+
ifTrueAssertionIndex = optFindComplementary(ifFalseAssertionIndex);
12974+
}
12975+
else
12976+
{
12977+
ifTrueAssertionIndex = info.GetAssertionIndex();
12978+
ifFalseAssertionIndex = optFindComplementary(ifTrueAssertionIndex);
12979+
}
12980+
12981+
if (ifTrueAssertionIndex != NO_ASSERTION_INDEX)
12982+
{
12983+
announce(ifTrueAssertionIndex, " [if true]");
12984+
unsigned const bvIndex = ifTrueAssertionIndex - 1;
12985+
BitVecOps::AddElemD(apTraits, apLocalIfTrue, bvIndex);
12986+
}
1293812987

12988+
if (ifFalseAssertionIndex != NO_ASSERTION_INDEX)
12989+
{
12990+
announce(ifFalseAssertionIndex, " [if false]");
12991+
unsigned const bvIndex = ifFalseAssertionIndex - 1;
12992+
BitVecOps::AddElemD(apTraits, apLocal, ifFalseAssertionIndex - 1);
12993+
}
12994+
}
12995+
else
12996+
{
12997+
AssertionIndex const apIndex = tree->GetAssertionInfo().GetAssertionIndex();
12998+
announce(apIndex, "");
12999+
unsigned const bvIndex = apIndex - 1;
1293913000
BitVecOps::AddElemD(apTraits, apLocal, bvIndex);
1294013001
}
1294113002
}
@@ -13833,17 +13894,35 @@ void Compiler::fgMorphBlock(BasicBlock* block, unsigned highestReachablePostorde
1383313894
continue;
1383413895
}
1383513896

13836-
// Yes, pred assertions are available. If this is the first pred, copy.
13897+
// Yes, pred assertions are available.
13898+
// If the pred is (a non-degenerate) BBJ_COND, fetch the appropriate out set.
13899+
//
13900+
ASSERT_TP assertionsOut = pred->bbAssertionOut;
13901+
13902+
if (pred->KindIs(BBJ_COND) && (pred->NumSucc() == 2) && (block == pred->GetJumpDest()))
13903+
{
13904+
JITDUMP("Using `if true` assertions from pred " FMT_BB "\n", pred->bbNum);
13905+
assertionsOut = pred->bbAssertionGen;
13906+
}
13907+
13908+
// If this is the first pred, copy (or share, when block is the only successor).
1383713909
// If this is a subsequent pred, intersect.
1383813910
//
1383913911
if (!hasPredAssertions)
1384013912
{
13841-
apLocal = BitVecOps::MakeCopy(apTraits, pred->bbAssertionOut);
13913+
if (block->NumSucc() == 1)
13914+
{
13915+
apLocal = assertionsOut;
13916+
}
13917+
else
13918+
{
13919+
apLocal = BitVecOps::MakeCopy(apTraits, assertionsOut);
13920+
}
1384213921
hasPredAssertions = true;
1384313922
}
1384413923
else
1384513924
{
13846-
BitVecOps::IntersectionD(apTraits, apLocal, pred->bbAssertionOut);
13925+
BitVecOps::IntersectionD(apTraits, apLocal, assertionsOut);
1384713926
}
1384813927
}
1384913928

@@ -13885,6 +13964,13 @@ void Compiler::fgMorphBlock(BasicBlock* block, unsigned highestReachablePostorde
1388513964
{
1388613965
assert(optLocalAssertionProp);
1388713966
block->bbAssertionOut = BitVecOps::MakeCopy(apTraits, apLocal);
13967+
13968+
if (block->KindIs(BBJ_COND))
13969+
{
13970+
// We don't need to make a copy here as this BV
13971+
// was freshly copied in fgAssertionGen
13972+
block->bbAssertionGen = apLocalIfTrue;
13973+
}
1388813974
}
1388913975

1389013976
compCurBB = nullptr;

0 commit comments

Comments
 (0)