Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

emit branchless form of (i >= 0 && j >= 0)/(i!=0&& j!= 0) for signed integers #62689

Merged
merged 6 commits into from
Oct 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 89 additions & 20 deletions src/coreclr/jit/optimizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9053,7 +9053,7 @@ bool OptBoolsDsc::optOptimizeBoolsCondBlock()
foldType = TYP_I_IMPL;
}

assert(m_testInfo1.compTree->gtOper == GT_EQ || m_testInfo1.compTree->gtOper == GT_NE);
assert(m_testInfo1.compTree->OperIs(GT_EQ, GT_NE, GT_LT, GT_GE));

if (m_sameTarget)
{
Expand All @@ -9072,6 +9072,18 @@ bool OptBoolsDsc::optOptimizeBoolsCondBlock()
foldOp = GT_AND;
cmpOp = GT_EQ;
}
else if (m_testInfo1.compTree->gtOper == GT_LT)
{
// t1:c1<0 t2:c2<0 ==> Branch to BX if either value < 0
// So we will branch to BX if (c1|c2)<0

foldOp = GT_OR;
cmpOp = GT_LT;
}
else if (m_testInfo1.compTree->gtOper == GT_GE)
{
return false;
}
else
{
// t1:c1!=0 t2:c2!=0 ==> Branch to BX if either value is non-0
Expand All @@ -9083,30 +9095,43 @@ bool OptBoolsDsc::optOptimizeBoolsCondBlock()
}
else
{
// The m_b1 condition must be the reverse of the m_b2 condition because the only operators
// that we will see here are GT_EQ and GT_NE. So, if they are not the same, we have one of each.

if (m_testInfo1.compTree->gtOper == m_testInfo2.compTree->gtOper)
{
return false;
}

if (m_testInfo1.compTree->gtOper == GT_EQ)
if (m_testInfo1.compTree->gtOper == GT_EQ && m_testInfo2.compTree->gtOper == GT_NE)
{
// t1:c1==0 t2:c2!=0 ==> Branch to BX if both values are non-0
// So we will branch to BX if (c1&c2)!=0

foldOp = GT_AND;
cmpOp = GT_NE;
}
else
else if (m_testInfo1.compTree->gtOper == GT_LT && m_testInfo2.compTree->gtOper == GT_GE)
{
// t1:c1<0 t2:c2>=0 ==> Branch to BX if both values >= 0
// So we will branch to BX if (c1|c2)>=0

foldOp = GT_OR;
cmpOp = GT_GE;
}
else if (m_testInfo1.compTree->gtOper == GT_GE)
{
return false;
}
else if (m_testInfo1.compTree->gtOper == GT_NE && m_testInfo2.compTree->gtOper == GT_EQ)
{
// t1:c1!=0 t2:c2==0 ==> Branch to BX if both values are 0
// So we will branch to BX if (c1|c2)==0

foldOp = GT_OR;
cmpOp = GT_EQ;
}
else
{
return false;
}
}

// Anding requires both values to be 0 or 1
Expand Down Expand Up @@ -9248,8 +9273,8 @@ Statement* OptBoolsDsc::optOptimizeBoolsChkBlkCond()
//
bool OptBoolsDsc::optOptimizeBoolsChkTypeCostCond()
{
assert(m_testInfo1.compTree->OperIs(GT_EQ, GT_NE) && m_testInfo1.compTree->AsOp()->gtOp1 == m_c1);
assert(m_testInfo2.compTree->OperIs(GT_EQ, GT_NE) && m_testInfo2.compTree->AsOp()->gtOp1 == m_c2);
assert(m_testInfo1.compTree->OperIs(GT_EQ, GT_NE, GT_LT, GT_GE) && m_testInfo1.compTree->AsOp()->gtOp1 == m_c1);
assert(m_testInfo2.compTree->OperIs(GT_EQ, GT_NE, GT_LT, GT_GE) && m_testInfo2.compTree->AsOp()->gtOp1 == m_c2);

//
// Leave out floats where the bit-representation is more complicated
Expand Down Expand Up @@ -9520,7 +9545,7 @@ bool OptBoolsDsc::optOptimizeBoolsReturnBlock(BasicBlock* b3)
}

// Get the fold operator (m_foldOp, e.g., GT_OR/GT_AND) and
// the comparison operator (m_cmpOp, e.g., GT_EQ/GT_NE)
// the comparison operator (m_cmpOp, e.g., GT_EQ/GT_NE/GT_GE/GT_LT)

var_types foldType = m_c1->TypeGet();
if (varTypeIsGC(foldType))
Expand Down Expand Up @@ -9557,6 +9582,16 @@ bool OptBoolsDsc::optOptimizeBoolsReturnBlock(BasicBlock* b3)
foldOp = GT_AND;
cmpOp = GT_NE;
}
else if ((m_testInfo1.compTree->gtOper == GT_LT && m_testInfo2.compTree->gtOper == GT_GE) &&
(it1val == 0 && it2val == 0 && it3val == 0))
{
// Case: x >= 0 && y >= 0
// t1:c1<0 t2:c2>=0 t3:c3==0
// ==> true if (c1|c2)>=0

foldOp = GT_OR;
cmpOp = GT_GE;
}
else if ((m_testInfo1.compTree->gtOper == GT_EQ && m_testInfo2.compTree->gtOper == GT_EQ) &&
(it1val == 0 && it2val == 0 && it3val == 1))
{
Expand All @@ -9575,13 +9610,23 @@ bool OptBoolsDsc::optOptimizeBoolsReturnBlock(BasicBlock* b3)
foldOp = GT_OR;
cmpOp = GT_NE;
}
else if ((m_testInfo1.compTree->gtOper == GT_LT && m_testInfo2.compTree->gtOper == GT_LT) &&
(it1val == 0 && it2val == 0 && it3val == 1))
{
// Case: x < 0 || y < 0
// t1:c1<0 t2:c2<0 t3:c3==1
// ==> true if (c1|c2)<0

foldOp = GT_OR;
cmpOp = GT_LT;
}
else
{
// Require NOT operation for operand(s). Do Not fold.
return false;
}

if ((foldOp == GT_AND || cmpOp == GT_NE) && (!m_testInfo1.isBool || !m_testInfo2.isBool))
if ((foldOp == GT_AND || (cmpOp == GT_NE && foldOp != GT_OR)) && (!m_testInfo1.isBool || !m_testInfo2.isBool))
{
// x == 1 && y == 1: Skip cases where x or y is greater than 1, e.g., x=3, y=1
// x == 0 || y == 0: Skip cases where x and y have opposite bits set, e.g., x=2, y=1
Expand Down Expand Up @@ -9680,15 +9725,15 @@ void OptBoolsDsc::optOptimizeBoolsGcStress()
//
// Notes:
// On entry, testTree is set.
// On success, compTree is set to the compare node (i.e. GT_EQ or GT_NE) of the testTree.
// On success, compTree is set to the compare node (i.e. GT_EQ or GT_NE or GT_LT or GT_GE) of the testTree.
// isBool is set to true if the comparand (i.e., operand 1 of compTree is boolean. Otherwise, false.
//
// Given a GT_JTRUE or GT_RETURN node, this method checks if it is a boolean comparison
// of the form "if (boolVal ==/!= 0/1)".This is translated into
// a GT_EQ/GT_NE node with "opr1" being a boolean lclVar and "opr2" the const 0/1.
// of the form "if (boolVal ==/!=/>=/< 0/1)".This is translated into
// a GT_EQ/GT_NE/GT_GE/GT_LT node with "opr1" being a boolean lclVar and "opr2" the const 0/1.
//
// When isBool == true, if the comparison was against a 1 (i.e true)
// then we morph the tree by reversing the GT_EQ/GT_NE and change the 1 to 0.
// then we morph the tree by reversing the GT_EQ/GT_NE/GT_GE/GT_LT and change the 1 to 0.
//
GenTree* OptBoolsDsc::optIsBoolComp(OptTestInfo* pOptTest)
{
Expand All @@ -9697,9 +9742,9 @@ GenTree* OptBoolsDsc::optIsBoolComp(OptTestInfo* pOptTest)
assert(pOptTest->testTree->gtOper == GT_JTRUE || pOptTest->testTree->gtOper == GT_RETURN);
GenTree* cond = pOptTest->testTree->AsOp()->gtOp1;

// The condition must be "!= 0" or "== 0"

if ((cond->gtOper != GT_EQ) && (cond->gtOper != GT_NE))
// The condition must be "!= 0" or "== 0" or >=0 or <0
// we don't optimize unsigned < and >= operations
if (!cond->OperIs(GT_EQ, GT_NE) && (!cond->OperIs(GT_LT, GT_GE) || cond->IsUnsigned()))
{
return nullptr;
}
Expand Down Expand Up @@ -9776,9 +9821,9 @@ GenTree* OptBoolsDsc::optIsBoolComp(OptTestInfo* pOptTest)
// suitable phase status
//
// Notes:
// If the operand of GT_JTRUE/GT_RETURN node is GT_EQ/GT_NE of the form
// "if (boolVal ==/!= 0/1)", the GT_EQ/GT_NE nodes are translated into a
// GT_EQ/GT_NE node with
// If the operand of GT_JTRUE/GT_RETURN node is GT_EQ/GT_NE/GT_GE/GT_LT of the form
// "if (boolVal ==/!=/>=/< 0/1)", the GT_EQ/GT_NE/GT_GE/GT_LT nodes are translated into a
// GT_EQ/GT_NE/GT_GE/GT_LT node with
// "op1" being a boolean GT_OR/GT_AND lclVar and
// "op2" the const 0/1.
// For example, the folded tree for the below boolean optimization is shown below:
Expand Down Expand Up @@ -9820,6 +9865,30 @@ GenTree* OptBoolsDsc::optIsBoolComp(OptTestInfo* pOptTest)
// | \--* LCL_VAR int V03 arg3
// \--* CNS_INT int 0
//
// Case 5: (x != 0 && y != 0) => (x | y) != 0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be (x != 0 || y != 0) I'm assuming?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes it should be (x != 0 || y != 0)

// * RETURN int
// \--* NE int
// +--* OR int
// | +--* LCL_VAR int V00 arg0
// | \--* LCL_VAR int V01 arg1
// \--* CNS_INT int 0
//
// Case 6: (x >= 0 && y >= 0) => (x | y) >= 0
// * RETURN int
// \--* GE int
// +--* OR int
// | +--* LCL_VAR int V00 arg0
// | \--* LCL_VAR int V01 arg1
// \--* CNS_INT int 0
//
// Case 7: (x < 0 || y < 0) => (x & y) < 0
// * RETURN int
// \--* LT int
// +--* AND int
// | +--* LCL_VAR int V00 arg0
// | \--* LCL_VAR int V01 arg1
// \--* CNS_INT int 0
//
// Patterns that are not optimized include (x == 1 && y == 1), (x == 1 || y == 1),
// (x == 0 || y == 0) because currently their comptree is not marked as boolean expression.
// When m_foldOp == GT_AND or m_cmpOp == GT_NE, both compTrees must be boolean expression
Expand Down
Loading