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 | /*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX |
6 | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX |
7 | XX XX |
8 | XX GCInfo XX |
9 | XX XX |
10 | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX |
11 | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX |
12 | */ |
13 | |
14 | #include "jitpch.h" |
15 | #ifdef _MSC_VER |
16 | #pragma hdrstop |
17 | #endif |
18 | |
19 | #include "gcinfo.h" |
20 | #include "emit.h" |
21 | #include "jitgcinfo.h" |
22 | |
23 | #ifdef _TARGET_AMD64_ |
24 | #include "gcinfoencoder.h" //this includes a LOT of other files too |
25 | #endif |
26 | |
27 | /*****************************************************************************/ |
28 | /*****************************************************************************/ |
29 | |
30 | /*****************************************************************************/ |
31 | |
32 | extern int JITGcBarrierCall; |
33 | |
34 | /*****************************************************************************/ |
35 | |
36 | #if MEASURE_PTRTAB_SIZE |
37 | /* static */ size_t GCInfo::s_gcRegPtrDscSize = 0; |
38 | /* static */ size_t GCInfo::s_gcTotalPtrTabSize = 0; |
39 | #endif // MEASURE_PTRTAB_SIZE |
40 | |
41 | /* |
42 | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX |
43 | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX |
44 | XX GCInfo XX |
45 | XX XX |
46 | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX |
47 | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX |
48 | */ |
49 | |
50 | GCInfo::GCInfo(Compiler* theCompiler) : compiler(theCompiler) |
51 | { |
52 | regSet = nullptr; |
53 | gcVarPtrList = nullptr; |
54 | gcVarPtrLast = nullptr; |
55 | gcRegPtrList = nullptr; |
56 | gcRegPtrLast = nullptr; |
57 | gcPtrArgCnt = 0; |
58 | gcCallDescList = nullptr; |
59 | gcCallDescLast = nullptr; |
60 | #ifdef JIT32_GCENCODER |
61 | gcEpilogTable = nullptr; |
62 | #else // !JIT32_GCENCODER |
63 | m_regSlotMap = nullptr; |
64 | m_stackSlotMap = nullptr; |
65 | #endif // JIT32_GCENCODER |
66 | } |
67 | |
68 | /*****************************************************************************/ |
69 | /***************************************************************************** |
70 | * Reset tracking info at the start of a basic block. |
71 | */ |
72 | |
73 | void GCInfo::gcResetForBB() |
74 | { |
75 | gcRegGCrefSetCur = RBM_NONE; |
76 | gcRegByrefSetCur = RBM_NONE; |
77 | VarSetOps::AssignNoCopy(compiler, gcVarPtrSetCur, VarSetOps::MakeEmpty(compiler)); |
78 | } |
79 | |
80 | #ifdef DEBUG |
81 | |
82 | /***************************************************************************** |
83 | * |
84 | * Print the changes in the gcRegGCrefSetCur sets. |
85 | */ |
86 | |
87 | void GCInfo::gcDspGCrefSetChanges(regMaskTP gcRegGCrefSetNew DEBUGARG(bool forceOutput)) |
88 | { |
89 | if (compiler->verbose) |
90 | { |
91 | if (forceOutput || (gcRegGCrefSetCur != gcRegGCrefSetNew)) |
92 | { |
93 | printf("\t\t\t\t\t\t\tGC regs: " ); |
94 | if (gcRegGCrefSetCur == gcRegGCrefSetNew) |
95 | { |
96 | printf("(unchanged) " ); |
97 | } |
98 | else |
99 | { |
100 | printRegMaskInt(gcRegGCrefSetCur); |
101 | compiler->getEmitter()->emitDispRegSet(gcRegGCrefSetCur); |
102 | printf(" => " ); |
103 | } |
104 | printRegMaskInt(gcRegGCrefSetNew); |
105 | compiler->getEmitter()->emitDispRegSet(gcRegGCrefSetNew); |
106 | printf("\n" ); |
107 | } |
108 | } |
109 | } |
110 | |
111 | /***************************************************************************** |
112 | * |
113 | * Print the changes in the gcRegByrefSetCur sets. |
114 | */ |
115 | |
116 | void GCInfo::gcDspByrefSetChanges(regMaskTP gcRegByrefSetNew DEBUGARG(bool forceOutput)) |
117 | { |
118 | if (compiler->verbose) |
119 | { |
120 | if (forceOutput || (gcRegByrefSetCur != gcRegByrefSetNew)) |
121 | { |
122 | printf("\t\t\t\t\t\t\tByref regs: " ); |
123 | if (gcRegByrefSetCur == gcRegByrefSetNew) |
124 | { |
125 | printf("(unchanged) " ); |
126 | } |
127 | else |
128 | { |
129 | printRegMaskInt(gcRegByrefSetCur); |
130 | compiler->getEmitter()->emitDispRegSet(gcRegByrefSetCur); |
131 | printf(" => " ); |
132 | } |
133 | printRegMaskInt(gcRegByrefSetNew); |
134 | compiler->getEmitter()->emitDispRegSet(gcRegByrefSetNew); |
135 | printf("\n" ); |
136 | } |
137 | } |
138 | } |
139 | |
140 | #endif // DEBUG |
141 | |
142 | /***************************************************************************** |
143 | * |
144 | * Mark the set of registers given by the specified mask as holding |
145 | * GCref pointer values. |
146 | */ |
147 | |
148 | void GCInfo::gcMarkRegSetGCref(regMaskTP regMask DEBUGARG(bool forceOutput)) |
149 | { |
150 | #ifdef DEBUG |
151 | if (compiler->compRegSetCheckLevel == 0) |
152 | { |
153 | // This set of registers are going to hold REFs. |
154 | // Make sure they were not holding BYREFs. |
155 | assert((gcRegByrefSetCur & regMask) == 0); |
156 | } |
157 | #endif |
158 | |
159 | regMaskTP gcRegByrefSetNew = gcRegByrefSetCur & ~regMask; // Clear it if set in Byref mask |
160 | regMaskTP gcRegGCrefSetNew = gcRegGCrefSetCur | regMask; // Set it in GCref mask |
161 | |
162 | INDEBUG(gcDspGCrefSetChanges(gcRegGCrefSetNew, forceOutput)); |
163 | INDEBUG(gcDspByrefSetChanges(gcRegByrefSetNew)); |
164 | |
165 | gcRegByrefSetCur = gcRegByrefSetNew; |
166 | gcRegGCrefSetCur = gcRegGCrefSetNew; |
167 | } |
168 | |
169 | /***************************************************************************** |
170 | * |
171 | * Mark the set of registers given by the specified mask as holding |
172 | * Byref pointer values. |
173 | */ |
174 | |
175 | void GCInfo::gcMarkRegSetByref(regMaskTP regMask DEBUGARG(bool forceOutput)) |
176 | { |
177 | regMaskTP gcRegByrefSetNew = gcRegByrefSetCur | regMask; // Set it in Byref mask |
178 | regMaskTP gcRegGCrefSetNew = gcRegGCrefSetCur & ~regMask; // Clear it if set in GCref mask |
179 | |
180 | INDEBUG(gcDspGCrefSetChanges(gcRegGCrefSetNew)); |
181 | INDEBUG(gcDspByrefSetChanges(gcRegByrefSetNew, forceOutput)); |
182 | |
183 | gcRegByrefSetCur = gcRegByrefSetNew; |
184 | gcRegGCrefSetCur = gcRegGCrefSetNew; |
185 | } |
186 | |
187 | /***************************************************************************** |
188 | * |
189 | * Mark the set of registers given by the specified mask as holding |
190 | * non-pointer values. |
191 | */ |
192 | |
193 | void GCInfo::gcMarkRegSetNpt(regMaskTP regMask DEBUGARG(bool forceOutput)) |
194 | { |
195 | /* NOTE: don't unmark any live register variables */ |
196 | |
197 | regMaskTP gcRegByrefSetNew = gcRegByrefSetCur & ~(regMask & ~regSet->rsMaskVars); |
198 | regMaskTP gcRegGCrefSetNew = gcRegGCrefSetCur & ~(regMask & ~regSet->rsMaskVars); |
199 | |
200 | INDEBUG(gcDspGCrefSetChanges(gcRegGCrefSetNew, forceOutput)); |
201 | INDEBUG(gcDspByrefSetChanges(gcRegByrefSetNew, forceOutput)); |
202 | |
203 | gcRegByrefSetCur = gcRegByrefSetNew; |
204 | gcRegGCrefSetCur = gcRegGCrefSetNew; |
205 | } |
206 | |
207 | /***************************************************************************** |
208 | * |
209 | * Mark the specified register as now holding a value of the given type. |
210 | */ |
211 | |
212 | void GCInfo::gcMarkRegPtrVal(regNumber reg, var_types type) |
213 | { |
214 | regMaskTP regMask = genRegMask(reg); |
215 | |
216 | switch (type) |
217 | { |
218 | case TYP_REF: |
219 | gcMarkRegSetGCref(regMask); |
220 | break; |
221 | case TYP_BYREF: |
222 | gcMarkRegSetByref(regMask); |
223 | break; |
224 | default: |
225 | gcMarkRegSetNpt(regMask); |
226 | break; |
227 | } |
228 | } |
229 | |
230 | /*****************************************************************************/ |
231 | |
232 | GCInfo::WriteBarrierForm GCInfo::gcIsWriteBarrierCandidate(GenTree* tgt, GenTree* assignVal) |
233 | { |
234 | /* Are we storing a GC ptr? */ |
235 | |
236 | if (!varTypeIsGC(tgt->TypeGet())) |
237 | { |
238 | return WBF_NoBarrier; |
239 | } |
240 | |
241 | /* Ignore any assignments of NULL */ |
242 | |
243 | // 'assignVal' can be the constant Null or something else (LclVar, etc..) |
244 | // that is known to be null via Value Numbering. |
245 | if (assignVal->GetVN(VNK_Liberal) == ValueNumStore::VNForNull()) |
246 | { |
247 | return WBF_NoBarrier; |
248 | } |
249 | |
250 | if (assignVal->gtOper == GT_CNS_INT && assignVal->gtIntCon.gtIconVal == 0) |
251 | { |
252 | return WBF_NoBarrier; |
253 | } |
254 | |
255 | /* Where are we storing into? */ |
256 | |
257 | tgt = tgt->gtEffectiveVal(); |
258 | |
259 | switch (tgt->gtOper) |
260 | { |
261 | |
262 | case GT_STOREIND: |
263 | case GT_IND: /* Could be the managed heap */ |
264 | if (tgt->TypeGet() == TYP_BYREF) |
265 | { |
266 | // Byref values cannot be in managed heap. |
267 | // This case occurs for Span<T>. |
268 | return WBF_NoBarrier; |
269 | } |
270 | return gcWriteBarrierFormFromTargetAddress(tgt->gtOp.gtOp1); |
271 | |
272 | case GT_LEA: |
273 | return gcWriteBarrierFormFromTargetAddress(tgt->AsAddrMode()->Base()); |
274 | |
275 | case GT_ARR_ELEM: /* Definitely in the managed heap */ |
276 | case GT_CLS_VAR: |
277 | return WBF_BarrierUnchecked; |
278 | |
279 | case GT_LCL_VAR: /* Definitely not in the managed heap */ |
280 | case GT_LCL_FLD: |
281 | case GT_STORE_LCL_VAR: |
282 | case GT_STORE_LCL_FLD: |
283 | return WBF_NoBarrier; |
284 | |
285 | default: |
286 | break; |
287 | } |
288 | |
289 | assert(!"Missing case in gcIsWriteBarrierCandidate" ); |
290 | |
291 | return WBF_NoBarrier; |
292 | } |
293 | |
294 | bool GCInfo::gcIsWriteBarrierStoreIndNode(GenTree* op) |
295 | { |
296 | assert(op->OperIs(GT_STOREIND)); |
297 | |
298 | return gcIsWriteBarrierCandidate(op, op->gtOp.gtOp2) != WBF_NoBarrier; |
299 | } |
300 | |
301 | /*****************************************************************************/ |
302 | /***************************************************************************** |
303 | * |
304 | * Initialize the non-register pointer variable tracking logic. |
305 | */ |
306 | |
307 | void GCInfo::gcVarPtrSetInit() |
308 | { |
309 | VarSetOps::AssignNoCopy(compiler, gcVarPtrSetCur, VarSetOps::MakeEmpty(compiler)); |
310 | |
311 | /* Initialize the list of lifetime entries */ |
312 | gcVarPtrList = gcVarPtrLast = nullptr; |
313 | } |
314 | |
315 | /***************************************************************************** |
316 | * |
317 | * Allocate a new pointer register set / pointer argument entry and append |
318 | * it to the list. |
319 | */ |
320 | |
321 | GCInfo::regPtrDsc* GCInfo::gcRegPtrAllocDsc() |
322 | { |
323 | regPtrDsc* regPtrNext; |
324 | |
325 | assert(compiler->genFullPtrRegMap); |
326 | |
327 | /* Allocate a new entry and initialize it */ |
328 | |
329 | regPtrNext = new (compiler, CMK_GC) regPtrDsc; |
330 | |
331 | regPtrNext->rpdIsThis = FALSE; |
332 | |
333 | regPtrNext->rpdOffs = 0; |
334 | regPtrNext->rpdNext = nullptr; |
335 | |
336 | // Append the entry to the end of the list. |
337 | if (gcRegPtrLast == nullptr) |
338 | { |
339 | assert(gcRegPtrList == nullptr); |
340 | gcRegPtrList = gcRegPtrLast = regPtrNext; |
341 | } |
342 | else |
343 | { |
344 | assert(gcRegPtrList != nullptr); |
345 | gcRegPtrLast->rpdNext = regPtrNext; |
346 | gcRegPtrLast = regPtrNext; |
347 | } |
348 | |
349 | #if MEASURE_PTRTAB_SIZE |
350 | s_gcRegPtrDscSize += sizeof(*regPtrNext); |
351 | #endif |
352 | |
353 | return regPtrNext; |
354 | } |
355 | |
356 | /***************************************************************************** |
357 | * |
358 | * Compute the various counts that get stored in the info block header. |
359 | */ |
360 | |
361 | void GCInfo::(UNALIGNED unsigned int* untrackedCount, UNALIGNED unsigned int* varPtrTableSize) |
362 | { |
363 | unsigned varNum; |
364 | LclVarDsc* varDsc; |
365 | varPtrDsc* varTmp; |
366 | |
367 | bool thisKeptAliveIsInUntracked = false; // did we track "this" in a synchronized method? |
368 | unsigned int count = 0; |
369 | |
370 | /* Count the untracked locals and non-enregistered args */ |
371 | |
372 | for (varNum = 0, varDsc = compiler->lvaTable; varNum < compiler->lvaCount; varNum++, varDsc++) |
373 | { |
374 | if (varTypeIsGC(varDsc->TypeGet())) |
375 | { |
376 | if (compiler->lvaIsFieldOfDependentlyPromotedStruct(varDsc)) |
377 | { |
378 | // Field local of a PROMOTION_TYPE_DEPENDENT struct must have been |
379 | // reported through its parent local |
380 | continue; |
381 | } |
382 | |
383 | /* Do we have an argument or local variable? */ |
384 | if (!varDsc->lvIsParam) |
385 | { |
386 | if (varDsc->lvTracked || !varDsc->lvOnFrame) |
387 | { |
388 | continue; |
389 | } |
390 | } |
391 | else |
392 | { |
393 | /* Stack-passed arguments which are not enregistered |
394 | * are always reported in this "untracked stack |
395 | * pointers" section of the GC info even if lvTracked==true |
396 | */ |
397 | |
398 | /* Has this argument been fully enregistered? */ |
399 | CLANG_FORMAT_COMMENT_ANCHOR; |
400 | |
401 | if (!varDsc->lvOnFrame) |
402 | { |
403 | /* if a CEE_JMP has been used, then we need to report all the arguments |
404 | even if they are enregistered, since we will be using this value |
405 | in JMP call. Note that this is subtle as we require that |
406 | argument offsets are always fixed up properly even if lvRegister |
407 | is set */ |
408 | if (!compiler->compJmpOpUsed) |
409 | { |
410 | continue; |
411 | } |
412 | } |
413 | else |
414 | { |
415 | if (!varDsc->lvOnFrame) |
416 | { |
417 | /* If this non-enregistered pointer arg is never |
418 | * used, we don't need to report it |
419 | */ |
420 | assert(varDsc->lvRefCnt() == 0); |
421 | continue; |
422 | } |
423 | else if (varDsc->lvIsRegArg && varDsc->lvTracked) |
424 | { |
425 | /* If this register-passed arg is tracked, then |
426 | * it has been allocated space near the other |
427 | * pointer variables and we have accurate life- |
428 | * time info. It will be reported with |
429 | * gcVarPtrList in the "tracked-pointer" section |
430 | */ |
431 | |
432 | continue; |
433 | } |
434 | } |
435 | } |
436 | |
437 | #if !defined(JIT32_GCENCODER) || !defined(WIN64EXCEPTIONS) |
438 | // For x86/WIN64EXCEPTIONS, "this" must always be in untracked variables |
439 | // so we cannot have "this" in variable lifetimes |
440 | if (compiler->lvaIsOriginalThisArg(varNum) && compiler->lvaKeepAliveAndReportThis()) |
441 | { |
442 | // Encoding of untracked variables does not support reporting |
443 | // "this". So report it as a tracked variable with a liveness |
444 | // extending over the entire method. |
445 | |
446 | thisKeptAliveIsInUntracked = true; |
447 | continue; |
448 | } |
449 | #endif |
450 | |
451 | #ifdef DEBUG |
452 | if (compiler->verbose) |
453 | { |
454 | int offs = varDsc->lvStkOffs; |
455 | |
456 | printf("GCINFO: untrckd %s lcl at [%s" , varTypeGCstring(varDsc->TypeGet()), |
457 | compiler->genEmitter->emitGetFrameReg()); |
458 | |
459 | if (offs < 0) |
460 | { |
461 | printf("-%02XH" , -offs); |
462 | } |
463 | else if (offs > 0) |
464 | { |
465 | printf("+%02XH" , +offs); |
466 | } |
467 | |
468 | printf("]\n" ); |
469 | } |
470 | #endif |
471 | |
472 | count++; |
473 | } |
474 | else if (varDsc->lvType == TYP_STRUCT && varDsc->lvOnFrame && (varDsc->lvExactSize >= TARGET_POINTER_SIZE)) |
475 | { |
476 | unsigned slots = compiler->lvaLclSize(varNum) / TARGET_POINTER_SIZE; |
477 | BYTE* gcPtrs = compiler->lvaGetGcLayout(varNum); |
478 | |
479 | // walk each member of the array |
480 | for (unsigned i = 0; i < slots; i++) |
481 | { |
482 | if (gcPtrs[i] != TYPE_GC_NONE) |
483 | { // count only gc slots |
484 | count++; |
485 | } |
486 | } |
487 | } |
488 | } |
489 | |
490 | /* Also count spill temps that hold pointers */ |
491 | |
492 | assert(regSet->tmpAllFree()); |
493 | for (TempDsc* tempThis = regSet->tmpListBeg(); tempThis != nullptr; tempThis = regSet->tmpListNxt(tempThis)) |
494 | { |
495 | if (varTypeIsGC(tempThis->tdTempType()) == false) |
496 | { |
497 | continue; |
498 | } |
499 | |
500 | #ifdef DEBUG |
501 | if (compiler->verbose) |
502 | { |
503 | int offs = tempThis->tdTempOffs(); |
504 | |
505 | printf("GCINFO: untrck %s Temp at [%s" , varTypeGCstring(varDsc->TypeGet()), |
506 | compiler->genEmitter->emitGetFrameReg()); |
507 | |
508 | if (offs < 0) |
509 | { |
510 | printf("-%02XH" , -offs); |
511 | } |
512 | else if (offs > 0) |
513 | { |
514 | printf("+%02XH" , +offs); |
515 | } |
516 | |
517 | printf("]\n" ); |
518 | } |
519 | #endif |
520 | |
521 | count++; |
522 | } |
523 | |
524 | #ifdef DEBUG |
525 | if (compiler->verbose) |
526 | { |
527 | printf("GCINFO: untrckVars = %u\n" , count); |
528 | } |
529 | #endif |
530 | |
531 | *untrackedCount = count; |
532 | |
533 | /* Count the number of entries in the table of non-register pointer |
534 | variable lifetimes. */ |
535 | |
536 | count = 0; |
537 | |
538 | if (thisKeptAliveIsInUntracked) |
539 | { |
540 | count++; |
541 | } |
542 | |
543 | if (gcVarPtrList) |
544 | { |
545 | /* We'll use a delta encoding for the lifetime offsets */ |
546 | |
547 | for (varTmp = gcVarPtrList; varTmp; varTmp = varTmp->vpdNext) |
548 | { |
549 | /* Special case: skip any 0-length lifetimes */ |
550 | |
551 | if (varTmp->vpdBegOfs == varTmp->vpdEndOfs) |
552 | { |
553 | continue; |
554 | } |
555 | |
556 | count++; |
557 | } |
558 | } |
559 | |
560 | #ifdef DEBUG |
561 | if (compiler->verbose) |
562 | { |
563 | printf("GCINFO: trackdLcls = %u\n" , count); |
564 | } |
565 | #endif |
566 | |
567 | *varPtrTableSize = count; |
568 | } |
569 | |
570 | #ifdef JIT32_GCENCODER |
571 | /***************************************************************************** |
572 | * |
573 | * Shutdown the 'pointer value' register tracking logic and save the necessary |
574 | * info (which will be used at runtime to locate all pointers) at the specified |
575 | * address. The number of bytes written to 'destPtr' must be identical to that |
576 | * returned from gcPtrTableSize(). |
577 | */ |
578 | |
579 | BYTE* GCInfo::gcPtrTableSave(BYTE* destPtr, const InfoHdr& header, unsigned codeSize, size_t* pArgTabOffset) |
580 | { |
581 | /* Write the tables to the info block */ |
582 | |
583 | return destPtr + gcMakeRegPtrTable(destPtr, -1, header, codeSize, pArgTabOffset); |
584 | } |
585 | #endif |
586 | |
587 | /***************************************************************************** |
588 | * |
589 | * Initialize the 'pointer value' register/argument tracking logic. |
590 | */ |
591 | |
592 | void GCInfo::gcRegPtrSetInit() |
593 | { |
594 | gcRegGCrefSetCur = gcRegByrefSetCur = 0; |
595 | |
596 | if (compiler->genFullPtrRegMap) |
597 | { |
598 | gcRegPtrList = gcRegPtrLast = nullptr; |
599 | } |
600 | else |
601 | { |
602 | /* Initialize the 'call descriptor' list */ |
603 | gcCallDescList = gcCallDescLast = nullptr; |
604 | } |
605 | } |
606 | |
607 | #ifdef JIT32_GCENCODER |
608 | |
609 | /***************************************************************************** |
610 | * |
611 | * Helper passed to genEmitter.emitGenEpilogLst() to generate |
612 | * the table of epilogs. |
613 | */ |
614 | |
615 | /* static */ size_t GCInfo::gcRecordEpilog(void* pCallBackData, unsigned offset) |
616 | { |
617 | GCInfo* gcInfo = (GCInfo*)pCallBackData; |
618 | |
619 | assert(gcInfo); |
620 | |
621 | size_t result = encodeUDelta(gcInfo->gcEpilogTable, offset, gcInfo->gcEpilogPrevOffset); |
622 | |
623 | if (gcInfo->gcEpilogTable) |
624 | gcInfo->gcEpilogTable += result; |
625 | |
626 | gcInfo->gcEpilogPrevOffset = offset; |
627 | |
628 | return result; |
629 | } |
630 | |
631 | #endif // JIT32_GCENCODER |
632 | |
633 | GCInfo::WriteBarrierForm GCInfo::gcWriteBarrierFormFromTargetAddress(GenTree* tgtAddr) |
634 | { |
635 | GCInfo::WriteBarrierForm result = GCInfo::WBF_BarrierUnknown; // Default case, we have no information. |
636 | |
637 | // If we store through an int to a GC_REF field, we'll assume that needs to use a checked barriers. |
638 | if (tgtAddr->TypeGet() == TYP_I_IMPL) |
639 | { |
640 | return GCInfo::WBF_BarrierChecked; // Why isn't this GCInfo::WBF_BarrierUnknown? |
641 | } |
642 | |
643 | // Otherwise... |
644 | assert(tgtAddr->TypeGet() == TYP_BYREF); |
645 | bool simplifiedExpr = true; |
646 | while (simplifiedExpr) |
647 | { |
648 | simplifiedExpr = false; |
649 | |
650 | tgtAddr = tgtAddr->gtSkipReloadOrCopy(); |
651 | |
652 | while (tgtAddr->OperGet() == GT_ADDR && tgtAddr->gtOp.gtOp1->OperGet() == GT_IND) |
653 | { |
654 | tgtAddr = tgtAddr->gtOp.gtOp1->gtOp.gtOp1; |
655 | simplifiedExpr = true; |
656 | assert(tgtAddr->TypeGet() == TYP_BYREF); |
657 | } |
658 | // For additions, one of the operands is a byref or a ref (and the other is not). Follow this down to its |
659 | // source. |
660 | while (tgtAddr->OperGet() == GT_ADD || tgtAddr->OperGet() == GT_LEA) |
661 | { |
662 | if (tgtAddr->OperGet() == GT_ADD) |
663 | { |
664 | if (tgtAddr->gtOp.gtOp1->TypeGet() == TYP_BYREF || tgtAddr->gtOp.gtOp1->TypeGet() == TYP_REF) |
665 | { |
666 | assert(!(tgtAddr->gtOp.gtOp2->TypeGet() == TYP_BYREF || tgtAddr->gtOp.gtOp2->TypeGet() == TYP_REF)); |
667 | tgtAddr = tgtAddr->gtOp.gtOp1; |
668 | simplifiedExpr = true; |
669 | } |
670 | else if (tgtAddr->gtOp.gtOp2->TypeGet() == TYP_BYREF || tgtAddr->gtOp.gtOp2->TypeGet() == TYP_REF) |
671 | { |
672 | tgtAddr = tgtAddr->gtOp.gtOp2; |
673 | simplifiedExpr = true; |
674 | } |
675 | else |
676 | { |
677 | // We might have a native int. For example: |
678 | // const int 0 |
679 | // + byref |
680 | // lclVar int V06 loc5 // this is a local declared "valuetype VType*" |
681 | return GCInfo::WBF_BarrierUnknown; |
682 | } |
683 | } |
684 | else |
685 | { |
686 | // Must be an LEA (i.e., an AddrMode) |
687 | assert(tgtAddr->OperGet() == GT_LEA); |
688 | tgtAddr = tgtAddr->AsAddrMode()->Base(); |
689 | if (tgtAddr->TypeGet() == TYP_BYREF || tgtAddr->TypeGet() == TYP_REF) |
690 | { |
691 | simplifiedExpr = true; |
692 | } |
693 | else |
694 | { |
695 | // We might have a native int. |
696 | return GCInfo::WBF_BarrierUnknown; |
697 | } |
698 | } |
699 | } |
700 | } |
701 | if (tgtAddr->IsLocalAddrExpr() != nullptr) |
702 | { |
703 | // No need for a GC barrier when writing to a local variable. |
704 | return GCInfo::WBF_NoBarrier; |
705 | } |
706 | if (tgtAddr->OperGet() == GT_LCL_VAR) |
707 | { |
708 | unsigned lclNum = tgtAddr->AsLclVar()->GetLclNum(); |
709 | |
710 | LclVarDsc* varDsc = &compiler->lvaTable[lclNum]; |
711 | |
712 | // Instead of marking LclVar with 'lvStackByref', |
713 | // Consider decomposing the Value Number given to this LclVar to see if it was |
714 | // created using a GT_ADDR(GT_LCLVAR) or a GT_ADD( GT_ADDR(GT_LCLVAR), Constant) |
715 | |
716 | // We may have an internal compiler temp created in fgMorphCopyBlock() that we know |
717 | // points at one of our stack local variables, it will have lvStackByref set to true. |
718 | // |
719 | if (varDsc->lvStackByref) |
720 | { |
721 | assert(varDsc->TypeGet() == TYP_BYREF); |
722 | return GCInfo::WBF_NoBarrier; |
723 | } |
724 | |
725 | // We don't eliminate for inlined methods, where we (can) know where the "retBuff" points. |
726 | if (!compiler->compIsForInlining() && lclNum == compiler->info.compRetBuffArg) |
727 | { |
728 | assert(compiler->info.compRetType == TYP_STRUCT); // Else shouldn't have a ret buff. |
729 | |
730 | // Are we assured that the ret buff pointer points into the stack of a caller? |
731 | if (compiler->info.compRetBuffDefStack) |
732 | { |
733 | #if 0 |
734 | // This is an optional debugging mode. If the #if 0 above is changed to #if 1, |
735 | // every barrier we remove for stores to GC ref fields of a retbuff use a special |
736 | // helper that asserts that the target is not in the heap. |
737 | #ifdef DEBUG |
738 | return WBF_NoBarrier_CheckNotHeapInDebug; |
739 | #else |
740 | return WBF_NoBarrier; |
741 | #endif |
742 | #else // 0 |
743 | return GCInfo::WBF_NoBarrier; |
744 | #endif // 0 |
745 | } |
746 | } |
747 | } |
748 | if (tgtAddr->TypeGet() == TYP_REF) |
749 | { |
750 | return GCInfo::WBF_BarrierUnchecked; |
751 | } |
752 | // Otherwise, we have no information. |
753 | return GCInfo::WBF_BarrierUnknown; |
754 | } |
755 | |
756 | //------------------------------------------------------------------------ |
757 | // gcUpdateForRegVarMove: Update the masks when a variable is moved |
758 | // |
759 | // Arguments: |
760 | // srcMask - The register mask for the register(s) from which it is being moved |
761 | // dstMask - The register mask for the register(s) to which it is being moved |
762 | // type - The type of the variable |
763 | // |
764 | // Return Value: |
765 | // None |
766 | // |
767 | // Notes: |
768 | // This is called during codegen when a var is moved due to an LSRA_ASG. |
769 | // It is also called by LinearScan::recordVarLocationAtStartOfBB() which is in turn called by |
770 | // CodeGen::genCodeForBBList() at the block boundary. |
771 | |
772 | void GCInfo::gcUpdateForRegVarMove(regMaskTP srcMask, regMaskTP dstMask, LclVarDsc* varDsc) |
773 | { |
774 | var_types type = varDsc->TypeGet(); |
775 | bool isGCRef = (type == TYP_REF); |
776 | bool isByRef = (type == TYP_BYREF); |
777 | |
778 | if (srcMask != RBM_NONE) |
779 | { |
780 | regSet->RemoveMaskVars(srcMask); |
781 | if (isGCRef) |
782 | { |
783 | assert((gcRegByrefSetCur & srcMask) == 0); |
784 | gcRegGCrefSetCur &= ~srcMask; |
785 | gcRegGCrefSetCur |= dstMask; // safe if no dst, i.e. RBM_NONE |
786 | } |
787 | else if (isByRef) |
788 | { |
789 | assert((gcRegGCrefSetCur & srcMask) == 0); |
790 | gcRegByrefSetCur &= ~srcMask; |
791 | gcRegByrefSetCur |= dstMask; // safe if no dst, i.e. RBM_NONE |
792 | } |
793 | } |
794 | else if (isGCRef || isByRef) |
795 | { |
796 | // In this case, we are moving it from the stack to a register, |
797 | // so remove it from the set of live stack gc refs |
798 | VarSetOps::RemoveElemD(compiler, gcVarPtrSetCur, varDsc->lvVarIndex); |
799 | } |
800 | if (dstMask != RBM_NONE) |
801 | { |
802 | regSet->AddMaskVars(dstMask); |
803 | // If the source is a reg, then the gc sets have been set appropriately |
804 | // Otherwise, we have to determine whether to set them |
805 | if (srcMask == RBM_NONE) |
806 | { |
807 | if (isGCRef) |
808 | { |
809 | gcRegGCrefSetCur |= dstMask; |
810 | } |
811 | else if (isByRef) |
812 | { |
813 | gcRegByrefSetCur |= dstMask; |
814 | } |
815 | } |
816 | } |
817 | else if (isGCRef || isByRef) |
818 | { |
819 | VarSetOps::AddElemD(compiler, gcVarPtrSetCur, varDsc->lvVarIndex); |
820 | } |
821 | } |
822 | |
823 | /*****************************************************************************/ |
824 | /*****************************************************************************/ |
825 | |