1// Licensed to the .NET Foundation under one or more agreements.
2// The .NET Foundation licenses this file to you under the MIT license.
3// See the LICENSE file in the project root for more information.
4// ===========================================================================
5// File: CallCounter.CPP
6//
7// ===========================================================================
8
9
10
11#include "common.h"
12#include "excep.h"
13#include "log.h"
14#include "tieredcompilation.h"
15#include "callcounter.h"
16
17#ifdef FEATURE_TIERED_COMPILATION
18
19CallCounter::CallCounter()
20{
21 LIMITED_METHOD_CONTRACT;
22
23 m_lock.Init(LOCK_TYPE_DEFAULT);
24}
25
26// This is called by the prestub each time the method is invoked in a particular
27// AppDomain (the AppDomain for which AppDomain.GetCallCounter() == this). These
28// calls continue until we backpatch the prestub to avoid future calls. This allows
29// us to track the number of calls to each method and use it as a trigger for tiered
30// compilation.
31//
32// Returns TRUE if no future invocations are needed (we reached the count we cared about)
33// and FALSE otherwise. It is permissible to keep calling even when TRUE was previously
34// returned and multi-threaded race conditions will surely cause this to occur.
35void CallCounter::OnMethodCalled(
36 MethodDesc* pMethodDesc,
37 TieredCompilationManager *pTieredCompilationManager,
38 BOOL* shouldStopCountingCallsRef,
39 BOOL* wasPromotedToTier1Ref)
40{
41 STANDARD_VM_CONTRACT;
42
43 _ASSERTE(pMethodDesc->IsEligibleForTieredCompilation());
44 _ASSERTE(pTieredCompilationManager != nullptr);
45 _ASSERTE(shouldStopCountingCallsRef != nullptr);
46 _ASSERTE(wasPromotedToTier1Ref != nullptr);
47
48 // PERF: This as a simple to implement, but not so performant, call counter
49 // Currently this is only called until we reach a fixed call count and then
50 // disabled. Its likely we'll want to improve this at some point but
51 // its not as bad as you might expect. Allocating a counter inline in the
52 // MethodDesc or at some location computable from the MethodDesc should
53 // eliminate 1 pointer per-method (the MethodDesc* key) and the CPU
54 // overhead to acquire the lock/search the dictionary. Depending on where it
55 // is we may also be able to reduce it to 1 byte counter without wasting the
56 // following bytes for alignment. Further work to inline the OnMethodCalled
57 // callback directly into the jitted code would eliminate CPU overhead of
58 // leaving the prestub unpatched, but may not be good overall as it increases
59 // the size of the jitted code.
60
61
62 TieredCompilationManager* pCallCounterSink = NULL;
63 int callCount;
64 {
65 //Be careful if you convert to something fully lock/interlocked-free that
66 //you correctly handle what happens when some N simultaneous calls don't
67 //all increment the counter. The slight drift is probably neglible for tuning
68 //but TieredCompilationManager::OnMethodCalled() doesn't expect multiple calls
69 //each claiming to be exactly the threshhold call count needed to trigger
70 //optimization.
71 SpinLockHolder holder(&m_lock);
72 CallCounterEntry* pEntry = const_cast<CallCounterEntry*>(m_methodToCallCount.LookupPtr(pMethodDesc));
73 if (pEntry == NULL)
74 {
75 callCount = 1;
76 m_methodToCallCount.Add(CallCounterEntry(pMethodDesc, callCount));
77 }
78 else
79 {
80 pEntry->callCount++;
81 callCount = pEntry->callCount;
82 }
83 }
84
85 pTieredCompilationManager->OnMethodCalled(pMethodDesc, callCount, shouldStopCountingCallsRef, wasPromotedToTier1Ref);
86}
87
88#endif // FEATURE_TIERED_COMPILATION
89