Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JIT: enable complementary jtrue assertions for cross-block local ap #94741

Merged
merged 2 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 66 additions & 48 deletions src/coreclr/jit/assertionprop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -590,12 +590,19 @@ void Compiler::optAssertionInit(bool isLocalProp)
//
optAssertionDep =
new (this, CMK_AssertionProp) JitExpandArray<ASSERT_TP>(getAllocator(CMK_AssertionProp), max(1, lvaCount));

if (optCrossBlockLocalAssertionProp)
{
optComplementaryAssertionMap = new (this, CMK_AssertionProp)
AssertionIndex[optMaxAssertionCount + 1](); // zero-inited (NO_ASSERTION_INDEX)
}
}
else
{
// General assertion prop.
//
optLocalAssertionProp = false;
optLocalAssertionProp = false;
optCrossBlockLocalAssertionProp = 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
Expand All @@ -615,7 +622,6 @@ void Compiler::optAssertionInit(bool isLocalProp)
}

optAssertionTabPrivate = new (this, CMK_AssertionProp) AssertionDsc[optMaxAssertionCount];

optAssertionTraitsInit(optMaxAssertionCount);

optAssertionCount = 0;
Expand Down Expand Up @@ -1641,7 +1647,8 @@ AssertionIndex Compiler::optAddAssertion(AssertionDsc* newAssertion)
//
if (optLocalAssertionProp)
{
assert(newAssertion->op1.kind == O1K_LCLVAR);
assert((newAssertion->op1.kind == O1K_LCLVAR) || (newAssertion->op1.kind == O1K_SUBTYPE) ||
(newAssertion->op1.kind == O1K_EXACT_TYPE));

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

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

AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree)
{
// These assertions are VN based, so not relevant for local prop
//
if (optLocalAssertionProp)
{
return NO_ASSERTION_INDEX;
}

GenTree* relop = tree->gtGetOp1();
if (!relop->OperIsCompare())
{
Expand Down Expand Up @@ -2138,13 +2153,7 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree)
*/
AssertionInfo Compiler::optAssertionGenJtrue(GenTree* tree)
{
// Only create assertions for JTRUE when we are in the global phase
if (optLocalAssertionProp)
{
return NO_ASSERTION_INDEX;
}

GenTree* relop = tree->AsOp()->gtOp1;
GenTree* const relop = tree->AsOp()->gtOp1;
if (!relop->OperIsCompare())
{
return NO_ASSERTION_INDEX;
Expand All @@ -2158,6 +2167,11 @@ AssertionInfo Compiler::optAssertionGenJtrue(GenTree* tree)
return info;
}

if (optLocalAssertionProp && !optCrossBlockLocalAssertionProp)
{
return NO_ASSERTION_INDEX;
}

// Find assertion kind.
switch (relop->gtOper)
{
Expand Down Expand Up @@ -2185,53 +2199,57 @@ AssertionInfo Compiler::optAssertionGenJtrue(GenTree* tree)
std::swap(op1, op2);
}

ValueNum op1VN = vnStore->VNConservativeNormalValue(op1->gtVNPair);
ValueNum op2VN = vnStore->VNConservativeNormalValue(op2->gtVNPair);
// If op1 is lcl and op2 is const or lcl, create assertion.
if ((op1->gtOper == GT_LCL_VAR) && (op2->OperIsConst() || (op2->gtOper == GT_LCL_VAR))) // Fix for Dev10 851483
{
return optCreateJtrueAssertions(op1, op2, assertionKind);
}
else if (vnStore->IsVNCheckedBound(op1VN) && vnStore->IsVNInt32Constant(op2VN))
else if (!optLocalAssertionProp)
{
assert(relop->OperIs(GT_EQ, GT_NE));
ValueNum op1VN = vnStore->VNConservativeNormalValue(op1->gtVNPair);
ValueNum op2VN = vnStore->VNConservativeNormalValue(op2->gtVNPair);

int con = vnStore->ConstantValue<int>(op2VN);
if (con >= 0)
if (vnStore->IsVNCheckedBound(op1VN) && vnStore->IsVNInt32Constant(op2VN))
{
AssertionDsc dsc;
assert(relop->OperIs(GT_EQ, GT_NE));

// 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
if (con == 0)
int con = vnStore->ConstantValue<int>(op2VN);
if (con >= 0)
{
dsc.assertionKind = OAK_NOT_EQUAL;
dsc.op1.bnd.vnIdx = vnStore->VNForIntCon(0);
}
else
{
dsc.assertionKind = OAK_EQUAL;
dsc.op1.bnd.vnIdx = vnStore->VNForIntCon(con - 1);
}
AssertionDsc dsc;

dsc.op1.vn = op1VN;
dsc.op1.kind = O1K_ARR_BND;
dsc.op1.bnd.vnLen = op1VN;
dsc.op2.vn = vnStore->VNConservativeNormalValue(op2->gtVNPair);
dsc.op2.kind = O2K_CONST_INT;
dsc.op2.u1.iconVal = 0;
dsc.op2.SetIconFlag(GTF_EMPTY);

// when con is not zero, create an assertion on the arr.Length == con edge
// when con is zero, create an assertion on the arr.Length != 0 edge
AssertionIndex index = optAddAssertion(&dsc);
if (relop->OperIs(GT_NE) != (con == 0))
{
return AssertionInfo::ForNextEdge(index);
}
else
{
return index;
// 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
if (con == 0)
{
dsc.assertionKind = OAK_NOT_EQUAL;
dsc.op1.bnd.vnIdx = vnStore->VNForIntCon(0);
}
else
{
dsc.assertionKind = OAK_EQUAL;
dsc.op1.bnd.vnIdx = vnStore->VNForIntCon(con - 1);
}

dsc.op1.vn = op1VN;
dsc.op1.kind = O1K_ARR_BND;
dsc.op1.bnd.vnLen = op1VN;
dsc.op2.vn = vnStore->VNConservativeNormalValue(op2->gtVNPair);
dsc.op2.kind = O2K_CONST_INT;
dsc.op2.u1.iconVal = 0;
dsc.op2.SetIconFlag(GTF_EMPTY);

// when con is not zero, create an assertion on the arr.Length == con edge
// when con is zero, create an assertion on the arr.Length != 0 edge
AssertionIndex index = optAddAssertion(&dsc);
if (relop->OperIs(GT_NE) != (con == 0))
{
return AssertionInfo::ForNextEdge(index);
}
else
{
return index;
}
}
}
}
Expand Down Expand Up @@ -2260,7 +2278,7 @@ AssertionInfo Compiler::optAssertionGenJtrue(GenTree* tree)
return NO_ASSERTION_INDEX;
}

GenTreeCall* call = op1->AsCall();
GenTreeCall* const call = op1->AsCall();

// Note CORINFO_HELP_READYTORUN_ISINSTANCEOF does not have the same argument pattern.
// In particular, it is not possible to deduce what class is being tested from its args.
Expand Down
12 changes: 7 additions & 5 deletions src/coreclr/jit/block.h
Original file line number Diff line number Diff line change
Expand Up @@ -1230,18 +1230,20 @@ struct BasicBlock : private LIR::Range
*/

union {
EXPSET_TP bbCseGen; // CSEs computed by block
ASSERT_TP bbAssertionGen; // assertions computed by block
EXPSET_TP bbCseGen; // CSEs computed by block
ASSERT_TP bbAssertionGen; // assertions created by block (global prop)
ASSERT_TP bbAssertionOutIfTrue; // assertions available on exit along true/jump edge (BBJ_COND, local prop)
};

union {
EXPSET_TP bbCseIn; // CSEs available on entry
ASSERT_TP bbAssertionIn; // assertions available on entry
ASSERT_TP bbAssertionIn; // assertions available on entry (global prop)
};

union {
EXPSET_TP bbCseOut; // CSEs available on exit
ASSERT_TP bbAssertionOut; // assertions available on exit
EXPSET_TP bbCseOut; // CSEs available on exit
ASSERT_TP bbAssertionOut; // assertions available on exit (global prop, local prop & !BBJ_COND)
ASSERT_TP bbAssertionOutIfFalse; // assertions available on exit along false/next edge (BBJ_COND, local prop)
};

void* bbEmitCookie;
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -7356,6 +7356,7 @@ class Compiler
BitVecTraits* apTraits;
ASSERT_TP apFull;
ASSERT_TP apLocal;
ASSERT_TP apLocalIfTrue;

enum optAssertionKind
{
Expand Down
122 changes: 111 additions & 11 deletions src/coreclr/jit/morph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12903,22 +12903,21 @@ void Compiler::fgAssertionGen(GenTree* tree)
INDEBUG(unsigned oldAssertionCount = optAssertionCount;);
optAssertionGen(tree);

if (tree->GeneratesAssertion())
{
AssertionIndex const apIndex = tree->GetAssertionInfo().GetAssertionIndex();
unsigned const bvIndex = apIndex - 1;

// Helper to note when an existing assertion has been
// brought back to life.
//
auto announce = [&](AssertionIndex apIndex, const char* condition) {
#ifdef DEBUG
if (verbose)
{
if (oldAssertionCount == optAssertionCount)
{
if (!BitVecOps::IsMember(apTraits, apLocal, bvIndex))
if (!BitVecOps::IsMember(apTraits, apLocal, apIndex - 1))
{
// This tree resurrected an existing assertion.
// We call that out here since assertion prop won't.
//
printf("GenTreeNode creates assertion:\n");
printf("GenTreeNode creates %sassertion:\n", condition);
gtDispTree(tree, nullptr, nullptr, true);
printf("In " FMT_BB " New Local ", compCurBB->bbNum);
optPrintAssertion(optGetAssertion(apIndex), apIndex);
Expand All @@ -12935,7 +12934,69 @@ void Compiler::fgAssertionGen(GenTree* tree)
}
}
#endif
};

// For BBJ_COND nodes, we have two assertion out BVs.
// apLocal will be stored on bbAssertionOutIfFalse and be used for false successors.
// apLocalIfTrue will be stored on bbAssertionOutIfTrue and be used for true successors.
//
const bool doCondUpdates = tree->OperIs(GT_JTRUE) && compCurBB->KindIs(BBJ_COND) && (compCurBB->NumSucc() == 2);

// Intialize apLocalIfTrue if we might look for it later,
// even if it ends up identical to apLocal.
//
if (doCondUpdates)
{
apLocalIfTrue = BitVecOps::MakeCopy(apTraits, apLocal);
}

if (!tree->GeneratesAssertion())
{
return;
}

AssertionInfo info = tree->GetAssertionInfo();

if (doCondUpdates)
{
// Update apLocal and apIfTrue with suitable assertions
// from the JTRUE
//
assert(optCrossBlockLocalAssertionProp);

AssertionIndex ifFalseAssertionIndex;
AssertionIndex ifTrueAssertionIndex;

if (info.IsNextEdgeAssertion())
{
ifFalseAssertionIndex = info.GetAssertionIndex();
ifTrueAssertionIndex = optFindComplementary(ifFalseAssertionIndex);
}
else
{
ifTrueAssertionIndex = info.GetAssertionIndex();
ifFalseAssertionIndex = optFindComplementary(ifTrueAssertionIndex);
}

if (ifTrueAssertionIndex != NO_ASSERTION_INDEX)
{
announce(ifTrueAssertionIndex, " [if true]");
unsigned const bvIndex = ifTrueAssertionIndex - 1;
BitVecOps::AddElemD(apTraits, apLocalIfTrue, bvIndex);
}

if (ifFalseAssertionIndex != NO_ASSERTION_INDEX)
{
announce(ifFalseAssertionIndex, " [if false]");
unsigned const bvIndex = ifFalseAssertionIndex - 1;
BitVecOps::AddElemD(apTraits, apLocal, ifFalseAssertionIndex - 1);
}
}
else
{
AssertionIndex const apIndex = tree->GetAssertionInfo().GetAssertionIndex();
announce(apIndex, "");
unsigned const bvIndex = apIndex - 1;
BitVecOps::AddElemD(apTraits, apLocal, bvIndex);
}
}
Expand Down Expand Up @@ -13833,17 +13894,44 @@ void Compiler::fgMorphBlock(BasicBlock* block, unsigned highestReachablePostorde
continue;
}

// Yes, pred assertions are available. If this is the first pred, copy.
// Yes, pred assertions are available.
// If the pred is (a non-degenerate) BBJ_COND, fetch the appropriate out set.
//
ASSERT_TP assertionsOut = pred->bbAssertionOut;

if (pred->KindIs(BBJ_COND) && (pred->NumSucc() == 2))
{
if (block == pred->GetJumpDest())
{
JITDUMP("Using `if true` assertions from pred " FMT_BB "\n", pred->bbNum);
assertionsOut = pred->bbAssertionOutIfTrue;
}
else
{
assert(block == pred->Next());
JITDUMP("Using `if false` assertions from pred " FMT_BB "\n", pred->bbNum);
assertionsOut = pred->bbAssertionOutIfFalse;
}
}

// If this is the first pred, copy (or share, when block is the only successor).
// If this is a subsequent pred, intersect.
//
if (!hasPredAssertions)
{
apLocal = BitVecOps::MakeCopy(apTraits, pred->bbAssertionOut);
if (block->NumSucc() == 1)
{
apLocal = assertionsOut;
}
else
{
apLocal = BitVecOps::MakeCopy(apTraits, assertionsOut);
}
hasPredAssertions = true;
}
else
{
BitVecOps::IntersectionD(apTraits, apLocal, pred->bbAssertionOut);
BitVecOps::IntersectionD(apTraits, apLocal, assertionsOut);
}
}

Expand Down Expand Up @@ -13884,7 +13972,19 @@ void Compiler::fgMorphBlock(BasicBlock* block, unsigned highestReachablePostorde
if (optCrossBlockLocalAssertionProp && (block->NumSucc() > 0))
{
assert(optLocalAssertionProp);
block->bbAssertionOut = BitVecOps::MakeCopy(apTraits, apLocal);

if (block->KindIs(BBJ_COND))
{
// We don't need to make a copy of the if true set; this BV
// was freshly copied in fgAssertionGen
//
block->bbAssertionOutIfTrue = apLocalIfTrue;
block->bbAssertionOutIfFalse = BitVecOps::MakeCopy(apTraits, apLocal);
}
else
{
block->bbAssertionOut = BitVecOps::MakeCopy(apTraits, apLocal);
}
}

compCurBB = nullptr;
Expand Down