Skip to content

Commit

Permalink
Convert DIV/MOD to UDIV/UMOD using assertions (#94347)
Browse files Browse the repository at this point in the history
  • Loading branch information
EgorBo committed Nov 6, 2023
1 parent 6d76479 commit 6066de1
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 25 deletions.
84 changes: 82 additions & 2 deletions src/coreclr/jit/assertionprop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3541,6 +3541,84 @@ GenTree* Compiler::optAssertionProp_BlockStore(ASSERT_VALARG_TP assertions, GenT
return nullptr;
}

//------------------------------------------------------------------------
// optAssertionProp_ModDiv: Convert DIV/MOD to UDIV/UMOD if both operands
// can be proven to be non-negative, e.g.:
//
// if (x > 0) // creates "x > 0" assertion
// return x / 8; // DIV can be converted to UDIV
//
// Arguments:
// assertions - set of live assertions
// tree - the DIV/MOD node to optimize
// stmt - statement containing DIV/MOD
//
// Returns:
// Updated UDIV/UMOD node, or "nullptr"
//
GenTree* Compiler::optAssertionProp_ModDiv(ASSERT_VALARG_TP assertions, GenTreeOp* tree, Statement* stmt)
{
if (optLocalAssertionProp || !varTypeIsIntegral(tree) || BitVecOps::IsEmpty(apTraits, assertions))
{
return nullptr;
}

// For now, we're mainly interested in "X op CNS" pattern (where CNS > 0).
// Technically, we can check assertions for both operands, but it's not clear if it's worth it.
if (!tree->gtGetOp2()->IsNeverNegative(this))
{
return nullptr;
}

const ValueNum dividendVN = vnStore->VNConservativeNormalValue(tree->gtGetOp1()->gtVNPair);
BitVecOps::Iter iter(apTraits, assertions);
unsigned index = 0;
while (iter.NextElem(&index))
{
AssertionDsc* curAssertion = optGetAssertion(GetAssertionIndex(index));

// OAK_[NOT]_EQUAL assertion with op1 being O1K_CONSTANT_LOOP_BND
// representing "(X relop CNS) ==/!= 0" assertion.
if (!curAssertion->IsConstantBound())
{
continue;
}

ValueNumStore::ConstantBoundInfo info;
vnStore->GetConstantBoundInfo(curAssertion->op1.vn, &info);

if (info.cmpOpVN != dividendVN)
{
continue;
}

// Root assertion has to be either:
// (X relop CNS) == 0
// (X relop CNS) != 0
if ((curAssertion->op2.kind != O2K_CONST_INT) || (curAssertion->op2.u1.iconVal != 0))
{
continue;
}

genTreeOps cmpOper = static_cast<genTreeOps>(info.cmpOper);

// Normalize "(X relop CNS) == false" to "(X reversed_relop CNS) == true"
if (curAssertion->assertionKind == OAK_EQUAL)
{
cmpOper = GenTree::ReverseRelop(cmpOper);
}

// "X >= CNS" or "X > CNS" where CNS is >= 0
if ((info.constVal >= 0) && ((cmpOper == GT_GE) || (cmpOper == GT_GT)))
{
JITDUMP("Converting DIV/MOD to unsigned UDIV/UMOD since both operands are never negative...\n")
tree->SetOper(tree->OperIs(GT_DIV) ? GT_UDIV : GT_UMOD, GenTree::PRESERVE_VN);
return optAssertionProp_Update(tree, tree, stmt);
}
}
return nullptr;
}

//------------------------------------------------------------------------
// optAssertionProp_Return: Try and optimize a GT_RETURN via assertions.
//
Expand Down Expand Up @@ -4787,6 +4865,10 @@ GenTree* Compiler::optAssertionProp(ASSERT_VALARG_TP assertions, GenTree* tree,
case GT_RETURN:
return optAssertionProp_Return(assertions, tree->AsUnOp(), stmt);

case GT_MOD:
case GT_DIV:
return optAssertionProp_ModDiv(assertions, tree->AsOp(), stmt);

case GT_BLK:
case GT_IND:
case GT_STOREIND:
Expand All @@ -4812,11 +4894,9 @@ GenTree* Compiler::optAssertionProp(ASSERT_VALARG_TP assertions, GenTree* tree,
case GT_LE:
case GT_GT:
case GT_GE:

return optAssertionProp_RelOp(assertions, tree, stmt);

case GT_JTRUE:

if (block != nullptr)
{
return optVNConstantPropOnJTrue(block, tree);
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -7746,6 +7746,7 @@ class Compiler
GenTree* optAssertionProp_LclFld(ASSERT_VALARG_TP assertions, GenTreeLclVarCommon* tree, Statement* stmt);
GenTree* optAssertionProp_LocalStore(ASSERT_VALARG_TP assertions, GenTreeLclVarCommon* store, Statement* stmt);
GenTree* optAssertionProp_BlockStore(ASSERT_VALARG_TP assertions, GenTreeBlk* store, Statement* stmt);
GenTree* optAssertionProp_ModDiv(ASSERT_VALARG_TP assertions, GenTreeOp* tree, Statement* stmt);
GenTree* optAssertionProp_Return(ASSERT_VALARG_TP assertions, GenTreeUnOp* ret, Statement* stmt);
GenTree* optAssertionProp_Ind(ASSERT_VALARG_TP assertions, GenTree* tree, Statement* stmt);
GenTree* optAssertionProp_Cast(ASSERT_VALARG_TP assertions, GenTreeCast* cast, Statement* stmt);
Expand Down
14 changes: 0 additions & 14 deletions src/coreclr/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8764,8 +8764,6 @@ void GenTreeOp::CheckDivideByConstOptimized(Compiler* comp)
{
if (UsesDivideByConstOptimized(comp))
{
gtFlags |= GTF_DIV_BY_CNS_OPT;

// Now set DONT_CSE on the GT_CNS_INT divisor, note that
// with ValueNumbering we can have a non GT_CNS_INT divisor
GenTree* divisor = gtGetOp2()->gtEffectiveVal(/*commaOnly*/ true);
Expand Down Expand Up @@ -11143,18 +11141,6 @@ void Compiler::gtDispNode(GenTree* tree, IndentStack* indentStack, _In_ _In_opt_
}
goto DASH;

case GT_DIV:
case GT_MOD:
case GT_UDIV:
case GT_UMOD:
if (tree->gtFlags & GTF_DIV_BY_CNS_OPT)
{
printf("M"); // We will use a Multiply by reciprical
--msgLength;
break;
}
goto DASH;

case GT_LCL_FLD:
case GT_LCL_VAR:
case GT_LCL_ADDR:
Expand Down
8 changes: 0 additions & 8 deletions src/coreclr/jit/gentree.h
Original file line number Diff line number Diff line change
Expand Up @@ -547,8 +547,6 @@ enum GenTreeFlags : unsigned int

GTF_DIV_MOD_NO_OVERFLOW = 0x40000000, // GT_DIV, GT_MOD -- Div or mod definitely does not overflow.

GTF_DIV_BY_CNS_OPT = 0x80000000, // GT_DIV -- Uses the division by constant optimization to compute this division

GTF_CHK_INDEX_INBND = 0x80000000, // GT_BOUNDS_CHECK -- have proven this check is always in-bounds

GTF_ARRLEN_NONFAULTING = 0x20000000, // GT_ARR_LENGTH -- An array length operation that cannot fault. Same as GT_IND_NONFAULTING.
Expand Down Expand Up @@ -3007,12 +3005,6 @@ struct GenTreeOp : public GenTreeUnOp
// then sets the flag GTF_DIV_BY_CNS_OPT and GTF_DONT_CSE on the constant
void CheckDivideByConstOptimized(Compiler* comp);

// True if this node is marked as using the division by constant optimization
bool MarkedDivideByConstOptimized() const
{
return (gtFlags & GTF_DIV_BY_CNS_OPT) != 0;
}

#if !defined(TARGET_64BIT) || defined(TARGET_ARM64)
bool IsValidLongMul();
#endif
Expand Down
1 change: 0 additions & 1 deletion src/coreclr/jit/lower.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6584,7 +6584,6 @@ bool Lowering::LowerUnsignedDivOrMod(GenTreeOp* divMod)
unreached();
#endif
}
assert(divMod->MarkedDivideByConstOptimized());

const bool requiresDividendMultiuse = !isDiv;
const weight_t curBBWeight = m_block->getBBWeight(comp);
Expand Down

0 comments on commit 6066de1

Please sign in to comment.