Skip to content

JIT: fix loop cloning logic for jagged arrays - #134153

Open
AndyAyersMS wants to merge 2 commits into
dotnet:mainfrom
AndyAyersMS:fix/133757-jagged-array-cloning
Open

AndyAyersMS wants to merge 2 commits into
dotnet:mainfrom
AndyAyersMS:fix/133757-jagged-array-cloning

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

Fixes #133757.

Loop cloning can reuse stale bounds when a loop replaces a jagged array
row, even though the base-array and index locals remain invariant. Add a
cached loop-effects check before considering inner-array bounds checks
for cloning.

Reject potential array-reference mutations and synchronization while
keeping scalar element stores and independent one-dimensional accesses
eligible. The regression runs in the merged runner, using
AggressiveOptimization on the loop helpers rather than process isolation.

Validation

  • Windows x64 Checked JIT build and jit-format.
  • All 10 regression cases pass in Regression_ro_2 with tiering enabled
    and disabled.
  • All 12 existing JIT cloning test runners pass.

Codegen and performance

  • Initial full-fix SPMI comparison on the earlier base: 14 changed contexts
    out of 987,559, net -764 bytes, with 908 matching missing contexts.
  • Subsequent barrier-only comparison on the refreshed base: no assembly
    differences across 2,805,980 processed contexts in 12 collections.
  • InProd.Inner, original 700x700 workload with a non-inlined callee:
    +0.08% to +0.17% timing point estimates across three Release comparisons.
    Paired BenchmarkDotNet runs were equivalent within a 1% threshold.
    Measurements used one pinned logical CPU on a Hyper-V Xeon 8370C VM
    with tiering disabled; they do not establish zero overhead.

Note

This PR description was generated with GitHub Copilot.

Fixes dotnet#133757.

To safely reason about bounds checks for the inner arrays of jagged arrays we
must prove those inner arrays cannot be re-assigned. Otherwise there is no
up-front check we can make that will ensure the inner array accesses remain
in bounds.

Add an analysis pass that fires when a multidimensional array is seen during
cloning that tries to prove that the inner array values are also loop
invariants; if not, then the inner array bounds checks are not considered as
part of the cloning criteria.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings September 17, 2026 19:41
@github-actions github-actions Bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Sep 17, 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.

Copilot AI 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.

🔵 Needs a closer look

The synchronization bailout lacks a deterministic regression test.

Pull request overview

This PR fixes stale jagged-array bounds assumptions during loop cloning by detecting unsafe inner-array reference changes.

Changes:

  • Adds cached loop-effect analysis for array-reference mutations and synchronization.
  • Preserves cloning for safe accesses.
  • Adds regression coverage for row replacement scenarios.
File summaries
File Reviewed changes
src/tests/JIT/Regression_ro_2/Runtime_133757.cs Adds jagged-array regression and preservation tests.
src/coreclr/jit/loopcloning.cpp Implements mutation and synchronization checks for cloning.
src/coreclr/jit/compiler.h Adds cached loop-analysis state and declarations.
Review details

Suppressed comments (1)

src/coreclr/jit/loopcloning.cpp:2589

  • This new synchronization bailout is not covered by the regression file: the added cases exercise direct/aliased/reference stores and scalar or independent reference-element stores, but no volatile, memory-barrier, or atomic update of the row slot. Add a deterministic case that replaces the jagged row through one of these synchronization paths and verifies that the subsequent iteration still throws; otherwise this safety gate can regress without a test failure.
            if (node->OperIs(GT_MEMORYBARRIER) || (node->OperIsIndir() && node->AsIndir()->IsVolatile()))
            {
                return WALK_ABORT;
  • Files reviewed: 3/3 changed files
  • Comments generated: 0
  • Review effort level: Lite

@AndyAyersMS

Copy link
Copy Markdown
Member Author

@jakobbotsch this is the PR with the fairly complex analysis.
fyi @dotnet/jit-contrib

It is likely a long-standing issue, but I still need to verify.

We are trying to prove that the values of jagged-array sub-arrays cannot change during the loop, including from synchronized writes on other threads. We do this analysis once per cloning attempt; a failed result disqualifies all jagged sub-arrays from cloning criteria.

Small number of diffs.

Copilot AI review requested due to automatic review settings September 22, 2026 21:08
@AndyAyersMS

Copy link
Copy Markdown
Member Author

@jakobbotsch ping

Copilot AI 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

The critical destination-type issue and synchronization coverage gap remain unresolved.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 High severity · 1 Medium severity

Open (2)

Comment on lines +2624 to +2626
if (node->OperIs(GT_STOREIND) && !varTypeIsGC(node->TypeGet()))
{
return WALK_CONTINUE;
Comment on lines +2587 to +2591
// Synchronization may make other-thread updates to array references observable.
//
if (node->OperIs(GT_MEMORYBARRIER) || (node->OperIsIndir() && node->AsIndir()->IsVolatile()))
{
return WALK_ABORT;
Comment on lines +2587 to +2592
// Synchronization may make other-thread updates to array references observable.
//
if (node->OperIs(GT_MEMORYBARRIER) || (node->OperIsIndir() && node->AsIndir()->IsVolatile()))
{
return WALK_ABORT;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is that sufficient? In practice we can observe the change without any explicit synchronization primitive, can't we?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good point.

Comment thread src/coreclr/jit/loopcloning.cpp

@jakobbotsch jakobbotsch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM as an improvement on the current situation, but in practice with the possibility of observing concurrent updates I am not so sure that we can really recover this optimization at all (without some kind of escape analysis).

@AndyAyersMS

Copy link
Copy Markdown
Member Author

LGTM as an improvement on the current situation, but in practice with the possibility of observing concurrent updates I am not so sure that we can really recover this optimization at all (without some kind of escape analysis).

I can try and see how often escape analysis can rescue this optimization, but aside from the inherent limitations of escape analysis, it will be limited to just the outermost array today.

Given where we're headed with unsafe it seems like we must otherwise disable this optimization. I don't know what that means for perf, but (at least for uniform dimension cases) it tilts the scales in favor of multi-dimensonal arrays. So if we disable this, we should also finish the work on bringing those up to par.

This branch has not been deployed

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

Labels

area-CodeGen-coreclr CLR 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.

JIT: (bug) Loop cloning treats a jagged-array row a[k] as loop-invariant, dropping bounds checks when the row is replaced inside the loop

3 participants