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 | // Garbage-collector information |
6 | // Keeps track of which variables hold pointers. |
7 | // Generates the GC-tables |
8 | |
9 | #ifndef _JITGCINFO_H_ |
10 | #define _JITGCINFO_H_ |
11 | |
12 | #include "gcinfotypes.h" |
13 | |
14 | #ifndef JIT32_GCENCODER |
15 | #include "gcinfoencoder.h" |
16 | #endif |
17 | |
18 | /*****************************************************************************/ |
19 | |
20 | #ifndef JIT32_GCENCODER |
21 | // Shash typedefs |
22 | struct RegSlotIdKey |
23 | { |
24 | unsigned short m_regNum; |
25 | unsigned short m_flags; |
26 | |
27 | RegSlotIdKey() |
28 | { |
29 | } |
30 | |
31 | RegSlotIdKey(unsigned short regNum, unsigned short flags) : m_regNum(regNum), m_flags(flags) |
32 | { |
33 | } |
34 | |
35 | static unsigned GetHashCode(RegSlotIdKey rsk) |
36 | { |
37 | return (rsk.m_flags << (8 * sizeof(unsigned short))) + rsk.m_regNum; |
38 | } |
39 | |
40 | static bool Equals(RegSlotIdKey rsk1, RegSlotIdKey rsk2) |
41 | { |
42 | return rsk1.m_regNum == rsk2.m_regNum && rsk1.m_flags == rsk2.m_flags; |
43 | } |
44 | }; |
45 | |
46 | struct StackSlotIdKey |
47 | { |
48 | int m_offset; |
49 | bool m_fpRel; |
50 | unsigned short m_flags; |
51 | |
52 | StackSlotIdKey() |
53 | { |
54 | } |
55 | |
56 | StackSlotIdKey(int offset, bool fpRel, unsigned short flags) : m_offset(offset), m_fpRel(fpRel), m_flags(flags) |
57 | { |
58 | } |
59 | |
60 | static unsigned GetHashCode(StackSlotIdKey ssk) |
61 | { |
62 | return (ssk.m_flags << (8 * sizeof(unsigned short))) ^ (unsigned)ssk.m_offset ^ (ssk.m_fpRel ? 0x1000000 : 0); |
63 | } |
64 | |
65 | static bool Equals(StackSlotIdKey ssk1, StackSlotIdKey ssk2) |
66 | { |
67 | return ssk1.m_offset == ssk2.m_offset && ssk1.m_fpRel == ssk2.m_fpRel && ssk1.m_flags == ssk2.m_flags; |
68 | } |
69 | }; |
70 | |
71 | typedef JitHashTable<RegSlotIdKey, RegSlotIdKey, GcSlotId> RegSlotMap; |
72 | typedef JitHashTable<StackSlotIdKey, StackSlotIdKey, GcSlotId> StackSlotMap; |
73 | #endif |
74 | |
75 | typedef JitHashTable<GenTree*, JitPtrKeyFuncs<GenTree>, VARSET_TP*> NodeToVarsetPtrMap; |
76 | |
77 | class GCInfo |
78 | { |
79 | friend class CodeGen; |
80 | |
81 | private: |
82 | Compiler* compiler; |
83 | RegSet* regSet; |
84 | |
85 | public: |
86 | GCInfo(Compiler* theCompiler); |
87 | |
88 | void gcResetForBB(); |
89 | |
90 | void gcMarkRegSetGCref(regMaskTP regMask DEBUGARG(bool forceOutput = false)); |
91 | void gcMarkRegSetByref(regMaskTP regMask DEBUGARG(bool forceOutput = false)); |
92 | void gcMarkRegSetNpt(regMaskTP regMask DEBUGARG(bool forceOutput = false)); |
93 | void gcMarkRegPtrVal(regNumber reg, var_types type); |
94 | |
95 | #ifdef DEBUG |
96 | void gcDspGCrefSetChanges(regMaskTP gcRegGCrefSetNew DEBUGARG(bool forceOutput = false)); |
97 | void gcDspByrefSetChanges(regMaskTP gcRegByrefSetNew DEBUGARG(bool forceOutput = false)); |
98 | #endif // DEBUG |
99 | |
100 | /*****************************************************************************/ |
101 | |
102 | //------------------------------------------------------------------------- |
103 | // |
104 | // The following keeps track of which registers currently hold pointer |
105 | // values. |
106 | // |
107 | |
108 | regMaskTP gcRegGCrefSetCur; // current regs holding GCrefs |
109 | regMaskTP gcRegByrefSetCur; // current regs holding Byrefs |
110 | |
111 | VARSET_TP gcTrkStkPtrLcls; // set of tracked stack ptr lcls (GCref and Byref) - no args |
112 | VARSET_TP gcVarPtrSetCur; // currently live part of "gcTrkStkPtrLcls" |
113 | |
114 | //------------------------------------------------------------------------- |
115 | // |
116 | // The following keeps track of the lifetimes of non-register variables that |
117 | // hold pointers. |
118 | // |
119 | |
120 | struct varPtrDsc |
121 | { |
122 | varPtrDsc* vpdNext; |
123 | |
124 | unsigned vpdVarNum; // which variable is this about? |
125 | |
126 | unsigned vpdBegOfs; // the offset where life starts |
127 | unsigned vpdEndOfs; // the offset where life starts |
128 | }; |
129 | |
130 | varPtrDsc* gcVarPtrList; |
131 | varPtrDsc* gcVarPtrLast; |
132 | |
133 | void gcVarPtrSetInit(); |
134 | |
135 | /*****************************************************************************/ |
136 | |
137 | // 'pointer value' register tracking and argument pushes/pops tracking. |
138 | |
139 | enum rpdArgType_t |
140 | { |
141 | rpdARG_POP, |
142 | rpdARG_PUSH, |
143 | rpdARG_KILL |
144 | }; |
145 | |
146 | struct regPtrDsc |
147 | { |
148 | regPtrDsc* rpdNext; // next entry in the list |
149 | unsigned rpdOffs; // the offset of the instruction |
150 | |
151 | union // 2-16 byte union (depending on architecture) |
152 | { |
153 | struct // 2-16 byte structure (depending on architecture) |
154 | { |
155 | regMaskSmall rpdAdd; // regptr bitset being added |
156 | regMaskSmall rpdDel; // regptr bitset being removed |
157 | } rpdCompiler; |
158 | |
159 | unsigned short rpdPtrArg; // arg offset or popped arg count |
160 | }; |
161 | |
162 | #ifndef JIT32_GCENCODER |
163 | unsigned char rpdCallInstrSize; // Length of the call instruction. |
164 | #endif |
165 | |
166 | unsigned short rpdArg : 1; // is this an argument descriptor? |
167 | unsigned short rpdArgType : 2; // is this an argument push,pop, or kill? |
168 | rpdArgType_t rpdArgTypeGet() |
169 | { |
170 | return (rpdArgType_t)rpdArgType; |
171 | } |
172 | |
173 | unsigned short rpdGCtype : 2; // is this a pointer, after all? |
174 | GCtype rpdGCtypeGet() |
175 | { |
176 | return (GCtype)rpdGCtype; |
177 | } |
178 | |
179 | unsigned short rpdIsThis : 1; // is it the 'this' pointer |
180 | unsigned short rpdCall : 1; // is this a true call site? |
181 | unsigned short : 1; // Padding bit, so next two start on a byte boundary |
182 | unsigned short rpdCallGCrefRegs : CNT_CALLEE_SAVED; // Callee-saved registers containing GC pointers. |
183 | unsigned short rpdCallByrefRegs : CNT_CALLEE_SAVED; // Callee-saved registers containing byrefs. |
184 | |
185 | #ifndef JIT32_GCENCODER |
186 | bool rpdIsCallInstr() |
187 | { |
188 | return rpdCall && rpdCallInstrSize != 0; |
189 | } |
190 | #endif |
191 | }; |
192 | |
193 | regPtrDsc* gcRegPtrList; |
194 | regPtrDsc* gcRegPtrLast; |
195 | unsigned gcPtrArgCnt; |
196 | |
197 | #ifndef JIT32_GCENCODER |
198 | enum MakeRegPtrMode |
199 | { |
200 | MAKE_REG_PTR_MODE_ASSIGN_SLOTS, |
201 | MAKE_REG_PTR_MODE_DO_WORK |
202 | }; |
203 | |
204 | // This method has two modes. In the "assign slots" mode, it figures out what stack locations are |
205 | // used to contain GC references, and whether those locations contain byrefs or pinning references, |
206 | // building up mappings from tuples of <offset X byref/pinning> to the corresponding slot id. |
207 | // In the "do work" mode, we use these slot ids to actually declare live ranges to the encoder. |
208 | void gcMakeVarPtrTable(GcInfoEncoder* gcInfoEncoder, MakeRegPtrMode mode); |
209 | |
210 | // At instruction offset "instrOffset," the set of registers indicated by "regMask" is becoming live or dead, |
211 | // depending on whether "newState" is "GC_SLOT_DEAD" or "GC_SLOT_LIVE". The subset of registers whose corresponding |
212 | // bits are set in "byRefMask" contain by-refs rather than regular GC pointers. "*pPtrRegs" is the set of |
213 | // registers currently known to contain pointers. If "mode" is "ASSIGN_SLOTS", computes and records slot |
214 | // ids for the registers. If "mode" is "DO_WORK", informs "gcInfoEncoder" about the state transition, |
215 | // using the previously assigned slot ids, and updates "*pPtrRegs" appropriately. |
216 | void gcInfoRecordGCRegStateChange(GcInfoEncoder* gcInfoEncoder, |
217 | MakeRegPtrMode mode, |
218 | unsigned instrOffset, |
219 | regMaskSmall regMask, |
220 | GcSlotState newState, |
221 | regMaskSmall byRefMask, |
222 | regMaskSmall* pPtrRegs); |
223 | |
224 | // regPtrDsc is also used to encode writes to the outgoing argument space (as if they were pushes) |
225 | void gcInfoRecordGCStackArgLive(GcInfoEncoder* gcInfoEncoder, MakeRegPtrMode mode, regPtrDsc* genStackPtr); |
226 | |
227 | // Walk all the pushes between genStackPtrFirst (inclusive) and genStackPtrLast (exclusive) |
228 | // and mark them as going dead at instrOffset |
229 | void gcInfoRecordGCStackArgsDead(GcInfoEncoder* gcInfoEncoder, |
230 | unsigned instrOffset, |
231 | regPtrDsc* genStackPtrFirst, |
232 | regPtrDsc* genStackPtrLast); |
233 | |
234 | // Update the flags for a stack allocated object |
235 | void gcUpdateFlagForStackAllocatedObjects(GcSlotFlags& flags); |
236 | |
237 | #endif |
238 | |
239 | #if MEASURE_PTRTAB_SIZE |
240 | static size_t s_gcRegPtrDscSize; |
241 | static size_t s_gcTotalPtrTabSize; |
242 | #endif |
243 | |
244 | regPtrDsc* gcRegPtrAllocDsc(); |
245 | |
246 | /*****************************************************************************/ |
247 | |
248 | //------------------------------------------------------------------------- |
249 | // |
250 | // If we're not generating fully interruptible code, we create a simple |
251 | // linked list of call descriptors. |
252 | // |
253 | |
254 | struct CallDsc |
255 | { |
256 | CallDsc* cdNext; |
257 | void* cdBlock; // the code block of the call |
258 | unsigned cdOffs; // the offset of the call |
259 | #ifndef JIT32_GCENCODER |
260 | unsigned short cdCallInstrSize; // the size of the call instruction. |
261 | #endif |
262 | |
263 | unsigned short cdArgCnt; |
264 | |
265 | union { |
266 | struct // used if cdArgCnt == 0 |
267 | { |
268 | unsigned cdArgMask; // ptr arg bitfield |
269 | unsigned cdByrefArgMask; // byref qualifier for cdArgMask |
270 | } u1; |
271 | |
272 | unsigned* cdArgTable; // used if cdArgCnt != 0 |
273 | }; |
274 | |
275 | regMaskSmall cdGCrefRegs; |
276 | regMaskSmall cdByrefRegs; |
277 | }; |
278 | |
279 | CallDsc* gcCallDescList; |
280 | CallDsc* gcCallDescLast; |
281 | |
282 | //------------------------------------------------------------------------- |
283 | |
284 | void (UNALIGNED unsigned int* untrackedCount, UNALIGNED unsigned int* varPtrTableSize); |
285 | |
286 | #ifdef JIT32_GCENCODER |
287 | size_t gcMakeRegPtrTable(BYTE* dest, int mask, const InfoHdr& header, unsigned codeSize, size_t* pArgTabOffset); |
288 | #else |
289 | RegSlotMap* m_regSlotMap; |
290 | StackSlotMap* m_stackSlotMap; |
291 | // This method has two modes. In the "assign slots" mode, it figures out what registers and stack |
292 | // locations are used to contain GC references, and whether those locations contain byrefs or pinning |
293 | // references, building up mappings from tuples of <reg/offset X byref/pinning> to the corresponding |
294 | // slot id (in the two member fields declared above). In the "do work" mode, we use these slot ids to |
295 | // actually declare live ranges to the encoder. |
296 | void gcMakeRegPtrTable(GcInfoEncoder* gcInfoEncoder, |
297 | unsigned codeSize, |
298 | unsigned prologSize, |
299 | MakeRegPtrMode mode, |
300 | unsigned* callCntRef); |
301 | #endif |
302 | |
303 | #ifdef JIT32_GCENCODER |
304 | size_t gcPtrTableSize(const InfoHdr& header, unsigned codeSize, size_t* pArgTabOffset); |
305 | BYTE* gcPtrTableSave(BYTE* destPtr, const InfoHdr& header, unsigned codeSize, size_t* pArgTabOffset); |
306 | #endif |
307 | void gcRegPtrSetInit(); |
308 | /*****************************************************************************/ |
309 | |
310 | // This enumeration yields the result of the analysis below, whether a store |
311 | // requires a write barrier: |
312 | enum WriteBarrierForm |
313 | { |
314 | WBF_NoBarrier, // No barrier is required |
315 | WBF_BarrierUnknown, // A barrier is required, no information on checked/unchecked. |
316 | WBF_BarrierChecked, // A checked barrier is required. |
317 | WBF_BarrierUnchecked, // An unchecked barrier is required. |
318 | WBF_NoBarrier_CheckNotHeapInDebug, // We believe that no barrier is required because the |
319 | // target is not in the heap -- but in debug build use a |
320 | // barrier call that verifies this property. (Because the |
321 | // target not being in the heap relies on a convention that |
322 | // might accidentally be violated in the future.) |
323 | }; |
324 | |
325 | WriteBarrierForm gcIsWriteBarrierCandidate(GenTree* tgt, GenTree* assignVal); |
326 | bool gcIsWriteBarrierStoreIndNode(GenTree* op); |
327 | |
328 | // Returns a WriteBarrierForm decision based on the form of "tgtAddr", which is assumed to be the |
329 | // argument of a GT_IND LHS. |
330 | WriteBarrierForm gcWriteBarrierFormFromTargetAddress(GenTree* tgtAddr); |
331 | |
332 | //------------------------------------------------------------------------- |
333 | // |
334 | // These record the info about the procedure in the info-block |
335 | // |
336 | CLANG_FORMAT_COMMENT_ANCHOR; |
337 | |
338 | #ifdef JIT32_GCENCODER |
339 | private: |
340 | BYTE* gcEpilogTable; |
341 | |
342 | unsigned gcEpilogPrevOffset; |
343 | |
344 | size_t gcInfoBlockHdrSave(BYTE* dest, |
345 | int mask, |
346 | unsigned methodSize, |
347 | unsigned prologSize, |
348 | unsigned epilogSize, |
349 | InfoHdr* header, |
350 | int* s_cached); |
351 | |
352 | public: |
353 | static void gcInitEncoderLookupTable(); |
354 | |
355 | private: |
356 | static size_t gcRecordEpilog(void* pCallBackData, unsigned offset); |
357 | #else // JIT32_GCENCODER |
358 | void gcInfoBlockHdrSave(GcInfoEncoder* gcInfoEncoder, unsigned methodSize, unsigned prologSize); |
359 | |
360 | #endif // JIT32_GCENCODER |
361 | |
362 | #if !defined(JIT32_GCENCODER) || defined(WIN64EXCEPTIONS) |
363 | |
364 | // This method expands the tracked stack variables lifetimes so that any lifetimes within filters |
365 | // are reported as pinned. |
366 | void gcMarkFilterVarsPinned(); |
367 | |
368 | // Insert a varPtrDsc to gcVarPtrList that was generated by splitting lifetimes |
369 | void gcInsertVarPtrDscSplit(varPtrDsc* desc, varPtrDsc* begin); |
370 | |
371 | #ifdef DEBUG |
372 | void gcDumpVarPtrDsc(varPtrDsc* desc); |
373 | #endif // DEBUG |
374 | |
375 | #endif // !defined(JIT32_GCENCODER) || defined(WIN64EXCEPTIONS) |
376 | |
377 | #if DUMP_GC_TABLES |
378 | |
379 | void gcFindPtrsInFrame(const void* infoBlock, const void* codeBlock, unsigned offs); |
380 | |
381 | #ifdef JIT32_GCENCODER |
382 | unsigned gcInfoBlockHdrDump(const BYTE* table, |
383 | InfoHdr* header, /* OUT */ |
384 | unsigned* methodSize); /* OUT */ |
385 | |
386 | unsigned gcDumpPtrTable(const BYTE* table, const InfoHdr& header, unsigned methodSize); |
387 | |
388 | #endif // JIT32_GCENCODER |
389 | #endif // DUMP_GC_TABLES |
390 | |
391 | public: |
392 | // This method updates the appropriate reg masks when a variable is moved. |
393 | void gcUpdateForRegVarMove(regMaskTP srcMask, regMaskTP dstMask, LclVarDsc* varDsc); |
394 | |
395 | private: |
396 | ReturnKind getReturnKind(); |
397 | }; |
398 | |
399 | inline unsigned char encodeUnsigned(BYTE* dest, unsigned value) |
400 | { |
401 | unsigned char size = 1; |
402 | unsigned tmp = value; |
403 | while (tmp > 0x7F) |
404 | { |
405 | tmp >>= 7; |
406 | assert(size < 6); // Invariant. |
407 | size++; |
408 | } |
409 | if (dest) |
410 | { |
411 | // write the bytes starting at the end of dest in LSB to MSB order |
412 | BYTE* p = dest + size; |
413 | BYTE cont = 0; // The last byte has no continuation flag |
414 | while (value > 0x7F) |
415 | { |
416 | *--p = cont | (value & 0x7f); |
417 | value >>= 7; |
418 | cont = 0x80; // Non last bytes have a continuation flag |
419 | } |
420 | *--p = cont | (BYTE)value; // Now write the first byte |
421 | assert(p == dest); |
422 | } |
423 | return size; |
424 | } |
425 | |
426 | inline unsigned char encodeUDelta(BYTE* dest, unsigned value, unsigned lastValue) |
427 | { |
428 | assert(value >= lastValue); |
429 | return encodeUnsigned(dest, value - lastValue); |
430 | } |
431 | |
432 | inline unsigned char encodeSigned(BYTE* dest, int val) |
433 | { |
434 | unsigned char size = 1; |
435 | unsigned value = val; |
436 | BYTE neg = 0; |
437 | if (val < 0) |
438 | { |
439 | value = -val; |
440 | neg = 0x40; |
441 | } |
442 | unsigned tmp = value; |
443 | while (tmp > 0x3F) |
444 | { |
445 | tmp >>= 7; |
446 | assert(size < 16); // Definitely sufficient for unsigned. Fits in an unsigned char, certainly. |
447 | size++; |
448 | } |
449 | if (dest) |
450 | { |
451 | // write the bytes starting at the end of dest in LSB to MSB order |
452 | BYTE* p = dest + size; |
453 | BYTE cont = 0; // The last byte has no continuation flag |
454 | while (value > 0x3F) |
455 | { |
456 | *--p = cont | (value & 0x7f); |
457 | value >>= 7; |
458 | cont = 0x80; // Non last bytes have a continuation flag |
459 | } |
460 | *--p = neg | cont | (BYTE)value; // Now write the first byte |
461 | assert(p == dest); |
462 | } |
463 | return size; |
464 | } |
465 | |
466 | #endif // _JITGCINFO_H_ |
467 | |