diff --git a/src/coreclr/jit/codegen.h b/src/coreclr/jit/codegen.h index aec68a7bff2a..5d89ef8d0183 100644 --- a/src/coreclr/jit/codegen.h +++ b/src/coreclr/jit/codegen.h @@ -901,7 +901,6 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX void genCodeForCompare(GenTreeOp* tree); #ifdef TARGET_ARM64 void genCodeForCCMP(GenTreeCCMP* ccmp); - void genCodeForCinc(GenTreeOp* cinc); #endif void genCodeForSelect(GenTreeOp* select); void genIntrinsic(GenTreeIntrinsic* treeNode); @@ -1250,7 +1249,6 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX #if defined(TARGET_ARM64) void genCodeForJumpCompare(GenTreeOpCC* tree); void genCodeForBfiz(GenTreeOp* tree); - void genCodeForCond(GenTreeOp* tree); #endif // TARGET_ARM64 #if defined(FEATURE_EH_FUNCLETS) diff --git a/src/coreclr/jit/codegenarm64.cpp b/src/coreclr/jit/codegenarm64.cpp index 256b76667954..8e0faf1f05e3 100644 --- a/src/coreclr/jit/codegenarm64.cpp +++ b/src/coreclr/jit/codegenarm64.cpp @@ -4683,32 +4683,49 @@ void CodeGen::genCodeForCCMP(GenTreeCCMP* ccmp) } //------------------------------------------------------------------------ -// genCodeForSelect: Produce code for a GT_SELECT node. +// genCodeForSelect: Produce code for a GT_SELECT/GT_SELECT_INV/GT_SELECT_NEG node. // // Arguments: // tree - the node // void CodeGen::genCodeForSelect(GenTreeOp* tree) { - assert(tree->OperIs(GT_SELECT, GT_SELECTCC)); - GenTree* opcond = nullptr; - if (tree->OperIs(GT_SELECT)) + assert(tree->OperIs(GT_SELECT, GT_SELECTCC, GT_SELECT_INC, GT_SELECT_INCCC, GT_SELECT_INV, GT_SELECT_INVCC, + GT_SELECT_NEG, GT_SELECT_NEGCC)); + GenTree* opcond = nullptr; + instruction ins = INS_csel; + GenTree* op1 = tree->gtOp1; + GenTree* op2 = tree->gtOp2; + + if (tree->OperIs(GT_SELECT_INV, GT_SELECT_INVCC)) + { + ins = (op2 == nullptr) ? INS_cinv : INS_csinv; + } + else if (tree->OperIs(GT_SELECT_NEG, GT_SELECT_NEGCC)) + { + ins = (op2 == nullptr) ? INS_cneg : INS_csneg; + } + else if (tree->OperIs(GT_SELECT_INC, GT_SELECT_INCCC)) + { + ins = (op2 == nullptr) ? INS_cinc : INS_csinc; + } + + if (tree->OperIs(GT_SELECT, GT_SELECT_INV, GT_SELECT_NEG)) { opcond = tree->AsConditional()->gtCond; genConsumeRegs(opcond); } - emitter* emit = GetEmitter(); - - GenTree* op1 = tree->gtOp1; - GenTree* op2 = tree->gtOp2; - var_types op1Type = genActualType(op1); - var_types op2Type = genActualType(op2); - emitAttr attr = emitActualTypeSize(tree); + if (op2 != nullptr) + { + var_types op1Type = genActualType(op1); + var_types op2Type = genActualType(op2); + assert(genTypeSize(op1Type) == genTypeSize(op2Type)); + } assert(!op1->isUsedFromMemory()); - assert(genTypeSize(op1Type) == genTypeSize(op2Type)); + emitter* emit = GetEmitter(); GenCondition cond; if (opcond != nullptr) @@ -4719,92 +4736,62 @@ void CodeGen::genCodeForSelect(GenTreeOp* tree) } else { - assert(tree->OperIs(GT_SELECTCC)); + assert(tree->OperIs(GT_SELECTCC, GT_SELECT_INCCC, GT_SELECT_INVCC, GT_SELECT_NEGCC)); cond = tree->AsOpCC()->gtCondition; } assert(!op1->isContained() || op1->IsIntegralConst(0)); - assert(!op2->isContained() || op2->IsIntegralConst(0)); + assert(op2 == nullptr || !op2->isContained() || op2->IsIntegralConst(0)); regNumber targetReg = tree->GetRegNum(); regNumber srcReg1 = op1->IsIntegralConst(0) ? REG_ZR : genConsumeReg(op1); - regNumber srcReg2 = op2->IsIntegralConst(0) ? REG_ZR : genConsumeReg(op2); const GenConditionDesc& prevDesc = GenConditionDesc::Get(cond); + emitAttr attr = emitActualTypeSize(tree); + regNumber srcReg2; - emit->emitIns_R_R_R_COND(INS_csel, attr, targetReg, srcReg1, srcReg2, JumpKindToInsCond(prevDesc.jumpKind1)); - - // Some conditions require an additional condition check. - if (prevDesc.oper == GT_OR) - { - emit->emitIns_R_R_R_COND(INS_csel, attr, targetReg, srcReg1, targetReg, JumpKindToInsCond(prevDesc.jumpKind2)); - } - else if (prevDesc.oper == GT_AND) - { - emit->emitIns_R_R_R_COND(INS_csel, attr, targetReg, targetReg, srcReg2, JumpKindToInsCond(prevDesc.jumpKind2)); - } - - regSet.verifyRegUsed(targetReg); - genProduceReg(tree); -} - -//------------------------------------------------------------------------ -// genCodeForCinc: Produce code for a GT_CINC/GT_CINCCC node. -// -// Arguments: -// tree - the node -// -void CodeGen::genCodeForCinc(GenTreeOp* cinc) -{ - assert(cinc->OperIs(GT_CINC, GT_CINCCC)); - - GenTree* opcond = nullptr; - GenTree* op = cinc->gtOp1; - if (cinc->OperIs(GT_CINC)) + if (op2 == nullptr) { - opcond = cinc->gtOp1; - op = cinc->gtOp2; - genConsumeRegs(opcond); - } - - emitter* emit = GetEmitter(); - var_types opType = genActualType(op->TypeGet()); - emitAttr attr = emitActualTypeSize(cinc->TypeGet()); - - assert(!op->isUsedFromMemory()); - genConsumeRegs(op); - - GenCondition cond; - - if (cinc->OperIs(GT_CINC)) - { - assert(!opcond->isContained()); - // Condition has been generated into a register - move it into flags. - emit->emitIns_R_I(INS_cmp, emitActualTypeSize(opcond), opcond->GetRegNum(), 0); - cond = GenCondition::NE; + srcReg2 = srcReg1; + emit->emitIns_R_R_COND(ins, attr, targetReg, srcReg1, JumpKindToInsCond(prevDesc.jumpKind1)); } else { - assert(cinc->OperIs(GT_CINCCC)); - cond = cinc->AsOpCC()->gtCondition; + srcReg2 = (op2->IsIntegralConst(0) ? REG_ZR : genConsumeReg(op2)); + emit->emitIns_R_R_R_COND(ins, attr, targetReg, srcReg1, srcReg2, JumpKindToInsCond(prevDesc.jumpKind1)); } - const GenConditionDesc& prevDesc = GenConditionDesc::Get(cond); - regNumber targetReg = cinc->GetRegNum(); - regNumber srcReg; - if (op->isContained()) + // Some floating point comparision conditions require an additional condition check. + // These checks are emitted as a subsequent check using GT_AND or GT_OR nodes. + // e.g., using GT_OR => `dest = (cond1 || cond2) ? src1 : src2` + // GT_AND => `dest = (cond1 && cond2) ? src1 : src2` + // The GT_OR case results in emitting the following sequence of two csel instructions. + // csel dest, src1, src2, cond1 # emitted previously + // csel dest, src1, dest, cond2 + // + if (prevDesc.oper == GT_AND) { - assert(op->IsIntegralConst(0)); - srcReg = REG_ZR; + // To ensure correctness with invert and negate variants of conditional select, the second instruction needs to + // be csinv or csneg respectively. + // dest = (cond1 && cond2) ? src1 : ~src2 + // csinv dest, src1, src2, cond1 + // csinv dest, dest, src2, cond2 + // + // However, the other variants - increment and select, the second instruction needs to be csel. + // dest = (cond1 && cond2) ? src1 : src2++ + // csinc dest, src1, src2, cond1 + // csel dest, dest, src1 cond2 + ins = ((ins == INS_csinv) || (ins == INS_csneg)) ? ins : INS_csel; + emit->emitIns_R_R_R_COND(ins, attr, targetReg, targetReg, srcReg2, JumpKindToInsCond(prevDesc.jumpKind2)); } - else + else if (prevDesc.oper == GT_OR) { - srcReg = op->GetRegNum(); + // Similarly, the second instruction needs to be csinc while emitting conditional increment. + ins = (ins == INS_csinc) ? ins : INS_csel; + emit->emitIns_R_R_R_COND(ins, attr, targetReg, srcReg1, targetReg, JumpKindToInsCond(prevDesc.jumpKind2)); } - assert(prevDesc.oper != GT_OR && prevDesc.oper != GT_AND); - emit->emitIns_R_R_COND(INS_cinc, attr, targetReg, srcReg, JumpKindToInsCond(prevDesc.jumpKind1)); regSet.verifyRegUsed(targetReg); - genProduceReg(cinc); + genProduceReg(tree); } //------------------------------------------------------------------------ @@ -10365,53 +10352,6 @@ void CodeGen::genCodeForBfiz(GenTreeOp* tree) genProduceReg(tree); } -//------------------------------------------------------------------------ -// genCodeForCond: Generates the code sequence for a GenTree node that -// represents a conditional instruction. -// -// Arguments: -// tree - conditional op -// -void CodeGen::genCodeForCond(GenTreeOp* tree) -{ - assert(tree->OperIs(GT_CSNEG_MI, GT_CNEG_LT)); - assert(!(tree->gtFlags & GTF_SET_FLAGS)); - genConsumeOperands(tree); - - switch (tree->OperGet()) - { - case GT_CSNEG_MI: - { - instruction ins = INS_csneg; - insCond cond = INS_COND_MI; - - regNumber dstReg = tree->GetRegNum(); - regNumber op1Reg = tree->gtGetOp1()->GetRegNum(); - regNumber op2Reg = tree->gtGetOp2()->GetRegNum(); - - GetEmitter()->emitIns_R_R_R_COND(ins, emitActualTypeSize(tree), dstReg, op1Reg, op2Reg, cond); - break; - } - - case GT_CNEG_LT: - { - instruction ins = INS_cneg; - insCond cond = INS_COND_LT; - - regNumber dstReg = tree->GetRegNum(); - regNumber op1Reg = tree->gtGetOp1()->GetRegNum(); - - GetEmitter()->emitIns_R_R_COND(ins, emitActualTypeSize(tree), dstReg, op1Reg, cond); - break; - } - - default: - unreached(); - } - - genProduceReg(tree); -} - //------------------------------------------------------------------------ // JumpKindToInsCond: Convert a Jump Kind to a condition. // diff --git a/src/coreclr/jit/codegenarmarch.cpp b/src/coreclr/jit/codegenarmarch.cpp index 80a624546189..ce5b8d547503 100644 --- a/src/coreclr/jit/codegenarmarch.cpp +++ b/src/coreclr/jit/codegenarmarch.cpp @@ -315,11 +315,6 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) case GT_BFIZ: genCodeForBfiz(treeNode->AsOp()); break; - - case GT_CSNEG_MI: - case GT_CNEG_LT: - genCodeForCond(treeNode->AsOp()); - break; #endif // TARGET_ARM64 case GT_JMP: @@ -355,15 +350,16 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) break; #ifdef TARGET_ARM64 + case GT_SELECT_NEG: + case GT_SELECT_INV: + case GT_SELECT_INC: case GT_SELECT: genCodeForSelect(treeNode->AsConditional()); break; - case GT_CINC: - case GT_CINCCC: - genCodeForCinc(treeNode->AsOp()); - break; - + case GT_SELECT_NEGCC: + case GT_SELECT_INVCC: + case GT_SELECT_INCCC: case GT_SELECTCC: genCodeForSelect(treeNode->AsOp()); break; diff --git a/src/coreclr/jit/compiler.hpp b/src/coreclr/jit/compiler.hpp index f8f171549940..ad2dbcfb69bf 100644 --- a/src/coreclr/jit/compiler.hpp +++ b/src/coreclr/jit/compiler.hpp @@ -3984,11 +3984,7 @@ void GenTree::VisitOperands(TVisitor visitor) } FALLTHROUGH; -// Standard unary operators -#ifdef TARGET_ARM64 - case GT_CNEG_LT: - case GT_CINCCC: -#endif // TARGET_ARM64 + // Standard unary operators case GT_STORE_LCL_VAR: case GT_STORE_LCL_FLD: case GT_NOT: diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 3529a344ea0f..2309c1a05c79 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -6442,11 +6442,7 @@ bool GenTree::TryGetUse(GenTree* operand, GenTree*** pUse) case GT_IL_OFFSET: return false; -// Standard unary operators -#ifdef TARGET_ARM64 - case GT_CNEG_LT: - case GT_CINCCC: -#endif // TARGET_ARM64 + // Standard unary operators case GT_STORE_LCL_VAR: case GT_STORE_LCL_FLD: case GT_NOT: @@ -6635,7 +6631,11 @@ bool GenTree::TryGetUse(GenTree* operand, GenTree*** pUse) } return false; } - +#ifdef TARGET_ARM64 + case GT_SELECT_NEG: + case GT_SELECT_INV: + case GT_SELECT_INC: +#endif case GT_SELECT: { GenTreeConditional* const conditional = this->AsConditional(); @@ -9836,11 +9836,7 @@ GenTreeUseEdgeIterator::GenTreeUseEdgeIterator(GenTree* node) m_state = -1; return; -// Standard unary operators -#ifdef TARGET_ARM64 - case GT_CNEG_LT: - case GT_CINCCC: -#endif // TARGET_ARM64 + // Standard unary operators case GT_STORE_LCL_VAR: case GT_STORE_LCL_FLD: case GT_NOT: @@ -9952,7 +9948,11 @@ GenTreeUseEdgeIterator::GenTreeUseEdgeIterator(GenTree* node) m_advance = &GenTreeUseEdgeIterator::AdvanceCall; AdvanceCall(); return; - +#ifdef TARGET_ARM64 + case GT_SELECT_NEG: + case GT_SELECT_INV: + case GT_SELECT_INC: +#endif case GT_SELECT: m_edge = &m_node->AsConditional()->gtCond; assert(*m_edge != nullptr); @@ -10078,8 +10078,15 @@ void GenTreeUseEdgeIterator::AdvanceConditional() switch (m_state) { case 0: - m_edge = &conditional->gtOp1; - m_state = 1; + m_edge = &conditional->gtOp1; + if (conditional->gtOp2 == nullptr) + { + m_advance = &GenTreeUseEdgeIterator::Terminate; + } + else + { + m_state = 1; + } break; case 1: m_edge = &conditional->gtOp2; @@ -12361,7 +12368,7 @@ void Compiler::gtDispTree(GenTree* tree, printf(" cond=%s", tree->AsOpCC()->gtCondition.Name()); } #ifdef TARGET_ARM64 - else if (tree->OperIs(GT_CINCCC)) + else if (tree->OperIs(GT_SELECT_INCCC, GT_SELECT_INVCC, GT_SELECT_NEGCC)) { printf(" cond=%s", tree->AsOpCC()->gtCondition.Name()); } diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index 346265470520..e48dba3caa3f 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -1679,7 +1679,7 @@ struct GenTree } #endif #if defined(TARGET_ARM64) - if (OperIs(GT_CCMP, GT_CINCCC)) + if (OperIs(GT_CCMP, GT_SELECT_INCCC, GT_SELECT_INVCC, GT_SELECT_NEGCC)) { return true; } @@ -1736,6 +1736,10 @@ struct GenTree #if defined(TARGET_ARM) case GT_PUTARG_REG: #endif // defined(TARGET_ARM) +#if defined(TARGET_ARM64) + case GT_SELECT_NEGCC: + case GT_SELECT_INCCC: +#endif // defined(TARGET_ARM64) return true; default: @@ -8645,7 +8649,11 @@ struct GenTreeOpCC : public GenTreeOp GenTreeOpCC(genTreeOps oper, var_types type, GenCondition condition, GenTree* op1 = nullptr, GenTree* op2 = nullptr) : GenTreeOp(oper, type, op1, op2 DEBUGARG(/*largeNode*/ FALSE)), gtCondition(condition) { +#ifdef TARGET_ARM64 + assert(OperIs(GT_SELECTCC, GT_SELECT_INCCC, GT_SELECT_INVCC, GT_SELECT_NEGCC)); +#else assert(OperIs(GT_SELECTCC)); +#endif } #if DEBUGGABLE_GENTREE diff --git a/src/coreclr/jit/gtlist.h b/src/coreclr/jit/gtlist.h index bfa00180ac86..87683c36ca69 100644 --- a/src/coreclr/jit/gtlist.h +++ b/src/coreclr/jit/gtlist.h @@ -216,8 +216,6 @@ GTNODE(AND_NOT , GenTreeOp ,0,GTK_BINOP|DBK_NOTHIR) #ifdef TARGET_ARM64 GTNODE(BFIZ , GenTreeOp ,0,GTK_BINOP|DBK_NOTHIR) // Bitfield Insert in Zero. -GTNODE(CSNEG_MI , GenTreeOp ,0,GTK_BINOP|DBK_NOTHIR) // Conditional select, negate, minus result -GTNODE(CNEG_LT , GenTreeOp ,0,GTK_UNOP|DBK_NOTHIR) // Conditional, negate, signed less than result #endif //----------------------------------------------------------------------------- @@ -247,11 +245,21 @@ GTNODE(SELECTCC , GenTreeOpCC ,0,GTK_BINOP|DBK_NOTHIR) // operands and sets the condition flags according to the result. Otherwise // sets the condition flags to the specified immediate value. GTNODE(CCMP , GenTreeCCMP ,0,GTK_BINOP|GTK_NOVALUE|DBK_NOTHIR) -// Maps to arm64 cinc instruction. It returns the operand incremented by one when the condition is true. -// Otherwise returns the unchanged operand. Optimises for patterns such as, result = condition ? op1 + 1 : op1 -GTNODE(CINC , GenTreeOp ,0,GTK_BINOP|DBK_NOTHIR) -// Variant of CINC that reuses flags computed by a previous node with the specified condition. -GTNODE(CINCCC , GenTreeOpCC ,0,GTK_UNOP|DBK_NOTHIR) +// Maps to arm64 csinc/cinc instruction. Computes result = condition ? op1 : op2 + 1. +// If op2 is null, computes result = condition ? op1 + 1 : op1. +GTNODE(SELECT_INC , GenTreeOp ,0,GTK_BINOP|DBK_NOTHIR) +// Variant of SELECT_INC that reuses flags computed by a previous node with the specified condition. +GTNODE(SELECT_INCCC , GenTreeOpCC ,0,GTK_BINOP|DBK_NOTHIR) +// Maps to arm64 csinv/cinv instruction. Computes result = condition ? op1 : ~op2. +// If op2 is null, computes result = condition ? ~op1 : op1. +GTNODE(SELECT_INV , GenTreeOp ,0,GTK_BINOP|DBK_NOTHIR) +// Variant of SELECT_INV that reuses flags computed by a previous node with the specified condition. +GTNODE(SELECT_INVCC , GenTreeOpCC ,0,GTK_BINOP|DBK_NOTHIR) +// Maps to arm64 csneg/cneg instruction.. Computes result = condition ? op1 : -op2. +// If op2 is null, computes result = condition ? -op1 : op1. +GTNODE(SELECT_NEG , GenTreeOp ,0,GTK_BINOP|DBK_NOTHIR) +// Variant of SELECT_NEG that reuses flags computed by a previous node with the specified condition. +GTNODE(SELECT_NEGCC , GenTreeOpCC ,0,GTK_BINOP|DBK_NOTHIR) #endif //----------------------------------------------------------------------------- diff --git a/src/coreclr/jit/gtstructs.h b/src/coreclr/jit/gtstructs.h index b4d68694d9da..317e0b3f071b 100644 --- a/src/coreclr/jit/gtstructs.h +++ b/src/coreclr/jit/gtstructs.h @@ -94,7 +94,11 @@ GTSTRUCT_1(PhiArg , GT_PHI_ARG) GTSTRUCT_1(Phi , GT_PHI) GTSTRUCT_1(StoreInd , GT_STOREIND) GTSTRUCT_N(Indir , GT_STOREIND, GT_IND, GT_NULLCHECK, GT_BLK, GT_STORE_BLK, GT_STORE_DYN_BLK) +#ifdef TARGET_ARM64 +GTSTRUCT_N(Conditional , GT_SELECT, GT_SELECT_INC, GT_SELECT_INV, GT_SELECT_NEG) +#else GTSTRUCT_N(Conditional , GT_SELECT) +#endif //TARGET_ARM64 #if FEATURE_ARG_SPLIT GTSTRUCT_2_SPECIAL(PutArgStk, GT_PUTARG_STK, GT_PUTARG_SPLIT) GTSTRUCT_1(PutArgSplit , GT_PUTARG_SPLIT) @@ -111,7 +115,7 @@ GTSTRUCT_1(ArrAddr , GT_ARR_ADDR) GTSTRUCT_2(CC , GT_JCC, GT_SETCC) #ifdef TARGET_ARM64 GTSTRUCT_1(CCMP , GT_CCMP) -GTSTRUCT_4(OpCC , GT_SELECTCC, GT_CINCCC, GT_JCMP, GT_JTEST) +GTSTRUCT_N(OpCC , GT_SELECTCC, GT_SELECT_INCCC, GT_JCMP, GT_JTEST, GT_SELECT_INVCC, GT_SELECT_NEGCC) #else GTSTRUCT_3(OpCC , GT_SELECTCC, GT_JCMP, GT_JTEST) #endif diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index d14dbc4be0eb..c857788cc643 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -3851,11 +3851,16 @@ GenTree* Lowering::LowerSelect(GenTreeConditional* select) } #ifdef TARGET_ARM64 - if (trueVal->IsCnsIntOrI() && falseVal->IsCnsIntOrI()) + if (trueVal->OperIs(GT_NOT, GT_NEG) || falseVal->OperIs(GT_NOT, GT_NEG)) + { + TryLowerCselToCinvOrCneg(select, cond); + } + else if (trueVal->IsCnsIntOrI() && falseVal->IsCnsIntOrI()) { TryLowerCselToCinc(select, cond); } #endif + return newSelect != nullptr ? newSelect->gtNext : select->gtNext; } diff --git a/src/coreclr/jit/lower.h b/src/coreclr/jit/lower.h index 264405298e96..5b5ed156aaa2 100644 --- a/src/coreclr/jit/lower.h +++ b/src/coreclr/jit/lower.h @@ -89,6 +89,7 @@ class Lowering final : public Phase void ContainCheckConditionalCompare(GenTreeCCMP* ccmp); void ContainCheckNeg(GenTreeOp* neg); void TryLowerCselToCinc(GenTreeOp* select, GenTree* cond); + void TryLowerCselToCinvOrCneg(GenTreeOp* select, GenTree* cond); #endif void ContainCheckSelect(GenTreeOp* select); void ContainCheckBitCast(GenTree* node); diff --git a/src/coreclr/jit/lowerarmarch.cpp b/src/coreclr/jit/lowerarmarch.cpp index 83f7be1b91a0..64826c428b77 100644 --- a/src/coreclr/jit/lowerarmarch.cpp +++ b/src/coreclr/jit/lowerarmarch.cpp @@ -874,8 +874,11 @@ void Lowering::LowerModPow2(GenTree* node) BlockRange().InsertAfter(cnsZero, cmp); LowerNode(cmp); - mod->ChangeOper(GT_CNEG_LT); - mod->gtOp1 = trueExpr; + mod->ChangeOper(GT_SELECT_NEGCC); + GenTreeOpCC* node = mod->AsOpCC(); + node->gtOp1 = trueExpr; + node->gtOp2 = nullptr; + node->gtCondition = GenCondition::SLT; } else { @@ -900,9 +903,11 @@ void Lowering::LowerModPow2(GenTree* node) BlockRange().InsertAfter(cns2, falseExpr); LowerNode(falseExpr); - mod->ChangeOper(GT_CSNEG_MI); - mod->gtOp1 = trueExpr; - mod->gtOp2 = falseExpr; + mod->SetOper(GT_SELECT_NEGCC); + GenTreeOpCC* node = mod->AsOpCC(); + node->gtOp1 = trueExpr; + node->gtOp2 = falseExpr; + node->gtCondition = GenCondition::S; } ContainCheckNode(mod); @@ -2552,8 +2557,93 @@ void Lowering::ContainCheckNeg(GenTreeOp* neg) } //---------------------------------------------------------------------------------------------- -// Try converting SELECT/SELECTCC to CINC/CINCCC. Conversion is possible only if -// both the trueVal and falseVal are integral constants and abs(trueVal - falseVal) = 1. +// TryLowerCselToCinvOrCneg: Try converting SELECT/SELECTCC to SELECT_INV/SELECT_INVCC. Conversion is possible only if +// one of the operands of the select node is inverted. +// +// Arguments: +// select - The select node that is now SELECT or SELECTCC +// cond - The condition node that SELECT or SELECTCC uses +// +void Lowering::TryLowerCselToCinvOrCneg(GenTreeOp* select, GenTree* cond) +{ + assert(select->OperIs(GT_SELECT, GT_SELECTCC)); + + bool shouldReverseCondition; + GenTree* invertedOrNegatedVal; + GenTree* nonInvertedOrNegatedVal; + GenTree* nodeToRemove; + + GenTree* trueVal = select->gtOp1; + GenTree* falseVal = select->gtOp2; + const bool isCneg = trueVal->OperIs(GT_NEG) || falseVal->OperIs(GT_NEG); + + assert(trueVal->OperIs(GT_NOT, GT_NEG) || falseVal->OperIs(GT_NOT, GT_NEG)); + + if (trueVal->OperIs(GT_NOT) || trueVal->OperIs(GT_NEG)) + { + shouldReverseCondition = true; + invertedOrNegatedVal = trueVal->gtGetOp1(); + nonInvertedOrNegatedVal = falseVal; + nodeToRemove = trueVal; + } + else + { + shouldReverseCondition = false; + invertedOrNegatedVal = falseVal->gtGetOp1(); + nonInvertedOrNegatedVal = trueVal; + nodeToRemove = falseVal; + } + + if (shouldReverseCondition && !cond->OperIsCompare() && select->OperIs(GT_SELECT)) + { + // Non-compare nodes add additional GT_NOT node after reversing. + // This would remove gains from this optimisation so don't proceed. + return; + } + + if (!(IsInvariantInRange(invertedOrNegatedVal, select) && IsInvariantInRange(nonInvertedOrNegatedVal, select))) + { + return; + } + + // As the select node would handle the negation/inversion, the op is not required. + // If a value is contained in the negate/invert op, it cannot be contained anymore. + BlockRange().Remove(nodeToRemove); + invertedOrNegatedVal->ClearContained(); + select->gtOp1 = nonInvertedOrNegatedVal; + select->gtOp2 = invertedOrNegatedVal; + + if (select->OperIs(GT_SELECT)) + { + if (shouldReverseCondition) + { + GenTree* revCond = comp->gtReverseCond(cond); + assert(cond == revCond); // Ensure `gtReverseCond` did not create a new node. + } + select->SetOper(isCneg ? GT_SELECT_NEG : GT_SELECT_INV); + JITDUMP("Converted to: %s\n", isCneg ? "SELECT_NEG" : "SELECT_INV"); + DISPTREERANGE(BlockRange(), select); + JITDUMP("\n"); + } + else + { + GenTreeOpCC* selectcc = select->AsOpCC(); + GenCondition selectCond = selectcc->gtCondition; + if (shouldReverseCondition) + { + // Reverse the condition so that op2 will be selected + selectcc->gtCondition = GenCondition::Reverse(selectCond); + } + selectcc->SetOper(isCneg ? GT_SELECT_NEGCC : GT_SELECT_INVCC); + JITDUMP("Converted to: %s\n", isCneg ? "SELECT_NEGCC" : "SELECT_INVCC"); + DISPTREERANGE(BlockRange(), selectcc); + JITDUMP("\n"); + } +} + +//---------------------------------------------------------------------------------------------- +// TryLowerCselToCinc: Try converting SELECT/SELECTCC to SELECT_INC/SELECT_INCCC. Conversion is possible only if both +// the trueVal and falseVal are integral constants and abs(trueVal - falseVal) = 1. // // Arguments: // select - The select node that is now SELECT or SELECTCC @@ -2568,11 +2658,10 @@ void Lowering::TryLowerCselToCinc(GenTreeOp* select, GenTree* cond) size_t op1Val = (size_t)trueVal->AsIntCon()->IconValue(); size_t op2Val = (size_t)falseVal->AsIntCon()->IconValue(); - if (op1Val + 1 == op2Val || op2Val + 1 == op1Val) + if ((op1Val + 1 == op2Val) || (op2Val + 1 == op1Val)) { - const bool shouldReverseCondition = op1Val + 1 == op2Val; + const bool shouldReverseCondition = (op1Val + 1 == op2Val); - // Create a cinc node, insert it and update the use. if (select->OperIs(GT_SELECT)) { if (shouldReverseCondition) @@ -2584,18 +2673,21 @@ void Lowering::TryLowerCselToCinc(GenTreeOp* select, GenTree* cond) // This would remove gains from this optimisation so don't proceed. return; } - select->gtOp2 = select->gtOp1; GenTree* revCond = comp->gtReverseCond(cond); assert(cond == revCond); // Ensure `gtReverseCond` did not create a new node. } - select->gtOp1 = cond->AsOp(); - select->SetOper(GT_CINC); + BlockRange().Remove(select->gtOp2, true); + select->gtOp2 = nullptr; + select->SetOper(GT_SELECT_INC); + JITDUMP("Converted to: GT_SELECT_INC\n"); DISPTREERANGE(BlockRange(), select); + JITDUMP("\n"); } else { GenTreeOpCC* selectcc = select->AsOpCC(); GenCondition selectCond = selectcc->gtCondition; + if (shouldReverseCondition) { // Reverse the condition so that op2 will be selected @@ -2605,9 +2697,13 @@ void Lowering::TryLowerCselToCinc(GenTreeOp* select, GenTree* cond) { std::swap(selectcc->gtOp1, selectcc->gtOp2); } - BlockRange().Remove(selectcc->gtOp2); - selectcc->SetOper(GT_CINCCC); + + BlockRange().Remove(selectcc->gtOp2, true); + selectcc->gtOp2 = nullptr; + selectcc->SetOper(GT_SELECT_INCCC); + JITDUMP("Converted to: GT_SELECT_INCCC\n"); DISPTREERANGE(BlockRange(), selectcc); + JITDUMP("\n"); } } } diff --git a/src/tests/JIT/opt/Compares/conditionalIncrements.cs b/src/tests/JIT/opt/Compares/conditionalIncrements.cs index 2580fa3d5ca5..8cf201fa1a92 100644 --- a/src/tests/JIT/opt/Compares/conditionalIncrements.cs +++ b/src/tests/JIT/opt/Compares/conditionalIncrements.cs @@ -10,193 +10,148 @@ public class ConditionalIncrementTest { + [Theory] + [InlineData(72, 6)] + [InlineData(32, 5)] [MethodImpl(MethodImplOptions.NoInlining)] - static int cinc_byte(byte op1) + public static void cinc_byte(byte op1, int expected) { //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #42 //ARM64-FULL-LINE-NEXT: cinc {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}} - return op1 > 42 ? 6: 5; + int result = op1 > 42 ? 6: 5; + Assert.Equal(expected, result); } + [Theory] + [InlineData(72, byte.MinValue)] + [InlineData(32, byte.MaxValue)] [MethodImpl(MethodImplOptions.NoInlining)] - static byte cinc_byte_min_max(byte op1) + public static void cinc_byte_min_max(byte op1, byte expected) { //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #43 //ARM64-FULL-LINE-NEXT: csel {{w[0-9]+}}, wzr, {{w[0-9]+}}, {{ge|lt}} - return op1 >= 43 ? byte.MinValue : byte.MaxValue; + byte result = op1 >= 43 ? byte.MinValue : byte.MaxValue; + Assert.Equal(expected, result); } + [Theory] + [InlineData(72, byte.MinValue)] + [InlineData(32, byte.MaxValue)] [MethodImpl(MethodImplOptions.NoInlining)] - static int cinc_short(short op1) + public static void cinv_byte_min_max(byte op1, byte expected) + { + //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #43 + //ARM64-FULL-LINE-NEXT: cinc {{w[0-9]+}}, {{w[0-9]+}}, {{ge|lt}} + byte result = (byte) (op1 >= 43 ? byte.MinValue : ~byte.MinValue); + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(74, 5)] + [InlineData(34, 6)] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void cinc_short(short op1, short expected) { //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #44 //ARM64-FULL-LINE-NEXT: cinc {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}} - return op1 <= 44 ? 6 : 5; + short result = (short) (op1 <= 44 ? 6 : 5); + Assert.Equal(expected, result); } - + [Theory] + [InlineData(76, short.MinValue)] + [InlineData(-35, short.MaxValue)] [MethodImpl(MethodImplOptions.NoInlining)] - static short cinc_short_min_max(short op1) + public static void cinc_short_min_max(short op1, short expected) { //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #45 //ARM64-FULL-LINE-NEXT: csel {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}} - return op1 > 45 ? short.MinValue : short.MaxValue; + short result = op1 > 45 ? short.MinValue : short.MaxValue; + Assert.Equal(expected, result); } + [Theory] + [InlineData(76, 6)] + [InlineData(36, 5)] [MethodImpl(MethodImplOptions.NoInlining)] - static int cinc_int(int op1) + public static void cinc_int(int op1, int expected) { //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #46 //ARM64-FULL-LINE-NEXT: cinc {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}} - return op1 > 46 ? 6 : 5; + int result = op1 > 46 ? 6 : 5; + Assert.Equal(expected, result); } + [Theory] + [InlineData(77, int.MinValue)] + [InlineData(37, int.MaxValue)] [MethodImpl(MethodImplOptions.NoInlining)] - static int cinc_int_min_max(int op1) + public static void cinc_int_min_max(int op1, int expected) { //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #47 //ARM64-FULL-LINE-NEXT: csel {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{ge|lt}} - return op1 >= 47 ? int.MinValue : int.MaxValue; + int result = op1 >= 47 ? int.MinValue : int.MaxValue; + Assert.Equal(expected, result); } + [Theory] + [InlineData(78, 5)] + [InlineData(38, 6)] [MethodImpl(MethodImplOptions.NoInlining)] - static long cinc_long(long op1) + public static void cinc_long(long op1, long expected) { //ARM64-FULL-LINE: cmp {{x[0-9]+}}, #48 //ARM64-FULL-LINE-NEXT: cinc {{w[0-9]+}}, {{w[0-9]+}}, {{ge|lt}} //ARM64-FULL-LINE-NEXT: sxtw {{x[0-9]+}}, {{w[0-9]+}} - return op1 < 48 ? 6 : 5; + long result = op1 < 48 ? 6 : 5; + Assert.Equal(expected, result); } + [Theory] + [InlineData(79, long.MaxValue)] + [InlineData(39, long.MinValue)] [MethodImpl(MethodImplOptions.NoInlining)] - static long cinc_long_min_max(long op1) + public static void cinc_long_min_max(long op1, long expected) { //ARM64-FULL-LINE: cmp {{x[0-9]+}}, #49 //ARM64-FULL-LINE-NEXT: cinc {{x[0-9]+}}, {{x[0-9]+}}, {{ge|lt}} - return op1 < 49 ? long.MinValue : long.MaxValue; + long result = op1 < 49 ? long.MinValue : long.MaxValue; + Assert.Equal(expected, result); } + [Theory] + [InlineData(79, long.MaxValue)] + [InlineData(39, long.MinValue)] [MethodImpl(MethodImplOptions.NoInlining)] - static int cinc_float(float op1) + public static void cinv_long_min_max(long op1, long expected) + { + //ARM64-FULL-LINE: cmp {{x[0-9]+}}, #49 + //ARM64-FULL-LINE-NEXT: cinc {{x[0-9]+}}, {{x[0-9]+}}, {{ge|lt}} + long result = op1 < 49 ? long.MinValue : ~long.MinValue; + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(80.0f, 6)] + [InlineData(30.0f, 5)] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void cinc_float(float op1, int expected) { //ARM64-FULL-LINE: fcmp {{s[0-9]+}}, {{s[0-9]+}} //ARM64-FULL-LINE-NEXT: cinc {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}} - return op1 > 50.0f ? 6 : 5; + int result = op1 > 50.0f ? 6 : 5; + Assert.Equal(expected, result); } - - [Fact] - public static int TestEntryPoint() + [Theory] + [InlineData(80.0, 5)] + [InlineData(30.0, 6)] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void cinc_double(double op1, int expected) { - if (cinc_byte(72) != 6) - { - Console.WriteLine("ConditionalIncrementTest:cinc_byte() failed"); - return 101; - } - - if (cinc_byte(32) != 5) - { - Console.WriteLine("ConditionalIncrementTest:cinc_byte() failed"); - return 101; - } - - if (cinc_byte_min_max(72) != byte.MinValue) - { - Console.WriteLine("ConditionalIncrementTest:cinc_byte_min_max() failed"); - return 101; - } - - if (cinc_byte_min_max(32) != byte.MaxValue) - { - Console.WriteLine("ConditionalIncrementTest:cinc_byte_min_max() failed"); - return 101; - } - - if (cinc_short(34) != 6) - { - Console.WriteLine("ConditionalIncrementTest:cinc_short() failed"); - return 101; - } - - if (cinc_short(74) != 5) - { - Console.WriteLine("ConditionalIncrementTest:cinc_short() failed"); - return 101; - } - - if (cinc_short_min_max(75) != short.MinValue) - { - Console.WriteLine("ConditionalIncrementTest:cinc_short_min_max() failed"); - return 101; - } - - if (cinc_short_min_max(-35) != short.MaxValue) - { - Console.WriteLine("ConditionalIncrementTest:cinc_short_min_max() failed"); - return 101; - } - - if (cinc_int(76) != 6) - { - Console.WriteLine("ConditionalIncrementTest:cinc_int() failed"); - return 101; - } - - if (cinc_int(36) != 5) - { - Console.WriteLine("ConditionalIncrementTest:cinc_int() failed"); - return 101; - } - - if (cinc_int_min_max(77) != int.MinValue) - { - Console.WriteLine("ConditionalIncrementTest:cinc_int_min_max() failed"); - return 101; - } - - if (cinc_int_min_max(37) != int.MaxValue) - { - Console.WriteLine("ConditionalIncrementTest:cinc_int_min_max() failed"); - return 101; - } - - if (cinc_long(78) != 5) - { - Console.WriteLine("ConditionalIncrementTest:cinc_long() failed"); - return 101; - } - - if (cinc_long(38) != 6) - { - Console.WriteLine("ConditionalIncrementTest:cinc_long() failed"); - return 101; - } - - if (cinc_long_min_max(79) != long.MaxValue) - { - Console.WriteLine("ConditionalIncrementTest:cinc_long_min_max() failed"); - return 101; - } - - if (cinc_long_min_max(39) != long.MinValue) - { - Console.WriteLine("ConditionalIncrementTest:cinc_long_min_max() failed"); - return 101; - } - - if (cinc_float(80.0f) != 6) - { - Console.WriteLine("ConditionalIncrementTest:cinc_float() failed"); - return 101; - } - - if (cinc_float(30.0f) != 5) - { - Console.WriteLine("ConditionalIncrementTest:cinc_float() failed"); - return 101; - } - - Console.WriteLine("PASSED"); - return 100; + //ARM64-FULL-LINE: fcmp {{d[0-9]+}}, {{d[0-9]+}} + //ARM64-FULL-LINE-NEXT: cinc {{w[0-9]+}}, {{w[0-9]+}}, {{hs|lo}} + int result = op1 < 51.0 ? 6 : 5; + Assert.Equal(expected, result); } } diff --git a/src/tests/JIT/opt/Compares/conditionalInverts.cs b/src/tests/JIT/opt/Compares/conditionalInverts.cs new file mode 100644 index 000000000000..e99ddabefee7 --- /dev/null +++ b/src/tests/JIT/opt/Compares/conditionalInverts.cs @@ -0,0 +1,133 @@ +// 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 the full range comparison optimization + +using System; +using System.Runtime.CompilerServices; +using Xunit; + +public class ConditionalInvertTest +{ + + [Theory] + [InlineData(72, 13, 13)] + [InlineData(32, 13, 223)] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void cinv_byte(byte op1, byte op2, byte expected) + { + //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #42 + //ARM64-FULL-LINE-NEXT: csinv {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}} + byte result = (byte) (op1 > 42 ? op2: ~op1); + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(34, 13, ~13)] + [InlineData(74, 13, 74)] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void cinv_short(short op1, short op2, short expected) + { + //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #43 + //ARM64-FULL-LINE-NEXT: csinv {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}} + short result = (short) (op1 <= 43 ? ~op2 : op1); + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(75, ~short.MaxValue)] + [InlineData(-35, short.MaxValue)] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void cinv_short_min_max(short op1, short expected) + { + //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #44 + //ARM64-FULL-LINE-NEXT: csel {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}} + short result = (short) (op1 > 44 ? ~short.MaxValue : short.MaxValue); + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(76, 17, 17)] + [InlineData(36, 17, ~36)] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void cinv_int(int op1, int op2, int expected) + { + //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #45 + //ARM64-FULL-LINE-NEXT: csinv {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}} + int result = op1 > 45 ? op2 : ~op1; + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(77, int.MaxValue)] + [InlineData(37, ~int.MaxValue)] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void cinv_int_min_max(int op1, int expected) + { + //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #46 + //ARM64-FULL-LINE-NEXT: csel {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{ge|lt}} + int result = op1 >= 46 ? int.MaxValue : ~int.MaxValue; + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(78, 23, 78)] + [InlineData(38, 23, ~23)] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void cinv_long(long op1, long op2, long expected) + { + //ARM64-FULL-LINE: cmp {{x[0-9]+}}, #47 + //ARM64-FULL-LINE-NEXT: csinv {{x[0-9]+}}, {{x[0-9]+}}, {{x[0-9]+}}, {{ge|lt}} + long result = op1 < 47 ? ~op2 : op1; + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(80.0f, 29, 29)] + [InlineData(30.0f, 29, ~29)] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void cinv_float(float op1, int op2, int expected) + { + //ARM64-FULL-LINE: fcmp {{s[0-9]+}}, {{s[0-9]+}} + //ARM64-FULL-LINE-NEXT: csinv {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}} + int result = op1 > 48.0f ? op2 : ~op2; + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(60.0, 31, ~31)] + [InlineData(30.0, 31, 31)] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void cinv_double(double op1, int op2, int expected) + { + //ARM64-FULL-LINE: fcmp {{d[0-9]+}}, {{d[0-9]+}} + //ARM64-FULL-LINE-NEXT: csinv {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}} + int result = op1 > 49.0 ? ~op2 : op2; + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(81, 21, 21)] + [InlineData(31, 17, -8)] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void cinv_int_shifted_false_opr(int op1, int op2, int expected) + { + //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #50 + //ARM64-FULL-LINE-NEXT: csinv {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}} + int result = op1 > 50 ? op2 : ~(op1 >> 2); + Assert.Equal(expected, result); + } + + + [Theory] + [InlineData(81, 21, -21)] + [InlineData(31, 17, 17)] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void cinv_int_shifted_true_opr(int op1, int op2, int expected) + { + //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #51 + //ARM64-FULL-LINE-NEXT: csinv {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}} + int result = op1 > 51 ? ~(op1 >> 2) : op2; + Assert.Equal(expected, result); + } +} diff --git a/src/tests/JIT/opt/Compares/conditionalInverts.csproj b/src/tests/JIT/opt/Compares/conditionalInverts.csproj new file mode 100644 index 000000000000..dbc3ab7f2f95 --- /dev/null +++ b/src/tests/JIT/opt/Compares/conditionalInverts.csproj @@ -0,0 +1,18 @@ + + + + true + + + None + True + + + + true + + + + + + diff --git a/src/tests/JIT/opt/Compares/conditionalNegates.cs b/src/tests/JIT/opt/Compares/conditionalNegates.cs new file mode 100644 index 000000000000..8366f1b9b9db --- /dev/null +++ b/src/tests/JIT/opt/Compares/conditionalNegates.cs @@ -0,0 +1,132 @@ +// 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 the full range comparison optimization + +using System; +using System.Runtime.CompilerServices; +using Xunit; + +public class ConditionalNegateTest +{ + + [Theory] + [InlineData(72, 13, 13)] + [InlineData(32, 13, 224)] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void cneg_byte(byte op1, byte op2, byte expected) + { + //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #42 + //ARM64-FULL-LINE-NEXT: csneg {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}} + byte result = (byte) (op1 > 42 ? op2: -op1); + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(74, 13, 74)] + [InlineData(34, 13, -13)] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void cneg_short(short op1, short op2, short expected) + { + //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #43 + //ARM64-FULL-LINE-NEXT: csneg {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}} + short result = (short) (op1 <= 43 ? -op2 : op1); + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(75, -short.MaxValue)] + [InlineData(-35, short.MaxValue)] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void cneg_short_min_max(short op1, short expected) + { + //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #44 + //ARM64-FULL-LINE-NEXT: csel {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}} + short result = (short) (op1 > 44 ? -short.MaxValue : short.MaxValue); + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(76, 17, 17)] + [InlineData(36, 17, -36)] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void cneg_int(int op1, int op2, int expected) + { + //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #45 + //ARM64-FULL-LINE-NEXT: csneg {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}} + int result = op1 > 45 ? op2 : -op1; + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(77, int.MaxValue)] + [InlineData(37, -int.MaxValue)] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void cneg_int_min_max(int op1, int expected) + { + //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #46 + //ARM64-FULL-LINE-NEXT: csel {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{ge|lt}} + int result = op1 >= 46 ? int.MaxValue : -int.MaxValue; + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(78, 23, 78)] + [InlineData(38, 23, -23)] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void cneg_long(long op1, long op2, long expected) + { + //ARM64-FULL-LINE: cmp {{x[0-9]+}}, #47 + //ARM64-FULL-LINE-NEXT: csneg {{x[0-9]+}}, {{x[0-9]+}}, {{x[0-9]+}}, {{ge|lt}} + long result = op1 < 47 ? -op2 : op1; + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(80.0f, 29, 29)] + [InlineData(30.0f, 29, -29)] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void cneg_float(float op1, int op2, int expected) + { + //ARM64-FULL-LINE: fcmp {{s[0-9]+}}, {{s[0-9]+}} + //ARM64-FULL-LINE-NEXT: csneg {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}} + int result = op1 > 48.0f ? op2 : -op2; + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(60.0, 31, -31)] + [InlineData(30.0, 31, 31)] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void cneg_double(double op1, int op2, int expected) + { + //ARM64-FULL-LINE: fcmp {{d[0-9]+}}, {{d[0-9]+}} + //ARM64-FULL-LINE-NEXT: csneg {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}} + int result = op1 > 49.0 ? -op2 : op2; + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(81, 21, 21)] + [InlineData(31, 21, -62)] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void cneg_shifted_false_oper(int op1, int op2, int expected) + { + //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #50 + //ARM64-FULL-LINE-NEXT: csneg {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}} + int result = op1 > 50 ? op2 : -(op1 << 1); + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(82, 22, 22)] + [InlineData(32, 22, -4)] + [MethodImpl(MethodImplOptions.NoInlining)] + public static void cneg_shifted_true_oper(int op1, int op2, int expected) + { + //ARM64-FULL-LINE: cmp {{w[0-9]+}}, #51 + //ARM64-FULL-LINE-NEXT: csneg {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{ge|lt}} + int result = op1 < 51 ? -(op1 >> 3) : op2; + Assert.Equal(expected, result); + } +} diff --git a/src/tests/JIT/opt/Compares/conditionalNegates.csproj b/src/tests/JIT/opt/Compares/conditionalNegates.csproj new file mode 100644 index 000000000000..dbc3ab7f2f95 --- /dev/null +++ b/src/tests/JIT/opt/Compares/conditionalNegates.csproj @@ -0,0 +1,18 @@ + + + + true + + + None + True + + + + true + + + + + +