Rework and simplify initial versioning and tiering rules#125243
Rework and simplify initial versioning and tiering rules#125243davidwrighton wants to merge 5 commits intodotnet:mainfrom
Conversation
davidwrighton
commented
Mar 5, 2026
- Remove CallCountingInfo::Disabled stage and associated methods (DisableCallCounting, IsCallCountingEnabled, CreateWithCallCountingDisabled)
- Move initial optimization tier determination from per-method JIT-time logic to a config-time decision (TieredCompilation_DefaultTier) computed once during EEConfig::sync()
- Store the default code version's optimization tier directly on MethodDescCodeData, replacing the indirect approach of disabling call counting to signal optimized tier
- Remove WasTieringDisabledBeforeJitting flag from PrepareCodeConfig and MethodDescVersioningState
- Simplify GetJitFlags by removing the special default-version fast path and using the unified optimization tier switch for all code versions
- Add OptimizationTierUnknown sentinel to distinguish uninitialized state
- Simplify FinalizeOptimizationTierForTier0Load to handle R2R loading based on the stored optimization tier
- Remove CallCountingInfo::Disabled stage and associated methods (DisableCallCounting, IsCallCountingEnabled, CreateWithCallCountingDisabled) - Move initial optimization tier determination from per-method JIT-time logic to a config-time decision (TieredCompilation_DefaultTier) computed once during EEConfig::sync() - Store the default code version's optimization tier directly on MethodDescCodeData, replacing the indirect approach of disabling call counting to signal optimized tier - Remove WasTieringDisabledBeforeJitting flag from PrepareCodeConfig and MethodDescVersioningState - Simplify GetJitFlags by removing the special default-version fast path and using the unified optimization tier switch for all code versions - Add OptimizationTierUnknown sentinel to distinguish uninitialized state - Simplify FinalizeOptimizationTierForTier0Load to handle R2R loading based on the stored optimization tier Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Tagging subscribers to this area: @agocke |
There was a problem hiding this comment.
Pull request overview
This PR refactors CoreCLR tiered compilation’s initial tier selection and tiering signaling by moving “default tier” decisions into EEConfig::sync(), removing the call-counting “disabled” signaling path, and storing the default version tier on MethodDesc code data.
Changes:
- Compute and expose a single
TieredCompilation_DefaultTiervalue inEEConfig, and use it for initial tier selection. - Add
OptimizationTierUnknownand a per-MethodDescstored optimization tier, and route default-version tier reads/writes through it. - Remove the call-counting “Disabled” stage and related APIs/flags, simplifying tier-related logic in the JIT flag path and prestub tier finalization.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/coreclr/vm/tieredcompilation.cpp | Switch initial tier selection to use EEConfig’s computed default tier. |
| src/coreclr/vm/prestub.cpp | Remove “disable call counting” signaling; revise tier finalization for R2R load based on stored tier. |
| src/coreclr/vm/method.hpp | Add stored optimization tier to MethodDescCodeData; remove WasTieringDisabledBeforeJitting plumbing. |
| src/coreclr/vm/method.cpp | Initialize and implement MethodDesc optimization tier storage helpers. |
| src/coreclr/vm/eeconfig.h | Add TieredCompilation_DefaultTier() accessor and config backing field. |
| src/coreclr/vm/eeconfig.cpp | Compute tieredCompilation_DefaultTier during EEConfig::sync(). |
| src/coreclr/vm/codeversion.h | Add OptimizationTierUnknown sentinel value. |
| src/coreclr/vm/codeversion.cpp | Use stored MethodDesc tier when available; set it for default versions via MethodDesc. |
| src/coreclr/vm/callcounting.h | Remove CallCountingInfo::Disabled stage and disabled-construction helpers. |
| src/coreclr/vm/callcounting.cpp | Remove disabled-stage construction/guards and remove disabled-callcounting APIs’ implementations. |
src/coreclr/vm/method.cpp
Outdated
| IfFailRet(EnsureCodeDataExists(NULL)); | ||
|
|
||
| _ASSERTE(m_codeData != NULL); | ||
| if (InterlockedExchangeT(&m_codeData->OptimizationTier, tier) != NativeCodeVersion::OptimizationTierUnknown) |
There was a problem hiding this comment.
SetMethodDescOptimizationTier() uses InterlockedExchangeT, which overwrites any previously-set OptimizationTier even when returning S_FALSE. This breaks the intended “set once from Unknown” semantics and can race with other threads, potentially flipping a method’s tier unexpectedly. Use InterlockedCompareExchangeT to only store the tier when the current value is OptimizationTierUnknown (and return S_FALSE without modifying otherwise).
| if (InterlockedExchangeT(&m_codeData->OptimizationTier, tier) != NativeCodeVersion::OptimizationTierUnknown) | |
| if (InterlockedCompareExchangeT(&m_codeData->OptimizationTier, tier, NativeCodeVersion::OptimizationTierUnknown) != NativeCodeVersion::OptimizationTierUnknown) |
There was a problem hiding this comment.
I agree with copilot here. This is an indiscriminate exchange. Why don't we want InterlockedCompareExchange?
There was a problem hiding this comment.
Mmm... the latest change does a simple set, but there is some merit to not wanting to march over stuff willy nilly. I'll think about that tomorrow.
There was a problem hiding this comment.
Looking into this, I've concluded that the current logic, which simply sets the value is correct. We could in theory add asserts that its behaving as expected, but just doing the set unconditionally is acceptable.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
….com:davidwrighton/runtime into rework_and_simplify_initial_versioning_rules
src/coreclr/vm/method.cpp
Outdated
| IfFailRet(EnsureCodeDataExists(NULL)); | ||
|
|
||
| _ASSERTE(m_codeData != NULL); | ||
| if (InterlockedExchangeT(&m_codeData->OptimizationTier, tier) != NativeCodeVersion::OptimizationTierUnknown) |
There was a problem hiding this comment.
I agree with copilot here. This is an indiscriminate exchange. Why don't we want InterlockedCompareExchange?
- Remove redundant IsDefaultVersion() guard before IsFinalTier() check in CallCountingManager::SetCodeEntryPoint - Query optimization tier from CodeVersion instead of MethodDesc in FinalizeOptimizationTierForTier0Load - Clean up comment formatting in EEConfig::sync Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| } | ||
| } | ||
| #else // !FEATURE_TIERED_COMPILATION | ||
| tieredCompilation_DefaultTier = NativeCodeVersion::OptimizationTierOptimized; |
There was a problem hiding this comment.
In the !FEATURE_TIERED_COMPILATION branch, tieredCompilation_DefaultTier (a DWORD) is assigned an enum value without an explicit cast, while other assignments in this method cast to DWORD. Using a consistent explicit cast here helps avoid compiler warnings (and matches the nearby code style).
| tieredCompilation_DefaultTier = NativeCodeVersion::OptimizationTierOptimized; | |
| tieredCompilation_DefaultTier = (DWORD)NativeCodeVersion::OptimizationTierOptimized; |