From f193a8837a57e2e221fa8a0bbb77dfeb10744c90 Mon Sep 17 00:00:00 2001 From: Egor Bogatov Date: Thu, 4 Jun 2026 00:02:36 +0200 Subject: [PATCH 1/6] cleanup --- src/coreclr/jit/optimizebools.cpp | 330 +++++++----------------------- 1 file changed, 71 insertions(+), 259 deletions(-) diff --git a/src/coreclr/jit/optimizebools.cpp b/src/coreclr/jit/optimizebools.cpp index 61317173653735..bd5e4432331d2c 100644 --- a/src/coreclr/jit/optimizebools.cpp +++ b/src/coreclr/jit/optimizebools.cpp @@ -104,6 +104,48 @@ class OptBoolsDsc bool FindCompareChain(GenTree* condition, bool* isTestCondition); }; +//----------------------------------------------------------------------------- +// OptBoolsRule: one rewrite rule consumed by OptBoolsDsc::optOptimizeBoolsCondBlock. +// +// The table is written in the m_sameTarget == true frame. +// A rule matches when (sameLcl, op1, op2) agree and produces the folded compare: +// +// foldOp == GT_NONE : keep c1 as the sole operand and rewrite the compare to cmpOp +// (used for redundant tests against the same local). +// foldOp == GT_AND : (c1 & c2) cmpOp 0 (also requires both inputs to be boolean) +// foldOp == GT_OR : (c1 | c2) cmpOp 0 +// +// When 'requiresSigned' is set, neither test may carry GTF_UNSIGNED because the +// folded compare is signed (one of <0, <=0, >=0, >0). +// +struct OptBoolsRule +{ + bool sameLcl; // c1 and c2 must be the same LCL_VAR + genTreeOps op1; // m_testInfo1.compTree oper + genTreeOps op2; // m_testInfo2.compTree oper, normalized to the sameTarget=true frame + genTreeOps foldOp; // GT_AND, GT_OR, or GT_NONE (no fold; rewrite cmp only) + genTreeOps cmpOp; // resulting comparison, normalized to the sameTarget=true frame + bool requiresSigned; // reject if either test has GTF_UNSIGNED +}; + +// clang-format off +static const OptBoolsRule s_optBoolsRules[] = +{ + // sameLcl | op1 | op2 | foldOp | cmpOp | requiresSigned + // + // Redundant tests on the same local fold to a single signed compare. + { true, GT_LT, GT_EQ, GT_NONE, GT_LE, true }, // c1<0 || c1==0 -> c1<=0 + { true, GT_EQ, GT_LT, GT_NONE, GT_LE, true }, // c1==0 || c1<0 -> c1<=0 + { true, GT_GT, GT_EQ, GT_NONE, GT_GE, true }, // c1>0 || c1==0 -> c1>=0 + { true, GT_EQ, GT_GT, GT_NONE, GT_GE, true }, // c1==0 || c1>0 -> c1>=0 + + // Independent tests fold to a single bitwise compare. + { false, GT_EQ, GT_EQ, GT_AND, GT_EQ, false }, // c1==0 || c2==0 -> (c1&c2)==0 + { false, GT_LT, GT_LT, GT_OR, GT_LT, true }, // c1<0 || c2<0 -> (c1|c2)<0 + { false, GT_NE, GT_NE, GT_OR, GT_NE, false }, // c1!=0 || c2!=0 -> (c1|c2)!=0 +}; +// clang-format on + //----------------------------------------------------------------------------- // optOptimizeBoolsCondBlock: Optimize boolean when bbKind of both m_b1 and m_b2 are BBJ_COND // @@ -192,160 +234,46 @@ bool OptBoolsDsc::optOptimizeBoolsCondBlock() return false; } - // Get the fold operator and the comparison operator + // Derive the fold type (used when constructing the folded bitwise op). - genTreeOps foldOp; - genTreeOps cmpOp; - var_types foldType = genActualType(m_c1); + var_types foldType = genActualType(m_c1); if (varTypeIsGC(foldType)) { foldType = TYP_I_IMPL; } assert(m_testInfo1.compTree->OperIs(GT_EQ, GT_NE, GT_LT, GT_GT, GT_GE, GT_LE)); + assert(m_testInfo2.compTree->OperIs(GT_EQ, GT_NE, GT_LT, GT_GT, GT_GE, GT_LE)); - if (m_sameTarget) - { - if (m_c1->OperIs(GT_LCL_VAR) && m_c2->OperIs(GT_LCL_VAR) && - m_c1->AsLclVarCommon()->GetLclNum() == m_c2->AsLclVarCommon()->GetLclNum()) - { - // The folded comparisons below are signed (e.g. "c1 <= 0", "c1 >= 0"), so - // bail if either input has GTF_UNSIGNED. - if (m_testInfo1.GetTestOp()->IsUnsigned() || m_testInfo2.GetTestOp()->IsUnsigned()) - { - return false; - } + // Pick the rewrite rule from the s_optBoolsRules table. + const genTreeOps op1 = m_testInfo1.compTree->OperGet(); + const genTreeOps op2raw = m_testInfo2.compTree->OperGet(); + const genTreeOps op2 = m_sameTarget ? op2raw : GenTree::ReverseRelop(op2raw); + const bool sameLcl = m_c1->OperIs(GT_LCL_VAR) && GenTree::Compare(m_c1, m_c2); - if ((m_testInfo1.compTree->OperIs(GT_LT) && m_testInfo2.compTree->OperIs(GT_EQ)) || - (m_testInfo1.compTree->OperIs(GT_EQ) && m_testInfo2.compTree->OperIs(GT_LT))) - { - // Case: t1:c1<0 t2:c1==0 - // So we will branch to BX if c1<=0 - // - // Case: t1:c1==0 t2:c1<0 - // So we will branch to BX if c1<=0 - cmpOp = GT_LE; - } - else if ((m_testInfo1.compTree->OperIs(GT_GT) && m_testInfo2.compTree->OperIs(GT_EQ)) || - (m_testInfo1.compTree->OperIs(GT_EQ) && m_testInfo2.compTree->OperIs(GT_GT))) - { - // Case: t1:c1>0 t2:c1==0 - // So we will branch to BX if c1>=0 - // - // Case: t1:c1==0 t2:c1>0 - // So we will branch to BX if c1>=0 - cmpOp = GT_GE; - } - else - { - return false; - } - - foldOp = GT_NONE; - } - else if (m_testInfo1.compTree->OperIs(GT_EQ) && m_testInfo2.compTree->OperIs(GT_EQ)) - { - // t1:c1==0 t2:c2==0 ==> Branch to BX if either value is 0 - // So we will branch to BX if (c1&c2)==0 - - foldOp = GT_AND; - cmpOp = GT_EQ; - } - else if (m_testInfo1.compTree->OperIs(GT_LT) && m_testInfo2.compTree->OperIs(GT_LT) && - (!m_testInfo1.GetTestOp()->IsUnsigned() && !m_testInfo2.GetTestOp()->IsUnsigned())) - { - // 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->OperIs(GT_NE) && m_testInfo2.compTree->OperIs(GT_NE)) - { - // t1:c1!=0 t2:c2!=0 ==> Branch to BX if either value is non-0 - // So we will branch to BX if (c1|c2)!=0 - - foldOp = GT_OR; - cmpOp = GT_NE; - } - else - { - return false; - } - } - else + const OptBoolsRule* rule = nullptr; + for (const OptBoolsRule& r : s_optBoolsRules) { - if (m_c1->OperIs(GT_LCL_VAR) && m_c2->OperIs(GT_LCL_VAR) && - m_c1->AsLclVarCommon()->GetLclNum() == m_c2->AsLclVarCommon()->GetLclNum()) + if ((r.sameLcl == sameLcl) && (r.op1 == op1) && (r.op2 == op2)) { - // The folded comparisons below are signed (e.g. "c1 > 0", "c1 < 0"), so - // bail if either input has GTF_UNSIGNED. - if (m_testInfo1.GetTestOp()->IsUnsigned() || m_testInfo2.GetTestOp()->IsUnsigned()) - { - return false; - } - - if ((m_testInfo1.compTree->OperIs(GT_LT) && m_testInfo2.compTree->OperIs(GT_NE)) || - (m_testInfo1.compTree->OperIs(GT_EQ) && m_testInfo2.compTree->OperIs(GT_GE))) - { - // Case: t1:c1<0 t2:c1!=0 - // So we will branch to BX if c1>0 - // - // Case: t1:c1==0 t2:c1>=0 - // So we will branch to BX if c1>0 - cmpOp = GT_GT; - } - else if ((m_testInfo1.compTree->OperIs(GT_GT) && m_testInfo2.compTree->OperIs(GT_NE)) || - (m_testInfo1.compTree->OperIs(GT_EQ) && m_testInfo2.compTree->OperIs(GT_LE))) - { - // Case: t1:c1>0 t2:c1!=0 - // So we will branch to BX if c1<0 - // - // Case: t1:c1==0 t2:c1<=0 - // So we will branch to BX if c1<0 - cmpOp = GT_LT; - } - else - { - return false; - } - - foldOp = GT_NONE; - } - else if (m_testInfo1.compTree->OperIs(GT_EQ) && m_testInfo2.compTree->OperIs(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; + rule = &r; + break; } - else if (m_testInfo1.compTree->OperIs(GT_LT) && m_testInfo2.compTree->OperIs(GT_GE) && - (!m_testInfo1.GetTestOp()->IsUnsigned() && !m_testInfo2.GetTestOp()->IsUnsigned())) - { - // 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->OperIs(GT_NE) && m_testInfo2.compTree->OperIs(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 + if (rule == nullptr) + { + return false; + } - foldOp = GT_OR; - cmpOp = GT_EQ; - } - else - { - return false; - } + if (rule->requiresSigned && (m_testInfo1.GetTestOp()->IsUnsigned() || m_testInfo2.GetTestOp()->IsUnsigned())) + { + return false; } - // Anding requires both values to be 0 or 1 + // Anding requires both values to be 0 or 1. - if ((foldOp == GT_AND) && (!m_testInfo1.isBool || !m_testInfo2.isBool)) + if ((rule->foldOp == GT_AND) && (!m_testInfo1.isBool || !m_testInfo2.isBool)) { return false; } @@ -354,9 +282,9 @@ bool OptBoolsDsc::optOptimizeBoolsCondBlock() // Now update the trees // - m_foldOp = foldOp; + m_foldOp = rule->foldOp; m_foldType = foldType; - m_cmpOp = cmpOp; + m_cmpOp = m_sameTarget ? rule->cmpOp : GenTree::ReverseRelop(rule->cmpOp); optOptimizeBoolsUpdateTrees(); @@ -1413,35 +1341,28 @@ GenTree* OptBoolsDsc::optIsBoolComp(OptTestInfo* pOptTest) GenTree* opr1 = cond->AsOp()->gtOp1; GenTree* opr2 = cond->AsOp()->gtOp2; - if (!opr2->OperIs(GT_CNS_INT)) - { - return nullptr; - } - if (!opr2->IsIntegralConst(0) && !opr2->IsIntegralConst(1)) { return nullptr; } - ssize_t ival2 = opr2->AsIntCon()->gtIconVal; - // Is the value a boolean? // We can either have a boolean expression (marked GTF_BOOLEAN) or a constant 0/1. - if (opr1->OperIs(GT_CNS_INT) && (opr1->IsIntegralConst(0) || opr1->IsIntegralConst(1))) + if (opr1->IsIntegralConst(0) || opr1->IsIntegralConst(1)) { pOptTest->isBool = true; } // Was our comparison against the constant 1 (i.e. true) - if (ival2 == 1) + if (opr2->IsIntegralConst(1)) { // If this is a boolean expression tree we can reverse the relop // and change the true to false. if (pOptTest->isBool) { m_compiler->gtReverseCond(cond); - opr2->AsIntCon()->gtIconVal = 0; + opr2->AsIntCon()->SetIconValue(0); } else { @@ -1464,116 +1385,7 @@ GenTree* OptBoolsDsc::optIsBoolComp(OptTestInfo* pOptTest) // GT_EQ/GT_NE/GT_GE/GT_LE/GT_GT/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: -// Case 1: (x == 0 && y ==0) => (x | y) == 0 -// * RETURN int -// \--* EQ int -// +--* OR int -// | +--* LCL_VAR int V00 arg0 -// | \--* LCL_VAR int V01 arg1 -// \--* CNS_INT int 0 -// -// Case 2: (x == null && y == null) ==> (x | y) == 0 -// * RETURN int -// \-- * EQ int -// + -- * OR long -// | +-- * LCL_VAR ref V00 arg0 -// | \-- * LCL_VAR ref V01 arg1 -// \-- * CNS_INT long 0 -// -// Case 3: (x == 0 && y == 0 && z == 0) ==> ((x | y) | z) == 0 -// * RETURN int -// \-- * EQ int -// + -- * OR int -// | +-- * OR int -// | | +-- * LCL_VAR int V00 arg0 -// | | \-- * LCL_VAR int V01 arg1 -// | \-- * LCL_VAR int V02 arg2 -// \-- * CNS_INT int 0 -// -// Case 4: (x == 0 && y == 0 && z == 0 && w == 0) ==> (((x | y) | z) | w) == 0 -// * RETURN int -// \-- * EQ int -// + * OR int -// | +--* OR int -// | | +--* OR int -// | | | +--* LCL_VAR int V00 arg0 -// | | | \--* LCL_VAR int V01 arg1 -// | | \--* LCL_VAR int V02 arg2 -// | \--* 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 -// -// Case 8: (x < 0 || x == 0) => x <= 0 -// * RETURN int -// \--* LE int -// +--* LCL_VAR int V00 arg0 -// \--* CNS_INT int 0 -// -// Case 9: (x == 0 || x < 0) => x <= 0 -// * RETURN int -// \--* LE int -// +--* LCL_VAR int V00 arg0 -// \--* CNS_INT int 0 -// -// Case 10: (x > 0 || x == 0) => x >= 0 -// * RETURN int -// \--* GE int -// +--* LCL_VAR int V00 arg0 -// \--* CNS_INT int 0 -// -// Case 11: (x == 0 || x > 0) => x >= 0 -// * RETURN int -// \--* GE int -// +--* LCL_VAR int V00 arg0 -// \--* CNS_INT int 0 -// -// Case 12: (x >= 0 && x != 0) => x > 0 -// * RETURN int -// \--* GT int -// +--* LCL_VAR int V00 arg0 -// \--* CNS_INT int 0 -// -// Case 13: (x != 0 && x >= 0) => x > 0 -// * RETURN int -// \--* GT int -// +--* LCL_VAR int V00 arg0 -// \--* CNS_INT int 0 -// -// Case 14: (x <= 0 && x != 0) => x < 0 -// * RETURN int -// \--* LT int -// +--* LCL_VAR int V00 arg0 -// \--* CNS_INT int 0 -// -// Case 15: (x != 0 && x <= 0) => x < 0 -// * RETURN int -// \--* LT int -// +--* LCL_VAR int V00 arg0 -// \--* CNS_INT int 0 +// For example, (x == 0 && y ==0) => (x | y) == 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. From 413196c451fa198113045a8c3e0c60a5ac5b1781 Mon Sep 17 00:00:00 2001 From: Egor Bogatov Date: Thu, 4 Jun 2026 00:32:55 +0200 Subject: [PATCH 2/6] cleanup --- src/coreclr/jit/optimizebools.cpp | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/src/coreclr/jit/optimizebools.cpp b/src/coreclr/jit/optimizebools.cpp index bd5e4432331d2c..8ba22cba5857fa 100644 --- a/src/coreclr/jit/optimizebools.cpp +++ b/src/coreclr/jit/optimizebools.cpp @@ -288,15 +288,10 @@ bool OptBoolsDsc::optOptimizeBoolsCondBlock() optOptimizeBoolsUpdateTrees(); -#ifdef DEBUG - if (m_compiler->verbose) - { - printf("Folded %sboolean conditions of " FMT_BB " and " FMT_BB " to :\n", m_c2->OperIsLeaf() ? "" : "non-leaf ", - m_b1->bbNum, m_b2->bbNum); - m_compiler->gtDispStmt(s1); - printf("\n"); - } -#endif + JITDUMP("Folded %sboolean conditions of " FMT_BB " and " FMT_BB " to :\n", m_c2->OperIsLeaf() ? "" : "non-leaf ", + m_b1->bbNum, m_b2->bbNum); + DISPSTMT(s1); + JITDUMP("\n"); // Return true to continue the bool optimization for the rest of the BB chain return true; @@ -1220,7 +1215,7 @@ void OptBoolsDsc::optOptimizeBoolsUpdateTrees() if ((trueTarget->NumSucc() > 0) || (falseTarget->NumSucc() > 0)) { - JITDUMP("optOptimizeRangeTests: Profile needs to be propagated through " FMT_BB + JITDUMP("optOptimizeBoolsUpdateTrees: Profile needs to be propagated through " FMT_BB "'s successors. Data %s inconsistent.\n", m_b1->bbNum, m_compiler->fgPgoConsistent ? "is now" : "was already"); m_compiler->fgPgoConsistent = false; @@ -1268,8 +1263,7 @@ void OptBoolsDsc::optOptimizeBoolsGcStress() { return; } - GenTree* relop = test.compTree; - bool isBool = test.isBool; + GenTree* relop = test.compTree; if (comparand->gtFlags & (GTF_ASG | GTF_CALL | GTF_ORDER_SIDEEFF)) { @@ -1397,17 +1391,13 @@ GenTree* OptBoolsDsc::optIsBoolComp(OptTestInfo* pOptTest) // PhaseStatus Compiler::optOptimizeBools() { -#ifdef DEBUG - if (verbose) - { - printf("*************** In optOptimizeBools()\n"); - } -#endif + JITDUMP("*************** In optOptimizeBools()\n"); + bool change = false; bool retry = false; unsigned numCond = 0; unsigned numPasses = 0; - unsigned stress = false; + bool stress = false; BitVecTraits ccmpTraits(fgBBNumMax + 1, this); BitVec ccmpVec = BitVecOps::MakeEmpty(&ccmpTraits); From 2789807554d9b1afd878325be3eb0f2a9c9cf4f3 Mon Sep 17 00:00:00 2001 From: Egor Bogatov Date: Thu, 4 Jun 2026 00:43:52 +0200 Subject: [PATCH 3/6] revert a change --- src/coreclr/jit/optimizebools.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/optimizebools.cpp b/src/coreclr/jit/optimizebools.cpp index 8ba22cba5857fa..4eb5fd869a2af0 100644 --- a/src/coreclr/jit/optimizebools.cpp +++ b/src/coreclr/jit/optimizebools.cpp @@ -1356,7 +1356,7 @@ GenTree* OptBoolsDsc::optIsBoolComp(OptTestInfo* pOptTest) if (pOptTest->isBool) { m_compiler->gtReverseCond(cond); - opr2->AsIntCon()->SetIconValue(0); + opr2->AsIntCon()->gtIconVal = 0; } else { From 0fc534955dd6a27a710192313c8f93f01ad2532c Mon Sep 17 00:00:00 2001 From: Egor Bogatov Date: Thu, 4 Jun 2026 02:52:46 +0200 Subject: [PATCH 4/6] Update optimizebools.cpp --- src/coreclr/jit/optimizebools.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/coreclr/jit/optimizebools.cpp b/src/coreclr/jit/optimizebools.cpp index 4eb5fd869a2af0..6d058d67724d22 100644 --- a/src/coreclr/jit/optimizebools.cpp +++ b/src/coreclr/jit/optimizebools.cpp @@ -1335,6 +1335,11 @@ GenTree* OptBoolsDsc::optIsBoolComp(OptTestInfo* pOptTest) GenTree* opr1 = cond->AsOp()->gtOp1; GenTree* opr2 = cond->AsOp()->gtOp2; + if (!opr2->OperIs(GT_CNS_INT)) + { + return nullptr; + } + if (!opr2->IsIntegralConst(0) && !opr2->IsIntegralConst(1)) { return nullptr; @@ -1343,7 +1348,7 @@ GenTree* OptBoolsDsc::optIsBoolComp(OptTestInfo* pOptTest) // Is the value a boolean? // We can either have a boolean expression (marked GTF_BOOLEAN) or a constant 0/1. - if (opr1->IsIntegralConst(0) || opr1->IsIntegralConst(1)) + if (opr1->OperIs(GT_CNS_INT) && (opr1->IsIntegralConst(0) || opr1->IsIntegralConst(1))) { pOptTest->isBool = true; } From 2c90fd43a29a9391d97972e412eff344eab4ed5d Mon Sep 17 00:00:00 2001 From: Egor Bogatov Date: Thu, 4 Jun 2026 02:53:20 +0200 Subject: [PATCH 5/6] Update optimizebools.cpp --- src/coreclr/jit/optimizebools.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/optimizebools.cpp b/src/coreclr/jit/optimizebools.cpp index 6d058d67724d22..91ab694ba94d6b 100644 --- a/src/coreclr/jit/optimizebools.cpp +++ b/src/coreclr/jit/optimizebools.cpp @@ -1339,7 +1339,7 @@ GenTree* OptBoolsDsc::optIsBoolComp(OptTestInfo* pOptTest) { return nullptr; } - + if (!opr2->IsIntegralConst(0) && !opr2->IsIntegralConst(1)) { return nullptr; From ee180889d2d44dc09291a6ab57ee81ad2ae860f0 Mon Sep 17 00:00:00 2001 From: Egor Bogatov Date: Thu, 4 Jun 2026 13:00:51 +0200 Subject: [PATCH 6/6] Expand rule table to 14 explicit rows; drop implicit De Morgan transform Each row now reads literally as (sameTarget, sameLcl, op1, op2) -> (foldOp, cmpOp, requiresSigned) with no implicit normalization at the lookup site. --- src/coreclr/jit/optimizebools.cpp | 54 +++++++++++++++++++------------ 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/coreclr/jit/optimizebools.cpp b/src/coreclr/jit/optimizebools.cpp index 91ab694ba94d6b..3034ddd427849a 100644 --- a/src/coreclr/jit/optimizebools.cpp +++ b/src/coreclr/jit/optimizebools.cpp @@ -107,8 +107,7 @@ class OptBoolsDsc //----------------------------------------------------------------------------- // OptBoolsRule: one rewrite rule consumed by OptBoolsDsc::optOptimizeBoolsCondBlock. // -// The table is written in the m_sameTarget == true frame. -// A rule matches when (sameLcl, op1, op2) agree and produces the folded compare: +// A rule matches when (sameTarget, sameLcl, op1, op2) agree and produces the folded compare: // // foldOp == GT_NONE : keep c1 as the sole operand and rewrite the compare to cmpOp // (used for redundant tests against the same local). @@ -120,29 +119,44 @@ class OptBoolsDsc // struct OptBoolsRule { + bool sameTarget; // matches OptBoolsDsc::m_sameTarget bool sameLcl; // c1 and c2 must be the same LCL_VAR genTreeOps op1; // m_testInfo1.compTree oper - genTreeOps op2; // m_testInfo2.compTree oper, normalized to the sameTarget=true frame + genTreeOps op2; // m_testInfo2.compTree oper genTreeOps foldOp; // GT_AND, GT_OR, or GT_NONE (no fold; rewrite cmp only) - genTreeOps cmpOp; // resulting comparison, normalized to the sameTarget=true frame + genTreeOps cmpOp; // resulting comparison bool requiresSigned; // reject if either test has GTF_UNSIGNED }; // clang-format off static const OptBoolsRule s_optBoolsRules[] = { - // sameLcl | op1 | op2 | foldOp | cmpOp | requiresSigned + // sameTgt | sameLcl | op1 | op2 | foldOp | cmpOp | requiresSigned // - // Redundant tests on the same local fold to a single signed compare. - { true, GT_LT, GT_EQ, GT_NONE, GT_LE, true }, // c1<0 || c1==0 -> c1<=0 - { true, GT_EQ, GT_LT, GT_NONE, GT_LE, true }, // c1==0 || c1<0 -> c1<=0 - { true, GT_GT, GT_EQ, GT_NONE, GT_GE, true }, // c1>0 || c1==0 -> c1>=0 - { true, GT_EQ, GT_GT, GT_NONE, GT_GE, true }, // c1==0 || c1>0 -> c1>=0 - - // Independent tests fold to a single bitwise compare. - { false, GT_EQ, GT_EQ, GT_AND, GT_EQ, false }, // c1==0 || c2==0 -> (c1&c2)==0 - { false, GT_LT, GT_LT, GT_OR, GT_LT, true }, // c1<0 || c2<0 -> (c1|c2)<0 - { false, GT_NE, GT_NE, GT_OR, GT_NE, false }, // c1!=0 || c2!=0 -> (c1|c2)!=0 + // m_sameTarget == true ("branch to BX if t1 || t2") + // + // Same local: fold to a single signed compare. + { true, true, GT_LT, GT_EQ, GT_NONE, GT_LE, true }, // c1<0 || c1==0 -> c1<=0 + { true, true, GT_EQ, GT_LT, GT_NONE, GT_LE, true }, // c1==0 || c1<0 -> c1<=0 + { true, true, GT_GT, GT_EQ, GT_NONE, GT_GE, true }, // c1>0 || c1==0 -> c1>=0 + { true, true, GT_EQ, GT_GT, GT_NONE, GT_GE, true }, // c1==0 || c1>0 -> c1>=0 + // Independent operands: fold to a single bitwise compare. + { true, false, GT_EQ, GT_EQ, GT_AND, GT_EQ, false }, // c1==0 || c2==0 -> (c1&c2)==0 + { true, false, GT_LT, GT_LT, GT_OR, GT_LT, true }, // c1<0 || c2<0 -> (c1|c2)<0 + { true, false, GT_NE, GT_NE, GT_OR, GT_NE, false }, // c1!=0 || c2!=0 -> (c1|c2)!=0 + + // + // m_sameTarget == false ("branch to BX if !t1 && t2") + // + // Same local: fold to a single signed compare. + { false, true, GT_LT, GT_NE, GT_NONE, GT_GT, true }, // !(c1<0) && c1!=0 -> c1>0 + { false, true, GT_EQ, GT_GE, GT_NONE, GT_GT, true }, // !(c1==0) && c1>=0 -> c1>0 + { false, true, GT_GT, GT_NE, GT_NONE, GT_LT, true }, // !(c1>0) && c1!=0 -> c1<0 + { false, true, GT_EQ, GT_LE, GT_NONE, GT_LT, true }, // !(c1==0) && c1<=0 -> c1<0 + // Independent operands: fold to a single bitwise compare. + { false, false, GT_EQ, GT_NE, GT_AND, GT_NE, false }, // !(c1==0) && c2!=0 -> (c1&c2)!=0 + { false, false, GT_LT, GT_GE, GT_OR, GT_GE, true }, // !(c1<0) && c2>=0 -> (c1|c2)>=0 + { false, false, GT_NE, GT_EQ, GT_OR, GT_EQ, false }, // !(c1!=0) && c2==0 -> (c1|c2)==0 }; // clang-format on @@ -245,16 +259,16 @@ bool OptBoolsDsc::optOptimizeBoolsCondBlock() assert(m_testInfo1.compTree->OperIs(GT_EQ, GT_NE, GT_LT, GT_GT, GT_GE, GT_LE)); assert(m_testInfo2.compTree->OperIs(GT_EQ, GT_NE, GT_LT, GT_GT, GT_GE, GT_LE)); - // Pick the rewrite rule from the s_optBoolsRules table. + // Pick the rewrite rule from the s_optBoolsRules table. The match key is the literal + // (sameTarget, sameLcl, op1, op2) -- no implicit transform on either side. const genTreeOps op1 = m_testInfo1.compTree->OperGet(); - const genTreeOps op2raw = m_testInfo2.compTree->OperGet(); - const genTreeOps op2 = m_sameTarget ? op2raw : GenTree::ReverseRelop(op2raw); + const genTreeOps op2 = m_testInfo2.compTree->OperGet(); const bool sameLcl = m_c1->OperIs(GT_LCL_VAR) && GenTree::Compare(m_c1, m_c2); const OptBoolsRule* rule = nullptr; for (const OptBoolsRule& r : s_optBoolsRules) { - if ((r.sameLcl == sameLcl) && (r.op1 == op1) && (r.op2 == op2)) + if ((r.sameTarget == m_sameTarget) && (r.sameLcl == sameLcl) && (r.op1 == op1) && (r.op2 == op2)) { rule = &r; break; @@ -284,7 +298,7 @@ bool OptBoolsDsc::optOptimizeBoolsCondBlock() m_foldOp = rule->foldOp; m_foldType = foldType; - m_cmpOp = m_sameTarget ? rule->cmpOp : GenTree::ReverseRelop(rule->cmpOp); + m_cmpOp = rule->cmpOp; optOptimizeBoolsUpdateTrees();