From e69a0de3790ff1c1aab8aa99d34c029f9cec6b05 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Thu, 5 Mar 2026 13:15:08 -0800 Subject: [PATCH 1/5] Rework and simplify initial versioning and tiering rules - 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> --- src/coreclr/vm/callcounting.cpp | 83 ---------------------------- src/coreclr/vm/callcounting.h | 6 -- src/coreclr/vm/codeversion.cpp | 11 +++- src/coreclr/vm/codeversion.h | 1 + src/coreclr/vm/eeconfig.cpp | 24 ++++++++ src/coreclr/vm/eeconfig.h | 6 ++ src/coreclr/vm/method.cpp | 25 +++++++++ src/coreclr/vm/method.hpp | 18 +----- src/coreclr/vm/prestub.cpp | 40 +++++++++----- src/coreclr/vm/tieredcompilation.cpp | 78 +------------------------- 10 files changed, 94 insertions(+), 198 deletions(-) diff --git a/src/coreclr/vm/callcounting.cpp b/src/coreclr/vm/callcounting.cpp index adef9ca678b97c..c63fe83e42ecf9 100644 --- a/src/coreclr/vm/callcounting.cpp +++ b/src/coreclr/vm/callcounting.cpp @@ -25,30 +25,6 @@ const PCODE CallCountingStub::TargetForThresholdReached = (PCODE)GetEEFuncEntryP #ifndef DACCESS_COMPILE -CallCountingManager::CallCountingInfo::CallCountingInfo(NativeCodeVersion codeVersion) - : m_codeVersion(codeVersion), - m_callCountingStub(nullptr), - m_remainingCallCount(0), - m_stage(Stage::Disabled) -{ - WRAPPER_NO_CONTRACT; - _ASSERTE(!codeVersion.IsNull()); -} - -CallCountingManager::CallCountingInfo * -CallCountingManager::CallCountingInfo::CreateWithCallCountingDisabled(NativeCodeVersion codeVersion) -{ - CONTRACTL - { - THROWS; - GC_NOTRIGGER; - MODE_ANY; - } - CONTRACTL_END; - - return new CallCountingInfo(codeVersion); -} - CallCountingManager::CallCountingInfo::CallCountingInfo(NativeCodeVersion codeVersion, CallCount callCountThreshold) : m_codeVersion(codeVersion), m_callCountingStub(nullptr), @@ -88,7 +64,6 @@ NativeCodeVersion CallCountingManager::CallCountingInfo::GetCodeVersion() const const CallCountingStub *CallCountingManager::CallCountingInfo::GetCallCountingStub() const { WRAPPER_NO_CONTRACT; - _ASSERTE(m_stage != Stage::Disabled); return m_callCountingStub; } @@ -118,7 +93,6 @@ void CallCountingManager::CallCountingInfo::ClearCallCountingStub() PTR_CallCount CallCountingManager::CallCountingInfo::GetRemainingCallCountCell() { WRAPPER_NO_CONTRACT; - _ASSERTE(m_stage != Stage::Disabled); //_ASSERTE(m_callCountingStub != nullptr); return &m_remainingCallCount; @@ -136,7 +110,6 @@ CallCountingManager::CallCountingInfo::Stage CallCountingManager::CallCountingIn FORCEINLINE void CallCountingManager::CallCountingInfo::SetStage(Stage stage) { WRAPPER_NO_CONTRACT; - _ASSERTE(m_stage != Stage::Disabled); _ASSERTE(stage <= Stage::Complete); switch (stage) @@ -482,59 +455,8 @@ void CallCountingManager::StaticInitialize() } #endif -bool CallCountingManager::IsCallCountingEnabled(NativeCodeVersion codeVersion) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - } - CONTRACTL_END; - - _ASSERTE(!codeVersion.IsNull()); - _ASSERTE(codeVersion.IsDefaultVersion()); - _ASSERTE(codeVersion.GetMethodDesc()->IsEligibleForTieredCompilation()); - - CodeVersionManager::LockHolder codeVersioningLockHolder; - - PTR_CallCountingInfo callCountingInfo = m_callCountingInfoByCodeVersionHash.Lookup(codeVersion); - return callCountingInfo == NULL || callCountingInfo->GetStage() != CallCountingInfo::Stage::Disabled; -} - #ifndef DACCESS_COMPILE -void CallCountingManager::DisableCallCounting(NativeCodeVersion codeVersion) -{ - CONTRACTL - { - THROWS; - GC_NOTRIGGER; - MODE_ANY; - } - CONTRACTL_END; - - _ASSERTE(!codeVersion.IsNull()); - _ASSERTE(codeVersion.IsDefaultVersion()); - _ASSERTE(codeVersion.GetMethodDesc()->IsEligibleForTieredCompilation()); - - CodeVersionManager::LockHolder codeVersioningLockHolder; - - CallCountingInfo *callCountingInfo = m_callCountingInfoByCodeVersionHash.Lookup(codeVersion); - if (callCountingInfo != nullptr) - { - // Call counting may already have been disabled due to the possibility of concurrent or reentering JIT of the same - // native code version of a method. The call counting info is created with call counting enabled or disabled and it - // cannot be changed thereafter for consistency in dependents of the info. - _ASSERTE(callCountingInfo->GetStage() == CallCountingInfo::Stage::Disabled); - return; - } - - NewHolder callCountingInfoHolder = CallCountingInfo::CreateWithCallCountingDisabled(codeVersion); - m_callCountingInfoByCodeVersionHash.Add(callCountingInfoHolder); - callCountingInfoHolder.SuppressRelease(); -} - // Returns true if the code entry point was updated to reflect the active code version, false otherwise. In normal paths, the // code entry point is not updated only when the use of call counting stubs is disabled, as in that case returning to the // prestub is necessary for further call counting. On exception, the code entry point may or may not have been updated and it's @@ -1148,11 +1070,6 @@ void CallCountingManager::DeleteAllCallCountingStubs() { CallCountingInfo *callCountingInfo = *it; CallCountingInfo::Stage callCountingStage = callCountingInfo->GetStage(); - if (callCountingStage == CallCountingInfo::Stage::Disabled) - { - continue; - } - if (callCountingInfo->GetCallCountingStub() != nullptr) { callCountingInfo->ClearCallCountingStub(); diff --git a/src/coreclr/vm/callcounting.h b/src/coreclr/vm/callcounting.h index fcc7bc834226e7..3a519993d78145 100644 --- a/src/coreclr/vm/callcounting.h +++ b/src/coreclr/vm/callcounting.h @@ -196,9 +196,6 @@ class CallCountingManager // Stub is not active and will not become active, call counting complete, promoted, stub may be deleted Complete, - - // Call counting is disabled, only used for the default code version to indicate that it is to be optimized - Disabled }; private: @@ -208,10 +205,7 @@ class CallCountingManager Stage m_stage; #ifndef DACCESS_COMPILE - private: - CallCountingInfo(NativeCodeVersion codeVersion); public: - static CallCountingInfo *CreateWithCallCountingDisabled(NativeCodeVersion codeVersion); CallCountingInfo(NativeCodeVersion codeVersion, CallCount callCountThreshold); ~CallCountingInfo(); #endif diff --git a/src/coreclr/vm/codeversion.cpp b/src/coreclr/vm/codeversion.cpp index 0333d2d7941422..8b1b02848ed615 100644 --- a/src/coreclr/vm/codeversion.cpp +++ b/src/coreclr/vm/codeversion.cpp @@ -346,7 +346,13 @@ NativeCodeVersion::OptimizationTier NativeCodeVersion::GetOptimizationTier() con } else { - return TieredCompilationManager::GetInitialOptimizationTier(GetMethodDesc()); + PTR_MethodDesc pMethodDesc = GetMethodDesc(); + OptimizationTier tier = pMethodDesc->GetMethodDescOptimizationTier(); + if (tier == OptimizationTier::OptimizationTierUnknown) + { + tier = TieredCompilationManager::GetInitialOptimizationTier(pMethodDesc); + } + return tier; } } @@ -360,8 +366,7 @@ void NativeCodeVersion::SetOptimizationTier(OptimizationTier tier) } else { - // State changes should have been made previously such that the initial tier is the new tier - _ASSERTE(TieredCompilationManager::GetInitialOptimizationTier(GetMethodDesc()) == tier); + GetMethodDesc()->SetMethodDescOptimizationTier(tier); } } #endif diff --git a/src/coreclr/vm/codeversion.h b/src/coreclr/vm/codeversion.h index 2849f77285c627..990085695f5345 100644 --- a/src/coreclr/vm/codeversion.h +++ b/src/coreclr/vm/codeversion.h @@ -82,6 +82,7 @@ class NativeCodeVersion OptimizationTierOptimized, // may do less optimizations than tier 1 OptimizationTier0Instrumented, OptimizationTier1Instrumented, + OptimizationTierUnknown = 0xFFFFFFFF }; #ifdef FEATURE_TIERED_COMPILATION OptimizationTier GetOptimizationTier() const; diff --git a/src/coreclr/vm/eeconfig.cpp b/src/coreclr/vm/eeconfig.cpp index a6b9d47b3e36d6..f6ed6e99d8e35e 100644 --- a/src/coreclr/vm/eeconfig.cpp +++ b/src/coreclr/vm/eeconfig.cpp @@ -789,11 +789,35 @@ HRESULT EEConfig::sync() } #endif +#ifdef FEATURE_PGO + if (fTieredPGO) + { + // Initial tier for R2R is always just OptimizationTier0 + // For ILOnly it depends on TieredPGO_InstrumentOnlyHotCode: + // 1 - OptimizationTier0 as we don't want to instrument the initial version (will only instrument hot Tier0) + // 2 - OptimizationTier0Instrumented - instrument all ILOnly code + if (g_pConfig->TieredPGO_InstrumentOnlyHotCode()) + { + tieredCompilation_DefaultTier = (DWORD)NativeCodeVersion::OptimizationTier0; + } + else + { + tieredCompilation_DefaultTier = (DWORD)NativeCodeVersion::OptimizationTier0Instrumented; + } + } + else +#endif + { + tieredCompilation_DefaultTier = (DWORD)NativeCodeVersion::OptimizationTier0; + } + if (ETW::CompilationLog::TieredCompilation::Runtime::IsEnabled()) { ETW::CompilationLog::TieredCompilation::Runtime::SendSettings(); } } +#else // !FEATURE_TIERED_COMPILATION + tieredCompilation_DefaultTier = NativeCodeVersion::OptimizationTierOptimized; #endif #if defined(FEATURE_ON_STACK_REPLACEMENT) diff --git a/src/coreclr/vm/eeconfig.h b/src/coreclr/vm/eeconfig.h index 23c4c3d99ed3d9..fecb76eb69fb41 100644 --- a/src/coreclr/vm/eeconfig.h +++ b/src/coreclr/vm/eeconfig.h @@ -96,6 +96,11 @@ class EEConfig bool TieredCompilation_UseCallCountingStubs() const { LIMITED_METHOD_CONTRACT; return fTieredCompilation_UseCallCountingStubs; } DWORD TieredCompilation_DeleteCallCountingStubsAfter() const { LIMITED_METHOD_CONTRACT; return tieredCompilation_DeleteCallCountingStubsAfter; } #endif // FEATURE_TIERED_COMPILATION + DWORD TieredCompilation_DefaultTier() const + { + LIMITED_METHOD_CONTRACT; + return tieredCompilation_DefaultTier; + } #if defined(FEATURE_PGO) bool TieredPGO(void) const { LIMITED_METHOD_CONTRACT; return fTieredPGO; } @@ -607,6 +612,7 @@ class EEConfig DWORD tieredCompilation_CallCountingDelayMs; DWORD tieredCompilation_DeleteCallCountingStubsAfter; #endif + DWORD tieredCompilation_DefaultTier; #if defined(FEATURE_PGO) bool fTieredPGO; diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp index 7e2544abaef588..3fba32ba9350de 100644 --- a/src/coreclr/vm/method.cpp +++ b/src/coreclr/vm/method.cpp @@ -239,6 +239,8 @@ HRESULT MethodDesc::EnsureCodeDataExists(AllocMemTracker *pamTracker) if (alloc == NULL) return E_OUTOFMEMORY; + alloc->OptimizationTier = NativeCodeVersion::OptimizationTierUnknown; + // Try to set the field. Suppress clean-up if we win the race. if (InterlockedCompareExchangeT(&m_codeData, (MethodDescCodeData*)alloc, NULL) == NULL) amTracker.SuppressRelease(); @@ -260,6 +262,20 @@ HRESULT MethodDesc::SetMethodDescVersionState(PTR_MethodDescVersioningState stat return S_OK; } + +HRESULT MethodDesc::SetMethodDescOptimizationTier(NativeCodeVersion::OptimizationTier tier) +{ + WRAPPER_NO_CONTRACT; + + HRESULT hr; + IfFailRet(EnsureCodeDataExists(NULL)); + + _ASSERTE(m_codeData != NULL); + if (InterlockedExchangeT(&m_codeData->OptimizationTier, tier) != NativeCodeVersion::OptimizationTierUnknown) + return S_FALSE; + + return S_OK; +} #endif // FEATURE_CODE_VERSIONING #ifdef FEATURE_INTERPRETER @@ -297,6 +313,15 @@ PTR_MethodDescVersioningState MethodDesc::GetMethodDescVersionState() return NULL; return VolatileLoadWithoutBarrier(&codeData->VersioningState); } + +NativeCodeVersion::OptimizationTier MethodDesc::GetMethodDescOptimizationTier() +{ + WRAPPER_NO_CONTRACT; + PTR_MethodDescCodeData codeData = VolatileLoadWithoutBarrier(&m_codeData); + if (codeData == NULL) + return NativeCodeVersion::OptimizationTierUnknown; + return VolatileLoadWithoutBarrier(&codeData->OptimizationTier); +} #endif // FEATURE_CODE_VERSIONING //******************************************************************************* diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index 97c1e0d8e633f8..f308ce30213c6a 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -254,6 +254,7 @@ struct MethodDescCodeData final { #ifdef FEATURE_CODE_VERSIONING PTR_MethodDescVersioningState VersioningState; + NativeCodeVersion::OptimizationTier OptimizationTier; #endif // FEATURE_CODE_VERSIONING PCODE TemporaryEntryPoint; #ifdef FEATURE_INTERPRETER @@ -1936,8 +1937,10 @@ class MethodDesc #ifdef FEATURE_CODE_VERSIONING #ifndef DACCESS_COMPILE HRESULT SetMethodDescVersionState(PTR_MethodDescVersioningState state); + HRESULT SetMethodDescOptimizationTier(NativeCodeVersion::OptimizationTier tier); #endif // !DACCESS_COMPILE PTR_MethodDescVersioningState GetMethodDescVersionState(); + NativeCodeVersion::OptimizationTier GetMethodDescOptimizationTier(); #endif // FEATURE_CODE_VERSIONING public: @@ -2319,20 +2322,6 @@ class PrepareCodeConfig #ifdef FEATURE_TIERED_COMPILATION public: - bool WasTieringDisabledBeforeJitting() const - { - WRAPPER_NO_CONTRACT; - return m_wasTieringDisabledBeforeJitting; - } - - void SetWasTieringDisabledBeforeJitting() - { - WRAPPER_NO_CONTRACT; - _ASSERTE(GetMethodDesc()->IsEligibleForTieredCompilation()); - - m_wasTieringDisabledBeforeJitting = true; - } - bool ShouldCountCalls() const { WRAPPER_NO_CONTRACT; @@ -2441,7 +2430,6 @@ class PrepareCodeConfig #ifdef FEATURE_TIERED_COMPILATION private: - bool m_wasTieringDisabledBeforeJitting; bool m_shouldCountCalls; #endif diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 297c3e84e33053..5dce58a8c31c7e 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -362,16 +362,10 @@ PCODE MethodDesc::PrepareILBasedCode(PrepareCodeConfig* pConfig) && HasUnmanagedCallersOnlyAttribute()))) { NativeCodeVersion codeVersion = pConfig->GetCodeVersion(); - if (codeVersion.IsDefaultVersion()) - { - pConfig->GetMethodDesc()->GetLoaderAllocator()->GetCallCountingManager()->DisableCallCounting(codeVersion); - _ASSERTE(codeVersion.IsFinalTier()); - } - else if (!codeVersion.IsFinalTier()) + if (!codeVersion.IsFinalTier()) { codeVersion.SetOptimizationTier(NativeCodeVersion::OptimizationTierOptimized); } - pConfig->SetWasTieringDisabledBeforeJitting(); shouldTier = false; } #endif // FEATURE_TIERED_COMPILATION @@ -1101,7 +1095,6 @@ PrepareCodeConfig::PrepareCodeConfig(NativeCodeVersion codeVersion, BOOL needsMu m_generatedOrLoadedNewCode(false), #endif #ifdef FEATURE_TIERED_COMPILATION - m_wasTieringDisabledBeforeJitting(false), m_shouldCountCalls(false), #endif m_jitSwitchedToMinOpt(false), @@ -1276,15 +1269,38 @@ bool PrepareCodeConfig::FinalizeOptimizationTierForTier0Load() { _ASSERTE(GetMethodDesc()->IsEligibleForTieredCompilation()); _ASSERTE(!JitSwitchedToOptimized()); + bool shouldTier = true; + + switch (GetMethodDesc()->GetMethodDescOptimizationTier()) + { + case NativeCodeVersion::OptimizationTier0: // This is the default when we may tier up further + break; + + case NativeCodeVersion::OptimizationTierOptimized: // If we've decided for some reason that the R2R code is the final tier + shouldTier = false; + break; + + case NativeCodeVersion::OptimizationTier0Instrumented: + // We should adjust the tier back to regular Tier 0, since the R2R code is not instrumented. + GetCodeVersion().SetOptimizationTier(NativeCodeVersion::OptimizationTier0); + break; + + default: + _ASSERTE(!"Unexpected optimization tier for a method loaded via R2R"); + UNREACHABLE(); + } if (!IsForMulticoreJit()) { - return true; // should count calls if SetNativeCode() succeeds + return shouldTier; // should count calls if SetNativeCode() succeeds } // When using multi-core JIT, the loaded code would not be used until the method is called. Record some information that may // be used later when the method is called. - ((MulticoreJitPrepareCodeConfig *)this)->SetWasTier0(); + if (shouldTier) + { + ((MulticoreJitPrepareCodeConfig *)this)->SetWasTier0(); + } return false; // don't count calls } @@ -1320,10 +1336,6 @@ bool PrepareCodeConfig::FinalizeOptimizationTierForTier0LoadOrJit() // Update the tier in the code version. The JIT may have decided to switch from tier 0 to optimized, in which case // call counting would have to be disabled for the method. NativeCodeVersion codeVersion = GetCodeVersion(); - if (codeVersion.IsDefaultVersion()) - { - GetMethodDesc()->GetLoaderAllocator()->GetCallCountingManager()->DisableCallCounting(codeVersion); - } codeVersion.SetOptimizationTier(NativeCodeVersion::OptimizationTierOptimized); return false; // don't count calls } diff --git a/src/coreclr/vm/tieredcompilation.cpp b/src/coreclr/vm/tieredcompilation.cpp index 5a66f26ef328b3..71231e36426b27 100644 --- a/src/coreclr/vm/tieredcompilation.cpp +++ b/src/coreclr/vm/tieredcompilation.cpp @@ -100,34 +100,9 @@ NativeCodeVersion::OptimizationTier TieredCompilationManager::GetInitialOptimiza } _ASSERT(!pMethodDesc->RequestedAggressiveOptimization()); - - if (!pMethodDesc->GetLoaderAllocator()->GetCallCountingManager()->IsCallCountingEnabled(NativeCodeVersion(pMethodDesc))) - { - // Tier 0 call counting may have been disabled for several reasons, the intention is to start with and stay at an - // optimized tier - return NativeCodeVersion::OptimizationTierOptimized; - } - -#ifdef FEATURE_PGO - if (g_pConfig->TieredPGO()) - { - // Initial tier for R2R is always just OptimizationTier0 - // For ILOnly it depends on TieredPGO_InstrumentOnlyHotCode: - // 1 - OptimizationTier0 as we don't want to instrument the initial version (will only instrument hot Tier0) - // 2 - OptimizationTier0Instrumented - instrument all ILOnly code - if (g_pConfig->TieredPGO_InstrumentOnlyHotCode() || - ExecutionManager::IsReadyToRunCode(pMethodDesc->GetNativeCode())) - { - return NativeCodeVersion::OptimizationTier0; - } - return NativeCodeVersion::OptimizationTier0Instrumented; - } #endif - return NativeCodeVersion::OptimizationTier0; -#else - return NativeCodeVersion::OptimizationTierOptimized; -#endif + return (NativeCodeVersion::OptimizationTier)g_pConfig->TieredCompilation_DefaultTier(); } bool TieredCompilationManager::IsTieringDelayActive() @@ -1052,61 +1027,10 @@ CORJIT_FLAGS TieredCompilationManager::GetJitFlags(PrepareCodeConfig *config) { WRAPPER_NO_CONTRACT; _ASSERTE(config != nullptr); - _ASSERTE( - !config->WasTieringDisabledBeforeJitting() || - config->GetCodeVersion().IsFinalTier()); CORJIT_FLAGS flags; - // Determine the optimization tier for the default code version (slightly faster common path during startup compared to - // below), and disable call counting and set the optimization tier if it's not going to be tier 0 (this is used in other - // places for the default code version where necessary to avoid the extra expense of GetOptimizationTier()). NativeCodeVersion nativeCodeVersion = config->GetCodeVersion(); - if (nativeCodeVersion.IsDefaultVersion() && !config->WasTieringDisabledBeforeJitting()) - { - MethodDesc *methodDesc = nativeCodeVersion.GetMethodDesc(); - if (!methodDesc->IsEligibleForTieredCompilation()) - { - _ASSERTE(nativeCodeVersion.GetOptimizationTier() == NativeCodeVersion::OptimizationTierOptimized); - return flags; - } - - _ASSERT(!methodDesc->RequestedAggressiveOptimization()); - - if (g_pConfig->TieredCompilation_QuickJit()) - { - NativeCodeVersion::OptimizationTier currentTier = nativeCodeVersion.GetOptimizationTier(); - if (currentTier == NativeCodeVersion::OptimizationTier::OptimizationTier0Instrumented) - { - flags.Set(CORJIT_FLAGS::CORJIT_FLAG_BBINSTR); - flags.Set(CORJIT_FLAGS::CORJIT_FLAG_TIER0); - return flags; - } - - if (currentTier == NativeCodeVersion::OptimizationTier::OptimizationTier1Instrumented) - { - flags.Set(CORJIT_FLAGS::CORJIT_FLAG_BBINSTR); - flags.Set(CORJIT_FLAGS::CORJIT_FLAG_TIER1); - return flags; - } - - _ASSERTE(!nativeCodeVersion.IsFinalTier()); - flags.Set(CORJIT_FLAGS::CORJIT_FLAG_TIER0); - if (g_pConfig->TieredPGO() && g_pConfig->TieredPGO_InstrumentOnlyHotCode()) - { - // If we plan to only instrument hot code we have to make an exception - // for cold methods with loops so if those self promote to OSR they need - // some profile to optimize, so here we allow JIT to enable instrumentation - // if current method has loops and is eligible for OSR. - flags.Set(CORJIT_FLAGS::CORJIT_FLAG_BBINSTR_IF_LOOPS); - } - return flags; - } - - methodDesc->GetLoaderAllocator()->GetCallCountingManager()->DisableCallCounting(nativeCodeVersion); - nativeCodeVersion.SetOptimizationTier(NativeCodeVersion::OptimizationTierOptimized); - return flags; - } switch (nativeCodeVersion.GetOptimizationTier()) { From c639ff0cd80942b9c445300459b04e87abeb47c8 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Thu, 5 Mar 2026 15:31:37 -0800 Subject: [PATCH 2/5] Update src/coreclr/vm/method.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/coreclr/vm/method.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp index 3fba32ba9350de..f0c6a9747d068d 100644 --- a/src/coreclr/vm/method.cpp +++ b/src/coreclr/vm/method.cpp @@ -239,7 +239,9 @@ HRESULT MethodDesc::EnsureCodeDataExists(AllocMemTracker *pamTracker) if (alloc == NULL) return E_OUTOFMEMORY; +#ifdef FEATURE_CODE_VERSIONING alloc->OptimizationTier = NativeCodeVersion::OptimizationTierUnknown; +#endif // Try to set the field. Suppress clean-up if we win the race. if (InterlockedCompareExchangeT(&m_codeData, (MethodDescCodeData*)alloc, NULL) == NULL) From 12130e66437b774e48274b9989799ad806fbf59d Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Thu, 5 Mar 2026 16:08:22 -0800 Subject: [PATCH 3/5] Feedback --- src/coreclr/vm/callcounting.h | 6 ------ src/coreclr/vm/codeversion.cpp | 3 ++- src/coreclr/vm/method.cpp | 12 ++++-------- src/coreclr/vm/method.hpp | 2 +- src/coreclr/vm/prestub.cpp | 4 ++++ 5 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/coreclr/vm/callcounting.h b/src/coreclr/vm/callcounting.h index 3a519993d78145..8c87758de7f939 100644 --- a/src/coreclr/vm/callcounting.h +++ b/src/coreclr/vm/callcounting.h @@ -330,13 +330,7 @@ class CallCountingManager static void StaticInitialize(); #endif // !DACCESS_COMPILE -public: - bool IsCallCountingEnabled(NativeCodeVersion codeVersion); - #ifndef DACCESS_COMPILE -public: - void DisableCallCounting(NativeCodeVersion codeVersion); - public: static bool SetCodeEntryPoint( NativeCodeVersion activeCodeVersion, diff --git a/src/coreclr/vm/codeversion.cpp b/src/coreclr/vm/codeversion.cpp index 8b1b02848ed615..50bae39dc7046d 100644 --- a/src/coreclr/vm/codeversion.cpp +++ b/src/coreclr/vm/codeversion.cpp @@ -359,7 +359,8 @@ NativeCodeVersion::OptimizationTier NativeCodeVersion::GetOptimizationTier() con #ifndef DACCESS_COMPILE void NativeCodeVersion::SetOptimizationTier(OptimizationTier tier) { - WRAPPER_NO_CONTRACT; + STANDARD_VM_CONTRACT; + if (m_storageKind == StorageKind::Explicit) { AsNode()->SetOptimizationTier(tier); diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp index 3fba32ba9350de..2e388ea116987e 100644 --- a/src/coreclr/vm/method.cpp +++ b/src/coreclr/vm/method.cpp @@ -263,18 +263,14 @@ HRESULT MethodDesc::SetMethodDescVersionState(PTR_MethodDescVersioningState stat return S_OK; } -HRESULT MethodDesc::SetMethodDescOptimizationTier(NativeCodeVersion::OptimizationTier tier) +void MethodDesc::SetMethodDescOptimizationTier(NativeCodeVersion::OptimizationTier tier) { - WRAPPER_NO_CONTRACT; + STANDARD_VM_CONTRACT; - HRESULT hr; - IfFailRet(EnsureCodeDataExists(NULL)); + IfFailThrow(EnsureCodeDataExists(NULL)); _ASSERTE(m_codeData != NULL); - if (InterlockedExchangeT(&m_codeData->OptimizationTier, tier) != NativeCodeVersion::OptimizationTierUnknown) - return S_FALSE; - - return S_OK; + VolatileStoreWithoutBarrier(&m_codeData->OptimizationTier, tier); } #endif // FEATURE_CODE_VERSIONING diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index f308ce30213c6a..1015a42060ad30 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -1937,7 +1937,7 @@ class MethodDesc #ifdef FEATURE_CODE_VERSIONING #ifndef DACCESS_COMPILE HRESULT SetMethodDescVersionState(PTR_MethodDescVersioningState state); - HRESULT SetMethodDescOptimizationTier(NativeCodeVersion::OptimizationTier tier); + void SetMethodDescOptimizationTier(NativeCodeVersion::OptimizationTier tier); #endif // !DACCESS_COMPILE PTR_MethodDescVersioningState GetMethodDescVersionState(); NativeCodeVersion::OptimizationTier GetMethodDescOptimizationTier(); diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 5dce58a8c31c7e..cde5458ede2fca 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1267,6 +1267,8 @@ const char *PrepareCodeConfig::GetJitOptimizationTierStr(PrepareCodeConfig *conf // This function should be called before SetNativeCode() for consistency with usage of FinalizeOptimizationTierForTier0Jit bool PrepareCodeConfig::FinalizeOptimizationTierForTier0Load() { + STANDARD_VM_CONTRACT; + _ASSERTE(GetMethodDesc()->IsEligibleForTieredCompilation()); _ASSERTE(!JitSwitchedToOptimized()); bool shouldTier = true; @@ -1309,6 +1311,8 @@ bool PrepareCodeConfig::FinalizeOptimizationTierForTier0Load() // version, and it should have already been finalized. bool PrepareCodeConfig::FinalizeOptimizationTierForTier0LoadOrJit() { + STANDARD_VM_CONTRACT; + _ASSERTE(GetMethodDesc()->IsEligibleForTieredCompilation()); if (IsForMulticoreJit()) From 0632f9d844b583fccdb1c5d65aace2492d2a4038 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Fri, 6 Mar 2026 12:12:08 -0800 Subject: [PATCH 4/5] Simplify initial versioning rules in tiered compilation - 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> --- src/coreclr/vm/callcounting.cpp | 7 +------ src/coreclr/vm/eeconfig.cpp | 4 ++-- src/coreclr/vm/prestub.cpp | 2 +- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/coreclr/vm/callcounting.cpp b/src/coreclr/vm/callcounting.cpp index c63fe83e42ecf9..908017f8a28093 100644 --- a/src/coreclr/vm/callcounting.cpp +++ b/src/coreclr/vm/callcounting.cpp @@ -488,12 +488,7 @@ bool CallCountingManager::SetCodeEntryPoint( _ASSERTE(createTieringBackgroundWorkerRef == nullptr || !*createTieringBackgroundWorkerRef); if (!methodDesc->IsEligibleForTieredCompilation() || - ( - // For a default code version that is not tier 0, call counting will have been disabled by this time (checked - // below). Avoid the redundant and not-insignificant expense of GetOptimizationTier() on a default code version. - !activeCodeVersion.IsDefaultVersion() && - activeCodeVersion.IsFinalTier() - ) || + activeCodeVersion.IsFinalTier() || !g_pConfig->TieredCompilation_CallCounting()) { methodDesc->SetCodeEntryPoint(codeEntryPoint); diff --git a/src/coreclr/vm/eeconfig.cpp b/src/coreclr/vm/eeconfig.cpp index f6ed6e99d8e35e..a7db164e1c6048 100644 --- a/src/coreclr/vm/eeconfig.cpp +++ b/src/coreclr/vm/eeconfig.cpp @@ -794,8 +794,8 @@ HRESULT EEConfig::sync() { // Initial tier for R2R is always just OptimizationTier0 // For ILOnly it depends on TieredPGO_InstrumentOnlyHotCode: - // 1 - OptimizationTier0 as we don't want to instrument the initial version (will only instrument hot Tier0) - // 2 - OptimizationTier0Instrumented - instrument all ILOnly code + // OptimizationTier0 as we don't want to instrument the initial version (will only instrument hot Tier0) + // OptimizationTier0Instrumented - instrument all ILOnly code if (g_pConfig->TieredPGO_InstrumentOnlyHotCode()) { tieredCompilation_DefaultTier = (DWORD)NativeCodeVersion::OptimizationTier0; diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index cde5458ede2fca..633927ab95775c 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1273,7 +1273,7 @@ bool PrepareCodeConfig::FinalizeOptimizationTierForTier0Load() _ASSERTE(!JitSwitchedToOptimized()); bool shouldTier = true; - switch (GetMethodDesc()->GetMethodDescOptimizationTier()) + switch (GetCodeVersion().GetOptimizationTier()) { case NativeCodeVersion::OptimizationTier0: // This is the default when we may tier up further break; From 932393cda7858244818491901247ce92e34764f0 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Mon, 9 Mar 2026 13:53:17 -0700 Subject: [PATCH 5/5] Update src/coreclr/vm/eeconfig.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/coreclr/vm/eeconfig.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/eeconfig.cpp b/src/coreclr/vm/eeconfig.cpp index a7db164e1c6048..776febb4045381 100644 --- a/src/coreclr/vm/eeconfig.cpp +++ b/src/coreclr/vm/eeconfig.cpp @@ -817,7 +817,7 @@ HRESULT EEConfig::sync() } } #else // !FEATURE_TIERED_COMPILATION - tieredCompilation_DefaultTier = NativeCodeVersion::OptimizationTierOptimized; + tieredCompilation_DefaultTier = (DWORD)NativeCodeVersion::OptimizationTierOptimized; #endif #if defined(FEATURE_ON_STACK_REPLACEMENT)