diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index d3e66c159f4d6..e6334311c6aab 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -15,6 +15,327 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX #pragma hdrstop #endif +//------------------------------------------------------------------------ +// Contains: Whether the range contains a given integral value, inclusive. +// +// Arguments: +// value - the integral value in question +// +// Return Value: +// "true" if the value is within the range's bounds, "false" otherwise. +// +bool IntegralRange::Contains(int64_t value) const +{ + int64_t lowerBound = SymbolicToRealValue(m_lowerBound); + int64_t upperBound = SymbolicToRealValue(m_upperBound); + + return (lowerBound <= value) && (value <= upperBound); +} + +//------------------------------------------------------------------------ +// SymbolicToRealValue: Convert a symbolic value to a 64-bit signed integer. +// +// Arguments: +// value - the symbolic value in question +// +// Return Value: +// Integer correspoding to the symbolic value. +// +/* static */ int64_t IntegralRange::SymbolicToRealValue(SymbolicIntegerValue value) +{ + static const int64_t SymbolicToRealMap[]{INT64_MIN, INT32_MIN, INT16_MIN, INT8_MIN, 0, + 1, INT8_MAX, UINT8_MAX, INT16_MAX, UINT16_MAX, + INT32_MAX, UINT32_MAX, INT64_MAX}; + + assert(sizeof(SymbolicIntegerValue) == sizeof(int32_t)); + assert(SymbolicToRealMap[static_cast(SymbolicIntegerValue::LongMin)] == INT64_MIN); + assert(SymbolicToRealMap[static_cast(SymbolicIntegerValue::Zero)] == 0); + assert(SymbolicToRealMap[static_cast(SymbolicIntegerValue::LongMax)] == INT64_MAX); + + return SymbolicToRealMap[static_cast(value)]; +} + +//------------------------------------------------------------------------ +// LowerBoundForType: Get the symbolic lower bound for a type. +// +// Arguments: +// type - the integral type in question +// +// Return Value: +// Symbolic value representing the smallest possible value "type" can represent. +// +/* static */ SymbolicIntegerValue IntegralRange::LowerBoundForType(var_types type) +{ + switch (type) + { + case TYP_BOOL: + case TYP_UBYTE: + case TYP_USHORT: + return SymbolicIntegerValue::Zero; + case TYP_BYTE: + return SymbolicIntegerValue::ByteMin; + case TYP_SHORT: + return SymbolicIntegerValue::ShortMin; + case TYP_INT: + return SymbolicIntegerValue::IntMin; + case TYP_LONG: + return SymbolicIntegerValue::LongMin; + default: + unreached(); + } +} + +//------------------------------------------------------------------------ +// UpperBoundForType: Get the symbolic upper bound for a type. +// +// Arguments: +// type - the integral type in question +// +// Return Value: +// Symbolic value representing the largest possible value "type" can represent. +// +/* static */ SymbolicIntegerValue IntegralRange::UpperBoundForType(var_types type) +{ + switch (type) + { + case TYP_BYTE: + return SymbolicIntegerValue::ByteMax; + case TYP_BOOL: + case TYP_UBYTE: + return SymbolicIntegerValue::UByteMax; + case TYP_SHORT: + return SymbolicIntegerValue::ShortMax; + case TYP_USHORT: + return SymbolicIntegerValue::UShortMax; + case TYP_INT: + return SymbolicIntegerValue::IntMax; + case TYP_UINT: + return SymbolicIntegerValue::UIntMax; + case TYP_LONG: + return SymbolicIntegerValue::LongMax; + default: + unreached(); + } +} + +//------------------------------------------------------------------------ +// ForCastInput: Get the non-overflowing input range for a cast. +// +// This routine computes the input range for a cast from +// an integer to an integer for which it will not overflow. +// See also the specification comment for IntegralRange. +// +// Arguments: +// cast - the cast node for which the range will be computed +// +// Return Value: +// The range this cast consumes without overflowing - see description. +// +/* static */ IntegralRange IntegralRange::ForCastInput(GenTreeCast* cast) +{ + var_types fromType = genActualType(cast->CastOp()); + var_types toType = cast->CastToType(); + bool fromUnsigned = cast->IsUnsigned(); + + assert((fromType == TYP_INT) || (fromType == TYP_LONG) || varTypeIsGC(fromType)); + assert(varTypeIsIntegral(toType)); + + // Cast from a GC type is the same as a cast from TYP_I_IMPL for our purposes. + if (varTypeIsGC(fromType)) + { + fromType = TYP_I_IMPL; + } + + if (!cast->gtOverflow()) + { + // CAST(small type <- uint/int/ulong/long) - [TO_TYPE_MIN..TO_TYPE_MAX] + if (varTypeIsSmall(toType)) + { + return {LowerBoundForType(toType), UpperBoundForType(toType)}; + } + + // We choose to say here that representation-changing casts never overflow. + // It does not really matter what we do here because representation-changing + // non-overflowing casts cannot be deleted from the IR in any case. + // CAST(uint/int <- uint/int) - [INT_MIN..INT_MAX] + // CAST(uint/int <- ulong/long) - [LONG_MIN..LONG_MAX] + // CAST(ulong/long <- uint/int) - [INT_MIN..INT_MAX] + // CAST(ulong/long <- ulong/long) - [LONG_MIN..LONG_MAX] + return ForType(fromType); + } + + SymbolicIntegerValue lowerBound; + SymbolicIntegerValue upperBound; + + // CAST_OVF(small type <- int/long) - [TO_TYPE_MIN..TO_TYPE_MAX] + // CAST_OVF(small type <- uint/ulong) - [0..TO_TYPE_MAX] + if (varTypeIsSmall(toType)) + { + lowerBound = fromUnsigned ? SymbolicIntegerValue::Zero : LowerBoundForType(toType); + upperBound = UpperBoundForType(toType); + } + else + { + switch (toType) + { + // CAST_OVF(uint <- uint) - [INT_MIN..INT_MAX] + // CAST_OVF(uint <- int) - [0..INT_MAX] + // CAST_OVF(uint <- ulong/long) - [0..UINT_MAX] + case TYP_UINT: + if (fromType == TYP_LONG) + { + lowerBound = SymbolicIntegerValue::Zero; + upperBound = SymbolicIntegerValue::UIntMax; + } + else + { + lowerBound = fromUnsigned ? SymbolicIntegerValue::IntMin : SymbolicIntegerValue::Zero; + upperBound = SymbolicIntegerValue::IntMax; + } + break; + + // CAST_OVF(int <- uint/ulong) - [0..INT_MAX] + // CAST_OVF(int <- int/long) - [INT_MIN..INT_MAX] + case TYP_INT: + lowerBound = fromUnsigned ? SymbolicIntegerValue::Zero : SymbolicIntegerValue::IntMin; + upperBound = SymbolicIntegerValue::IntMax; + break; + + // CAST_OVF(ulong <- uint) - [INT_MIN..INT_MAX] + // CAST_OVF(ulong <- int) - [0..INT_MAX] + // CAST_OVF(ulong <- ulong) - [LONG_MIN..LONG_MAX] + // CAST_OVF(ulong <- long) - [0..LONG_MAX] + case TYP_ULONG: + lowerBound = fromUnsigned ? LowerBoundForType(fromType) : SymbolicIntegerValue::Zero; + upperBound = UpperBoundForType(fromType); + break; + + // CAST_OVF(long <- uint/int) - [INT_MIN..INT_MAX] + // CAST_OVF(long <- ulong) - [0..LONG_MAX] + // CAST_OVF(long <- long) - [LONG_MIN..LONG_MAX] + case TYP_LONG: + lowerBound = fromUnsigned ? SymbolicIntegerValue::Zero : LowerBoundForType(fromType); + upperBound = UpperBoundForType(fromType); + break; + + default: + unreached(); + } + } + + return {lowerBound, upperBound}; +} + +//------------------------------------------------------------------------ +// ForCastOutput: Get the output range for a cast. +// +// This method is the "output" counterpart to ForCastInput, it returns +// a range produced by a cast (by definition, non-overflowing one). +// The output range is the same for representation-preserving casts, but +// can be different for others. One example is CAST_OVF(uint <- long). +// The input range is [0..UINT_MAX], while the output is [INT_MIN..INT_MAX]. +// Unlike ForCastInput, this method supports casts from floating point types. +// +// Arguments: +// cast - the cast node for which the range will be computed +// +// Return Value: +// The range this cast produces - see description. +// +/* static */ IntegralRange IntegralRange::ForCastOutput(GenTreeCast* cast) +{ + var_types fromType = genActualType(cast->CastOp()); + var_types toType = cast->CastToType(); + bool fromUnsigned = cast->IsUnsigned(); + + assert((fromType == TYP_INT) || (fromType == TYP_LONG) || varTypeIsFloating(fromType) || varTypeIsGC(fromType)); + assert(varTypeIsIntegral(toType)); + + // CAST/CAST_OVF(small type <- float/double) - [TO_TYPE_MIN..TO_TYPE_MAX] + // CAST/CAST_OVF(uint/int <- float/double) - [INT_MIN..INT_MAX] + // CAST/CAST_OVF(ulong/long <- float/double) - [LONG_MIN..LONG_MAX] + if (varTypeIsFloating(fromType)) + { + if (!varTypeIsSmall(toType)) + { + toType = genActualType(toType); + } + + return IntegralRange::ForType(toType); + } + + // Cast from a GC type is the same as a cast from TYP_I_IMPL for our purposes. + if (varTypeIsGC(fromType)) + { + fromType = TYP_I_IMPL; + } + + if (varTypeIsSmall(toType) || (genActualType(toType) == fromType)) + { + return ForCastInput(cast); + } + + // CAST(uint/int <- ulong/long) - [INT_MIN..INT_MAX] + // CAST(ulong/long <- uint) - [0..UINT_MAX] + // CAST(ulong/long <- int) - [INT_MIN..INT_MAX] + if (!cast->gtOverflow()) + { + if ((fromType == TYP_INT) && fromUnsigned) + { + return {SymbolicIntegerValue::Zero, SymbolicIntegerValue::UIntMax}; + } + + return {SymbolicIntegerValue::IntMin, SymbolicIntegerValue::IntMax}; + } + + SymbolicIntegerValue lowerBound; + SymbolicIntegerValue upperBound; + switch (toType) + { + // CAST_OVF(uint <- ulong) - [INT_MIN..INT_MAX] + // CAST_OVF(uint <- long) - [INT_MIN..INT_MAX] + case TYP_UINT: + lowerBound = SymbolicIntegerValue::IntMin; + upperBound = SymbolicIntegerValue::IntMax; + break; + + // CAST_OVF(int <- ulong) - [0..INT_MAX] + // CAST_OVF(int <- long) - [INT_MIN..INT_MAX] + case TYP_INT: + lowerBound = fromUnsigned ? SymbolicIntegerValue::Zero : SymbolicIntegerValue::IntMin; + upperBound = SymbolicIntegerValue::IntMax; + break; + + // CAST_OVF(ulong <- uint) - [0..UINT_MAX] + // CAST_OVF(ulong <- int) - [0..INT_MAX] + case TYP_ULONG: + lowerBound = SymbolicIntegerValue::Zero; + upperBound = fromUnsigned ? SymbolicIntegerValue::UIntMax : SymbolicIntegerValue::IntMax; + break; + + // CAST_OVF(long <- uint) - [0..UINT_MAX] + // CAST_OVF(long <- int) - [INT_MIN..INT_MAX] + case TYP_LONG: + lowerBound = fromUnsigned ? SymbolicIntegerValue::Zero : SymbolicIntegerValue::IntMin; + upperBound = fromUnsigned ? SymbolicIntegerValue::UIntMax : SymbolicIntegerValue::IntMax; + break; + + default: + unreached(); + } + + return {lowerBound, upperBound}; +} + +#ifdef DEBUG +/* static */ void IntegralRange::Print(IntegralRange range) +{ + printf("[%lld", SymbolicToRealValue(range.m_lowerBound)); + printf(".."); + printf("%lld]", SymbolicToRealValue(range.m_upperBound)); +} +#endif // DEBUG + /***************************************************************************** * * Helper passed to Compiler::fgWalkTreePre() to find the Asgn node for optAddCopies() @@ -762,7 +1083,7 @@ void Compiler::optPrintAssertion(AssertionDsc* curAssertion, AssertionIndex asse break; case O2K_SUBRANGE: - printf("[%u..%u]", curAssertion->op2.u2.loBound, curAssertion->op2.u2.hiBound); + IntegralRange::Print(curAssertion->op2.u2); break; default: @@ -881,12 +1202,9 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, assert((op2 == nullptr) || !op2->OperIs(GT_LIST)); assert(!helperCallArgs || (op2 != nullptr)); - AssertionDsc assertion; - memset(&assertion, 0, sizeof(AssertionDsc)); + AssertionDsc assertion = {OAK_INVALID}; assert(assertion.assertionKind == OAK_INVALID); - var_types toType; - if (op1->gtOper == GT_ARR_BOUNDS_CHECK) { if (assertionKind == OAK_NO_THROW) @@ -1087,11 +1405,6 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, switch (op2->gtOper) { optOp2Kind op2Kind; - // - // No Assertion - // - default: - goto DONE_ASSERTION; // Don't make an assertion // // Constant Assertions @@ -1170,8 +1483,9 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, // Ok everything has been set and the assertion looks good // assertion.assertionKind = assertionKind; + + goto DONE_ASSERTION; } - break; // // Copy Assertions @@ -1220,103 +1534,31 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, assertion.op2.vn = vnStore->VNConservativeNormalValue(op2->gtVNPair); assertion.op2.lcl.ssaNum = op2->AsLclVarCommon()->GetSsaNum(); - // // Ok everything has been set and the assertion looks good - // assertion.assertionKind = assertionKind; - } - break; - - // Subrange Assertions - case GT_EQ: - case GT_NE: - case GT_LT: - case GT_LE: - case GT_GT: - case GT_GE: - /* Assigning the result of a RELOP, we can add a boolean subrange assertion */ - - toType = TYP_BOOL; - goto SUBRANGE_COMMON; - - case GT_CLS_VAR: - - /* Assigning the result of an indirection into a LCL_VAR, see if we can add a subrange assertion */ - - toType = op2->gtType; - goto SUBRANGE_COMMON; - - case GT_LCL_FLD: - - /* Assigning the result of an indirection into a LCL_VAR, see if we can add a subrange assertion */ - - toType = op2->gtType; - goto SUBRANGE_COMMON; - - case GT_IND: - - /* Assigning the result of an indirection into a LCL_VAR, see if we can add a subrange assertion */ + goto DONE_ASSERTION; + } - toType = op2->gtType; - goto SUBRANGE_COMMON; + default: + break; + } - case GT_CAST: + // Try and see if we can make a subrange assertion. + if (((assertionKind == OAK_SUBRANGE) || (assertionKind == OAK_EQUAL))) + { + // Keep the casts on small struct fields. + // TODO-Review: why? + if (op2->OperIs(GT_CAST) && lvaTable[lclNum].lvIsStructField && lvaTable[lclNum].lvNormalizeOnLoad()) { - if (lvaTable[lclNum].lvIsStructField && lvaTable[lclNum].lvNormalizeOnLoad()) - { - // Keep the cast on small struct fields. - goto DONE_ASSERTION; // Don't make an assertion - } - - toType = op2->CastToType(); - - // Casts to TYP_UINT produce the same ranges as casts to TYP_INT, - // except in overflow cases which we do not yet handle. To avoid - // issues with the propagation code dropping, e. g., CAST_OVF(uint <- int) - // based on an assertion created from CAST(uint <- ulong), normalize the - // type for the range here. Note that TYP_ULONG theoretically has the same - // problem, but we do not create assertions for it. - // TODO-Cleanup: this assertion is not useful - this code exists to preserve - // previous behavior. Refactor it to stop generating such assertions. - if (toType == TYP_UINT) - { - toType = TYP_INT; - } - SUBRANGE_COMMON: - if ((assertionKind != OAK_SUBRANGE) && (assertionKind != OAK_EQUAL)) - { - goto DONE_ASSERTION; // Don't make an assertion - } - - if (varTypeIsFloating(op1->TypeGet())) - { - // We don't make assertions on a cast from floating point - goto DONE_ASSERTION; - } - - switch (toType) - { - case TYP_BOOL: - case TYP_BYTE: - case TYP_UBYTE: - case TYP_SHORT: - case TYP_USHORT: -#ifdef TARGET_64BIT - case TYP_UINT: - case TYP_INT: -#endif // TARGET_64BIT - assertion.op2.u2.loBound = AssertionDsc::GetLowerBoundForIntegralType(toType); - assertion.op2.u2.hiBound = AssertionDsc::GetUpperBoundForIntegralType(toType); - break; + goto DONE_ASSERTION; + } - default: - goto DONE_ASSERTION; // Don't make an assertion - } + if (optTryExtractSubrangeAssertion(op2, &assertion.op2.u2)) + { assertion.op2.kind = O2K_SUBRANGE; assertion.assertionKind = OAK_SUBRANGE; } - break; } } // else // !helperCallArgs } // if (op1->gtOper == GT_LCL_VAR) @@ -1410,30 +1652,115 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, } DONE_ASSERTION: - if (assertion.assertionKind == OAK_INVALID) + return optFinalizeCreatingAssertion(&assertion); +} + +//------------------------------------------------------------------------ +// optFinalizeCreatingAssertion: Add the assertion, if well-formed, to the table. +// +// Checks that in global assertion propagation assertions do not have missing +// value and SSA numbers. +// +// Arguments: +// assertion - assertion to check and add to the table +// +// Return Value: +// Index of the assertion if it was successfully created, NO_ASSERTION_INDEX otherwise. +// +AssertionIndex Compiler::optFinalizeCreatingAssertion(AssertionDsc* assertion) +{ + if (assertion->assertionKind == OAK_INVALID) { return NO_ASSERTION_INDEX; } if (!optLocalAssertionProp) { - if ((assertion.op1.vn == ValueNumStore::NoVN) || (assertion.op2.vn == ValueNumStore::NoVN) || - (assertion.op1.vn == ValueNumStore::VNForVoid()) || (assertion.op2.vn == ValueNumStore::VNForVoid())) + if ((assertion->op1.vn == ValueNumStore::NoVN) || (assertion->op2.vn == ValueNumStore::NoVN) || + (assertion->op1.vn == ValueNumStore::VNForVoid()) || (assertion->op2.vn == ValueNumStore::VNForVoid())) { return NO_ASSERTION_INDEX; } // TODO: only copy assertions rely on valid SSA number so we could generate more assertions here - if ((assertion.op1.kind != O1K_VALUE_NUMBER) && (assertion.op1.lcl.ssaNum == SsaConfig::RESERVED_SSA_NUM)) + if ((assertion->op1.kind != O1K_VALUE_NUMBER) && (assertion->op1.lcl.ssaNum == SsaConfig::RESERVED_SSA_NUM)) { return NO_ASSERTION_INDEX; } } // Now add the assertion to our assertion table - noway_assert(assertion.op1.kind != O1K_INVALID); - noway_assert((assertion.op1.kind == O1K_ARR_BND) || (assertion.op2.kind != O2K_INVALID)); - return optAddAssertion(&assertion); + noway_assert(assertion->op1.kind != O1K_INVALID); + noway_assert((assertion->op1.kind == O1K_ARR_BND) || (assertion->op2.kind != O2K_INVALID)); + + return optAddAssertion(assertion); +} + +//------------------------------------------------------------------------ +// optTryExtractSubrangeAssertion: Extract the bounds of the value a tree produces. +// +// Generates [0..1] ranges for relops, [T_MIN..T_MAX] for small-typed indirections +// and casts to small types. Generates various ranges for casts to and from "large" +// types - see "IntegralRange::ForCastOutput". +// +// Arguments: +// source - tree producing the value +// pRange - [out] parameter for the range +// +// Return Value: +// "true" if the "source" computes a value that could be used for a subrange +// assertion, i. e. narrower than the range of the node's type. +// +// Notes: +// The "pRange" parameter is only written to if the function returns "true". +// +bool Compiler::optTryExtractSubrangeAssertion(GenTree* source, IntegralRange* pRange) +{ + var_types sourceType = TYP_UNDEF; + + switch (source->OperGet()) + { + case GT_EQ: + case GT_NE: + case GT_LT: + case GT_LE: + case GT_GT: + case GT_GE: + *pRange = {SymbolicIntegerValue::Zero, SymbolicIntegerValue::One}; + return true; + + case GT_CLS_VAR: + case GT_LCL_FLD: + case GT_IND: + sourceType = source->TypeGet(); + break; + + case GT_CAST: + if (varTypeIsIntegral(source)) + { + IntegralRange castRange = IntegralRange::ForCastOutput(source->AsCast()); + IntegralRange nodeRange = IntegralRange::ForType(source->TypeGet()); + assert(nodeRange.Contains(castRange)); + + if (!castRange.Equals(nodeRange)) + { + *pRange = castRange; + return true; + } + } + return false; + + default: + return false; + } + + if (varTypeIsSmall(sourceType)) + { + *pRange = IntegralRange::ForType(sourceType); + return true; + } + + return false; } /***************************************************************************** @@ -1788,6 +2115,65 @@ void Compiler::optCreateComplementaryAssertion(AssertionIndex assertionIndex, } } +// optAssertionGenCast: Create a tentative subrange assertion for a cast. +// +// This function will try to create an assertion that the cast's operand +// is within the "input" range for the cast, so that this assertion can +// later be proven via implication and the cast removed. Such assertions +// are only generated during global propagation, and only for LCL_VARs. +// +// Arguments: +// cast - the cast node for which to create the assertion +// +// Return Value: +// Index of the generated assertion, or NO_ASSERTION_INDEX if it was not +// legal, profitable, or possible to create one. +// +AssertionIndex Compiler::optAssertionGenCast(GenTreeCast* cast) +{ + if (optLocalAssertionProp || !varTypeIsIntegral(cast) || !varTypeIsIntegral(cast->CastOp())) + { + return NO_ASSERTION_INDEX; + } + + // This condition exists to preverve previous behavior. + if (!cast->CastOp()->OperIs(GT_LCL_VAR)) + { + return NO_ASSERTION_INDEX; + } + + GenTreeLclVar* lclVar = cast->CastOp()->AsLclVar(); + LclVarDsc* varDsc = lvaGetDesc(lclVar); + + // It is not useful to make assertions about address-exposed variables, they will never be proven. + if (varDsc->IsAddressExposed()) + { + return NO_ASSERTION_INDEX; + } + + // A representation-changing cast cannot be simplified if it is not checked. + if (!cast->gtOverflow() && (genActualType(cast) != genActualType(lclVar))) + { + return NO_ASSERTION_INDEX; + } + + // This condition also exists to preverve previous behavior. + if (varDsc->lvIsStructField && varDsc->lvNormalizeOnLoad()) + { + return NO_ASSERTION_INDEX; + } + + AssertionDsc assertion = {OAK_SUBRANGE}; + assertion.op1.kind = O1K_LCLVAR; + assertion.op1.vn = vnStore->VNConservativeNormalValue(lclVar->gtVNPair); + assertion.op1.lcl.lclNum = lclVar->GetLclNum(); + assertion.op1.lcl.ssaNum = lclVar->GetSsaNum(); + assertion.op2.kind = O2K_SUBRANGE; + assertion.op2.u2 = IntegralRange::ForCastInput(cast); + + return optFinalizeCreatingAssertion(&assertion); +} + //------------------------------------------------------------------------ // optCreateJtrueAssertions: Create assertions about a JTRUE's relop operands. // @@ -2250,15 +2636,11 @@ void Compiler::optAssertionGen(GenTree* tree) break; case GT_CAST: - // We only create this assertion for global assertion prop - if (!optLocalAssertionProp) - { - // This represets an assertion that we would like to prove to be true. It is not actually a true - // assertion. - // If we can prove this assertion true then we can eliminate this cast. - assertionInfo = optCreateAssertion(tree->AsOp()->gtOp1, tree, OAK_SUBRANGE); - assertionProven = false; - } + // This represets an assertion that we would like to prove to be true. + // If we can prove this assertion true then we can eliminate this cast. + // We only create this assertion for global assertion propagation. + assertionInfo = optAssertionGenCast(tree->AsCast()); + assertionProven = false; break; case GT_JTRUE: @@ -2334,18 +2716,22 @@ AssertionIndex Compiler::optFindComplementary(AssertionIndex assertIndex) return NO_ASSERTION_INDEX; } -/***************************************************************************** - * - * Given a lclNum, a fromType and a toType, return assertion index of the assertion that - * claims that a variable's value is always a valid subrange of the fromType. - * Thus we can discard or omit a cast to fromType. Returns NO_ASSERTION_INDEX - * if one such assertion could not be found in "assertions." - */ - -AssertionIndex Compiler::optAssertionIsSubrange(GenTree* tree, - var_types fromType, - var_types toType, - ASSERT_VALARG_TP assertions) +//------------------------------------------------------------------------ +// optAssertionIsSubrange: Find a subrange assertion for the given range and tree. +// +// This function will return the index of the first assertion in "assertions" +// which claims that the value of "tree" is withing the bounds of the provided +// "range" (i. e. "range.Contains(assertedRange)"). +// +// Arguments: +// tree - the tree for which to find the assertion +// range - range the subrange of which to look for +// assertions - the set of assertions +// +// Return Value: +// Index of the found assertion, NO_ASSERTION_INDEX otherwise. +// +AssertionIndex Compiler::optAssertionIsSubrange(GenTree* tree, IntegralRange range, ASSERT_VALARG_TP assertions) { if (!optLocalAssertionProp && BitVecOps::IsEmpty(apTraits, assertions)) { @@ -2369,46 +2755,13 @@ AssertionIndex Compiler::optAssertionIsSubrange(GenTree* tree, continue; } - // If we have an unsigned fromType, then the loBound can't be negative - // - if (varTypeIsUnsigned(fromType)) - { - if (curAssertion->op2.u2.loBound < 0) - { - continue; - } - } - - // Make sure the toType is within current assertion's bounds. - switch (toType) + if (range.Contains(curAssertion->op2.u2)) { - case TYP_BYTE: - case TYP_UBYTE: - case TYP_SHORT: - case TYP_USHORT: - if ((curAssertion->op2.u2.loBound < AssertionDsc::GetLowerBoundForIntegralType(toType)) || - (curAssertion->op2.u2.hiBound > AssertionDsc::GetUpperBoundForIntegralType(toType))) - { - continue; - } - break; - - case TYP_UINT: - if (curAssertion->op2.u2.loBound < AssertionDsc::GetLowerBoundForIntegralType(toType)) - { - continue; - } - break; - - case TYP_INT: - break; - - default: - continue; + return index; } - return index; } } + return NO_ASSERTION_INDEX; } @@ -3627,111 +3980,91 @@ GenTree* Compiler::optAssertionPropLocal_RelOp(ASSERT_VALARG_TP assertions, GenT return optAssertionProp_Update(op2, tree, stmt); } -/***************************************************************************** - * - * Given a tree consisting of a Cast and a set of available assertions - * we try to propagate an assertion and modify the Cast tree if we can. - * We pass in the root of the tree via 'stmt', for local copy prop 'stmt' - * will be nullptr. - * - * Returns the modified tree, or nullptr if no assertion prop took place. - */ -GenTree* Compiler::optAssertionProp_Cast(ASSERT_VALARG_TP assertions, GenTree* tree, Statement* stmt) +//------------------------------------------------------------------------ +// optAssertionProp_Cast: Propagate assertion for a cast, possibly removing it. +// +// The function use "optAssertionIsSubrange" to find an assertion which claims the +// cast's operand (only locals are supported) is a subrange of the "input" range +// for the cast, as computed by "IntegralRange::ForCastInput", and, if such +// assertion is found, act on it - either remove the cast if it is not changing +// representation, or try to remove the GTF_OVERFLOW flag from it. +// +// Arguments: +// assertions - the set of live assertions +// cast - the cast for which to propagate the assertions +// stmt - statement "cast" is a part of, "nullptr" for local prop +// +// Return Value: +// The, possibly modified, cast tree or "nullptr" if no propagation took place. +// +GenTree* Compiler::optAssertionProp_Cast(ASSERT_VALARG_TP assertions, GenTreeCast* cast, Statement* stmt) { - assert(tree->gtOper == GT_CAST); - - var_types fromType = tree->CastFromType(); - var_types toType = tree->AsCast()->gtCastType; - GenTree* op1 = tree->AsCast()->CastOp(); - - // force the fromType to unsigned if GT_UNSIGNED flag is set - if (tree->IsUnsigned()) - { - fromType = varTypeToUnsigned(fromType); - } + GenTree* op1 = cast->CastOp(); - // If we have a cast involving floating point types, then bail. - if (varTypeIsFloating(toType) || varTypeIsFloating(fromType)) + // Bail if we have a cast involving floating point or GC types. + if (!varTypeIsIntegral(cast) || !varTypeIsIntegral(op1)) { return nullptr; } // Skip over a GT_COMMA node(s), if necessary to get to the lcl. - GenTree* lcl = op1; - while (lcl->gtOper == GT_COMMA) - { - lcl = lcl->AsOp()->gtOp2; - } + GenTree* lcl = op1->gtEffectiveVal(); // If we don't have a cast of a LCL_VAR then bail. - if (lcl->gtOper != GT_LCL_VAR) + if (!lcl->OperIs(GT_LCL_VAR)) { return nullptr; } - AssertionIndex index = optAssertionIsSubrange(lcl, fromType, toType, assertions); + IntegralRange range = IntegralRange::ForCastInput(cast); + AssertionIndex index = optAssertionIsSubrange(lcl, range, assertions); if (index != NO_ASSERTION_INDEX) { - LclVarDsc* varDsc = &lvaTable[lcl->AsLclVarCommon()->GetLclNum()]; - if (varDsc->lvNormalizeOnLoad() || varTypeIsLong(varDsc->TypeGet())) + LclVarDsc* varDsc = lvaGetDesc(lcl->AsLclVarCommon()); + + // Representation-changing casts cannot be removed. + if ((genActualType(cast) != genActualType(lcl))) { - // For normalize on load variables it must be a narrowing cast to remove - if (genTypeSize(toType) > genTypeSize(varDsc->TypeGet())) + // Can we just remove the GTF_OVERFLOW flag? + if (!cast->gtOverflow()) { - // Can we just remove the GTF_OVERFLOW flag? - if ((tree->gtFlags & GTF_OVERFLOW) == 0) - { - return nullptr; - } - else - { - -#ifdef DEBUG - if (verbose) - { - printf("\nSubrange prop for index #%02u in " FMT_BB ":\n", index, compCurBB->bbNum); - gtDispTree(tree, nullptr, nullptr, true); - } -#endif - tree->gtFlags &= ~GTF_OVERFLOW; // This cast cannot overflow - return optAssertionProp_Update(tree, tree, stmt); - } + return nullptr; } - - // GT_CAST long -> uint -> int - // | - // GT_LCL_VAR long - // - // Where the lclvar is known to be in the range of [0..MAX_UINT] - // - // A load of a 32-bit unsigned int is the same as a load of a 32-bit signed int - // - if (toType == TYP_UINT) +#ifdef DEBUG + if (verbose) { - toType = TYP_INT; + printf("\nSubrange prop for index #%02u in " FMT_BB ":\n", index, compCurBB->bbNum); + DISPNODE(cast); } +#endif + cast->ClearOverflow(); + return optAssertionProp_Update(cast, cast, stmt); + } - // Change the "lcl" type to match what the cast wanted, by propagating the type - // change down the comma nodes leading to the "lcl", if we skipped them earlier. - GenTree* tmp = op1; - while (tmp->gtOper == GT_COMMA) + // We might need to retype a "normalize on load" local back to its original small type + // so that codegen recognizes it needs to use narrow loads if the local ends up in memory. + if (varDsc->lvNormalizeOnLoad()) + { + // The Jit is known to play somewhat loose with small types, so let's restrict this code + // to the pattern we know is "safe and sound", i. e. CAST(type <- LCL_VAR(int, V00 type)). + if ((varDsc->TypeGet() != cast->CastToType()) || !lcl->TypeIs(TYP_INT)) { - tmp->gtType = toType; - tmp = tmp->AsOp()->gtOp2; + return nullptr; } - noway_assert(tmp == lcl); - tmp->gtType = toType; + + op1->ChangeType(varDsc->TypeGet()); } #ifdef DEBUG if (verbose) { printf("\nSubrange prop for index #%02u in " FMT_BB ":\n", index, compCurBB->bbNum); - gtDispTree(tree, nullptr, nullptr, true); + DISPNODE(cast); } #endif - return optAssertionProp_Update(op1, tree, stmt); + return optAssertionProp_Update(op1, cast, stmt); } + return nullptr; } @@ -4308,7 +4641,7 @@ GenTree* Compiler::optAssertionProp(ASSERT_VALARG_TP assertions, GenTree* tree, return optAssertionProp_Comma(assertions, tree, stmt); case GT_CAST: - return optAssertionProp_Cast(assertions, tree, stmt); + return optAssertionProp_Cast(assertions, tree->AsCast(), stmt); case GT_CALL: return optAssertionProp_Call(assertions, tree->AsCall(), stmt); @@ -4552,7 +4885,7 @@ void Compiler::optImpliedByConstAssertion(AssertionDsc* constAssertion, ASSERT_T { case O2K_SUBRANGE: // Is the const assertion's constant, within implied assertion's bounds? - usable = ((iconVal >= impAssertion->op2.u2.loBound) && (iconVal <= impAssertion->op2.u2.hiBound)); + usable = impAssertion->op2.u2.Contains(iconVal); break; case O2K_CONST_INT: @@ -4681,8 +5014,7 @@ void Compiler::optImpliedByCopyAssertion(AssertionDsc* copyAssertion, AssertionD switch (impAssertion->op2.kind) { case O2K_SUBRANGE: - usable = op1MatchesCopy && ((impAssertion->op2.u2.loBound <= depAssertion->op2.u2.loBound) && - (impAssertion->op2.u2.hiBound >= depAssertion->op2.u2.hiBound)); + usable = op1MatchesCopy && impAssertion->op2.u2.Contains(depAssertion->op2.u2); break; case O2K_CONST_LONG: diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index ffde30cf5bd04..196ae0acf0501 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -1134,6 +1134,109 @@ class LclVarDsc }; // class LclVarDsc +enum class SymbolicIntegerValue : int32_t +{ + LongMin, + IntMin, + ShortMin, + ByteMin, + Zero, + One, + ByteMax, + UByteMax, + ShortMax, + UShortMax, + IntMax, + UIntMax, + LongMax, +}; + +inline constexpr bool operator>(SymbolicIntegerValue left, SymbolicIntegerValue right) +{ + return static_cast(left) > static_cast(right); +} + +inline constexpr bool operator>=(SymbolicIntegerValue left, SymbolicIntegerValue right) +{ + return static_cast(left) >= static_cast(right); +} + +inline constexpr bool operator<(SymbolicIntegerValue left, SymbolicIntegerValue right) +{ + return static_cast(left) < static_cast(right); +} + +inline constexpr bool operator<=(SymbolicIntegerValue left, SymbolicIntegerValue right) +{ + return static_cast(left) <= static_cast(right); +} + +// Represents an integral range useful for reasoning about integral casts. +// It uses a symbolic representation for lower and upper bounds so +// that it can efficiently handle integers of all sizes on all hosts. +// +// Note that the ranges represented by this class are **always** in the +// "signed" domain. This is so that if we know the range a node produces, it +// can be trivially used to determine if a cast above the node does or does not +// overflow, which requires that the interpretation of integers be the same both +// for the "input" and "output". We choose signed interpretation here because it +// produces nice continuous ranges and because IR uses sign-extension for constants. +// +// Some examples of how ranges are computed for casts: +// 1. CAST_OVF(ubyte <- uint): does not overflow for [0..UBYTE_MAX], produces the +// same range - all casts that do not change the representation, i. e. have the same +// "actual" input and output type, have the same "input" and "output" range. +// 2. CAST_OVF(ulong <- uint): never oveflows => the "input" range is [INT_MIN..INT_MAX] +// (aka all possible 32 bit integers). Produces [0..UINT_MAX] (aka all possible 32 +// bit integers zero-extended to 64 bits). +// 3. CAST_OVF(int <- uint): overflows for inputs larger than INT_MAX <=> less than 0 +// when interpreting as signed => the "input" range is [0..INT_MAX], the same range +// being the produced one as the node does not change the width of the integer. +// +class IntegralRange +{ +private: + SymbolicIntegerValue m_lowerBound; + SymbolicIntegerValue m_upperBound; + +public: + IntegralRange() = default; + + IntegralRange(SymbolicIntegerValue lowerBound, SymbolicIntegerValue upperBound) + : m_lowerBound(lowerBound), m_upperBound(upperBound) + { + assert(lowerBound <= upperBound); + } + + bool Contains(int64_t value) const; + + bool Contains(IntegralRange other) const + { + return (m_lowerBound <= other.m_lowerBound) && (other.m_upperBound <= m_upperBound); + } + + bool Equals(IntegralRange other) const + { + return (m_lowerBound == other.m_lowerBound) && (m_upperBound == other.m_upperBound); + } + + static int64_t SymbolicToRealValue(SymbolicIntegerValue value); + static SymbolicIntegerValue LowerBoundForType(var_types type); + static SymbolicIntegerValue UpperBoundForType(var_types type); + + static IntegralRange ForType(var_types type) + { + return {LowerBoundForType(type), UpperBoundForType(type)}; + } + + static IntegralRange ForCastInput(GenTreeCast* cast); + static IntegralRange ForCastOutput(GenTreeCast* cast); + +#ifdef DEBUG + static void Print(IntegralRange range); +#endif // DEBUG +}; + /* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX @@ -7347,6 +7450,7 @@ class Compiler O2K_SUBRANGE, O2K_COUNT }; + struct AssertionDsc { optAssertionKind assertionKind; @@ -7375,21 +7479,18 @@ class Compiler ValueNum vn; struct IntVal { - ssize_t iconVal; // integer - unsigned padding; // unused; ensures iconFlags does not overlap lconVal + ssize_t iconVal; // integer +#if !defined(HOST_64BIT) + unsigned padding; // unused; ensures iconFlags does not overlap lconVal +#endif GenTreeFlags iconFlags; // gtFlags }; - struct Range // integer subrange - { - ssize_t loBound; - ssize_t hiBound; - }; union { - SsaVar lcl; - IntVal u1; - __int64 lconVal; - double dconVal; - Range u2; + SsaVar lcl; + IntVal u1; + __int64 lconVal; + double dconVal; + IntegralRange u2; }; } op2; @@ -7440,48 +7541,6 @@ class Compiler return false; } - static ssize_t GetLowerBoundForIntegralType(var_types type) - { - switch (type) - { - case TYP_BYTE: - return SCHAR_MIN; - case TYP_SHORT: - return SHRT_MIN; - case TYP_INT: - return INT_MIN; - case TYP_BOOL: - case TYP_UBYTE: - case TYP_USHORT: - case TYP_UINT: - return 0; - default: - unreached(); - } - } - static ssize_t GetUpperBoundForIntegralType(var_types type) - { - switch (type) - { - case TYP_BOOL: - return 1; - case TYP_BYTE: - return SCHAR_MAX; - case TYP_SHORT: - return SHRT_MAX; - case TYP_INT: - return INT_MAX; - case TYP_UBYTE: - return UCHAR_MAX; - case TYP_USHORT: - return USHRT_MAX; - case TYP_UINT: - return UINT_MAX; - default: - unreached(); - } - } - bool HasSameOp1(AssertionDsc* that, bool vnBased) { if (op1.kind != that->op1.kind) @@ -7525,7 +7584,7 @@ class Compiler (!vnBased || op2.lcl.ssaNum == that->op2.lcl.ssaNum); case O2K_SUBRANGE: - return ((op2.u2.loBound == that->op2.u2.loBound) && (op2.u2.hiBound == that->op2.u2.hiBound)); + return op2.u2.Equals(that->op2.u2); case O2K_INVALID: // we will return false @@ -7615,6 +7674,7 @@ class Compiler // Assertion Gen functions. void optAssertionGen(GenTree* tree); + AssertionIndex optAssertionGenCast(GenTreeCast* cast); AssertionIndex optAssertionGenPhiDefn(GenTree* tree); AssertionInfo optCreateJTrueBoundsAssertion(GenTree* tree); AssertionInfo optAssertionGenJtrue(GenTree* tree); @@ -7630,6 +7690,11 @@ class Compiler GenTree* op2, optAssertionKind assertionKind, bool helperCallArgs = false); + + AssertionIndex optFinalizeCreatingAssertion(AssertionDsc* assertion); + + bool optTryExtractSubrangeAssertion(GenTree* source, IntegralRange* pRange); + void optCreateComplementaryAssertion(AssertionIndex assertionIndex, GenTree* op1, GenTree* op2, @@ -7644,10 +7709,7 @@ class Compiler ASSERT_TP optGetVnMappedAssertions(ValueNum vn); // Used for respective assertion propagations. - AssertionIndex optAssertionIsSubrange(GenTree* tree, - var_types fromType, - var_types toType, - ASSERT_VALARG_TP assertions); + AssertionIndex optAssertionIsSubrange(GenTree* tree, IntegralRange range, ASSERT_VALARG_TP assertions); AssertionIndex optAssertionIsSubtype(GenTree* tree, GenTree* methodTableArg, ASSERT_VALARG_TP assertions); AssertionIndex optAssertionIsNonNullInternal(GenTree* op, ASSERT_VALARG_TP assertions DEBUGARG(bool* pVnBased)); bool optAssertionIsNonNull(GenTree* op, @@ -7672,7 +7734,7 @@ class Compiler GenTree* optAssertionProp(ASSERT_VALARG_TP assertions, GenTree* tree, Statement* stmt, BasicBlock* block); GenTree* optAssertionProp_LclVar(ASSERT_VALARG_TP assertions, GenTreeLclVarCommon* tree, Statement* stmt); GenTree* optAssertionProp_Ind(ASSERT_VALARG_TP assertions, GenTree* tree, Statement* stmt); - GenTree* optAssertionProp_Cast(ASSERT_VALARG_TP assertions, GenTree* tree, Statement* stmt); + GenTree* optAssertionProp_Cast(ASSERT_VALARG_TP assertions, GenTreeCast* cast, Statement* stmt); GenTree* optAssertionProp_Call(ASSERT_VALARG_TP assertions, GenTreeCall* call, Statement* stmt); GenTree* optAssertionProp_RelOp(ASSERT_VALARG_TP assertions, GenTree* tree, Statement* stmt); GenTree* optAssertionProp_Comma(ASSERT_VALARG_TP assertions, GenTree* tree, Statement* stmt); diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 79f0e20bba643..25931088a8522 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -6018,11 +6018,21 @@ GenTree* Compiler::fgMorphLocalVar(GenTree* tree, bool forceRemorph) noway_assert(!(tree->gtFlags & GTF_VAR_DEF) || varAddr); // GTF_VAR_DEF should always imply varAddr - if (!varAddr && varTypeIsSmall(varDsc->TypeGet()) && varDsc->lvNormalizeOnLoad()) + if (!varAddr && varDsc->lvNormalizeOnLoad()) { #if LOCAL_ASSERTION_PROP - // Assertion prop can tell us to omit adding a cast here - if (optLocalAssertionProp && optAssertionIsSubrange(tree, TYP_INT, varType, apFull) != NO_ASSERTION_INDEX) + // TYP_BOOL quirk: previously, the code in optAssertionIsSubrange did not handle TYP_BOOL. + // Now it does, but this leads to some regressions because we lose the uniform VNs for trees + // that represent the "reduced" normalize-on-load locals, i. e. LCL_VAR(small type V00), created + // here with local assertions, and "expanded", i. e. CAST(small type <- LCL_VAR(int V00)). + // This is a pretty fundamental problem with how normalize-on-load locals appear to the optimizer. + // This quirk preserves the previous behavior. + // TODO-CQ: fix the VNs for normalize-on-load locals and remove this quirk. + bool isBoolQuirk = varType == TYP_BOOL; + + // Assertion prop can tell us to omit adding a cast here. + if (optLocalAssertionProp && !isBoolQuirk && + optAssertionIsSubrange(tree, IntegralRange::ForType(varType), apFull) != NO_ASSERTION_INDEX) { // The previous assertion can guarantee us that if this node gets // assigned a register, it will be normalized already. It is still