From dc22495e546b8c34dcef7eb3c4a68177be18e015 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Tue, 14 Apr 2026 02:00:20 +0200 Subject: [PATCH 1/6] Checkpoint from Copilot CLI for coding agent session --- src/coreclr/jit/assertionprop.cpp | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index 55dc18f6022cf6..5e69d461ee81f5 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -5332,9 +5332,30 @@ GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, GenTree Range lenRng = RangeCheck::GetRangeFromAssertions(this, vnCurLen, assertions); if (idxRng.IsConstantRange() && lenRng.IsConstantRange()) { - // idx.lo >= 0 && idx.hi < len.lo --> drop bounds check - if (idxRng.LowerLimit().GetConstant() >= 0 && - idxRng.UpperLimit().GetConstant() < 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" in case if we also have a + // "length != 0" assertion for it. + if (lenLo <= 0) + { + 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)) + { + lenLo = 1; + } + } + } + + // 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")); } From 0de796c297ea22414361fbc051ea529e06248a55 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 00:25:13 +0000 Subject: [PATCH 2/6] JIT: Elide bounds check for span[0] after non-empty check Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/81448e73-1d65-4ae9-90d4-38de0a5459de Co-authored-by: EgorBo <523221+EgorBo@users.noreply.github.com> --- src/coreclr/jit/assertionprop.cpp | 2 +- .../JIT/opt/RangeChecks/ElidedBoundsChecks.cs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index 5e69d461ee81f5..5ed082a64b34a3 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -5337,7 +5337,7 @@ GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, GenTree 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" in case if we also have a + // So we additionally tighten the lower bound of lenLo to be ">= 1" in case if we also have a // "length != 0" assertion for it. if (lenLo <= 0) { diff --git a/src/tests/JIT/opt/RangeChecks/ElidedBoundsChecks.cs b/src/tests/JIT/opt/RangeChecks/ElidedBoundsChecks.cs index e9438a1ba78934..6e379ad7d004f5 100644 --- a/src/tests/JIT/opt/RangeChecks/ElidedBoundsChecks.cs +++ b/src/tests/JIT/opt/RangeChecks/ElidedBoundsChecks.cs @@ -83,6 +83,16 @@ static bool IndexPlusConstLessThanLen(ReadOnlySpan span) return false; } + [MethodImpl(MethodImplOptions.NoInlining)] + static byte FirstElementAfterEmptyCheck(ReadOnlySpan span) + { + // X64-NOT: CORINFO_HELP_RNGCHKFAIL + // ARM64-NOT: CORINFO_HELP_RNGCHKFAIL + if (span.IsEmpty) + return 0; + return span[0]; + } + [Fact] public static int TestEntryPoint() { @@ -119,6 +129,12 @@ public static int TestEntryPoint() if (IndexPlusConstLessThanLen("hello".AsSpan()) != false) return 0; + if (FirstElementAfterEmptyCheck(new byte[] { 42 }) != 42) + return 0; + + if (FirstElementAfterEmptyCheck(Array.Empty()) != 0) + return 0; + return 100; } } From 405007dd06125d6c710cf2519e47d6eec2fc6ab2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 00:30:13 +0000 Subject: [PATCH 3/6] JIT: Add early break in assertion scan loop Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/81448e73-1d65-4ae9-90d4-38de0a5459de Co-authored-by: EgorBo <523221+EgorBo@users.noreply.github.com> --- src/coreclr/jit/assertionprop.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index 5ed082a64b34a3..468f6e931f3d1b 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -5350,6 +5350,7 @@ GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, GenTree (assertion.GetOp1().GetVN() == vnCurLen) && (assertion.GetOp2().GetIntConstant() == 0)) { lenLo = 1; + break; } } } From 7d9c61564df2f8fde79dad1564cdfd2c659969ac Mon Sep 17 00:00:00 2001 From: Egor Bogatov Date: Tue, 14 Apr 2026 02:45:04 +0200 Subject: [PATCH 4/6] Update src/coreclr/jit/assertionprop.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/coreclr/jit/assertionprop.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index 468f6e931f3d1b..b00a68dc85693a 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -5337,7 +5337,7 @@ GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, GenTree 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" in case if we also have a + // So we additionally tighten the lower bound of lenLo to be ">= 1" when we also have a // "length != 0" assertion for it. if (lenLo <= 0) { From 12d10a0219f9cbf555f71c295d5edb48555103f2 Mon Sep 17 00:00:00 2001 From: Egor Bogatov Date: Tue, 14 Apr 2026 02:51:30 +0200 Subject: [PATCH 5/6] Tighten lower bound for lenLo in bounds check --- src/coreclr/jit/assertionprop.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index b00a68dc85693a..55818ea698817a 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -5339,7 +5339,7 @@ GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, GenTree // 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 (lenLo <= 0) + if ((idxLo == 0) && (idxHi == 0) && (lenLo <= 0)) { BitVecOps::Iter iter(apTraits, assertions); unsigned bvIndex = 0; From 6685332d659c63f89ad3297a0cf1897194be68da Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 00:59:27 +0000 Subject: [PATCH 6/6] Replace test with exact repro from issue #126857 Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/4739c73a-b933-4754-a5c7-063f961e69b0 Co-authored-by: EgorBo <523221+EgorBo@users.noreply.github.com> --- .../JIT/opt/RangeChecks/ElidedBoundsChecks.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/tests/JIT/opt/RangeChecks/ElidedBoundsChecks.cs b/src/tests/JIT/opt/RangeChecks/ElidedBoundsChecks.cs index 6e379ad7d004f5..d6196e0a1922a9 100644 --- a/src/tests/JIT/opt/RangeChecks/ElidedBoundsChecks.cs +++ b/src/tests/JIT/opt/RangeChecks/ElidedBoundsChecks.cs @@ -84,13 +84,15 @@ static bool IndexPlusConstLessThanLen(ReadOnlySpan span) } [MethodImpl(MethodImplOptions.NoInlining)] - static byte FirstElementAfterEmptyCheck(ReadOnlySpan span) + static bool TryStripFirstChar(ref ReadOnlySpan span, char value) { // X64-NOT: CORINFO_HELP_RNGCHKFAIL // ARM64-NOT: CORINFO_HELP_RNGCHKFAIL - if (span.IsEmpty) - return 0; - return span[0]; + if (!span.IsEmpty && span[0] == value) + { + return true; + } + return false; } [Fact] @@ -129,10 +131,12 @@ public static int TestEntryPoint() if (IndexPlusConstLessThanLen("hello".AsSpan()) != false) return 0; - if (FirstElementAfterEmptyCheck(new byte[] { 42 }) != 42) + ReadOnlySpan chars = "hello".AsSpan(); + if (TryStripFirstChar(ref chars, 'h') != true) return 0; - if (FirstElementAfterEmptyCheck(Array.Empty()) != 0) + chars = ReadOnlySpan.Empty; + if (TryStripFirstChar(ref chars, 'h') != false) return 0; return 100;