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
110struct 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 * header);
156};
157#endif
158
159struct 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
194class BitArray;
195class BitStreamWriter
196{
197public:
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
241private:
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
285private:
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
316typedef UINT32 GcSlotId;
317
318
319inline UINT32 GetNormCodeOffsetChunk(UINT32 normCodeOffset)
320{
321 return normCodeOffset / NUM_NORM_CODE_OFFSETS_PER_CHUNK;
322}
323
324inline UINT32 GetCodeOffsetChunk(UINT32 codeOffset)
325{
326 return (NORMALIZE_CODE_OFFSET(codeOffset)) / NUM_NORM_CODE_OFFSETS_PER_CHUNK;
327}
328
329enum 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
337extern void DECLSPEC_NORETURN ThrowOutOfMemory();
338
339class GcInfoEncoder
340{
341public:
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
470private:
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
523private:
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