Skip to content

JIT: Preserve whole struct definitions through async liveness - #135185

Closed
Garume wants to merge 1 commit into
dotnet:mainfrom
Garume:jit/async-full-definitions-133415
Closed

Garume wants to merge 1 commit into
dotnet:mainfrom
Garume:jit/async-full-definitions-133415

Conversation

@Garume

@Garume Garume commented Oct 4, 2026

Copy link
Copy Markdown

Physical promotion can split a whole struct assignment into partial stores before runtime-async liveness runs. That loses the full-definition kill and can capture a struct from the previous loop iteration even though it is overwritten before its next use.

Preserve eligible whole-local copies and initialization as STORE_LCL_VAR(FIELD_LIST(...)) through async liveness, including sparse and empty lists. Expand them during lowering after every operand has been evaluated, preserving snapshot, exception, and overlapping-remainder ordering. Keep sparse lists out of forward substitution into unrelated destinations, and recognize all-zero lists during async default-value analysis.

The change applies to true whole-local definitions; indirect/full-block copies, regularly promoted destinations, implicit-byref parameters, and x86 varargs-cookie parameters keep their existing handling.

Tests

The regression exercises held suspensions, compacting GC, whole copies, initialization, partial writes, exception paths, overlapping remainders, empty definitions, and live zero values. It also includes an optimizing-x64 lifetime check: a weakly tracked object from an overwritten struct must be collectible while the next continuation is held. This check is skipped under MinOpts/JIT stress, Debug VMs, and interpreter execution; the semantic test continues to run.

Fresh validation after applying to cfe8a6c46009:

  • Windows x64 Release and Checked JIT builds passed with no warnings or errors, using the coherent Release VM/CoreLib from 6f1d9331b9b4 (upstream JIT sources are unchanged between these base commits).
  • The existing generated async suite passed all 147 entries with the fresh Checked JIT, with no failures or skips.
  • The updated standalone regression passed with the Checked JIT under normal execution, MinOpts, JitStress=1/2, JitStressRegs=8, GCStress=0xC, and disabled hardware intrinsics. Normal execution also passed with the Release JIT.
  • The new lifetime check failed with the baseline Release and Checked JITs and passed with both fixed JITs. Checked-JIT dumps confirm a whole-definition kill and a continuation layout change from 64 bytes/four GC references to 40 bytes/three GC references.
  • C# whitespace verification and the full Windows x64 jitformat.py configuration matrix passed.

Earlier validation of the product patch against 6f1d9331b9b4 passed the 147-entry async suite on Linux x64 and all eight Windows x64/WOW64 x86 baseline/fixed × Release/Checked JIT cells. The x86 whole-copy control already uses 28 bytes and is unchanged; the empty-definition case shrinks from 72 to 40 bytes on x64 and from 60 to 28 bytes on x86. The newly added lifetime check was validated on Windows x64.

BenchmarkDotNet

Retained same-input measurements versus unmodified 6f1d9331b9b4: Windows build 26200, AMD Ryzen AI 9 HX 370, x64, .NET 11, BDN 0.16.0 nightly. Baseline/fixed Release JITs ran on the same Release VM/CoreLib and byte-identical managed benchmark input, with ReadyToRun, tiered compilation, and tiered PGO disabled. The forward run used two launches, six warmups, and fifteen 250 ms measurement iterations per case; a separate reverse-order run was also checked.

For CopyDecomposedSuspending, an operation includes the entire loop:

Iterations per operation Baseline mean (ns) Fixed mean (ns) Baseline allocated Fixed allocated
1 112.43 114.97 296 B 272 B
8 538.48 522.18 464 B 440 B
64 3,908.19 3,906.64 1,808 B 1,784 B

The saving is 24 bytes per complete suspended operation, on the initial capture rather than each resume. Linux x64 reproduced the same allocation reduction. Completed-await allocations (136/304/1,648 bytes) and the full-block suspending control (272/440/1,784 bytes) were unchanged. Timing was noisy across the runs and does not establish a general throughput improvement. These BDN results were retained from the supplied product-patch evidence; they were not rerun during this PR preparation.

Earlier SuperPMI replays had 164,972 comparable successful Linux compilations and 91,692 on Windows, with six/eight assembly differences and net native code-size changes of -149/-147 bytes. Matching missing-data contexts were excluded from these coverage counts; individual code-size increases remain in a few contexts.

Resolves #133415

Note

This pull request description was generated with assistance from OpenAI Codex.

@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 Oct 4, 2026
@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Oct 4, 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.

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 community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JIT: We should try to maintain full defs through async liveness

1 participant