diff --git a/src/coreclr/jit/codegenarm64.cpp b/src/coreclr/jit/codegenarm64.cpp index ec01f356e194ed..07f90a6a74213d 100644 --- a/src/coreclr/jit/codegenarm64.cpp +++ b/src/coreclr/jit/codegenarm64.cpp @@ -4775,8 +4775,44 @@ void CodeGen::genCodeForCompare(GenTreeOp* tree) emit->emitIns_R_R_I(ins, cmpSize, op1->GetRegNum(), shiftOp1->GetRegNum(), shiftOp2->AsIntConCommon()->IntegralValue(), ShiftOpToInsOpts(oper)); + break; + } + case GT_CAST: + { + GenTreeCast* cast = op2->gtGetOp1()->AsCast(); + + GenIntCastDesc desc(cast); + + // These casts should not lead to an overflow check. + assert(desc.CheckKind() == GenIntCastDesc::CHECK_NONE); + + insOpts extOpts = INS_OPTS_NONE; + switch (desc.ExtendKind()) + { + case GenIntCastDesc::ZERO_EXTEND_SMALL_INT: + extOpts = (desc.ExtendSrcSize() == 1) ? INS_OPTS_UXTB : INS_OPTS_UXTH; + break; + case GenIntCastDesc::SIGN_EXTEND_SMALL_INT: + extOpts = (desc.ExtendSrcSize() == 1) ? INS_OPTS_SXTB : INS_OPTS_SXTH; + break; + case GenIntCastDesc::ZERO_EXTEND_INT: + extOpts = INS_OPTS_UXTW; + break; + case GenIntCastDesc::SIGN_EXTEND_INT: + extOpts = INS_OPTS_SXTW; + break; + case GenIntCastDesc::COPY: + extOpts = INS_OPTS_NONE; // Perform cast implicitly. + break; + default: + // Other casts should not lead here as they will not pass the + // IsContainableUnaryOrBinaryOp check. + unreached(); + } + + emit->emitIns_R_R(ins, cmpSize, op1->GetRegNum(), cast->CastOp()->GetRegNum(), extOpts); + break; } - break; default: unreached(); diff --git a/src/coreclr/jit/emitarm64.cpp b/src/coreclr/jit/emitarm64.cpp index 68cdadcb1c7cda..ea9a9b53b2797e 100644 --- a/src/coreclr/jit/emitarm64.cpp +++ b/src/coreclr/jit/emitarm64.cpp @@ -4695,13 +4695,13 @@ void emitter::emitIns_R_R(instruction ins, case INS_str: case INS_strb: case INS_strh: - case INS_cmn: case INS_tst: assert(insOptsNone(opt)); emitIns_R_R_I(ins, attr, reg1, reg2, 0, INS_OPTS_NONE); return; case INS_cmp: + case INS_cmn: emitIns_R_R_I(ins, attr, reg1, reg2, 0, opt); return; diff --git a/src/coreclr/jit/lowerarmarch.cpp b/src/coreclr/jit/lowerarmarch.cpp index d06bfaa892abce..eddffed4f2dae2 100644 --- a/src/coreclr/jit/lowerarmarch.cpp +++ b/src/coreclr/jit/lowerarmarch.cpp @@ -384,7 +384,7 @@ bool Lowering::IsContainableUnaryOrBinaryOp(GenTree* parentNode, GenTree* childN if (childNode->OperIs(GT_NEG)) { // If we have a contained LSH, RSH or RSZ, we can still contain NEG if the parent is a EQ or NE. - if (childNode->gtGetOp1()->isContained() && !childNode->gtGetOp1()->OperIs(GT_LSH, GT_RSH, GT_RSZ)) + if (childNode->gtGetOp1()->isContained() && !childNode->gtGetOp1()->OperIs(GT_LSH, GT_RSH, GT_RSZ, GT_CAST)) { // Cannot contain if the childs op1 is already contained return false; @@ -399,10 +399,23 @@ bool Lowering::IsContainableUnaryOrBinaryOp(GenTree* parentNode, GenTree* childN // EQ and NE are the only valid comparison ops that can contain NEG. if (parentNode->OperIs(GT_EQ, GT_NE)) { - if (IsInvariantInRange(childNode, parentNode)) + if (!IsInvariantInRange(childNode, parentNode)) { - return true; + return false; + } + + if (childNode->gtGetOp1()->OperIs(GT_CAST)) + { + // Grab the cast as well, we can contain this with cmn. + GenTreeCast* cast = childNode->gtGetOp1()->AsCast(); + + assert(!cast->gtOverflow()); + assert(varTypeIsIntegral(cast) && varTypeIsIntegral(cast->CastToType())); + + MakeSrcContained(childNode, cast); } + + return true; } return false; @@ -459,7 +472,6 @@ bool Lowering::IsContainableUnaryOrBinaryOp(GenTree* parentNode, GenTree* childN } } - // TODO: Handle CMN return false; } diff --git a/src/tests/JIT/opt/InstructionCombining/Cmn.cs b/src/tests/JIT/opt/InstructionCombining/Cmn.cs index f8e05ed9125a0d..991a0c746d6f5f 100644 --- a/src/tests/JIT/opt/InstructionCombining/Cmn.cs +++ b/src/tests/JIT/opt/InstructionCombining/Cmn.cs @@ -50,6 +50,31 @@ public static int CheckCompareNegative() fail = true; } + if (!CmnExtendedB(1, 0xff)) + { + fail = true; + } + if (!CmnExtendedH(1, 0xffff)) + { + fail = true; + } + if (!CmnExtendedS(1, 0xffffffff)) + { + fail = true; + } + if (!CmnExtendedUB(-1, 0x101)) + { + fail = true; + } + if (!CmnExtendedUH(-1, 0x10001)) + { + fail = true; + } + if (!CmnExtendedUS(-1, 0x100000001)) + { + fail = true; + } + if (fail) { return 101; @@ -134,5 +159,47 @@ static bool CmnLargeShift64Bit(long a, long b) } return false; } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool CmnExtendedB(int a, int b) + { + //ARM64-FULL-LINE: cmn {{w[0-9]+}}, {{w[0-9]+}}, SXTB + return (a == -(sbyte)b); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool CmnExtendedH(int a, int b) + { + //ARM64-FULL-LINE: cmn {{w[0-9]+}}, {{w[0-9]+}}, SXTH + return (a == -(short)b); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool CmnExtendedS(long a, long b) + { + //ARM64-FULL-LINE: cmn {{x[0-9]+}}, {{w[0-9]+}}, SXTW + return (a == -(long)(int)b); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool CmnExtendedUB(int a, int b) + { + //ARM64-FULL-LINE: cmn {{w[0-9]+}}, {{w[0-9]+}}, UXTB + return (a == -(byte)b); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool CmnExtendedUH(int a, int b) + { + //ARM64-FULL-LINE: cmn {{w[0-9]+}}, {{w[0-9]+}}, UXTH + return (a == -(ushort)b); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool CmnExtendedUS(long a, long b) + { + //ARM64-FULL-LINE: cmn {{x[0-9]+}}, {{w[0-9]+}}, UXTW + return (a == -(uint)b); + } } }