diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index febf028ac088c5..be347988d131ae 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -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) { @@ -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 @@ -9083,15 +9095,12 @@ 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 @@ -9099,7 +9108,19 @@ bool OptBoolsDsc::optOptimizeBoolsCondBlock() 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 @@ -9107,6 +9128,10 @@ bool OptBoolsDsc::optOptimizeBoolsCondBlock() foldOp = GT_OR; cmpOp = GT_EQ; } + else + { + return false; + } } // Anding requires both values to be 0 or 1 @@ -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 @@ -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)) @@ -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)) { @@ -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 @@ -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) { @@ -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; } @@ -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: @@ -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 +// * 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 diff --git a/src/tests/JIT/opt/OptimizeBools/optboolsreturn.cs b/src/tests/JIT/opt/OptimizeBools/optboolsreturn.cs index 3b2fe8af97b3ce..2144be73aedca4 100644 --- a/src/tests/JIT/opt/OptimizeBools/optboolsreturn.cs +++ b/src/tests/JIT/opt/OptimizeBools/optboolsreturn.cs @@ -140,6 +140,57 @@ private static bool IsEitherOne(int x, int y) return (x == 1 || y == 1); } + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool BothGreatThanZero(int x, int y) + { + return x >= 0 && y >= 0; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool EitherLessThanZero(int x, int y) + { + return x < 0 || y < 0; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool EitherNonZero(int x, int y) + { + return x != 0 || y != 0; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool AreBothGreatThanZero(int x, int y) + { + bool b = x >= 0 && y >= 0; + if (b) + { + Console.WriteLine("AreBothGreatThanZero true"); + } + return b; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool IsEitherLessThanZero(int x, int y) + { + bool b = x < 0 || y < 0; + if (b) + { + Console.WriteLine("IsEitherLessThanZero true"); + } + return b; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool IsEitherNonZero(int x, int y) + { + bool b = x != 0 || y != 0; + if (b) + { + Console.WriteLine("IsEitherNonZero true"); + } + return b; + } + public static int Main() { // Optimize boolean @@ -234,6 +285,114 @@ public static int Main() return 101; } + if (!BothGreatThanZero(45, 23)) + { + Console.WriteLine("CBoolTest:BothGreatThanZero(45, 23) failed"); + return 101; + } + + if (!BothGreatThanZero(0, 23)) + { + Console.WriteLine("CBoolTest:BothGreatThanZero(0, 23) failed"); + return 101; + } + + if (!BothGreatThanZero(45, 0)) + { + Console.WriteLine("CBoolTest:BothGreatThanZero(45, 0) failed"); + return 101; + } + + if (!BothGreatThanZero(0, 0)) + { + Console.WriteLine("CBoolTest:BothGreatThanZero(0, 0) failed"); + return 101; + } + + if (BothGreatThanZero(-22, 23)) + { + Console.WriteLine("CBoolTest:BothGreatThanZero(-22, 23) failed"); + return 101; + } + + if (BothGreatThanZero(45, -36)) + { + Console.WriteLine("CBoolTest:BothGreatThanZero(45, -36) failed"); + return 101; + } + + if (BothGreatThanZero(-22, -36)) + { + Console.WriteLine("CBoolTest:BothGreatThanZero(-22, -36) failed"); + return 101; + } + + if (EitherLessThanZero(45, 23)) + { + Console.WriteLine("CBoolTest:EitherLessThanZero(45, 23) failed"); + return 101; + } + + if (EitherLessThanZero(0, 23)) + { + Console.WriteLine("CBoolTest:EitherLessThanZero(0, 23) failed"); + return 101; + } + + if (EitherLessThanZero(45, 0)) + { + Console.WriteLine("CBoolTest:EitherLessThanZero(45, 0) failed"); + return 101; + } + + if (EitherLessThanZero(0, 0)) + { + Console.WriteLine("CBoolTest:EitherLessThanZero(0, 0) failed"); + return 101; + } + + if (!EitherLessThanZero(-22, 23)) + { + Console.WriteLine("CBoolTest:EitherLessThanZero(-22, 23) failed"); + return 101; + } + + if (!EitherLessThanZero(45, -36)) + { + Console.WriteLine("CBoolTest:EitherLessThanZero(45, -36) failed"); + return 101; + } + + if (!EitherLessThanZero(-22, -36)) + { + Console.WriteLine("CBoolTest:EitherLessThanZero(-22, -36) failed"); + return 101; + } + + if (!EitherNonZero(45, 23)) + { + Console.WriteLine("CBoolTest:EitherNonZero(45, 23) failed"); + return 101; + } + + if (!EitherNonZero(0, 23)) + { + Console.WriteLine("CBoolTest:EitherNonZero(0, 23) failed"); + return 101; + } + + if (!EitherNonZero(45, 0)) + { + Console.WriteLine("CBoolTest:EitherNonZero(45, 0) failed"); + return 101; + } + + if (EitherNonZero(0, 0)) + { + Console.WriteLine("CBoolTest:EitherNonZero(0, 0) failed"); + return 101; + } + if (!AreZero3(0, 0, 0)) { Console.WriteLine("CBoolTest:AreZero3(0, 0, 0) failed"); @@ -308,6 +467,114 @@ public static int Main() return 101; } + if (!AreBothGreatThanZero(45, 23)) + { + Console.WriteLine("CBoolTest:AreBothGreatThanZero(45, 23) failed"); + return 101; + } + + if (!AreBothGreatThanZero(0, 23)) + { + Console.WriteLine("CBoolTest:AreBothGreatThanZero(0, 23) failed"); + return 101; + } + + if (!AreBothGreatThanZero(45, 0)) + { + Console.WriteLine("CBoolTest:AreBothGreatThanZero(45, 0) failed"); + return 101; + } + + if (!AreBothGreatThanZero(0, 0)) + { + Console.WriteLine("CBoolTest:AreBothGreatThanZero(0, 0) failed"); + return 101; + } + + if (AreBothGreatThanZero(-22, 23)) + { + Console.WriteLine("CBoolTest:AreBothGreatThanZero(-22, 23) failed"); + return 101; + } + + if (AreBothGreatThanZero(45, -36)) + { + Console.WriteLine("CBoolTest:AreBothGreatThanZero(45, -36) failed"); + return 101; + } + + if (AreBothGreatThanZero(-22, -36)) + { + Console.WriteLine("CBoolTest:AreBothGreatThanZero(-22, -36) failed"); + return 101; + } + + if (IsEitherLessThanZero(45, 23)) + { + Console.WriteLine("CBoolTest:IsEitherLessThanZero(45, 23) failed"); + return 101; + } + + if (IsEitherLessThanZero(0, 23)) + { + Console.WriteLine("CBoolTest:IsEitherLessThanZero(0, 23) failed"); + return 101; + } + + if (IsEitherLessThanZero(45, 0)) + { + Console.WriteLine("CBoolTest:IsEitherLessThanZero(45, 0) failed"); + return 101; + } + + if (IsEitherLessThanZero(0, 0)) + { + Console.WriteLine("CBoolTest:IsEitherLessThanZero(0, 0) failed"); + return 101; + } + + if (!IsEitherLessThanZero(-22, 23)) + { + Console.WriteLine("CBoolTest:IsEitherLessThanZero(-22, 23) failed"); + return 101; + } + + if (!IsEitherLessThanZero(45, -36)) + { + Console.WriteLine("CBoolTest:IsEitherLessThanZero(45, -36) failed"); + return 101; + } + + if (!IsEitherLessThanZero(-22, -36)) + { + Console.WriteLine("CBoolTest:IsEitherLessThanZero(-22, -36) failed"); + return 101; + } + + if (!IsEitherNonZero(45, 23)) + { + Console.WriteLine("CBoolTest:IsEitherNonZero(45, 23) failed"); + return 101; + } + + if (!IsEitherNonZero(0, 23)) + { + Console.WriteLine("CBoolTest:IsEitherNonZero(0, 23) failed"); + return 101; + } + + if (!IsEitherNonZero(45, 0)) + { + Console.WriteLine("CBoolTest:IsEitherNonZero(45, 0) failed"); + return 101; + } + + if (IsEitherNonZero(0, 0)) + { + Console.WriteLine("CBoolTest:IsEitherNonZero(0, 0) failed"); + return 101; + } + if (!AreNullWithOutput(null, null)) { Console.WriteLine("CBoolTest:AreNullWithOutput(null, null) failed");