diff --git a/src/coreclr/jit/codegen.h b/src/coreclr/jit/codegen.h index fc9aae292d5e96..cb1701f0ff6b0a 100644 --- a/src/coreclr/jit/codegen.h +++ b/src/coreclr/jit/codegen.h @@ -1047,7 +1047,9 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX void genCkfinite(GenTree* treeNode); void genCodeForCompare(GenTreeOp* tree); #ifdef TARGET_ARM64 - void genCodeForConditional(GenTreeConditional* tree); + void genCodeForConditionalCompare(GenTreeOp* tree, GenCondition prevCond); + void genCodeForContainedCompareChain(GenTree* tree, bool* inchain, GenCondition* prevCond); + void genCodeForSelect(GenTreeConditional* tree); #endif void genIntrinsic(GenTree* treeNode); void genPutArgStk(GenTreePutArgStk* treeNode); @@ -1712,9 +1714,8 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX #endif // TARGET_XARCH #ifdef TARGET_ARM64 - static insCond InsCondForCompareOp(GenTree* tree); - static insCond InvertInsCond(insCond cond); - static insCflags InsCflagsForCcmp(insCond cond); + static insCflags InsCflagsForCcmp(GenCondition cond); + static insCond JumpKindToInsCond(emitJumpKind condition); #endif #ifndef TARGET_LOONGARCH64 diff --git a/src/coreclr/jit/codegenarm64.cpp b/src/coreclr/jit/codegenarm64.cpp index 13328168ac551b..14bff5b5c67e2c 100644 --- a/src/coreclr/jit/codegenarm64.cpp +++ b/src/coreclr/jit/codegenarm64.cpp @@ -2513,6 +2513,9 @@ void CodeGen::genCodeForBinary(GenTreeOp* tree) GenTree* op1 = tree->gtGetOp1(); GenTree* op2 = tree->gtGetOp2(); + // The arithmetic node must be sitting in a register (since it's not contained) + assert(targetReg != REG_NA); + // Handles combined operations: 'madd', 'msub' if (op2->OperIs(GT_MUL) && op2->isContained()) { @@ -2552,6 +2555,35 @@ void CodeGen::genCodeForBinary(GenTreeOp* tree) return; } + if (tree->OperIs(GT_AND) && op2->isContainedAndNotIntOrIImmed()) + { + GenCondition cond; + bool chain = false; + + JITDUMP("Generating compare chain:\n"); + if (op1->isContained()) + { + // Generate Op1 into flags. + genCodeForContainedCompareChain(op1, &chain, &cond); + assert(chain); + } + else + { + // Op1 is not contained, move it from a register into flags. + emit->emitIns_R_I(INS_cmp, emitActualTypeSize(op1), op1->GetRegNum(), 0); + cond = GenCondition::NE; + chain = true; + } + // Gen Op2 into flags. + genCodeForContainedCompareChain(op2, &chain, &cond); + assert(chain); + + // Move the result from flags into a register. + inst_SETCC(cond, tree->TypeGet(), targetReg); + genProduceReg(tree); + return; + } + instruction ins = genGetInsForOper(tree->OperGet(), targetType); if ((tree->gtFlags & GTF_SET_FLAGS) != 0) @@ -2575,9 +2607,6 @@ void CodeGen::genCodeForBinary(GenTreeOp* tree) } } - // The arithmetic node must be sitting in a register (since it's not contained) - assert(targetReg != REG_NA); - regNumber r = emit->emitInsTernary(ins, emitActualTypeSize(tree), tree, op1, op2); assert(r == targetReg); @@ -4361,8 +4390,6 @@ void CodeGen::genCodeForCompare(GenTreeOp* tree) assert(!op1->isUsedFromMemory()); - genConsumeOperands(tree); - emitAttr cmpSize = EA_ATTR(genTypeSize(op1Type)); assert(genTypeSize(op1Type) == genTypeSize(op2Type)); @@ -4412,12 +4439,121 @@ void CodeGen::genCodeForCompare(GenTreeOp* tree) } //------------------------------------------------------------------------ -// genCodeForCompare: Produce code for a GT_CEQ/GT_CNE node. +// genCodeForConditionalCompare: Produce code for a compare that's dependent on a previous compare. +// +// Arguments: +// tree - a compare node (GT_EQ etc) +// cond - the condition of the previous generated compare. +// +void CodeGen::genCodeForConditionalCompare(GenTreeOp* tree, GenCondition prevCond) +{ + emitter* emit = GetEmitter(); + + GenTree* op1 = tree->gtGetOp1(); + GenTree* op2 = tree->gtGetOp2(); + var_types op1Type = genActualType(op1->TypeGet()); + var_types op2Type = genActualType(op2->TypeGet()); + emitAttr cmpSize = EA_ATTR(genTypeSize(op1Type)); + regNumber targetReg = tree->GetRegNum(); + regNumber srcReg1 = op1->GetRegNum(); + + // No float support or swapping op1 and op2 to generate cmp reg, imm. + assert(!varTypeIsFloating(op2Type)); + assert(!op1->isContainedIntOrIImmed()); + + // Should only be called on contained nodes. + assert(targetReg == REG_NA); + + // For the ccmp flags, invert the condition of the compare. + insCflags cflags = InsCflagsForCcmp(GenCondition::FromRelop(tree)); + + // For the condition, use the previous compare. + const GenConditionDesc& prevDesc = GenConditionDesc::Get(prevCond); + insCond prevInsCond = JumpKindToInsCond(prevDesc.jumpKind1); + + if (op2->isContainedIntOrIImmed()) + { + GenTreeIntConCommon* intConst = op2->AsIntConCommon(); + emit->emitIns_R_I_FLAGS_COND(INS_ccmp, cmpSize, srcReg1, (int)intConst->IconValue(), cflags, prevInsCond); + } + else + { + regNumber srcReg2 = op2->GetRegNum(); + emit->emitIns_R_R_FLAGS_COND(INS_ccmp, cmpSize, srcReg1, srcReg2, cflags, prevInsCond); + } +} + +//------------------------------------------------------------------------ +// genCodeForContainedCompareChain: Produce code for a chain of conditional compares. +// +// Only generates for contained nodes. Nodes that are not contained are assumed to be +// generated as part of standard tree generation. +// +// Arguments: +// tree - the node. Either a compare or a tree of compares connected by ANDs. +// inChain - whether a contained chain is in progress. +// prevCond - If a chain is in progress, the condition of the previous compare. +// Return: +// The last compare node generated. +// +void CodeGen::genCodeForContainedCompareChain(GenTree* tree, bool* inChain, GenCondition* prevCond) +{ + assert(tree->isContained()); + + if (tree->OperIs(GT_AND)) + { + GenTree* op1 = tree->gtGetOp1(); + GenTree* op2 = tree->gtGetOp2(); + + assert(op2->isContained()); + + // If Op1 is contained, generate into flags. Otherwise, move the result into flags. + if (op1->isContained()) + { + genCodeForContainedCompareChain(op1, inChain, prevCond); + assert(*inChain); + } + else + { + emitter* emit = GetEmitter(); + emit->emitIns_R_I(INS_cmp, emitActualTypeSize(op1), op1->GetRegNum(), 0); + *prevCond = GenCondition::NE; + *inChain = true; + } + + // Generate Op2 based on Op1. + genCodeForContainedCompareChain(op2, inChain, prevCond); + assert(*inChain); + } + else + { + assert(tree->OperIsCmpCompare()); + + // Generate the compare, putting the result in the flags register. + if (!*inChain) + { + // First item in a chain. Use a standard compare. + genCodeForCompare(tree->AsOp()); + } + else + { + // Within the chain. Use a conditional compare (which is + // dependent on the previous emitted compare). + genCodeForConditionalCompare(tree->AsOp(), *prevCond); + } + + *inChain = true; + *prevCond = GenCondition::FromRelop(tree); + } +} + +//------------------------------------------------------------------------ +// genCodeForSelect: Produce code for a GT_SELECT node. // // Arguments: // tree - the node // -void CodeGen::genCodeForConditional(GenTreeConditional* tree) +void CodeGen::genCodeForSelect(GenTreeConditional* tree) { emitter* emit = GetEmitter(); @@ -4427,42 +4563,34 @@ void CodeGen::genCodeForConditional(GenTreeConditional* tree) var_types op1Type = genActualType(op1->TypeGet()); var_types op2Type = genActualType(op2->TypeGet()); emitAttr cmpSize = EA_ATTR(genTypeSize(op1Type)); - insCond cond = InsCondForCompareOp(opcond); assert(!op1->isUsedFromMemory()); assert(genTypeSize(op1Type) == genTypeSize(op2Type)); - regNumber targetReg = tree->GetRegNum(); - regNumber srcReg1 = genConsumeReg(op1); - - if (tree->OperIs(GT_SELECT)) + GenCondition prevCond; + genConsumeRegs(opcond); + if (opcond->isContained()) { - regNumber srcReg2 = genConsumeReg(op2); - emit->emitIns_R_R_R_COND(INS_csel, cmpSize, targetReg, srcReg1, srcReg2, cond); - regSet.verifyRegUsed(targetReg); + // Generate the contained condition. + bool chain = false; + JITDUMP("Generating compare chain:\n"); + genCodeForContainedCompareChain(opcond, &chain, &prevCond); + assert(chain); } else { - assert(!varTypeIsFloating(op2Type)); - // We don't support swapping op1 and op2 to generate cmp reg, imm. - assert(!op1->isContainedIntOrIImmed()); - // This should not be generating into a register. - assert(targetReg == REG_NA); + // Condition has been generated into a register - move it into flags. + emit->emitIns_R_I(INS_cmp, emitActualTypeSize(opcond), opcond->GetRegNum(), 0); + prevCond = GenCondition::NE; + } - // For the ccmp flags, get the condition of the compare. - insCflags cflags = InsCflagsForCcmp(InsCondForCompareOp(tree)); + regNumber targetReg = tree->GetRegNum(); + regNumber srcReg1 = genConsumeReg(op1); + regNumber srcReg2 = genConsumeReg(op2); + const GenConditionDesc& prevDesc = GenConditionDesc::Get(prevCond); - if (op2->isContainedIntOrIImmed()) - { - GenTreeIntConCommon* intConst = op2->AsIntConCommon(); - emit->emitIns_R_I_FLAGS_COND(INS_ccmp, cmpSize, srcReg1, (int)intConst->IconValue(), cflags, cond); - } - else - { - regNumber srcReg2 = genConsumeReg(op2); - emit->emitIns_R_R_FLAGS_COND(INS_ccmp, cmpSize, srcReg1, srcReg2, cflags, cond); - } - } + emit->emitIns_R_R_R_COND(INS_csel, cmpSize, targetReg, srcReg1, srcReg2, JumpKindToInsCond(prevDesc.jumpKind1)); + regSet.verifyRegUsed(targetReg); } //------------------------------------------------------------------------ @@ -10435,90 +10563,6 @@ void CodeGen::genCodeForCond(GenTreeOp* tree) genProduceReg(tree); } -//------------------------------------------------------------------------ -// InsCondForCompareOp: Map the condition in a Compare/Conditional op to a insCond. -// -// Arguments: -// tree - the node -// -insCond CodeGen::InsCondForCompareOp(GenTree* tree) -{ - assert(tree->OperIsCompare() || tree->OperIsConditionalCompare()); - - if (tree->OperIsCompare()) - { - switch (tree->AsOp()->OperGet()) - { - case GT_EQ: - case GT_TEST_EQ: - return INS_COND_EQ; - case GT_NE: - case GT_TEST_NE: - return INS_COND_NE; - case GT_GE: - return INS_COND_GE; - case GT_GT: - return INS_COND_GT; - case GT_LT: - return INS_COND_LT; - case GT_LE: - return INS_COND_LE; - default: - assert(false && "Invalid condition"); - return INS_COND_EQ; - } - } - else - { - switch (tree->AsConditional()->OperGet()) - { - case GT_CEQ: - return INS_COND_EQ; - case GT_CNE: - return INS_COND_NE; - case GT_CGE: - return INS_COND_GE; - case GT_CGT: - return INS_COND_GT; - case GT_CLT: - return INS_COND_LT; - case GT_CLE: - return INS_COND_LE; - default: - assert(false && "Invalid condition"); - return INS_COND_EQ; - } - } -} - -//------------------------------------------------------------------------ -// InvertInsCond: Invert an insCond -// -// Arguments: -// cond - the insCond. -// -insCond CodeGen::InvertInsCond(insCond cond) -{ - switch (cond) - { - case INS_COND_EQ: - return INS_COND_NE; - case INS_COND_NE: - return INS_COND_EQ; - case INS_COND_GE: - return INS_COND_LT; - case INS_COND_GT: - return INS_COND_LE; - case INS_COND_LT: - return INS_COND_GE; - case INS_COND_LE: - return INS_COND_GT; - default: - assert(false && "Invalid condition"); - return INS_COND_EQ; - } -} - //------------------------------------------------------------------------ // InsCflagsForCcmp: Get the Cflags for a required for a CCMP instruction. // @@ -10530,28 +10574,82 @@ insCond CodeGen::InvertInsCond(insCond cond) // Given COND, this function returns A. // // Arguments: -// cond - the insCond. +// cond - the GenCondition. // -insCflags CodeGen::InsCflagsForCcmp(insCond cond) +insCflags CodeGen::InsCflagsForCcmp(GenCondition cond) { - switch (InvertInsCond(cond)) + GenCondition inverted = GenCondition::Reverse(cond); + switch (inverted.GetCode()) { - case INS_COND_EQ: + case GenCondition::EQ: return INS_FLAGS_Z; - case INS_COND_NE: + case GenCondition::NE: return INS_FLAGS_NONE; - case INS_COND_GE: + case GenCondition::SGE: return INS_FLAGS_Z; - case INS_COND_GT: + case GenCondition::SGT: return INS_FLAGS_NONE; - case INS_COND_LT: + case GenCondition::SLT: return INS_FLAGS_NC; - case INS_COND_LE: + case GenCondition::SLE: return INS_FLAGS_NZC; + case GenCondition::UGE: + return INS_FLAGS_C; + case GenCondition::UGT: + return INS_FLAGS_C; + case GenCondition::ULT: + return INS_FLAGS_NONE; + case GenCondition::ULE: + return INS_FLAGS_Z; default: - assert(false && "Invalid condition"); + NO_WAY("unexpected condition type"); return INS_FLAGS_NONE; } } +//------------------------------------------------------------------------ +// JumpKindToInsCond: Convert a Jump Kind to a condition. +// +// Arguments: +// condition - the emitJumpKind. +// +insCond CodeGen::JumpKindToInsCond(emitJumpKind condition) +{ + /* Convert the condition to an insCond value */ + switch (condition) + { + case EJ_eq: + return INS_COND_EQ; + case EJ_ne: + return INS_COND_NE; + case EJ_hs: + return INS_COND_HS; + case EJ_lo: + return INS_COND_LO; + case EJ_mi: + return INS_COND_MI; + case EJ_pl: + return INS_COND_PL; + case EJ_vs: + return INS_COND_VS; + case EJ_vc: + return INS_COND_VC; + case EJ_hi: + return INS_COND_HI; + case EJ_ls: + return INS_COND_LS; + case EJ_ge: + return INS_COND_GE; + case EJ_lt: + return INS_COND_LT; + case EJ_gt: + return INS_COND_GT; + case EJ_le: + return INS_COND_LE; + default: + NO_WAY("unexpected condition type"); + return INS_COND_EQ; + } +} + #endif // TARGET_ARM64 diff --git a/src/coreclr/jit/codegenarmarch.cpp b/src/coreclr/jit/codegenarmarch.cpp index 69c85482637811..536797eaba9593 100644 --- a/src/coreclr/jit/codegenarmarch.cpp +++ b/src/coreclr/jit/codegenarmarch.cpp @@ -363,19 +363,18 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) #ifdef TARGET_ARM64 case GT_TEST_EQ: case GT_TEST_NE: + // On ARM64 genCodeForCompare does not consume its own operands because + // genCodeForBinary also has this behavior and it can end up calling + // genCodeForCompare when generating compare chains for GT_AND. + // Thus, we must do it here. + genConsumeOperands(treeNode->AsOp()); #endif // TARGET_ARM64 genCodeForCompare(treeNode->AsOp()); break; #ifdef TARGET_ARM64 case GT_SELECT: - case GT_CEQ: - case GT_CNE: - case GT_CLT: - case GT_CLE: - case GT_CGE: - case GT_CGT: - genCodeForConditional(treeNode->AsConditional()); + genCodeForSelect(treeNode->AsConditional()); break; #endif diff --git a/src/coreclr/jit/codegenlinear.cpp b/src/coreclr/jit/codegenlinear.cpp index 8045344441020f..e3ac21ba504a27 100644 --- a/src/coreclr/jit/codegenlinear.cpp +++ b/src/coreclr/jit/codegenlinear.cpp @@ -1629,6 +1629,12 @@ void CodeGen::genConsumeRegs(GenTree* tree) assert(cast->isContained()); genConsumeAddress(cast->CastOp()); } + else if (tree->OperIsCmpCompare() || tree->OperIs(GT_AND)) + { + // Compares and ANDs may be contained in a conditional chain. + genConsumeRegs(tree->gtGetOp1()); + genConsumeRegs(tree->gtGetOp2()); + } #endif else if (tree->OperIsLocalRead()) { @@ -2630,16 +2636,7 @@ void CodeGen::genCodeForJumpTrue(GenTreeOp* jtrue) assert(compiler->compCurBB->bbJumpKind == BBJ_COND); assert(jtrue->OperIs(GT_JTRUE)); - GenTreeOp* relop; - if (jtrue->gtGetOp1()->OperIsCompare()) - { - relop = jtrue->gtGetOp1()->AsOp(); - } - else - { - assert(jtrue->gtGetOp1()->OperIsConditionalCompare()); - relop = jtrue->gtGetOp1()->AsConditional(); - } + GenTreeOp* relop = jtrue->gtGetOp1()->AsOp(); GenCondition condition = GenCondition::FromRelop(relop); if (condition.PreferSwap()) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index a69148d93d5ef2..deb8d95072aba6 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -10984,6 +10984,28 @@ class GenTreeVisitor break; #endif // defined(FEATURE_SIMD) || defined(FEATURE_HW_INTRINSICS) + case GT_SELECT: + { + GenTreeConditional* const conditional = node->AsConditional(); + + result = WalkTree(&conditional->gtCond, conditional); + if (result == fgWalkResult::WALK_ABORT) + { + return result; + } + result = WalkTree(&conditional->gtOp1, conditional); + if (result == fgWalkResult::WALK_ABORT) + { + return result; + } + result = WalkTree(&conditional->gtOp2, conditional); + if (result == fgWalkResult::WALK_ABORT) + { + return result; + } + break; + } + // Binary nodes default: { diff --git a/src/coreclr/jit/compiler.hpp b/src/coreclr/jit/compiler.hpp index d353807493f5a1..37d8b979280a60 100644 --- a/src/coreclr/jit/compiler.hpp +++ b/src/coreclr/jit/compiler.hpp @@ -4393,6 +4393,21 @@ void GenTree::VisitOperands(TVisitor visitor) return; } + case GT_SELECT: + { + GenTreeConditional* const cond = this->AsConditional(); + if (visitor(cond->gtCond) == VisitResult::Abort) + { + return; + } + if (visitor(cond->gtOp1) == VisitResult::Abort) + { + return; + } + visitor(cond->gtOp2); + return; + } + // Binary nodes default: assert(this->OperIsBinary()); diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 65809969a120b2..b214781d6642c7 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -5790,6 +5790,22 @@ unsigned Compiler::gtSetEvalOrder(GenTree* tree) costSz += tree->AsStoreDynBlk()->gtDynamicSize->GetCostSz(); break; + case GT_SELECT: + level = gtSetEvalOrder(tree->AsConditional()->gtCond); + costEx = tree->AsConditional()->gtCond->GetCostEx(); + costSz = tree->AsConditional()->gtCond->GetCostSz(); + + lvl2 = gtSetEvalOrder(tree->AsConditional()->gtOp1); + level = max(level, lvl2); + costEx += tree->AsConditional()->gtOp1->GetCostEx(); + costSz += tree->AsConditional()->gtOp1->GetCostSz(); + + lvl2 = gtSetEvalOrder(tree->AsConditional()->gtOp2); + level = max(level, lvl2); + costEx += tree->AsConditional()->gtOp2->GetCostEx(); + costSz += tree->AsConditional()->gtOp2->GetCostSz(); + break; + default: JITDUMP("unexpected operator in this tree:\n"); DISPTREE(tree); @@ -6182,6 +6198,27 @@ bool GenTree::TryGetUse(GenTree* operand, GenTree*** pUse) return false; } + case GT_SELECT: + { + GenTreeConditional* const conditional = this->AsConditional(); + if (operand == conditional->gtCond) + { + *pUse = &conditional->gtCond; + return true; + } + if (operand == conditional->gtOp1) + { + *pUse = &conditional->gtOp1; + return true; + } + if (operand == conditional->gtOp2) + { + *pUse = &conditional->gtOp2; + return true; + } + return false; + } + // Binary nodes default: assert(this->OperIsBinary()); @@ -8710,6 +8747,13 @@ GenTree* Compiler::gtCloneExpr( gtCloneExpr(tree->AsStoreDynBlk()->gtDynamicSize, addFlags, deepVarNum, deepVarVal)); break; + case GT_SELECT: + copy = new (this, oper) + GenTreeConditional(oper, tree->TypeGet(), + gtCloneExpr(tree->AsConditional()->gtCond, addFlags, deepVarNum, deepVarVal), + gtCloneExpr(tree->AsConditional()->gtOp1, addFlags, deepVarNum, deepVarVal), + gtCloneExpr(tree->AsConditional()->gtOp2, addFlags, deepVarNum, deepVarVal)); + break; default: #ifdef DEBUG gtDispTree(tree); @@ -9369,6 +9413,12 @@ GenTreeUseEdgeIterator::GenTreeUseEdgeIterator(GenTree* node) AdvanceCall(); return; + case GT_SELECT: + m_edge = &m_node->AsConditional()->gtCond; + assert(*m_edge != nullptr); + m_advance = &GenTreeUseEdgeIterator::AdvanceConditional; + return; + // Binary nodes default: assert(m_node->OperIsBinary()); @@ -9501,6 +9551,29 @@ void GenTreeUseEdgeIterator::AdvancePhi() } } +//------------------------------------------------------------------------ +// GenTreeUseEdgeIterator::AdvanceConditional: produces the next operand of a conditional node and advances the state. +// +void GenTreeUseEdgeIterator::AdvanceConditional() +{ + GenTreeConditional* const conditional = m_node->AsConditional(); + switch (m_state) + { + case 0: + m_edge = &conditional->gtOp1; + m_state = 1; + break; + case 1: + m_edge = &conditional->gtOp2; + m_advance = &GenTreeUseEdgeIterator::Terminate; + break; + default: + unreached(); + } + + assert(*m_edge != nullptr); +} + //------------------------------------------------------------------------ // GenTreeUseEdgeIterator::AdvanceBinOp: produces the next operand of a binary node and advances the state. // @@ -10372,6 +10445,7 @@ void Compiler::gtDispNode(GenTree* tree, IndentStack* indentStack, _In_ _In_opt_ case GT_GT: case GT_TEST_EQ: case GT_TEST_NE: + case GT_SELECT: if (tree->gtFlags & GTF_RELOP_NAN_UN) { printf("N"); @@ -12017,6 +12091,17 @@ void Compiler::gtDispTree(GenTree* tree, } break; + case GT_SELECT: + gtDispCommonEndLine(tree); + + if (!topOnly) + { + gtDispChild(tree->AsConditional()->gtCond, indentStack, IIArc, childMsg, topOnly); + gtDispChild(tree->AsConditional()->gtOp1, indentStack, IIArc, childMsg, topOnly); + gtDispChild(tree->AsConditional()->gtOp2, indentStack, IIArcBottom, childMsg, topOnly); + } + break; + default: printf(" :"); printf(""); // null string means flush @@ -16719,14 +16804,6 @@ bool GenTree::isContained() const assert(!isMarkedContained); } - // these actually produce a register (the flags reg, we just don't model it) - // and are a separate instruction from the branch that consumes the result. - // They can only produce a result if the child is a SIMD equality comparison. - else if (OperIsCompare()) - { - assert(isMarkedContained == false); - } - // if it's contained it can't be unused. if (isMarkedContained) { diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index e9d512e16c6d18..43ea5b7d992583 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -915,6 +915,12 @@ struct GenTree return isContained() && IsCnsIntOrI() && !isUsedFromSpillTemp(); } + // Node is contained, but it isn't contained due to being a containable int. + bool isContainedAndNotIntOrIImmed() const + { + return isContained() && !isContainedIntOrIImmed(); + } + bool isContainedFltOrDblImmed() const { return isContained() && OperIs(GT_CNS_DBL); @@ -1088,8 +1094,8 @@ struct GenTree if (gtType == TYP_VOID) { // These are the only operators which can produce either VOID or non-VOID results. - assert(OperIs(GT_NOP, GT_CALL, GT_COMMA) || OperIsCompare() || OperIsConditionalCompare() || OperIsLong() || - OperIsSimdOrHWintrinsic() || IsCnsVec()); + assert(OperIs(GT_NOP, GT_CALL, GT_COMMA) || OperIsCompare() || OperIsLong() || OperIsSimdOrHWintrinsic() || + IsCnsVec()); return false; } @@ -1353,10 +1359,21 @@ struct GenTree return OperIsCompare(OperGet()); } + // Oper is a compare that generates a cmp instruction (as opposed to a test instruction). + static bool OperIsCmpCompare(genTreeOps gtOper) + { + static_assert_no_msg(AreContiguous(GT_EQ, GT_NE, GT_LT, GT_LE, GT_GE, GT_GT)); + return (GT_EQ <= gtOper) && (gtOper <= GT_GT); + } + + bool OperIsCmpCompare() const + { + return OperIsCmpCompare(OperGet()); + } + static bool OperIsConditional(genTreeOps gtOper) { - static_assert_no_msg(AreContiguous(GT_SELECT, GT_CEQ, GT_CNE, GT_CLT, GT_CLE, GT_CGE, GT_CGT)); - return (GT_SELECT <= gtOper) && (gtOper <= GT_CGT); + return (GT_SELECT == gtOper); } bool OperIsConditional() const @@ -1364,15 +1381,14 @@ struct GenTree return OperIsConditional(OperGet()); } - static bool OperIsConditionalCompare(genTreeOps gtOper) + static bool OperIsCC(genTreeOps gtOper) { - static_assert_no_msg(AreContiguous(GT_CEQ, GT_CNE, GT_CLT, GT_CLE, GT_CGE, GT_CGT)); - return (GT_CEQ <= gtOper) && (gtOper <= GT_CGT); + return (gtOper == GT_JCC) || (gtOper == GT_SETCC); } - bool OperIsConditionalCompare() const + bool OperIsCC() const { - return OperIsConditionalCompare(OperGet()); + return OperIsCC(OperGet()); } static bool OperIsShift(genTreeOps gtOper) @@ -8222,7 +8238,7 @@ struct GenCondition static GenCondition FromRelop(GenTree* relop) { - assert(relop->OperIsCompare() || relop->OperIsConditionalCompare()); + assert(relop->OperIsCompare()); if (varTypeIsFloating(relop->gtGetOp1())) { @@ -8259,30 +8275,17 @@ struct GenCondition static GenCondition FromIntegralRelop(GenTree* relop) { - if (relop->OperIsConditionalCompare()) - { - assert(!varTypeIsFloating(relop->AsConditional()->gtOp1) && - !varTypeIsFloating(relop->AsConditional()->gtOp2)); - } - else - { - assert(!varTypeIsFloating(relop->gtGetOp1()) && !varTypeIsFloating(relop->gtGetOp2())); - } - + assert(!varTypeIsFloating(relop->gtGetOp1()) && !varTypeIsFloating(relop->gtGetOp2())); return FromIntegralRelop(relop->OperGet(), relop->IsUnsigned()); } static GenCondition FromIntegralRelop(genTreeOps oper, bool isUnsigned) { - assert(GenTree::OperIsCompare(oper) || GenTree::OperIsConditionalCompare(oper)); + assert(GenTree::OperIsCompare(oper)); // GT_TEST_EQ/NE are special, they need to be mapped as GT_EQ/NE unsigned code; - if (oper >= GT_CEQ) - { - code = oper - GT_CEQ; - } - else if (oper >= GT_TEST_EQ) + if (oper >= GT_TEST_EQ) { code = oper - GT_TEST_EQ; } diff --git a/src/coreclr/jit/gtlist.h b/src/coreclr/jit/gtlist.h index fe8b5c89d3ad14..56ea06b2a061bd 100644 --- a/src/coreclr/jit/gtlist.h +++ b/src/coreclr/jit/gtlist.h @@ -147,12 +147,6 @@ GTNODE(TEST_EQ , GenTreeOp ,0,GTK_BINOP|DBK_NOTHIR) GTNODE(TEST_NE , GenTreeOp ,0,GTK_BINOP|DBK_NOTHIR) GTNODE(SELECT , GenTreeConditional ,0,GTK_SPECIAL) -GTNODE(CEQ , GenTreeConditional ,0,GTK_SPECIAL) -GTNODE(CNE , GenTreeConditional ,0,GTK_SPECIAL) -GTNODE(CLT , GenTreeConditional ,0,GTK_SPECIAL) -GTNODE(CLE , GenTreeConditional ,0,GTK_SPECIAL) -GTNODE(CGE , GenTreeConditional ,0,GTK_SPECIAL) -GTNODE(CGT , GenTreeConditional ,0,GTK_SPECIAL) GTNODE(COMMA , GenTreeOp ,0,GTK_BINOP|DBK_NOTLIR) GTNODE(QMARK , GenTreeQmark ,0,GTK_BINOP|GTK_EXOP|DBK_NOTLIR) diff --git a/src/coreclr/jit/gtstructs.h b/src/coreclr/jit/gtstructs.h index f3e89080c390ea..7d50adbca39f97 100644 --- a/src/coreclr/jit/gtstructs.h +++ b/src/coreclr/jit/gtstructs.h @@ -101,7 +101,7 @@ 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_OBJ, GT_STORE_OBJ, GT_STORE_DYN_BLK) -GTSTRUCT_N(Conditional , GT_SELECT, GT_CEQ, GT_CNE, GT_CLT, GT_CLE, GT_CGE, GT_CGT) +GTSTRUCT_N(Conditional , GT_SELECT) #if FEATURE_ARG_SPLIT GTSTRUCT_2_SPECIAL(PutArgStk, GT_PUTARG_STK, GT_PUTARG_SPLIT) GTSTRUCT_1(PutArgSplit , GT_PUTARG_SPLIT) diff --git a/src/coreclr/jit/instr.cpp b/src/coreclr/jit/instr.cpp index 9843482bbef6c6..c9fbb2b27a821d 100644 --- a/src/coreclr/jit/instr.cpp +++ b/src/coreclr/jit/instr.cpp @@ -326,61 +326,8 @@ void CodeGen::inst_SET(emitJumpKind condition, regNumber reg) // These instructions only write the low byte of 'reg' GetEmitter()->emitIns_R(ins, EA_1BYTE, reg); #elif defined(TARGET_ARM64) - insCond cond; - /* Convert the condition to an insCond value */ - switch (condition) - { - case EJ_eq: - cond = INS_COND_EQ; - break; - case EJ_ne: - cond = INS_COND_NE; - break; - case EJ_hs: - cond = INS_COND_HS; - break; - case EJ_lo: - cond = INS_COND_LO; - break; - case EJ_mi: - cond = INS_COND_MI; - break; - case EJ_pl: - cond = INS_COND_PL; - break; - case EJ_vs: - cond = INS_COND_VS; - break; - case EJ_vc: - cond = INS_COND_VC; - break; - - case EJ_hi: - cond = INS_COND_HI; - break; - case EJ_ls: - cond = INS_COND_LS; - break; - case EJ_ge: - cond = INS_COND_GE; - break; - case EJ_lt: - cond = INS_COND_LT; - break; - - case EJ_gt: - cond = INS_COND_GT; - break; - case EJ_le: - cond = INS_COND_LE; - break; - - default: - NO_WAY("unexpected condition type"); - return; - } - GetEmitter()->emitIns_R_COND(INS_cset, EA_8BYTE, reg, cond); + GetEmitter()->emitIns_R_COND(INS_cset, EA_8BYTE, reg, JumpKindToInsCond(condition)); #else NYI("inst_SET"); #endif diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index db81ecb0f7d301..3808abf08d7191 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -238,6 +238,12 @@ GenTree* Lowering::LowerNode(GenTree* node) case GT_JTRUE: return LowerJTrue(node->AsOp()); + case GT_SELECT: +#ifdef TARGET_ARM64 + ContainCheckSelect(node->AsConditional()); +#endif + break; + case GT_JMP: LowerJmpMethod(node); break; @@ -2742,6 +2748,15 @@ GenTree* Lowering::OptimizeConstCompare(GenTree* cmp) GenTreeIntCon* op2 = cmp->gtGetOp2()->AsIntCon(); ssize_t op2Value = op2->IconValue(); +#ifdef TARGET_ARM64 + // Do not optimise further if op1 has a contained chain. + if (op1->OperIs(GT_AND) && + (op1->gtGetOp1()->isContainedAndNotIntOrIImmed() || op1->gtGetOp2()->isContainedAndNotIntOrIImmed())) + { + return cmp; + } +#endif + #ifdef TARGET_XARCH var_types op1Type = op1->TypeGet(); if (IsContainableMemoryOp(op1) && varTypeIsSmall(op1Type) && FitsIn(op1Type, op2Value)) @@ -2836,7 +2851,8 @@ GenTree* Lowering::OptimizeConstCompare(GenTree* cmp) if ((op2Value == 1) && cmp->OperIs(GT_EQ)) { if (andOp2->IsIntegralConst(1) && (genActualType(op1) == cmp->TypeGet()) && - BlockRange().TryGetUse(cmp, &cmpUse) && !cmpUse.User()->OperIs(GT_JTRUE)) + BlockRange().TryGetUse(cmp, &cmpUse) && !cmpUse.User()->OperIs(GT_JTRUE) && + !cmpUse.User()->OperIsConditional()) { GenTree* next = cmp->gtNext; @@ -6814,6 +6830,12 @@ void Lowering::ContainCheckNode(GenTree* node) ContainCheckJTrue(node->AsOp()); break; + case GT_SELECT: +#ifdef TARGET_ARM64 + ContainCheckSelect(node->AsConditional()); +#endif + break; + case GT_ADD: case GT_SUB: #if !defined(TARGET_64BIT) diff --git a/src/coreclr/jit/lower.h b/src/coreclr/jit/lower.h index b8b5686133efc2..b0d880c030227a 100644 --- a/src/coreclr/jit/lower.h +++ b/src/coreclr/jit/lower.h @@ -85,7 +85,13 @@ class Lowering final : public Phase void ContainCheckLclHeap(GenTreeOp* node); void ContainCheckRet(GenTreeUnOp* ret); void ContainCheckJTrue(GenTreeOp* node); - +#ifdef TARGET_ARM64 + bool IsValidCompareChain(GenTree* child, GenTree* parent); + bool ContainCheckCompareChain(GenTree* child, GenTree* parent, GenTree** earliestValid); + void ContainCheckCompareChainForAnd(GenTree* tree); + void ContainCheckConditionalCompare(GenTreeOp* cmp); + void ContainCheckSelect(GenTreeConditional* node); +#endif void ContainCheckBitCast(GenTree* node); void ContainCheckCallOperands(GenTreeCall* call); void ContainCheckIndir(GenTreeIndir* indirNode); diff --git a/src/coreclr/jit/lowerarmarch.cpp b/src/coreclr/jit/lowerarmarch.cpp index 1e2420b37a953b..0ff88151503201 100644 --- a/src/coreclr/jit/lowerarmarch.cpp +++ b/src/coreclr/jit/lowerarmarch.cpp @@ -365,6 +365,12 @@ GenTree* Lowering::LowerBinaryArithmetic(GenTreeOp* binOp) binOp->ChangeOper(GT_AND_NOT); BlockRange().Remove(notNode); } +#ifdef TARGET_ARM64 + else + { + ContainCheckCompareChainForAnd(binOp); + } +#endif } ContainCheckBinary(binOp); @@ -2042,6 +2048,216 @@ void Lowering::ContainCheckCompare(GenTreeOp* cmp) CheckImmedAndMakeContained(cmp, cmp->gtOp2); } +#ifdef TARGET_ARM64 +//------------------------------------------------------------------------ +// IsValidCompareChain : Determine if the node contains a valid chain of ANDs and CMPs. +// +// Arguments: +// child - pointer to the node being checked. +// parent - parent node of the child. +// +// Return value: +// True if a valid chain is found. +// +// Notes: +// A compare chain is a sequence of CMP nodes connected by AND nodes. +// For example: AND (AND (CMP A B) (CMP C D)) (CMP E F) +// The chain can just be a single compare node, however it's parent +// must always be an AND or SELECT node. +// If a CMP or AND node is contained then it and all it's children are +// considered to be in a valid chain. +// Chains are built up during the lowering of each successive parent. +// +bool Lowering::IsValidCompareChain(GenTree* child, GenTree* parent) +{ + assert(parent->OperIs(GT_AND) || parent->OperIs(GT_SELECT)); + + if (child->isContainedAndNotIntOrIImmed()) + { + // Already have a chain. + assert(child->OperIs(GT_AND) || child->OperIsCmpCompare()); + return true; + } + else + { + if (child->OperIs(GT_AND)) + { + // Count both sides. + return IsValidCompareChain(child->AsOp()->gtGetOp2(), child) && + IsValidCompareChain(child->AsOp()->gtGetOp1(), child); + } + else if (child->OperIsCmpCompare()) + { + // Can the child compare be contained. + return IsSafeToContainMem(parent, child); + } + } + + return false; +} + +//------------------------------------------------------------------------ +// ContainCheckCompareChain : Determine if a chain of ANDs and CMPs can be contained. +// +// Arguments: +// child - pointer to the node being checked. +// parent - parent node of the child. +// startOfChain - If found, returns the earliest valid op in the chain. +// +// Return value: +// True if a valid chain is was contained. +// +// Notes: +// Assumes the chain was checked via IsValidCompareChain. +// +bool Lowering::ContainCheckCompareChain(GenTree* child, GenTree* parent, GenTree** startOfChain) +{ + assert(parent->OperIs(GT_AND) || parent->OperIs(GT_SELECT)); + *startOfChain = nullptr; // Nothing found yet. + + if (child->isContainedAndNotIntOrIImmed()) + { + // Already have a chain. + return true; + } + // Can the child be contained. + else if (IsSafeToContainMem(parent, child)) + { + if (child->OperIs(GT_AND)) + { + // If Op2 is not contained, then try to contain it. + if (!child->AsOp()->gtGetOp2()->isContainedAndNotIntOrIImmed()) + { + if (!ContainCheckCompareChain(child->gtGetOp2(), child, startOfChain)) + { + // Op2 must be contained in order to contain Op1 or the AND. + return false; + } + } + + // If Op1 is not contained, then try to contain it. + if (!child->AsOp()->gtGetOp1()->isContainedAndNotIntOrIImmed()) + { + if (!ContainCheckCompareChain(child->gtGetOp1(), child, startOfChain)) + { + return false; + } + } + + // Contain the AND. + child->SetContained(); + return true; + } + else if (child->OperIsCmpCompare()) + { + child->AsOp()->SetContained(); + + // Ensure the children of the compare are contained correctly. + child->AsOp()->gtGetOp1()->ClearContained(); + child->AsOp()->gtGetOp2()->ClearContained(); + ContainCheckConditionalCompare(child->AsOp()); + *startOfChain = child; + return true; + } + } + + return false; +} + +//------------------------------------------------------------------------ +// ContainCheckCompareChainForAnd : Determine if an AND is a containable chain +// +// Arguments: +// node - pointer to the node +// +void Lowering::ContainCheckCompareChainForAnd(GenTree* tree) +{ + assert(tree->OperIs(GT_AND)); + + if (!comp->opts.OptimizationEnabled()) + { + return; + } + + // First check there is a valid chain. + if (IsValidCompareChain(tree->AsOp()->gtGetOp2(), tree) && IsValidCompareChain(tree->AsOp()->gtGetOp1(), tree)) + { + GenTree* startOfChain = nullptr; + + // To ensure ordering at code generation, Op1 and the parent can + // only be contained if Op2 is contained. + if (ContainCheckCompareChain(tree->AsOp()->gtGetOp2(), tree, &startOfChain)) + { + if (ContainCheckCompareChain(tree->AsOp()->gtGetOp1(), tree, &startOfChain)) + { + // If op1 is the start of a chain, then it'll be generated as a standard compare. + if (startOfChain != nullptr) + { + // The earliest node in the chain will be generated as a standard compare. + assert(startOfChain->OperIsCmpCompare()); + startOfChain->AsOp()->gtGetOp1()->ClearContained(); + startOfChain->AsOp()->gtGetOp2()->ClearContained(); + ContainCheckCompare(startOfChain->AsOp()); + } + } + } + + JITDUMP("Lowered `AND` chain:\n"); + DISPTREE(tree); + } +} + +//------------------------------------------------------------------------ +// ContainCheckConditionalCompare: determine whether the source of a compare within a compare chain should be contained. +// +// Arguments: +// node - pointer to the node +// +void Lowering::ContainCheckConditionalCompare(GenTreeOp* cmp) +{ + assert(cmp->OperIsCmpCompare()); + GenTree* op2 = cmp->gtOp2; + + if (op2->IsCnsIntOrI() && !op2->AsIntCon()->ImmedValNeedsReloc(comp)) + { + target_ssize_t immVal = (target_ssize_t)op2->AsIntCon()->gtIconVal; + + if (emitter::emitIns_valid_imm_for_ccmp(immVal)) + { + MakeSrcContained(cmp, op2); + } + } +} + +//------------------------------------------------------------------------ +// ContainCheckSelect : determine whether the source of a select should be contained. +// +// Arguments: +// node - pointer to the node +// +void Lowering::ContainCheckSelect(GenTreeConditional* node) +{ + if (!comp->opts.OptimizationEnabled()) + { + return; + } + + // Check if the compare does not need to be generated into a register. + GenTree* startOfChain = nullptr; + ContainCheckCompareChain(node->gtCond, node, &startOfChain); + + if (startOfChain != nullptr) + { + // The earliest node in the chain will be generated as a standard compare. + assert(startOfChain->OperIsCmpCompare()); + startOfChain->AsOp()->gtGetOp1()->ClearContained(); + startOfChain->AsOp()->gtGetOp2()->ClearContained(); + ContainCheckCompare(startOfChain->AsOp()); + } +} + +#endif // TARGET_ARM64 + //------------------------------------------------------------------------ // ContainCheckBoundsChk: determine whether any source of a bounds check node should be contained. // diff --git a/src/coreclr/jit/lsraarm64.cpp b/src/coreclr/jit/lsraarm64.cpp index 21134d2dab42a5..b2c815bb1e396e 100644 --- a/src/coreclr/jit/lsraarm64.cpp +++ b/src/coreclr/jit/lsraarm64.cpp @@ -792,6 +792,14 @@ int LinearScan::BuildNode(GenTree* tree) BuildDef(tree); break; + case GT_SELECT: + assert(dstCount == 1); + srcCount = BuildOperandUses(tree->AsConditional()->gtCond); + srcCount += BuildOperandUses(tree->AsConditional()->gtOp1); + srcCount += BuildOperandUses(tree->AsConditional()->gtOp2); + BuildDef(tree, dstCandidates); + break; + } // end switch (tree->OperGet()) if (tree->IsUnusedValue() && (dstCount != 0)) diff --git a/src/coreclr/jit/lsrabuild.cpp b/src/coreclr/jit/lsrabuild.cpp index 9f01df462e978b..8ae4f5107cd5fc 100644 --- a/src/coreclr/jit/lsrabuild.cpp +++ b/src/coreclr/jit/lsrabuild.cpp @@ -3142,9 +3142,10 @@ int LinearScan::BuildOperandUses(GenTree* node, regMaskTP candidates) } #endif // FEATURE_HW_INTRINSICS #ifdef TARGET_ARM64 - if (node->OperIs(GT_MUL)) + if (node->OperIs(GT_MUL) || node->OperIsCmpCompare() || node->OperIs(GT_AND)) { - // Can be contained for MultiplyAdd on arm64 + // Can be contained for MultiplyAdd on arm64. + // Compare and AND may be contained due to If Conversion. return BuildBinaryUses(node->AsOp(), candidates); } if (node->OperIs(GT_NEG, GT_CAST, GT_LSH)) diff --git a/src/tests/JIT/opt/Compares/compareAnd2Chains.cs b/src/tests/JIT/opt/Compares/compareAnd2Chains.cs new file mode 100644 index 00000000000000..0fd39d96cbcfa6 --- /dev/null +++ b/src/tests/JIT/opt/Compares/compareAnd2Chains.cs @@ -0,0 +1,367 @@ +// 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 compare AND chains of length 2. + +using System; +using System.Runtime.CompilerServices; + +public class ComparisonTestAnd2Chains +{ + // Using bitwise AND to ensure compare chains are generated. + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_byte_2(byte a1, byte a2) => a1 == 10 & a2 == 11; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_short_2(short a1, short a2) => a1 == 10 & a2 == 11; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_int_2(int a1, int a2) => a1 == 10 & a2 == 11; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_long_2(long a1, long a2) => a1 == 10 & a2 == 11; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_ushort_2(ushort a1, ushort a2) => a1 == 10 & a2 == 11; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_uint_2(uint a1, uint a2) => a1 == 10 & a2 == 11; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_ulong_2(ulong a1, ulong a2) => a1 == 10 & a2 == 11; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_byte_2(byte a1, byte a2) => a1 != 5 & a2 != 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_short_2(short a1, short a2) => a1 != 5 & a2 != 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_int_2(int a1, int a2) => a1 != 5 & a2 != 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_long_2(long a1, long a2) => a1 != 5 & a2 != 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_ushort_2(ushort a1, ushort a2) => a1 != 5 & a2 != 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_uint_2(uint a1, uint a2) => a1 != 5 & a2 != 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_ulong_2(ulong a1, ulong a2) => a1 != 5 & a2 != 5; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_byte_2(byte a1, byte a2) => a1 < 5 & a2 < 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_short_2(short a1, short a2) => a1 < 5 & a2 < 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_int_2(int a1, int a2) => a1 < 5 & a2 < 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_long_2(long a1, long a2) => a1 < 5 & a2 < 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_ushort_2(ushort a1, ushort a2) => a1 < 5 & a2 < 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_uint_2(uint a1, uint a2) => a1 < 5 & a2 < 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_ulong_2(ulong a1, ulong a2) => a1 < 5 & a2 < 5; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_byte_2(byte a1, byte a2) => a1 <= 5 & a2 <= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_short_2(short a1, short a2) => a1 <= 5 & a2 <= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_int_2(int a1, int a2) => a1 <= 5 & a2 <= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_long_2(long a1, long a2) => a1 <= 5 & a2 <= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_ushort_2(ushort a1, ushort a2) => a1 <= 5 & a2 <= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_uint_2(uint a1, uint a2) => a1 <= 5 & a2 <= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_ulong_2(ulong a1, ulong a2) => a1 <= 5 & a2 <= 5; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_byte_2(byte a1, byte a2) => a1 > 5 & a2 > 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_short_2(short a1, short a2) => a1 > 5 & a2 > 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_int_2(int a1, int a2) => a1 > 5 & a2 > 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_long_2(long a1, long a2) => a1 > 5 & a2 > 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_ushort_2(ushort a1, ushort a2) => a1 > 5 & a2 > 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_uint_2(uint a1, uint a2) => a1 > 5 & a2 > 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_ulong_2(ulong a1, ulong a2) => a1 > 5 & a2 > 5; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_byte_2(byte a1, byte a2) => a1 >= 5 & a2 >= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_short_2(short a1, short a2) => a1 >= 5 & a2 >= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_int_2(int a1, int a2) => a1 >= 5 & a2 >= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_long_2(long a1, long a2) => a1 >= 5 & a2 >= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_ushort_2(ushort a1, ushort a2) => a1 >= 5 & a2 >= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_uint_2(uint a1, uint a2) => a1 >= 5 & a2 >= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_ulong_2(ulong a1, ulong a2) => a1 >= 5 & a2 >= 5; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static int Main() + { + if (!Eq_byte_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Eq_byte_2(10, 11) failed"); + return 101; + } + if (!Eq_short_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Eq_short_2(10, 11) failed"); + return 101; + } + if (!Eq_int_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Eq_int_2(10, 11) failed"); + return 101; + } + if (!Eq_long_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Eq_long_2(10, 11) failed"); + return 101; + } + if (!Eq_ushort_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Eq_ushort_2(10, 11) failed"); + return 101; + } + if (!Eq_uint_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Eq_uint_2(10, 11) failed"); + return 101; + } + if (!Eq_ulong_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Eq_ulong_2(10, 11) failed"); + return 101; + } + + if (!Ne_byte_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ne_byte_2(10, 11) failed"); + return 101; + } + if (!Ne_short_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ne_short_2(10, 11) failed"); + return 101; + } + if (!Ne_int_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ne_int_2(10, 11) failed"); + return 101; + } + if (!Ne_long_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ne_long_2(10, 11) failed"); + return 101; + } + if (!Ne_ushort_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ne_ushort_2(10, 11) failed"); + return 101; + } + if (!Ne_uint_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ne_uint_2(10, 11) failed"); + return 101; + } + if (!Ne_ulong_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ne_ulong_2(10, 11) failed"); + return 101; + } + + if (!Lt_byte_2(3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Lt_byte_2(3, 4) failed"); + return 101; + } + if (!Lt_short_2(3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Lt_short_2(3, 4) failed"); + return 101; + } + if (!Lt_int_2(3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Lt_int_2(3, 4) failed"); + return 101; + } + if (!Lt_long_2(3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Lt_long_2(3, 4) failed"); + return 101; + } + if (!Lt_ushort_2(3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Lt_ushort_2(3, 4) failed"); + return 101; + } + if (!Lt_uint_2(3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Lt_uint_2(3, 4) failed"); + return 101; + } + if (!Lt_ulong_2(3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Lt_ulong_2(3, 4) failed"); + return 101; + } + + if (!Le_byte_2(3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_byte_2(3, 4) failed"); + return 101; + } + if (!Le_short_2(3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_short_2(3, 4) failed"); + return 101; + } + if (!Le_int_2(3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_int_2(3, 4) failed"); + return 101; + } + if (!Le_long_2(3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_long_2(3, 4) failed"); + return 101; + } + if (!Le_ushort_2(3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_ushort_2(3, 4) failed"); + return 101; + } + if (!Le_uint_2(3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_uint_2(3, 4) failed"); + return 101; + } + if (!Le_ulong_2(3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_ulong_2(3, 4) failed"); + return 101; + } + + if (!Gt_byte_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Gt_byte_2(10, 11) failed"); + return 101; + } + if (!Gt_short_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Gt_short_2(10, 11) failed"); + return 101; + } + if (!Gt_int_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Gt_int_2(10, 11) failed"); + return 101; + } + if (!Gt_long_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Gt_long_2(10, 11) failed"); + return 101; + } + if (!Gt_ushort_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Gt_ushort_2(10, 11) failed"); + return 101; + } + if (!Gt_uint_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Gt_uint_2(10, 11) failed"); + return 101; + } + if (!Gt_ulong_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Gt_ulong_2(10, 11) failed"); + return 101; + } + + if (!Ge_byte_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ge_byte_2(10, 11) failed"); + return 101; + } + if (!Ge_short_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ge_short_2(10, 11) failed"); + return 101; + } + if (!Ge_int_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ge_int_2(10, 11) failed"); + return 101; + } + if (!Ge_long_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ge_long_2(10, 11) failed"); + return 101; + } + if (!Ge_ushort_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ge_ushort_2(10, 11) failed"); + return 101; + } + if (!Ge_uint_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ge_uint_2(10, 11) failed"); + return 101; + } + if (!Ge_ulong_2(10, 11)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_ulong_2(10, 11) failed"); + return 101; + } + + Console.WriteLine("PASSED"); + return 100; + } +} diff --git a/src/tests/JIT/opt/Compares/compareAnd2Chains.csproj b/src/tests/JIT/opt/Compares/compareAnd2Chains.csproj new file mode 100644 index 00000000000000..5e5fbae5cb863b --- /dev/null +++ b/src/tests/JIT/opt/Compares/compareAnd2Chains.csproj @@ -0,0 +1,12 @@ + + + Exe + + + PdbOnly + True + + + + + diff --git a/src/tests/JIT/opt/Compares/compareAnd3Chains.cs b/src/tests/JIT/opt/Compares/compareAnd3Chains.cs new file mode 100644 index 00000000000000..c609cdebc2d011 --- /dev/null +++ b/src/tests/JIT/opt/Compares/compareAnd3Chains.cs @@ -0,0 +1,367 @@ +// 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 compare AND chains of length 3. + +using System; +using System.Runtime.CompilerServices; + +public class ComparisonTestAnd3Chains +{ + // Using bitwise AND to ensure compare chains are generated. + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_byte_3(byte a1, byte a2, byte a3) => a1 == 10 & a2 == 11 & a3 == 12; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_short_3(short a1, short a2, short a3) => a1 == 10 & a2 == 11 & a3 == 12; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_int_3(int a1, int a2, int a3) => a1 == 10 & a2 == 11 & a3 == 12; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_long_3(long a1, long a2, long a3) => a1 == 10 & a2 == 11 & a3 == 12; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_ushort_3(ushort a1, ushort a2, ushort a3) => a1 == 10 & a2 == 11 & a3 == 12; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_uint_3(uint a1, uint a2, uint a3) => a1 == 10 & a2 == 11 & a3 == 12; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_ulong_3(ulong a1, ulong a2, ulong a3) => a1 == 10 & a2 == 11 & a3 == 12; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_byte_3(byte a1, byte a2, byte a3) => a1 != 5 & a2 != 5 & a3 != 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_short_3(short a1, short a2, short a3) => a1 != 5 & a2 != 5 & a3 != 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_int_3(int a1, int a2, int a3) => a1 != 5 & a2 != 5 & a3 != 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_long_3(long a1, long a2, long a3) => a1 != 5 & a2 != 5 & a3 != 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_ushort_3(ushort a1, ushort a2, ushort a3) => a1 != 5 & a2 != 5 & a3 != 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_uint_3(uint a1, uint a2, uint a3) => a1 != 5 & a2 != 5 & a3 != 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_ulong_3(ulong a1, ulong a2, ulong a3) => a1 != 5 & a2 != 5 & a3 != 5; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_byte_3(byte a1, byte a2, byte a3) => a1 < 5 & a2 < 5 & a3 < 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_short_3(short a1, short a2, short a3) => a1 < 5 & a2 < 5 & a3 < 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_int_3(int a1, int a2, int a3) => a1 < 5 & a2 < 5 & a3 < 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_long_3(long a1, long a2, long a3) => a1 < 5 & a2 < 5 & a3 < 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_ushort_3(ushort a1, ushort a2, ushort a3) => a1 < 5 & a2 < 5 & a3 < 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_uint_3(uint a1, uint a2, uint a3) => a1 < 5 & a2 < 5 & a3 < 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_ulong_3(ulong a1, ulong a2, ulong a3) => a1 < 5 & a2 < 5 & a3 < 5; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_byte_3(byte a1, byte a2, byte a3) => a1 <= 5 & a2 <= 5 & a3 <= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_short_3(short a1, short a2, short a3) => a1 <= 5 & a2 <= 5 & a3 <= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_int_3(int a1, int a2, int a3) => a1 <= 5 & a2 <= 5 & a3 <= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_long_3(long a1, long a2, long a3) => a1 <= 5 & a2 <= 5 & a3 <= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_ushort_3(ushort a1, ushort a2, ushort a3) => a1 <= 5 & a2 <= 5 & a3 <= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_uint_3(uint a1, uint a2, uint a3) => a1 <= 5 & a2 <= 5 & a3 <= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_ulong_3(ulong a1, ulong a2, ulong a3) => a1 <= 5 & a2 <= 5 & a3 <= 5; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_byte_3(byte a1, byte a2, byte a3) => a1 > 5 & a2 > 5 & a3 > 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_short_3(short a1, short a2, short a3) => a1 > 5 & a2 > 5 & a3 > 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_int_3(int a1, int a2, int a3) => a1 > 5 & a2 > 5 & a3 > 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_long_3(long a1, long a2, long a3) => a1 > 5 & a2 > 5 & a3 > 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_ushort_3(ushort a1, ushort a2, ushort a3) => a1 > 5 & a2 > 5 & a3 > 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_uint_3(uint a1, uint a2, uint a3) => a1 > 5 & a2 > 5 & a3 > 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_ulong_3(ulong a1, ulong a2, ulong a3) => a1 > 5 & a2 > 5 & a3 > 5; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_byte_3(byte a1, byte a2, byte a3) => a1 >= 5 & a2 >= 5 & a3 >= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_short_3(short a1, short a2, short a3) => a1 >= 5 & a2 >= 5 & a3 >= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_int_3(int a1, int a2, int a3) => a1 >= 5 & a2 >= 5 & a3 >= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_long_3(long a1, long a2, long a3) => a1 >= 5 & a2 >= 5 & a3 >= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_ushort_3(ushort a1, ushort a2, ushort a3) => a1 >= 5 & a2 >= 5 & a3 >= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_uint_3(uint a1, uint a2, uint a3) => a1 >= 5 & a2 >= 5 & a3 >= 5; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_ulong_3(ulong a1, ulong a2, ulong a3) => a1 >= 5 & a2 >= 5 & a3 >= 5; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static int Main() + { + if (!Eq_byte_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Eq_byte_3(10, 11, 12) failed"); + return 101; + } + if (!Eq_short_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Eq_short_3(10, 11, 12) failed"); + return 101; + } + if (!Eq_int_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Eq_int_3(10, 11, 12) failed"); + return 101; + } + if (!Eq_long_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Eq_long_3(10, 11, 12) failed"); + return 101; + } + if (!Eq_ushort_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Eq_ushort_3(10, 11, 12) failed"); + return 101; + } + if (!Eq_uint_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Eq_uint_3(10, 11, 12) failed"); + return 101; + } + if (!Eq_ulong_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Eq_ulong_3(10, 11, 12) failed"); + return 101; + } + + if (!Ne_byte_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ne_byte_3(10, 11, 12) failed"); + return 101; + } + if (!Ne_short_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ne_short_3(10, 11, 12) failed"); + return 101; + } + if (!Ne_int_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ne_int_3(10, 11, 12) failed"); + return 101; + } + if (!Ne_long_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ne_long_3(10, 11, 12) failed"); + return 101; + } + if (!Ne_ushort_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ne_ushort_3(10, 11, 12) failed"); + return 101; + } + if (!Ne_uint_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ne_uint_3(10, 11, 12) failed"); + return 101; + } + if (!Ne_ulong_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ne_ulong_3(10, 11, 12) failed"); + return 101; + } + + if (!Lt_byte_3(2, 3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Lt_byte_3(2, 3, 4) failed"); + return 101; + } + if (!Lt_short_3(2, 3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Lt_short_3(2, 3, 4) failed"); + return 101; + } + if (!Lt_int_3(2, 3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Lt_int_3(2, 3, 4) failed"); + return 101; + } + if (!Lt_long_3(2, 3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Lt_long_3(2, 3, 4) failed"); + return 101; + } + if (!Lt_ushort_3(2, 3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Lt_ushort_3(2, 3, 4) failed"); + return 101; + } + if (!Lt_uint_3(2, 3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Lt_uint_3(2, 3, 4) failed"); + return 101; + } + if (!Lt_ulong_3(2, 3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Lt_ulong_3(2, 3, 4) failed"); + return 101; + } + + if (!Le_byte_3(2, 3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_byte_3(2, 3, 4) failed"); + return 101; + } + if (!Le_short_3(2, 3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_short_3(2, 3, 4) failed"); + return 101; + } + if (!Le_int_3(2, 3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_int_3(2, 3, 4) failed"); + return 101; + } + if (!Le_long_3(2, 3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_long_3(2, 3, 4) failed"); + return 101; + } + if (!Le_ushort_3(2, 3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_ushort_3(2, 3, 4) failed"); + return 101; + } + if (!Le_uint_3(2, 3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_uint_3(2, 3, 4) failed"); + return 101; + } + if (!Le_ulong_3(2, 3, 4)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_ulong_3(2, 3, 4) failed"); + return 101; + } + + if (!Gt_byte_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Gt_byte_3(10, 11, 12) failed"); + return 101; + } + if (!Gt_short_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Gt_short_3(10, 11, 12) failed"); + return 101; + } + if (!Gt_int_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Gt_int_3(10, 11, 12) failed"); + return 101; + } + if (!Gt_long_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Gt_long_3(10, 11, 12) failed"); + return 101; + } + if (!Gt_ushort_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Gt_ushort_3(10, 11, 12) failed"); + return 101; + } + if (!Gt_uint_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Gt_uint_3(10, 11, 12) failed"); + return 101; + } + if (!Gt_ulong_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Gt_ulong_3(10, 11, 12) failed"); + return 101; + } + + if (!Ge_byte_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ge_byte_3(10, 11, 12) failed"); + return 101; + } + if (!Ge_short_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ge_short_3(10, 11, 12) failed"); + return 101; + } + if (!Ge_int_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ge_int_3(10, 11, 12) failed"); + return 101; + } + if (!Ge_long_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ge_long_3(10, 11, 12) failed"); + return 101; + } + if (!Ge_ushort_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ge_ushort_3(10, 11, 12) failed"); + return 101; + } + if (!Ge_uint_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Ge_uint_3(10, 11, 12) failed"); + return 101; + } + if (!Ge_ulong_3(10, 11, 12)) + { + Console.WriteLine("ComparisonTestAnd2Chains:Le_ulong_3(10, 11, 12) failed"); + return 101; + } + + Console.WriteLine("PASSED"); + return 100; + } +} diff --git a/src/tests/JIT/opt/Compares/compareAnd3Chains.csproj b/src/tests/JIT/opt/Compares/compareAnd3Chains.csproj new file mode 100644 index 00000000000000..5e5fbae5cb863b --- /dev/null +++ b/src/tests/JIT/opt/Compares/compareAnd3Chains.csproj @@ -0,0 +1,12 @@ + + + Exe + + + PdbOnly + True + + + + + diff --git a/src/tests/JIT/opt/Compares/compareAndTestChains.cs b/src/tests/JIT/opt/Compares/compareAndTestChains.cs new file mode 100644 index 00000000000000..c89e04f487ecf1 --- /dev/null +++ b/src/tests/JIT/opt/Compares/compareAndTestChains.cs @@ -0,0 +1,367 @@ +// 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 compare AND chains that include a binary test. + +using System; +using System.Runtime.CompilerServices; + +public class ComparisonTestAndTestChains +{ + // Using bitwise AND to ensure compare chains are generated. + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_byte_bool(byte a1, bool a2) => (a1 == 10) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_short_bool(short a1, bool a2) => (a1 == 10) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_int_bool(int a1, bool a2) => (a1 == 10) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_long_bool(long a1, bool a2) => (a1 == 10) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_ushort_bool(ushort a1, bool a2) => (a1 == 10) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_uint_bool(uint a1, bool a2) => (a1 == 10) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Eq_ulong_bool(ulong a1, bool a2) => (a1 == 10) & !a2; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_byte_bool(byte a1, bool a2) => (a1 != 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_short_bool(short a1, bool a2) => (a1 != 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_int_bool(int a1, bool a2) => (a1 != 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_long_bool(long a1, bool a2) => (a1 != 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_ushort_bool(ushort a1, bool a2) => (a1 != 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_uint_bool(uint a1, bool a2) => (a1 != 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ne_ulong_bool(ulong a1, bool a2) => (a1 != 5) & !a2; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_byte_bool(byte a1, bool a2) => (a1 < 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_short_bool(short a1, bool a2) => (a1 < 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_int_bool(int a1, bool a2) => (a1 < 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_long_bool(long a1, bool a2) => (a1 < 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_ushort_bool(ushort a1, bool a2) => (a1 < 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_uint_bool(uint a1, bool a2) => (a1 < 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Lt_ulong_bool(ulong a1, bool a2) => (a1 < 5) & !a2; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_byte_bool(byte a1, bool a2) => (a1 <= 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_short_bool(short a1, bool a2) => (a1 <= 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_int_bool(int a1, bool a2) => (a1 <= 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_long_bool(long a1, bool a2) => (a1 <= 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_ushort_bool(ushort a1, bool a2) => (a1 <= 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_uint_bool(uint a1, bool a2) => (a1 <= 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Le_ulong_bool(ulong a1, bool a2) => (a1 <= 5) & !a2; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_byte_bool(byte a1, bool a2) => (a1 > 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_short_bool(short a1, bool a2) => (a1 > 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_int_bool(int a1, bool a2) => (a1 > 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_long_bool(long a1, bool a2) => (a1 > 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_ushort_bool(ushort a1, bool a2) => (a1 > 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_uint_bool(uint a1, bool a2) => (a1 > 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Gt_ulong_bool(ulong a1, bool a2) => (a1 > 5) & !a2; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_byte_bool(byte a1, bool a2) => (a1 >= 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_short_bool(short a1, bool a2) => (a1 >= 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_int_bool(int a1, bool a2) => (a1 >= 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_long_bool(long a1, bool a2) => (a1 >= 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_ushort_bool(ushort a1, bool a2) => (a1 >= 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_uint_bool(uint a1, bool a2) => (a1 >= 5) & !a2; + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool Ge_ulong_bool(ulong a1, bool a2) => (a1 >= 5) & !a2; + + + [MethodImpl(MethodImplOptions.NoInlining)] + public static int Main() + { + if (!Eq_byte_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Eq_byte_bool(10, false) failed"); + return 101; + } + if (!Eq_short_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Eq_short_bool(10, false) failed"); + return 101; + } + if (!Eq_int_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Eq_int_bool(10, false) failed"); + return 101; + } + if (!Eq_long_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Eq_long_bool(10, false) failed"); + return 101; + } + if (!Eq_ushort_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Eq_ushort_bool(10, false) failed"); + return 101; + } + if (!Eq_uint_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Eq_uint_bool(10, false) failed"); + return 101; + } + if (!Eq_ulong_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Eq_ulong_bool(10, false) failed"); + return 101; + } + + if (!Ne_byte_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Ne_byte_bool(10, false) failed"); + return 101; + } + if (!Ne_short_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Ne_short_bool(10, false) failed"); + return 101; + } + if (!Ne_int_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Ne_int_bool(10, false) failed"); + return 101; + } + if (!Ne_long_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Ne_long_bool(10, false) failed"); + return 101; + } + if (!Ne_ushort_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Ne_ushort_bool(10, false) failed"); + return 101; + } + if (!Ne_uint_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Ne_uint_bool(10, false) failed"); + return 101; + } + if (!Ne_ulong_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Ne_ulong_bool(10, false) failed"); + return 101; + } + + if (!Lt_byte_bool(3, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Lt_byte_bool(3, false) failed"); + return 101; + } + if (!Lt_short_bool(3, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Lt_short_bool(3, false) failed"); + return 101; + } + if (!Lt_int_bool(3, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Lt_int_bool(3, false) failed"); + return 101; + } + if (!Lt_long_bool(3, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Lt_long_bool(3, false) failed"); + return 101; + } + if (!Lt_ushort_bool(3, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Lt_ushort_bool(3, false) failed"); + return 101; + } + if (!Lt_uint_bool(3, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Lt_uint_bool(3, false) failed"); + return 101; + } + if (!Lt_ulong_bool(3, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Lt_ulong_bool(3, false) failed"); + return 101; + } + + if (!Le_byte_bool(3, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Le_byte_bool(3, false) failed"); + return 101; + } + if (!Le_short_bool(3, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Le_short_bool(3, false) failed"); + return 101; + } + if (!Le_int_bool(3, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Le_int_bool(3, false) failed"); + return 101; + } + if (!Le_long_bool(3, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Le_long_bool(3, false) failed"); + return 101; + } + if (!Le_ushort_bool(3, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Le_ushort_bool(3, false) failed"); + return 101; + } + if (!Le_uint_bool(3, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Le_uint_bool(3, false) failed"); + return 101; + } + if (!Le_ulong_bool(3, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Le_ulong_bool(3, false) failed"); + return 101; + } + + if (!Gt_byte_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Gt_byte_bool(10, false) failed"); + return 101; + } + if (!Gt_short_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Gt_short_bool(10, false) failed"); + return 101; + } + if (!Gt_int_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Gt_int_bool(10, false) failed"); + return 101; + } + if (!Gt_long_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Gt_long_bool(10, false) failed"); + return 101; + } + if (!Gt_ushort_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Gt_ushort_bool(10, false) failed"); + return 101; + } + if (!Gt_uint_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Gt_uint_bool(10, false) failed"); + return 101; + } + if (!Gt_ulong_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Gt_ulong_bool(10, false) failed"); + return 101; + } + + if (!Ge_byte_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Ge_byte_bool(10, false) failed"); + return 101; + } + if (!Ge_short_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Ge_short_bool(10, false) failed"); + return 101; + } + if (!Ge_int_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Ge_int_bool(10, false) failed"); + return 101; + } + if (!Ge_long_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Ge_long_bool(10, false) failed"); + return 101; + } + if (!Ge_ushort_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Ge_ushort_bool(10, false) failed"); + return 101; + } + if (!Ge_uint_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Ge_uint_bool(10, false) failed"); + return 101; + } + if (!Ge_ulong_bool(10, false)) + { + Console.WriteLine("ComparisonTestAndTestChains:Le_ulong_bool(10, false) failed"); + return 101; + } + + Console.WriteLine("PASSED"); + return 100; + } +} diff --git a/src/tests/JIT/opt/Compares/compareAndTestChains.csproj b/src/tests/JIT/opt/Compares/compareAndTestChains.csproj new file mode 100644 index 00000000000000..5e5fbae5cb863b --- /dev/null +++ b/src/tests/JIT/opt/Compares/compareAndTestChains.csproj @@ -0,0 +1,12 @@ + + + Exe + + + PdbOnly + True + + + + +