From b105749c3bac78c0c2934f32061e448a35287121 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 27 May 2026 20:14:15 -0700 Subject: [PATCH 1/6] Have RangeCheck::ComputeRange handle some TYP_LONG scenarios --- src/coreclr/jit/rangecheck.cpp | 190 +++++++++++++++++++++++++++++++-- src/coreclr/jit/utils.h | 16 +++ 2 files changed, 196 insertions(+), 10 deletions(-) diff --git a/src/coreclr/jit/rangecheck.cpp b/src/coreclr/jit/rangecheck.cpp index 7d641b5688b62d..2f92de26638d2c 100644 --- a/src/coreclr/jit/rangecheck.cpp +++ b/src/coreclr/jit/rangecheck.cpp @@ -1759,31 +1759,100 @@ Range RangeCheck::ComputeRangeForBinOp(BasicBlock* block, GenTreeOp* binop, bool switch (binop->OperGet()) { case GT_ADD: + { r = RangeOps::Add(op1Range, op2Range); break; + } + case GT_MUL: + { r = RangeOps::Multiply(op1Range, op2Range); break; + } + case GT_RSH: + { + if (varTypeIsLong(binop)) + { + int shiftAmount; + + if (op2Range.IsSingleValueConstant(&shiftAmount) && (shiftAmount >= 32) && (shiftAmount < 63)) + { + // The upper 33-bits of the input will all match, so we are within [INT32_MIN, INT32_MAX] + // and can further reduce based on the remaining shift amount. + + op1Range = GetRangeFromType(TYP_INT); + op2Range = Range(Limit(Limit::keConstant, shiftAmount - 32)); + } + else + { + return Range(Limit::keUnknown); + } + } + r = RangeOps::ShiftRight(op1Range, op2Range, /*logical*/ false); break; + } + case GT_RSZ: + { + if (varTypeIsLong(binop)) + { + int shiftAmount; + + if (op2Range.IsSingleValueConstant(&shiftAmount) && (shiftAmount >= 33) && (shiftAmount < 63)) + { + // The upper 33-bits of the input must all be zero, 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] + + op1Range = Range(Limit(Limit::keConstant, 0), Limit(Limit::keConstant, INT32_MAX)); + op2Range = Range(Limit(Limit::keConstant, shiftAmount - 33)); + } + else + { + return Range(Limit::keUnknown); + } + } + r = RangeOps::ShiftRight(op1Range, op2Range, /*logical*/ true); break; + } + case GT_LSH: + { + if (varTypeIsLong(binop)) + { + // We can't handle LSH for long since we don't know the state of the upper 32-bits + return Range(Limit::keUnknown); + } + r = RangeOps::ShiftLeft(op1Range, op2Range); break; + } + case GT_AND: + { r = RangeOps::And(op1Range, op2Range); break; + } + case GT_OR: + { r = RangeOps::Or(op1Range, op2Range); break; + } + case GT_UMOD: + { r = RangeOps::UnsignedMod(op1Range, op2Range); break; + } + default: + { return Range(Limit::keUnknown); + } } JITDUMP("BinOp %s %s %s = %s\n", op1Range.ToString(m_compiler), GenTree::OpName(binop->OperGet()), @@ -2189,18 +2258,33 @@ 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)) - { - range = Range(Limit(Limit::keUnknown)); - JITDUMP("GetRangeWorker long, setting to unknown value.\n"); - } // If VN is constant return range as constant. else if (m_compiler->vnStore->IsVNConstant(vn)) { - range = (m_compiler->vnStore->TypeOfVN(vn) == TYP_INT) - ? Range(Limit(Limit::keConstant, m_compiler->vnStore->ConstantValue(vn))) - : Limit(Limit::keUnknown); + var_types vnType = m_compiler->vnStore->TypeOfVN(vn); + bool handled = false; + + if (vnType == TYP_INT) + { + range = Range(Limit(Limit::keConstant, m_compiler->vnStore->GetConstantInt32(vn))); + handled = true; + } + else if (vnType == TYP_LONG) + { + int64_t cns = m_compiler->vnStore->GetConstantInt64(vn); + + if (FitsIn(cns)) + { + range = Range(Limit(Limit::keConstant, static_cast(cns))); + handled = true; + } + } + + if (!handled) + { + range = Limit(Limit::keUnknown); + JITDUMP("GetRangeWorker unsupported VN constant, setting to unknown value.\n"); + } } // If local, find the definition from the def map and evaluate the range for rhs. else if (expr->IsLocal()) @@ -2253,8 +2337,34 @@ Range RangeCheck::ComputeRange(BasicBlock* block, GenTree* expr, bool monIncreas } else if (expr->OperIs(GT_CAST)) { + GenTreeCast* cast = expr->AsCast(); + + var_types toType = cast->CastToType(); + var_types fromType = cast->CastFromType(); + + var_types rangeType; + + if (genTypeSize(fromType) < genTypeSize(toType)) + { + // 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. + + rangeType = fromType; + } + else + { + // 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. + + rangeType = toType; + } + // TODO: consider computing range for CastOp and intersect it with this. - range = GetRangeFromType(expr->AsCast()->CastToType()); + range = GetRangeFromType(rangeType); } else if (expr->OperIs(GT_ARR_LENGTH)) { @@ -2271,6 +2381,66 @@ Range RangeCheck::ComputeRange(BasicBlock* block, GenTree* expr, bool monIncreas range = Range(Limit(Limit::keConstant, 0), Limit(Limit::keConstant, CORINFO_Array_MaxLength)); } } + else if (expr->OperIs(GT_INTRINSIC)) + { + GenTreeIntrinsic* intrinsic = expr->AsIntrinsic(); + NamedIntrinsic ni = intrinsic->gtIntrinsicName; + + switch (ni) + { + case NI_PRIMITIVE_LeadingZeroCount: + case NI_PRIMITIVE_TrailingZeroCount: + case NI_PRIMITIVE_PopCount: + { + var_types op1Type = intrinsic->gtGetOp1()->TypeGet(); + + range.lLimit = Limit(Limit::keConstant, 0); + range.uLimit = Limit(Limit::keConstant, varTypeIsLong(op1Type) ? 64 : 32); + break; + } + + default: + { + range = Range(Limit(Limit::keUnknown)); + break; + } + } + } +#if defined(FEATURE_HW_INTRINSICS) + else if (expr->OperIs(GT_HWINTRINSIC)) + { + GenTreeHWIntrinsic* hwintrinsic = expr->AsHWIntrinsic(); + NamedIntrinsic ni = hwintrinsic->GetHWIntrinsicId(); + + switch (ni) + { +#if defined(TARGET_XARCH) + case NI_AVX2_LeadingZeroCount: + case NI_AVX2_TrailingZeroCount: + case NI_AVX2_X64_LeadingZeroCount: + case NI_AVX2_X64_TrailingZeroCount: + case NI_X86Base_PopCount: + case NI_X86Base_X64_PopCount: +#elif defined(TARGET_ARM64) + case NI_ArmBase_LeadingZeroCount: + case NI_ArmBase_Arm64_LeadingZeroCount: +#endif + { + var_types op1Type = hwintrinsic->Op(1)->TypeGet(); + + range.lLimit = Limit(Limit::keConstant, 0); + range.uLimit = Limit(Limit::keConstant, varTypeIsLong(op1Type) ? 64 : 32); + break; + } + + default: + { + range = Range(Limit(Limit::keUnknown)); + break; + } + } + } +#endif else { // The expression is not recognized, so the result is unknown. diff --git a/src/coreclr/jit/utils.h b/src/coreclr/jit/utils.h index 06b72fc33cd71e..6268d48958d9a0 100644 --- a/src/coreclr/jit/utils.h +++ b/src/coreclr/jit/utils.h @@ -1022,6 +1022,22 @@ class BitOperations static uint64_t RotateRight(uint64_t value, uint32_t offset); + static uint32_t RoundUpToPowerOf2(uint32_t value) + { + return static_cast(0x1'0000'0000ULL >> LeadingZeroCount(value - 1)); + } + + static uint64_t RoundUpToPowerOf2(uint64_t value) + { + if ((value == 0) || (value > 0x8000'0000'0000'0000ULL)) + { + return 0; + } + + uint64_t shift = 64 - LeadingZeroCount(value - 1); + return (1ULL ^ (shift >> 6)) << shift; + } + static uint32_t SingleToUInt32Bits(float value); static uint32_t TrailingZeroCount(uint32_t value); From d59545ad398edce554b6350b1ec1086bb68900df Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 28 May 2026 09:42:29 -0700 Subject: [PATCH 2/6] Apply formatting patch --- src/coreclr/jit/utils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/utils.h b/src/coreclr/jit/utils.h index 6268d48958d9a0..9c6584e2c4caeb 100644 --- a/src/coreclr/jit/utils.h +++ b/src/coreclr/jit/utils.h @@ -1031,7 +1031,7 @@ class BitOperations { if ((value == 0) || (value > 0x8000'0000'0000'0000ULL)) { - return 0; + return 0; } uint64_t shift = 64 - LeadingZeroCount(value - 1); From 2d548e6f66a34c51cf91e1de9a744213920c189e Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 28 May 2026 09:53:30 -0700 Subject: [PATCH 3/6] Handle the cast fromType not being TYP_UINT and allowing a shift amount of 63 --- src/coreclr/jit/rangecheck.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/rangecheck.cpp b/src/coreclr/jit/rangecheck.cpp index 2f92de26638d2c..dff2a9d2c88d50 100644 --- a/src/coreclr/jit/rangecheck.cpp +++ b/src/coreclr/jit/rangecheck.cpp @@ -1776,7 +1776,7 @@ Range RangeCheck::ComputeRangeForBinOp(BasicBlock* block, GenTreeOp* binop, bool { int shiftAmount; - if (op2Range.IsSingleValueConstant(&shiftAmount) && (shiftAmount >= 32) && (shiftAmount < 63)) + if (op2Range.IsSingleValueConstant(&shiftAmount) && (shiftAmount >= 32) && (shiftAmount < 64)) { // The upper 33-bits of the input will all match, so we are within [INT32_MIN, INT32_MAX] // and can further reduce based on the remaining shift amount. @@ -1800,7 +1800,7 @@ Range RangeCheck::ComputeRangeForBinOp(BasicBlock* block, GenTreeOp* binop, bool { int shiftAmount; - if (op2Range.IsSingleValueConstant(&shiftAmount) && (shiftAmount >= 33) && (shiftAmount < 63)) + if (op2Range.IsSingleValueConstant(&shiftAmount) && (shiftAmount >= 33) && (shiftAmount < 64)) { // The upper 33-bits of the input must all be zero, so we are within [0, INT32_MAX] // and can further reduce based on the remaining shift amount. This is notably one @@ -2342,6 +2342,11 @@ Range RangeCheck::ComputeRange(BasicBlock* block, GenTree* expr, bool monIncreas var_types toType = cast->CastToType(); var_types fromType = cast->CastFromType(); + if (cast->IsUnsigned()) + { + fromType = varTypeToUnsigned(fromType); + } + var_types rangeType; if (genTypeSize(fromType) < genTypeSize(toType)) From ff25dae9ea4735d9ab445230367254541c2fb59a Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 28 May 2026 14:05:58 -0700 Subject: [PATCH 4/6] Improve wording on two comments --- src/coreclr/jit/rangecheck.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/rangecheck.cpp b/src/coreclr/jit/rangecheck.cpp index dff2a9d2c88d50..ccfea0d7ba3c2d 100644 --- a/src/coreclr/jit/rangecheck.cpp +++ b/src/coreclr/jit/rangecheck.cpp @@ -1778,7 +1778,7 @@ Range RangeCheck::ComputeRangeForBinOp(BasicBlock* block, GenTreeOp* binop, bool if (op2Range.IsSingleValueConstant(&shiftAmount) && (shiftAmount >= 32) && (shiftAmount < 64)) { - // The upper 33-bits of the input will all match, so we are within [INT32_MIN, INT32_MAX] + // The upper 33-bits will all match post shift, so we are within [INT32_MIN, INT32_MAX] // and can further reduce based on the remaining shift amount. op1Range = GetRangeFromType(TYP_INT); @@ -1802,7 +1802,7 @@ Range RangeCheck::ComputeRangeForBinOp(BasicBlock* block, GenTreeOp* binop, bool if (op2Range.IsSingleValueConstant(&shiftAmount) && (shiftAmount >= 33) && (shiftAmount < 64)) { - // The upper 33-bits of the input must all be zero, so we are within [0, INT32_MAX] + // The upper 33-bits of the 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] From 968d4232d7d9a5a89163a1a0608fd43d3ea2d2c4 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Fri, 29 May 2026 06:07:16 -0700 Subject: [PATCH 5/6] use `IsVNIntegralConstant` --- src/coreclr/jit/rangecheck.cpp | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/src/coreclr/jit/rangecheck.cpp b/src/coreclr/jit/rangecheck.cpp index ccfea0d7ba3c2d..0e13a5b525adc0 100644 --- a/src/coreclr/jit/rangecheck.cpp +++ b/src/coreclr/jit/rangecheck.cpp @@ -2261,27 +2261,14 @@ Range RangeCheck::ComputeRange(BasicBlock* block, GenTree* expr, bool monIncreas // If VN is constant return range as constant. else if (m_compiler->vnStore->IsVNConstant(vn)) { - var_types vnType = m_compiler->vnStore->TypeOfVN(vn); - bool handled = false; - - if (vnType == TYP_INT) - { - range = Range(Limit(Limit::keConstant, m_compiler->vnStore->GetConstantInt32(vn))); - handled = true; - } - else if (vnType == TYP_LONG) + int32_t cns; + if (IsVNIntegralConstant(vn, &cns)) { - int64_t cns = m_compiler->vnStore->GetConstantInt64(vn); - - if (FitsIn(cns)) - { - range = Range(Limit(Limit::keConstant, static_cast(cns))); - handled = true; - } + range = Range(Limit(Limit::keConstant, static_cast(cns))); } - - if (!handled) + else { + // TOOD: We may want to also check for `uint32_t` since that allows knowing a TYP_LONG is never negative range = Limit(Limit::keUnknown); JITDUMP("GetRangeWorker unsupported VN constant, setting to unknown value.\n"); } From 3a9508d42e8c4ed71bc2aceacff79432092126cb Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Fri, 29 May 2026 06:32:11 -0700 Subject: [PATCH 6/6] Fix a typo, comment, and build failure --- src/coreclr/jit/rangecheck.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/coreclr/jit/rangecheck.cpp b/src/coreclr/jit/rangecheck.cpp index 0e13a5b525adc0..003acd9adc6377 100644 --- a/src/coreclr/jit/rangecheck.cpp +++ b/src/coreclr/jit/rangecheck.cpp @@ -1778,7 +1778,7 @@ Range RangeCheck::ComputeRangeForBinOp(BasicBlock* block, GenTreeOp* binop, bool if (op2Range.IsSingleValueConstant(&shiftAmount) && (shiftAmount >= 32) && (shiftAmount < 64)) { - // The upper 33-bits will all match post shift, so we are within [INT32_MIN, INT32_MAX] + // The upper 33-bits will all match post shift, so we are within [INT32_MIN, INT32_MAX] // and can further reduce based on the remaining shift amount. op1Range = GetRangeFromType(TYP_INT); @@ -1802,7 +1802,7 @@ Range RangeCheck::ComputeRangeForBinOp(BasicBlock* block, GenTreeOp* binop, bool if (op2Range.IsSingleValueConstant(&shiftAmount) && (shiftAmount >= 33) && (shiftAmount < 64)) { - // The upper 33-bits of the must all be zero post shift, so we are within [0, INT32_MAX] + // 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] @@ -2262,13 +2262,13 @@ Range RangeCheck::ComputeRange(BasicBlock* block, GenTree* expr, bool monIncreas else if (m_compiler->vnStore->IsVNConstant(vn)) { int32_t cns; - if (IsVNIntegralConstant(vn, &cns)) + if (m_compiler->vnStore->IsVNIntegralConstant(vn, &cns)) { range = Range(Limit(Limit::keConstant, static_cast(cns))); } else { - // TOOD: We may want to also check for `uint32_t` since that allows knowing a TYP_LONG is never negative + // TODO: We may want to also check for `uint32_t` since that allows knowing a TYP_LONG is never negative range = Limit(Limit::keUnknown); JITDUMP("GetRangeWorker unsupported VN constant, setting to unknown value.\n"); }