Skip to content

JIT: fix x86 GC hole in stack-to-stack struct copies#128805

Open
EgorBo wants to merge 1 commit into
dotnet:mainfrom
EgorBo:fix-x86-gc-hole-128801
Open

JIT: fix x86 GC hole in stack-to-stack struct copies#128805
EgorBo wants to merge 1 commit into
dotnet:mainfrom
EgorBo:fix-x86-gc-hole-128801

Conversation

@EgorBo
Copy link
Copy Markdown
Member

@EgorBo EgorBo commented May 30, 2026

On x86 (JIT32_GCENCODER), LowerCopyBlockStore unconditionally set doCpObj = false for stack-target struct copies, but only set the matching gtBlkOpGcUnsafe = true on non-JIT32 targets. The struct copy then took the plain unroll path (genCodeForCpBlkUnroll), which emits the copy as INS_mov through a scratch register without reporting the register as a byref and without disabling GC. A GC between the load and store of the byref slot left the scratch register stale; the stale value was then stored to the local, which the GC info correctly reports as a byref, now pointing into a free object.

Fix: guard the isNotHeap shortcut behind !JIT32_GCENCODER so x86 stays on the GC-aware CpObj path (per-slot decomposition for small structs, bulk write-barrier helper for large ones). Also drop the !IsAddressNotOnHeap assert in LowerBlockStoreAsGcBulkCopyCall since the helper already handles off-heap destinations correctly (its notInHeap check skips GCHeapMemoryBarrier), so it is a valid fallback for large stack-target copies.

Fixes #128801

Copilot AI review requested due to automatic review settings May 30, 2026 17:13
@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 May 30, 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.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 CoreCLR JIT (x86 JIT32_GCENCODER) GC safety hole when lowering stack-to-stack struct copies containing byrefs/GC pointers, ensuring the copy path remains GC-aware instead of using an unrolled copy sequence that can create an unreported byref temporary.

Changes:

  • In LowerCopyBlockStore, prevents the isNotHeap shortcut from disabling CpObj on JIT32_GCENCODER, keeping x86 on the GC-aware decomposition/bulk-helper path.
  • Removes an assert in LowerBlockStoreAsGcBulkCopyCall that unnecessarily restricted using the bulk write-barrier helper for off-heap destinations.
  • Adds a new JIT regression test for the scenario (Span copy + Task.WhenAll stress) under JitBlue/Runtime_128801.

Reviewed changes

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

File Description
src/coreclr/jit/lower.cpp Keeps x86 JIT32 on the GC-aware struct-copy path; relaxes an overly strict assert for bulk write-barrier lowering.
src/tests/JIT/Regression/JitBlue/Runtime_128801/Runtime_128801.csproj Adds a new JitBlue test project with process isolation and env var configuration (TieredCompilation/GCStress).
src/tests/JIT/Regression/JitBlue/Runtime_128801/Runtime_128801.cs Adds the regression test body (Span copy micro-test + Task.WhenAll stress).

Comment thread src/tests/JIT/Regression/JitBlue/Runtime_128801/Runtime_128801.cs
Comment thread src/tests/JIT/Regression/JitBlue/Runtime_128801/Runtime_128801.cs
Comment thread src/tests/JIT/Regression/JitBlue/Runtime_128801/Runtime_128801.csproj Outdated
Comment thread src/coreclr/jit/lower.cpp Outdated
@EgorBo EgorBo force-pushed the fix-x86-gc-hole-128801 branch from dd7f641 to 4873b96 Compare May 30, 2026 17:21
In LowerCopyBlockStore, the 'isNotHeap' optimization unconditionally set
doCpObj=false but only set gtBlkOpGcUnsafe=true on non-JIT32 targets.
On x86 (JIT32_GCENCODER) this routed stack-to-stack struct copies to the
plain unroll path, which emits the copy as INS_mov through a scratch
register without reporting the register as a byref and without disabling
GC. A GC between the load and store of the byref slot left the scratch
register stale; the stale value was then stored to the local, which the
GC info correctly reports as a byref, pointing into a free object.

Guard the 'isNotHeap' shortcut behind !JIT32_GCENCODER so x86 stays on
the GC-aware CpObj path (per-slot decomposition for small structs, bulk
write-barrier helper for large ones). Drop the !IsAddressNotOnHeap
assert in LowerBlockStoreAsGcBulkCopyCall: the helper already handles
off-heap destinations correctly (its notInHeap check skips
GCHeapMemoryBarrier), so it is a valid fallback for large stack-target
copies.

Fixes dotnet#128801
Copilot AI review requested due to automatic review settings May 30, 2026 17:23
@EgorBo EgorBo force-pushed the fix-x86-gc-hole-128801 branch from 4873b96 to 2baf9bb Compare May 30, 2026 17:23
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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/tests/JIT/Regression/JitBlue/Runtime_128801/Runtime_128801.cs
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.

Bad GC info during ReadOnlySpan<T> byref copy

3 participants