forked from dotnet/runtime
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcallcounting.h
471 lines (373 loc) · 16.7 KB
/
callcounting.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#pragma once
#include "codeversion.h"
#ifdef FEATURE_TIERED_COMPILATION
/*******************************************************************************************************************************
** Summary
Outline of phases
-----------------
When starting call counting for a method (see CallCountingManager::SetCodeEntryPoint):
- A CallCountingInfo is created (associated with the NativeCodeVersion to be counted), which initializes a remaining call count
with a threshold
- A CallCountingStub is created. It contains a small amount of code that decrements the remaining call count and checks for
zero. When nonzero, it jumps to the code version's native code entry point. When zero, it forwards to a helper function that
handles tier promotion.
- For tiered methods that don't have a precode (virtual and interface methods when slot backpatching is enabled), a forwarder
stub (a precode) is created and it forwards to the call counting stub. This is so that the call counting stub can be safely
and easily deleted. The forwarder stubs are only used when counting calls, there is one per method (not per code version), and
they are not deleted.
- The method's code entry point is set to the forwarder stub or the call counting stub to count calls to the code version
When the call count threshold is reached (see CallCountingManager::OnCallCountThresholdReached):
- The helper call enqueues completion of call counting for background processing
- When completing call counting in the background, the code version is enqueued for promotion, and the call counting stub is
removed from the call chain
After all work queued for promotion is completed and methods transitioned to optimized tier, some cleanup follows
(see CallCountingManager::StopAndDeleteAllCallCountingStubs):
- Some heuristics are checked and if cleanup will be done, the runtime is suspended
- All call counting stubs are deleted. For code versions that have not completed counting, the method's code entry point is
reset such that call counting would be reestablished on the next call.
- Completed call counting infos are deleted
- For methods that no longer have any code versions that need to be counted, the forwarder stubs are no longer tracked. If a
new IL code version is added thereafter (perhaps by a profiler), a new forwarder stub may be created.
Miscellaneous
-------------
- The CallCountingManager is the main class with most of the logic. Its private subclasses are just simple data structures.
- The code versioning lock is used for data structures used for call counting. Installing a call counting stub requires that we
know what the currently active code version is, it made sense to use the same lock.
- Call counting stubs have hardcoded code. x64 has short and long stubs, short stubs are used when possible (often) and use
IP-relative branches to the method's code and helper stub. Other archs have only one type of stub (a short stub).
- Call counting stubs pass a stub-identifying token to the threshold-reached helper function. The stub's address can be
determined from it. On x64, it also indicates whether the stub is a short or long stub.
- From a call counting stub, the call counting info can be determined using the remaining call count cell, and from the call
counting info the code version and method can be determined
- Call counting is not stopped when the tiering delay is reactivated (often happens in larger and more realistic scenarios). The
overhead necessary to stop and restart call counting (among other things, many methods will have to go through the prestub
again) is greater than the overhead of completing call counting + calling the threshold-reached helper function, even for very
high call count thresholds. While it may at times be desirable to not count method calls during startup phases, there would be
a fair bit of additional overhead to stop counting. On the other hand, it may at times be beneficial to rejit some methods
during startup. So for now, only newly called methods during the current tiering delay would not be counted, any that already
started counting will continue (their delay already expired).
*******************************************************************************************************************************/
#define DISABLE_COPY(T) \
T(const T &) = delete; \
T &operator =(const T &) = delete
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Call counting
typedef UINT16 CallCount;
typedef DPTR(CallCount) PTR_CallCount;
////////////////////////////////////////////////////////////////
// CallCountingStub
class CallCountingStub;
typedef DPTR(const CallCountingStub) PTR_CallCountingStub;
struct CallCountingStubData
{
PTR_CallCount RemainingCallCountCell;
PCODE TargetForMethod;
PCODE TargetForThresholdReached;
};
typedef DPTR(CallCountingStubData) PTR_CallCountingStubData;
class CallCountingStub
{
public:
#if defined(TARGET_AMD64)
static const SIZE_T CodeSize = 24;
#elif defined(TARGET_X86)
static const SIZE_T CodeSize = 24;
#elif defined(TARGET_ARM64)
static const SIZE_T CodeSize = 40;
#elif defined(TARGET_ARM)
static const SIZE_T CodeSize = 32;
#elif defined(TARGET_LOONGARCH64)
static const SIZE_T CodeSize = 40;
#elif defined(TARGET_RISCV64)
static const SIZE_T CodeSize = 40;
#elif defined(TARGET_WASM)
static const SIZE_T CodeSize = 0;
#endif
private:
UINT8 m_code[CodeSize];
#if defined(TARGET_ARM64) && defined(TARGET_UNIX)
static void (*CallCountingStubCode)();
#endif
public:
static const SIZE_T Alignment = sizeof(void *);
protected:
PTR_CallCountingStubData GetData() const
{
return dac_cast<PTR_CallCountingStubData>(dac_cast<TADDR>(this) + GetStubCodePageSize());
}
#ifndef DACCESS_COMPILE
static const PCODE TargetForThresholdReached;
CallCountingStub() = default;
public:
static const CallCountingStub *From(TADDR stubIdentifyingToken);
PCODE GetEntryPoint() const
{
WRAPPER_NO_CONTRACT;
return PINSTRToPCODE((TADDR)this);
}
#endif // !DACCESS_COMPILE
public:
#ifndef DACCESS_COMPILE
void Initialize(PCODE targetForMethod, CallCount* remainingCallCountCell)
{
PTR_CallCountingStubData pStubData = GetData();
pStubData->RemainingCallCountCell = remainingCallCountCell;
pStubData->TargetForMethod = targetForMethod;
pStubData->TargetForThresholdReached = CallCountingStub::TargetForThresholdReached;
}
static void StaticInitialize();
#endif // !DACCESS_COMPILE
static void GenerateCodePage(BYTE* pageBase, BYTE* pageBaseRX, SIZE_T size);
PTR_CallCount GetRemainingCallCountCell() const;
PCODE GetTargetForMethod() const;
protected:
DISABLE_COPY(CallCountingStub);
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CallCountingManager
class CallCountingManager;
typedef DPTR(CallCountingManager) PTR_CallCountingManager;
class CallCountingManager
{
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CallCountingManager::CallCountingInfo
private:
class CallCountingInfo;
typedef DPTR(CallCountingInfo) PTR_CallCountingInfo;
class CallCountingInfo
{
public:
enum class Stage : UINT8
{
// Stub is definitely not going to be called, stub may be deleted
StubIsNotActive,
// Stub may be called, don't know if it's actually active (changes to code versions, etc.)
StubMayBeActive,
// Stub may be active, call counting complete, not yet promoted
PendingCompletion,
// 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:
const NativeCodeVersion m_codeVersion;
const CallCountingStub *m_callCountingStub;
CallCount m_remainingCallCount;
Stage m_stage;
#ifndef DACCESS_COMPILE
private:
CallCountingInfo(NativeCodeVersion codeVersion);
public:
static CallCountingInfo *CreateWithCallCountingDisabled(NativeCodeVersion codeVersion);
CallCountingInfo(NativeCodeVersion codeVersion, CallCount callCountThreshold);
~CallCountingInfo();
#endif
public:
static PTR_CallCountingInfo From(PTR_CallCount remainingCallCountCell);
NativeCodeVersion GetCodeVersion() const;
#ifndef DACCESS_COMPILE
public:
const CallCountingStub *GetCallCountingStub() const;
void SetCallCountingStub(const CallCountingStub *callCountingStub);
void ClearCallCountingStub();
CallCount *GetRemainingCallCountCell();
#endif
public:
Stage GetStage() const;
#ifndef DACCESS_COMPILE
public:
void SetStage(Stage stage);
#endif
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CallCountingManager::CallCountingInfo::CodeVersionHashTraits
public:
class CodeVersionHashTraits : public DefaultSHashTraits<PTR_CallCountingInfo>
{
private:
typedef DefaultSHashTraits<PTR_CallCountingInfo> Base;
public:
typedef Base::element_t element_t;
typedef Base::count_t count_t;
typedef const NativeCodeVersion key_t;
public:
static key_t GetKey(const element_t &e);
static BOOL Equals(const key_t &k1, const key_t &k2);
static count_t Hash(const key_t &k);
};
};
typedef SHash<CallCountingInfo::CodeVersionHashTraits> CallCountingInfoByCodeVersionHash;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CallCountingManager::CallCountingStubAllocator
private:
class CallCountingStubAllocator
{
private:
// LoaderHeap cannot be constructed when DACCESS_COMPILE is defined (at the time, its destructor was private). Working
// around that by controlling creation/destruction using a pointer.
InterleavedLoaderHeap *m_heap;
RangeList m_heapRangeList;
public:
CallCountingStubAllocator();
~CallCountingStubAllocator();
#ifndef DACCESS_COMPILE
public:
void Reset();
const CallCountingStub *AllocateStub(CallCount *remainingCallCountCell, PCODE targetForMethod);
private:
InterleavedLoaderHeap *AllocateHeap();
#endif // !DACCESS_COMPILE
public:
bool IsStub(TADDR entryPoint);
#ifdef DACCESS_COMPILE
void EnumerateHeapRanges(CLRDataEnumMemoryFlags flags);
#endif
DISABLE_COPY(CallCountingStubAllocator);
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CallCountingManager::MethodDescForwarderStub
private:
class MethodDescForwarderStubHashTraits : public DefaultSHashTraits<Precode *>
{
private:
typedef DefaultSHashTraits<Precode *> Base;
public:
typedef Base::element_t element_t;
typedef Base::count_t count_t;
typedef MethodDesc *key_t;
public:
static key_t GetKey(const element_t &e);
static BOOL Equals(const key_t &k1, const key_t &k2);
static count_t Hash(const key_t &k);
};
typedef SHash<MethodDescForwarderStubHashTraits> MethodDescForwarderStubHash;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CallCountingManager::CallCountingManagerHashTraits
private:
class CallCountingManagerHashTraits : public DefaultSHashTraits<PTR_CallCountingManager>
{
private:
typedef DefaultSHashTraits<PTR_CallCountingManager> Base;
public:
typedef Base::element_t element_t;
typedef Base::count_t count_t;
typedef PTR_CallCountingManager key_t;
public:
static key_t GetKey(const element_t &e);
static BOOL Equals(const key_t &k1, const key_t &k2);
static count_t Hash(const key_t &k);
};
typedef SHash<CallCountingManagerHashTraits> CallCountingManagerHash;
typedef DPTR(CallCountingManagerHash) PTR_CallCountingManagerHash;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CallCountingManager members
private:
static PTR_CallCountingManagerHash s_callCountingManagers;
static COUNT_T s_callCountingStubCount;
static COUNT_T s_activeCallCountingStubCount;
static COUNT_T s_completedCallCountingStubCount;
private:
CallCountingInfoByCodeVersionHash m_callCountingInfoByCodeVersionHash;
CallCountingStubAllocator m_callCountingStubAllocator;
MethodDescForwarderStubHash m_methodDescForwarderStubHash;
SArray<CallCountingInfo *> m_callCountingInfosPendingCompletion;
public:
CallCountingManager();
~CallCountingManager();
#ifndef DACCESS_COMPILE
public:
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,
PCODE codeEntryPoint,
bool wasMethodCalled,
bool *createTieringBackgroundWorker);
static PCODE OnCallCountThresholdReached(TransitionBlock *transitionBlock, TADDR stubIdentifyingToken);
static COUNT_T GetCountOfCodeVersionsPendingCompletion();
static void CompleteCallCounting();
public:
static void StopAndDeleteAllCallCountingStubs();
static const CallCountingStub* GetCallCountingStub(CallCount *pCallCount)
{
return CallCountingInfo::From(pCallCount)->GetCallCountingStub();
}
private:
static void StopAllCallCounting(TieredCompilationManager *tieredCompilationManager);
static void DeleteAllCallCountingStubs();
void TrimCollections();
#endif // !DACCESS_COMPILE
public:
static bool IsCallCountingStub(PCODE entryPoint);
static PCODE GetTargetForMethod(PCODE callCountingStubEntryPoint);
#ifdef DACCESS_COMPILE
static void DacEnumerateCallCountingStubHeapRanges(CLRDataEnumMemoryFlags flags);
#endif
DISABLE_COPY(CallCountingManager);
};
////////////////////////////////////////////////////////////////
// CallCountingStub definitions
#ifndef DACCESS_COMPILE
inline const CallCountingStub *CallCountingStub::From(TADDR stubIdentifyingToken)
{
WRAPPER_NO_CONTRACT;
_ASSERTE(stubIdentifyingToken != (TADDR)NULL);
// The stubIdentifyingToken is the pointer to the CallCount
const CallCountingStub *stub = CallCountingManager::GetCallCountingStub((CallCount*)stubIdentifyingToken);
_ASSERTE(IS_ALIGNED(stub, Alignment));
return stub;
}
#endif
inline PTR_CallCount CallCountingStub::GetRemainingCallCountCell() const
{
WRAPPER_NO_CONTRACT;
return GetData()->RemainingCallCountCell;
}
inline PCODE CallCountingStub::GetTargetForMethod() const
{
WRAPPER_NO_CONTRACT;
return GetData()->TargetForMethod;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CallCountingManager::CallCountingStubManager
class CallCountingStubManager;
typedef VPTR(CallCountingStubManager) PTR_CallCountingStubManager;
class CallCountingStubManager : public StubManager
{
VPTR_VTABLE_CLASS(CallCountingStubManager, StubManager);
private:
SPTR_DECL(CallCountingStubManager, g_pManager);
#ifndef DACCESS_COMPILE
public:
CallCountingStubManager();
public:
static void Init();
#endif
#ifdef _DEBUG
public:
virtual const char *DbgGetName(); // override
#endif
#ifdef DACCESS_COMPILE
public:
virtual LPCWSTR GetStubManagerName(PCODE addr);
#endif
protected:
virtual BOOL CheckIsStub_Internal(PCODE entryPoint); // override
virtual BOOL DoTraceStub(PCODE callCountingStubEntryPoint, TraceDestination *trace); // override
#ifdef DACCESS_COMPILE
protected:
virtual void DoEnumMemoryRegions(CLRDataEnumMemoryFlags flags); // override
#endif
DISABLE_COPY(CallCountingStubManager);
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#undef DISABLE_COPY
#endif // FEATURE_TIERED_COMPILATION