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 | * |
6 | * GC Information Encoding API |
7 | * |
8 | *****************************************************************/ |
9 | |
10 | /***************************************************************** |
11 | |
12 | ENCODING LAYOUT |
13 | |
14 | 1. Header |
15 | |
16 | Slim Header for simple and common cases: |
17 | - EncodingType[Slim] |
18 | - ReturnKind (Fat: 2 bits) |
19 | - CodeLength |
20 | - NumCallSites (#ifdef PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED) |
21 | |
22 | Fat Header for other cases: |
23 | - EncodingType[Fat] |
24 | - Flag: isVarArg, |
25 | hasSecurityObject, |
26 | hasGSCookie, |
27 | hasPSPSymStackSlot, |
28 | hasGenericsInstContextStackSlot, |
29 | hasStackBaseregister, |
30 | wantsReportOnlyLeaf (AMD64 use only), |
31 | hasTailCalls (ARM/ARM64 only) |
32 | hasSizeOfEditAndContinuePreservedArea |
33 | hasReversePInvokeFrame, |
34 | - ReturnKind (Fat: 4 bits) |
35 | - CodeLength |
36 | - Prolog (if hasSecurityObject || hasGenericsInstContextStackSlot || hasGSCookie) |
37 | - Epilog (if hasGSCookie) |
38 | - SecurityObjectStackSlot (if any) |
39 | - GSCookieStackSlot (if any) |
40 | - PSPSymStackSlot (if any) |
41 | - GenericsInstContextStackSlot (if any) |
42 | - StackBaseRegister (if any) |
43 | - SizeOfEditAndContinuePreservedArea (if any) |
44 | - ReversePInvokeFrameSlot (if any) |
45 | - SizeOfStackOutgoingAndScratchArea (#ifdef FIXED_STACK_PARAMETER_SCRATCH_AREA) |
46 | - NumCallSites (#ifdef PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED) |
47 | - NumInterruptibleRanges |
48 | |
49 | 2. Call sites offsets (#ifdef PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED) |
50 | 3. Fully-interruptible ranges |
51 | 4. Slot table |
52 | 5. GC state at call sites (#ifdef PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED) |
53 | 6. GC state at try clauses (#ifdef PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED) |
54 | 7. Chunk pointers |
55 | 8. Chunk encodings |
56 | |
57 | |
58 | STANDALONE_BUILD |
59 | |
60 | The STANDALONE_BUILD switch can be used to build the GcInfoEncoder library |
61 | independently by clients outside the CoreClr tree. |
62 | |
63 | The GcInfo library uses some custom data-structures (ex: ArrayList, SimplerHashTable) |
64 | and includes some utility libraries (ex: UtilCode) which pull in several other |
65 | headers with considerable unrelated content. Rather than porting all the |
66 | utility code to suite other clients, the STANDALONE_BUILD switch can be used |
67 | to include only the minimal set of headers specific to GcInfo encodings. |
68 | |
69 | Clients of STANDALONE_BUILD will likely use standard library |
70 | implementations of data-structures like ArrayList, HashMap etc., in place |
71 | of the custom implementation currently used by GcInfoEncoder. |
72 | |
73 | Rather than spew the GcInfoEnoder code with |
74 | #ifdef STANDALONE_BUILD ... #else .. #endif blocks, we include a special |
75 | header GcInfoUtil.h in STANDALONE_BUILD mode. GcInfoUtil.h is expected to |
76 | supply the interface/implementation for the data-structures and utilities |
77 | used by GcInfoEncoder. This header should be provided by the clients doing |
78 | the standalone build in their source tree. |
79 | |
80 | *****************************************************************/ |
81 | |
82 | |
83 | #ifndef __GCINFOENCODER_H__ |
84 | #define __GCINFOENCODER_H__ |
85 | |
86 | #ifdef STANDALONE_BUILD |
87 | #include <wchar.h> |
88 | #include <stdio.h> |
89 | #include "GcInfoUtil.h" |
90 | #include "corjit.h" |
91 | #else |
92 | #include <windows.h> |
93 | #include <wchar.h> |
94 | #include <stdio.h> |
95 | #include "corjit.h" |
96 | #include "iallocator.h" |
97 | #include "gcinfoarraylist.h" |
98 | #include "stdmacros.h" |
99 | #include "eexcp.h" |
100 | #endif |
101 | |
102 | #include "gcinfotypes.h" |
103 | |
104 | // As stated in issue #6008, GcInfoSize should be incorporated into debug builds. |
105 | #ifdef _DEBUG |
106 | #define MEASURE_GCINFO |
107 | #endif |
108 | |
109 | #ifdef MEASURE_GCINFO |
110 | struct GcInfoSize |
111 | { |
112 | size_t TotalSize; |
113 | |
114 | size_t NumMethods; |
115 | size_t NumCallSites; |
116 | size_t NumRanges; |
117 | size_t NumRegs; |
118 | size_t NumStack; |
119 | size_t NumUntracked; |
120 | size_t NumTransitions; |
121 | size_t SizeOfCode; |
122 | size_t EncPreservedSlots; |
123 | |
124 | size_t UntrackedSlotSize; |
125 | size_t NumUntrackedSize; |
126 | size_t FlagsSize; |
127 | size_t RetKindSize; |
128 | size_t CodeLengthSize; |
129 | size_t ProEpilogSize; |
130 | size_t SecObjSize; |
131 | size_t GsCookieSize; |
132 | size_t PspSymSize; |
133 | size_t GenericsCtxSize; |
134 | size_t StackBaseSize; |
135 | size_t ReversePInvokeFrameSize; |
136 | size_t FixedAreaSize; |
137 | size_t NumCallSitesSize; |
138 | size_t NumRangesSize; |
139 | size_t CallSitePosSize; |
140 | size_t RangeSize; |
141 | size_t NumRegsSize; |
142 | size_t NumStackSize; |
143 | size_t RegSlotSize; |
144 | size_t StackSlotSize; |
145 | size_t CallSiteStateSize; |
146 | size_t EhPosSize; |
147 | size_t EhStateSize; |
148 | size_t ChunkPtrSize; |
149 | size_t ChunkMaskSize; |
150 | size_t ChunkFinalStateSize; |
151 | size_t ChunkTransitionSize; |
152 | |
153 | GcInfoSize(); |
154 | GcInfoSize& operator+=(const GcInfoSize& other); |
155 | void Log(DWORD level, const char * ); |
156 | }; |
157 | #endif |
158 | |
159 | struct GcSlotDesc |
160 | { |
161 | union |
162 | { |
163 | UINT32 RegisterNumber; |
164 | GcStackSlot Stack; |
165 | } Slot; |
166 | GcSlotFlags Flags; |
167 | |
168 | BOOL IsRegister() const |
169 | { |
170 | return (Flags & GC_SLOT_IS_REGISTER); |
171 | } |
172 | BOOL IsInterior() const |
173 | { |
174 | return (Flags & GC_SLOT_INTERIOR); |
175 | } |
176 | BOOL IsPinned() const |
177 | { |
178 | return (Flags & GC_SLOT_PINNED); |
179 | } |
180 | BOOL IsUntracked() const |
181 | { |
182 | return (Flags & GC_SLOT_UNTRACKED); |
183 | } |
184 | BOOL IsDeleted() const |
185 | { |
186 | return (Flags & GC_SLOT_IS_DELETED); |
187 | } |
188 | void MarkDeleted() |
189 | { |
190 | Flags = (GcSlotFlags) (Flags | GC_SLOT_IS_DELETED); |
191 | } |
192 | }; |
193 | |
194 | class BitArray; |
195 | class BitStreamWriter |
196 | { |
197 | public: |
198 | BitStreamWriter( IAllocator* pAllocator ); |
199 | |
200 | // bit 0 is the least significative bit |
201 | void Write( size_t data, UINT32 count ); |
202 | |
203 | inline size_t GetBitCount() |
204 | { |
205 | return m_BitCount; |
206 | } |
207 | |
208 | inline size_t GetByteCount() |
209 | { |
210 | return ( m_BitCount + 7 ) / 8; |
211 | } |
212 | |
213 | |
214 | void CopyTo( BYTE* buffer ); |
215 | void Dispose(); |
216 | |
217 | //-------------------------------------------------------- |
218 | // Compute the number of bits used to encode variable length numbers |
219 | // Uses base+1 bits at minimum |
220 | // Bits 0..(base-1) represent the encoded quantity |
221 | // If it doesn't fit, set bit #base to 1 and use base+1 more bits |
222 | //-------------------------------------------------------- |
223 | static int SizeofVarLengthUnsigned( size_t n, UINT32 base ); |
224 | |
225 | //-------------------------------------------------------- |
226 | // Encode variable length numbers |
227 | // Uses base+1 bits at minimum |
228 | // Bits 0..(base-1) represent the encoded quantity |
229 | // If it doesn't fit, set bit #base to 1 and use base+1 more bits |
230 | //-------------------------------------------------------- |
231 | int EncodeVarLengthUnsigned( size_t n, UINT32 base ); |
232 | |
233 | //-------------------------------------------------------- |
234 | // Signed quantities are encoded the same as unsigned |
235 | // The most relevant difference is that a number is considered |
236 | // to fit in base bits if the topmost bit of a base-long chunk |
237 | // matches the sign of the whole number |
238 | //-------------------------------------------------------- |
239 | int EncodeVarLengthSigned( SSIZE_T n, UINT32 base ); |
240 | |
241 | private: |
242 | class MemoryBlockList; |
243 | class MemoryBlock |
244 | { |
245 | friend class MemoryBlockList; |
246 | MemoryBlock* m_next; |
247 | |
248 | public: |
249 | size_t Contents[]; |
250 | |
251 | inline MemoryBlock* Next() |
252 | { |
253 | return m_next; |
254 | } |
255 | }; |
256 | |
257 | class MemoryBlockList |
258 | { |
259 | MemoryBlock* m_head; |
260 | MemoryBlock* m_tail; |
261 | |
262 | public: |
263 | MemoryBlockList(); |
264 | |
265 | inline MemoryBlock* Head() |
266 | { |
267 | return m_head; |
268 | } |
269 | |
270 | MemoryBlock* AppendNew(IAllocator* allocator, size_t bytes); |
271 | void Dispose(IAllocator* allocator); |
272 | }; |
273 | |
274 | IAllocator* m_pAllocator; |
275 | size_t m_BitCount; |
276 | UINT32 m_FreeBitsInCurrentSlot; |
277 | MemoryBlockList m_MemoryBlocks; |
278 | const static int m_MemoryBlockSize = 128; // must be a multiple of the pointer size |
279 | size_t* m_pCurrentSlot; // bits are written through this pointer |
280 | size_t* m_OutOfBlockSlot; // sentinel value to determine when the block is full |
281 | #ifdef _DEBUG |
282 | int m_MemoryBlocksCount; |
283 | #endif |
284 | |
285 | private: |
286 | // Writes bits knowing that they will all fit in the current memory slot |
287 | inline void WriteInCurrentSlot( size_t data, UINT32 count ) |
288 | { |
289 | data &= SAFE_SHIFT_LEFT(1, count) - 1; |
290 | data <<= (BITS_PER_SIZE_T - m_FreeBitsInCurrentSlot); |
291 | *m_pCurrentSlot |= data; |
292 | } |
293 | |
294 | inline void AllocMemoryBlock() |
295 | { |
296 | _ASSERTE( IS_ALIGNED( m_MemoryBlockSize, sizeof( size_t ) ) ); |
297 | MemoryBlock* pMemBlock = m_MemoryBlocks.AppendNew(m_pAllocator, m_MemoryBlockSize); |
298 | |
299 | m_pCurrentSlot = pMemBlock->Contents; |
300 | m_OutOfBlockSlot = m_pCurrentSlot + m_MemoryBlockSize / sizeof( size_t ); |
301 | |
302 | #ifdef _DEBUG |
303 | m_MemoryBlocksCount++; |
304 | #endif |
305 | |
306 | } |
307 | |
308 | inline void InitCurrentSlot() |
309 | { |
310 | m_FreeBitsInCurrentSlot = BITS_PER_SIZE_T; |
311 | *m_pCurrentSlot = 0; |
312 | } |
313 | }; |
314 | |
315 | |
316 | typedef UINT32 GcSlotId; |
317 | |
318 | |
319 | inline UINT32 GetNormCodeOffsetChunk(UINT32 normCodeOffset) |
320 | { |
321 | return normCodeOffset / NUM_NORM_CODE_OFFSETS_PER_CHUNK; |
322 | } |
323 | |
324 | inline UINT32 GetCodeOffsetChunk(UINT32 codeOffset) |
325 | { |
326 | return (NORMALIZE_CODE_OFFSET(codeOffset)) / NUM_NORM_CODE_OFFSETS_PER_CHUNK; |
327 | } |
328 | |
329 | enum GENERIC_CONTEXTPARAM_TYPE |
330 | { |
331 | GENERIC_CONTEXTPARAM_NONE = 0, |
332 | GENERIC_CONTEXTPARAM_MT = 1, |
333 | GENERIC_CONTEXTPARAM_MD = 2, |
334 | GENERIC_CONTEXTPARAM_THIS = 3, |
335 | }; |
336 | |
337 | extern void DECLSPEC_NORETURN ThrowOutOfMemory(); |
338 | |
339 | class GcInfoEncoder |
340 | { |
341 | public: |
342 | typedef void (*NoMemoryFunction)(void); |
343 | |
344 | GcInfoEncoder( |
345 | ICorJitInfo* pCorJitInfo, |
346 | CORINFO_METHOD_INFO* pMethodInfo, |
347 | IAllocator* pJitAllocator, |
348 | NoMemoryFunction pNoMem = ::ThrowOutOfMemory |
349 | ); |
350 | |
351 | struct LifetimeTransition |
352 | { |
353 | UINT32 CodeOffset; |
354 | GcSlotId SlotId; |
355 | BYTE BecomesLive; |
356 | BYTE IsDeleted; |
357 | }; |
358 | |
359 | |
360 | #ifdef PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED |
361 | void DefineCallSites(UINT32* pCallSites, BYTE* pCallSiteSizes, UINT32 numCallSites); |
362 | #endif |
363 | |
364 | //------------------------------------------------------------------------ |
365 | // Interruptibility |
366 | //------------------------------------------------------------------------ |
367 | |
368 | // An instruction at offset x will be interruptible |
369 | // if-and-only-if startInstructionOffset <= x < startInstructionOffset+length |
370 | void DefineInterruptibleRange( UINT32 startInstructionOffset, UINT32 length ); |
371 | |
372 | |
373 | //------------------------------------------------------------------------ |
374 | // Slot information |
375 | //------------------------------------------------------------------------ |
376 | |
377 | // |
378 | // If spOffset is relative to the current SP, spOffset must be non-negative. |
379 | // If spOffset is relative to the SP of the caller (same as SP at the method entry and exit) |
380 | // Negative offsets describe GC refs in the local and outgoing areas. |
381 | // Positive offsets describe GC refs in the scratch area |
382 | // Note that if the dynamic allocation area is resized, the outgoing area will not be valid anymore |
383 | // Old slots must be declared dead and new ones can be defined. |
384 | // It's up to the JIT to do the right thing. We don't enforce this. |
385 | |
386 | GcSlotId GetRegisterSlotId( UINT32 regNum, GcSlotFlags flags ); |
387 | GcSlotId GetStackSlotId( INT32 spOffset, GcSlotFlags flags, GcStackSlotBase spBase = GC_CALLER_SP_REL ); |
388 | |
389 | // |
390 | // After a FinalizeSlotIds is called, no more slot definitions can be made. |
391 | // FinalizeSlotIds must be called once and only once before calling Build() |
392 | // |
393 | void FinalizeSlotIds(); |
394 | |
395 | |
396 | //------------------------------------------------------------------------ |
397 | // Fully-interruptible information |
398 | //------------------------------------------------------------------------ |
399 | |
400 | // |
401 | // For inputs, pass zero as offset |
402 | // |
403 | |
404 | // Indicates that the GC state of slot "slotId" becomes (and remains, until another transition) |
405 | // "slotState" after the instruction preceding "instructionOffset" (so it is first in this state when |
406 | // the IP of a suspended thread is at this instruction offset). |
407 | |
408 | void SetSlotState( UINT32 instructionOffset, |
409 | GcSlotId slotId, |
410 | GcSlotState slotState |
411 | ); |
412 | |
413 | |
414 | //------------------------------------------------------------------------ |
415 | // ReturnKind |
416 | //------------------------------------------------------------------------ |
417 | |
418 | void SetReturnKind(ReturnKind returnKind); |
419 | |
420 | //------------------------------------------------------------------------ |
421 | // Miscellaneous method information |
422 | //------------------------------------------------------------------------ |
423 | |
424 | void SetSecurityObjectStackSlot( INT32 spOffset ); |
425 | void SetPrologSize( UINT32 prologSize ); |
426 | void SetGSCookieStackSlot( INT32 spOffsetGSCookie, UINT32 validRangeStart, UINT32 validRangeEnd ); |
427 | void SetPSPSymStackSlot( INT32 spOffsetPSPSym ); |
428 | void SetGenericsInstContextStackSlot( INT32 spOffsetGenericsContext, GENERIC_CONTEXTPARAM_TYPE type); |
429 | void SetReversePInvokeFrameSlot(INT32 spOffset); |
430 | void SetIsVarArg(); |
431 | void SetCodeLength( UINT32 length ); |
432 | |
433 | // Optional in the general case. Required if the method uses GC_FRAMEREG_REL stack slots |
434 | void SetStackBaseRegister( UINT32 registerNumber ); |
435 | |
436 | // Number of slots preserved during EnC remap |
437 | void SetSizeOfEditAndContinuePreservedArea( UINT32 size ); |
438 | |
439 | #ifdef _TARGET_AMD64_ |
440 | // Used to only report a frame once for the leaf function/funclet |
441 | // instead of once for each live function/funclet on the stack. |
442 | // Called only by RyuJIT (not JIT64) |
443 | void SetWantsReportOnlyLeaf(); |
444 | #elif defined(_TARGET_ARM_) || defined(_TARGET_ARM64_) |
445 | void SetHasTailCalls(); |
446 | #endif // _TARGET_AMD64_ |
447 | |
448 | #ifdef FIXED_STACK_PARAMETER_SCRATCH_AREA |
449 | void SetSizeOfStackOutgoingAndScratchArea( UINT32 size ); |
450 | #endif // FIXED_STACK_PARAMETER_SCRATCH_AREA |
451 | |
452 | |
453 | //------------------------------------------------------------------------ |
454 | // Encoding |
455 | //------------------------------------------------------------------------ |
456 | |
457 | // |
458 | // Build() encodes GC information into temporary buffers. |
459 | // The method description cannot change after Build is called |
460 | // |
461 | void Build(); |
462 | |
463 | // |
464 | // Write encoded information to its final destination and frees temporary buffers. |
465 | // The encoder shouldn't be used anymore after calling this method. |
466 | // It returns a pointer to the destination buffer, which address is byte-aligned |
467 | // |
468 | BYTE* Emit(); |
469 | |
470 | private: |
471 | |
472 | friend int __cdecl CompareLifetimeTransitionsByOffsetThenSlot(const void*, const void*); |
473 | friend int CompareLifetimeTransitionsByChunk(const void*, const void*); |
474 | |
475 | |
476 | struct InterruptibleRange |
477 | { |
478 | UINT32 NormStartOffset; |
479 | UINT32 NormStopOffset; |
480 | }; |
481 | |
482 | ICorJitInfo* m_pCorJitInfo; |
483 | CORINFO_METHOD_INFO* m_pMethodInfo; |
484 | IAllocator* m_pAllocator; |
485 | NoMemoryFunction m_pNoMem; |
486 | |
487 | #ifdef _DEBUG |
488 | const char *m_MethodName, *m_ModuleName; |
489 | #endif |
490 | |
491 | BitStreamWriter m_Info1; // Used for everything except for chunk encodings |
492 | BitStreamWriter m_Info2; // Used for chunk encodings |
493 | |
494 | GcInfoArrayList<InterruptibleRange, 8> m_InterruptibleRanges; |
495 | GcInfoArrayList<LifetimeTransition, 64> m_LifetimeTransitions; |
496 | |
497 | bool m_IsVarArg; |
498 | #if defined(_TARGET_AMD64_) |
499 | bool m_WantsReportOnlyLeaf; |
500 | #elif defined(_TARGET_ARM_) || defined(_TARGET_ARM64_) |
501 | bool m_HasTailCalls; |
502 | #endif // _TARGET_AMD64_ |
503 | INT32 m_SecurityObjectStackSlot; |
504 | INT32 m_GSCookieStackSlot; |
505 | UINT32 m_GSCookieValidRangeStart; |
506 | UINT32 m_GSCookieValidRangeEnd; |
507 | INT32 m_PSPSymStackSlot; |
508 | INT32 m_GenericsInstContextStackSlot; |
509 | GENERIC_CONTEXTPARAM_TYPE m_contextParamType; |
510 | ReturnKind m_ReturnKind; |
511 | UINT32 m_CodeLength; |
512 | UINT32 m_StackBaseRegister; |
513 | UINT32 m_SizeOfEditAndContinuePreservedArea; |
514 | INT32 m_ReversePInvokeFrameSlot; |
515 | InterruptibleRange* m_pLastInterruptibleRange; |
516 | |
517 | #ifdef FIXED_STACK_PARAMETER_SCRATCH_AREA |
518 | UINT32 m_SizeOfStackOutgoingAndScratchArea; |
519 | #endif // FIXED_STACK_PARAMETER_SCRATCH_AREA |
520 | |
521 | void * eeAllocGCInfo (size_t blockSize); |
522 | |
523 | private: |
524 | |
525 | friend class EncoderCheckState; |
526 | |
527 | static const UINT32 m_SlotTableInitialSize = 32; |
528 | UINT32 m_SlotTableSize; |
529 | UINT32 m_NumSlots; |
530 | GcSlotDesc *m_SlotTable; |
531 | |
532 | #ifdef PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED |
533 | UINT32* m_pCallSites; |
534 | BYTE* m_pCallSiteSizes; |
535 | UINT32 m_NumCallSites; |
536 | #endif // PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED |
537 | |
538 | void GrowSlotTable(); |
539 | |
540 | void WriteSlotStateVector(BitStreamWriter &writer, const BitArray& vector); |
541 | |
542 | UINT32 SizeofSlotStateVarLengthVector(const BitArray& vector, UINT32 baseSkip, UINT32 baseRun); |
543 | void SizeofSlotStateVarLengthVector(const BitArray& vector, UINT32 baseSkip, UINT32 baseRun, UINT32 * pSizeofSimple, UINT32 * pSizeofRLE, UINT32 * pSizeofRLENeg); |
544 | UINT32 WriteSlotStateVarLengthVector(BitStreamWriter &writer, const BitArray& vector, UINT32 baseSkip, UINT32 baseRun); |
545 | |
546 | bool IsAlwaysScratch(GcSlotDesc &slot); |
547 | |
548 | // Assumes that "*ppTransitions" is has size "numTransitions", is sorted by CodeOffset then by SlotId, |
549 | // and that "*ppEndTransitions" points one beyond the end of the array. If "*ppTransitions" contains |
550 | // any dead/live transitions pairs for the same CodeOffset and SlotID, removes those, by allocating a |
551 | // new array, and copying the non-removed elements into it. If it does this, sets "*ppTransitions" to |
552 | // point to the new array, "*pNumTransitions" to its shorted length, and "*ppEndTransitions" to |
553 | // point one beyond the used portion of this array. |
554 | void EliminateRedundantLiveDeadPairs(LifetimeTransition** ppTransitions, |
555 | size_t* pNumTransitions, |
556 | LifetimeTransition** ppEndTransitions); |
557 | |
558 | #ifdef _DEBUG |
559 | bool m_IsSlotTableFrozen; |
560 | #endif |
561 | |
562 | #ifdef MEASURE_GCINFO |
563 | GcInfoSize m_CurrentMethodSize; |
564 | #endif |
565 | }; |
566 | |
567 | #endif // !__GCINFOENCODER_H__ |
568 | |