Skip to content

pmi: run all class constructors up front for deterministic -cctors output#439

Open
EgorBo wants to merge 1 commit into
dotnet:mainfrom
EgorBo:pmi-deterministic-cctors
Open

pmi: run all class constructors up front for deterministic -cctors output#439
EgorBo wants to merge 1 commit into
dotnet:mainfrom
EgorBo:pmi-deterministic-cctors

Conversation

@EgorBo

@EgorBo EgorBo commented Jun 15, 2026

Copy link
Copy Markdown
Member

Problem

PMI-based jit-diff (e.g. MihuBot's -nuget runs) produces noisy diffs even on PRs that change nothing — see MihuBot/runtime-utils#1981. The diffs show types that are randomly already statically initialized (or not) by the time a method is compiled, which changes inlining, class-init helpers and static readonly folding between the base and diff runs.

Root cause

The JIT specializes codegen based on whether a referenced type is already initialized (initClass / getStaticFieldContent): once a type's .cctor has run, the JIT can drop class-init helpers, fold static readonly fields and inline more aggressively. PrepareMethod itself does not run any .cctor, so the JIT observes whatever init state happened to exist incidentally at that moment (runtime startup, PMI's own reflection, background-thread activity). Under the parallel load of a jit-diff run that timing shifts, so the same method sees a type as initialized in one run and not in another — i.e. non-deterministic output.

Fix

When PMI runs with -cctors (which MihuBot passes by default), initialize every loaded type up front, before any method is JIT'd, instead of lazily/per-type. This walks the target assembly's whole reference closure — most importantly System.Private.CoreLib — and runs every concrete type's .cctor. With all classes deterministically initialized, initClass consistently reports INITIALIZED and codegen no longer depends on init timing.

The existing per-type cctor path is kept because it also initializes the closed generic instantiations PMI creates during the walk (e.g. Dictionary<string,int>), which the up-front pass can't enumerate. The two are complementary.

Notes:

  • Open generic definitions are skipped; .cctor failures are swallowed so initialization is exhaustive and order-independent.
  • Only affects -cctors mode; default behavior is unchanged.
  • The extra CoreLib methods jitted by running cctors are filtered out of the dasm by DOTNET_JitDisasmAssemblies in jit-dasm-pmi, so they don't appear in diffs.

cc @MihaZupan

…tput

When PMI is invoked with -cctors, initialize every loaded type before any
method is JIT'd, instead of lazily/per-type. This covers the target assembly
and its whole reference closure, most importantly System.Private.CoreLib.

The JIT specializes codegen based on whether a referenced type is already
initialized (initClass / getStaticFieldContent): once a type's .cctor has run
the JIT can drop class-init helpers, fold 'static readonly' fields and inline
more aggressively. Whether a given .cctor happened to run before a particular
method was compiled is timing-dependent, which is the main source of
non-deterministic PMI diffs under the parallel load of a jit-diff run (e.g.
types appearing randomly already-initialized or not between base and diff).

Forcing a fully and deterministically initialized class state removes that
variability. The existing per-type cctor path is kept as it also initializes
the closed generic instantiations PMI creates during the walk.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@EgorBo

EgorBo commented Jun 15, 2026

Copy link
Copy Markdown
Member Author

@dotnet/jit-contrib can someone take a look? this fixes non-determinism for @MihuBot -nuget (where it runs diffs on a big set of OSS libs).

Basically, instead of "visit a class, run its cctor, jit its methods" we have a pre-pass where we unconditionally initialize all classes first (in -cctors mode only). This kills very verbose/non-deterministic diffs

Before this PR: MihuBot/runtime-utils#1984
After this PR: MihuBot/runtime-utils#1985

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants