Skip to content

JIT: treat static base helpers that may run a cctor as heap writes - #136106

Draft
lewing wants to merge 2 commits into
dotnet:mainfrom
lewing:lewing/jit-cctor-heap-effects
Draft

lewing wants to merge 2 commits into
dotnet:mainfrom
lewing:lewing/jit-cctor-heap-effects

Conversation

@lewing

@lewing lewing commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

For beforefieldinit classes, static base helper calls carry GTF_CALL_HOISTABLE. Loop hoisting reads it as "may run the cctor early", which is legal. Value numbering and the loop memory summary also read it as "doesn't modify the heap", which is wrong when the call runs the cctor, so a static read after the access can reuse a value loaded before the cctor ran:

int before = Counter; int value = G.Value; return (before, value, Counter); // (0, 1, 0) with TC=0 or in R2R

Every helper that may run a cctor now counts as a heap write in VN and the loop summary; the flag keeps only its hoisting meaning. The cost is small FullOpts regressions, at most 0.01% of any collection.

Resolves #136042

SuperPMI: diffs

Local SuperPMI breakdown and typical regressions
Target Collection Bytes Contexts with diffs
osx-arm64 benchmarks.run +1,488 149
osx-arm64 libraries.crossgen2 +1,120 311
linux-x64 libraries.crossgen2 +1,238 564
linux-x64 benchmarks.run +2,064 230
win-x64 libraries.crossgen2 +1,359 621
win-x64 benchmarks.run +1,400 147
  • A static read in a loop containing a static base call stays in the loop (LowLevelPerf:GenericClassGenericStaticField reloads the loop bound).
  • A heap read is no longer reused across a static base call (Lock:Exit reloads a field after READYTORUN_THREADSTATIC_BASE).

A less conservative model (tracking classes already initialized on dominating paths) is possible but not needed for correctness.

Note

This PR description was drafted with GitHub Copilot.

lewing and others added 2 commits October 18, 2026 21:40
GTF_CALL_HOISTABLE on a static base helper means the class is
beforefieldinit, so the class constructor may run earlier and the call
can be hoisted. Value numbering and the loop memory summary also used
the flag to treat the call as not modifying the heap. The cctor still
modifies the heap when the call runs it, so a static read after the
access could reuse a value read before the cctor ran, and a static read
in a loop could be hoisted above the first iteration's cctor.

Always treat a helper that may run a cctor as a heap write in value
numbering and the loop memory summary. Loop hoisting still uses the
flag to hoist the call itself.

Fixes dotnet#136042

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 54a50cd7-49f7-4a8f-b719-8ac44ccc806a
Fold the cctor heap effect into the modHeap initialization in
fgValueNumberHelperCall and shorten the related comments.

Give each static class in Runtime_136042 its own counter so the
expected values do not depend on the order in which the class
constructors run.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 54a50cd7-49f7-4a8f-b719-8ac44ccc806a
@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 19, 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.

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: static field read after a beforefieldinit static constructor can observe the pre-constructor value

1 participant