Skip to content

JIT: Fix CEA cloning when enumerator local is reassigned from an untracked source - #127079

Merged
EgorBo merged 5 commits into
dotnet:mainfrom
EgorBo:fix-127075-cea-enumerator-reuse
Jun 2, 2026
Merged

EgorBo merged 5 commits into
dotnet:mainfrom
EgorBo:fix-127075-cea-enumerator-reuse

Conversation

@EgorBo

@EgorBo EgorBo commented Apr 17, 2026

Copy link
Copy Markdown
Member

Fixes #127075.

In the conditional escape analysis pipeline for GDV-guarded enumerator allocations, CheckForGuardedAllocationOrCopy only records stores whose data is GT_ALLOCOBJ, GT_LCL_VAR, or GT_BOX. Stores from other sources (e.g. a virtual call that did not devirtualize) were silently ignored, so a second def of a shared enumerator local was never recorded.

CheckCanClone therefore saw only a single def and proceeded to clone, reusing the same stack-allocated enumerator slot for both loops. The second loop's fast-path guard still passed (method table slot retains the first constructor's write), so it iterated using the first enumerator's _endIndex and _array, producing wrong results for [.. head, .. Tail]-style collection expressions under Tier1+PGO.

The fix records such stores as appearances so CheckCanClone detects multiple defs and bails out of unsafe cloning.

Copilot AI review requested due to automatic review settings April 17, 2026 12:17
@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 Apr 17, 2026
@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.

Pull request overview

Fixes a JIT conditional escape analysis (CEA) miscompile by ensuring “unrecognized” stores to tracked GDV-guarded enumerator locals are recorded as appearances, so cloning is rejected when the enumerator local is multiply-defined.

Changes:

  • Extend ObjectAllocator::CheckForGuardedAllocationOrCopy to record appearances for stores from previously-untracked sources into tracked enumerator locals (preventing unsafe cloning).
  • Add a JIT regression test that exercises [.. head, .. tail] collection expression spread under Tiered Compilation + PGO.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/coreclr/jit/objectalloc.cpp Records unrecognized enumerator-local defs as appearances so CheckCanClone detects multiple defs and bails out.
src/tests/JIT/Regression/JitBlue/Runtime_127075/Runtime_127075.csproj Configures isolated test execution with Tiered Compilation + Tiered PGO (+ QuickJitForLoops).
src/tests/JIT/Regression/JitBlue/Runtime_127075/Runtime_127075.cs Adds regression coverage for [.. head, .. tail] spread scenario that previously produced wrong results.

Comment thread src/tests/JIT/Regression/JitBlue/Runtime_127075/Runtime_127075.cs Outdated
Comment thread src/coreclr/jit/objectalloc.cpp Outdated
Copilot AI review requested due to automatic review settings April 17, 2026 20:18

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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

Comment thread src/coreclr/jit/objectalloc.cpp Outdated
Comment thread src/tests/JIT/Regression/JitBlue/Runtime_127075/Runtime_127075.cs
…cognized source

In the conditional escape analysis (CEA) pipeline for GDV-guarded

enumerator allocations, CheckForGuardedAllocationOrCopy only recorded

stores whose data was GT_ALLOCOBJ, GT_LCL_VAR, or GT_BOX. Stores whose

data was something else (e.g. a non-devirtualized virtual call) were

silently ignored, so a reassignment of the enumerator local in a second

spread loop was never recorded as a def.

CheckCanClone therefore saw the local as single-def and proceeded to

clone, renaming all uses of the local in the cloned region to a new

local that held the first allocation. The second loop's body ended up

iterating over the first source array.

Record such stores as appearances so the multiple-defs check fires and

we bail out of unsafe cloning. Skip null/zero constant stores: the

inliner emits these to clear dead inlinee gc-ref temps and treating

them as real defs would pessimize unrelated methods.

Fixes dotnet#127075

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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comment thread src/coreclr/jit/objectalloc.cpp Outdated
… source

Direct I[] -> IReadOnlyCollection<I> assignment lets Tier1+PGO devirt the

second GetEnumerator() into an inlined SZGenericArrayEnumerator allocation,

so the buggy code path (where the second store is a non-devirt virtual call

into the shared enumerator local) never executes and the test passes even

against the broken JIT. Storing a collection literal directly to the

interface type forces Roslyn's <>z__ReadOnlyArray<I> wrapper, whose

GetEnumerator() remains a virtual call and exercises the fix.
@EgorBo
EgorBo force-pushed the fix-127075-cea-enumerator-reuse branch from 3fc42d1 to 3901ce4 Compare June 1, 2026 19:27
Copilot AI review requested due to automatic review settings June 1, 2026 19:32

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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comment thread src/coreclr/jit/objectalloc.cpp Outdated
@EgorBo
EgorBo marked this pull request as ready for review June 2, 2026 12:24
Copilot AI review requested due to automatic review settings June 2, 2026 12:24

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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

@EgorBo

EgorBo commented Jun 2, 2026

Copy link
Copy Markdown
Member Author

PTAL @AndyAyersMS bug in the EA for iterators, we might want to backport it. No diffs cc @dotnet/jit-contrib

PS: I checked that the added tests fails if I remove the fix on CI.

@EgorBo
EgorBo requested a review from AndyAyersMS June 2, 2026 15:37

@AndyAyersMS AndyAyersMS 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.

Thanks for fixing this.

Yes, we should backport.

@EgorBo

EgorBo commented Jun 2, 2026

Copy link
Copy Markdown
Member Author

/ba-g unrelated CSharpMissingShebangInFileBasedProgram failure

@EgorBo
EgorBo merged commit b3db78f into dotnet:main Jun 2, 2026
136 of 143 checks passed
@EgorBo

EgorBo commented Jun 2, 2026

Copy link
Copy Markdown
Member Author

/backport to release/10.0

@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Started backporting to release/10.0 (link to workflow run)

@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 11.0-preview6 milestone Jun 3, 2026
JulieLeeMSFT pushed a commit that referenced this pull request Jun 9, 2026
…ed from an untracked source (#128909)

Backport of #127079 to release/10.0

/cc @EgorBo

## Customer Impact

- [x] Customer reported
- [ ] Found internally

Customer-reported (#127075). Silent data loss: a C# collection
expression spread with two sources, `[.. a, .. b]`, miscompiles under
Tier1 + PGO. The result array has the correct length but the tail
elements coming from the second source are left at their default value
(`null` for reference types) because the second copy loop becomes dead
code.

A bug in JIT's escape analysis.

## Regression

- [x] Yes
- [ ] No

Introduced in .NET 10

## Testing

Added a standalone JIT regression test
(`src/tests/JIT/Regression/JitBlue/Runtime_127075`) that reproduces the
wrong codegen under Tier1 + PGO.

## Risk

Low

---------

Co-authored-by: Egor Bogatov <egorbo@gmail.com>
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 3, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

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 Tier1 miscompiles [.. array, .. collection] — second copy loop is dead (NET 10.0.6)

3 participants