Permalink
5159 lines (4578 sloc) 183 KB
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
/*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XX XX
XX AssertionProp XX
XX XX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
*/
#include "jitpch.h"
#ifdef _MSC_VER
#pragma hdrstop
#endif
/*****************************************************************************
*
* Helper passed to Compiler::fgWalkTreePre() to find the Asgn node for optAddCopies()
*/
/* static */
Compiler::fgWalkResult Compiler::optAddCopiesCallback(GenTree** pTree, fgWalkData* data)
{
GenTree* tree = *pTree;
if (tree->OperIsAssignment())
{
GenTree* op1 = tree->gtOp.gtOp1;
Compiler* comp = data->compiler;
if ((op1->gtOper == GT_LCL_VAR) && (op1->gtLclVarCommon.gtLclNum == comp->optAddCopyLclNum))
{
comp->optAddCopyAsgnNode = tree;
return WALK_ABORT;
}
}
return WALK_CONTINUE;
}
/*****************************************************************************
*
* Add new copies before Assertion Prop.
*/
void Compiler::optAddCopies()
{
unsigned lclNum;
LclVarDsc* varDsc;
#ifdef DEBUG
if (verbose)
{
printf("\n*************** In optAddCopies()\n\n");
}
if (verboseTrees)
{
printf("Blocks/Trees at start of phase\n");
fgDispBasicBlocks(true);
}
#endif
// Don't add any copies if we have reached the tracking limit.
if (lvaHaveManyLocals())
{
return;
}
for (lclNum = 0, varDsc = lvaTable; lclNum < lvaCount; lclNum++, varDsc++)
{
var_types typ = varDsc->TypeGet();
// We only add copies for non temp local variables
// that have a single def and that can possibly be enregistered
if (varDsc->lvIsTemp || !varDsc->lvSingleDef || !varTypeCanReg(typ))
{
continue;
}
/* For lvNormalizeOnLoad(), we need to add a cast to the copy-assignment
like "copyLclNum = int(varDsc)" and optAssertionGen() only
tracks simple assignments. The same goes for lvNormalizedOnStore as
the cast is generated in fgMorphSmpOpAsg. This boils down to not having
a copy until optAssertionGen handles this*/
if (varDsc->lvNormalizeOnLoad() || varDsc->lvNormalizeOnStore() || typ == TYP_BOOL)
{
continue;
}
if (varTypeIsSmall(varDsc->TypeGet()) || typ == TYP_BOOL)
{
continue;
}
// If locals must be initialized to zero, that initialization counts as a second definition.
// VB in particular allows usage of variables not explicitly initialized.
// Note that this effectively disables this optimization for all local variables
// as C# sets InitLocals all the time starting in Whidbey.
if (!varDsc->lvIsParam && info.compInitMem)
{
continue;
}
// On x86 we may want to add a copy for an incoming double parameter
// because we can ensure that the copy we make is double aligned
// where as we can never ensure the alignment of an incoming double parameter
//
// On all other platforms we will never need to make a copy
// for an incoming double parameter
bool isFloatParam = false;
#ifdef _TARGET_X86_
isFloatParam = varDsc->lvIsParam && varTypeIsFloating(typ);
#endif
if (!isFloatParam && !varDsc->lvVolatileHint)
{
continue;
}
// We don't want to add a copy for a variable that is part of a struct
if (varDsc->lvIsStructField)
{
continue;
}
// We require that the weighted ref count be significant.
if (varDsc->lvRefCntWtd <= (BB_LOOP_WEIGHT * BB_UNITY_WEIGHT / 2))
{
continue;
}
// For parameters, we only want to add a copy for the heavier-than-average
// uses instead of adding a copy to cover every single use.
// 'paramImportantUseDom' is the set of blocks that dominate the
// heavier-than-average uses of a parameter.
// Initial value is all blocks.
BlockSet paramImportantUseDom(BlockSetOps::MakeFull(this));
// This will be threshold for determining heavier-than-average uses
unsigned paramAvgWtdRefDiv2 = (varDsc->lvRefCntWtd + varDsc->lvRefCnt / 2) / (varDsc->lvRefCnt * 2);
bool paramFoundImportantUse = false;
#ifdef DEBUG
if (verbose)
{
printf("Trying to add a copy for V%02u %s, avg_wtd = %s\n", lclNum,
varDsc->lvIsParam ? "an arg" : "a local", refCntWtd2str(paramAvgWtdRefDiv2));
}
#endif
//
// We must have a ref in a block that is dominated only by the entry block
//
if (BlockSetOps::MayBeUninit(varDsc->lvRefBlks))
{
// No references
continue;
}
bool isDominatedByFirstBB = false;
BlockSetOps::Iter iter(this, varDsc->lvRefBlks);
unsigned bbNum = 0;
while (iter.NextElem(&bbNum))
{
/* Find the block 'bbNum' */
BasicBlock* block = fgFirstBB;
while (block && (block->bbNum != bbNum))
{
block = block->bbNext;
}
noway_assert(block && (block->bbNum == bbNum));
bool importantUseInBlock = (varDsc->lvIsParam) && (block->getBBWeight(this) > paramAvgWtdRefDiv2);
bool isPreHeaderBlock = ((block->bbFlags & BBF_LOOP_PREHEADER) != 0);
BlockSet blockDom(BlockSetOps::UninitVal());
BlockSet blockDomSub0(BlockSetOps::UninitVal());
if (block->bbIDom == nullptr && isPreHeaderBlock)
{
// Loop Preheader blocks that we insert will have a bbDom set that is nullptr
// but we can instead use the bNext successor block's dominator information
noway_assert(block->bbNext != nullptr);
BlockSetOps::AssignNoCopy(this, blockDom, fgGetDominatorSet(block->bbNext));
}
else
{
BlockSetOps::AssignNoCopy(this, blockDom, fgGetDominatorSet(block));
}
if (!BlockSetOps::IsEmpty(this, blockDom))
{
BlockSetOps::Assign(this, blockDomSub0, blockDom);
if (isPreHeaderBlock)
{
// We must clear bbNext block number from the dominator set
BlockSetOps::RemoveElemD(this, blockDomSub0, block->bbNext->bbNum);
}
/* Is this block dominated by fgFirstBB? */
if (BlockSetOps::IsMember(this, blockDomSub0, fgFirstBB->bbNum))
{
isDominatedByFirstBB = true;
}
}
#ifdef DEBUG
if (verbose)
{
printf(" Referenced in BB%02u, bbWeight is %s", bbNum, refCntWtd2str(block->getBBWeight(this)));
if (isDominatedByFirstBB)
{
printf(", which is dominated by BB01");
}
if (importantUseInBlock)
{
printf(", ImportantUse");
}
printf("\n");
}
#endif
/* If this is a heavier-than-average block, then track which
blocks dominate this use of the parameter. */
if (importantUseInBlock)
{
paramFoundImportantUse = true;
BlockSetOps::IntersectionD(this, paramImportantUseDom,
blockDomSub0); // Clear blocks that do not dominate
}
}
// We should have found at least one heavier-than-averageDiv2 block.
if (varDsc->lvIsParam)
{
if (!paramFoundImportantUse)
{
continue;
}
}
// For us to add a new copy:
// we require that we have a floating point parameter
// or a lvVolatile variable that is always reached from the first BB
// and we have at least one block available in paramImportantUseDom
//
bool doCopy = (isFloatParam || (isDominatedByFirstBB && varDsc->lvVolatileHint)) &&
!BlockSetOps::IsEmpty(this, paramImportantUseDom);
// Under stress mode we expand the number of candidates
// to include parameters of any type
// or any variable that is always reached from the first BB
//
if (compStressCompile(STRESS_GENERIC_VARN, 30))
{
// Ensure that we preserve the invariants required by the subsequent code.
if (varDsc->lvIsParam || isDominatedByFirstBB)
{
doCopy = true;
}
}
if (!doCopy)
{
continue;
}
GenTree* stmt;
unsigned copyLclNum = lvaGrabTemp(false DEBUGARG("optAddCopies"));
// Because lvaGrabTemp may have reallocated the lvaTable, ensure varDsc
// is still in sync with lvaTable[lclNum];
varDsc = &lvaTable[lclNum];
// Set lvType on the new Temp Lcl Var
lvaTable[copyLclNum].lvType = typ;
#ifdef DEBUG
if (verbose)
{
printf("\n Finding the best place to insert the assignment V%02i=V%02i\n", copyLclNum, lclNum);
}
#endif
if (varDsc->lvIsParam)
{
noway_assert(varDsc->lvDefStmt == nullptr || varDsc->lvIsStructField);
// Create a new copy assignment tree
GenTree* copyAsgn = gtNewTempAssign(copyLclNum, gtNewLclvNode(lclNum, typ));
/* Find the best block to insert the new assignment */
/* We will choose the lowest weighted block, and within */
/* those block, the highest numbered block which */
/* dominates all the uses of the local variable */
/* Our default is to use the first block */
BasicBlock* bestBlock = fgFirstBB;
unsigned bestWeight = bestBlock->getBBWeight(this);
BasicBlock* block = bestBlock;
#ifdef DEBUG
if (verbose)
{
printf(" Starting at BB%02u, bbWeight is %s", block->bbNum,
refCntWtd2str(block->getBBWeight(this)));
printf(", bestWeight is %s\n", refCntWtd2str(bestWeight));
}
#endif
/* We have already calculated paramImportantUseDom above. */
BlockSetOps::Iter iter(this, paramImportantUseDom);
unsigned bbNum = 0;
while (iter.NextElem(&bbNum))
{
/* Advance block to point to 'bbNum' */
/* This assumes that the iterator returns block number is increasing lexical order. */
while (block && (block->bbNum != bbNum))
{
block = block->bbNext;
}
noway_assert(block && (block->bbNum == bbNum));
#ifdef DEBUG
if (verbose)
{
printf(" Considering BB%02u, bbWeight is %s", block->bbNum,
refCntWtd2str(block->getBBWeight(this)));
printf(", bestWeight is %s\n", refCntWtd2str(bestWeight));
}
#endif
// Does this block have a smaller bbWeight value?
if (block->getBBWeight(this) > bestWeight)
{
#ifdef DEBUG
if (verbose)
{
printf("bbWeight too high\n");
}
#endif
continue;
}
// Don't use blocks that are exception handlers because
// inserting a new first statement will interface with
// the CATCHARG
if (handlerGetsXcptnObj(block->bbCatchTyp))
{
#ifdef DEBUG
if (verbose)
{
printf("Catch block\n");
}
#endif
continue;
}
// Don't use the BBJ_ALWAYS block marked with BBF_KEEP_BBJ_ALWAYS. These
// are used by EH code. The JIT can not generate code for such a block.
if (block->bbFlags & BBF_KEEP_BBJ_ALWAYS)
{
#if FEATURE_EH_FUNCLETS
// With funclets, this is only used for BBJ_CALLFINALLY/BBJ_ALWAYS pairs. For x86, it is also used
// as the "final step" block for leaving finallys.
assert((block->bbPrev != nullptr) && block->bbPrev->isBBCallAlwaysPair());
#endif // FEATURE_EH_FUNCLETS
#ifdef DEBUG
if (verbose)
{
printf("Internal EH BBJ_ALWAYS block\n");
}
#endif
continue;
}
// This block will be the new candidate for the insert point
// for the new assignment
CLANG_FORMAT_COMMENT_ANCHOR;
#ifdef DEBUG
if (verbose)
{
printf("new bestBlock\n");
}
#endif
bestBlock = block;
bestWeight = block->getBBWeight(this);
}
// If there is a use of the variable in this block
// then we insert the assignment at the beginning
// otherwise we insert the statement at the end
CLANG_FORMAT_COMMENT_ANCHOR;
#ifdef DEBUG
if (verbose)
{
printf(" Insert copy at the %s of BB%02u\n",
(BlockSetOps::IsEmpty(this, paramImportantUseDom) ||
BlockSetOps::IsMember(this, varDsc->lvRefBlks, bestBlock->bbNum))
? "start"
: "end",
bestBlock->bbNum);
}
#endif
if (BlockSetOps::IsEmpty(this, paramImportantUseDom) ||
BlockSetOps::IsMember(this, varDsc->lvRefBlks, bestBlock->bbNum))
{
stmt = fgInsertStmtAtBeg(bestBlock, copyAsgn);
}
else
{
stmt = fgInsertStmtNearEnd(bestBlock, copyAsgn);
}
/* Increment its lvRefCnt and lvRefCntWtd */
lvaTable[lclNum].incRefCnts(fgFirstBB->getBBWeight(this), this);
/* Increment its lvRefCnt and lvRefCntWtd */
lvaTable[copyLclNum].incRefCnts(fgFirstBB->getBBWeight(this), this);
}
else
{
noway_assert(varDsc->lvDefStmt != nullptr);
/* Locate the assignment to varDsc in the lvDefStmt */
stmt = varDsc->lvDefStmt;
noway_assert(stmt->gtOper == GT_STMT);
optAddCopyLclNum = lclNum; // in
optAddCopyAsgnNode = nullptr; // out
fgWalkTreePre(&stmt->gtStmt.gtStmtExpr, Compiler::optAddCopiesCallback, (void*)this, false);
noway_assert(optAddCopyAsgnNode);
GenTree* tree = optAddCopyAsgnNode;
GenTree* op1 = tree->gtOp.gtOp1;
noway_assert(tree && op1 && tree->OperIsAssignment() && (op1->gtOper == GT_LCL_VAR) &&
(op1->gtLclVarCommon.gtLclNum == lclNum));
/* TODO-Review: BB_UNITY_WEIGHT is not the correct block weight */
unsigned blockWeight = BB_UNITY_WEIGHT;
/* Increment its lvRefCnt and lvRefCntWtd twice */
lvaTable[copyLclNum].incRefCnts(blockWeight, this);
lvaTable[copyLclNum].incRefCnts(blockWeight, this);
/* Assign the old expression into the new temp */
GenTree* newAsgn = gtNewTempAssign(copyLclNum, tree->gtOp.gtOp2);
/* Copy the new temp to op1 */
GenTree* copyAsgn = gtNewAssignNode(op1, gtNewLclvNode(copyLclNum, typ));
/* Change the tree to a GT_COMMA with the two assignments as child nodes */
tree->gtBashToNOP();
tree->ChangeOper(GT_COMMA);
tree->gtOp.gtOp1 = newAsgn;
tree->gtOp.gtOp2 = copyAsgn;
tree->gtFlags |= (newAsgn->gtFlags & GTF_ALL_EFFECT);
tree->gtFlags |= (copyAsgn->gtFlags & GTF_ALL_EFFECT);
}
#ifdef DEBUG
if (verbose)
{
printf("\nIntroducing a new copy for V%02u\n", lclNum);
gtDispTree(stmt->gtStmt.gtStmtExpr);
printf("\n");
}
#endif
}
}
//------------------------------------------------------------------------------
// GetAssertionDep: Retrieve the assertions on this local variable
//
// Arguments:
// lclNum - The local var id.
//
// Return Value:
// The dependent assertions (assertions using the value of the local var)
// of the local var.
//
ASSERT_TP& Compiler::GetAssertionDep(unsigned lclNum)
{
JitExpandArray<ASSERT_TP>& dep = *optAssertionDep;
if (dep[lclNum] == nullptr)
{
dep[lclNum] = BitVecOps::MakeEmpty(apTraits);
}
return dep[lclNum];
}
/*****************************************************************************
*
* Initialize the assertion prop bitset traits and the default bitsets.
*/
void Compiler::optAssertionTraitsInit(AssertionIndex assertionCount)
{
apTraits = new (this, CMK_AssertionProp) BitVecTraits(assertionCount, this);
apFull = BitVecOps::MakeFull(apTraits);
}
/*****************************************************************************
*
* Initialize the assertion prop tracking logic.
*/
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 = _countof(countFunc) - 1;
const unsigned codeSize = info.compILCodeSize / 512;
optMaxAssertionCount = countFunc[isLocalProp ? lowerBound : min(upperBound, codeSize)];
optLocalAssertionProp = isLocalProp;
optAssertionTabPrivate = new (this, CMK_AssertionProp) AssertionDsc[optMaxAssertionCount];
optComplementaryAssertionMap =
new (this, CMK_AssertionProp) AssertionIndex[optMaxAssertionCount + 1](); // zero-inited (NO_ASSERTION_INDEX)
assert(NO_ASSERTION_INDEX == 0);
if (!isLocalProp)
{
optValueNumToAsserts = new (getAllocator()) ValueNumToAssertsMap(getAllocator());
}
if (optAssertionDep == nullptr)
{
optAssertionDep = new (this, CMK_AssertionProp) JitExpandArray<ASSERT_TP>(getAllocator(), max(1, lvaCount));
}
optAssertionTraitsInit(optMaxAssertionCount);
optAssertionCount = 0;
optAssertionPropagated = false;
bbJtrueAssertionOut = nullptr;
}
#ifdef DEBUG
void Compiler::optPrintAssertion(AssertionDsc* curAssertion, AssertionIndex assertionIndex /* =0 */)
{
if (curAssertion->op1.kind == O1K_EXACT_TYPE)
{
printf("Type ");
}
else if (curAssertion->op1.kind == O1K_ARR_BND)
{
printf("ArrBnds ");
}
else if (curAssertion->op1.kind == O1K_SUBTYPE)
{
printf("Subtype ");
}
else if (curAssertion->op2.kind == O2K_LCLVAR_COPY)
{
printf("Copy ");
}
else if ((curAssertion->op2.kind == O2K_CONST_INT) || (curAssertion->op2.kind == O2K_CONST_LONG) ||
(curAssertion->op2.kind == O2K_CONST_DOUBLE))
{
printf("Constant ");
}
else if (curAssertion->op2.kind == O2K_SUBRANGE)
{
printf("Subrange ");
}
else
{
printf("?assertion classification? ");
}
printf("Assertion: ");
if (!optLocalAssertionProp)
{
printf("(%d, %d) ", curAssertion->op1.vn, curAssertion->op2.vn);
}
if (!optLocalAssertionProp)
{
printf("(" STR_VN "%x," STR_VN "%x) ", curAssertion->op1.vn, curAssertion->op2.vn);
}
if ((curAssertion->op1.kind == O1K_LCLVAR) || (curAssertion->op1.kind == O1K_EXACT_TYPE) ||
(curAssertion->op1.kind == O1K_SUBTYPE))
{
printf("V%02u", curAssertion->op1.lcl.lclNum);
if (curAssertion->op1.lcl.ssaNum != SsaConfig::RESERVED_SSA_NUM)
{
printf(".%02u", curAssertion->op1.lcl.ssaNum);
}
}
else if (curAssertion->op1.kind == O1K_ARR_BND)
{
printf("[idx:");
vnStore->vnDump(this, curAssertion->op1.bnd.vnIdx);
printf(";len:");
vnStore->vnDump(this, curAssertion->op1.bnd.vnLen);
printf("]");
}
else if (curAssertion->op1.kind == O1K_BOUND_OPER_BND)
{
printf("Oper_Bnd");
vnStore->vnDump(this, curAssertion->op1.vn);
}
else if (curAssertion->op1.kind == O1K_BOUND_LOOP_BND)
{
printf("Loop_Bnd");
vnStore->vnDump(this, curAssertion->op1.vn);
}
else if (curAssertion->op1.kind == O1K_CONSTANT_LOOP_BND)
{
printf("Loop_Bnd");
vnStore->vnDump(this, curAssertion->op1.vn);
}
else if (curAssertion->op1.kind == O1K_VALUE_NUMBER)
{
printf("Value_Number");
vnStore->vnDump(this, curAssertion->op1.vn);
}
else
{
printf("?op1.kind?");
}
if (curAssertion->assertionKind == OAK_SUBRANGE)
{
printf(" in ");
}
else if (curAssertion->assertionKind == OAK_EQUAL)
{
if (curAssertion->op1.kind == O1K_LCLVAR)
{
printf(" == ");
}
else
{
printf(" is ");
}
}
else if (curAssertion->assertionKind == OAK_NO_THROW)
{
printf(" in range ");
}
else if (curAssertion->assertionKind == OAK_NOT_EQUAL)
{
if (curAssertion->op1.kind == O1K_LCLVAR)
{
printf(" != ");
}
else
{
printf(" is not ");
}
}
else
{
printf(" ?assertionKind? ");
}
if (curAssertion->op1.kind != O1K_ARR_BND)
{
switch (curAssertion->op2.kind)
{
case O2K_LCLVAR_COPY:
printf("V%02u", curAssertion->op2.lcl.lclNum);
if (curAssertion->op1.lcl.ssaNum != SsaConfig::RESERVED_SSA_NUM)
{
printf(".%02u", curAssertion->op1.lcl.ssaNum);
}
break;
case O2K_CONST_INT:
case O2K_IND_CNS_INT:
if (curAssertion->op1.kind == O1K_EXACT_TYPE)
{
printf("Exact Type MT(%08X)", dspPtr(curAssertion->op2.u1.iconVal));
assert(curAssertion->op2.u1.iconFlags != 0);
}
else if (curAssertion->op1.kind == O1K_SUBTYPE)
{
printf("MT(%08X)", dspPtr(curAssertion->op2.u1.iconVal));
assert(curAssertion->op2.u1.iconFlags != 0);
}
else if (curAssertion->op1.kind == O1K_BOUND_OPER_BND)
{
assert(!optLocalAssertionProp);
vnStore->vnDump(this, curAssertion->op2.vn);
}
else if (curAssertion->op1.kind == O1K_BOUND_LOOP_BND)
{
assert(!optLocalAssertionProp);
vnStore->vnDump(this, curAssertion->op2.vn);
}
else if (curAssertion->op1.kind == O1K_CONSTANT_LOOP_BND)
{
assert(!optLocalAssertionProp);
vnStore->vnDump(this, curAssertion->op2.vn);
}
else
{
var_types op1Type;
if (curAssertion->op1.kind == O1K_VALUE_NUMBER)
{
op1Type = vnStore->TypeOfVN(curAssertion->op1.vn);
}
else
{
unsigned lclNum = curAssertion->op1.lcl.lclNum;
assert(lclNum < lvaCount);
LclVarDsc* varDsc = lvaTable + lclNum;
op1Type = varDsc->lvType;
}
if (op1Type == TYP_REF)
{
assert(curAssertion->op2.u1.iconVal == 0);
printf("null");
}
else
{
if ((curAssertion->op2.u1.iconFlags & GTF_ICON_HDL_MASK) != 0)
{
printf("[%08p]", dspPtr(curAssertion->op2.u1.iconVal));
}
else
{
printf("%d", curAssertion->op2.u1.iconVal);
}
}
}
break;
case O2K_CONST_LONG:
printf("0x%016llx", curAssertion->op2.lconVal);
break;
case O2K_CONST_DOUBLE:
if (*((__int64*)&curAssertion->op2.dconVal) == (__int64)I64(0x8000000000000000))
{
printf("-0.00000");
}
else
{
printf("%#lg", curAssertion->op2.dconVal);
}
break;
case O2K_SUBRANGE:
printf("[%d..%d]", curAssertion->op2.u2.loBound, curAssertion->op2.u2.hiBound);
break;
default:
printf("?op2.kind?");
break;
}
}
if (assertionIndex > 0)
{
printf(" index=#%02u, mask=", assertionIndex);
printf("%s", BitVecOps::ToString(apTraits, BitVecOps::MakeSingleton(apTraits, assertionIndex - 1)));
}
printf("\n");
}
#endif // DEBUG
/******************************************************************************
*
* Helper to retrieve the "assertIndex" assertion. Note that assertIndex 0
* is NO_ASSERTION_INDEX and "optAssertionCount" is the last valid index.
*
*/
Compiler::AssertionDsc* Compiler::optGetAssertion(AssertionIndex assertIndex)
{
assert(NO_ASSERTION_INDEX == 0);
assert(assertIndex != NO_ASSERTION_INDEX);
assert(assertIndex <= optAssertionCount);
AssertionDsc* assertion = &optAssertionTabPrivate[assertIndex - 1];
#ifdef DEBUG
optDebugCheckAssertion(assertion);
#endif
return assertion;
}
/*****************************************************************************
*
* A simple helper routine so not all callers need to supply a AssertionDsc*
* if they don't care about it. Refer overloaded method optCreateAssertion.
*
*/
AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, optAssertionKind assertionKind)
{
AssertionDsc assertionDsc;
return optCreateAssertion(op1, op2, assertionKind, &assertionDsc);
}
/*****************************************************************************
*
* We attempt to create the following assertion:
*
* op1 assertionKind op2
*
* If we can create the assertion then update 'assertion' if we are
* unsuccessful assertion->assertionKind will be OAK_INVALID. If we are
* successful in creating the assertion we call optAddAssertion which adds
* the assertion to our assertion table.
*
* If we are able to create the assertion the return value is the
* assertionIndex for this assertion otherwise the return value is
* NO_ASSERTION_INDEX and we could not create the assertion.
*
*/
AssertionIndex Compiler::optCreateAssertion(GenTree* op1,
GenTree* op2,
optAssertionKind assertionKind,
AssertionDsc* assertion)
{
memset(assertion, 0, sizeof(AssertionDsc));
//
// If we cannot create an assertion using op1 and op2 then the assertionKind
// must be OAK_INVALID, so we initialize it to OAK_INVALID and only change it
// to a valid assertion when everything is good.
//
assertion->assertionKind = OAK_INVALID;
bool haveArgs = false;
var_types toType;
if (op1->gtOper == GT_ARR_BOUNDS_CHECK)
{
if (assertionKind == OAK_NO_THROW)
{
GenTreeBoundsChk* arrBndsChk = op1->AsBoundsChk();
assertion->assertionKind = assertionKind;
assertion->op1.kind = O1K_ARR_BND;
assertion->op1.bnd.vnIdx = arrBndsChk->gtIndex->gtVNPair.GetConservative();
assertion->op1.bnd.vnLen = arrBndsChk->gtArrLen->gtVNPair.GetConservative();
goto DONE_ASSERTION;
}
}
//
// Did we receive Helper call args?
//
if (op1->gtOper == GT_LIST)
{
if (op2->gtOper != GT_LIST)
{
goto DONE_ASSERTION; // Don't make an assertion
}
op1 = op1->gtOp.gtOp1;
op2 = op2->gtOp.gtOp1;
haveArgs = true;
}
//
// Are we trying to make a non-null assertion?
//
if (op2 == nullptr)
{
assert(haveArgs == false);
//
// Must an OAK_NOT_EQUAL assertion
//
noway_assert(assertionKind == OAK_NOT_EQUAL);
//
// Set op1 to the instance pointer of the indirection
//
ssize_t offset = 0;
while ((op1->gtOper == GT_ADD) && (op1->gtType == TYP_BYREF))
{
if (op1->gtGetOp2()->IsCnsIntOrI())
{
offset += op1->gtGetOp2()->gtIntCon.gtIconVal;
op1 = op1->gtGetOp1();
}
else if (op1->gtGetOp1()->IsCnsIntOrI())
{
offset += op1->gtGetOp1()->gtIntCon.gtIconVal;
op1 = op1->gtGetOp2();
}
else
{
break;
}
}
if (fgIsBigOffset(offset) || op1->gtOper != GT_LCL_VAR)
{
goto DONE_ASSERTION; // Don't make an assertion
}
unsigned lclNum = op1->gtLclVarCommon.gtLclNum;
noway_assert(lclNum < lvaCount);
LclVarDsc* lclVar = &lvaTable[lclNum];
ValueNum vn;
//
// We only perform null-checks on GC refs
// so only make non-null assertions about GC refs
//
if (lclVar->TypeGet() != TYP_REF)
{
if (optLocalAssertionProp || (lclVar->TypeGet() != TYP_BYREF))
{
goto DONE_ASSERTION; // Don't make an assertion
}
vn = op1->gtVNPair.GetConservative();
VNFuncApp funcAttr;
// Try to get value number corresponding to the GC ref of the indirection
while (vnStore->GetVNFunc(vn, &funcAttr) && (funcAttr.m_func == (VNFunc)GT_ADD) &&
(vnStore->TypeOfVN(vn) == TYP_BYREF))
{
if (vnStore->IsVNConstant(funcAttr.m_args[1]) &&
varTypeIsIntegral(vnStore->TypeOfVN(funcAttr.m_args[1])))
{
offset += vnStore->CoercedConstantValue<ssize_t>(funcAttr.m_args[1]);
vn = funcAttr.m_args[0];
}
else if (vnStore->IsVNConstant(funcAttr.m_args[0]) &&
varTypeIsIntegral(vnStore->TypeOfVN(funcAttr.m_args[0])))
{
offset += vnStore->CoercedConstantValue<ssize_t>(funcAttr.m_args[0]);
vn = funcAttr.m_args[1];
}
else
{
break;
}
}
if (fgIsBigOffset(offset) || (vnStore->TypeOfVN(vn) != TYP_REF))
{
goto DONE_ASSERTION; // Don't make an assertion
}
assertion->op1.kind = O1K_VALUE_NUMBER;
}
else
{
// If the local variable has its address exposed then bail
if (lclVar->lvAddrExposed)
{
goto DONE_ASSERTION; // Don't make an assertion
}
assertion->op1.kind = O1K_LCLVAR;
assertion->op1.lcl.lclNum = lclNum;
assertion->op1.lcl.ssaNum = op1->AsLclVarCommon()->GetSsaNum();
vn = op1->gtVNPair.GetConservative();
}
assertion->op1.vn = vn;
assertion->assertionKind = assertionKind;
assertion->op2.kind = O2K_CONST_INT;
assertion->op2.vn = ValueNumStore::VNForNull();
assertion->op2.u1.iconVal = 0;
assertion->op2.u1.iconFlags = 0;
#ifdef _TARGET_64BIT_
assertion->op2.u1.iconFlags |= 1; // Signify that this is really TYP_LONG
#endif // _TARGET_64BIT_
}
//
// Are we making an assertion about a local variable?
//
else if (op1->gtOper == GT_LCL_VAR)
{
unsigned lclNum = op1->gtLclVarCommon.gtLclNum;
noway_assert(lclNum < lvaCount);
LclVarDsc* lclVar = &lvaTable[lclNum];
// If the local variable has its address exposed then bail
if (lclVar->lvAddrExposed)
{
goto DONE_ASSERTION; // Don't make an assertion
}
if (haveArgs)
{
//
// Must either be an OAK_EQUAL or an OAK_NOT_EQUAL assertion
//
if ((assertionKind != OAK_EQUAL) && (assertionKind != OAK_NOT_EQUAL))
{
goto DONE_ASSERTION; // Don't make an assertion
}
if (op2->gtOper == GT_IND)
{
op2 = op2->gtOp.gtOp1;
assertion->op2.kind = O2K_IND_CNS_INT;
}
else
{
assertion->op2.kind = O2K_CONST_INT;
}
if (op2->gtOper != GT_CNS_INT)
{
goto DONE_ASSERTION; // Don't make an assertion
}
//
// TODO-CQ: Check for Sealed class and change kind to O1K_EXACT_TYPE
// And consider the special cases, like CORINFO_FLG_SHAREDINST or CORINFO_FLG_VARIANCE
// where a class can be sealed, but they don't behave as exact types because casts to
// non-base types sometimes still succeed.
//
assertion->op1.kind = O1K_SUBTYPE;
assertion->op1.lcl.lclNum = lclNum;
assertion->op1.vn = op1->gtVNPair.GetConservative();
assertion->op1.lcl.ssaNum = op1->AsLclVarCommon()->GetSsaNum();
assertion->op2.u1.iconVal = op2->gtIntCon.gtIconVal;
assertion->op2.vn = op2->gtVNPair.GetConservative();
assertion->op2.u1.iconFlags = op2->GetIconHandleFlag();
//
// Ok everything has been set and the assertion looks good
//
assertion->assertionKind = assertionKind;
}
else // !haveArgs
{
/* Skip over a GT_COMMA node(s), if necessary */
while (op2->gtOper == GT_COMMA)
{
op2 = op2->gtOp.gtOp2;
}
assertion->op1.kind = O1K_LCLVAR;
assertion->op1.lcl.lclNum = lclNum;
assertion->op1.vn = op1->gtVNPair.GetConservative();
assertion->op1.lcl.ssaNum = op1->AsLclVarCommon()->GetSsaNum();
switch (op2->gtOper)
{
optOp2Kind op2Kind;
//
// No Assertion
//
default:
goto DONE_ASSERTION; // Don't make an assertion
//
// Constant Assertions
//
case GT_CNS_INT:
op2Kind = O2K_CONST_INT;
goto CNS_COMMON;
case GT_CNS_LNG:
op2Kind = O2K_CONST_LONG;
goto CNS_COMMON;
case GT_CNS_DBL:
op2Kind = O2K_CONST_DOUBLE;
goto CNS_COMMON;
CNS_COMMON:
{
//
// Must either be an OAK_EQUAL or an OAK_NOT_EQUAL assertion
//
if ((assertionKind != OAK_EQUAL) && (assertionKind != OAK_NOT_EQUAL))
{
goto DONE_ASSERTION; // Don't make an assertion
}
// If the LclVar is a TYP_LONG then we only make
// assertions where op2 is also TYP_LONG
//
if ((lclVar->TypeGet() == TYP_LONG) && (op2->TypeGet() != TYP_LONG))
{
goto DONE_ASSERTION; // Don't make an assertion
}
assertion->op2.kind = op2Kind;
assertion->op2.lconVal = 0;
assertion->op2.vn = op2->gtVNPair.GetConservative();
if (op2->gtOper == GT_CNS_INT)
{
#ifdef _TARGET_ARM_
// Do not Constant-Prop large constants for ARM
if (!codeGen->validImmForMov(op2->gtIntCon.gtIconVal))
{
goto DONE_ASSERTION; // Don't make an assertion
}
#endif // _TARGET_ARM_
assertion->op2.u1.iconVal = op2->gtIntCon.gtIconVal;
assertion->op2.u1.iconFlags = op2->GetIconHandleFlag();
#ifdef _TARGET_64BIT_
if (op2->TypeGet() == TYP_LONG || op2->TypeGet() == TYP_BYREF)
{
assertion->op2.u1.iconFlags |= 1; // Signify that this is really TYP_LONG
}
#endif // _TARGET_64BIT_
}
else if (op2->gtOper == GT_CNS_LNG)
{
assertion->op2.lconVal = op2->gtLngCon.gtLconVal;
}
else
{
noway_assert(op2->gtOper == GT_CNS_DBL);
/* If we have an NaN value then don't record it */
if (_isnan(op2->gtDblCon.gtDconVal))
{
goto DONE_ASSERTION; // Don't make an assertion
}
assertion->op2.dconVal = op2->gtDblCon.gtDconVal;
}
//
// Ok everything has been set and the assertion looks good
//
assertion->assertionKind = assertionKind;
}
break;
//
// Copy Assertions
//
case GT_LCL_VAR:
{
//
// Must either be an OAK_EQUAL or an OAK_NOT_EQUAL assertion
//
if ((assertionKind != OAK_EQUAL) && (assertionKind != OAK_NOT_EQUAL))
{
goto DONE_ASSERTION; // Don't make an assertion
}
unsigned lclNum2 = op2->gtLclVarCommon.gtLclNum;
noway_assert(lclNum2 < lvaCount);
LclVarDsc* lclVar2 = &lvaTable[lclNum2];
// If the two locals are the same then bail
if (lclNum == lclNum2)
{
goto DONE_ASSERTION; // Don't make an assertion
}
// If the types are different then bail */
if (lclVar->lvType != lclVar2->lvType)
{
goto DONE_ASSERTION; // Don't make an assertion
}
// If the local variable has its address exposed then bail
if (lclVar2->lvAddrExposed)
{
goto DONE_ASSERTION; // Don't make an assertion
}
assertion->op2.kind = O2K_LCLVAR_COPY;
assertion->op2.lcl.lclNum = lclNum2;
assertion->op2.vn = op2->gtVNPair.GetConservative();
assertion->op2.lcl.ssaNum = op2->AsLclVarCommon()->GetSsaNum();
//
// Ok everything has been set and the assertion looks good
//
assertion->assertionKind = assertionKind;
}
break;
// Subrange Assertions
case GT_EQ:
case GT_NE:
case GT_LT:
case GT_LE:
case GT_GT:
case GT_GE:
/* Assigning the result of a RELOP, we can add a boolean subrange assertion */
toType = TYP_BOOL;
goto SUBRANGE_COMMON;
case GT_CLS_VAR:
/* Assigning the result of an indirection into a LCL_VAR, see if we can add a subrange assertion */
toType = op2->gtType;
goto SUBRANGE_COMMON;
case GT_ARR_ELEM:
/* Assigning the result of an indirection into a LCL_VAR, see if we can add a subrange assertion */
toType = op2->gtType;
goto SUBRANGE_COMMON;
case GT_LCL_FLD:
/* Assigning the result of an indirection into a LCL_VAR, see if we can add a subrange assertion */
toType = op2->gtType;
goto SUBRANGE_COMMON;
case GT_IND:
/* Assigning the result of an indirection into a LCL_VAR, see if we can add a subrange assertion */
toType = op2->gtType;
goto SUBRANGE_COMMON;
case GT_CAST:
{
if (lvaTable[lclNum].lvIsStructField && lvaTable[lclNum].lvNormalizeOnLoad())
{
// Keep the cast on small struct fields.
goto DONE_ASSERTION; // Don't make an assertion
}
toType = op2->CastToType();
SUBRANGE_COMMON:
if ((assertionKind != OAK_SUBRANGE) && (assertionKind != OAK_EQUAL))
{
goto DONE_ASSERTION; // Don't make an assertion
}
if (varTypeIsFloating(op1->TypeGet()))
{
// We don't make assertions on a cast from floating point
goto DONE_ASSERTION;
}
switch (toType)
{
case TYP_BOOL:
case TYP_BYTE:
case TYP_UBYTE:
case TYP_SHORT:
case TYP_USHORT:
#ifdef _TARGET_64BIT_
case TYP_UINT:
case TYP_INT:
#endif // _TARGET_64BIT_
assertion->op2.u2.loBound = AssertionDsc::GetLowerBoundForIntegralType(toType);
assertion->op2.u2.hiBound = AssertionDsc::GetUpperBoundForIntegralType(toType);
break;
default:
goto DONE_ASSERTION; // Don't make an assertion
}
assertion->op2.kind = O2K_SUBRANGE;
assertion->assertionKind = OAK_SUBRANGE;
}
break;
}
} // else // !haveArgs
} // if (op1->gtOper == GT_LCL_VAR)
//
// Are we making an IsType assertion?
//
else if (op1->gtOper == GT_IND)
{
op1 = op1->gtOp.gtOp1;
//
// Is this an indirection of a local variable?
//
if (op1->gtOper == GT_LCL_VAR)
{
unsigned lclNum = op1->gtLclVarCommon.gtLclNum;
noway_assert(lclNum < lvaCount);
LclVarDsc* lclVar = &lvaTable[lclNum];
// If the local variable has its address exposed then bail
if (fgExcludeFromSsa(lclNum))
{
goto DONE_ASSERTION;
}
// If we have an typeHnd indirection then op1 must be a TYP_REF
// and the indirection must produce a TYP_I
//
if (op1->gtType != TYP_REF)
{
goto DONE_ASSERTION; // Don't make an assertion
}
assertion->op1.kind = O1K_EXACT_TYPE;
assertion->op1.lcl.lclNum = lclNum;
assertion->op1.vn = op1->gtVNPair.GetConservative();
assertion->op1.lcl.ssaNum = op1->AsLclVarCommon()->GetSsaNum();
assert(assertion->op1.lcl.ssaNum == SsaConfig::RESERVED_SSA_NUM ||
assertion->op1.vn ==
lvaTable[lclNum].GetPerSsaData(assertion->op1.lcl.ssaNum)->m_vnPair.GetConservative());
ssize_t cnsValue = 0;
unsigned iconFlags = 0;
// Ngen case
if (op2->gtOper == GT_IND)
{
if (!optIsTreeKnownIntValue(!optLocalAssertionProp, op2->gtOp.gtOp1, &cnsValue, &iconFlags))
{
goto DONE_ASSERTION; // Don't make an assertion
}
assertion->assertionKind = assertionKind;
assertion->op2.kind = O2K_IND_CNS_INT;
assertion->op2.u1.iconVal = cnsValue;
assertion->op2.vn = op2->gtOp.gtOp1->gtVNPair.GetConservative();
/* iconFlags should only contain bits in GTF_ICON_HDL_MASK */
assert((iconFlags & ~GTF_ICON_HDL_MASK) == 0);
assertion->op2.u1.iconFlags = iconFlags;
#ifdef _TARGET_64BIT_
if (op2->gtOp.gtOp1->TypeGet() == TYP_LONG)
{
assertion->op2.u1.iconFlags |= 1; // Signify that this is really TYP_LONG
}
#endif // _TARGET_64BIT_
}
// JIT case
else if (optIsTreeKnownIntValue(!optLocalAssertionProp, op2, &cnsValue, &iconFlags))
{
assertion->assertionKind = assertionKind;
assertion->op2.kind = O2K_IND_CNS_INT;
assertion->op2.u1.iconVal = cnsValue;
assertion->op2.vn = op2->gtVNPair.GetConservative();
/* iconFlags should only contain bits in GTF_ICON_HDL_MASK */
assert((iconFlags & ~GTF_ICON_HDL_MASK) == 0);
assertion->op2.u1.iconFlags = iconFlags;
#ifdef _TARGET_64BIT_
if (op2->TypeGet() == TYP_LONG)
{
assertion->op2.u1.iconFlags |= 1; // Signify that this is really TYP_LONG
}
#endif // _TARGET_64BIT_
}
else
{
goto DONE_ASSERTION; // Don't make an assertion
}
}
}
DONE_ASSERTION:
if (assertion->assertionKind == OAK_INVALID)
{
return NO_ASSERTION_INDEX;
}
if (!optLocalAssertionProp)
{
if ((assertion->op1.vn == ValueNumStore::NoVN) || (assertion->op2.vn == ValueNumStore::NoVN) ||
(assertion->op1.vn == ValueNumStore::VNForVoid()) || (assertion->op2.vn == ValueNumStore::VNForVoid()))
{
return NO_ASSERTION_INDEX;
}
// TODO: only copy assertions rely on valid SSA number so we could generate more assertions here
if ((assertion->op1.kind != O1K_VALUE_NUMBER) && (assertion->op1.lcl.ssaNum == SsaConfig::RESERVED_SSA_NUM))
{
return NO_ASSERTION_INDEX;
}
}
// Now add the assertion to our assertion table
noway_assert(assertion->op1.kind != O1K_INVALID);
noway_assert(assertion->op1.kind == O1K_ARR_BND || assertion->op2.kind != O2K_INVALID);
return optAddAssertion(assertion);
}
/*****************************************************************************
*
* If tree is a constant node holding an integral value, retrieve the value in
* pConstant. If the method returns true, pConstant holds the appropriate
* constant. Set "vnBased" to true to indicate local or global assertion prop.
* "pFlags" indicates if the constant is a handle marked by GTF_ICON_HDL_MASK.
*/
bool Compiler::optIsTreeKnownIntValue(bool vnBased, GenTree* tree, ssize_t* pConstant, unsigned* pFlags)
{
// Is Local assertion prop?
if (!vnBased)
{
if (tree->OperGet() == GT_CNS_INT)
{
*pConstant = tree->gtIntCon.IconValue();
*pFlags = tree->GetIconHandleFlag();
return true;
}
#ifdef _TARGET_64BIT_
// Just to be clear, get it from gtLconVal rather than
// overlapping gtIconVal.
else if (tree->OperGet() == GT_CNS_LNG)
{
*pConstant = tree->gtLngCon.gtLconVal;
*pFlags = tree->GetIconHandleFlag();
return true;
}
#endif
return false;
}
// Global assertion prop
if (!vnStore->IsVNConstant(tree->gtVNPair.GetConservative()))
{
return false;
}
ValueNum vn = tree->gtVNPair.GetConservative();
var_types vnType = vnStore->TypeOfVN(vn);
if (vnType == TYP_INT)
{
*pConstant = vnStore->ConstantValue<int>(vn);
*pFlags = vnStore->IsVNHandle(vn) ? vnStore->GetHandleFlags(vn) : 0;
return true;
}
#ifdef _TARGET_64BIT_
else if (vnType == TYP_LONG)
{
*pConstant = vnStore->ConstantValue<INT64>(vn);
*pFlags = vnStore->IsVNHandle(vn) ? vnStore->GetHandleFlags(vn) : 0;
return true;
}
#endif
return false;
}
#ifdef DEBUG
/*****************************************************************************
*
* Print the assertions related to a VN for all VNs.
*
*/
void Compiler::optPrintVnAssertionMapping()
{
printf("\nVN Assertion Mapping\n");
printf("---------------------\n");
for (ValueNumToAssertsMap::KeyIterator ki = optValueNumToAsserts->Begin(); !ki.Equal(optValueNumToAsserts->End());
++ki)
{
printf("(%d => ", ki.Get());
printf("%s)\n", BitVecOps::ToString(apTraits, ki.GetValue()));
}
}
#endif
/*****************************************************************************
*
* Maintain a map "optValueNumToAsserts" i.e., vn -> to set of assertions
* about that VN. Given "assertions" about a "vn" add it to the previously
* mapped assertions about that "vn."
*/
void Compiler::optAddVnAssertionMapping(ValueNum vn, AssertionIndex index)
{
ASSERT_TP* cur = optValueNumToAsserts->LookupPointer(vn);
if (cur == nullptr)
{
optValueNumToAsserts->Set(vn, BitVecOps::MakeSingleton(apTraits, index - 1));
}
else
{
BitVecOps::AddElemD(apTraits, *cur, index - 1);
}
}
/*****************************************************************************
* Statically if we know that this assertion's VN involves a NaN don't bother
* wasting an assertion table slot.
*/
bool Compiler::optAssertionVnInvolvesNan(AssertionDsc* assertion)
{
if (optLocalAssertionProp)
{
return false;
}
static const int SZ = 2;
ValueNum vns[SZ] = {assertion->op1.vn, assertion->op2.vn};
for (int i = 0; i < SZ; ++i)
{
if (vnStore->IsVNConstant(vns[i]))
{
var_types type = vnStore->TypeOfVN(vns[i]);
if ((type == TYP_FLOAT && _isnan(vnStore->ConstantValue<float>(vns[i])) != 0) ||
(type == TYP_DOUBLE && _isnan(vnStore->ConstantValue<double>(vns[i])) != 0))
{
return true;
}
}
}
return false;
}
/*****************************************************************************
*
* Given an assertion add it to the assertion table
*
* If it is already in the assertion table return the assertionIndex that
* we use to refer to this element.
* Otherwise add it to the assertion table ad return the assertionIndex that
* we use to refer to this element.
* If we need to add to the table and the table is full return the value zero
*/
AssertionIndex Compiler::optAddAssertion(AssertionDsc* newAssertion)
{
noway_assert(newAssertion->assertionKind != OAK_INVALID);
// Even though the propagation step takes care of NaN, just a check
// to make sure there is no slot involving a NaN.
if (optAssertionVnInvolvesNan(newAssertion))
{
JITDUMP("Assertion involved Nan not adding\n");
return NO_ASSERTION_INDEX;
}
// 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))
{
return index;
}
}
// Check if we are within max count.
if (optAssertionCount >= optMaxAssertionCount)
{
return NO_ASSERTION_INDEX;
}
optAssertionTabPrivate[optAssertionCount] = *newAssertion;
optAssertionCount++;
#ifdef DEBUG
if (verbose)
{
printf("GenTreeNode creates assertion:\n");
gtDispTree(optAssertionPropCurrentTree, nullptr, nullptr, true);
printf(optLocalAssertionProp ? "In BB%02u New Local " : "In BB%02u New Global ", compCurBB->bbNum);
optPrintAssertion(newAssertion, optAssertionCount);
}
#endif // DEBUG
// Assertion mask bits are [index + 1].
if (optLocalAssertionProp)
{
assert(newAssertion->op1.kind == O1K_LCLVAR);
// Mark the variables this index depends on
unsigned lclNum = newAssertion->op1.lcl.lclNum;
BitVecOps::AddElemD(apTraits, GetAssertionDep(lclNum), optAssertionCount - 1);
if (newAssertion->op2.kind == O2K_LCLVAR_COPY)
{
lclNum = newAssertion->op2.lcl.lclNum;
BitVecOps::AddElemD(apTraits, GetAssertionDep(lclNum), optAssertionCount - 1);
}
}
else
// If global assertion prop, then add it to the dependents map.
{
optAddVnAssertionMapping(newAssertion->op1.vn, optAssertionCount);
if (newAssertion->op2.kind == O2K_LCLVAR_COPY)
{
optAddVnAssertionMapping(newAssertion->op2.vn, optAssertionCount);
}
}
#ifdef DEBUG
optDebugCheckAssertions(optAssertionCount);
#endif
return optAssertionCount;
}
#ifdef DEBUG
void Compiler::optDebugCheckAssertion(AssertionDsc* assertion)
{
assert(assertion->assertionKind < OAK_COUNT);
assert(assertion->op1.kind < O1K_COUNT);
assert(assertion->op2.kind < O2K_COUNT);
// It would be good to check that op1.vn and op2.vn are valid value numbers.
switch (assertion->op1.kind)
{
case O1K_LCLVAR:
case O1K_EXACT_TYPE:
case O1K_SUBTYPE:
assert(assertion->op1.lcl.lclNum < lvaCount);
assert(optLocalAssertionProp || ((assertion->op1.lcl.ssaNum - SsaConfig::UNINIT_SSA_NUM) <
lvaTable[assertion->op1.lcl.lclNum].lvNumSsaNames));
break;
case O1K_ARR_BND:
// It would be good to check that bnd.vnIdx and bnd.vnLen are valid value numbers.
break;
case O1K_BOUND_OPER_BND:
case O1K_BOUND_LOOP_BND:
case O1K_CONSTANT_LOOP_BND:
case O1K_VALUE_NUMBER:
assert(!optLocalAssertionProp);
break;
default:
break;
}
switch (assertion->op2.kind)
{
case O2K_IND_CNS_INT:
case O2K_CONST_INT:
{
// The only flags that can be set are those in the GTF_ICON_HDL_MASK, or bit 0, which is
// used to indicate a long constant.
assert((assertion->op2.u1.iconFlags & ~(GTF_ICON_HDL_MASK | 1)) == 0);
switch (assertion->op1.kind)
{
case O1K_EXACT_TYPE:
case O1K_SUBTYPE:
assert(assertion->op2.u1.iconFlags != 0);
break;
case O1K_LCLVAR:
case O1K_ARR_BND:
assert((lvaTable[assertion->op1.lcl.lclNum].lvType != TYP_REF) || (assertion->op2.u1.iconVal == 0));
break;
case O1K_VALUE_NUMBER:
assert((vnStore->TypeOfVN(assertion->op1.vn) != TYP_REF) || (assertion->op2.u1.iconVal == 0));
break;
default:
break;
}
}
break;
default:
// for all other 'assertion->op2.kind' values we don't check anything
break;
}
}
/*****************************************************************************
*
* Verify that assertion prop related assumptions are valid. If "index"
* is 0 (i.e., NO_ASSERTION_INDEX) then verify all assertions in the table.
* If "index" is between 1 and optAssertionCount, then verify the assertion
* desc corresponding to "index."
*/
void Compiler::optDebugCheckAssertions(AssertionIndex index)
{
AssertionIndex start = (index == NO_ASSERTION_INDEX) ? 1 : index;
AssertionIndex end = (index == NO_ASSERTION_INDEX) ? optAssertionCount : index;
for (AssertionIndex ind = start; ind <= end; ++ind)
{
AssertionDsc* assertion = optGetAssertion(ind);
optDebugCheckAssertion(assertion);
}
}
#endif
/*****************************************************************************
*
* Given a "candidateAssertion", and the assertion operands op1 and op2,
* create a complementary assertion and add it to the assertion table,
* which can be retrieved using optFindComplementary(index)
*
*/
void Compiler::optCreateComplementaryAssertion(AssertionIndex assertionIndex, GenTree* op1, GenTree* op2)
{
if (assertionIndex == NO_ASSERTION_INDEX)
{
return;
}
AssertionDsc& candidateAssertion = *optGetAssertion(assertionIndex);
if (candidateAssertion.op1.kind == O1K_BOUND_OPER_BND || candidateAssertion.op1.kind == O1K_BOUND_LOOP_BND ||
candidateAssertion.op1.kind == O1K_CONSTANT_LOOP_BND)
{
AssertionDsc dsc = candidateAssertion;
dsc.assertionKind = dsc.assertionKind == OAK_EQUAL ? OAK_NOT_EQUAL : OAK_EQUAL;
optAddAssertion(&dsc);
return;
}
if (candidateAssertion.assertionKind == OAK_EQUAL)
{
AssertionIndex index = optCreateAssertion(op1, op2, OAK_NOT_EQUAL);
optMapComplementary(index, assertionIndex);
}
else if (candidateAssertion.assertionKind == OAK_NOT_EQUAL)
{
AssertionIndex index = optCreateAssertion(op1, op2, OAK_EQUAL);
optMapComplementary(index, assertionIndex);
}
// Are we making a subtype or exact type assertion?
if ((candidateAssertion.op1.kind == O1K_SUBTYPE) || (candidateAssertion.op1.kind == O1K_EXACT_TYPE))
{
// Did we recieve helper call args?
if (op1->gtOper == GT_LIST)
{
op1 = op1->gtOp.gtOp1;
}
optCreateAssertion(op1, nullptr, OAK_NOT_EQUAL);
}
}
/*****************************************************************************
*
* Create assertions for jtrue operands. Given operands "op1" and "op2" that
* are used in a conditional evaluation of a jtrue stmt, create assertions
* for the operands.
*/
AssertionIndex Compiler::optCreateJtrueAssertions(GenTree* op1, GenTree* op2, Compiler::optAssertionKind assertionKind)
{
AssertionDsc candidateAssertion;
AssertionIndex assertionIndex = optCreateAssertion(op1, op2, assertionKind, &candidateAssertion);
// Don't bother if we don't have an assertion on the JTrue False path. Current implementation
// allows for a complementary only if there is an assertion on the False path (tree->HasAssertion()).
if (assertionIndex != NO_ASSERTION_INDEX)
{
optCreateComplementaryAssertion(assertionIndex, op1, op2);
}
return assertionIndex;
}
AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree)
{
GenTree* relop = tree->gtGetOp1();
if ((relop->OperKind() & GTK_RELOP) == 0)
{
return NO_ASSERTION_INDEX;
}
GenTree* op1 = relop->gtGetOp1();
GenTree* op2 = relop->gtGetOp2();
ValueNum vn = op1->gtVNPair.GetConservative();
ValueNumStore::UnsignedCompareCheckedBoundInfo unsignedCompareBnd;
// Cases where op1 holds the upper bound arithmetic and op2 is 0.
// Loop condition like: "i < bnd +/-k == 0"
// Assertion: "i < bnd +/- k == 0"
if (vnStore->IsVNCompareCheckedBoundArith(vn) &&
op2->gtVNPair.GetConservative() == vnStore->VNZeroForType(op2->TypeGet()) &&
(relop->gtOper == GT_EQ || relop->gtOper == GT_NE))
{
AssertionDsc dsc;
dsc.assertionKind = relop->gtOper == GT_EQ ? OAK_EQUAL : OAK_NOT_EQUAL;
dsc.op1.kind = O1K_BOUND_OPER_BND;
dsc.op1.vn = vn;
dsc.op2.kind = O2K_CONST_INT;
dsc.op2.vn = vnStore->VNZeroForType(op2->TypeGet());
dsc.op2.u1.iconVal = 0;
dsc.op2.u1.iconFlags = 0;
AssertionIndex index = optAddAssertion(&dsc);
optCreateComplementaryAssertion(index, nullptr, nullptr);
return index;
}
// Cases where op1 holds the upper bound and op2 is 0.
// Loop condition like: "i < bnd == 0"
// Assertion: "i < bnd == false"
else if (vnStore->IsVNCompareCheckedBound(vn) &&
(op2->gtVNPair.GetConservative() == vnStore->VNZeroForType(op2->TypeGet())) &&
(relop->gtOper == GT_EQ || relop->gtOper == GT_NE))
{
AssertionDsc dsc;
dsc.assertionKind = relop->gtOper == GT_EQ ? OAK_EQUAL : OAK_NOT_EQUAL;
dsc.op1.kind = O1K_BOUND_LOOP_BND;
dsc.op1.vn = vn;
dsc.op2.kind = O2K_CONST_INT;
dsc.op2.vn = vnStore->VNZeroForType(op2->TypeGet());
dsc.op2.u1.iconVal = 0;
dsc.op2.u1.iconFlags = 0;
AssertionIndex index = optAddAssertion(&dsc);
optCreateComplementaryAssertion(index, nullptr, nullptr);
return index;
}
// Cases where op1 holds the lhs of the condition op2 holds the bound.
// Loop condition like "i < bnd"
// Assertion: "i < bnd != 0"
else if (vnStore->IsVNCompareCheckedBound(relop->gtVNPair.GetConservative()))
{
AssertionDsc dsc;
dsc.assertionKind = OAK_NOT_EQUAL;
dsc.op1.kind = O1K_BOUND_LOOP_BND;
dsc.op1.vn = relop->gtVNPair.GetConservative();
dsc.op2.kind = O2K_CONST_INT;
dsc.op2.vn = vnStore->VNZeroForType(TYP_INT);
dsc.op2.u1.iconVal = 0;
dsc.op2.u1.iconFlags = 0;
AssertionIndex index = optAddAssertion(&dsc);
optCreateComplementaryAssertion(index, nullptr, nullptr);
return index;
}
// Loop condition like "(uint)i < (uint)bnd" or equivalent
// Assertion: "no throw" since this condition guarantees that i is both >= 0 and < bnd (on the appropiate edge)
else if (vnStore->IsVNUnsignedCompareCheckedBound(relop->gtVNPair.GetConservative(), &unsignedCompareBnd))
{
assert(unsignedCompareBnd.vnIdx != ValueNumStore::NoVN);
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 = relop->gtVNPair.GetConservative();
dsc.op1.bnd.vnIdx = unsignedCompareBnd.vnIdx;
dsc.op1.bnd.vnLen = unsignedCompareBnd.vnBound;
dsc.op2.kind = O2K_INVALID;
dsc.op2.vn = ValueNumStore::NoVN;
AssertionIndex index = optAddAssertion(&dsc);
if (unsignedCompareBnd.cmpOper == VNF_GE_UN)
{
// By default JTRUE generated assertions hold on the "jump" edge. We have i >= bnd but we're really
// after i < bnd so we need to change the assertion edge to "next".
return AssertionInfo::ForNextEdge(index);
}
return index;
}
// Cases where op1 holds the condition bound check and op2 is 0.
// Loop condition like: "i < 100 == 0"
// Assertion: "i < 100 == false"
else if (vnStore->IsVNConstantBound(vn) &&
(op2->gtVNPair.GetConservative() == vnStore->VNZeroForType(op2->TypeGet())) &&
(relop->gtOper == GT_EQ || relop->gtOper == GT_NE))
{
AssertionDsc dsc;
dsc.assertionKind = relop->gtOper == GT_EQ ? OAK_EQUAL : OAK_NOT_EQUAL;
dsc.op1.kind = O1K_CONSTANT_LOOP_BND;
dsc.op1.vn = vn;
dsc.op2.kind = O2K_CONST_INT;
dsc.op2.vn = vnStore->VNZeroForType(op2->TypeGet());
dsc.op2.u1.iconVal = 0;
dsc.op2.u1.iconFlags = 0;
AssertionIndex index = optAddAssertion(&dsc);
optCreateComplementaryAssertion(index, nullptr, nullptr);
return index;
}
// Cases where op1 holds the lhs of the condition op2 holds rhs.
// Loop condition like "i < 100"
// Assertion: "i < 100 != 0"
else if (vnStore->IsVNConstantBound(relop->gtVNPair.GetConservative()))
{
AssertionDsc dsc;
dsc.assertionKind = OAK_NOT_EQUAL;
dsc.op1.kind = O1K_CONSTANT_LOOP_BND;
dsc.op1.vn = relop->gtVNPair.GetConservative();
dsc.op2.kind = O2K_CONST_INT;
dsc.op2.vn = vnStore->VNZeroForType(TYP_INT);
dsc.op2.u1.iconVal = 0;
dsc.op2.u1.iconFlags = 0;
AssertionIndex index = optAddAssertion(&dsc);
optCreateComplementaryAssertion(index, nullptr, nullptr);
return index;
}
return NO_ASSERTION_INDEX;
}
/*****************************************************************************
*
* Compute assertions for the JTrue node.
*/
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->gtOp.gtOp1;
if ((relop->OperKind() & GTK_RELOP) == 0)
{
return NO_ASSERTION_INDEX;
}
Compiler::optAssertionKind assertionKind = OAK_INVALID;
GenTree* op1 = relop->gtOp.gtOp1;
GenTree* op2 = relop->gtOp.gtOp2;
AssertionInfo info = optCreateJTrueBoundsAssertion(tree);
if (info.HasAssertion())
{
return info;
}
// Find assertion kind.
switch (relop->gtOper)
{
case GT_EQ:
assertionKind = OAK_EQUAL;
break;
case GT_NE:
assertionKind = OAK_NOT_EQUAL;
break;
default:
// TODO-CQ: add other relop operands. Disabled for now to measure perf
// and not occupy assertion table slots. We'll add them when used.
return NO_ASSERTION_INDEX;
}
// Check for op1 or op2 to be lcl var and if so, keep it in op1.
if ((op1->gtOper != GT_LCL_VAR) && (op2->gtOper == GT_LCL_VAR))
{
jitstd::swap(op1, op2);
}
// If op1 is lcl and op2 is const or lcl, create assertion.
if ((op1->gtOper == GT_LCL_VAR) &&
((op2->OperKind() & GTK_CONST) || (op2->gtOper == GT_LCL_VAR))) // Fix for Dev10 851483
{
return optCreateJtrueAssertions(op1, op2, assertionKind);
}
// Check op1 and op2 for an indirection of a GT_LCL_VAR and keep it in op1.
if (((op1->gtOper != GT_IND) || (op1->gtOp.gtOp1->gtOper != GT_LCL_VAR)) &&
((op2->gtOper == GT_IND) && (op2->gtOp.gtOp1->gtOper == GT_LCL_VAR)))
{
jitstd::swap(op1, op2);
}
// If op1 is ind, then extract op1's oper.
if ((op1->gtOper == GT_IND) && (op1->gtOp.gtOp1->gtOper == GT_LCL_VAR))
{
return optCreateJtrueAssertions(op1, op2, assertionKind);
}
// Look for a call to an IsInstanceOf helper compared to a nullptr
if ((op2->gtOper != GT_CNS_INT) && (op1->gtOper == GT_CNS_INT))
{
jitstd::swap(op1, op2);
}
// Validate op1 and op2
if ((op1->gtOper != GT_CALL) || (op1->gtCall.gtCallType != CT_HELPER) || (op1->TypeGet() != TYP_REF) || // op1
(op2->gtOper != GT_CNS_INT) || (op2->gtIntCon.gtIconVal != 0)) // op2
{
return NO_ASSERTION_INDEX;
}
if (op1->gtCall.gtCallMethHnd != eeFindHelper(CORINFO_HELP_ISINSTANCEOFINTERFACE) &&
op1->gtCall.gtCallMethHnd != eeFindHelper(CORINFO_HELP_ISINSTANCEOFARRAY) &&
op1->gtCall.gtCallMethHnd != eeFindHelper(CORINFO_HELP_ISINSTANCEOFCLASS) &&
op1->gtCall.gtCallMethHnd != eeFindHelper(CORINFO_HELP_ISINSTANCEOFANY))
{
return NO_ASSERTION_INDEX;
}
op2 = op1->gtCall.gtCallLateArgs->gtOp.gtOp2;
op1 = op1->gtCall.gtCallLateArgs;
// Reverse the assertion
assert(assertionKind == OAK_EQUAL || assertionKind == OAK_NOT_EQUAL);
assertionKind = (assertionKind == OAK_EQUAL) ? OAK_NOT_EQUAL : OAK_EQUAL;
if (op1->gtOp.gtOp1->gtOper == GT_LCL_VAR)
{
return optCreateJtrueAssertions(op1, op2, assertionKind);
}
return NO_ASSERTION_INDEX;
}
/*****************************************************************************
*
* Create an assertion on the phi node if some information can be gleaned
* from all of the constituent phi operands.
*
*/
AssertionIndex Compiler::optAssertionGenPhiDefn(GenTree* tree)
{
if (!tree->IsPhiDefn())
{
return NO_ASSERTION_INDEX;
}
GenTree* phi = tree->gtOp.gtOp2;
// Try to find if all phi arguments are known to be non-null.
bool isNonNull = true;
for (GenTreeArgList* args = phi->gtOp.gtOp1->AsArgList(); args != nullptr; args = args->Rest())
{
if (!vnStore->IsKnownNonNull(args->Current()->gtVNPair.GetConservative()))
{
isNonNull = false;
break;
}
}
// All phi arguments are non-null implies phi rhs is non-null.
if (isNonNull)
{
return optCreateAssertion(tree->gtOp.gtOp1, nullptr, OAK_NOT_EQUAL);
}
return NO_ASSERTION_INDEX;
}
/*****************************************************************************
*
* If this statement creates a value assignment or assertion
* then assign an index to the given value assignment by adding
* it to the lookup table, if necessary.
*/
void Compiler::optAssertionGen(GenTree* tree)
{
tree->ClearAssertion();
if (tree->gtFlags & GTF_COLON_COND)
{
return;
}
#ifdef DEBUG
optAssertionPropCurrentTree = tree;
#endif
// For most of the assertions that we create below
// the assertion is true after the tree is processed
bool assertionProven = true;
AssertionInfo assertionInfo;
switch (tree->gtOper)
{
case GT_ASG:
// VN takes care of non local assertions for assignments and data flow.
if (optLocalAssertionProp)
{
assertionInfo = optCreateAssertion(tree->gtOp.gtOp1, tree->gtOp.gtOp2, OAK_EQUAL);
}
else
{
assertionInfo = optAssertionGenPhiDefn(tree);
}
break;
case GT_OBJ:
case GT_BLK:
case GT_DYN_BLK:
case GT_IND:
case GT_NULLCHECK:
// All indirections create non-null assertions
assertionInfo = optCreateAssertion(tree->AsIndir()->Addr(), nullptr, OAK_NOT_EQUAL);
break;
case GT_ARR_LENGTH:
// An array length is an indirection (but doesn't derive from GenTreeIndir).
assertionInfo = optCreateAssertion(tree->AsArrLen()->ArrRef(), nullptr, OAK_NOT_EQUAL);
break;
case GT_ARR_BOUNDS_CHECK:
if (!optLocalAssertionProp)
{
assertionInfo = optCreateAssertion(tree, nullptr, OAK_NO_THROW);
}
break;
case GT_ARR_ELEM:
// An array element reference can create a non-null assertion
assertionInfo = optCreateAssertion(tree->gtArrElem.gtArrObj, nullptr, OAK_NOT_EQUAL);
break;
case GT_CALL:
// A virtual call can create a non-null assertion. We transform some virtual calls into non-virtual calls
// with a GTF_CALL_NULLCHECK flag set.
if ((tree->gtFlags & GTF_CALL_NULLCHECK) || tree->AsCall()->IsVirtual())
{
// Retrieve the 'this' arg
GenTree* thisArg = gtGetThisArg(tree->AsCall());
#if defined(_TARGET_X86_) || defined(_TARGET_AMD64_) || defined(_TARGET_ARM_)
if (thisArg == nullptr)
{
// For tail calls we lose the this pointer in the argument list but that's OK because a null check
// was made explicit, so we get the assertion when we walk the GT_IND in the argument list.
noway_assert(tree->gtCall.IsTailCall());
break;
}
#endif // _TARGET_X86_ || _TARGET_AMD64_ || _TARGET_ARM_
noway_assert(thisArg != nullptr);
assertionInfo = optCreateAssertion(thisArg, nullptr, OAK_NOT_EQUAL);
}
break;
case GT_CAST:
// We only create this assertion for global assertion prop
if (!optLocalAssertionProp)
{
// This represets an assertion that we would like to prove to be true. It is not actually a true
// assertion.
// If we can prove this assertion true then we can eliminate this cast.
assertionInfo = optCreateAssertion(tree->gtOp.gtOp1, tree, OAK_SUBRANGE);
assertionProven = false;
}
break;
case GT_JTRUE:
assertionInfo = optAssertionGenJtrue(tree);
break;
default:
// All other gtOper node kinds, leave 'assertionIndex' = NO_ASSERTION_INDEX
break;
}
// For global assertion prop we must store the assertion number in the tree node
if (assertionInfo.HasAssertion() && assertionProven && !optLocalAssertionProp)
{
tree->SetAssertionInfo(assertionInfo);
}
}
/*****************************************************************************
*
* Maps a complementary assertion to its original assertion so it can be
* retrieved faster.
*/
void Compiler::optMapComplementary(AssertionIndex assertionIndex, AssertionIndex index)
{
if (assertionIndex == NO_ASSERTION_INDEX || index == NO_ASSERTION_INDEX)
{
return;
}
assert(assertionIndex <= optMaxAssertionCount);
assert(index <= optMaxAssertionCount);
optComplementaryAssertionMap[assertionIndex] = index;
optComplementaryAssertionMap[index] = assertionIndex;
}
/*****************************************************************************
*
* Given an assertion index, return the assertion index of the complementary
* assertion or 0 if one does not exist.
*/
AssertionIndex Compiler::optFindComplementary(AssertionIndex assertIndex)
{
if (assertIndex == NO_ASSERTION_INDEX)
{
return NO_ASSERTION_INDEX;
}
AssertionDsc* inputAssertion = optGetAssertion(assertIndex);
// Must be an equal or not equal assertion.
if (inputAssertion->assertionKind != OAK_EQUAL && inputAssertion->assertionKind != OAK_NOT_EQUAL)
{
return NO_ASSERTION_INDEX;
}
AssertionIndex index = optComplementaryAssertionMap[assertIndex];
if (index != NO_ASSERTION_INDEX && index <= optAssertionCount)
{
return index;
}
optAssertionKind complementaryAssertionKind =
(inputAssertion->assertionKind == OAK_EQUAL) ? OAK_NOT_EQUAL : OAK_EQUAL;
for (AssertionIndex index = 1; index <= optAssertionCount; ++index)
{
// Make sure assertion kinds are complementary and op1, op2 kinds match.
AssertionDsc* curAssertion = optGetAssertion(index);
if (curAssertion->Complementary(inputAssertion, !optLocalAssertionProp))
{
optMapComplementary(assertIndex, index);
return index;
}
}
return NO_ASSERTION_INDEX;
}
/*****************************************************************************
*
* Given a lclNum and a toType, return assertion index of the assertion that
* claims that a variable's value is always a valid subrange of toType.
* Thus we can discard or omit a cast to toType. Returns NO_ASSERTION_INDEX
* if one such assertion could not be found in "assertions."
*/
AssertionIndex Compiler::optAssertionIsSubrange(GenTree* tree, var_types toType, ASSERT_VALARG_TP assertions)
{
if (!optLocalAssertionProp && BitVecOps::IsEmpty(apTraits, assertions))
{
return NO_ASSERTION_INDEX;
}
for (AssertionIndex index = 1; index <= optAssertionCount; index++)
{
AssertionDsc* curAssertion = optGetAssertion(index);
if ((optLocalAssertionProp ||
BitVecOps::IsMember(apTraits, assertions, index - 1)) && // either local prop or use propagated assertions
(curAssertion->assertionKind == OAK_SUBRANGE) &&
(curAssertion->op1.kind == O1K_LCLVAR))
{
// For local assertion prop use comparison on locals, and use comparison on vns for global prop.
bool isEqual = optLocalAssertionProp ? (curAssertion->op1.lcl.lclNum == tree->AsLclVarCommon()->GetLclNum())
: (curAssertion->op1.vn == tree->gtVNPair.GetConservative());
if (!isEqual)
{
continue;
}
// Make sure the toType is within current assertion's bounds.
switch (toType)
{
case TYP_BYTE:
case TYP_UBYTE:
case TYP_SHORT:
case TYP_USHORT:
if ((curAssertion->op2.u2.loBound < AssertionDsc::GetLowerBoundForIntegralType(toType)) ||
(curAssertion->op2.u2.hiBound > AssertionDsc::GetUpperBoundForIntegralType(toType)))
{
continue;
}
break;
case TYP_UINT:
if (curAssertion->op2.u2.loBound < AssertionDsc::GetLowerBoundForIntegralType(toType))
{
continue;
}
break;
case TYP_INT:
break;
default:
continue;
}
return index;
}
}
return NO_ASSERTION_INDEX;
}
/**********************************************************************************
*
* Given a "tree" that is usually arg1 of a isinst/cast kind of GT_CALL (a class
* handle), and "methodTableArg" which is a const int (a class handle), then search
* if there is an assertion in "assertions", that asserts the equality of the two
* class handles and then returns the index of the assertion. If one such assertion
* could not be found, then it returns NO_ASSERTION_INDEX.
*
*/
AssertionIndex Compiler::optAssertionIsSubtype(GenTree* tree, GenTree* methodTableArg, ASSERT_VALARG_TP assertions)
{
if (!optLocalAssertionProp && BitVecOps::IsEmpty(apTraits, assertions))
{
return NO_ASSERTION_INDEX;
}
for (AssertionIndex index = 1; index <= optAssertionCount; index++)
{
if (!optLocalAssertionProp && !BitVecOps::IsMember(apTraits, assertions, index - 1))
{
continue;
}
AssertionDsc* curAssertion = optGetAssertion(index);
if (curAssertion->assertionKind != OAK_EQUAL ||
(curAssertion->op1.kind != O1K_SUBTYPE && curAssertion->op1.kind != O1K_EXACT_TYPE))
{
continue;
}
// If local assertion prop use "lcl" based comparison, if global assertion prop use vn based comparison.
if ((optLocalAssertionProp) ? (curAssertion->op1.lcl.lclNum != tree->AsLclVarCommon()->GetLclNum())
: (curAssertion->op1.vn != tree->gtVNPair.GetConservative()))
{
continue;
}
if (curAssertion->op2.kind == O2K_IND_CNS_INT)
{
if (methodTableArg->gtOper != GT_IND)
{
continue;
}
methodTableArg = methodTableArg->gtOp.gtOp1;
}
else if (curAssertion->op2.kind != O2K_CONST_INT)
{
continue;
}
ssize_t methodTableVal = 0;
unsigned iconFlags = 0;
if (!optIsTreeKnownIntValue(!optLocalAssertionProp, methodTableArg, &methodTableVal, &iconFlags))
{
continue;
}
if (curAssertion->op2.u1.iconVal == methodTableVal)
{
return index;
}
}
return NO_ASSERTION_INDEX;
}
//------------------------------------------------------------------------------
// optVNConstantPropOnTree: Substitutes tree with an evaluated constant while
// managing ref-counts and side-effects.
//
// Arguments:
// block - The block containing the tree.
// stmt - The statement in the block containing the tree.
// tree - The tree node whose value is known at compile time.
// The tree should have a constant value number.
//
// Return Value:
// Returns a potentially new or a transformed tree node.
// Returns nullptr when no transformation is possible.
//
// Description:
// Transforms a tree node if its result evaluates to a constant. The
// transformation can be a "ChangeOper" to a constant or a new constant node
// with extracted side-effects.
//
// Before replacing or substituting the "tree" with a constant, extracts any
// side effects from the "tree" and creates a comma separated side effect list
// and then appends the transformed node at the end of the list.
// This comma separated list is then returned.
//
// For JTrue nodes, side effects are not put into a comma separated list. If
// the relop will evaluate to "true" or "false" statically, then the side-effects
// will be put into new statements, presuming the JTrue will be folded away.
//
// The ref-counts of any variables in the tree being replaced, will be
// appropriately decremented. The ref-counts of variables in the side-effect
// nodes will be retained.
//
GenTree* Compiler::optVNConstantPropOnTree(BasicBlock* block, GenTree* stmt, GenTree* tree)
{
if (tree->OperGet() == GT_JTRUE)
{
// Treat JTRUE separately to extract side effects into respective statements rather
// than using a COMMA separated op1.
return optVNConstantPropOnJTrue(block, stmt, tree);
}
// If relop is part of JTRUE, this should be optimized as part of the parent JTRUE.
// Or if relop is part of QMARK or anything else, we simply bail here.
else if (tree->OperIsCompare() && (tree->gtFlags & GTF_RELOP_JMP_USED))
{
return nullptr;
}
ValueNum vnCns = tree->gtVNPair.GetConservative();
ValueNum vnLib = tree->gtVNPair.GetLiberal();
// Check if node evaluates to a constant.
if (!vnStore->IsVNConstant(vnCns))
{
return nullptr;
}
GenTree* newTree = tree;
GenTree* sideEffList = nullptr;
switch (vnStore->TypeOfVN(vnCns))
{
case TYP_FLOAT:
{
float value = vnStore->ConstantValue<float>(vnCns);
if (tree->TypeGet() == TYP_INT)
{
// Same sized reinterpretation of bits to integer
newTree = optPrepareTreeForReplacement(tree, tree);
tree->ChangeOperConst(GT_CNS_INT);
tree->gtIntCon.gtIconVal = *(reinterpret_cast<int*>(&value));
tree->gtVNPair = ValueNumPair(vnLib, vnCns);
}
else
{
// Implicit assignment conversion to float or double
assert(varTypeIsFloating(tree->TypeGet()));
newTree = optPrepareTreeForReplacement(tree, tree);
tree->ChangeOperConst(GT_CNS_DBL);
tree->gtDblCon.gtDconVal = value;
tree->gtVNPair = ValueNumPair(vnLib, vnCns);
}
break;
}
case TYP_DOUBLE:
{
double value = vnStore->ConstantValue<double>(vnCns);
if (tree->TypeGet() == TYP_LONG)
{
// Same sized reinterpretation of bits to long
newTree = optPrepareTreeForReplacement(tree, tree);
tree->ChangeOperConst(GT_CNS_NATIVELONG);
tree->gtIntConCommon.SetLngValue(*(reinterpret_cast<INT64*>(&value)));
tree->gtVNPair = ValueNumPair(vnLib, vnCns);
}
else
{
// Implicit assignment conversion to float or double
assert(varTypeIsFloating(tree->TypeGet()));
newTree = optPrepareTreeForReplacement(tree, tree);
tree->ChangeOperConst(GT_CNS_DBL);
tree->gtDblCon.gtDconVal = value;
tree->gtVNPair = ValueNumPair(vnLib, vnCns);
}
break;
}
case TYP_LONG:
{
INT64 value = vnStore->ConstantValue<INT64>(vnCns);
#ifdef _TARGET_64BIT_
if (vnStore->IsVNHandle(vnCns))
{
// Don't perform constant folding that involves a handle that needs
// to be recorded as a relocation with the VM.
if (!opts.compReloc)
{
newTree = gtNewIconHandleNode(value, vnStore->GetHandleFlags(vnCns));
newTree->gtVNPair = ValueNumPair(vnLib, vnCns);
newTree = optPrepareTreeForReplacement(tree, newTree);
}
}
else
#endif
{
switch (tree->TypeGet())
{
case TYP_INT:
// Implicit assignment conversion to smaller integer
newTree = optPrepareTreeForReplacement(tree, tree);
tree->ChangeOperConst(GT_CNS_INT);
tree->gtIntCon.gtIconVal = (int)value;
tree->gtVNPair = ValueNumPair(vnLib, vnCns);
break;
case TYP_LONG:
// Same type no conversion required
newTree = optPrepareTreeForReplacement(tree, tree);
tree->ChangeOperConst(GT_CNS_NATIVELONG);
tree->gtIntConCommon.SetLngValue(value);
tree->gtVNPair = ValueNumPair(vnLib, vnCns);
break;
case TYP_FLOAT:
// No implicit conversions from long to float and value numbering will
// not propagate through memory reinterpretations of different size.
unreached();
break;
case TYP_DOUBLE:
// Same sized reinterpretation of bits to double
newTree = optPrepareTreeForReplacement(tree, tree);
tree->ChangeOperConst(GT_CNS_DBL);
tree->gtDblCon.gtDconVal = *(reinterpret_cast<double*>(&value));
tree->gtVNPair = ValueNumPair(vnLib, vnCns);
break;
default:
return nullptr;
}
}
}
break;
case TYP_REF:
if (tree->TypeGet() != TYP_REF)
{
return nullptr;
}
assert(vnStore->ConstantValue<size_t>(vnCns) == 0);
newTree = optPrepareTreeForReplacement(tree, tree);
tree->ChangeOperConst(GT_CNS_INT);
tree->gtIntCon.gtIconVal = 0;
tree->ClearIconHandleMask();
tree->gtVNPair = ValueNumPair(vnLib, vnCns);
break;
case TYP_INT:
{
int value = vnStore->ConstantValue<int>(vnCns);
#ifndef _TARGET_64BIT_
if (vnStore->IsVNHandle(vnCns))
{
// Don't perform constant folding that involves a handle that needs
// to be recorded as a relocation with the VM.
if (!opts.compReloc)
{
newTree = gtNewIconHandleNode(value, vnStore->GetHandleFlags(vnCns));
newTree->gtVNPair = ValueNumPair(vnLib, vnCns);
newTree = optPrepareTreeForReplacement(tree, newTree);
}
}
else
#endif
{
switch (tree->TypeGet())
{
case TYP_REF:
case TYP_INT:
// Same type no conversion required
newTree = optPrepareTreeForReplacement(tree, tree);
tree->ChangeOperConst(GT_CNS_INT);
tree->gtIntCon.gtIconVal = value;
tree->ClearIconHandleMask();
tree->gtVNPair = ValueNumPair(vnLib, vnCns);
break;
case TYP_LONG:
// Implicit assignment conversion to larger integer
newTree = optPrepareTreeForReplacement(tree, tree);
tree->ChangeOperConst(GT_CNS_NATIVELONG);
tree->gtIntConCommon.SetLngValue(value);
tree->gtVNPair = ValueNumPair(vnLib, vnCns);
break;
case TYP_FLOAT:
// Same sized reinterpretation of bits to float
newTree = optPrepareTreeForReplacement(tree, tree);
tree->ChangeOperConst(GT_CNS_DBL);
tree->gtDblCon.gtDconVal = *(reinterpret_cast<float*>(&value));
tree->gtVNPair = ValueNumPair(vnLib, vnCns);
break;
case TYP_DOUBLE:
// No implicit conversions from int to double and value numbering will
// not propagate through memory reinterpretations of different size.
unreached();
break;
default:
return nullptr;
}
}
}
break;
default:
return nullptr;
}
return newTree;
}
/*******************************************************************************************************
*
* Perform constant propagation on a tree given the "curAssertion" is true at the point of the "tree."
*
*/
GenTree* Compiler::optConstantAssertionProp(AssertionDsc* curAssertion,
GenTree* tree,
GenTree* stmt DEBUGARG(AssertionIndex index))
{
unsigned lclNum = tree->gtLclVarCommon.gtLclNum;
if (lclNumIsCSE(lclNum))
{
return nullptr;
}
GenTree* newTree = tree;
// Update 'newTree' with the new value from our table
// Typically newTree == tree and we are updating the node in place
switch (curAssertion->op2.kind)
{
case O2K_CONST_DOUBLE:
// There could be a positive zero and a negative zero, so don't propagate zeroes.
if (curAssertion->op2.dconVal == 0.0)
{
return nullptr;
}
newTree->ChangeOperConst(GT_CNS_DBL);
newTree->gtDblCon.gtDconVal = curAssertion->op2.dconVal;
break;
case O2K_CONST_LONG:
if (newTree->gtType == TYP_LONG)
{
newTree->ChangeOperConst(GT_CNS_NATIVELONG);
newTree->gtIntConCommon.SetLngValue(curAssertion->op2.lconVal);
}
else
{
newTree->ChangeOperConst(GT_CNS_INT);
newTree->gtIntCon.gtIconVal = (int)curAssertion->op2.lconVal;
newTree->gtType = TYP_INT;
}
break;
case O2K_CONST_INT:
if (curAssertion->op2.u1.iconFlags & GTF_ICON_HDL_MASK)
{
// Here we have to allocate a new 'large' node to replace the old one
newTree = gtNewIconHandleNode(curAssertion->op2.u1.iconVal,
curAssertion->op2.u1.iconFlags & GTF_ICON_HDL_MASK);
}
else
{
bool isArrIndex = ((tree->gtFlags & GTF_VAR_ARR_INDEX) != 0);
// If we have done constant propagation of a struct type, it is only valid for zero-init,
// and we have to ensure that we have the right zero for the type.
if (varTypeIsStruct(tree))
{
assert(curAssertion->op2.u1.iconVal == 0);
}
#ifdef FEATURE_SIMD
if (varTypeIsSIMD(tree))
{
var_types simdType = tree->TypeGet();
tree->ChangeOperConst(GT_CNS_DBL);
GenTree* initVal = tree;
initVal->gtType = TYP_FLOAT;
newTree =
gtNewSIMDNode(simdType, initVal, nullptr, SIMDIntrinsicInit, TYP_FLOAT, genTypeSize(simdType));
}
else
#endif // FEATURE_SIMD
{
newTree->ChangeOperConst(GT_CNS_INT);
newTree->gtIntCon.gtIconVal = curAssertion->op2.u1.iconVal;
newTree->ClearIconHandleMask();
}
// If we're doing an array index address, assume any constant propagated contributes to the index.
if (isArrIndex)
{
newTree->gtIntCon.gtFieldSeq =
GetFieldSeqStore()->CreateSingleton(FieldSeqStore::ConstantIndexPseudoField);
}
newTree->gtFlags &= ~GTF_VAR_ARR_INDEX;
}
// Constant ints are of type TYP_INT, not any of the short forms.
if (varTypeIsIntegral(newTree->TypeGet()))
{
#ifdef _TARGET_64BIT_
var_types newType = (var_types)((curAssertion->op2.u1.iconFlags & 1) ? TYP_LONG : TYP_INT);
if (newTree->TypeGet() != newType)
{
noway_assert(newTree->gtType != TYP_REF);
newTree->gtType = newType;
}
#else
if (newTree->TypeGet() != TYP_INT)
{
noway_assert(newTree->gtType != TYP_REF && newTree->gtType != TYP_LONG);
newTree->gtType = TYP_INT;
}
#endif
}
break;
default:
return nullptr;
}
if (!optLocalAssertionProp)
{
assert(newTree->OperIsConst()); // We should have a simple Constant node for newTree
assert(vnStore->IsVNConstant(curAssertion->op2.vn)); // The value number stored for op2 should be a valid
// VN representing the constant
newTree->gtVNPair.SetBoth(curAssertion->op2.vn); // Set the ValueNumPair to the constant VN from op2
// of the assertion
}
#ifdef DEBUG
if (verbose)
{
printf("\nAssertion prop in BB%02u:\n", compCurBB->bbNum);
optPrintAssertion(curAssertion, index);
gtDispTree(newTree, nullptr, nullptr, true);
}
#endif
if (lvaLocalVarRefCounted)
{
lvaTable[lclNum].decRefCnts(compCurBB->getBBWeight(this), this);
}
return optAssertionProp_Update(newTree, tree, stmt);
}
/*******************************************************************************************************
*
* Called in the context of an existing copy assertion which makes an "==" assertion on "lclVar" and
* "copyVar." Before substituting "copyVar" for "lclVar", we make sure using "copy" doesn't widen access.
*
*/
bool Compiler::optAssertionProp_LclVarTypeCheck(GenTree* tree, LclVarDsc* lclVarDsc, LclVarDsc* copyVarDsc)
{
/*
Small struct field locals are stored using the exact width and loaded widened
(i.e. lvNormalizeOnStore==false lvNormalizeOnLoad==true),
because the field locals might end up embedded in the parent struct local with the exact width.
In other words, a store to a short field local should always done using an exact width store
[00254538] 0x0009 ------------ const int 0x1234
[002545B8] 0x000B -A--G--NR--- = short
[00254570] 0x000A D------N---- lclVar short V43 tmp40
mov word ptr [L_043], 0x1234
Now, if we copy prop, say a short field local V43, to another short local V34
for the following tree:
[04E18650] 0x0001 ------------ lclVar int V34 tmp31
[04E19714] 0x0002 -A---------- = int
[04E196DC] 0x0001 D------N---- lclVar int V36 tmp33
We will end with this tree:
[04E18650] 0x0001 ------------ lclVar int V43 tmp40
[04E19714] 0x0002 -A-----NR--- = int
[04E196DC] 0x0001 D------N---- lclVar int V36 tmp33 EAX
And eventually causing a fetch of 4-byte out from [L_043] :(
mov EAX, dword ptr [L_043]
The following check is to make sure we only perform the copy prop
when we don't retrieve the wider value.
*/
if (copyVarDsc->lvIsStructField)
{
var_types varType = (var_types)copyVarDsc->lvType;
// Make sure we don't retrieve the wider value.
return !varTypeIsSmall(varType) || (varType == tree->TypeGet());
}
// Called in the context of a single copy assertion, so the types should have been
// taken care by the assertion gen logic for other cases. Just return true.
return true;
}
/**********************************************************************************
*
* Perform copy assertion propagation when the lclNum and ssaNum of the "tree" match
* the "curAssertion."
*
*/
GenTree* Compiler::optCopyAssertionProp(AssertionDsc* curAssertion,
GenTree* tree,
GenTree* stmt DEBUGARG(AssertionIndex index))
{
const AssertionDsc::AssertionDscOp1& op1 = curAssertion->op1;
const AssertionDsc::AssertionDscOp2& op2 = curAssertion->op2;
noway_assert(op1.lcl.lclNum != op2.lcl.lclNum);
unsigned lclNum = tree->gtLclVarCommon.GetLclNum();
// Make sure one of the lclNum of the assertion matches with that of the tree.
if (op1.lcl.lclNum != lclNum && op2.lcl.lclNum != lclNum)
{
return nullptr;
}
// Extract the matching lclNum and ssaNum.
unsigned copyLclNum = (op1.lcl.lclNum == lclNum) ? op2.lcl.lclNum : op1.lcl.lclNum;
unsigned copySsaNum = BAD_VAR_NUM;
if (!optLocalAssertionProp)
{
// Extract the ssaNum of the matching lclNum.
unsigned ssaNum = (op1.lcl.lclNum == lclNum) ? op1.lcl.ssaNum : op2.lcl.ssaNum;
copySsaNum = (op1.lcl.lclNum == lclNum) ? op2.lcl.ssaNum : op1.lcl.ssaNum;
if (ssaNum != tree->AsLclVarCommon()->GetSsaNum())
{
return nullptr;
}
}
LclVarDsc* copyVarDsc = &lvaTable[copyLclNum];
LclVarDsc* lclVarDsc = &lvaTable[lclNum];
// Make sure the types are compatible.
if (!optAssertionProp_LclVarTypeCheck(tree, lclVarDsc, copyVarDsc))
{
return nullptr;
}
// Make sure we can perform this copy prop.
if (optCopyProp_LclVarScore(lclVarDsc, copyVarDsc, curAssertion->op1.lcl.lclNum == lclNum) <= 0)
{
return nullptr;
}
// If global assertion prop, by now we should have ref counts, fix them.
if (lvaLocalVarRefCounted)
{
lvaTable[lclNum].decRefCnts(compCurBB->getBBWeight(this), this);
lvaTable[copyLclNum].incRefCnts(compCurBB->getBBWeight(this), this);
tree->gtLclVarCommon.SetSsaNum(copySsaNum);
}
tree->gtLclVarCommon.SetLclNum(copyLclNum);
#ifdef DEBUG
if (verbose)
{
printf("\nAssertion prop in BB%02u:\n", compCurBB->bbNum);
optPrintAssertion(curAssertion, index);
gtDispTree(tree, nullptr, nullptr, true);
}
#endif
// Update and morph the tree.
return optAssertionProp_Update(tree, tree, stmt);
}
/*****************************************************************************
*
* Given a tree consisting of a just a LclVar and a set of available assertions
* we try to propagate an assertion and modify the LclVar tree if we can.
* We pass in the root of the tree via 'stmt', for local copy prop 'stmt' will
* be nullptr. Returns the modified tree, or nullptr if no assertion prop took place.
*/
GenTree* Compiler::optAssertionProp_LclVar(ASSERT_VALARG_TP assertions, GenTree* tree, GenTree* stmt)
{
assert(tree->gtOper == GT_LCL_VAR);
// If we have a var definition then bail or
// If this is the address of the var then it will have the GTF_DONT_CSE
// flag set and we don't want to to assertion prop on it.
if (tree->gtFlags & (GTF_VAR_DEF | GTF_DONT_CSE))
{
return nullptr;
}
BitVecOps::Iter iter(apTraits, assertions);
unsigned index = 0;
while (iter.NextElem(&index))
{
AssertionIndex assertionIndex = GetAssertionIndex(index);
if (assertionIndex > optAssertionCount)
{
break;
}
// See if the variable is equal to a constant or another variable.
AssertionDsc* curAssertion = optGetAssertion(assertionIndex);
if (curAssertion->assertionKind != OAK_EQUAL || curAssertion->op1.kind != O1K_LCLVAR)
{
continue;
}
// Copy prop.
if (curAssertion->op2.kind == O2K_LCLVAR_COPY)
{
// Cannot do copy prop during global assertion prop because of no knowledge
// of kill sets. We will still make a == b copy assertions during the global phase to allow
// for any implied assertions that can be retrieved. Because implied assertions look for
// matching SSA numbers (i.e., if a0 == b1 and b1 == c0 then a0 == c0) they don't need kill sets.
if (optLocalAssertionProp)
{
// Perform copy assertion prop.
GenTree* newTree = optCopyAssertionProp(curAssertion, tree, stmt DEBUGARG(assertionIndex));
if (newTree == nullptr)
{
// Skip and try next assertion.
continue;
}
return newTree;
}
}
// Constant prop (for local assertion prop.)
// The case where the tree type could be different than the LclVar type is caused by
// gtFoldExpr, specifically the case of a cast, where the fold operation changes the type of the LclVar
// node. In such a case is not safe to perform the substitution since later on the JIT will assert mismatching
// types between trees.
else if (curAssertion->op1.lcl.lclNum == tree->gtLclVarCommon.GetLclNum() &&
tree->gtType == lvaTable[tree->gtLclVarCommon.GetLclNum()].lvType)
{
// If local assertion prop just, perform constant prop.
if (optLocalAssertionProp)
{
return optConstantAssertionProp(curAssertion, tree, stmt DEBUGARG(assertionIndex));
}
// If global assertion, perform constant propagation only if the VN's match and the lcl is non-CSE.
else if (curAssertion->op1.vn == tree->gtVNPair.GetConservative())
{
#if FEATURE_ANYCSE
// Don't perform constant prop for CSE LclVars
if (!lclNumIsCSE(tree->AsLclVarCommon()->GetLclNum()))
#endif
{
return optConstantAssertionProp(curAssertion, tree, stmt DEBUGARG(assertionIndex));
}
}
}
}
return nullptr;
}
/*****************************************************************************
*
* Given a set of "assertions" to search, find an assertion that matches
* op1Kind and lclNum, op2Kind and the constant value and is either equal or
* not equal assertion.
*/
AssertionIndex Compiler::optLocalAssertionIsEqualOrNotEqual(
optOp1Kind op1Kind, unsigned lclNum, optOp2Kind op2Kind, ssize_t cnsVal, ASSERT_VALARG_TP assertions)
{
noway_assert((op1Kind == O1K_LCLVAR) || (op1Kind == O1K_EXACT_TYPE) || (op1Kind == O1K_SUBTYPE));
noway_assert((op2Kind == O2K_CONST_INT) || (op2Kind == O2K_IND_CNS_INT));
if (!optLocalAssertionProp && BitVecOps::IsEmpty(apTraits, assertions))
{
return NO_ASSERTION_INDEX;
}
for (AssertionIndex index = 1; index <= optAssertionCount; ++index)
{
AssertionDsc* curAssertion = optGetAssertion(index);
if (optLocalAssertionProp || BitVecOps::IsMember(apTraits, assertions, index - 1))
{
if ((curAssertion->assertionKind != OAK_EQUAL) && (curAssertion->assertionKind != OAK_NOT_EQUAL))
{
continue;
}
if ((curAssertion->op1.kind == op1Kind) && (curAssertion->op1.lcl.lclNum == lclNum) &&
(curAssertion->op2.kind == op2Kind))
{
bool constantIsEqual = (curAssertion->op2.u1.iconVal == cnsVal);
bool assertionIsEqual = (curAssertion->assertionKind == OAK_EQUAL);
if (constantIsEqual || assertionIsEqual)
{
return index;
}
}
}
}
return NO_ASSERTION_INDEX;
}
/*****************************************************************************
*
* Given a set of "assertions" to search for, find an assertion that is either
* "op1" == "op2" or "op1" != "op2." Does a value number based comparison.
*
*/
AssertionIndex Compiler::optGlobalAssertionIsEqualOrNotEqual(ASSERT_VALARG_TP assertions, GenTree* op1, GenTree* op2)
{
if (BitVecOps::IsEmpty(apTraits, assertions))
{
return NO_ASSERTION_INDEX;
}
BitVecOps::Iter iter(apTraits, assertions);
unsigned index = 0;
while (iter.NextElem(&index))
{
AssertionIndex assertionIndex = GetAssertionIndex(index);
if (assertionIndex > optAssertionCount)
{
break;
}
AssertionDsc* curAssertion = optGetAssertion(assertionIndex);
if ((curAssertion->assertionKind != OAK_EQUAL && curAssertion->assertionKind != OAK_NOT_EQUAL))
{
continue;
}
if (curAssertion->op1.vn == op1->gtVNPair.GetConservative() &&
curAssertion->op2.vn == op2->gtVNPair.GetConservative())
{
return assertionIndex;
}
}
return NO_ASSERTION_INDEX;
}
/*****************************************************************************
*
* Given a tree consisting of a RelOp and a set of available assertions
* we try to propagate an assertion and modify the RelOp tree if we can.
* We pass in the root of the tree via 'stmt', for local copy prop 'stmt' will be nullptr
* Returns the modified tree, or nullptr if no assertion prop took place
*/
GenTree* Compiler::optAssertionProp_RelOp(ASSERT_VALARG_TP assertions, GenTree* tree, GenTree* stmt)
{
assert(tree->OperKind() & GTK_RELOP);
//
// Currently only GT_EQ or GT_NE are supported Relops for AssertionProp
//
if ((tree->gtOper != GT_EQ) && (tree->gtOper != GT_NE))
{
return nullptr;
}
if (!optLocalAssertionProp)
{
// If global assertion prop then use value numbering.
return optAssertionPropGlobal_RelOp(assertions, tree, stmt);
}
else
{
// If local assertion prop then use variable based prop.
return optAssertionPropLocal_RelOp(assertions, tree, stmt);
}
}
/*************************************************************************************
*
* Given the set of "assertions" to look up a relop assertion about the relop "tree",
* perform Value numbering based relop assertion propagation on the tree.
*
*/
GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, GenTree* tree, GenTree* stmt)
{
assert(tree->OperGet() == GT_EQ || tree->OperGet() == GT_NE);
GenTree* newTree = tree;
GenTree* op1 = tree->gtOp.gtOp1;
GenTree* op2 = tree->gtOp.gtOp2;
if (op1->gtOper != GT_LCL_VAR)
{
return nullptr;
}
// Find an equal or not equal assertion involving "op1" and "op2".
AssertionIndex index = optGlobalAssertionIsEqualOrNotEqual(assertions, op1, op2);
if (index == NO_ASSERTION_INDEX)
{
return nullptr;
}
AssertionDsc* curAssertion = optGetAssertion(index);
// Allow or not to reverse condition for OAK_NOT_EQUAL assertions.
bool allowReverse = true;
// If the assertion involves "op2" and it is a constant, then check if "op1" also has a constant value.
if (vnStore->IsVNConstant(op2->gtVNPair.GetConservative()))
{
ValueNum vnCns = op2->gtVNPair.GetConservative();
#ifdef DEBUG
if (verbose)
{
printf("\nVN relop based constant assertion prop in BB%02u:\n", compCurBB->bbNum);
printf("Assertion index=#%02u: ", index);
printTreeID(op1);
printf(" %s ", (curAssertion->assertionKind == OAK_EQUAL) ? "==" : "!=");
if (genActualType(op1->TypeGet()) == TYP_INT)
{
printf("%d\n", vnStore->ConstantValue<int>(vnCns));
}
else if (op1->TypeGet() == TYP_LONG)
{
printf("%I64d\n", vnStore->ConstantValue<INT64>(vnCns));
}
else if (op1->TypeGet() == TYP_DOUBLE)
{
printf("%f\n", vnStore->ConstantValue<double>(vnCns));
}
else if (op1->TypeGet() == TYP_FLOAT)
{
printf("%f\n", vnStore->ConstantValue<float>(vnCns));
}
else if (op1->TypeGet() == TYP_REF)
{
// The only constant of TYP_REF that ValueNumbering supports is 'null'
assert(vnStore->ConstantValue<size_t>(vnCns) == 0);
printf("null\n");
}
else
{
printf("??unknown\n");
}
gtDispTree(tree, nullptr, nullptr, true);
}
#endif
// Decrement the ref counts, before we change the oper.
lvaTable[op1->gtLclVar.gtLclNum].decRefCnts(compCurBB->getBBWeight(this), this);
// Change the oper to const.
if (genActualType(op1->TypeGet()) == TYP_INT)
{
op1->ChangeOperConst(GT_CNS_INT);
op1->gtIntCon.gtIconVal = vnStore->ConstantValue<int>(vnCns);
}
else if (op1->TypeGet() == TYP_LONG)
{
op1->ChangeOperConst(GT_CNS_NATIVELONG);
op1->gtIntConCommon.SetLngValue(vnStore->ConstantValue<INT64>(vnCns));
}
else if (op1->TypeGet() == TYP_DOUBLE)
{
double constant = vnStore->ConstantValue<double>(vnCns);
op1->ChangeOperConst(GT_CNS_DBL);
op1->gtDblCon.gtDconVal = constant;
// Nothing can be equal to NaN. So if IL had "op1 == NaN", then we already made op1 NaN,
// which will yield a false correctly. Instead if IL had "op1 != NaN", then we already
// made op1 NaN which will yield a true correctly. Note that this is irrespective of the
// assertion we have made.
allowReverse = (_isnan(constant) == 0);
}
else if (op1->TypeGet() == TYP_FLOAT)
{
float constant = vnStore->ConstantValue<float>(vnCns);
op1->ChangeOperConst(GT_CNS_DBL);
op1->gtDblCon.gtDconVal = constant;
// See comments for TYP_DOUBLE.
allowReverse = (_isnan(constant) == 0);
}
else if (op1->TypeGet() == TYP_REF)
{
op1->ChangeOperConst(GT_CNS_INT);
// The only constant of TYP_REF that ValueNumbering supports is 'null'
noway_assert(vnStore->ConstantValue<size_t>(vnCns) == 0);
op1->gtIntCon.gtIconVal = 0;
}
else
{
noway_assert(!"unknown type in Global_RelOp");
}
op1->gtVNPair.SetBoth(vnCns); // Preserve the ValueNumPair, as ChangeOperConst/SetOper will clear it.
}
// If the assertion involves "op2" and "op1" is also a local var, then just morph the tree.
else if (op2->gtOper == GT_LCL_VAR)
{
#ifdef DEBUG
if (verbose)
{
printf("\nVN relop based copy assertion prop in BB%02u:\n", compCurBB->bbNum);
printf("Assertion index=#%02u: V%02d.%02d %s V%02d.%02d\n", index, op1->gtLclVar.gtLclNum,
op1->gtLclVar.gtSsaNum, (curAssertion->assertionKind == OAK_EQUAL) ? "==" : "!=",
op2->gtLclVar.gtLclNum, op2->gtLclVar.gtSsaNum);
gtDispTree(tree, nullptr, nullptr, true);
}
#endif
lvaTable[op1->gtLclVar.gtLclNum].decRefCnts(compCurBB->getBBWeight(this), this);
// If floating point, don't just substitute op1 with op2, this won't work if
// op2 is NaN. Just turn it into a "true" or "false" yielding expression.
if (op1->TypeGet() == TYP_DOUBLE || op1->TypeGet() == TYP_FLOAT)
{
// Note we can't trust the OAK_EQUAL as the value could end up being a NaN
// violating the assertion. However, we create OAK_EQUAL assertions for floating
// point only on JTrue nodes, so if the condition held earlier, it will hold
// now. We don't create OAK_EQUAL assertion on floating point from GT_ASG
// because we depend on value num which would constant prop the NaN.
lvaTable[op2->gtLclVar.gtLclNum].decRefCnts(compCurBB->getBBWeight(this), this);
op1->ChangeOperConst(GT_CNS_DBL);
op1->gtDblCon.gtDconVal = 0;
op2->ChangeOperConst(GT_CNS_DBL);
op2->gtDblCon.gtDconVal = 0;
}
// Change the op1 LclVar to the op2 LclVar
else
{
noway_assert(varTypeIsIntegralOrI(op1->TypeGet()));
lvaTable[op2->gtLclVar.gtLclNum].incRefCnts(compCurBB->getBBWeight(this), this);
op1->AsLclVarCommon()->SetLclNum(op2->AsLclVarCommon()->GetLclNum());
op1->AsLclVarCommon()->SetSsaNum(op2->AsLclVarCommon()->GetSsaNum());
}
}
else
{
return nullptr;
}
// Finally reverse the condition, if we have a not equal assertion.
if (allowReverse && curAssertion->assertionKind == OAK_NOT_EQUAL)
{
gtReverseCond(tree);
}
newTree = fgMorphTree(tree);
#ifdef DEBUG
if (verbose)
{
gtDispTree(newTree, nullptr, nullptr, true);
}
#endif
return optAssertionProp_Update(newTree, tree, stmt);
}
/*************************************************************************************
*
* Given the set of "assertions" to look up a relop assertion about the relop "tree",
* perform local variable name based relop assertion propagation on the tree.
*
*/
GenTree* Compiler::optAssertionPropLocal_RelOp(ASSERT_VALARG_TP assertions, GenTree* tree, GenTree* stmt)
{
assert(tree->OperGet() == GT_EQ || tree->OperGet() == GT_NE);
GenTree* op1 = tree->gtOp.gtOp1;
GenTree* op2 = tree->gtOp.gtOp2;
// For Local AssertionProp we only can fold when op1 is a GT_LCL_VAR
if (op1->gtOper != GT_LCL_VAR)
{
return nullptr;
}
// For Local AssertionProp we only can fold when op2 is a GT_CNS_INT
if (op2->gtOper != GT_CNS_INT)
{
return nullptr;
}
optOp1Kind op1Kind = O1K_LCLVAR;
optOp2Kind op2Kind = O2K_CONST_INT;
ssize_t cnsVal = op2->gtIntCon.gtIconVal;
var_types cmpType = op1->TypeGet();
// Don't try to fold/optimize Floating Compares; there are multiple zero values.
if (varTypeIsFloating(cmpType))
{
return nullptr;
}
// Find an equal or not equal assertion about op1 var.
unsigned lclNum = op1->gtLclVarCommon.gtLclNum;
noway_assert(lclNum < lvaCount);
AssertionIndex index = optLocalAssertionIsEqualOrNotEqual(op1Kind, lclNum, op2Kind, cnsVal, assertions);
if (index == NO_ASSERTION_INDEX)
{
return nullptr;
}
AssertionDsc* curAssertion = optGetAssertion(index);
bool assertionKindIsEqual = (curAssertion->assertionKind == OAK_EQUAL);
bool constantIsEqual = false;
if (genTypeSize(cmpType) == TARGET_POINTER_SIZE)
{
constantIsEqual = (curAssertion->op2.u1.iconVal == cnsVal);
}
#ifdef _TARGET_64BIT_
else if (genTypeSize(cmpType) == sizeof(INT32))
{
// Compare the low 32-bits only
constantIsEqual = (((INT32)curAssertion->op2.u1.iconVal) == ((INT32)cnsVal));
}
#endif
else
{
// We currently don't fold/optimize when the GT_LCL_VAR has been cast to a small type
return nullptr;
}
noway_assert(constantIsEqual || assertionKindIsEqual);
#ifdef DEBUG
if (verbose)
{
printf("\nAssertion prop for index #%02u in BB%02u:\n", index, compCurBB->bbNum);
gtDispTree(tree, nullptr, nullptr, true);
}
#endif
// Return either CNS_INT 0 or CNS_INT 1.
bool foldResult = (constantIsEqual == assertionKindIsEqual);
if (tree->gtOper == GT_NE)
{
foldResult = !foldResult;
}
op2->gtIntCon.gtIconVal = foldResult;
op2->gtType = TYP_INT;
return optAssertionProp_Update(op2, tree, stmt);
}
/*****************************************************************************
*
* Given a tree consisting of a Cast and a set of available assertions
* we try to propagate an assertion and modify the Cast tree if we can.
* We pass in the root of the tree via 'stmt', for local copy prop 'stmt'
* will be nullptr.
*
* Returns the modified tree, or nullptr if no assertion prop took place.
*/
GenTree* Compiler::optAssertionProp_Cast(ASSERT_VALARG_TP assertions, GenTree* tree, GenTree* stmt)
{
assert(tree->gtOper == GT_CAST);
var_types toType = tree->gtCast.gtCastType;
GenTree* op1 = tree->gtCast.CastOp();
// If we have a cast involving floating point types, then bail.
if (varTypeIsFloating(toType) || varTypeIsFloating(op1->TypeGet()))
{
return nullptr;
}
// Skip over a GT_COMMA node(s), if necessary to get to the lcl.
GenTree* lcl = op1;
while (lcl->gtOper == GT_COMMA)
{
lcl = lcl->gtOp.gtOp2;
}
// If we don't have a cast of a LCL_VAR then bail.
if (lcl->gtOper != GT_LCL_VAR)
{
return nullptr;
}
AssertionIndex index = optAssertionIsSubrange(lcl, toType, assertions);
if (index != NO_ASSERTION_INDEX)
{
LclVarDsc* varDsc = &lvaTable[lcl->gtLclVarCommon.gtLclNum];
if (varDsc->lvNormalizeOnLoad() || varTypeIsLong(varDsc->TypeGet()))
{
// For normalize on load variables it must be a narrowing cast to remove
if (genTypeSize(toType) > genTypeSize(varDsc->TypeGet()))
{
// Can we just remove the GTF_OVERFLOW flag?
if ((tree->gtFlags & GTF_OVERFLOW) == 0)
{
return nullptr;
}
else
{
#ifdef DEBUG
if (verbose)
{
printf("\nSubrange prop for index #%02u in BB%02u:\n", index, compCurBB->bbNum);
gtDispTree(tree, nullptr, nullptr, true);
}
#endif
tree->gtFlags &= ~GTF_OVERFLOW; // This cast cannot overflow
return optAssertionProp_Update(tree, tree, stmt);
}
}
// GT_CAST long -> uint -> int
// |
// GT_LCL_VAR long
//
// Where the lclvar is known to be in the range of [0..MAX_UINT]
//
// A load of a 32-bit unsigned int is the same as a load of a 32-bit signed int
//
if (toType == TYP_UINT)
{
toType = TYP_INT;
}
// Change the "lcl" type to match what the cast wanted, by propagating the type
// change down the comma nodes leading to the "lcl", if we skipped them earlier.
GenTree* tmp = op1;
while (tmp->gtOper == GT_COMMA)
{
tmp->gtType = toType;
tmp = tmp->gtOp.gtOp2;
}
noway_assert(tmp == lcl);
tmp->gtType = toType;
}
#ifdef DEBUG
if (verbose)
{
printf("\nSubrange prop for index #%02u in BB%02u:\n", index, compCurBB->bbNum);
gtDispTree(tree, nullptr, nullptr, true);
}
#endif
return optAssertionProp_Update(op1, tree, stmt);
}
return nullptr;
}
/*****************************************************************************
*
* Given a tree with an array bounds check node, eliminate it because it was
* checked already in the program.
*/
GenTree* Compiler::optAssertionProp_Comma(ASSERT_VALARG_TP assertions, GenTree* tree, GenTree* stmt)
{
// Remove the bounds check as part of the GT_COMMA node since we need parent pointer to remove nodes.
// When processing visits the bounds check, it sets the throw kind to None if the check is redundant.
if ((tree->gtGetOp1()->OperGet() == GT_ARR_BOUNDS_CHECK) &&
((tree->gtGetOp1()->gtFlags & GTF_ARR_BOUND_INBND) != 0))
{
optRemoveRangeCheck(tree, stmt);
return optAssertionProp_Update(tree, tree, stmt);
}
return nullptr;
}
/*****************************************************************************
*
* Given a tree consisting of a Ind and a set of available assertions, we try
* to propagate an assertion and modify the Ind tree if we can. We pass in the
* root of the tree via 'stmt', for local copy prop 'stmt' will be nullptr.
*
* Returns the modified tree, or nullptr if no assertion prop took place.
*
*/
GenTree* Compiler::optAssertionProp_Ind(ASSERT_VALARG_TP assertions, GenTree* tree, GenTree* stmt)
{
assert(tree->OperIsIndir());
if (!(tree->gtFlags & GTF_EXCEPT))
{
return nullptr;
}
// Check for add of a constant.
GenTree* op1 = tree->AsIndir()->Addr();
if ((op1->gtOper == GT_ADD) && (op1->gtOp.gtOp2->gtOper == GT_CNS_INT))
{
op1 = op1->gtOp.gtOp1;
}
if (op1->gtOper != GT_LCL_VAR)
{
return nullptr;
}
unsigned lclNum = op1->gtLclVarCommon.gtLclNum;
#ifdef DEBUG
bool vnBased = false;
AssertionIndex index = NO_ASSERTION_INDEX;
#endif
if (optAssertionIsNonNull(op1, assertions DEBUGARG(&vnBased) DEBUGARG(&index)))
{
#ifdef DEBUG
if (verbose)
{
(vnBased) ? printf("\nVN based non-null prop in BB%02u:\n", compCurBB->bbNum)
: printf("\nNon-null prop for index #%02u in BB%02u:\n", index, compCurBB->bbNum);
gtDispTree(tree, nullptr, nullptr, true);
}
#endif
tree->gtFlags &= ~GTF_EXCEPT;
tree->gtFlags |= GTF_IND_NONFAULTING;
// Set this flag to prevent reordering
tree->gtFlags |= GTF_ORDER_SIDEEFF;
return optAssertionProp_Update(tree, tree, stmt);
}
return nullptr;
}
/*****************************************************************************
* Check if a non-null assertion can be made about the input operand "op"
* from the set of "assertions," or implicitly from the value number on "op."
*
* Sets "pVnBased" if the assertion is value number based. If no matching
* assertions are found from the table, then returns "NO_ASSERTION_INDEX."
*
* Note: If both VN and assertion table yield a matching assertion, "pVnBased"
* is only set and the return value is "NO_ASSERTION_INDEX."
*/
bool Compiler::optAssertionIsNonNull(GenTree* op,
ASSERT_VALARG_TP assertions DEBUGARG(bool* pVnBased)
DEBUGARG(AssertionIndex* pIndex))
{
bool vnBased = (!optLocalAssertionProp && vnStore->IsKnownNonNull(op->gtVNPair.GetConservative()));
#ifdef DEBUG
*pVnBased = vnBased;
#endif
if (vnBased)
{
#ifdef DEBUG
*pIndex = NO_ASSERTION_INDEX;
#endif
return true;
}
AssertionIndex index = optAssertionIsNonNullInternal(op, assertions);
#ifdef DEBUG
*pIndex = index;
#endif
return index != NO_ASSERTION_INDEX;
}
/*****************************************************************************
* Check if a non-null assertion can be made about the input operand "op"
* from the set of "assertions."
*
*/
AssertionIndex Compiler::optAssertionIsNonNullInternal(GenTree* op, ASSERT_VALARG_TP assertions)
{
// If local assertion prop use lcl comparison, else use VN comparison.
if (!optLocalAssertionProp)
{
if (BitVecOps::MayBeUninit(assertions) || BitVecOps::IsEmpty(apTraits, assertions))
{
return NO_ASSERTION_INDEX;
}
ValueNum vn = op->gtVNPair.GetConservative();
// Check each assertion to find if we have a vn == or != null assertion.
BitVecOps::Iter iter(apTraits, assertions);
unsigned index = 0;
while (iter.NextElem(&index))
{
AssertionIndex assertionIndex = GetAssertionIndex(index);
if (assertionIndex > optAssertionCount)
{
break;
}
AssertionDsc* curAssertion = optGetAssertion(assertionIndex);
if (curAssertion->assertionKind != OAK_NOT_EQUAL)
{
continue;
}
if (curAssertion->op1.vn != vn || curAssertion->op2.vn != ValueNumStore::VNForNull())
{
continue;
}
return assertionIndex;
}
}
else
{
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++)
{
AssertionDsc* curAssertion = optGetAssertion(index);
if ((curAssertion->assertionKind == OAK_NOT_EQUAL) && // kind
(curAssertion->op1.kind == O1K_LCLVAR) && // op1
(curAssertion->op2.kind == O2K_CONST_INT) && // op2
(curAssertion->op1.lcl.lclNum == lclNum) && (curAssertion->op2.u1.iconVal == 0))
{
return index;
}
}
}
return NO_ASSERTION_INDEX;
}
/*****************************************************************************
*
* Given a tree consisting of a call and a set of available assertions, we
* try to propagate a non-null assertion and modify the Call tree if we can.
* Returns the modified tree, or nullptr if no assertion prop took place.
*
*/
GenTree* Compiler::optNonNullAssertionProp_Call(ASSERT_VALARG_TP assertions, GenTreeCall* call, GenTree* stmt)
{
if ((call->gtFlags & GTF_CALL_NULLCHECK) == 0)
{
return nullptr;
}
GenTree* op1 = gtGetThisArg(call);
noway_assert(op1 != nullptr);
if (op1->gtOper != GT_LCL_VAR)
{
return nullptr;
}
#ifdef DEBUG
bool vnBased = false;
AssertionIndex index = NO_ASSERTION_INDEX;
#endif
if (optAssertionIsNonNull(op1, assertions DEBUGARG(&vnBased) DEBUGARG(&index)))
{
#ifdef DEBUG
if (verbose)
{
(vnBased) ? printf("\nVN based non-null prop in BB%02u:\n", compCurBB->bbNum)
: printf("\nNon-null prop for index #%02u in BB%02u:\n", index, compCurBB->bbNum);
gtDispTree(call, nullptr, nullptr, true);
}
#endif
call->gtFlags &= ~GTF_CALL_NULLCHECK;
call->gtFlags &= ~GTF_EXCEPT;
noway_assert(call->gtFlags & GTF_SIDE_EFFECT);
return call;
}
return nullptr;
}
/*****************************************************************************
*
* Given a tree consisting of a call and a set of available assertions, we
* try to propagate an assertion and modify the Call tree if we can. Our
* current modifications are limited to removing the nullptrCHECK flag from
* the call.
* We pass in the root of the tree via 'stmt', for local copy prop 'stmt'
* will be nullptr. Returns the modified tree, or nullptr if no assertion prop
* took place.
*
*/
GenTree* Compiler::optAssertionProp_Call(ASSERT_VALARG_TP assertions, GenTreeCall* call, GenTree* stmt)
{
if (optNonNullAssertionProp_Call(assertions, call, stmt))
{
return optAssertionProp_Update(call, call, stmt);
}
else if (!optLocalAssertionProp && (call->gtCallType == CT_HELPER))
{
if (call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_ISINSTANCEOFINTERFACE) ||
call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_ISINSTANCEOFARRAY) ||
call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_ISINSTANCEOFCLASS) ||
call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_ISINSTANCEOFANY) ||
call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_CHKCASTINTERFACE) ||
call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_CHKCASTARRAY) ||
call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_CHKCASTCLASS) ||
call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_CHKCASTANY) ||
call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_CHKCASTCLASS_SPECIAL))
{
GenTree* arg1 = gtArgEntryByArgNum(call, 1)->node;
if (arg1->gtOper != GT_LCL_VAR)
{
return nullptr;
}
GenTree* arg2 = gtArgEntryByArgNum(call, 0)->node;
unsigned index = optAssertionIsSubtype(arg1, arg2, assertions);
if (index != NO_ASSERTION_INDEX)
{
#ifdef DEBUG
if (verbose)
{
printf("\nDid VN based subtype prop for index #%02u in BB%02u:\n", index, compCurBB->bbNum);
gtDispTree(call, nullptr, nullptr, true);
}
#endif
GenTree* list = nullptr;
gtExtractSideEffList(call, &list, GTF_SIDE_EFFECT, true);
if (list != nullptr)
{
arg1 = gtNewOperNode(GT_COMMA, call->TypeGet(), list, arg1);
fgSetTreeSeq(arg1);
}
return optAssertionProp_Update(arg1, call, stmt);
}
}
}
return nullptr;
}
/*****************************************************************************
*
* Given a tree consisting of a comma node with a bounds check, remove any
* redundant bounds check that has already been checked in the program flow.
*/
GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, GenTree* tree, GenTree* stmt)
{
if (optLocalAssertionProp)
{
return nullptr;
}
assert(tree->gtOper == GT_ARR_BOUNDS_CHECK);
#ifdef FEATURE_ENABLE_NO_RANGE_CHECKS
if (JitConfig.JitNoRangeChks())
{
#ifdef DEBUG
if (verbose)
{
printf("\nFlagging check redundant due to JitNoRangeChks in BB%02u:\n", compCurBB->bbNum);
gtDispTree(tree, nullptr, nullptr, true);
}
#endif // DEBUG
tree->gtFlags |= GTF_ARR_BOUND_INBND;
return nullptr;
}
#endif // FEATURE_ENABLE_NO_RANGE_CHECKS
BitVecOps::Iter iter(apTraits, assertions);
unsigned index = 0;
while (iter.NextElem(&index))
{
AssertionIndex assertionIndex = GetAssertionIndex(index);
if (assertionIndex > optAssertionCount)