diff --git a/src/coreclr/jit/lower.h b/src/coreclr/jit/lower.h index c5c771167025e..00cdeb8b63308 100644 --- a/src/coreclr/jit/lower.h +++ b/src/coreclr/jit/lower.h @@ -89,8 +89,10 @@ class Lowering final : public Phase void ContainCheckReturnTrap(GenTreeOp* node); void ContainCheckLclHeap(GenTreeOp* node); void ContainCheckRet(GenTreeUnOp* ret); +#if defined(TARGET_ARM64) || defined(TARGET_XARCH) + bool TryLowerAndOrToCCMP(GenTreeOp* tree, GenTree** next); +#endif #ifdef TARGET_ARM64 - bool TryLowerAndOrToCCMP(GenTreeOp* tree, GenTree** next); insCflags TruthifyingFlags(GenCondition cond); void ContainCheckConditionalCompare(GenTreeCCMP* ccmp); void ContainCheckNeg(GenTreeOp* neg); diff --git a/src/coreclr/jit/lowerxarch.cpp b/src/coreclr/jit/lowerxarch.cpp index 3adeccf516fd1..377cc7f9d37a5 100644 --- a/src/coreclr/jit/lowerxarch.cpp +++ b/src/coreclr/jit/lowerxarch.cpp @@ -318,6 +318,17 @@ GenTree* Lowering::LowerBinaryArithmetic(GenTreeOp* binOp) } #endif +#ifdef TARGET_XARCH + if (binOp->OperIs(GT_OR, GT_AND)) + { + GenTree* next; + if (TryLowerAndOrToCCMP(binOp, &next)) + { + return next; + } + } +#endif + ContainCheckBinary(binOp); return binOp->gtNext; @@ -8638,6 +8649,78 @@ void Lowering::ContainCheckCast(GenTreeCast* node) #endif // !defined(TARGET_64BIT) } +//------------------------------------------------------------------------ +// TryLowerAndOrToCCMP : Lower AND/OR of two conditions into test + CCMP + SETCC nodes. +// +// Arguments: +// tree - pointer to the node +// next - [out] Next node to lower if this function returns true +// +// Return Value: +// false if no changes were made +// +bool Lowering::TryLowerAndOrToCCMP(GenTreeOp* tree, GenTree** next) +{ + assert(tree->OperIs(GT_OR, GT_AND)); + + if (!comp->opts.OptimizationEnabled()) + { + return false; + } + + GenTree* lastNode = BlockRange().LastNode(); + + if (!lastNode->OperIs(GT_JTRUE, GT_RETURN)) + { + return false; + } + + GenTree* op1 = tree->gtGetOp1(); + GenTree* op2 = tree->gtGetOp2(); + + if (!op1->OperIs(GT_NE, GT_EQ) || !op2->OperIs(GT_NE, GT_EQ) || + (lastNode->OperIs(GT_JTRUE) && ((tree->OperIs(GT_OR) && (!op1->OperIs(GT_NE) || !op2->OperIs(GT_NE))) || + (tree->OperIs(GT_AND) && (!op1->OperIs(GT_EQ) || !op2->OperIs(GT_EQ))))) || + (lastNode->OperIs(GT_RETURN) && ((tree->OperIs(GT_OR) && (!op1->OperIs(GT_EQ) || !op2->OperIs(GT_EQ))) || + (tree->OperIs(GT_AND) && (!op1->OperIs(GT_NE) || !op2->OperIs(GT_NE)))))) + { + return false; + } + + GenTree* op11 = op1->gtGetOp1(); + GenTree* op12 = op1->gtGetOp2(); + GenTree* op21 = op2->gtGetOp1(); + GenTree* op22 = op2->gtGetOp2(); + + if (!op11->TypeIs(TYP_INT) || !op11->OperIs(GT_LCL_VAR, GT_CNS_INT) || !op12->TypeIs(TYP_INT) || + !op12->OperIs(GT_LCL_VAR, GT_CNS_INT) || !op21->TypeIs(TYP_INT) || !op21->OperIs(GT_LCL_VAR, GT_CNS_INT) || + !op22->TypeIs(TYP_INT) || !op22->OperIs(GT_LCL_VAR, GT_CNS_INT)) + { + return false; + } + + op1->gtGetOp1()->ClearContained(); + op1->gtGetOp2()->ClearContained(); + op2->gtGetOp1()->ClearContained(); + op2->gtGetOp2()->ClearContained(); + + op1->SetOper(GT_XOR); + op2->SetOper(GT_XOR); + + if (lastNode->OperIs(GT_JTRUE) && tree->OperIs(GT_AND)) + { + tree->SetOper(GT_OR); + tree->gtNext->gtNext->SetOper(GT_EQ); + } + + JITDUMP("Conversion was legal. Result:\n"); + DISPTREERANGE(BlockRange(), tree); + JITDUMP("\n"); + + *next = tree->gtNext; + return true; +} + //------------------------------------------------------------------------ // ContainCheckCompare: determine whether the sources of a compare node should be contained. // diff --git a/src/coreclr/jit/optimizebools.cpp b/src/coreclr/jit/optimizebools.cpp index 2346ef7e9e278..d8665df41c786 100644 --- a/src/coreclr/jit/optimizebools.cpp +++ b/src/coreclr/jit/optimizebools.cpp @@ -96,6 +96,7 @@ class OptBoolsDsc bool optOptimizeCompareChainCondBlock(); bool optOptimizeRangeTests(); bool optOptimizeBoolsReturnBlock(BasicBlock* b3); + bool optOptimizeCompareChainReturnBlock(BasicBlock* b3); #ifdef DEBUG void optOptimizeBoolsGcStress(); #endif @@ -106,6 +107,7 @@ class OptBoolsDsc bool optOptimizeBoolsChkTypeCostCond(); void optOptimizeBoolsUpdateTrees(); bool FindCompareChain(GenTree* condition, bool* isTestCondition); + void optOptimizeBoolsKeepFirstBlockAndFreeTheRest(bool optReturnBlock); }; //----------------------------------------------------------------------------- @@ -400,7 +402,8 @@ bool OptBoolsDsc::FindCompareChain(GenTree* condition, bool* isTestCondition) if (condition->OperIs(GT_EQ, GT_NE) && condOp2->IsIntegralConst()) { - ssize_t condOp2Value = condOp2->AsIntCon()->IconValue(); + INT64 condOp2Value = + condOp2->gtOper == GT_CNS_INT ? condOp2->AsIntCon()->gtIconVal : condOp2->AsLngCon()->gtLconVal; if (condOp2Value == 0) { @@ -421,7 +424,10 @@ bool OptBoolsDsc::FindCompareChain(GenTree* condition, bool* isTestCondition) *isTestCondition = true; } else if (condOp1->OperIs(GT_AND) && isPow2(static_cast(condOp2Value)) && - condOp1->gtGetOp2()->IsIntegralConst(condOp2Value)) + ((condOp1->gtGetOp2()->gtOper == GT_CNS_INT && + condOp1->gtGetOp2()->AsIntCon()->gtIconVal == condOp2Value) || + (condOp1->gtGetOp2()->gtOper == GT_CNS_LNG && + condOp1->gtGetOp2()->AsLngCon()->gtLconVal == condOp2Value))) { // Found a EQ/NE(AND(...,n),n) which will be optimized to tbz/tbnz during lowering. *isTestCondition = true; @@ -1235,39 +1241,13 @@ bool OptBoolsDsc::optOptimizeBoolsChkTypeCostCond() } //----------------------------------------------------------------------------- -// optOptimizeBoolsUpdateTrees: Fold the trees based on fold type and comparison type, -// update the edges, and unlink removed blocks +// optOptimizeBoolsKeepFirstBlockAndFreeTheRest: update the edges, and unlink removed blocks // -void OptBoolsDsc::optOptimizeBoolsUpdateTrees() +// Arguments: +// optReturnBlock - defines whether to make first block a return or condition block +// +void OptBoolsDsc::optOptimizeBoolsKeepFirstBlockAndFreeTheRest(bool optReturnBlock) { - assert(m_b1 != nullptr && m_b2 != nullptr); - - bool optReturnBlock = false; - if (m_b3 != nullptr) - { - optReturnBlock = true; - } - - assert(m_cmpOp != GT_NONE && m_c1 != nullptr && m_c2 != nullptr); - - GenTree* cmpOp1 = m_foldOp == GT_NONE ? m_c1 : m_comp->gtNewOperNode(m_foldOp, m_foldType, m_c1, m_c2); - - GenTree* t1Comp = m_testInfo1.compTree; - t1Comp->SetOper(m_cmpOp); - t1Comp->AsOp()->gtOp1 = cmpOp1; - t1Comp->AsOp()->gtOp2->gtType = m_foldType; // Could have been varTypeIsGC() - if (optReturnBlock) - { - // Update tree when m_b1 is BBJ_COND and m_b2 and m_b3 are GT_RETURN/GT_SWIFT_ERROR_RET (BBJ_RETURN) - t1Comp->AsOp()->gtOp2->AsIntCon()->gtIconVal = 0; - m_testInfo1.testTree->gtOper = m_testInfo2.testTree->OperGet(); - m_testInfo1.testTree->gtType = m_testInfo2.testTree->TypeGet(); - - // Update the return count of flow graph - assert(m_comp->fgReturnCount >= 2); - --m_comp->fgReturnCount; - } - // Recost/rethread the tree if necessary // if (m_comp->fgNodeThreading != NodeThreading::None) @@ -1360,6 +1340,39 @@ void OptBoolsDsc::optOptimizeBoolsUpdateTrees() m_b1->bbCodeOffsEnd = optReturnBlock ? m_b3->bbCodeOffsEnd : m_b2->bbCodeOffsEnd; } +void OptBoolsDsc::optOptimizeBoolsUpdateTrees() +{ + assert(m_b1 != nullptr && m_b2 != nullptr); + + bool optReturnBlock = false; + if (m_b3 != nullptr) + { + optReturnBlock = true; + } + + assert(m_cmpOp != GT_NONE && m_c1 != nullptr && m_c2 != nullptr); + + GenTree* cmpOp1 = m_foldOp == GT_NONE ? m_c1 : m_comp->gtNewOperNode(m_foldOp, m_foldType, m_c1, m_c2); + + GenTree* t1Comp = m_testInfo1.compTree; + t1Comp->SetOper(m_cmpOp); + t1Comp->AsOp()->gtOp1 = cmpOp1; + t1Comp->AsOp()->gtOp2->gtType = m_foldType; // Could have been varTypeIsGC() + if (optReturnBlock) + { + // Update tree when m_b1 is BBJ_COND and m_b2 and m_b3 are GT_RETURN/GT_SWIFT_ERROR_RET (BBJ_RETURN) + t1Comp->AsOp()->gtOp2->AsIntCon()->gtIconVal = 0; + m_testInfo1.testTree->gtOper = m_testInfo2.testTree->OperGet(); + m_testInfo1.testTree->gtType = m_testInfo2.testTree->TypeGet(); + + // Update the return count of flow graph + assert(m_comp->fgReturnCount >= 2); + --m_comp->fgReturnCount; + } + + optOptimizeBoolsKeepFirstBlockAndFreeTheRest(optReturnBlock); +} + //----------------------------------------------------------------------------- // optOptimizeBoolsReturnBlock: Optimize boolean when m_b1 is BBJ_COND and m_b2 and m_b3 are BBJ_RETURN // @@ -1737,6 +1750,82 @@ GenTree* OptBoolsDsc::optIsBoolComp(OptTestInfo* pOptTest) return opr1; } +#ifdef TARGET_XARCH +bool OptBoolsDsc::optOptimizeCompareChainReturnBlock(BasicBlock* b3) +{ + Statement* const s1 = optOptimizeBoolsChkBlkCond(); + if (s1 == nullptr) + { + return false; + } + + GenTree* cond1 = m_testInfo1.GetTestOp(); + GenTree* cond2 = m_testInfo2.GetTestOp(); + + GenTree* block3ReturnTree = m_b3->firstStmt()->GetRootNode()->AsOp()->GetReturnValue(); + + if (block3ReturnTree == nullptr || !block3ReturnTree->IsIntegralConst() || !block3ReturnTree->TypeIs(TYP_INT)) + { + return false; + } + + INT64 block3RetVal = block3ReturnTree->AsIntConCommon()->IntegralValue(); + + if (!cond1->OperIs(GT_NE) || !cond2->OperIs(GT_EQ, GT_NE) || (block3RetVal != 0 && cond2->OperIs(GT_EQ)) || + (block3RetVal != 1 && cond2->OperIs(GT_NE))) + { + return false; + }; + + GenTree* op11 = cond1->AsOp()->gtOp1; + GenTree* op12 = cond1->AsOp()->gtOp2; + GenTree* op21 = cond2->AsOp()->gtOp1; + GenTree* op22 = cond2->AsOp()->gtOp2; + + if (!op11->OperIs(GT_LCL_VAR, GT_CNS_INT) || !op12->OperIs(GT_LCL_VAR, GT_CNS_INT) || + !op21->OperIs(GT_LCL_VAR, GT_CNS_INT) || !op22->OperIs(GT_LCL_VAR, GT_CNS_INT) || !op11->TypeIs(TYP_INT) || + !op12->TypeIs(TYP_INT) || !op21->TypeIs(TYP_INT) || !op22->TypeIs(TYP_INT)) + { + return false; + } + + GenTreeOp* xorOp1 = m_comp->gtNewOperNode(GT_EQ, TYP_INT, op11, op12); + xorOp1->gtPrev = op12; + op12->gtNext = xorOp1; + op12->gtPrev = op11; + op11->gtNext = op12; + op11->gtPrev = nullptr; + GenTreeOp* xorOp2 = m_comp->gtNewOperNode(GT_EQ, TYP_INT, op21, op22); + xorOp2->gtPrev = op22; + op22->gtNext = xorOp2; + op22->gtPrev = op21; + op21->gtNext = op22; + + GenTreeOp* branchlessOrEqOp = m_comp->gtNewOperNode(GT_OR, TYP_INT, xorOp1, xorOp2); + branchlessOrEqOp->gtPrev = xorOp2; + xorOp2->gtNext = branchlessOrEqOp; + op21->gtPrev = xorOp1; + xorOp1->gtNext = op21; + + GenTreeIntCon* trueOp = m_comp->gtNewFalse(); + m_testInfo1.compTree->SetOper(cond2->OperIs(GT_EQ) ? GT_EQ : GT_NE); + m_testInfo1.compTree->gtType = TYP_INT; + + m_testInfo1.compTree->AsOp()->gtOp1 = branchlessOrEqOp; + m_testInfo1.compTree->AsOp()->gtOp2 = trueOp; + m_testInfo1.compTree->gtPrev = trueOp; + trueOp->gtNext = m_testInfo1.compTree; + trueOp->gtPrev = branchlessOrEqOp; + branchlessOrEqOp->gtNext = trueOp; + + m_testInfo1.testTree->gtOper = m_testInfo2.testTree->OperGet(); + m_testInfo1.testTree->gtType = m_testInfo2.testTree->TypeGet(); + + optOptimizeBoolsKeepFirstBlockAndFreeTheRest(true); + return true; +} +#endif + //----------------------------------------------------------------------------- // optOptimizeBools: Folds boolean conditionals for GT_JTRUE/GT_RETURN/GT_SWIFT_ERROR_RET nodes // @@ -1937,7 +2026,7 @@ PhaseStatus Compiler::optOptimizeBools() retry = true; numCond++; } -#ifdef TARGET_ARM64 +#if defined(TARGET_ARM64) || defined(TARGET_XARCH) else if (optBoolsDsc.optOptimizeCompareChainCondBlock()) { // The optimization will have merged b1 and b2. Retry the loop so that @@ -1972,6 +2061,13 @@ PhaseStatus Compiler::optOptimizeBools() change = true; numReturn++; } +#ifdef TARGET_XARCH + else if (optBoolsDsc.optOptimizeCompareChainReturnBlock(b3)) + { + change = true; + numReturn++; + } +#endif } else {