diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index bec5ec8f347e9..a504045b40945 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -14257,60 +14257,35 @@ GenTree* Compiler::gtFoldExprConst(GenTree* tree) break; case GT_CAST: - // assert (genActualType(tree->CastToType()) == tree->gtType); + // assert (genActualType(tree->CastToType()) == tree->TypeGet()); + + if (tree->gtOverflow() && + CheckedOps::CastFromIntOverflows((INT32)i1, tree->CastToType(), tree->IsUnsigned())) + { + goto INTEGRAL_OVF; + } + switch (tree->CastToType()) { case TYP_BYTE: - itemp = INT32(INT8(i1)); - goto CHK_OVF; + i1 = INT32(INT8(i1)); + goto CNS_INT; case TYP_SHORT: - itemp = INT32(INT16(i1)); - CHK_OVF: - if (tree->gtOverflow() && ((itemp != i1) || (tree->IsUnsigned() && (i1 < 0)))) - { - goto INT_OVF; - } - i1 = itemp; + i1 = INT32(INT16(i1)); goto CNS_INT; case TYP_USHORT: - itemp = INT32(UINT16(i1)); - if (tree->gtOverflow()) - { - if (itemp != i1) - { - goto INT_OVF; - } - } - i1 = itemp; + i1 = INT32(UINT16(i1)); goto CNS_INT; case TYP_BOOL: case TYP_UBYTE: - itemp = INT32(UINT8(i1)); - if (tree->gtOverflow()) - { - if (itemp != i1) - { - goto INT_OVF; - } - } - i1 = itemp; + i1 = INT32(UINT8(i1)); goto CNS_INT; case TYP_UINT: - if (!tree->IsUnsigned() && tree->gtOverflow() && (i1 < 0)) - { - goto INT_OVF; - } - goto CNS_INT; - case TYP_INT: - if (tree->IsUnsigned() && tree->gtOverflow() && (i1 < 0)) - { - goto INT_OVF; - } goto CNS_INT; case TYP_ULONG: @@ -14320,10 +14295,6 @@ GenTree* Compiler::gtFoldExprConst(GenTree* tree) } else { - if (tree->gtOverflow() && (i1 < 0)) - { - goto LNG_OVF; - } lval1 = UINT64(INT32(i1)); } goto CNS_LONG; @@ -14363,10 +14334,9 @@ GenTree* Compiler::gtFoldExprConst(GenTree* tree) goto CNS_DOUBLE; default: - assert(!"BAD_TYP"); - break; + assert(!"Bad CastToType() in gtFoldExprConst() for a cast from int"); + return tree; } - return tree; default: return tree; @@ -14404,63 +14374,41 @@ GenTree* Compiler::gtFoldExprConst(GenTree* tree) case GT_CAST: assert(tree->TypeIs(genActualType(tree->CastToType()))); + + if (tree->gtOverflow() && + CheckedOps::CastFromLongOverflows(lval1, tree->CastToType(), tree->IsUnsigned())) + { + goto INTEGRAL_OVF; + } + switch (tree->CastToType()) { case TYP_BYTE: i1 = INT32(INT8(lval1)); - goto CHECK_INT_OVERFLOW; + goto CNS_INT; case TYP_SHORT: i1 = INT32(INT16(lval1)); - goto CHECK_INT_OVERFLOW; + goto CNS_INT; case TYP_USHORT: i1 = INT32(UINT16(lval1)); - goto CHECK_UINT_OVERFLOW; + goto CNS_INT; case TYP_UBYTE: i1 = INT32(UINT8(lval1)); - goto CHECK_UINT_OVERFLOW; + goto CNS_INT; case TYP_INT: i1 = INT32(lval1); - - CHECK_INT_OVERFLOW: - if (tree->gtOverflow()) - { - if (i1 != lval1) - { - goto INT_OVF; - } - if (tree->IsUnsigned() && (i1 < 0)) - { - goto INT_OVF; - } - } goto CNS_INT; case TYP_UINT: i1 = UINT32(lval1); - - CHECK_UINT_OVERFLOW: - if (tree->gtOverflow() && UINT32(i1) != lval1) - { - goto INT_OVF; - } goto CNS_INT; case TYP_ULONG: - if (!tree->IsUnsigned() && tree->gtOverflow() && (lval1 < 0)) - { - goto LNG_OVF; - } - goto CNS_LONG; - case TYP_LONG: - if (tree->IsUnsigned() && tree->gtOverflow() && (lval1 < 0)) - { - goto LNG_OVF; - } goto CNS_LONG; case TYP_FLOAT: @@ -14481,10 +14429,9 @@ GenTree* Compiler::gtFoldExprConst(GenTree* tree) } goto CNS_DOUBLE; default: - assert(!"BAD_TYP"); - break; + assert(!"Bad CastToType() in gtFoldExprConst() for a cast from long"); + return tree; } - return tree; default: return tree; @@ -14508,7 +14455,10 @@ GenTree* Compiler::gtFoldExprConst(GenTree* tree) case GT_CAST: - if (tree->gtOverflowEx()) + if (tree->gtOverflow() && + ((op1->TypeIs(TYP_DOUBLE) && CheckedOps::CastFromDoubleOverflows(d1, tree->CastToType())) || + (op1->TypeIs(TYP_FLOAT) && + CheckedOps::CastFromFloatOverflows(forceCastToFloat(d1), tree->CastToType())))) { return tree; } @@ -14591,7 +14541,7 @@ GenTree* Compiler::gtFoldExprConst(GenTree* tree) goto CNS_DOUBLE; // Redundant cast. default: - assert(!"BAD_TYP"); + assert(!"Bad CastToType() in gtFoldExprConst() for a cast from double/float"); break; } return tree; @@ -14792,7 +14742,7 @@ GenTree* Compiler::gtFoldExprConst(GenTree* tree) itemp = i1 + i2; if (tree->gtOverflow() && CheckedOps::IntAddOverflows(INT32(i1), INT32(i2), tree->IsUnsigned())) { - goto INT_OVF; + goto INTEGRAL_OVF; } i1 = itemp; fieldSeq = GetFieldSeqStore()->Append(op1->AsIntCon()->gtFieldSeq, op2->AsIntCon()->gtFieldSeq); @@ -14801,7 +14751,7 @@ GenTree* Compiler::gtFoldExprConst(GenTree* tree) itemp = i1 - i2; if (tree->gtOverflow() && CheckedOps::IntSubOverflows(INT32(i1), INT32(i2), tree->IsUnsigned())) { - goto INT_OVF; + goto INTEGRAL_OVF; } i1 = itemp; break; @@ -14809,7 +14759,7 @@ GenTree* Compiler::gtFoldExprConst(GenTree* tree) itemp = i1 * i2; if (tree->gtOverflow() && CheckedOps::IntMulOverflows(INT32(i1), INT32(i2), tree->IsUnsigned())) { - goto INT_OVF; + goto INTEGRAL_OVF; } // For the very particular case of the "constant array index" pseudo-field, we @@ -14932,100 +14882,6 @@ GenTree* Compiler::gtFoldExprConst(GenTree* tree) goto DONE; - // This operation is going to cause an overflow exception. Morph into - // an overflow helper. Put a dummy constant value for code generation. - // - // We could remove all subsequent trees in the current basic block, - // unless this node is a child of GT_COLON - // - // NOTE: Since the folded value is not constant we should not change the - // "tree" node - otherwise we confuse the logic that checks if the folding - // was successful - instead use one of the operands, e.g. op1. - // - - LNG_OVF: - // Don't fold overflow operations if not global morph phase. - // The reason for this is that this optimization is replacing a gentree node - // with another new gentree node. Say a GT_CALL(arglist) has one 'arg' - // involving overflow arithmetic. During assertion prop, it is possible - // that the 'arg' could be constant folded and the result could lead to an - // overflow. In such a case 'arg' will get replaced with GT_COMMA node - // but fgMorphArgs() - see the logic around "if(lateArgsComputed)" - doesn't - // update args table. For this reason this optimization is enabled only - // for global morphing phase. - // - // TODO-CQ: Once fgMorphArgs() is fixed this restriction could be removed. - - if (!fgGlobalMorph) - { - assert(tree->gtOverflow()); - return tree; - } - - op1 = gtNewLconNode(0); - if (vnStore != nullptr) - { - op1->gtVNPair.SetBoth(vnStore->VNZeroForType(TYP_LONG)); - } - goto OVF; - - INT_OVF: - // Don't fold overflow operations if not global morph phase. - // The reason for this is that this optimization is replacing a gentree node - // with another new gentree node. Say a GT_CALL(arglist) has one 'arg' - // involving overflow arithmetic. During assertion prop, it is possible - // that the 'arg' could be constant folded and the result could lead to an - // overflow. In such a case 'arg' will get replaced with GT_COMMA node - // but fgMorphArgs() - see the logic around "if(lateArgsComputed)" - doesn't - // update args table. For this reason this optimization is enabled only - // for global morphing phase. - // - // TODO-CQ: Once fgMorphArgs() is fixed this restriction could be removed. - - if (!fgGlobalMorph) - { - assert(tree->gtOverflow()); - return tree; - } - - op1 = gtNewIconNode(0); - if (vnStore != nullptr) - { - op1->gtVNPair.SetBoth(vnStore->VNZeroForType(TYP_INT)); - } - goto OVF; - - OVF: - - JITDUMP("\nFolding binary operator with constant nodes into a comma throw:\n"); - DISPTREE(tree); - - // We will change the cast to a GT_COMMA and attach the exception helper as AsOp()->gtOp1. - // The constant expression zero becomes op2. - - assert(tree->gtOverflow()); - assert(tree->OperIs(GT_ADD, GT_SUB, GT_CAST, GT_MUL)); - assert(op1 != nullptr); - - op2 = op1; - op1 = gtNewHelperCallNode(CORINFO_HELP_OVERFLOW, TYP_VOID, - gtNewCallArgs(gtNewIconNode(compCurBB->bbTryIndex))); - - // op1 is a call to the JIT helper that throws an Overflow exception. - // Attach the ExcSet for VNF_OverflowExc(Void) to this call. - - if (vnStore != nullptr) - { - op1->gtVNPair = - vnStore->VNPWithExc(ValueNumPair(ValueNumStore::VNForVoid(), ValueNumStore::VNForVoid()), - vnStore->VNPExcSetSingleton( - vnStore->VNPairForFunc(TYP_REF, VNF_OverflowExc, vnStore->VNPForVoid()))); - } - - tree = gtNewOperNode(GT_COMMA, tree->TypeGet(), op1, op2); - - return tree; - // Fold constant LONG binary operator. case TYP_LONG: @@ -15110,7 +14966,7 @@ GenTree* Compiler::gtFoldExprConst(GenTree* tree) ltemp = lval1 + lval2; if (tree->gtOverflow() && CheckedOps::LongAddOverflows(lval1, lval2, tree->IsUnsigned())) { - goto LNG_OVF; + goto INTEGRAL_OVF; } lval1 = ltemp; break; @@ -15119,7 +14975,7 @@ GenTree* Compiler::gtFoldExprConst(GenTree* tree) ltemp = lval1 - lval2; if (tree->gtOverflow() && CheckedOps::LongSubOverflows(lval1, lval2, tree->IsUnsigned())) { - goto LNG_OVF; + goto INTEGRAL_OVF; } lval1 = ltemp; break; @@ -15128,7 +14984,7 @@ GenTree* Compiler::gtFoldExprConst(GenTree* tree) ltemp = lval1 * lval2; if (tree->gtOverflow() && CheckedOps::LongMulOverflows(lval1, lval2, tree->IsUnsigned())) { - goto LNG_OVF; + goto INTEGRAL_OVF; } lval1 = ltemp; break; @@ -15412,6 +15268,70 @@ GenTree* Compiler::gtFoldExprConst(GenTree* tree) tree->gtFlags &= ~GTF_ALL_EFFECT; + return tree; + +INTEGRAL_OVF: + + // This operation is going to cause an overflow exception. Morph into + // an overflow helper. Put a dummy constant value for code generation. + // + // We could remove all subsequent trees in the current basic block, + // unless this node is a child of GT_COLON + // + // NOTE: Since the folded value is not constant we should not change the + // "tree" node - otherwise we confuse the logic that checks if the folding + // was successful - instead use one of the operands, e.g. op1. + + // Don't fold overflow operations if not global morph phase. + // The reason for this is that this optimization is replacing a gentree node + // with another new gentree node. Say a GT_CALL(arglist) has one 'arg' + // involving overflow arithmetic. During assertion prop, it is possible + // that the 'arg' could be constant folded and the result could lead to an + // overflow. In such a case 'arg' will get replaced with GT_COMMA node + // but fgMorphArgs() - see the logic around "if(lateArgsComputed)" - doesn't + // update args table. For this reason this optimization is enabled only + // for global morphing phase. + // + // TODO-CQ: Once fgMorphArgs() is fixed this restriction could be removed. + + if (!fgGlobalMorph) + { + assert(tree->gtOverflow()); + return tree; + } + + var_types type = genActualType(tree->TypeGet()); + op1 = type == TYP_LONG ? gtNewLconNode(0) : gtNewIconNode(0); + if (vnStore != nullptr) + { + op1->gtVNPair.SetBoth(vnStore->VNZeroForType(type)); + } + + JITDUMP("\nFolding binary operator with constant nodes into a comma throw:\n"); + DISPTREE(tree); + + // We will change the cast to a GT_COMMA and attach the exception helper as AsOp()->gtOp1. + // The constant expression zero becomes op2. + + assert(tree->gtOverflow()); + assert(tree->OperIs(GT_ADD, GT_SUB, GT_CAST, GT_MUL)); + assert(op1 != nullptr); + + op2 = op1; + op1 = gtNewHelperCallNode(CORINFO_HELP_OVERFLOW, TYP_VOID, gtNewCallArgs(gtNewIconNode(compCurBB->bbTryIndex))); + + // op1 is a call to the JIT helper that throws an Overflow exception. + // Attach the ExcSet for VNF_OverflowExc(Void) to this call. + + if (vnStore != nullptr) + { + op1->gtVNPair = vnStore->VNPWithExc(ValueNumPair(ValueNumStore::VNForVoid(), ValueNumStore::VNForVoid()), + vnStore->VNPExcSetSingleton(vnStore->VNPairForFunc(TYP_REF, VNF_OverflowExc, + vnStore->VNPForVoid()))); + } + + tree = gtNewOperNode(GT_COMMA, tree->TypeGet(), op1, op2); + return tree; } #ifdef _PREFAST_ diff --git a/src/coreclr/jit/utils.cpp b/src/coreclr/jit/utils.cpp index 8330ab3968b7c..5ac6d4b180377 100644 --- a/src/coreclr/jit/utils.cpp +++ b/src/coreclr/jit/utils.cpp @@ -2746,4 +2746,207 @@ bool LongMulOverflows(int64_t firstFactor, int64_t secondFactor, bool unsignedMu return false; } + +bool CastFromIntOverflows(int32_t fromValue, var_types toType, bool fromUnsigned) +{ + switch (toType) + { + case TYP_BYTE: + return ((int8_t)fromValue != fromValue) || (fromUnsigned && fromValue < 0); + case TYP_BOOL: + case TYP_UBYTE: + return (uint8_t)fromValue != fromValue; + case TYP_SHORT: + return ((int16_t)fromValue != fromValue) || (fromUnsigned && fromValue < 0); + case TYP_USHORT: + return (uint16_t)fromValue != fromValue; + case TYP_INT: + return fromUnsigned && (fromValue < 0); + case TYP_UINT: + case TYP_ULONG: + return !fromUnsigned && (fromValue < 0); + case TYP_LONG: + case TYP_FLOAT: + case TYP_DOUBLE: + return false; + default: + unreached(); + } +} + +bool CastFromLongOverflows(int64_t fromValue, var_types toType, bool fromUnsigned) +{ + switch (toType) + { + case TYP_BYTE: + return ((int8_t)fromValue != fromValue) || (fromUnsigned && fromValue < 0); + case TYP_BOOL: + case TYP_UBYTE: + return (uint8_t)fromValue != fromValue; + case TYP_SHORT: + return ((int16_t)fromValue != fromValue) || (fromUnsigned && fromValue < 0); + case TYP_USHORT: + return (uint16_t)fromValue != fromValue; + case TYP_INT: + return ((int32_t)fromValue != fromValue) || (fromUnsigned && fromValue < 0); + case TYP_UINT: + return (uint32_t)fromValue != fromValue; + case TYP_LONG: + return fromUnsigned && (fromValue < 0); + case TYP_ULONG: + return !fromUnsigned && (fromValue < 0); + case TYP_FLOAT: + case TYP_DOUBLE: + return false; + default: + unreached(); + } +} + +// ________________________________________________ +// | | +// | Casting from floating point to integer types | +// |________________________________________________| +// +// The code below uses the following pattern to determine if an overflow would +// occur when casting from a floating point type to an integer type: +// +// return !(MIN <= fromValue && fromValue <= MAX); +// +// This section will provide some background on how MIN and MAX were derived +// and why they are in fact the values to use in that comparison. +// +// First - edge cases: +// 1) NaNs - they compare "false" to normal numbers, which MIN and MAX are, making +// the condition return "false" as well, which is flipped to true via "!", indicating +// overflow - exactly what we want. +// 2) Infinities - they are outside of range of any normal numbers, making one of the comparisons +// always return "false", indicating overflow. +// 3) Subnormal numbers - have no special behavior with respect to comparisons. +// 4) Minus zero - compares equal to "+0", which is what we want as it can be safely casted to an integer "0". +// +// Binary normal floating point numbers are represented in the following format: +// +// number = sign * (1 + mantissa) * 2^exponent +// +// Where "exponent" is a biased binary integer. +// And "mantissa" is a fixed-point binary fraction of the following form: +// +// mantissa = bits[1] * 2^-1 + bits[2] * 2^-2 + ... + bits[n] * 2^-N +// +// Where "N" is the number of digits that depends on the width of floating point type +// in question. Is is equal to "23" for "float"s and to "52" for "double"s. +// +// If we did our calculations with real numbers, the condition to check would simply be: +// +// return !((INT_MIN - 1) < fromValue && fromValue < (INT_MAX + 1)); +// +// This is because casting uses the "round to zero" semantic: "checked((int)((double)int.MaxValue + 0.9))" +// yields "(double)int.MaxValue" - not an error. Likewise, "checked((int)((double)int.MinValue - 0.9))" +// results in "(double)int.MinValue". However, "checked((int)((double)int.MaxValue + 1))" will not compile. +// +// The problem, of course, is that we are not dealing with real numbers, but rather floating point approximations. +// At the same time, some real numbers - powers of two - can be represented in the floating point world exactly. +// It so happens that both "INT_MIN - 1" and "INT_MAX + 1" can satsify that requirement for most cases. +// For unsigned integers, where M is the width of the type in bits: +// +// INT_MIN - 1 = 0 - 1 = -2^0 - exactly representable. +// INT_MAX + 1 = (2^M - 1) + 1 = 2^M - exactly representable. +// +// For signed integers: +// +// INT_MIN - 1 = -(2^(M - 1)) - 1 - not always exactly representable. +// INT_MAX + 1 = (2^(M - 1) - 1) + 1 = 2^(M - 1) - exactly representable. +// +// So, we have simple values for MIN and MAX in all but the signed MIN case. +// To find out what value should be used then, the following equation needs to be solved: +// +// -(2^(M - 1)) - 1 = -(2^(M - 1)) * (1 + m) +// 1 + 1 / 2^(M - 1) = 1 + m +// m = 2^(1 - M) +// +// In this case "m" is the "mantissa". The result obtained means that we can find the exact +// value in cases when "|1 - M| <= N" <=> "M <= N - 1" - i. e. the precision is high enough for there to be a position +// in the fixed point mantissa that could represent the "-1". It is the case for the following combinations of types: +// +// float + int8 / int16 +// double + int8 / int16 / int32 +// +// For the remaining cases, we could use a value that is the first representable one for the respective type +// and is less than the infinitely precise MIN: -(1 + 2^-N) * 2^(M - 1). +// However, a simpler appoach is to just use a different comparison. +// Instead of "MIN < fromValue", we'll do "MAX_MIN <= fromValue", where +// "MAX_MIN" is just "-(2^(M - 1))" - the smallest representable value that can be cast safely. +// The following table shows the final values and operations for MIN: +// +// | Cast | MIN | Comparison | +// |-----------------|-------------------------|------------| +// | float -> int8 | -129.0f | < | +// | float -> int16 | -32769.0f | < | +// | float -> int32 | -2147483648.0f | <= | +// | float -> int64 | -9223372036854775808.0f | <= | +// | double -> int8 | -129.0 | < | +// | double -> int16 | -32769.0 | < | +// | double -> int32 | -2147483649.0 | < | +// | double -> int64 | -9223372036854775808.0 | <= | +// +// Note: casts from floating point to floating point never overflow. + +bool CastFromFloatOverflows(float fromValue, var_types toType) +{ + switch (toType) + { + case TYP_BYTE: + return !(-129.0f < fromValue && fromValue < 128.0f); + case TYP_BOOL: + case TYP_UBYTE: + return !(-1.0f < fromValue && fromValue < 256.0f); + case TYP_SHORT: + return !(-32769.0f < fromValue && fromValue < 32768.0f); + case TYP_USHORT: + return !(-1.0f < fromValue && fromValue < 65536.0f); + case TYP_INT: + return !(-2147483648.0f <= fromValue && fromValue < 2147483648.0f); + case TYP_UINT: + return !(-1.0 < fromValue && fromValue < 4294967296.0f); + case TYP_LONG: + return !(-9223372036854775808.0 <= fromValue && fromValue < 9223372036854775808.0f); + case TYP_ULONG: + return !(-1.0f < fromValue && fromValue < 18446744073709551616.0f); + case TYP_FLOAT: + case TYP_DOUBLE: + return false; + default: + unreached(); + } +} + +bool CastFromDoubleOverflows(double fromValue, var_types toType) +{ + switch (toType) + { + case TYP_BYTE: + return !(-129.0 < fromValue && fromValue < 128.0); + case TYP_BOOL: + case TYP_UBYTE: + return !(-1.0 < fromValue && fromValue < 256.0); + case TYP_SHORT: + return !(-32769.0 < fromValue && fromValue < 32768.0); + case TYP_USHORT: + return !(-1.0 < fromValue && fromValue < 65536.0); + case TYP_INT: + return !(-2147483649.0 < fromValue && fromValue < 2147483648.0); + case TYP_UINT: + return !(-1.0 < fromValue && fromValue < 4294967296.0); + case TYP_LONG: + return !(-9223372036854775808.0 <= fromValue && fromValue < 9223372036854775808.0); + case TYP_ULONG: + return !(-1.0 < fromValue && fromValue < 18446744073709551616.0); + case TYP_FLOAT: + case TYP_DOUBLE: + return false; + default: + unreached(); + } +} } diff --git a/src/coreclr/jit/utils.h b/src/coreclr/jit/utils.h index da1e1600c6ed0..b7d378ca57669 100644 --- a/src/coreclr/jit/utils.h +++ b/src/coreclr/jit/utils.h @@ -786,6 +786,11 @@ bool IntMulOverflows(int32_t firstFactor, int32_t secondFactor, bool unsignedMul bool LongAddOverflows(int64_t firstAddend, int64_t secondAddend, bool unsignedAddition); bool LongSubOverflows(int64_t minuend, int64_t subtrahend, bool unsignedSubtraction); bool LongMulOverflows(int64_t firstFactor, int64_t secondFactor, bool unsignedMultiplication); + +bool CastFromIntOverflows(int32_t fromValue, var_types toType, bool fromUnsigned); +bool CastFromLongOverflows(int64_t fromValue, var_types toType, bool fromUnsigned); +bool CastFromFloatOverflows(float fromValue, var_types toType); +bool CastFromDoubleOverflows(double fromValue, var_types toType); } #endif // _UTILS_H_