JIT: Fix CEA cloning when enumerator local is reassigned from an untracked source#127079
Conversation
|
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
There was a problem hiding this comment.
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::CheckForGuardedAllocationOrCopyto 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. |
…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
72c79f0 to
c73106e
Compare
… 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.
3fc42d1 to
3901ce4
Compare
|
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. |
AndyAyersMS
left a comment
There was a problem hiding this comment.
Thanks for fixing this.
Yes, we should backport.
|
/ba-g unrelated CSharpMissingShebangInFileBasedProgram failure |
|
/backport to release/10.0 |
|
Started backporting to |
Fixes #127075.
In the conditional escape analysis pipeline for GDV-guarded enumerator allocations,
CheckForGuardedAllocationOrCopyonly records stores whose data isGT_ALLOCOBJ,GT_LCL_VAR, orGT_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.CheckCanClonetherefore 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_endIndexand_array, producing wrong results for[.. head, .. Tail]-style collection expressions under Tier1+PGO.The fix records such stores as appearances so
CheckCanClonedetects multiple defs and bails out of unsafe cloning.