diff --git a/src/jit/codegen.h b/src/jit/codegen.h index 9eb1ba397a79..0523d53b0188 100644 --- a/src/jit/codegen.h +++ b/src/jit/codegen.h @@ -789,7 +789,78 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX void genLongToIntCast(GenTree* treeNode); #endif - void genIntToIntCast(GenTree* treeNode); + struct GenIntCastDesc + { + enum CheckKind + { + CHECK_NONE, + CHECK_SMALL_INT_RANGE, + CHECK_POSITIVE, +#ifdef _TARGET_64BIT_ + CHECK_UINT_RANGE, + CHECK_POSITIVE_INT_RANGE, + CHECK_INT_RANGE, +#endif + }; + + enum ExtendKind + { + COPY, + ZERO_EXTEND_SMALL_INT, + SIGN_EXTEND_SMALL_INT, +#ifdef _TARGET_64BIT_ + ZERO_EXTEND_INT, + SIGN_EXTEND_INT, +#endif + }; + + private: + CheckKind m_checkKind; + unsigned m_checkSrcSize; + int m_checkSmallIntMin; + int m_checkSmallIntMax; + ExtendKind m_extendKind; + unsigned m_extendSrcSize; + + public: + GenIntCastDesc(GenTreeCast* cast); + + CheckKind CheckKind() const + { + return m_checkKind; + } + + unsigned CheckSrcSize() const + { + assert(m_checkKind != CHECK_NONE); + return m_checkSrcSize; + } + + int CheckSmallIntMin() const + { + assert(m_checkKind == CHECK_SMALL_INT_RANGE); + return m_checkSmallIntMin; + } + + int CheckSmallIntMax() const + { + assert(m_checkKind == CHECK_SMALL_INT_RANGE); + return m_checkSmallIntMax; + } + + ExtendKind ExtendKind() const + { + return m_extendKind; + } + + unsigned ExtendSrcSize() const + { + return m_extendSrcSize; + } + }; + + void genIntCastOverflowCheck(GenTreeCast* cast, const GenIntCastDesc& desc, regNumber reg); + void genIntToIntCast(GenTreeCast* cast); void genFloatToFloatCast(GenTree* treeNode); void genFloatToIntCast(GenTree* treeNode); void genIntToFloatCast(GenTree* treeNode); diff --git a/src/jit/codegenarmarch.cpp b/src/jit/codegenarmarch.cpp index eb53d2b0b71c..746636a69dc4 100644 --- a/src/jit/codegenarmarch.cpp +++ b/src/jit/codegenarmarch.cpp @@ -2850,202 +2850,150 @@ void CodeGen::genJmpMethod(GenTree* jmp) } //------------------------------------------------------------------------ -// genIntToIntCast: Generate code for an integer cast +// genIntCastOverflowCheck: Generate overflow checking code for an integer cast. // // Arguments: -// treeNode - The GT_CAST node -// -// Return Value: -// None. +// cast - The GT_CAST node +// desc - The cast description +// reg - The register containing the value to check // -// Assumptions: -// The treeNode must have an assigned register. -// For a signed convert from byte, the source must be in a byte-addressable register. -// Neither the source nor target type can be a floating point type. -// -// TODO-ARM64-CQ: Allow castOp to be a contained node without an assigned register. -// -void CodeGen::genIntToIntCast(GenTree* treeNode) +void CodeGen::genIntCastOverflowCheck(GenTreeCast* cast, const GenIntCastDesc& desc, regNumber reg) { - assert(treeNode->OperGet() == GT_CAST); - - GenTree* castOp = treeNode->gtCast.CastOp(); - emitter* emit = getEmitter(); - - var_types dstType = treeNode->CastToType(); - var_types srcType = genActualType(castOp->TypeGet()); - - assert(genTypeSize(srcType) <= genTypeSize(TYP_I_IMPL)); - - regNumber targetReg = treeNode->gtRegNum; - regNumber sourceReg = castOp->gtRegNum; - - // For Long to Int conversion we will have a reserved integer register to hold the immediate mask - regNumber tmpReg = (treeNode->AvailableTempRegCount() == 0) ? REG_NA : treeNode->GetSingleTempReg(); - - assert(genIsValidIntReg(targetReg)); - assert(genIsValidIntReg(sourceReg)); - - genConsumeReg(castOp); - Lowering::CastInfo castInfo; + switch (desc.CheckKind()) + { + case GenIntCastDesc::CHECK_POSITIVE: + getEmitter()->emitIns_R_I(INS_cmp, EA_ATTR(desc.CheckSrcSize()), reg, 0); + genJumpToThrowHlpBlk(EJ_lt, SCK_OVERFLOW); + break; - // Get information about the cast. - Lowering::getCastDescription(treeNode, &castInfo); +#ifdef _TARGET_64BIT_ + case GenIntCastDesc::CHECK_UINT_RANGE: + // We need to check if the value is not greater than 0xFFFFFFFF but this value + // cannot be encoded in the immediate operand of CMP. Use TST instead to check + // if the upper 32 bits are zero. + getEmitter()->emitIns_R_I(INS_tst, EA_8BYTE, reg, 0xFFFFFFFF00000000LL); + genJumpToThrowHlpBlk(EJ_ne, SCK_OVERFLOW); + break; - if (castInfo.requiresOverflowCheck) - { - bool movRequired = (sourceReg != targetReg); - emitAttr movSize = emitActualTypeSize(dstType); - emitAttr cmpSize = EA_ATTR(genTypeSize(srcType)); + case GenIntCastDesc::CHECK_POSITIVE_INT_RANGE: + // We need to check if the value is not greater than 0x7FFFFFFF but this value + // cannot be encoded in the immediate operand of CMP. Use TST instead to check + // if the upper 33 bits are zero. + getEmitter()->emitIns_R_I(INS_tst, EA_8BYTE, reg, 0xFFFFFFFF80000000LL); + genJumpToThrowHlpBlk(EJ_ne, SCK_OVERFLOW); + break; - if (castInfo.signCheckOnly) + case GenIntCastDesc::CHECK_INT_RANGE: { - // We only need to check for a negative value in sourceReg - emit->emitIns_R_I(INS_cmp, cmpSize, sourceReg, 0); - emitJumpKind jmpLT = genJumpKindForOper(GT_LT, CK_SIGNED); - genJumpToThrowHlpBlk(jmpLT, SCK_OVERFLOW); - noway_assert(genTypeSize(srcType) == 4 || genTypeSize(srcType) == 8); - // This is only interesting case to ensure zero-upper bits. - if ((srcType == TYP_INT) && (dstType == TYP_ULONG)) - { - // cast to TYP_ULONG: - // We use a mov with size=EA_4BYTE - // which will zero out the upper bits - movSize = EA_4BYTE; - movRequired = true; - } + const regNumber tempReg = cast->GetSingleTempReg(); + assert(tempReg != reg); + instGen_Set_Reg_To_Imm(EA_8BYTE, tempReg, INT32_MAX); + getEmitter()->emitIns_R_R(INS_cmp, EA_8BYTE, reg, tempReg); + genJumpToThrowHlpBlk(EJ_gt, SCK_OVERFLOW); + instGen_Set_Reg_To_Imm(EA_8BYTE, tempReg, INT32_MIN); + getEmitter()->emitIns_R_R(INS_cmp, EA_8BYTE, reg, tempReg); + genJumpToThrowHlpBlk(EJ_lt, SCK_OVERFLOW); } - else if (castInfo.unsignedSource || castInfo.unsignedDest) + break; +#endif + + default: { - // When we are converting from/to unsigned, - // we only have to check for any bits set in 'typeMask' + assert(desc.CheckKind() == GenIntCastDesc::CHECK_SMALL_INT_RANGE); + const int castMaxValue = desc.CheckSmallIntMax(); + const int castMinValue = desc.CheckSmallIntMin(); - noway_assert(castInfo.typeMask != 0); -#if defined(_TARGET_ARM_) - if (arm_Valid_Imm_For_Instr(INS_tst, castInfo.typeMask, INS_FLAGS_DONT_CARE)) + // Values greater than 255 cannot be encoded in the immediate operand of CMP. + // Replace (x > max) with (x >= max + 1) where max + 1 (a power of 2) can be + // encoded. We could do this for all max values but on ARM32 "cmp r0, 255" + // is better than "cmp r0, 256" because it has a shorter encoding. + if (castMaxValue > 255) { - emit->emitIns_R_I(INS_tst, cmpSize, sourceReg, castInfo.typeMask); + assert((castMaxValue == 32767) || (castMaxValue == 65535)); + getEmitter()->emitIns_R_I(INS_cmp, EA_SIZE(desc.CheckSrcSize()), reg, castMaxValue + 1); + genJumpToThrowHlpBlk((castMinValue == 0) ? EJ_hs : EJ_ge, SCK_OVERFLOW); } else { - noway_assert(tmpReg != REG_NA); - instGen_Set_Reg_To_Imm(cmpSize, tmpReg, castInfo.typeMask); - emit->emitIns_R_R(INS_tst, cmpSize, sourceReg, tmpReg); + getEmitter()->emitIns_R_I(INS_cmp, EA_SIZE(desc.CheckSrcSize()), reg, castMaxValue); + genJumpToThrowHlpBlk((castMinValue == 0) ? EJ_hi : EJ_gt, SCK_OVERFLOW); } -#elif defined(_TARGET_ARM64_) - emit->emitIns_R_I(INS_tst, cmpSize, sourceReg, castInfo.typeMask); -#endif // _TARGET_ARM* - emitJumpKind jmpNotEqual = genJumpKindForOper(GT_NE, CK_SIGNED); - genJumpToThrowHlpBlk(jmpNotEqual, SCK_OVERFLOW); - } - else - { - // For a narrowing signed cast - // - // We must check the value is in a signed range. - - // Compare with the MAX - - noway_assert((castInfo.typeMin != 0) && (castInfo.typeMax != 0)); -#if defined(_TARGET_ARM_) - if (emitter::emitIns_valid_imm_for_cmp(castInfo.typeMax, INS_FLAGS_DONT_CARE)) -#elif defined(_TARGET_ARM64_) - if (emitter::emitIns_valid_imm_for_cmp(castInfo.typeMax, cmpSize)) -#endif // _TARGET_* - { - emit->emitIns_R_I(INS_cmp, cmpSize, sourceReg, castInfo.typeMax); - } - else + if (castMinValue != 0) { - noway_assert(tmpReg != REG_NA); - instGen_Set_Reg_To_Imm(cmpSize, tmpReg, castInfo.typeMax); - emit->emitIns_R_R(INS_cmp, cmpSize, sourceReg, tmpReg); + getEmitter()->emitIns_R_I(INS_cmp, EA_SIZE(desc.CheckSrcSize()), reg, castMinValue); + genJumpToThrowHlpBlk(EJ_lt, SCK_OVERFLOW); } + } + break; + } +} - emitJumpKind jmpGT = genJumpKindForOper(GT_GT, CK_SIGNED); - genJumpToThrowHlpBlk(jmpGT, SCK_OVERFLOW); +//------------------------------------------------------------------------ +// genIntToIntCast: Generate code for an integer cast, with or without overflow check. +// +// Arguments: +// cast - The GT_CAST node +// +// Assumptions: +// The cast node is not a contained node and must have an assigned register. +// Neither the source nor target type can be a floating point type. +// +// TODO-ARM64-CQ: Allow castOp to be a contained node without an assigned register. +// +void CodeGen::genIntToIntCast(GenTreeCast* cast) +{ + genConsumeRegs(cast->gtGetOp1()); -// Compare with the MIN + const regNumber srcReg = cast->gtGetOp1()->gtRegNum; + const regNumber dstReg = cast->gtRegNum; -#if defined(_TARGET_ARM_) - if (emitter::emitIns_valid_imm_for_cmp(castInfo.typeMin, INS_FLAGS_DONT_CARE)) -#elif defined(_TARGET_ARM64_) - if (emitter::emitIns_valid_imm_for_cmp(castInfo.typeMin, cmpSize)) -#endif // _TARGET_* - { - emit->emitIns_R_I(INS_cmp, cmpSize, sourceReg, castInfo.typeMin); - } - else - { - noway_assert(tmpReg != REG_NA); - instGen_Set_Reg_To_Imm(cmpSize, tmpReg, castInfo.typeMin); - emit->emitIns_R_R(INS_cmp, cmpSize, sourceReg, tmpReg); - } + assert(genIsValidIntReg(srcReg)); + assert(genIsValidIntReg(dstReg)); - emitJumpKind jmpLT = genJumpKindForOper(GT_LT, CK_SIGNED); - genJumpToThrowHlpBlk(jmpLT, SCK_OVERFLOW); - } + GenIntCastDesc desc(cast); - if (movRequired) - { - emit->emitIns_R_R(INS_mov, movSize, targetReg, sourceReg); - } + if (desc.CheckKind() != GenIntCastDesc::CHECK_NONE) + { + genIntCastOverflowCheck(cast, desc, srcReg); } - else // Non-overflow checking cast. + + if ((desc.ExtendKind() != GenIntCastDesc::COPY) || (srcReg != dstReg)) { - const unsigned srcSize = genTypeSize(srcType); - const unsigned dstSize = genTypeSize(dstType); - instruction ins; - emitAttr insSize; + instruction ins; + unsigned insSize; - if (dstSize < 4) + switch (desc.ExtendKind()) { - // Casting to a small type really means widening from that small type to INT/LONG. - ins = ins_Move_Extend(dstType, true); - insSize = emitActualTypeSize(treeNode->TypeGet()); - } + case GenIntCastDesc::ZERO_EXTEND_SMALL_INT: + ins = (desc.ExtendSrcSize() == 1) ? INS_uxtb : INS_uxth; + insSize = 4; + break; + case GenIntCastDesc::SIGN_EXTEND_SMALL_INT: + ins = (desc.ExtendSrcSize() == 1) ? INS_sxtb : INS_sxth; + insSize = 4; + break; #ifdef _TARGET_64BIT_ - // dstType cannot be a long type on 32 bit targets, such casts should have been decomposed. - // srcType cannot be a small type since it's the "actual type" of the cast operand. - // This means that widening casts do not occur on 32 bit targets. - else if (dstSize > srcSize) - { - // (U)INT to (U)LONG widening cast - assert((srcSize == 4) && (dstSize == 8)); - // Make sure the node type has the same size as the destination type. - assert(genTypeSize(treeNode->TypeGet()) == dstSize); - - ins = treeNode->IsUnsigned() ? INS_mov : INS_sxtw; - // SXTW requires EA_8BYTE but MOV requires EA_4BYTE in order to zero out the upper 32 bits. - insSize = (ins == INS_sxtw) ? EA_8BYTE : EA_4BYTE; - } + case GenIntCastDesc::ZERO_EXTEND_INT: + ins = INS_mov; + insSize = 4; + break; + case GenIntCastDesc::SIGN_EXTEND_INT: + ins = INS_sxtw; + insSize = 8; + break; #endif - else - { - // Sign changing cast or narrowing cast - assert(dstSize <= srcSize); - // Note that narrowing casts are possible only on 64 bit targets. - assert(srcSize <= genTypeSize(TYP_I_IMPL)); - // Make sure the node type has the same size as the destination type. - assert(genTypeSize(treeNode->TypeGet()) == dstSize); - - // This cast basically does nothing, even when narrowing it is the job of the - // consumer of this node to use the appropiate register size (32 or 64 bit) - // and not rely on the cast to set the upper 32 bits in a certain manner. - // Still, we will need to generate a MOV instruction if the source and target - // registers are different. - ins = (sourceReg != targetReg) ? INS_mov : INS_none; - insSize = EA_SIZE(dstSize); + default: + assert(desc.ExtendKind() == GenIntCastDesc::COPY); + ins = INS_mov; + insSize = desc.ExtendSrcSize(); + break; } - if (ins != INS_none) - { - emit->emitIns_R_R(ins, insSize, targetReg, sourceReg); - } + getEmitter()->emitIns_R_R(ins, EA_ATTR(insSize), dstReg, srcReg); } - genProduceReg(treeNode); + genProduceReg(cast); } //------------------------------------------------------------------------ diff --git a/src/jit/codegenlinear.cpp b/src/jit/codegenlinear.cpp index 9962a627f285..4d9fe4eb55ba 100644 --- a/src/jit/codegenlinear.cpp +++ b/src/jit/codegenlinear.cpp @@ -1943,11 +1943,129 @@ void CodeGen::genCodeForCast(GenTreeOp* tree) else { // Casts int <--> int - genIntToIntCast(tree); + genIntToIntCast(tree->AsCast()); } // The per-case functions call genProduceReg() } +CodeGen::GenIntCastDesc::GenIntCastDesc(GenTreeCast* cast) +{ + const var_types srcType = genActualType(cast->gtGetOp1()->TypeGet()); + const bool srcUnsigned = cast->IsUnsigned(); + const unsigned srcSize = genTypeSize(srcType); + const var_types castType = cast->gtCastType; + const bool castUnsigned = varTypeIsUnsigned(castType); + const unsigned castSize = genTypeSize(castType); + const var_types dstType = genActualType(cast->TypeGet()); + const unsigned dstSize = genTypeSize(dstType); + const bool overflow = cast->gtOverflow(); + + assert((srcSize == 4) || (srcSize == genTypeSize(TYP_I_IMPL))); + assert((dstSize == 4) || (dstSize == genTypeSize(TYP_I_IMPL))); + + assert(dstSize == genTypeSize(genActualType(castType))); + + if (castSize < 4) // Cast to small int type + { + if (overflow) + { + m_checkKind = CHECK_SMALL_INT_RANGE; + m_checkSrcSize = srcSize; + // Since these are small int types we can compute the min and max + // values of the castType without risk of integer overflow. + const int castNumBits = (castSize * 8) - (castUnsigned ? 0 : 1); + m_checkSmallIntMax = (1 << castNumBits) - 1; + m_checkSmallIntMin = (castUnsigned | srcUnsigned) ? 0 : (-m_checkSmallIntMax - 1); + + m_extendKind = COPY; + m_extendSrcSize = dstSize; + } + else + { + m_checkKind = CHECK_NONE; + + // Casting to a small type really means widening from that small type to INT/LONG. + m_extendKind = castUnsigned ? ZERO_EXTEND_SMALL_INT : SIGN_EXTEND_SMALL_INT; + m_extendSrcSize = castSize; + } + } +#ifdef _TARGET_64BIT_ + // castType cannot be (U)LONG on 32 bit targets, such casts should have been decomposed. + // srcType cannot be a small int type since it's the "actual type" of the cast operand. + // This means that widening casts do not occur on 32 bit targets. + else if (castSize > srcSize) // (U)INT to (U)LONG widening cast + { + assert((srcSize == 4) && (castSize == 8)); + + if (overflow && !srcUnsigned && castUnsigned) + { + // Widening from INT to ULONG, check if the value is positive + m_checkKind = CHECK_POSITIVE; + m_checkSrcSize = 4; + + // This is the only overflow checking cast that requires changing the + // source value (by zero extending), all others copy the value as is. + assert((srcType == TYP_INT) && (castType == TYP_ULONG)); + m_extendKind = ZERO_EXTEND_INT; + m_extendSrcSize = 4; + } + else + { + m_checkKind = CHECK_NONE; + + m_extendKind = srcUnsigned ? ZERO_EXTEND_INT : SIGN_EXTEND_INT; + m_extendSrcSize = 4; + } + } + else if (castSize < srcSize) // (U)LONG to (U)INT narrowing cast + { + assert((srcSize == 8) && (castSize == 4)); + + if (overflow) + { + if (castUnsigned) // (U)LONG to UINT cast + { + m_checkKind = CHECK_UINT_RANGE; + } + else if (srcUnsigned) // ULONG to INT cast + { + m_checkKind = CHECK_POSITIVE_INT_RANGE; + } + else // LONG to INT cast + { + m_checkKind = CHECK_INT_RANGE; + } + + m_checkSrcSize = 8; + } + else + { + m_checkKind = CHECK_NONE; + } + + m_extendKind = COPY; + m_extendSrcSize = 4; + } +#endif + else // if (castSize == srcSize) // Sign changing or same type cast + { + assert(castSize == srcSize); + + if (overflow && (srcUnsigned != castUnsigned)) + { + m_checkKind = CHECK_POSITIVE; + m_checkSrcSize = srcSize; + } + else + { + m_checkKind = CHECK_NONE; + } + + m_extendKind = COPY; + m_extendSrcSize = srcSize; + } +} + #if !defined(_TARGET_64BIT_) //------------------------------------------------------------------------ // genStoreLongLclVar: Generate code to store a non-enregistered long lclVar diff --git a/src/jit/codegenxarch.cpp b/src/jit/codegenxarch.cpp index caccf4700bc4..ca8e1417ec6e 100644 --- a/src/jit/codegenxarch.cpp +++ b/src/jit/codegenxarch.cpp @@ -6345,269 +6345,134 @@ void CodeGen::genLongToIntCast(GenTree* cast) #endif //------------------------------------------------------------------------ -// genIntToIntCast: Generate code for an integer cast -// This method handles integer overflow checking casts -// as well as ordinary integer casts. +// genIntCastOverflowCheck: Generate overflow checking code for an integer cast. // // Arguments: -// treeNode - The GT_CAST node -// -// Return Value: -// None. -// -// Assumptions: -// The treeNode is not a contained node and must have an assigned register. -// For a signed convert from byte, the source must be in a byte-addressable register. -// Neither the source nor target type can be a floating point type. -// -// TODO-XArch-CQ: Allow castOp to be a contained node without an assigned register. -// TODO: refactor to use getCastDescription +// cast - The GT_CAST node +// desc - The cast description +// reg - The register containing the value to check // -void CodeGen::genIntToIntCast(GenTree* treeNode) +void CodeGen::genIntCastOverflowCheck(GenTreeCast* cast, const GenIntCastDesc& desc, regNumber reg) { - assert(treeNode->OperGet() == GT_CAST); - - GenTree* castOp = treeNode->gtCast.CastOp(); - var_types srcType = genActualType(castOp->TypeGet()); - noway_assert(genTypeSize(srcType) >= 4); - assert(genTypeSize(srcType) <= genTypeSize(TYP_I_IMPL)); - - regNumber targetReg = treeNode->gtRegNum; - regNumber sourceReg = castOp->gtRegNum; - var_types dstType = treeNode->CastToType(); - bool isUnsignedDst = varTypeIsUnsigned(dstType); - bool isUnsignedSrc = varTypeIsUnsigned(srcType); - - // if necessary, force the srcType to unsigned when the GT_UNSIGNED flag is set - if (!isUnsignedSrc && treeNode->IsUnsigned()) + switch (desc.CheckKind()) { - srcType = genUnsignedType(srcType); - isUnsignedSrc = true; - } - - bool requiresOverflowCheck = false; - - assert(genIsValidIntReg(targetReg)); - assert(genIsValidIntReg(sourceReg)); + case GenIntCastDesc::CHECK_POSITIVE: + getEmitter()->emitIns_R_R(INS_test, EA_SIZE(desc.CheckSrcSize()), reg, reg); + genJumpToThrowHlpBlk(EJ_jl, SCK_OVERFLOW); + break; - instruction ins = INS_invalid; - emitAttr srcSize = EA_ATTR(genTypeSize(srcType)); - emitAttr dstSize = EA_ATTR(genTypeSize(dstType)); +#ifdef _TARGET_64BIT_ + case GenIntCastDesc::CHECK_UINT_RANGE: + { + // We need to check if the value is not greater than 0xFFFFFFFF but this value + // cannot be encoded in an immediate operand. Use a right shift to test if the + // upper 32 bits are zero. This requires a temporary register. + const regNumber tempReg = cast->GetSingleTempReg(); + assert(tempReg != reg); + getEmitter()->emitIns_R_R(INS_mov, EA_8BYTE, tempReg, reg); + getEmitter()->emitIns_R_I(INS_shr_N, EA_8BYTE, tempReg, 32); + genJumpToThrowHlpBlk(EJ_jne, SCK_OVERFLOW); + } + break; - if (srcSize < dstSize) - { -#ifdef _TARGET_X86_ - // dstType cannot be a long type on x86, such casts should have been decomposed. - // srcType cannot be a small type since it's the "actual type" of the cast operand. - // This means that widening casts do not actually occur on x86. - unreached(); -#else - // This is a widening cast from TYP_(U)INT to TYP_(U)LONG. - assert(dstSize == EA_8BYTE); - assert(srcSize == EA_4BYTE); + case GenIntCastDesc::CHECK_POSITIVE_INT_RANGE: + getEmitter()->emitIns_R_I(INS_cmp, EA_8BYTE, reg, INT32_MAX); + genJumpToThrowHlpBlk(EJ_ja, SCK_OVERFLOW); + break; - // When widening, overflows can only happen if the source type is signed and the - // destination type is unsigned. Since the overflow check ensures that the value - // is positive a cheaper mov instruction can be used instead of movsxd. - if (treeNode->gtOverflow() && !isUnsignedSrc && isUnsignedDst) - { - requiresOverflowCheck = true; - ins = INS_mov; - } - else - { - ins = isUnsignedSrc ? INS_mov : INS_movsxd; - } + case GenIntCastDesc::CHECK_INT_RANGE: + getEmitter()->emitIns_R_I(INS_cmp, EA_8BYTE, reg, INT32_MAX); + genJumpToThrowHlpBlk(EJ_jg, SCK_OVERFLOW); + getEmitter()->emitIns_R_I(INS_cmp, EA_8BYTE, reg, INT32_MIN); + genJumpToThrowHlpBlk(EJ_jl, SCK_OVERFLOW); + break; #endif - } - else - { - // Narrowing cast, or sign-changing cast - noway_assert(srcSize >= dstSize); - // Is this an Overflow checking cast? - if (treeNode->gtOverflow()) - { - requiresOverflowCheck = true; - ins = INS_mov; - } - else + default: { - ins = ins_Move_Extend(dstType, false); - } - } + assert(desc.CheckKind() == GenIntCastDesc::CHECK_SMALL_INT_RANGE); + const int castMaxValue = desc.CheckSmallIntMax(); + const int castMinValue = desc.CheckSmallIntMin(); - noway_assert(ins != INS_invalid); + getEmitter()->emitIns_R_I(INS_cmp, EA_SIZE(desc.CheckSrcSize()), reg, castMaxValue); + genJumpToThrowHlpBlk((castMinValue == 0) ? EJ_ja : EJ_jg, SCK_OVERFLOW); - genConsumeReg(castOp); + if (castMinValue != 0) + { + getEmitter()->emitIns_R_I(INS_cmp, EA_SIZE(desc.CheckSrcSize()), reg, castMinValue); + genJumpToThrowHlpBlk(EJ_jl, SCK_OVERFLOW); + } + } + break; + } +} - if (requiresOverflowCheck) - { - ssize_t typeMin = 0; - ssize_t typeMax = 0; - ssize_t typeMask = 0; - bool needScratchReg = false; - bool signCheckOnly = false; +//------------------------------------------------------------------------ +// genIntToIntCast: Generate code for an integer cast, with or without overflow check. +// +// Arguments: +// cast - The GT_CAST node +// +// Assumptions: +// The cast node is not a contained node and must have an assigned register. +// Neither the source nor target type can be a floating point type. +// On x86 casts to (U)BYTE require that the source be in a byte register. +// +// TODO-XArch-CQ: Allow castOp to be a contained node without an assigned register. +// +void CodeGen::genIntToIntCast(GenTreeCast* cast) +{ + genConsumeRegs(cast->gtGetOp1()); - /* Do we need to compare the value, or just check masks */ + const regNumber srcReg = cast->gtGetOp1()->gtRegNum; + const regNumber dstReg = cast->gtRegNum; - switch (dstType) - { - case TYP_BYTE: - typeMask = ssize_t((int)0xFFFFFF80); - typeMin = SCHAR_MIN; - typeMax = SCHAR_MAX; - break; + assert(genIsValidIntReg(srcReg)); + assert(genIsValidIntReg(dstReg)); - case TYP_UBYTE: - typeMask = ssize_t((int)0xFFFFFF00L); - break; + GenIntCastDesc desc(cast); - case TYP_SHORT: - typeMask = ssize_t((int)0xFFFF8000); - typeMin = SHRT_MIN; - typeMax = SHRT_MAX; - break; + if (desc.CheckKind() != GenIntCastDesc::CHECK_NONE) + { + genIntCastOverflowCheck(cast, desc, srcReg); + } - case TYP_USHORT: - typeMask = ssize_t((int)0xFFFF0000L); - break; + if ((desc.ExtendKind() != GenIntCastDesc::COPY) || (srcReg != dstReg)) + { + instruction ins; + unsigned insSize; - case TYP_INT: - if (srcType == TYP_UINT) - { - signCheckOnly = true; - } - else - { - typeMask = ssize_t((int)0x80000000); - typeMin = INT_MIN; - typeMax = INT_MAX; - } + switch (desc.ExtendKind()) + { + case GenIntCastDesc::ZERO_EXTEND_SMALL_INT: + ins = INS_movzx; + insSize = desc.ExtendSrcSize(); break; - - case TYP_UINT: - if (srcType == TYP_INT) - { - signCheckOnly = true; - } - else - { - needScratchReg = true; - } + case GenIntCastDesc::SIGN_EXTEND_SMALL_INT: + ins = INS_movsx; + insSize = desc.ExtendSrcSize(); break; - - case TYP_LONG: - noway_assert(srcType == TYP_ULONG); - signCheckOnly = true; +#ifdef _TARGET_64BIT_ + case GenIntCastDesc::ZERO_EXTEND_INT: + ins = INS_mov; + insSize = 4; break; - - case TYP_ULONG: - noway_assert((srcType == TYP_LONG) || (srcType == TYP_INT)); - signCheckOnly = true; + case GenIntCastDesc::SIGN_EXTEND_INT: + ins = INS_movsxd; + insSize = 4; break; - +#endif default: - NO_WAY("Unknown type"); - return; - } - - if (signCheckOnly) - { - // We only need to check for a negative value in sourceReg - inst_RV_RV(INS_test, sourceReg, sourceReg, srcType, srcSize); - genJumpToThrowHlpBlk(EJ_jl, SCK_OVERFLOW); - } - else - { - // When we are converting from unsigned or to unsigned, we - // will only have to check for any bits set using 'typeMask' - if (isUnsignedSrc || isUnsignedDst) - { - if (needScratchReg) - { - regNumber tmpReg = treeNode->GetSingleTempReg(); - inst_RV_RV(INS_mov, tmpReg, sourceReg, TYP_LONG); // Move the 64-bit value to a writable temp reg - inst_RV_SH(INS_SHIFT_RIGHT_LOGICAL, srcSize, tmpReg, 32); // Shift right by 32 bits - genJumpToThrowHlpBlk(EJ_jne, SCK_OVERFLOW); // Throw if result shift is non-zero - } - else - { - noway_assert(typeMask != 0); - inst_RV_IV(INS_TEST, sourceReg, typeMask, srcSize); - genJumpToThrowHlpBlk(EJ_jne, SCK_OVERFLOW); - } - } - else - { - // For a narrowing signed cast - // - // We must check the value is in a signed range. - - // Compare with the MAX - - noway_assert((typeMin != 0) && (typeMax != 0)); - - inst_RV_IV(INS_cmp, sourceReg, typeMax, srcSize); - genJumpToThrowHlpBlk(EJ_jg, SCK_OVERFLOW); - - // Compare with the MIN - - inst_RV_IV(INS_cmp, sourceReg, typeMin, srcSize); - genJumpToThrowHlpBlk(EJ_jl, SCK_OVERFLOW); - } - } - - if (targetReg != sourceReg -#ifdef _TARGET_AMD64_ - // On amd64, we can hit this path for a same-register - // 4-byte to 8-byte widening conversion, and need to - // emit the instruction to set the high bits correctly. - || (dstSize == EA_8BYTE && srcSize == EA_4BYTE) -#endif // _TARGET_AMD64_ - ) - inst_RV_RV(ins, targetReg, sourceReg, srcType, srcSize); - } - else // non-overflow checking cast - { - // We may have code transformations that result in casts where srcType is the same as dstType. - // e.g. Bug 824281, in which a comma is split by the rationalizer, leaving an assignment of a - // long constant to a long lclVar. - if (srcType == dstType) - { - ins = INS_mov; - } - - if (ins == INS_mov) - { - if (targetReg != sourceReg -#ifdef _TARGET_AMD64_ - // On amd64, 'mov' is the opcode used to zero-extend from - // 4 bytes to 8 bytes. - || (dstSize == EA_8BYTE && srcSize == EA_4BYTE) -#endif // _TARGET_AMD64_ - ) - { - inst_RV_RV(ins, targetReg, sourceReg, srcType, srcSize); - } - } -#ifdef _TARGET_AMD64_ - else if (ins == INS_movsxd) - { - inst_RV_RV(ins, targetReg, sourceReg, srcType, srcSize); + assert(desc.ExtendKind() == GenIntCastDesc::COPY); + ins = INS_mov; + insSize = desc.ExtendSrcSize(); + break; } -#endif // _TARGET_AMD64_ - else - { - noway_assert(ins == INS_movsx || ins == INS_movzx); - noway_assert(srcSize >= dstSize); - /* Generate "mov targetReg, castOp->gtReg */ - inst_RV_RV(ins, targetReg, sourceReg, srcType, dstSize); - } + getEmitter()->emitIns_R_R(ins, EA_ATTR(insSize), dstReg, srcReg); } - genProduceReg(treeNode); + genProduceReg(cast); } //------------------------------------------------------------------------ diff --git a/src/jit/lower.cpp b/src/jit/lower.cpp index 0d0f4804d2c5..fb5fab276314 100644 --- a/src/jit/lower.cpp +++ b/src/jit/lower.cpp @@ -5519,121 +5519,6 @@ bool Lowering::NodesAreEquivalentLeaves(GenTree* tree1, GenTree* tree2) } } -/** - * Get common information required to handle a cast instruction - */ -void Lowering::getCastDescription(GenTree* treeNode, CastInfo* castInfo) -{ - // Intialize castInfo - memset(castInfo, 0, sizeof(*castInfo)); - - GenTree* castOp = treeNode->gtCast.CastOp(); - - var_types dstType = treeNode->CastToType(); - var_types srcType = genActualType(castOp->TypeGet()); - - castInfo->unsignedDest = varTypeIsUnsigned(dstType); - castInfo->unsignedSource = varTypeIsUnsigned(srcType); - - // If necessary, force the srcType to unsigned when the GT_UNSIGNED flag is set. - if (!castInfo->unsignedSource && (treeNode->gtFlags & GTF_UNSIGNED) != 0) - { - srcType = genUnsignedType(srcType); - castInfo->unsignedSource = true; - } - - if (treeNode->gtOverflow() && - (genTypeSize(srcType) >= genTypeSize(dstType) || (srcType == TYP_INT && dstType == TYP_ULONG))) - { - castInfo->requiresOverflowCheck = true; - } - - if (castInfo->requiresOverflowCheck) - { - ssize_t typeMin = 0; - ssize_t typeMax = 0; - ssize_t typeMask = 0; - bool signCheckOnly = false; - - // Do we need to compare the value, or just check masks - switch (dstType) - { - default: - assert(!"unreachable: getCastDescription"); - break; - - case TYP_BYTE: - typeMask = ssize_t((int)0xFFFFFF80); - typeMin = SCHAR_MIN; - typeMax = SCHAR_MAX; - break; - - case TYP_UBYTE: - typeMask = ssize_t((int)0xFFFFFF00L); - break; - - case TYP_SHORT: - typeMask = ssize_t((int)0xFFFF8000); - typeMin = SHRT_MIN; - typeMax = SHRT_MAX; - break; - - case TYP_USHORT: - typeMask = ssize_t((int)0xFFFF0000L); - break; - - case TYP_INT: - if (srcType == TYP_UINT) - { - signCheckOnly = true; - } - else - { -#ifdef _TARGET_64BIT_ - typeMask = 0xFFFFFFFF80000000LL; -#else - typeMask = 0x80000000; -#endif - typeMin = INT_MIN; - typeMax = INT_MAX; - } - break; - - case TYP_UINT: - if (srcType == TYP_INT) - { - signCheckOnly = true; - } - else - { -#ifdef _TARGET_64BIT_ - typeMask = 0xFFFFFFFF00000000LL; -#else - typeMask = 0x00000000; -#endif - } - break; - - case TYP_LONG: - signCheckOnly = true; - break; - - case TYP_ULONG: - signCheckOnly = true; - break; - } - - if (signCheckOnly) - { - castInfo->signCheckOnly = true; - } - - castInfo->typeMax = typeMax; - castInfo->typeMin = typeMin; - castInfo->typeMask = typeMask; - } -} - //------------------------------------------------------------------------ // Containment Analysis //------------------------------------------------------------------------ diff --git a/src/jit/lower.h b/src/jit/lower.h index 656e1773245f..c78e26be2cca 100644 --- a/src/jit/lower.h +++ b/src/jit/lower.h @@ -30,23 +30,6 @@ class Lowering : public Phase } virtual void DoPhase() override; - // If requiresOverflowCheck is false, all other values will be unset - struct CastInfo - { - bool requiresOverflowCheck; // Will the cast require an overflow check - bool unsignedSource; // Is the source unsigned - bool unsignedDest; // is the dest unsigned - - // All other fields are only meaningful if requiresOverflowCheck is set. - - ssize_t typeMin; // Lowest storable value of the dest type - ssize_t typeMax; // Highest storable value of the dest type - ssize_t typeMask; // For converting from/to unsigned - bool signCheckOnly; // For converting between unsigned/signed int - }; - - static void getCastDescription(GenTree* treeNode, CastInfo* castInfo); - // This variant of LowerRange is called from outside of the main Lowering pass, // so it creates its own instance of Lowering to do so. void LowerRange(BasicBlock* block, LIR::ReadOnlyRange& range) diff --git a/src/jit/lsra.h b/src/jit/lsra.h index eddeee91b459..ca772bdfeef4 100644 --- a/src/jit/lsra.h +++ b/src/jit/lsra.h @@ -1578,7 +1578,7 @@ class LinearScan : public LinearScanInterface int BuildStoreLoc(GenTreeLclVarCommon* tree); int BuildIndir(GenTreeIndir* indirTree); int BuildGCWriteBarrier(GenTree* tree); - int BuildCast(GenTree* tree); + int BuildCast(GenTreeCast* cast); #if defined(_TARGET_XARCH_) // returns true if the tree can use the read-modify-write memory instruction form diff --git a/src/jit/lsraarm.cpp b/src/jit/lsraarm.cpp index 00496687c69a..980b2a3d6493 100644 --- a/src/jit/lsraarm.cpp +++ b/src/jit/lsraarm.cpp @@ -287,77 +287,9 @@ int LinearScan::BuildNode(GenTree* tree) break; case GT_CAST: - { assert(dstCount == 1); - - // Non-overflow casts to/from float/double are done using SSE2 instructions - // and that allow the source operand to be either a reg or memop. Given the - // fact that casts from small int to float/double are done as two-level casts, - // the source operand is always guaranteed to be of size 4 or 8 bytes. - var_types castToType = tree->CastToType(); - GenTree* castOp = tree->gtCast.CastOp(); - var_types castOpType = castOp->TypeGet(); - if (tree->gtFlags & GTF_UNSIGNED) - { - castOpType = genUnsignedType(castOpType); - } - - if (varTypeIsLong(castOpType)) - { - assert((castOp->OperGet() == GT_LONG) && castOp->isContained()); - } - - // FloatToIntCast needs a temporary register - if (varTypeIsFloating(castOpType) && varTypeIsIntOrI(tree)) - { - buildInternalFloatRegisterDefForNode(tree, RBM_ALLFLOAT); - setInternalRegsDelayFree = true; - } - - Lowering::CastInfo castInfo; - - // Get information about the cast. - Lowering::getCastDescription(tree, &castInfo); - - if (castInfo.requiresOverflowCheck) - { - var_types srcType = castOp->TypeGet(); - emitAttr cmpSize = EA_ATTR(genTypeSize(srcType)); - - // If we cannot store data in an immediate for instructions, - // then we will need to reserve a temporary register. - - if (!castInfo.signCheckOnly) // In case of only sign check, temp regs are not needeed. - { - if (castInfo.unsignedSource || castInfo.unsignedDest) - { - // check typeMask - bool canStoreTypeMask = emitter::emitIns_valid_imm_for_alu(castInfo.typeMask); - if (!canStoreTypeMask) - { - buildInternalIntRegisterDefForNode(tree); - } - } - else - { - // For comparing against the max or min value - bool canStoreMaxValue = - emitter::emitIns_valid_imm_for_cmp(castInfo.typeMax, INS_FLAGS_DONT_CARE); - bool canStoreMinValue = - emitter::emitIns_valid_imm_for_cmp(castInfo.typeMin, INS_FLAGS_DONT_CARE); - - if (!canStoreMaxValue || !canStoreMinValue) - { - buildInternalIntRegisterDefForNode(tree); - } - } - } - } - srcCount = BuildOperandUses(castOp); - buildInternalRegisterUses(); - BuildDef(tree); - } - break; + srcCount = BuildCast(tree->AsCast()); + break; case GT_JTRUE: srcCount = 0; diff --git a/src/jit/lsraarm64.cpp b/src/jit/lsraarm64.cpp index 13b45a943e55..76b56b368896 100644 --- a/src/jit/lsraarm64.cpp +++ b/src/jit/lsraarm64.cpp @@ -335,53 +335,9 @@ int LinearScan::BuildNode(GenTree* tree) #endif // FEATURE_HW_INTRINSICS case GT_CAST: - { - // TODO-ARM64-CQ: Int-To-Int conversions - castOp cannot be a memory op and must have an assigned - // register. - // see CodeGen::genIntToIntCast() - - // Non-overflow casts to/from float/double are done using SSE2 instructions - // and that allow the source operand to be either a reg or memop. Given the - // fact that casts from small int to float/double are done as two-level casts, - // the source operand is always guaranteed to be of size 4 or 8 bytes. - var_types castToType = tree->CastToType(); - GenTree* castOp = tree->gtCast.CastOp(); - var_types castOpType = castOp->TypeGet(); - if (tree->gtFlags & GTF_UNSIGNED) - { - castOpType = genUnsignedType(castOpType); - } - - // Some overflow checks need a temp reg - - Lowering::CastInfo castInfo; - // Get information about the cast. - Lowering::getCastDescription(tree, &castInfo); - - if (castInfo.requiresOverflowCheck) - { - var_types srcType = castOp->TypeGet(); - emitAttr cmpSize = EA_ATTR(genTypeSize(srcType)); - - // If we cannot store the comparisons in an immediate for either - // comparing against the max or min value, then we will need to - // reserve a temporary register. - - bool canStoreMaxValue = emitter::emitIns_valid_imm_for_cmp(castInfo.typeMax, cmpSize); - bool canStoreMinValue = emitter::emitIns_valid_imm_for_cmp(castInfo.typeMin, cmpSize); - - if (!canStoreMaxValue || !canStoreMinValue) - { - buildInternalIntRegisterDefForNode(tree); - } - } - BuildUse(tree->gtGetOp1()); - srcCount = 1; - buildInternalRegisterUses(); assert(dstCount == 1); - BuildDef(tree); - } - break; + srcCount = BuildCast(tree->AsCast()); + break; case GT_NEG: case GT_NOT: diff --git a/src/jit/lsraarmarch.cpp b/src/jit/lsraarmarch.cpp index 87c0991ed518..aee569861522 100644 --- a/src/jit/lsraarmarch.cpp +++ b/src/jit/lsraarmarch.cpp @@ -716,4 +716,44 @@ int LinearScan::BuildBlockStore(GenTreeBlk* blkNode) return srcCount; } +//------------------------------------------------------------------------ +// BuildCast: Set the NodeInfo for a GT_CAST. +// +// Arguments: +// cast - The GT_CAST node +// +// Return Value: +// The number of sources consumed by this node. +// +int LinearScan::BuildCast(GenTreeCast* cast) +{ + GenTree* src = cast->gtGetOp1(); + + const var_types srcType = genActualType(src->TypeGet()); + const var_types castType = cast->gtCastType; + +#ifdef _TARGET_ARM_ + assert(!varTypeIsLong(srcType) || (src->OperIs(GT_LONG) && src->isContained())); + + // Floating point to integer casts requires a temporary register. + if (varTypeIsFloating(srcType) && !varTypeIsFloating(castType)) + { + buildInternalFloatRegisterDefForNode(cast, RBM_ALLFLOAT); + setInternalRegsDelayFree = true; + } +#else + // Overflow checking cast from TYP_LONG to TYP_INT requires a temporary register to + // store the min and max immediate values that cannot be encoded in the CMP instruction. + if (cast->gtOverflow() && varTypeIsLong(srcType) && !cast->IsUnsigned() && (castType == TYP_INT)) + { + buildInternalIntRegisterDefForNode(cast); + } +#endif + + int srcCount = BuildOperandUses(src); + buildInternalRegisterUses(); + BuildDef(cast); + return srcCount; +} + #endif // _TARGET_ARMARCH_ diff --git a/src/jit/lsraxarch.cpp b/src/jit/lsraxarch.cpp index c64ea11ac330..b131123c423b 100644 --- a/src/jit/lsraxarch.cpp +++ b/src/jit/lsraxarch.cpp @@ -354,7 +354,8 @@ int LinearScan::BuildNode(GenTree* tree) #endif // FEATURE_HW_INTRINSICS case GT_CAST: - srcCount = BuildCast(tree); + assert(dstCount == 1); + srcCount = BuildCast(tree->AsCast()); break; case GT_BITCAST: @@ -2684,52 +2685,40 @@ int LinearScan::BuildHWIntrinsic(GenTreeHWIntrinsic* intrinsicTree) // BuildCast: Set the NodeInfo for a GT_CAST. // // Arguments: -// tree - The node of interest +// cast - The GT_CAST node // // Return Value: // The number of sources consumed by this node. // -int LinearScan::BuildCast(GenTree* tree) +int LinearScan::BuildCast(GenTreeCast* cast) { - // TODO-XArch-CQ: Int-To-Int conversions - castOp cannot be a memory op and must have an assigned register. - // see CodeGen::genIntToIntCast() - - // Non-overflow casts to/from float/double are done using SSE2 instructions - // and that allow the source operand to be either a reg or memop. Given the - // fact that casts from small int to float/double are done as two-level casts, - // the source operand is always guaranteed to be of size 4 or 8 bytes. - var_types castToType = tree->CastToType(); - GenTree* castOp = tree->gtCast.CastOp(); - var_types castOpType = castOp->TypeGet(); - regMaskTP candidates = RBM_NONE; + GenTree* src = cast->gtGetOp1(); - if (tree->gtFlags & GTF_UNSIGNED) - { - castOpType = genUnsignedType(castOpType); - } + const var_types srcType = genActualType(src->TypeGet()); + const var_types castType = cast->gtCastType; + regMaskTP candidates = RBM_NONE; #ifdef _TARGET_X86_ - if (varTypeIsByte(castToType)) + if (varTypeIsByte(castType)) { candidates = allByteRegs(); } -#endif // _TARGET_X86_ - // some overflow checks need a temp reg: - // - GT_CAST from INT64/UINT64 to UINT32 - RefPosition* internalDef = nullptr; - if (tree->gtOverflow() && (castToType == TYP_UINT)) + assert(!varTypeIsLong(srcType) || (src->OperIs(GT_LONG) && src->isContained())); +#else + // Overflow checking cast from TYP_(U)LONG to TYP_UINT requires a temporary + // register to extract the upper 32 bits of the 64 bit source register. + if (cast->gtOverflow() && varTypeIsLong(srcType) && (castType == TYP_UINT)) { - if (genTypeSize(castOpType) == 8) - { - // Here we don't need internal register to be different from targetReg, - // rather require it to be different from operand's reg. - buildInternalIntRegisterDefForNode(tree); - } + // Here we don't need internal register to be different from targetReg, + // rather require it to be different from operand's reg. + buildInternalIntRegisterDefForNode(cast); } - int srcCount = BuildOperandUses(castOp, candidates); +#endif + + int srcCount = BuildOperandUses(src, candidates); buildInternalRegisterUses(); - BuildDef(tree, candidates); + BuildDef(cast, candidates); return srcCount; }