From 5b16f0abdb0acdc1f16d3101cf3ef1ca917ecd71 Mon Sep 17 00:00:00 2001 From: Julie Lee Date: Wed, 17 Feb 2021 10:00:58 -0800 Subject: [PATCH 01/25] Equals to 0 optimization in Boolean logic --- src/coreclr/jit/compiler.h | 4 +- src/coreclr/jit/optimizer.cpp | 878 ++++++++++++++++++++++------------ 2 files changed, 580 insertions(+), 302 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 117d7d28a167a..3be0e7b991eb8 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -6315,7 +6315,9 @@ class Compiler void optOptimizeBools(); private: - GenTree* optIsBoolCond(GenTree* condBranch, GenTree** compPtr, bool* boolPtr); + void optOptimizeBoolsBbjCond(BasicBlock* b1, BasicBlock* b2, bool* change); + void optOptimizeBoolsBbjReturn(BasicBlock* b1, BasicBlock* b2, BasicBlock* b3, bool* change); + GenTree* optIsBoolComp(GenTree* tree, GenTree** compPtr, bool* boolPtr); #ifdef DEBUG void optOptimizeBoolsGcStress(BasicBlock* condBlock); #endif diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index a7df5b95905a2..eafd42d06c0ad 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -7608,6 +7608,568 @@ bool Compiler::optIsRangeCheckRemovable(GenTree* tree) return true; } +/* + Optimizes boolean when the second BB is BBJ_COND +*/ +void Compiler::optOptimizeBoolsBbjCond(BasicBlock* b1, BasicBlock* b2, bool* change) +{ + bool sameTarget; // Do b1 and b2 have the same bbJumpDest? + + if (b1->bbJumpDest == b2->bbJumpDest) + { + /* Given the following sequence of blocks : + B1: brtrue(t1, BX) + B2: brtrue(t2, BX) + B3: + we will try to fold it to : + B1: brtrue(t1|t2, BX) + B3: + */ + + sameTarget = true; + } + else if (b1->bbJumpDest == b2->bbNext) /*b1->bbJumpDest->bbNum == n1+2*/ + { + /* Given the following sequence of blocks : + B1: brtrue(t1, B3) + B2: brtrue(t2, BX) + B3: + we will try to fold it to : + B1: brtrue((!t1)&&t2, BX) + B3: + */ + + sameTarget = false; + } + else + { + return; + } + + /* The second block must contain a single statement */ + + Statement* s2 = b2->firstStmt(); + if (s2->GetPrevStmt() != s2) + { + return; + } + + GenTree* t2 = s2->GetRootNode(); + noway_assert(t2->gtOper == GT_JTRUE); + + /* Find the condition for the first block */ + + Statement* s1 = b1->lastStmt(); + + GenTree* t1 = s1->GetRootNode(); + noway_assert(t1->gtOper == GT_JTRUE); + + if (b2->countOfInEdges() > 1) + { + return; + } + + /* Find the branch conditions of b1 and b2 */ + + bool bool1, bool2; + + GenTree* c1 = optIsBoolComp(t1, &t1, &bool1); + if (!c1) + { + return; + } + + GenTree* c2 = optIsBoolComp(t2, &t2, &bool2); + if (!c2) + { + return; + } + + noway_assert(t1->OperIs(GT_EQ, GT_NE) && t1->AsOp()->gtOp1 == c1); + noway_assert(t2->OperIs(GT_EQ, GT_NE) && t2->AsOp()->gtOp1 == c2); + + // Leave out floats where the bit-representation is more complicated + // - there are two representations for 0. + // + if (varTypeIsFloating(c1->TypeGet()) || varTypeIsFloating(c2->TypeGet())) + { + return; + } + + // Make sure the types involved are of the same sizes + if (genTypeSize(c1->TypeGet()) != genTypeSize(c2->TypeGet())) + { + return; + } + if (genTypeSize(t1->TypeGet()) != genTypeSize(t2->TypeGet())) + { + return; + } +#ifdef TARGET_ARMARCH + // Skip the small operand which we cannot encode. + if (varTypeIsSmall(c1->TypeGet())) + return; +#endif + /* The second condition must not contain side effects */ + + if (c2->gtFlags & GTF_GLOB_EFFECT) + { + return; + } + + /* The second condition must not be too expensive */ + + gtPrepareCost(c2); + + if (c2->GetCostEx() > 12) + { + return; + } + + genTreeOps foldOp; + genTreeOps cmpOp; + var_types foldType = c1->TypeGet(); + if (varTypeIsGC(foldType)) + { + foldType = TYP_I_IMPL; + } + + if (sameTarget) + { + /* Both conditions must be the same */ + + if (t1->gtOper != t2->gtOper) + { + return; + } + + if (t1->gtOper == 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 + { + /* 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 + { + /* The b1 condition must be the reverse of the b2 condition */ + + if (t1->gtOper == t2->gtOper) + { + return; + } + + if (t1->gtOper == GT_EQ) + { + /* 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 + { + /* 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; + } + } + + // Anding requires both values to be 0 or 1 + + if ((foldOp == GT_AND) && (!bool1 || !bool2)) + { + return; + } + + // + // Now update the trees + // + GenTree* cmpOp1 = gtNewOperNode(foldOp, foldType, c1, c2); + if (bool1 && bool2) + { + /* When we 'OR'/'AND' two booleans, the result is boolean as well */ + cmpOp1->gtFlags |= GTF_BOOLEAN; + } + + t1->SetOper(cmpOp); + t1->AsOp()->gtOp1 = cmpOp1; + t1->AsOp()->gtOp2->gtType = foldType; // Could have been varTypeIsGC() + +#if FEATURE_SET_FLAGS + // For comparisons against zero we will have the GTF_SET_FLAGS set + // and this can cause an assert to fire in fgMoveOpsLeft(GenTree* tree) + // during the CSE phase. + // + // So make sure to clear any GTF_SET_FLAGS bit on these operations + // as they are no longer feeding directly into a comparisons against zero + + // Make sure that the GTF_SET_FLAGS bit is cleared. + // Fix 388436 ARM JitStress WP7 + c1->gtFlags &= ~GTF_SET_FLAGS; + c2->gtFlags &= ~GTF_SET_FLAGS; + + // The new top level node that we just created does feed directly into + // a comparison against zero, so set the GTF_SET_FLAGS bit so that + // we generate an instruction that sets the flags, which allows us + // to omit the cmp with zero instruction. + + // Request that the codegen for cmpOp1 sets the condition flags + // when it generates the code for cmpOp1. + // + cmpOp1->gtRequestSetFlags(); +#endif + + flowList* edge1 = fgGetPredForBlock(b1->bbJumpDest, b1); + flowList* edge2; + + /* Modify the target of the conditional jump and update bbRefs and bbPreds */ + + if (sameTarget) + { + edge2 = fgGetPredForBlock(b2->bbJumpDest, b2); + } + else + { + edge2 = fgGetPredForBlock(b2->bbNext, b2); + + fgRemoveRefPred(b1->bbJumpDest, b1); + + b1->bbJumpDest = b2->bbJumpDest; + + fgAddRefPred(b2->bbJumpDest, b1); + } + + noway_assert(edge1 != nullptr); + noway_assert(edge2 != nullptr); + + BasicBlock::weight_t edgeSumMin = edge1->edgeWeightMin() + edge2->edgeWeightMin(); + BasicBlock::weight_t edgeSumMax = edge1->edgeWeightMax() + edge2->edgeWeightMax(); + if ((edgeSumMax >= edge1->edgeWeightMax()) && (edgeSumMax >= edge2->edgeWeightMax())) + { + edge1->setEdgeWeights(edgeSumMin, edgeSumMax); + } + else + { + edge1->setEdgeWeights(BB_ZERO_WEIGHT, BB_MAX_WEIGHT); + } + + /* Get rid of the second block (which is a BBJ_COND) */ + + noway_assert(b1->bbJumpKind == BBJ_COND); + noway_assert(b2->bbJumpKind == BBJ_COND); + noway_assert(b1->bbJumpDest == b2->bbJumpDest); + noway_assert(b1->bbNext == b2); + noway_assert(b2->bbNext); + + fgUnlinkBlock(b2); + b2->bbFlags |= BBF_REMOVED; + + // If b2 was the last block of a try or handler, update the EH table. + + ehUpdateForDeletedBlock(b2); + + /* Update bbRefs and bbPreds */ + + /* Replace pred 'b2' for 'b2->bbNext' with 'b1' + * Remove pred 'b2' for 'b2->bbJumpDest' */ + + fgReplacePred(b2->bbNext, b2, b1); + + fgRemoveRefPred(b2->bbJumpDest, b2); + + /* Update the block numbers and try again */ + + *change = true; + /* + do + { + b2->bbNum = ++n1; + b2 = b2->bbNext; + } + while (b2); + */ + + // Update loop table + fgUpdateLoopsAfterCompacting(b1, b2); + +#ifdef DEBUG + if (verbose) + { + printf("Folded %sboolean conditions of " FMT_BB " and " FMT_BB " to :\n", c2->OperIsLeaf() ? "" : "non-leaf ", + b1->bbNum, b2->bbNum); + gtDispStmt(s1); + printf("\n"); + } +#endif + +} + +/* + Optimizes boolean when the second BB is BBJ_RETURN +*/ +void Compiler::optOptimizeBoolsBbjReturn(BasicBlock* b1, BasicBlock* b2, BasicBlock* b3, bool* change) +{ + /* b3 must not be marked as BBF_DONT_REMOVE */ + + if (b3->bbFlags & BBF_DONT_REMOVE) + { + return; + } + + /* b3 also needs to be BBJ_RETURN */ + + if (b3->bbJumpKind != BBJ_RETURN) + { +#ifdef DEBUG + optOptimizeBoolsGcStress(b2); +#endif + return; + } + + /* Does b1 jump to b3? */ + /* Given the following sequence of blocks : + B1: brtrue(!t1, B3) + B2: return(t2) + B3: return(false) + B4: + we will try to fold it to : + B1: return(t1|t2) + B4: + */ + if (b1->bbJumpDest != b2->bbNext) /*b1->bbJumpDest->bbNum == n1+2*/ + { + return; + } + + /* The second block and the third block must contain a single statement */ + + Statement* s2 = b2->firstStmt(); + Statement* s3 = b3->firstStmt(); + if (s2->GetPrevStmt() != s2 || s3->GetPrevStmt() != s3) + { + return; + } + + GenTree* t2 = s2->GetRootNode(); + GenTree* t3 = s3->GetRootNode(); + if(t2->gtOper != GT_RETURN || t3->gtOper!= GT_RETURN) + { + return; + } + + /* Find the condition for the first block */ + + Statement* s1 = b1->lastStmt(); + + GenTree* t1 = s1->GetRootNode(); + noway_assert(t1->gtOper == GT_JTRUE); + + if (b2->countOfInEdges() > 1 || b3->countOfInEdges() > 1) + { + return; + } + + /* Find the branch conditions of b1 and b2 */ + + bool bool1, bool2; + + GenTree* c1 = optIsBoolComp(t1, &t1, &bool1); + if (!c1) + { + return; + } + + GenTree* c2 = optIsBoolComp(t2, &t2, &bool2); + if (!c2) + { + return; + } + + noway_assert(t1->OperIs(GT_EQ, GT_NE) && t1->AsOp()->gtOp1 == c1); + noway_assert(t2->OperIs(GT_EQ, GT_NE) && t2->AsOp()->gtOp1 == c2); + + // Leave out floats where the bit-representation is more complicated + // - there are two representations for 0. + // + if (varTypeIsFloating(c1->TypeGet()) || varTypeIsFloating(c2->TypeGet())) + { + return; + } + + // Make sure the types involved are of the same sizes + if (genTypeSize(c1->TypeGet()) != genTypeSize(c2->TypeGet())) + { + return; + } + if (genTypeSize(t1->TypeGet()) != genTypeSize(t2->TypeGet())) + { + return; + } +#ifdef TARGET_ARMARCH + // Skip the small operand which we cannot encode. + if (varTypeIsSmall(c1->TypeGet())) + return; +#endif + /* The second condition must not contain side effects */ + + if (c2->gtFlags & GTF_GLOB_EFFECT) + { + return; + } + + /* The second condition must not be too expensive */ + + gtPrepareCost(c2); + + if (c2->GetCostEx() > 12) + { + return; + } + + genTreeOps foldOp; + genTreeOps cmpOp; + var_types foldType = c1->TypeGet(); + if (varTypeIsGC(foldType)) + { + foldType = TYP_I_IMPL; + } + + /* The b1 condition must be the reverse of the b2 condition */ + + if (t1->gtOper == t2->gtOper) + { + return; + } + + if (t1->gtOper == GT_EQ) + { + /* 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 + { + /* 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; + } + + // Anding requires both values to be 0 or 1 + + if ((foldOp == GT_AND) && (!bool1 || !bool2)) + { + return; + } + + // + // Now update the trees + // + GenTree* cmpOp1 = gtNewOperNode(foldOp, foldType, c1, c2); + if (bool1 && bool2) + { + /* When we 'OR'/'AND' two booleans, the result is boolean as well */ + cmpOp1->gtFlags |= GTF_BOOLEAN; + } + + t1->SetOper(cmpOp); + t1->AsOp()->gtOp1 = cmpOp1; + t1->AsOp()->gtOp2->gtType = foldType; // Could have been varTypeIsGC() + s1->GetRootNode()->gtOper = GT_RETURN; + s1->GetRootNode()->gtType = t2->gtType; + +#if FEATURE_SET_FLAGS + // For comparisons against zero we will have the GTF_SET_FLAGS set + // and this can cause an assert to fire in fgMoveOpsLeft(GenTree* tree) + // during the CSE phase. + // + // So make sure to clear any GTF_SET_FLAGS bit on these operations + // as they are no longer feeding directly into a comparisons against zero + + // Make sure that the GTF_SET_FLAGS bit is cleared. + // Fix 388436 ARM JitStress WP7 + c1->gtFlags &= ~GTF_SET_FLAGS; + c2->gtFlags &= ~GTF_SET_FLAGS; + + // The new top level node that we just created does feed directly into + // a comparison against zero, so set the GTF_SET_FLAGS bit so that + // we generate an instruction that sets the flags, which allows us + // to omit the cmp with zero instruction. + + // Request that the codegen for cmpOp1 sets the condition flags + // when it generates the code for cmpOp1. + // + cmpOp1->gtRequestSetFlags(); +#endif + + /* Modify the target of the conditional jump and update bbRefs and bbPreds */ + + fgRemoveRefPred(b1->bbJumpDest, b1); + + noway_assert(!b2->bbJumpDest); + b1->bbJumpDest = b2->bbJumpDest; + b1->bbJumpKind = b2->bbJumpKind; + b1->bbJumpSwt = b2->bbJumpSwt; + + //fgAddRefPred(b2->bbJumpDest, b1); + + /* Get rid of the second & third block (which is a BBJ_RETURN) */ + + noway_assert(b1->bbJumpKind == BBJ_RETURN); + noway_assert(b2->bbJumpKind == BBJ_RETURN); + noway_assert(b1->bbNext == b2); + noway_assert(b2->bbNext == b3); + noway_assert(b3); + + fgUnlinkBlock(b2); + b2->bbFlags |= BBF_REMOVED; + + // If b2 was the last block of a try or handler, update the EH table. + + ehUpdateForDeletedBlock(b2); + + fgUnlinkBlock(b3); + b3->bbFlags |= BBF_REMOVED; + + ehUpdateForDeletedBlock(b3); + + ///* Update bbRefs and bbPreds */ + + //fgReplacePred(b2->bbNext, b2, b1); // TODO-Julie: b2 is null, so this does not work. + + *change = true; + + //// Update loop table + //fgUpdateLoopsAfterCompacting(b1, b2); // TODO-Julie: b2 is null, so this does not work. + +#ifdef DEBUG + if (verbose) + { + printf("Folded %sboolean conditions of " FMT_BB " and " FMT_BB " to :\n", c2->OperIsLeaf() ? "" : "non-leaf ", + b1->bbNum, b2->bbNum); + gtDispStmt(s1); + printf("\n"); + } +#endif +} + + /****************************************************************************** * * Replace x==null with (x|x)==0 if x is a GC-type. @@ -7631,7 +8193,7 @@ void Compiler::optOptimizeBoolsGcStress(BasicBlock* condBlock) bool isBool; GenTree* relop; - GenTree* comparand = optIsBoolCond(cond, &relop, &isBool); + GenTree* comparand = optIsBoolComp(cond, &relop, &isBool); if (comparand == nullptr || !varTypeIsGC(comparand->TypeGet())) { @@ -7669,12 +8231,12 @@ void Compiler::optOptimizeBoolsGcStress(BasicBlock* condBlock) * value then we morph the tree by reversing the GT_EQ/GT_NE and change the 1 to 0. */ -GenTree* Compiler::optIsBoolCond(GenTree* condBranch, GenTree** compPtr, bool* boolPtr) +GenTree* Compiler::optIsBoolComp(GenTree* tree, GenTree** compPtr, bool* boolPtr) { bool isBool = false; - noway_assert(condBranch->gtOper == GT_JTRUE); - GenTree* cond = condBranch->AsOp()->gtOp1; + noway_assert(tree->gtOper == GT_JTRUE || tree->gtOper == GT_RETURN); + GenTree* cond = tree->AsOp()->gtOp1; /* The condition must be "!= 0" or "== 0" */ @@ -7793,315 +8355,29 @@ void Compiler::optOptimizeBools() /* The next block also needs to be a condition */ - if (b2->bbJumpKind != BBJ_COND) - { -#ifdef DEBUG - optOptimizeBoolsGcStress(b1); -#endif - continue; - } - - bool sameTarget; // Do b1 and b2 have the same bbJumpDest? - - if (b1->bbJumpDest == b2->bbJumpDest) - { - /* Given the following sequence of blocks : - B1: brtrue(t1, BX) - B2: brtrue(t2, BX) - B3: - we will try to fold it to : - B1: brtrue(t1|t2, BX) - B3: - */ - - sameTarget = true; - } - else if (b1->bbJumpDest == b2->bbNext) /*b1->bbJumpDest->bbNum == n1+2*/ - { - /* Given the following sequence of blocks : - B1: brtrue(t1, B3) - B2: brtrue(t2, BX) - B3: - we will try to fold it to : - B1: brtrue((!t1)&&t2, BX) - B3: - */ - - sameTarget = false; - } - else - { - continue; - } - - /* The second block must contain a single statement */ - - Statement* s2 = b2->firstStmt(); - if (s2->GetPrevStmt() != s2) - { - continue; - } - - GenTree* t2 = s2->GetRootNode(); - noway_assert(t2->gtOper == GT_JTRUE); - - /* Find the condition for the first block */ - - Statement* s1 = b1->lastStmt(); - - GenTree* t1 = s1->GetRootNode(); - noway_assert(t1->gtOper == GT_JTRUE); - - if (b2->countOfInEdges() > 1) - { - continue; - } - - /* Find the branch conditions of b1 and b2 */ - - bool bool1, bool2; - - GenTree* c1 = optIsBoolCond(t1, &t1, &bool1); - if (!c1) - { - continue; - } - - GenTree* c2 = optIsBoolCond(t2, &t2, &bool2); - if (!c2) - { - continue; - } - - noway_assert(t1->OperIs(GT_EQ, GT_NE) && t1->AsOp()->gtOp1 == c1); - noway_assert(t2->OperIs(GT_EQ, GT_NE) && t2->AsOp()->gtOp1 == c2); - - // Leave out floats where the bit-representation is more complicated - // - there are two representations for 0. - // - if (varTypeIsFloating(c1->TypeGet()) || varTypeIsFloating(c2->TypeGet())) - { - continue; - } - - // Make sure the types involved are of the same sizes - if (genTypeSize(c1->TypeGet()) != genTypeSize(c2->TypeGet())) - { - continue; - } - if (genTypeSize(t1->TypeGet()) != genTypeSize(t2->TypeGet())) - { - continue; - } -#ifdef TARGET_ARMARCH - // Skip the small operand which we cannot encode. - if (varTypeIsSmall(c1->TypeGet())) - continue; -#endif - /* The second condition must not contain side effects */ - - if (c2->gtFlags & GTF_GLOB_EFFECT) - { - continue; - } - - /* The second condition must not be too expensive */ - - gtPrepareCost(c2); - - if (c2->GetCostEx() > 12) + if (b2->bbJumpKind == BBJ_COND) { - continue; + // When it is conditional jumps + optOptimizeBoolsBbjCond(b1, b2, &change); } - - genTreeOps foldOp; - genTreeOps cmpOp; - var_types foldType = c1->TypeGet(); - if (varTypeIsGC(foldType)) - { - foldType = TYP_I_IMPL; - } - - if (sameTarget) + else if (b2->bbJumpKind == BBJ_RETURN) { - /* Both conditions must be the same */ + /* If there is no next block after b2, we're done */ - if (t1->gtOper != t2->gtOper) + BasicBlock* b3 = b2->bbNext; + if (!b3) { - continue; - } - - if (t1->gtOper == 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 - { - /* 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 - { - /* The b1 condition must be the reverse of the b2 condition */ - - if (t1->gtOper == t2->gtOper) - { - continue; - } - - if (t1->gtOper == GT_EQ) - { - /* 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 - { - /* 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; + break; } - } - - // Anding requires both values to be 0 or 1 - if ((foldOp == GT_AND) && (!bool1 || !bool2)) - { - continue; - } - - // - // Now update the trees - // - GenTree* cmpOp1 = gtNewOperNode(foldOp, foldType, c1, c2); - if (bool1 && bool2) - { - /* When we 'OR'/'AND' two booleans, the result is boolean as well */ - cmpOp1->gtFlags |= GTF_BOOLEAN; - } - - t1->SetOper(cmpOp); - t1->AsOp()->gtOp1 = cmpOp1; - t1->AsOp()->gtOp2->gtType = foldType; // Could have been varTypeIsGC() - -#if FEATURE_SET_FLAGS - // For comparisons against zero we will have the GTF_SET_FLAGS set - // and this can cause an assert to fire in fgMoveOpsLeft(GenTree* tree) - // during the CSE phase. - // - // So make sure to clear any GTF_SET_FLAGS bit on these operations - // as they are no longer feeding directly into a comparisons against zero - - // Make sure that the GTF_SET_FLAGS bit is cleared. - // Fix 388436 ARM JitStress WP7 - c1->gtFlags &= ~GTF_SET_FLAGS; - c2->gtFlags &= ~GTF_SET_FLAGS; - - // The new top level node that we just created does feed directly into - // a comparison against zero, so set the GTF_SET_FLAGS bit so that - // we generate an instruction that sets the flags, which allows us - // to omit the cmp with zero instruction. - - // Request that the codegen for cmpOp1 sets the condition flags - // when it generates the code for cmpOp1. - // - cmpOp1->gtRequestSetFlags(); -#endif - - flowList* edge1 = fgGetPredForBlock(b1->bbJumpDest, b1); - flowList* edge2; - - /* Modify the target of the conditional jump and update bbRefs and bbPreds */ - - if (sameTarget) - { - edge2 = fgGetPredForBlock(b2->bbJumpDest, b2); + optOptimizeBoolsBbjReturn(b1, b2, b3, &change); } else { - edge2 = fgGetPredForBlock(b2->bbNext, b2); - - fgRemoveRefPred(b1->bbJumpDest, b1); - - b1->bbJumpDest = b2->bbJumpDest; - - fgAddRefPred(b2->bbJumpDest, b1); - } - - noway_assert(edge1 != nullptr); - noway_assert(edge2 != nullptr); - - BasicBlock::weight_t edgeSumMin = edge1->edgeWeightMin() + edge2->edgeWeightMin(); - BasicBlock::weight_t edgeSumMax = edge1->edgeWeightMax() + edge2->edgeWeightMax(); - if ((edgeSumMax >= edge1->edgeWeightMax()) && (edgeSumMax >= edge2->edgeWeightMax())) - { - edge1->setEdgeWeights(edgeSumMin, edgeSumMax, b1->bbJumpDest); - } - else - { - edge1->setEdgeWeights(BB_ZERO_WEIGHT, BB_MAX_WEIGHT, b1->bbJumpDest); - } - - /* Get rid of the second block (which is a BBJ_COND) */ - - noway_assert(b1->bbJumpKind == BBJ_COND); - noway_assert(b2->bbJumpKind == BBJ_COND); - noway_assert(b1->bbJumpDest == b2->bbJumpDest); - noway_assert(b1->bbNext == b2); - noway_assert(b2->bbNext); - - fgUnlinkBlock(b2); - b2->bbFlags |= BBF_REMOVED; - - // If b2 was the last block of a try or handler, update the EH table. - - ehUpdateForDeletedBlock(b2); - - /* Update bbRefs and bbPreds */ - - /* Replace pred 'b2' for 'b2->bbNext' with 'b1' - * Remove pred 'b2' for 'b2->bbJumpDest' */ - - fgReplacePred(b2->bbNext, b2, b1); - - fgRemoveRefPred(b2->bbJumpDest, b2); - - /* Update the block numbers and try again */ - - change = true; - /* - do - { - b2->bbNum = ++n1; - b2 = b2->bbNext; - } - while (b2); - */ - - // Update loop table - fgUpdateLoopsAfterCompacting(b1, b2); - #ifdef DEBUG - if (verbose) - { - printf("Folded %sboolean conditions of " FMT_BB " and " FMT_BB " to :\n", - c2->OperIsLeaf() ? "" : "non-leaf ", b1->bbNum, b2->bbNum); - gtDispStmt(s1); - printf("\n"); - } + optOptimizeBoolsGcStress(b1); #endif + } } } while (change); From bac5938ef5304ffe5c8e65e4769759001ece958a Mon Sep 17 00:00:00 2001 From: Julie Lee Date: Wed, 10 Mar 2021 13:46:03 -0800 Subject: [PATCH 02/25] Limit bool optimization to Integral return type only --- src/coreclr/jit/optimizer.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index eafd42d06c0ad..31cd5383b8a39 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -7970,6 +7970,11 @@ void Compiler::optOptimizeBoolsBbjReturn(BasicBlock* b1, BasicBlock* b2, BasicBl return; } + if (!varTypeIsIntegral(t2->TypeGet()) || !varTypeIsIntegral(t3->TypeGet())) + { + return; + } + /* Find the condition for the first block */ Statement* s1 = b1->lastStmt(); From 8d9484cfe8b8169b8f6f0ca0b8c282ef9da26bff Mon Sep 17 00:00:00 2001 From: Julie Lee Date: Wed, 10 Mar 2021 19:11:07 -0800 Subject: [PATCH 03/25] Use the updated flowList:setEdgeWeights method with the 3rd parameter --- src/coreclr/jit/optimizer.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index 31cd5383b8a39..b536148c04e21 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -7859,11 +7859,11 @@ void Compiler::optOptimizeBoolsBbjCond(BasicBlock* b1, BasicBlock* b2, bool* cha BasicBlock::weight_t edgeSumMax = edge1->edgeWeightMax() + edge2->edgeWeightMax(); if ((edgeSumMax >= edge1->edgeWeightMax()) && (edgeSumMax >= edge2->edgeWeightMax())) { - edge1->setEdgeWeights(edgeSumMin, edgeSumMax); + edge1->setEdgeWeights(edgeSumMin, edgeSumMax, b1->bbJumpDest); } else { - edge1->setEdgeWeights(BB_ZERO_WEIGHT, BB_MAX_WEIGHT); + edge1->setEdgeWeights(BB_ZERO_WEIGHT, BB_MAX_WEIGHT, b1->bbJumpDest); } /* Get rid of the second block (which is a BBJ_COND) */ @@ -8166,8 +8166,8 @@ void Compiler::optOptimizeBoolsBbjReturn(BasicBlock* b1, BasicBlock* b2, BasicBl #ifdef DEBUG if (verbose) { - printf("Folded %sboolean conditions of " FMT_BB " and " FMT_BB " to :\n", c2->OperIsLeaf() ? "" : "non-leaf ", - b1->bbNum, b2->bbNum); + printf("Folded %sboolean conditions of " FMT_BB ", " FMT_BB " and " FMT_BB " to :\n", c2->OperIsLeaf() ? "" : "non-leaf ", + b1->bbNum, b2->bbNum, b3->bbNum); gtDispStmt(s1); printf("\n"); } From 635e60567bc534cd39dc0a10a45a23133118aad0 Mon Sep 17 00:00:00 2001 From: Julie Lee Date: Wed, 10 Mar 2021 23:41:13 -0800 Subject: [PATCH 04/25] Skip bool optimization for cases that require NOT transformation --- src/coreclr/jit/optimizer.cpp | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index b536148c04e21..fcf27c8ad0568 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -7940,14 +7940,12 @@ void Compiler::optOptimizeBoolsBbjReturn(BasicBlock* b1, BasicBlock* b2, BasicBl } /* Does b1 jump to b3? */ - /* Given the following sequence of blocks : + /* One example: Given the following sequence of blocks : B1: brtrue(!t1, B3) B2: return(t2) B3: return(false) - B4: we will try to fold it to : B1: return(t1|t2) - B4: */ if (b1->bbJumpDest != b2->bbNext) /*b1->bbJumpDest->bbNum == n1+2*/ { @@ -8059,22 +8057,27 @@ void Compiler::optOptimizeBoolsBbjReturn(BasicBlock* b1, BasicBlock* b2, BasicBl return; } - if (t1->gtOper == GT_EQ) - { - /* 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 */ + ssize_t it3val = t3->AsOp()->gtOp1->AsIntCon()->gtIconVal; + if (t1->gtOper == GT_EQ && it3val == 0) + { + /* t1:c1==0 t2:c2!=0 t3:c3==0 ==> Return true if (c1&c2)!=0 */ foldOp = GT_AND; cmpOp = GT_NE; } - else + else if (t1->gtOper == GT_NE && it3val == 0) { - /* t1:c1!=0 t2:c2==0 ==> Branch to BX if both values are 0 - So we will branch to BX if (c1|c2)==0 */ - + /* t1:c1!=0 t2:c2==0 t3:c3==0 ==> Return true if (c1|c2)==0 */ foldOp = GT_OR; cmpOp = GT_EQ; } + else + { + /* Both cases requires NOT operation for operand. Do Not fold. + t1:c1==0 t2:c2!=0 t3:c3==1 ==> true if (c1&!c2)==0 + t1:c1!=0 t2:c2==0 t3:c3==1 ==> true if (!c1&c2)==0 */ + return; + } // Anding requires both values to be 0 or 1 @@ -8122,7 +8125,6 @@ void Compiler::optOptimizeBoolsBbjReturn(BasicBlock* b1, BasicBlock* b2, BasicBl // cmpOp1->gtRequestSetFlags(); #endif - /* Modify the target of the conditional jump and update bbRefs and bbPreds */ fgRemoveRefPred(b1->bbJumpDest, b1); @@ -8154,14 +8156,11 @@ void Compiler::optOptimizeBoolsBbjReturn(BasicBlock* b1, BasicBlock* b2, BasicBl ehUpdateForDeletedBlock(b3); - ///* Update bbRefs and bbPreds */ - - //fgReplacePred(b2->bbNext, b2, b1); // TODO-Julie: b2 is null, so this does not work. - *change = true; //// Update loop table - //fgUpdateLoopsAfterCompacting(b1, b2); // TODO-Julie: b2 is null, so this does not work. + fgUpdateLoopsAfterCompacting(b1, b2); + fgUpdateLoopsAfterCompacting(b1, b3); #ifdef DEBUG if (verbose) @@ -8226,7 +8225,7 @@ void Compiler::optOptimizeBoolsGcStress(BasicBlock* condBlock) /****************************************************************************** * Function used by folding of boolean conditionals - * Given a GT_JTRUE node, checks that it is a boolean comparison of the form + * Given a GT_JTRUE or GT_RETURN node, checks that it is a boolean comparison of the form * "if (boolVal ==/!= 0/1)". This is translated into a GT_EQ node with "op1" * being a boolean lclVar and "op2" the const 0/1. * On success, the comparand (ie. boolVal) is returned. Else NULL. From b8858fc58201114970456b669fe85945a3fd6f6b Mon Sep 17 00:00:00 2001 From: Julie Lee Date: Tue, 16 Mar 2021 19:57:51 -0700 Subject: [PATCH 05/25] Skip bool optimization when the third block GT_RETURN is not CNT_INT int --- src/coreclr/jit/optimizer.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index fcf27c8ad0568..cdb33cd412fa8 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -7973,6 +7973,17 @@ void Compiler::optOptimizeBoolsBbjReturn(BasicBlock* b1, BasicBlock* b2, BasicBl return; } + /* The third block is Return with "CNS_INT int 0/1" */ + if (t3->AsOp()->gtOp1->gtOper != GT_CNS_INT) + { + return; + } + + if (t3->AsOp()->gtOp1->gtType != TYP_INT) + { + return; + } + /* Find the condition for the first block */ Statement* s1 = b1->lastStmt(); From afbd0756b1404e0f7737cd52081c1f4d8040f2a3 Mon Sep 17 00:00:00 2001 From: Julie Lee Date: Tue, 16 Mar 2021 20:33:06 -0700 Subject: [PATCH 06/25] format patch --- src/coreclr/jit/optimizer.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index cdb33cd412fa8..35676568711e4 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -7914,7 +7914,6 @@ void Compiler::optOptimizeBoolsBbjCond(BasicBlock* b1, BasicBlock* b2, bool* cha printf("\n"); } #endif - } /* @@ -7963,7 +7962,7 @@ void Compiler::optOptimizeBoolsBbjReturn(BasicBlock* b1, BasicBlock* b2, BasicBl GenTree* t2 = s2->GetRootNode(); GenTree* t3 = s3->GetRootNode(); - if(t2->gtOper != GT_RETURN || t3->gtOper!= GT_RETURN) + if (t2->gtOper != GT_RETURN || t3->gtOper != GT_RETURN) { return; } @@ -8145,7 +8144,7 @@ void Compiler::optOptimizeBoolsBbjReturn(BasicBlock* b1, BasicBlock* b2, BasicBl b1->bbJumpKind = b2->bbJumpKind; b1->bbJumpSwt = b2->bbJumpSwt; - //fgAddRefPred(b2->bbJumpDest, b1); + // fgAddRefPred(b2->bbJumpDest, b1); /* Get rid of the second & third block (which is a BBJ_RETURN) */ @@ -8176,15 +8175,14 @@ void Compiler::optOptimizeBoolsBbjReturn(BasicBlock* b1, BasicBlock* b2, BasicBl #ifdef DEBUG if (verbose) { - printf("Folded %sboolean conditions of " FMT_BB ", " FMT_BB " and " FMT_BB " to :\n", c2->OperIsLeaf() ? "" : "non-leaf ", - b1->bbNum, b2->bbNum, b3->bbNum); + printf("Folded %sboolean conditions of " FMT_BB ", " FMT_BB " and " FMT_BB " to :\n", + c2->OperIsLeaf() ? "" : "non-leaf ", b1->bbNum, b2->bbNum, b3->bbNum); gtDispStmt(s1); printf("\n"); } #endif } - /****************************************************************************** * * Replace x==null with (x|x)==0 if x is a GC-type. From a43751406d314e0eb58cbb4701008ac557a19408 Mon Sep 17 00:00:00 2001 From: Julie Lee Date: Sun, 21 Mar 2021 20:58:21 -0700 Subject: [PATCH 07/25] Added more bool optimization cases --- src/coreclr/jit/compiler.h | 4 +- src/coreclr/jit/optimizer.cpp | 377 +++++++++++------- .../JIT/opt/OptimizeBools/optboolsreturn.cs | 91 +++++ .../opt/OptimizeBools/optboolsreturn.csproj | 13 + 4 files changed, 342 insertions(+), 143 deletions(-) create mode 100644 src/tests/JIT/opt/OptimizeBools/optboolsreturn.cs create mode 100644 src/tests/JIT/opt/OptimizeBools/optboolsreturn.csproj diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 3be0e7b991eb8..9a483c7fc1d86 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -6315,8 +6315,8 @@ class Compiler void optOptimizeBools(); private: - void optOptimizeBoolsBbjCond(BasicBlock* b1, BasicBlock* b2, bool* change); - void optOptimizeBoolsBbjReturn(BasicBlock* b1, BasicBlock* b2, BasicBlock* b3, bool* change); + void optOptimizeBoolsCondBlock(BasicBlock* b1, BasicBlock* b2, bool* change); + void optOptimizeBoolsReturnBlock(BasicBlock* b1, BasicBlock* b2, BasicBlock* b3, bool* change); GenTree* optIsBoolComp(GenTree* tree, GenTree** compPtr, bool* boolPtr); #ifdef DEBUG void optOptimizeBoolsGcStress(BasicBlock* condBlock); diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index 35676568711e4..77b7ae798d023 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -7608,36 +7608,62 @@ bool Compiler::optIsRangeCheckRemovable(GenTree* tree) return true; } -/* - Optimizes boolean when the second BB is BBJ_COND -*/ -void Compiler::optOptimizeBoolsBbjCond(BasicBlock* b1, BasicBlock* b2, bool* change) +// +// optOptimizeBoolsCondBlock: Optimize boolean when bbJumpKind of both b1 and b2 are BBJ_COND (conditional jump). +// For example, (x==0 && y==0 && z==0) generates +// b1: GT_JTRUE (BBJ_COND) +// b2: GT_JTRUE (BBJ_COND) +// b3: GT_RETURN (BBJ_RETURN), +// and it is folded into +// b1: GT_JTRUE (BBJ_COND) +// b3: GT_RETURN (BBJ_RETURN) +// +// - if b1.bbJumpDest == b2.bbJumpDest, it transforms +// b1 : brtrue(t1, b3) +// b2 : brtrue(t2, bx) +// b3 : +// to +// b1 : brtrue((!t1) && t2, bx) +// b3 : +// +// - if b1.bbJumpDest == b2->bbNext, it transforms +// b1 : brtrue(t1, b3) +// b2 : brtrue(t2, bx) +// b3 : +// to +// b1 : brtrue((!t1) && t2, bx) +// b3 : +// +// Arguments: +// b1 The first Basic Block with the BBJ_COND conditional jump type. +// b2 The next basic block of b1 with the BBJ_COND conditional jump type. +// change Set to true if boolean optimization is done and b1 and b2 are folded into b1. +// +void Compiler::optOptimizeBoolsCondBlock(BasicBlock* b1, BasicBlock* b2, bool* change) { bool sameTarget; // Do b1 and b2 have the same bbJumpDest? if (b1->bbJumpDest == b2->bbJumpDest) { - /* Given the following sequence of blocks : - B1: brtrue(t1, BX) - B2: brtrue(t2, BX) - B3: - we will try to fold it to : - B1: brtrue(t1|t2, BX) - B3: - */ + // Given the following sequence of blocks : + // B1: brtrue(t1, BX) + // B2: brtrue(t2, BX) + // B3: + // we will try to fold it to : + // B1: brtrue(t1|t2, BX) + // B3: sameTarget = true; } else if (b1->bbJumpDest == b2->bbNext) /*b1->bbJumpDest->bbNum == n1+2*/ { - /* Given the following sequence of blocks : - B1: brtrue(t1, B3) - B2: brtrue(t2, BX) - B3: - we will try to fold it to : - B1: brtrue((!t1)&&t2, BX) - B3: - */ + // Given the following sequence of blocks : + // B1: brtrue(t1, B3) + // B2: brtrue(t2, BX) + // B3: + // we will try to fold it to : + // B1: brtrue((!t1)&&t2, BX) + // B3: sameTarget = false; } @@ -7646,7 +7672,12 @@ void Compiler::optOptimizeBoolsBbjCond(BasicBlock* b1, BasicBlock* b2, bool* cha return; } - /* The second block must contain a single statement */ + if (b2->countOfInEdges() > 1) + { + return; + } + + // The second block must contain a single statement Statement* s2 = b2->firstStmt(); if (s2->GetPrevStmt() != s2) @@ -7657,19 +7688,14 @@ void Compiler::optOptimizeBoolsBbjCond(BasicBlock* b1, BasicBlock* b2, bool* cha GenTree* t2 = s2->GetRootNode(); noway_assert(t2->gtOper == GT_JTRUE); - /* Find the condition for the first block */ + // Find the condition for the first block Statement* s1 = b1->lastStmt(); GenTree* t1 = s1->GetRootNode(); noway_assert(t1->gtOper == GT_JTRUE); - if (b2->countOfInEdges() > 1) - { - return; - } - - /* Find the branch conditions of b1 and b2 */ + // Find the branch conditions of b1 and b2 bool bool1, bool2; @@ -7688,6 +7714,7 @@ void Compiler::optOptimizeBoolsBbjCond(BasicBlock* b1, BasicBlock* b2, bool* cha noway_assert(t1->OperIs(GT_EQ, GT_NE) && t1->AsOp()->gtOp1 == c1); noway_assert(t2->OperIs(GT_EQ, GT_NE) && t2->AsOp()->gtOp1 == c2); + // // Leave out floats where the bit-representation is more complicated // - there are two representations for 0. // @@ -7710,14 +7737,14 @@ void Compiler::optOptimizeBoolsBbjCond(BasicBlock* b1, BasicBlock* b2, bool* cha if (varTypeIsSmall(c1->TypeGet())) return; #endif - /* The second condition must not contain side effects */ + // The second condition must not contain side effects if (c2->gtFlags & GTF_GLOB_EFFECT) { return; } - /* The second condition must not be too expensive */ + // The second condition must not be too expensive gtPrepareCost(c2); @@ -7736,7 +7763,7 @@ void Compiler::optOptimizeBoolsBbjCond(BasicBlock* b1, BasicBlock* b2, bool* cha if (sameTarget) { - /* Both conditions must be the same */ + // Both conditions must be the same if (t1->gtOper != t2->gtOper) { @@ -7745,16 +7772,16 @@ void Compiler::optOptimizeBoolsBbjCond(BasicBlock* b1, BasicBlock* b2, bool* cha if (t1->gtOper == 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 */ + // 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 { - /* 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 */ + // 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; @@ -7762,7 +7789,7 @@ void Compiler::optOptimizeBoolsBbjCond(BasicBlock* b1, BasicBlock* b2, bool* cha } else { - /* The b1 condition must be the reverse of the b2 condition */ + // The b1 condition must be the reverse of the b2 condition if (t1->gtOper == t2->gtOper) { @@ -7771,16 +7798,16 @@ void Compiler::optOptimizeBoolsBbjCond(BasicBlock* b1, BasicBlock* b2, bool* cha if (t1->gtOper == GT_EQ) { - /* 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 */ + // 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 { - /* t1:c1!=0 t2:c2==0 ==> Branch to BX if both values are 0 - So we will branch to BX if (c1|c2)==0 */ + // 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; @@ -7800,7 +7827,7 @@ void Compiler::optOptimizeBoolsBbjCond(BasicBlock* b1, BasicBlock* b2, bool* cha GenTree* cmpOp1 = gtNewOperNode(foldOp, foldType, c1, c2); if (bool1 && bool2) { - /* When we 'OR'/'AND' two booleans, the result is boolean as well */ + // When we 'OR'/'AND' two booleans, the result is boolean as well cmpOp1->gtFlags |= GTF_BOOLEAN; } @@ -7881,26 +7908,17 @@ void Compiler::optOptimizeBoolsBbjCond(BasicBlock* b1, BasicBlock* b2, bool* cha ehUpdateForDeletedBlock(b2); - /* Update bbRefs and bbPreds */ - - /* Replace pred 'b2' for 'b2->bbNext' with 'b1' - * Remove pred 'b2' for 'b2->bbJumpDest' */ + // Update bbRefs and bbPreds + // + // Replace pred 'b2' for 'b2->bbNext' with 'b1' + // Remove pred 'b2' for 'b2->bbJumpDest' fgReplacePred(b2->bbNext, b2, b1); fgRemoveRefPred(b2->bbJumpDest, b2); - /* Update the block numbers and try again */ - + // Update the change to true to continue the bool optimization for the rest of the BB chain *change = true; - /* - do - { - b2->bbNum = ++n1; - b2 = b2->bbNext; - } - while (b2); - */ // Update loop table fgUpdateLoopsAfterCompacting(b1, b2); @@ -7916,19 +7934,38 @@ void Compiler::optOptimizeBoolsBbjCond(BasicBlock* b1, BasicBlock* b2, bool* cha #endif } -/* - Optimizes boolean when the second BB is BBJ_RETURN -*/ -void Compiler::optOptimizeBoolsBbjReturn(BasicBlock* b1, BasicBlock* b2, BasicBlock* b3, bool* change) +// +// optOptimizeBoolsReturnBlock: Optimize boolean when b1.bbJumpKind is BBJ_COND and b2.bbJumpKind is BBJ_RETURN. +// For example, (x==0 && y==0) generates +// b1: GT_JTRUE (BBJ_COND) +// b2: GT_RETURN (BBJ_RETURN) +// b3: GT_RETURN (BBJ_RETURN), +// and it is folded into +// b1: GT_RETURN (BBJ_RETURN) +// +// - if b1.bbJumpDest == b2.bbNext, it transforms +// b1 : brtrue(t1, b3) +// b2 : ret(t2) +// b3 : ret(0) +// to +// b1 : ret((!t1) && t2) +// +// Arguments: +// b1 The first Basic Block with the BBJ_COND conditional jump type. +// b2 The next basic block of b1 with the BBJ_RETURN jump type. +// b3 The next basic block of b2 with GT_RETURN. +// change Set to true if boolean optimization is done and b1, b2 and b3 are folded into b1. +// +void Compiler::optOptimizeBoolsReturnBlock(BasicBlock* b1, BasicBlock* b2, BasicBlock* b3, bool* change) { - /* b3 must not be marked as BBF_DONT_REMOVE */ + // b3 must not be marked as BBF_DONT_REMOVE if (b3->bbFlags & BBF_DONT_REMOVE) { return; } - /* b3 also needs to be BBJ_RETURN */ + // b3 also needs to be BBJ_RETURN if (b3->bbJumpKind != BBJ_RETURN) { @@ -7938,20 +7975,25 @@ void Compiler::optOptimizeBoolsBbjReturn(BasicBlock* b1, BasicBlock* b2, BasicBl return; } - /* Does b1 jump to b3? */ - /* One example: Given the following sequence of blocks : - B1: brtrue(!t1, B3) - B2: return(t2) - B3: return(false) - we will try to fold it to : - B1: return(t1|t2) - */ - if (b1->bbJumpDest != b2->bbNext) /*b1->bbJumpDest->bbNum == n1+2*/ + // Does b1 jump to b3? + // One example: Given the following sequence of blocks : + // B1: brtrue(!t1, B3) + // B2: return(t2) + // B3: return(false) + // we will try to fold it to : + // B1: return(t1|t2) + if (b1->bbJumpDest != b2->bbNext) + { + // b1->bbJumpDest->bbNum == n1 + 2 + return; + } + + if (b2->countOfInEdges() > 1 || b3->countOfInEdges() > 1) { return; } - /* The second block and the third block must contain a single statement */ + // The second block and the third block must contain a single statement Statement* s2 = b2->firstStmt(); Statement* s3 = b3->firstStmt(); @@ -7972,7 +8014,7 @@ void Compiler::optOptimizeBoolsBbjReturn(BasicBlock* b1, BasicBlock* b2, BasicBl return; } - /* The third block is Return with "CNS_INT int 0/1" */ + // The third block is Return with "CNS_INT int 0/1" if (t3->AsOp()->gtOp1->gtOper != GT_CNS_INT) { return; @@ -7983,19 +8025,14 @@ void Compiler::optOptimizeBoolsBbjReturn(BasicBlock* b1, BasicBlock* b2, BasicBl return; } - /* Find the condition for the first block */ + // Find the condition for the first block Statement* s1 = b1->lastStmt(); GenTree* t1 = s1->GetRootNode(); noway_assert(t1->gtOper == GT_JTRUE); - if (b2->countOfInEdges() > 1 || b3->countOfInEdges() > 1) - { - return; - } - - /* Find the branch conditions of b1 and b2 */ + // Find the branch conditions of b1 and b2 bool bool1, bool2; @@ -8036,14 +8073,14 @@ void Compiler::optOptimizeBoolsBbjReturn(BasicBlock* b1, BasicBlock* b2, BasicBl if (varTypeIsSmall(c1->TypeGet())) return; #endif - /* The second condition must not contain side effects */ + // The second condition must not contain side effects if (c2->gtFlags & GTF_GLOB_EFFECT) { return; } - /* The second condition must not be too expensive */ + // The second condition must not be too expensive gtPrepareCost(c2); @@ -8060,40 +8097,84 @@ void Compiler::optOptimizeBoolsBbjReturn(BasicBlock* b1, BasicBlock* b2, BasicBl foldType = TYP_I_IMPL; } - /* The b1 condition must be the reverse of the b2 condition */ + ssize_t it1val = t1->AsOp()->gtOp2->AsIntCon()->gtIconVal; + ssize_t it2val = t2->AsOp()->gtOp2->AsIntCon()->gtIconVal; + ssize_t it3val = t3->AsOp()->gtOp1->AsIntCon()->gtIconVal; - if (t1->gtOper == t2->gtOper) + if (t1->gtOper == GT_EQ && t2->gtOper == GT_EQ) { - return; + if (it1val == 0 && it2val == 0 && it3val == 1) + { + // t1:c1==0 t2:c2==0 t3:c3==1 ==> true if (c1&c2)==0 (e.g., x==0 || y==0) + foldOp = GT_AND; + cmpOp = GT_EQ; + } + else + { + // Require NOT operation for operand(s). Do Not fold. + return; + } } - - ssize_t it3val = t3->AsOp()->gtOp1->AsIntCon()->gtIconVal; - - if (t1->gtOper == GT_EQ && it3val == 0) + else if (t1->gtOper == GT_EQ && t2->gtOper == GT_NE) { - /* t1:c1==0 t2:c2!=0 t3:c3==0 ==> Return true if (c1&c2)!=0 */ - foldOp = GT_AND; - cmpOp = GT_NE; + if (it1val == 0 && it2val == 0 && it3val == 0) + { + // t1:c1!=1 t2:c2==1 got reversed from optIsBoolComp() to: + // t1:c1==0 t2:c2!=0 t3:c3==0 ==> true if (c1&c2)!=0 (e.g., x==1 && y==1) + foldOp = GT_AND; + cmpOp = GT_NE; + } + else + { + // Require NOT operation for operand(s). Do Not fold. + return; + } } - else if (t1->gtOper == GT_NE && it3val == 0) + else if (t1->gtOper == GT_NE && t2->gtOper == GT_EQ) { - /* t1:c1!=0 t2:c2==0 t3:c3==0 ==> Return true if (c1|c2)==0 */ - foldOp = GT_OR; - cmpOp = GT_EQ; + if (it1val == 0 && it2val == 0 && it3val == 0) + { + // t1:c1!=0 t2:c2==0 t3:c3==0 ==> true if (c1|c2)==0 (e.g., x==0 && y==0) + foldOp = GT_OR; + cmpOp = GT_EQ; + } + else + { + // Require NOT operation for operand(s). Do Not fold. + return; + } + } + else if (t1->gtOper == GT_NE && t2->gtOper == GT_NE) + { + if (it1val == 0 && it2val == 0 && it3val == 1) + { + // t1:c1==1 t2:c2==1 got reversed from optIsBoolComp() to: + // t1:c1!=0 t2:c2!=0 t3:c3==1 ==> true if (c1|c2)!=0 (e.g., x==1 || y==1) + foldOp = GT_OR; + cmpOp = GT_NE; + } + else + { + // Require NOT operation for operand(s). Do Not fold. + return; + } } else { - /* Both cases requires NOT operation for operand. Do Not fold. - t1:c1==0 t2:c2!=0 t3:c3==1 ==> true if (c1&!c2)==0 - t1:c1!=0 t2:c2==0 t3:c3==1 ==> true if (!c1&c2)==0 */ + // Require NOT operation for operand(s). Do Not fold. return; } - // Anding requires both values to be 0 or 1 + // Anding or NE requires both values to be 0 or 1 because, for example, + // when x=1, y=2, (x==0 || y==0) is not the same as (x & y) == 0, and + // when x=0, y=2, (x==1 || y==1) is not the same as (x | y) !=0. - if ((foldOp == GT_AND) && (!bool1 || !bool2)) + if (foldOp == GT_AND || cmpOp == GT_NE) { - return; + if (!bool1 || !bool2) + { + return; + } } // @@ -8102,7 +8183,7 @@ void Compiler::optOptimizeBoolsBbjReturn(BasicBlock* b1, BasicBlock* b2, BasicBl GenTree* cmpOp1 = gtNewOperNode(foldOp, foldType, c1, c2); if (bool1 && bool2) { - /* When we 'OR'/'AND' two booleans, the result is boolean as well */ + // When we 'OR'/'AND' two booleans, the result is boolean as well cmpOp1->gtFlags |= GTF_BOOLEAN; } @@ -8110,7 +8191,7 @@ void Compiler::optOptimizeBoolsBbjReturn(BasicBlock* b1, BasicBlock* b2, BasicBl t1->AsOp()->gtOp1 = cmpOp1; t1->AsOp()->gtOp2->gtType = foldType; // Could have been varTypeIsGC() s1->GetRootNode()->gtOper = GT_RETURN; - s1->GetRootNode()->gtType = t2->gtType; + s1->GetRootNode()->gtType = s2->GetRootNode()->gtType; #if FEATURE_SET_FLAGS // For comparisons against zero we will have the GTF_SET_FLAGS set @@ -8135,7 +8216,7 @@ void Compiler::optOptimizeBoolsBbjReturn(BasicBlock* b1, BasicBlock* b2, BasicBl // cmpOp1->gtRequestSetFlags(); #endif - /* Modify the target of the conditional jump and update bbRefs and bbPreds */ + // Modify the target of the conditional jump and update bbRefs and bbPreds fgRemoveRefPred(b1->bbJumpDest, b1); @@ -8144,9 +8225,7 @@ void Compiler::optOptimizeBoolsBbjReturn(BasicBlock* b1, BasicBlock* b2, BasicBl b1->bbJumpKind = b2->bbJumpKind; b1->bbJumpSwt = b2->bbJumpSwt; - // fgAddRefPred(b2->bbJumpDest, b1); - - /* Get rid of the second & third block (which is a BBJ_RETURN) */ + // Get rid of the second & third block (which is a BBJ_RETURN) noway_assert(b1->bbJumpKind == BBJ_RETURN); noway_assert(b2->bbJumpKind == BBJ_RETURN); @@ -8168,7 +8247,7 @@ void Compiler::optOptimizeBoolsBbjReturn(BasicBlock* b1, BasicBlock* b2, BasicBl *change = true; - //// Update loop table + // Update loop table fgUpdateLoopsAfterCompacting(b1, b2); fgUpdateLoopsAfterCompacting(b1, b3); @@ -8183,12 +8262,10 @@ void Compiler::optOptimizeBoolsBbjReturn(BasicBlock* b1, BasicBlock* b2, BasicBl #endif } -/****************************************************************************** - * - * Replace x==null with (x|x)==0 if x is a GC-type. - * This will stress code-gen and the emitter to make sure they support such trees. - */ - +// +// Replace x==null with (x|x)==0 if x is a GC-type. +// This will stress code-gen and the emitter to make sure they support such trees. +// #ifdef DEBUG void Compiler::optOptimizeBoolsGcStress(BasicBlock* condBlock) @@ -8232,18 +8309,19 @@ void Compiler::optOptimizeBoolsGcStress(BasicBlock* condBlock) #endif -/****************************************************************************** - * Function used by folding of boolean conditionals - * Given a GT_JTRUE or GT_RETURN node, checks that it is a boolean comparison of the form - * "if (boolVal ==/!= 0/1)". This is translated into a GT_EQ node with "op1" - * being a boolean lclVar and "op2" the const 0/1. - * On success, the comparand (ie. boolVal) is returned. Else NULL. - * compPtr returns the compare node (i.e. GT_EQ or GT_NE node) - * boolPtr returns whether the comparand is a boolean value (must be 0 or 1). - * When return boolPtr equal to true, if the comparison was against a 1 (i.e true) - * value then we morph the tree by reversing the GT_EQ/GT_NE and change the 1 to 0. - */ - +// +// optIsBoolComp: Function used by folding of boolean conditionals. +// Given a GT_JTRUE or GT_RETURN node, checks that it is a boolean comparison +// of the form "if (boolVal ==/!= 0/1)".This is translated into +// a GT_EQ/GT_NE node with "op1" being a boolean lclVar and "op2" the const 0/1. +// Arguments: +// tree Tree node with GT_JTRUE or GT_RETURN type to check boolean condition on. +// compPtr Returns the compare node (i.e. GT_EQ or GT_NE node). +// boolPtr Returns whether the comparand is a boolean value (must be 0 or 1). +// When return boolPtr == 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. +// Return: On success, the comparand (ie. boolVal) is returned. Else NULL. +// GenTree* Compiler::optIsBoolComp(GenTree* tree, GenTree** compPtr, bool* boolPtr) { bool isBool = false; @@ -8251,18 +8329,18 @@ GenTree* Compiler::optIsBoolComp(GenTree* tree, GenTree** compPtr, bool* boolPtr noway_assert(tree->gtOper == GT_JTRUE || tree->gtOper == GT_RETURN); GenTree* cond = tree->AsOp()->gtOp1; - /* The condition must be "!= 0" or "== 0" */ + // The condition must be "!= 0" or "== 0" if ((cond->gtOper != GT_EQ) && (cond->gtOper != GT_NE)) { return nullptr; } - /* Return the compare node to the caller */ + // Return the compare node to the caller *compPtr = cond; - /* Get hold of the comparands */ + // Get hold of the comparands GenTree* opr1 = cond->AsOp()->gtOp1; GenTree* opr2 = cond->AsOp()->gtOp2; @@ -8279,9 +8357,9 @@ GenTree* Compiler::optIsBoolComp(GenTree* tree, GenTree** compPtr, bool* boolPtr ssize_t ival2 = opr2->AsIntCon()->gtIconVal; - /* Is the value a boolean? - * We can either have a boolean expression (marked GTF_BOOLEAN) or - * a local variable that is marked as being boolean (lvIsBoolean) */ + // Is the value a boolean? + // We can either have a boolean expression (marked GTF_BOOLEAN) or + // a local variable that is marked as being boolean (lvIsBoolean) if (opr1->gtFlags & GTF_BOOLEAN) { @@ -8293,7 +8371,7 @@ GenTree* Compiler::optIsBoolComp(GenTree* tree, GenTree** compPtr, bool* boolPtr } else if (opr1->gtOper == GT_LCL_VAR) { - /* is it a boolean local variable */ + // is it a boolean local variable? unsigned lclNum = opr1->AsLclVarCommon()->GetLclNum(); noway_assert(lclNum < lvaCount); @@ -8304,7 +8382,7 @@ GenTree* Compiler::optIsBoolComp(GenTree* tree, GenTree** compPtr, bool* boolPtr } } - /* Was our comparison against the constant 1 (i.e. true) */ + // Was our comparison against the constant 1 (i.e. true) if (ival2 == 1) { // If this is a boolean expression tree we can reverse the relop @@ -8324,6 +8402,23 @@ GenTree* Compiler::optIsBoolComp(GenTree* tree, GenTree** compPtr, bool* boolPtr return opr1; } +// +// optOptimizeBools: Folds boolean conditionals for GT_JTRUE/GR_RETURN nodes. +// 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 +// "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: +// (x == 0 && y ==0) => (c1 | c2) == 0 +// +// * RETURN int +// \--* EQ int +// +--* OR int +// | +--* LCL_VAR int V00 arg0 +// | \--* LCL_VAR int V01 arg1 +// \--* CNS_INT int 0 +// void Compiler::optOptimizeBools() { #ifdef DEBUG @@ -8345,14 +8440,14 @@ void Compiler::optOptimizeBools() for (BasicBlock* const b1 : Blocks()) { - /* We're only interested in conditional jumps here */ + // We're only interested in conditional jumps here if (b1->bbJumpKind != BBJ_COND) { continue; } - /* If there is no next block, we're done */ + // If there is no next block, we're done BasicBlock* b2 = b1->bbNext; if (!b2) @@ -8360,22 +8455,22 @@ void Compiler::optOptimizeBools() break; } - /* The next block must not be marked as BBF_DONT_REMOVE */ + // The next block must not be marked as BBF_DONT_REMOVE if (b2->bbFlags & BBF_DONT_REMOVE) { continue; } - /* The next block also needs to be a condition */ + // The next block also needs to be a condition if (b2->bbJumpKind == BBJ_COND) { // When it is conditional jumps - optOptimizeBoolsBbjCond(b1, b2, &change); + optOptimizeBoolsCondBlock(b1, b2, &change); } else if (b2->bbJumpKind == BBJ_RETURN) { - /* If there is no next block after b2, we're done */ + // If there is no next block after b2, we're done BasicBlock* b3 = b2->bbNext; if (!b3) @@ -8383,7 +8478,7 @@ void Compiler::optOptimizeBools() break; } - optOptimizeBoolsBbjReturn(b1, b2, b3, &change); + optOptimizeBoolsReturnBlock(b1, b2, b3, &change); } else { diff --git a/src/tests/JIT/opt/OptimizeBools/optboolsreturn.cs b/src/tests/JIT/opt/OptimizeBools/optboolsreturn.cs new file mode 100644 index 0000000000000..c0c921e92f2c3 --- /dev/null +++ b/src/tests/JIT/opt/OptimizeBools/optboolsreturn.cs @@ -0,0 +1,91 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// unit test for boolean optimization + +using System; +using System.Runtime.CompilerServices; + +public class CBoolTest +{ + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool AreZero(int x, int y) + { + return (x == 0 && y == 0); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool AreNull(object x, object y) + { + return (x == null && y == null); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool AreZero2(int x, int y) + { + return x == 0 && y == 0 && BitConverter.IsLittleEndian; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool AreZero3(int x, int y, int z) + { + return x == 0 && y == 0 && z == 0; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool AreZero4(int x, int y, int z, int w) + { + return (x == 0 && y == 0 && z == 0 && w == 0); + } + + // Cases that skip optimization + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool AreOne(int x, int y) + { + return (x == 1 && y == 1); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool IsEitherZero(int x, int y) + { + return (x == 0 || y == 0); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool IsEitherOne(int x, int y) + { + return (x == 1 || y == 1); + } + + public static int Main() + { + try + { + // Optimize boolean + + AreZero(0, 0); + AreNull(null, null); + AreNull(new Object(), new Object()); + AreZero(1, 1); + AreZero2(0, 0); + AreZero3(0, 0, 0); + AreZero4(0, 0, 0, 0); + + // Skip optimization + + // Test if ANDing or GT_NE requires both operands to be boolean + AreOne(1, 1); + // Test if ANDing requires both operands to be boolean + IsEitherZero(0, 1); + // Test if GT_NE requires both operands to be boolean + IsEitherOne(0, 1); + } + catch (Exception e) + { + Console.WriteLine(e.Message); + return 101; + } + Console.WriteLine("PASSED"); + return 100; + } +} diff --git a/src/tests/JIT/opt/OptimizeBools/optboolsreturn.csproj b/src/tests/JIT/opt/OptimizeBools/optboolsreturn.csproj new file mode 100644 index 0000000000000..fe4a2822d7cab --- /dev/null +++ b/src/tests/JIT/opt/OptimizeBools/optboolsreturn.csproj @@ -0,0 +1,13 @@ + + + Exe + 1 + + + PdbOnly + True + + + + + From 3b519387861ee92f1db983859c8cf2ad0216e9d2 Mon Sep 17 00:00:00 2001 From: Julie Lee Date: Tue, 23 Mar 2021 12:36:22 -0700 Subject: [PATCH 08/25] format patch --- src/coreclr/jit/optimizer.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index 77b7ae798d023..4be4c9d27a227 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -8130,7 +8130,7 @@ void Compiler::optOptimizeBoolsReturnBlock(BasicBlock* b1, BasicBlock* b2, Basic return; } } - else if (t1->gtOper == GT_NE && t2->gtOper == GT_EQ) + else if (t1->gtOper == GT_NE && t2->gtOper == GT_EQ) { if (it1val == 0 && it2val == 0 && it3val == 0) { @@ -8177,9 +8177,8 @@ void Compiler::optOptimizeBoolsReturnBlock(BasicBlock* b1, BasicBlock* b2, Basic } } - // // Now update the trees - // + GenTree* cmpOp1 = gtNewOperNode(foldOp, foldType, c1, c2); if (bool1 && bool2) { From 854b011895f1530d51556d59a3ab44a8af1ceae0 Mon Sep 17 00:00:00 2001 From: Julie Lee Date: Thu, 25 Mar 2021 23:36:14 -0700 Subject: [PATCH 09/25] Refactored setting fold type and comparison type to fix jitstress error --- src/coreclr/jit/compiler.h | 7 ++ src/coreclr/jit/optimizer.cpp | 180 ++++++++++++++++++---------------- 2 files changed, 100 insertions(+), 87 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 9a483c7fc1d86..1d3ba05687248 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -6318,6 +6318,13 @@ class Compiler void optOptimizeBoolsCondBlock(BasicBlock* b1, BasicBlock* b2, bool* change); void optOptimizeBoolsReturnBlock(BasicBlock* b1, BasicBlock* b2, BasicBlock* b3, bool* change); GenTree* optIsBoolComp(GenTree* tree, GenTree** compPtr, bool* boolPtr); + void optReturnGetFoldAndCompOper(GenTree* tree1, + GenTree* tree2, + ssize_t it3val, + bool bool1, + bool bool2, + genTreeOps* foldOp, + genTreeOps* cmpOp); #ifdef DEBUG void optOptimizeBoolsGcStress(BasicBlock* condBlock); #endif diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index 4be4c9d27a227..5589229daaebd 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -7965,16 +7965,6 @@ void Compiler::optOptimizeBoolsReturnBlock(BasicBlock* b1, BasicBlock* b2, Basic return; } - // b3 also needs to be BBJ_RETURN - - if (b3->bbJumpKind != BBJ_RETURN) - { -#ifdef DEBUG - optOptimizeBoolsGcStress(b2); -#endif - return; - } - // Does b1 jump to b3? // One example: Given the following sequence of blocks : // B1: brtrue(!t1, B3) @@ -8089,94 +8079,23 @@ void Compiler::optOptimizeBoolsReturnBlock(BasicBlock* b1, BasicBlock* b2, Basic return; } - genTreeOps foldOp; - genTreeOps cmpOp; + genTreeOps foldOp = GT_NONE; + genTreeOps cmpOp = GT_NONE; var_types foldType = c1->TypeGet(); if (varTypeIsGC(foldType)) { foldType = TYP_I_IMPL; } - ssize_t it1val = t1->AsOp()->gtOp2->AsIntCon()->gtIconVal; - ssize_t it2val = t2->AsOp()->gtOp2->AsIntCon()->gtIconVal; - ssize_t it3val = t3->AsOp()->gtOp1->AsIntCon()->gtIconVal; + // Get the fold operator and the comparison operator - if (t1->gtOper == GT_EQ && t2->gtOper == GT_EQ) - { - if (it1val == 0 && it2val == 0 && it3val == 1) - { - // t1:c1==0 t2:c2==0 t3:c3==1 ==> true if (c1&c2)==0 (e.g., x==0 || y==0) - foldOp = GT_AND; - cmpOp = GT_EQ; - } - else - { - // Require NOT operation for operand(s). Do Not fold. - return; - } - } - else if (t1->gtOper == GT_EQ && t2->gtOper == GT_NE) - { - if (it1val == 0 && it2val == 0 && it3val == 0) - { - // t1:c1!=1 t2:c2==1 got reversed from optIsBoolComp() to: - // t1:c1==0 t2:c2!=0 t3:c3==0 ==> true if (c1&c2)!=0 (e.g., x==1 && y==1) - foldOp = GT_AND; - cmpOp = GT_NE; - } - else - { - // Require NOT operation for operand(s). Do Not fold. - return; - } - } - else if (t1->gtOper == GT_NE && t2->gtOper == GT_EQ) - { - if (it1val == 0 && it2val == 0 && it3val == 0) - { - // t1:c1!=0 t2:c2==0 t3:c3==0 ==> true if (c1|c2)==0 (e.g., x==0 && y==0) - foldOp = GT_OR; - cmpOp = GT_EQ; - } - else - { - // Require NOT operation for operand(s). Do Not fold. - return; - } - } - else if (t1->gtOper == GT_NE && t2->gtOper == GT_NE) - { - if (it1val == 0 && it2val == 0 && it3val == 1) - { - // t1:c1==1 t2:c2==1 got reversed from optIsBoolComp() to: - // t1:c1!=0 t2:c2!=0 t3:c3==1 ==> true if (c1|c2)!=0 (e.g., x==1 || y==1) - foldOp = GT_OR; - cmpOp = GT_NE; - } - else - { - // Require NOT operation for operand(s). Do Not fold. - return; - } - } - else + ssize_t it3val = t3->AsOp()->gtOp1->AsIntCon()->gtIconVal; + optReturnGetFoldAndCompOper(t1, t2, it3val, bool1, bool2, &foldOp, &cmpOp); + if (foldOp == GT_NONE) { - // Require NOT operation for operand(s). Do Not fold. return; } - // Anding or NE requires both values to be 0 or 1 because, for example, - // when x=1, y=2, (x==0 || y==0) is not the same as (x & y) == 0, and - // when x=0, y=2, (x==1 || y==1) is not the same as (x | y) !=0. - - if (foldOp == GT_AND || cmpOp == GT_NE) - { - if (!bool1 || !bool2) - { - return; - } - } - // Now update the trees GenTree* cmpOp1 = gtNewOperNode(foldOp, foldType, c1, c2); @@ -8189,6 +8108,7 @@ void Compiler::optOptimizeBoolsReturnBlock(BasicBlock* b1, BasicBlock* b2, Basic t1->SetOper(cmpOp); t1->AsOp()->gtOp1 = cmpOp1; t1->AsOp()->gtOp2->gtType = foldType; // Could have been varTypeIsGC() + t1->AsOp()->gtOp2->AsIntCon()->gtIconVal = 0; s1->GetRootNode()->gtOper = GT_RETURN; s1->GetRootNode()->gtType = s2->GetRootNode()->gtType; @@ -8261,6 +8181,92 @@ void Compiler::optOptimizeBoolsReturnBlock(BasicBlock* b1, BasicBlock* b2, Basic #endif } +// +// optReturnGetFoldAndCompOper: Based on tree1 and tree2 conditions, determine fold type and comparison type. +// - The fold operator (foldOp) of below tree is GT_OR, and +// comparison operator (cmpOp) is GT_EQ. +// +// * RETURN int +// \--* EQ int +// +--* OR int +// | +--* LCL_VAR int V00 arg0 +// | \--* LCL_VAR int V01 arg1 +// \--* CNS_INT int 0 +// Arguments: +// tree1: The first tree +// tree2: The second tree +// it3val: The value of the third tree +// bool1: If tree1 is boolean comparison +// bool2: If tree2 is boolean comparison +// foldOp: On success, return the fold operator of GT_AND or GT_OR. +// cmpOp: On success, return the comparison operator of GT_EQ or GT_NE +// +void Compiler::optReturnGetFoldAndCompOper(GenTree* tree1, + GenTree* tree2, + ssize_t it3val, + bool bool1, + bool bool2, + genTreeOps* foldOp, + genTreeOps* cmpOp) +{ + genTreeOps foldOper; + genTreeOps cmpOper; + genTreeOps t1Oper = tree1->gtOper; + genTreeOps t2Oper = tree2->gtOper; + + ssize_t it1val = tree1->AsOp()->gtOp2->AsIntCon()->gtIconVal; + ssize_t it2val = tree2->AsOp()->gtOp2->AsIntCon()->gtIconVal; + + if ((t1Oper == GT_NE && t2Oper == GT_EQ) && (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 + foldOper = GT_OR; + cmpOper = GT_EQ; + } + else if ((t1Oper == GT_EQ && t2Oper == GT_NE) && (it1val == 0 && it2val == 0 && it3val == 0)) + { + // Case: x == 1 && y ==1 + // t1:c1!=1 t2:c2==1 t3:c3==0 is reversed from optIsBoolComp() to: t1:c1==0 t2:c2!=0 t3:c3==0 + // ==> true if (c1&c2)!=0 + foldOper = GT_AND; + cmpOper = GT_NE; + } + else if ((t1Oper == GT_EQ && t2Oper == GT_EQ) && (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 + foldOper = GT_AND; + cmpOper = GT_EQ; + } + else if((t1Oper == GT_NE && t2Oper == GT_NE) && (it1val == 0 && it2val == 0 && it3val == 1)) + { + // Case: x == 1 || y == 1 + // t1:c1==1 t2:c2==1 is reversed from optIsBoolComp() to: t1:c1!=0 t2:c2!=0 t3:c3==1 + // ==> true if (c1|c2)!=0 + foldOper = GT_OR; + cmpOper = GT_NE; + } + else + { + // Require NOT operation for operand(s). Do Not fold. + return; + } + + if ((foldOper == GT_AND || cmpOper == GT_NE) && (!bool1 || !bool2)) + { + // x == 1 && y == 1: Skip cases where x or y is greather 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 + // x == 1 || y == 1: Skip cases where either x or y is greater than 1, e.g., x=2, y=0 + return; + } + + *foldOp = foldOper; + *cmpOp = cmpOper; +} + // // Replace x==null with (x|x)==0 if x is a GC-type. // This will stress code-gen and the emitter to make sure they support such trees. From f28e0d6d40c976f27b77047fb3774eb5eb602c93 Mon Sep 17 00:00:00 2001 From: Julie Lee Date: Fri, 26 Mar 2021 08:21:48 -0700 Subject: [PATCH 10/25] format patch --- src/coreclr/jit/compiler.h | 9 ++------- src/coreclr/jit/optimizer.cpp | 23 +++++++++-------------- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 1d3ba05687248..5543451d6efde 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -6318,13 +6318,8 @@ class Compiler void optOptimizeBoolsCondBlock(BasicBlock* b1, BasicBlock* b2, bool* change); void optOptimizeBoolsReturnBlock(BasicBlock* b1, BasicBlock* b2, BasicBlock* b3, bool* change); GenTree* optIsBoolComp(GenTree* tree, GenTree** compPtr, bool* boolPtr); - void optReturnGetFoldAndCompOper(GenTree* tree1, - GenTree* tree2, - ssize_t it3val, - bool bool1, - bool bool2, - genTreeOps* foldOp, - genTreeOps* cmpOp); + void optReturnGetFoldAndCompOper( + GenTree* tree1, GenTree* tree2, ssize_t it3val, bool bool1, bool bool2, genTreeOps* foldOp, genTreeOps* cmpOp); #ifdef DEBUG void optOptimizeBoolsGcStress(BasicBlock* condBlock); #endif diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index 5589229daaebd..884ddc51a29d5 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -8079,8 +8079,8 @@ void Compiler::optOptimizeBoolsReturnBlock(BasicBlock* b1, BasicBlock* b2, Basic return; } - genTreeOps foldOp = GT_NONE; - genTreeOps cmpOp = GT_NONE; + genTreeOps foldOp = GT_NONE; + genTreeOps cmpOp = GT_NONE; var_types foldType = c1->TypeGet(); if (varTypeIsGC(foldType)) { @@ -8106,11 +8106,11 @@ void Compiler::optOptimizeBoolsReturnBlock(BasicBlock* b1, BasicBlock* b2, Basic } t1->SetOper(cmpOp); - t1->AsOp()->gtOp1 = cmpOp1; - t1->AsOp()->gtOp2->gtType = foldType; // Could have been varTypeIsGC() + t1->AsOp()->gtOp1 = cmpOp1; + t1->AsOp()->gtOp2->gtType = foldType; // Could have been varTypeIsGC() t1->AsOp()->gtOp2->AsIntCon()->gtIconVal = 0; - s1->GetRootNode()->gtOper = GT_RETURN; - s1->GetRootNode()->gtType = s2->GetRootNode()->gtType; + s1->GetRootNode()->gtOper = GT_RETURN; + s1->GetRootNode()->gtType = s2->GetRootNode()->gtType; #if FEATURE_SET_FLAGS // For comparisons against zero we will have the GTF_SET_FLAGS set @@ -8201,13 +8201,8 @@ void Compiler::optOptimizeBoolsReturnBlock(BasicBlock* b1, BasicBlock* b2, Basic // foldOp: On success, return the fold operator of GT_AND or GT_OR. // cmpOp: On success, return the comparison operator of GT_EQ or GT_NE // -void Compiler::optReturnGetFoldAndCompOper(GenTree* tree1, - GenTree* tree2, - ssize_t it3val, - bool bool1, - bool bool2, - genTreeOps* foldOp, - genTreeOps* cmpOp) +void Compiler::optReturnGetFoldAndCompOper( + GenTree* tree1, GenTree* tree2, ssize_t it3val, bool bool1, bool bool2, genTreeOps* foldOp, genTreeOps* cmpOp) { genTreeOps foldOper; genTreeOps cmpOper; @@ -8241,7 +8236,7 @@ void Compiler::optReturnGetFoldAndCompOper(GenTree* tree1, foldOper = GT_AND; cmpOper = GT_EQ; } - else if((t1Oper == GT_NE && t2Oper == GT_NE) && (it1val == 0 && it2val == 0 && it3val == 1)) + else if ((t1Oper == GT_NE && t2Oper == GT_NE) && (it1val == 0 && it2val == 0 && it3val == 1)) { // Case: x == 1 || y == 1 // t1:c1==1 t2:c2==1 is reversed from optIsBoolComp() to: t1:c1!=0 t2:c2!=0 t3:c3==1 From ef65ee3d98a862957cc520eaf4dec94ae1467031 Mon Sep 17 00:00:00 2001 From: Julie Lee Date: Fri, 26 Mar 2021 17:45:01 -0700 Subject: [PATCH 11/25] Refactored common codes for conditional block and return block boolean optimizations --- src/coreclr/jit/compiler.h | 24 +- src/coreclr/jit/optimizer.cpp | 654 +++++++++++++++++++--------------- 2 files changed, 378 insertions(+), 300 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 5543451d6efde..c2cd0313a56ce 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -6315,11 +6315,25 @@ class Compiler void optOptimizeBools(); private: - void optOptimizeBoolsCondBlock(BasicBlock* b1, BasicBlock* b2, bool* change); - void optOptimizeBoolsReturnBlock(BasicBlock* b1, BasicBlock* b2, BasicBlock* b3, bool* change); - GenTree* optIsBoolComp(GenTree* tree, GenTree** compPtr, bool* boolPtr); - void optReturnGetFoldAndCompOper( - GenTree* tree1, GenTree* tree2, ssize_t it3val, bool bool1, bool bool2, genTreeOps* foldOp, genTreeOps* cmpOp); + void optOptimizeBoolsCondBlock(BasicBlock* b1, BasicBlock* b2, bool* change); + Statement* optOptimizeBoolsChkBlkCond( + BasicBlock* b1, BasicBlock* b2, BasicBlock* b3, GenTree** t1, GenTree** t2, GenTree** t3, bool* sameTarget); + GenTree* optIsBoolComp(GenTree* tree, GenTree** compPtr, bool* boolPtr); + bool optOptimizeBoolsChkTypeCostCond(GenTree* t1, GenTree* t2, GenTree* c1, GenTree* c2); + void optOptimizeBoolsUpdateTrees(BasicBlock* b1, + BasicBlock* b2, + BasicBlock* b3, + GenTree* t1, + GenTree* t2, + genTreeOps foldOp, + var_types foldType, + genTreeOps cmpOp, + bool bool1, + bool bool2, + bool sameTarget); + void optOptimizeBoolsReturnBlock(BasicBlock* b1, BasicBlock* b2, BasicBlock* b3, bool* change); + void optReturnGetFoldAndCompOper( + GenTree* tree1, GenTree* tree2, ssize_t it3val, bool bool1, bool bool2, genTreeOps* foldOp, genTreeOps* cmpOp); #ifdef DEBUG void optOptimizeBoolsGcStress(BasicBlock* condBlock); #endif diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index 884ddc51a29d5..3cbe88253301b 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -7641,60 +7641,17 @@ bool Compiler::optIsRangeCheckRemovable(GenTree* tree) // void Compiler::optOptimizeBoolsCondBlock(BasicBlock* b1, BasicBlock* b2, bool* change) { - bool sameTarget; // Do b1 and b2 have the same bbJumpDest? + bool sameTarget = false; // Do b1 and b2 have the same bbJumpDest? + GenTree* t1; + GenTree* t2; + Statement* s1; - if (b1->bbJumpDest == b2->bbJumpDest) - { - // Given the following sequence of blocks : - // B1: brtrue(t1, BX) - // B2: brtrue(t2, BX) - // B3: - // we will try to fold it to : - // B1: brtrue(t1|t2, BX) - // B3: - - sameTarget = true; - } - else if (b1->bbJumpDest == b2->bbNext) /*b1->bbJumpDest->bbNum == n1+2*/ - { - // Given the following sequence of blocks : - // B1: brtrue(t1, B3) - // B2: brtrue(t2, BX) - // B3: - // we will try to fold it to : - // B1: brtrue((!t1)&&t2, BX) - // B3: - - sameTarget = false; - } - else + s1 = optOptimizeBoolsChkBlkCond(b1, b2, nullptr, &t1, &t2, nullptr, &sameTarget); + if (!s1) { return; } - if (b2->countOfInEdges() > 1) - { - return; - } - - // The second block must contain a single statement - - Statement* s2 = b2->firstStmt(); - if (s2->GetPrevStmt() != s2) - { - return; - } - - GenTree* t2 = s2->GetRootNode(); - noway_assert(t2->gtOper == GT_JTRUE); - - // Find the condition for the first block - - Statement* s1 = b1->lastStmt(); - - GenTree* t1 = s1->GetRootNode(); - noway_assert(t1->gtOper == GT_JTRUE); - // Find the branch conditions of b1 and b2 bool bool1, bool2; @@ -7711,47 +7668,14 @@ void Compiler::optOptimizeBoolsCondBlock(BasicBlock* b1, BasicBlock* b2, bool* c return; } - noway_assert(t1->OperIs(GT_EQ, GT_NE) && t1->AsOp()->gtOp1 == c1); - noway_assert(t2->OperIs(GT_EQ, GT_NE) && t2->AsOp()->gtOp1 == c2); + // Find the type and cost conditions of t1 and t2 - // - // Leave out floats where the bit-representation is more complicated - // - there are two representations for 0. - // - if (varTypeIsFloating(c1->TypeGet()) || varTypeIsFloating(c2->TypeGet())) + if (!optOptimizeBoolsChkTypeCostCond(t1, t2, c1, c2)) { return; } - // Make sure the types involved are of the same sizes - if (genTypeSize(c1->TypeGet()) != genTypeSize(c2->TypeGet())) - { - return; - } - if (genTypeSize(t1->TypeGet()) != genTypeSize(t2->TypeGet())) - { - return; - } -#ifdef TARGET_ARMARCH - // Skip the small operand which we cannot encode. - if (varTypeIsSmall(c1->TypeGet())) - return; -#endif - // The second condition must not contain side effects - - if (c2->gtFlags & GTF_GLOB_EFFECT) - { - return; - } - - // The second condition must not be too expensive - - gtPrepareCost(c2); - - if (c2->GetCostEx() > 12) - { - return; - } + // Get the fold operator and the comparison operator genTreeOps foldOp; genTreeOps cmpOp; @@ -7824,105 +7748,12 @@ void Compiler::optOptimizeBoolsCondBlock(BasicBlock* b1, BasicBlock* b2, bool* c // // Now update the trees // - GenTree* cmpOp1 = gtNewOperNode(foldOp, foldType, c1, c2); - if (bool1 && bool2) - { - // When we 'OR'/'AND' two booleans, the result is boolean as well - cmpOp1->gtFlags |= GTF_BOOLEAN; - } - t1->SetOper(cmpOp); - t1->AsOp()->gtOp1 = cmpOp1; - t1->AsOp()->gtOp2->gtType = foldType; // Could have been varTypeIsGC() - -#if FEATURE_SET_FLAGS - // For comparisons against zero we will have the GTF_SET_FLAGS set - // and this can cause an assert to fire in fgMoveOpsLeft(GenTree* tree) - // during the CSE phase. - // - // So make sure to clear any GTF_SET_FLAGS bit on these operations - // as they are no longer feeding directly into a comparisons against zero - - // Make sure that the GTF_SET_FLAGS bit is cleared. - // Fix 388436 ARM JitStress WP7 - c1->gtFlags &= ~GTF_SET_FLAGS; - c2->gtFlags &= ~GTF_SET_FLAGS; - - // The new top level node that we just created does feed directly into - // a comparison against zero, so set the GTF_SET_FLAGS bit so that - // we generate an instruction that sets the flags, which allows us - // to omit the cmp with zero instruction. - - // Request that the codegen for cmpOp1 sets the condition flags - // when it generates the code for cmpOp1. - // - cmpOp1->gtRequestSetFlags(); -#endif - - flowList* edge1 = fgGetPredForBlock(b1->bbJumpDest, b1); - flowList* edge2; - - /* Modify the target of the conditional jump and update bbRefs and bbPreds */ - - if (sameTarget) - { - edge2 = fgGetPredForBlock(b2->bbJumpDest, b2); - } - else - { - edge2 = fgGetPredForBlock(b2->bbNext, b2); - - fgRemoveRefPred(b1->bbJumpDest, b1); - - b1->bbJumpDest = b2->bbJumpDest; - - fgAddRefPred(b2->bbJumpDest, b1); - } - - noway_assert(edge1 != nullptr); - noway_assert(edge2 != nullptr); - - BasicBlock::weight_t edgeSumMin = edge1->edgeWeightMin() + edge2->edgeWeightMin(); - BasicBlock::weight_t edgeSumMax = edge1->edgeWeightMax() + edge2->edgeWeightMax(); - if ((edgeSumMax >= edge1->edgeWeightMax()) && (edgeSumMax >= edge2->edgeWeightMax())) - { - edge1->setEdgeWeights(edgeSumMin, edgeSumMax, b1->bbJumpDest); - } - else - { - edge1->setEdgeWeights(BB_ZERO_WEIGHT, BB_MAX_WEIGHT, b1->bbJumpDest); - } - - /* Get rid of the second block (which is a BBJ_COND) */ - - noway_assert(b1->bbJumpKind == BBJ_COND); - noway_assert(b2->bbJumpKind == BBJ_COND); - noway_assert(b1->bbJumpDest == b2->bbJumpDest); - noway_assert(b1->bbNext == b2); - noway_assert(b2->bbNext); - - fgUnlinkBlock(b2); - b2->bbFlags |= BBF_REMOVED; - - // If b2 was the last block of a try or handler, update the EH table. - - ehUpdateForDeletedBlock(b2); - - // Update bbRefs and bbPreds - // - // Replace pred 'b2' for 'b2->bbNext' with 'b1' - // Remove pred 'b2' for 'b2->bbJumpDest' - - fgReplacePred(b2->bbNext, b2, b1); - - fgRemoveRefPred(b2->bbJumpDest, b2); + optOptimizeBoolsUpdateTrees(b1, b2, nullptr, t1, t2, foldOp, foldType, cmpOp, bool1, bool2, sameTarget); // Update the change to true to continue the bool optimization for the rest of the BB chain *change = true; - // Update loop table - fgUpdateLoopsAfterCompacting(b1, b2); - #ifdef DEBUG if (verbose) { @@ -7935,84 +7766,82 @@ void Compiler::optOptimizeBoolsCondBlock(BasicBlock* b1, BasicBlock* b2, bool* c } // -// optOptimizeBoolsReturnBlock: Optimize boolean when b1.bbJumpKind is BBJ_COND and b2.bbJumpKind is BBJ_RETURN. -// For example, (x==0 && y==0) generates -// b1: GT_JTRUE (BBJ_COND) -// b2: GT_RETURN (BBJ_RETURN) -// b3: GT_RETURN (BBJ_RETURN), -// and it is folded into -// b1: GT_RETURN (BBJ_RETURN) -// -// - if b1.bbJumpDest == b2.bbNext, it transforms -// b1 : brtrue(t1, b3) -// b2 : ret(t2) -// b3 : ret(0) -// to -// b1 : ret((!t1) && t2) +// optOptimizeBoolsChkBlkCond: Checks block conditions if it can be boolean optimized: +// - if the second or third block contains only one statement +// - if tree operators are of the right type, e.g, GT_JTRUE, GT_RETURN // -// Arguments: -// b1 The first Basic Block with the BBJ_COND conditional jump type. -// b2 The next basic block of b1 with the BBJ_RETURN jump type. -// b3 The next basic block of b2 with GT_RETURN. -// change Set to true if boolean optimization is done and b1, b2 and b3 are folded into b1. +// Arguments: +// b1: The first block +// b2: The second block +// b3: The third block +// tree1: The root node of b1 +// tree2: The root node of b2 +// tree3 The root node of b3 +// sameTarget: true if b1 and b2 jumps to the same destination // -void Compiler::optOptimizeBoolsReturnBlock(BasicBlock* b1, BasicBlock* b2, BasicBlock* b3, bool* change) +Statement* Compiler::optOptimizeBoolsChkBlkCond( + BasicBlock* b1, BasicBlock* b2, BasicBlock* b3, GenTree** tree1, GenTree** tree2, GenTree** tree3, bool* sameTarget) { - // b3 must not be marked as BBF_DONT_REMOVE - - if (b3->bbFlags & BBF_DONT_REMOVE) + bool optReturnBlock = false; + if (b3) { - return; + optReturnBlock = true; } - // Does b1 jump to b3? - // One example: Given the following sequence of blocks : - // B1: brtrue(!t1, B3) - // B2: return(t2) - // B3: return(false) - // we will try to fold it to : - // B1: return(t1|t2) - if (b1->bbJumpDest != b2->bbNext) - { - // b1->bbJumpDest->bbNum == n1 + 2 - return; - } + // Check jump destination condition - if (b2->countOfInEdges() > 1 || b3->countOfInEdges() > 1) + if (!optReturnBlock) { - return; - } - - // The second block and the third block must contain a single statement + if (b1->bbJumpDest == b2->bbJumpDest) + { + // Given the following sequence of blocks : + // B1: brtrue(t1, BX) + // B2: brtrue(t2, BX) + // B3: + // we will try to fold it to : + // B1: brtrue(t1|t2, BX) + // B3: - Statement* s2 = b2->firstStmt(); - Statement* s3 = b3->firstStmt(); - if (s2->GetPrevStmt() != s2 || s3->GetPrevStmt() != s3) - { - return; - } + *sameTarget = true; + } + else if (b1->bbJumpDest == b2->bbNext) /*b1->bbJumpDest->bbNum == n1+2*/ + { + // Given the following sequence of blocks : + // B1: brtrue(t1, B3) + // B2: brtrue(t2, BX) + // B3: + // we will try to fold it to : + // B1: brtrue((!t1)&&t2, BX) + // B3: - GenTree* t2 = s2->GetRootNode(); - GenTree* t3 = s3->GetRootNode(); - if (t2->gtOper != GT_RETURN || t3->gtOper != GT_RETURN) - { - return; + *sameTarget = false; + } + else + { + return nullptr; + } } - - if (!varTypeIsIntegral(t2->TypeGet()) || !varTypeIsIntegral(t3->TypeGet())) + else { - return; + // Does b1 jump to b3? + // One example: Given the following sequence of blocks : + // B1: brtrue(!t1, B3) + // B2: return(t2) + // B3: return(false) + // we will try to fold it to : + // B1: return(t1|t2) + if (b1->bbJumpDest != b2->bbNext) + { + // b1->bbJumpDest->bbNum == n1 + 2 + return nullptr; + } } - // The third block is Return with "CNS_INT int 0/1" - if (t3->AsOp()->gtOp1->gtOper != GT_CNS_INT) - { - return; - } + // Find the block conditions of b1 and b2 - if (t3->AsOp()->gtOp1->gtType != TYP_INT) + if (b2->countOfInEdges() > 1 || (optReturnBlock && b3->countOfInEdges() > 1)) { - return; + return nullptr; } // Find the condition for the first block @@ -8022,52 +7851,108 @@ void Compiler::optOptimizeBoolsReturnBlock(BasicBlock* b1, BasicBlock* b2, Basic GenTree* t1 = s1->GetRootNode(); noway_assert(t1->gtOper == GT_JTRUE); - // Find the branch conditions of b1 and b2 - - bool bool1, bool2; + // The second and the third block must contain a single statement - GenTree* c1 = optIsBoolComp(t1, &t1, &bool1); - if (!c1) + Statement* s2 = b2->firstStmt(); + if (s2->GetPrevStmt() != s2) { - return; + return nullptr; } - GenTree* c2 = optIsBoolComp(t2, &t2, &bool2); - if (!c2) + GenTree* t2 = s2->GetRootNode(); + + if (!optReturnBlock) { - return; + noway_assert(t2->gtOper == GT_JTRUE); } + else + { + if (t2->gtOper != GT_RETURN) + { + return nullptr; + } + Statement* s3 = b3->firstStmt(); + if (s3->GetPrevStmt() != s3) + { + return nullptr; + } + + GenTree* t3 = s3->GetRootNode(); + if (t3->gtOper != GT_RETURN) + { + return nullptr; + } + + if (!varTypeIsIntegral(t2->TypeGet()) || !varTypeIsIntegral(t3->TypeGet())) + { + return nullptr; + } + + // The third block is Return with "CNS_INT int 0/1" + if (t3->AsOp()->gtOp1->gtOper != GT_CNS_INT) + { + return nullptr; + } + + if (t3->AsOp()->gtOp1->gtType != TYP_INT) + { + return nullptr; + } + + *tree3 = t3; + } + + *tree1 = t1; + *tree2 = t2; + + return s1; +} + +// +// optOptimizeBoolsChkTypeCostCond: Checks if type conditions meet the folding condition, and +// if cost to fold is not too expensive. +// +// Arguments: +// t1: The first tree pointing to GT_NE or GT_EQ GenTree node +// t2: The second tree pointing to GT_NE or GT_EQ GenTree node +// c1: The first operand of t1 +// c2: The first operand of t2 +// Return: True if it meets type conditions and cost conditions. Else false. +// +bool Compiler::optOptimizeBoolsChkTypeCostCond(GenTree* t1, GenTree* t2, GenTree* c1, GenTree* c2) +{ noway_assert(t1->OperIs(GT_EQ, GT_NE) && t1->AsOp()->gtOp1 == c1); noway_assert(t2->OperIs(GT_EQ, GT_NE) && t2->AsOp()->gtOp1 == c2); + // // Leave out floats where the bit-representation is more complicated // - there are two representations for 0. // if (varTypeIsFloating(c1->TypeGet()) || varTypeIsFloating(c2->TypeGet())) { - return; + return false; } // Make sure the types involved are of the same sizes if (genTypeSize(c1->TypeGet()) != genTypeSize(c2->TypeGet())) { - return; + return false; } if (genTypeSize(t1->TypeGet()) != genTypeSize(t2->TypeGet())) { - return; + return false; } #ifdef TARGET_ARMARCH // Skip the small operand which we cannot encode. if (varTypeIsSmall(c1->TypeGet())) - return; + return false; #endif // The second condition must not contain side effects if (c2->gtFlags & GTF_GLOB_EFFECT) { - return; + return false; } // The second condition must not be too expensive @@ -8076,27 +7961,49 @@ void Compiler::optOptimizeBoolsReturnBlock(BasicBlock* b1, BasicBlock* b2, Basic if (c2->GetCostEx() > 12) { - return; - } - - genTreeOps foldOp = GT_NONE; - genTreeOps cmpOp = GT_NONE; - var_types foldType = c1->TypeGet(); - if (varTypeIsGC(foldType)) - { - foldType = TYP_I_IMPL; + return false; } - // Get the fold operator and the comparison operator + return true; +} - ssize_t it3val = t3->AsOp()->gtOp1->AsIntCon()->gtIconVal; - optReturnGetFoldAndCompOper(t1, t2, it3val, bool1, bool2, &foldOp, &cmpOp); - if (foldOp == GT_NONE) +// +// optOptimizeBoolsUpdateTrees: Fold the trees based on fold type and comparison type, +// update the edges, unlink removed blocks and update loop table. +// +// Arguments: +// b1: The first Basic Block +// b2: The second Basic Block +// b3: The thrid Basic Block. null if b1: BBJ_COND and b2: BBJ_RETURN +// tree1: The tree of b1 +// tree2: The tree of b2 +// foldOp: The fold operator of GT_AND or GT_OR +// foldType: The type of the folded tree +// cmpOp: The comparison operator of GT_EQ or GT_NE +// bool1: If tree1 is boolean comparison +// bool2: If tree2 is boolean comparison +// sameTarget: If b1 and b2 has the same jump target +// +void Compiler::optOptimizeBoolsUpdateTrees(BasicBlock* b1, + BasicBlock* b2, + BasicBlock* b3, + GenTree* t1, + GenTree* t2, + genTreeOps foldOp, + var_types foldType, + genTreeOps cmpOp, + bool bool1, + bool bool2, + bool sameTarget) +{ + bool optReturnBlock = false; + if (b3) { - return; + optReturnBlock = true; } - // Now update the trees + GenTree* c1 = t1->AsOp()->gtOp1; + GenTree* c2 = t2->AsOp()->gtOp1; GenTree* cmpOp1 = gtNewOperNode(foldOp, foldType, c1, c2); if (bool1 && bool2) @@ -8106,11 +8013,15 @@ void Compiler::optOptimizeBoolsReturnBlock(BasicBlock* b1, BasicBlock* b2, Basic } t1->SetOper(cmpOp); - t1->AsOp()->gtOp1 = cmpOp1; - t1->AsOp()->gtOp2->gtType = foldType; // Could have been varTypeIsGC() - t1->AsOp()->gtOp2->AsIntCon()->gtIconVal = 0; - s1->GetRootNode()->gtOper = GT_RETURN; - s1->GetRootNode()->gtType = s2->GetRootNode()->gtType; + t1->AsOp()->gtOp1 = cmpOp1; + t1->AsOp()->gtOp2->gtType = foldType; // Could have been varTypeIsGC() + if (optReturnBlock) + { + // Update tree when b1: BBJ_COND and b2: GT_RETURN (BBJ_RETURN) + t1->AsOp()->gtOp2->AsIntCon()->gtIconVal = 0; + b1->lastStmt()->GetRootNode()->gtOper = GT_RETURN; + b1->lastStmt()->GetRootNode()->gtType = b2->lastStmt()->GetRootNode()->gtType; + } #if FEATURE_SET_FLAGS // For comparisons against zero we will have the GTF_SET_FLAGS set @@ -8135,40 +8046,186 @@ void Compiler::optOptimizeBoolsReturnBlock(BasicBlock* b1, BasicBlock* b2, Basic // cmpOp1->gtRequestSetFlags(); #endif - // Modify the target of the conditional jump and update bbRefs and bbPreds - fgRemoveRefPred(b1->bbJumpDest, b1); + if (!optReturnBlock) + { + // Update edges if b1: BBJ_COND and b2: BBJ_COND + + flowList* edge1 = fgGetPredForBlock(b1->bbJumpDest, b1); + flowList* edge2; + + if (sameTarget) + { + edge2 = fgGetPredForBlock(b2->bbJumpDest, b2); + } + else + { + edge2 = fgGetPredForBlock(b2->bbNext, b2); + + fgRemoveRefPred(b1->bbJumpDest, b1); + + b1->bbJumpDest = b2->bbJumpDest; + + fgAddRefPred(b2->bbJumpDest, b1); + } + + noway_assert(edge1 != nullptr); + noway_assert(edge2 != nullptr); + + BasicBlock::weight_t edgeSumMin = edge1->edgeWeightMin() + edge2->edgeWeightMin(); + BasicBlock::weight_t edgeSumMax = edge1->edgeWeightMax() + edge2->edgeWeightMax(); + if ((edgeSumMax >= edge1->edgeWeightMax()) && (edgeSumMax >= edge2->edgeWeightMax())) + { + edge1->setEdgeWeights(edgeSumMin, edgeSumMax, b1->bbJumpDest); + } + else + { + edge1->setEdgeWeights(BB_ZERO_WEIGHT, BB_MAX_WEIGHT, b1->bbJumpDest); + } + } + + /* Modify the target of the conditional jump and update bbRefs and bbPreds */ - noway_assert(!b2->bbJumpDest); - b1->bbJumpDest = b2->bbJumpDest; - b1->bbJumpKind = b2->bbJumpKind; - b1->bbJumpSwt = b2->bbJumpSwt; + if (optReturnBlock) + { + fgRemoveRefPred(b1->bbJumpDest, b1); - // Get rid of the second & third block (which is a BBJ_RETURN) + noway_assert(!b2->bbJumpDest); + b1->bbJumpDest = b2->bbJumpDest; + b1->bbJumpKind = b2->bbJumpKind; + b1->bbJumpSwt = b2->bbJumpSwt; - noway_assert(b1->bbJumpKind == BBJ_RETURN); - noway_assert(b2->bbJumpKind == BBJ_RETURN); - noway_assert(b1->bbNext == b2); - noway_assert(b2->bbNext == b3); - noway_assert(b3); + noway_assert(b1->bbJumpKind == BBJ_RETURN); + noway_assert(b2->bbJumpKind == BBJ_RETURN); + noway_assert(b1->bbNext == b2); + noway_assert(b2->bbNext == b3); + noway_assert(b3); + } + else + { + noway_assert(b1->bbJumpKind == BBJ_COND); + noway_assert(b2->bbJumpKind == BBJ_COND); + noway_assert(b1->bbJumpDest == b2->bbJumpDest); + noway_assert(b1->bbNext == b2); + noway_assert(b2->bbNext); + } + + // Get rid of the second block fgUnlinkBlock(b2); b2->bbFlags |= BBF_REMOVED; - // If b2 was the last block of a try or handler, update the EH table. - ehUpdateForDeletedBlock(b2); - fgUnlinkBlock(b3); - b3->bbFlags |= BBF_REMOVED; - - ehUpdateForDeletedBlock(b3); + if (optReturnBlock) + { + // Get rid of the third block + fgUnlinkBlock(b3); + b3->bbFlags |= BBF_REMOVED; + // If b3 was the last block of a try or handler, update the EH table. + ehUpdateForDeletedBlock(b3); + } - *change = true; + if (!optReturnBlock) + { + // Update bbRefs and bbPreds + // + // Replace pred 'b2' for 'b2->bbNext' with 'b1' + // Remove pred 'b2' for 'b2->bbJumpDest' + fgReplacePred(b2->bbNext, b2, b1); + fgRemoveRefPred(b2->bbJumpDest, b2); + } // Update loop table fgUpdateLoopsAfterCompacting(b1, b2); - fgUpdateLoopsAfterCompacting(b1, b3); + if (optReturnBlock) + { + fgUpdateLoopsAfterCompacting(b1, b3); + } +} + + +// +// optOptimizeBoolsReturnBlock: Optimize boolean when b1.bbJumpKind is BBJ_COND and b2.bbJumpKind is BBJ_RETURN. +// For example, (x==0 && y==0) generates +// b1: GT_JTRUE (BBJ_COND) +// b2: GT_RETURN (BBJ_RETURN) +// b3: GT_RETURN (BBJ_RETURN), +// and it is folded into +// b1: GT_RETURN (BBJ_RETURN) +// +// - if b1.bbJumpDest == b2.bbNext, it transforms +// b1 : brtrue(t1, b3) +// b2 : ret(t2) +// b3 : ret(0) +// to +// b1 : ret((!t1) && t2) +// +// Arguments: +// b1 The first Basic Block with the BBJ_COND conditional jump type. +// b2 The next basic block of b1 with the BBJ_RETURN jump type. +// b3 The next basic block of b2 with GT_RETURN. +// change Set to true if boolean optimization is done and b1, b2 and b3 are folded into b1. +// +void Compiler::optOptimizeBoolsReturnBlock(BasicBlock* b1, BasicBlock* b2, BasicBlock* b3, bool* change) +{ + bool sameTarget = true; + GenTree* t1; + GenTree* t2; + GenTree* t3; + Statement* s1; + + s1 = optOptimizeBoolsChkBlkCond(b1, b2, b3, &t1, &t2, &t3, &sameTarget); + if (!s1) + { + return; + } + + // Find the branch conditions of b1 and b2 + + bool bool1, bool2; + + GenTree* c1 = optIsBoolComp(t1, &t1, &bool1); + if (!c1) + { + return; + } + + GenTree* c2 = optIsBoolComp(t2, &t2, &bool2); + if (!c2) + { + return; + } + + // Find the type and cost conditions of t1 and t2 + + if (!optOptimizeBoolsChkTypeCostCond(t1, t2, c1, c2)) + { + return; + } + + // Get the fold operator and the comparison operator + + genTreeOps foldOp = GT_NONE; + genTreeOps cmpOp = GT_NONE; + var_types foldType = c1->TypeGet(); + if (varTypeIsGC(foldType)) + { + foldType = TYP_I_IMPL; + } + + ssize_t it3val = t3->AsOp()->gtOp1->AsIntCon()->gtIconVal; + optReturnGetFoldAndCompOper(t1, t2, it3val, bool1, bool2, &foldOp, &cmpOp); + if (foldOp == GT_NONE || cmpOp == GT_NONE) + { + return; + } + + // Now update the trees + + optOptimizeBoolsUpdateTrees(b1, b2, b3, t1, t2, foldOp, foldType, cmpOp, bool1, bool2, false); + + *change = true; #ifdef DEBUG if (verbose) @@ -8478,6 +8535,13 @@ void Compiler::optOptimizeBools() break; } + // b3 must not be marked as BBF_DONT_REMOVE + + if (b3->bbFlags & BBF_DONT_REMOVE) + { + continue; + } + optOptimizeBoolsReturnBlock(b1, b2, b3, &change); } else From 5746b61fa140a983034a5b7f18b00f9db4f55000 Mon Sep 17 00:00:00 2001 From: Julie Lee Date: Fri, 26 Mar 2021 18:24:14 -0700 Subject: [PATCH 12/25] format patch --- src/coreclr/jit/compiler.h | 34 +++++++++++++++++----------------- src/coreclr/jit/optimizer.cpp | 3 +-- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index c2cd0313a56ce..a9c0d0d84585c 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -6315,25 +6315,25 @@ class Compiler void optOptimizeBools(); private: - void optOptimizeBoolsCondBlock(BasicBlock* b1, BasicBlock* b2, bool* change); + void optOptimizeBoolsCondBlock(BasicBlock* b1, BasicBlock* b2, bool* change); Statement* optOptimizeBoolsChkBlkCond( BasicBlock* b1, BasicBlock* b2, BasicBlock* b3, GenTree** t1, GenTree** t2, GenTree** t3, bool* sameTarget); - GenTree* optIsBoolComp(GenTree* tree, GenTree** compPtr, bool* boolPtr); - bool optOptimizeBoolsChkTypeCostCond(GenTree* t1, GenTree* t2, GenTree* c1, GenTree* c2); - void optOptimizeBoolsUpdateTrees(BasicBlock* b1, - BasicBlock* b2, - BasicBlock* b3, - GenTree* t1, - GenTree* t2, - genTreeOps foldOp, - var_types foldType, - genTreeOps cmpOp, - bool bool1, - bool bool2, - bool sameTarget); - void optOptimizeBoolsReturnBlock(BasicBlock* b1, BasicBlock* b2, BasicBlock* b3, bool* change); - void optReturnGetFoldAndCompOper( - GenTree* tree1, GenTree* tree2, ssize_t it3val, bool bool1, bool bool2, genTreeOps* foldOp, genTreeOps* cmpOp); + GenTree* optIsBoolComp(GenTree* tree, GenTree** compPtr, bool* boolPtr); + bool optOptimizeBoolsChkTypeCostCond(GenTree* t1, GenTree* t2, GenTree* c1, GenTree* c2); + void optOptimizeBoolsUpdateTrees(BasicBlock* b1, + BasicBlock* b2, + BasicBlock* b3, + GenTree* t1, + GenTree* t2, + genTreeOps foldOp, + var_types foldType, + genTreeOps cmpOp, + bool bool1, + bool bool2, + bool sameTarget); + void optOptimizeBoolsReturnBlock(BasicBlock* b1, BasicBlock* b2, BasicBlock* b3, bool* change); + void optReturnGetFoldAndCompOper( + GenTree* tree1, GenTree* tree2, ssize_t it3val, bool bool1, bool bool2, genTreeOps* foldOp, genTreeOps* cmpOp); #ifdef DEBUG void optOptimizeBoolsGcStress(BasicBlock* condBlock); #endif diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index 3cbe88253301b..351ebfc44d220 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -7860,7 +7860,7 @@ Statement* Compiler::optOptimizeBoolsChkBlkCond( } GenTree* t2 = s2->GetRootNode(); - + if (!optReturnBlock) { noway_assert(t2->gtOper == GT_JTRUE); @@ -8144,7 +8144,6 @@ void Compiler::optOptimizeBoolsUpdateTrees(BasicBlock* b1, } } - // // optOptimizeBoolsReturnBlock: Optimize boolean when b1.bbJumpKind is BBJ_COND and b2.bbJumpKind is BBJ_RETURN. // For example, (x==0 && y==0) generates From 77400646c9b360099ea7662030d1828669e21ec1 Mon Sep 17 00:00:00 2001 From: Julie Lee Date: Fri, 2 Apr 2021 13:02:50 -0700 Subject: [PATCH 13/25] Unit test changed to remove EH handling and add return value checks --- .../JIT/opt/OptimizeBools/optboolsreturn.cs | 76 +++++++++---------- 1 file changed, 37 insertions(+), 39 deletions(-) diff --git a/src/tests/JIT/opt/OptimizeBools/optboolsreturn.cs b/src/tests/JIT/opt/OptimizeBools/optboolsreturn.cs index c0c921e92f2c3..3b100ca67df12 100644 --- a/src/tests/JIT/opt/OptimizeBools/optboolsreturn.cs +++ b/src/tests/JIT/opt/OptimizeBools/optboolsreturn.cs @@ -38,54 +38,52 @@ private static bool AreZero4(int x, int y, int z, int w) return (x == 0 && y == 0 && z == 0 && w == 0); } - // Cases that skip optimization - [MethodImpl(MethodImplOptions.NoInlining)] - private static bool AreOne(int x, int y) + public static int Main() { - return (x == 1 && y == 1); - } + // Optimize boolean - [MethodImpl(MethodImplOptions.NoInlining)] - private static bool IsEitherZero(int x, int y) - { - return (x == 0 || y == 0); - } + if (!AreZero(0, 0)) + { + Console.WriteLine("CBoolTest:AreZero failed"); + return 101; + } - [MethodImpl(MethodImplOptions.NoInlining)] - private static bool IsEitherOne(int x, int y) - { - return (x == 1 || y == 1); - } + if (!AreNull(null, null)) + { + Console.WriteLine("CBoolTest:AreNull(null, null) failed"); + return 101; + } - public static int Main() - { - try + if (AreNull(new Object(), new Object())) + { + Console.WriteLine("CBoolTest:AreNull(obj, obj) failed"); + return 101; + } + + if (AreZero(1, 1)) + { + Console.WriteLine("CBoolTest:AreZero(1, 1) failed"); + return 101; + } + + if (!AreZero2(0, 0)) + { + Console.WriteLine("CBoolTest:AreZero2 failed"); + return 101; + } + + if (!AreZero3(0, 0, 0)) { - // Optimize boolean - - AreZero(0, 0); - AreNull(null, null); - AreNull(new Object(), new Object()); - AreZero(1, 1); - AreZero2(0, 0); - AreZero3(0, 0, 0); - AreZero4(0, 0, 0, 0); - - // Skip optimization - - // Test if ANDing or GT_NE requires both operands to be boolean - AreOne(1, 1); - // Test if ANDing requires both operands to be boolean - IsEitherZero(0, 1); - // Test if GT_NE requires both operands to be boolean - IsEitherOne(0, 1); + Console.WriteLine("CBoolTest:AreZero3 failed"); + return 101; } - catch (Exception e) + + if (!AreZero4(0, 0, 0, 0)) { - Console.WriteLine(e.Message); + Console.WriteLine("CBoolTest:AreZero4 failed"); return 101; } - Console.WriteLine("PASSED"); + return 100; } } From d0c47e822e46e8cd143dad791798282bf92b74e3 Mon Sep 17 00:00:00 2001 From: Julie Lee Date: Sun, 4 Apr 2021 01:56:02 +0900 Subject: [PATCH 14/25] Unit test: add back test cases for ANDing and NE cases --- .../JIT/opt/OptimizeBools/optboolsreturn.cs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/tests/JIT/opt/OptimizeBools/optboolsreturn.cs b/src/tests/JIT/opt/OptimizeBools/optboolsreturn.cs index 3b100ca67df12..fc160ccaca3a8 100644 --- a/src/tests/JIT/opt/OptimizeBools/optboolsreturn.cs +++ b/src/tests/JIT/opt/OptimizeBools/optboolsreturn.cs @@ -38,6 +38,25 @@ private static bool AreZero4(int x, int y, int z, int w) return (x == 0 && y == 0 && z == 0 && w == 0); } + // Cases that skip optimization + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool AreOne(int x, int y) + { + return (x == 1 && y == 1); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool IsEitherZero(int x, int y) + { + return (x == 0 || y == 0); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool IsEitherOne(int x, int y) + { + return (x == 1 || y == 1); + } + public static int Main() { // Optimize boolean @@ -84,6 +103,29 @@ public static int Main() return 101; } + // Skip optimization + + // Test if ANDing or GT_NE requires both operands to be boolean + if (!AreOne(1, 1)) + { + Console.WriteLine("CBoolTest:AreOne failed"); + return 101; + } + + // Test if ANDing requires both operands to be boolean + if (!IsEitherZero(0, 1)) + { + Console.WriteLine("CBoolTest:IsEitherZero failed"); + return 101; + } + + // Test if GT_NE requires both operands to be boolean + if (!IsEitherOne(0, 1)) + { + Console.WriteLine("CBoolTest:IsEitherOne failed"); + return 101; + } + return 100; } } From f0adde561e0dec061be01df306cf33472de4df49 Mon Sep 17 00:00:00 2001 From: Julie Lee Date: Fri, 23 Apr 2021 20:19:47 +0900 Subject: [PATCH 15/25] Made OptBoolsDsc struct to pass it off to the helper methods. --- src/coreclr/jit/compiler.h | 51 ++++--- src/coreclr/jit/optimizer.cpp | 248 +++++++++++++++++++--------------- 2 files changed, 170 insertions(+), 129 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index a9c0d0d84585c..a5e69008c52ea 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -6315,25 +6315,38 @@ class Compiler void optOptimizeBools(); private: - void optOptimizeBoolsCondBlock(BasicBlock* b1, BasicBlock* b2, bool* change); - Statement* optOptimizeBoolsChkBlkCond( - BasicBlock* b1, BasicBlock* b2, BasicBlock* b3, GenTree** t1, GenTree** t2, GenTree** t3, bool* sameTarget); - GenTree* optIsBoolComp(GenTree* tree, GenTree** compPtr, bool* boolPtr); - bool optOptimizeBoolsChkTypeCostCond(GenTree* t1, GenTree* t2, GenTree* c1, GenTree* c2); - void optOptimizeBoolsUpdateTrees(BasicBlock* b1, - BasicBlock* b2, - BasicBlock* b3, - GenTree* t1, - GenTree* t2, - genTreeOps foldOp, - var_types foldType, - genTreeOps cmpOp, - bool bool1, - bool bool2, - bool sameTarget); - void optOptimizeBoolsReturnBlock(BasicBlock* b1, BasicBlock* b2, BasicBlock* b3, bool* change); - void optReturnGetFoldAndCompOper( - GenTree* tree1, GenTree* tree2, ssize_t it3val, bool bool1, bool bool2, genTreeOps* foldOp, genTreeOps* cmpOp); + struct OptBoolsDsc + { + BasicBlock* b1; // The first Basic Block with the BBJ_COND conditional jump type + BasicBlock* b2; // The next basic block of b1 + BasicBlock* b3; // The next basic block of b2 + + GenTree* t1; // The root node of b1 + GenTree* t2; // The root node of b2 + GenTree* t3; // The root node of b3 + + GenTree* t1CompPtr; // The compare node (i.e. GT_EQ or GT_NE node) of t1 + GenTree* t2CompPtr; // The compare node (i.e. GT_EQ or GT_NE node) of t2 + + GenTree* c1; // The first operand of t1CompPtr + GenTree* c2; // The first operand of t2CompPtr + + bool sameTarget; // if b1 and b2 jumps to the same destination + + genTreeOps foldOp; // The fold operator (e.g., GT_AND or GT_OR) + var_types foldType; // The type of the folded tree + genTreeOps cmpOp; // The comparison operator (e.g., GT_EQ or GT_NE) + bool bool1; // If tree t1CompPtr is boolean comparison + bool bool2; // If tree t2CompPtr is boolean comparison + }; + + void optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc, bool* change); + void optOptimizeBoolsReturnBlock(OptBoolsDsc* pOptBoolsDsc, bool* change); + Statement* optOptimizeBoolsChkBlkCond(OptBoolsDsc* pOptBoolsDsc); + GenTree* optIsBoolComp(GenTree* tree, GenTree** compPtr, bool* boolPtr); + bool optOptimizeBoolsChkTypeCostCond(OptBoolsDsc* pOptBoolsDsc); + void optOptimizeBoolsUpdateTrees(OptBoolsDsc* pOptBoolsDsc); + void optReturnGetFoldAndCompOper(OptBoolsDsc* pOptBoolsDsc); #ifdef DEBUG void optOptimizeBoolsGcStress(BasicBlock* condBlock); #endif diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index 351ebfc44d220..2716a9083df2e 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -7635,18 +7635,20 @@ bool Compiler::optIsRangeCheckRemovable(GenTree* tree) // b3 : // // Arguments: -// b1 The first Basic Block with the BBJ_COND conditional jump type. -// b2 The next basic block of b1 with the BBJ_COND conditional jump type. -// change Set to true if boolean optimization is done and b1 and b2 are folded into b1. +// pOptBoolsDsc The descriptor with Basic Block b1 and b2 set, where +// b1 is the first Basic Block with the BBJ_COND conditional jump type, and +// b2 is the next basic block of b1 with the BBJ_COND conditional jump type. +// change Set to true if boolean optimization is done and b1 and b2 are folded into b1. // -void Compiler::optOptimizeBoolsCondBlock(BasicBlock* b1, BasicBlock* b2, bool* change) +void Compiler::optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc, bool* change) { - bool sameTarget = false; // Do b1 and b2 have the same bbJumpDest? - GenTree* t1; - GenTree* t2; - Statement* s1; + // Check if b1 and b2 jump to the same target and get back pointers to t1 and t2 tree nodes + + pOptBoolsDsc->t3 = nullptr; + pOptBoolsDsc->sameTarget = false; // Do b1 and b2 have the same bbJumpDest? - s1 = optOptimizeBoolsChkBlkCond(b1, b2, nullptr, &t1, &t2, nullptr, &sameTarget); + Statement* s1; + s1 = optOptimizeBoolsChkBlkCond(pOptBoolsDsc); if (!s1) { return; @@ -7654,23 +7656,23 @@ void Compiler::optOptimizeBoolsCondBlock(BasicBlock* b1, BasicBlock* b2, bool* c // Find the branch conditions of b1 and b2 - bool bool1, bool2; - - GenTree* c1 = optIsBoolComp(t1, &t1, &bool1); + GenTree* c1 = optIsBoolComp(pOptBoolsDsc->t1, &pOptBoolsDsc->t1CompPtr, &pOptBoolsDsc->bool1); if (!c1) { return; } + pOptBoolsDsc->c1 = c1; - GenTree* c2 = optIsBoolComp(t2, &t2, &bool2); + GenTree* c2 = optIsBoolComp(pOptBoolsDsc->t2, &pOptBoolsDsc->t2CompPtr, &pOptBoolsDsc->bool2); if (!c2) { return; } + pOptBoolsDsc->c2 = c2; // Find the type and cost conditions of t1 and t2 - if (!optOptimizeBoolsChkTypeCostCond(t1, t2, c1, c2)) + if (!optOptimizeBoolsChkTypeCostCond(pOptBoolsDsc)) { return; } @@ -7685,16 +7687,16 @@ void Compiler::optOptimizeBoolsCondBlock(BasicBlock* b1, BasicBlock* b2, bool* c foldType = TYP_I_IMPL; } - if (sameTarget) + if (pOptBoolsDsc->sameTarget) { // Both conditions must be the same - if (t1->gtOper != t2->gtOper) + if (pOptBoolsDsc->t1CompPtr->gtOper != pOptBoolsDsc->t2CompPtr->gtOper) { return; } - if (t1->gtOper == GT_EQ) + if (pOptBoolsDsc->t1CompPtr->gtOper == 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 @@ -7715,12 +7717,12 @@ void Compiler::optOptimizeBoolsCondBlock(BasicBlock* b1, BasicBlock* b2, bool* c { // The b1 condition must be the reverse of the b2 condition - if (t1->gtOper == t2->gtOper) + if (pOptBoolsDsc->t1CompPtr->gtOper == pOptBoolsDsc->t2CompPtr->gtOper) { return; } - if (t1->gtOper == GT_EQ) + if (pOptBoolsDsc->t1CompPtr->gtOper == GT_EQ) { // 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 @@ -7740,7 +7742,7 @@ void Compiler::optOptimizeBoolsCondBlock(BasicBlock* b1, BasicBlock* b2, bool* c // Anding requires both values to be 0 or 1 - if ((foldOp == GT_AND) && (!bool1 || !bool2)) + if ((foldOp == GT_AND) && (!pOptBoolsDsc->bool1 || !pOptBoolsDsc->bool2)) { return; } @@ -7749,7 +7751,11 @@ void Compiler::optOptimizeBoolsCondBlock(BasicBlock* b1, BasicBlock* b2, bool* c // Now update the trees // - optOptimizeBoolsUpdateTrees(b1, b2, nullptr, t1, t2, foldOp, foldType, cmpOp, bool1, bool2, sameTarget); + pOptBoolsDsc->foldOp = foldOp; + pOptBoolsDsc->foldType = foldType; + pOptBoolsDsc->cmpOp = cmpOp; + + optOptimizeBoolsUpdateTrees(pOptBoolsDsc); // Update the change to true to continue the bool optimization for the rest of the BB chain *change = true; @@ -7758,7 +7764,7 @@ void Compiler::optOptimizeBoolsCondBlock(BasicBlock* b1, BasicBlock* b2, bool* c if (verbose) { printf("Folded %sboolean conditions of " FMT_BB " and " FMT_BB " to :\n", c2->OperIsLeaf() ? "" : "non-leaf ", - b1->bbNum, b2->bbNum); + pOptBoolsDsc->b1->bbNum, pOptBoolsDsc->b2->bbNum); gtDispStmt(s1); printf("\n"); } @@ -7771,17 +7777,21 @@ void Compiler::optOptimizeBoolsCondBlock(BasicBlock* b1, BasicBlock* b2, bool* c // - if tree operators are of the right type, e.g, GT_JTRUE, GT_RETURN // // Arguments: -// b1: The first block -// b2: The second block -// b3: The third block -// tree1: The root node of b1 -// tree2: The root node of b2 -// tree3 The root node of b3 -// sameTarget: true if b1 and b2 jumps to the same destination +// pOptBoolsDsc The descriptor with Basic Block b1, b2 and b3 info set when the method is called, +// where b1 is the first block, b2 is the second block and b3 is the third block. +// It if passes all the conditions, t1, t2 and t3 are set to the root nodes of b1, b2 and b3. +// SameTarget is also updated to true if b1 and b2 jump to the same destination. +// +// Return: +// s1 If all conditions pass, returns the last statement of b1. // -Statement* Compiler::optOptimizeBoolsChkBlkCond( - BasicBlock* b1, BasicBlock* b2, BasicBlock* b3, GenTree** tree1, GenTree** tree2, GenTree** tree3, bool* sameTarget) +Statement* Compiler::optOptimizeBoolsChkBlkCond(OptBoolsDsc* pOptBoolsDsc) { + BasicBlock* b1 = pOptBoolsDsc->b1; + BasicBlock* b2 = pOptBoolsDsc->b2; + BasicBlock* b3 = pOptBoolsDsc->b3; + bool* sameTarget = &pOptBoolsDsc->sameTarget; + bool optReturnBlock = false; if (b3) { @@ -7900,11 +7910,11 @@ Statement* Compiler::optOptimizeBoolsChkBlkCond( return nullptr; } - *tree3 = t3; + pOptBoolsDsc->t3 = t3; } - *tree1 = t1; - *tree2 = t2; + pOptBoolsDsc->t1 = t1; + pOptBoolsDsc->t2 = t2; return s1; } @@ -7914,16 +7924,24 @@ Statement* Compiler::optOptimizeBoolsChkBlkCond( // if cost to fold is not too expensive. // // Arguments: -// t1: The first tree pointing to GT_NE or GT_EQ GenTree node -// t2: The second tree pointing to GT_NE or GT_EQ GenTree node -// c1: The first operand of t1 -// c2: The first operand of t2 -// Return: True if it meets type conditions and cost conditions. Else false. +// pOptBoolsDsc The descriptor for boolean optimization with pointers to tree compare nodes and its first operand, +// where, +// t1CompPtr: The first tree pointing to GT_NE or GT_EQ GenTree node +// t2CompPtr: The second tree pointing to GT_NE or GT_EQ GenTree node +// c1: The first operand of t1CompPtr +// c2: The first operand of t2CompPtr +// Return: +// True if it meets type conditions and cost conditions. Else false. // -bool Compiler::optOptimizeBoolsChkTypeCostCond(GenTree* t1, GenTree* t2, GenTree* c1, GenTree* c2) +bool Compiler::optOptimizeBoolsChkTypeCostCond(OptBoolsDsc* pOptBoolsDsc) { - noway_assert(t1->OperIs(GT_EQ, GT_NE) && t1->AsOp()->gtOp1 == c1); - noway_assert(t2->OperIs(GT_EQ, GT_NE) && t2->AsOp()->gtOp1 == c2); + GenTree* t1CompPtr = pOptBoolsDsc->t1CompPtr; + GenTree* t2CompPtr = pOptBoolsDsc->t2CompPtr; + GenTree* c1 = pOptBoolsDsc->c1; + GenTree* c2 = pOptBoolsDsc->c2; + + noway_assert(t1CompPtr->OperIs(GT_EQ, GT_NE) && t1CompPtr->AsOp()->gtOp1 == c1); + noway_assert(t2CompPtr->OperIs(GT_EQ, GT_NE) && t2CompPtr->AsOp()->gtOp1 == c2); // // Leave out floats where the bit-representation is more complicated @@ -7939,7 +7957,7 @@ bool Compiler::optOptimizeBoolsChkTypeCostCond(GenTree* t1, GenTree* t2, GenTree { return false; } - if (genTypeSize(t1->TypeGet()) != genTypeSize(t2->TypeGet())) + if (genTypeSize(t1CompPtr->TypeGet()) != genTypeSize(t2CompPtr->TypeGet())) { return false; } @@ -7972,53 +7990,49 @@ bool Compiler::optOptimizeBoolsChkTypeCostCond(GenTree* t1, GenTree* t2, GenTree // update the edges, unlink removed blocks and update loop table. // // Arguments: -// b1: The first Basic Block -// b2: The second Basic Block -// b3: The thrid Basic Block. null if b1: BBJ_COND and b2: BBJ_RETURN -// tree1: The tree of b1 -// tree2: The tree of b2 -// foldOp: The fold operator of GT_AND or GT_OR -// foldType: The type of the folded tree -// cmpOp: The comparison operator of GT_EQ or GT_NE -// bool1: If tree1 is boolean comparison -// bool2: If tree2 is boolean comparison -// sameTarget: If b1 and b2 has the same jump target +// pOptBoolsDsc The descriptor for boolean optimization with below members set when called: +// b1: The first Basic Block +// b2: The second Basic Block +// b3: The thrid Basic Block. null if b1: BBJ_COND and b2: BBJ_RETURN +// t1CompPtr: The compare tree of b1 +// t2CompPtr: The compare tree of b2 +// foldOp: The fold operator of GT_AND or GT_OR +// foldType: The type of the folded tree +// cmpOp: The comparison operator of GT_EQ or GT_NE +// bool1: If t1CompPtr is boolean comparison +// bool2: If t2CompPtr is boolean comparison +// sameTarget: If b1 and b2 has the same jump target // -void Compiler::optOptimizeBoolsUpdateTrees(BasicBlock* b1, - BasicBlock* b2, - BasicBlock* b3, - GenTree* t1, - GenTree* t2, - genTreeOps foldOp, - var_types foldType, - genTreeOps cmpOp, - bool bool1, - bool bool2, - bool sameTarget) +void Compiler::optOptimizeBoolsUpdateTrees(OptBoolsDsc* pOptBoolsDsc) { + BasicBlock* b1 = pOptBoolsDsc->b1; + BasicBlock* b2 = pOptBoolsDsc->b2; + BasicBlock* b3 = pOptBoolsDsc->b3; + bool optReturnBlock = false; if (b3) { optReturnBlock = true; } - GenTree* c1 = t1->AsOp()->gtOp1; - GenTree* c2 = t2->AsOp()->gtOp1; + GenTree* c1 = pOptBoolsDsc->c1; + GenTree* c2 = pOptBoolsDsc->c2; - GenTree* cmpOp1 = gtNewOperNode(foldOp, foldType, c1, c2); - if (bool1 && bool2) + GenTree* cmpOp1 = gtNewOperNode(pOptBoolsDsc->foldOp, pOptBoolsDsc->foldType, c1, c2); + if (pOptBoolsDsc->bool1 && pOptBoolsDsc->bool2) { // When we 'OR'/'AND' two booleans, the result is boolean as well cmpOp1->gtFlags |= GTF_BOOLEAN; } - t1->SetOper(cmpOp); - t1->AsOp()->gtOp1 = cmpOp1; - t1->AsOp()->gtOp2->gtType = foldType; // Could have been varTypeIsGC() + GenTree* t1CompPtr = pOptBoolsDsc->t1CompPtr; + t1CompPtr->SetOper(pOptBoolsDsc->cmpOp); + t1CompPtr->AsOp()->gtOp1 = cmpOp1; + t1CompPtr->AsOp()->gtOp2->gtType = pOptBoolsDsc->foldType; // Could have been varTypeIsGC() if (optReturnBlock) { // Update tree when b1: BBJ_COND and b2: GT_RETURN (BBJ_RETURN) - t1->AsOp()->gtOp2->AsIntCon()->gtIconVal = 0; + t1CompPtr->AsOp()->gtOp2->AsIntCon()->gtIconVal = 0; b1->lastStmt()->GetRootNode()->gtOper = GT_RETURN; b1->lastStmt()->GetRootNode()->gtType = b2->lastStmt()->GetRootNode()->gtType; } @@ -8054,7 +8068,7 @@ void Compiler::optOptimizeBoolsUpdateTrees(BasicBlock* b1, flowList* edge1 = fgGetPredForBlock(b1->bbJumpDest, b1); flowList* edge2; - if (sameTarget) + if (pOptBoolsDsc->sameTarget) { edge2 = fgGetPredForBlock(b2->bbJumpDest, b2); } @@ -8161,20 +8175,21 @@ void Compiler::optOptimizeBoolsUpdateTrees(BasicBlock* b1, // b1 : ret((!t1) && t2) // // Arguments: -// b1 The first Basic Block with the BBJ_COND conditional jump type. -// b2 The next basic block of b1 with the BBJ_RETURN jump type. -// b3 The next basic block of b2 with GT_RETURN. -// change Set to true if boolean optimization is done and b1, b2 and b3 are folded into b1. +// pOptBoolsDsc The descriptor for boolean optimization with below members set when called: +// b1 The first Basic Block with the BBJ_COND conditional jump type. +// b2 The next basic block of b1 with the BBJ_RETURN jump type. +// b3 The next basic block of b2 with GT_RETURN. +// change Set to true if boolean optimization is done and b1, b2 and b3 are folded into b1. // -void Compiler::optOptimizeBoolsReturnBlock(BasicBlock* b1, BasicBlock* b2, BasicBlock* b3, bool* change) +void Compiler::optOptimizeBoolsReturnBlock(OptBoolsDsc* pOptBoolsDsc, bool* change) { - bool sameTarget = true; - GenTree* t1; - GenTree* t2; - GenTree* t3; - Statement* s1; + BasicBlock* b1 = pOptBoolsDsc->b1; + BasicBlock* b2 = pOptBoolsDsc->b2; + BasicBlock* b3 = pOptBoolsDsc->b3; - s1 = optOptimizeBoolsChkBlkCond(b1, b2, b3, &t1, &t2, &t3, &sameTarget); + pOptBoolsDsc->sameTarget = false; + Statement* s1; + s1 = optOptimizeBoolsChkBlkCond(pOptBoolsDsc); if (!s1) { return; @@ -8182,47 +8197,48 @@ void Compiler::optOptimizeBoolsReturnBlock(BasicBlock* b1, BasicBlock* b2, Basic // Find the branch conditions of b1 and b2 - bool bool1, bool2; - - GenTree* c1 = optIsBoolComp(t1, &t1, &bool1); + GenTree* c1 = optIsBoolComp(pOptBoolsDsc->t1, &pOptBoolsDsc->t1CompPtr, &pOptBoolsDsc->bool1); if (!c1) { return; } + pOptBoolsDsc->c1 = c1; - GenTree* c2 = optIsBoolComp(t2, &t2, &bool2); + GenTree* c2 = optIsBoolComp(pOptBoolsDsc->t2, &pOptBoolsDsc->t2CompPtr, &pOptBoolsDsc->bool2); if (!c2) { return; } + pOptBoolsDsc->c2 = c2; // Find the type and cost conditions of t1 and t2 - if (!optOptimizeBoolsChkTypeCostCond(t1, t2, c1, c2)) + if (!optOptimizeBoolsChkTypeCostCond(pOptBoolsDsc)) { return; } // Get the fold operator and the comparison operator - genTreeOps foldOp = GT_NONE; - genTreeOps cmpOp = GT_NONE; var_types foldType = c1->TypeGet(); if (varTypeIsGC(foldType)) { foldType = TYP_I_IMPL; } + pOptBoolsDsc->foldType = foldType; - ssize_t it3val = t3->AsOp()->gtOp1->AsIntCon()->gtIconVal; - optReturnGetFoldAndCompOper(t1, t2, it3val, bool1, bool2, &foldOp, &cmpOp); - if (foldOp == GT_NONE || cmpOp == GT_NONE) + pOptBoolsDsc->foldOp = GT_NONE; + pOptBoolsDsc->cmpOp = GT_NONE; + + optReturnGetFoldAndCompOper(pOptBoolsDsc); + if (pOptBoolsDsc->foldOp == GT_NONE || pOptBoolsDsc->cmpOp == GT_NONE) { return; } // Now update the trees - optOptimizeBoolsUpdateTrees(b1, b2, b3, t1, t2, foldOp, foldType, cmpOp, bool1, bool2, false); + optOptimizeBoolsUpdateTrees(pOptBoolsDsc); *change = true; @@ -8249,17 +8265,21 @@ void Compiler::optOptimizeBoolsReturnBlock(BasicBlock* b1, BasicBlock* b2, Basic // | \--* LCL_VAR int V01 arg1 // \--* CNS_INT int 0 // Arguments: -// tree1: The first tree -// tree2: The second tree -// it3val: The value of the third tree -// bool1: If tree1 is boolean comparison -// bool2: If tree2 is boolean comparison -// foldOp: On success, return the fold operator of GT_AND or GT_OR. -// cmpOp: On success, return the comparison operator of GT_EQ or GT_NE +// pOptBoolsDsc The descriptor for boolean optimization with below members set when called: +// tree1: The first tree +// tree2: The second tree +// it3val: The value of the third tree +// bool1: If tree1 is boolean comparison +// bool2: If tree2 is boolean comparison +// On success, below values are set: +// foldOp: return the fold operator of GT_AND or GT_OR. +// cmpOp: return the comparison operator of GT_EQ or GT_NE // -void Compiler::optReturnGetFoldAndCompOper( - GenTree* tree1, GenTree* tree2, ssize_t it3val, bool bool1, bool bool2, genTreeOps* foldOp, genTreeOps* cmpOp) +void Compiler::optReturnGetFoldAndCompOper(OptBoolsDsc* pOptBoolsDsc) { + GenTree* tree1 = pOptBoolsDsc->t1CompPtr; + GenTree* tree2 = pOptBoolsDsc->t2CompPtr; + genTreeOps foldOper; genTreeOps cmpOper; genTreeOps t1Oper = tree1->gtOper; @@ -8267,6 +8287,7 @@ void Compiler::optReturnGetFoldAndCompOper( ssize_t it1val = tree1->AsOp()->gtOp2->AsIntCon()->gtIconVal; ssize_t it2val = tree2->AsOp()->gtOp2->AsIntCon()->gtIconVal; + ssize_t it3val = pOptBoolsDsc->t3->AsOp()->gtOp1->AsIntCon()->gtIconVal; if ((t1Oper == GT_NE && t2Oper == GT_EQ) && (it1val == 0 && it2val == 0 && it3val == 0)) { @@ -8306,7 +8327,7 @@ void Compiler::optReturnGetFoldAndCompOper( return; } - if ((foldOper == GT_AND || cmpOper == GT_NE) && (!bool1 || !bool2)) + if ((foldOper == GT_AND || cmpOper == GT_NE) && (!pOptBoolsDsc->bool1 || !pOptBoolsDsc->bool2)) { // x == 1 && y == 1: Skip cases where x or y is greather 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 @@ -8314,8 +8335,8 @@ void Compiler::optReturnGetFoldAndCompOper( return; } - *foldOp = foldOper; - *cmpOp = cmpOper; + pOptBoolsDsc->foldOp = foldOper; + pOptBoolsDsc->cmpOp = cmpOper; } // @@ -8518,11 +8539,17 @@ void Compiler::optOptimizeBools() } // The next block also needs to be a condition + OptBoolsDsc optBoolsDsc; + + optBoolsDsc.b1 = b1; + optBoolsDsc.b2 = b2; if (b2->bbJumpKind == BBJ_COND) { // When it is conditional jumps - optOptimizeBoolsCondBlock(b1, b2, &change); + + optBoolsDsc.b3 = nullptr; + optOptimizeBoolsCondBlock(&optBoolsDsc, &change); } else if (b2->bbJumpKind == BBJ_RETURN) { @@ -8541,7 +8568,8 @@ void Compiler::optOptimizeBools() continue; } - optOptimizeBoolsReturnBlock(b1, b2, b3, &change); + optBoolsDsc.b3 = b3; + optOptimizeBoolsReturnBlock(&optBoolsDsc, &change); } else { From 29dc7198b451db12a44eaf0808eef0b94d4f842b Mon Sep 17 00:00:00 2001 From: Julie Lee Date: Sat, 24 Apr 2021 05:25:41 +0900 Subject: [PATCH 16/25] format patch --- src/coreclr/jit/compiler.h | 44 +++++++++++++++++------------------ src/coreclr/jit/optimizer.cpp | 25 ++++++++++---------- 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index a5e69008c52ea..824d0691fcf8b 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -6317,36 +6317,36 @@ class Compiler private: struct OptBoolsDsc { - BasicBlock* b1; // The first Basic Block with the BBJ_COND conditional jump type - BasicBlock* b2; // The next basic block of b1 - BasicBlock* b3; // The next basic block of b2 + BasicBlock* b1; // The first Basic Block with the BBJ_COND conditional jump type + BasicBlock* b2; // The next basic block of b1 + BasicBlock* b3; // The next basic block of b2 - GenTree* t1; // The root node of b1 - GenTree* t2; // The root node of b2 - GenTree* t3; // The root node of b3 + GenTree* t1; // The root node of b1 + GenTree* t2; // The root node of b2 + GenTree* t3; // The root node of b3 - GenTree* t1CompPtr; // The compare node (i.e. GT_EQ or GT_NE node) of t1 - GenTree* t2CompPtr; // The compare node (i.e. GT_EQ or GT_NE node) of t2 + GenTree* t1CompPtr; // The compare node (i.e. GT_EQ or GT_NE node) of t1 + GenTree* t2CompPtr; // The compare node (i.e. GT_EQ or GT_NE node) of t2 - GenTree* c1; // The first operand of t1CompPtr - GenTree* c2; // The first operand of t2CompPtr + GenTree* c1; // The first operand of t1CompPtr + GenTree* c2; // The first operand of t2CompPtr - bool sameTarget; // if b1 and b2 jumps to the same destination + bool sameTarget; // if b1 and b2 jumps to the same destination - genTreeOps foldOp; // The fold operator (e.g., GT_AND or GT_OR) - var_types foldType; // The type of the folded tree - genTreeOps cmpOp; // The comparison operator (e.g., GT_EQ or GT_NE) - bool bool1; // If tree t1CompPtr is boolean comparison - bool bool2; // If tree t2CompPtr is boolean comparison + genTreeOps foldOp; // The fold operator (e.g., GT_AND or GT_OR) + var_types foldType; // The type of the folded tree + genTreeOps cmpOp; // The comparison operator (e.g., GT_EQ or GT_NE) + bool bool1; // If tree t1CompPtr is boolean comparison + bool bool2; // If tree t2CompPtr is boolean comparison }; - void optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc, bool* change); - void optOptimizeBoolsReturnBlock(OptBoolsDsc* pOptBoolsDsc, bool* change); + void optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc, bool* change); + void optOptimizeBoolsReturnBlock(OptBoolsDsc* pOptBoolsDsc, bool* change); Statement* optOptimizeBoolsChkBlkCond(OptBoolsDsc* pOptBoolsDsc); - GenTree* optIsBoolComp(GenTree* tree, GenTree** compPtr, bool* boolPtr); - bool optOptimizeBoolsChkTypeCostCond(OptBoolsDsc* pOptBoolsDsc); - void optOptimizeBoolsUpdateTrees(OptBoolsDsc* pOptBoolsDsc); - void optReturnGetFoldAndCompOper(OptBoolsDsc* pOptBoolsDsc); + GenTree* optIsBoolComp(GenTree* tree, GenTree** compPtr, bool* boolPtr); + bool optOptimizeBoolsChkTypeCostCond(OptBoolsDsc* pOptBoolsDsc); + void optOptimizeBoolsUpdateTrees(OptBoolsDsc* pOptBoolsDsc); + void optReturnGetFoldAndCompOper(OptBoolsDsc* pOptBoolsDsc); #ifdef DEBUG void optOptimizeBoolsGcStress(BasicBlock* condBlock); #endif diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index 2716a9083df2e..15007bebc509d 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -7644,8 +7644,8 @@ void Compiler::optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc, bool* change { // Check if b1 and b2 jump to the same target and get back pointers to t1 and t2 tree nodes - pOptBoolsDsc->t3 = nullptr; - pOptBoolsDsc->sameTarget = false; // Do b1 and b2 have the same bbJumpDest? + pOptBoolsDsc->t3 = nullptr; + pOptBoolsDsc->sameTarget = false; // Do b1 and b2 have the same bbJumpDest? Statement* s1; s1 = optOptimizeBoolsChkBlkCond(pOptBoolsDsc); @@ -7787,10 +7787,10 @@ void Compiler::optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc, bool* change // Statement* Compiler::optOptimizeBoolsChkBlkCond(OptBoolsDsc* pOptBoolsDsc) { - BasicBlock* b1 = pOptBoolsDsc->b1; - BasicBlock* b2 = pOptBoolsDsc->b2; - BasicBlock* b3 = pOptBoolsDsc->b3; - bool* sameTarget = &pOptBoolsDsc->sameTarget; + BasicBlock* b1 = pOptBoolsDsc->b1; + BasicBlock* b2 = pOptBoolsDsc->b2; + BasicBlock* b3 = pOptBoolsDsc->b3; + bool* sameTarget = &pOptBoolsDsc->sameTarget; bool optReturnBlock = false; if (b3) @@ -7924,12 +7924,13 @@ Statement* Compiler::optOptimizeBoolsChkBlkCond(OptBoolsDsc* pOptBoolsDsc) // if cost to fold is not too expensive. // // Arguments: -// pOptBoolsDsc The descriptor for boolean optimization with pointers to tree compare nodes and its first operand, -// where, +// pOptBoolsDsc The descriptor for boolean optimization with pointers to tree compare nodes +// and its first operand, where // t1CompPtr: The first tree pointing to GT_NE or GT_EQ GenTree node // t2CompPtr: The second tree pointing to GT_NE or GT_EQ GenTree node // c1: The first operand of t1CompPtr // c2: The first operand of t2CompPtr + // Return: // True if it meets type conditions and cost conditions. Else false. // @@ -8033,8 +8034,8 @@ void Compiler::optOptimizeBoolsUpdateTrees(OptBoolsDsc* pOptBoolsDsc) { // Update tree when b1: BBJ_COND and b2: GT_RETURN (BBJ_RETURN) t1CompPtr->AsOp()->gtOp2->AsIntCon()->gtIconVal = 0; - b1->lastStmt()->GetRootNode()->gtOper = GT_RETURN; - b1->lastStmt()->GetRootNode()->gtType = b2->lastStmt()->GetRootNode()->gtType; + pOptBoolsDsc->t1->gtOper = GT_RETURN; + pOptBoolsDsc->t1->gtType = pOptBoolsDsc->t2->gtType; } #if FEATURE_SET_FLAGS @@ -8220,7 +8221,7 @@ void Compiler::optOptimizeBoolsReturnBlock(OptBoolsDsc* pOptBoolsDsc, bool* chan // Get the fold operator and the comparison operator - var_types foldType = c1->TypeGet(); + var_types foldType = c1->TypeGet(); if (varTypeIsGC(foldType)) { foldType = TYP_I_IMPL; @@ -8228,7 +8229,7 @@ void Compiler::optOptimizeBoolsReturnBlock(OptBoolsDsc* pOptBoolsDsc, bool* chan pOptBoolsDsc->foldType = foldType; pOptBoolsDsc->foldOp = GT_NONE; - pOptBoolsDsc->cmpOp = GT_NONE; + pOptBoolsDsc->cmpOp = GT_NONE; optReturnGetFoldAndCompOper(pOptBoolsDsc); if (pOptBoolsDsc->foldOp == GT_NONE || pOptBoolsDsc->cmpOp == GT_NONE) From d02d18e9c07e091ea2f9e2ecb216aea7b05ea738 Mon Sep 17 00:00:00 2001 From: Julie Lee Date: Sat, 19 Jun 2021 14:37:51 -0700 Subject: [PATCH 17/25] Changed to substructure OptTestInfo within OptBoolsDisc --- src/coreclr/jit/compiler.h | 33 +- src/coreclr/jit/optimizer.cpp | 448 ++++++++++-------- .../opt/OptimizeBools/optboolsreturn.csproj | 2 +- 3 files changed, 264 insertions(+), 219 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 824d0691fcf8b..413d8d54f3e32 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -79,6 +79,8 @@ class FgStack; // defined in fgbasic.cpp class Instrumentor; // defined in fgprofile.cpp class SpanningTreeVisitor; // defined in fgprofile.cpp class CSE_DataFlow; // defined in OptCSE.cpp +struct OptBoolsDsc; // defined in optimizer.cpp +struct OptTestInfo; #ifdef DEBUG struct IndentStack; #endif @@ -6315,35 +6317,10 @@ class Compiler void optOptimizeBools(); private: - struct OptBoolsDsc - { - BasicBlock* b1; // The first Basic Block with the BBJ_COND conditional jump type - BasicBlock* b2; // The next basic block of b1 - BasicBlock* b3; // The next basic block of b2 - - GenTree* t1; // The root node of b1 - GenTree* t2; // The root node of b2 - GenTree* t3; // The root node of b3 - - GenTree* t1CompPtr; // The compare node (i.e. GT_EQ or GT_NE node) of t1 - GenTree* t2CompPtr; // The compare node (i.e. GT_EQ or GT_NE node) of t2 - - GenTree* c1; // The first operand of t1CompPtr - GenTree* c2; // The first operand of t2CompPtr - - bool sameTarget; // if b1 and b2 jumps to the same destination - - genTreeOps foldOp; // The fold operator (e.g., GT_AND or GT_OR) - var_types foldType; // The type of the folded tree - genTreeOps cmpOp; // The comparison operator (e.g., GT_EQ or GT_NE) - bool bool1; // If tree t1CompPtr is boolean comparison - bool bool2; // If tree t2CompPtr is boolean comparison - }; - - void optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc, bool* change); - void optOptimizeBoolsReturnBlock(OptBoolsDsc* pOptBoolsDsc, bool* change); + bool optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc); + bool optOptimizeBoolsReturnBlock(OptBoolsDsc* pOptBoolsDsc); Statement* optOptimizeBoolsChkBlkCond(OptBoolsDsc* pOptBoolsDsc); - GenTree* optIsBoolComp(GenTree* tree, GenTree** compPtr, bool* boolPtr); + GenTree* optIsBoolComp(OptTestInfo* optTest); bool optOptimizeBoolsChkTypeCostCond(OptBoolsDsc* pOptBoolsDsc); void optOptimizeBoolsUpdateTrees(OptBoolsDsc* pOptBoolsDsc); void optReturnGetFoldAndCompOper(OptBoolsDsc* pOptBoolsDsc); diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index 15007bebc509d..119c968d37b49 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -7608,65 +7608,99 @@ bool Compiler::optIsRangeCheckRemovable(GenTree* tree) return true; } +struct OptTestInfo +{ + GenTree* testTree; // The root node of Basic Block + GenTree* compTree; // The compare node (i.e. GT_EQ or GT_NE node) of the testTree + bool isBool; // If the compTree is boolean comparison +}; + +struct OptBoolsDsc +{ + BasicBlock* b1; // The first Basic Block with the BBJ_COND conditional jump type + BasicBlock* b2; // The next basic block of b1. Either BBJ_COND or BBJ_RETURN type + BasicBlock* b3; // The next basic block of b2. Null if b2 is not a return block + + GenTree* t1; // The root node of the last statement of b1 + GenTree* t2; // The root node of the first statement of b2 + GenTree* t3; // The root node of the first statement of b3 + + bool sameTarget; // if b1 and b2 jumps to the same destination + + genTreeOps foldOp; // The fold operator (e.g., GT_AND or GT_OR) + var_types foldType; // The type of the folded tree + genTreeOps cmpOp; // The comparison operator (e.g., GT_EQ or GT_NE) + + OptTestInfo test1; // The first test info + OptTestInfo test2; // The second test info + + GenTree* c1; // The first operand of test1 compTree + GenTree* c2; // The first operand of test2 compTree +}; + // -// optOptimizeBoolsCondBlock: Optimize boolean when bbJumpKind of both b1 and b2 are BBJ_COND (conditional jump). -// For example, (x==0 && y==0 && z==0) generates -// b1: GT_JTRUE (BBJ_COND) -// b2: GT_JTRUE (BBJ_COND) -// b3: GT_RETURN (BBJ_RETURN), -// and it is folded into -// b1: GT_JTRUE (BBJ_COND) -// b3: GT_RETURN (BBJ_RETURN) -// -// - if b1.bbJumpDest == b2.bbJumpDest, it transforms -// b1 : brtrue(t1, b3) -// b2 : brtrue(t2, bx) -// b3 : -// to -// b1 : brtrue((!t1) && t2, bx) -// b3 : -// -// - if b1.bbJumpDest == b2->bbNext, it transforms -// b1 : brtrue(t1, b3) -// b2 : brtrue(t2, bx) -// b3 : -// to -// b1 : brtrue((!t1) && t2, bx) -// b3 : +// optOptimizeBoolsCondBlock: Optimize boolean when bbJumpKind of both b1 and b2 are BBJ_COND // // Arguments: -// pOptBoolsDsc The descriptor with Basic Block b1 and b2 set, where -// b1 is the first Basic Block with the BBJ_COND conditional jump type, and -// b2 is the next basic block of b1 with the BBJ_COND conditional jump type. -// change Set to true if boolean optimization is done and b1 and b2 are folded into b1. +// pOptBoolsDsc The descriptor with Basic Block b1 and b2 set when called +// +// Returns: +// changeCond Set to true if boolean optimization is done and b1 and b2 are folded into b1. +// +// Notes: +// For example, (x==0 && y==0 && z==0) generates +// b1: GT_JTRUE (BBJ_COND) +// b2: GT_JTRUE (BBJ_COND) +// b3: GT_RETURN (BBJ_RETURN), +// and it is folded into +// b1: GT_JTRUE (BBJ_COND) +// b3: GT_RETURN (BBJ_RETURN) +// +// if b1.bbJumpDest == b2.bbJumpDest, it transforms +// b1 : brtrue(t1, b3) +// b2 : brtrue(t2, bx) +// b3 : +// to +// b1 : brtrue((!t1) && t2, bx) +// b3 : // -void Compiler::optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc, bool* change) +// if b1.bbJumpDest == b2->bbNext, it transforms +// b1 : brtrue(t1, b3) +// b2 : brtrue(t2, bx) +// b3 : +// to +// b1 : brtrue((!t1) && t2, bx) +// b3 : +// +bool Compiler::optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc) { + bool changeCond = false; + // Check if b1 and b2 jump to the same target and get back pointers to t1 and t2 tree nodes pOptBoolsDsc->t3 = nullptr; pOptBoolsDsc->sameTarget = false; // Do b1 and b2 have the same bbJumpDest? - Statement* s1; - s1 = optOptimizeBoolsChkBlkCond(pOptBoolsDsc); - if (!s1) + Statement* const s1 = optOptimizeBoolsChkBlkCond(pOptBoolsDsc); + if (s1 == nullptr) { - return; + return changeCond; } // Find the branch conditions of b1 and b2 - - GenTree* c1 = optIsBoolComp(pOptBoolsDsc->t1, &pOptBoolsDsc->t1CompPtr, &pOptBoolsDsc->bool1); - if (!c1) + pOptBoolsDsc->test1.testTree = pOptBoolsDsc->t1; + GenTree* const c1 = optIsBoolComp(&pOptBoolsDsc->test1); + if (c1 == nullptr) { - return; + return changeCond; } pOptBoolsDsc->c1 = c1; - GenTree* c2 = optIsBoolComp(pOptBoolsDsc->t2, &pOptBoolsDsc->t2CompPtr, &pOptBoolsDsc->bool2); - if (!c2) + pOptBoolsDsc->test2.testTree = pOptBoolsDsc->t2; + GenTree* const c2 = optIsBoolComp(&pOptBoolsDsc->test2); + if (c2 == nullptr) { - return; + return changeCond; } pOptBoolsDsc->c2 = c2; @@ -7674,7 +7708,7 @@ void Compiler::optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc, bool* change if (!optOptimizeBoolsChkTypeCostCond(pOptBoolsDsc)) { - return; + return changeCond; } // Get the fold operator and the comparison operator @@ -7691,12 +7725,12 @@ void Compiler::optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc, bool* change { // Both conditions must be the same - if (pOptBoolsDsc->t1CompPtr->gtOper != pOptBoolsDsc->t2CompPtr->gtOper) + if (pOptBoolsDsc->test1.compTree->gtOper != pOptBoolsDsc->test2.compTree->gtOper) { - return; + return changeCond; } - if (pOptBoolsDsc->t1CompPtr->gtOper == GT_EQ) + if (pOptBoolsDsc->test1.compTree->gtOper == 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 @@ -7717,12 +7751,12 @@ void Compiler::optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc, bool* change { // The b1 condition must be the reverse of the b2 condition - if (pOptBoolsDsc->t1CompPtr->gtOper == pOptBoolsDsc->t2CompPtr->gtOper) + if (pOptBoolsDsc->test1.compTree->gtOper == pOptBoolsDsc->test2.compTree->gtOper) { - return; + return changeCond; } - if (pOptBoolsDsc->t1CompPtr->gtOper == GT_EQ) + if (pOptBoolsDsc->test1.compTree->gtOper == GT_EQ) { // 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 @@ -7742,9 +7776,9 @@ void Compiler::optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc, bool* change // Anding requires both values to be 0 or 1 - if ((foldOp == GT_AND) && (!pOptBoolsDsc->bool1 || !pOptBoolsDsc->bool2)) + if ((foldOp == GT_AND) && (!pOptBoolsDsc->test1.isBool || !pOptBoolsDsc->test2.isBool)) { - return; + return changeCond; } // @@ -7758,7 +7792,7 @@ void Compiler::optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc, bool* change optOptimizeBoolsUpdateTrees(pOptBoolsDsc); // Update the change to true to continue the bool optimization for the rest of the BB chain - *change = true; + changeCond = true; #ifdef DEBUG if (verbose) @@ -7769,28 +7803,33 @@ void Compiler::optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc, bool* change printf("\n"); } #endif + + return changeCond; } // -// optOptimizeBoolsChkBlkCond: Checks block conditions if it can be boolean optimized: -// - if the second or third block contains only one statement -// - if tree operators are of the right type, e.g, GT_JTRUE, GT_RETURN +// optOptimizeBoolsChkBlkCond: Checks block conditions if it can be boolean optimized // // Arguments: -// pOptBoolsDsc The descriptor with Basic Block b1, b2 and b3 info set when the method is called, -// where b1 is the first block, b2 is the second block and b3 is the third block. -// It if passes all the conditions, t1, t2 and t3 are set to the root nodes of b1, b2 and b3. -// SameTarget is also updated to true if b1 and b2 jump to the same destination. +// pOptBoolsDsc The descriptor with Basic Block b1, b2 and b3 set when the method is called // // Return: // s1 If all conditions pass, returns the last statement of b1. // +// Notes: +// This method checks if the second or third block contains only one statement, and +// checks if tree operators are of the right type, e.g, GT_JTRUE, GT_RETURN. +// +// When called, b1, b2 and b3 of pOptBoolsDsc are set, where +// b1 is the first block, b2 is the second block and b3 is the third block. +// If it passes all the conditions, t1, t2 and t3 are set to the root nodes of b1, b2 and b3 each. +// SameTarget is also updated to true if b1 and b2 jump to the same destination. +// Statement* Compiler::optOptimizeBoolsChkBlkCond(OptBoolsDsc* pOptBoolsDsc) { BasicBlock* b1 = pOptBoolsDsc->b1; BasicBlock* b2 = pOptBoolsDsc->b2; BasicBlock* b3 = pOptBoolsDsc->b3; - bool* sameTarget = &pOptBoolsDsc->sameTarget; bool optReturnBlock = false; if (b3) @@ -7812,9 +7851,9 @@ Statement* Compiler::optOptimizeBoolsChkBlkCond(OptBoolsDsc* pOptBoolsDsc) // B1: brtrue(t1|t2, BX) // B3: - *sameTarget = true; + pOptBoolsDsc->sameTarget = true; } - else if (b1->bbJumpDest == b2->bbNext) /*b1->bbJumpDest->bbNum == n1+2*/ + else if (b1->bbJumpDest == b2->bbNext) { // Given the following sequence of blocks : // B1: brtrue(t1, B3) @@ -7824,7 +7863,7 @@ Statement* Compiler::optOptimizeBoolsChkBlkCond(OptBoolsDsc* pOptBoolsDsc) // B1: brtrue((!t1)&&t2, BX) // B3: - *sameTarget = false; + pOptBoolsDsc->sameTarget = false; } else { @@ -7842,7 +7881,6 @@ Statement* Compiler::optOptimizeBoolsChkBlkCond(OptBoolsDsc* pOptBoolsDsc) // B1: return(t1|t2) if (b1->bbJumpDest != b2->bbNext) { - // b1->bbJumpDest->bbNum == n1 + 2 return nullptr; } } @@ -7921,28 +7959,24 @@ Statement* Compiler::optOptimizeBoolsChkBlkCond(OptBoolsDsc* pOptBoolsDsc) // // optOptimizeBoolsChkTypeCostCond: Checks if type conditions meet the folding condition, and -// if cost to fold is not too expensive. +// if cost to fold is not too expensive // // Arguments: -// pOptBoolsDsc The descriptor for boolean optimization with pointers to tree compare nodes -// and its first operand, where -// t1CompPtr: The first tree pointing to GT_NE or GT_EQ GenTree node -// t2CompPtr: The second tree pointing to GT_NE or GT_EQ GenTree node -// c1: The first operand of t1CompPtr -// c2: The first operand of t2CompPtr - +// pOptBoolsDsc The descriptor for boolean optimization with test compare trees +// and their first operand c1 and c2 +// // Return: // True if it meets type conditions and cost conditions. Else false. // bool Compiler::optOptimizeBoolsChkTypeCostCond(OptBoolsDsc* pOptBoolsDsc) { - GenTree* t1CompPtr = pOptBoolsDsc->t1CompPtr; - GenTree* t2CompPtr = pOptBoolsDsc->t2CompPtr; - GenTree* c1 = pOptBoolsDsc->c1; - GenTree* c2 = pOptBoolsDsc->c2; + GenTree* t1Comp = pOptBoolsDsc->test1.compTree; + GenTree* t2Comp = pOptBoolsDsc->test2.compTree; + GenTree* c1 = pOptBoolsDsc->c1; + GenTree* c2 = pOptBoolsDsc->c2; - noway_assert(t1CompPtr->OperIs(GT_EQ, GT_NE) && t1CompPtr->AsOp()->gtOp1 == c1); - noway_assert(t2CompPtr->OperIs(GT_EQ, GT_NE) && t2CompPtr->AsOp()->gtOp1 == c2); + noway_assert(t1Comp->OperIs(GT_EQ, GT_NE) && t1Comp->AsOp()->gtOp1 == c1); + noway_assert(t2Comp->OperIs(GT_EQ, GT_NE) && t2Comp->AsOp()->gtOp1 == c2); // // Leave out floats where the bit-representation is more complicated @@ -7958,7 +7992,7 @@ bool Compiler::optOptimizeBoolsChkTypeCostCond(OptBoolsDsc* pOptBoolsDsc) { return false; } - if (genTypeSize(t1CompPtr->TypeGet()) != genTypeSize(t2CompPtr->TypeGet())) + if (genTypeSize(t1Comp->TypeGet()) != genTypeSize(t2Comp->TypeGet())) { return false; } @@ -7988,21 +8022,21 @@ bool Compiler::optOptimizeBoolsChkTypeCostCond(OptBoolsDsc* pOptBoolsDsc) // // optOptimizeBoolsUpdateTrees: Fold the trees based on fold type and comparison type, -// update the edges, unlink removed blocks and update loop table. +// update the edges, unlink removed blocks and update loop table // // Arguments: -// pOptBoolsDsc The descriptor for boolean optimization with below members set when called: -// b1: The first Basic Block -// b2: The second Basic Block -// b3: The thrid Basic Block. null if b1: BBJ_COND and b2: BBJ_RETURN -// t1CompPtr: The compare tree of b1 -// t2CompPtr: The compare tree of b2 -// foldOp: The fold operator of GT_AND or GT_OR -// foldType: The type of the folded tree -// cmpOp: The comparison operator of GT_EQ or GT_NE -// bool1: If t1CompPtr is boolean comparison -// bool2: If t2CompPtr is boolean comparison -// sameTarget: If b1 and b2 has the same jump target +// pOptBoolsDsc The descriptor for boolean optimization +// +// Notes: +// The pOptBoolsDsc has below key members: +// b1: The first Basic Block +// b2: The second Basic Block +// b3: The thrid Basic Block. null if both b1 and b2 are BBJ_COND +// test1: The test info for the first test tree +// test2: The second info for the second test tree +// foldOp: The fold operator of GT_AND or GT_OR +// foldType: The type of the folded tree +// cmpOp: The comparison operator of GT_EQ or GT_NE // void Compiler::optOptimizeBoolsUpdateTrees(OptBoolsDsc* pOptBoolsDsc) { @@ -8020,22 +8054,22 @@ void Compiler::optOptimizeBoolsUpdateTrees(OptBoolsDsc* pOptBoolsDsc) GenTree* c2 = pOptBoolsDsc->c2; GenTree* cmpOp1 = gtNewOperNode(pOptBoolsDsc->foldOp, pOptBoolsDsc->foldType, c1, c2); - if (pOptBoolsDsc->bool1 && pOptBoolsDsc->bool2) + if (pOptBoolsDsc->test1.isBool && pOptBoolsDsc->test2.isBool) { // When we 'OR'/'AND' two booleans, the result is boolean as well cmpOp1->gtFlags |= GTF_BOOLEAN; } - GenTree* t1CompPtr = pOptBoolsDsc->t1CompPtr; - t1CompPtr->SetOper(pOptBoolsDsc->cmpOp); - t1CompPtr->AsOp()->gtOp1 = cmpOp1; - t1CompPtr->AsOp()->gtOp2->gtType = pOptBoolsDsc->foldType; // Could have been varTypeIsGC() + GenTree* t1Comp = pOptBoolsDsc->test1.compTree; + t1Comp->SetOper(pOptBoolsDsc->cmpOp); + t1Comp->AsOp()->gtOp1 = cmpOp1; + t1Comp->AsOp()->gtOp2->gtType = pOptBoolsDsc->foldType; // Could have been varTypeIsGC() if (optReturnBlock) { // Update tree when b1: BBJ_COND and b2: GT_RETURN (BBJ_RETURN) - t1CompPtr->AsOp()->gtOp2->AsIntCon()->gtIconVal = 0; - pOptBoolsDsc->t1->gtOper = GT_RETURN; - pOptBoolsDsc->t1->gtType = pOptBoolsDsc->t2->gtType; + t1Comp->AsOp()->gtOp2->AsIntCon()->gtIconVal = 0; + pOptBoolsDsc->t1->gtOper = GT_RETURN; + pOptBoolsDsc->t1->gtType = pOptBoolsDsc->t2->gtType; } #if FEATURE_SET_FLAGS @@ -8160,55 +8194,63 @@ void Compiler::optOptimizeBoolsUpdateTrees(OptBoolsDsc* pOptBoolsDsc) } // -// optOptimizeBoolsReturnBlock: Optimize boolean when b1.bbJumpKind is BBJ_COND and b2.bbJumpKind is BBJ_RETURN. -// For example, (x==0 && y==0) generates -// b1: GT_JTRUE (BBJ_COND) -// b2: GT_RETURN (BBJ_RETURN) -// b3: GT_RETURN (BBJ_RETURN), -// and it is folded into -// b1: GT_RETURN (BBJ_RETURN) -// -// - if b1.bbJumpDest == b2.bbNext, it transforms -// b1 : brtrue(t1, b3) -// b2 : ret(t2) -// b3 : ret(0) -// to -// b1 : ret((!t1) && t2) +// optOptimizeBoolsReturnBlock: Optimize boolean when b1.bbJumpKind is BBJ_COND and b2.bbJumpKind is BBJ_RETURN // // Arguments: -// pOptBoolsDsc The descriptor for boolean optimization with below members set when called: -// b1 The first Basic Block with the BBJ_COND conditional jump type. -// b2 The next basic block of b1 with the BBJ_RETURN jump type. -// b3 The next basic block of b2 with GT_RETURN. -// change Set to true if boolean optimization is done and b1, b2 and b3 are folded into b1. +// pOptBoolsDsc The descriptor for boolean optimization with b1, b2 and b3 members set when called +// +// Returns: +// changeReturn Set to true if boolean optimization is done and b1, b2 and b3 are folded into b1. // -void Compiler::optOptimizeBoolsReturnBlock(OptBoolsDsc* pOptBoolsDsc, bool* change) +// Notes: +// Below members of pOptBoolsDsc are set when called: +// b1 The first Basic Block with the BBJ_COND conditional jump type +// b2 The next basic block of b1 with the BBJ_RETURN +// b3 The next basic block of b2 with GT_RETURN +// +// For example, (x==0 && y==0) generates: +// b1: GT_JTRUE (BBJ_COND) +// b2: GT_RETURN (BBJ_RETURN) +// b3: GT_RETURN (BBJ_RETURN), +// and it is folded into +// b1: GT_RETURN (BBJ_RETURN) +// +// if b1.bbJumpDest == b2.bbNext, it transforms +// b1 : brtrue(t1, b3) +// b2 : ret(t2) +// b3 : ret(0) +// to +// b1 : ret((!t1) && t2) +// +bool Compiler::optOptimizeBoolsReturnBlock(OptBoolsDsc* pOptBoolsDsc) { + bool changeReturn = false; + BasicBlock* b1 = pOptBoolsDsc->b1; BasicBlock* b2 = pOptBoolsDsc->b2; BasicBlock* b3 = pOptBoolsDsc->b3; pOptBoolsDsc->sameTarget = false; - Statement* s1; - s1 = optOptimizeBoolsChkBlkCond(pOptBoolsDsc); - if (!s1) + Statement* const s1 = optOptimizeBoolsChkBlkCond(pOptBoolsDsc); + if (s1 == nullptr) { - return; + return changeReturn; } // Find the branch conditions of b1 and b2 - - GenTree* c1 = optIsBoolComp(pOptBoolsDsc->t1, &pOptBoolsDsc->t1CompPtr, &pOptBoolsDsc->bool1); - if (!c1) + pOptBoolsDsc->test1.testTree = pOptBoolsDsc->t1; + GenTree* const c1 = optIsBoolComp(&pOptBoolsDsc->test1); + if (c1 == nullptr) { - return; + return changeReturn; } pOptBoolsDsc->c1 = c1; - GenTree* c2 = optIsBoolComp(pOptBoolsDsc->t2, &pOptBoolsDsc->t2CompPtr, &pOptBoolsDsc->bool2); - if (!c2) + pOptBoolsDsc->test2.testTree = pOptBoolsDsc->t2; + GenTree* const c2 = optIsBoolComp(&pOptBoolsDsc->test2); + if (c2 == nullptr) { - return; + return changeReturn; } pOptBoolsDsc->c2 = c2; @@ -8216,7 +8258,7 @@ void Compiler::optOptimizeBoolsReturnBlock(OptBoolsDsc* pOptBoolsDsc, bool* chan if (!optOptimizeBoolsChkTypeCostCond(pOptBoolsDsc)) { - return; + return changeReturn; } // Get the fold operator and the comparison operator @@ -8234,14 +8276,15 @@ void Compiler::optOptimizeBoolsReturnBlock(OptBoolsDsc* pOptBoolsDsc, bool* chan optReturnGetFoldAndCompOper(pOptBoolsDsc); if (pOptBoolsDsc->foldOp == GT_NONE || pOptBoolsDsc->cmpOp == GT_NONE) { - return; + return changeReturn; } // Now update the trees optOptimizeBoolsUpdateTrees(pOptBoolsDsc); - *change = true; + // Update the change to true to continue the bool optimization for the rest of the BB chain + changeReturn = true; #ifdef DEBUG if (verbose) @@ -8252,34 +8295,39 @@ void Compiler::optOptimizeBoolsReturnBlock(OptBoolsDsc* pOptBoolsDsc, bool* chan printf("\n"); } #endif + + return changeReturn; } // -// optReturnGetFoldAndCompOper: Based on tree1 and tree2 conditions, determine fold type and comparison type. -// - The fold operator (foldOp) of below tree is GT_OR, and -// comparison operator (cmpOp) is GT_EQ. +// optReturnGetFoldAndCompOper: Based on tree1 and tree2 conditions, determine fold type and comparison type +// +// Arguments: +// pOptBoolsDsc The descriptor for boolean optimization // +// Notes: +// For example, the fold operator (foldOp) of below tree is GT_OR, and +// comparison operator (cmpOp) is GT_EQ. // * RETURN int // \--* EQ int // +--* OR int // | +--* LCL_VAR int V00 arg0 // | \--* LCL_VAR int V01 arg1 // \--* CNS_INT int 0 -// Arguments: -// pOptBoolsDsc The descriptor for boolean optimization with below members set when called: -// tree1: The first tree -// tree2: The second tree -// it3val: The value of the third tree -// bool1: If tree1 is boolean comparison -// bool2: If tree2 is boolean comparison -// On success, below values are set: -// foldOp: return the fold operator of GT_AND or GT_OR. -// cmpOp: return the comparison operator of GT_EQ or GT_NE +// +// Below members of pOptBoolsDsc are set when called: +// tree1: The first test info +// tree2: The second test info +// it3val: The value of the third tree +// +// On success, below values are set: +// foldOp: the fold operator of GT_AND or GT_OR. +// cmpOp: the comparison operator of GT_EQ or GT_NE // void Compiler::optReturnGetFoldAndCompOper(OptBoolsDsc* pOptBoolsDsc) { - GenTree* tree1 = pOptBoolsDsc->t1CompPtr; - GenTree* tree2 = pOptBoolsDsc->t2CompPtr; + GenTree* tree1 = pOptBoolsDsc->test1.compTree; + GenTree* tree2 = pOptBoolsDsc->test2.compTree; genTreeOps foldOper; genTreeOps cmpOper; @@ -8328,7 +8376,7 @@ void Compiler::optReturnGetFoldAndCompOper(OptBoolsDsc* pOptBoolsDsc) return; } - if ((foldOper == GT_AND || cmpOper == GT_NE) && (!pOptBoolsDsc->bool1 || !pOptBoolsDsc->bool2)) + if ((foldOper == GT_AND || cmpOper == GT_NE) && (!pOptBoolsDsc->test1.isBool || !pOptBoolsDsc->test2.isBool)) { // x == 1 && y == 1: Skip cases where x or y is greather 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 @@ -8341,8 +8389,11 @@ void Compiler::optReturnGetFoldAndCompOper(OptBoolsDsc* pOptBoolsDsc) } // -// Replace x==null with (x|x)==0 if x is a GC-type. -// This will stress code-gen and the emitter to make sure they support such trees. +// optOptimizeBoolsGcStress: Replace x==null with (x|x)==0 if x is a GC-type. +// This will stress code-gen and the emitter to make sure they support such trees. +// +// Arguments: +// condBlock The conditional Basic Block // #ifdef DEBUG @@ -8358,15 +8409,17 @@ void Compiler::optOptimizeBoolsGcStress(BasicBlock* condBlock) noway_assert(cond->gtOper == GT_JTRUE); - bool isBool; - GenTree* relop; + OptTestInfo test; + test.testTree = cond; - GenTree* comparand = optIsBoolComp(cond, &relop, &isBool); + GenTree* comparand = optIsBoolComp(&test); if (comparand == nullptr || !varTypeIsGC(comparand->TypeGet())) { return; } + GenTree* relop = test.compTree; + bool isBool = test.isBool; if (comparand->gtFlags & (GTF_ASG | GTF_CALL | GTF_ORDER_SIDEEFF)) { @@ -8388,24 +8441,32 @@ void Compiler::optOptimizeBoolsGcStress(BasicBlock* condBlock) #endif // -// optIsBoolComp: Function used by folding of boolean conditionals. -// Given a GT_JTRUE or GT_RETURN node, checks that it is a boolean comparison -// of the form "if (boolVal ==/!= 0/1)".This is translated into -// a GT_EQ/GT_NE node with "op1" being a boolean lclVar and "op2" the const 0/1. +// optIsBoolComp: Function used by folding of boolean conditionals +// // Arguments: -// tree Tree node with GT_JTRUE or GT_RETURN type to check boolean condition on. -// compPtr Returns the compare node (i.e. GT_EQ or GT_NE node). -// boolPtr Returns whether the comparand is a boolean value (must be 0 or 1). -// When return boolPtr == 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. -// Return: On success, the comparand (ie. boolVal) is returned. Else NULL. -// -GenTree* Compiler::optIsBoolComp(GenTree* tree, GenTree** compPtr, bool* boolPtr) +// pOptTest The test info that the tree to test is set when called +// +// Return: +// opr1 On success, the first operand is returned. Else NULL. +// +// Notes: +// pOptTest->tree Tree node with GT_JTRUE or GT_RETURN type to check boolean condition on. +// pOptTest->compTree On success, the compare tree (i.e. GT_EQ or GT_NE node) is set +// isBool true if the comparand is boolean. Otherwise, false. +// +// Given a GT_JTRUE or GT_RETURN node, this method checks that 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. +// +// 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. +// +GenTree* Compiler::optIsBoolComp(OptTestInfo* pOptTest) { - bool isBool = false; + pOptTest->isBool = false; - noway_assert(tree->gtOper == GT_JTRUE || tree->gtOper == GT_RETURN); - GenTree* cond = tree->AsOp()->gtOp1; + noway_assert(pOptTest->testTree->gtOper == GT_JTRUE || pOptTest->testTree->gtOper == GT_RETURN); + GenTree* cond = pOptTest->testTree->AsOp()->gtOp1; // The condition must be "!= 0" or "== 0" @@ -8416,7 +8477,7 @@ GenTree* Compiler::optIsBoolComp(GenTree* tree, GenTree** compPtr, bool* boolPtr // Return the compare node to the caller - *compPtr = cond; + pOptTest->compTree = cond; // Get hold of the comparands @@ -8441,11 +8502,11 @@ GenTree* Compiler::optIsBoolComp(GenTree* tree, GenTree** compPtr, bool* boolPtr if (opr1->gtFlags & GTF_BOOLEAN) { - isBool = true; + pOptTest->isBool = true; } else if ((opr1->gtOper == GT_CNS_INT) && (opr1->IsIntegralConst(0) || opr1->IsIntegralConst(1))) { - isBool = true; + pOptTest->isBool = true; } else if (opr1->gtOper == GT_LCL_VAR) { @@ -8456,7 +8517,7 @@ GenTree* Compiler::optIsBoolComp(GenTree* tree, GenTree** compPtr, bool* boolPtr if (lvaTable[lclNum].lvIsBoolean) { - isBool = true; + pOptTest->isBool = true; } } @@ -8465,7 +8526,7 @@ GenTree* Compiler::optIsBoolComp(GenTree* tree, GenTree** compPtr, bool* boolPtr { // If this is a boolean expression tree we can reverse the relop // and change the true to false. - if (isBool) + if (pOptTest->isBool) { gtReverseCond(cond); opr2->AsIntCon()->gtIconVal = 0; @@ -8476,26 +8537,27 @@ GenTree* Compiler::optIsBoolComp(GenTree* tree, GenTree** compPtr, bool* boolPtr } } - *boolPtr = isBool; return opr1; } // -// optOptimizeBools: Folds boolean conditionals for GT_JTRUE/GR_RETURN nodes. -// 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 -// "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: -// (x == 0 && y ==0) => (c1 | c2) == 0 +// optOptimizeBools: Folds boolean conditionals for GT_JTRUE/GR_RETURN nodes // -// * RETURN int -// \--* EQ int -// +--* OR int -// | +--* LCL_VAR int V00 arg0 -// | \--* LCL_VAR int V01 arg1 -// \--* CNS_INT int 0 +// 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 +// "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: +// (x == 0 && y ==0) => (c1 | c2) == 0 +// +// * RETURN int +// \--* EQ int +// +--* OR int +// | +--* LCL_VAR int V00 arg0 +// | \--* LCL_VAR int V01 arg1 +// \--* CNS_INT int 0 // void Compiler::optOptimizeBools() { @@ -8550,7 +8612,10 @@ void Compiler::optOptimizeBools() // When it is conditional jumps optBoolsDsc.b3 = nullptr; - optOptimizeBoolsCondBlock(&optBoolsDsc, &change); + if (optOptimizeBoolsCondBlock(&optBoolsDsc)) + { + change = true; + } } else if (b2->bbJumpKind == BBJ_RETURN) { @@ -8570,7 +8635,10 @@ void Compiler::optOptimizeBools() } optBoolsDsc.b3 = b3; - optOptimizeBoolsReturnBlock(&optBoolsDsc, &change); + if (optOptimizeBoolsReturnBlock(&optBoolsDsc)) + { + change = true; + } } else { diff --git a/src/tests/JIT/opt/OptimizeBools/optboolsreturn.csproj b/src/tests/JIT/opt/OptimizeBools/optboolsreturn.csproj index fe4a2822d7cab..318a0eb9c3dc7 100644 --- a/src/tests/JIT/opt/OptimizeBools/optboolsreturn.csproj +++ b/src/tests/JIT/opt/OptimizeBools/optboolsreturn.csproj @@ -1,7 +1,7 @@ Exe - 1 + 0 PdbOnly From ebf201000d22df1144dff0b506a7d35999fe94c4 Mon Sep 17 00:00:00 2001 From: Julie Lee Date: Mon, 21 Jun 2021 14:52:47 -0700 Subject: [PATCH 18/25] Cleaned up tree variables in OptBoolsDsc struct --- src/coreclr/jit/optimizer.cpp | 106 +++++++++++++++++----------------- 1 file changed, 52 insertions(+), 54 deletions(-) diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index 119c968d37b49..89dc0f20dca84 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -7621,21 +7621,18 @@ struct OptBoolsDsc BasicBlock* b2; // The next basic block of b1. Either BBJ_COND or BBJ_RETURN type BasicBlock* b3; // The next basic block of b2. Null if b2 is not a return block - GenTree* t1; // The root node of the last statement of b1 - GenTree* t2; // The root node of the first statement of b2 - GenTree* t3; // The root node of the first statement of b3 + OptTestInfo t1; // The first test info + OptTestInfo t2; // The second test info + GenTree* t3; // The root node of the first statement of b3 + + GenTree* c1; // The first operand of t1.compTree + GenTree* c2; // The first operand of t2.compTree bool sameTarget; // if b1 and b2 jumps to the same destination genTreeOps foldOp; // The fold operator (e.g., GT_AND or GT_OR) var_types foldType; // The type of the folded tree genTreeOps cmpOp; // The comparison operator (e.g., GT_EQ or GT_NE) - - OptTestInfo test1; // The first test info - OptTestInfo test2; // The second test info - - GenTree* c1; // The first operand of test1 compTree - GenTree* c2; // The first operand of test2 compTree }; // @@ -7688,16 +7685,15 @@ bool Compiler::optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc) } // Find the branch conditions of b1 and b2 - pOptBoolsDsc->test1.testTree = pOptBoolsDsc->t1; - GenTree* const c1 = optIsBoolComp(&pOptBoolsDsc->test1); + + GenTree* const c1 = optIsBoolComp(&pOptBoolsDsc->t1); if (c1 == nullptr) { return changeCond; } pOptBoolsDsc->c1 = c1; - pOptBoolsDsc->test2.testTree = pOptBoolsDsc->t2; - GenTree* const c2 = optIsBoolComp(&pOptBoolsDsc->test2); + GenTree* const c2 = optIsBoolComp(&pOptBoolsDsc->t2); if (c2 == nullptr) { return changeCond; @@ -7721,16 +7717,18 @@ bool Compiler::optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc) foldType = TYP_I_IMPL; } + assert(pOptBoolsDsc->t1.compTree->gtOper == GT_EQ || pOptBoolsDsc->t1.compTree->gtOper == GT_NE); + if (pOptBoolsDsc->sameTarget) { // Both conditions must be the same - if (pOptBoolsDsc->test1.compTree->gtOper != pOptBoolsDsc->test2.compTree->gtOper) + if (pOptBoolsDsc->t1.compTree->gtOper != pOptBoolsDsc->t2.compTree->gtOper) { return changeCond; } - if (pOptBoolsDsc->test1.compTree->gtOper == GT_EQ) + if (pOptBoolsDsc->t1.compTree->gtOper == 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 @@ -7749,14 +7747,15 @@ bool Compiler::optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc) } else { - // The b1 condition must be the reverse of the b2 condition + // The b1 condition must be the reverse of the 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 (pOptBoolsDsc->test1.compTree->gtOper == pOptBoolsDsc->test2.compTree->gtOper) + if (pOptBoolsDsc->t1.compTree->gtOper == pOptBoolsDsc->t2.compTree->gtOper) { return changeCond; } - if (pOptBoolsDsc->test1.compTree->gtOper == GT_EQ) + if (pOptBoolsDsc->t1.compTree->gtOper == GT_EQ) { // 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 @@ -7776,7 +7775,7 @@ bool Compiler::optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc) // Anding requires both values to be 0 or 1 - if ((foldOp == GT_AND) && (!pOptBoolsDsc->test1.isBool || !pOptBoolsDsc->test2.isBool)) + if ((foldOp == GT_AND) && (!pOptBoolsDsc->t1.isBool || !pOptBoolsDsc->t2.isBool)) { return changeCond; } @@ -7820,16 +7819,16 @@ bool Compiler::optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc) // This method checks if the second or third block contains only one statement, and // checks if tree operators are of the right type, e.g, GT_JTRUE, GT_RETURN. // -// When called, b1, b2 and b3 of pOptBoolsDsc are set, where +// When called, b1, b2 and b3 of pOptBoolsDsc are set, where // b1 is the first block, b2 is the second block and b3 is the third block. // If it passes all the conditions, t1, t2 and t3 are set to the root nodes of b1, b2 and b3 each. // SameTarget is also updated to true if b1 and b2 jump to the same destination. // Statement* Compiler::optOptimizeBoolsChkBlkCond(OptBoolsDsc* pOptBoolsDsc) { - BasicBlock* b1 = pOptBoolsDsc->b1; - BasicBlock* b2 = pOptBoolsDsc->b2; - BasicBlock* b3 = pOptBoolsDsc->b3; + BasicBlock* b1 = pOptBoolsDsc->b1; + BasicBlock* b2 = pOptBoolsDsc->b2; + BasicBlock* b3 = pOptBoolsDsc->b3; bool optReturnBlock = false; if (b3) @@ -7896,8 +7895,8 @@ Statement* Compiler::optOptimizeBoolsChkBlkCond(OptBoolsDsc* pOptBoolsDsc) Statement* s1 = b1->lastStmt(); - GenTree* t1 = s1->GetRootNode(); - noway_assert(t1->gtOper == GT_JTRUE); + GenTree* testTree1 = s1->GetRootNode(); + noway_assert(testTree1->gtOper == GT_JTRUE); // The second and the third block must contain a single statement @@ -7907,15 +7906,15 @@ Statement* Compiler::optOptimizeBoolsChkBlkCond(OptBoolsDsc* pOptBoolsDsc) return nullptr; } - GenTree* t2 = s2->GetRootNode(); + GenTree* testTree2 = s2->GetRootNode(); if (!optReturnBlock) { - noway_assert(t2->gtOper == GT_JTRUE); + noway_assert(testTree2->gtOper == GT_JTRUE); } else { - if (t2->gtOper != GT_RETURN) + if (testTree2->gtOper != GT_RETURN) { return nullptr; } @@ -7926,33 +7925,33 @@ Statement* Compiler::optOptimizeBoolsChkBlkCond(OptBoolsDsc* pOptBoolsDsc) return nullptr; } - GenTree* t3 = s3->GetRootNode(); - if (t3->gtOper != GT_RETURN) + GenTree* testTree3 = s3->GetRootNode(); + if (testTree3->gtOper != GT_RETURN) { return nullptr; } - if (!varTypeIsIntegral(t2->TypeGet()) || !varTypeIsIntegral(t3->TypeGet())) + if (!varTypeIsIntegral(testTree2->TypeGet()) || !varTypeIsIntegral(testTree3->TypeGet())) { return nullptr; } // The third block is Return with "CNS_INT int 0/1" - if (t3->AsOp()->gtOp1->gtOper != GT_CNS_INT) + if (testTree3->AsOp()->gtOp1->gtOper != GT_CNS_INT) { return nullptr; } - if (t3->AsOp()->gtOp1->gtType != TYP_INT) + if (testTree3->AsOp()->gtOp1->gtType != TYP_INT) { return nullptr; } - pOptBoolsDsc->t3 = t3; + pOptBoolsDsc->t3 = testTree3; } - pOptBoolsDsc->t1 = t1; - pOptBoolsDsc->t2 = t2; + pOptBoolsDsc->t1.testTree = testTree1; + pOptBoolsDsc->t2.testTree = testTree2; return s1; } @@ -7970,8 +7969,8 @@ Statement* Compiler::optOptimizeBoolsChkBlkCond(OptBoolsDsc* pOptBoolsDsc) // bool Compiler::optOptimizeBoolsChkTypeCostCond(OptBoolsDsc* pOptBoolsDsc) { - GenTree* t1Comp = pOptBoolsDsc->test1.compTree; - GenTree* t2Comp = pOptBoolsDsc->test2.compTree; + GenTree* t1Comp = pOptBoolsDsc->t1.compTree; + GenTree* t2Comp = pOptBoolsDsc->t2.compTree; GenTree* c1 = pOptBoolsDsc->c1; GenTree* c2 = pOptBoolsDsc->c2; @@ -8032,8 +8031,8 @@ bool Compiler::optOptimizeBoolsChkTypeCostCond(OptBoolsDsc* pOptBoolsDsc) // b1: The first Basic Block // b2: The second Basic Block // b3: The thrid Basic Block. null if both b1 and b2 are BBJ_COND -// test1: The test info for the first test tree -// test2: The second info for the second test tree +// t1: The test info for the first test tree +// t2: The second info for the second test tree // foldOp: The fold operator of GT_AND or GT_OR // foldType: The type of the folded tree // cmpOp: The comparison operator of GT_EQ or GT_NE @@ -8054,13 +8053,13 @@ void Compiler::optOptimizeBoolsUpdateTrees(OptBoolsDsc* pOptBoolsDsc) GenTree* c2 = pOptBoolsDsc->c2; GenTree* cmpOp1 = gtNewOperNode(pOptBoolsDsc->foldOp, pOptBoolsDsc->foldType, c1, c2); - if (pOptBoolsDsc->test1.isBool && pOptBoolsDsc->test2.isBool) + if (pOptBoolsDsc->t1.isBool && pOptBoolsDsc->t2.isBool) { // When we 'OR'/'AND' two booleans, the result is boolean as well cmpOp1->gtFlags |= GTF_BOOLEAN; } - GenTree* t1Comp = pOptBoolsDsc->test1.compTree; + GenTree* t1Comp = pOptBoolsDsc->t1.compTree; t1Comp->SetOper(pOptBoolsDsc->cmpOp); t1Comp->AsOp()->gtOp1 = cmpOp1; t1Comp->AsOp()->gtOp2->gtType = pOptBoolsDsc->foldType; // Could have been varTypeIsGC() @@ -8068,8 +8067,8 @@ void Compiler::optOptimizeBoolsUpdateTrees(OptBoolsDsc* pOptBoolsDsc) { // Update tree when b1: BBJ_COND and b2: GT_RETURN (BBJ_RETURN) t1Comp->AsOp()->gtOp2->AsIntCon()->gtIconVal = 0; - pOptBoolsDsc->t1->gtOper = GT_RETURN; - pOptBoolsDsc->t1->gtType = pOptBoolsDsc->t2->gtType; + pOptBoolsDsc->t1.testTree->gtOper = GT_RETURN; + pOptBoolsDsc->t1.testTree->gtType = pOptBoolsDsc->t2.testTree->gtType; } #if FEATURE_SET_FLAGS @@ -8231,23 +8230,22 @@ bool Compiler::optOptimizeBoolsReturnBlock(OptBoolsDsc* pOptBoolsDsc) BasicBlock* b3 = pOptBoolsDsc->b3; pOptBoolsDsc->sameTarget = false; - Statement* const s1 = optOptimizeBoolsChkBlkCond(pOptBoolsDsc); + Statement* const s1 = optOptimizeBoolsChkBlkCond(pOptBoolsDsc); if (s1 == nullptr) { return changeReturn; } // Find the branch conditions of b1 and b2 - pOptBoolsDsc->test1.testTree = pOptBoolsDsc->t1; - GenTree* const c1 = optIsBoolComp(&pOptBoolsDsc->test1); + + GenTree* const c1 = optIsBoolComp(&pOptBoolsDsc->t1); if (c1 == nullptr) { return changeReturn; } pOptBoolsDsc->c1 = c1; - pOptBoolsDsc->test2.testTree = pOptBoolsDsc->t2; - GenTree* const c2 = optIsBoolComp(&pOptBoolsDsc->test2); + GenTree* const c2 = optIsBoolComp(&pOptBoolsDsc->t2); if (c2 == nullptr) { return changeReturn; @@ -8319,15 +8317,15 @@ bool Compiler::optOptimizeBoolsReturnBlock(OptBoolsDsc* pOptBoolsDsc) // tree1: The first test info // tree2: The second test info // it3val: The value of the third tree -// +// // On success, below values are set: // foldOp: the fold operator of GT_AND or GT_OR. // cmpOp: the comparison operator of GT_EQ or GT_NE // void Compiler::optReturnGetFoldAndCompOper(OptBoolsDsc* pOptBoolsDsc) { - GenTree* tree1 = pOptBoolsDsc->test1.compTree; - GenTree* tree2 = pOptBoolsDsc->test2.compTree; + GenTree* tree1 = pOptBoolsDsc->t1.compTree; + GenTree* tree2 = pOptBoolsDsc->t2.compTree; genTreeOps foldOper; genTreeOps cmpOper; @@ -8376,7 +8374,7 @@ void Compiler::optReturnGetFoldAndCompOper(OptBoolsDsc* pOptBoolsDsc) return; } - if ((foldOper == GT_AND || cmpOper == GT_NE) && (!pOptBoolsDsc->test1.isBool || !pOptBoolsDsc->test2.isBool)) + if ((foldOper == GT_AND || cmpOper == GT_NE) && (!pOptBoolsDsc->t1.isBool || !pOptBoolsDsc->t2.isBool)) { // x == 1 && y == 1: Skip cases where x or y is greather 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 @@ -8453,7 +8451,7 @@ void Compiler::optOptimizeBoolsGcStress(BasicBlock* condBlock) // pOptTest->tree Tree node with GT_JTRUE or GT_RETURN type to check boolean condition on. // pOptTest->compTree On success, the compare tree (i.e. GT_EQ or GT_NE node) is set // isBool true if the comparand is boolean. Otherwise, false. -// +// // Given a GT_JTRUE or GT_RETURN node, this method checks that 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. From a7cdf1c16766e461038903eba27b930fd9c91d4e Mon Sep 17 00:00:00 2001 From: Julie Lee Date: Thu, 24 Jun 2021 14:46:31 -0700 Subject: [PATCH 19/25] Moved some methods for Boolean Optimization to OptBoolsDsc struct --- src/coreclr/jit/compiler.h | 6 +- src/coreclr/jit/optimizer.cpp | 159 ++++++++++++++++------------------ 2 files changed, 74 insertions(+), 91 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 413d8d54f3e32..2a5f977a1f1ea 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -6319,13 +6319,9 @@ class Compiler private: bool optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc); bool optOptimizeBoolsReturnBlock(OptBoolsDsc* pOptBoolsDsc); - Statement* optOptimizeBoolsChkBlkCond(OptBoolsDsc* pOptBoolsDsc); GenTree* optIsBoolComp(OptTestInfo* optTest); - bool optOptimizeBoolsChkTypeCostCond(OptBoolsDsc* pOptBoolsDsc); - void optOptimizeBoolsUpdateTrees(OptBoolsDsc* pOptBoolsDsc); - void optReturnGetFoldAndCompOper(OptBoolsDsc* pOptBoolsDsc); #ifdef DEBUG - void optOptimizeBoolsGcStress(BasicBlock* condBlock); + void optOptimizeBoolsGcStress(BasicBlock* b1); #endif public: PhaseStatus optInvertLoops(); // Invert loops so they're entered at top and tested at bottom. diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index 89dc0f20dca84..b0906a87042cd 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -7633,6 +7633,11 @@ struct OptBoolsDsc genTreeOps foldOp; // The fold operator (e.g., GT_AND or GT_OR) var_types foldType; // The type of the folded tree genTreeOps cmpOp; // The comparison operator (e.g., GT_EQ or GT_NE) + + Statement* optOptimizeBoolsChkBlkCond(); + bool optOptimizeBoolsChkTypeCostCond(Compiler* comp); + void optOptimizeBoolsUpdateTrees(Compiler* comp); + void optReturnGetFoldAndCompOper(); }; // @@ -7678,7 +7683,7 @@ bool Compiler::optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc) pOptBoolsDsc->t3 = nullptr; pOptBoolsDsc->sameTarget = false; // Do b1 and b2 have the same bbJumpDest? - Statement* const s1 = optOptimizeBoolsChkBlkCond(pOptBoolsDsc); + Statement* const s1 = pOptBoolsDsc->optOptimizeBoolsChkBlkCond(); if (s1 == nullptr) { return changeCond; @@ -7702,7 +7707,7 @@ bool Compiler::optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc) // Find the type and cost conditions of t1 and t2 - if (!optOptimizeBoolsChkTypeCostCond(pOptBoolsDsc)) + if (!pOptBoolsDsc->optOptimizeBoolsChkTypeCostCond(this)) { return changeCond; } @@ -7788,7 +7793,7 @@ bool Compiler::optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc) pOptBoolsDsc->foldType = foldType; pOptBoolsDsc->cmpOp = cmpOp; - optOptimizeBoolsUpdateTrees(pOptBoolsDsc); + pOptBoolsDsc->optOptimizeBoolsUpdateTrees(this); // Update the change to true to continue the bool optimization for the rest of the BB chain changeCond = true; @@ -7809,9 +7814,6 @@ bool Compiler::optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc) // // optOptimizeBoolsChkBlkCond: Checks block conditions if it can be boolean optimized // -// Arguments: -// pOptBoolsDsc The descriptor with Basic Block b1, b2 and b3 set when the method is called -// // Return: // s1 If all conditions pass, returns the last statement of b1. // @@ -7819,16 +7821,14 @@ bool Compiler::optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc) // This method checks if the second or third block contains only one statement, and // checks if tree operators are of the right type, e.g, GT_JTRUE, GT_RETURN. // -// When called, b1, b2 and b3 of pOptBoolsDsc are set, where +// When called, b1, b2 are set and b3 is set for GT_RETURN, where // b1 is the first block, b2 is the second block and b3 is the third block. // If it passes all the conditions, t1, t2 and t3 are set to the root nodes of b1, b2 and b3 each. // SameTarget is also updated to true if b1 and b2 jump to the same destination. // -Statement* Compiler::optOptimizeBoolsChkBlkCond(OptBoolsDsc* pOptBoolsDsc) +Statement* OptBoolsDsc::optOptimizeBoolsChkBlkCond() { - BasicBlock* b1 = pOptBoolsDsc->b1; - BasicBlock* b2 = pOptBoolsDsc->b2; - BasicBlock* b3 = pOptBoolsDsc->b3; + assert(b1 != nullptr && b2 != nullptr); bool optReturnBlock = false; if (b3) @@ -7850,7 +7850,7 @@ Statement* Compiler::optOptimizeBoolsChkBlkCond(OptBoolsDsc* pOptBoolsDsc) // B1: brtrue(t1|t2, BX) // B3: - pOptBoolsDsc->sameTarget = true; + sameTarget = true; } else if (b1->bbJumpDest == b2->bbNext) { @@ -7862,7 +7862,7 @@ Statement* Compiler::optOptimizeBoolsChkBlkCond(OptBoolsDsc* pOptBoolsDsc) // B1: brtrue((!t1)&&t2, BX) // B3: - pOptBoolsDsc->sameTarget = false; + sameTarget = false; } else { @@ -7947,11 +7947,11 @@ Statement* Compiler::optOptimizeBoolsChkBlkCond(OptBoolsDsc* pOptBoolsDsc) return nullptr; } - pOptBoolsDsc->t3 = testTree3; + t3 = testTree3; } - pOptBoolsDsc->t1.testTree = testTree1; - pOptBoolsDsc->t2.testTree = testTree2; + t1.testTree = testTree1; + t2.testTree = testTree2; return s1; } @@ -7961,21 +7961,17 @@ Statement* Compiler::optOptimizeBoolsChkBlkCond(OptBoolsDsc* pOptBoolsDsc) // if cost to fold is not too expensive // // Arguments: -// pOptBoolsDsc The descriptor for boolean optimization with test compare trees -// and their first operand c1 and c2 -// +// comp Instance of Compiler class +// // Return: // True if it meets type conditions and cost conditions. Else false. // -bool Compiler::optOptimizeBoolsChkTypeCostCond(OptBoolsDsc* pOptBoolsDsc) +bool OptBoolsDsc::optOptimizeBoolsChkTypeCostCond(Compiler* comp) { - GenTree* t1Comp = pOptBoolsDsc->t1.compTree; - GenTree* t2Comp = pOptBoolsDsc->t2.compTree; - GenTree* c1 = pOptBoolsDsc->c1; - GenTree* c2 = pOptBoolsDsc->c2; - - noway_assert(t1Comp->OperIs(GT_EQ, GT_NE) && t1Comp->AsOp()->gtOp1 == c1); - noway_assert(t2Comp->OperIs(GT_EQ, GT_NE) && t2Comp->AsOp()->gtOp1 == c2); + noway_assert(t1.compTree->OperIs(GT_EQ, GT_NE) && + t1.compTree->AsOp()->gtOp1 == c1); + noway_assert(t2.compTree->OperIs(GT_EQ, GT_NE) && + t2.compTree->AsOp()->gtOp1 == c2); // // Leave out floats where the bit-representation is more complicated @@ -7991,7 +7987,7 @@ bool Compiler::optOptimizeBoolsChkTypeCostCond(OptBoolsDsc* pOptBoolsDsc) { return false; } - if (genTypeSize(t1Comp->TypeGet()) != genTypeSize(t2Comp->TypeGet())) + if (genTypeSize(t1.compTree->TypeGet()) != genTypeSize(t2.compTree->TypeGet())) { return false; } @@ -8009,7 +8005,7 @@ bool Compiler::optOptimizeBoolsChkTypeCostCond(OptBoolsDsc* pOptBoolsDsc) // The second condition must not be too expensive - gtPrepareCost(c2); + comp->gtPrepareCost(c2); if (c2->GetCostEx() > 12) { @@ -8024,7 +8020,7 @@ bool Compiler::optOptimizeBoolsChkTypeCostCond(OptBoolsDsc* pOptBoolsDsc) // update the edges, unlink removed blocks and update loop table // // Arguments: -// pOptBoolsDsc The descriptor for boolean optimization +// comp Instance of Compiler class // // Notes: // The pOptBoolsDsc has below key members: @@ -8037,38 +8033,35 @@ bool Compiler::optOptimizeBoolsChkTypeCostCond(OptBoolsDsc* pOptBoolsDsc) // foldType: The type of the folded tree // cmpOp: The comparison operator of GT_EQ or GT_NE // -void Compiler::optOptimizeBoolsUpdateTrees(OptBoolsDsc* pOptBoolsDsc) +void OptBoolsDsc::optOptimizeBoolsUpdateTrees(Compiler* comp) { - BasicBlock* b1 = pOptBoolsDsc->b1; - BasicBlock* b2 = pOptBoolsDsc->b2; - BasicBlock* b3 = pOptBoolsDsc->b3; + assert(b1 != nullptr && b2 != nullptr); bool optReturnBlock = false; - if (b3) + if (b3 != nullptr) { optReturnBlock = true; } - GenTree* c1 = pOptBoolsDsc->c1; - GenTree* c2 = pOptBoolsDsc->c2; + assert(foldOp != NULL && foldType != NULL && c1 != nullptr && c2 != nullptr); - GenTree* cmpOp1 = gtNewOperNode(pOptBoolsDsc->foldOp, pOptBoolsDsc->foldType, c1, c2); - if (pOptBoolsDsc->t1.isBool && pOptBoolsDsc->t2.isBool) + GenTree* cmpOp1 = comp->gtNewOperNode(foldOp, foldType, c1, c2); + if (t1.isBool && t2.isBool) { // When we 'OR'/'AND' two booleans, the result is boolean as well cmpOp1->gtFlags |= GTF_BOOLEAN; } - GenTree* t1Comp = pOptBoolsDsc->t1.compTree; - t1Comp->SetOper(pOptBoolsDsc->cmpOp); + GenTree* t1Comp = t1.compTree; + t1Comp->SetOper(cmpOp); t1Comp->AsOp()->gtOp1 = cmpOp1; - t1Comp->AsOp()->gtOp2->gtType = pOptBoolsDsc->foldType; // Could have been varTypeIsGC() + t1Comp->AsOp()->gtOp2->gtType = foldType; // Could have been varTypeIsGC() if (optReturnBlock) { // Update tree when b1: BBJ_COND and b2: GT_RETURN (BBJ_RETURN) t1Comp->AsOp()->gtOp2->AsIntCon()->gtIconVal = 0; - pOptBoolsDsc->t1.testTree->gtOper = GT_RETURN; - pOptBoolsDsc->t1.testTree->gtType = pOptBoolsDsc->t2.testTree->gtType; + t1.testTree->gtOper = GT_RETURN; + t1.testTree->gtType = t2.testTree->gtType; } #if FEATURE_SET_FLAGS @@ -8099,22 +8092,22 @@ void Compiler::optOptimizeBoolsUpdateTrees(OptBoolsDsc* pOptBoolsDsc) { // Update edges if b1: BBJ_COND and b2: BBJ_COND - flowList* edge1 = fgGetPredForBlock(b1->bbJumpDest, b1); + flowList* edge1 = comp->fgGetPredForBlock(b1->bbJumpDest, b1); flowList* edge2; - if (pOptBoolsDsc->sameTarget) + if (sameTarget) { - edge2 = fgGetPredForBlock(b2->bbJumpDest, b2); + edge2 = comp->fgGetPredForBlock(b2->bbJumpDest, b2); } else { - edge2 = fgGetPredForBlock(b2->bbNext, b2); + edge2 = comp->fgGetPredForBlock(b2->bbNext, b2); - fgRemoveRefPred(b1->bbJumpDest, b1); + comp->fgRemoveRefPred(b1->bbJumpDest, b1); b1->bbJumpDest = b2->bbJumpDest; - fgAddRefPred(b2->bbJumpDest, b1); + comp->fgAddRefPred(b2->bbJumpDest, b1); } noway_assert(edge1 != nullptr); @@ -8136,7 +8129,7 @@ void Compiler::optOptimizeBoolsUpdateTrees(OptBoolsDsc* pOptBoolsDsc) if (optReturnBlock) { - fgRemoveRefPred(b1->bbJumpDest, b1); + comp->fgRemoveRefPred(b1->bbJumpDest, b1); noway_assert(!b2->bbJumpDest); b1->bbJumpDest = b2->bbJumpDest; @@ -8160,18 +8153,18 @@ void Compiler::optOptimizeBoolsUpdateTrees(OptBoolsDsc* pOptBoolsDsc) // Get rid of the second block - fgUnlinkBlock(b2); + comp->fgUnlinkBlock(b2); b2->bbFlags |= BBF_REMOVED; // If b2 was the last block of a try or handler, update the EH table. - ehUpdateForDeletedBlock(b2); + comp->ehUpdateForDeletedBlock(b2); if (optReturnBlock) { // Get rid of the third block - fgUnlinkBlock(b3); + comp->fgUnlinkBlock(b3); b3->bbFlags |= BBF_REMOVED; // If b3 was the last block of a try or handler, update the EH table. - ehUpdateForDeletedBlock(b3); + comp->ehUpdateForDeletedBlock(b3); } if (!optReturnBlock) @@ -8180,15 +8173,15 @@ void Compiler::optOptimizeBoolsUpdateTrees(OptBoolsDsc* pOptBoolsDsc) // // Replace pred 'b2' for 'b2->bbNext' with 'b1' // Remove pred 'b2' for 'b2->bbJumpDest' - fgReplacePred(b2->bbNext, b2, b1); - fgRemoveRefPred(b2->bbJumpDest, b2); + comp->fgReplacePred(b2->bbNext, b2, b1); + comp->fgRemoveRefPred(b2->bbJumpDest, b2); } // Update loop table - fgUpdateLoopsAfterCompacting(b1, b2); + comp->fgUpdateLoopsAfterCompacting(b1, b2); if (optReturnBlock) { - fgUpdateLoopsAfterCompacting(b1, b3); + comp->fgUpdateLoopsAfterCompacting(b1, b3); } } @@ -8230,7 +8223,7 @@ bool Compiler::optOptimizeBoolsReturnBlock(OptBoolsDsc* pOptBoolsDsc) BasicBlock* b3 = pOptBoolsDsc->b3; pOptBoolsDsc->sameTarget = false; - Statement* const s1 = optOptimizeBoolsChkBlkCond(pOptBoolsDsc); + Statement* const s1 = pOptBoolsDsc->optOptimizeBoolsChkBlkCond(); if (s1 == nullptr) { return changeReturn; @@ -8254,7 +8247,7 @@ bool Compiler::optOptimizeBoolsReturnBlock(OptBoolsDsc* pOptBoolsDsc) // Find the type and cost conditions of t1 and t2 - if (!optOptimizeBoolsChkTypeCostCond(pOptBoolsDsc)) + if (!pOptBoolsDsc->optOptimizeBoolsChkTypeCostCond(this)) { return changeReturn; } @@ -8271,7 +8264,7 @@ bool Compiler::optOptimizeBoolsReturnBlock(OptBoolsDsc* pOptBoolsDsc) pOptBoolsDsc->foldOp = GT_NONE; pOptBoolsDsc->cmpOp = GT_NONE; - optReturnGetFoldAndCompOper(pOptBoolsDsc); + pOptBoolsDsc->optReturnGetFoldAndCompOper(); if (pOptBoolsDsc->foldOp == GT_NONE || pOptBoolsDsc->cmpOp == GT_NONE) { return changeReturn; @@ -8279,7 +8272,7 @@ bool Compiler::optOptimizeBoolsReturnBlock(OptBoolsDsc* pOptBoolsDsc) // Now update the trees - optOptimizeBoolsUpdateTrees(pOptBoolsDsc); + pOptBoolsDsc->optOptimizeBoolsUpdateTrees(this); // Update the change to true to continue the bool optimization for the rest of the BB chain changeReturn = true; @@ -8298,10 +8291,7 @@ bool Compiler::optOptimizeBoolsReturnBlock(OptBoolsDsc* pOptBoolsDsc) } // -// optReturnGetFoldAndCompOper: Based on tree1 and tree2 conditions, determine fold type and comparison type -// -// Arguments: -// pOptBoolsDsc The descriptor for boolean optimization +// optReturnGetFoldAndCompOper: Based on t1 and t2 conditions, determine fold type and comparison type // // Notes: // For example, the fold operator (foldOp) of below tree is GT_OR, and @@ -8313,30 +8303,27 @@ bool Compiler::optOptimizeBoolsReturnBlock(OptBoolsDsc* pOptBoolsDsc) // | \--* LCL_VAR int V01 arg1 // \--* CNS_INT int 0 // -// Below members of pOptBoolsDsc are set when called: -// tree1: The first test info -// tree2: The second test info -// it3val: The value of the third tree +// Below members of OptBoolsDsc are set when called: +// t1: The first test info +// t2: The second test info +// t3: The tree of b3 // // On success, below values are set: // foldOp: the fold operator of GT_AND or GT_OR. // cmpOp: the comparison operator of GT_EQ or GT_NE // -void Compiler::optReturnGetFoldAndCompOper(OptBoolsDsc* pOptBoolsDsc) +void OptBoolsDsc::optReturnGetFoldAndCompOper() { - GenTree* tree1 = pOptBoolsDsc->t1.compTree; - GenTree* tree2 = pOptBoolsDsc->t2.compTree; + assert(t1.compTree != nullptr && t2.compTree != nullptr && t3 != nullptr); genTreeOps foldOper; genTreeOps cmpOper; - genTreeOps t1Oper = tree1->gtOper; - genTreeOps t2Oper = tree2->gtOper; - ssize_t it1val = tree1->AsOp()->gtOp2->AsIntCon()->gtIconVal; - ssize_t it2val = tree2->AsOp()->gtOp2->AsIntCon()->gtIconVal; - ssize_t it3val = pOptBoolsDsc->t3->AsOp()->gtOp1->AsIntCon()->gtIconVal; + ssize_t it1val = t1.compTree->AsOp()->gtOp2->AsIntCon()->gtIconVal; + ssize_t it2val = t2.compTree->AsOp()->gtOp2->AsIntCon()->gtIconVal; + ssize_t it3val = t3->AsOp()->gtOp1->AsIntCon()->gtIconVal; - if ((t1Oper == GT_NE && t2Oper == GT_EQ) && (it1val == 0 && it2val == 0 && it3val == 0)) + if ((t1.compTree->gtOper == GT_NE && t2.compTree->gtOper == GT_EQ) && (it1val == 0 && it2val == 0 && it3val == 0)) { // Case: x == 0 && y == 0 // t1:c1!=0 t2:c2==0 t3:c3==0 @@ -8344,7 +8331,7 @@ void Compiler::optReturnGetFoldAndCompOper(OptBoolsDsc* pOptBoolsDsc) foldOper = GT_OR; cmpOper = GT_EQ; } - else if ((t1Oper == GT_EQ && t2Oper == GT_NE) && (it1val == 0 && it2val == 0 && it3val == 0)) + else if ((t1.compTree->gtOper == GT_EQ && t2.compTree->gtOper == GT_NE) && (it1val == 0 && it2val == 0 && it3val == 0)) { // Case: x == 1 && y ==1 // t1:c1!=1 t2:c2==1 t3:c3==0 is reversed from optIsBoolComp() to: t1:c1==0 t2:c2!=0 t3:c3==0 @@ -8352,7 +8339,7 @@ void Compiler::optReturnGetFoldAndCompOper(OptBoolsDsc* pOptBoolsDsc) foldOper = GT_AND; cmpOper = GT_NE; } - else if ((t1Oper == GT_EQ && t2Oper == GT_EQ) && (it1val == 0 && it2val == 0 && it3val == 1)) + else if ((t1.compTree->gtOper == GT_EQ && t2.compTree->gtOper == GT_EQ) && (it1val == 0 && it2val == 0 && it3val == 1)) { // Case: x == 0 || y == 0 // t1:c1==0 t2:c2==0 t3:c3==1 @@ -8360,7 +8347,7 @@ void Compiler::optReturnGetFoldAndCompOper(OptBoolsDsc* pOptBoolsDsc) foldOper = GT_AND; cmpOper = GT_EQ; } - else if ((t1Oper == GT_NE && t2Oper == GT_NE) && (it1val == 0 && it2val == 0 && it3val == 1)) + else if ((t1.compTree->gtOper == GT_NE && t2.compTree->gtOper == GT_NE) && (it1val == 0 && it2val == 0 && it3val == 1)) { // Case: x == 1 || y == 1 // t1:c1==1 t2:c2==1 is reversed from optIsBoolComp() to: t1:c1!=0 t2:c2!=0 t3:c3==1 @@ -8374,7 +8361,7 @@ void Compiler::optReturnGetFoldAndCompOper(OptBoolsDsc* pOptBoolsDsc) return; } - if ((foldOper == GT_AND || cmpOper == GT_NE) && (!pOptBoolsDsc->t1.isBool || !pOptBoolsDsc->t2.isBool)) + if ((foldOper == GT_AND || cmpOper == GT_NE) && (!t1.isBool || !t2.isBool)) { // x == 1 && y == 1: Skip cases where x or y is greather 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 @@ -8382,8 +8369,8 @@ void Compiler::optReturnGetFoldAndCompOper(OptBoolsDsc* pOptBoolsDsc) return; } - pOptBoolsDsc->foldOp = foldOper; - pOptBoolsDsc->cmpOp = cmpOper; + this->foldOp = foldOper; + this->cmpOp = cmpOper; } // From e3a65202802589d392d8aa258bf2a3b00ab4ca88 Mon Sep 17 00:00:00 2001 From: Julie Lee Date: Thu, 24 Jun 2021 16:03:56 -0700 Subject: [PATCH 20/25] Moved all private methods for Boolean Optimization to OptBoolsDsc struct --- src/coreclr/jit/compiler.h | 7 -- src/coreclr/jit/optimizer.cpp | 182 +++++++++++++++++++--------------- 2 files changed, 102 insertions(+), 87 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 2a5f977a1f1ea..269ff6dafba24 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -6316,13 +6316,6 @@ class Compiler public: void optOptimizeBools(); -private: - bool optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc); - bool optOptimizeBoolsReturnBlock(OptBoolsDsc* pOptBoolsDsc); - GenTree* optIsBoolComp(OptTestInfo* optTest); -#ifdef DEBUG - void optOptimizeBoolsGcStress(BasicBlock* b1); -#endif public: PhaseStatus optInvertLoops(); // Invert loops so they're entered at top and tested at bottom. PhaseStatus optOptimizeLayout(); // Optimize the BasicBlock layout of the method diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index b0906a87042cd..0ca1d71f9f2de 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -7608,6 +7608,10 @@ bool Compiler::optIsRangeCheckRemovable(GenTree* tree) return true; } +// +// OptTestInfo: Member of OptBoolsDsc struct used to test if a GT-JTRUE or GT_RETURN node +// is a boolean comparison +// struct OptTestInfo { GenTree* testTree; // The root node of Basic Block @@ -7615,12 +7619,17 @@ struct OptTestInfo bool isBool; // If the compTree is boolean comparison }; +// +// OptBoolsDsc: Descriptor used for Boolean Optimization +// struct OptBoolsDsc { +public: BasicBlock* b1; // The first Basic Block with the BBJ_COND conditional jump type BasicBlock* b2; // The next basic block of b1. Either BBJ_COND or BBJ_RETURN type BasicBlock* b3; // The next basic block of b2. Null if b2 is not a return block +private: OptTestInfo t1; // The first test info OptTestInfo t2; // The second test info GenTree* t3; // The root node of the first statement of b3 @@ -7634,7 +7643,16 @@ struct OptBoolsDsc var_types foldType; // The type of the folded tree genTreeOps cmpOp; // The comparison operator (e.g., GT_EQ or GT_NE) +public: + bool optOptimizeBoolsCondBlock(Compiler* comp); + bool optOptimizeBoolsReturnBlock(Compiler* comp); +#ifdef DEBUG + void optOptimizeBoolsGcStress(Compiler* comp, BasicBlock* condBlock); +#endif + +private: Statement* optOptimizeBoolsChkBlkCond(); + GenTree* optIsBoolComp(Compiler* comp, OptTestInfo* pOptTest); bool optOptimizeBoolsChkTypeCostCond(Compiler* comp); void optOptimizeBoolsUpdateTrees(Compiler* comp); void optReturnGetFoldAndCompOper(); @@ -7644,12 +7662,14 @@ struct OptBoolsDsc // optOptimizeBoolsCondBlock: Optimize boolean when bbJumpKind of both b1 and b2 are BBJ_COND // // Arguments: -// pOptBoolsDsc The descriptor with Basic Block b1 and b2 set when called +// comp The instance of Compiler class // // Returns: -// changeCond Set to true if boolean optimization is done and b1 and b2 are folded into b1. +// changeCond Set to true if boolean optimization is done and b1 and b2 are folded into b1. // // Notes: +// When called, Basic Block b1 and b2 are set. +// // For example, (x==0 && y==0 && z==0) generates // b1: GT_JTRUE (BBJ_COND) // b2: GT_JTRUE (BBJ_COND) @@ -7674,16 +7694,18 @@ struct OptBoolsDsc // b1 : brtrue((!t1) && t2, bx) // b3 : // -bool Compiler::optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc) +bool OptBoolsDsc::optOptimizeBoolsCondBlock(Compiler* comp) { + assert(b1 != nullptr && b2 != nullptr && b3 == nullptr); + bool changeCond = false; // Check if b1 and b2 jump to the same target and get back pointers to t1 and t2 tree nodes - pOptBoolsDsc->t3 = nullptr; - pOptBoolsDsc->sameTarget = false; // Do b1 and b2 have the same bbJumpDest? + this->t3 = nullptr; + this->sameTarget = false; // Do b1 and b2 have the same bbJumpDest? - Statement* const s1 = pOptBoolsDsc->optOptimizeBoolsChkBlkCond(); + Statement* const s1 = optOptimizeBoolsChkBlkCond(); if (s1 == nullptr) { return changeCond; @@ -7691,23 +7713,23 @@ bool Compiler::optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc) // Find the branch conditions of b1 and b2 - GenTree* const c1 = optIsBoolComp(&pOptBoolsDsc->t1); + GenTree* const c1 = optIsBoolComp(comp, &t1); if (c1 == nullptr) { return changeCond; } - pOptBoolsDsc->c1 = c1; + this->c1 = c1; - GenTree* const c2 = optIsBoolComp(&pOptBoolsDsc->t2); + GenTree* const c2 = optIsBoolComp(comp, &t2); if (c2 == nullptr) { return changeCond; } - pOptBoolsDsc->c2 = c2; + this->c2 = c2; // Find the type and cost conditions of t1 and t2 - if (!pOptBoolsDsc->optOptimizeBoolsChkTypeCostCond(this)) + if (!optOptimizeBoolsChkTypeCostCond(comp)) { return changeCond; } @@ -7716,24 +7738,24 @@ bool Compiler::optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc) genTreeOps foldOp; genTreeOps cmpOp; - var_types foldType = c1->TypeGet(); + var_types foldType = this->c1->TypeGet(); if (varTypeIsGC(foldType)) { foldType = TYP_I_IMPL; } - assert(pOptBoolsDsc->t1.compTree->gtOper == GT_EQ || pOptBoolsDsc->t1.compTree->gtOper == GT_NE); + assert(t1.compTree->gtOper == GT_EQ || t1.compTree->gtOper == GT_NE); - if (pOptBoolsDsc->sameTarget) + if (sameTarget) { // Both conditions must be the same - if (pOptBoolsDsc->t1.compTree->gtOper != pOptBoolsDsc->t2.compTree->gtOper) + if (t1.compTree->gtOper != t2.compTree->gtOper) { return changeCond; } - if (pOptBoolsDsc->t1.compTree->gtOper == GT_EQ) + if (t1.compTree->gtOper == 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 @@ -7755,12 +7777,12 @@ bool Compiler::optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc) // The b1 condition must be the reverse of the 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 (pOptBoolsDsc->t1.compTree->gtOper == pOptBoolsDsc->t2.compTree->gtOper) + if (t1.compTree->gtOper == t2.compTree->gtOper) { return changeCond; } - if (pOptBoolsDsc->t1.compTree->gtOper == GT_EQ) + if (t1.compTree->gtOper == GT_EQ) { // 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 @@ -7780,7 +7802,7 @@ bool Compiler::optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc) // Anding requires both values to be 0 or 1 - if ((foldOp == GT_AND) && (!pOptBoolsDsc->t1.isBool || !pOptBoolsDsc->t2.isBool)) + if ((foldOp == GT_AND) && (!t1.isBool || !t2.isBool)) { return changeCond; } @@ -7789,21 +7811,21 @@ bool Compiler::optOptimizeBoolsCondBlock(OptBoolsDsc* pOptBoolsDsc) // Now update the trees // - pOptBoolsDsc->foldOp = foldOp; - pOptBoolsDsc->foldType = foldType; - pOptBoolsDsc->cmpOp = cmpOp; + this->foldOp = foldOp; + this->foldType = foldType; + this->cmpOp = cmpOp; - pOptBoolsDsc->optOptimizeBoolsUpdateTrees(this); + optOptimizeBoolsUpdateTrees(comp); // Update the change to true to continue the bool optimization for the rest of the BB chain changeCond = true; #ifdef DEBUG - if (verbose) + if (comp->verbose) { - printf("Folded %sboolean conditions of " FMT_BB " and " FMT_BB " to :\n", c2->OperIsLeaf() ? "" : "non-leaf ", - pOptBoolsDsc->b1->bbNum, pOptBoolsDsc->b2->bbNum); - gtDispStmt(s1); + printf("Folded %sboolean conditions of " FMT_BB " and " FMT_BB " to :\n", + this->c2->OperIsLeaf() ? "" : "non-leaf ", b1->bbNum, b2->bbNum); + comp->gtDispStmt(s1); printf("\n"); } #endif @@ -7831,7 +7853,7 @@ Statement* OptBoolsDsc::optOptimizeBoolsChkBlkCond() assert(b1 != nullptr && b2 != nullptr); bool optReturnBlock = false; - if (b3) + if (b3 != nullptr) { optReturnBlock = true; } @@ -7850,7 +7872,7 @@ Statement* OptBoolsDsc::optOptimizeBoolsChkBlkCond() // B1: brtrue(t1|t2, BX) // B3: - sameTarget = true; + this->sameTarget = true; } else if (b1->bbJumpDest == b2->bbNext) { @@ -7862,7 +7884,7 @@ Statement* OptBoolsDsc::optOptimizeBoolsChkBlkCond() // B1: brtrue((!t1)&&t2, BX) // B3: - sameTarget = false; + this->sameTarget = false; } else { @@ -7947,11 +7969,11 @@ Statement* OptBoolsDsc::optOptimizeBoolsChkBlkCond() return nullptr; } - t3 = testTree3; + this->t3 = testTree3; } - t1.testTree = testTree1; - t2.testTree = testTree2; + this->t1.testTree = testTree1; + this->t2.testTree = testTree2; return s1; } @@ -7962,16 +7984,14 @@ Statement* OptBoolsDsc::optOptimizeBoolsChkBlkCond() // // Arguments: // comp Instance of Compiler class -// +// // Return: // True if it meets type conditions and cost conditions. Else false. // bool OptBoolsDsc::optOptimizeBoolsChkTypeCostCond(Compiler* comp) { - noway_assert(t1.compTree->OperIs(GT_EQ, GT_NE) && - t1.compTree->AsOp()->gtOp1 == c1); - noway_assert(t2.compTree->OperIs(GT_EQ, GT_NE) && - t2.compTree->AsOp()->gtOp1 == c2); + noway_assert(t1.compTree->OperIs(GT_EQ, GT_NE) && t1.compTree->AsOp()->gtOp1 == c1); + noway_assert(t2.compTree->OperIs(GT_EQ, GT_NE) && t2.compTree->AsOp()->gtOp1 == c2); // // Leave out floats where the bit-representation is more complicated @@ -8189,13 +8209,13 @@ void OptBoolsDsc::optOptimizeBoolsUpdateTrees(Compiler* comp) // optOptimizeBoolsReturnBlock: Optimize boolean when b1.bbJumpKind is BBJ_COND and b2.bbJumpKind is BBJ_RETURN // // Arguments: -// pOptBoolsDsc The descriptor for boolean optimization with b1, b2 and b3 members set when called +// comp The instance of Compiler class // // Returns: // changeReturn Set to true if boolean optimization is done and b1, b2 and b3 are folded into b1. // // Notes: -// Below members of pOptBoolsDsc are set when called: +// Below members of OptBoolsDsc are set when called: // b1 The first Basic Block with the BBJ_COND conditional jump type // b2 The next basic block of b1 with the BBJ_RETURN // b3 The next basic block of b2 with GT_RETURN @@ -8214,16 +8234,14 @@ void OptBoolsDsc::optOptimizeBoolsUpdateTrees(Compiler* comp) // to // b1 : ret((!t1) && t2) // -bool Compiler::optOptimizeBoolsReturnBlock(OptBoolsDsc* pOptBoolsDsc) +bool OptBoolsDsc::optOptimizeBoolsReturnBlock(Compiler* comp) { - bool changeReturn = false; + assert(b1 != nullptr && b2 != nullptr && b3 != nullptr); - BasicBlock* b1 = pOptBoolsDsc->b1; - BasicBlock* b2 = pOptBoolsDsc->b2; - BasicBlock* b3 = pOptBoolsDsc->b3; + bool changeReturn = false; - pOptBoolsDsc->sameTarget = false; - Statement* const s1 = pOptBoolsDsc->optOptimizeBoolsChkBlkCond(); + sameTarget = false; + Statement* const s1 = optOptimizeBoolsChkBlkCond(); if (s1 == nullptr) { return changeReturn; @@ -8231,23 +8249,23 @@ bool Compiler::optOptimizeBoolsReturnBlock(OptBoolsDsc* pOptBoolsDsc) // Find the branch conditions of b1 and b2 - GenTree* const c1 = optIsBoolComp(&pOptBoolsDsc->t1); - if (c1 == nullptr) + GenTree* const op1 = optIsBoolComp(comp, &t1); + if (op1 == nullptr) { return changeReturn; } - pOptBoolsDsc->c1 = c1; + c1 = op1; - GenTree* const c2 = optIsBoolComp(&pOptBoolsDsc->t2); - if (c2 == nullptr) + GenTree* const op2 = optIsBoolComp(comp, &t2); + if (op2 == nullptr) { return changeReturn; } - pOptBoolsDsc->c2 = c2; + c2 = op2; // Find the type and cost conditions of t1 and t2 - if (!pOptBoolsDsc->optOptimizeBoolsChkTypeCostCond(this)) + if (!optOptimizeBoolsChkTypeCostCond(comp)) { return changeReturn; } @@ -8259,30 +8277,30 @@ bool Compiler::optOptimizeBoolsReturnBlock(OptBoolsDsc* pOptBoolsDsc) { foldType = TYP_I_IMPL; } - pOptBoolsDsc->foldType = foldType; + this->foldType = foldType; - pOptBoolsDsc->foldOp = GT_NONE; - pOptBoolsDsc->cmpOp = GT_NONE; + foldOp = GT_NONE; + cmpOp = GT_NONE; - pOptBoolsDsc->optReturnGetFoldAndCompOper(); - if (pOptBoolsDsc->foldOp == GT_NONE || pOptBoolsDsc->cmpOp == GT_NONE) + optReturnGetFoldAndCompOper(); + if (foldOp == GT_NONE || cmpOp == GT_NONE) { return changeReturn; } // Now update the trees - pOptBoolsDsc->optOptimizeBoolsUpdateTrees(this); + optOptimizeBoolsUpdateTrees(comp); // Update the change to true to continue the bool optimization for the rest of the BB chain changeReturn = true; #ifdef DEBUG - if (verbose) + if (comp->verbose) { printf("Folded %sboolean conditions of " FMT_BB ", " FMT_BB " and " FMT_BB " to :\n", c2->OperIsLeaf() ? "" : "non-leaf ", b1->bbNum, b2->bbNum, b3->bbNum); - gtDispStmt(s1); + comp->gtDispStmt(s1); printf("\n"); } #endif @@ -8331,7 +8349,8 @@ void OptBoolsDsc::optReturnGetFoldAndCompOper() foldOper = GT_OR; cmpOper = GT_EQ; } - else if ((t1.compTree->gtOper == GT_EQ && t2.compTree->gtOper == GT_NE) && (it1val == 0 && it2val == 0 && it3val == 0)) + else if ((t1.compTree->gtOper == GT_EQ && t2.compTree->gtOper == GT_NE) && + (it1val == 0 && it2val == 0 && it3val == 0)) { // Case: x == 1 && y ==1 // t1:c1!=1 t2:c2==1 t3:c3==0 is reversed from optIsBoolComp() to: t1:c1==0 t2:c2!=0 t3:c3==0 @@ -8339,7 +8358,8 @@ void OptBoolsDsc::optReturnGetFoldAndCompOper() foldOper = GT_AND; cmpOper = GT_NE; } - else if ((t1.compTree->gtOper == GT_EQ && t2.compTree->gtOper == GT_EQ) && (it1val == 0 && it2val == 0 && it3val == 1)) + else if ((t1.compTree->gtOper == GT_EQ && t2.compTree->gtOper == GT_EQ) && + (it1val == 0 && it2val == 0 && it3val == 1)) { // Case: x == 0 || y == 0 // t1:c1==0 t2:c2==0 t3:c3==1 @@ -8347,7 +8367,8 @@ void OptBoolsDsc::optReturnGetFoldAndCompOper() foldOper = GT_AND; cmpOper = GT_EQ; } - else if ((t1.compTree->gtOper == GT_NE && t2.compTree->gtOper == GT_NE) && (it1val == 0 && it2val == 0 && it3val == 1)) + else if ((t1.compTree->gtOper == GT_NE && t2.compTree->gtOper == GT_NE) && + (it1val == 0 && it2val == 0 && it3val == 1)) { // Case: x == 1 || y == 1 // t1:c1==1 t2:c2==1 is reversed from optIsBoolComp() to: t1:c1!=0 t2:c2!=0 t3:c3==1 @@ -8378,13 +8399,14 @@ void OptBoolsDsc::optReturnGetFoldAndCompOper() // This will stress code-gen and the emitter to make sure they support such trees. // // Arguments: -// condBlock The conditional Basic Block +// pOptBoolsDsc Boolean optimization descriptor +// condBlock The conditional Basic Block // #ifdef DEBUG -void Compiler::optOptimizeBoolsGcStress(BasicBlock* condBlock) +void OptBoolsDsc::optOptimizeBoolsGcStress(Compiler* comp, BasicBlock* condBlock) { - if (!compStressCompile(STRESS_OPT_BOOLS_GC, 20)) + if (!comp->compStressCompile(comp->STRESS_OPT_BOOLS_GC, 20)) { return; } @@ -8397,7 +8419,7 @@ void Compiler::optOptimizeBoolsGcStress(BasicBlock* condBlock) OptTestInfo test; test.testTree = cond; - GenTree* comparand = optIsBoolComp(&test); + GenTree* comparand = optIsBoolComp(comp, &test); if (comparand == nullptr || !varTypeIsGC(comparand->TypeGet())) { @@ -8411,11 +8433,11 @@ void Compiler::optOptimizeBoolsGcStress(BasicBlock* condBlock) return; } - GenTree* comparandClone = gtCloneExpr(comparand); + GenTree* comparandClone = comp->gtCloneExpr(comparand); noway_assert(relop->AsOp()->gtOp1 == comparand); - genTreeOps oper = compStressCompile(STRESS_OPT_BOOLS_GC, 50) ? GT_OR : GT_AND; - relop->AsOp()->gtOp1 = gtNewOperNode(oper, TYP_I_IMPL, comparand, comparandClone); + genTreeOps oper = comp->compStressCompile(comp->STRESS_OPT_BOOLS_GC, 50) ? GT_OR : GT_AND; + relop->AsOp()->gtOp1 = comp->gtNewOperNode(oper, TYP_I_IMPL, comparand, comparandClone); // Comparand type is already checked, and we have const int, there is no harm // morphing it into a TYP_I_IMPL. @@ -8446,7 +8468,7 @@ void Compiler::optOptimizeBoolsGcStress(BasicBlock* condBlock) // 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. // -GenTree* Compiler::optIsBoolComp(OptTestInfo* pOptTest) +GenTree* OptBoolsDsc::optIsBoolComp(Compiler* comp, OptTestInfo* pOptTest) { pOptTest->isBool = false; @@ -8498,9 +8520,9 @@ GenTree* Compiler::optIsBoolComp(OptTestInfo* pOptTest) // is it a boolean local variable? unsigned lclNum = opr1->AsLclVarCommon()->GetLclNum(); - noway_assert(lclNum < lvaCount); + noway_assert(lclNum < comp->lvaCount); - if (lvaTable[lclNum].lvIsBoolean) + if (comp->lvaTable[lclNum].lvIsBoolean) { pOptTest->isBool = true; } @@ -8513,7 +8535,7 @@ GenTree* Compiler::optIsBoolComp(OptTestInfo* pOptTest) // and change the true to false. if (pOptTest->isBool) { - gtReverseCond(cond); + comp->gtReverseCond(cond); opr2->AsIntCon()->gtIconVal = 0; } else @@ -8597,7 +8619,7 @@ void Compiler::optOptimizeBools() // When it is conditional jumps optBoolsDsc.b3 = nullptr; - if (optOptimizeBoolsCondBlock(&optBoolsDsc)) + if (optBoolsDsc.optOptimizeBoolsCondBlock(this)) { change = true; } @@ -8607,7 +8629,7 @@ void Compiler::optOptimizeBools() // If there is no next block after b2, we're done BasicBlock* b3 = b2->bbNext; - if (!b3) + if (b3 == nullptr) { break; } @@ -8620,7 +8642,7 @@ void Compiler::optOptimizeBools() } optBoolsDsc.b3 = b3; - if (optOptimizeBoolsReturnBlock(&optBoolsDsc)) + if (optBoolsDsc.optOptimizeBoolsReturnBlock(this)) { change = true; } @@ -8628,7 +8650,7 @@ void Compiler::optOptimizeBools() else { #ifdef DEBUG - optOptimizeBoolsGcStress(b1); + optBoolsDsc.optOptimizeBoolsGcStress(this, b1); #endif } } From 5f029654b378ee39d5ff57ec71fcdbba7e6b07c2 Mon Sep 17 00:00:00 2001 From: Julie Lee Date: Fri, 2 Jul 2021 12:15:05 -0700 Subject: [PATCH 21/25] Boolean Optimization: Handled code review feedback --- src/coreclr/jit/compiler.h | 2 +- src/coreclr/jit/optimizer.cpp | 730 +++++++++++++++++----------------- 2 files changed, 357 insertions(+), 375 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 269ff6dafba24..4a88348766f7f 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -79,7 +79,7 @@ class FgStack; // defined in fgbasic.cpp class Instrumentor; // defined in fgprofile.cpp class SpanningTreeVisitor; // defined in fgprofile.cpp class CSE_DataFlow; // defined in OptCSE.cpp -struct OptBoolsDsc; // defined in optimizer.cpp +class OptBoolsDsc; // defined in optimizer.cpp struct OptTestInfo; #ifdef DEBUG struct IndentStack; diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index 0ca1d71f9f2de..9e40bc56287fc 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -7608,154 +7608,150 @@ bool Compiler::optIsRangeCheckRemovable(GenTree* tree) return true; } -// -// OptTestInfo: Member of OptBoolsDsc struct used to test if a GT-JTRUE or GT_RETURN node +//----------------------------------------------------------------------------- +// OptTestInfo: Member of OptBoolsDsc struct used to test if a GT_JTRUE or GT_RETURN node // is a boolean comparison // struct OptTestInfo { - GenTree* testTree; // The root node of Basic Block + GenTree* testTree; // The root node of basic block with GT_JTRUE or GT_RETURN type to check boolean condition on GenTree* compTree; // The compare node (i.e. GT_EQ or GT_NE node) of the testTree - bool isBool; // If the compTree is boolean comparison + bool isBool; // If the compTree is boolean expression }; -// +//----------------------------------------------------------------------------- // OptBoolsDsc: Descriptor used for Boolean Optimization // -struct OptBoolsDsc +class OptBoolsDsc { public: - BasicBlock* b1; // The first Basic Block with the BBJ_COND conditional jump type - BasicBlock* b2; // The next basic block of b1. Either BBJ_COND or BBJ_RETURN type - BasicBlock* b3; // The next basic block of b2. Null if b2 is not a return block + BasicBlock* m_b1; // The first basic block with the BBJ_COND conditional jump type + BasicBlock* m_b2; // The next basic block of m_b1. Either BBJ_COND or BBJ_RETURN type + BasicBlock* m_b3; // m_b1->bbJumpDest. Null if m_b2 is not a return block. + + Compiler* m_comp; // The pointer to the Compiler instance private: - OptTestInfo t1; // The first test info - OptTestInfo t2; // The second test info - GenTree* t3; // The root node of the first statement of b3 + OptTestInfo m_testInfo1; // The first test info + OptTestInfo m_testInfo2; // The second test info + GenTree* m_t3; // The root node of the first statement of m_b3 - GenTree* c1; // The first operand of t1.compTree - GenTree* c2; // The first operand of t2.compTree + GenTree* m_c1; // The first operand of m_testInfo1.compTree + GenTree* m_c2; // The first operand of m_testInfo2.compTree - bool sameTarget; // if b1 and b2 jumps to the same destination + bool m_sameTarget; // if m_b1 and m_b2 jumps to the same destination - genTreeOps foldOp; // The fold operator (e.g., GT_AND or GT_OR) - var_types foldType; // The type of the folded tree - genTreeOps cmpOp; // The comparison operator (e.g., GT_EQ or GT_NE) + genTreeOps m_foldOp; // The fold operator (e.g., GT_AND or GT_OR) + var_types m_foldType; // The type of the folded tree + genTreeOps m_cmpOp; // The comparison operator (e.g., GT_EQ or GT_NE) public: - bool optOptimizeBoolsCondBlock(Compiler* comp); - bool optOptimizeBoolsReturnBlock(Compiler* comp); + bool optOptimizeBoolsCondBlock(); + bool optOptimizeBoolsReturnBlock(); #ifdef DEBUG - void optOptimizeBoolsGcStress(Compiler* comp, BasicBlock* condBlock); + void optOptimizeBoolsGcStress(BasicBlock* condBlock); #endif private: Statement* optOptimizeBoolsChkBlkCond(); - GenTree* optIsBoolComp(Compiler* comp, OptTestInfo* pOptTest); - bool optOptimizeBoolsChkTypeCostCond(Compiler* comp); - void optOptimizeBoolsUpdateTrees(Compiler* comp); - void optReturnGetFoldAndCompOper(); + GenTree* optIsBoolComp(OptTestInfo* pOptTest); + bool optOptimizeBoolsChkTypeCostCond(); + void optOptimizeBoolsUpdateTrees(); }; -// -// optOptimizeBoolsCondBlock: Optimize boolean when bbJumpKind of both b1 and b2 are BBJ_COND -// -// Arguments: -// comp The instance of Compiler class +//----------------------------------------------------------------------------- +// optOptimizeBoolsCondBlock: Optimize boolean when bbJumpKind of both m_b1 and m_b2 are BBJ_COND // // Returns: -// changeCond Set to true if boolean optimization is done and b1 and b2 are folded into b1. +// true if boolean optimization is done and m_b1 and m_b2 are folded into m_b1, else false. // // Notes: -// When called, Basic Block b1 and b2 are set. -// -// For example, (x==0 && y==0 && z==0) generates -// b1: GT_JTRUE (BBJ_COND) -// b2: GT_JTRUE (BBJ_COND) -// b3: GT_RETURN (BBJ_RETURN), -// and it is folded into -// b1: GT_JTRUE (BBJ_COND) -// b3: GT_RETURN (BBJ_RETURN) +// m_b1 and m_b2 are set on entry. // -// if b1.bbJumpDest == b2.bbJumpDest, it transforms -// b1 : brtrue(t1, b3) -// b2 : brtrue(t2, bx) -// b3 : +// Case 1: if b1.bbJumpDest == b2.bbJumpDest, it transforms +// B1 : brtrue(t1, Bx) +// B2 : brtrue(t2, Bx) +// B3 : // to -// b1 : brtrue((!t1) && t2, bx) -// b3 : -// -// if b1.bbJumpDest == b2->bbNext, it transforms -// b1 : brtrue(t1, b3) -// b2 : brtrue(t2, bx) -// b3 : +// B1 : brtrue(t1|t2, BX) +// B3 : +// +// For example, (x == 0 && y == 0 && z == 0) generates +// B1: GT_JTRUE (BBJ_COND), jump to B4 +// B2: GT_JTRUE (BBJ_COND), jump to B4 +// B3: GT_RETURN (BBJ_RETURN) +// B4: GT_RETURN (BBJ_RETURN) +// and B1 and B2 are folded into B1: +// B1: GT_JTRUE (BBJ_COND), jump to B4 +// B3: GT_RETURN (BBJ_RETURN) +// B4: GT_RETURN (BBJ_RETURN) +// +// Case 2: if B1.bbJumpDest == B2->bbNext, it transforms +// B1 : brtrue(t1, B3) +// B2 : brtrue(t2, Bx) +// B3 : // to -// b1 : brtrue((!t1) && t2, bx) -// b3 : +// B1 : brtrue((!t1) && t2, Bx) +// B3 : // -bool OptBoolsDsc::optOptimizeBoolsCondBlock(Compiler* comp) +bool OptBoolsDsc::optOptimizeBoolsCondBlock() { - assert(b1 != nullptr && b2 != nullptr && b3 == nullptr); - - bool changeCond = false; + assert(m_b1 != nullptr && m_b2 != nullptr && m_b3 == nullptr); - // Check if b1 and b2 jump to the same target and get back pointers to t1 and t2 tree nodes + // Check if m_b1 and m_b2 jump to the same target and get back pointers to m_testInfo1 and t2 tree nodes - this->t3 = nullptr; - this->sameTarget = false; // Do b1 and b2 have the same bbJumpDest? + m_t3 = nullptr; + m_sameTarget = false; // Do m_b1 and m_b2 have the same bbJumpDest? Statement* const s1 = optOptimizeBoolsChkBlkCond(); if (s1 == nullptr) { - return changeCond; + return false; } - // Find the branch conditions of b1 and b2 + // Find the branch conditions of m_b1 and m_b2 - GenTree* const c1 = optIsBoolComp(comp, &t1); - if (c1 == nullptr) + m_c1 = optIsBoolComp(&m_testInfo1); + if (m_c1 == nullptr) { - return changeCond; + return false; } - this->c1 = c1; - GenTree* const c2 = optIsBoolComp(comp, &t2); - if (c2 == nullptr) + m_c2 = optIsBoolComp(&m_testInfo2); + if (m_c2 == nullptr) { - return changeCond; + return false; } - this->c2 = c2; - // Find the type and cost conditions of t1 and t2 + // Find the type and cost conditions of m_testInfo1 and m_testInfo2 - if (!optOptimizeBoolsChkTypeCostCond(comp)) + if (!optOptimizeBoolsChkTypeCostCond()) { - return changeCond; + return false; } // Get the fold operator and the comparison operator genTreeOps foldOp; genTreeOps cmpOp; - var_types foldType = this->c1->TypeGet(); + var_types foldType = m_c1->TypeGet(); if (varTypeIsGC(foldType)) { foldType = TYP_I_IMPL; } - assert(t1.compTree->gtOper == GT_EQ || t1.compTree->gtOper == GT_NE); + assert(m_testInfo1.compTree->gtOper == GT_EQ || m_testInfo1.compTree->gtOper == GT_NE); - if (sameTarget) + if (m_sameTarget) { // Both conditions must be the same - if (t1.compTree->gtOper != t2.compTree->gtOper) + if (m_testInfo1.compTree->gtOper != m_testInfo2.compTree->gtOper) { - return changeCond; + return false; } - if (t1.compTree->gtOper == GT_EQ) + if (m_testInfo1.compTree->gtOper == 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 @@ -7774,15 +7770,15 @@ bool OptBoolsDsc::optOptimizeBoolsCondBlock(Compiler* comp) } else { - // The b1 condition must be the reverse of the b2 condition because the only operators + // 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 (t1.compTree->gtOper == t2.compTree->gtOper) + if (m_testInfo1.compTree->gtOper == m_testInfo2.compTree->gtOper) { - return changeCond; + return false; } - if (t1.compTree->gtOper == GT_EQ) + if (m_testInfo1.compTree->gtOper == GT_EQ) { // 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 @@ -7802,58 +7798,56 @@ bool OptBoolsDsc::optOptimizeBoolsCondBlock(Compiler* comp) // Anding requires both values to be 0 or 1 - if ((foldOp == GT_AND) && (!t1.isBool || !t2.isBool)) + if ((foldOp == GT_AND) && (!m_testInfo1.isBool || !m_testInfo2.isBool)) { - return changeCond; + return false; } // // Now update the trees // - this->foldOp = foldOp; - this->foldType = foldType; - this->cmpOp = cmpOp; + m_foldOp = foldOp; + m_foldType = foldType; + m_cmpOp = cmpOp; - optOptimizeBoolsUpdateTrees(comp); - - // Update the change to true to continue the bool optimization for the rest of the BB chain - changeCond = true; + optOptimizeBoolsUpdateTrees(); #ifdef DEBUG - if (comp->verbose) + if (m_comp->verbose) { - printf("Folded %sboolean conditions of " FMT_BB " and " FMT_BB " to :\n", - this->c2->OperIsLeaf() ? "" : "non-leaf ", b1->bbNum, b2->bbNum); - comp->gtDispStmt(s1); + printf("Folded %sboolean conditions of " FMT_BB " and " FMT_BB " to :\n", m_c2->OperIsLeaf() ? "" : "non-leaf ", + m_b1->bbNum, m_b2->bbNum); + m_comp->gtDispStmt(s1); printf("\n"); } #endif - return changeCond; + // Return true to continue the bool optimization for the rest of the BB chain + return true; } -// +//----------------------------------------------------------------------------- // optOptimizeBoolsChkBlkCond: Checks block conditions if it can be boolean optimized // // Return: -// s1 If all conditions pass, returns the last statement of b1. +// If all conditions pass, returns the last statement of m_b1, else return nullptr. // // Notes: -// This method checks if the second or third block contains only one statement, and -// checks if tree operators are of the right type, e.g, GT_JTRUE, GT_RETURN. +// This method checks if the second (and third block for cond/return/return case) contains only one statement, +// and checks if tree operators are of the right type, e.g, GT_JTRUE, GT_RETURN. // -// When called, b1, b2 are set and b3 is set for GT_RETURN, where -// b1 is the first block, b2 is the second block and b3 is the third block. -// If it passes all the conditions, t1, t2 and t3 are set to the root nodes of b1, b2 and b3 each. -// SameTarget is also updated to true if b1 and b2 jump to the same destination. +// On entry, m_b1, m_b2 are set and m_b3 is set for cond/return/return case. +// If it passes all the conditions, m_testInfo1.testTree, m_testInfo2.testTree and m_t3 are set +// to the root nodes of m_b1, m_b2 and m_b3 each. +// SameTarget is also updated to true if m_b1 and m_b2 jump to the same destination. // Statement* OptBoolsDsc::optOptimizeBoolsChkBlkCond() { - assert(b1 != nullptr && b2 != nullptr); + assert(m_b1 != nullptr && m_b2 != nullptr); bool optReturnBlock = false; - if (b3 != nullptr) + if (m_b3 != nullptr) { optReturnBlock = true; } @@ -7862,7 +7856,7 @@ Statement* OptBoolsDsc::optOptimizeBoolsChkBlkCond() if (!optReturnBlock) { - if (b1->bbJumpDest == b2->bbJumpDest) + if (m_b1->bbJumpDest == m_b2->bbJumpDest) { // Given the following sequence of blocks : // B1: brtrue(t1, BX) @@ -7872,9 +7866,9 @@ Statement* OptBoolsDsc::optOptimizeBoolsChkBlkCond() // B1: brtrue(t1|t2, BX) // B3: - this->sameTarget = true; + m_sameTarget = true; } - else if (b1->bbJumpDest == b2->bbNext) + else if (m_b1->bbJumpDest == m_b2->bbNext) { // Given the following sequence of blocks : // B1: brtrue(t1, B3) @@ -7884,7 +7878,7 @@ Statement* OptBoolsDsc::optOptimizeBoolsChkBlkCond() // B1: brtrue((!t1)&&t2, BX) // B3: - this->sameTarget = false; + m_sameTarget = false; } else { @@ -7893,36 +7887,36 @@ Statement* OptBoolsDsc::optOptimizeBoolsChkBlkCond() } else { - // Does b1 jump to b3? + // Does m_b1 jump to m_b3? // One example: Given the following sequence of blocks : // B1: brtrue(!t1, B3) // B2: return(t2) // B3: return(false) // we will try to fold it to : // B1: return(t1|t2) - if (b1->bbJumpDest != b2->bbNext) + if (m_b1->bbJumpDest != m_b3) { return nullptr; } } - // Find the block conditions of b1 and b2 + // Find the block conditions of m_b1 and m_b2 - if (b2->countOfInEdges() > 1 || (optReturnBlock && b3->countOfInEdges() > 1)) + if (m_b2->countOfInEdges() > 1 || (optReturnBlock && m_b3->countOfInEdges() > 1)) { return nullptr; } // Find the condition for the first block - Statement* s1 = b1->lastStmt(); + Statement* s1 = m_b1->lastStmt(); GenTree* testTree1 = s1->GetRootNode(); - noway_assert(testTree1->gtOper == GT_JTRUE); + assert(testTree1->gtOper == GT_JTRUE); // The second and the third block must contain a single statement - Statement* s2 = b2->firstStmt(); + Statement* s2 = m_b2->firstStmt(); if (s2->GetPrevStmt() != s2) { return nullptr; @@ -7932,7 +7926,7 @@ Statement* OptBoolsDsc::optOptimizeBoolsChkBlkCond() if (!optReturnBlock) { - noway_assert(testTree2->gtOper == GT_JTRUE); + assert(testTree2->gtOper == GT_JTRUE); } else { @@ -7941,7 +7935,7 @@ Statement* OptBoolsDsc::optOptimizeBoolsChkBlkCond() return nullptr; } - Statement* s3 = b3->firstStmt(); + Statement* s3 = m_b3->firstStmt(); if (s3->GetPrevStmt() != s3) { return nullptr; @@ -7969,65 +7963,62 @@ Statement* OptBoolsDsc::optOptimizeBoolsChkBlkCond() return nullptr; } - this->t3 = testTree3; + m_t3 = testTree3; } - this->t1.testTree = testTree1; - this->t2.testTree = testTree2; + m_testInfo1.testTree = testTree1; + m_testInfo2.testTree = testTree2; return s1; } -// +//----------------------------------------------------------------------------- // optOptimizeBoolsChkTypeCostCond: Checks if type conditions meet the folding condition, and // if cost to fold is not too expensive // -// Arguments: -// comp Instance of Compiler class -// // Return: // True if it meets type conditions and cost conditions. Else false. // -bool OptBoolsDsc::optOptimizeBoolsChkTypeCostCond(Compiler* comp) +bool OptBoolsDsc::optOptimizeBoolsChkTypeCostCond() { - noway_assert(t1.compTree->OperIs(GT_EQ, GT_NE) && t1.compTree->AsOp()->gtOp1 == c1); - noway_assert(t2.compTree->OperIs(GT_EQ, GT_NE) && t2.compTree->AsOp()->gtOp1 == c2); + 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); // // Leave out floats where the bit-representation is more complicated // - there are two representations for 0. // - if (varTypeIsFloating(c1->TypeGet()) || varTypeIsFloating(c2->TypeGet())) + if (varTypeIsFloating(m_c1->TypeGet()) || varTypeIsFloating(m_c2->TypeGet())) { return false; } // Make sure the types involved are of the same sizes - if (genTypeSize(c1->TypeGet()) != genTypeSize(c2->TypeGet())) + if (genTypeSize(m_c1->TypeGet()) != genTypeSize(m_c2->TypeGet())) { return false; } - if (genTypeSize(t1.compTree->TypeGet()) != genTypeSize(t2.compTree->TypeGet())) + if (genTypeSize(m_testInfo1.compTree->TypeGet()) != genTypeSize(m_testInfo2.compTree->TypeGet())) { return false; } #ifdef TARGET_ARMARCH // Skip the small operand which we cannot encode. - if (varTypeIsSmall(c1->TypeGet())) + if (varTypeIsSmall(m_c1->TypeGet())) return false; #endif // The second condition must not contain side effects - if (c2->gtFlags & GTF_GLOB_EFFECT) + if (m_c2->gtFlags & GTF_GLOB_EFFECT) { return false; } // The second condition must not be too expensive - comp->gtPrepareCost(c2); + m_comp->gtPrepareCost(m_c2); - if (c2->GetCostEx() > 12) + if (m_c2->GetCostEx() > 12) { return false; } @@ -8035,53 +8026,43 @@ bool OptBoolsDsc::optOptimizeBoolsChkTypeCostCond(Compiler* comp) return true; } -// +//----------------------------------------------------------------------------- // optOptimizeBoolsUpdateTrees: Fold the trees based on fold type and comparison type, // update the edges, unlink removed blocks and update loop table // -// Arguments: -// comp Instance of Compiler class -// -// Notes: -// The pOptBoolsDsc has below key members: -// b1: The first Basic Block -// b2: The second Basic Block -// b3: The thrid Basic Block. null if both b1 and b2 are BBJ_COND -// t1: The test info for the first test tree -// t2: The second info for the second test tree -// foldOp: The fold operator of GT_AND or GT_OR -// foldType: The type of the folded tree -// cmpOp: The comparison operator of GT_EQ or GT_NE -// -void OptBoolsDsc::optOptimizeBoolsUpdateTrees(Compiler* comp) +void OptBoolsDsc::optOptimizeBoolsUpdateTrees() { - assert(b1 != nullptr && b2 != nullptr); + assert(m_b1 != nullptr && m_b2 != nullptr); bool optReturnBlock = false; - if (b3 != nullptr) + if (m_b3 != nullptr) { optReturnBlock = true; } - assert(foldOp != NULL && foldType != NULL && c1 != nullptr && c2 != nullptr); + assert(m_foldOp != NULL && m_foldType != NULL && m_c1 != nullptr && m_c2 != nullptr); - GenTree* cmpOp1 = comp->gtNewOperNode(foldOp, foldType, c1, c2); - if (t1.isBool && t2.isBool) + GenTree* cmpOp1 = m_comp->gtNewOperNode(m_foldOp, m_foldType, m_c1, m_c2); + if (m_testInfo1.isBool && m_testInfo2.isBool) { // When we 'OR'/'AND' two booleans, the result is boolean as well cmpOp1->gtFlags |= GTF_BOOLEAN; } - GenTree* t1Comp = t1.compTree; - t1Comp->SetOper(cmpOp); + GenTree* t1Comp = m_testInfo1.compTree; + t1Comp->SetOper(m_cmpOp); t1Comp->AsOp()->gtOp1 = cmpOp1; - t1Comp->AsOp()->gtOp2->gtType = foldType; // Could have been varTypeIsGC() + t1Comp->AsOp()->gtOp2->gtType = m_foldType; // Could have been varTypeIsGC() if (optReturnBlock) { - // Update tree when b1: BBJ_COND and b2: GT_RETURN (BBJ_RETURN) + // Update tree when m_b1 is BBJ_COND and m_b2 and m_b3 are GT_RETURN (BBJ_RETURN) t1Comp->AsOp()->gtOp2->AsIntCon()->gtIconVal = 0; - t1.testTree->gtOper = GT_RETURN; - t1.testTree->gtType = t2.testTree->gtType; + m_testInfo1.testTree->gtOper = GT_RETURN; + m_testInfo1.testTree->gtType = m_testInfo2.testTree->gtType; + + // Update the return count of flow graph + assert(m_comp->fgReturnCount >= 2); + --m_comp->fgReturnCount; } #if FEATURE_SET_FLAGS @@ -8094,8 +8075,8 @@ void OptBoolsDsc::optOptimizeBoolsUpdateTrees(Compiler* comp) // Make sure that the GTF_SET_FLAGS bit is cleared. // Fix 388436 ARM JitStress WP7 - c1->gtFlags &= ~GTF_SET_FLAGS; - c2->gtFlags &= ~GTF_SET_FLAGS; + m_c1->gtFlags &= ~GTF_SET_FLAGS; + m_c2->gtFlags &= ~GTF_SET_FLAGS; // The new top level node that we just created does feed directly into // a comparison against zero, so set the GTF_SET_FLAGS bit so that @@ -8110,38 +8091,38 @@ void OptBoolsDsc::optOptimizeBoolsUpdateTrees(Compiler* comp) if (!optReturnBlock) { - // Update edges if b1: BBJ_COND and b2: BBJ_COND + // Update edges if m_b1: BBJ_COND and m_b2: BBJ_COND - flowList* edge1 = comp->fgGetPredForBlock(b1->bbJumpDest, b1); + flowList* edge1 = m_comp->fgGetPredForBlock(m_b1->bbJumpDest, m_b1); flowList* edge2; - if (sameTarget) + if (m_sameTarget) { - edge2 = comp->fgGetPredForBlock(b2->bbJumpDest, b2); + edge2 = m_comp->fgGetPredForBlock(m_b2->bbJumpDest, m_b2); } else { - edge2 = comp->fgGetPredForBlock(b2->bbNext, b2); + edge2 = m_comp->fgGetPredForBlock(m_b2->bbNext, m_b2); - comp->fgRemoveRefPred(b1->bbJumpDest, b1); + m_comp->fgRemoveRefPred(m_b1->bbJumpDest, m_b1); - b1->bbJumpDest = b2->bbJumpDest; + m_b1->bbJumpDest = m_b2->bbJumpDest; - comp->fgAddRefPred(b2->bbJumpDest, b1); + m_comp->fgAddRefPred(m_b2->bbJumpDest, m_b1); } - noway_assert(edge1 != nullptr); - noway_assert(edge2 != nullptr); + assert(edge1 != nullptr); + assert(edge2 != nullptr); BasicBlock::weight_t edgeSumMin = edge1->edgeWeightMin() + edge2->edgeWeightMin(); BasicBlock::weight_t edgeSumMax = edge1->edgeWeightMax() + edge2->edgeWeightMax(); if ((edgeSumMax >= edge1->edgeWeightMax()) && (edgeSumMax >= edge2->edgeWeightMax())) { - edge1->setEdgeWeights(edgeSumMin, edgeSumMax, b1->bbJumpDest); + edge1->setEdgeWeights(edgeSumMin, edgeSumMax, m_b1->bbJumpDest); } else { - edge1->setEdgeWeights(BB_ZERO_WEIGHT, BB_MAX_WEIGHT, b1->bbJumpDest); + edge1->setEdgeWeights(BB_ZERO_WEIGHT, BB_MAX_WEIGHT, m_b1->bbJumpDest); } } @@ -8149,277 +8130,229 @@ void OptBoolsDsc::optOptimizeBoolsUpdateTrees(Compiler* comp) if (optReturnBlock) { - comp->fgRemoveRefPred(b1->bbJumpDest, b1); - - noway_assert(!b2->bbJumpDest); - b1->bbJumpDest = b2->bbJumpDest; - b1->bbJumpKind = b2->bbJumpKind; - b1->bbJumpSwt = b2->bbJumpSwt; - - noway_assert(b1->bbJumpKind == BBJ_RETURN); - noway_assert(b2->bbJumpKind == BBJ_RETURN); - noway_assert(b1->bbNext == b2); - noway_assert(b2->bbNext == b3); - noway_assert(b3); + m_b1->bbJumpDest = nullptr; + m_b1->bbJumpKind = BBJ_RETURN; +#ifdef DEBUG + m_b1->bbJumpSwt = m_b2->bbJumpSwt; +#endif + assert(m_b2->bbJumpKind == BBJ_RETURN); + assert(m_b1->bbNext == m_b2); + assert(m_b3 != nullptr); } else { - noway_assert(b1->bbJumpKind == BBJ_COND); - noway_assert(b2->bbJumpKind == BBJ_COND); - noway_assert(b1->bbJumpDest == b2->bbJumpDest); - noway_assert(b1->bbNext == b2); - noway_assert(b2->bbNext); + assert(m_b1->bbJumpKind == BBJ_COND); + assert(m_b2->bbJumpKind == BBJ_COND); + assert(m_b1->bbJumpDest == m_b2->bbJumpDest); + assert(m_b1->bbNext == m_b2); + assert(m_b2->bbNext != nullptr); + } + + if (!optReturnBlock) + { + // Update bbRefs and bbPreds + // + // Replace pred 'm_b2' for 'm_b2->bbNext' with 'm_b1' + // Remove pred 'm_b2' for 'm_b2->bbJumpDest' + m_comp->fgReplacePred(m_b2->bbNext, m_b2, m_b1); + m_comp->fgRemoveRefPred(m_b2->bbJumpDest, m_b2); } // Get rid of the second block - comp->fgUnlinkBlock(b2); - b2->bbFlags |= BBF_REMOVED; - // If b2 was the last block of a try or handler, update the EH table. - comp->ehUpdateForDeletedBlock(b2); + m_comp->fgUnlinkBlock(m_b2); + m_b2->bbFlags |= BBF_REMOVED; + // If m_b2 was the last block of a try or handler, update the EH table. + m_comp->ehUpdateForDeletedBlock(m_b2); if (optReturnBlock) { // Get rid of the third block - comp->fgUnlinkBlock(b3); - b3->bbFlags |= BBF_REMOVED; - // If b3 was the last block of a try or handler, update the EH table. - comp->ehUpdateForDeletedBlock(b3); - } - - if (!optReturnBlock) - { - // Update bbRefs and bbPreds - // - // Replace pred 'b2' for 'b2->bbNext' with 'b1' - // Remove pred 'b2' for 'b2->bbJumpDest' - comp->fgReplacePred(b2->bbNext, b2, b1); - comp->fgRemoveRefPred(b2->bbJumpDest, b2); + m_comp->fgUnlinkBlock(m_b3); + m_b3->bbFlags |= BBF_REMOVED; + // If m_b3 was the last block of a try or handler, update the EH table. + m_comp->ehUpdateForDeletedBlock(m_b3); } // Update loop table - comp->fgUpdateLoopsAfterCompacting(b1, b2); + m_comp->fgUpdateLoopsAfterCompacting(m_b1, m_b2); if (optReturnBlock) { - comp->fgUpdateLoopsAfterCompacting(b1, b3); + m_comp->fgUpdateLoopsAfterCompacting(m_b1, m_b3); } } -// -// optOptimizeBoolsReturnBlock: Optimize boolean when b1.bbJumpKind is BBJ_COND and b2.bbJumpKind is BBJ_RETURN -// -// Arguments: -// comp The instance of Compiler class +//----------------------------------------------------------------------------- +// optOptimizeBoolsReturnBlock: Optimize boolean when m_b1 is BBJ_COND and m_b2 and m_b3 are BBJ_RETURN // // Returns: -// changeReturn Set to true if boolean optimization is done and b1, b2 and b3 are folded into b1. +// true if boolean optimization is done and m_b1, m_b2 and m_b3 are folded into m_b1, else false. // // Notes: -// Below members of OptBoolsDsc are set when called: -// b1 The first Basic Block with the BBJ_COND conditional jump type -// b2 The next basic block of b1 with the BBJ_RETURN -// b3 The next basic block of b2 with GT_RETURN -// -// For example, (x==0 && y==0) generates: -// b1: GT_JTRUE (BBJ_COND) -// b2: GT_RETURN (BBJ_RETURN) -// b3: GT_RETURN (BBJ_RETURN), -// and it is folded into -// b1: GT_RETURN (BBJ_RETURN) +// m_b1, m_b2, and m_b3 of OptBoolsDsc are set on entry. // -// if b1.bbJumpDest == b2.bbNext, it transforms -// b1 : brtrue(t1, b3) -// b2 : ret(t2) -// b3 : ret(0) +// if B1.bbJumpDest == b3, it transforms +// B1 : brtrue(t1, B3) +// B2 : ret(t2) +// B3 : ret(0) // to -// b1 : ret((!t1) && t2) +// B1 : ret((!t1) && t2) // -bool OptBoolsDsc::optOptimizeBoolsReturnBlock(Compiler* comp) +// For example, (x==0 && y==0) generates: +// B1: GT_JTRUE (BBJ_COND), jumps to B3 +// B2: GT_RETURN (BBJ_RETURN) +// B3: GT_RETURN (BBJ_RETURN), +// and it is folded into +// B1: GT_RETURN (BBJ_RETURN) +// +bool OptBoolsDsc::optOptimizeBoolsReturnBlock() { - assert(b1 != nullptr && b2 != nullptr && b3 != nullptr); - - bool changeReturn = false; + assert(m_b1 != nullptr && m_b2 != nullptr && m_b3 != nullptr); - sameTarget = false; + m_sameTarget = false; Statement* const s1 = optOptimizeBoolsChkBlkCond(); if (s1 == nullptr) { - return changeReturn; + return false; } - // Find the branch conditions of b1 and b2 + // Find the branch conditions of m_b1 and m_b2 - GenTree* const op1 = optIsBoolComp(comp, &t1); - if (op1 == nullptr) + m_c1 = optIsBoolComp(&m_testInfo1); + if (m_c1 == nullptr) { - return changeReturn; + return false; } - c1 = op1; - GenTree* const op2 = optIsBoolComp(comp, &t2); - if (op2 == nullptr) + m_c2 = optIsBoolComp(&m_testInfo2); + if (m_c2 == nullptr) { - return changeReturn; + return false; } - c2 = op2; - // Find the type and cost conditions of t1 and t2 + // Find the type and cost conditions of m_testInfo1 and m_testInfo2 - if (!optOptimizeBoolsChkTypeCostCond(comp)) + if (!optOptimizeBoolsChkTypeCostCond()) { - return changeReturn; + return false; } - // Get the fold operator and the comparison operator + // Get the fold operator (m_foldOp, e.g., GT_OR/GT_AND) and + // the comparison operator (m_cmpOp, e.g., GT_EQ/GT_NE) - var_types foldType = c1->TypeGet(); + var_types foldType = m_c1->TypeGet(); if (varTypeIsGC(foldType)) { foldType = TYP_I_IMPL; } - this->foldType = foldType; - - foldOp = GT_NONE; - cmpOp = GT_NONE; - - optReturnGetFoldAndCompOper(); - if (foldOp == GT_NONE || cmpOp == GT_NONE) - { - return changeReturn; - } - - // Now update the trees - - optOptimizeBoolsUpdateTrees(comp); - - // Update the change to true to continue the bool optimization for the rest of the BB chain - changeReturn = true; - -#ifdef DEBUG - if (comp->verbose) - { - printf("Folded %sboolean conditions of " FMT_BB ", " FMT_BB " and " FMT_BB " to :\n", - c2->OperIsLeaf() ? "" : "non-leaf ", b1->bbNum, b2->bbNum, b3->bbNum); - comp->gtDispStmt(s1); - printf("\n"); - } -#endif + m_foldType = foldType; - return changeReturn; -} + m_foldOp = GT_NONE; + m_cmpOp = GT_NONE; -// -// optReturnGetFoldAndCompOper: Based on t1 and t2 conditions, determine fold type and comparison type -// -// Notes: -// For example, the fold operator (foldOp) of below tree is GT_OR, and -// comparison operator (cmpOp) is GT_EQ. -// * RETURN int -// \--* EQ int -// +--* OR int -// | +--* LCL_VAR int V00 arg0 -// | \--* LCL_VAR int V01 arg1 -// \--* CNS_INT int 0 -// -// Below members of OptBoolsDsc are set when called: -// t1: The first test info -// t2: The second test info -// t3: The tree of b3 -// -// On success, below values are set: -// foldOp: the fold operator of GT_AND or GT_OR. -// cmpOp: the comparison operator of GT_EQ or GT_NE -// -void OptBoolsDsc::optReturnGetFoldAndCompOper() -{ - assert(t1.compTree != nullptr && t2.compTree != nullptr && t3 != nullptr); - - genTreeOps foldOper; - genTreeOps cmpOper; + genTreeOps foldOp; + genTreeOps cmpOp; - ssize_t it1val = t1.compTree->AsOp()->gtOp2->AsIntCon()->gtIconVal; - ssize_t it2val = t2.compTree->AsOp()->gtOp2->AsIntCon()->gtIconVal; - ssize_t it3val = t3->AsOp()->gtOp1->AsIntCon()->gtIconVal; + ssize_t it1val = m_testInfo1.compTree->AsOp()->gtOp2->AsIntCon()->gtIconVal; + ssize_t it2val = m_testInfo2.compTree->AsOp()->gtOp2->AsIntCon()->gtIconVal; + ssize_t it3val = m_t3->AsOp()->gtOp1->AsIntCon()->gtIconVal; - if ((t1.compTree->gtOper == GT_NE && t2.compTree->gtOper == GT_EQ) && (it1val == 0 && it2val == 0 && it3val == 0)) + if ((m_testInfo1.compTree->gtOper == GT_NE && m_testInfo2.compTree->gtOper == GT_EQ) && + (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 - foldOper = GT_OR; - cmpOper = GT_EQ; + foldOp = GT_OR; + cmpOp = GT_EQ; } - else if ((t1.compTree->gtOper == GT_EQ && t2.compTree->gtOper == GT_NE) && + else if ((m_testInfo1.compTree->gtOper == GT_EQ && m_testInfo2.compTree->gtOper == GT_NE) && (it1val == 0 && it2val == 0 && it3val == 0)) { // Case: x == 1 && y ==1 // t1:c1!=1 t2:c2==1 t3:c3==0 is reversed from optIsBoolComp() to: t1:c1==0 t2:c2!=0 t3:c3==0 // ==> true if (c1&c2)!=0 - foldOper = GT_AND; - cmpOper = GT_NE; + foldOp = GT_AND; + cmpOp = GT_NE; } - else if ((t1.compTree->gtOper == GT_EQ && t2.compTree->gtOper == GT_EQ) && + else if ((m_testInfo1.compTree->gtOper == GT_EQ && m_testInfo2.compTree->gtOper == GT_EQ) && (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 - foldOper = GT_AND; - cmpOper = GT_EQ; + foldOp = GT_AND; + cmpOp = GT_EQ; } - else if ((t1.compTree->gtOper == GT_NE && t2.compTree->gtOper == GT_NE) && + else if ((m_testInfo1.compTree->gtOper == GT_NE && m_testInfo2.compTree->gtOper == GT_NE) && (it1val == 0 && it2val == 0 && it3val == 1)) { // Case: x == 1 || y == 1 - // t1:c1==1 t2:c2==1 is reversed from optIsBoolComp() to: t1:c1!=0 t2:c2!=0 t3:c3==1 + // t1:c1==1 t2:c2==1 t3:c3==1 is reversed from optIsBoolComp() to: t1:c1!=0 t2:c2!=0 t3:c3==1 // ==> true if (c1|c2)!=0 - foldOper = GT_OR; - cmpOper = GT_NE; + foldOp = GT_OR; + cmpOp = GT_NE; } else { // Require NOT operation for operand(s). Do Not fold. - return; + return false; } - if ((foldOper == GT_AND || cmpOper == GT_NE) && (!t1.isBool || !t2.isBool)) + if ((foldOp == GT_AND || cmpOp == GT_NE) && (!m_testInfo1.isBool || !m_testInfo2.isBool)) { // x == 1 && y == 1: Skip cases where x or y is greather 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 // x == 1 || y == 1: Skip cases where either x or y is greater than 1, e.g., x=2, y=0 - return; + return false; } - this->foldOp = foldOper; - this->cmpOp = cmpOper; + m_foldOp = foldOp; + m_cmpOp = cmpOp; + + // Now update the trees + + optOptimizeBoolsUpdateTrees(); + +#ifdef DEBUG + if (m_comp->verbose) + { + printf("Folded %sboolean conditions of " FMT_BB ", " FMT_BB " and " FMT_BB " to :\n", + m_c2->OperIsLeaf() ? "" : "non-leaf ", m_b1->bbNum, m_b2->bbNum, m_b3->bbNum); + m_comp->gtDispStmt(s1); + printf("\n"); + } +#endif + + // Return true to continue the bool optimization for the rest of the BB chain + return true; } -// +//----------------------------------------------------------------------------- // optOptimizeBoolsGcStress: Replace x==null with (x|x)==0 if x is a GC-type. // This will stress code-gen and the emitter to make sure they support such trees. // // Arguments: -// pOptBoolsDsc Boolean optimization descriptor -// condBlock The conditional Basic Block +// condBlock The conditional basic block // #ifdef DEBUG -void OptBoolsDsc::optOptimizeBoolsGcStress(Compiler* comp, BasicBlock* condBlock) +void OptBoolsDsc::optOptimizeBoolsGcStress(BasicBlock* condBlock) { - if (!comp->compStressCompile(comp->STRESS_OPT_BOOLS_GC, 20)) + if (!m_comp->compStressCompile(m_comp->STRESS_OPT_BOOLS_GC, 20)) { return; } - noway_assert(condBlock->bbJumpKind == BBJ_COND); + assert(condBlock->bbJumpKind == BBJ_COND); GenTree* cond = condBlock->lastStmt()->GetRootNode(); - noway_assert(cond->gtOper == GT_JTRUE); + assert(cond->gtOper == GT_JTRUE); OptTestInfo test; test.testTree = cond; - GenTree* comparand = optIsBoolComp(comp, &test); + GenTree* comparand = optIsBoolComp(&test); if (comparand == nullptr || !varTypeIsGC(comparand->TypeGet())) { @@ -8433,11 +8366,11 @@ void OptBoolsDsc::optOptimizeBoolsGcStress(Compiler* comp, BasicBlock* condBlock return; } - GenTree* comparandClone = comp->gtCloneExpr(comparand); + GenTree* comparandClone = m_comp->gtCloneExpr(comparand); noway_assert(relop->AsOp()->gtOp1 == comparand); - genTreeOps oper = comp->compStressCompile(comp->STRESS_OPT_BOOLS_GC, 50) ? GT_OR : GT_AND; - relop->AsOp()->gtOp1 = comp->gtNewOperNode(oper, TYP_I_IMPL, comparand, comparandClone); + genTreeOps oper = m_comp->compStressCompile(m_comp->STRESS_OPT_BOOLS_GC, 50) ? GT_OR : GT_AND; + relop->AsOp()->gtOp1 = m_comp->gtNewOperNode(oper, TYP_I_IMPL, comparand, comparandClone); // Comparand type is already checked, and we have const int, there is no harm // morphing it into a TYP_I_IMPL. @@ -8447,32 +8380,32 @@ void OptBoolsDsc::optOptimizeBoolsGcStress(Compiler* comp, BasicBlock* condBlock #endif -// +//----------------------------------------------------------------------------- // optIsBoolComp: Function used by folding of boolean conditionals // // Arguments: -// pOptTest The test info that the tree to test is set when called +// pOptTest The test info for the test tree // // Return: -// opr1 On success, the first operand is returned. Else NULL. +// On success, return the first operand (gtOp1) of compTree, else return nullptr. // // Notes: -// pOptTest->tree Tree node with GT_JTRUE or GT_RETURN type to check boolean condition on. -// pOptTest->compTree On success, the compare tree (i.e. GT_EQ or GT_NE node) is set -// isBool true if the comparand is boolean. Otherwise, false. +// On entry, testTree is set. +// On success, compTree is set to the compare node (i.e. GT_EQ or GT_NE) 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 that it is a boolean comparison +// 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. // // 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. // -GenTree* OptBoolsDsc::optIsBoolComp(Compiler* comp, OptTestInfo* pOptTest) +GenTree* OptBoolsDsc::optIsBoolComp(OptTestInfo* pOptTest) { pOptTest->isBool = false; - noway_assert(pOptTest->testTree->gtOper == GT_JTRUE || pOptTest->testTree->gtOper == GT_RETURN); + assert(pOptTest->testTree->gtOper == GT_JTRUE || pOptTest->testTree->gtOper == GT_RETURN); GenTree* cond = pOptTest->testTree->AsOp()->gtOp1; // The condition must be "!= 0" or "== 0" @@ -8520,9 +8453,9 @@ GenTree* OptBoolsDsc::optIsBoolComp(Compiler* comp, OptTestInfo* pOptTest) // is it a boolean local variable? unsigned lclNum = opr1->AsLclVarCommon()->GetLclNum(); - noway_assert(lclNum < comp->lvaCount); + noway_assert(lclNum < m_comp->lvaCount); - if (comp->lvaTable[lclNum].lvIsBoolean) + if (m_comp->lvaTable[lclNum].lvIsBoolean) { pOptTest->isBool = true; } @@ -8535,7 +8468,7 @@ GenTree* OptBoolsDsc::optIsBoolComp(Compiler* comp, OptTestInfo* pOptTest) // and change the true to false. if (pOptTest->isBool) { - comp->gtReverseCond(cond); + m_comp->gtReverseCond(cond); opr2->AsIntCon()->gtIconVal = 0; } else @@ -8547,7 +8480,7 @@ GenTree* OptBoolsDsc::optIsBoolComp(Compiler* comp, OptTestInfo* pOptTest) return opr1; } -// +//----------------------------------------------------------------------------- // optOptimizeBools: Folds boolean conditionals for GT_JTRUE/GR_RETURN nodes // // Notes: @@ -8557,8 +8490,7 @@ GenTree* OptBoolsDsc::optIsBoolComp(Compiler* comp, OptTestInfo* pOptTest) // "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: -// (x == 0 && y ==0) => (c1 | c2) == 0 -// +// Case 1: (x == 0 && y ==0) => (x | y) == 0 // * RETURN int // \--* EQ int // +--* OR int @@ -8566,6 +8498,44 @@ GenTree* OptBoolsDsc::optIsBoolComp(Compiler* comp, OptTestInfo* pOptTest) // | \--* 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 +// +// 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 +// in order to skip below cases when compTree is not boolean expression: +// - x == 1 && y == 1 ==> (x&y)!=0: Skip cases where x or y is greather than 1, e.g., x=3, y=1 +// - x == 1 || y == 1 ==> (x|y)!=0: Skip cases where either x or y is greater than 1, e.g., x=2, y=0 +// - x == 0 || y == 0 ==> (x&y)==0: Skip cases where x and y have opposite bits set, e.g., x=2, y=1 +// void Compiler::optOptimizeBools() { #ifdef DEBUG @@ -8597,7 +8567,7 @@ void Compiler::optOptimizeBools() // If there is no next block, we're done BasicBlock* b2 = b1->bbNext; - if (!b2) + if (b2 == nullptr) { break; } @@ -8609,26 +8579,25 @@ void Compiler::optOptimizeBools() } // The next block also needs to be a condition - OptBoolsDsc optBoolsDsc; - - optBoolsDsc.b1 = b1; - optBoolsDsc.b2 = b2; if (b2->bbJumpKind == BBJ_COND) { // When it is conditional jumps - - optBoolsDsc.b3 = nullptr; - if (optBoolsDsc.optOptimizeBoolsCondBlock(this)) + OptBoolsDsc optBoolsDsc; + optBoolsDsc.m_b1 = b1; + optBoolsDsc.m_b2 = b2; + optBoolsDsc.m_b3 = nullptr; + optBoolsDsc.m_comp = this; + if (optBoolsDsc.optOptimizeBoolsCondBlock()) { change = true; } } else if (b2->bbJumpKind == BBJ_RETURN) { - // If there is no next block after b2, we're done + // If there is no b1->bbJumpDest, we're done - BasicBlock* b3 = b2->bbNext; + BasicBlock* b3 = b1->bbJumpDest; if (b3 == nullptr) { break; @@ -8641,8 +8610,19 @@ void Compiler::optOptimizeBools() continue; } - optBoolsDsc.b3 = b3; - if (optBoolsDsc.optOptimizeBoolsReturnBlock(this)) + // b3 must be RETURN type + + if (b3->bbJumpKind != BBJ_RETURN) + { + continue; + } + + OptBoolsDsc optBoolsDsc; + optBoolsDsc.m_b1 = b1; + optBoolsDsc.m_b2 = b2; + optBoolsDsc.m_b3 = b3; + optBoolsDsc.m_comp = this; + if (optBoolsDsc.optOptimizeBoolsReturnBlock()) { change = true; } @@ -8650,7 +8630,9 @@ void Compiler::optOptimizeBools() else { #ifdef DEBUG - optBoolsDsc.optOptimizeBoolsGcStress(this, b1); + OptBoolsDsc optBoolsDsc; + optBoolsDsc.m_comp = this; + optBoolsDsc.optOptimizeBoolsGcStress(b1); #endif } } From d14053e4f093d5ea85312f59ee3c9a5783fd7434 Mon Sep 17 00:00:00 2001 From: Julie Lee Date: Fri, 9 Jul 2021 16:10:58 -0700 Subject: [PATCH 22/25] Optimize bools: hoisted jump destination check to optOptimizeBools() and added test cases --- src/coreclr/jit/compiler.h | 1 - src/coreclr/jit/optimizer.cpp | 86 ++++++++----------- .../JIT/opt/OptimizeBools/optboolsreturn.cs | 71 ++++++++++++--- 3 files changed, 98 insertions(+), 60 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 4a88348766f7f..c4e5a52b922b1 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -80,7 +80,6 @@ class Instrumentor; // defined in fgprofile.cpp class SpanningTreeVisitor; // defined in fgprofile.cpp class CSE_DataFlow; // defined in OptCSE.cpp class OptBoolsDsc; // defined in optimizer.cpp -struct OptTestInfo; #ifdef DEBUG struct IndentStack; #endif diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index 9e40bc56287fc..d774a80e35250 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -7701,7 +7701,37 @@ bool OptBoolsDsc::optOptimizeBoolsCondBlock() // Check if m_b1 and m_b2 jump to the same target and get back pointers to m_testInfo1 and t2 tree nodes m_t3 = nullptr; - m_sameTarget = false; // Do m_b1 and m_b2 have the same bbJumpDest? + + // Check if m_b1 and m_b2 have the same bbJumpDest + + if (m_b1->bbJumpDest == m_b2->bbJumpDest) + { + // Given the following sequence of blocks : + // B1: brtrue(t1, BX) + // B2: brtrue(t2, BX) + // B3: + // we will try to fold it to : + // B1: brtrue(t1|t2, BX) + // B3: + + m_sameTarget = true; + } + else if (m_b1->bbJumpDest == m_b2->bbNext) + { + // Given the following sequence of blocks : + // B1: brtrue(t1, B3) + // B2: brtrue(t2, BX) + // B3: + // we will try to fold it to : + // B1: brtrue((!t1)&&t2, BX) + // B3: + + m_sameTarget = false; + } + else + { + return false; + } Statement* const s1 = optOptimizeBoolsChkBlkCond(); if (s1 == nullptr) @@ -7852,54 +7882,6 @@ Statement* OptBoolsDsc::optOptimizeBoolsChkBlkCond() optReturnBlock = true; } - // Check jump destination condition - - if (!optReturnBlock) - { - if (m_b1->bbJumpDest == m_b2->bbJumpDest) - { - // Given the following sequence of blocks : - // B1: brtrue(t1, BX) - // B2: brtrue(t2, BX) - // B3: - // we will try to fold it to : - // B1: brtrue(t1|t2, BX) - // B3: - - m_sameTarget = true; - } - else if (m_b1->bbJumpDest == m_b2->bbNext) - { - // Given the following sequence of blocks : - // B1: brtrue(t1, B3) - // B2: brtrue(t2, BX) - // B3: - // we will try to fold it to : - // B1: brtrue((!t1)&&t2, BX) - // B3: - - m_sameTarget = false; - } - else - { - return nullptr; - } - } - else - { - // Does m_b1 jump to m_b3? - // One example: Given the following sequence of blocks : - // B1: brtrue(!t1, B3) - // B2: return(t2) - // B3: return(false) - // we will try to fold it to : - // B1: return(t1|t2) - if (m_b1->bbJumpDest != m_b3) - { - return nullptr; - } - } - // Find the block conditions of m_b1 and m_b2 if (m_b2->countOfInEdges() > 1 || (optReturnBlock && m_b3->countOfInEdges() > 1)) @@ -8582,12 +8564,18 @@ void Compiler::optOptimizeBools() if (b2->bbJumpKind == BBJ_COND) { + if ((b1->bbJumpDest != b2->bbJumpDest) && (b1->bbJumpDest != b2->bbNext)) + { + continue; + } + // When it is conditional jumps OptBoolsDsc optBoolsDsc; optBoolsDsc.m_b1 = b1; optBoolsDsc.m_b2 = b2; optBoolsDsc.m_b3 = nullptr; optBoolsDsc.m_comp = this; + if (optBoolsDsc.optOptimizeBoolsCondBlock()) { change = true; diff --git a/src/tests/JIT/opt/OptimizeBools/optboolsreturn.cs b/src/tests/JIT/opt/OptimizeBools/optboolsreturn.cs index fc160ccaca3a8..c6c3964191d36 100644 --- a/src/tests/JIT/opt/OptimizeBools/optboolsreturn.cs +++ b/src/tests/JIT/opt/OptimizeBools/optboolsreturn.cs @@ -63,7 +63,25 @@ public static int Main() if (!AreZero(0, 0)) { - Console.WriteLine("CBoolTest:AreZero failed"); + Console.WriteLine("CBoolTest:AreZero(0, 0) failed"); + return 101; + } + + if (AreZero(1, 1)) + { + Console.WriteLine("CBoolTest:AreZero(1, 1) failed"); + return 101; + } + + if (AreZero(0, 2)) + { + Console.WriteLine("CBoolTest:AreZero(0, 2) failed"); + return 101; + } + + if (AreZero(3, 0)) + { + Console.WriteLine("CBoolTest:AreZero(3, 0) failed"); return 101; } @@ -79,27 +97,39 @@ public static int Main() return 101; } - if (AreZero(1, 1)) + if (!AreZero2(0, 0)) { - Console.WriteLine("CBoolTest:AreZero(1, 1) failed"); + Console.WriteLine("CBoolTest:AreZero2(0, 0) failed"); return 101; } - if (!AreZero2(0, 0)) + if (AreZero2(2, 1)) { - Console.WriteLine("CBoolTest:AreZero2 failed"); + Console.WriteLine("CBoolTest:AreZero2(2, 1) failed"); return 101; } if (!AreZero3(0, 0, 0)) { - Console.WriteLine("CBoolTest:AreZero3 failed"); + Console.WriteLine("CBoolTest:AreZero3(0, 0, 0) failed"); + return 101; + } + + if (AreZero3(0, 1, 2)) + { + Console.WriteLine("CBoolTest:AreZero3(0, 1, 2) failed"); return 101; } if (!AreZero4(0, 0, 0, 0)) { - Console.WriteLine("CBoolTest:AreZero4 failed"); + Console.WriteLine("CBoolTest:AreZero4(0, 0, 0, 0) failed"); + return 101; + } + + if (AreZero4(0, 1, 2, 3)) + { + Console.WriteLine("CBoolTest:AreZero4(0, 1, 2, 3) failed"); return 101; } @@ -108,21 +138,42 @@ public static int Main() // Test if ANDing or GT_NE requires both operands to be boolean if (!AreOne(1, 1)) { - Console.WriteLine("CBoolTest:AreOne failed"); + Console.WriteLine("CBoolTest:AreOne(1, 1) failed"); + return 101; + } + + // Skip cases where x or y is greather than 1 + if (AreOne(3, 1)) + { + Console.WriteLine("CBoolTest:AreOne(1, 3) failed"); return 101; } // Test if ANDing requires both operands to be boolean if (!IsEitherZero(0, 1)) { - Console.WriteLine("CBoolTest:IsEitherZero failed"); + Console.WriteLine("CBoolTest:IsEitherZero(0, 1) failed"); + return 101; + } + + // Skip cases where x and y have opposite bits set + if (IsEitherZero(2, 1)) + { + Console.WriteLine("CBoolTest:IsEitherZero(2, 1) failed"); return 101; } // Test if GT_NE requires both operands to be boolean if (!IsEitherOne(0, 1)) { - Console.WriteLine("CBoolTest:IsEitherOne failed"); + Console.WriteLine("CBoolTest:IsEitherOne(0, 1) failed"); + return 101; + } + + // Skip cases where either x or y is greater than 1 + if (IsEitherOne(2, 0)) + { + Console.WriteLine("CBoolTest:IsEitherOne(2, 0) failed"); return 101; } From 12c7a8a51b1b47246e42cf2fdee28f5e47acd0f8 Mon Sep 17 00:00:00 2001 From: Julie Lee Date: Fri, 9 Jul 2021 16:21:06 -0700 Subject: [PATCH 23/25] format patch --- src/coreclr/jit/optimizer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index d774a80e35250..098530af145ab 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -7700,7 +7700,7 @@ bool OptBoolsDsc::optOptimizeBoolsCondBlock() // Check if m_b1 and m_b2 jump to the same target and get back pointers to m_testInfo1 and t2 tree nodes - m_t3 = nullptr; + m_t3 = nullptr; // Check if m_b1 and m_b2 have the same bbJumpDest From 0e9660814b670d38ac61e60db4621ecf06a6be4a Mon Sep 17 00:00:00 2001 From: Julie Lee Date: Tue, 13 Jul 2021 14:06:15 -0700 Subject: [PATCH 24/25] Moved initialization to OptBoolsDsc constructor --- src/coreclr/jit/optimizer.cpp | 64 ++++++++++++++++------------------- 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index 098530af145ab..c88be2d653829 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -7625,13 +7625,21 @@ struct OptTestInfo class OptBoolsDsc { public: + OptBoolsDsc(BasicBlock* b1, BasicBlock* b2, Compiler* comp) + { + m_b1 = b1; + m_b2 = b2; + m_b3 = nullptr; + m_comp = comp; + } + +private: BasicBlock* m_b1; // The first basic block with the BBJ_COND conditional jump type BasicBlock* m_b2; // The next basic block of m_b1. Either BBJ_COND or BBJ_RETURN type BasicBlock* m_b3; // m_b1->bbJumpDest. Null if m_b2 is not a return block. Compiler* m_comp; // The pointer to the Compiler instance -private: OptTestInfo m_testInfo1; // The first test info OptTestInfo m_testInfo2; // The second test info GenTree* m_t3; // The root node of the first statement of m_b3 @@ -7647,9 +7655,9 @@ class OptBoolsDsc public: bool optOptimizeBoolsCondBlock(); - bool optOptimizeBoolsReturnBlock(); + bool optOptimizeBoolsReturnBlock(BasicBlock* b3); #ifdef DEBUG - void optOptimizeBoolsGcStress(BasicBlock* condBlock); + void optOptimizeBoolsGcStress(); #endif private: @@ -8167,11 +8175,14 @@ void OptBoolsDsc::optOptimizeBoolsUpdateTrees() //----------------------------------------------------------------------------- // optOptimizeBoolsReturnBlock: Optimize boolean when m_b1 is BBJ_COND and m_b2 and m_b3 are BBJ_RETURN // +// Arguments: +// b3: Pointer to basic block b3 +// // Returns: // true if boolean optimization is done and m_b1, m_b2 and m_b3 are folded into m_b1, else false. // // Notes: -// m_b1, m_b2, and m_b3 of OptBoolsDsc are set on entry. +// m_b1, m_b2 and m_b3 of OptBoolsDsc are set on entry. // // if B1.bbJumpDest == b3, it transforms // B1 : brtrue(t1, B3) @@ -8187,9 +8198,12 @@ void OptBoolsDsc::optOptimizeBoolsUpdateTrees() // and it is folded into // B1: GT_RETURN (BBJ_RETURN) // -bool OptBoolsDsc::optOptimizeBoolsReturnBlock() +bool OptBoolsDsc::optOptimizeBoolsReturnBlock(BasicBlock* b3) { - assert(m_b1 != nullptr && m_b2 != nullptr && m_b3 != nullptr); + assert(m_b1 != nullptr && m_b2 != nullptr); + + // m_b3 is set for cond/return/return case + m_b3 = b3; m_sameTarget = false; Statement* const s1 = optOptimizeBoolsChkBlkCond(); @@ -8314,20 +8328,17 @@ bool OptBoolsDsc::optOptimizeBoolsReturnBlock() // optOptimizeBoolsGcStress: Replace x==null with (x|x)==0 if x is a GC-type. // This will stress code-gen and the emitter to make sure they support such trees. // -// Arguments: -// condBlock The conditional basic block -// #ifdef DEBUG -void OptBoolsDsc::optOptimizeBoolsGcStress(BasicBlock* condBlock) +void OptBoolsDsc::optOptimizeBoolsGcStress() { if (!m_comp->compStressCompile(m_comp->STRESS_OPT_BOOLS_GC, 20)) { return; } - assert(condBlock->bbJumpKind == BBJ_COND); - GenTree* cond = condBlock->lastStmt()->GetRootNode(); + assert(m_b1->bbJumpKind == BBJ_COND); + GenTree* cond = m_b1->lastStmt()->GetRootNode(); assert(cond->gtOper == GT_JTRUE); @@ -8463,7 +8474,7 @@ GenTree* OptBoolsDsc::optIsBoolComp(OptTestInfo* pOptTest) } //----------------------------------------------------------------------------- -// optOptimizeBools: Folds boolean conditionals for GT_JTRUE/GR_RETURN nodes +// optOptimizeBools: Folds boolean conditionals for GT_JTRUE/GT_RETURN nodes // // Notes: // If the operand of GT_JTRUE/GT_RETURN node is GT_EQ/GT_NE of the form @@ -8560,7 +8571,9 @@ void Compiler::optOptimizeBools() continue; } - // The next block also needs to be a condition + OptBoolsDsc optBoolsDsc(b1, b2, this); + + // The next block needs to be a condition or return block. if (b2->bbJumpKind == BBJ_COND) { @@ -8570,11 +8583,6 @@ void Compiler::optOptimizeBools() } // When it is conditional jumps - OptBoolsDsc optBoolsDsc; - optBoolsDsc.m_b1 = b1; - optBoolsDsc.m_b2 = b2; - optBoolsDsc.m_b3 = nullptr; - optBoolsDsc.m_comp = this; if (optBoolsDsc.optOptimizeBoolsCondBlock()) { @@ -8583,13 +8591,8 @@ void Compiler::optOptimizeBools() } else if (b2->bbJumpKind == BBJ_RETURN) { - // If there is no b1->bbJumpDest, we're done - + // Set b3 to b1 jump destination BasicBlock* b3 = b1->bbJumpDest; - if (b3 == nullptr) - { - break; - } // b3 must not be marked as BBF_DONT_REMOVE @@ -8605,12 +8608,7 @@ void Compiler::optOptimizeBools() continue; } - OptBoolsDsc optBoolsDsc; - optBoolsDsc.m_b1 = b1; - optBoolsDsc.m_b2 = b2; - optBoolsDsc.m_b3 = b3; - optBoolsDsc.m_comp = this; - if (optBoolsDsc.optOptimizeBoolsReturnBlock()) + if (optBoolsDsc.optOptimizeBoolsReturnBlock(b3)) { change = true; } @@ -8618,9 +8616,7 @@ void Compiler::optOptimizeBools() else { #ifdef DEBUG - OptBoolsDsc optBoolsDsc; - optBoolsDsc.m_comp = this; - optBoolsDsc.optOptimizeBoolsGcStress(b1); + optBoolsDsc.optOptimizeBoolsGcStress(); #endif } } From 1cdcdb83f582d09b3fa16b2d58959cbc88eb7278 Mon Sep 17 00:00:00 2001 From: Julie Lee Date: Tue, 13 Jul 2021 14:11:20 -0700 Subject: [PATCH 25/25] format patch --- src/coreclr/jit/optimizer.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index c88be2d653829..da7ae70e888e2 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -7627,9 +7627,9 @@ class OptBoolsDsc public: OptBoolsDsc(BasicBlock* b1, BasicBlock* b2, Compiler* comp) { - m_b1 = b1; - m_b2 = b2; - m_b3 = nullptr; + m_b1 = b1; + m_b2 = b2; + m_b3 = nullptr; m_comp = comp; } @@ -8177,7 +8177,7 @@ void OptBoolsDsc::optOptimizeBoolsUpdateTrees() // // Arguments: // b3: Pointer to basic block b3 -// +// // Returns: // true if boolean optimization is done and m_b1, m_b2 and m_b3 are folded into m_b1, else false. //