RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_TieredCompilation_Tier1CallCountThreshold, W("TieredCompilation_Tier1CallCountThreshold"), 30, "Number of times a method must be called after which it is promoted to tier 1.")
RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_TieredCompilation_Tier1CallCountingDelayMs, W("TieredCompilation_Tier1CallCountingDelayMs"), 100, "A perpetual delay in milliseconds that is applied to tier 1 call counting and jitting, while there is tier 0 activity.")
RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_TieredCompilation_Tier1DelaySingleProcMultiplier, W("TieredCompilation_Tier1DelaySingleProcMultiplier"), 10, "Multiplier for TieredCompilation_Tier1CallCountingDelayMs that is applied on a single-processor machine or when the process is affinitized to a single processor.")
+RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_TieredCompilation_DisableTier0Jit, W("TieredCompilation_DisableTier0Jit"), 0, "For methods that don't have pregenerated code, disable jitting them at tier 0 and start with a higher tier instead. For methods that have pregenerated code, tiering occurs normally.")
RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_TieredCompilation_Test_CallCounting, W("TieredCompilation_Test_CallCounting"), 1, "Enabled by default (only activates when TieredCompilation is also enabled). If disabled immediately backpatches prestub, and likely prevents any tier1 promotion")
RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_TieredCompilation_Test_OptimizeTier0, W("TieredCompilation_Test_OptimizeTier0"), 0, "Use optimized codegen (normally used by tier1) in tier0")
m_lock.Init(LOCK_TYPE_DEFAULT);
}
+bool CallCounter::IsEligibleForTier0CallCounting(MethodDesc* pMethodDesc)
+{
+ WRAPPER_NO_CONTRACT;
+ _ASSERTE(pMethodDesc != NULL);
+ _ASSERTE(pMethodDesc->IsEligibleForTieredCompilation());
+
+ return g_pConfig->TieredCompilation_CallCounting() && !pMethodDesc->RequestedAggressiveOptimization();
+}
+
+bool CallCounter::IsTier0CallCountingEnabled(MethodDesc* pMethodDesc)
+{
+ WRAPPER_NO_CONTRACT;
+ _ASSERTE(pMethodDesc != NULL);
+ _ASSERTE(pMethodDesc->IsEligibleForTieredCompilation());
+ _ASSERTE(IsEligibleForTier0CallCounting(pMethodDesc));
+
+ SpinLockHolder holder(&m_lock);
+
+ const CallCounterEntry *entry = m_methodToCallCount.LookupPtr(pMethodDesc);
+ return entry == nullptr || entry->IsTier0CallCountingEnabled();
+}
+
+void CallCounter::DisableTier0CallCounting(MethodDesc* pMethodDesc)
+{
+ WRAPPER_NO_CONTRACT;
+ _ASSERTE(pMethodDesc != NULL);
+ _ASSERTE(pMethodDesc->IsEligibleForTieredCompilation());
+ _ASSERTE(IsEligibleForTier0CallCounting(pMethodDesc));
+
+ SpinLockHolder holder(&m_lock);
+
+ CallCounterEntry *entry = const_cast<CallCounterEntry *>(m_methodToCallCount.LookupPtr(pMethodDesc));
+
+ // Disabling call counting will affect the tier of the MethodDesc's first native code version. Callers must ensure that this
+ // change is made deterministically and prior to or while jitting the first native code version such that the tier would not
+ // be changed after it is already jitted. At that point, the call count threshold would already be initialized and the entry
+ // would exist. To disable call counting at different points in time, it would be ok to do so if the method has not been
+ // called yet (if the entry does not yet exist in the hash table), if necessary that could be a different function like
+ // TryDisable...() that would fail to disable call counting if the method has already been called.
+ _ASSERTE(entry != nullptr);
+ entry->DisableTier0CallCounting();
+}
+
// This is called by the prestub each time the method is invoked in a particular
// AppDomain (the AppDomain for which AppDomain.GetCallCounter() == this). These
// calls continue until we backpatch the prestub to avoid future calls. This allows
// us to track the number of calls to each method and use it as a trigger for tiered
// compilation.
-//
-// Returns TRUE if no future invocations are needed (we reached the count we cared about)
-// and FALSE otherwise. It is permissible to keep calling even when TRUE was previously
-// returned and multi-threaded race conditions will surely cause this to occur.
void CallCounter::OnMethodCalled(
MethodDesc* pMethodDesc,
TieredCompilationManager *pTieredCompilationManager,
BOOL* shouldStopCountingCallsRef,
- BOOL* wasPromotedToTier1Ref)
+ BOOL* wasPromotedToNextTierRef)
{
STANDARD_VM_CONTRACT;
_ASSERTE(pMethodDesc->IsEligibleForTieredCompilation());
_ASSERTE(pTieredCompilationManager != nullptr);
_ASSERTE(shouldStopCountingCallsRef != nullptr);
- _ASSERTE(wasPromotedToTier1Ref != nullptr);
+ _ASSERTE(wasPromotedToNextTierRef != nullptr);
+
+ // At the moment, call counting is only done for tier 0 code
+ _ASSERTE(IsEligibleForTier0CallCounting(pMethodDesc));
// PERF: This as a simple to implement, but not so performant, call counter
// Currently this is only called until we reach a fixed call count and then
// leaving the prestub unpatched, but may not be good overall as it increases
// the size of the jitted code.
-
- TieredCompilationManager* pCallCounterSink = NULL;
- int callCount;
+ bool isFirstTier0Call = false;
+ int tier0CallCountLimit;
{
//Be careful if you convert to something fully lock/interlocked-free that
//you correctly handle what happens when some N simultaneous calls don't
CallCounterEntry* pEntry = const_cast<CallCounterEntry*>(m_methodToCallCount.LookupPtr(pMethodDesc));
if (pEntry == NULL)
{
- callCount = 1;
- m_methodToCallCount.Add(CallCounterEntry(pMethodDesc, callCount));
+ isFirstTier0Call = true;
+ tier0CallCountLimit = (int)g_pConfig->TieredCompilation_Tier1CallCountThreshold() - 1;
+ _ASSERTE(tier0CallCountLimit >= 0);
+ m_methodToCallCount.Add(CallCounterEntry(pMethodDesc, tier0CallCountLimit));
+ }
+ else if (pEntry->IsTier0CallCountingEnabled())
+ {
+ pEntry->tier0CallCountLimit--;
+ tier0CallCountLimit = pEntry->tier0CallCountLimit;
}
else
{
- pEntry->callCount++;
- callCount = pEntry->callCount;
+ *shouldStopCountingCallsRef = true;
+ *wasPromotedToNextTierRef = true;
+ return;
}
}
- pTieredCompilationManager->OnMethodCalled(pMethodDesc, callCount, shouldStopCountingCallsRef, wasPromotedToTier1Ref);
+ pTieredCompilationManager->OnTier0MethodCalled(
+ pMethodDesc,
+ isFirstTier0Call,
+ tier0CallCountLimit,
+ shouldStopCountingCallsRef,
+ wasPromotedToNextTierRef);
}
#endif // FEATURE_TIERED_COMPILATION
struct CallCounterEntry
{
CallCounterEntry() {}
- CallCounterEntry(const MethodDesc* m, const int c)
- : pMethod(m), callCount(c) {}
+ CallCounterEntry(const MethodDesc* m, const int tier0CallCountLimit)
+ : pMethod(m), tier0CallCountLimit(tier0CallCountLimit) {}
const MethodDesc* pMethod;
- int callCount;
+ int tier0CallCountLimit;
+
+#if !defined(DACCESS_COMPILE) && !defined(CROSSGEN_COMPILE)
+ bool IsTier0CallCountingEnabled() const
+ {
+ LIMITED_METHOD_CONTRACT;
+ return tier0CallCountLimit != INT_MAX;
+ }
+
+ void DisableTier0CallCounting()
+ {
+ LIMITED_METHOD_CONTRACT;
+ tier0CallCountLimit = INT_MAX;
+ }
+#endif
};
class CallCounterHashTraits : public DefaultSHashTraits<CallCounterEntry>
CallCounter();
#endif
- void OnMethodCalled(MethodDesc* pMethodDesc, TieredCompilationManager *pTieredCompilationManager, BOOL* shouldStopCountingCallsRef, BOOL* wasPromotedToTier1Ref);
+#if !defined(DACCESS_COMPILE) && !defined(CROSSGEN_COMPILE)
+ static bool IsEligibleForCallCounting(MethodDesc* pMethodDesc)
+ {
+ WRAPPER_NO_CONTRACT;
+ return IsEligibleForTier0CallCounting(pMethodDesc);
+ }
+
+ static bool IsEligibleForTier0CallCounting(MethodDesc* pMethodDesc);
+
+ bool IsCallCountingEnabled(MethodDesc* pMethodDesc)
+ {
+ WRAPPER_NO_CONTRACT;
+ return IsTier0CallCountingEnabled(pMethodDesc);
+ }
+
+ bool IsTier0CallCountingEnabled(MethodDesc* pMethodDesc);
+ void DisableTier0CallCounting(MethodDesc* pMethodDesc);
+#endif
+
+ void OnMethodCalled(MethodDesc* pMethodDesc, TieredCompilationManager *pTieredCompilationManager, BOOL* shouldStopCountingCallsRef, BOOL* wasPromotedToNextTierRef);
private:
#ifdef FEATURE_TIERED_COMPILATION
+
NativeCodeVersion::OptimizationTier NativeCodeVersionNode::GetOptimizationTier() const
{
LIMITED_METHOD_DAC_CONTRACT;
return m_optTier;
}
+
+#ifndef DACCESS_COMPILE
+void NativeCodeVersionNode::SetOptimizationTier(NativeCodeVersion::OptimizationTier tier)
+{
+ LIMITED_METHOD_CONTRACT;
+ _ASSERTE(tier >= m_optTier);
+
+ m_optTier = tier;
+}
+#endif
+
#endif // FEATURE_TIERED_COMPILATION
NativeCodeVersion::NativeCodeVersion() :
#endif
#ifdef FEATURE_TIERED_COMPILATION
+
NativeCodeVersion::OptimizationTier NativeCodeVersion::GetOptimizationTier() const
{
LIMITED_METHOD_DAC_CONTRACT;
return TieredCompilationManager::GetInitialOptimizationTier(GetMethodDesc());
}
}
+
+#ifndef DACCESS_COMPILE
+void NativeCodeVersion::SetOptimizationTier(OptimizationTier tier)
+{
+ WRAPPER_NO_CONTRACT;
+ if (m_storageKind == StorageKind::Explicit)
+ {
+ AsNode()->SetOptimizationTier(tier);
+ }
+ else
+ {
+ // State changes should have been made previously such that the initial tier is the new tier
+ _ASSERTE(TieredCompilationManager::GetInitialOptimizationTier(GetMethodDesc()) == tier);
+ }
+}
+#endif
+
#endif
PTR_NativeCodeVersionNode NativeCodeVersion::AsNode() const
};
#ifdef FEATURE_TIERED_COMPILATION
OptimizationTier GetOptimizationTier() const;
+#ifndef DACCESS_COMPILE
+ void SetOptimizationTier(OptimizationTier tier);
+#endif
#endif // FEATURE_TIERED_COMPILATION
bool operator==(const NativeCodeVersion & rhs) const;
bool operator!=(const NativeCodeVersion & rhs) const;
#endif
#ifdef FEATURE_TIERED_COMPILATION
NativeCodeVersion::OptimizationTier GetOptimizationTier() const;
+#ifndef DACCESS_COMPILE
+ void SetOptimizationTier(NativeCodeVersion::OptimizationTier tier);
#endif
+#endif // FEATURE_TIERED_COMPILATION
private:
//union - could save a little memory?
#if defined(FEATURE_TIERED_COMPILATION)
fTieredCompilation = false;
+ fTieredCompilation_DisableTier0Jit = false;
fTieredCompilation_CallCounting = false;
fTieredCompilation_OptimizeTier0 = false;
tieredCompilation_tier1CallCountThreshold = 1;
#if defined(FEATURE_TIERED_COMPILATION)
fTieredCompilation = Configuration::GetKnobBooleanValue(W("System.Runtime.TieredCompilation"), CLRConfig::EXTERNAL_TieredCompilation) != 0;
+ fTieredCompilation_DisableTier0Jit =
+ Configuration::GetKnobBooleanValue(
+ W("System.Runtime.TieredCompilation.DisableTier0Jit"),
+ CLRConfig::UNSUPPORTED_TieredCompilation_DisableTier0Jit) != 0;
fTieredCompilation_CallCounting = CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_TieredCompilation_Test_CallCounting) != 0;
fTieredCompilation_OptimizeTier0 = CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_TieredCompilation_Test_OptimizeTier0) != 0;
{
tieredCompilation_tier1CallCountThreshold = 1;
}
+ else if (tieredCompilation_tier1CallCountThreshold > INT_MAX) // CallCounter uses 'int'
+ {
+ tieredCompilation_tier1CallCountThreshold = INT_MAX;
+ }
tieredCompilation_tier1CallCountingDelayMs =
CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_TieredCompilation_Tier1CallCountingDelayMs);
// Tiered Compilation config
#if defined(FEATURE_TIERED_COMPILATION)
bool TieredCompilation(void) const {LIMITED_METHOD_CONTRACT; return fTieredCompilation; }
+ bool TieredCompilation_DisableTier0Jit() const { LIMITED_METHOD_CONTRACT; return fTieredCompilation_DisableTier0Jit; }
bool TieredCompilation_CallCounting() const {LIMITED_METHOD_CONTRACT; return fTieredCompilation_CallCounting; }
bool TieredCompilation_OptimizeTier0() const {LIMITED_METHOD_CONTRACT; return fTieredCompilation_OptimizeTier0; }
DWORD TieredCompilation_Tier1CallCountThreshold() const { LIMITED_METHOD_CONTRACT; return tieredCompilation_tier1CallCountThreshold; }
#if defined(FEATURE_TIERED_COMPILATION)
bool fTieredCompilation;
+ bool fTieredCompilation_DisableTier0Jit;
bool fTieredCompilation_CallCounting;
bool fTieredCompilation_OptimizeTier0;
DWORD tieredCompilation_tier1CallCountThreshold;
if (pCode == NULL)
{
+#ifdef FEATURE_TIERED_COMPILATION
+ if (g_pConfig->TieredCompilation_DisableTier0Jit() &&
+ IsEligibleForTieredCompilation() &&
+ pConfig->GetCodeVersion().GetOptimizationTier() == NativeCodeVersion::OptimizationTier0 &&
+ CallCounter::IsEligibleForTier0CallCounting(this))
+ {
+ GetCallCounter()->DisableTier0CallCounting(this);
+ pConfig->GetCodeVersion().SetOptimizationTier(NativeCodeVersion::OptimizationTier1);
+ }
+#endif
+
LOG((LF_CLASSLOADER, LL_INFO1000000,
" In PrepareILBasedCode, calling JitCompileCode\n"));
pCode = JitCompileCode(pConfig);
#ifdef FEATURE_TIERED_COMPILATION
BOOL fNeedsCallCounting = FALSE;
TieredCompilationManager* pTieredCompilationManager = nullptr;
- if (IsEligibleForTieredCompilation() && TieredCompilationManager::RequiresCallCounting(this))
+ if (IsEligibleForTieredCompilation() && CallCounter::IsEligibleForCallCounting(this))
{
pTieredCompilationManager = GetAppDomain()->GetTieredCompilationManager();
- CallCounter * pCallCounter = GetCallCounter();
- BOOL fWasPromotedToTier1 = FALSE;
- pCallCounter->OnMethodCalled(this, pTieredCompilationManager, &fCanBackpatchPrestub, &fWasPromotedToTier1);
- fNeedsCallCounting = !fWasPromotedToTier1;
+ BOOL fWasPromotedToNextTier = FALSE;
+ GetCallCounter()->OnMethodCalled(this, pTieredCompilationManager, &fCanBackpatchPrestub, &fWasPromotedToNextTier);
+ fNeedsCallCounting = !fWasPromotedToNextTier;
}
#endif
#ifdef FEATURE_TIERED_COMPILATION
if (pTieredCompilationManager != nullptr && fNeedsCallCounting && fCanBackpatchPrestub && pCode != NULL)
{
- pTieredCompilationManager->OnMethodCallCountingStoppedWithoutTier1Promotion(this);
+ pTieredCompilationManager->OnMethodCallCountingStoppedWithoutTierPromotion(this);
}
#endif
m_lock(CrstTieredCompilation),
m_isAppDomainShuttingDown(FALSE),
m_countOptimizationThreadsRunning(0),
- m_callCountOptimizationThreshhold(1),
m_optimizationQuantumMs(50),
m_methodsPendingCountingForTier1(nullptr),
m_tieringDelayTimerHandle(nullptr),
CrstHolder holder(&m_lock);
m_domainId = appDomainId;
- m_callCountOptimizationThreshhold = g_pConfig->TieredCompilation_Tier1CallCountThreshold();
}
#endif // FEATURE_TIERED_COMPILATION && !DACCESS_COMPILE
WRAPPER_NO_CONTRACT;
_ASSERTE(pMethodDesc != NULL);
-#ifdef FEATURE_TIERED_COMPILATION
+#if defined(FEATURE_TIERED_COMPILATION) && !defined(DACCESS_COMPILE)
+ if (!pMethodDesc->IsEligibleForTieredCompilation())
+ {
+ // The optimization tier is not used
+ return NativeCodeVersion::OptimizationTier0;
+ }
+
if (pMethodDesc->RequestedAggressiveOptimization())
{
// Methods flagged with MethodImplOptions.AggressiveOptimization begin at tier 1, as a workaround to cold methods with
// hot loops performing poorly (https://github.com/dotnet/coreclr/issues/19751)
return NativeCodeVersion::OptimizationTier1;
}
-#endif // FEATURE_TIERED_COMPILATION
+
+ if (!g_pConfig->TieredCompilation_CallCounting())
+ {
+ // Call counting is disabled altogether through config, the intention is to remain at the initial tier
+ return NativeCodeVersion::OptimizationTier0;
+ }
+
+ if (!pMethodDesc->GetCallCounter()->IsTier0CallCountingEnabled(pMethodDesc))
+ {
+ // Tier 0 call counting may have been disabled based on information about precompiled code or for other reasons, the
+ // intention is to begin at tier 1
+ return NativeCodeVersion::OptimizationTier1;
+ }
+#endif
return NativeCodeVersion::OptimizationTier0;
}
#if defined(FEATURE_TIERED_COMPILATION) && !defined(DACCESS_COMPILE)
-bool TieredCompilationManager::RequiresCallCounting(MethodDesc* pMethodDesc)
-{
- WRAPPER_NO_CONTRACT;
- _ASSERTE(pMethodDesc != NULL);
- _ASSERTE(pMethodDesc->IsEligibleForTieredCompilation());
-
- return
- g_pConfig->TieredCompilation_CallCounting() &&
- GetInitialOptimizationTier(pMethodDesc) == NativeCodeVersion::OptimizationTier0;
-}
-
// Called each time code in this AppDomain has been run. This is our sole entrypoint to begin
// tiered compilation for now. Returns TRUE if no more notifications are necessary, but
// more notifications may come anyways.
//
-// currentCallCount is pre-incremented, that is to say the value is 1 on first call for a given
-// method.
-void TieredCompilationManager::OnMethodCalled(
+// currentCallCountLimit is pre-decremented, that is to say the value is <= 0 when the
+// threshold for promoting to tier 1 is reached.
+void TieredCompilationManager::OnTier0MethodCalled(
MethodDesc* pMethodDesc,
- DWORD currentCallCount,
+ bool isFirstCall,
+ int currentCallCountLimit,
BOOL* shouldStopCountingCallsRef,
- BOOL* wasPromotedToTier1Ref)
+ BOOL* wasPromotedToNextTierRef)
{
WRAPPER_NO_CONTRACT;
_ASSERTE(pMethodDesc->IsEligibleForTieredCompilation());
_ASSERTE(shouldStopCountingCallsRef != nullptr);
- _ASSERTE(wasPromotedToTier1Ref != nullptr);
+ _ASSERTE(wasPromotedToNextTierRef != nullptr);
*shouldStopCountingCallsRef =
// Stop call counting when the delay is in effect
IsTieringDelayActive() ||
// Initiate the delay on tier 0 activity (when a new eligible method is called the first time)
- (currentCallCount == 1 && g_pConfig->TieredCompilation_Tier1CallCountingDelayMs() != 0) ||
+ (isFirstCall && g_pConfig->TieredCompilation_Tier1CallCountingDelayMs() != 0) ||
// Stop call counting when ready for tier 1 promotion
- currentCallCount >= m_callCountOptimizationThreshhold;
+ currentCallCountLimit <= 0;
- *wasPromotedToTier1Ref = currentCallCount >= m_callCountOptimizationThreshhold;
+ *wasPromotedToNextTierRef = currentCallCountLimit <= 0;
- if (currentCallCount == m_callCountOptimizationThreshhold)
+ if (currentCallCountLimit == 0)
{
AsyncPromoteMethodToTier1(pMethodDesc);
}
}
-void TieredCompilationManager::OnMethodCallCountingStoppedWithoutTier1Promotion(MethodDesc* pMethodDesc)
+void TieredCompilationManager::OnMethodCallCountingStoppedWithoutTierPromotion(MethodDesc* pMethodDesc)
{
WRAPPER_NO_CONTRACT;
_ASSERTE(pMethodDesc != nullptr);
_ASSERTE(pMethodDesc->IsEligibleForTieredCompilation());
- if (g_pConfig->TieredCompilation_Tier1CallCountingDelayMs() == 0)
+ if (g_pConfig->TieredCompilation_Tier1CallCountingDelayMs() == 0 ||
+ !pMethodDesc->GetCallCounter()->IsCallCountingEnabled(pMethodDesc))
{
return;
}
#ifdef FEATURE_TIERED_COMPILATION
public:
- static bool RequiresCallCounting(MethodDesc* pMethodDesc);
- void OnMethodCalled(MethodDesc* pMethodDesc, DWORD currentCallCount, BOOL* shouldStopCountingCallsRef, BOOL* wasPromotedToTier1Ref);
- void OnMethodCallCountingStoppedWithoutTier1Promotion(MethodDesc* pMethodDesc);
+ void OnTier0MethodCalled(MethodDesc* pMethodDesc, bool isFirstCall, int currentCallCountLimit, BOOL* shouldStopCountingCallsRef, BOOL* wasPromotedToNextTierRef);
+ void OnMethodCallCountingStoppedWithoutTierPromotion(MethodDesc* pMethodDesc);
void AsyncPromoteMethodToTier1(MethodDesc* pMethodDesc);
void Shutdown();
static CORJIT_FLAGS GetJitFlags(NativeCodeVersion nativeCodeVersion);
ADID m_domainId;
BOOL m_isAppDomainShuttingDown;
DWORD m_countOptimizationThreadsRunning;
- DWORD m_callCountOptimizationThreshhold;
DWORD m_optimizationQuantumMs;
SArray<MethodDesc*>* m_methodsPendingCountingForTier1;
HANDLE m_tieringDelayTimerHandle;