Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
9050724
Eliminate bound checks
henriquewr Jul 3, 2026
9d229d7
Merge branch 'main' into boundCheckMod
henriquewr Jul 3, 2026
f634420
Merge branch 'main' into boundCheckMod
henriquewr Jul 3, 2026
96c3b7e
Merge branch 'main' into boundCheckMod
henriquewr Jul 3, 2026
6d99328
fix length % index variants
henriquewr Jul 3, 2026
234115b
Merge branch 'main' into boundCheckMod
henriquewr Jul 3, 2026
6364ada
fix for different culture
henriquewr Jul 4, 2026
c649501
Just a test
henriquewr Jul 5, 2026
ec671f2
Test Mod
henriquewr Jul 5, 2026
c6619e7
fix mod range
henriquewr Jul 5, 2026
cbaa703
Merge branch 'main' into boundCheckMod
henriquewr Jul 5, 2026
8c216d3
Merge branch 'main' into boundCheckMod
henriquewr Jul 7, 2026
226270c
Merge branch 'main' into boundCheckMod
henriquewr Jul 7, 2026
eab2ec5
unsigned assertion
henriquewr Jul 7, 2026
3072a11
Merge branch 'boundCheckMod' of https://github.com/henriquewr/runtime…
henriquewr Jul 7, 2026
67ed3ba
Merge branch 'main' into boundCheckMod
henriquewr Jul 7, 2026
c0e5ed7
fix for (uint)i <= length
henriquewr Jul 10, 2026
0b5b694
Merge branch 'main' into boundCheckMod
henriquewr Jul 10, 2026
aab460e
Removed support for arr.Length % index
henriquewr Jul 11, 2026
c262d5c
Merge branch 'boundCheckMod' of https://github.com/henriquewr/runtime…
henriquewr Jul 11, 2026
c9fea9f
Merge branch 'main' into boundCheckMod
henriquewr Jul 16, 2026
0ed5f50
Merge branch 'main' into boundCheckMod
henriquewr Jul 19, 2026
1fcf6f2
Wrong identation
henriquewr Jul 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/coreclr/jit/assertionprop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5655,6 +5655,15 @@ GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions,
// 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) && (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"));
}
}

if (getIdxRng().IsConstantRange() && getLenRng().IsConstantRange())
{
Expand Down
4 changes: 4 additions & 0 deletions src/coreclr/jit/rangecheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,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.
Expand Down Expand Up @@ -816,6 +817,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;
Expand Down
56 changes: 56 additions & 0 deletions src/coreclr/jit/rangecheck.h
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,62 @@ struct RangeOps
return Range(Limit(Limit::keUnknown));
}

static Range Mod(const Range& r1, const Range& r2)
{
if (r1.LowerLimit().IsConstant())
{
int r1ConstVal;
if (r1.IsSingleValueConstant(&r1ConstVal) && (r1ConstVal == 0))
{
return Range(Limit(Limit::keConstant, 0));
}

int r2ConstVal;
if (r2.IsSingleValueConstant(&r2ConstVal))
{
if (r2ConstVal == 0)
{
return Range(Limit(Limit::keUnknown));
}

if (r1.IsSingleValueConstant())
{
return Range(Limit(Limit::keConstant, r1ConstVal % r2ConstVal));
}
}

const int r1lo = r1.LowerLimit().GetConstant();

const bool isAlwaysPositive = r1lo >= 0;
const bool isAlwaysNegative = r1.UpperLimit().IsConstant() && r1.UpperLimit().GetConstant() <= 0;

if (r2.IsConstantRange())
{
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));
}

static Range UnsignedDivide(const Range& r1, const Range& r2)
{
// We only handle constant ranges for both operands.
Expand Down
131 changes: 131 additions & 0 deletions src/tests/JIT/opt/RangeChecks/ModLength.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,38 @@ public static void TestEntryPoint()
Test6(new int[10], 0);
Test7(new int[10], 0);
Throws<DivideByZeroException>(() => Test8(new int[10], 0));

Test10(new int[10], 9U);
Throws<DivideByZeroException>(() => Test10(new int[10], 0U));

Test11(new int[10], 0);
Test11(new int[10], 100);
Throws<DivideByZeroException>(() => Test11(new int[0], 10));

Test12(new int[10], 9);
Throws<DivideByZeroException>(() => Test12(new int[10], 0));

Test13(new int[10], 9U);
Test13(new int[10], 10U);
Throws<DivideByZeroException>(() => Test13(new int[10], 0U));

Test14(new int[10], 9);
Test14(new int[10], 10);
Throws<DivideByZeroException>(() => Test14(new int[10], 0));

Test15(new int[10], 9U);
Throws<DivideByZeroException>(() => Test15(new int[10], 0U));

Test16(new int[10], 9);
Throws<DivideByZeroException>(() => Test16(new int[10], 0));

Test17(new int[10], 9U);
Test17(new int[10], 10U);
Throws<DivideByZeroException>(() => Test17(new int[10], 0U));

Test18(new int[10], 9);
Test18(new int[10], 10);
Throws<DivideByZeroException>(() => Test18(new int[10], 0));
}

static void Throws<T>(Action action, [CallerLineNumber] int line = 0)
Expand Down Expand Up @@ -97,4 +129,103 @@ 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 && arr.Length > index)
{
return arr[(uint)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 && 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];
}

return 1234;
}
}
4 changes: 2 additions & 2 deletions src/tests/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -1428,7 +1428,7 @@ def parse_test_results_xml_file(args, item, item_name, tests, assemblies):
"skipped": 0,
"active_issue": 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 `<errors />` tag within `<assembly>`.
Expand Down Expand Up @@ -1457,7 +1457,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")
skip_reason = test.findtext("reason") if result == "Skip" else None
tests.append(defaultdict(lambda: None, {
Expand Down
Loading