From 90507241b3f26993a900e42012dbe01d9148e3a3 Mon Sep 17 00:00:00 2001 From: Henrique Ruschel Date: Thu, 2 Jul 2026 21:49:39 -0300 Subject: [PATCH 1/7] Eliminate bound checks --- src/coreclr/jit/assertionprop.cpp | 43 +++++++++++++-- src/coreclr/jit/rangecheck.cpp | 4 ++ src/coreclr/jit/rangecheck.h | 64 ++++++++++++++++++++++ src/tests/JIT/opt/RangeChecks/ModLength.cs | 43 ++++++++++++++- 4 files changed, 149 insertions(+), 5 deletions(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index ca8688793d0f6e..996e5c1e2bdd24 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -5668,11 +5668,46 @@ GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, } } } - else if (vnStore->IsVNBinFunc(vnCurIdx, VNF_UMOD, &idxOp0, &idxOp1) && (idxOp1 == vnCurLen)) + else if (vnStore->IsVNBinFunc(vnCurIdx, VNF_UMOD, &idxOp0, &idxOp1)) { - // If arr.Length is 0 we technically should keep the bounds check, but since the expression - // has to throw DivideByZeroException anyway - no special handling needed. - return dropBoundsCheck(INDEBUG("a[X u% a.Length] is always within bounds")); + if (idxOp0 == vnCurLen) + { + Range lenRng = GetRange(this, arrBndsChkLen, block, assertions, /*fast*/ true); + + if (lenRng.LowerLimit().IsConstant() && lenRng.LowerLimit().GetConstant() > 0) + { + return dropBoundsCheck( + INDEBUG("a[a.Length u% X] is always within bounds when a.Length is known to be > 0")); + } + } + else if (idxOp1 == vnCurLen) + { + // If arr.Length is 0 we technically should keep the bounds check, but since the expression + // has to throw DivideByZeroException anyway - no special handling needed. + return dropBoundsCheck(INDEBUG("a[X u% a.Length] is always within bounds")); + } + } + else if (vnStore->IsVNBinFunc(vnCurIdx, VNF_MOD, &idxOp0, &idxOp1)) + { + if (idxOp0 == vnCurLen) + { + Range lenRng = GetRange(this, arrBndsChkLen, block, assertions, /*fast*/ true); + + if (lenRng.LowerLimit().IsConstant() && lenRng.LowerLimit().GetConstant() > 0) + { + return dropBoundsCheck( + INDEBUG("a[a.Length % X] is always within bounds when a.Length is known to be > 0")); + } + } + else if (idxOp1 == vnCurLen) + { + Range idxRng = GetRange(this, arrBndsChkIdx, block, assertions, /*fast*/ true); + + if (idxRng.LowerLimit().IsConstant() && idxRng.LowerLimit().GetConstant() >= 0) + { + return dropBoundsCheck(INDEBUG("a[X % a.Length] is always within bounds when X is known to be >= 0")); + } + } } // Let's see if we can remove the bounds check based on the ranges. diff --git a/src/coreclr/jit/rangecheck.cpp b/src/coreclr/jit/rangecheck.cpp index 4fd6e4a96b65ad..f56276b5f056a1 100644 --- a/src/coreclr/jit/rangecheck.cpp +++ b/src/coreclr/jit/rangecheck.cpp @@ -711,6 +711,7 @@ Range RangeCheck::GetRangeFromAssertionsWorker( case VNF_RSH: case VNF_RSZ: case VNF_UMOD: + case VNF_MOD: case VNF_UDIV: { // Get ranges of both operands and perform the same operation on the ranges. @@ -793,6 +794,9 @@ Range RangeCheck::GetRangeFromAssertionsWorker( case VNF_UMOD: binOpResult = RangeOps::UnsignedMod(r1, r2); break; + case VNF_MOD: + binOpResult = RangeOps::Mod(r1, r2); + break; case VNF_UDIV: binOpResult = RangeOps::UnsignedDivide(r1, r2); break; diff --git a/src/coreclr/jit/rangecheck.h b/src/coreclr/jit/rangecheck.h index 9d8d75301340d0..093f627aa1c66f 100644 --- a/src/coreclr/jit/rangecheck.h +++ b/src/coreclr/jit/rangecheck.h @@ -521,6 +521,70 @@ struct RangeOps return Range(Limit(Limit::keUnknown)); } + static Range Mod(const Range& r1, const Range& r2) + { + if (r1.LowerLimit().IsConstant()) + { + int r2ConstVal; + + if (r2.IsSingleValueConstant(&r2ConstVal)) + { + if (r2ConstVal == 0) + { + return Range(Limit(Limit::keUnknown)); + } + + int r1ConstVal; + + if (r1.IsSingleValueConstant(&r1ConstVal)) + { + return Range(Limit(Limit::keConstant, r1ConstVal % r2ConstVal)); + } + } + + const auto SafeAbsMinusOne = [](const int value) { + return value == INT32_MIN ? INT32_MAX : (abs(value) - 1); + }; + + const int r1lo = r1.LowerLimit().GetConstant(); + + if (r1lo >= 0) + { + if (r2.IsConstantRange()) + { + const int r2lo = r2.LowerLimit().GetConstant(); + const int r2hi = r2.UpperLimit().GetConstant(); + // x % [-4..2] -> [0..3] + const int maxAbsRight = max(SafeAbsMinusOne(r2lo), SafeAbsMinusOne(r2hi)); + const int rightLimit = + r1.UpperLimit().IsConstant() ? min(r1.UpperLimit().GetConstant(), maxAbsRight) : maxAbsRight; + + return Range(Limit(Limit::keConstant, 0), Limit(Limit::keConstant, rightLimit)); + } + + return Range(Limit(Limit::keConstant, 0), Limit(Limit::keUnknown)); + } + else + { + if (r2.IsConstantRange()) + { + const int r2lo = r2.LowerLimit().GetConstant(); + const int r2hi = r2.UpperLimit().GetConstant(); + // x % [-4..2] -> [0..3] + const int maxAbsRight = max(SafeAbsMinusOne(r2lo), SafeAbsMinusOne(r2hi)); + + const int leftLimit = max(r1lo, -maxAbsRight); + + return Range(Limit(Limit::keConstant, leftLimit), Limit(Limit::keConstant, -0)); + } + + return Range(Limit(Limit::keUnknown), Limit(Limit::keConstant, -0)); + } + } + + return Range(Limit(Limit::keUnknown)); + } + static Range UnsignedDivide(const Range& r1, const Range& r2) { // We only handle constant ranges for both operands. diff --git a/src/tests/JIT/opt/RangeChecks/ModLength.cs b/src/tests/JIT/opt/RangeChecks/ModLength.cs index fc9737b99e17c0..c38b1cb10b78c8 100644 --- a/src/tests/JIT/opt/RangeChecks/ModLength.cs +++ b/src/tests/JIT/opt/RangeChecks/ModLength.cs @@ -36,6 +36,14 @@ public static void TestEntryPoint() Test6(new int[10], 0); Test7(new int[10], 0); Throws(() => Test8(new int[10], 0)); + Test10(new int[10], 100); + Test11(new int[10], 0); + Test11(new int[10], 100); + Throws(() => Test11(new int[0], 10)); + Throws(() => Test12(new int[0], 10)); + Test12(new int[10], 0); + Test12(new int[10], 100); + Throws(() => Test12(new int[10], 0)); } static void Throws(Action action, [CallerLineNumber] int line = 0) @@ -80,7 +88,7 @@ static int Test6(int[] arr, int index) var span = arr.AsSpan(); return span[(int)index % (int)span.Length]; } - + [MethodImpl(MethodImplOptions.NoInlining)] static int Test7(int[] arr, int index) { @@ -97,4 +105,37 @@ static int Test8(int[] arr, int index) [MethodImpl(MethodImplOptions.NoInlining)] static int Test9(int[] arr, int index) => arr[index / arr.Length]; + + [MethodImpl(MethodImplOptions.NoInlining)] + static int Test10(int[] arr, uint index) + { + if (arr.Length > 0) + { + return arr[arr.Length % index]; + } + + return 1234; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int Test11(int[] arr, int index) + { + if (index >= 0) + { + return arr[index % arr.Length]; + } + + return 1234; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int Test12(int[] arr, int index) + { + if (arr.Length > 0) + { + return arr[arr.Length % index]; + } + + return 1234; + } } From 6d993287060768d0c0f1572fd548bc4221d4f8b0 Mon Sep 17 00:00:00 2001 From: Henrique Ruschel Date: Fri, 3 Jul 2026 19:06:38 -0300 Subject: [PATCH 2/7] fix length % index variants --- src/coreclr/jit/assertionprop.cpp | 82 +++++++++++++++- src/tests/JIT/opt/RangeChecks/ModLength.cs | 108 +++++++++++++++++++-- 2 files changed, 177 insertions(+), 13 deletions(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index c0a8b666efedaa..416bb8c21b2824 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -5661,8 +5661,45 @@ GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, if (lenRng.LowerLimit().IsConstant() && lenRng.LowerLimit().GetConstant() > 0) { - return dropBoundsCheck( - INDEBUG("a[a.Length u% X] is always within bounds when a.Length is known to be > 0")); + BitVecOps::Iter iter(apTraits, assertions); + unsigned index = 0; + while (iter.NextElem(&index)) + { + AssertionIndex assertionIndex = GetAssertionIndex(index); + + const Compiler::AssertionDsc& curAssertion = optGetAssertion(assertionIndex); + + if (curAssertion.GetOp1().GetVN() == vnCurLen && curAssertion.GetOp2().GetVN() == idxOp1) + { + if (curAssertion.KindIs(OAK_GT)) + { + // when length > index, length % index has the range [0..length - 1] + return dropBoundsCheck(INDEBUG( + "a[a.Length u% X] is always within bounds when a.Length is known to be > 0 and a.Length > X")); + } + else if(curAssertion.KindIs(OAK_GE)) + { + // when length >= index, length % index has the range [0..length - 1] + return dropBoundsCheck(INDEBUG( + "a[a.Length u% X] is always within bounds when a.Length is known to be > 0 and a.Length >= X")); + } + } + else if (curAssertion.GetOp1().GetVN() == idxOp1 && curAssertion.GetOp2().GetVN() == vnCurLen) + { + if (curAssertion.KindIs(OAK_LT)) + { + // when index < length, length % index has the range [0..length - 1] + return dropBoundsCheck(INDEBUG( + "a[a.Length u% X] is always within bounds when a.Length is known to be > 0 and X < a.Length")); + } + else if (curAssertion.KindIs(OAK_LE)) + { + // when index <= length, length % index has the range [0..length - 1] + return dropBoundsCheck(INDEBUG( + "a[a.Length u% X] is always within bounds when a.Length is known to be > 0 and X <= a.Length")); + } + } + } } } else if (idxOp1 == vnCurLen) @@ -5680,8 +5717,45 @@ GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, if (lenRng.LowerLimit().IsConstant() && lenRng.LowerLimit().GetConstant() > 0) { - return dropBoundsCheck( - INDEBUG("a[a.Length % X] is always within bounds when a.Length is known to be > 0")); + BitVecOps::Iter iter(apTraits, assertions); + unsigned index = 0; + while (iter.NextElem(&index)) + { + AssertionIndex assertionIndex = GetAssertionIndex(index); + + const Compiler::AssertionDsc& curAssertion = optGetAssertion(assertionIndex); + + if (curAssertion.GetOp1().GetVN() == vnCurLen && curAssertion.GetOp2().GetVN() == idxOp1) + { + if (curAssertion.KindIs(OAK_GT)) + { + // when length > index, length % index has the range [0..length - 1] + return dropBoundsCheck( + INDEBUG("a[a.Length % X] is always within bounds when a.Length is known to be > 0 and a.Length > X")); + } + else if(curAssertion.KindIs(OAK_GE)) + { + // when length >= index, length % index has the range [0..length - 1] + return dropBoundsCheck( + INDEBUG("a[a.Length % X] is always within bounds when a.Length is known to be > 0 and a.Length >= X")); + } + } + else if (curAssertion.GetOp1().GetVN() == idxOp1 && curAssertion.GetOp2().GetVN() == vnCurLen) + { + if (curAssertion.KindIs(OAK_LT)) + { + // when index < length, length % index has the range [0..length - 1] + return dropBoundsCheck(INDEBUG( + "a[a.Length % X] is always within bounds when a.Length is known to be > 0 and X < a.Length")); + } + else if (curAssertion.KindIs(OAK_LE)) + { + // when index <= length, length % index has the range [0..length - 1] + return dropBoundsCheck(INDEBUG( + "a[a.Length % X] is always within bounds when a.Length is known to be > 0 and X <= a.Length")); + } + } + } } } else if (idxOp1 == vnCurLen) diff --git a/src/tests/JIT/opt/RangeChecks/ModLength.cs b/src/tests/JIT/opt/RangeChecks/ModLength.cs index c38b1cb10b78c8..09c0bbfe10d565 100644 --- a/src/tests/JIT/opt/RangeChecks/ModLength.cs +++ b/src/tests/JIT/opt/RangeChecks/ModLength.cs @@ -7,7 +7,7 @@ public class ModLength { - [Fact] + [Fact] public static void TestEntryPoint() { Throws(() => Test1(new int[0], 0)); @@ -36,14 +36,38 @@ public static void TestEntryPoint() Test6(new int[10], 0); Test7(new int[10], 0); Throws(() => Test8(new int[10], 0)); - Test10(new int[10], 100); + + Test10(new int[10], 9U); + Throws(() => Test10(new int[10], 0U)); + Test11(new int[10], 0); Test11(new int[10], 100); Throws(() => Test11(new int[0], 10)); - Throws(() => Test12(new int[0], 10)); - Test12(new int[10], 0); - Test12(new int[10], 100); + + Test12(new int[10], 9); Throws(() => Test12(new int[10], 0)); + + Test13(new int[10], 9U); + Test13(new int[10], 10U); + Throws(() => Test13(new int[10], 0U)); + + Test14(new int[10], 9); + Test14(new int[10], 10); + Throws(() => Test14(new int[10], 0)); + + Test15(new int[10], 9U); + Throws(() => Test15(new int[10], 0U)); + + Test16(new int[10], 9); + Throws(() => Test16(new int[10], 0)); + + Test17(new int[10], 9U); + Test17(new int[10], 10U); + Throws(() => Test17(new int[10], 0U)); + + Test18(new int[10], 9); + Test18(new int[10], 10); + Throws(() => Test18(new int[10], 0)); } static void Throws(Action action, [CallerLineNumber] int line = 0) @@ -88,7 +112,7 @@ static int Test6(int[] arr, int index) var span = arr.AsSpan(); return span[(int)index % (int)span.Length]; } - + [MethodImpl(MethodImplOptions.NoInlining)] static int Test7(int[] arr, int index) { @@ -109,9 +133,9 @@ static int Test8(int[] arr, int index) [MethodImpl(MethodImplOptions.NoInlining)] static int Test10(int[] arr, uint index) { - if (arr.Length > 0) + if (arr.Length > 0 && arr.Length > index) { - return arr[arr.Length % index]; + return arr[(uint)arr.Length % index]; } return 1234; @@ -131,7 +155,73 @@ static int Test11(int[] arr, int index) [MethodImpl(MethodImplOptions.NoInlining)] static int Test12(int[] arr, int index) { - if (arr.Length > 0) + if (arr.Length > 0 && arr.Length > index) + { + return arr[arr.Length % index]; + } + + return 1234; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int Test13(int[] arr, uint index) + { + if (arr.Length > 0 && arr.Length >= index) + { + return arr[(uint)arr.Length % index]; + } + + return 1234; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int Test14(int[] arr, int index) + { + if (arr.Length > 0 && arr.Length >= index) + { + return arr[arr.Length % index]; + } + + return 1234; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int Test15(int[] arr, uint index) + { + if (arr.Length > 0 && index < arr.Length) + { + return arr[(uint)arr.Length % index]; + } + + return 1234; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int Test16(int[] arr, int index) + { + if (arr.Length > 0 && index < arr.Length) + { + return arr[arr.Length % index]; + } + + return 1234; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int Test17(int[] arr, uint index) + { + if (arr.Length > 0 && index <= arr.Length) + { + return arr[(uint)arr.Length % index]; + } + + return 1234; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int Test18(int[] arr, int index) + { + if (arr.Length > 0 && index <= arr.Length) { return arr[arr.Length % index]; } From 6364ada8050983fdf1d53a182e13b130f10c8f1c Mon Sep 17 00:00:00 2001 From: Henrique Ruschel Date: Sat, 4 Jul 2026 13:29:49 -0300 Subject: [PATCH 3/7] fix for different culture --- src/tests/run.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tests/run.py b/src/tests/run.py index 0be0b8514259d2..b022e227afb2cf 100755 --- a/src/tests/run.py +++ b/src/tests/run.py @@ -1358,7 +1358,7 @@ def parse_test_results_xml_file(args, item, item_name, tests, assemblies): "failed": 0, "skipped": 0, }) - assembly_info["time"] += float(assembly.attrib["time"]) + assembly_info["time"] += float(assembly.attrib["time"].replace(",", ".")) for collection in assembly: # In the non-merged tests model, we expect to see a `` tag within ``. @@ -1387,7 +1387,7 @@ def parse_test_results_xml_file(args, item, item_name, tests, assemblies): if len(name) > 0: test_name += " (" + name + ")" result = test.attrib["result"] - time = float(collection.attrib["time"]) + time = float(collection.attrib["time"].replace(",", ".")) test_output = test.findtext("output") tests.append(defaultdict(lambda: None, { "name": test_name, From c649501990a8079bf8a180e78c61ca59eed0a2db Mon Sep 17 00:00:00 2001 From: Henrique Ruschel Date: Sun, 5 Jul 2026 02:04:29 -0300 Subject: [PATCH 4/7] Just a test --- src/coreclr/jit/assertionprop.cpp | 213 +++++++++++++++--------------- src/coreclr/jit/rangecheck.cpp | 2 +- 2 files changed, 107 insertions(+), 108 deletions(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index 8bb8c9c7da6f10..17f121823f0355 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -5619,119 +5619,118 @@ GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, } else if (vnStore->IsVNBinFunc(vnCurIdx, VNF_UMOD, &idxOp0, &idxOp1)) { - if (idxOp0 == vnCurLen) - { - Range lenRng = GetRange(this, arrBndsChkLen, block, assertions, /*fast*/ true); - - if (lenRng.LowerLimit().IsConstant() && lenRng.LowerLimit().GetConstant() > 0) - { - BitVecOps::Iter iter(apTraits, assertions); - unsigned index = 0; - while (iter.NextElem(&index)) - { - AssertionIndex assertionIndex = GetAssertionIndex(index); - - const Compiler::AssertionDsc& curAssertion = optGetAssertion(assertionIndex); - - if (curAssertion.GetOp1().GetVN() == vnCurLen && curAssertion.GetOp2().GetVN() == idxOp1) - { - if (curAssertion.KindIs(OAK_GT)) - { - // when length > index, length % index has the range [0..length - 1] - return dropBoundsCheck(INDEBUG( - "a[a.Length u% X] is always within bounds when a.Length is known to be > 0 and a.Length > X")); - } - else if(curAssertion.KindIs(OAK_GE)) - { - // when length >= index, length % index has the range [0..length - 1] - return dropBoundsCheck(INDEBUG( - "a[a.Length u% X] is always within bounds when a.Length is known to be > 0 and a.Length >= X")); - } - } - else if (curAssertion.GetOp1().GetVN() == idxOp1 && curAssertion.GetOp2().GetVN() == vnCurLen) - { - if (curAssertion.KindIs(OAK_LT)) - { - // when index < length, length % index has the range [0..length - 1] - return dropBoundsCheck(INDEBUG( - "a[a.Length u% X] is always within bounds when a.Length is known to be > 0 and X < a.Length")); - } - else if (curAssertion.KindIs(OAK_LE)) - { - // when index <= length, length % index has the range [0..length - 1] - return dropBoundsCheck(INDEBUG( - "a[a.Length u% X] is always within bounds when a.Length is known to be > 0 and X <= a.Length")); - } - } - } - } - } - else if (idxOp1 == vnCurLen) + //if (idxOp0 == vnCurLen) + //{ + // Range lenRng = GetRange(this, arrBndsChkLen, block, assertions, /*fast*/ true); + + // if (lenRng.LowerLimit().IsConstant() && lenRng.LowerLimit().GetConstant() > 0) + // { + // BitVecOps::Iter iter(apTraits, assertions); + // unsigned index = 0; + // while (iter.NextElem(&index)) + // { + // AssertionIndex assertionIndex = GetAssertionIndex(index); + // const Compiler::AssertionDsc& curAssertion = optGetAssertion(assertionIndex); + + // if (curAssertion.GetOp1().GetVN() == vnCurLen && curAssertion.GetOp2().GetVN() == idxOp1) + // { + // if (curAssertion.KindIs(OAK_GT)) + // { + // // when length > index, length % index has the range [0..length - 1] + // return dropBoundsCheck(INDEBUG( + // "a[a.Length u% X] is always within bounds when a.Length is known to be > 0 and a.Length > X")); + // } + // else if (curAssertion.KindIs(OAK_GE)) + // { + // // when length >= index, length % index has the range [0..length - 1] + // return dropBoundsCheck(INDEBUG( + // "a[a.Length u% X] is always within bounds when a.Length is known to be > 0 and a.Length >= X")); + // } + // } + // else if (curAssertion.GetOp1().GetVN() == idxOp1 && curAssertion.GetOp2().GetVN() == vnCurLen) + // { + // if (curAssertion.KindIs(OAK_LT)) + // { + // // when index < length, length % index has the range [0..length - 1] + // return dropBoundsCheck(INDEBUG( + // "a[a.Length u% X] is always within bounds when a.Length is known to be > 0 and X < a.Length")); + // } + // else if (curAssertion.KindIs(OAK_LE)) + // { + // // when index <= length, length % index has the range [0..length - 1] + // return dropBoundsCheck(INDEBUG( + // "a[a.Length u% X] is always within bounds when a.Length is known to be > 0 and X <= a.Length")); + // } + // } + // } + // } + //} + //else + if (idxOp1 == vnCurLen) { // If arr.Length is 0 we technically should keep the bounds check, but since the expression // has to throw DivideByZeroException anyway - no special handling needed. return dropBoundsCheck(INDEBUG("a[X u% a.Length] is always within bounds")); } } - else if (vnStore->IsVNBinFunc(vnCurIdx, VNF_MOD, &idxOp0, &idxOp1)) - { - if (idxOp0 == vnCurLen) - { - Range lenRng = GetRange(this, arrBndsChkLen, block, assertions, /*fast*/ true); - - if (lenRng.LowerLimit().IsConstant() && lenRng.LowerLimit().GetConstant() > 0) - { - BitVecOps::Iter iter(apTraits, assertions); - unsigned index = 0; - while (iter.NextElem(&index)) - { - AssertionIndex assertionIndex = GetAssertionIndex(index); - - const Compiler::AssertionDsc& curAssertion = optGetAssertion(assertionIndex); - - if (curAssertion.GetOp1().GetVN() == vnCurLen && curAssertion.GetOp2().GetVN() == idxOp1) - { - if (curAssertion.KindIs(OAK_GT)) - { - // when length > index, length % index has the range [0..length - 1] - return dropBoundsCheck( - INDEBUG("a[a.Length % X] is always within bounds when a.Length is known to be > 0 and a.Length > X")); - } - else if(curAssertion.KindIs(OAK_GE)) - { - // when length >= index, length % index has the range [0..length - 1] - return dropBoundsCheck( - INDEBUG("a[a.Length % X] is always within bounds when a.Length is known to be > 0 and a.Length >= X")); - } - } - else if (curAssertion.GetOp1().GetVN() == idxOp1 && curAssertion.GetOp2().GetVN() == vnCurLen) - { - if (curAssertion.KindIs(OAK_LT)) - { - // when index < length, length % index has the range [0..length - 1] - return dropBoundsCheck(INDEBUG( - "a[a.Length % X] is always within bounds when a.Length is known to be > 0 and X < a.Length")); - } - else if (curAssertion.KindIs(OAK_LE)) - { - // when index <= length, length % index has the range [0..length - 1] - return dropBoundsCheck(INDEBUG( - "a[a.Length % X] is always within bounds when a.Length is known to be > 0 and X <= a.Length")); - } - } - } - } - } - else if (idxOp1 == vnCurLen) - { - Range idxRng = GetRange(this, arrBndsChkIdx, block, assertions, /*fast*/ true); - - if (idxRng.LowerLimit().IsConstant() && idxRng.LowerLimit().GetConstant() >= 0) - { - return dropBoundsCheck(INDEBUG("a[X % a.Length] is always within bounds when X is known to be >= 0")); - } - } - } + //else if (vnStore->IsVNBinFunc(vnCurIdx, VNF_MOD, &idxOp0, &idxOp1)) + //{ + // if (idxOp0 == vnCurLen) + // { + // Range lenRng = GetRange(this, arrBndsChkLen, block, assertions, /*fast*/ true); + + // if (lenRng.LowerLimit().IsConstant() && lenRng.LowerLimit().GetConstant() > 0) + // { + // BitVecOps::Iter iter(apTraits, assertions); + // unsigned index = 0; + // while (iter.NextElem(&index)) + // { + // AssertionIndex assertionIndex = GetAssertionIndex(index); + // const Compiler::AssertionDsc& curAssertion = optGetAssertion(assertionIndex); + + // if (curAssertion.GetOp1().GetVN() == vnCurLen && curAssertion.GetOp2().GetVN() == idxOp1) + // { + // if (curAssertion.KindIs(OAK_GT)) + // { + // // when length > index, length % index has the range [0..length - 1] + // return dropBoundsCheck( + // INDEBUG("a[a.Length % X] is always within bounds when a.Length is known to be > 0 and a.Length > X")); + // } + // else if (curAssertion.KindIs(OAK_GE)) + // { + // // when length >= index, length % index has the range [0..length - 1] + // return dropBoundsCheck( + // INDEBUG("a[a.Length % X] is always within bounds when a.Length is known to be > 0 and a.Length >= X")); + // } + // } + // else if (curAssertion.GetOp1().GetVN() == idxOp1 && curAssertion.GetOp2().GetVN() == vnCurLen) + // { + // if (curAssertion.KindIs(OAK_LT)) + // { + // // when index < length, length % index has the range [0..length - 1] + // return dropBoundsCheck(INDEBUG( + // "a[a.Length % X] is always within bounds when a.Length is known to be > 0 and X < a.Length")); + // } + // else if (curAssertion.KindIs(OAK_LE)) + // { + // // when index <= length, length % index has the range [0..length - 1] + // return dropBoundsCheck(INDEBUG( + // "a[a.Length % X] is always within bounds when a.Length is known to be > 0 and X <= a.Length")); + // } + // } + // } + // } + // } + // else if (idxOp1 == vnCurLen) + // { + // Range idxRng = GetRange(this, arrBndsChkIdx, block, assertions, /*fast*/ true); + + // if (idxRng.LowerLimit().IsConstant() && idxRng.LowerLimit().GetConstant() >= 0) + // { + // return dropBoundsCheck(INDEBUG("a[X % a.Length] is always within bounds when X is known to be >= 0")); + // } + // } + //} // Let's see if we can remove the bounds check based on the ranges. Range idxRng = GetRange(this, arrBndsChkIdx, block, assertions, /*fast*/ true); diff --git a/src/coreclr/jit/rangecheck.cpp b/src/coreclr/jit/rangecheck.cpp index a3f46bbc8b0096..e5b6e34fbabf1a 100644 --- a/src/coreclr/jit/rangecheck.cpp +++ b/src/coreclr/jit/rangecheck.cpp @@ -711,7 +711,7 @@ Range RangeCheck::GetRangeFromAssertionsWorker( case VNF_RSH: case VNF_RSZ: case VNF_UMOD: - case VNF_MOD: + //case VNF_MOD: case VNF_UDIV: { // Get ranges of both operands and perform the same operation on the ranges. From ec671f231406432e8c5bbff41ed9f32022e2b8a1 Mon Sep 17 00:00:00 2001 From: Henrique Ruschel Date: Sun, 5 Jul 2026 12:44:59 -0300 Subject: [PATCH 5/7] Test Mod --- src/coreclr/jit/rangecheck.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/rangecheck.cpp b/src/coreclr/jit/rangecheck.cpp index e5b6e34fbabf1a..a3f46bbc8b0096 100644 --- a/src/coreclr/jit/rangecheck.cpp +++ b/src/coreclr/jit/rangecheck.cpp @@ -711,7 +711,7 @@ Range RangeCheck::GetRangeFromAssertionsWorker( case VNF_RSH: case VNF_RSZ: case VNF_UMOD: - //case VNF_MOD: + case VNF_MOD: case VNF_UDIV: { // Get ranges of both operands and perform the same operation on the ranges. From c6619e7a49f2db7fec544d4d9fb9e55db5b7017f Mon Sep 17 00:00:00 2001 From: Henrique Ruschel Date: Sun, 5 Jul 2026 15:27:46 -0300 Subject: [PATCH 6/7] fix mod range --- src/coreclr/jit/assertionprop.cpp | 211 +++++++++++++++--------------- src/coreclr/jit/rangecheck.h | 66 ++++------ 2 files changed, 134 insertions(+), 143 deletions(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index 17f121823f0355..a5573af3e53716 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -5619,118 +5619,117 @@ GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, } else if (vnStore->IsVNBinFunc(vnCurIdx, VNF_UMOD, &idxOp0, &idxOp1)) { - //if (idxOp0 == vnCurLen) - //{ - // Range lenRng = GetRange(this, arrBndsChkLen, block, assertions, /*fast*/ true); - - // if (lenRng.LowerLimit().IsConstant() && lenRng.LowerLimit().GetConstant() > 0) - // { - // BitVecOps::Iter iter(apTraits, assertions); - // unsigned index = 0; - // while (iter.NextElem(&index)) - // { - // AssertionIndex assertionIndex = GetAssertionIndex(index); - // const Compiler::AssertionDsc& curAssertion = optGetAssertion(assertionIndex); - - // if (curAssertion.GetOp1().GetVN() == vnCurLen && curAssertion.GetOp2().GetVN() == idxOp1) - // { - // if (curAssertion.KindIs(OAK_GT)) - // { - // // when length > index, length % index has the range [0..length - 1] - // return dropBoundsCheck(INDEBUG( - // "a[a.Length u% X] is always within bounds when a.Length is known to be > 0 and a.Length > X")); - // } - // else if (curAssertion.KindIs(OAK_GE)) - // { - // // when length >= index, length % index has the range [0..length - 1] - // return dropBoundsCheck(INDEBUG( - // "a[a.Length u% X] is always within bounds when a.Length is known to be > 0 and a.Length >= X")); - // } - // } - // else if (curAssertion.GetOp1().GetVN() == idxOp1 && curAssertion.GetOp2().GetVN() == vnCurLen) - // { - // if (curAssertion.KindIs(OAK_LT)) - // { - // // when index < length, length % index has the range [0..length - 1] - // return dropBoundsCheck(INDEBUG( - // "a[a.Length u% X] is always within bounds when a.Length is known to be > 0 and X < a.Length")); - // } - // else if (curAssertion.KindIs(OAK_LE)) - // { - // // when index <= length, length % index has the range [0..length - 1] - // return dropBoundsCheck(INDEBUG( - // "a[a.Length u% X] is always within bounds when a.Length is known to be > 0 and X <= a.Length")); - // } - // } - // } - // } - //} - //else - if (idxOp1 == vnCurLen) + if (idxOp0 == vnCurLen) + { + Range lenRng = GetRange(this, arrBndsChkLen, block, assertions, /*fast*/ true); + + if (lenRng.LowerLimit().IsConstant() && lenRng.LowerLimit().GetConstant() > 0) + { + BitVecOps::Iter iter(apTraits, assertions); + unsigned index = 0; + while (iter.NextElem(&index)) + { + AssertionIndex assertionIndex = GetAssertionIndex(index); + const Compiler::AssertionDsc& curAssertion = optGetAssertion(assertionIndex); + + if (curAssertion.GetOp1().GetVN() == vnCurLen && curAssertion.GetOp2().GetVN() == idxOp1) + { + if (curAssertion.KindIs(OAK_GT)) + { + // when length > index, length % index has the range [0..length - 1] + return dropBoundsCheck(INDEBUG( + "a[a.Length u% X] is always within bounds when a.Length is known to be > 0 and a.Length > X")); + } + else if (curAssertion.KindIs(OAK_GE)) + { + // when length >= index, length % index has the range [0..length - 1] + return dropBoundsCheck(INDEBUG( + "a[a.Length u% X] is always within bounds when a.Length is known to be > 0 and a.Length >= X")); + } + } + else if (curAssertion.GetOp1().GetVN() == idxOp1 && curAssertion.GetOp2().GetVN() == vnCurLen) + { + if (curAssertion.KindIs(OAK_LT)) + { + // when index < length, length % index has the range [0..length - 1] + return dropBoundsCheck(INDEBUG( + "a[a.Length u% X] is always within bounds when a.Length is known to be > 0 and X < a.Length")); + } + else if (curAssertion.KindIs(OAK_LE)) + { + // when index <= length, length % index has the range [0..length - 1] + return dropBoundsCheck(INDEBUG( + "a[a.Length u% X] is always within bounds when a.Length is known to be > 0 and X <= a.Length")); + } + } + } + } + } + else if (idxOp1 == vnCurLen) { // If arr.Length is 0 we technically should keep the bounds check, but since the expression // has to throw DivideByZeroException anyway - no special handling needed. return dropBoundsCheck(INDEBUG("a[X u% a.Length] is always within bounds")); } } - //else if (vnStore->IsVNBinFunc(vnCurIdx, VNF_MOD, &idxOp0, &idxOp1)) - //{ - // if (idxOp0 == vnCurLen) - // { - // Range lenRng = GetRange(this, arrBndsChkLen, block, assertions, /*fast*/ true); - - // if (lenRng.LowerLimit().IsConstant() && lenRng.LowerLimit().GetConstant() > 0) - // { - // BitVecOps::Iter iter(apTraits, assertions); - // unsigned index = 0; - // while (iter.NextElem(&index)) - // { - // AssertionIndex assertionIndex = GetAssertionIndex(index); - // const Compiler::AssertionDsc& curAssertion = optGetAssertion(assertionIndex); - - // if (curAssertion.GetOp1().GetVN() == vnCurLen && curAssertion.GetOp2().GetVN() == idxOp1) - // { - // if (curAssertion.KindIs(OAK_GT)) - // { - // // when length > index, length % index has the range [0..length - 1] - // return dropBoundsCheck( - // INDEBUG("a[a.Length % X] is always within bounds when a.Length is known to be > 0 and a.Length > X")); - // } - // else if (curAssertion.KindIs(OAK_GE)) - // { - // // when length >= index, length % index has the range [0..length - 1] - // return dropBoundsCheck( - // INDEBUG("a[a.Length % X] is always within bounds when a.Length is known to be > 0 and a.Length >= X")); - // } - // } - // else if (curAssertion.GetOp1().GetVN() == idxOp1 && curAssertion.GetOp2().GetVN() == vnCurLen) - // { - // if (curAssertion.KindIs(OAK_LT)) - // { - // // when index < length, length % index has the range [0..length - 1] - // return dropBoundsCheck(INDEBUG( - // "a[a.Length % X] is always within bounds when a.Length is known to be > 0 and X < a.Length")); - // } - // else if (curAssertion.KindIs(OAK_LE)) - // { - // // when index <= length, length % index has the range [0..length - 1] - // return dropBoundsCheck(INDEBUG( - // "a[a.Length % X] is always within bounds when a.Length is known to be > 0 and X <= a.Length")); - // } - // } - // } - // } - // } - // else if (idxOp1 == vnCurLen) - // { - // Range idxRng = GetRange(this, arrBndsChkIdx, block, assertions, /*fast*/ true); - - // if (idxRng.LowerLimit().IsConstant() && idxRng.LowerLimit().GetConstant() >= 0) - // { - // return dropBoundsCheck(INDEBUG("a[X % a.Length] is always within bounds when X is known to be >= 0")); - // } - // } - //} + else if (vnStore->IsVNBinFunc(vnCurIdx, VNF_MOD, &idxOp0, &idxOp1)) + { + if (idxOp0 == vnCurLen) + { + Range lenRng = GetRange(this, arrBndsChkLen, block, assertions, /*fast*/ true); + + if (lenRng.LowerLimit().IsConstant() && lenRng.LowerLimit().GetConstant() > 0) + { + BitVecOps::Iter iter(apTraits, assertions); + unsigned index = 0; + while (iter.NextElem(&index)) + { + AssertionIndex assertionIndex = GetAssertionIndex(index); + const Compiler::AssertionDsc& curAssertion = optGetAssertion(assertionIndex); + + if (curAssertion.GetOp1().GetVN() == vnCurLen && curAssertion.GetOp2().GetVN() == idxOp1) + { + if (curAssertion.KindIs(OAK_GT)) + { + // when length > index, length % index has the range [0..length - 1] + return dropBoundsCheck( + INDEBUG("a[a.Length % X] is always within bounds when a.Length is known to be > 0 and a.Length > X")); + } + else if (curAssertion.KindIs(OAK_GE)) + { + // when length >= index, length % index has the range [0..length - 1] + return dropBoundsCheck( + INDEBUG("a[a.Length % X] is always within bounds when a.Length is known to be > 0 and a.Length >= X")); + } + } + else if (curAssertion.GetOp1().GetVN() == idxOp1 && curAssertion.GetOp2().GetVN() == vnCurLen) + { + if (curAssertion.KindIs(OAK_LT)) + { + // when index < length, length % index has the range [0..length - 1] + return dropBoundsCheck(INDEBUG( + "a[a.Length % X] is always within bounds when a.Length is known to be > 0 and X < a.Length")); + } + else if (curAssertion.KindIs(OAK_LE)) + { + // when index <= length, length % index has the range [0..length - 1] + return dropBoundsCheck(INDEBUG( + "a[a.Length % X] is always within bounds when a.Length is known to be > 0 and X <= a.Length")); + } + } + } + } + } + else if (idxOp1 == vnCurLen) + { + Range idxRng = GetRange(this, arrBndsChkIdx, block, assertions, /*fast*/ true); + + if (idxRng.LowerLimit().IsConstant() && idxRng.LowerLimit().GetConstant() >= 0) + { + return dropBoundsCheck(INDEBUG("a[X % a.Length] is always within bounds when X is known to be >= 0")); + } + } + } // Let's see if we can remove the bounds check based on the ranges. Range idxRng = GetRange(this, arrBndsChkIdx, block, assertions, /*fast*/ true); diff --git a/src/coreclr/jit/rangecheck.h b/src/coreclr/jit/rangecheck.h index 093f627aa1c66f..a089f4d547fe64 100644 --- a/src/coreclr/jit/rangecheck.h +++ b/src/coreclr/jit/rangecheck.h @@ -525,8 +525,13 @@ struct RangeOps { if (r1.LowerLimit().IsConstant()) { - int r2ConstVal; + int r1ConstVal; + if (r1.IsSingleValueConstant(&r1ConstVal) && (r1ConstVal == 0)) + { + return Range(Limit(Limit::keConstant, 0)); + } + int r2ConstVal; if (r2.IsSingleValueConstant(&r2ConstVal)) { if (r2ConstVal == 0) @@ -534,52 +539,39 @@ struct RangeOps return Range(Limit(Limit::keUnknown)); } - int r1ConstVal; - - if (r1.IsSingleValueConstant(&r1ConstVal)) + if (r1.IsSingleValueConstant()) { return Range(Limit(Limit::keConstant, r1ConstVal % r2ConstVal)); } } - const auto SafeAbsMinusOne = [](const int value) { - return value == INT32_MIN ? INT32_MAX : (abs(value) - 1); - }; - const int r1lo = r1.LowerLimit().GetConstant(); - if (r1lo >= 0) - { - if (r2.IsConstantRange()) - { - const int r2lo = r2.LowerLimit().GetConstant(); - const int r2hi = r2.UpperLimit().GetConstant(); - // x % [-4..2] -> [0..3] - const int maxAbsRight = max(SafeAbsMinusOne(r2lo), SafeAbsMinusOne(r2hi)); - const int rightLimit = - r1.UpperLimit().IsConstant() ? min(r1.UpperLimit().GetConstant(), maxAbsRight) : maxAbsRight; - - return Range(Limit(Limit::keConstant, 0), Limit(Limit::keConstant, rightLimit)); - } + const bool isAlwaysPositive = r1lo >= 0; + const bool isAlwaysNegative = r1.UpperLimit().IsConstant() && r1.UpperLimit().GetConstant() <= 0; - return Range(Limit(Limit::keConstant, 0), Limit(Limit::keUnknown)); - } - else + if (r2.IsConstantRange()) { - if (r2.IsConstantRange()) - { - const int r2lo = r2.LowerLimit().GetConstant(); - const int r2hi = r2.UpperLimit().GetConstant(); - // x % [-4..2] -> [0..3] - const int maxAbsRight = max(SafeAbsMinusOne(r2lo), SafeAbsMinusOne(r2hi)); - - const int leftLimit = max(r1lo, -maxAbsRight); - - return Range(Limit(Limit::keConstant, leftLimit), Limit(Limit::keConstant, -0)); - } - - return Range(Limit(Limit::keUnknown), Limit(Limit::keConstant, -0)); + const auto SafeAbsMinusOne = [](const int value) { + return value == INT32_MIN ? INT32_MAX : (abs(value) - 1); + }; + + const int r2lo = r2.LowerLimit().GetConstant(); + const int r2hi = r2.UpperLimit().GetConstant(); + // x % [-4..2] -> [0..3] + const int maxAbsRight = max(SafeAbsMinusOne(r2lo), SafeAbsMinusOne(r2hi)); + + const int leftLimit = isAlwaysPositive ? 0 : max(r1lo, -maxAbsRight); + const int rightLimit = isAlwaysNegative ? -0 + : r1.UpperLimit().IsConstant() + ? min(r1.UpperLimit().GetConstant(), maxAbsRight) + : maxAbsRight; + + return Range(Limit(Limit::keConstant, leftLimit), Limit(Limit::keConstant, rightLimit)); } + + return Range(isAlwaysPositive ? Limit(Limit::keConstant, 0) : Limit(Limit::keUnknown), + isAlwaysNegative ? Limit(Limit::keConstant, -0) : Limit(Limit::keUnknown)); } return Range(Limit(Limit::keUnknown)); From eab2ec5ed7b9bbf960fa857094c221e07e049a36 Mon Sep 17 00:00:00 2001 From: Henrique Ruschel Date: Tue, 7 Jul 2026 20:11:15 -0300 Subject: [PATCH 7/7] unsigned assertion --- src/coreclr/jit/assertionprop.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index 627e016e462af0..17c3c841ce7170 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -5622,13 +5622,13 @@ GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, if (curAssertion.GetOp1().GetVN() == vnCurLen && curAssertion.GetOp2().GetVN() == idxOp1) { - if (curAssertion.KindIs(OAK_GT)) + if (curAssertion.KindIs(OAK_GT_UN)) { // when length > index, length % index has the range [0..length - 1] return dropBoundsCheck(INDEBUG( "a[a.Length u% X] is always within bounds when a.Length is known to be > 0 and a.Length > X")); } - else if (curAssertion.KindIs(OAK_GE)) + else if (curAssertion.KindIs(OAK_GE_UN)) { // when length >= index, length % index has the range [0..length - 1] return dropBoundsCheck(INDEBUG( @@ -5637,13 +5637,13 @@ GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, } else if (curAssertion.GetOp1().GetVN() == idxOp1 && curAssertion.GetOp2().GetVN() == vnCurLen) { - if (curAssertion.KindIs(OAK_LT)) + if (curAssertion.KindIs(OAK_LT_UN)) { // when index < length, length % index has the range [0..length - 1] return dropBoundsCheck(INDEBUG( "a[a.Length u% X] is always within bounds when a.Length is known to be > 0 and X < a.Length")); } - else if (curAssertion.KindIs(OAK_LE)) + else if (curAssertion.KindIs(OAK_LE_UN)) { // when index <= length, length % index has the range [0..length - 1] return dropBoundsCheck(INDEBUG(