pmi: run all class constructors up front for deterministic -cctors output#439
Open
EgorBo wants to merge 1 commit into
Open
pmi: run all class constructors up front for deterministic -cctors output#439EgorBo wants to merge 1 commit into
EgorBo wants to merge 1 commit into
Conversation
…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>
MihaZupan
approved these changes
Jun 15, 2026
Member
Author
|
@dotnet/jit-contrib can someone take a look? this fixes non-determinism for 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 Before this PR: MihuBot/runtime-utils#1984 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
PMI-based jit-diff (e.g. MihuBot's
-nugetruns) 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 andstatic readonlyfolding 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.cctorhas run, the JIT can drop class-init helpers, foldstatic readonlyfields and inline more aggressively.PrepareMethoditself 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 importantlySystem.Private.CoreLib— and runs every concrete type's.cctor. With all classes deterministically initialized,initClassconsistently 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:
.cctorfailures are swallowed so initialization is exhaustive and order-independent.-cctorsmode; default behavior is unchanged.DOTNET_JitDisasmAssembliesin jit-dasm-pmi, so they don't appear in diffs.cc @MihaZupan