Elide bounds checks in descending formatting loops - #132970

Open
tannergooding wants to merge 7 commits into
dotnet:mainfrom
tannergooding:tannergooding-investigate-tryformatl-arm64
Open

Elide bounds checks in descending formatting loops#132970
tannergooding wants to merge 7 commits into
dotnet:mainfrom
tannergooding:tannergooding-investigate-tryformatl-arm64

Conversation

@tannergooding

Copy link
Copy Markdown
Member

Adds conservative monotonically-decreasing induction-variable analysis to range-check elimination. The proof distinguishes dominating entries from backedges, accounts for overflow and subtraction, and rejects nested or irreducible phi shapes it cannot prove safely.

This lets pre-decrement loops establish both bounds for adjacent writes while retaining bounds protection when the induction variable can underflow.


Updates the UInt32 and UInt64 decimal formatters to use exact spans and safe descending pair writes through MemoryMarshal.CreateSpan and the existing WriteTwoDigits helper. This removes the hot destination bounds check involved in #132840 without architecture-specific or unsafe call-site code.

Local x64 BenchmarkDotNet results:

BenchmarkBeforeAfterChange
UTF-16 TryFormatL36.78 ns34.41 ns-6.4%
UTF-8 TryFormatUtf8L36.74 ns34.23 ns-6.8%

Arm64 AltJit disassembly confirms the destination range check is removed from the pair-write loop. Digit-table validation remains independent of this change.

Note

This pull request description was drafted by GitHub Copilot.

tannergoodingand others added 2 commits August 31, 2026 07:49
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI lite review requested due to automatic review settings August 31, 2026 14:50
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Aug 31, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 5 pipeline(s).
11 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Lite
Findings: 1 Medium severity

New issues introduced by this change (1)
SeverityFinding
Medium severitysrc/​coreclr/​jit/​rangecheck.cpp — In RangeCheck::Widen, the monotonic recomputation assigns *pRange = GetRangeWorker(...). If both…
What changed in this PR

This PR extends CoreCLR JIT range-check elimination with a conservative monotonicity direction concept (increasing vs decreasing) and updates the UInt32/UInt64 decimal formatting fast paths to use descending two-digit writes via MemoryMarshal.CreateSpan + the existing WriteTwoDigits helper, with accompanying JIT regression tests.

Changes:

  • Add Monotonicity (None/Increasing/Decreasing) to range analysis and update phi-merge/widening logic to support descending induction variables.
  • Rewrite UInt32ToDecChars/UInt64ToDecChars fast paths to write exact spans in a descending loop using WriteTwoDigits.
  • Add JIT opt tests covering descending pair writes and underflow/overflow cases to validate bounds-check behavior.
FileDescription
src/​coreclr/​jit/​rangecheck.hIntroduces Monotonicity and threads it through range computation/merge APIs.
src/​coreclr/​jit/​rangecheck.cppImplements decreasing-monotonicity proof logic and updates widening/overflow handling to consider GT_SUB and descending loops.
src/​libraries/​System.Private.CoreLib/​src/​System/​Number.Formatting.csUpdates UInt32/UInt64 decimal formatting loops to use descending two-digit span writes and adjusts destination length checks.
src/​tests/​JIT/​opt/​RangeChecks/​ElidedBoundsChecks.csAdds a descending pair-write loop test using MemoryMarshal.CreateSpan and validates results without reintroducing range-check helpers.
src/​tests/​JIT/​opt/​RangeChecks/​Overflow.csAdds a descending-underflow scenario to ensure bounds checks are not incorrectly elided when the IV can wrap.

Comment threadsrc/coreclr/jit/rangecheck.cpp Outdated
tannergoodingand others added 2 commits August 31, 2026 13:25
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 31, 2026 20:25

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Review tier: Lite
Findings: 1 Low severity

New issues introduced by this change (1)
SeverityFinding
Low severitysrc/​coreclr/​jit/​rangecheck.cpp — The BetweenBounds doc comment still says it assumes an increasing loop / symbolic upper bound, but…
Issues resolved since last review (1)
SeverityFinding
Medium severitysrc/​coreclr/​jit/​rangecheck.cpp — In RangeCheck::Widen, the monotonic recomputation assigns *pRange = GetRangeWorker(...). If both… View resolved comment

Comment threadsrc/coreclr/jit/rangecheck.cpp Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 31, 2026 21:02
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Review tier: Lite
Findings: None

Issues resolved since last review (1)
SeverityFinding
Low severitysrc/​coreclr/​jit/​rangecheck.cpp — The BetweenBounds doc comment still says it assumes an increasing loop / symbolic upper bound, but… View resolved comment
Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

src/coreclr/jit/assertionprop.cpp:4478

  • The doc comment says this returns true when range analysis proves the relop’s “non-throwing relation”, but this helper is also used for the throwing forms (e.g., start + width > limit), where “proven in-bounds” means the relop is always false. Updating the comment to describe what is actually proven (the access is in-bounds) will avoid future misuse/confusion when adding new operator shapes.
// Returns:
// True if range analysis proves the non-throwing relation represented by the relop.
//

CopilotAI review requested due to automatic review settings August 31, 2026 21:13

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Review tier: Lite
Findings: None

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 5 pipeline(s).
11 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@tannergooding

Copy link
Copy Markdown
MemberAuthor

SuperPMI shows net code-size improvements on every reported target:

TargetOverall code size
browser-wasm-971 bytes
linux-arm64-808,040 bytes
windows-arm64-683,920 bytes
osx-arm64-49,628 bytes
linux-x64-606,226 bytes
windows-x64-60,969 bytes

The absolute totals are corpus-dependent. In particular, Linux x64 includes coreclr_tests, libraries_tests, and libraries_tests_no_tiered_compilation, which are absent from the Windows x64 run and account for -535,396 bytes. Across the collections shared by both, Linux x64 is -70,830 bytes versus Windows x64 at -60,969 bytes.

Some representative wins:

  • Arm64 UInt32ToDecChars<byte>(..., digits) is -164 bytes (-43.62%), and UInt64ToDecChars<byte>(..., digits) is -164 bytes (-41.41%).
  • Arm64 Tier1 UInt32ToDecChars<char> goes from 240 to 232 bytes and 60 to 58 instructions; its reported PerfScore improves from 38.86 to 37.36 on Windows.
  • Windows x64 UInt32ToDecChars<byte>(..., digits) is -97 bytes (-34.77%), while the corresponding UInt64 method is -97 bytes (-32.23%).
  • The more general range-check improvements also show up in TimeSpanFormat.TryFormatStandard<char> (-402 bytes / -14.46% on Windows x64) and AssemblyNameFormatter.AppendDisplayName (-708 bytes on Arm64, -446 bytes on Windows x64).

Note

This comment was prepared with GitHub Copilot assistance.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Review tier: Lite
Findings: None

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@tannergooding
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Elide bounds checks in descending formatting loops - #132970

Open
tannergooding wants to merge 7 commits into
dotnet:mainfrom
tannergooding:tannergooding-investigate-tryformatl-arm64
Open

Elide bounds checks in descending formatting loops#132970
tannergooding wants to merge 7 commits into
dotnet:mainfrom
tannergooding:tannergooding-investigate-tryformatl-arm64

Conversation

@tannergooding

Copy link
Copy Markdown
Member

Adds conservative monotonically-decreasing induction-variable analysis to range-check elimination. The proof distinguishes dominating entries from backedges, accounts for overflow and subtraction, and rejects nested or irreducible phi shapes it cannot prove safely.

This lets pre-decrement loops establish both bounds for adjacent writes while retaining bounds protection when the induction variable can underflow.


Updates the UInt32 and UInt64 decimal formatters to use exact spans and safe descending pair writes through MemoryMarshal.CreateSpan and the existing WriteTwoDigits helper. This removes the hot destination bounds check involved in #132840 without architecture-specific or unsafe call-site code.

Local x64 BenchmarkDotNet results:

BenchmarkBeforeAfterChange
UTF-16 TryFormatL36.78 ns34.41 ns-6.4%
UTF-8 TryFormatUtf8L36.74 ns34.23 ns-6.8%

Arm64 AltJit disassembly confirms the destination range check is removed from the pair-write loop. Digit-table validation remains independent of this change.

Note

This pull request description was drafted by GitHub Copilot.

tannergoodingand others added 2 commits August 31, 2026 07:49
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI lite review requested due to automatic review settings August 31, 2026 14:50
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Aug 31, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 5 pipeline(s).
11 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Lite
Findings: 1 Medium severity

New issues introduced by this change (1)
SeverityFinding
Medium severitysrc/​coreclr/​jit/​rangecheck.cpp — In RangeCheck::Widen, the monotonic recomputation assigns *pRange = GetRangeWorker(...). If both…
What changed in this PR

This PR extends CoreCLR JIT range-check elimination with a conservative monotonicity direction concept (increasing vs decreasing) and updates the UInt32/UInt64 decimal formatting fast paths to use descending two-digit writes via MemoryMarshal.CreateSpan + the existing WriteTwoDigits helper, with accompanying JIT regression tests.

Changes:

  • Add Monotonicity (None/Increasing/Decreasing) to range analysis and update phi-merge/widening logic to support descending induction variables.
  • Rewrite UInt32ToDecChars/UInt64ToDecChars fast paths to write exact spans in a descending loop using WriteTwoDigits.
  • Add JIT opt tests covering descending pair writes and underflow/overflow cases to validate bounds-check behavior.
FileDescription
src/​coreclr/​jit/​rangecheck.hIntroduces Monotonicity and threads it through range computation/merge APIs.
src/​coreclr/​jit/​rangecheck.cppImplements decreasing-monotonicity proof logic and updates widening/overflow handling to consider GT_SUB and descending loops.
src/​libraries/​System.Private.CoreLib/​src/​System/​Number.Formatting.csUpdates UInt32/UInt64 decimal formatting loops to use descending two-digit span writes and adjusts destination length checks.
src/​tests/​JIT/​opt/​RangeChecks/​ElidedBoundsChecks.csAdds a descending pair-write loop test using MemoryMarshal.CreateSpan and validates results without reintroducing range-check helpers.
src/​tests/​JIT/​opt/​RangeChecks/​Overflow.csAdds a descending-underflow scenario to ensure bounds checks are not incorrectly elided when the IV can wrap.

Comment threadsrc/coreclr/jit/rangecheck.cpp Outdated
tannergoodingand others added 2 commits August 31, 2026 13:25
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 31, 2026 20:25

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Review tier: Lite
Findings: 1 Low severity

New issues introduced by this change (1)
SeverityFinding
Low severitysrc/​coreclr/​jit/​rangecheck.cpp — The BetweenBounds doc comment still says it assumes an increasing loop / symbolic upper bound, but…
Issues resolved since last review (1)
SeverityFinding
Medium severitysrc/​coreclr/​jit/​rangecheck.cpp — In RangeCheck::Widen, the monotonic recomputation assigns *pRange = GetRangeWorker(...). If both… View resolved comment

Comment threadsrc/coreclr/jit/rangecheck.cpp Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 31, 2026 21:02
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Review tier: Lite
Findings: None

Issues resolved since last review (1)
SeverityFinding
Low severitysrc/​coreclr/​jit/​rangecheck.cpp — The BetweenBounds doc comment still says it assumes an increasing loop / symbolic upper bound, but… View resolved comment
Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

src/coreclr/jit/assertionprop.cpp:4478

  • The doc comment says this returns true when range analysis proves the relop’s “non-throwing relation”, but this helper is also used for the throwing forms (e.g., start + width > limit), where “proven in-bounds” means the relop is always false. Updating the comment to describe what is actually proven (the access is in-bounds) will avoid future misuse/confusion when adding new operator shapes.
// Returns:
// True if range analysis proves the non-throwing relation represented by the relop.
//

CopilotAI review requested due to automatic review settings August 31, 2026 21:13

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Review tier: Lite
Findings: None

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 5 pipeline(s).
11 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@tannergooding

Copy link
Copy Markdown
MemberAuthor

SuperPMI shows net code-size improvements on every reported target:

TargetOverall code size
browser-wasm-971 bytes
linux-arm64-808,040 bytes
windows-arm64-683,920 bytes
osx-arm64-49,628 bytes
linux-x64-606,226 bytes
windows-x64-60,969 bytes

The absolute totals are corpus-dependent. In particular, Linux x64 includes coreclr_tests, libraries_tests, and libraries_tests_no_tiered_compilation, which are absent from the Windows x64 run and account for -535,396 bytes. Across the collections shared by both, Linux x64 is -70,830 bytes versus Windows x64 at -60,969 bytes.

Some representative wins:

  • Arm64 UInt32ToDecChars<byte>(..., digits) is -164 bytes (-43.62%), and UInt64ToDecChars<byte>(..., digits) is -164 bytes (-41.41%).
  • Arm64 Tier1 UInt32ToDecChars<char> goes from 240 to 232 bytes and 60 to 58 instructions; its reported PerfScore improves from 38.86 to 37.36 on Windows.
  • Windows x64 UInt32ToDecChars<byte>(..., digits) is -97 bytes (-34.77%), while the corresponding UInt64 method is -97 bytes (-32.23%).
  • The more general range-check improvements also show up in TimeSpanFormat.TryFormatStandard<char> (-402 bytes / -14.46% on Windows x64) and AssemblyNameFormatter.AppendDisplayName (-708 bytes on Arm64, -446 bytes on Windows x64).

Note

This comment was prepared with GitHub Copilot assistance.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Review tier: Lite
Findings: None

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@tannergooding
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Elide bounds checks in descending formatting loops - #132970

Open
tannergooding wants to merge 7 commits into
dotnet:mainfrom
tannergooding:tannergooding-investigate-tryformatl-arm64
Open

Elide bounds checks in descending formatting loops#132970
tannergooding wants to merge 7 commits into
dotnet:mainfrom
tannergooding:tannergooding-investigate-tryformatl-arm64

Conversation

@tannergooding

Copy link
Copy Markdown
Member

Adds conservative monotonically-decreasing induction-variable analysis to range-check elimination. The proof distinguishes dominating entries from backedges, accounts for overflow and subtraction, and rejects nested or irreducible phi shapes it cannot prove safely.

This lets pre-decrement loops establish both bounds for adjacent writes while retaining bounds protection when the induction variable can underflow.


Updates the UInt32 and UInt64 decimal formatters to use exact spans and safe descending pair writes through MemoryMarshal.CreateSpan and the existing WriteTwoDigits helper. This removes the hot destination bounds check involved in #132840 without architecture-specific or unsafe call-site code.

Local x64 BenchmarkDotNet results:

BenchmarkBeforeAfterChange
UTF-16 TryFormatL36.78 ns34.41 ns-6.4%
UTF-8 TryFormatUtf8L36.74 ns34.23 ns-6.8%

Arm64 AltJit disassembly confirms the destination range check is removed from the pair-write loop. Digit-table validation remains independent of this change.

Note

This pull request description was drafted by GitHub Copilot.

tannergoodingand others added 2 commits August 31, 2026 07:49
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI lite review requested due to automatic review settings August 31, 2026 14:50
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Aug 31, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 5 pipeline(s).
11 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Lite
Findings: 1 Medium severity

New issues introduced by this change (1)
SeverityFinding
Medium severitysrc/​coreclr/​jit/​rangecheck.cpp — In RangeCheck::Widen, the monotonic recomputation assigns *pRange = GetRangeWorker(...). If both…
What changed in this PR

This PR extends CoreCLR JIT range-check elimination with a conservative monotonicity direction concept (increasing vs decreasing) and updates the UInt32/UInt64 decimal formatting fast paths to use descending two-digit writes via MemoryMarshal.CreateSpan + the existing WriteTwoDigits helper, with accompanying JIT regression tests.

Changes:

  • Add Monotonicity (None/Increasing/Decreasing) to range analysis and update phi-merge/widening logic to support descending induction variables.
  • Rewrite UInt32ToDecChars/UInt64ToDecChars fast paths to write exact spans in a descending loop using WriteTwoDigits.
  • Add JIT opt tests covering descending pair writes and underflow/overflow cases to validate bounds-check behavior.
FileDescription
src/​coreclr/​jit/​rangecheck.hIntroduces Monotonicity and threads it through range computation/merge APIs.
src/​coreclr/​jit/​rangecheck.cppImplements decreasing-monotonicity proof logic and updates widening/overflow handling to consider GT_SUB and descending loops.
src/​libraries/​System.Private.CoreLib/​src/​System/​Number.Formatting.csUpdates UInt32/UInt64 decimal formatting loops to use descending two-digit span writes and adjusts destination length checks.
src/​tests/​JIT/​opt/​RangeChecks/​ElidedBoundsChecks.csAdds a descending pair-write loop test using MemoryMarshal.CreateSpan and validates results without reintroducing range-check helpers.
src/​tests/​JIT/​opt/​RangeChecks/​Overflow.csAdds a descending-underflow scenario to ensure bounds checks are not incorrectly elided when the IV can wrap.

Comment threadsrc/coreclr/jit/rangecheck.cpp Outdated
tannergoodingand others added 2 commits August 31, 2026 13:25
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 31, 2026 20:25

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Review tier: Lite
Findings: 1 Low severity

New issues introduced by this change (1)
SeverityFinding
Low severitysrc/​coreclr/​jit/​rangecheck.cpp — The BetweenBounds doc comment still says it assumes an increasing loop / symbolic upper bound, but…
Issues resolved since last review (1)
SeverityFinding
Medium severitysrc/​coreclr/​jit/​rangecheck.cpp — In RangeCheck::Widen, the monotonic recomputation assigns *pRange = GetRangeWorker(...). If both… View resolved comment

Comment threadsrc/coreclr/jit/rangecheck.cpp Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 31, 2026 21:02
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Review tier: Lite
Findings: None

Issues resolved since last review (1)
SeverityFinding
Low severitysrc/​coreclr/​jit/​rangecheck.cpp — The BetweenBounds doc comment still says it assumes an increasing loop / symbolic upper bound, but… View resolved comment
Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

src/coreclr/jit/assertionprop.cpp:4478

  • The doc comment says this returns true when range analysis proves the relop’s “non-throwing relation”, but this helper is also used for the throwing forms (e.g., start + width > limit), where “proven in-bounds” means the relop is always false. Updating the comment to describe what is actually proven (the access is in-bounds) will avoid future misuse/confusion when adding new operator shapes.
// Returns:
// True if range analysis proves the non-throwing relation represented by the relop.
//

CopilotAI review requested due to automatic review settings August 31, 2026 21:13

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Review tier: Lite
Findings: None

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 5 pipeline(s).
11 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@tannergooding

Copy link
Copy Markdown
MemberAuthor

SuperPMI shows net code-size improvements on every reported target:

TargetOverall code size
browser-wasm-971 bytes
linux-arm64-808,040 bytes
windows-arm64-683,920 bytes
osx-arm64-49,628 bytes
linux-x64-606,226 bytes
windows-x64-60,969 bytes

The absolute totals are corpus-dependent. In particular, Linux x64 includes coreclr_tests, libraries_tests, and libraries_tests_no_tiered_compilation, which are absent from the Windows x64 run and account for -535,396 bytes. Across the collections shared by both, Linux x64 is -70,830 bytes versus Windows x64 at -60,969 bytes.

Some representative wins:

  • Arm64 UInt32ToDecChars<byte>(..., digits) is -164 bytes (-43.62%), and UInt64ToDecChars<byte>(..., digits) is -164 bytes (-41.41%).
  • Arm64 Tier1 UInt32ToDecChars<char> goes from 240 to 232 bytes and 60 to 58 instructions; its reported PerfScore improves from 38.86 to 37.36 on Windows.
  • Windows x64 UInt32ToDecChars<byte>(..., digits) is -97 bytes (-34.77%), while the corresponding UInt64 method is -97 bytes (-32.23%).
  • The more general range-check improvements also show up in TimeSpanFormat.TryFormatStandard<char> (-402 bytes / -14.46% on Windows x64) and AssemblyNameFormatter.AppendDisplayName (-708 bytes on Arm64, -446 bytes on Windows x64).

Note

This comment was prepared with GitHub Copilot assistance.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Review tier: Lite
Findings: None

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@tannergooding
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Elide bounds checks in descending formatting loops - #132970

Open
tannergooding wants to merge 7 commits into
dotnet:mainfrom
tannergooding:tannergooding-investigate-tryformatl-arm64
Open

Elide bounds checks in descending formatting loops#132970
tannergooding wants to merge 7 commits into
dotnet:mainfrom
tannergooding:tannergooding-investigate-tryformatl-arm64

Conversation

@tannergooding

Copy link
Copy Markdown
Member

Adds conservative monotonically-decreasing induction-variable analysis to range-check elimination. The proof distinguishes dominating entries from backedges, accounts for overflow and subtraction, and rejects nested or irreducible phi shapes it cannot prove safely.

This lets pre-decrement loops establish both bounds for adjacent writes while retaining bounds protection when the induction variable can underflow.


Updates the UInt32 and UInt64 decimal formatters to use exact spans and safe descending pair writes through MemoryMarshal.CreateSpan and the existing WriteTwoDigits helper. This removes the hot destination bounds check involved in #132840 without architecture-specific or unsafe call-site code.

Local x64 BenchmarkDotNet results:

BenchmarkBeforeAfterChange
UTF-16 TryFormatL36.78 ns34.41 ns-6.4%
UTF-8 TryFormatUtf8L36.74 ns34.23 ns-6.8%

Arm64 AltJit disassembly confirms the destination range check is removed from the pair-write loop. Digit-table validation remains independent of this change.

Note

This pull request description was drafted by GitHub Copilot.

tannergoodingand others added 2 commits August 31, 2026 07:49
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI lite review requested due to automatic review settings August 31, 2026 14:50
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Aug 31, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 5 pipeline(s).
11 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Lite
Findings: 1 Medium severity

New issues introduced by this change (1)
SeverityFinding
Medium severitysrc/​coreclr/​jit/​rangecheck.cpp — In RangeCheck::Widen, the monotonic recomputation assigns *pRange = GetRangeWorker(...). If both…
What changed in this PR

This PR extends CoreCLR JIT range-check elimination with a conservative monotonicity direction concept (increasing vs decreasing) and updates the UInt32/UInt64 decimal formatting fast paths to use descending two-digit writes via MemoryMarshal.CreateSpan + the existing WriteTwoDigits helper, with accompanying JIT regression tests.

Changes:

  • Add Monotonicity (None/Increasing/Decreasing) to range analysis and update phi-merge/widening logic to support descending induction variables.
  • Rewrite UInt32ToDecChars/UInt64ToDecChars fast paths to write exact spans in a descending loop using WriteTwoDigits.
  • Add JIT opt tests covering descending pair writes and underflow/overflow cases to validate bounds-check behavior.
FileDescription
src/​coreclr/​jit/​rangecheck.hIntroduces Monotonicity and threads it through range computation/merge APIs.
src/​coreclr/​jit/​rangecheck.cppImplements decreasing-monotonicity proof logic and updates widening/overflow handling to consider GT_SUB and descending loops.
src/​libraries/​System.Private.CoreLib/​src/​System/​Number.Formatting.csUpdates UInt32/UInt64 decimal formatting loops to use descending two-digit span writes and adjusts destination length checks.
src/​tests/​JIT/​opt/​RangeChecks/​ElidedBoundsChecks.csAdds a descending pair-write loop test using MemoryMarshal.CreateSpan and validates results without reintroducing range-check helpers.
src/​tests/​JIT/​opt/​RangeChecks/​Overflow.csAdds a descending-underflow scenario to ensure bounds checks are not incorrectly elided when the IV can wrap.

Comment threadsrc/coreclr/jit/rangecheck.cpp Outdated
tannergoodingand others added 2 commits August 31, 2026 13:25
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 31, 2026 20:25

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Review tier: Lite
Findings: 1 Low severity

New issues introduced by this change (1)
SeverityFinding
Low severitysrc/​coreclr/​jit/​rangecheck.cpp — The BetweenBounds doc comment still says it assumes an increasing loop / symbolic upper bound, but…
Issues resolved since last review (1)
SeverityFinding
Medium severitysrc/​coreclr/​jit/​rangecheck.cpp — In RangeCheck::Widen, the monotonic recomputation assigns *pRange = GetRangeWorker(...). If both… View resolved comment

Comment threadsrc/coreclr/jit/rangecheck.cpp Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 31, 2026 21:02
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Review tier: Lite
Findings: None

Issues resolved since last review (1)
SeverityFinding
Low severitysrc/​coreclr/​jit/​rangecheck.cpp — The BetweenBounds doc comment still says it assumes an increasing loop / symbolic upper bound, but… View resolved comment
Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

src/coreclr/jit/assertionprop.cpp:4478

  • The doc comment says this returns true when range analysis proves the relop’s “non-throwing relation”, but this helper is also used for the throwing forms (e.g., start + width > limit), where “proven in-bounds” means the relop is always false. Updating the comment to describe what is actually proven (the access is in-bounds) will avoid future misuse/confusion when adding new operator shapes.
// Returns:
// True if range analysis proves the non-throwing relation represented by the relop.
//

CopilotAI review requested due to automatic review settings August 31, 2026 21:13

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Review tier: Lite
Findings: None

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 5 pipeline(s).
11 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@tannergooding

Copy link
Copy Markdown
MemberAuthor

SuperPMI shows net code-size improvements on every reported target:

TargetOverall code size
browser-wasm-971 bytes
linux-arm64-808,040 bytes
windows-arm64-683,920 bytes
osx-arm64-49,628 bytes
linux-x64-606,226 bytes
windows-x64-60,969 bytes

The absolute totals are corpus-dependent. In particular, Linux x64 includes coreclr_tests, libraries_tests, and libraries_tests_no_tiered_compilation, which are absent from the Windows x64 run and account for -535,396 bytes. Across the collections shared by both, Linux x64 is -70,830 bytes versus Windows x64 at -60,969 bytes.

Some representative wins:

  • Arm64 UInt32ToDecChars<byte>(..., digits) is -164 bytes (-43.62%), and UInt64ToDecChars<byte>(..., digits) is -164 bytes (-41.41%).
  • Arm64 Tier1 UInt32ToDecChars<char> goes from 240 to 232 bytes and 60 to 58 instructions; its reported PerfScore improves from 38.86 to 37.36 on Windows.
  • Windows x64 UInt32ToDecChars<byte>(..., digits) is -97 bytes (-34.77%), while the corresponding UInt64 method is -97 bytes (-32.23%).
  • The more general range-check improvements also show up in TimeSpanFormat.TryFormatStandard<char> (-402 bytes / -14.46% on Windows x64) and AssemblyNameFormatter.AppendDisplayName (-708 bytes on Arm64, -446 bytes on Windows x64).

Note

This comment was prepared with GitHub Copilot assistance.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Review tier: Lite
Findings: None

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@tannergooding
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Elide bounds checks in descending formatting loops - #132970

Open
tannergooding wants to merge 7 commits into
dotnet:mainfrom
tannergooding:tannergooding-investigate-tryformatl-arm64
Open

Elide bounds checks in descending formatting loops#132970
tannergooding wants to merge 7 commits into
dotnet:mainfrom
tannergooding:tannergooding-investigate-tryformatl-arm64

Conversation

@tannergooding

Copy link
Copy Markdown
Member

Adds conservative monotonically-decreasing induction-variable analysis to range-check elimination. The proof distinguishes dominating entries from backedges, accounts for overflow and subtraction, and rejects nested or irreducible phi shapes it cannot prove safely.

This lets pre-decrement loops establish both bounds for adjacent writes while retaining bounds protection when the induction variable can underflow.


Updates the UInt32 and UInt64 decimal formatters to use exact spans and safe descending pair writes through MemoryMarshal.CreateSpan and the existing WriteTwoDigits helper. This removes the hot destination bounds check involved in #132840 without architecture-specific or unsafe call-site code.

Local x64 BenchmarkDotNet results:

BenchmarkBeforeAfterChange
UTF-16 TryFormatL36.78 ns34.41 ns-6.4%
UTF-8 TryFormatUtf8L36.74 ns34.23 ns-6.8%

Arm64 AltJit disassembly confirms the destination range check is removed from the pair-write loop. Digit-table validation remains independent of this change.

Note

This pull request description was drafted by GitHub Copilot.

tannergoodingand others added 2 commits August 31, 2026 07:49
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI lite review requested due to automatic review settings August 31, 2026 14:50
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Aug 31, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 5 pipeline(s).
11 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Lite
Findings: 1 Medium severity

New issues introduced by this change (1)
SeverityFinding
Medium severitysrc/​coreclr/​jit/​rangecheck.cpp — In RangeCheck::Widen, the monotonic recomputation assigns *pRange = GetRangeWorker(...). If both…
What changed in this PR

This PR extends CoreCLR JIT range-check elimination with a conservative monotonicity direction concept (increasing vs decreasing) and updates the UInt32/UInt64 decimal formatting fast paths to use descending two-digit writes via MemoryMarshal.CreateSpan + the existing WriteTwoDigits helper, with accompanying JIT regression tests.

Changes:

  • Add Monotonicity (None/Increasing/Decreasing) to range analysis and update phi-merge/widening logic to support descending induction variables.
  • Rewrite UInt32ToDecChars/UInt64ToDecChars fast paths to write exact spans in a descending loop using WriteTwoDigits.
  • Add JIT opt tests covering descending pair writes and underflow/overflow cases to validate bounds-check behavior.
FileDescription
src/​coreclr/​jit/​rangecheck.hIntroduces Monotonicity and threads it through range computation/merge APIs.
src/​coreclr/​jit/​rangecheck.cppImplements decreasing-monotonicity proof logic and updates widening/overflow handling to consider GT_SUB and descending loops.
src/​libraries/​System.Private.CoreLib/​src/​System/​Number.Formatting.csUpdates UInt32/UInt64 decimal formatting loops to use descending two-digit span writes and adjusts destination length checks.
src/​tests/​JIT/​opt/​RangeChecks/​ElidedBoundsChecks.csAdds a descending pair-write loop test using MemoryMarshal.CreateSpan and validates results without reintroducing range-check helpers.
src/​tests/​JIT/​opt/​RangeChecks/​Overflow.csAdds a descending-underflow scenario to ensure bounds checks are not incorrectly elided when the IV can wrap.

Comment threadsrc/coreclr/jit/rangecheck.cpp Outdated
tannergoodingand others added 2 commits August 31, 2026 13:25
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 31, 2026 20:25

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Review tier: Lite
Findings: 1 Low severity

New issues introduced by this change (1)
SeverityFinding
Low severitysrc/​coreclr/​jit/​rangecheck.cpp — The BetweenBounds doc comment still says it assumes an increasing loop / symbolic upper bound, but…
Issues resolved since last review (1)
SeverityFinding
Medium severitysrc/​coreclr/​jit/​rangecheck.cpp — In RangeCheck::Widen, the monotonic recomputation assigns *pRange = GetRangeWorker(...). If both… View resolved comment

Comment threadsrc/coreclr/jit/rangecheck.cpp Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 31, 2026 21:02
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Review tier: Lite
Findings: None

Issues resolved since last review (1)
SeverityFinding
Low severitysrc/​coreclr/​jit/​rangecheck.cpp — The BetweenBounds doc comment still says it assumes an increasing loop / symbolic upper bound, but… View resolved comment
Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

src/coreclr/jit/assertionprop.cpp:4478

  • The doc comment says this returns true when range analysis proves the relop’s “non-throwing relation”, but this helper is also used for the throwing forms (e.g., start + width > limit), where “proven in-bounds” means the relop is always false. Updating the comment to describe what is actually proven (the access is in-bounds) will avoid future misuse/confusion when adding new operator shapes.
// Returns:
// True if range analysis proves the non-throwing relation represented by the relop.
//

CopilotAI review requested due to automatic review settings August 31, 2026 21:13

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Review tier: Lite
Findings: None

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 5 pipeline(s).
11 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@tannergooding

Copy link
Copy Markdown
MemberAuthor

SuperPMI shows net code-size improvements on every reported target:

TargetOverall code size
browser-wasm-971 bytes
linux-arm64-808,040 bytes
windows-arm64-683,920 bytes
osx-arm64-49,628 bytes
linux-x64-606,226 bytes
windows-x64-60,969 bytes

The absolute totals are corpus-dependent. In particular, Linux x64 includes coreclr_tests, libraries_tests, and libraries_tests_no_tiered_compilation, which are absent from the Windows x64 run and account for -535,396 bytes. Across the collections shared by both, Linux x64 is -70,830 bytes versus Windows x64 at -60,969 bytes.

Some representative wins:

  • Arm64 UInt32ToDecChars<byte>(..., digits) is -164 bytes (-43.62%), and UInt64ToDecChars<byte>(..., digits) is -164 bytes (-41.41%).
  • Arm64 Tier1 UInt32ToDecChars<char> goes from 240 to 232 bytes and 60 to 58 instructions; its reported PerfScore improves from 38.86 to 37.36 on Windows.
  • Windows x64 UInt32ToDecChars<byte>(..., digits) is -97 bytes (-34.77%), while the corresponding UInt64 method is -97 bytes (-32.23%).
  • The more general range-check improvements also show up in TimeSpanFormat.TryFormatStandard<char> (-402 bytes / -14.46% on Windows x64) and AssemblyNameFormatter.AppendDisplayName (-708 bytes on Arm64, -446 bytes on Windows x64).

Note

This comment was prepared with GitHub Copilot assistance.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Review tier: Lite
Findings: None

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@tannergooding
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Elide bounds checks in descending formatting loops - #132970

Open
tannergooding wants to merge 7 commits into
dotnet:mainfrom
tannergooding:tannergooding-investigate-tryformatl-arm64
Open

Elide bounds checks in descending formatting loops#132970
tannergooding wants to merge 7 commits into
dotnet:mainfrom
tannergooding:tannergooding-investigate-tryformatl-arm64

Conversation

@tannergooding

Copy link
Copy Markdown
Member

Adds conservative monotonically-decreasing induction-variable analysis to range-check elimination. The proof distinguishes dominating entries from backedges, accounts for overflow and subtraction, and rejects nested or irreducible phi shapes it cannot prove safely.

This lets pre-decrement loops establish both bounds for adjacent writes while retaining bounds protection when the induction variable can underflow.


Updates the UInt32 and UInt64 decimal formatters to use exact spans and safe descending pair writes through MemoryMarshal.CreateSpan and the existing WriteTwoDigits helper. This removes the hot destination bounds check involved in #132840 without architecture-specific or unsafe call-site code.

Local x64 BenchmarkDotNet results:

BenchmarkBeforeAfterChange
UTF-16 TryFormatL36.78 ns34.41 ns-6.4%
UTF-8 TryFormatUtf8L36.74 ns34.23 ns-6.8%

Arm64 AltJit disassembly confirms the destination range check is removed from the pair-write loop. Digit-table validation remains independent of this change.

Note

This pull request description was drafted by GitHub Copilot.

tannergoodingand others added 2 commits August 31, 2026 07:49
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI lite review requested due to automatic review settings August 31, 2026 14:50
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Aug 31, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 5 pipeline(s).
11 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Lite
Findings: 1 Medium severity

New issues introduced by this change (1)
SeverityFinding
Medium severitysrc/​coreclr/​jit/​rangecheck.cpp — In RangeCheck::Widen, the monotonic recomputation assigns *pRange = GetRangeWorker(...). If both…
What changed in this PR

This PR extends CoreCLR JIT range-check elimination with a conservative monotonicity direction concept (increasing vs decreasing) and updates the UInt32/UInt64 decimal formatting fast paths to use descending two-digit writes via MemoryMarshal.CreateSpan + the existing WriteTwoDigits helper, with accompanying JIT regression tests.

Changes:

  • Add Monotonicity (None/Increasing/Decreasing) to range analysis and update phi-merge/widening logic to support descending induction variables.
  • Rewrite UInt32ToDecChars/UInt64ToDecChars fast paths to write exact spans in a descending loop using WriteTwoDigits.
  • Add JIT opt tests covering descending pair writes and underflow/overflow cases to validate bounds-check behavior.
FileDescription
src/​coreclr/​jit/​rangecheck.hIntroduces Monotonicity and threads it through range computation/merge APIs.
src/​coreclr/​jit/​rangecheck.cppImplements decreasing-monotonicity proof logic and updates widening/overflow handling to consider GT_SUB and descending loops.
src/​libraries/​System.Private.CoreLib/​src/​System/​Number.Formatting.csUpdates UInt32/UInt64 decimal formatting loops to use descending two-digit span writes and adjusts destination length checks.
src/​tests/​JIT/​opt/​RangeChecks/​ElidedBoundsChecks.csAdds a descending pair-write loop test using MemoryMarshal.CreateSpan and validates results without reintroducing range-check helpers.
src/​tests/​JIT/​opt/​RangeChecks/​Overflow.csAdds a descending-underflow scenario to ensure bounds checks are not incorrectly elided when the IV can wrap.

Comment threadsrc/coreclr/jit/rangecheck.cpp Outdated
tannergoodingand others added 2 commits August 31, 2026 13:25
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 31, 2026 20:25

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Review tier: Lite
Findings: 1 Low severity

New issues introduced by this change (1)
SeverityFinding
Low severitysrc/​coreclr/​jit/​rangecheck.cpp — The BetweenBounds doc comment still says it assumes an increasing loop / symbolic upper bound, but…
Issues resolved since last review (1)
SeverityFinding
Medium severitysrc/​coreclr/​jit/​rangecheck.cpp — In RangeCheck::Widen, the monotonic recomputation assigns *pRange = GetRangeWorker(...). If both… View resolved comment

Comment threadsrc/coreclr/jit/rangecheck.cpp Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 31, 2026 21:02
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Review tier: Lite
Findings: None

Issues resolved since last review (1)
SeverityFinding
Low severitysrc/​coreclr/​jit/​rangecheck.cpp — The BetweenBounds doc comment still says it assumes an increasing loop / symbolic upper bound, but… View resolved comment
Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

src/coreclr/jit/assertionprop.cpp:4478

  • The doc comment says this returns true when range analysis proves the relop’s “non-throwing relation”, but this helper is also used for the throwing forms (e.g., start + width > limit), where “proven in-bounds” means the relop is always false. Updating the comment to describe what is actually proven (the access is in-bounds) will avoid future misuse/confusion when adding new operator shapes.
// Returns:
// True if range analysis proves the non-throwing relation represented by the relop.
//

CopilotAI review requested due to automatic review settings August 31, 2026 21:13

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Review tier: Lite
Findings: None

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 5 pipeline(s).
11 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@tannergooding

Copy link
Copy Markdown
MemberAuthor

SuperPMI shows net code-size improvements on every reported target:

TargetOverall code size
browser-wasm-971 bytes
linux-arm64-808,040 bytes
windows-arm64-683,920 bytes
osx-arm64-49,628 bytes
linux-x64-606,226 bytes
windows-x64-60,969 bytes

The absolute totals are corpus-dependent. In particular, Linux x64 includes coreclr_tests, libraries_tests, and libraries_tests_no_tiered_compilation, which are absent from the Windows x64 run and account for -535,396 bytes. Across the collections shared by both, Linux x64 is -70,830 bytes versus Windows x64 at -60,969 bytes.

Some representative wins:

  • Arm64 UInt32ToDecChars<byte>(..., digits) is -164 bytes (-43.62%), and UInt64ToDecChars<byte>(..., digits) is -164 bytes (-41.41%).
  • Arm64 Tier1 UInt32ToDecChars<char> goes from 240 to 232 bytes and 60 to 58 instructions; its reported PerfScore improves from 38.86 to 37.36 on Windows.
  • Windows x64 UInt32ToDecChars<byte>(..., digits) is -97 bytes (-34.77%), while the corresponding UInt64 method is -97 bytes (-32.23%).
  • The more general range-check improvements also show up in TimeSpanFormat.TryFormatStandard<char> (-402 bytes / -14.46% on Windows x64) and AssemblyNameFormatter.AppendDisplayName (-708 bytes on Arm64, -446 bytes on Windows x64).

Note

This comment was prepared with GitHub Copilot assistance.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Review tier: Lite
Findings: None

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@tannergooding
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Elide bounds checks in descending formatting loops - #132970

Open
tannergooding wants to merge 7 commits into
dotnet:mainfrom
tannergooding:tannergooding-investigate-tryformatl-arm64
Open

Elide bounds checks in descending formatting loops#132970
tannergooding wants to merge 7 commits into
dotnet:mainfrom
tannergooding:tannergooding-investigate-tryformatl-arm64

Conversation

@tannergooding

Copy link
Copy Markdown
Member

Adds conservative monotonically-decreasing induction-variable analysis to range-check elimination. The proof distinguishes dominating entries from backedges, accounts for overflow and subtraction, and rejects nested or irreducible phi shapes it cannot prove safely.

This lets pre-decrement loops establish both bounds for adjacent writes while retaining bounds protection when the induction variable can underflow.


Updates the UInt32 and UInt64 decimal formatters to use exact spans and safe descending pair writes through MemoryMarshal.CreateSpan and the existing WriteTwoDigits helper. This removes the hot destination bounds check involved in #132840 without architecture-specific or unsafe call-site code.

Local x64 BenchmarkDotNet results:

BenchmarkBeforeAfterChange
UTF-16 TryFormatL36.78 ns34.41 ns-6.4%
UTF-8 TryFormatUtf8L36.74 ns34.23 ns-6.8%

Arm64 AltJit disassembly confirms the destination range check is removed from the pair-write loop. Digit-table validation remains independent of this change.

Note

This pull request description was drafted by GitHub Copilot.

tannergoodingand others added 2 commits August 31, 2026 07:49
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI lite review requested due to automatic review settings August 31, 2026 14:50
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Aug 31, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 5 pipeline(s).
11 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Lite
Findings: 1 Medium severity

New issues introduced by this change (1)
SeverityFinding
Medium severitysrc/​coreclr/​jit/​rangecheck.cpp — In RangeCheck::Widen, the monotonic recomputation assigns *pRange = GetRangeWorker(...). If both…
What changed in this PR

This PR extends CoreCLR JIT range-check elimination with a conservative monotonicity direction concept (increasing vs decreasing) and updates the UInt32/UInt64 decimal formatting fast paths to use descending two-digit writes via MemoryMarshal.CreateSpan + the existing WriteTwoDigits helper, with accompanying JIT regression tests.

Changes:

  • Add Monotonicity (None/Increasing/Decreasing) to range analysis and update phi-merge/widening logic to support descending induction variables.
  • Rewrite UInt32ToDecChars/UInt64ToDecChars fast paths to write exact spans in a descending loop using WriteTwoDigits.
  • Add JIT opt tests covering descending pair writes and underflow/overflow cases to validate bounds-check behavior.
FileDescription
src/​coreclr/​jit/​rangecheck.hIntroduces Monotonicity and threads it through range computation/merge APIs.
src/​coreclr/​jit/​rangecheck.cppImplements decreasing-monotonicity proof logic and updates widening/overflow handling to consider GT_SUB and descending loops.
src/​libraries/​System.Private.CoreLib/​src/​System/​Number.Formatting.csUpdates UInt32/UInt64 decimal formatting loops to use descending two-digit span writes and adjusts destination length checks.
src/​tests/​JIT/​opt/​RangeChecks/​ElidedBoundsChecks.csAdds a descending pair-write loop test using MemoryMarshal.CreateSpan and validates results without reintroducing range-check helpers.
src/​tests/​JIT/​opt/​RangeChecks/​Overflow.csAdds a descending-underflow scenario to ensure bounds checks are not incorrectly elided when the IV can wrap.

Comment threadsrc/coreclr/jit/rangecheck.cpp Outdated
tannergoodingand others added 2 commits August 31, 2026 13:25
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 31, 2026 20:25

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Review tier: Lite
Findings: 1 Low severity

New issues introduced by this change (1)
SeverityFinding
Low severitysrc/​coreclr/​jit/​rangecheck.cpp — The BetweenBounds doc comment still says it assumes an increasing loop / symbolic upper bound, but…
Issues resolved since last review (1)
SeverityFinding
Medium severitysrc/​coreclr/​jit/​rangecheck.cpp — In RangeCheck::Widen, the monotonic recomputation assigns *pRange = GetRangeWorker(...). If both… View resolved comment

Comment threadsrc/coreclr/jit/rangecheck.cpp Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 31, 2026 21:02
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Review tier: Lite
Findings: None

Issues resolved since last review (1)
SeverityFinding
Low severitysrc/​coreclr/​jit/​rangecheck.cpp — The BetweenBounds doc comment still says it assumes an increasing loop / symbolic upper bound, but… View resolved comment
Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

src/coreclr/jit/assertionprop.cpp:4478

  • The doc comment says this returns true when range analysis proves the relop’s “non-throwing relation”, but this helper is also used for the throwing forms (e.g., start + width > limit), where “proven in-bounds” means the relop is always false. Updating the comment to describe what is actually proven (the access is in-bounds) will avoid future misuse/confusion when adding new operator shapes.
// Returns:
// True if range analysis proves the non-throwing relation represented by the relop.
//

CopilotAI review requested due to automatic review settings August 31, 2026 21:13

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Review tier: Lite
Findings: None

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 5 pipeline(s).
11 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@tannergooding

Copy link
Copy Markdown
MemberAuthor

SuperPMI shows net code-size improvements on every reported target:

TargetOverall code size
browser-wasm-971 bytes
linux-arm64-808,040 bytes
windows-arm64-683,920 bytes
osx-arm64-49,628 bytes
linux-x64-606,226 bytes
windows-x64-60,969 bytes

The absolute totals are corpus-dependent. In particular, Linux x64 includes coreclr_tests, libraries_tests, and libraries_tests_no_tiered_compilation, which are absent from the Windows x64 run and account for -535,396 bytes. Across the collections shared by both, Linux x64 is -70,830 bytes versus Windows x64 at -60,969 bytes.

Some representative wins:

  • Arm64 UInt32ToDecChars<byte>(..., digits) is -164 bytes (-43.62%), and UInt64ToDecChars<byte>(..., digits) is -164 bytes (-41.41%).
  • Arm64 Tier1 UInt32ToDecChars<char> goes from 240 to 232 bytes and 60 to 58 instructions; its reported PerfScore improves from 38.86 to 37.36 on Windows.
  • Windows x64 UInt32ToDecChars<byte>(..., digits) is -97 bytes (-34.77%), while the corresponding UInt64 method is -97 bytes (-32.23%).
  • The more general range-check improvements also show up in TimeSpanFormat.TryFormatStandard<char> (-402 bytes / -14.46% on Windows x64) and AssemblyNameFormatter.AppendDisplayName (-708 bytes on Arm64, -446 bytes on Windows x64).

Note

This comment was prepared with GitHub Copilot assistance.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Review tier: Lite
Findings: None

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@tannergooding
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Elide bounds checks in descending formatting loops - #132970

Open
tannergooding wants to merge 7 commits into
dotnet:mainfrom
tannergooding:tannergooding-investigate-tryformatl-arm64
Open

Elide bounds checks in descending formatting loops#132970
tannergooding wants to merge 7 commits into
dotnet:mainfrom
tannergooding:tannergooding-investigate-tryformatl-arm64

Conversation

@tannergooding

Copy link
Copy Markdown
Member

Adds conservative monotonically-decreasing induction-variable analysis to range-check elimination. The proof distinguishes dominating entries from backedges, accounts for overflow and subtraction, and rejects nested or irreducible phi shapes it cannot prove safely.

This lets pre-decrement loops establish both bounds for adjacent writes while retaining bounds protection when the induction variable can underflow.


Updates the UInt32 and UInt64 decimal formatters to use exact spans and safe descending pair writes through MemoryMarshal.CreateSpan and the existing WriteTwoDigits helper. This removes the hot destination bounds check involved in #132840 without architecture-specific or unsafe call-site code.

Local x64 BenchmarkDotNet results:

BenchmarkBeforeAfterChange
UTF-16 TryFormatL36.78 ns34.41 ns-6.4%
UTF-8 TryFormatUtf8L36.74 ns34.23 ns-6.8%

Arm64 AltJit disassembly confirms the destination range check is removed from the pair-write loop. Digit-table validation remains independent of this change.

Note

This pull request description was drafted by GitHub Copilot.

tannergoodingand others added 2 commits August 31, 2026 07:49
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI lite review requested due to automatic review settings August 31, 2026 14:50
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Aug 31, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 5 pipeline(s).
11 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Lite
Findings: 1 Medium severity

New issues introduced by this change (1)
SeverityFinding
Medium severitysrc/​coreclr/​jit/​rangecheck.cpp — In RangeCheck::Widen, the monotonic recomputation assigns *pRange = GetRangeWorker(...). If both…
What changed in this PR

This PR extends CoreCLR JIT range-check elimination with a conservative monotonicity direction concept (increasing vs decreasing) and updates the UInt32/UInt64 decimal formatting fast paths to use descending two-digit writes via MemoryMarshal.CreateSpan + the existing WriteTwoDigits helper, with accompanying JIT regression tests.

Changes:

  • Add Monotonicity (None/Increasing/Decreasing) to range analysis and update phi-merge/widening logic to support descending induction variables.
  • Rewrite UInt32ToDecChars/UInt64ToDecChars fast paths to write exact spans in a descending loop using WriteTwoDigits.
  • Add JIT opt tests covering descending pair writes and underflow/overflow cases to validate bounds-check behavior.
FileDescription
src/​coreclr/​jit/​rangecheck.hIntroduces Monotonicity and threads it through range computation/merge APIs.
src/​coreclr/​jit/​rangecheck.cppImplements decreasing-monotonicity proof logic and updates widening/overflow handling to consider GT_SUB and descending loops.
src/​libraries/​System.Private.CoreLib/​src/​System/​Number.Formatting.csUpdates UInt32/UInt64 decimal formatting loops to use descending two-digit span writes and adjusts destination length checks.
src/​tests/​JIT/​opt/​RangeChecks/​ElidedBoundsChecks.csAdds a descending pair-write loop test using MemoryMarshal.CreateSpan and validates results without reintroducing range-check helpers.
src/​tests/​JIT/​opt/​RangeChecks/​Overflow.csAdds a descending-underflow scenario to ensure bounds checks are not incorrectly elided when the IV can wrap.

Comment threadsrc/coreclr/jit/rangecheck.cpp Outdated
tannergoodingand others added 2 commits August 31, 2026 13:25
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 31, 2026 20:25

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Review tier: Lite
Findings: 1 Low severity

New issues introduced by this change (1)
SeverityFinding
Low severitysrc/​coreclr/​jit/​rangecheck.cpp — The BetweenBounds doc comment still says it assumes an increasing loop / symbolic upper bound, but…
Issues resolved since last review (1)
SeverityFinding
Medium severitysrc/​coreclr/​jit/​rangecheck.cpp — In RangeCheck::Widen, the monotonic recomputation assigns *pRange = GetRangeWorker(...). If both… View resolved comment

Comment threadsrc/coreclr/jit/rangecheck.cpp Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings August 31, 2026 21:02
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Review tier: Lite
Findings: None

Issues resolved since last review (1)
SeverityFinding
Low severitysrc/​coreclr/​jit/​rangecheck.cpp — The BetweenBounds doc comment still says it assumes an increasing loop / symbolic upper bound, but… View resolved comment
Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

src/coreclr/jit/assertionprop.cpp:4478

  • The doc comment says this returns true when range analysis proves the relop’s “non-throwing relation”, but this helper is also used for the throwing forms (e.g., start + width > limit), where “proven in-bounds” means the relop is always false. Updating the comment to describe what is actually proven (the access is in-bounds) will avoid future misuse/confusion when adding new operator shapes.
// Returns:
// True if range analysis proves the non-throwing relation represented by the relop.
//

CopilotAI review requested due to automatic review settings August 31, 2026 21:13

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Review tier: Lite
Findings: None

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 5 pipeline(s).
11 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@tannergooding

Copy link
Copy Markdown
MemberAuthor

SuperPMI shows net code-size improvements on every reported target:

TargetOverall code size
browser-wasm-971 bytes
linux-arm64-808,040 bytes
windows-arm64-683,920 bytes
osx-arm64-49,628 bytes
linux-x64-606,226 bytes
windows-x64-60,969 bytes

The absolute totals are corpus-dependent. In particular, Linux x64 includes coreclr_tests, libraries_tests, and libraries_tests_no_tiered_compilation, which are absent from the Windows x64 run and account for -535,396 bytes. Across the collections shared by both, Linux x64 is -70,830 bytes versus Windows x64 at -60,969 bytes.

Some representative wins:

  • Arm64 UInt32ToDecChars<byte>(..., digits) is -164 bytes (-43.62%), and UInt64ToDecChars<byte>(..., digits) is -164 bytes (-41.41%).
  • Arm64 Tier1 UInt32ToDecChars<char> goes from 240 to 232 bytes and 60 to 58 instructions; its reported PerfScore improves from 38.86 to 37.36 on Windows.
  • Windows x64 UInt32ToDecChars<byte>(..., digits) is -97 bytes (-34.77%), while the corresponding UInt64 method is -97 bytes (-32.23%).
  • The more general range-check improvements also show up in TimeSpanFormat.TryFormatStandard<char> (-402 bytes / -14.46% on Windows x64) and AssemblyNameFormatter.AppendDisplayName (-708 bytes on Arm64, -446 bytes on Windows x64).

Note

This comment was prepared with GitHub Copilot assistance.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Review tier: Lite
Findings: None

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@tannergooding