diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index 75859cd8c2b234..7d10b5f0f4165a 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -300,6 +300,10 @@ bool IntegralRange::Contains(int64_t value) const { rangeType = TYP_USHORT; } + else if ((elementCount == 32) && varTypeIsLong(rangeType)) + { + return {SymbolicIntegerValue::Zero, UpperBoundForType(TYP_UINT)}; + } break; } @@ -4070,10 +4074,10 @@ void Compiler::optAssertionProp_RangeProperties(ASSERT_VALARG_TP assertions, } // Let's see if MergeEdgeAssertions can help us: - if (genActualType(tree) == TYP_INT) + Range rng = RangeCheck::GetRangeFromAssertions(this, tree, assertions); + + if (rng.IsConstantRange()) { - Range rng = RangeCheck::GetRangeFromAssertions(this, treeVN, assertions); - assert(rng.IsConstantRange()); if (rng.LowerLimit().GetConstant() >= 0) { *isKnownNonNegative = true; @@ -4101,34 +4105,39 @@ GenTree* Compiler::optAssertionProp_AddMulSub(ASSERT_VALARG_TP assertions, GenTr { assert(tree->OperIs(GT_MUL, GT_ADD, GT_SUB)); - if (!optLocalAssertionProp && tree->TypeIs(TYP_INT) && tree->gtOverflow()) + if (!optLocalAssertionProp && varTypeIsIntegral(tree) && tree->gtOverflow()) { GenTree* op1 = tree->gtGetOp1(); GenTree* op2 = tree->gtGetOp2(); - Range op1Rng = RangeCheck::GetRangeFromAssertions(this, optConservativeNormalVN(op1), assertions); - Range op2Rng = RangeCheck::GetRangeFromAssertions(this, optConservativeNormalVN(op2), assertions); - Range result = Limit(Limit::keUnknown); - if (tree->OperIs(GT_MUL)) - { - result = RangeOps::Multiply(op1Rng, op2Rng, tree->IsUnsigned()); - } - else if (tree->OperIs(GT_ADD)) - { - result = RangeOps::Add(op1Rng, op2Rng, tree->IsUnsigned()); - } - else - { - assert(tree->OperIs(GT_SUB)); - result = RangeOps::Subtract(op1Rng, op2Rng, tree->IsUnsigned()); - } + Range op1Rng = RangeCheck::GetRangeFromAssertions(this, op1, assertions); + Range op2Rng = RangeCheck::GetRangeFromAssertions(this, op2, assertions); - // If it produced a constant range for the result, we know the operation - // cannot overflow for any values consistent with the current assertions. - if (result.IsConstantRange()) + if (op1Rng.IsConstantRange() && op2Rng.IsConstantRange()) { - tree->ClearOverflow(); - return optAssertionProp_Update(tree, tree, stmt); + Range result = Limit(Limit::keUnknown); + + if (tree->OperIs(GT_MUL)) + { + result = RangeOps::Multiply(op1Rng, op2Rng, tree->IsUnsigned()); + } + else if (tree->OperIs(GT_ADD)) + { + result = RangeOps::Add(op1Rng, op2Rng, tree->IsUnsigned()); + } + else + { + assert(tree->OperIs(GT_SUB)); + result = RangeOps::Subtract(op1Rng, op2Rng, tree->IsUnsigned()); + } + + // If it produced a constant range for the result, we know the operation + // cannot overflow for any values consistent with the current assertions. + if (result.IsConstantRange()) + { + tree->ClearOverflow(); + return optAssertionProp_Update(tree, tree, stmt); + } } } return nullptr; @@ -4481,37 +4490,40 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, } // See if we can fold the relop based on range information. - // We don't need the op1->TypeIs(TYP_INT) check, but it seems to improve the TP quite a bit. - if (op1->TypeIs(TYP_INT)) + // We don't need the varTypeIsIntegral(op1) check, but it seems to improve the TP quite a bit. + if (varTypeIsIntegral(op1)) { - Range relopRange = RangeCheck::GetRangeFromAssertions(this, relopVN, assertions); + Range relopRange = RangeCheck::GetRangeFromAssertions(this, tree, assertions); - int relopResult; - if (!relopRange.IsSingleValueConstant(&relopResult)) + if (relopRange.IsConstantRange()) { - // Retry by obtaining operand ranges individually. This accounts for cases where the - // relopVN's operands differ from the physical op1 and op2 due to optimization passes. - VNFuncApp relopFuncApp; - if (vnStore->IsVNRelop(relopVN, &relopFuncApp) && - (((relopFuncApp.GetArg(0) == op1VN) && (relopFuncApp.GetArg(1) == op2VN)) || - ((relopFuncApp.GetArg(0) == op2VN) && (relopFuncApp.GetArg(1) == op1VN)))) + int relopResult; + if (!relopRange.IsSingleValueConstant(&relopResult)) { - // VNs match - we'll find nothing new by looking at individual operand ranges. + // Retry by obtaining operand ranges individually. This accounts for cases where the + // relopVN's operands differ from the physical op1 and op2 due to optimization passes. + VNFuncApp relopFuncApp; + if (vnStore->IsVNRelop(relopVN, &relopFuncApp) && + (((relopFuncApp.GetArg(0) == op1VN) && (relopFuncApp.GetArg(1) == op2VN)) || + ((relopFuncApp.GetArg(0) == op2VN) && (relopFuncApp.GetArg(1) == op1VN)))) + { + // VNs match - we'll find nothing new by looking at individual operand ranges. + } + else + { + Range op1Range = RangeCheck::GetRangeFromAssertions(this, op1, assertions); + Range op2Range = RangeCheck::GetRangeFromAssertions(this, op2, assertions); + relopRange = RangeOps::EvalRelop(tree->OperGet(), tree->IsUnsigned(), op1Range, op2Range); + } } - else + + if (relopRange.IsSingleValueConstant(&relopResult)) { - Range op1Range = RangeCheck::GetRangeFromAssertions(this, op1VN, assertions); - Range op2Range = RangeCheck::GetRangeFromAssertions(this, op2VN, assertions); - relopRange = RangeOps::EvalRelop(tree->OperGet(), tree->IsUnsigned(), op1Range, op2Range); + assert((relopResult == 0) || (relopResult == 1)); + newTree = gtWrapWithSideEffects(relopResult == 1 ? gtNewTrue() : gtNewFalse(), tree, GTF_ALL_EFFECT); + return optAssertionProp_Update(newTree, tree, stmt); } } - - if (relopRange.IsSingleValueConstant(&relopResult)) - { - assert((relopResult == 0) || (relopResult == 1)); - newTree = gtWrapWithSideEffects(relopResult == 1 ? gtNewTrue() : gtNewFalse(), tree, GTF_ALL_EFFECT); - return optAssertionProp_Update(newTree, tree, stmt); - } } // Else check if we have an equality check involving a local or an indir @@ -4928,29 +4940,32 @@ GenTree* Compiler::optAssertionProp_Cast(ASSERT_VALARG_TP assertions, if (FitsIn(castLo) && FitsIn(castHi)) { Range castToTypeRange = Range(Limit(Limit::keConstant, (int)castLo), Limit(Limit::keConstant, (int)castHi)); - if (castToTypeRange.IsConstantRange() && (genActualType(cast->CastOp()) == TYP_INT)) - { - ValueNum castOpVN = optConservativeNormalVN(cast->CastOp()); - Range castOpRng = RangeCheck::GetRangeFromAssertions(this, castOpVN, assertions); - assert(castOpRng.IsConstantRange()); - int castFromLo = castOpRng.LowerLimit().GetConstant(); - int castFromHi = castOpRng.UpperLimit().GetConstant(); - int castToLo = castToTypeRange.LowerLimit().GetConstant(); - int castToHi = castToTypeRange.UpperLimit().GetConstant(); + if (castToTypeRange.IsConstantRange()) + { + GenTree* castOp = cast->CastOp(); + Range castOpRng = RangeCheck::GetRangeFromAssertions(this, castOp, assertions); - if ((castFromLo >= castToLo) && (castFromHi <= castToHi)) + if (castOpRng.IsConstantRange()) { - if (canDropCast) + int castFromLo = castOpRng.LowerLimit().GetConstant(); + int castFromHi = castOpRng.UpperLimit().GetConstant(); + int castToLo = castToTypeRange.LowerLimit().GetConstant(); + int castToHi = castToTypeRange.UpperLimit().GetConstant(); + + if ((castFromLo >= castToLo) && (castFromHi <= castToHi)) { - JITDUMP("Removing cast %06u as redundant based on VN assertions.\n", dspTreeID(cast)); - return optAssertionProp_Update(cast->CastOp(), cast, stmt); - } + if (canDropCast) + { + JITDUMP("Removing cast %06u as redundant based on VN assertions.\n", dspTreeID(cast)); + return optAssertionProp_Update(castOp, cast, stmt); + } - assert(cast->gtOverflow()); - JITDUMP("Clearing overflow flag for cast %06u based on VN assertions.\n", dspTreeID(cast)); - cast->ClearOverflow(); - return optAssertionProp_Update(cast, cast, stmt); + assert(cast->gtOverflow()); + JITDUMP("Clearing overflow flag for cast %06u based on VN assertions.\n", dspTreeID(cast)); + cast->ClearOverflow(); + return optAssertionProp_Update(cast, cast, stmt); + } } } } @@ -5519,9 +5534,11 @@ GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, GenTree } #endif // FEATURE_ENABLE_NO_RANGE_CHECKS - GenTreeBoundsChk* arrBndsChk = tree->AsBoundsChk(); - ValueNum vnCurIdx = vnStore->VNConservativeNormalValue(arrBndsChk->GetIndex()->gtVNPair); - ValueNum vnCurLen = vnStore->VNConservativeNormalValue(arrBndsChk->GetArrayLength()->gtVNPair); + GenTreeBoundsChk* arrBndsChk = tree->AsBoundsChk(); + GenTree* arrBndsChkIdx = arrBndsChk->GetIndex(); + GenTree* arrBndsChkLen = arrBndsChk->GetArrayLength(); + ValueNum vnCurIdx = vnStore->VNConservativeNormalValue(arrBndsChkIdx->gtVNPair); + ValueNum vnCurLen = vnStore->VNConservativeNormalValue(arrBndsChkLen->gtVNPair); auto dropBoundsCheck = [&](INDEBUG(const char* reason)) -> GenTree* { JITDUMP("\nVN based redundant (%s) bounds check assertion prop in " FMT_BB ":\n", reason, compCurBB->bbNum); @@ -5549,15 +5566,19 @@ GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, GenTree if ((add0 == vnCurLen) && vnStore->IsVNInt32Constant(add1)) { - Range rng = RangeCheck::GetRangeFromAssertions(this, vnCurLen, assertions); - // Lower known limit of ArrLen: - const int lenLowerLimit = rng.LowerLimit().GetConstant(); + Range rng = RangeCheck::GetRangeFromAssertions(this, arrBndsChkLen, assertions); - // Negative delta in the array access (ArrLen + -CNS) - const int delta = vnStore->GetConstantInt32(add1); - if ((lenLowerLimit > 0) && (delta < 0) && (delta > INT_MIN) && (lenLowerLimit >= -delta)) + if (rng.IsConstantRange()) { - return dropBoundsCheck(INDEBUG("a[a.Length-cns] when a.Length is known to be >= cns")); + // Lower known limit of ArrLen: + const int lenLowerLimit = rng.LowerLimit().GetConstant(); + + // Negative delta in the array access (ArrLen + -CNS) + const int delta = vnStore->GetConstantInt32(add1); + if ((lenLowerLimit > 0) && (delta < 0) && (delta > INT_MIN) && (lenLowerLimit >= -delta)) + { + return dropBoundsCheck(INDEBUG("a[a.Length-cns] when a.Length is known to be >= cns")); + } } } } @@ -5582,7 +5603,7 @@ GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, GenTree assert(curAssertion.GetOp2().IsVNNeverNegative()); // Do we have a previous range check involving the same 'vnLen' upper bound? - if (curAssertion.GetOp2().GetVN() == optConservativeNormalVN(arrBndsChk->GetArrayLength())) + if (curAssertion.GetOp2().GetVN() == optConservativeNormalVN(arrBndsChkLen)) { // Do we have the exact same lower bound 'vnIdx'? // a[i] followed by a[i] @@ -5593,7 +5614,7 @@ GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, GenTree // Are we using zero as the index? // It can always be considered as redundant with any previous value // a[*] followed by a[0] - else if (vnCurIdx == vnStore->VNZeroForType(arrBndsChk->GetIndex()->TypeGet())) + else if (vnCurIdx == vnStore->VNZeroForType(arrBndsChkIdx->TypeGet())) { return dropBoundsCheck(INDEBUG("a[*] followed by a[0]")); } @@ -5645,44 +5666,40 @@ GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, GenTree } // Let's see if we can remove the bounds check based on the ranges. - if ((genActualType(vnStore->TypeOfVN(vnCurIdx)) == TYP_INT) && - (genActualType(vnStore->TypeOfVN(vnCurLen)) == TYP_INT)) + Range idxRng = RangeCheck::GetRangeFromAssertions(this, arrBndsChkIdx, assertions); + Range lenRng = RangeCheck::GetRangeFromAssertions(this, arrBndsChkLen, assertions); + + if (idxRng.IsConstantRange() && lenRng.IsConstantRange()) { - Range idxRng = RangeCheck::GetRangeFromAssertions(this, vnCurIdx, assertions); - Range lenRng = RangeCheck::GetRangeFromAssertions(this, vnCurLen, assertions); - if (idxRng.IsConstantRange() && lenRng.IsConstantRange()) - { - int idxLo = idxRng.LowerLimit().GetConstant(); - int idxHi = idxRng.UpperLimit().GetConstant(); - int lenLo = lenRng.LowerLimit().GetConstant(); + int idxLo = idxRng.LowerLimit().GetConstant(); + int idxHi = idxRng.UpperLimit().GetConstant(); + int lenLo = lenRng.LowerLimit().GetConstant(); - // GT_BOUNDS_CHECK node has an implicit contract - the length node must always be non-negative. - // So we additionally tighten the lower bound of lenLo to be ">= 1" when we also have a - // "length != 0" assertion for it. - if ((idxLo == 0) && (idxHi == 0) && (lenLo <= 0)) + // GT_BOUNDS_CHECK node has an implicit contract - the length node must always be non-negative. + // So we additionally tighten the lower bound of lenLo to be ">= 1" when we also have a + // "length != 0" assertion for it. + if ((idxLo == 0) && (idxHi == 0) && (lenLo <= 0)) + { + BitVecOps::Iter iter(apTraits, assertions); + unsigned bvIndex = 0; + while (iter.NextElem(&bvIndex)) { - BitVecOps::Iter iter(apTraits, assertions); - unsigned bvIndex = 0; - while (iter.NextElem(&bvIndex)) + const AssertionDsc& assertion = optGetAssertion(GetAssertionIndex(bvIndex)); + if (assertion.IsConstantInt32Assertion() && assertion.KindIs(OAK_NOT_EQUAL) && + (assertion.GetOp1().GetVN() == vnCurLen) && (assertion.GetOp2().GetIntConstant() == 0)) { - const AssertionDsc& assertion = optGetAssertion(GetAssertionIndex(bvIndex)); - if (assertion.IsConstantInt32Assertion() && assertion.KindIs(OAK_NOT_EQUAL) && - (assertion.GetOp1().GetVN() == vnCurLen) && (assertion.GetOp2().GetIntConstant() == 0)) - { - lenLo = 1; - break; - } + lenLo = 1; + break; } } + } - // index is always within [0..lenLo) --> drop bounds check - if ((idxLo >= 0) && (idxHi < lenLo)) - { - return dropBoundsCheck(INDEBUG("upper bound of index is less than lower bound of length")); - } + // index is always within [0..lenLo) --> drop bounds check + if ((idxLo >= 0) && (idxHi < lenLo)) + { + return dropBoundsCheck(INDEBUG("upper bound of index is less than lower bound of length")); } } - return nullptr; } diff --git a/src/coreclr/jit/rangecheck.cpp b/src/coreclr/jit/rangecheck.cpp index 868d0ffd1aea52..e5deb18da0243c 100644 --- a/src/coreclr/jit/rangecheck.cpp +++ b/src/coreclr/jit/rangecheck.cpp @@ -649,15 +649,30 @@ void RangeCheck::MergeEdgeAssertions(GenTreeLclVarCommon* lcl, ASSERT_VALARG_TP // // Arguments: // comp - the compiler instance -// num - the value number to analyze range for +// tree - the tree to analyze range for // assertions - the assertions to use // budget - the remaining budget for recursive analysis // // Return Value: // The computed range // -Range RangeCheck::GetRangeFromAssertions(Compiler* comp, ValueNum num, ASSERT_VALARG_TP assertions, int budget) +Range RangeCheck::GetRangeFromAssertions(Compiler* comp, GenTree* tree, ASSERT_VALARG_TP assertions, int budget) { + var_types type = tree->TypeGet(); + + if (!varTypeIsIntegral(type)) + { + return Limit(Limit::keUnknown); + } + + ValueNum num = comp->vnStore->VNConservativeNormalValue(tree->gtVNPair); + + if (num == ValueNumStore::NoVN) + { + // Use the widest supported constant range for type + return GetRangeFromType(type); + } + ValueNumStore::SmallValueNumSet set; return GetRangeFromAssertionsWorker(comp, num, assertions, budget, &set); } @@ -680,41 +695,50 @@ Range RangeCheck::GetRangeFromAssertions(Compiler* comp, ValueNum num, ASSERT_VA Range RangeCheck::GetRangeFromAssertionsWorker( Compiler* comp, ValueNum num, ASSERT_VALARG_TP assertions, int budget, ValueNumStore::SmallValueNumSet* visited) { - // Start with the widest possible constant range. - Range result = Range(Limit(Limit::keConstant, INT32_MIN), Limit(Limit::keConstant, INT32_MAX)); + assert(num != ValueNumStore::NoVN); + + var_types vnType = comp->vnStore->TypeOfVN(num); + Range result = GetRangeFromType(vnType); - if ((num == ValueNumStore::NoVN) || (budget <= 0)) + if (budget <= 0) { return result; } - // Currently, we only handle int32 and smaller integer types. - var_types vnType = comp->vnStore->TypeOfVN(num); - if (varTypeIsGC(vnType)) { +#if TARGET_64BIT + return Limit(Limit::keUnknown); +#else // On 32-bit targets TYP_BYREF/TYP_REF and TYP_INT are all 4 bytes, so the JIT can // legally store a byref-valued expression (e.g. LCL_ADDR) into an int-typed local. // The local itself is TYP_INT, but its VN is a TYP_BYREF function like PtrToLoc. // The PhiDef visitor below recurses into reaching VNs, so a BYREF VN can show up // here even though our public callers only pass us int-typed trees. // We have no useful integer range to derive from a pointer, so just give up. - assert(TARGET_POINTER_SIZE == 4); - return result; - } - assert(genActualType(vnType) == TYP_INT); - result = GetRangeFromType(vnType); + return GetRangeFromType(TYP_INT); +#endif + } // // First, let's see if we can tighten the range based on VN information. // // If it's a constant, it's already as tight as it can get. - int cns; - if (comp->vnStore->IsVNIntegralConstant(num, &cns)) + if (comp->vnStore->IsVNConstant(num)) { - return Range(Limit(Limit::keConstant, cns)); + int cns; + if (comp->vnStore->IsVNIntegralConstant(num, &cns)) + { + return Range(Limit(Limit::keConstant, cns)); + } + else + { + // TODO: We could return `0, keUnknown` if the constant is known positive + // but this would require more handling in other places to take advantage of. + return Limit(Limit::keUnknown); + } } VNFuncApp funcApp; @@ -728,25 +752,48 @@ Range RangeCheck::GetRangeFromAssertionsWorker( bool srcIsUnsigned; comp->vnStore->GetCastOperFromVN(funcApp.GetArg(1), &castToType, &srcIsUnsigned); - // GetRangeFromType returns a non-constant range if it can't be represented with Range - Range castToTypeRange = GetRangeFromType(castToType); - if (castToTypeRange.IsConstantRange()) + ValueNum arg0VN = funcApp.GetArg(0); + var_types arg0Typ = comp->vnStore->TypeOfVN(arg0VN); + + var_types castFromType = srcIsUnsigned ? varTypeToUnsigned(arg0Typ) : arg0Typ; + + if (genTypeSize(castFromType) < genTypeSize(castToType)) + { + // We're going from a small type to a large type + // and so regardless of whether we zero or sign-extend + // the value is preserved within the confines of its + // original input for the destination, i.e. it always + // passes the FitsIn check. + + result = GetRangeFromType(castFromType); + } + else { - result = castToTypeRange; + // We're either going from a big type to a small type + // or between signed and unsigned types of the same size + // so we want to use toType as the range. - // Now see if we can do better by looking at the cast source. - // if its range is within the castTo range, we can use that (and the cast is basically a no-op). - if (genActualType(comp->vnStore->TypeOfVN(funcApp.GetArg(0))) == TYP_INT) + result = GetRangeFromType((castToType == TYP_UINT) ? TYP_INT : castToType); + } + + // Now see if we can do better by looking at the cast source. + // if its range is within the castTo range, we can use that (and the cast is basically a no-op). + Range castOpRange = GetRangeFromAssertionsWorker(comp, arg0VN, assertions, --budget, visited); + + if (castOpRange.IsConstantRange()) + { + if (!result.IsConstantRange()) { - Range castOpRange = - GetRangeFromAssertionsWorker(comp, funcApp.GetArg(0), assertions, --budget, visited); - if (castOpRange.IsConstantRange() && - (castOpRange.LowerLimit().GetConstant() >= castToTypeRange.LowerLimit().GetConstant()) && - (castOpRange.UpperLimit().GetConstant() <= castToTypeRange.UpperLimit().GetConstant())) + if (!srcIsUnsigned || (castOpRange.LowerLimit().GetConstant() >= 0)) { result = castOpRange; } } + else if ((castOpRange.LowerLimit().GetConstant() >= result.LowerLimit().GetConstant()) && + (castOpRange.UpperLimit().GetConstant() <= result.UpperLimit().GetConstant())) + { + result = castOpRange; + } } } break; @@ -793,14 +840,61 @@ Range RangeCheck::GetRangeFromAssertionsWorker( binOpResult = RangeOps::Or(r1, r2); break; case VNF_LSH: + { + if (varTypeIsLong(vnType)) + { + // We can't handle LSH for long since we don't know the state of the upper 32-bits + return Limit(Limit::keUnknown); + } + binOpResult = RangeOps::ShiftLeft(r1, r2); break; + } case VNF_RSH: + { + if (varTypeIsLong(vnType)) + { + int shiftAmount; + + if (r2.IsSingleValueConstant(&shiftAmount) && (shiftAmount >= 32) && (shiftAmount < 64)) + { + // The upper 33-bits will all match post shift, so we are within [INT32_MIN, INT32_MAX] + binOpResult = GetRangeFromType(TYP_INT); + break; + } + else + { + return Range(Limit::keUnknown); + } + } + binOpResult = RangeOps::ShiftRight(r1, r2, /*logical*/ false); break; + } case VNF_RSZ: + { + if (varTypeIsLong(vnType)) + { + int shiftAmount; + + if (r2.IsSingleValueConstant(&shiftAmount) && (shiftAmount >= 33) && (shiftAmount < 64)) + { + // The upper 33-bits must all be zero post shift, so we are within [0, INT32_MAX] + // and can further reduce based on the remaining shift amount. This is notably one + // higher than RSH since we'd otherwise get a value within [INT32_MAX + 1, UINT32_MAX] + + r1 = Range(Limit(Limit::keConstant, 0), Limit(Limit::keConstant, INT32_MAX)); + r2 = Range(Limit(Limit::keConstant, shiftAmount - 33)); + } + else + { + return Range(Limit::keUnknown); + } + } + binOpResult = RangeOps::ShiftRight(r1, r2, /*logical*/ true); break; + } case VNF_UMOD: binOpResult = RangeOps::UnsignedMod(r1, r2); break; @@ -836,12 +930,11 @@ Range RangeCheck::GetRangeFromAssertionsWorker( // But maybe we can do better and determine if they are always true or always false, // hence, return [1..1] or [0..0] - if ((genActualType(comp->vnStore->TypeOfVN(funcApp.GetArg(0))) == TYP_INT) && - (genActualType(comp->vnStore->TypeOfVN(funcApp.GetArg(1))) == TYP_INT)) - { - Range r1 = GetRangeFromAssertionsWorker(comp, funcApp.GetArg(0), assertions, --budget, visited); - Range r2 = GetRangeFromAssertionsWorker(comp, funcApp.GetArg(1), assertions, --budget, visited); + Range r1 = GetRangeFromAssertionsWorker(comp, funcApp.GetArg(0), assertions, --budget, visited); + Range r2 = GetRangeFromAssertionsWorker(comp, funcApp.GetArg(1), assertions, --budget, visited); + if (r1.IsConstantRange() && r2.IsConstantRange()) + { bool isUnsigned = true; genTreeOps cmpOper; @@ -864,7 +957,7 @@ Range RangeCheck::GetRangeFromAssertionsWorker( // Example: "(uint)(length - 4) > (uint)length" folds to false when // length >= 4 (the typical Slice(length - cns) bounds check). - if (!result.IsSingleValueConstant()) + if (!result.IsSingleValueConstant() && (genActualType(vnType) == TYP_INT)) { ValueNum op1VN = funcApp.GetArg(0); ValueNum op2VN = funcApp.GetArg(1); @@ -927,6 +1020,11 @@ Range RangeCheck::GetRangeFromAssertionsWorker( result.lLimit = Limit(Limit::keConstant, 0); result.uLimit = Limit(Limit::keConstant, (1 << elementCount) - 1); } + else + { + // TODO: We could return `0, keUnknown` for `elementCount == 32` if the result is TYP_LONG + // but this would require more handling in other places to take advantage of. + } break; } @@ -1023,17 +1121,25 @@ Range RangeCheck::GetRangeFromAssertionsWorker( } } - // If it was evaluated to a single constant value by now, return it. - // We can't do better anyway. if (result.IsSingleValueConstant()) { + // If it was evaluated to a single constant value by now, return it, we can't do better anyway. return result; } Range phiRange = Range(Limit(Limit::keUndef)); - auto visitor = [comp, &phiRange, &budget, visited](ValueNum reachingVN, ASSERT_TP reachingAssertions) { + auto visitor = [comp, vnType, &phiRange, &budget, visited](ValueNum reachingVN, ASSERT_TP reachingAssertions) { // call GetRangeFromAssertionsWorker for each reaching VN using reachingAssertions - Range edgeRange = GetRangeFromAssertionsWorker(comp, reachingVN, reachingAssertions, --budget, visited); + Range edgeRange; + + if (reachingVN != ValueNumStore::NoVN) + { + edgeRange = GetRangeFromAssertionsWorker(comp, reachingVN, reachingAssertions, --budget, visited); + } + else + { + edgeRange = GetRangeFromType(vnType); + } // If phiRange is not yet set, set it to the first edgeRange // else merge it with the new edgeRange. Example: [10..100] U [50..150] = [10..150] @@ -1063,8 +1169,7 @@ Range RangeCheck::GetRangeFromAssertionsWorker( MergeEdgeAssertionsWorker(comp, num, ValueNumStore::NoVN, assertions, &result, /* canUseCheckedBounds */ false, edgeAssertionsBudget, visited); - assert(result.IsConstantRange()); - return result; + return result.IsConstantRange() ? result : Limit(Limit::keUnknown); } //------------------------------------------------------------------------ @@ -1787,6 +1892,7 @@ void RangeCheck::MergeAssertion(BasicBlock* block, GenTree* op, Range* pRange DE // Compute the range for a binary operation. Range RangeCheck::ComputeRangeForBinOp(BasicBlock* block, GenTreeOp* binop, bool monIncreasing DEBUGARG(int indent)) { + assert(genActualType(binop) == TYP_INT); assert(binop->OperIs(GT_ADD, GT_OR, GT_XOR, GT_AND, GT_RSH, GT_RSZ, GT_LSH, GT_UMOD, GT_MUL)); // For XOR we only care about Log2 pattern for now @@ -2278,11 +2384,10 @@ Range RangeCheck::ComputeRange(BasicBlock* block, GenTree* expr, bool monIncreas range = Range(Limit(Limit::keUnknown)); JITDUMP("GetRangeWorker not tractable within max stack depth.\n"); } - // TYP_LONG is not supported anyway. - else if (expr->TypeIs(TYP_LONG)) + else if (genActualType(expr) != TYP_INT) { - range = Range(Limit(Limit::keUnknown)); - JITDUMP("GetRangeWorker long, setting to unknown value.\n"); + // TYP_LONG and other expressions are only supported for non dependent/symbolic ranges + range = GetRangeFromAssertions(m_compiler, expr, block->bbAssertionIn); } // If VN is constant return range as constant. else if (m_compiler->vnStore->IsVNConstant(vn)) @@ -2360,15 +2465,10 @@ Range RangeCheck::ComputeRange(BasicBlock* block, GenTree* expr, bool monIncreas range = Range(Limit(Limit::keConstant, 0), Limit(Limit::keConstant, CORINFO_Array_MaxLength)); } } - else if (genActualType(expr) == TYP_INT) - { - // Use GetRangeFromAssertions for everything else since they won't produce dependent or symbolic ranges - range = GetRangeFromAssertions(m_compiler, vn, block->bbAssertionIn); - } else { - // The expression is not recognized, so the result is unknown. - range = Range(Limit(Limit::keUnknown)); + // Use GetRangeFromAssertions for everything else since they won't produce dependent or symbolic ranges + range = GetRangeFromAssertions(m_compiler, expr, block->bbAssertionIn); } GetRangeMap()->Set(expr, new (m_alloc) Range(range), RangeMap::Overwrite); diff --git a/src/coreclr/jit/rangecheck.h b/src/coreclr/jit/rangecheck.h index c9f189deeb73de..065c248157f3b1 100644 --- a/src/coreclr/jit/rangecheck.h +++ b/src/coreclr/jit/rangecheck.h @@ -776,7 +776,7 @@ class RangeCheck bool TryGetRange(BasicBlock* block, GenTree* expr, Range* pRange); // Cheaper version of TryGetRange that is based only on incoming assertions. - static Range GetRangeFromAssertions(Compiler* comp, ValueNum num, ASSERT_VALARG_TP assertions, int budget = 10); + static Range GetRangeFromAssertions(Compiler* comp, GenTree* tree, ASSERT_VALARG_TP assertions, int budget = 10); // Compute the range from the given type static Range GetRangeFromType(var_types type);