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 Importer XX |
9 | XX XX |
10 | XX Imports the given method and converts it to semantic trees XX |
11 | XX XX |
12 | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX |
13 | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX |
14 | */ |
15 | |
16 | #include "jitpch.h" |
17 | #ifdef _MSC_VER |
18 | #pragma hdrstop |
19 | #endif |
20 | |
21 | #include "corexcep.h" |
22 | |
23 | #define Verify(cond, msg) \ |
24 | do \ |
25 | { \ |
26 | if (!(cond)) \ |
27 | { \ |
28 | verRaiseVerifyExceptionIfNeeded(INDEBUG(msg) DEBUGARG(__FILE__) DEBUGARG(__LINE__)); \ |
29 | } \ |
30 | } while (0) |
31 | |
32 | #define VerifyOrReturn(cond, msg) \ |
33 | do \ |
34 | { \ |
35 | if (!(cond)) \ |
36 | { \ |
37 | verRaiseVerifyExceptionIfNeeded(INDEBUG(msg) DEBUGARG(__FILE__) DEBUGARG(__LINE__)); \ |
38 | return; \ |
39 | } \ |
40 | } while (0) |
41 | |
42 | #define VerifyOrReturnSpeculative(cond, msg, speculative) \ |
43 | do \ |
44 | { \ |
45 | if (speculative) \ |
46 | { \ |
47 | if (!(cond)) \ |
48 | { \ |
49 | return false; \ |
50 | } \ |
51 | } \ |
52 | else \ |
53 | { \ |
54 | if (!(cond)) \ |
55 | { \ |
56 | verRaiseVerifyExceptionIfNeeded(INDEBUG(msg) DEBUGARG(__FILE__) DEBUGARG(__LINE__)); \ |
57 | return false; \ |
58 | } \ |
59 | } \ |
60 | } while (0) |
61 | |
62 | /*****************************************************************************/ |
63 | |
64 | void Compiler::impInit() |
65 | { |
66 | |
67 | #ifdef DEBUG |
68 | impTreeList = nullptr; |
69 | impTreeLast = nullptr; |
70 | impInlinedCodeSize = 0; |
71 | #endif |
72 | } |
73 | |
74 | /***************************************************************************** |
75 | * |
76 | * Pushes the given tree on the stack. |
77 | */ |
78 | |
79 | void Compiler::impPushOnStack(GenTree* tree, typeInfo ti) |
80 | { |
81 | /* Check for overflow. If inlining, we may be using a bigger stack */ |
82 | |
83 | if ((verCurrentState.esStackDepth >= info.compMaxStack) && |
84 | (verCurrentState.esStackDepth >= impStkSize || ((compCurBB->bbFlags & BBF_IMPORTED) == 0))) |
85 | { |
86 | BADCODE("stack overflow" ); |
87 | } |
88 | |
89 | #ifdef DEBUG |
90 | // If we are pushing a struct, make certain we know the precise type! |
91 | if (tree->TypeGet() == TYP_STRUCT) |
92 | { |
93 | assert(ti.IsType(TI_STRUCT)); |
94 | CORINFO_CLASS_HANDLE clsHnd = ti.GetClassHandle(); |
95 | assert(clsHnd != NO_CLASS_HANDLE); |
96 | } |
97 | |
98 | if (tiVerificationNeeded && !ti.IsDead()) |
99 | { |
100 | assert(typeInfo::AreEquivalent(NormaliseForStack(ti), ti)); // types are normalized |
101 | |
102 | // The ti type is consistent with the tree type. |
103 | // |
104 | |
105 | // On 64-bit systems, nodes whose "proper" type is "native int" get labeled TYP_LONG. |
106 | // In the verification type system, we always transform "native int" to "TI_INT". |
107 | // Ideally, we would keep track of which nodes labeled "TYP_LONG" are really "native int", but |
108 | // attempts to do that have proved too difficult. Instead, we'll assume that in checks like this, |
109 | // when there's a mismatch, it's because of this reason -- the typeInfo::AreEquivalentModuloNativeInt |
110 | // method used in the last disjunct allows exactly this mismatch. |
111 | assert(ti.IsDead() || ti.IsByRef() && (tree->TypeGet() == TYP_I_IMPL || tree->TypeGet() == TYP_BYREF) || |
112 | ti.IsUnboxedGenericTypeVar() && tree->TypeGet() == TYP_REF || |
113 | ti.IsObjRef() && tree->TypeGet() == TYP_REF || ti.IsMethod() && tree->TypeGet() == TYP_I_IMPL || |
114 | ti.IsType(TI_STRUCT) && tree->TypeGet() != TYP_REF || |
115 | typeInfo::AreEquivalentModuloNativeInt(NormaliseForStack(ti), |
116 | NormaliseForStack(typeInfo(tree->TypeGet())))); |
117 | |
118 | // If it is a struct type, make certain we normalized the primitive types |
119 | assert(!ti.IsType(TI_STRUCT) || |
120 | info.compCompHnd->getTypeForPrimitiveValueClass(ti.GetClassHandle()) == CORINFO_TYPE_UNDEF); |
121 | } |
122 | |
123 | #if VERBOSE_VERIFY |
124 | if (VERBOSE && tiVerificationNeeded) |
125 | { |
126 | printf("\n" ); |
127 | printf(TI_DUMP_PADDING); |
128 | printf("About to push to stack: " ); |
129 | ti.Dump(); |
130 | } |
131 | #endif // VERBOSE_VERIFY |
132 | |
133 | #endif // DEBUG |
134 | |
135 | verCurrentState.esStack[verCurrentState.esStackDepth].seTypeInfo = ti; |
136 | verCurrentState.esStack[verCurrentState.esStackDepth++].val = tree; |
137 | |
138 | if ((tree->gtType == TYP_LONG) && (compLongUsed == false)) |
139 | { |
140 | compLongUsed = true; |
141 | } |
142 | else if (((tree->gtType == TYP_FLOAT) || (tree->gtType == TYP_DOUBLE)) && (compFloatingPointUsed == false)) |
143 | { |
144 | compFloatingPointUsed = true; |
145 | } |
146 | } |
147 | |
148 | inline void Compiler::impPushNullObjRefOnStack() |
149 | { |
150 | impPushOnStack(gtNewIconNode(0, TYP_REF), typeInfo(TI_NULL)); |
151 | } |
152 | |
153 | // This method gets called when we run into unverifiable code |
154 | // (and we are verifying the method) |
155 | |
156 | inline void Compiler::verRaiseVerifyExceptionIfNeeded(INDEBUG(const char* msg) DEBUGARG(const char* file) |
157 | DEBUGARG(unsigned line)) |
158 | { |
159 | // Remember that the code is not verifiable |
160 | // Note that the method may yet pass canSkipMethodVerification(), |
161 | // and so the presence of unverifiable code may not be an issue. |
162 | tiIsVerifiableCode = FALSE; |
163 | |
164 | #ifdef DEBUG |
165 | const char* tail = strrchr(file, '\\'); |
166 | if (tail) |
167 | { |
168 | file = tail + 1; |
169 | } |
170 | |
171 | if (JitConfig.JitBreakOnUnsafeCode()) |
172 | { |
173 | assert(!"Unsafe code detected" ); |
174 | } |
175 | #endif |
176 | |
177 | JITLOG((LL_INFO10000, "Detected unsafe code: %s:%d : %s, while compiling %s opcode %s, IL offset %x\n" , file, line, |
178 | msg, info.compFullName, impCurOpcName, impCurOpcOffs)); |
179 | |
180 | if (verNeedsVerification() || compIsForImportOnly()) |
181 | { |
182 | JITLOG((LL_ERROR, "Verification failure: %s:%d : %s, while compiling %s opcode %s, IL offset %x\n" , file, line, |
183 | msg, info.compFullName, impCurOpcName, impCurOpcOffs)); |
184 | verRaiseVerifyException(INDEBUG(msg) DEBUGARG(file) DEBUGARG(line)); |
185 | } |
186 | } |
187 | |
188 | inline void DECLSPEC_NORETURN Compiler::verRaiseVerifyException(INDEBUG(const char* msg) DEBUGARG(const char* file) |
189 | DEBUGARG(unsigned line)) |
190 | { |
191 | JITLOG((LL_ERROR, "Verification failure: %s:%d : %s, while compiling %s opcode %s, IL offset %x\n" , file, line, |
192 | msg, info.compFullName, impCurOpcName, impCurOpcOffs)); |
193 | |
194 | #ifdef DEBUG |
195 | // BreakIfDebuggerPresent(); |
196 | if (getBreakOnBadCode()) |
197 | { |
198 | assert(!"Typechecking error" ); |
199 | } |
200 | #endif |
201 | |
202 | RaiseException(SEH_VERIFICATION_EXCEPTION, EXCEPTION_NONCONTINUABLE, 0, nullptr); |
203 | UNREACHABLE(); |
204 | } |
205 | |
206 | // helper function that will tell us if the IL instruction at the addr passed |
207 | // by param consumes an address at the top of the stack. We use it to save |
208 | // us lvAddrTaken |
209 | bool Compiler::impILConsumesAddr(const BYTE* codeAddr, CORINFO_METHOD_HANDLE fncHandle, CORINFO_MODULE_HANDLE scpHandle) |
210 | { |
211 | assert(!compIsForInlining()); |
212 | |
213 | OPCODE opcode; |
214 | |
215 | opcode = (OPCODE)getU1LittleEndian(codeAddr); |
216 | |
217 | switch (opcode) |
218 | { |
219 | // case CEE_LDFLDA: We're taking this one out as if you have a sequence |
220 | // like |
221 | // |
222 | // ldloca.0 |
223 | // ldflda whatever |
224 | // |
225 | // of a primitivelike struct, you end up after morphing with addr of a local |
226 | // that's not marked as addrtaken, which is wrong. Also ldflda is usually used |
227 | // for structs that contain other structs, which isnt a case we handle very |
228 | // well now for other reasons. |
229 | |
230 | case CEE_LDFLD: |
231 | { |
232 | // We won't collapse small fields. This is probably not the right place to have this |
233 | // check, but we're only using the function for this purpose, and is easy to factor |
234 | // out if we need to do so. |
235 | |
236 | CORINFO_RESOLVED_TOKEN resolvedToken; |
237 | impResolveToken(codeAddr + sizeof(__int8), &resolvedToken, CORINFO_TOKENKIND_Field); |
238 | |
239 | var_types lclTyp = JITtype2varType(info.compCompHnd->getFieldType(resolvedToken.hField)); |
240 | |
241 | // Preserve 'small' int types |
242 | if (!varTypeIsSmall(lclTyp)) |
243 | { |
244 | lclTyp = genActualType(lclTyp); |
245 | } |
246 | |
247 | if (varTypeIsSmall(lclTyp)) |
248 | { |
249 | return false; |
250 | } |
251 | |
252 | return true; |
253 | } |
254 | default: |
255 | break; |
256 | } |
257 | |
258 | return false; |
259 | } |
260 | |
261 | void Compiler::impResolveToken(const BYTE* addr, CORINFO_RESOLVED_TOKEN* pResolvedToken, CorInfoTokenKind kind) |
262 | { |
263 | pResolvedToken->tokenContext = impTokenLookupContextHandle; |
264 | pResolvedToken->tokenScope = info.compScopeHnd; |
265 | pResolvedToken->token = getU4LittleEndian(addr); |
266 | pResolvedToken->tokenType = kind; |
267 | |
268 | if (!tiVerificationNeeded) |
269 | { |
270 | info.compCompHnd->resolveToken(pResolvedToken); |
271 | } |
272 | else |
273 | { |
274 | Verify(eeTryResolveToken(pResolvedToken), "Token resolution failed" ); |
275 | } |
276 | } |
277 | |
278 | /***************************************************************************** |
279 | * |
280 | * Pop one tree from the stack. |
281 | */ |
282 | |
283 | StackEntry Compiler::impPopStack() |
284 | { |
285 | if (verCurrentState.esStackDepth == 0) |
286 | { |
287 | BADCODE("stack underflow" ); |
288 | } |
289 | |
290 | #ifdef DEBUG |
291 | #if VERBOSE_VERIFY |
292 | if (VERBOSE && tiVerificationNeeded) |
293 | { |
294 | JITDUMP("\n" ); |
295 | printf(TI_DUMP_PADDING); |
296 | printf("About to pop from the stack: " ); |
297 | const typeInfo& ti = verCurrentState.esStack[verCurrentState.esStackDepth - 1].seTypeInfo; |
298 | ti.Dump(); |
299 | } |
300 | #endif // VERBOSE_VERIFY |
301 | #endif // DEBUG |
302 | |
303 | return verCurrentState.esStack[--verCurrentState.esStackDepth]; |
304 | } |
305 | |
306 | /***************************************************************************** |
307 | * |
308 | * Peep at n'th (0-based) tree on the top of the stack. |
309 | */ |
310 | |
311 | StackEntry& Compiler::impStackTop(unsigned n) |
312 | { |
313 | if (verCurrentState.esStackDepth <= n) |
314 | { |
315 | BADCODE("stack underflow" ); |
316 | } |
317 | |
318 | return verCurrentState.esStack[verCurrentState.esStackDepth - n - 1]; |
319 | } |
320 | |
321 | unsigned Compiler::impStackHeight() |
322 | { |
323 | return verCurrentState.esStackDepth; |
324 | } |
325 | |
326 | /***************************************************************************** |
327 | * Some of the trees are spilled specially. While unspilling them, or |
328 | * making a copy, these need to be handled specially. The function |
329 | * enumerates the operators possible after spilling. |
330 | */ |
331 | |
332 | #ifdef DEBUG // only used in asserts |
333 | static bool impValidSpilledStackEntry(GenTree* tree) |
334 | { |
335 | if (tree->gtOper == GT_LCL_VAR) |
336 | { |
337 | return true; |
338 | } |
339 | |
340 | if (tree->OperIsConst()) |
341 | { |
342 | return true; |
343 | } |
344 | |
345 | return false; |
346 | } |
347 | #endif |
348 | |
349 | /***************************************************************************** |
350 | * |
351 | * The following logic is used to save/restore stack contents. |
352 | * If 'copy' is true, then we make a copy of the trees on the stack. These |
353 | * have to all be cloneable/spilled values. |
354 | */ |
355 | |
356 | void Compiler::impSaveStackState(SavedStack* savePtr, bool copy) |
357 | { |
358 | savePtr->ssDepth = verCurrentState.esStackDepth; |
359 | |
360 | if (verCurrentState.esStackDepth) |
361 | { |
362 | savePtr->ssTrees = new (this, CMK_ImpStack) StackEntry[verCurrentState.esStackDepth]; |
363 | size_t saveSize = verCurrentState.esStackDepth * sizeof(*savePtr->ssTrees); |
364 | |
365 | if (copy) |
366 | { |
367 | StackEntry* table = savePtr->ssTrees; |
368 | |
369 | /* Make a fresh copy of all the stack entries */ |
370 | |
371 | for (unsigned level = 0; level < verCurrentState.esStackDepth; level++, table++) |
372 | { |
373 | table->seTypeInfo = verCurrentState.esStack[level].seTypeInfo; |
374 | GenTree* tree = verCurrentState.esStack[level].val; |
375 | |
376 | assert(impValidSpilledStackEntry(tree)); |
377 | |
378 | switch (tree->gtOper) |
379 | { |
380 | case GT_CNS_INT: |
381 | case GT_CNS_LNG: |
382 | case GT_CNS_DBL: |
383 | case GT_CNS_STR: |
384 | case GT_LCL_VAR: |
385 | table->val = gtCloneExpr(tree); |
386 | break; |
387 | |
388 | default: |
389 | assert(!"Bad oper - Not covered by impValidSpilledStackEntry()" ); |
390 | break; |
391 | } |
392 | } |
393 | } |
394 | else |
395 | { |
396 | memcpy(savePtr->ssTrees, verCurrentState.esStack, saveSize); |
397 | } |
398 | } |
399 | } |
400 | |
401 | void Compiler::impRestoreStackState(SavedStack* savePtr) |
402 | { |
403 | verCurrentState.esStackDepth = savePtr->ssDepth; |
404 | |
405 | if (verCurrentState.esStackDepth) |
406 | { |
407 | memcpy(verCurrentState.esStack, savePtr->ssTrees, |
408 | verCurrentState.esStackDepth * sizeof(*verCurrentState.esStack)); |
409 | } |
410 | } |
411 | |
412 | /***************************************************************************** |
413 | * |
414 | * Get the tree list started for a new basic block. |
415 | */ |
416 | inline void Compiler::impBeginTreeList() |
417 | { |
418 | assert(impTreeList == nullptr && impTreeLast == nullptr); |
419 | |
420 | impTreeList = impTreeLast = new (this, GT_BEG_STMTS) GenTree(GT_BEG_STMTS, TYP_VOID); |
421 | } |
422 | |
423 | /***************************************************************************** |
424 | * |
425 | * Store the given start and end stmt in the given basic block. This is |
426 | * mostly called by impEndTreeList(BasicBlock *block). It is called |
427 | * directly only for handling CEE_LEAVEs out of finally-protected try's. |
428 | */ |
429 | |
430 | inline void Compiler::impEndTreeList(BasicBlock* block, GenTree* firstStmt, GenTree* lastStmt) |
431 | { |
432 | assert(firstStmt->gtOper == GT_STMT); |
433 | assert(lastStmt->gtOper == GT_STMT); |
434 | |
435 | /* Make the list circular, so that we can easily walk it backwards */ |
436 | |
437 | firstStmt->gtPrev = lastStmt; |
438 | |
439 | /* Store the tree list in the basic block */ |
440 | |
441 | block->bbTreeList = firstStmt; |
442 | |
443 | /* The block should not already be marked as imported */ |
444 | assert((block->bbFlags & BBF_IMPORTED) == 0); |
445 | |
446 | block->bbFlags |= BBF_IMPORTED; |
447 | } |
448 | |
449 | /***************************************************************************** |
450 | * |
451 | * Store the current tree list in the given basic block. |
452 | */ |
453 | |
454 | inline void Compiler::impEndTreeList(BasicBlock* block) |
455 | { |
456 | assert(impTreeList->gtOper == GT_BEG_STMTS); |
457 | |
458 | GenTree* firstTree = impTreeList->gtNext; |
459 | |
460 | if (!firstTree) |
461 | { |
462 | /* The block should not already be marked as imported */ |
463 | assert((block->bbFlags & BBF_IMPORTED) == 0); |
464 | |
465 | // Empty block. Just mark it as imported |
466 | block->bbFlags |= BBF_IMPORTED; |
467 | } |
468 | else |
469 | { |
470 | // Ignore the GT_BEG_STMTS |
471 | assert(firstTree->gtPrev == impTreeList); |
472 | |
473 | impEndTreeList(block, firstTree, impTreeLast); |
474 | } |
475 | |
476 | #ifdef DEBUG |
477 | if (impLastILoffsStmt != nullptr) |
478 | { |
479 | impLastILoffsStmt->gtStmt.gtStmtLastILoffs = compIsForInlining() ? BAD_IL_OFFSET : impCurOpcOffs; |
480 | impLastILoffsStmt = nullptr; |
481 | } |
482 | |
483 | impTreeList = impTreeLast = nullptr; |
484 | #endif |
485 | } |
486 | |
487 | /***************************************************************************** |
488 | * |
489 | * Check that storing the given tree doesnt mess up the semantic order. Note |
490 | * that this has only limited value as we can only check [0..chkLevel). |
491 | */ |
492 | |
493 | inline void Compiler::impAppendStmtCheck(GenTree* stmt, unsigned chkLevel) |
494 | { |
495 | #ifndef DEBUG |
496 | return; |
497 | #else |
498 | assert(stmt->gtOper == GT_STMT); |
499 | |
500 | if (chkLevel == (unsigned)CHECK_SPILL_ALL) |
501 | { |
502 | chkLevel = verCurrentState.esStackDepth; |
503 | } |
504 | |
505 | if (verCurrentState.esStackDepth == 0 || chkLevel == 0 || chkLevel == (unsigned)CHECK_SPILL_NONE) |
506 | { |
507 | return; |
508 | } |
509 | |
510 | GenTree* tree = stmt->gtStmt.gtStmtExpr; |
511 | |
512 | // Calls can only be appended if there are no GTF_GLOB_EFFECT on the stack |
513 | |
514 | if (tree->gtFlags & GTF_CALL) |
515 | { |
516 | for (unsigned level = 0; level < chkLevel; level++) |
517 | { |
518 | assert((verCurrentState.esStack[level].val->gtFlags & GTF_GLOB_EFFECT) == 0); |
519 | } |
520 | } |
521 | |
522 | if (tree->gtOper == GT_ASG) |
523 | { |
524 | // For an assignment to a local variable, all references of that |
525 | // variable have to be spilled. If it is aliased, all calls and |
526 | // indirect accesses have to be spilled |
527 | |
528 | if (tree->gtOp.gtOp1->gtOper == GT_LCL_VAR) |
529 | { |
530 | unsigned lclNum = tree->gtOp.gtOp1->gtLclVarCommon.gtLclNum; |
531 | for (unsigned level = 0; level < chkLevel; level++) |
532 | { |
533 | assert(!gtHasRef(verCurrentState.esStack[level].val, lclNum, false)); |
534 | assert(!lvaTable[lclNum].lvAddrExposed || |
535 | (verCurrentState.esStack[level].val->gtFlags & GTF_SIDE_EFFECT) == 0); |
536 | } |
537 | } |
538 | |
539 | // If the access may be to global memory, all side effects have to be spilled. |
540 | |
541 | else if (tree->gtOp.gtOp1->gtFlags & GTF_GLOB_REF) |
542 | { |
543 | for (unsigned level = 0; level < chkLevel; level++) |
544 | { |
545 | assert((verCurrentState.esStack[level].val->gtFlags & GTF_GLOB_REF) == 0); |
546 | } |
547 | } |
548 | } |
549 | #endif |
550 | } |
551 | |
552 | /***************************************************************************** |
553 | * |
554 | * Append the given GT_STMT node to the current block's tree list. |
555 | * [0..chkLevel) is the portion of the stack which we will check for |
556 | * interference with stmt and spill if needed. |
557 | */ |
558 | |
559 | inline void Compiler::impAppendStmt(GenTree* stmt, unsigned chkLevel) |
560 | { |
561 | assert(stmt->gtOper == GT_STMT); |
562 | noway_assert(impTreeLast != nullptr); |
563 | |
564 | /* If the statement being appended has any side-effects, check the stack |
565 | to see if anything needs to be spilled to preserve correct ordering. */ |
566 | |
567 | GenTree* expr = stmt->gtStmt.gtStmtExpr; |
568 | unsigned flags = expr->gtFlags & GTF_GLOB_EFFECT; |
569 | |
570 | // Assignment to (unaliased) locals don't count as a side-effect as |
571 | // we handle them specially using impSpillLclRefs(). Temp locals should |
572 | // be fine too. |
573 | |
574 | if ((expr->gtOper == GT_ASG) && (expr->gtOp.gtOp1->gtOper == GT_LCL_VAR) && |
575 | !(expr->gtOp.gtOp1->gtFlags & GTF_GLOB_REF) && !gtHasLocalsWithAddrOp(expr->gtOp.gtOp2)) |
576 | { |
577 | unsigned op2Flags = expr->gtOp.gtOp2->gtFlags & GTF_GLOB_EFFECT; |
578 | assert(flags == (op2Flags | GTF_ASG)); |
579 | flags = op2Flags; |
580 | } |
581 | |
582 | if (chkLevel == (unsigned)CHECK_SPILL_ALL) |
583 | { |
584 | chkLevel = verCurrentState.esStackDepth; |
585 | } |
586 | |
587 | if (chkLevel && chkLevel != (unsigned)CHECK_SPILL_NONE) |
588 | { |
589 | assert(chkLevel <= verCurrentState.esStackDepth); |
590 | |
591 | if (flags) |
592 | { |
593 | // If there is a call, we have to spill global refs |
594 | bool spillGlobEffects = (flags & GTF_CALL) ? true : false; |
595 | |
596 | if (expr->gtOper == GT_ASG) |
597 | { |
598 | GenTree* lhs = expr->gtGetOp1(); |
599 | // If we are assigning to a global ref, we have to spill global refs on stack. |
600 | // TODO-1stClassStructs: Previously, spillGlobEffects was set to true for |
601 | // GT_INITBLK and GT_COPYBLK, but this is overly conservative, and should be |
602 | // revisited. (Note that it was NOT set to true for GT_COPYOBJ.) |
603 | if (!expr->OperIsBlkOp()) |
604 | { |
605 | // If we are assigning to a global ref, we have to spill global refs on stack |
606 | if ((lhs->gtFlags & GTF_GLOB_REF) != 0) |
607 | { |
608 | spillGlobEffects = true; |
609 | } |
610 | } |
611 | else if ((lhs->OperIsBlk() && !lhs->AsBlk()->HasGCPtr()) || |
612 | ((lhs->OperGet() == GT_LCL_VAR) && |
613 | (lvaTable[lhs->AsLclVarCommon()->gtLclNum].lvStructGcCount == 0))) |
614 | { |
615 | spillGlobEffects = true; |
616 | } |
617 | } |
618 | |
619 | impSpillSideEffects(spillGlobEffects, chkLevel DEBUGARG("impAppendStmt" )); |
620 | } |
621 | else |
622 | { |
623 | impSpillSpecialSideEff(); |
624 | } |
625 | } |
626 | |
627 | impAppendStmtCheck(stmt, chkLevel); |
628 | |
629 | /* Point 'prev' at the previous node, so that we can walk backwards */ |
630 | |
631 | stmt->gtPrev = impTreeLast; |
632 | |
633 | /* Append the expression statement to the list */ |
634 | |
635 | impTreeLast->gtNext = stmt; |
636 | impTreeLast = stmt; |
637 | |
638 | #ifdef FEATURE_SIMD |
639 | impMarkContiguousSIMDFieldAssignments(stmt); |
640 | #endif |
641 | |
642 | /* Once we set impCurStmtOffs in an appended tree, we are ready to |
643 | report the following offsets. So reset impCurStmtOffs */ |
644 | |
645 | if (impTreeLast->gtStmt.gtStmtILoffsx == impCurStmtOffs) |
646 | { |
647 | impCurStmtOffsSet(BAD_IL_OFFSET); |
648 | } |
649 | |
650 | #ifdef DEBUG |
651 | if (impLastILoffsStmt == nullptr) |
652 | { |
653 | impLastILoffsStmt = stmt; |
654 | } |
655 | |
656 | if (verbose) |
657 | { |
658 | printf("\n\n" ); |
659 | gtDispTree(stmt); |
660 | } |
661 | #endif |
662 | } |
663 | |
664 | /***************************************************************************** |
665 | * |
666 | * Insert the given GT_STMT "stmt" before GT_STMT "stmtBefore" |
667 | */ |
668 | |
669 | inline void Compiler::impInsertStmtBefore(GenTree* stmt, GenTree* stmtBefore) |
670 | { |
671 | assert(stmt->gtOper == GT_STMT); |
672 | assert(stmtBefore->gtOper == GT_STMT); |
673 | |
674 | GenTree* stmtPrev = stmtBefore->gtPrev; |
675 | stmt->gtPrev = stmtPrev; |
676 | stmt->gtNext = stmtBefore; |
677 | stmtPrev->gtNext = stmt; |
678 | stmtBefore->gtPrev = stmt; |
679 | } |
680 | |
681 | /***************************************************************************** |
682 | * |
683 | * Append the given expression tree to the current block's tree list. |
684 | * Return the newly created statement. |
685 | */ |
686 | |
687 | GenTree* Compiler::impAppendTree(GenTree* tree, unsigned chkLevel, IL_OFFSETX offset) |
688 | { |
689 | assert(tree); |
690 | |
691 | /* Allocate an 'expression statement' node */ |
692 | |
693 | GenTree* expr = gtNewStmt(tree, offset); |
694 | |
695 | /* Append the statement to the current block's stmt list */ |
696 | |
697 | impAppendStmt(expr, chkLevel); |
698 | |
699 | return expr; |
700 | } |
701 | |
702 | /***************************************************************************** |
703 | * |
704 | * Insert the given exression tree before GT_STMT "stmtBefore" |
705 | */ |
706 | |
707 | void Compiler::impInsertTreeBefore(GenTree* tree, IL_OFFSETX offset, GenTree* stmtBefore) |
708 | { |
709 | assert(stmtBefore->gtOper == GT_STMT); |
710 | |
711 | /* Allocate an 'expression statement' node */ |
712 | |
713 | GenTree* expr = gtNewStmt(tree, offset); |
714 | |
715 | /* Append the statement to the current block's stmt list */ |
716 | |
717 | impInsertStmtBefore(expr, stmtBefore); |
718 | } |
719 | |
720 | /***************************************************************************** |
721 | * |
722 | * Append an assignment of the given value to a temp to the current tree list. |
723 | * curLevel is the stack level for which the spill to the temp is being done. |
724 | */ |
725 | |
726 | void Compiler::impAssignTempGen(unsigned tmp, |
727 | GenTree* val, |
728 | unsigned curLevel, |
729 | GenTree** pAfterStmt, /* = NULL */ |
730 | IL_OFFSETX ilOffset, /* = BAD_IL_OFFSET */ |
731 | BasicBlock* block /* = NULL */ |
732 | ) |
733 | { |
734 | GenTree* asg = gtNewTempAssign(tmp, val); |
735 | |
736 | if (!asg->IsNothingNode()) |
737 | { |
738 | if (pAfterStmt) |
739 | { |
740 | GenTree* asgStmt = gtNewStmt(asg, ilOffset); |
741 | *pAfterStmt = fgInsertStmtAfter(block, *pAfterStmt, asgStmt); |
742 | } |
743 | else |
744 | { |
745 | impAppendTree(asg, curLevel, impCurStmtOffs); |
746 | } |
747 | } |
748 | } |
749 | |
750 | /***************************************************************************** |
751 | * same as above, but handle the valueclass case too |
752 | */ |
753 | |
754 | void Compiler::impAssignTempGen(unsigned tmpNum, |
755 | GenTree* val, |
756 | CORINFO_CLASS_HANDLE structType, |
757 | unsigned curLevel, |
758 | GenTree** pAfterStmt, /* = NULL */ |
759 | IL_OFFSETX ilOffset, /* = BAD_IL_OFFSET */ |
760 | BasicBlock* block /* = NULL */ |
761 | ) |
762 | { |
763 | GenTree* asg; |
764 | |
765 | assert(val->TypeGet() != TYP_STRUCT || structType != NO_CLASS_HANDLE); |
766 | if (varTypeIsStruct(val) && (structType != NO_CLASS_HANDLE)) |
767 | { |
768 | assert(tmpNum < lvaCount); |
769 | assert(structType != NO_CLASS_HANDLE); |
770 | |
771 | // if the method is non-verifiable the assert is not true |
772 | // so at least ignore it in the case when verification is turned on |
773 | // since any block that tries to use the temp would have failed verification. |
774 | var_types varType = lvaTable[tmpNum].lvType; |
775 | assert(tiVerificationNeeded || varType == TYP_UNDEF || varTypeIsStruct(varType)); |
776 | lvaSetStruct(tmpNum, structType, false); |
777 | |
778 | // Now, set the type of the struct value. Note that lvaSetStruct may modify the type |
779 | // of the lclVar to a specialized type (e.g. TYP_SIMD), based on the handle (structType) |
780 | // that has been passed in for the value being assigned to the temp, in which case we |
781 | // need to set 'val' to that same type. |
782 | // Note also that if we always normalized the types of any node that might be a struct |
783 | // type, this would not be necessary - but that requires additional JIT/EE interface |
784 | // calls that may not actually be required - e.g. if we only access a field of a struct. |
785 | |
786 | val->gtType = lvaTable[tmpNum].lvType; |
787 | |
788 | GenTree* dst = gtNewLclvNode(tmpNum, val->gtType); |
789 | asg = impAssignStruct(dst, val, structType, curLevel, pAfterStmt, ilOffset, block); |
790 | } |
791 | else |
792 | { |
793 | asg = gtNewTempAssign(tmpNum, val); |
794 | } |
795 | |
796 | if (!asg->IsNothingNode()) |
797 | { |
798 | if (pAfterStmt) |
799 | { |
800 | GenTree* asgStmt = gtNewStmt(asg, ilOffset); |
801 | *pAfterStmt = fgInsertStmtAfter(block, *pAfterStmt, asgStmt); |
802 | } |
803 | else |
804 | { |
805 | impAppendTree(asg, curLevel, impCurStmtOffs); |
806 | } |
807 | } |
808 | } |
809 | |
810 | /***************************************************************************** |
811 | * |
812 | * Pop the given number of values from the stack and return a list node with |
813 | * their values. |
814 | * The 'prefixTree' argument may optionally contain an argument |
815 | * list that is prepended to the list returned from this function. |
816 | * |
817 | * The notion of prepended is a bit misleading in that the list is backwards |
818 | * from the way I would expect: The first element popped is at the end of |
819 | * the returned list, and prefixTree is 'before' that, meaning closer to |
820 | * the end of the list. To get to prefixTree, you have to walk to the |
821 | * end of the list. |
822 | * |
823 | * For ARG_ORDER_R2L prefixTree is only used to insert extra arguments, as |
824 | * such we reverse its meaning such that returnValue has a reversed |
825 | * prefixTree at the head of the list. |
826 | */ |
827 | |
828 | GenTreeArgList* Compiler::impPopList(unsigned count, CORINFO_SIG_INFO* sig, GenTreeArgList* prefixTree) |
829 | { |
830 | assert(sig == nullptr || count == sig->numArgs); |
831 | |
832 | CORINFO_CLASS_HANDLE structType; |
833 | GenTreeArgList* treeList; |
834 | |
835 | if (Target::g_tgtArgOrder == Target::ARG_ORDER_R2L) |
836 | { |
837 | treeList = nullptr; |
838 | } |
839 | else |
840 | { // ARG_ORDER_L2R |
841 | treeList = prefixTree; |
842 | } |
843 | |
844 | while (count--) |
845 | { |
846 | StackEntry se = impPopStack(); |
847 | typeInfo ti = se.seTypeInfo; |
848 | GenTree* temp = se.val; |
849 | |
850 | if (varTypeIsStruct(temp)) |
851 | { |
852 | // Morph trees that aren't already OBJs or MKREFANY to be OBJs |
853 | assert(ti.IsType(TI_STRUCT)); |
854 | structType = ti.GetClassHandleForValueClass(); |
855 | |
856 | bool forceNormalization = false; |
857 | if (varTypeIsSIMD(temp)) |
858 | { |
859 | // We need to ensure that fgMorphArgs will use the correct struct handle to ensure proper |
860 | // ABI handling of this argument. |
861 | // Note that this can happen, for example, if we have a SIMD intrinsic that returns a SIMD type |
862 | // with a different baseType than we've seen. |
863 | // TODO-Cleanup: Consider whether we can eliminate all of these cases. |
864 | if (gtGetStructHandleIfPresent(temp) != structType) |
865 | { |
866 | forceNormalization = true; |
867 | } |
868 | } |
869 | #ifdef DEBUG |
870 | if (verbose) |
871 | { |
872 | printf("Calling impNormStructVal on:\n" ); |
873 | gtDispTree(temp); |
874 | } |
875 | #endif |
876 | temp = impNormStructVal(temp, structType, (unsigned)CHECK_SPILL_ALL, forceNormalization); |
877 | #ifdef DEBUG |
878 | if (verbose) |
879 | { |
880 | printf("resulting tree:\n" ); |
881 | gtDispTree(temp); |
882 | } |
883 | #endif |
884 | } |
885 | |
886 | /* NOTE: we defer bashing the type for I_IMPL to fgMorphArgs */ |
887 | treeList = gtNewListNode(temp, treeList); |
888 | } |
889 | |
890 | if (sig != nullptr) |
891 | { |
892 | if (sig->retTypeSigClass != nullptr && sig->retType != CORINFO_TYPE_CLASS && |
893 | sig->retType != CORINFO_TYPE_BYREF && sig->retType != CORINFO_TYPE_PTR && sig->retType != CORINFO_TYPE_VAR) |
894 | { |
895 | // Make sure that all valuetypes (including enums) that we push are loaded. |
896 | // This is to guarantee that if a GC is triggerred from the prestub of this methods, |
897 | // all valuetypes in the method signature are already loaded. |
898 | // We need to be able to find the size of the valuetypes, but we cannot |
899 | // do a class-load from within GC. |
900 | info.compCompHnd->classMustBeLoadedBeforeCodeIsRun(sig->retTypeSigClass); |
901 | } |
902 | |
903 | CORINFO_ARG_LIST_HANDLE argLst = sig->args; |
904 | CORINFO_CLASS_HANDLE argClass; |
905 | CORINFO_CLASS_HANDLE argRealClass; |
906 | GenTreeArgList* args; |
907 | |
908 | for (args = treeList, count = sig->numArgs; count > 0; args = args->Rest(), count--) |
909 | { |
910 | PREFIX_ASSUME(args != nullptr); |
911 | |
912 | CorInfoType corType = strip(info.compCompHnd->getArgType(sig, argLst, &argClass)); |
913 | |
914 | // insert implied casts (from float to double or double to float) |
915 | |
916 | if (corType == CORINFO_TYPE_DOUBLE && args->Current()->TypeGet() == TYP_FLOAT) |
917 | { |
918 | args->Current() = gtNewCastNode(TYP_DOUBLE, args->Current(), false, TYP_DOUBLE); |
919 | } |
920 | else if (corType == CORINFO_TYPE_FLOAT && args->Current()->TypeGet() == TYP_DOUBLE) |
921 | { |
922 | args->Current() = gtNewCastNode(TYP_FLOAT, args->Current(), false, TYP_FLOAT); |
923 | } |
924 | |
925 | // insert any widening or narrowing casts for backwards compatibility |
926 | |
927 | args->Current() = impImplicitIorI4Cast(args->Current(), JITtype2varType(corType)); |
928 | |
929 | if (corType != CORINFO_TYPE_CLASS && corType != CORINFO_TYPE_BYREF && corType != CORINFO_TYPE_PTR && |
930 | corType != CORINFO_TYPE_VAR && (argRealClass = info.compCompHnd->getArgClass(sig, argLst)) != nullptr) |
931 | { |
932 | // Everett MC++ could generate IL with a mismatched valuetypes. It used to work with Everett JIT, |
933 | // but it stopped working in Whidbey when we have started passing simple valuetypes as underlying |
934 | // primitive types. |
935 | // We will try to adjust for this case here to avoid breaking customers code (see VSW 485789 for |
936 | // details). |
937 | if (corType == CORINFO_TYPE_VALUECLASS && !varTypeIsStruct(args->Current())) |
938 | { |
939 | args->Current() = impNormStructVal(args->Current(), argRealClass, (unsigned)CHECK_SPILL_ALL, true); |
940 | } |
941 | |
942 | // Make sure that all valuetypes (including enums) that we push are loaded. |
943 | // This is to guarantee that if a GC is triggered from the prestub of this methods, |
944 | // all valuetypes in the method signature are already loaded. |
945 | // We need to be able to find the size of the valuetypes, but we cannot |
946 | // do a class-load from within GC. |
947 | info.compCompHnd->classMustBeLoadedBeforeCodeIsRun(argRealClass); |
948 | } |
949 | |
950 | argLst = info.compCompHnd->getArgNext(argLst); |
951 | } |
952 | } |
953 | |
954 | if (Target::g_tgtArgOrder == Target::ARG_ORDER_R2L) |
955 | { |
956 | // Prepend the prefixTree |
957 | |
958 | // Simple in-place reversal to place treeList |
959 | // at the end of a reversed prefixTree |
960 | while (prefixTree != nullptr) |
961 | { |
962 | GenTreeArgList* next = prefixTree->Rest(); |
963 | prefixTree->Rest() = treeList; |
964 | treeList = prefixTree; |
965 | prefixTree = next; |
966 | } |
967 | } |
968 | return treeList; |
969 | } |
970 | |
971 | /***************************************************************************** |
972 | * |
973 | * Pop the given number of values from the stack in reverse order (STDCALL/CDECL etc.) |
974 | * The first "skipReverseCount" items are not reversed. |
975 | */ |
976 | |
977 | GenTreeArgList* Compiler::impPopRevList(unsigned count, CORINFO_SIG_INFO* sig, unsigned skipReverseCount) |
978 | |
979 | { |
980 | assert(skipReverseCount <= count); |
981 | |
982 | GenTreeArgList* list = impPopList(count, sig); |
983 | |
984 | // reverse the list |
985 | if (list == nullptr || skipReverseCount == count) |
986 | { |
987 | return list; |
988 | } |
989 | |
990 | GenTreeArgList* ptr = nullptr; // Initialized to the first node that needs to be reversed |
991 | GenTreeArgList* lastSkipNode = nullptr; // Will be set to the last node that does not need to be reversed |
992 | |
993 | if (skipReverseCount == 0) |
994 | { |
995 | ptr = list; |
996 | } |
997 | else |
998 | { |
999 | lastSkipNode = list; |
1000 | // Get to the first node that needs to be reversed |
1001 | for (unsigned i = 0; i < skipReverseCount - 1; i++) |
1002 | { |
1003 | lastSkipNode = lastSkipNode->Rest(); |
1004 | } |
1005 | |
1006 | PREFIX_ASSUME(lastSkipNode != nullptr); |
1007 | ptr = lastSkipNode->Rest(); |
1008 | } |
1009 | |
1010 | GenTreeArgList* reversedList = nullptr; |
1011 | |
1012 | do |
1013 | { |
1014 | GenTreeArgList* tmp = ptr->Rest(); |
1015 | ptr->Rest() = reversedList; |
1016 | reversedList = ptr; |
1017 | ptr = tmp; |
1018 | } while (ptr != nullptr); |
1019 | |
1020 | if (skipReverseCount) |
1021 | { |
1022 | lastSkipNode->Rest() = reversedList; |
1023 | return list; |
1024 | } |
1025 | else |
1026 | { |
1027 | return reversedList; |
1028 | } |
1029 | } |
1030 | |
1031 | //------------------------------------------------------------------------ |
1032 | // impAssignStruct: Create a struct assignment |
1033 | // |
1034 | // Arguments: |
1035 | // dest - the destination of the assignment |
1036 | // src - the value to be assigned |
1037 | // structHnd - handle representing the struct type |
1038 | // curLevel - stack level for which a spill may be being done |
1039 | // pAfterStmt - statement to insert any additional statements after |
1040 | // ilOffset - il offset for new statements |
1041 | // block - block to insert any additional statements in |
1042 | // |
1043 | // Return Value: |
1044 | // The tree that should be appended to the statement list that represents the assignment. |
1045 | // |
1046 | // Notes: |
1047 | // Temp assignments may be appended to impTreeList if spilling is necessary. |
1048 | |
1049 | GenTree* Compiler::impAssignStruct(GenTree* dest, |
1050 | GenTree* src, |
1051 | CORINFO_CLASS_HANDLE structHnd, |
1052 | unsigned curLevel, |
1053 | GenTree** pAfterStmt, /* = nullptr */ |
1054 | IL_OFFSETX ilOffset, /* = BAD_IL_OFFSET */ |
1055 | BasicBlock* block /* = nullptr */ |
1056 | ) |
1057 | { |
1058 | assert(varTypeIsStruct(dest)); |
1059 | |
1060 | if (ilOffset == BAD_IL_OFFSET) |
1061 | { |
1062 | ilOffset = impCurStmtOffs; |
1063 | } |
1064 | |
1065 | while (dest->gtOper == GT_COMMA) |
1066 | { |
1067 | // Second thing is the struct. |
1068 | assert(varTypeIsStruct(dest->gtOp.gtOp2)); |
1069 | |
1070 | // Append all the op1 of GT_COMMA trees before we evaluate op2 of the GT_COMMA tree. |
1071 | if (pAfterStmt) |
1072 | { |
1073 | *pAfterStmt = fgInsertStmtAfter(block, *pAfterStmt, gtNewStmt(dest->gtOp.gtOp1, ilOffset)); |
1074 | } |
1075 | else |
1076 | { |
1077 | impAppendTree(dest->gtOp.gtOp1, curLevel, ilOffset); // do the side effect |
1078 | } |
1079 | |
1080 | // set dest to the second thing |
1081 | dest = dest->gtOp.gtOp2; |
1082 | } |
1083 | |
1084 | assert(dest->gtOper == GT_LCL_VAR || dest->gtOper == GT_RETURN || dest->gtOper == GT_FIELD || |
1085 | dest->gtOper == GT_IND || dest->gtOper == GT_OBJ || dest->gtOper == GT_INDEX); |
1086 | |
1087 | // Return a NOP if this is a self-assignment. |
1088 | if (dest->OperGet() == GT_LCL_VAR && src->OperGet() == GT_LCL_VAR && |
1089 | src->gtLclVarCommon.gtLclNum == dest->gtLclVarCommon.gtLclNum) |
1090 | { |
1091 | return gtNewNothingNode(); |
1092 | } |
1093 | |
1094 | // TODO-1stClassStructs: Avoid creating an address if it is not needed, |
1095 | // or re-creating a Blk node if it is. |
1096 | GenTree* destAddr; |
1097 | |
1098 | if (dest->gtOper == GT_IND || dest->OperIsBlk()) |
1099 | { |
1100 | destAddr = dest->gtOp.gtOp1; |
1101 | } |
1102 | else |
1103 | { |
1104 | destAddr = gtNewOperNode(GT_ADDR, TYP_BYREF, dest); |
1105 | } |
1106 | |
1107 | return (impAssignStructPtr(destAddr, src, structHnd, curLevel, pAfterStmt, ilOffset, block)); |
1108 | } |
1109 | |
1110 | //------------------------------------------------------------------------ |
1111 | // impAssignStructPtr: Assign (copy) the structure from 'src' to 'destAddr'. |
1112 | // |
1113 | // Arguments: |
1114 | // destAddr - address of the destination of the assignment |
1115 | // src - source of the assignment |
1116 | // structHnd - handle representing the struct type |
1117 | // curLevel - stack level for which a spill may be being done |
1118 | // pAfterStmt - statement to insert any additional statements after |
1119 | // ilOffset - il offset for new statements |
1120 | // block - block to insert any additional statements in |
1121 | // |
1122 | // Return Value: |
1123 | // The tree that should be appended to the statement list that represents the assignment. |
1124 | // |
1125 | // Notes: |
1126 | // Temp assignments may be appended to impTreeList if spilling is necessary. |
1127 | |
1128 | GenTree* Compiler::impAssignStructPtr(GenTree* destAddr, |
1129 | GenTree* src, |
1130 | CORINFO_CLASS_HANDLE structHnd, |
1131 | unsigned curLevel, |
1132 | GenTree** pAfterStmt, /* = NULL */ |
1133 | IL_OFFSETX ilOffset, /* = BAD_IL_OFFSET */ |
1134 | BasicBlock* block /* = NULL */ |
1135 | ) |
1136 | { |
1137 | var_types destType; |
1138 | GenTree* dest = nullptr; |
1139 | unsigned destFlags = 0; |
1140 | |
1141 | if (ilOffset == BAD_IL_OFFSET) |
1142 | { |
1143 | ilOffset = impCurStmtOffs; |
1144 | } |
1145 | |
1146 | assert(src->gtOper == GT_LCL_VAR || src->gtOper == GT_FIELD || src->gtOper == GT_IND || src->gtOper == GT_OBJ || |
1147 | src->gtOper == GT_CALL || src->gtOper == GT_MKREFANY || src->gtOper == GT_RET_EXPR || |
1148 | src->gtOper == GT_COMMA || |
1149 | (src->TypeGet() != TYP_STRUCT && |
1150 | (GenTree::OperIsSIMD(src->gtOper) || src->OperIsSimdHWIntrinsic() || src->gtOper == GT_LCL_FLD))); |
1151 | |
1152 | if (destAddr->OperGet() == GT_ADDR) |
1153 | { |
1154 | GenTree* destNode = destAddr->gtGetOp1(); |
1155 | // If the actual destination is a local, or already a block node, or is a node that |
1156 | // will be morphed, don't insert an OBJ(ADDR). |
1157 | if (destNode->gtOper == GT_INDEX || destNode->OperIsBlk() || |
1158 | ((destNode->OperGet() == GT_LCL_VAR) && (destNode->TypeGet() == src->TypeGet()))) |
1159 | { |
1160 | dest = destNode; |
1161 | } |
1162 | destType = destNode->TypeGet(); |
1163 | } |
1164 | else |
1165 | { |
1166 | destType = src->TypeGet(); |
1167 | } |
1168 | |
1169 | var_types asgType = src->TypeGet(); |
1170 | |
1171 | if (src->gtOper == GT_CALL) |
1172 | { |
1173 | if (src->AsCall()->TreatAsHasRetBufArg(this)) |
1174 | { |
1175 | // Case of call returning a struct via hidden retbuf arg |
1176 | |
1177 | // insert the return value buffer into the argument list as first byref parameter |
1178 | src->gtCall.gtCallArgs = gtNewListNode(destAddr, src->gtCall.gtCallArgs); |
1179 | |
1180 | // now returns void, not a struct |
1181 | src->gtType = TYP_VOID; |
1182 | |
1183 | // return the morphed call node |
1184 | return src; |
1185 | } |
1186 | else |
1187 | { |
1188 | // Case of call returning a struct in one or more registers. |
1189 | |
1190 | var_types returnType = (var_types)src->gtCall.gtReturnType; |
1191 | |
1192 | // We won't use a return buffer, so change the type of src->gtType to 'returnType' |
1193 | src->gtType = genActualType(returnType); |
1194 | |
1195 | // First we try to change this to "LclVar/LclFld = call" |
1196 | // |
1197 | if ((destAddr->gtOper == GT_ADDR) && (destAddr->gtOp.gtOp1->gtOper == GT_LCL_VAR)) |
1198 | { |
1199 | // If it is a multi-reg struct return, don't change the oper to GT_LCL_FLD. |
1200 | // That is, the IR will be of the form lclVar = call for multi-reg return |
1201 | // |
1202 | GenTree* lcl = destAddr->gtOp.gtOp1; |
1203 | if (src->AsCall()->HasMultiRegRetVal()) |
1204 | { |
1205 | // Mark the struct LclVar as used in a MultiReg return context |
1206 | // which currently makes it non promotable. |
1207 | // TODO-1stClassStructs: Eliminate this pessimization when we can more generally |
1208 | // handle multireg returns. |
1209 | lcl->gtFlags |= GTF_DONT_CSE; |
1210 | lvaTable[lcl->gtLclVarCommon.gtLclNum].lvIsMultiRegRet = true; |
1211 | } |
1212 | else // The call result is not a multireg return |
1213 | { |
1214 | // We change this to a GT_LCL_FLD (from a GT_ADDR of a GT_LCL_VAR) |
1215 | lcl->ChangeOper(GT_LCL_FLD); |
1216 | fgLclFldAssign(lcl->gtLclVarCommon.gtLclNum); |
1217 | lcl->gtType = src->gtType; |
1218 | asgType = src->gtType; |
1219 | } |
1220 | |
1221 | dest = lcl; |
1222 | |
1223 | #if defined(_TARGET_ARM_) |
1224 | // TODO-Cleanup: This should have been taken care of in the above HasMultiRegRetVal() case, |
1225 | // but that method has not been updadted to include ARM. |
1226 | impMarkLclDstNotPromotable(lcl->gtLclVarCommon.gtLclNum, src, structHnd); |
1227 | lcl->gtFlags |= GTF_DONT_CSE; |
1228 | #elif defined(UNIX_AMD64_ABI) |
1229 | // Not allowed for FEATURE_CORCLR which is the only SKU available for System V OSs. |
1230 | assert(!src->gtCall.IsVarargs() && "varargs not allowed for System V OSs." ); |
1231 | |
1232 | // Make the struct non promotable. The eightbytes could contain multiple fields. |
1233 | // TODO-1stClassStructs: Eliminate this pessimization when we can more generally |
1234 | // handle multireg returns. |
1235 | // TODO-Cleanup: Why is this needed here? This seems that it will set this even for |
1236 | // non-multireg returns. |
1237 | lcl->gtFlags |= GTF_DONT_CSE; |
1238 | lvaTable[lcl->gtLclVarCommon.gtLclNum].lvIsMultiRegRet = true; |
1239 | #endif |
1240 | } |
1241 | else // we don't have a GT_ADDR of a GT_LCL_VAR |
1242 | { |
1243 | // !!! The destination could be on stack. !!! |
1244 | // This flag will let us choose the correct write barrier. |
1245 | asgType = returnType; |
1246 | destFlags = GTF_IND_TGTANYWHERE; |
1247 | } |
1248 | } |
1249 | } |
1250 | else if (src->gtOper == GT_RET_EXPR) |
1251 | { |
1252 | GenTreeCall* call = src->gtRetExpr.gtInlineCandidate->AsCall(); |
1253 | noway_assert(call->gtOper == GT_CALL); |
1254 | |
1255 | if (call->HasRetBufArg()) |
1256 | { |
1257 | // insert the return value buffer into the argument list as first byref parameter |
1258 | call->gtCallArgs = gtNewListNode(destAddr, call->gtCallArgs); |
1259 | |
1260 | // now returns void, not a struct |
1261 | src->gtType = TYP_VOID; |
1262 | call->gtType = TYP_VOID; |
1263 | |
1264 | // We already have appended the write to 'dest' GT_CALL's args |
1265 | // So now we just return an empty node (pruning the GT_RET_EXPR) |
1266 | return src; |
1267 | } |
1268 | else |
1269 | { |
1270 | // Case of inline method returning a struct in one or more registers. |
1271 | // |
1272 | var_types returnType = (var_types)call->gtReturnType; |
1273 | |
1274 | // We won't need a return buffer |
1275 | asgType = returnType; |
1276 | src->gtType = genActualType(returnType); |
1277 | call->gtType = src->gtType; |
1278 | |
1279 | // If we've changed the type, and it no longer matches a local destination, |
1280 | // we must use an indirection. |
1281 | if ((dest != nullptr) && (dest->OperGet() == GT_LCL_VAR) && (dest->TypeGet() != asgType)) |
1282 | { |
1283 | dest = nullptr; |
1284 | } |
1285 | |
1286 | // !!! The destination could be on stack. !!! |
1287 | // This flag will let us choose the correct write barrier. |
1288 | destFlags = GTF_IND_TGTANYWHERE; |
1289 | } |
1290 | } |
1291 | else if (src->OperIsBlk()) |
1292 | { |
1293 | asgType = impNormStructType(structHnd); |
1294 | if (src->gtOper == GT_OBJ) |
1295 | { |
1296 | assert(src->gtObj.gtClass == structHnd); |
1297 | } |
1298 | } |
1299 | else if (src->gtOper == GT_INDEX) |
1300 | { |
1301 | asgType = impNormStructType(structHnd); |
1302 | assert(src->gtIndex.gtStructElemClass == structHnd); |
1303 | } |
1304 | else if (src->gtOper == GT_MKREFANY) |
1305 | { |
1306 | // Since we are assigning the result of a GT_MKREFANY, |
1307 | // "destAddr" must point to a refany. |
1308 | |
1309 | GenTree* destAddrClone; |
1310 | destAddr = |
1311 | impCloneExpr(destAddr, &destAddrClone, structHnd, curLevel, pAfterStmt DEBUGARG("MKREFANY assignment" )); |
1312 | |
1313 | assert(OFFSETOF__CORINFO_TypedReference__dataPtr == 0); |
1314 | assert(destAddr->gtType == TYP_I_IMPL || destAddr->gtType == TYP_BYREF); |
1315 | GetZeroOffsetFieldMap()->Set(destAddr, GetFieldSeqStore()->CreateSingleton(GetRefanyDataField())); |
1316 | GenTree* ptrSlot = gtNewOperNode(GT_IND, TYP_I_IMPL, destAddr); |
1317 | GenTreeIntCon* typeFieldOffset = gtNewIconNode(OFFSETOF__CORINFO_TypedReference__type, TYP_I_IMPL); |
1318 | typeFieldOffset->gtFieldSeq = GetFieldSeqStore()->CreateSingleton(GetRefanyTypeField()); |
1319 | GenTree* typeSlot = |
1320 | gtNewOperNode(GT_IND, TYP_I_IMPL, gtNewOperNode(GT_ADD, destAddr->gtType, destAddrClone, typeFieldOffset)); |
1321 | |
1322 | // append the assign of the pointer value |
1323 | GenTree* asg = gtNewAssignNode(ptrSlot, src->gtOp.gtOp1); |
1324 | if (pAfterStmt) |
1325 | { |
1326 | *pAfterStmt = fgInsertStmtAfter(block, *pAfterStmt, gtNewStmt(asg, ilOffset)); |
1327 | } |
1328 | else |
1329 | { |
1330 | impAppendTree(asg, curLevel, ilOffset); |
1331 | } |
1332 | |
1333 | // return the assign of the type value, to be appended |
1334 | return gtNewAssignNode(typeSlot, src->gtOp.gtOp2); |
1335 | } |
1336 | else if (src->gtOper == GT_COMMA) |
1337 | { |
1338 | // The second thing is the struct or its address. |
1339 | assert(varTypeIsStruct(src->gtOp.gtOp2) || src->gtOp.gtOp2->gtType == TYP_BYREF); |
1340 | if (pAfterStmt) |
1341 | { |
1342 | *pAfterStmt = fgInsertStmtAfter(block, *pAfterStmt, gtNewStmt(src->gtOp.gtOp1, ilOffset)); |
1343 | } |
1344 | else |
1345 | { |
1346 | impAppendTree(src->gtOp.gtOp1, curLevel, ilOffset); // do the side effect |
1347 | } |
1348 | |
1349 | // Evaluate the second thing using recursion. |
1350 | return impAssignStructPtr(destAddr, src->gtOp.gtOp2, structHnd, curLevel, pAfterStmt, ilOffset, block); |
1351 | } |
1352 | else if (src->IsLocal()) |
1353 | { |
1354 | asgType = src->TypeGet(); |
1355 | } |
1356 | else if (asgType == TYP_STRUCT) |
1357 | { |
1358 | asgType = impNormStructType(structHnd); |
1359 | src->gtType = asgType; |
1360 | } |
1361 | if (dest == nullptr) |
1362 | { |
1363 | // TODO-1stClassStructs: We shouldn't really need a block node as the destination |
1364 | // if this is a known struct type. |
1365 | if (asgType == TYP_STRUCT) |
1366 | { |
1367 | dest = gtNewObjNode(structHnd, destAddr); |
1368 | gtSetObjGcInfo(dest->AsObj()); |
1369 | // Although an obj as a call argument was always assumed to be a globRef |
1370 | // (which is itself overly conservative), that is not true of the operands |
1371 | // of a block assignment. |
1372 | dest->gtFlags &= ~GTF_GLOB_REF; |
1373 | dest->gtFlags |= (destAddr->gtFlags & GTF_GLOB_REF); |
1374 | } |
1375 | else if (varTypeIsStruct(asgType)) |
1376 | { |
1377 | dest = new (this, GT_BLK) GenTreeBlk(GT_BLK, asgType, destAddr, genTypeSize(asgType)); |
1378 | } |
1379 | else |
1380 | { |
1381 | dest = gtNewOperNode(GT_IND, asgType, destAddr); |
1382 | } |
1383 | } |
1384 | else |
1385 | { |
1386 | dest->gtType = asgType; |
1387 | } |
1388 | |
1389 | dest->gtFlags |= destFlags; |
1390 | destFlags = dest->gtFlags; |
1391 | |
1392 | // return an assignment node, to be appended |
1393 | GenTree* asgNode = gtNewAssignNode(dest, src); |
1394 | gtBlockOpInit(asgNode, dest, src, false); |
1395 | |
1396 | // TODO-1stClassStructs: Clean up the settings of GTF_DONT_CSE on the lhs |
1397 | // of assignments. |
1398 | if ((destFlags & GTF_DONT_CSE) == 0) |
1399 | { |
1400 | dest->gtFlags &= ~(GTF_DONT_CSE); |
1401 | } |
1402 | return asgNode; |
1403 | } |
1404 | |
1405 | /***************************************************************************** |
1406 | Given a struct value, and the class handle for that structure, return |
1407 | the expression for the address for that structure value. |
1408 | |
1409 | willDeref - does the caller guarantee to dereference the pointer. |
1410 | */ |
1411 | |
1412 | GenTree* Compiler::impGetStructAddr(GenTree* structVal, |
1413 | CORINFO_CLASS_HANDLE structHnd, |
1414 | unsigned curLevel, |
1415 | bool willDeref) |
1416 | { |
1417 | assert(varTypeIsStruct(structVal) || eeIsValueClass(structHnd)); |
1418 | |
1419 | var_types type = structVal->TypeGet(); |
1420 | |
1421 | genTreeOps oper = structVal->gtOper; |
1422 | |
1423 | if (oper == GT_OBJ && willDeref) |
1424 | { |
1425 | assert(structVal->gtObj.gtClass == structHnd); |
1426 | return (structVal->gtObj.Addr()); |
1427 | } |
1428 | else if (oper == GT_CALL || oper == GT_RET_EXPR || oper == GT_OBJ || oper == GT_MKREFANY || |
1429 | structVal->OperIsSimdHWIntrinsic()) |
1430 | { |
1431 | unsigned tmpNum = lvaGrabTemp(true DEBUGARG("struct address for call/obj" )); |
1432 | |
1433 | impAssignTempGen(tmpNum, structVal, structHnd, curLevel); |
1434 | |
1435 | // The 'return value' is now the temp itself |
1436 | |
1437 | type = genActualType(lvaTable[tmpNum].TypeGet()); |
1438 | GenTree* temp = gtNewLclvNode(tmpNum, type); |
1439 | temp = gtNewOperNode(GT_ADDR, TYP_BYREF, temp); |
1440 | return temp; |
1441 | } |
1442 | else if (oper == GT_COMMA) |
1443 | { |
1444 | assert(structVal->gtOp.gtOp2->gtType == type); // Second thing is the struct |
1445 | |
1446 | GenTree* oldTreeLast = impTreeLast; |
1447 | structVal->gtOp.gtOp2 = impGetStructAddr(structVal->gtOp.gtOp2, structHnd, curLevel, willDeref); |
1448 | structVal->gtType = TYP_BYREF; |
1449 | |
1450 | if (oldTreeLast != impTreeLast) |
1451 | { |
1452 | // Some temp assignment statement was placed on the statement list |
1453 | // for Op2, but that would be out of order with op1, so we need to |
1454 | // spill op1 onto the statement list after whatever was last |
1455 | // before we recursed on Op2 (i.e. before whatever Op2 appended). |
1456 | impInsertTreeBefore(structVal->gtOp.gtOp1, impCurStmtOffs, oldTreeLast->gtNext); |
1457 | structVal->gtOp.gtOp1 = gtNewNothingNode(); |
1458 | } |
1459 | |
1460 | return (structVal); |
1461 | } |
1462 | |
1463 | return (gtNewOperNode(GT_ADDR, TYP_BYREF, structVal)); |
1464 | } |
1465 | |
1466 | //------------------------------------------------------------------------ |
1467 | // impNormStructType: Given a (known to be) struct class handle structHnd, normalize its type, |
1468 | // and optionally determine the GC layout of the struct. |
1469 | // |
1470 | // Arguments: |
1471 | // structHnd - The class handle for the struct type of interest. |
1472 | // gcLayout - (optional, default nullptr) - a BYTE pointer, allocated by the caller, |
1473 | // into which the gcLayout will be written. |
1474 | // pNumGCVars - (optional, default nullptr) - if non-null, a pointer to an unsigned, |
1475 | // which will be set to the number of GC fields in the struct. |
1476 | // pSimdBaseType - (optional, default nullptr) - if non-null, and the struct is a SIMD |
1477 | // type, set to the SIMD base type |
1478 | // |
1479 | // Return Value: |
1480 | // The JIT type for the struct (e.g. TYP_STRUCT, or TYP_SIMD*). |
1481 | // The gcLayout will be returned using the pointers provided by the caller, if non-null. |
1482 | // It may also modify the compFloatingPointUsed flag if the type is a SIMD type. |
1483 | // |
1484 | // Assumptions: |
1485 | // The caller must set gcLayout to nullptr OR ensure that it is large enough |
1486 | // (see ICorStaticInfo::getClassGClayout in corinfo.h). |
1487 | // |
1488 | // Notes: |
1489 | // Normalizing the type involves examining the struct type to determine if it should |
1490 | // be modified to one that is handled specially by the JIT, possibly being a candidate |
1491 | // for full enregistration, e.g. TYP_SIMD16. |
1492 | |
1493 | var_types Compiler::impNormStructType(CORINFO_CLASS_HANDLE structHnd, |
1494 | BYTE* gcLayout, |
1495 | unsigned* pNumGCVars, |
1496 | var_types* pSimdBaseType) |
1497 | { |
1498 | assert(structHnd != NO_CLASS_HANDLE); |
1499 | |
1500 | const DWORD structFlags = info.compCompHnd->getClassAttribs(structHnd); |
1501 | var_types structType = TYP_STRUCT; |
1502 | |
1503 | // On coreclr the check for GC includes a "may" to account for the special |
1504 | // ByRef like span structs. The added check for "CONTAINS_STACK_PTR" is the particular bit. |
1505 | // When this is set the struct will contain a ByRef that could be a GC pointer or a native |
1506 | // pointer. |
1507 | const bool mayContainGCPtrs = |
1508 | ((structFlags & CORINFO_FLG_CONTAINS_STACK_PTR) != 0 || ((structFlags & CORINFO_FLG_CONTAINS_GC_PTR) != 0)); |
1509 | |
1510 | #ifdef FEATURE_SIMD |
1511 | // Check to see if this is a SIMD type. |
1512 | if (featureSIMD && !mayContainGCPtrs) |
1513 | { |
1514 | unsigned originalSize = info.compCompHnd->getClassSize(structHnd); |
1515 | |
1516 | if ((originalSize >= minSIMDStructBytes()) && (originalSize <= maxSIMDStructBytes())) |
1517 | { |
1518 | unsigned int sizeBytes; |
1519 | var_types simdBaseType = getBaseTypeAndSizeOfSIMDType(structHnd, &sizeBytes); |
1520 | if (simdBaseType != TYP_UNKNOWN) |
1521 | { |
1522 | assert(sizeBytes == originalSize); |
1523 | structType = getSIMDTypeForSize(sizeBytes); |
1524 | if (pSimdBaseType != nullptr) |
1525 | { |
1526 | *pSimdBaseType = simdBaseType; |
1527 | } |
1528 | // Also indicate that we use floating point registers. |
1529 | compFloatingPointUsed = true; |
1530 | } |
1531 | } |
1532 | } |
1533 | #endif // FEATURE_SIMD |
1534 | |
1535 | // Fetch GC layout info if requested |
1536 | if (gcLayout != nullptr) |
1537 | { |
1538 | unsigned numGCVars = info.compCompHnd->getClassGClayout(structHnd, gcLayout); |
1539 | |
1540 | // Verify that the quick test up above via the class attributes gave a |
1541 | // safe view of the type's GCness. |
1542 | // |
1543 | // Note there are cases where mayContainGCPtrs is true but getClassGClayout |
1544 | // does not report any gc fields. |
1545 | |
1546 | assert(mayContainGCPtrs || (numGCVars == 0)); |
1547 | |
1548 | if (pNumGCVars != nullptr) |
1549 | { |
1550 | *pNumGCVars = numGCVars; |
1551 | } |
1552 | } |
1553 | else |
1554 | { |
1555 | // Can't safely ask for number of GC pointers without also |
1556 | // asking for layout. |
1557 | assert(pNumGCVars == nullptr); |
1558 | } |
1559 | |
1560 | return structType; |
1561 | } |
1562 | |
1563 | //------------------------------------------------------------------------ |
1564 | // Compiler::impNormStructVal: Normalize a struct value |
1565 | // |
1566 | // Arguments: |
1567 | // structVal - the node we are going to normalize |
1568 | // structHnd - the class handle for the node |
1569 | // curLevel - the current stack level |
1570 | // forceNormalization - Force the creation of an OBJ node (default is false). |
1571 | // |
1572 | // Notes: |
1573 | // Given struct value 'structVal', make sure it is 'canonical', that is |
1574 | // it is either: |
1575 | // - a known struct type (non-TYP_STRUCT, e.g. TYP_SIMD8) |
1576 | // - an OBJ or a MKREFANY node, or |
1577 | // - a node (e.g. GT_INDEX) that will be morphed. |
1578 | // If the node is a CALL or RET_EXPR, a copy will be made to a new temp. |
1579 | // |
1580 | GenTree* Compiler::impNormStructVal(GenTree* structVal, |
1581 | CORINFO_CLASS_HANDLE structHnd, |
1582 | unsigned curLevel, |
1583 | bool forceNormalization /*=false*/) |
1584 | { |
1585 | assert(forceNormalization || varTypeIsStruct(structVal)); |
1586 | assert(structHnd != NO_CLASS_HANDLE); |
1587 | var_types structType = structVal->TypeGet(); |
1588 | bool makeTemp = false; |
1589 | if (structType == TYP_STRUCT) |
1590 | { |
1591 | structType = impNormStructType(structHnd); |
1592 | } |
1593 | bool alreadyNormalized = false; |
1594 | GenTreeLclVarCommon* structLcl = nullptr; |
1595 | |
1596 | genTreeOps oper = structVal->OperGet(); |
1597 | switch (oper) |
1598 | { |
1599 | // GT_RETURN and GT_MKREFANY don't capture the handle. |
1600 | case GT_RETURN: |
1601 | break; |
1602 | case GT_MKREFANY: |
1603 | alreadyNormalized = true; |
1604 | break; |
1605 | |
1606 | case GT_CALL: |
1607 | structVal->gtCall.gtRetClsHnd = structHnd; |
1608 | makeTemp = true; |
1609 | break; |
1610 | |
1611 | case GT_RET_EXPR: |
1612 | structVal->gtRetExpr.gtRetClsHnd = structHnd; |
1613 | makeTemp = true; |
1614 | break; |
1615 | |
1616 | case GT_ARGPLACE: |
1617 | structVal->gtArgPlace.gtArgPlaceClsHnd = structHnd; |
1618 | break; |
1619 | |
1620 | case GT_INDEX: |
1621 | // This will be transformed to an OBJ later. |
1622 | alreadyNormalized = true; |
1623 | structVal->gtIndex.gtStructElemClass = structHnd; |
1624 | structVal->gtIndex.gtIndElemSize = info.compCompHnd->getClassSize(structHnd); |
1625 | break; |
1626 | |
1627 | case GT_FIELD: |
1628 | // Wrap it in a GT_OBJ. |
1629 | structVal->gtType = structType; |
1630 | structVal = gtNewObjNode(structHnd, gtNewOperNode(GT_ADDR, TYP_BYREF, structVal)); |
1631 | break; |
1632 | |
1633 | case GT_LCL_VAR: |
1634 | case GT_LCL_FLD: |
1635 | structLcl = structVal->AsLclVarCommon(); |
1636 | // Wrap it in a GT_OBJ. |
1637 | structVal = gtNewObjNode(structHnd, gtNewOperNode(GT_ADDR, TYP_BYREF, structVal)); |
1638 | __fallthrough; |
1639 | |
1640 | case GT_OBJ: |
1641 | case GT_BLK: |
1642 | case GT_DYN_BLK: |
1643 | case GT_ASG: |
1644 | // These should already have the appropriate type. |
1645 | assert(structVal->gtType == structType); |
1646 | alreadyNormalized = true; |
1647 | break; |
1648 | |
1649 | case GT_IND: |
1650 | assert(structVal->gtType == structType); |
1651 | structVal = gtNewObjNode(structHnd, structVal->gtGetOp1()); |
1652 | alreadyNormalized = true; |
1653 | break; |
1654 | |
1655 | #ifdef FEATURE_SIMD |
1656 | case GT_SIMD: |
1657 | assert(varTypeIsSIMD(structVal) && (structVal->gtType == structType)); |
1658 | break; |
1659 | #endif // FEATURE_SIMD |
1660 | #ifdef FEATURE_HW_INTRINSICS |
1661 | case GT_HWIntrinsic: |
1662 | assert(varTypeIsSIMD(structVal) && (structVal->gtType == structType)); |
1663 | break; |
1664 | #endif |
1665 | |
1666 | case GT_COMMA: |
1667 | { |
1668 | // The second thing could either be a block node or a GT_FIELD or a GT_SIMD or a GT_COMMA node. |
1669 | GenTree* blockNode = structVal->gtOp.gtOp2; |
1670 | assert(blockNode->gtType == structType); |
1671 | |
1672 | // Is this GT_COMMA(op1, GT_COMMA())? |
1673 | GenTree* parent = structVal; |
1674 | if (blockNode->OperGet() == GT_COMMA) |
1675 | { |
1676 | // Find the last node in the comma chain. |
1677 | do |
1678 | { |
1679 | assert(blockNode->gtType == structType); |
1680 | parent = blockNode; |
1681 | blockNode = blockNode->gtOp.gtOp2; |
1682 | } while (blockNode->OperGet() == GT_COMMA); |
1683 | } |
1684 | |
1685 | if (blockNode->OperGet() == GT_FIELD) |
1686 | { |
1687 | // If we have a GT_FIELD then wrap it in a GT_OBJ. |
1688 | blockNode = gtNewObjNode(structHnd, gtNewOperNode(GT_ADDR, TYP_BYREF, blockNode)); |
1689 | } |
1690 | |
1691 | #ifdef FEATURE_SIMD |
1692 | if (blockNode->OperIsSIMDorSimdHWintrinsic()) |
1693 | { |
1694 | parent->gtOp.gtOp2 = impNormStructVal(blockNode, structHnd, curLevel, forceNormalization); |
1695 | alreadyNormalized = true; |
1696 | } |
1697 | else |
1698 | #endif |
1699 | { |
1700 | noway_assert(blockNode->OperIsBlk()); |
1701 | |
1702 | // Sink the GT_COMMA below the blockNode addr. |
1703 | // That is GT_COMMA(op1, op2=blockNode) is tranformed into |
1704 | // blockNode(GT_COMMA(TYP_BYREF, op1, op2's op1)). |
1705 | // |
1706 | // In case of a chained GT_COMMA case, we sink the last |
1707 | // GT_COMMA below the blockNode addr. |
1708 | GenTree* blockNodeAddr = blockNode->gtOp.gtOp1; |
1709 | assert(blockNodeAddr->gtType == TYP_BYREF); |
1710 | GenTree* commaNode = parent; |
1711 | commaNode->gtType = TYP_BYREF; |
1712 | commaNode->gtOp.gtOp2 = blockNodeAddr; |
1713 | blockNode->gtOp.gtOp1 = commaNode; |
1714 | if (parent == structVal) |
1715 | { |
1716 | structVal = blockNode; |
1717 | } |
1718 | alreadyNormalized = true; |
1719 | } |
1720 | } |
1721 | break; |
1722 | |
1723 | default: |
1724 | noway_assert(!"Unexpected node in impNormStructVal()" ); |
1725 | break; |
1726 | } |
1727 | structVal->gtType = structType; |
1728 | |
1729 | if (!alreadyNormalized || forceNormalization) |
1730 | { |
1731 | if (makeTemp) |
1732 | { |
1733 | unsigned tmpNum = lvaGrabTemp(true DEBUGARG("struct address for call/obj" )); |
1734 | |
1735 | impAssignTempGen(tmpNum, structVal, structHnd, curLevel); |
1736 | |
1737 | // The structVal is now the temp itself |
1738 | |
1739 | structLcl = gtNewLclvNode(tmpNum, structType)->AsLclVarCommon(); |
1740 | structVal = structLcl; |
1741 | } |
1742 | if ((forceNormalization || (structType == TYP_STRUCT)) && !structVal->OperIsBlk()) |
1743 | { |
1744 | // Wrap it in a GT_OBJ |
1745 | structVal = gtNewObjNode(structHnd, gtNewOperNode(GT_ADDR, TYP_BYREF, structVal)); |
1746 | } |
1747 | } |
1748 | |
1749 | if (structLcl != nullptr) |
1750 | { |
1751 | // A OBJ on a ADDR(LCL_VAR) can never raise an exception |
1752 | // so we don't set GTF_EXCEPT here. |
1753 | if (!lvaIsImplicitByRefLocal(structLcl->gtLclNum)) |
1754 | { |
1755 | structVal->gtFlags &= ~GTF_GLOB_REF; |
1756 | } |
1757 | } |
1758 | else if (structVal->OperIsBlk()) |
1759 | { |
1760 | // In general a OBJ is an indirection and could raise an exception. |
1761 | structVal->gtFlags |= GTF_EXCEPT; |
1762 | } |
1763 | return structVal; |
1764 | } |
1765 | |
1766 | /******************************************************************************/ |
1767 | // Given a type token, generate code that will evaluate to the correct |
1768 | // handle representation of that token (type handle, field handle, or method handle) |
1769 | // |
1770 | // For most cases, the handle is determined at compile-time, and the code |
1771 | // generated is simply an embedded handle. |
1772 | // |
1773 | // Run-time lookup is required if the enclosing method is shared between instantiations |
1774 | // and the token refers to formal type parameters whose instantiation is not known |
1775 | // at compile-time. |
1776 | // |
1777 | GenTree* Compiler::impTokenToHandle(CORINFO_RESOLVED_TOKEN* pResolvedToken, |
1778 | BOOL* pRuntimeLookup /* = NULL */, |
1779 | BOOL mustRestoreHandle /* = FALSE */, |
1780 | BOOL importParent /* = FALSE */) |
1781 | { |
1782 | assert(!fgGlobalMorph); |
1783 | |
1784 | CORINFO_GENERICHANDLE_RESULT embedInfo; |
1785 | info.compCompHnd->embedGenericHandle(pResolvedToken, importParent, &embedInfo); |
1786 | |
1787 | if (pRuntimeLookup) |
1788 | { |
1789 | *pRuntimeLookup = embedInfo.lookup.lookupKind.needsRuntimeLookup; |
1790 | } |
1791 | |
1792 | if (mustRestoreHandle && !embedInfo.lookup.lookupKind.needsRuntimeLookup) |
1793 | { |
1794 | switch (embedInfo.handleType) |
1795 | { |
1796 | case CORINFO_HANDLETYPE_CLASS: |
1797 | info.compCompHnd->classMustBeLoadedBeforeCodeIsRun((CORINFO_CLASS_HANDLE)embedInfo.compileTimeHandle); |
1798 | break; |
1799 | |
1800 | case CORINFO_HANDLETYPE_METHOD: |
1801 | info.compCompHnd->methodMustBeLoadedBeforeCodeIsRun((CORINFO_METHOD_HANDLE)embedInfo.compileTimeHandle); |
1802 | break; |
1803 | |
1804 | case CORINFO_HANDLETYPE_FIELD: |
1805 | info.compCompHnd->classMustBeLoadedBeforeCodeIsRun( |
1806 | info.compCompHnd->getFieldClass((CORINFO_FIELD_HANDLE)embedInfo.compileTimeHandle)); |
1807 | break; |
1808 | |
1809 | default: |
1810 | break; |
1811 | } |
1812 | } |
1813 | |
1814 | // Generate the full lookup tree. May be null if we're abandoning an inline attempt. |
1815 | GenTree* result = impLookupToTree(pResolvedToken, &embedInfo.lookup, gtTokenToIconFlags(pResolvedToken->token), |
1816 | embedInfo.compileTimeHandle); |
1817 | |
1818 | // If we have a result and it requires runtime lookup, wrap it in a runtime lookup node. |
1819 | if ((result != nullptr) && embedInfo.lookup.lookupKind.needsRuntimeLookup) |
1820 | { |
1821 | result = gtNewRuntimeLookup(embedInfo.compileTimeHandle, embedInfo.handleType, result); |
1822 | } |
1823 | |
1824 | return result; |
1825 | } |
1826 | |
1827 | GenTree* Compiler::impLookupToTree(CORINFO_RESOLVED_TOKEN* pResolvedToken, |
1828 | CORINFO_LOOKUP* pLookup, |
1829 | unsigned handleFlags, |
1830 | void* compileTimeHandle) |
1831 | { |
1832 | if (!pLookup->lookupKind.needsRuntimeLookup) |
1833 | { |
1834 | // No runtime lookup is required. |
1835 | // Access is direct or memory-indirect (of a fixed address) reference |
1836 | |
1837 | CORINFO_GENERIC_HANDLE handle = nullptr; |
1838 | void* pIndirection = nullptr; |
1839 | assert(pLookup->constLookup.accessType != IAT_PPVALUE && pLookup->constLookup.accessType != IAT_RELPVALUE); |
1840 | |
1841 | if (pLookup->constLookup.accessType == IAT_VALUE) |
1842 | { |
1843 | handle = pLookup->constLookup.handle; |
1844 | } |
1845 | else if (pLookup->constLookup.accessType == IAT_PVALUE) |
1846 | { |
1847 | pIndirection = pLookup->constLookup.addr; |
1848 | } |
1849 | return gtNewIconEmbHndNode(handle, pIndirection, handleFlags, compileTimeHandle); |
1850 | } |
1851 | else if (compIsForInlining()) |
1852 | { |
1853 | // Don't import runtime lookups when inlining |
1854 | // Inlining has to be aborted in such a case |
1855 | compInlineResult->NoteFatal(InlineObservation::CALLSITE_GENERIC_DICTIONARY_LOOKUP); |
1856 | return nullptr; |
1857 | } |
1858 | else |
1859 | { |
1860 | // Need to use dictionary-based access which depends on the typeContext |
1861 | // which is only available at runtime, not at compile-time. |
1862 | |
1863 | return impRuntimeLookupToTree(pResolvedToken, pLookup, compileTimeHandle); |
1864 | } |
1865 | } |
1866 | |
1867 | #ifdef FEATURE_READYTORUN_COMPILER |
1868 | GenTree* Compiler::impReadyToRunLookupToTree(CORINFO_CONST_LOOKUP* pLookup, |
1869 | unsigned handleFlags, |
1870 | void* compileTimeHandle) |
1871 | { |
1872 | CORINFO_GENERIC_HANDLE handle = nullptr; |
1873 | void* pIndirection = nullptr; |
1874 | assert(pLookup->accessType != IAT_PPVALUE && pLookup->accessType != IAT_RELPVALUE); |
1875 | |
1876 | if (pLookup->accessType == IAT_VALUE) |
1877 | { |
1878 | handle = pLookup->handle; |
1879 | } |
1880 | else if (pLookup->accessType == IAT_PVALUE) |
1881 | { |
1882 | pIndirection = pLookup->addr; |
1883 | } |
1884 | return gtNewIconEmbHndNode(handle, pIndirection, handleFlags, compileTimeHandle); |
1885 | } |
1886 | |
1887 | GenTreeCall* Compiler::impReadyToRunHelperToTree( |
1888 | CORINFO_RESOLVED_TOKEN* pResolvedToken, |
1889 | CorInfoHelpFunc helper, |
1890 | var_types type, |
1891 | GenTreeArgList* args /* =NULL*/, |
1892 | CORINFO_LOOKUP_KIND* pGenericLookupKind /* =NULL. Only used with generics */) |
1893 | { |
1894 | CORINFO_CONST_LOOKUP lookup; |
1895 | if (!info.compCompHnd->getReadyToRunHelper(pResolvedToken, pGenericLookupKind, helper, &lookup)) |
1896 | { |
1897 | return nullptr; |
1898 | } |
1899 | |
1900 | GenTreeCall* op1 = gtNewHelperCallNode(helper, type, args); |
1901 | |
1902 | op1->setEntryPoint(lookup); |
1903 | |
1904 | return op1; |
1905 | } |
1906 | #endif |
1907 | |
1908 | GenTree* Compiler::impMethodPointer(CORINFO_RESOLVED_TOKEN* pResolvedToken, CORINFO_CALL_INFO* pCallInfo) |
1909 | { |
1910 | GenTree* op1 = nullptr; |
1911 | |
1912 | switch (pCallInfo->kind) |
1913 | { |
1914 | case CORINFO_CALL: |
1915 | op1 = new (this, GT_FTN_ADDR) GenTreeFptrVal(TYP_I_IMPL, pCallInfo->hMethod); |
1916 | |
1917 | #ifdef FEATURE_READYTORUN_COMPILER |
1918 | if (opts.IsReadyToRun()) |
1919 | { |
1920 | op1->gtFptrVal.gtEntryPoint = pCallInfo->codePointerLookup.constLookup; |
1921 | } |
1922 | else |
1923 | { |
1924 | op1->gtFptrVal.gtEntryPoint.addr = nullptr; |
1925 | op1->gtFptrVal.gtEntryPoint.accessType = IAT_VALUE; |
1926 | } |
1927 | #endif |
1928 | break; |
1929 | |
1930 | case CORINFO_CALL_CODE_POINTER: |
1931 | if (compIsForInlining()) |
1932 | { |
1933 | // Don't import runtime lookups when inlining |
1934 | // Inlining has to be aborted in such a case |
1935 | compInlineResult->NoteFatal(InlineObservation::CALLSITE_GENERIC_DICTIONARY_LOOKUP); |
1936 | return nullptr; |
1937 | } |
1938 | |
1939 | op1 = impLookupToTree(pResolvedToken, &pCallInfo->codePointerLookup, GTF_ICON_FTN_ADDR, pCallInfo->hMethod); |
1940 | break; |
1941 | |
1942 | default: |
1943 | noway_assert(!"unknown call kind" ); |
1944 | break; |
1945 | } |
1946 | |
1947 | return op1; |
1948 | } |
1949 | |
1950 | //------------------------------------------------------------------------ |
1951 | // getRuntimeContextTree: find pointer to context for runtime lookup. |
1952 | // |
1953 | // Arguments: |
1954 | // kind - lookup kind. |
1955 | // |
1956 | // Return Value: |
1957 | // Return GenTree pointer to generic shared context. |
1958 | // |
1959 | // Notes: |
1960 | // Reports about generic context using. |
1961 | |
1962 | GenTree* Compiler::getRuntimeContextTree(CORINFO_RUNTIME_LOOKUP_KIND kind) |
1963 | { |
1964 | GenTree* ctxTree = nullptr; |
1965 | |
1966 | // Collectible types requires that for shared generic code, if we use the generic context parameter |
1967 | // that we report it. (This is a conservative approach, we could detect some cases particularly when the |
1968 | // context parameter is this that we don't need the eager reporting logic.) |
1969 | lvaGenericsContextUseCount++; |
1970 | |
1971 | if (kind == CORINFO_LOOKUP_THISOBJ) |
1972 | { |
1973 | // this Object |
1974 | ctxTree = gtNewLclvNode(info.compThisArg, TYP_REF); |
1975 | |
1976 | // Vtable pointer of this object |
1977 | ctxTree = gtNewOperNode(GT_IND, TYP_I_IMPL, ctxTree); |
1978 | ctxTree->gtFlags |= GTF_EXCEPT; // Null-pointer exception |
1979 | ctxTree->gtFlags |= GTF_IND_INVARIANT; |
1980 | } |
1981 | else |
1982 | { |
1983 | assert(kind == CORINFO_LOOKUP_METHODPARAM || kind == CORINFO_LOOKUP_CLASSPARAM); |
1984 | |
1985 | ctxTree = gtNewLclvNode(info.compTypeCtxtArg, TYP_I_IMPL); // Exact method descriptor as passed in as last arg |
1986 | } |
1987 | return ctxTree; |
1988 | } |
1989 | |
1990 | /*****************************************************************************/ |
1991 | /* Import a dictionary lookup to access a handle in code shared between |
1992 | generic instantiations. |
1993 | The lookup depends on the typeContext which is only available at |
1994 | runtime, and not at compile-time. |
1995 | pLookup->token1 and pLookup->token2 specify the handle that is needed. |
1996 | The cases are: |
1997 | |
1998 | 1. pLookup->indirections == CORINFO_USEHELPER : Call a helper passing it the |
1999 | instantiation-specific handle, and the tokens to lookup the handle. |
2000 | 2. pLookup->indirections != CORINFO_USEHELPER : |
2001 | 2a. pLookup->testForNull == false : Dereference the instantiation-specific handle |
2002 | to get the handle. |
2003 | 2b. pLookup->testForNull == true : Dereference the instantiation-specific handle. |
2004 | If it is non-NULL, it is the handle required. Else, call a helper |
2005 | to lookup the handle. |
2006 | */ |
2007 | |
2008 | GenTree* Compiler::impRuntimeLookupToTree(CORINFO_RESOLVED_TOKEN* pResolvedToken, |
2009 | CORINFO_LOOKUP* pLookup, |
2010 | void* compileTimeHandle) |
2011 | { |
2012 | |
2013 | // This method can only be called from the importer instance of the Compiler. |
2014 | // In other word, it cannot be called by the instance of the Compiler for the inlinee. |
2015 | assert(!compIsForInlining()); |
2016 | |
2017 | GenTree* ctxTree = getRuntimeContextTree(pLookup->lookupKind.runtimeLookupKind); |
2018 | |
2019 | CORINFO_RUNTIME_LOOKUP* pRuntimeLookup = &pLookup->runtimeLookup; |
2020 | // It's available only via the run-time helper function |
2021 | if (pRuntimeLookup->indirections == CORINFO_USEHELPER) |
2022 | { |
2023 | #ifdef FEATURE_READYTORUN_COMPILER |
2024 | if (opts.IsReadyToRun()) |
2025 | { |
2026 | return impReadyToRunHelperToTree(pResolvedToken, CORINFO_HELP_READYTORUN_GENERIC_HANDLE, TYP_I_IMPL, |
2027 | gtNewArgList(ctxTree), &pLookup->lookupKind); |
2028 | } |
2029 | #endif |
2030 | GenTree* argNode = |
2031 | gtNewIconEmbHndNode(pRuntimeLookup->signature, nullptr, GTF_ICON_TOKEN_HDL, compileTimeHandle); |
2032 | GenTreeArgList* helperArgs = gtNewArgList(ctxTree, argNode); |
2033 | |
2034 | return gtNewHelperCallNode(pRuntimeLookup->helper, TYP_I_IMPL, helperArgs); |
2035 | } |
2036 | |
2037 | // Slot pointer |
2038 | GenTree* slotPtrTree = ctxTree; |
2039 | |
2040 | if (pRuntimeLookup->testForNull) |
2041 | { |
2042 | slotPtrTree = impCloneExpr(ctxTree, &ctxTree, NO_CLASS_HANDLE, (unsigned)CHECK_SPILL_ALL, |
2043 | nullptr DEBUGARG("impRuntimeLookup slot" )); |
2044 | } |
2045 | |
2046 | GenTree* indOffTree = nullptr; |
2047 | |
2048 | // Applied repeated indirections |
2049 | for (WORD i = 0; i < pRuntimeLookup->indirections; i++) |
2050 | { |
2051 | if ((i == 1 && pRuntimeLookup->indirectFirstOffset) || (i == 2 && pRuntimeLookup->indirectSecondOffset)) |
2052 | { |
2053 | indOffTree = impCloneExpr(slotPtrTree, &slotPtrTree, NO_CLASS_HANDLE, (unsigned)CHECK_SPILL_ALL, |
2054 | nullptr DEBUGARG("impRuntimeLookup indirectOffset" )); |
2055 | } |
2056 | |
2057 | if (i != 0) |
2058 | { |
2059 | slotPtrTree = gtNewOperNode(GT_IND, TYP_I_IMPL, slotPtrTree); |
2060 | slotPtrTree->gtFlags |= GTF_IND_NONFAULTING; |
2061 | slotPtrTree->gtFlags |= GTF_IND_INVARIANT; |
2062 | } |
2063 | |
2064 | if ((i == 1 && pRuntimeLookup->indirectFirstOffset) || (i == 2 && pRuntimeLookup->indirectSecondOffset)) |
2065 | { |
2066 | slotPtrTree = gtNewOperNode(GT_ADD, TYP_I_IMPL, indOffTree, slotPtrTree); |
2067 | } |
2068 | |
2069 | if (pRuntimeLookup->offsets[i] != 0) |
2070 | { |
2071 | slotPtrTree = |
2072 | gtNewOperNode(GT_ADD, TYP_I_IMPL, slotPtrTree, gtNewIconNode(pRuntimeLookup->offsets[i], TYP_I_IMPL)); |
2073 | } |
2074 | } |
2075 | |
2076 | // No null test required |
2077 | if (!pRuntimeLookup->testForNull) |
2078 | { |
2079 | if (pRuntimeLookup->indirections == 0) |
2080 | { |
2081 | return slotPtrTree; |
2082 | } |
2083 | |
2084 | slotPtrTree = gtNewOperNode(GT_IND, TYP_I_IMPL, slotPtrTree); |
2085 | slotPtrTree->gtFlags |= GTF_IND_NONFAULTING; |
2086 | |
2087 | if (!pRuntimeLookup->testForFixup) |
2088 | { |
2089 | return slotPtrTree; |
2090 | } |
2091 | |
2092 | impSpillSideEffects(true, CHECK_SPILL_ALL DEBUGARG("bubbling QMark0" )); |
2093 | |
2094 | unsigned slotLclNum = lvaGrabTemp(true DEBUGARG("impRuntimeLookup test" )); |
2095 | impAssignTempGen(slotLclNum, slotPtrTree, NO_CLASS_HANDLE, (unsigned)CHECK_SPILL_ALL, nullptr, impCurStmtOffs); |
2096 | |
2097 | GenTree* slot = gtNewLclvNode(slotLclNum, TYP_I_IMPL); |
2098 | // downcast the pointer to a TYP_INT on 64-bit targets |
2099 | slot = impImplicitIorI4Cast(slot, TYP_INT); |
2100 | // Use a GT_AND to check for the lowest bit and indirect if it is set |
2101 | GenTree* test = gtNewOperNode(GT_AND, TYP_INT, slot, gtNewIconNode(1)); |
2102 | GenTree* relop = gtNewOperNode(GT_EQ, TYP_INT, test, gtNewIconNode(0)); |
2103 | |
2104 | // slot = GT_IND(slot - 1) |
2105 | slot = gtNewLclvNode(slotLclNum, TYP_I_IMPL); |
2106 | GenTree* add = gtNewOperNode(GT_ADD, TYP_I_IMPL, slot, gtNewIconNode(-1, TYP_I_IMPL)); |
2107 | GenTree* indir = gtNewOperNode(GT_IND, TYP_I_IMPL, add); |
2108 | indir->gtFlags |= GTF_IND_NONFAULTING; |
2109 | indir->gtFlags |= GTF_IND_INVARIANT; |
2110 | |
2111 | slot = gtNewLclvNode(slotLclNum, TYP_I_IMPL); |
2112 | GenTree* asg = gtNewAssignNode(slot, indir); |
2113 | GenTree* colon = new (this, GT_COLON) GenTreeColon(TYP_VOID, gtNewNothingNode(), asg); |
2114 | GenTree* qmark = gtNewQmarkNode(TYP_VOID, relop, colon); |
2115 | impAppendTree(qmark, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); |
2116 | |
2117 | return gtNewLclvNode(slotLclNum, TYP_I_IMPL); |
2118 | } |
2119 | |
2120 | assert(pRuntimeLookup->indirections != 0); |
2121 | |
2122 | impSpillSideEffects(true, CHECK_SPILL_ALL DEBUGARG("bubbling QMark1" )); |
2123 | |
2124 | // Extract the handle |
2125 | GenTree* handle = gtNewOperNode(GT_IND, TYP_I_IMPL, slotPtrTree); |
2126 | handle->gtFlags |= GTF_IND_NONFAULTING; |
2127 | |
2128 | GenTree* handleCopy = impCloneExpr(handle, &handle, NO_CLASS_HANDLE, (unsigned)CHECK_SPILL_ALL, |
2129 | nullptr DEBUGARG("impRuntimeLookup typehandle" )); |
2130 | |
2131 | // Call to helper |
2132 | GenTree* argNode = gtNewIconEmbHndNode(pRuntimeLookup->signature, nullptr, GTF_ICON_TOKEN_HDL, compileTimeHandle); |
2133 | |
2134 | GenTreeArgList* helperArgs = gtNewArgList(ctxTree, argNode); |
2135 | GenTree* helperCall = gtNewHelperCallNode(pRuntimeLookup->helper, TYP_I_IMPL, helperArgs); |
2136 | |
2137 | // Check for null and possibly call helper |
2138 | GenTree* relop = gtNewOperNode(GT_NE, TYP_INT, handle, gtNewIconNode(0, TYP_I_IMPL)); |
2139 | |
2140 | GenTree* colon = new (this, GT_COLON) GenTreeColon(TYP_I_IMPL, |
2141 | gtNewNothingNode(), // do nothing if nonnull |
2142 | helperCall); |
2143 | |
2144 | GenTree* qmark = gtNewQmarkNode(TYP_I_IMPL, relop, colon); |
2145 | |
2146 | unsigned tmp; |
2147 | if (handleCopy->IsLocal()) |
2148 | { |
2149 | tmp = handleCopy->gtLclVarCommon.gtLclNum; |
2150 | } |
2151 | else |
2152 | { |
2153 | tmp = lvaGrabTemp(true DEBUGARG("spilling QMark1" )); |
2154 | } |
2155 | |
2156 | impAssignTempGen(tmp, qmark, (unsigned)CHECK_SPILL_NONE); |
2157 | return gtNewLclvNode(tmp, TYP_I_IMPL); |
2158 | } |
2159 | |
2160 | /****************************************************************************** |
2161 | * Spills the stack at verCurrentState.esStack[level] and replaces it with a temp. |
2162 | * If tnum!=BAD_VAR_NUM, the temp var used to replace the tree is tnum, |
2163 | * else, grab a new temp. |
2164 | * For structs (which can be pushed on the stack using obj, etc), |
2165 | * special handling is needed |
2166 | */ |
2167 | |
2168 | struct RecursiveGuard |
2169 | { |
2170 | public: |
2171 | RecursiveGuard() |
2172 | { |
2173 | m_pAddress = nullptr; |
2174 | } |
2175 | |
2176 | ~RecursiveGuard() |
2177 | { |
2178 | if (m_pAddress) |
2179 | { |
2180 | *m_pAddress = false; |
2181 | } |
2182 | } |
2183 | |
2184 | void Init(bool* pAddress, bool bInitialize) |
2185 | { |
2186 | assert(pAddress && *pAddress == false && "Recursive guard violation" ); |
2187 | m_pAddress = pAddress; |
2188 | |
2189 | if (bInitialize) |
2190 | { |
2191 | *m_pAddress = true; |
2192 | } |
2193 | } |
2194 | |
2195 | protected: |
2196 | bool* m_pAddress; |
2197 | }; |
2198 | |
2199 | bool Compiler::impSpillStackEntry(unsigned level, |
2200 | unsigned tnum |
2201 | #ifdef DEBUG |
2202 | , |
2203 | bool bAssertOnRecursion, |
2204 | const char* reason |
2205 | #endif |
2206 | ) |
2207 | { |
2208 | |
2209 | #ifdef DEBUG |
2210 | RecursiveGuard guard; |
2211 | guard.Init(&impNestedStackSpill, bAssertOnRecursion); |
2212 | #endif |
2213 | |
2214 | GenTree* tree = verCurrentState.esStack[level].val; |
2215 | |
2216 | /* Allocate a temp if we haven't been asked to use a particular one */ |
2217 | |
2218 | if (tiVerificationNeeded) |
2219 | { |
2220 | // Ignore bad temp requests (they will happen with bad code and will be |
2221 | // catched when importing the destblock) |
2222 | if ((tnum != BAD_VAR_NUM && tnum >= lvaCount) && verNeedsVerification()) |
2223 | { |
2224 | return false; |
2225 | } |
2226 | } |
2227 | else |
2228 | { |
2229 | if (tnum != BAD_VAR_NUM && (tnum >= lvaCount)) |
2230 | { |
2231 | return false; |
2232 | } |
2233 | } |
2234 | |
2235 | bool isNewTemp = false; |
2236 | |
2237 | if (tnum == BAD_VAR_NUM) |
2238 | { |
2239 | tnum = lvaGrabTemp(true DEBUGARG(reason)); |
2240 | isNewTemp = true; |
2241 | } |
2242 | else if (tiVerificationNeeded && lvaTable[tnum].TypeGet() != TYP_UNDEF) |
2243 | { |
2244 | // if verification is needed and tnum's type is incompatible with |
2245 | // type on that stack, we grab a new temp. This is safe since |
2246 | // we will throw a verification exception in the dest block. |
2247 | |
2248 | var_types valTyp = tree->TypeGet(); |
2249 | var_types dstTyp = lvaTable[tnum].TypeGet(); |
2250 | |
2251 | // if the two types are different, we return. This will only happen with bad code and will |
2252 | // be catched when importing the destblock. We still allow int/byrefs and float/double differences. |
2253 | if ((genActualType(valTyp) != genActualType(dstTyp)) && |
2254 | !( |
2255 | #ifndef _TARGET_64BIT_ |
2256 | (valTyp == TYP_I_IMPL && dstTyp == TYP_BYREF) || (valTyp == TYP_BYREF && dstTyp == TYP_I_IMPL) || |
2257 | #endif // !_TARGET_64BIT_ |
2258 | (varTypeIsFloating(dstTyp) && varTypeIsFloating(valTyp)))) |
2259 | { |
2260 | if (verNeedsVerification()) |
2261 | { |
2262 | return false; |
2263 | } |
2264 | } |
2265 | } |
2266 | |
2267 | /* Assign the spilled entry to the temp */ |
2268 | impAssignTempGen(tnum, tree, verCurrentState.esStack[level].seTypeInfo.GetClassHandle(), level); |
2269 | |
2270 | // If temp is newly introduced and a ref type, grab what type info we can. |
2271 | if (isNewTemp && (lvaTable[tnum].lvType == TYP_REF)) |
2272 | { |
2273 | assert(lvaTable[tnum].lvSingleDef == 0); |
2274 | lvaTable[tnum].lvSingleDef = 1; |
2275 | JITDUMP("Marked V%02u as a single def temp\n" , tnum); |
2276 | CORINFO_CLASS_HANDLE stkHnd = verCurrentState.esStack[level].seTypeInfo.GetClassHandle(); |
2277 | lvaSetClass(tnum, tree, stkHnd); |
2278 | |
2279 | // If we're assigning a GT_RET_EXPR, note the temp over on the call, |
2280 | // so the inliner can use it in case it needs a return spill temp. |
2281 | if (tree->OperGet() == GT_RET_EXPR) |
2282 | { |
2283 | JITDUMP("\n*** see V%02u = GT_RET_EXPR, noting temp\n" , tnum); |
2284 | GenTree* call = tree->gtRetExpr.gtInlineCandidate; |
2285 | InlineCandidateInfo* ici = call->gtCall.gtInlineCandidateInfo; |
2286 | ici->preexistingSpillTemp = tnum; |
2287 | } |
2288 | } |
2289 | |
2290 | // The tree type may be modified by impAssignTempGen, so use the type of the lclVar. |
2291 | var_types type = genActualType(lvaTable[tnum].TypeGet()); |
2292 | GenTree* temp = gtNewLclvNode(tnum, type); |
2293 | verCurrentState.esStack[level].val = temp; |
2294 | |
2295 | return true; |
2296 | } |
2297 | |
2298 | /***************************************************************************** |
2299 | * |
2300 | * Ensure that the stack has only spilled values |
2301 | */ |
2302 | |
2303 | void Compiler::impSpillStackEnsure(bool spillLeaves) |
2304 | { |
2305 | assert(!spillLeaves || opts.compDbgCode); |
2306 | |
2307 | for (unsigned level = 0; level < verCurrentState.esStackDepth; level++) |
2308 | { |
2309 | GenTree* tree = verCurrentState.esStack[level].val; |
2310 | |
2311 | if (!spillLeaves && tree->OperIsLeaf()) |
2312 | { |
2313 | continue; |
2314 | } |
2315 | |
2316 | // Temps introduced by the importer itself don't need to be spilled |
2317 | |
2318 | bool isTempLcl = (tree->OperGet() == GT_LCL_VAR) && (tree->gtLclVarCommon.gtLclNum >= info.compLocalsCount); |
2319 | |
2320 | if (isTempLcl) |
2321 | { |
2322 | continue; |
2323 | } |
2324 | |
2325 | impSpillStackEntry(level, BAD_VAR_NUM DEBUGARG(false) DEBUGARG("impSpillStackEnsure" )); |
2326 | } |
2327 | } |
2328 | |
2329 | void Compiler::impSpillEvalStack() |
2330 | { |
2331 | for (unsigned level = 0; level < verCurrentState.esStackDepth; level++) |
2332 | { |
2333 | impSpillStackEntry(level, BAD_VAR_NUM DEBUGARG(false) DEBUGARG("impSpillEvalStack" )); |
2334 | } |
2335 | } |
2336 | |
2337 | /***************************************************************************** |
2338 | * |
2339 | * If the stack contains any trees with side effects in them, assign those |
2340 | * trees to temps and append the assignments to the statement list. |
2341 | * On return the stack is guaranteed to be empty. |
2342 | */ |
2343 | |
2344 | inline void Compiler::impEvalSideEffects() |
2345 | { |
2346 | impSpillSideEffects(false, (unsigned)CHECK_SPILL_ALL DEBUGARG("impEvalSideEffects" )); |
2347 | verCurrentState.esStackDepth = 0; |
2348 | } |
2349 | |
2350 | /***************************************************************************** |
2351 | * |
2352 | * If the stack contains any trees with side effects in them, assign those |
2353 | * trees to temps and replace them on the stack with refs to their temps. |
2354 | * [0..chkLevel) is the portion of the stack which will be checked and spilled. |
2355 | */ |
2356 | |
2357 | inline void Compiler::impSpillSideEffects(bool spillGlobEffects, unsigned chkLevel DEBUGARG(const char* reason)) |
2358 | { |
2359 | assert(chkLevel != (unsigned)CHECK_SPILL_NONE); |
2360 | |
2361 | /* Before we make any appends to the tree list we must spill the |
2362 | * "special" side effects (GTF_ORDER_SIDEEFF on a GT_CATCH_ARG) */ |
2363 | |
2364 | impSpillSpecialSideEff(); |
2365 | |
2366 | if (chkLevel == (unsigned)CHECK_SPILL_ALL) |
2367 | { |
2368 | chkLevel = verCurrentState.esStackDepth; |
2369 | } |
2370 | |
2371 | assert(chkLevel <= verCurrentState.esStackDepth); |
2372 | |
2373 | unsigned spillFlags = spillGlobEffects ? GTF_GLOB_EFFECT : GTF_SIDE_EFFECT; |
2374 | |
2375 | for (unsigned i = 0; i < chkLevel; i++) |
2376 | { |
2377 | GenTree* tree = verCurrentState.esStack[i].val; |
2378 | |
2379 | GenTree* lclVarTree; |
2380 | |
2381 | if ((tree->gtFlags & spillFlags) != 0 || |
2382 | (spillGlobEffects && // Only consider the following when spillGlobEffects == TRUE |
2383 | !impIsAddressInLocal(tree, &lclVarTree) && // No need to spill the GT_ADDR node on a local. |
2384 | gtHasLocalsWithAddrOp(tree))) // Spill if we still see GT_LCL_VAR that contains lvHasLdAddrOp or |
2385 | // lvAddrTaken flag. |
2386 | { |
2387 | impSpillStackEntry(i, BAD_VAR_NUM DEBUGARG(false) DEBUGARG(reason)); |
2388 | } |
2389 | } |
2390 | } |
2391 | |
2392 | /***************************************************************************** |
2393 | * |
2394 | * If the stack contains any trees with special side effects in them, assign |
2395 | * those trees to temps and replace them on the stack with refs to their temps. |
2396 | */ |
2397 | |
2398 | inline void Compiler::impSpillSpecialSideEff() |
2399 | { |
2400 | // Only exception objects need to be carefully handled |
2401 | |
2402 | if (!compCurBB->bbCatchTyp) |
2403 | { |
2404 | return; |
2405 | } |
2406 | |
2407 | for (unsigned level = 0; level < verCurrentState.esStackDepth; level++) |
2408 | { |
2409 | GenTree* tree = verCurrentState.esStack[level].val; |
2410 | // Make sure if we have an exception object in the sub tree we spill ourselves. |
2411 | if (gtHasCatchArg(tree)) |
2412 | { |
2413 | impSpillStackEntry(level, BAD_VAR_NUM DEBUGARG(false) DEBUGARG("impSpillSpecialSideEff" )); |
2414 | } |
2415 | } |
2416 | } |
2417 | |
2418 | /***************************************************************************** |
2419 | * |
2420 | * Spill all stack references to value classes (TYP_STRUCT nodes) |
2421 | */ |
2422 | |
2423 | void Compiler::impSpillValueClasses() |
2424 | { |
2425 | for (unsigned level = 0; level < verCurrentState.esStackDepth; level++) |
2426 | { |
2427 | GenTree* tree = verCurrentState.esStack[level].val; |
2428 | |
2429 | if (fgWalkTreePre(&tree, impFindValueClasses) == WALK_ABORT) |
2430 | { |
2431 | // Tree walk was aborted, which means that we found a |
2432 | // value class on the stack. Need to spill that |
2433 | // stack entry. |
2434 | |
2435 | impSpillStackEntry(level, BAD_VAR_NUM DEBUGARG(false) DEBUGARG("impSpillValueClasses" )); |
2436 | } |
2437 | } |
2438 | } |
2439 | |
2440 | /***************************************************************************** |
2441 | * |
2442 | * Callback that checks if a tree node is TYP_STRUCT |
2443 | */ |
2444 | |
2445 | Compiler::fgWalkResult Compiler::impFindValueClasses(GenTree** pTree, fgWalkData* data) |
2446 | { |
2447 | fgWalkResult walkResult = WALK_CONTINUE; |
2448 | |
2449 | if ((*pTree)->gtType == TYP_STRUCT) |
2450 | { |
2451 | // Abort the walk and indicate that we found a value class |
2452 | |
2453 | walkResult = WALK_ABORT; |
2454 | } |
2455 | |
2456 | return walkResult; |
2457 | } |
2458 | |
2459 | /***************************************************************************** |
2460 | * |
2461 | * If the stack contains any trees with references to local #lclNum, assign |
2462 | * those trees to temps and replace their place on the stack with refs to |
2463 | * their temps. |
2464 | */ |
2465 | |
2466 | void Compiler::impSpillLclRefs(ssize_t lclNum) |
2467 | { |
2468 | /* Before we make any appends to the tree list we must spill the |
2469 | * "special" side effects (GTF_ORDER_SIDEEFF) - GT_CATCH_ARG */ |
2470 | |
2471 | impSpillSpecialSideEff(); |
2472 | |
2473 | for (unsigned level = 0; level < verCurrentState.esStackDepth; level++) |
2474 | { |
2475 | GenTree* tree = verCurrentState.esStack[level].val; |
2476 | |
2477 | /* If the tree may throw an exception, and the block has a handler, |
2478 | then we need to spill assignments to the local if the local is |
2479 | live on entry to the handler. |
2480 | Just spill 'em all without considering the liveness */ |
2481 | |
2482 | bool xcptnCaught = ehBlockHasExnFlowDsc(compCurBB) && (tree->gtFlags & (GTF_CALL | GTF_EXCEPT)); |
2483 | |
2484 | /* Skip the tree if it doesn't have an affected reference, |
2485 | unless xcptnCaught */ |
2486 | |
2487 | if (xcptnCaught || gtHasRef(tree, lclNum, false)) |
2488 | { |
2489 | impSpillStackEntry(level, BAD_VAR_NUM DEBUGARG(false) DEBUGARG("impSpillLclRefs" )); |
2490 | } |
2491 | } |
2492 | } |
2493 | |
2494 | /***************************************************************************** |
2495 | * |
2496 | * Push catch arg onto the stack. |
2497 | * If there are jumps to the beginning of the handler, insert basic block |
2498 | * and spill catch arg to a temp. Update the handler block if necessary. |
2499 | * |
2500 | * Returns the basic block of the actual handler. |
2501 | */ |
2502 | |
2503 | BasicBlock* Compiler::impPushCatchArgOnStack(BasicBlock* hndBlk, CORINFO_CLASS_HANDLE clsHnd, bool isSingleBlockFilter) |
2504 | { |
2505 | // Do not inject the basic block twice on reimport. This should be |
2506 | // hit only under JIT stress. See if the block is the one we injected. |
2507 | // Note that EH canonicalization can inject internal blocks here. We might |
2508 | // be able to re-use such a block (but we don't, right now). |
2509 | if ((hndBlk->bbFlags & (BBF_IMPORTED | BBF_INTERNAL | BBF_DONT_REMOVE | BBF_HAS_LABEL | BBF_JMP_TARGET)) == |
2510 | (BBF_IMPORTED | BBF_INTERNAL | BBF_DONT_REMOVE | BBF_HAS_LABEL | BBF_JMP_TARGET)) |
2511 | { |
2512 | GenTree* tree = hndBlk->bbTreeList; |
2513 | |
2514 | if (tree != nullptr && tree->gtOper == GT_STMT) |
2515 | { |
2516 | tree = tree->gtStmt.gtStmtExpr; |
2517 | assert(tree != nullptr); |
2518 | |
2519 | if ((tree->gtOper == GT_ASG) && (tree->gtOp.gtOp1->gtOper == GT_LCL_VAR) && |
2520 | (tree->gtOp.gtOp2->gtOper == GT_CATCH_ARG)) |
2521 | { |
2522 | tree = gtNewLclvNode(tree->gtOp.gtOp1->gtLclVarCommon.gtLclNum, TYP_REF); |
2523 | |
2524 | impPushOnStack(tree, typeInfo(TI_REF, clsHnd)); |
2525 | |
2526 | return hndBlk->bbNext; |
2527 | } |
2528 | } |
2529 | |
2530 | // If we get here, it must have been some other kind of internal block. It's possible that |
2531 | // someone prepended something to our injected block, but that's unlikely. |
2532 | } |
2533 | |
2534 | /* Push the exception address value on the stack */ |
2535 | GenTree* arg = new (this, GT_CATCH_ARG) GenTree(GT_CATCH_ARG, TYP_REF); |
2536 | |
2537 | /* Mark the node as having a side-effect - i.e. cannot be |
2538 | * moved around since it is tied to a fixed location (EAX) */ |
2539 | arg->gtFlags |= GTF_ORDER_SIDEEFF; |
2540 | |
2541 | #if defined(JIT32_GCENCODER) |
2542 | const bool forceInsertNewBlock = isSingleBlockFilter || compStressCompile(STRESS_CATCH_ARG, 5); |
2543 | #else |
2544 | const bool forceInsertNewBlock = compStressCompile(STRESS_CATCH_ARG, 5); |
2545 | #endif // defined(JIT32_GCENCODER) |
2546 | |
2547 | /* Spill GT_CATCH_ARG to a temp if there are jumps to the beginning of the handler */ |
2548 | if (hndBlk->bbRefs > 1 || forceInsertNewBlock) |
2549 | { |
2550 | if (hndBlk->bbRefs == 1) |
2551 | { |
2552 | hndBlk->bbRefs++; |
2553 | } |
2554 | |
2555 | /* Create extra basic block for the spill */ |
2556 | BasicBlock* newBlk = fgNewBBbefore(BBJ_NONE, hndBlk, /* extendRegion */ true); |
2557 | newBlk->bbFlags |= BBF_IMPORTED | BBF_DONT_REMOVE | BBF_HAS_LABEL | BBF_JMP_TARGET; |
2558 | newBlk->setBBWeight(hndBlk->bbWeight); |
2559 | newBlk->bbCodeOffs = hndBlk->bbCodeOffs; |
2560 | |
2561 | /* Account for the new link we are about to create */ |
2562 | hndBlk->bbRefs++; |
2563 | |
2564 | /* Spill into a temp */ |
2565 | unsigned tempNum = lvaGrabTemp(false DEBUGARG("SpillCatchArg" )); |
2566 | lvaTable[tempNum].lvType = TYP_REF; |
2567 | arg = gtNewTempAssign(tempNum, arg); |
2568 | |
2569 | hndBlk->bbStkTempsIn = tempNum; |
2570 | |
2571 | /* Report the debug info. impImportBlockCode won't treat |
2572 | * the actual handler as exception block and thus won't do it for us. */ |
2573 | if (info.compStmtOffsetsImplicit & ICorDebugInfo::CALL_SITE_BOUNDARIES) |
2574 | { |
2575 | impCurStmtOffs = newBlk->bbCodeOffs | IL_OFFSETX_STKBIT; |
2576 | arg = gtNewStmt(arg, impCurStmtOffs); |
2577 | } |
2578 | |
2579 | fgInsertStmtAtEnd(newBlk, arg); |
2580 | |
2581 | arg = gtNewLclvNode(tempNum, TYP_REF); |
2582 | } |
2583 | |
2584 | impPushOnStack(arg, typeInfo(TI_REF, clsHnd)); |
2585 | |
2586 | return hndBlk; |
2587 | } |
2588 | |
2589 | /***************************************************************************** |
2590 | * |
2591 | * Given a tree, clone it. *pClone is set to the cloned tree. |
2592 | * Returns the original tree if the cloning was easy, |
2593 | * else returns the temp to which the tree had to be spilled to. |
2594 | * If the tree has side-effects, it will be spilled to a temp. |
2595 | */ |
2596 | |
2597 | GenTree* Compiler::impCloneExpr(GenTree* tree, |
2598 | GenTree** pClone, |
2599 | CORINFO_CLASS_HANDLE structHnd, |
2600 | unsigned curLevel, |
2601 | GenTree** pAfterStmt DEBUGARG(const char* reason)) |
2602 | { |
2603 | if (!(tree->gtFlags & GTF_GLOB_EFFECT)) |
2604 | { |
2605 | GenTree* clone = gtClone(tree, true); |
2606 | |
2607 | if (clone) |
2608 | { |
2609 | *pClone = clone; |
2610 | return tree; |
2611 | } |
2612 | } |
2613 | |
2614 | /* Store the operand in a temp and return the temp */ |
2615 | |
2616 | unsigned temp = lvaGrabTemp(true DEBUGARG(reason)); |
2617 | |
2618 | // impAssignTempGen() may change tree->gtType to TYP_VOID for calls which |
2619 | // return a struct type. It also may modify the struct type to a more |
2620 | // specialized type (e.g. a SIMD type). So we will get the type from |
2621 | // the lclVar AFTER calling impAssignTempGen(). |
2622 | |
2623 | impAssignTempGen(temp, tree, structHnd, curLevel, pAfterStmt, impCurStmtOffs); |
2624 | var_types type = genActualType(lvaTable[temp].TypeGet()); |
2625 | |
2626 | *pClone = gtNewLclvNode(temp, type); |
2627 | return gtNewLclvNode(temp, type); |
2628 | } |
2629 | |
2630 | /***************************************************************************** |
2631 | * Remember the IL offset (including stack-empty info) for the trees we will |
2632 | * generate now. |
2633 | */ |
2634 | |
2635 | inline void Compiler::impCurStmtOffsSet(IL_OFFSET offs) |
2636 | { |
2637 | if (compIsForInlining()) |
2638 | { |
2639 | GenTree* callStmt = impInlineInfo->iciStmt; |
2640 | assert(callStmt->gtOper == GT_STMT); |
2641 | impCurStmtOffs = callStmt->gtStmt.gtStmtILoffsx; |
2642 | } |
2643 | else |
2644 | { |
2645 | assert(offs == BAD_IL_OFFSET || (offs & IL_OFFSETX_BITS) == 0); |
2646 | IL_OFFSETX stkBit = (verCurrentState.esStackDepth > 0) ? IL_OFFSETX_STKBIT : 0; |
2647 | impCurStmtOffs = offs | stkBit; |
2648 | } |
2649 | } |
2650 | |
2651 | /***************************************************************************** |
2652 | * Returns current IL offset with stack-empty and call-instruction info incorporated |
2653 | */ |
2654 | inline IL_OFFSETX Compiler::impCurILOffset(IL_OFFSET offs, bool callInstruction) |
2655 | { |
2656 | if (compIsForInlining()) |
2657 | { |
2658 | return BAD_IL_OFFSET; |
2659 | } |
2660 | else |
2661 | { |
2662 | assert(offs == BAD_IL_OFFSET || (offs & IL_OFFSETX_BITS) == 0); |
2663 | IL_OFFSETX stkBit = (verCurrentState.esStackDepth > 0) ? IL_OFFSETX_STKBIT : 0; |
2664 | IL_OFFSETX callInstructionBit = callInstruction ? IL_OFFSETX_CALLINSTRUCTIONBIT : 0; |
2665 | return offs | stkBit | callInstructionBit; |
2666 | } |
2667 | } |
2668 | |
2669 | //------------------------------------------------------------------------ |
2670 | // impCanSpillNow: check is it possible to spill all values from eeStack to local variables. |
2671 | // |
2672 | // Arguments: |
2673 | // prevOpcode - last importer opcode |
2674 | // |
2675 | // Return Value: |
2676 | // true if it is legal, false if it could be a sequence that we do not want to divide. |
2677 | bool Compiler::impCanSpillNow(OPCODE prevOpcode) |
2678 | { |
2679 | // Don't spill after ldtoken, newarr and newobj, because it could be a part of the InitializeArray sequence. |
2680 | // Avoid breaking up to guarantee that impInitializeArrayIntrinsic can succeed. |
2681 | return (prevOpcode != CEE_LDTOKEN) && (prevOpcode != CEE_NEWARR) && (prevOpcode != CEE_NEWOBJ); |
2682 | } |
2683 | |
2684 | /***************************************************************************** |
2685 | * |
2686 | * Remember the instr offset for the statements |
2687 | * |
2688 | * When we do impAppendTree(tree), we can't set tree->gtStmtLastILoffs to |
2689 | * impCurOpcOffs, if the append was done because of a partial stack spill, |
2690 | * as some of the trees corresponding to code up to impCurOpcOffs might |
2691 | * still be sitting on the stack. |
2692 | * So we delay marking of gtStmtLastILoffs until impNoteLastILoffs(). |
2693 | * This should be called when an opcode finally/explicitly causes |
2694 | * impAppendTree(tree) to be called (as opposed to being called because of |
2695 | * a spill caused by the opcode) |
2696 | */ |
2697 | |
2698 | #ifdef DEBUG |
2699 | |
2700 | void Compiler::impNoteLastILoffs() |
2701 | { |
2702 | if (impLastILoffsStmt == nullptr) |
2703 | { |
2704 | // We should have added a statement for the current basic block |
2705 | // Is this assert correct ? |
2706 | |
2707 | assert(impTreeLast); |
2708 | assert(impTreeLast->gtOper == GT_STMT); |
2709 | |
2710 | impTreeLast->gtStmt.gtStmtLastILoffs = compIsForInlining() ? BAD_IL_OFFSET : impCurOpcOffs; |
2711 | } |
2712 | else |
2713 | { |
2714 | impLastILoffsStmt->gtStmt.gtStmtLastILoffs = compIsForInlining() ? BAD_IL_OFFSET : impCurOpcOffs; |
2715 | impLastILoffsStmt = nullptr; |
2716 | } |
2717 | } |
2718 | |
2719 | #endif // DEBUG |
2720 | |
2721 | /***************************************************************************** |
2722 | * We don't create any GenTree (excluding spills) for a branch. |
2723 | * For debugging info, we need a placeholder so that we can note |
2724 | * the IL offset in gtStmt.gtStmtOffs. So append an empty statement. |
2725 | */ |
2726 | |
2727 | void Compiler::impNoteBranchOffs() |
2728 | { |
2729 | if (opts.compDbgCode) |
2730 | { |
2731 | impAppendTree(gtNewNothingNode(), (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); |
2732 | } |
2733 | } |
2734 | |
2735 | /***************************************************************************** |
2736 | * Locate the next stmt boundary for which we need to record info. |
2737 | * We will have to spill the stack at such boundaries if it is not |
2738 | * already empty. |
2739 | * Returns the next stmt boundary (after the start of the block) |
2740 | */ |
2741 | |
2742 | unsigned Compiler::impInitBlockLineInfo() |
2743 | { |
2744 | /* Assume the block does not correspond with any IL offset. This prevents |
2745 | us from reporting extra offsets. Extra mappings can cause confusing |
2746 | stepping, especially if the extra mapping is a jump-target, and the |
2747 | debugger does not ignore extra mappings, but instead rewinds to the |
2748 | nearest known offset */ |
2749 | |
2750 | impCurStmtOffsSet(BAD_IL_OFFSET); |
2751 | |
2752 | if (compIsForInlining()) |
2753 | { |
2754 | return ~0; |
2755 | } |
2756 | |
2757 | IL_OFFSET blockOffs = compCurBB->bbCodeOffs; |
2758 | |
2759 | if ((verCurrentState.esStackDepth == 0) && (info.compStmtOffsetsImplicit & ICorDebugInfo::STACK_EMPTY_BOUNDARIES)) |
2760 | { |
2761 | impCurStmtOffsSet(blockOffs); |
2762 | } |
2763 | |
2764 | if (false && (info.compStmtOffsetsImplicit & ICorDebugInfo::CALL_SITE_BOUNDARIES)) |
2765 | { |
2766 | impCurStmtOffsSet(blockOffs); |
2767 | } |
2768 | |
2769 | /* Always report IL offset 0 or some tests get confused. |
2770 | Probably a good idea anyways */ |
2771 | |
2772 | if (blockOffs == 0) |
2773 | { |
2774 | impCurStmtOffsSet(blockOffs); |
2775 | } |
2776 | |
2777 | if (!info.compStmtOffsetsCount) |
2778 | { |
2779 | return ~0; |
2780 | } |
2781 | |
2782 | /* Find the lowest explicit stmt boundary within the block */ |
2783 | |
2784 | /* Start looking at an entry that is based on our instr offset */ |
2785 | |
2786 | unsigned index = (info.compStmtOffsetsCount * blockOffs) / info.compILCodeSize; |
2787 | |
2788 | if (index >= info.compStmtOffsetsCount) |
2789 | { |
2790 | index = info.compStmtOffsetsCount - 1; |
2791 | } |
2792 | |
2793 | /* If we've guessed too far, back up */ |
2794 | |
2795 | while (index > 0 && info.compStmtOffsets[index - 1] >= blockOffs) |
2796 | { |
2797 | index--; |
2798 | } |
2799 | |
2800 | /* If we guessed short, advance ahead */ |
2801 | |
2802 | while (info.compStmtOffsets[index] < blockOffs) |
2803 | { |
2804 | index++; |
2805 | |
2806 | if (index == info.compStmtOffsetsCount) |
2807 | { |
2808 | return info.compStmtOffsetsCount; |
2809 | } |
2810 | } |
2811 | |
2812 | assert(index < info.compStmtOffsetsCount); |
2813 | |
2814 | if (info.compStmtOffsets[index] == blockOffs) |
2815 | { |
2816 | /* There is an explicit boundary for the start of this basic block. |
2817 | So we will start with bbCodeOffs. Else we will wait until we |
2818 | get to the next explicit boundary */ |
2819 | |
2820 | impCurStmtOffsSet(blockOffs); |
2821 | |
2822 | index++; |
2823 | } |
2824 | |
2825 | return index; |
2826 | } |
2827 | |
2828 | /*****************************************************************************/ |
2829 | |
2830 | static inline bool impOpcodeIsCallOpcode(OPCODE opcode) |
2831 | { |
2832 | switch (opcode) |
2833 | { |
2834 | case CEE_CALL: |
2835 | case CEE_CALLI: |
2836 | case CEE_CALLVIRT: |
2837 | return true; |
2838 | |
2839 | default: |
2840 | return false; |
2841 | } |
2842 | } |
2843 | |
2844 | /*****************************************************************************/ |
2845 | |
2846 | static inline bool impOpcodeIsCallSiteBoundary(OPCODE opcode) |
2847 | { |
2848 | switch (opcode) |
2849 | { |
2850 | case CEE_CALL: |
2851 | case CEE_CALLI: |
2852 | case CEE_CALLVIRT: |
2853 | case CEE_JMP: |
2854 | case CEE_NEWOBJ: |
2855 | case CEE_NEWARR: |
2856 | return true; |
2857 | |
2858 | default: |
2859 | return false; |
2860 | } |
2861 | } |
2862 | |
2863 | /*****************************************************************************/ |
2864 | |
2865 | // One might think it is worth caching these values, but results indicate |
2866 | // that it isn't. |
2867 | // In addition, caching them causes SuperPMI to be unable to completely |
2868 | // encapsulate an individual method context. |
2869 | CORINFO_CLASS_HANDLE Compiler::impGetRefAnyClass() |
2870 | { |
2871 | CORINFO_CLASS_HANDLE refAnyClass = info.compCompHnd->getBuiltinClass(CLASSID_TYPED_BYREF); |
2872 | assert(refAnyClass != (CORINFO_CLASS_HANDLE) nullptr); |
2873 | return refAnyClass; |
2874 | } |
2875 | |
2876 | CORINFO_CLASS_HANDLE Compiler::impGetTypeHandleClass() |
2877 | { |
2878 | CORINFO_CLASS_HANDLE typeHandleClass = info.compCompHnd->getBuiltinClass(CLASSID_TYPE_HANDLE); |
2879 | assert(typeHandleClass != (CORINFO_CLASS_HANDLE) nullptr); |
2880 | return typeHandleClass; |
2881 | } |
2882 | |
2883 | CORINFO_CLASS_HANDLE Compiler::impGetRuntimeArgumentHandle() |
2884 | { |
2885 | CORINFO_CLASS_HANDLE argIteratorClass = info.compCompHnd->getBuiltinClass(CLASSID_ARGUMENT_HANDLE); |
2886 | assert(argIteratorClass != (CORINFO_CLASS_HANDLE) nullptr); |
2887 | return argIteratorClass; |
2888 | } |
2889 | |
2890 | CORINFO_CLASS_HANDLE Compiler::impGetStringClass() |
2891 | { |
2892 | CORINFO_CLASS_HANDLE stringClass = info.compCompHnd->getBuiltinClass(CLASSID_STRING); |
2893 | assert(stringClass != (CORINFO_CLASS_HANDLE) nullptr); |
2894 | return stringClass; |
2895 | } |
2896 | |
2897 | CORINFO_CLASS_HANDLE Compiler::impGetObjectClass() |
2898 | { |
2899 | CORINFO_CLASS_HANDLE objectClass = info.compCompHnd->getBuiltinClass(CLASSID_SYSTEM_OBJECT); |
2900 | assert(objectClass != (CORINFO_CLASS_HANDLE) nullptr); |
2901 | return objectClass; |
2902 | } |
2903 | |
2904 | /***************************************************************************** |
2905 | * "&var" can be used either as TYP_BYREF or TYP_I_IMPL, but we |
2906 | * set its type to TYP_BYREF when we create it. We know if it can be |
2907 | * changed to TYP_I_IMPL only at the point where we use it |
2908 | */ |
2909 | |
2910 | /* static */ |
2911 | void Compiler::impBashVarAddrsToI(GenTree* tree1, GenTree* tree2) |
2912 | { |
2913 | if (tree1->IsVarAddr()) |
2914 | { |
2915 | tree1->gtType = TYP_I_IMPL; |
2916 | } |
2917 | |
2918 | if (tree2 && tree2->IsVarAddr()) |
2919 | { |
2920 | tree2->gtType = TYP_I_IMPL; |
2921 | } |
2922 | } |
2923 | |
2924 | /***************************************************************************** |
2925 | * TYP_INT and TYP_I_IMPL can be used almost interchangeably, but we want |
2926 | * to make that an explicit cast in our trees, so any implicit casts that |
2927 | * exist in the IL (at least on 64-bit where TYP_I_IMPL != TYP_INT) are |
2928 | * turned into explicit casts here. |
2929 | * We also allow an implicit conversion of a ldnull into a TYP_I_IMPL(0) |
2930 | */ |
2931 | |
2932 | GenTree* Compiler::impImplicitIorI4Cast(GenTree* tree, var_types dstTyp) |
2933 | { |
2934 | var_types currType = genActualType(tree->gtType); |
2935 | var_types wantedType = genActualType(dstTyp); |
2936 | |
2937 | if (wantedType != currType) |
2938 | { |
2939 | // Automatic upcast for a GT_CNS_INT into TYP_I_IMPL |
2940 | if ((tree->OperGet() == GT_CNS_INT) && varTypeIsI(dstTyp)) |
2941 | { |
2942 | if (!varTypeIsI(tree->gtType) || ((tree->gtType == TYP_REF) && (tree->gtIntCon.gtIconVal == 0))) |
2943 | { |
2944 | tree->gtType = TYP_I_IMPL; |
2945 | } |
2946 | } |
2947 | #ifdef _TARGET_64BIT_ |
2948 | else if (varTypeIsI(wantedType) && (currType == TYP_INT)) |
2949 | { |
2950 | // Note that this allows TYP_INT to be cast to a TYP_I_IMPL when wantedType is a TYP_BYREF or TYP_REF |
2951 | tree = gtNewCastNode(TYP_I_IMPL, tree, false, TYP_I_IMPL); |
2952 | } |
2953 | else if ((wantedType == TYP_INT) && varTypeIsI(currType)) |
2954 | { |
2955 | // Note that this allows TYP_BYREF or TYP_REF to be cast to a TYP_INT |
2956 | tree = gtNewCastNode(TYP_INT, tree, false, TYP_INT); |
2957 | } |
2958 | #endif // _TARGET_64BIT_ |
2959 | } |
2960 | |
2961 | return tree; |
2962 | } |
2963 | |
2964 | /***************************************************************************** |
2965 | * TYP_FLOAT and TYP_DOUBLE can be used almost interchangeably in some cases, |
2966 | * but we want to make that an explicit cast in our trees, so any implicit casts |
2967 | * that exist in the IL are turned into explicit casts here. |
2968 | */ |
2969 | |
2970 | GenTree* Compiler::impImplicitR4orR8Cast(GenTree* tree, var_types dstTyp) |
2971 | { |
2972 | if (varTypeIsFloating(tree) && varTypeIsFloating(dstTyp) && (dstTyp != tree->gtType)) |
2973 | { |
2974 | tree = gtNewCastNode(dstTyp, tree, false, dstTyp); |
2975 | } |
2976 | |
2977 | return tree; |
2978 | } |
2979 | |
2980 | //------------------------------------------------------------------------ |
2981 | // impInitializeArrayIntrinsic: Attempts to replace a call to InitializeArray |
2982 | // with a GT_COPYBLK node. |
2983 | // |
2984 | // Arguments: |
2985 | // sig - The InitializeArray signature. |
2986 | // |
2987 | // Return Value: |
2988 | // A pointer to the newly created GT_COPYBLK node if the replacement succeeds or |
2989 | // nullptr otherwise. |
2990 | // |
2991 | // Notes: |
2992 | // The function recognizes the following IL pattern: |
2993 | // ldc <length> or a list of ldc <lower bound>/<length> |
2994 | // newarr or newobj |
2995 | // dup |
2996 | // ldtoken <field handle> |
2997 | // call InitializeArray |
2998 | // The lower bounds need not be constant except when the array rank is 1. |
2999 | // The function recognizes all kinds of arrays thus enabling a small runtime |
3000 | // such as CoreRT to skip providing an implementation for InitializeArray. |
3001 | |
3002 | GenTree* Compiler::impInitializeArrayIntrinsic(CORINFO_SIG_INFO* sig) |
3003 | { |
3004 | assert(sig->numArgs == 2); |
3005 | |
3006 | GenTree* fieldTokenNode = impStackTop(0).val; |
3007 | GenTree* arrayLocalNode = impStackTop(1).val; |
3008 | |
3009 | // |
3010 | // Verify that the field token is known and valid. Note that It's also |
3011 | // possible for the token to come from reflection, in which case we cannot do |
3012 | // the optimization and must therefore revert to calling the helper. You can |
3013 | // see an example of this in bvt\DynIL\initarray2.exe (in Main). |
3014 | // |
3015 | |
3016 | // Check to see if the ldtoken helper call is what we see here. |
3017 | if (fieldTokenNode->gtOper != GT_CALL || (fieldTokenNode->gtCall.gtCallType != CT_HELPER) || |
3018 | (fieldTokenNode->gtCall.gtCallMethHnd != eeFindHelper(CORINFO_HELP_FIELDDESC_TO_STUBRUNTIMEFIELD))) |
3019 | { |
3020 | return nullptr; |
3021 | } |
3022 | |
3023 | // Strip helper call away |
3024 | fieldTokenNode = fieldTokenNode->gtCall.gtCallArgs->Current(); |
3025 | |
3026 | if (fieldTokenNode->gtOper == GT_IND) |
3027 | { |
3028 | fieldTokenNode = fieldTokenNode->gtOp.gtOp1; |
3029 | } |
3030 | |
3031 | // Check for constant |
3032 | if (fieldTokenNode->gtOper != GT_CNS_INT) |
3033 | { |
3034 | return nullptr; |
3035 | } |
3036 | |
3037 | CORINFO_FIELD_HANDLE fieldToken = (CORINFO_FIELD_HANDLE)fieldTokenNode->gtIntCon.gtCompileTimeHandle; |
3038 | if (!fieldTokenNode->IsIconHandle(GTF_ICON_FIELD_HDL) || (fieldToken == nullptr)) |
3039 | { |
3040 | return nullptr; |
3041 | } |
3042 | |
3043 | // |
3044 | // We need to get the number of elements in the array and the size of each element. |
3045 | // We verify that the newarr statement is exactly what we expect it to be. |
3046 | // If it's not then we just return NULL and we don't optimize this call |
3047 | // |
3048 | |
3049 | // |
3050 | // It is possible the we don't have any statements in the block yet |
3051 | // |
3052 | if (impTreeLast->gtOper != GT_STMT) |
3053 | { |
3054 | assert(impTreeLast->gtOper == GT_BEG_STMTS); |
3055 | return nullptr; |
3056 | } |
3057 | |
3058 | // |
3059 | // We start by looking at the last statement, making sure it's an assignment, and |
3060 | // that the target of the assignment is the array passed to InitializeArray. |
3061 | // |
3062 | GenTree* arrayAssignment = impTreeLast->gtStmt.gtStmtExpr; |
3063 | if ((arrayAssignment->gtOper != GT_ASG) || (arrayAssignment->gtOp.gtOp1->gtOper != GT_LCL_VAR) || |
3064 | (arrayLocalNode->gtOper != GT_LCL_VAR) || |
3065 | (arrayAssignment->gtOp.gtOp1->gtLclVarCommon.gtLclNum != arrayLocalNode->gtLclVarCommon.gtLclNum)) |
3066 | { |
3067 | return nullptr; |
3068 | } |
3069 | |
3070 | // |
3071 | // Make sure that the object being assigned is a helper call. |
3072 | // |
3073 | |
3074 | GenTree* newArrayCall = arrayAssignment->gtOp.gtOp2; |
3075 | if ((newArrayCall->gtOper != GT_CALL) || (newArrayCall->gtCall.gtCallType != CT_HELPER)) |
3076 | { |
3077 | return nullptr; |
3078 | } |
3079 | |
3080 | // |
3081 | // Verify that it is one of the new array helpers. |
3082 | // |
3083 | |
3084 | bool isMDArray = false; |
3085 | |
3086 | if (newArrayCall->gtCall.gtCallMethHnd != eeFindHelper(CORINFO_HELP_NEWARR_1_DIRECT) && |
3087 | newArrayCall->gtCall.gtCallMethHnd != eeFindHelper(CORINFO_HELP_NEWARR_1_OBJ) && |
3088 | newArrayCall->gtCall.gtCallMethHnd != eeFindHelper(CORINFO_HELP_NEWARR_1_VC) && |
3089 | newArrayCall->gtCall.gtCallMethHnd != eeFindHelper(CORINFO_HELP_NEWARR_1_ALIGN8) |
3090 | #ifdef FEATURE_READYTORUN_COMPILER |
3091 | && newArrayCall->gtCall.gtCallMethHnd != eeFindHelper(CORINFO_HELP_NEWARR_1_R2R_DIRECT) && |
3092 | newArrayCall->gtCall.gtCallMethHnd != eeFindHelper(CORINFO_HELP_READYTORUN_NEWARR_1) |
3093 | #endif |
3094 | ) |
3095 | { |
3096 | if (newArrayCall->gtCall.gtCallMethHnd != eeFindHelper(CORINFO_HELP_NEW_MDARR_NONVARARG)) |
3097 | { |
3098 | return nullptr; |
3099 | } |
3100 | |
3101 | isMDArray = true; |
3102 | } |
3103 | |
3104 | CORINFO_CLASS_HANDLE arrayClsHnd = (CORINFO_CLASS_HANDLE)newArrayCall->gtCall.compileTimeHelperArgumentHandle; |
3105 | |
3106 | // |
3107 | // Make sure we found a compile time handle to the array |
3108 | // |
3109 | |
3110 | if (!arrayClsHnd) |
3111 | { |
3112 | return nullptr; |
3113 | } |
3114 | |
3115 | unsigned rank = 0; |
3116 | S_UINT32 numElements; |
3117 | |
3118 | if (isMDArray) |
3119 | { |
3120 | rank = info.compCompHnd->getArrayRank(arrayClsHnd); |
3121 | |
3122 | if (rank == 0) |
3123 | { |
3124 | return nullptr; |
3125 | } |
3126 | |
3127 | GenTreeArgList* tokenArg = newArrayCall->gtCall.gtCallArgs; |
3128 | assert(tokenArg != nullptr); |
3129 | GenTreeArgList* numArgsArg = tokenArg->Rest(); |
3130 | assert(numArgsArg != nullptr); |
3131 | GenTreeArgList* argsArg = numArgsArg->Rest(); |
3132 | assert(argsArg != nullptr); |
3133 | |
3134 | // |
3135 | // The number of arguments should be a constant between 1 and 64. The rank can't be 0 |
3136 | // so at least one length must be present and the rank can't exceed 32 so there can |
3137 | // be at most 64 arguments - 32 lengths and 32 lower bounds. |
3138 | // |
3139 | |
3140 | if ((!numArgsArg->Current()->IsCnsIntOrI()) || (numArgsArg->Current()->AsIntCon()->IconValue() < 1) || |
3141 | (numArgsArg->Current()->AsIntCon()->IconValue() > 64)) |
3142 | { |
3143 | return nullptr; |
3144 | } |
3145 | |
3146 | unsigned numArgs = static_cast<unsigned>(numArgsArg->Current()->AsIntCon()->IconValue()); |
3147 | bool lowerBoundsSpecified; |
3148 | |
3149 | if (numArgs == rank * 2) |
3150 | { |
3151 | lowerBoundsSpecified = true; |
3152 | } |
3153 | else if (numArgs == rank) |
3154 | { |
3155 | lowerBoundsSpecified = false; |
3156 | |
3157 | // |
3158 | // If the rank is 1 and a lower bound isn't specified then the runtime creates |
3159 | // a SDArray. Note that even if a lower bound is specified it can be 0 and then |
3160 | // we get a SDArray as well, see the for loop below. |
3161 | // |
3162 | |
3163 | if (rank == 1) |
3164 | { |
3165 | isMDArray = false; |
3166 | } |
3167 | } |
3168 | else |
3169 | { |
3170 | return nullptr; |
3171 | } |
3172 | |
3173 | // |
3174 | // The rank is known to be at least 1 so we can start with numElements being 1 |
3175 | // to avoid the need to special case the first dimension. |
3176 | // |
3177 | |
3178 | numElements = S_UINT32(1); |
3179 | |
3180 | struct Match |
3181 | { |
3182 | static bool IsArgsFieldInit(GenTree* tree, unsigned index, unsigned lvaNewObjArrayArgs) |
3183 | { |
3184 | return (tree->OperGet() == GT_ASG) && IsArgsFieldIndir(tree->gtGetOp1(), index, lvaNewObjArrayArgs) && |
3185 | IsArgsAddr(tree->gtGetOp1()->gtGetOp1()->gtGetOp1(), lvaNewObjArrayArgs); |
3186 | } |
3187 | |
3188 | static bool IsArgsFieldIndir(GenTree* tree, unsigned index, unsigned lvaNewObjArrayArgs) |
3189 | { |
3190 | return (tree->OperGet() == GT_IND) && (tree->gtGetOp1()->OperGet() == GT_ADD) && |
3191 | (tree->gtGetOp1()->gtGetOp2()->IsIntegralConst(sizeof(INT32) * index)) && |
3192 | IsArgsAddr(tree->gtGetOp1()->gtGetOp1(), lvaNewObjArrayArgs); |
3193 | } |
3194 | |
3195 | static bool IsArgsAddr(GenTree* tree, unsigned lvaNewObjArrayArgs) |
3196 | { |
3197 | return (tree->OperGet() == GT_ADDR) && (tree->gtGetOp1()->OperGet() == GT_LCL_VAR) && |
3198 | (tree->gtGetOp1()->AsLclVar()->GetLclNum() == lvaNewObjArrayArgs); |
3199 | } |
3200 | |
3201 | static bool IsComma(GenTree* tree) |
3202 | { |
3203 | return (tree != nullptr) && (tree->OperGet() == GT_COMMA); |
3204 | } |
3205 | }; |
3206 | |
3207 | unsigned argIndex = 0; |
3208 | GenTree* comma; |
3209 | |
3210 | for (comma = argsArg->Current(); Match::IsComma(comma); comma = comma->gtGetOp2()) |
3211 | { |
3212 | if (lowerBoundsSpecified) |
3213 | { |
3214 | // |
3215 | // In general lower bounds can be ignored because they're not needed to |
3216 | // calculate the total number of elements. But for single dimensional arrays |
3217 | // we need to know if the lower bound is 0 because in this case the runtime |
3218 | // creates a SDArray and this affects the way the array data offset is calculated. |
3219 | // |
3220 | |
3221 | if (rank == 1) |
3222 | { |
3223 | GenTree* lowerBoundAssign = comma->gtGetOp1(); |
3224 | assert(Match::IsArgsFieldInit(lowerBoundAssign, argIndex, lvaNewObjArrayArgs)); |
3225 | GenTree* lowerBoundNode = lowerBoundAssign->gtGetOp2(); |
3226 | |
3227 | if (lowerBoundNode->IsIntegralConst(0)) |
3228 | { |
3229 | isMDArray = false; |
3230 | } |
3231 | } |
3232 | |
3233 | comma = comma->gtGetOp2(); |
3234 | argIndex++; |
3235 | } |
3236 | |
3237 | GenTree* lengthNodeAssign = comma->gtGetOp1(); |
3238 | assert(Match::IsArgsFieldInit(lengthNodeAssign, argIndex, lvaNewObjArrayArgs)); |
3239 | GenTree* lengthNode = lengthNodeAssign->gtGetOp2(); |
3240 | |
3241 | if (!lengthNode->IsCnsIntOrI()) |
3242 | { |
3243 | return nullptr; |
3244 | } |
3245 | |
3246 | numElements *= S_SIZE_T(lengthNode->AsIntCon()->IconValue()); |
3247 | argIndex++; |
3248 | } |
3249 | |
3250 | assert((comma != nullptr) && Match::IsArgsAddr(comma, lvaNewObjArrayArgs)); |
3251 | |
3252 | if (argIndex != numArgs) |
3253 | { |
3254 | return nullptr; |
3255 | } |
3256 | } |
3257 | else |
3258 | { |
3259 | // |
3260 | // Make sure there are exactly two arguments: the array class and |
3261 | // the number of elements. |
3262 | // |
3263 | |
3264 | GenTree* arrayLengthNode; |
3265 | |
3266 | GenTreeArgList* args = newArrayCall->gtCall.gtCallArgs; |
3267 | #ifdef FEATURE_READYTORUN_COMPILER |
3268 | if (newArrayCall->gtCall.gtCallMethHnd == eeFindHelper(CORINFO_HELP_READYTORUN_NEWARR_1)) |
3269 | { |
3270 | // Array length is 1st argument for readytorun helper |
3271 | arrayLengthNode = args->Current(); |
3272 | } |
3273 | else |
3274 | #endif |
3275 | { |
3276 | // Array length is 2nd argument for regular helper |
3277 | arrayLengthNode = args->Rest()->Current(); |
3278 | } |
3279 | |
3280 | // |
3281 | // Make sure that the number of elements look valid. |
3282 | // |
3283 | if (arrayLengthNode->gtOper != GT_CNS_INT) |
3284 | { |
3285 | return nullptr; |
3286 | } |
3287 | |
3288 | numElements = S_SIZE_T(arrayLengthNode->gtIntCon.gtIconVal); |
3289 | |
3290 | if (!info.compCompHnd->isSDArray(arrayClsHnd)) |
3291 | { |
3292 | return nullptr; |
3293 | } |
3294 | } |
3295 | |
3296 | CORINFO_CLASS_HANDLE elemClsHnd; |
3297 | var_types elementType = JITtype2varType(info.compCompHnd->getChildType(arrayClsHnd, &elemClsHnd)); |
3298 | |
3299 | // |
3300 | // Note that genTypeSize will return zero for non primitive types, which is exactly |
3301 | // what we want (size will then be 0, and we will catch this in the conditional below). |
3302 | // Note that we don't expect this to fail for valid binaries, so we assert in the |
3303 | // non-verification case (the verification case should not assert but rather correctly |
3304 | // handle bad binaries). This assert is not guarding any specific invariant, but rather |
3305 | // saying that we don't expect this to happen, and if it is hit, we need to investigate |
3306 | // why. |
3307 | // |
3308 | |
3309 | S_UINT32 elemSize(genTypeSize(elementType)); |
3310 | S_UINT32 size = elemSize * S_UINT32(numElements); |
3311 | |
3312 | if (size.IsOverflow()) |
3313 | { |
3314 | return nullptr; |
3315 | } |
3316 | |
3317 | if ((size.Value() == 0) || (varTypeIsGC(elementType))) |
3318 | { |
3319 | assert(verNeedsVerification()); |
3320 | return nullptr; |
3321 | } |
3322 | |
3323 | void* initData = info.compCompHnd->getArrayInitializationData(fieldToken, size.Value()); |
3324 | if (!initData) |
3325 | { |
3326 | return nullptr; |
3327 | } |
3328 | |
3329 | // |
3330 | // At this point we are ready to commit to implementing the InitializeArray |
3331 | // intrinsic using a struct assignment. Pop the arguments from the stack and |
3332 | // return the struct assignment node. |
3333 | // |
3334 | |
3335 | impPopStack(); |
3336 | impPopStack(); |
3337 | |
3338 | const unsigned blkSize = size.Value(); |
3339 | unsigned dataOffset; |
3340 | |
3341 | if (isMDArray) |
3342 | { |
3343 | dataOffset = eeGetMDArrayDataOffset(elementType, rank); |
3344 | } |
3345 | else |
3346 | { |
3347 | dataOffset = eeGetArrayDataOffset(elementType); |
3348 | } |
3349 | |
3350 | GenTree* dst = gtNewOperNode(GT_ADD, TYP_BYREF, arrayLocalNode, gtNewIconNode(dataOffset, TYP_I_IMPL)); |
3351 | GenTree* blk = gtNewBlockVal(dst, blkSize); |
3352 | GenTree* src = gtNewIndOfIconHandleNode(TYP_STRUCT, (size_t)initData, GTF_ICON_STATIC_HDL, false); |
3353 | |
3354 | return gtNewBlkOpNode(blk, // dst |
3355 | src, // src |
3356 | blkSize, // size |
3357 | false, // volatil |
3358 | true); // copyBlock |
3359 | } |
3360 | |
3361 | //------------------------------------------------------------------------ |
3362 | // impIntrinsic: possibly expand intrinsic call into alternate IR sequence |
3363 | // |
3364 | // Arguments: |
3365 | // newobjThis - for constructor calls, the tree for the newly allocated object |
3366 | // clsHnd - handle for the intrinsic method's class |
3367 | // method - handle for the intrinsic method |
3368 | // sig - signature of the intrinsic method |
3369 | // methodFlags - CORINFO_FLG_XXX flags of the intrinsic method |
3370 | // memberRef - the token for the intrinsic method |
3371 | // readonlyCall - true if call has a readonly prefix |
3372 | // tailCall - true if call is in tail position |
3373 | // pConstrainedResolvedToken -- resolved token for constrained call, or nullptr |
3374 | // if call is not constrained |
3375 | // constraintCallThisTransform -- this transform to apply for a constrained call |
3376 | // pIntrinsicID [OUT] -- intrinsic ID (see enumeration in corinfo.h) |
3377 | // for "traditional" jit intrinsics |
3378 | // isSpecialIntrinsic [OUT] -- set true if intrinsic expansion is a call |
3379 | // that is amenable to special downstream optimization opportunities |
3380 | // |
3381 | // Returns: |
3382 | // IR tree to use in place of the call, or nullptr if the jit should treat |
3383 | // the intrinsic call like a normal call. |
3384 | // |
3385 | // pIntrinsicID set to non-illegal value if the call is recognized as a |
3386 | // traditional jit intrinsic, even if the intrinsic is not expaned. |
3387 | // |
3388 | // isSpecial set true if the expansion is subject to special |
3389 | // optimizations later in the jit processing |
3390 | // |
3391 | // Notes: |
3392 | // On success the IR tree may be a call to a different method or an inline |
3393 | // sequence. If it is a call, then the intrinsic processing here is responsible |
3394 | // for handling all the special cases, as upon return to impImportCall |
3395 | // expanded intrinsics bypass most of the normal call processing. |
3396 | // |
3397 | // Intrinsics are generally not recognized in minopts and debug codegen. |
3398 | // |
3399 | // However, certain traditional intrinsics are identifed as "must expand" |
3400 | // if there is no fallback implmentation to invoke; these must be handled |
3401 | // in all codegen modes. |
3402 | // |
3403 | // New style intrinsics (where the fallback implementation is in IL) are |
3404 | // identified as "must expand" if they are invoked from within their |
3405 | // own method bodies. |
3406 | // |
3407 | |
3408 | GenTree* Compiler::impIntrinsic(GenTree* newobjThis, |
3409 | CORINFO_CLASS_HANDLE clsHnd, |
3410 | CORINFO_METHOD_HANDLE method, |
3411 | CORINFO_SIG_INFO* sig, |
3412 | unsigned methodFlags, |
3413 | int memberRef, |
3414 | bool readonlyCall, |
3415 | bool tailCall, |
3416 | CORINFO_RESOLVED_TOKEN* pConstrainedResolvedToken, |
3417 | CORINFO_THIS_TRANSFORM constraintCallThisTransform, |
3418 | CorInfoIntrinsics* pIntrinsicID, |
3419 | bool* isSpecialIntrinsic) |
3420 | { |
3421 | assert((methodFlags & (CORINFO_FLG_INTRINSIC | CORINFO_FLG_JIT_INTRINSIC)) != 0); |
3422 | |
3423 | bool mustExpand = false; |
3424 | bool isSpecial = false; |
3425 | CorInfoIntrinsics intrinsicID = CORINFO_INTRINSIC_Illegal; |
3426 | NamedIntrinsic ni = NI_Illegal; |
3427 | |
3428 | if ((methodFlags & CORINFO_FLG_INTRINSIC) != 0) |
3429 | { |
3430 | intrinsicID = info.compCompHnd->getIntrinsicID(method, &mustExpand); |
3431 | } |
3432 | |
3433 | if ((methodFlags & CORINFO_FLG_JIT_INTRINSIC) != 0) |
3434 | { |
3435 | // The recursive calls to Jit intrinsics are must-expand by convention. |
3436 | mustExpand = mustExpand || gtIsRecursiveCall(method); |
3437 | |
3438 | if (intrinsicID == CORINFO_INTRINSIC_Illegal) |
3439 | { |
3440 | ni = lookupNamedIntrinsic(method); |
3441 | |
3442 | #ifdef FEATURE_HW_INTRINSICS |
3443 | switch (ni) |
3444 | { |
3445 | #if defined(_TARGET_ARM64_) |
3446 | case NI_Base_Vector64_AsByte: |
3447 | case NI_Base_Vector64_AsInt16: |
3448 | case NI_Base_Vector64_AsInt32: |
3449 | case NI_Base_Vector64_AsSByte: |
3450 | case NI_Base_Vector64_AsSingle: |
3451 | case NI_Base_Vector64_AsUInt16: |
3452 | case NI_Base_Vector64_AsUInt32: |
3453 | #endif // _TARGET_ARM64_ |
3454 | case NI_Base_Vector128_As: |
3455 | case NI_Base_Vector128_AsByte: |
3456 | case NI_Base_Vector128_AsDouble: |
3457 | case NI_Base_Vector128_AsInt16: |
3458 | case NI_Base_Vector128_AsInt32: |
3459 | case NI_Base_Vector128_AsInt64: |
3460 | case NI_Base_Vector128_AsSByte: |
3461 | case NI_Base_Vector128_AsSingle: |
3462 | case NI_Base_Vector128_AsUInt16: |
3463 | case NI_Base_Vector128_AsUInt32: |
3464 | case NI_Base_Vector128_AsUInt64: |
3465 | #if defined(_TARGET_XARCH_) |
3466 | case NI_Base_Vector128_CreateScalarUnsafe: |
3467 | case NI_Base_Vector128_ToScalar: |
3468 | case NI_Base_Vector128_ToVector256: |
3469 | case NI_Base_Vector128_ToVector256Unsafe: |
3470 | case NI_Base_Vector128_Zero: |
3471 | case NI_Base_Vector256_As: |
3472 | case NI_Base_Vector256_AsByte: |
3473 | case NI_Base_Vector256_AsDouble: |
3474 | case NI_Base_Vector256_AsInt16: |
3475 | case NI_Base_Vector256_AsInt32: |
3476 | case NI_Base_Vector256_AsInt64: |
3477 | case NI_Base_Vector256_AsSByte: |
3478 | case NI_Base_Vector256_AsSingle: |
3479 | case NI_Base_Vector256_AsUInt16: |
3480 | case NI_Base_Vector256_AsUInt32: |
3481 | case NI_Base_Vector256_AsUInt64: |
3482 | case NI_Base_Vector256_CreateScalarUnsafe: |
3483 | case NI_Base_Vector256_GetLower: |
3484 | case NI_Base_Vector256_ToScalar: |
3485 | case NI_Base_Vector256_Zero: |
3486 | #endif // _TARGET_XARCH_ |
3487 | { |
3488 | return impBaseIntrinsic(ni, clsHnd, method, sig); |
3489 | } |
3490 | |
3491 | default: |
3492 | { |
3493 | break; |
3494 | } |
3495 | } |
3496 | |
3497 | if ((ni > NI_HW_INTRINSIC_START) && (ni < NI_HW_INTRINSIC_END)) |
3498 | { |
3499 | GenTree* hwintrinsic = impHWIntrinsic(ni, method, sig, mustExpand); |
3500 | |
3501 | if (mustExpand && (hwintrinsic == nullptr)) |
3502 | { |
3503 | return impUnsupportedHWIntrinsic(CORINFO_HELP_THROW_NOT_IMPLEMENTED, method, sig, mustExpand); |
3504 | } |
3505 | |
3506 | return hwintrinsic; |
3507 | } |
3508 | #endif // FEATURE_HW_INTRINSICS |
3509 | } |
3510 | } |
3511 | |
3512 | *pIntrinsicID = intrinsicID; |
3513 | |
3514 | #ifndef _TARGET_ARM_ |
3515 | genTreeOps interlockedOperator; |
3516 | #endif |
3517 | |
3518 | if (intrinsicID == CORINFO_INTRINSIC_StubHelpers_GetStubContext) |
3519 | { |
3520 | // must be done regardless of DbgCode and MinOpts |
3521 | return gtNewLclvNode(lvaStubArgumentVar, TYP_I_IMPL); |
3522 | } |
3523 | #ifdef _TARGET_64BIT_ |
3524 | if (intrinsicID == CORINFO_INTRINSIC_StubHelpers_GetStubContextAddr) |
3525 | { |
3526 | // must be done regardless of DbgCode and MinOpts |
3527 | return gtNewOperNode(GT_ADDR, TYP_I_IMPL, gtNewLclvNode(lvaStubArgumentVar, TYP_I_IMPL)); |
3528 | } |
3529 | #else |
3530 | assert(intrinsicID != CORINFO_INTRINSIC_StubHelpers_GetStubContextAddr); |
3531 | #endif |
3532 | |
3533 | GenTree* retNode = nullptr; |
3534 | |
3535 | // Under debug and minopts, only expand what is required. |
3536 | if (!mustExpand && opts.OptimizationDisabled()) |
3537 | { |
3538 | *pIntrinsicID = CORINFO_INTRINSIC_Illegal; |
3539 | return retNode; |
3540 | } |
3541 | |
3542 | var_types callType = JITtype2varType(sig->retType); |
3543 | |
3544 | /* First do the intrinsics which are always smaller than a call */ |
3545 | |
3546 | switch (intrinsicID) |
3547 | { |
3548 | GenTree* op1; |
3549 | GenTree* op2; |
3550 | |
3551 | case CORINFO_INTRINSIC_Sin: |
3552 | case CORINFO_INTRINSIC_Cbrt: |
3553 | case CORINFO_INTRINSIC_Sqrt: |
3554 | case CORINFO_INTRINSIC_Abs: |
3555 | case CORINFO_INTRINSIC_Cos: |
3556 | case CORINFO_INTRINSIC_Round: |
3557 | case CORINFO_INTRINSIC_Cosh: |
3558 | case CORINFO_INTRINSIC_Sinh: |
3559 | case CORINFO_INTRINSIC_Tan: |
3560 | case CORINFO_INTRINSIC_Tanh: |
3561 | case CORINFO_INTRINSIC_Asin: |
3562 | case CORINFO_INTRINSIC_Asinh: |
3563 | case CORINFO_INTRINSIC_Acos: |
3564 | case CORINFO_INTRINSIC_Acosh: |
3565 | case CORINFO_INTRINSIC_Atan: |
3566 | case CORINFO_INTRINSIC_Atan2: |
3567 | case CORINFO_INTRINSIC_Atanh: |
3568 | case CORINFO_INTRINSIC_Log10: |
3569 | case CORINFO_INTRINSIC_Pow: |
3570 | case CORINFO_INTRINSIC_Exp: |
3571 | case CORINFO_INTRINSIC_Ceiling: |
3572 | case CORINFO_INTRINSIC_Floor: |
3573 | retNode = impMathIntrinsic(method, sig, callType, intrinsicID, tailCall); |
3574 | break; |
3575 | |
3576 | #if defined(_TARGET_XARCH_) || defined(_TARGET_ARM64_) |
3577 | // TODO-ARM-CQ: reenable treating Interlocked operation as intrinsic |
3578 | |
3579 | // Note that CORINFO_INTRINSIC_InterlockedAdd32/64 are not actually used. |
3580 | // Anyway, we can import them as XADD and leave it to lowering/codegen to perform |
3581 | // whatever optimizations may arise from the fact that result value is not used. |
3582 | case CORINFO_INTRINSIC_InterlockedAdd32: |
3583 | case CORINFO_INTRINSIC_InterlockedXAdd32: |
3584 | interlockedOperator = GT_XADD; |
3585 | goto InterlockedBinOpCommon; |
3586 | case CORINFO_INTRINSIC_InterlockedXchg32: |
3587 | interlockedOperator = GT_XCHG; |
3588 | goto InterlockedBinOpCommon; |
3589 | |
3590 | #ifdef _TARGET_64BIT_ |
3591 | case CORINFO_INTRINSIC_InterlockedAdd64: |
3592 | case CORINFO_INTRINSIC_InterlockedXAdd64: |
3593 | interlockedOperator = GT_XADD; |
3594 | goto InterlockedBinOpCommon; |
3595 | case CORINFO_INTRINSIC_InterlockedXchg64: |
3596 | interlockedOperator = GT_XCHG; |
3597 | goto InterlockedBinOpCommon; |
3598 | #endif // _TARGET_AMD64_ |
3599 | |
3600 | InterlockedBinOpCommon: |
3601 | assert(callType != TYP_STRUCT); |
3602 | assert(sig->numArgs == 2); |
3603 | |
3604 | op2 = impPopStack().val; |
3605 | op1 = impPopStack().val; |
3606 | |
3607 | // This creates: |
3608 | // val |
3609 | // XAdd |
3610 | // addr |
3611 | // field (for example) |
3612 | // |
3613 | // In the case where the first argument is the address of a local, we might |
3614 | // want to make this *not* make the var address-taken -- but atomic instructions |
3615 | // on a local are probably pretty useless anyway, so we probably don't care. |
3616 | |
3617 | op1 = gtNewOperNode(interlockedOperator, genActualType(callType), op1, op2); |
3618 | op1->gtFlags |= GTF_GLOB_REF | GTF_ASG; |
3619 | retNode = op1; |
3620 | break; |
3621 | #endif // defined(_TARGET_XARCH_) || defined(_TARGET_ARM64_) |
3622 | |
3623 | case CORINFO_INTRINSIC_MemoryBarrier: |
3624 | |
3625 | assert(sig->numArgs == 0); |
3626 | |
3627 | op1 = new (this, GT_MEMORYBARRIER) GenTree(GT_MEMORYBARRIER, TYP_VOID); |
3628 | op1->gtFlags |= GTF_GLOB_REF | GTF_ASG; |
3629 | retNode = op1; |
3630 | break; |
3631 | |
3632 | #if defined(_TARGET_XARCH_) || defined(_TARGET_ARM64_) |
3633 | // TODO-ARM-CQ: reenable treating InterlockedCmpXchg32 operation as intrinsic |
3634 | case CORINFO_INTRINSIC_InterlockedCmpXchg32: |
3635 | #ifdef _TARGET_64BIT_ |
3636 | case CORINFO_INTRINSIC_InterlockedCmpXchg64: |
3637 | #endif |
3638 | { |
3639 | assert(callType != TYP_STRUCT); |
3640 | assert(sig->numArgs == 3); |
3641 | GenTree* op3; |
3642 | |
3643 | op3 = impPopStack().val; // comparand |
3644 | op2 = impPopStack().val; // value |
3645 | op1 = impPopStack().val; // location |
3646 | |
3647 | GenTree* node = new (this, GT_CMPXCHG) GenTreeCmpXchg(genActualType(callType), op1, op2, op3); |
3648 | |
3649 | node->gtCmpXchg.gtOpLocation->gtFlags |= GTF_DONT_CSE; |
3650 | retNode = node; |
3651 | break; |
3652 | } |
3653 | #endif // defined(_TARGET_XARCH_) || defined(_TARGET_ARM64_) |
3654 | |
3655 | case CORINFO_INTRINSIC_StringLength: |
3656 | op1 = impPopStack().val; |
3657 | if (opts.OptimizationEnabled()) |
3658 | { |
3659 | GenTreeArrLen* arrLen = gtNewArrLen(TYP_INT, op1, OFFSETOF__CORINFO_String__stringLen); |
3660 | op1 = arrLen; |
3661 | } |
3662 | else |
3663 | { |
3664 | /* Create the expression "*(str_addr + stringLengthOffset)" */ |
3665 | op1 = gtNewOperNode(GT_ADD, TYP_BYREF, op1, |
3666 | gtNewIconNode(OFFSETOF__CORINFO_String__stringLen, TYP_I_IMPL)); |
3667 | op1 = gtNewOperNode(GT_IND, TYP_INT, op1); |
3668 | } |
3669 | |
3670 | // Getting the length of a null string should throw |
3671 | op1->gtFlags |= GTF_EXCEPT; |
3672 | |
3673 | retNode = op1; |
3674 | break; |
3675 | |
3676 | case CORINFO_INTRINSIC_StringGetChar: |
3677 | op2 = impPopStack().val; |
3678 | op1 = impPopStack().val; |
3679 | op1 = gtNewIndexRef(TYP_USHORT, op1, op2); |
3680 | op1->gtFlags |= GTF_INX_STRING_LAYOUT; |
3681 | retNode = op1; |
3682 | break; |
3683 | |
3684 | case CORINFO_INTRINSIC_InitializeArray: |
3685 | retNode = impInitializeArrayIntrinsic(sig); |
3686 | break; |
3687 | |
3688 | case CORINFO_INTRINSIC_Array_Address: |
3689 | case CORINFO_INTRINSIC_Array_Get: |
3690 | case CORINFO_INTRINSIC_Array_Set: |
3691 | retNode = impArrayAccessIntrinsic(clsHnd, sig, memberRef, readonlyCall, intrinsicID); |
3692 | break; |
3693 | |
3694 | case CORINFO_INTRINSIC_GetTypeFromHandle: |
3695 | op1 = impStackTop(0).val; |
3696 | CorInfoHelpFunc typeHandleHelper; |
3697 | if (op1->gtOper == GT_CALL && (op1->gtCall.gtCallType == CT_HELPER) && |
3698 | gtIsTypeHandleToRuntimeTypeHandleHelper(op1->AsCall(), &typeHandleHelper)) |
3699 | { |
3700 | op1 = impPopStack().val; |
3701 | // Replace helper with a more specialized helper that returns RuntimeType |
3702 | if (typeHandleHelper == CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPEHANDLE) |
3703 | { |
3704 | typeHandleHelper = CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE; |
3705 | } |
3706 | else |
3707 | { |
3708 | assert(typeHandleHelper == CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPEHANDLE_MAYBENULL); |
3709 | typeHandleHelper = CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE_MAYBENULL; |
3710 | } |
3711 | assert(op1->gtCall.gtCallArgs->gtOp.gtOp2 == nullptr); |
3712 | op1 = gtNewHelperCallNode(typeHandleHelper, TYP_REF, op1->gtCall.gtCallArgs); |
3713 | op1->gtType = TYP_REF; |
3714 | retNode = op1; |
3715 | } |
3716 | // Call the regular function. |
3717 | break; |
3718 | |
3719 | case CORINFO_INTRINSIC_RTH_GetValueInternal: |
3720 | op1 = impStackTop(0).val; |
3721 | if (op1->gtOper == GT_CALL && (op1->gtCall.gtCallType == CT_HELPER) && |
3722 | gtIsTypeHandleToRuntimeTypeHandleHelper(op1->AsCall())) |
3723 | { |
3724 | // Old tree |
3725 | // Helper-RuntimeTypeHandle -> TreeToGetNativeTypeHandle |
3726 | // |
3727 | // New tree |
3728 | // TreeToGetNativeTypeHandle |
3729 | |
3730 | // Remove call to helper and return the native TypeHandle pointer that was the parameter |
3731 | // to that helper. |
3732 | |
3733 | op1 = impPopStack().val; |
3734 | |
3735 | // Get native TypeHandle argument to old helper |
3736 | op1 = op1->gtCall.gtCallArgs; |
3737 | assert(op1->OperIsList()); |
3738 | assert(op1->gtOp.gtOp2 == nullptr); |
3739 | op1 = op1->gtOp.gtOp1; |
3740 | retNode = op1; |
3741 | } |
3742 | // Call the regular function. |
3743 | break; |
3744 | |
3745 | case CORINFO_INTRINSIC_Object_GetType: |
3746 | { |
3747 | JITDUMP("\n impIntrinsic: call to Object.GetType\n" ); |
3748 | op1 = impStackTop(0).val; |
3749 | |
3750 | // If we're calling GetType on a boxed value, just get the type directly. |
3751 | if (op1->IsBoxedValue()) |
3752 | { |
3753 | JITDUMP("Attempting to optimize box(...).getType() to direct type construction\n" ); |
3754 | |
3755 | // Try and clean up the box. Obtain the handle we |
3756 | // were going to pass to the newobj. |
3757 | GenTree* boxTypeHandle = gtTryRemoveBoxUpstreamEffects(op1, BR_REMOVE_AND_NARROW_WANT_TYPE_HANDLE); |
3758 | |
3759 | if (boxTypeHandle != nullptr) |
3760 | { |
3761 | // Note we don't need to play the TYP_STRUCT games here like |
3762 | // do for LDTOKEN since the return value of this operator is Type, |
3763 | // not RuntimeTypeHandle. |
3764 | impPopStack(); |
3765 | GenTreeArgList* helperArgs = gtNewArgList(boxTypeHandle); |
3766 | GenTree* runtimeType = |
3767 | gtNewHelperCallNode(CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE, TYP_REF, helperArgs); |
3768 | retNode = runtimeType; |
3769 | } |
3770 | } |
3771 | |
3772 | // If we have a constrained callvirt with a "box this" transform |
3773 | // we know we have a value class and hence an exact type. |
3774 | // |
3775 | // If so, instead of boxing and then extracting the type, just |
3776 | // construct the type directly. |
3777 | if ((retNode == nullptr) && (pConstrainedResolvedToken != nullptr) && |
3778 | (constraintCallThisTransform == CORINFO_BOX_THIS)) |
3779 | { |
3780 | // Ensure this is one of the is simple box cases (in particular, rule out nullables). |
3781 | const CorInfoHelpFunc boxHelper = info.compCompHnd->getBoxHelper(pConstrainedResolvedToken->hClass); |
3782 | const bool isSafeToOptimize = (boxHelper == CORINFO_HELP_BOX); |
3783 | |
3784 | if (isSafeToOptimize) |
3785 | { |
3786 | JITDUMP("Optimizing constrained box-this obj.getType() to direct type construction\n" ); |
3787 | impPopStack(); |
3788 | GenTree* typeHandleOp = |
3789 | impTokenToHandle(pConstrainedResolvedToken, nullptr, TRUE /* mustRestoreHandle */); |
3790 | if (typeHandleOp == nullptr) |
3791 | { |
3792 | assert(compDonotInline()); |
3793 | return nullptr; |
3794 | } |
3795 | GenTreeArgList* helperArgs = gtNewArgList(typeHandleOp); |
3796 | GenTree* runtimeType = |
3797 | gtNewHelperCallNode(CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE, TYP_REF, helperArgs); |
3798 | retNode = runtimeType; |
3799 | } |
3800 | } |
3801 | |
3802 | #ifdef DEBUG |
3803 | if (retNode != nullptr) |
3804 | { |
3805 | JITDUMP("Optimized result for call to GetType is\n" ); |
3806 | if (verbose) |
3807 | { |
3808 | gtDispTree(retNode); |
3809 | } |
3810 | } |
3811 | #endif |
3812 | |
3813 | // Else expand as an intrinsic, unless the call is constrained, |
3814 | // in which case we defer expansion to allow impImportCall do the |
3815 | // special constraint processing. |
3816 | if ((retNode == nullptr) && (pConstrainedResolvedToken == nullptr)) |
3817 | { |
3818 | JITDUMP("Expanding as special intrinsic\n" ); |
3819 | impPopStack(); |
3820 | op1 = new (this, GT_INTRINSIC) GenTreeIntrinsic(genActualType(callType), op1, intrinsicID, method); |
3821 | |
3822 | // Set the CALL flag to indicate that the operator is implemented by a call. |
3823 | // Set also the EXCEPTION flag because the native implementation of |
3824 | // CORINFO_INTRINSIC_Object_GetType intrinsic can throw NullReferenceException. |
3825 | op1->gtFlags |= (GTF_CALL | GTF_EXCEPT); |
3826 | retNode = op1; |
3827 | // Might be further optimizable, so arrange to leave a mark behind |
3828 | isSpecial = true; |
3829 | } |
3830 | |
3831 | if (retNode == nullptr) |
3832 | { |
3833 | JITDUMP("Leaving as normal call\n" ); |
3834 | // Might be further optimizable, so arrange to leave a mark behind |
3835 | isSpecial = true; |
3836 | } |
3837 | |
3838 | break; |
3839 | } |
3840 | |
3841 | // Implement ByReference Ctor. This wraps the assignment of the ref into a byref-like field |
3842 | // in a value type. The canonical example of this is Span<T>. In effect this is just a |
3843 | // substitution. The parameter byref will be assigned into the newly allocated object. |
3844 | case CORINFO_INTRINSIC_ByReference_Ctor: |
3845 | { |
3846 | // Remove call to constructor and directly assign the byref passed |
3847 | // to the call to the first slot of the ByReference struct. |
3848 | op1 = impPopStack().val; |
3849 | GenTree* thisptr = newobjThis; |
3850 | CORINFO_FIELD_HANDLE fldHnd = info.compCompHnd->getFieldInClass(clsHnd, 0); |
3851 | GenTree* field = gtNewFieldRef(TYP_BYREF, fldHnd, thisptr, 0); |
3852 | GenTree* assign = gtNewAssignNode(field, op1); |
3853 | GenTree* byReferenceStruct = gtCloneExpr(thisptr->gtGetOp1()); |
3854 | assert(byReferenceStruct != nullptr); |
3855 | impPushOnStack(byReferenceStruct, typeInfo(TI_STRUCT, clsHnd)); |
3856 | retNode = assign; |
3857 | break; |
3858 | } |
3859 | // Implement ptr value getter for ByReference struct. |
3860 | case CORINFO_INTRINSIC_ByReference_Value: |
3861 | { |
3862 | op1 = impPopStack().val; |
3863 | CORINFO_FIELD_HANDLE fldHnd = info.compCompHnd->getFieldInClass(clsHnd, 0); |
3864 | GenTree* field = gtNewFieldRef(TYP_BYREF, fldHnd, op1, 0); |
3865 | retNode = field; |
3866 | break; |
3867 | } |
3868 | case CORINFO_INTRINSIC_Span_GetItem: |
3869 | case CORINFO_INTRINSIC_ReadOnlySpan_GetItem: |
3870 | { |
3871 | // Have index, stack pointer-to Span<T> s on the stack. Expand to: |
3872 | // |
3873 | // For Span<T> |
3874 | // Comma |
3875 | // BoundsCheck(index, s->_length) |
3876 | // s->_pointer + index * sizeof(T) |
3877 | // |
3878 | // For ReadOnlySpan<T> -- same expansion, as it now returns a readonly ref |
3879 | // |
3880 | // Signature should show one class type parameter, which |
3881 | // we need to examine. |
3882 | assert(sig->sigInst.classInstCount == 1); |
3883 | CORINFO_CLASS_HANDLE spanElemHnd = sig->sigInst.classInst[0]; |
3884 | const unsigned elemSize = info.compCompHnd->getClassSize(spanElemHnd); |
3885 | assert(elemSize > 0); |
3886 | |
3887 | const bool isReadOnly = (intrinsicID == CORINFO_INTRINSIC_ReadOnlySpan_GetItem); |
3888 | |
3889 | JITDUMP("\nimpIntrinsic: Expanding %sSpan<T>.get_Item, T=%s, sizeof(T)=%u\n" , isReadOnly ? "ReadOnly" : "" , |
3890 | info.compCompHnd->getClassName(spanElemHnd), elemSize); |
3891 | |
3892 | GenTree* index = impPopStack().val; |
3893 | GenTree* ptrToSpan = impPopStack().val; |
3894 | GenTree* indexClone = nullptr; |
3895 | GenTree* ptrToSpanClone = nullptr; |
3896 | assert(varTypeIsIntegral(index)); |
3897 | assert(ptrToSpan->TypeGet() == TYP_BYREF); |
3898 | |
3899 | #if defined(DEBUG) |
3900 | if (verbose) |
3901 | { |
3902 | printf("with ptr-to-span\n" ); |
3903 | gtDispTree(ptrToSpan); |
3904 | printf("and index\n" ); |
3905 | gtDispTree(index); |
3906 | } |
3907 | #endif // defined(DEBUG) |
3908 | |
3909 | // We need to use both index and ptr-to-span twice, so clone or spill. |
3910 | index = impCloneExpr(index, &indexClone, NO_CLASS_HANDLE, (unsigned)CHECK_SPILL_ALL, |
3911 | nullptr DEBUGARG("Span.get_Item index" )); |
3912 | ptrToSpan = impCloneExpr(ptrToSpan, &ptrToSpanClone, NO_CLASS_HANDLE, (unsigned)CHECK_SPILL_ALL, |
3913 | nullptr DEBUGARG("Span.get_Item ptrToSpan" )); |
3914 | |
3915 | // Bounds check |
3916 | CORINFO_FIELD_HANDLE lengthHnd = info.compCompHnd->getFieldInClass(clsHnd, 1); |
3917 | const unsigned lengthOffset = info.compCompHnd->getFieldOffset(lengthHnd); |
3918 | GenTree* length = gtNewFieldRef(TYP_INT, lengthHnd, ptrToSpan, lengthOffset); |
3919 | GenTree* boundsCheck = new (this, GT_ARR_BOUNDS_CHECK) |
3920 | GenTreeBoundsChk(GT_ARR_BOUNDS_CHECK, TYP_VOID, index, length, SCK_RNGCHK_FAIL); |
3921 | |
3922 | // Element access |
3923 | GenTree* indexIntPtr = impImplicitIorI4Cast(indexClone, TYP_I_IMPL); |
3924 | GenTree* sizeofNode = gtNewIconNode(elemSize); |
3925 | GenTree* mulNode = gtNewOperNode(GT_MUL, TYP_I_IMPL, indexIntPtr, sizeofNode); |
3926 | CORINFO_FIELD_HANDLE ptrHnd = info.compCompHnd->getFieldInClass(clsHnd, 0); |
3927 | const unsigned ptrOffset = info.compCompHnd->getFieldOffset(ptrHnd); |
3928 | GenTree* data = gtNewFieldRef(TYP_BYREF, ptrHnd, ptrToSpanClone, ptrOffset); |
3929 | GenTree* result = gtNewOperNode(GT_ADD, TYP_BYREF, data, mulNode); |
3930 | |
3931 | // Prepare result |
3932 | var_types resultType = JITtype2varType(sig->retType); |
3933 | assert(resultType == result->TypeGet()); |
3934 | retNode = gtNewOperNode(GT_COMMA, resultType, boundsCheck, result); |
3935 | |
3936 | break; |
3937 | } |
3938 | |
3939 | case CORINFO_INTRINSIC_GetRawHandle: |
3940 | { |
3941 | noway_assert(IsTargetAbi(CORINFO_CORERT_ABI)); // Only CoreRT supports it. |
3942 | CORINFO_RESOLVED_TOKEN resolvedToken; |
3943 | resolvedToken.tokenContext = MAKE_METHODCONTEXT(info.compMethodHnd); |
3944 | resolvedToken.tokenScope = info.compScopeHnd; |
3945 | resolvedToken.token = memberRef; |
3946 | resolvedToken.tokenType = CORINFO_TOKENKIND_Method; |
3947 | |
3948 | CORINFO_GENERICHANDLE_RESULT embedInfo; |
3949 | info.compCompHnd->expandRawHandleIntrinsic(&resolvedToken, &embedInfo); |
3950 | |
3951 | GenTree* rawHandle = impLookupToTree(&resolvedToken, &embedInfo.lookup, gtTokenToIconFlags(memberRef), |
3952 | embedInfo.compileTimeHandle); |
3953 | if (rawHandle == nullptr) |
3954 | { |
3955 | return nullptr; |
3956 | } |
3957 | |
3958 | noway_assert(genTypeSize(rawHandle->TypeGet()) == genTypeSize(TYP_I_IMPL)); |
3959 | |
3960 | unsigned rawHandleSlot = lvaGrabTemp(true DEBUGARG("rawHandle" )); |
3961 | impAssignTempGen(rawHandleSlot, rawHandle, clsHnd, (unsigned)CHECK_SPILL_NONE); |
3962 | |
3963 | GenTree* lclVar = gtNewLclvNode(rawHandleSlot, TYP_I_IMPL); |
3964 | GenTree* lclVarAddr = gtNewOperNode(GT_ADDR, TYP_I_IMPL, lclVar); |
3965 | var_types resultType = JITtype2varType(sig->retType); |
3966 | retNode = gtNewOperNode(GT_IND, resultType, lclVarAddr); |
3967 | |
3968 | break; |
3969 | } |
3970 | |
3971 | case CORINFO_INTRINSIC_TypeEQ: |
3972 | case CORINFO_INTRINSIC_TypeNEQ: |
3973 | { |
3974 | JITDUMP("Importing Type.op_*Equality intrinsic\n" ); |
3975 | op1 = impStackTop(1).val; |
3976 | op2 = impStackTop(0).val; |
3977 | GenTree* optTree = gtFoldTypeEqualityCall(intrinsicID, op1, op2); |
3978 | if (optTree != nullptr) |
3979 | { |
3980 | // Success, clean up the evaluation stack. |
3981 | impPopStack(); |
3982 | impPopStack(); |
3983 | |
3984 | // See if we can optimize even further, to a handle compare. |
3985 | optTree = gtFoldTypeCompare(optTree); |
3986 | |
3987 | // See if we can now fold a handle compare to a constant. |
3988 | optTree = gtFoldExpr(optTree); |
3989 | |
3990 | retNode = optTree; |
3991 | } |
3992 | else |
3993 | { |
3994 | // Retry optimizing these later |
3995 | isSpecial = true; |
3996 | } |
3997 | break; |
3998 | } |
3999 | |
4000 | case CORINFO_INTRINSIC_GetCurrentManagedThread: |
4001 | case CORINFO_INTRINSIC_GetManagedThreadId: |
4002 | { |
4003 | // Retry optimizing these during morph |
4004 | isSpecial = true; |
4005 | break; |
4006 | } |
4007 | |
4008 | default: |
4009 | /* Unknown intrinsic */ |
4010 | intrinsicID = CORINFO_INTRINSIC_Illegal; |
4011 | break; |
4012 | } |
4013 | |
4014 | // Look for new-style jit intrinsics by name |
4015 | if (ni != NI_Illegal) |
4016 | { |
4017 | assert(retNode == nullptr); |
4018 | switch (ni) |
4019 | { |
4020 | case NI_System_Enum_HasFlag: |
4021 | { |
4022 | GenTree* thisOp = impStackTop(1).val; |
4023 | GenTree* flagOp = impStackTop(0).val; |
4024 | GenTree* optTree = gtOptimizeEnumHasFlag(thisOp, flagOp); |
4025 | |
4026 | if (optTree != nullptr) |
4027 | { |
4028 | // Optimization successful. Pop the stack for real. |
4029 | impPopStack(); |
4030 | impPopStack(); |
4031 | retNode = optTree; |
4032 | } |
4033 | else |
4034 | { |
4035 | // Retry optimizing this during morph. |
4036 | isSpecial = true; |
4037 | } |
4038 | |
4039 | break; |
4040 | } |
4041 | |
4042 | #ifdef FEATURE_HW_INTRINSICS |
4043 | case NI_System_Math_FusedMultiplyAdd: |
4044 | case NI_System_MathF_FusedMultiplyAdd: |
4045 | { |
4046 | #ifdef _TARGET_XARCH_ |
4047 | if (compSupports(InstructionSet_FMA)) |
4048 | { |
4049 | assert(varTypeIsFloating(callType)); |
4050 | |
4051 | // We are constructing a chain of intrinsics similar to: |
4052 | // return FMA.MultiplyAddScalar( |
4053 | // Vector128.CreateScalar(x), |
4054 | // Vector128.CreateScalar(y), |
4055 | // Vector128.CreateScalar(z) |
4056 | // ).ToScalar(); |
4057 | |
4058 | GenTree* op3 = gtNewSimdHWIntrinsicNode(TYP_SIMD16, impPopStack().val, |
4059 | NI_Base_Vector128_CreateScalarUnsafe, callType, 16); |
4060 | GenTree* op2 = gtNewSimdHWIntrinsicNode(TYP_SIMD16, impPopStack().val, |
4061 | NI_Base_Vector128_CreateScalarUnsafe, callType, 16); |
4062 | GenTree* op1 = gtNewSimdHWIntrinsicNode(TYP_SIMD16, impPopStack().val, |
4063 | NI_Base_Vector128_CreateScalarUnsafe, callType, 16); |
4064 | GenTree* res = |
4065 | gtNewSimdHWIntrinsicNode(TYP_SIMD16, op1, op2, op3, NI_FMA_MultiplyAddScalar, callType, 16); |
4066 | |
4067 | retNode = gtNewSimdHWIntrinsicNode(callType, res, NI_Base_Vector128_ToScalar, callType, 16); |
4068 | } |
4069 | #endif // _TARGET_XARCH_ |
4070 | break; |
4071 | } |
4072 | #endif // FEATURE_HW_INTRINSICS |
4073 | |
4074 | case NI_System_Math_Round: |
4075 | case NI_System_MathF_Round: |
4076 | { |
4077 | // Math.Round and MathF.Round used to be a traditional JIT intrinsic. In order |
4078 | // to simplify the transition, we will just treat it as if it was still the |
4079 | // old intrinsic, CORINFO_INTRINSIC_Round. This should end up flowing properly |
4080 | // everywhere else. |
4081 | |
4082 | retNode = impMathIntrinsic(method, sig, callType, CORINFO_INTRINSIC_Round, tailCall); |
4083 | break; |
4084 | } |
4085 | |
4086 | case NI_System_Collections_Generic_EqualityComparer_get_Default: |
4087 | { |
4088 | // Flag for later handling during devirtualization. |
4089 | isSpecial = true; |
4090 | break; |
4091 | } |
4092 | |
4093 | case NI_System_Buffers_Binary_BinaryPrimitives_ReverseEndianness: |
4094 | { |
4095 | assert(sig->numArgs == 1); |
4096 | |
4097 | // We expect the return type of the ReverseEndianness routine to match the type of the |
4098 | // one and only argument to the method. We use a special instruction for 16-bit |
4099 | // BSWAPs since on x86 processors this is implemented as ROR <16-bit reg>, 8. Additionally, |
4100 | // we only emit 64-bit BSWAP instructions on 64-bit archs; if we're asked to perform a |
4101 | // 64-bit byte swap on a 32-bit arch, we'll fall to the default case in the switch block below. |
4102 | |
4103 | switch (sig->retType) |
4104 | { |
4105 | case CorInfoType::CORINFO_TYPE_SHORT: |
4106 | case CorInfoType::CORINFO_TYPE_USHORT: |
4107 | retNode = gtNewOperNode(GT_BSWAP16, callType, impPopStack().val); |
4108 | break; |
4109 | |
4110 | case CorInfoType::CORINFO_TYPE_INT: |
4111 | case CorInfoType::CORINFO_TYPE_UINT: |
4112 | #ifdef _TARGET_64BIT_ |
4113 | case CorInfoType::CORINFO_TYPE_LONG: |
4114 | case CorInfoType::CORINFO_TYPE_ULONG: |
4115 | #endif // _TARGET_64BIT_ |
4116 | retNode = gtNewOperNode(GT_BSWAP, callType, impPopStack().val); |
4117 | break; |
4118 | |
4119 | default: |
4120 | // This default case gets hit on 32-bit archs when a call to a 64-bit overload |
4121 | // of ReverseEndianness is encountered. In that case we'll let JIT treat this as a standard |
4122 | // method call, where the implementation decomposes the operation into two 32-bit |
4123 | // bswap routines. If the input to the 64-bit function is a constant, then we rely |
4124 | // on inlining + constant folding of 32-bit bswaps to effectively constant fold |
4125 | // the 64-bit call site. |
4126 | break; |
4127 | } |
4128 | |
4129 | break; |
4130 | } |
4131 | |
4132 | default: |
4133 | break; |
4134 | } |
4135 | } |
4136 | |
4137 | if (mustExpand && (retNode == nullptr)) |
4138 | { |
4139 | NO_WAY("JIT must expand the intrinsic!" ); |
4140 | } |
4141 | |
4142 | // Optionally report if this intrinsic is special |
4143 | // (that is, potentially re-optimizable during morph). |
4144 | if (isSpecialIntrinsic != nullptr) |
4145 | { |
4146 | *isSpecialIntrinsic = isSpecial; |
4147 | } |
4148 | |
4149 | return retNode; |
4150 | } |
4151 | |
4152 | #ifdef FEATURE_HW_INTRINSICS |
4153 | //------------------------------------------------------------------------ |
4154 | // impBaseIntrinsic: dispatch intrinsics to their own implementation |
4155 | // |
4156 | // Arguments: |
4157 | // intrinsic -- id of the intrinsic function. |
4158 | // clsHnd -- handle for the intrinsic method's class |
4159 | // method -- method handle of the intrinsic function. |
4160 | // sig -- signature of the intrinsic call |
4161 | // |
4162 | // Return Value: |
4163 | // the expanded intrinsic. |
4164 | // |
4165 | GenTree* Compiler::impBaseIntrinsic(NamedIntrinsic intrinsic, |
4166 | CORINFO_CLASS_HANDLE clsHnd, |
4167 | CORINFO_METHOD_HANDLE method, |
4168 | CORINFO_SIG_INFO* sig) |
4169 | { |
4170 | GenTree* retNode = nullptr; |
4171 | GenTree* op1 = nullptr; |
4172 | |
4173 | if (!featureSIMD) |
4174 | { |
4175 | return nullptr; |
4176 | } |
4177 | |
4178 | unsigned simdSize = 0; |
4179 | var_types baseType = TYP_UNKNOWN; |
4180 | var_types retType = JITtype2varType(sig->retType); |
4181 | |
4182 | if (sig->hasThis()) |
4183 | { |
4184 | baseType = getBaseTypeAndSizeOfSIMDType(clsHnd, &simdSize); |
4185 | |
4186 | if (retType == TYP_STRUCT) |
4187 | { |
4188 | unsigned retSimdSize = 0; |
4189 | var_types retBasetype = getBaseTypeAndSizeOfSIMDType(sig->retTypeClass, &retSimdSize); |
4190 | if (!varTypeIsArithmetic(retBasetype)) |
4191 | { |
4192 | return nullptr; |
4193 | } |
4194 | retType = getSIMDTypeForSize(retSimdSize); |
4195 | } |
4196 | } |
4197 | else |
4198 | { |
4199 | assert(retType == TYP_STRUCT); |
4200 | baseType = getBaseTypeAndSizeOfSIMDType(sig->retTypeClass, &simdSize); |
4201 | retType = getSIMDTypeForSize(simdSize); |
4202 | } |
4203 | |
4204 | if (!varTypeIsArithmetic(baseType)) |
4205 | { |
4206 | return nullptr; |
4207 | } |
4208 | |
4209 | switch (intrinsic) |
4210 | { |
4211 | #if defined(_TARGET_XARCH_) |
4212 | case NI_Base_Vector256_As: |
4213 | case NI_Base_Vector256_AsByte: |
4214 | case NI_Base_Vector256_AsDouble: |
4215 | case NI_Base_Vector256_AsInt16: |
4216 | case NI_Base_Vector256_AsInt32: |
4217 | case NI_Base_Vector256_AsInt64: |
4218 | case NI_Base_Vector256_AsSByte: |
4219 | case NI_Base_Vector256_AsSingle: |
4220 | case NI_Base_Vector256_AsUInt16: |
4221 | case NI_Base_Vector256_AsUInt32: |
4222 | case NI_Base_Vector256_AsUInt64: |
4223 | { |
4224 | if (!compSupports(InstructionSet_AVX)) |
4225 | { |
4226 | // We don't want to deal with TYP_SIMD32 if the compiler doesn't otherwise support the type. |
4227 | break; |
4228 | } |
4229 | |
4230 | __fallthrough; |
4231 | } |
4232 | #endif // _TARGET_XARCH_ |
4233 | |
4234 | #if defined(_TARGET_ARM64_) |
4235 | case NI_Base_Vector64_AsByte: |
4236 | case NI_Base_Vector64_AsInt16: |
4237 | case NI_Base_Vector64_AsInt32: |
4238 | case NI_Base_Vector64_AsSByte: |
4239 | case NI_Base_Vector64_AsSingle: |
4240 | case NI_Base_Vector64_AsUInt16: |
4241 | case NI_Base_Vector64_AsUInt32: |
4242 | #endif // _TARGET_ARM64_ |
4243 | case NI_Base_Vector128_As: |
4244 | case NI_Base_Vector128_AsByte: |
4245 | case NI_Base_Vector128_AsDouble: |
4246 | case NI_Base_Vector128_AsInt16: |
4247 | case NI_Base_Vector128_AsInt32: |
4248 | case NI_Base_Vector128_AsInt64: |
4249 | case NI_Base_Vector128_AsSByte: |
4250 | case NI_Base_Vector128_AsSingle: |
4251 | case NI_Base_Vector128_AsUInt16: |
4252 | case NI_Base_Vector128_AsUInt32: |
4253 | case NI_Base_Vector128_AsUInt64: |
4254 | { |
4255 | // We fold away the cast here, as it only exists to satisfy |
4256 | // the type system. It is safe to do this here since the retNode type |
4257 | // and the signature return type are both the same TYP_SIMD. |
4258 | |
4259 | assert(sig->numArgs == 0); |
4260 | assert(sig->hasThis()); |
4261 | |
4262 | retNode = impSIMDPopStack(retType, true, sig->retTypeClass); |
4263 | SetOpLclRelatedToSIMDIntrinsic(retNode); |
4264 | assert(retNode->gtType == getSIMDTypeForSize(getSIMDTypeSizeInBytes(sig->retTypeSigClass))); |
4265 | break; |
4266 | } |
4267 | |
4268 | #ifdef _TARGET_XARCH_ |
4269 | case NI_Base_Vector128_CreateScalarUnsafe: |
4270 | { |
4271 | assert(sig->numArgs == 1); |
4272 | |
4273 | #ifdef _TARGET_X86_ |
4274 | if (varTypeIsLong(baseType)) |
4275 | { |
4276 | // TODO-XARCH-CQ: It may be beneficial to emit the movq |
4277 | // instruction, which takes a 64-bit memory address and |
4278 | // works on 32-bit x86 systems. |
4279 | break; |
4280 | } |
4281 | #endif // _TARGET_X86_ |
4282 | |
4283 | if (compSupports(InstructionSet_SSE2) || (compSupports(InstructionSet_SSE) && (baseType == TYP_FLOAT))) |
4284 | { |
4285 | op1 = impPopStack().val; |
4286 | retNode = gtNewSimdHWIntrinsicNode(retType, op1, intrinsic, baseType, simdSize); |
4287 | } |
4288 | break; |
4289 | } |
4290 | |
4291 | case NI_Base_Vector128_ToScalar: |
4292 | { |
4293 | assert(sig->numArgs == 0); |
4294 | assert(sig->hasThis()); |
4295 | |
4296 | if (compSupports(InstructionSet_SSE) && varTypeIsFloating(baseType)) |
4297 | { |
4298 | op1 = impSIMDPopStack(getSIMDTypeForSize(simdSize), true, clsHnd); |
4299 | retNode = gtNewSimdHWIntrinsicNode(retType, op1, intrinsic, baseType, 16); |
4300 | } |
4301 | break; |
4302 | } |
4303 | |
4304 | case NI_Base_Vector128_ToVector256: |
4305 | case NI_Base_Vector128_ToVector256Unsafe: |
4306 | case NI_Base_Vector256_GetLower: |
4307 | { |
4308 | assert(sig->numArgs == 0); |
4309 | assert(sig->hasThis()); |
4310 | |
4311 | if (compSupports(InstructionSet_AVX)) |
4312 | { |
4313 | op1 = impSIMDPopStack(getSIMDTypeForSize(simdSize), true, clsHnd); |
4314 | retNode = gtNewSimdHWIntrinsicNode(retType, op1, intrinsic, baseType, simdSize); |
4315 | } |
4316 | break; |
4317 | } |
4318 | |
4319 | case NI_Base_Vector128_Zero: |
4320 | { |
4321 | assert(sig->numArgs == 0); |
4322 | |
4323 | if (compSupports(InstructionSet_SSE)) |
4324 | { |
4325 | retNode = gtNewSimdHWIntrinsicNode(retType, intrinsic, baseType, simdSize); |
4326 | } |
4327 | break; |
4328 | } |
4329 | |
4330 | case NI_Base_Vector256_CreateScalarUnsafe: |
4331 | { |
4332 | assert(sig->numArgs == 1); |
4333 | |
4334 | #ifdef _TARGET_X86_ |
4335 | if (varTypeIsLong(baseType)) |
4336 | { |
4337 | // TODO-XARCH-CQ: It may be beneficial to emit the movq |
4338 | // instruction, which takes a 64-bit memory address and |
4339 | // works on 32-bit x86 systems. |
4340 | break; |
4341 | } |
4342 | #endif // _TARGET_X86_ |
4343 | |
4344 | if (compSupports(InstructionSet_AVX)) |
4345 | { |
4346 | op1 = impPopStack().val; |
4347 | retNode = gtNewSimdHWIntrinsicNode(retType, op1, intrinsic, baseType, simdSize); |
4348 | } |
4349 | break; |
4350 | } |
4351 | |
4352 | case NI_Base_Vector256_ToScalar: |
4353 | { |
4354 | assert(sig->numArgs == 0); |
4355 | assert(sig->hasThis()); |
4356 | |
4357 | if (compSupports(InstructionSet_AVX) && varTypeIsFloating(baseType)) |
4358 | { |
4359 | op1 = impSIMDPopStack(getSIMDTypeForSize(simdSize), true, clsHnd); |
4360 | retNode = gtNewSimdHWIntrinsicNode(retType, op1, intrinsic, baseType, 32); |
4361 | } |
4362 | break; |
4363 | } |
4364 | |
4365 | case NI_Base_Vector256_Zero: |
4366 | { |
4367 | assert(sig->numArgs == 0); |
4368 | |
4369 | if (compSupports(InstructionSet_AVX)) |
4370 | { |
4371 | retNode = gtNewSimdHWIntrinsicNode(retType, intrinsic, baseType, simdSize); |
4372 | } |
4373 | break; |
4374 | } |
4375 | #endif // _TARGET_XARCH_ |
4376 | |
4377 | default: |
4378 | { |
4379 | unreached(); |
4380 | break; |
4381 | } |
4382 | } |
4383 | |
4384 | return retNode; |
4385 | } |
4386 | #endif // FEATURE_HW_INTRINSICS |
4387 | |
4388 | GenTree* Compiler::impMathIntrinsic(CORINFO_METHOD_HANDLE method, |
4389 | CORINFO_SIG_INFO* sig, |
4390 | var_types callType, |
4391 | CorInfoIntrinsics intrinsicID, |
4392 | bool tailCall) |
4393 | { |
4394 | GenTree* op1; |
4395 | GenTree* op2; |
4396 | |
4397 | assert(callType != TYP_STRUCT); |
4398 | assert(IsMathIntrinsic(intrinsicID)); |
4399 | |
4400 | op1 = nullptr; |
4401 | |
4402 | #if !defined(_TARGET_X86_) |
4403 | // Intrinsics that are not implemented directly by target instructions will |
4404 | // be re-materialized as users calls in rationalizer. For prefixed tail calls, |
4405 | // don't do this optimization, because |
4406 | // a) For back compatibility reasons on desktop.Net 4.6 / 4.6.1 |
4407 | // b) It will be non-trivial task or too late to re-materialize a surviving |
4408 | // tail prefixed GT_INTRINSIC as tail call in rationalizer. |
4409 | if (!IsIntrinsicImplementedByUserCall(intrinsicID) || !tailCall) |
4410 | #else |
4411 | // On x86 RyuJIT, importing intrinsics that are implemented as user calls can cause incorrect calculation |
4412 | // of the depth of the stack if these intrinsics are used as arguments to another call. This causes bad |
4413 | // code generation for certain EH constructs. |
4414 | if (!IsIntrinsicImplementedByUserCall(intrinsicID)) |
4415 | #endif |
4416 | { |
4417 | switch (sig->numArgs) |
4418 | { |
4419 | case 1: |
4420 | op1 = impPopStack().val; |
4421 | |
4422 | assert(varTypeIsFloating(op1)); |
4423 | |
4424 | if (op1->TypeGet() != callType) |
4425 | { |
4426 | op1 = gtNewCastNode(callType, op1, false, callType); |
4427 | } |
4428 | |
4429 | op1 = new (this, GT_INTRINSIC) GenTreeIntrinsic(genActualType(callType), op1, intrinsicID, method); |
4430 | break; |
4431 | |
4432 | case 2: |
4433 | op2 = impPopStack().val; |
4434 | op1 = impPopStack().val; |
4435 | |
4436 | assert(varTypeIsFloating(op1)); |
4437 | assert(varTypeIsFloating(op2)); |
4438 | |
4439 | if (op2->TypeGet() != callType) |
4440 | { |
4441 | op2 = gtNewCastNode(callType, op2, false, callType); |
4442 | } |
4443 | if (op1->TypeGet() != callType) |
4444 | { |
4445 | op1 = gtNewCastNode(callType, op1, false, callType); |
4446 | } |
4447 | |
4448 | op1 = new (this, GT_INTRINSIC) GenTreeIntrinsic(genActualType(callType), op1, op2, intrinsicID, method); |
4449 | break; |
4450 | |
4451 | default: |
4452 | NO_WAY("Unsupported number of args for Math Instrinsic" ); |
4453 | } |
4454 | |
4455 | if (IsIntrinsicImplementedByUserCall(intrinsicID)) |
4456 | { |
4457 | op1->gtFlags |= GTF_CALL; |
4458 | } |
4459 | } |
4460 | |
4461 | return op1; |
4462 | } |
4463 | |
4464 | //------------------------------------------------------------------------ |
4465 | // lookupNamedIntrinsic: map method to jit named intrinsic value |
4466 | // |
4467 | // Arguments: |
4468 | // method -- method handle for method |
4469 | // |
4470 | // Return Value: |
4471 | // Id for the named intrinsic, or Illegal if none. |
4472 | // |
4473 | // Notes: |
4474 | // method should have CORINFO_FLG_JIT_INTRINSIC set in its attributes, |
4475 | // otherwise it is not a named jit intrinsic. |
4476 | // |
4477 | |
4478 | NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method) |
4479 | { |
4480 | NamedIntrinsic result = NI_Illegal; |
4481 | |
4482 | const char* className = nullptr; |
4483 | const char* namespaceName = nullptr; |
4484 | const char* enclosingClassName = nullptr; |
4485 | const char* methodName = |
4486 | info.compCompHnd->getMethodNameFromMetadata(method, &className, &namespaceName, &enclosingClassName); |
4487 | |
4488 | if ((namespaceName == nullptr) || (className == nullptr) || (methodName == nullptr)) |
4489 | { |
4490 | return result; |
4491 | } |
4492 | |
4493 | if (strcmp(namespaceName, "System" ) == 0) |
4494 | { |
4495 | if ((strcmp(className, "Enum" ) == 0) && (strcmp(methodName, "HasFlag" ) == 0)) |
4496 | { |
4497 | result = NI_System_Enum_HasFlag; |
4498 | } |
4499 | else if (strncmp(className, "Math" , 4) == 0) |
4500 | { |
4501 | className += 4; |
4502 | |
4503 | if (className[0] == '\0') |
4504 | { |
4505 | if (strcmp(methodName, "FusedMultiplyAdd" ) == 0) |
4506 | { |
4507 | result = NI_System_Math_FusedMultiplyAdd; |
4508 | } |
4509 | else if (strcmp(methodName, "Round" ) == 0) |
4510 | { |
4511 | result = NI_System_Math_Round; |
4512 | } |
4513 | } |
4514 | else if (strcmp(className, "F" ) == 0) |
4515 | { |
4516 | if (strcmp(methodName, "FusedMultiplyAdd" ) == 0) |
4517 | { |
4518 | result = NI_System_MathF_FusedMultiplyAdd; |
4519 | } |
4520 | else if (strcmp(methodName, "Round" ) == 0) |
4521 | { |
4522 | result = NI_System_MathF_Round; |
4523 | } |
4524 | } |
4525 | } |
4526 | } |
4527 | #if defined(_TARGET_XARCH_) // We currently only support BSWAP on x86 |
4528 | else if (strcmp(namespaceName, "System.Buffers.Binary" ) == 0) |
4529 | { |
4530 | if ((strcmp(className, "BinaryPrimitives" ) == 0) && (strcmp(methodName, "ReverseEndianness" ) == 0)) |
4531 | { |
4532 | result = NI_System_Buffers_Binary_BinaryPrimitives_ReverseEndianness; |
4533 | } |
4534 | } |
4535 | #endif // !defined(_TARGET_XARCH_) |
4536 | else if (strcmp(namespaceName, "System.Collections.Generic" ) == 0) |
4537 | { |
4538 | if ((strcmp(className, "EqualityComparer`1" ) == 0) && (strcmp(methodName, "get_Default" ) == 0)) |
4539 | { |
4540 | result = NI_System_Collections_Generic_EqualityComparer_get_Default; |
4541 | } |
4542 | } |
4543 | #ifdef FEATURE_HW_INTRINSICS |
4544 | else if (strncmp(namespaceName, "System.Runtime.Intrinsics" , 25) == 0) |
4545 | { |
4546 | namespaceName += 25; |
4547 | |
4548 | if (namespaceName[0] == '\0') |
4549 | { |
4550 | if (strncmp(className, "Vector" , 6) == 0) |
4551 | { |
4552 | className += 6; |
4553 | |
4554 | #if defined(_TARGET_ARM64_) |
4555 | if (strncmp(className, "64" , 2) == 0) |
4556 | { |
4557 | className += 2; |
4558 | |
4559 | if (strcmp(className, "`1" ) == 0) |
4560 | { |
4561 | if (strncmp(methodName, "As" , 2) == 0) |
4562 | { |
4563 | methodName += 2; |
4564 | |
4565 | // Vector64_As, Vector64_AsDouble, Vector64_AsInt64, and Vector64_AsUInt64 |
4566 | // are not currently supported as they require additional plumbing to be |
4567 | // supported by the JIT as TYP_SIMD8. |
4568 | |
4569 | if (strcmp(methodName, "Byte" ) == 0) |
4570 | { |
4571 | result = NI_Base_Vector64_AsByte; |
4572 | } |
4573 | else if (strcmp(methodName, "Int16" ) == 0) |
4574 | { |
4575 | result = NI_Base_Vector64_AsInt16; |
4576 | } |
4577 | else if (strcmp(methodName, "Int32" ) == 0) |
4578 | { |
4579 | result = NI_Base_Vector64_AsInt32; |
4580 | } |
4581 | else if (strcmp(methodName, "SByte" ) == 0) |
4582 | { |
4583 | result = NI_Base_Vector64_AsSByte; |
4584 | } |
4585 | else if (strcmp(methodName, "Single" ) == 0) |
4586 | { |
4587 | result = NI_Base_Vector64_AsSingle; |
4588 | } |
4589 | else if (strcmp(methodName, "UInt16" ) == 0) |
4590 | { |
4591 | result = NI_Base_Vector64_AsUInt16; |
4592 | } |
4593 | else if (strcmp(methodName, "UInt32" ) == 0) |
4594 | { |
4595 | result = NI_Base_Vector64_AsUInt32; |
4596 | } |
4597 | } |
4598 | } |
4599 | } |
4600 | else |
4601 | #endif // _TARGET_ARM64_ |
4602 | if (strncmp(className, "128" , 3) == 0) |
4603 | { |
4604 | className += 3; |
4605 | |
4606 | #if defined(_TARGET_XARCH_) |
4607 | if (className[0] == '\0') |
4608 | { |
4609 | if (strcmp(methodName, "CreateScalarUnsafe" ) == 0) |
4610 | { |
4611 | result = NI_Base_Vector128_CreateScalarUnsafe; |
4612 | } |
4613 | } |
4614 | else |
4615 | #endif // _TARGET_XARCH_ |
4616 | if (strcmp(className, "`1" ) == 0) |
4617 | { |
4618 | if (strncmp(methodName, "As" , 2) == 0) |
4619 | { |
4620 | methodName += 2; |
4621 | |
4622 | if (methodName[0] == '\0') |
4623 | { |
4624 | result = NI_Base_Vector128_As; |
4625 | } |
4626 | else if (strcmp(methodName, "Byte" ) == 0) |
4627 | { |
4628 | result = NI_Base_Vector128_AsByte; |
4629 | } |
4630 | else if (strcmp(methodName, "Double" ) == 0) |
4631 | { |
4632 | result = NI_Base_Vector128_AsDouble; |
4633 | } |
4634 | else if (strcmp(methodName, "Int16" ) == 0) |
4635 | { |
4636 | result = NI_Base_Vector128_AsInt16; |
4637 | } |
4638 | else if (strcmp(methodName, "Int32" ) == 0) |
4639 | { |
4640 | result = NI_Base_Vector128_AsInt32; |
4641 | } |
4642 | else if (strcmp(methodName, "Int64" ) == 0) |
4643 | { |
4644 | result = NI_Base_Vector128_AsInt64; |
4645 | } |
4646 | else if (strcmp(methodName, "SByte" ) == 0) |
4647 | { |
4648 | result = NI_Base_Vector128_AsSByte; |
4649 | } |
4650 | else if (strcmp(methodName, "Single" ) == 0) |
4651 | { |
4652 | result = NI_Base_Vector128_AsSingle; |
4653 | } |
4654 | else if (strcmp(methodName, "UInt16" ) == 0) |
4655 | { |
4656 | result = NI_Base_Vector128_AsUInt16; |
4657 | } |
4658 | else if (strcmp(methodName, "UInt32" ) == 0) |
4659 | { |
4660 | result = NI_Base_Vector128_AsUInt32; |
4661 | } |
4662 | else if (strcmp(methodName, "UInt64" ) == 0) |
4663 | { |
4664 | result = NI_Base_Vector128_AsUInt64; |
4665 | } |
4666 | } |
4667 | #if defined(_TARGET_XARCH_) |
4668 | else if (strcmp(methodName, "get_Zero" ) == 0) |
4669 | { |
4670 | result = NI_Base_Vector128_Zero; |
4671 | } |
4672 | else if (strncmp(methodName, "To" , 2) == 0) |
4673 | { |
4674 | methodName += 2; |
4675 | |
4676 | if (strcmp(methodName, "Scalar" ) == 0) |
4677 | { |
4678 | result = NI_Base_Vector128_ToScalar; |
4679 | } |
4680 | else if (strncmp(methodName, "Vector256" , 9) == 0) |
4681 | { |
4682 | methodName += 9; |
4683 | |
4684 | if (methodName[0] == '\0') |
4685 | { |
4686 | result = NI_Base_Vector128_ToVector256; |
4687 | } |
4688 | else if (strcmp(methodName, "Unsafe" ) == 0) |
4689 | { |
4690 | result = NI_Base_Vector128_ToVector256Unsafe; |
4691 | } |
4692 | } |
4693 | } |
4694 | #endif // _TARGET_XARCH_ |
4695 | } |
4696 | } |
4697 | #if defined(_TARGET_XARCH_) |
4698 | else if (strncmp(className, "256" , 3) == 0) |
4699 | { |
4700 | className += 3; |
4701 | |
4702 | if (className[0] == '\0') |
4703 | { |
4704 | if (strcmp(methodName, "CreateScalarUnsafe" ) == 0) |
4705 | { |
4706 | result = NI_Base_Vector256_CreateScalarUnsafe; |
4707 | } |
4708 | } |
4709 | else if (strcmp(className, "`1" ) == 0) |
4710 | { |
4711 | if (strncmp(methodName, "As" , 2) == 0) |
4712 | { |
4713 | methodName += 2; |
4714 | |
4715 | if (methodName[0] == '\0') |
4716 | { |
4717 | result = NI_Base_Vector256_As; |
4718 | } |
4719 | else if (strcmp(methodName, "Byte" ) == 0) |
4720 | { |
4721 | result = NI_Base_Vector256_AsByte; |
4722 | } |
4723 | else if (strcmp(methodName, "Double" ) == 0) |
4724 | { |
4725 | result = NI_Base_Vector256_AsDouble; |
4726 | } |
4727 | else if (strcmp(methodName, "Int16" ) == 0) |
4728 | { |
4729 | result = NI_Base_Vector256_AsInt16; |
4730 | } |
4731 | else if (strcmp(methodName, "Int32" ) == 0) |
4732 | { |
4733 | result = NI_Base_Vector256_AsInt32; |
4734 | } |
4735 | else if (strcmp(methodName, "Int64" ) == 0) |
4736 | { |
4737 | result = NI_Base_Vector256_AsInt64; |
4738 | } |
4739 | else if (strcmp(methodName, "SByte" ) == 0) |
4740 | { |
4741 | result = NI_Base_Vector256_AsSByte; |
4742 | } |
4743 | else if (strcmp(methodName, "Single" ) == 0) |
4744 | { |
4745 | result = NI_Base_Vector256_AsSingle; |
4746 | } |
4747 | else if (strcmp(methodName, "UInt16" ) == 0) |
4748 | { |
4749 | result = NI_Base_Vector256_AsUInt16; |
4750 | } |
4751 | else if (strcmp(methodName, "UInt32" ) == 0) |
4752 | { |
4753 | result = NI_Base_Vector256_AsUInt32; |
4754 | } |
4755 | else if (strcmp(methodName, "UInt64" ) == 0) |
4756 | { |
4757 | result = NI_Base_Vector256_AsUInt64; |
4758 | } |
4759 | } |
4760 | else if (strcmp(methodName, "get_Zero" ) == 0) |
4761 | { |
4762 | result = NI_Base_Vector256_Zero; |
4763 | } |
4764 | else if (strcmp(methodName, "GetLower" ) == 0) |
4765 | { |
4766 | result = NI_Base_Vector256_GetLower; |
4767 | } |
4768 | else if (strcmp(methodName, "ToScalar" ) == 0) |
4769 | { |
4770 | result = NI_Base_Vector256_ToScalar; |
4771 | } |
4772 | } |
4773 | } |
4774 | #endif // _TARGET_XARCH_ |
4775 | } |
4776 | } |
4777 | #if defined(_TARGET_XARCH_) |
4778 | else if (strcmp(namespaceName, ".X86" ) == 0) |
4779 | { |
4780 | result = HWIntrinsicInfo::lookupId(className, methodName, enclosingClassName); |
4781 | } |
4782 | #elif defined(_TARGET_ARM64_) |
4783 | else if (strcmp(namespaceName, ".Arm.Arm64" ) == 0) |
4784 | { |
4785 | result = lookupHWIntrinsic(className, methodName); |
4786 | } |
4787 | #else // !defined(_TARGET_XARCH_) && !defined(_TARGET_ARM64_) |
4788 | #error Unsupported platform |
4789 | #endif // !defined(_TARGET_XARCH_) && !defined(_TARGET_ARM64_) |
4790 | } |
4791 | #endif // FEATURE_HW_INTRINSICS |
4792 | |
4793 | return result; |
4794 | } |
4795 | |
4796 | /*****************************************************************************/ |
4797 | |
4798 | GenTree* Compiler::impArrayAccessIntrinsic( |
4799 | CORINFO_CLASS_HANDLE clsHnd, CORINFO_SIG_INFO* sig, int memberRef, bool readonlyCall, CorInfoIntrinsics intrinsicID) |
4800 | { |
4801 | /* If we are generating SMALL_CODE, we don't want to use intrinsics for |
4802 | the following, as it generates fatter code. |
4803 | */ |
4804 | |
4805 | if (compCodeOpt() == SMALL_CODE) |
4806 | { |
4807 | return nullptr; |
4808 | } |
4809 | |
4810 | /* These intrinsics generate fatter (but faster) code and are only |
4811 | done if we don't need SMALL_CODE */ |
4812 | |
4813 | unsigned rank = (intrinsicID == CORINFO_INTRINSIC_Array_Set) ? (sig->numArgs - 1) : sig->numArgs; |
4814 | |
4815 | // The rank 1 case is special because it has to handle two array formats |
4816 | // we will simply not do that case |
4817 | if (rank > GT_ARR_MAX_RANK || rank <= 1) |
4818 | { |
4819 | return nullptr; |
4820 | } |
4821 | |
4822 | CORINFO_CLASS_HANDLE arrElemClsHnd = nullptr; |
4823 | var_types elemType = JITtype2varType(info.compCompHnd->getChildType(clsHnd, &arrElemClsHnd)); |
4824 | |
4825 | // For the ref case, we will only be able to inline if the types match |
4826 | // (verifier checks for this, we don't care for the nonverified case and the |
4827 | // type is final (so we don't need to do the cast) |
4828 | if ((intrinsicID != CORINFO_INTRINSIC_Array_Get) && !readonlyCall && varTypeIsGC(elemType)) |
4829 | { |
4830 | // Get the call site signature |
4831 | CORINFO_SIG_INFO LocalSig; |
4832 | eeGetCallSiteSig(memberRef, info.compScopeHnd, impTokenLookupContextHandle, &LocalSig); |
4833 | assert(LocalSig.hasThis()); |
4834 | |
4835 | CORINFO_CLASS_HANDLE actualElemClsHnd; |
4836 | |
4837 | if (intrinsicID == CORINFO_INTRINSIC_Array_Set) |
4838 | { |
4839 | // Fetch the last argument, the one that indicates the type we are setting. |
4840 | CORINFO_ARG_LIST_HANDLE argType = LocalSig.args; |
4841 | for (unsigned r = 0; r < rank; r++) |
4842 | { |
4843 | argType = info.compCompHnd->getArgNext(argType); |
4844 | } |
4845 | |
4846 | typeInfo argInfo = verParseArgSigToTypeInfo(&LocalSig, argType); |
4847 | actualElemClsHnd = argInfo.GetClassHandle(); |
4848 | } |
4849 | else |
4850 | { |
4851 | assert(intrinsicID == CORINFO_INTRINSIC_Array_Address); |
4852 | |
4853 | // Fetch the return type |
4854 | typeInfo retInfo = verMakeTypeInfo(LocalSig.retType, LocalSig.retTypeClass); |
4855 | assert(retInfo.IsByRef()); |
4856 | actualElemClsHnd = retInfo.GetClassHandle(); |
4857 | } |
4858 | |
4859 | // if it's not final, we can't do the optimization |
4860 | if (!(info.compCompHnd->getClassAttribs(actualElemClsHnd) & CORINFO_FLG_FINAL)) |
4861 | { |
4862 | return nullptr; |
4863 | } |
4864 | } |
4865 | |
4866 | unsigned arrayElemSize; |
4867 | if (elemType == TYP_STRUCT) |
4868 | { |
4869 | assert(arrElemClsHnd); |
4870 | |
4871 | arrayElemSize = info.compCompHnd->getClassSize(arrElemClsHnd); |
4872 | } |
4873 | else |
4874 | { |
4875 | arrayElemSize = genTypeSize(elemType); |
4876 | } |
4877 | |
4878 | if ((unsigned char)arrayElemSize != arrayElemSize) |
4879 | { |
4880 | // arrayElemSize would be truncated as an unsigned char. |
4881 | // This means the array element is too large. Don't do the optimization. |
4882 | return nullptr; |
4883 | } |
4884 | |
4885 | GenTree* val = nullptr; |
4886 | |
4887 | if (intrinsicID == CORINFO_INTRINSIC_Array_Set) |
4888 | { |
4889 | // Assignment of a struct is more work, and there are more gets than sets. |
4890 | if (elemType == TYP_STRUCT) |
4891 | { |
4892 | return nullptr; |
4893 | } |
4894 | |
4895 | val = impPopStack().val; |
4896 | assert(genActualType(elemType) == genActualType(val->gtType) || |
4897 | (elemType == TYP_FLOAT && val->gtType == TYP_DOUBLE) || |
4898 | (elemType == TYP_INT && val->gtType == TYP_BYREF) || |
4899 | (elemType == TYP_DOUBLE && val->gtType == TYP_FLOAT)); |
4900 | } |
4901 | |
4902 | noway_assert((unsigned char)GT_ARR_MAX_RANK == GT_ARR_MAX_RANK); |
4903 | |
4904 | GenTree* inds[GT_ARR_MAX_RANK]; |
4905 | for (unsigned k = rank; k > 0; k--) |
4906 | { |
4907 | inds[k - 1] = impPopStack().val; |
4908 | } |
4909 | |
4910 | GenTree* arr = impPopStack().val; |
4911 | assert(arr->gtType == TYP_REF); |
4912 | |
4913 | GenTree* arrElem = |
4914 | new (this, GT_ARR_ELEM) GenTreeArrElem(TYP_BYREF, arr, static_cast<unsigned char>(rank), |
4915 | static_cast<unsigned char>(arrayElemSize), elemType, &inds[0]); |
4916 | |
4917 | if (intrinsicID != CORINFO_INTRINSIC_Array_Address) |
4918 | { |
4919 | arrElem = gtNewOperNode(GT_IND, elemType, arrElem); |
4920 | } |
4921 | |
4922 | if (intrinsicID == CORINFO_INTRINSIC_Array_Set) |
4923 | { |
4924 | assert(val != nullptr); |
4925 | return gtNewAssignNode(arrElem, val); |
4926 | } |
4927 | else |
4928 | { |
4929 | return arrElem; |
4930 | } |
4931 | } |
4932 | |
4933 | BOOL Compiler::verMergeEntryStates(BasicBlock* block, bool* changed) |
4934 | { |
4935 | unsigned i; |
4936 | |
4937 | // do some basic checks first |
4938 | if (block->bbStackDepthOnEntry() != verCurrentState.esStackDepth) |
4939 | { |
4940 | return FALSE; |
4941 | } |
4942 | |
4943 | if (verCurrentState.esStackDepth > 0) |
4944 | { |
4945 | // merge stack types |
4946 | StackEntry* parentStack = block->bbStackOnEntry(); |
4947 | StackEntry* childStack = verCurrentState.esStack; |
4948 | |
4949 | for (i = 0; i < verCurrentState.esStackDepth; i++, parentStack++, childStack++) |
4950 | { |
4951 | if (tiMergeToCommonParent(&parentStack->seTypeInfo, &childStack->seTypeInfo, changed) == FALSE) |
4952 | { |
4953 | return FALSE; |
4954 | } |
4955 | } |
4956 | } |
4957 | |
4958 | // merge initialization status of this ptr |
4959 | |
4960 | if (verTrackObjCtorInitState) |
4961 | { |
4962 | // If we're tracking the CtorInitState, then it must not be unknown in the current state. |
4963 | assert(verCurrentState.thisInitialized != TIS_Bottom); |
4964 | |
4965 | // If the successor block's thisInit state is unknown, copy it from the current state. |
4966 | if (block->bbThisOnEntry() == TIS_Bottom) |
4967 | { |
4968 | *changed = true; |
4969 | verSetThisInit(block, verCurrentState.thisInitialized); |
4970 | } |
4971 | else if (verCurrentState.thisInitialized != block->bbThisOnEntry()) |
4972 | { |
4973 | if (block->bbThisOnEntry() != TIS_Top) |
4974 | { |
4975 | *changed = true; |
4976 | verSetThisInit(block, TIS_Top); |
4977 | |
4978 | if (block->bbFlags & BBF_FAILED_VERIFICATION) |
4979 | { |
4980 | // The block is bad. Control can flow through the block to any handler that catches the |
4981 | // verification exception, but the importer ignores bad blocks and therefore won't model |
4982 | // this flow in the normal way. To complete the merge into the bad block, the new state |
4983 | // needs to be manually pushed to the handlers that may be reached after the verification |
4984 | // exception occurs. |
4985 | // |
4986 | // Usually, the new state was already propagated to the relevant handlers while processing |
4987 | // the predecessors of the bad block. The exception is when the bad block is at the start |
4988 | // of a try region, meaning it is protected by additional handlers that do not protect its |
4989 | // predecessors. |
4990 | // |
4991 | if (block->hasTryIndex() && ((block->bbFlags & BBF_TRY_BEG) != 0)) |
4992 | { |
4993 | // Push TIS_Top to the handlers that protect the bad block. Note that this can cause |
4994 | // recursive calls back into this code path (if successors of the current bad block are |
4995 | // also bad blocks). |
4996 | // |
4997 | ThisInitState origTIS = verCurrentState.thisInitialized; |
4998 | verCurrentState.thisInitialized = TIS_Top; |
4999 | impVerifyEHBlock(block, true); |
5000 | verCurrentState.thisInitialized = origTIS; |
5001 | } |
5002 | } |
5003 | } |
5004 | } |
5005 | } |
5006 | else |
5007 | { |
5008 | assert(verCurrentState.thisInitialized == TIS_Bottom && block->bbThisOnEntry() == TIS_Bottom); |
5009 | } |
5010 | |
5011 | return TRUE; |
5012 | } |
5013 | |
5014 | /***************************************************************************** |
5015 | * 'logMsg' is true if a log message needs to be logged. false if the caller has |
5016 | * already logged it (presumably in a more detailed fashion than done here) |
5017 | * 'bVerificationException' is true for a verification exception, false for a |
5018 | * "call unauthorized by host" exception. |
5019 | */ |
5020 | |
5021 | void Compiler::verConvertBBToThrowVerificationException(BasicBlock* block DEBUGARG(bool logMsg)) |
5022 | { |
5023 | block->bbJumpKind = BBJ_THROW; |
5024 | block->bbFlags |= BBF_FAILED_VERIFICATION; |
5025 | |
5026 | impCurStmtOffsSet(block->bbCodeOffs); |
5027 | |
5028 | #ifdef DEBUG |
5029 | // we need this since BeginTreeList asserts otherwise |
5030 | impTreeList = impTreeLast = nullptr; |
5031 | block->bbFlags &= ~BBF_IMPORTED; |
5032 | |
5033 | if (logMsg) |
5034 | { |
5035 | JITLOG((LL_ERROR, "Verification failure: while compiling %s near IL offset %x..%xh \n" , info.compFullName, |
5036 | block->bbCodeOffs, block->bbCodeOffsEnd)); |
5037 | if (verbose) |
5038 | { |
5039 | printf("\n\nVerification failure: %s near IL %xh \n" , info.compFullName, block->bbCodeOffs); |
5040 | } |
5041 | } |
5042 | |
5043 | if (JitConfig.DebugBreakOnVerificationFailure()) |
5044 | { |
5045 | DebugBreak(); |
5046 | } |
5047 | #endif |
5048 | |
5049 | impBeginTreeList(); |
5050 | |
5051 | // if the stack is non-empty evaluate all the side-effects |
5052 | if (verCurrentState.esStackDepth > 0) |
5053 | { |
5054 | impEvalSideEffects(); |
5055 | } |
5056 | assert(verCurrentState.esStackDepth == 0); |
5057 | |
5058 | GenTree* op1 = |
5059 | gtNewHelperCallNode(CORINFO_HELP_VERIFICATION, TYP_VOID, gtNewArgList(gtNewIconNode(block->bbCodeOffs))); |
5060 | // verCurrentState.esStackDepth = 0; |
5061 | impAppendTree(op1, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); |
5062 | |
5063 | // The inliner is not able to handle methods that require throw block, so |
5064 | // make sure this methods never gets inlined. |
5065 | info.compCompHnd->setMethodAttribs(info.compMethodHnd, CORINFO_FLG_BAD_INLINEE); |
5066 | } |
5067 | |
5068 | /***************************************************************************** |
5069 | * |
5070 | */ |
5071 | void Compiler::verHandleVerificationFailure(BasicBlock* block DEBUGARG(bool logMsg)) |
5072 | |
5073 | { |
5074 | // In AMD64, for historical reasons involving design limitations of JIT64, the VM has a |
5075 | // slightly different mechanism in which it calls the JIT to perform IL verification: |
5076 | // in the case of transparent methods the VM calls for a predicate IsVerifiable() |
5077 | // that consists of calling the JIT with the IMPORT_ONLY flag and with the IL verify flag on. |
5078 | // If the JIT determines the method is not verifiable, it should raise the exception to the VM and let |
5079 | // it bubble up until reported by the runtime. Currently in RyuJIT, this method doesn't bubble |
5080 | // up the exception, instead it embeds a throw inside the offending basic block and lets this |
5081 | // to fail upon runtime of the jitted method. |
5082 | // |
5083 | // For AMD64 we don't want this behavior when the JIT has been called only for verification (i.e. |
5084 | // with the IMPORT_ONLY and IL Verification flag set) because this won't actually generate code, |
5085 | // just try to find out whether to fail this method before even actually jitting it. So, in case |
5086 | // we detect these two conditions, instead of generating a throw statement inside the offending |
5087 | // basic block, we immediately fail to JIT and notify the VM to make the IsVerifiable() predicate |
5088 | // to return false and make RyuJIT behave the same way JIT64 does. |
5089 | // |
5090 | // The rationale behind this workaround is to avoid modifying the VM and maintain compatibility between JIT64 and |
5091 | // RyuJIT for the time being until we completely replace JIT64. |
5092 | // TODO-ARM64-Cleanup: We probably want to actually modify the VM in the future to avoid the unnecesary two passes. |
5093 | |
5094 | // In AMD64 we must make sure we're behaving the same way as JIT64, meaning we should only raise the verification |
5095 | // exception if we are only importing and verifying. The method verNeedsVerification() can also modify the |
5096 | // tiVerificationNeeded flag in the case it determines it can 'skip verification' during importation and defer it |
5097 | // to a runtime check. That's why we must assert one or the other (since the flag tiVerificationNeeded can |
5098 | // be turned off during importation). |
5099 | CLANG_FORMAT_COMMENT_ANCHOR; |
5100 | |
5101 | #ifdef _TARGET_64BIT_ |
5102 | |
5103 | #ifdef DEBUG |
5104 | bool canSkipVerificationResult = |
5105 | info.compCompHnd->canSkipMethodVerification(info.compMethodHnd) != CORINFO_VERIFICATION_CANNOT_SKIP; |
5106 | assert(tiVerificationNeeded || canSkipVerificationResult); |
5107 | #endif // DEBUG |
5108 | |
5109 | // Add the non verifiable flag to the compiler |
5110 | if (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_IMPORT_ONLY)) |
5111 | { |
5112 | tiIsVerifiableCode = FALSE; |
5113 | } |
5114 | #endif //_TARGET_64BIT_ |
5115 | verResetCurrentState(block, &verCurrentState); |
5116 | verConvertBBToThrowVerificationException(block DEBUGARG(logMsg)); |
5117 | |
5118 | #ifdef DEBUG |
5119 | impNoteLastILoffs(); // Remember at which BC offset the tree was finished |
5120 | #endif // DEBUG |
5121 | } |
5122 | |
5123 | /******************************************************************************/ |
5124 | typeInfo Compiler::verMakeTypeInfo(CorInfoType ciType, CORINFO_CLASS_HANDLE clsHnd) |
5125 | { |
5126 | assert(ciType < CORINFO_TYPE_COUNT); |
5127 | |
5128 | typeInfo tiResult; |
5129 | switch (ciType) |
5130 | { |
5131 | case CORINFO_TYPE_STRING: |
5132 | case CORINFO_TYPE_CLASS: |
5133 | tiResult = verMakeTypeInfo(clsHnd); |
5134 | if (!tiResult.IsType(TI_REF)) |
5135 | { // type must be consistent with element type |
5136 | return typeInfo(); |
5137 | } |
5138 | break; |
5139 | |
5140 | #ifdef _TARGET_64BIT_ |
5141 | case CORINFO_TYPE_NATIVEINT: |
5142 | case CORINFO_TYPE_NATIVEUINT: |
5143 | if (clsHnd) |
5144 | { |
5145 | // If we have more precise information, use it |
5146 | return verMakeTypeInfo(clsHnd); |
5147 | } |
5148 | else |
5149 | { |
5150 | return typeInfo::nativeInt(); |
5151 | } |
5152 | break; |
5153 | #endif // _TARGET_64BIT_ |
5154 | |
5155 | case CORINFO_TYPE_VALUECLASS: |
5156 | case CORINFO_TYPE_REFANY: |
5157 | tiResult = verMakeTypeInfo(clsHnd); |
5158 | // type must be constant with element type; |
5159 | if (!tiResult.IsValueClass()) |
5160 | { |
5161 | return typeInfo(); |
5162 | } |
5163 | break; |
5164 | case CORINFO_TYPE_VAR: |
5165 | return verMakeTypeInfo(clsHnd); |
5166 | |
5167 | case CORINFO_TYPE_PTR: // for now, pointers are treated as an error |
5168 | case CORINFO_TYPE_VOID: |
5169 | return typeInfo(); |
5170 | break; |
5171 | |
5172 | case CORINFO_TYPE_BYREF: |
5173 | { |
5174 | CORINFO_CLASS_HANDLE childClassHandle; |
5175 | CorInfoType childType = info.compCompHnd->getChildType(clsHnd, &childClassHandle); |
5176 | return ByRef(verMakeTypeInfo(childType, childClassHandle)); |
5177 | } |
5178 | break; |
5179 | |
5180 | default: |
5181 | if (clsHnd) |
5182 | { // If we have more precise information, use it |
5183 | return typeInfo(TI_STRUCT, clsHnd); |
5184 | } |
5185 | else |
5186 | { |
5187 | return typeInfo(JITtype2tiType(ciType)); |
5188 | } |
5189 | } |
5190 | return tiResult; |
5191 | } |
5192 | |
5193 | /******************************************************************************/ |
5194 | |
5195 | typeInfo Compiler::verMakeTypeInfo(CORINFO_CLASS_HANDLE clsHnd, bool bashStructToRef /* = false */) |
5196 | { |
5197 | if (clsHnd == nullptr) |
5198 | { |
5199 | return typeInfo(); |
5200 | } |
5201 | |
5202 | // Byrefs should only occur in method and local signatures, which are accessed |
5203 | // using ICorClassInfo and ICorClassInfo.getChildType. |
5204 | // So findClass() and getClassAttribs() should not be called for byrefs |
5205 | |
5206 | if (JITtype2varType(info.compCompHnd->asCorInfoType(clsHnd)) == TYP_BYREF) |
5207 | { |
5208 | assert(!"Did findClass() return a Byref?" ); |
5209 | return typeInfo(); |
5210 | } |
5211 | |
5212 | unsigned attribs = info.compCompHnd->getClassAttribs(clsHnd); |
5213 | |
5214 | if (attribs & CORINFO_FLG_VALUECLASS) |
5215 | { |
5216 | CorInfoType t = info.compCompHnd->getTypeForPrimitiveValueClass(clsHnd); |
5217 | |
5218 | // Meta-data validation should ensure that CORINF_TYPE_BYREF should |
5219 | // not occur here, so we may want to change this to an assert instead. |
5220 | if (t == CORINFO_TYPE_VOID || t == CORINFO_TYPE_BYREF || t == CORINFO_TYPE_PTR) |
5221 | { |
5222 | return typeInfo(); |
5223 | } |
5224 | |
5225 | #ifdef _TARGET_64BIT_ |
5226 | if (t == CORINFO_TYPE_NATIVEINT || t == CORINFO_TYPE_NATIVEUINT) |
5227 | { |
5228 | return typeInfo::nativeInt(); |
5229 | } |
5230 | #endif // _TARGET_64BIT_ |
5231 | |
5232 | if (t != CORINFO_TYPE_UNDEF) |
5233 | { |
5234 | return (typeInfo(JITtype2tiType(t))); |
5235 | } |
5236 | else if (bashStructToRef) |
5237 | { |
5238 | return (typeInfo(TI_REF, clsHnd)); |
5239 | } |
5240 | else |
5241 | { |
5242 | return (typeInfo(TI_STRUCT, clsHnd)); |
5243 | } |
5244 | } |
5245 | else if (attribs & CORINFO_FLG_GENERIC_TYPE_VARIABLE) |
5246 | { |
5247 | // See comment in _typeInfo.h for why we do it this way. |
5248 | return (typeInfo(TI_REF, clsHnd, true)); |
5249 | } |
5250 | else |
5251 | { |
5252 | return (typeInfo(TI_REF, clsHnd)); |
5253 | } |
5254 | } |
5255 | |
5256 | /******************************************************************************/ |
5257 | BOOL Compiler::verIsSDArray(typeInfo ti) |
5258 | { |
5259 | if (ti.IsNullObjRef()) |
5260 | { // nulls are SD arrays |
5261 | return TRUE; |
5262 | } |
5263 | |
5264 | if (!ti.IsType(TI_REF)) |
5265 | { |
5266 | return FALSE; |
5267 | } |
5268 | |
5269 | if (!info.compCompHnd->isSDArray(ti.GetClassHandleForObjRef())) |
5270 | { |
5271 | return FALSE; |
5272 | } |
5273 | return TRUE; |
5274 | } |
5275 | |
5276 | /******************************************************************************/ |
5277 | /* Given 'arrayObjectType' which is an array type, fetch the element type. */ |
5278 | /* Returns an error type if anything goes wrong */ |
5279 | |
5280 | typeInfo Compiler::verGetArrayElemType(typeInfo arrayObjectType) |
5281 | { |
5282 | assert(!arrayObjectType.IsNullObjRef()); // you need to check for null explictly since that is a success case |
5283 | |
5284 | if (!verIsSDArray(arrayObjectType)) |
5285 | { |
5286 | return typeInfo(); |
5287 | } |
5288 | |
5289 | CORINFO_CLASS_HANDLE childClassHandle = nullptr; |
5290 | CorInfoType ciType = info.compCompHnd->getChildType(arrayObjectType.GetClassHandleForObjRef(), &childClassHandle); |
5291 | |
5292 | return verMakeTypeInfo(ciType, childClassHandle); |
5293 | } |
5294 | |
5295 | /***************************************************************************** |
5296 | */ |
5297 | typeInfo Compiler::verParseArgSigToTypeInfo(CORINFO_SIG_INFO* sig, CORINFO_ARG_LIST_HANDLE args) |
5298 | { |
5299 | CORINFO_CLASS_HANDLE classHandle; |
5300 | CorInfoType ciType = strip(info.compCompHnd->getArgType(sig, args, &classHandle)); |
5301 | |
5302 | var_types type = JITtype2varType(ciType); |
5303 | if (varTypeIsGC(type)) |
5304 | { |
5305 | // For efficiency, getArgType only returns something in classHandle for |
5306 | // value types. For other types that have addition type info, you |
5307 | // have to call back explicitly |
5308 | classHandle = info.compCompHnd->getArgClass(sig, args); |
5309 | if (!classHandle) |
5310 | { |
5311 | NO_WAY("Could not figure out Class specified in argument or local signature" ); |
5312 | } |
5313 | } |
5314 | |
5315 | return verMakeTypeInfo(ciType, classHandle); |
5316 | } |
5317 | |
5318 | /*****************************************************************************/ |
5319 | |
5320 | // This does the expensive check to figure out whether the method |
5321 | // needs to be verified. It is called only when we fail verification, |
5322 | // just before throwing the verification exception. |
5323 | |
5324 | BOOL Compiler::verNeedsVerification() |
5325 | { |
5326 | // If we have previously determined that verification is NOT needed |
5327 | // (for example in Compiler::compCompile), that means verification is really not needed. |
5328 | // Return the same decision we made before. |
5329 | // (Note: This literally means that tiVerificationNeeded can never go from 0 to 1.) |
5330 | |
5331 | if (!tiVerificationNeeded) |
5332 | { |
5333 | return tiVerificationNeeded; |
5334 | } |
5335 | |
5336 | assert(tiVerificationNeeded); |
5337 | |
5338 | // Ok, we haven't concluded that verification is NOT needed. Consult the EE now to |
5339 | // obtain the answer. |
5340 | CorInfoCanSkipVerificationResult canSkipVerificationResult = |
5341 | info.compCompHnd->canSkipMethodVerification(info.compMethodHnd); |
5342 | |
5343 | // canSkipVerification will return one of the following three values: |
5344 | // CORINFO_VERIFICATION_CANNOT_SKIP = 0, // Cannot skip verification during jit time. |
5345 | // CORINFO_VERIFICATION_CAN_SKIP = 1, // Can skip verification during jit time. |
5346 | // CORINFO_VERIFICATION_RUNTIME_CHECK = 2, // Skip verification during jit time, |
5347 | // but need to insert a callout to the VM to ask during runtime |
5348 | // whether to skip verification or not. |
5349 | |
5350 | // Set tiRuntimeCalloutNeeded if canSkipVerification() instructs us to insert a callout for runtime check |
5351 | if (canSkipVerificationResult == CORINFO_VERIFICATION_RUNTIME_CHECK) |
5352 | { |
5353 | tiRuntimeCalloutNeeded = true; |
5354 | } |
5355 | |
5356 | if (canSkipVerificationResult == CORINFO_VERIFICATION_DONT_JIT) |
5357 | { |
5358 | // Dev10 706080 - Testers don't like the assert, so just silence it |
5359 | // by not using the macros that invoke debugAssert. |
5360 | badCode(); |
5361 | } |
5362 | |
5363 | // When tiVerificationNeeded is true, JIT will do the verification during JIT time. |
5364 | // The following line means we will NOT do jit time verification if canSkipVerification |
5365 | // returns CORINFO_VERIFICATION_CAN_SKIP or CORINFO_VERIFICATION_RUNTIME_CHECK. |
5366 | tiVerificationNeeded = (canSkipVerificationResult == CORINFO_VERIFICATION_CANNOT_SKIP); |
5367 | return tiVerificationNeeded; |
5368 | } |
5369 | |
5370 | BOOL Compiler::verIsByRefLike(const typeInfo& ti) |
5371 | { |
5372 | if (ti.IsByRef()) |
5373 | { |
5374 | return TRUE; |
5375 | } |
5376 | if (!ti.IsType(TI_STRUCT)) |
5377 | { |
5378 | return FALSE; |
5379 | } |
5380 | return info.compCompHnd->getClassAttribs(ti.GetClassHandleForValueClass()) & CORINFO_FLG_CONTAINS_STACK_PTR; |
5381 | } |
5382 | |
5383 | BOOL Compiler::verIsSafeToReturnByRef(const typeInfo& ti) |
5384 | { |
5385 | if (ti.IsPermanentHomeByRef()) |
5386 | { |
5387 | return TRUE; |
5388 | } |
5389 | else |
5390 | { |
5391 | return FALSE; |
5392 | } |
5393 | } |
5394 | |
5395 | BOOL Compiler::verIsBoxable(const typeInfo& ti) |
5396 | { |
5397 | return (ti.IsPrimitiveType() || ti.IsObjRef() // includes boxed generic type variables |
5398 | || ti.IsUnboxedGenericTypeVar() || |
5399 | (ti.IsType(TI_STRUCT) && |
5400 | // exclude byreflike structs |
5401 | !(info.compCompHnd->getClassAttribs(ti.GetClassHandleForValueClass()) & CORINFO_FLG_CONTAINS_STACK_PTR))); |
5402 | } |
5403 | |
5404 | // Is it a boxed value type? |
5405 | bool Compiler::verIsBoxedValueType(typeInfo ti) |
5406 | { |
5407 | if (ti.GetType() == TI_REF) |
5408 | { |
5409 | CORINFO_CLASS_HANDLE clsHnd = ti.GetClassHandleForObjRef(); |
5410 | return !!eeIsValueClass(clsHnd); |
5411 | } |
5412 | else |
5413 | { |
5414 | return false; |
5415 | } |
5416 | } |
5417 | |
5418 | /***************************************************************************** |
5419 | * |
5420 | * Check if a TailCall is legal. |
5421 | */ |
5422 | |
5423 | bool Compiler::verCheckTailCallConstraint( |
5424 | OPCODE opcode, |
5425 | CORINFO_RESOLVED_TOKEN* pResolvedToken, |
5426 | CORINFO_RESOLVED_TOKEN* pConstrainedResolvedToken, // Is this a "constrained." call on a type parameter? |
5427 | bool speculative // If true, won't throw if verificatoin fails. Instead it will |
5428 | // return false to the caller. |
5429 | // If false, it will throw. |
5430 | ) |
5431 | { |
5432 | DWORD mflags; |
5433 | CORINFO_SIG_INFO sig; |
5434 | unsigned int popCount = 0; // we can't pop the stack since impImportCall needs it, so |
5435 | // this counter is used to keep track of how many items have been |
5436 | // virtually popped |
5437 | |
5438 | CORINFO_METHOD_HANDLE methodHnd = nullptr; |
5439 | CORINFO_CLASS_HANDLE methodClassHnd = nullptr; |
5440 | unsigned methodClassFlgs = 0; |
5441 | |
5442 | assert(impOpcodeIsCallOpcode(opcode)); |
5443 | |
5444 | if (compIsForInlining()) |
5445 | { |
5446 | return false; |
5447 | } |
5448 | |
5449 | // for calli, VerifyOrReturn that this is not a virtual method |
5450 | if (opcode == CEE_CALLI) |
5451 | { |
5452 | /* Get the call sig */ |
5453 | eeGetSig(pResolvedToken->token, pResolvedToken->tokenScope, pResolvedToken->tokenContext, &sig); |
5454 | |
5455 | // We don't know the target method, so we have to infer the flags, or |
5456 | // assume the worst-case. |
5457 | mflags = (sig.callConv & CORINFO_CALLCONV_HASTHIS) ? 0 : CORINFO_FLG_STATIC; |
5458 | } |
5459 | else |
5460 | { |
5461 | methodHnd = pResolvedToken->hMethod; |
5462 | |
5463 | mflags = info.compCompHnd->getMethodAttribs(methodHnd); |
5464 | |
5465 | // When verifying generic code we pair the method handle with its |
5466 | // owning class to get the exact method signature. |
5467 | methodClassHnd = pResolvedToken->hClass; |
5468 | assert(methodClassHnd); |
5469 | |
5470 | eeGetMethodSig(methodHnd, &sig, methodClassHnd); |
5471 | |
5472 | // opcode specific check |
5473 | methodClassFlgs = info.compCompHnd->getClassAttribs(methodClassHnd); |
5474 | } |
5475 | |
5476 | // We must have got the methodClassHnd if opcode is not CEE_CALLI |
5477 | assert((methodHnd != nullptr && methodClassHnd != nullptr) || opcode == CEE_CALLI); |
5478 | |
5479 | if ((sig.callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_VARARG) |
5480 | { |
5481 | eeGetCallSiteSig(pResolvedToken->token, pResolvedToken->tokenScope, pResolvedToken->tokenContext, &sig); |
5482 | } |
5483 | |
5484 | // check compatibility of the arguments |
5485 | unsigned int argCount; |
5486 | argCount = sig.numArgs; |
5487 | CORINFO_ARG_LIST_HANDLE args; |
5488 | args = sig.args; |
5489 | while (argCount--) |
5490 | { |
5491 | typeInfo tiDeclared = verParseArgSigToTypeInfo(&sig, args).NormaliseForStack(); |
5492 | |
5493 | // check that the argument is not a byref for tailcalls |
5494 | VerifyOrReturnSpeculative(!verIsByRefLike(tiDeclared), "tailcall on byrefs" , speculative); |
5495 | |
5496 | // For unsafe code, we might have parameters containing pointer to the stack location. |
5497 | // Disallow the tailcall for this kind. |
5498 | CORINFO_CLASS_HANDLE classHandle; |
5499 | CorInfoType ciType = strip(info.compCompHnd->getArgType(&sig, args, &classHandle)); |
5500 | VerifyOrReturnSpeculative(ciType != CORINFO_TYPE_PTR, "tailcall on CORINFO_TYPE_PTR" , speculative); |
5501 | |
5502 | args = info.compCompHnd->getArgNext(args); |
5503 | } |
5504 | |
5505 | // update popCount |
5506 | popCount += sig.numArgs; |
5507 | |
5508 | // check for 'this' which is on non-static methods, not called via NEWOBJ |
5509 | if (!(mflags & CORINFO_FLG_STATIC)) |
5510 | { |
5511 | // Always update the popCount. |
5512 | // This is crucial for the stack calculation to be correct. |
5513 | typeInfo tiThis = impStackTop(popCount).seTypeInfo; |
5514 | popCount++; |
5515 | |
5516 | if (opcode == CEE_CALLI) |
5517 | { |
5518 | // For CALLI, we don't know the methodClassHnd. Therefore, let's check the "this" object |
5519 | // on the stack. |
5520 | if (tiThis.IsValueClass()) |
5521 | { |
5522 | tiThis.MakeByRef(); |
5523 | } |
5524 | VerifyOrReturnSpeculative(!verIsByRefLike(tiThis), "byref in tailcall" , speculative); |
5525 | } |
5526 | else |
5527 | { |
5528 | // Check type compatibility of the this argument |
5529 | typeInfo tiDeclaredThis = verMakeTypeInfo(methodClassHnd); |
5530 | if (tiDeclaredThis.IsValueClass()) |
5531 | { |
5532 | tiDeclaredThis.MakeByRef(); |
5533 | } |
5534 | |
5535 | VerifyOrReturnSpeculative(!verIsByRefLike(tiDeclaredThis), "byref in tailcall" , speculative); |
5536 | } |
5537 | } |
5538 | |
5539 | // Tail calls on constrained calls should be illegal too: |
5540 | // when instantiated at a value type, a constrained call may pass the address of a stack allocated value |
5541 | VerifyOrReturnSpeculative(!pConstrainedResolvedToken, "byref in constrained tailcall" , speculative); |
5542 | |
5543 | // Get the exact view of the signature for an array method |
5544 | if (sig.retType != CORINFO_TYPE_VOID) |
5545 | { |
5546 | if (methodClassFlgs & CORINFO_FLG_ARRAY) |
5547 | { |
5548 | assert(opcode != CEE_CALLI); |
5549 | eeGetCallSiteSig(pResolvedToken->token, pResolvedToken->tokenScope, pResolvedToken->tokenContext, &sig); |
5550 | } |
5551 | } |
5552 | |
5553 | typeInfo tiCalleeRetType = verMakeTypeInfo(sig.retType, sig.retTypeClass); |
5554 | typeInfo tiCallerRetType = |
5555 | verMakeTypeInfo(info.compMethodInfo->args.retType, info.compMethodInfo->args.retTypeClass); |
5556 | |
5557 | // void return type gets morphed into the error type, so we have to treat them specially here |
5558 | if (sig.retType == CORINFO_TYPE_VOID) |
5559 | { |
5560 | VerifyOrReturnSpeculative(info.compMethodInfo->args.retType == CORINFO_TYPE_VOID, "tailcall return mismatch" , |
5561 | speculative); |
5562 | } |
5563 | else |
5564 | { |
5565 | VerifyOrReturnSpeculative(tiCompatibleWith(NormaliseForStack(tiCalleeRetType), |
5566 | NormaliseForStack(tiCallerRetType), true), |
5567 | "tailcall return mismatch" , speculative); |
5568 | } |
5569 | |
5570 | // for tailcall, stack must be empty |
5571 | VerifyOrReturnSpeculative(verCurrentState.esStackDepth == popCount, "stack non-empty on tailcall" , speculative); |
5572 | |
5573 | return true; // Yes, tailcall is legal |
5574 | } |
5575 | |
5576 | /***************************************************************************** |
5577 | * |
5578 | * Checks the IL verification rules for the call |
5579 | */ |
5580 | |
5581 | void Compiler::verVerifyCall(OPCODE opcode, |
5582 | CORINFO_RESOLVED_TOKEN* pResolvedToken, |
5583 | CORINFO_RESOLVED_TOKEN* pConstrainedResolvedToken, |
5584 | bool tailCall, |
5585 | bool readonlyCall, |
5586 | const BYTE* delegateCreateStart, |
5587 | const BYTE* codeAddr, |
5588 | CORINFO_CALL_INFO* callInfo DEBUGARG(const char* methodName)) |
5589 | { |
5590 | DWORD mflags; |
5591 | CORINFO_SIG_INFO* sig = nullptr; |
5592 | unsigned int popCount = 0; // we can't pop the stack since impImportCall needs it, so |
5593 | // this counter is used to keep track of how many items have been |
5594 | // virtually popped |
5595 | |
5596 | // for calli, VerifyOrReturn that this is not a virtual method |
5597 | if (opcode == CEE_CALLI) |
5598 | { |
5599 | Verify(false, "Calli not verifiable" ); |
5600 | return; |
5601 | } |
5602 | |
5603 | //<NICE> It would be nice to cache the rest of it, but eeFindMethod is the big ticket item. |
5604 | mflags = callInfo->verMethodFlags; |
5605 | |
5606 | sig = &callInfo->verSig; |
5607 | |
5608 | if ((sig->callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_VARARG) |
5609 | { |
5610 | eeGetCallSiteSig(pResolvedToken->token, pResolvedToken->tokenScope, pResolvedToken->tokenContext, sig); |
5611 | } |
5612 | |
5613 | // opcode specific check |
5614 | unsigned methodClassFlgs = callInfo->classFlags; |
5615 | switch (opcode) |
5616 | { |
5617 | case CEE_CALLVIRT: |
5618 | // cannot do callvirt on valuetypes |
5619 | VerifyOrReturn(!(methodClassFlgs & CORINFO_FLG_VALUECLASS), "callVirt on value class" ); |
5620 | VerifyOrReturn(sig->hasThis(), "CallVirt on static method" ); |
5621 | break; |
5622 | |
5623 | case CEE_NEWOBJ: |
5624 | { |
5625 | assert(!tailCall); // Importer should not allow this |
5626 | VerifyOrReturn((mflags & CORINFO_FLG_CONSTRUCTOR) && !(mflags & CORINFO_FLG_STATIC), |
5627 | "newobj must be on instance" ); |
5628 | |
5629 | if (methodClassFlgs & CORINFO_FLG_DELEGATE) |
5630 | { |
5631 | VerifyOrReturn(sig->numArgs == 2, "wrong number args to delegate ctor" ); |
5632 | typeInfo tiDeclaredObj = verParseArgSigToTypeInfo(sig, sig->args).NormaliseForStack(); |
5633 | typeInfo tiDeclaredFtn = |
5634 | verParseArgSigToTypeInfo(sig, info.compCompHnd->getArgNext(sig->args)).NormaliseForStack(); |
5635 | VerifyOrReturn(tiDeclaredFtn.IsNativeIntType(), "ftn arg needs to be a native int type" ); |
5636 | |
5637 | assert(popCount == 0); |
5638 | typeInfo tiActualObj = impStackTop(1).seTypeInfo; |
5639 | typeInfo tiActualFtn = impStackTop(0).seTypeInfo; |
5640 | |
5641 | VerifyOrReturn(tiActualFtn.IsMethod(), "delegate needs method as first arg" ); |
5642 | VerifyOrReturn(tiCompatibleWith(tiActualObj, tiDeclaredObj, true), "delegate object type mismatch" ); |
5643 | VerifyOrReturn(tiActualObj.IsNullObjRef() || tiActualObj.IsType(TI_REF), |
5644 | "delegate object type mismatch" ); |
5645 | |
5646 | CORINFO_CLASS_HANDLE objTypeHandle = |
5647 | tiActualObj.IsNullObjRef() ? nullptr : tiActualObj.GetClassHandleForObjRef(); |
5648 | |
5649 | // the method signature must be compatible with the delegate's invoke method |
5650 | |
5651 | // check that for virtual functions, the type of the object used to get the |
5652 | // ftn ptr is the same as the type of the object passed to the delegate ctor. |
5653 | // since this is a bit of work to determine in general, we pattern match stylized |
5654 | // code sequences |
5655 | |
5656 | // the delegate creation code check, which used to be done later, is now done here |
5657 | // so we can read delegateMethodRef directly from |
5658 | // from the preceding LDFTN or CEE_LDVIRTFN instruction sequence; |
5659 | // we then use it in our call to isCompatibleDelegate(). |
5660 | |
5661 | mdMemberRef delegateMethodRef = mdMemberRefNil; |
5662 | VerifyOrReturn(verCheckDelegateCreation(delegateCreateStart, codeAddr, delegateMethodRef), |
5663 | "must create delegates with certain IL" ); |
5664 | |
5665 | CORINFO_RESOLVED_TOKEN delegateResolvedToken; |
5666 | delegateResolvedToken.tokenContext = impTokenLookupContextHandle; |
5667 | delegateResolvedToken.tokenScope = info.compScopeHnd; |
5668 | delegateResolvedToken.token = delegateMethodRef; |
5669 | delegateResolvedToken.tokenType = CORINFO_TOKENKIND_Method; |
5670 | info.compCompHnd->resolveToken(&delegateResolvedToken); |
5671 | |
5672 | CORINFO_CALL_INFO delegateCallInfo; |
5673 | eeGetCallInfo(&delegateResolvedToken, nullptr /* constraint typeRef */, |
5674 | addVerifyFlag(CORINFO_CALLINFO_SECURITYCHECKS), &delegateCallInfo); |
5675 | |
5676 | BOOL isOpenDelegate = FALSE; |
5677 | VerifyOrReturn(info.compCompHnd->isCompatibleDelegate(objTypeHandle, delegateResolvedToken.hClass, |
5678 | tiActualFtn.GetMethod(), pResolvedToken->hClass, |
5679 | &isOpenDelegate), |
5680 | "function incompatible with delegate" ); |
5681 | |
5682 | // check the constraints on the target method |
5683 | VerifyOrReturn(info.compCompHnd->satisfiesClassConstraints(delegateResolvedToken.hClass), |
5684 | "delegate target has unsatisfied class constraints" ); |
5685 | VerifyOrReturn(info.compCompHnd->satisfiesMethodConstraints(delegateResolvedToken.hClass, |
5686 | tiActualFtn.GetMethod()), |
5687 | "delegate target has unsatisfied method constraints" ); |
5688 | |
5689 | // See ECMA spec section 1.8.1.5.2 (Delegating via instance dispatch) |
5690 | // for additional verification rules for delegates |
5691 | CORINFO_METHOD_HANDLE actualMethodHandle = tiActualFtn.GetMethod(); |
5692 | DWORD actualMethodAttribs = info.compCompHnd->getMethodAttribs(actualMethodHandle); |
5693 | if (impIsLDFTN_TOKEN(delegateCreateStart, codeAddr)) |
5694 | { |
5695 | |
5696 | if ((actualMethodAttribs & CORINFO_FLG_VIRTUAL) && ((actualMethodAttribs & CORINFO_FLG_FINAL) == 0) |
5697 | #ifdef DEBUG |
5698 | && StrictCheckForNonVirtualCallToVirtualMethod() |
5699 | #endif |
5700 | ) |
5701 | { |
5702 | if (info.compCompHnd->shouldEnforceCallvirtRestriction(info.compScopeHnd)) |
5703 | { |
5704 | VerifyOrReturn(tiActualObj.IsThisPtr() && lvaIsOriginalThisReadOnly() || |
5705 | verIsBoxedValueType(tiActualObj), |
5706 | "The 'this' parameter to the call must be either the calling method's " |
5707 | "'this' parameter or " |
5708 | "a boxed value type." ); |
5709 | } |
5710 | } |
5711 | } |
5712 | |
5713 | if (actualMethodAttribs & CORINFO_FLG_PROTECTED) |
5714 | { |
5715 | BOOL targetIsStatic = actualMethodAttribs & CORINFO_FLG_STATIC; |
5716 | |
5717 | Verify(targetIsStatic || !isOpenDelegate, |
5718 | "Unverifiable creation of an open instance delegate for a protected member." ); |
5719 | |
5720 | CORINFO_CLASS_HANDLE instanceClassHnd = (tiActualObj.IsNullObjRef() || targetIsStatic) |
5721 | ? info.compClassHnd |
5722 | : tiActualObj.GetClassHandleForObjRef(); |
5723 | |
5724 | // In the case of protected methods, it is a requirement that the 'this' |
5725 | // pointer be a subclass of the current context. Perform this check. |
5726 | Verify(info.compCompHnd->canAccessFamily(info.compMethodHnd, instanceClassHnd), |
5727 | "Accessing protected method through wrong type." ); |
5728 | } |
5729 | goto DONE_ARGS; |
5730 | } |
5731 | } |
5732 | // fall thru to default checks |
5733 | default: |
5734 | VerifyOrReturn(!(mflags & CORINFO_FLG_ABSTRACT), "method abstract" ); |
5735 | } |
5736 | VerifyOrReturn(!((mflags & CORINFO_FLG_CONSTRUCTOR) && (methodClassFlgs & CORINFO_FLG_DELEGATE)), |
5737 | "can only newobj a delegate constructor" ); |
5738 | |
5739 | // check compatibility of the arguments |
5740 | unsigned int argCount; |
5741 | argCount = sig->numArgs; |
5742 | CORINFO_ARG_LIST_HANDLE args; |
5743 | args = sig->args; |
5744 | while (argCount--) |
5745 | { |
5746 | typeInfo tiActual = impStackTop(popCount + argCount).seTypeInfo; |
5747 | |
5748 | typeInfo tiDeclared = verParseArgSigToTypeInfo(sig, args).NormaliseForStack(); |
5749 | VerifyOrReturn(tiCompatibleWith(tiActual, tiDeclared, true), "type mismatch" ); |
5750 | |
5751 | args = info.compCompHnd->getArgNext(args); |
5752 | } |
5753 | |
5754 | DONE_ARGS: |
5755 | |
5756 | // update popCount |
5757 | popCount += sig->numArgs; |
5758 | |
5759 | // check for 'this' which are is non-static methods, not called via NEWOBJ |
5760 | CORINFO_CLASS_HANDLE instanceClassHnd = info.compClassHnd; |
5761 | if (!(mflags & CORINFO_FLG_STATIC) && (opcode != CEE_NEWOBJ)) |
5762 | { |
5763 | typeInfo tiThis = impStackTop(popCount).seTypeInfo; |
5764 | popCount++; |
5765 | |
5766 | // If it is null, we assume we can access it (since it will AV shortly) |
5767 | // If it is anything but a reference class, there is no hierarchy, so |
5768 | // again, we don't need the precise instance class to compute 'protected' access |
5769 | if (tiThis.IsType(TI_REF)) |
5770 | { |
5771 | instanceClassHnd = tiThis.GetClassHandleForObjRef(); |
5772 | } |
5773 | |
5774 | // Check type compatibility of the this argument |
5775 | typeInfo tiDeclaredThis = verMakeTypeInfo(pResolvedToken->hClass); |
5776 | if (tiDeclaredThis.IsValueClass()) |
5777 | { |
5778 | tiDeclaredThis.MakeByRef(); |
5779 | } |
5780 | |
5781 | // If this is a call to the base class .ctor, set thisPtr Init for |
5782 | // this block. |
5783 | if (mflags & CORINFO_FLG_CONSTRUCTOR) |
5784 | { |
5785 | if (verTrackObjCtorInitState && tiThis.IsThisPtr() && |
5786 | verIsCallToInitThisPtr(info.compClassHnd, pResolvedToken->hClass)) |
5787 | { |
5788 | assert(verCurrentState.thisInitialized != |
5789 | TIS_Bottom); // This should never be the case just from the logic of the verifier. |
5790 | VerifyOrReturn(verCurrentState.thisInitialized == TIS_Uninit, |
5791 | "Call to base class constructor when 'this' is possibly initialized" ); |
5792 | // Otherwise, 'this' is now initialized. |
5793 | verCurrentState.thisInitialized = TIS_Init; |
5794 | tiThis.SetInitialisedObjRef(); |
5795 | } |
5796 | else |
5797 | { |
5798 | // We allow direct calls to value type constructors |
5799 | // NB: we have to check that the contents of tiThis is a value type, otherwise we could use a |
5800 | // constrained callvirt to illegally re-enter a .ctor on a value of reference type. |
5801 | VerifyOrReturn(tiThis.IsByRef() && DereferenceByRef(tiThis).IsValueClass(), |
5802 | "Bad call to a constructor" ); |
5803 | } |
5804 | } |
5805 | |
5806 | if (pConstrainedResolvedToken != nullptr) |
5807 | { |
5808 | VerifyOrReturn(tiThis.IsByRef(), "non-byref this type in constrained call" ); |
5809 | |
5810 | typeInfo tiConstraint = verMakeTypeInfo(pConstrainedResolvedToken->hClass); |
5811 | |
5812 | // We just dereference this and test for equality |
5813 | tiThis.DereferenceByRef(); |
5814 | VerifyOrReturn(typeInfo::AreEquivalent(tiThis, tiConstraint), |
5815 | "this type mismatch with constrained type operand" ); |
5816 | |
5817 | // Now pretend the this type is the boxed constrained type, for the sake of subsequent checks |
5818 | tiThis = typeInfo(TI_REF, pConstrainedResolvedToken->hClass); |
5819 | } |
5820 | |
5821 | // To support direct calls on readonly byrefs, just pretend tiDeclaredThis is readonly too |
5822 | if (tiDeclaredThis.IsByRef() && tiThis.IsReadonlyByRef()) |
5823 | { |
5824 | tiDeclaredThis.SetIsReadonlyByRef(); |
5825 | } |
5826 | |
5827 | VerifyOrReturn(tiCompatibleWith(tiThis, tiDeclaredThis, true), "this type mismatch" ); |
5828 | |
5829 | if (tiThis.IsByRef()) |
5830 | { |
5831 | // Find the actual type where the method exists (as opposed to what is declared |
5832 | // in the metadata). This is to prevent passing a byref as the "this" argument |
5833 | // while calling methods like System.ValueType.GetHashCode() which expect boxed objects. |
5834 | |
5835 | CORINFO_CLASS_HANDLE actualClassHnd = info.compCompHnd->getMethodClass(pResolvedToken->hMethod); |
5836 | VerifyOrReturn(eeIsValueClass(actualClassHnd), |
5837 | "Call to base type of valuetype (which is never a valuetype)" ); |
5838 | } |
5839 | |
5840 | // Rules for non-virtual call to a non-final virtual method: |
5841 | |
5842 | // Define: |
5843 | // The "this" pointer is considered to be "possibly written" if |
5844 | // 1. Its address have been taken (LDARGA 0) anywhere in the method. |
5845 | // (or) |
5846 | // 2. It has been stored to (STARG.0) anywhere in the method. |
5847 | |
5848 | // A non-virtual call to a non-final virtual method is only allowed if |
5849 | // 1. The this pointer passed to the callee is an instance of a boxed value type. |
5850 | // (or) |
5851 | // 2. The this pointer passed to the callee is the current method's this pointer. |
5852 | // (and) The current method's this pointer is not "possibly written". |
5853 | |
5854 | // Thus the rule is that if you assign to this ANYWHERE you can't make "base" calls to |
5855 | // virtual methods. (Luckily this does affect .ctors, since they are not virtual). |
5856 | // This is stronger that is strictly needed, but implementing a laxer rule is significantly |
5857 | // hard and more error prone. |
5858 | |
5859 | if (opcode == CEE_CALL && (mflags & CORINFO_FLG_VIRTUAL) && ((mflags & CORINFO_FLG_FINAL) == 0) |
5860 | #ifdef DEBUG |
5861 | && StrictCheckForNonVirtualCallToVirtualMethod() |
5862 | #endif |
5863 | ) |
5864 | { |
5865 | if (info.compCompHnd->shouldEnforceCallvirtRestriction(info.compScopeHnd)) |
5866 | { |
5867 | VerifyOrReturn( |
5868 | tiThis.IsThisPtr() && lvaIsOriginalThisReadOnly() || verIsBoxedValueType(tiThis), |
5869 | "The 'this' parameter to the call must be either the calling method's 'this' parameter or " |
5870 | "a boxed value type." ); |
5871 | } |
5872 | } |
5873 | } |
5874 | |
5875 | // check any constraints on the callee's class and type parameters |
5876 | VerifyOrReturn(info.compCompHnd->satisfiesClassConstraints(pResolvedToken->hClass), |
5877 | "method has unsatisfied class constraints" ); |
5878 | VerifyOrReturn(info.compCompHnd->satisfiesMethodConstraints(pResolvedToken->hClass, pResolvedToken->hMethod), |
5879 | "method has unsatisfied method constraints" ); |
5880 | |
5881 | if (mflags & CORINFO_FLG_PROTECTED) |
5882 | { |
5883 | VerifyOrReturn(info.compCompHnd->canAccessFamily(info.compMethodHnd, instanceClassHnd), |
5884 | "Can't access protected method" ); |
5885 | } |
5886 | |
5887 | // Get the exact view of the signature for an array method |
5888 | if (sig->retType != CORINFO_TYPE_VOID) |
5889 | { |
5890 | eeGetMethodSig(pResolvedToken->hMethod, sig, pResolvedToken->hClass); |
5891 | } |
5892 | |
5893 | // "readonly." prefixed calls only allowed for the Address operation on arrays. |
5894 | // The methods supported by array types are under the control of the EE |
5895 | // so we can trust that only the Address operation returns a byref. |
5896 | if (readonlyCall) |
5897 | { |
5898 | typeInfo tiCalleeRetType = verMakeTypeInfo(sig->retType, sig->retTypeClass); |
5899 | VerifyOrReturn((methodClassFlgs & CORINFO_FLG_ARRAY) && tiCalleeRetType.IsByRef(), |
5900 | "unexpected use of readonly prefix" ); |
5901 | } |
5902 | |
5903 | // Verify the tailcall |
5904 | if (tailCall) |
5905 | { |
5906 | verCheckTailCallConstraint(opcode, pResolvedToken, pConstrainedResolvedToken, false); |
5907 | } |
5908 | } |
5909 | |
5910 | /***************************************************************************** |
5911 | * Checks that a delegate creation is done using the following pattern: |
5912 | * dup |
5913 | * ldvirtftn targetMemberRef |
5914 | * OR |
5915 | * ldftn targetMemberRef |
5916 | * |
5917 | * 'delegateCreateStart' points at the last dup or ldftn in this basic block (null if |
5918 | * not in this basic block) |
5919 | * |
5920 | * targetMemberRef is read from the code sequence. |
5921 | * targetMemberRef is validated iff verificationNeeded. |
5922 | */ |
5923 | |
5924 | BOOL Compiler::verCheckDelegateCreation(const BYTE* delegateCreateStart, |
5925 | const BYTE* codeAddr, |
5926 | mdMemberRef& targetMemberRef) |
5927 | { |
5928 | if (impIsLDFTN_TOKEN(delegateCreateStart, codeAddr)) |
5929 | { |
5930 | targetMemberRef = getU4LittleEndian(&delegateCreateStart[2]); |
5931 | return TRUE; |
5932 | } |
5933 | else if (impIsDUP_LDVIRTFTN_TOKEN(delegateCreateStart, codeAddr)) |
5934 | { |
5935 | targetMemberRef = getU4LittleEndian(&delegateCreateStart[3]); |
5936 | return TRUE; |
5937 | } |
5938 | |
5939 | return FALSE; |
5940 | } |
5941 | |
5942 | typeInfo Compiler::verVerifySTIND(const typeInfo& tiTo, const typeInfo& value, const typeInfo& instrType) |
5943 | { |
5944 | Verify(!tiTo.IsReadonlyByRef(), "write to readonly byref" ); |
5945 | typeInfo ptrVal = verVerifyLDIND(tiTo, instrType); |
5946 | typeInfo normPtrVal = typeInfo(ptrVal).NormaliseForStack(); |
5947 | if (!tiCompatibleWith(value, normPtrVal, true)) |
5948 | { |
5949 | Verify(tiCompatibleWith(value, normPtrVal, true), "type mismatch" ); |
5950 | compUnsafeCastUsed = true; |
5951 | } |
5952 | return ptrVal; |
5953 | } |
5954 | |
5955 | typeInfo Compiler::verVerifyLDIND(const typeInfo& ptr, const typeInfo& instrType) |
5956 | { |
5957 | assert(!instrType.IsStruct()); |
5958 | |
5959 | typeInfo ptrVal; |
5960 | if (ptr.IsByRef()) |
5961 | { |
5962 | ptrVal = DereferenceByRef(ptr); |
5963 | if (instrType.IsObjRef() && !ptrVal.IsObjRef()) |
5964 | { |
5965 | Verify(false, "bad pointer" ); |
5966 | compUnsafeCastUsed = true; |
5967 | } |
5968 | else if (!instrType.IsObjRef() && !typeInfo::AreEquivalent(instrType, ptrVal)) |
5969 | { |
5970 | Verify(false, "pointer not consistent with instr" ); |
5971 | compUnsafeCastUsed = true; |
5972 | } |
5973 | } |
5974 | else |
5975 | { |
5976 | Verify(false, "pointer not byref" ); |
5977 | compUnsafeCastUsed = true; |
5978 | } |
5979 | |
5980 | return ptrVal; |
5981 | } |
5982 | |
5983 | // Verify that the field is used properly. 'tiThis' is NULL for statics, |
5984 | // 'fieldFlags' is the fields attributes, and mutator is TRUE if it is a |
5985 | // ld*flda or a st*fld. |
5986 | // 'enclosingClass' is given if we are accessing a field in some specific type. |
5987 | |
5988 | void Compiler::verVerifyField(CORINFO_RESOLVED_TOKEN* pResolvedToken, |
5989 | const CORINFO_FIELD_INFO& fieldInfo, |
5990 | const typeInfo* tiThis, |
5991 | BOOL mutator, |
5992 | BOOL allowPlainStructAsThis) |
5993 | { |
5994 | CORINFO_CLASS_HANDLE enclosingClass = pResolvedToken->hClass; |
5995 | unsigned fieldFlags = fieldInfo.fieldFlags; |
5996 | CORINFO_CLASS_HANDLE instanceClass = |
5997 | info.compClassHnd; // for statics, we imagine the instance is the current class. |
5998 | |
5999 | bool isStaticField = ((fieldFlags & CORINFO_FLG_FIELD_STATIC) != 0); |
6000 | if (mutator) |
6001 | { |
6002 | Verify(!(fieldFlags & CORINFO_FLG_FIELD_UNMANAGED), "mutating an RVA bases static" ); |
6003 | if ((fieldFlags & CORINFO_FLG_FIELD_FINAL)) |
6004 | { |
6005 | Verify((info.compFlags & CORINFO_FLG_CONSTRUCTOR) && enclosingClass == info.compClassHnd && |
6006 | info.compIsStatic == isStaticField, |
6007 | "bad use of initonly field (set or address taken)" ); |
6008 | } |
6009 | } |
6010 | |
6011 | if (tiThis == nullptr) |
6012 | { |
6013 | Verify(isStaticField, "used static opcode with non-static field" ); |
6014 | } |
6015 | else |
6016 | { |
6017 | typeInfo tThis = *tiThis; |
6018 | |
6019 | if (allowPlainStructAsThis && tThis.IsValueClass()) |
6020 | { |
6021 | tThis.MakeByRef(); |
6022 | } |
6023 | |
6024 | // If it is null, we assume we can access it (since it will AV shortly) |
6025 | // If it is anything but a refernce class, there is no hierarchy, so |
6026 | // again, we don't need the precise instance class to compute 'protected' access |
6027 | if (tiThis->IsType(TI_REF)) |
6028 | { |
6029 | instanceClass = tiThis->GetClassHandleForObjRef(); |
6030 | } |
6031 | |
6032 | // Note that even if the field is static, we require that the this pointer |
6033 | // satisfy the same constraints as a non-static field This happens to |
6034 | // be simpler and seems reasonable |
6035 | typeInfo tiDeclaredThis = verMakeTypeInfo(enclosingClass); |
6036 | if (tiDeclaredThis.IsValueClass()) |
6037 | { |
6038 | tiDeclaredThis.MakeByRef(); |
6039 | |
6040 | // we allow read-only tThis, on any field access (even stores!), because if the |
6041 | // class implementor wants to prohibit stores he should make the field private. |
6042 | // we do this by setting the read-only bit on the type we compare tThis to. |
6043 | tiDeclaredThis.SetIsReadonlyByRef(); |
6044 | } |
6045 | else if (verTrackObjCtorInitState && tThis.IsThisPtr()) |
6046 | { |
6047 | // Any field access is legal on "uninitialized" this pointers. |
6048 | // The easiest way to implement this is to simply set the |
6049 | // initialized bit for the duration of the type check on the |
6050 | // field access only. It does not change the state of the "this" |
6051 | // for the function as a whole. Note that the "tThis" is a copy |
6052 | // of the original "this" type (*tiThis) passed in. |
6053 | tThis.SetInitialisedObjRef(); |
6054 | } |
6055 | |
6056 | Verify(tiCompatibleWith(tThis, tiDeclaredThis, true), "this type mismatch" ); |
6057 | } |
6058 | |
6059 | // Presently the JIT does not check that we don't store or take the address of init-only fields |
6060 | // since we cannot guarantee their immutability and it is not a security issue. |
6061 | |
6062 | // check any constraints on the fields's class --- accessing the field might cause a class constructor to run. |
6063 | VerifyOrReturn(info.compCompHnd->satisfiesClassConstraints(enclosingClass), |
6064 | "field has unsatisfied class constraints" ); |
6065 | if (fieldFlags & CORINFO_FLG_FIELD_PROTECTED) |
6066 | { |
6067 | Verify(info.compCompHnd->canAccessFamily(info.compMethodHnd, instanceClass), |
6068 | "Accessing protected method through wrong type." ); |
6069 | } |
6070 | } |
6071 | |
6072 | void Compiler::verVerifyCond(const typeInfo& tiOp1, const typeInfo& tiOp2, unsigned opcode) |
6073 | { |
6074 | if (tiOp1.IsNumberType()) |
6075 | { |
6076 | #ifdef _TARGET_64BIT_ |
6077 | Verify(tiCompatibleWith(tiOp1, tiOp2, true), "Cond type mismatch" ); |
6078 | #else // _TARGET_64BIT |
6079 | // [10/17/2013] Consider changing this: to put on my verification lawyer hat, |
6080 | // this is non-conforming to the ECMA Spec: types don't have to be equivalent, |
6081 | // but compatible, since we can coalesce native int with int32 (see section III.1.5). |
6082 | Verify(typeInfo::AreEquivalent(tiOp1, tiOp2), "Cond type mismatch" ); |
6083 | #endif // !_TARGET_64BIT_ |
6084 | } |
6085 | else if (tiOp1.IsObjRef()) |
6086 | { |
6087 | switch (opcode) |
6088 | { |
6089 | case CEE_BEQ_S: |
6090 | case CEE_BEQ: |
6091 | case CEE_BNE_UN_S: |
6092 | case CEE_BNE_UN: |
6093 | case CEE_CEQ: |
6094 | case CEE_CGT_UN: |
6095 | break; |
6096 | default: |
6097 | Verify(FALSE, "Cond not allowed on object types" ); |
6098 | } |
6099 | Verify(tiOp2.IsObjRef(), "Cond type mismatch" ); |
6100 | } |
6101 | else if (tiOp1.IsByRef()) |
6102 | { |
6103 | Verify(tiOp2.IsByRef(), "Cond type mismatch" ); |
6104 | } |
6105 | else |
6106 | { |
6107 | Verify(tiOp1.IsMethod() && tiOp2.IsMethod(), "Cond type mismatch" ); |
6108 | } |
6109 | } |
6110 | |
6111 | void Compiler::verVerifyThisPtrInitialised() |
6112 | { |
6113 | if (verTrackObjCtorInitState) |
6114 | { |
6115 | Verify(verCurrentState.thisInitialized == TIS_Init, "this ptr is not initialized" ); |
6116 | } |
6117 | } |
6118 | |
6119 | BOOL Compiler::verIsCallToInitThisPtr(CORINFO_CLASS_HANDLE context, CORINFO_CLASS_HANDLE target) |
6120 | { |
6121 | // Either target == context, in this case calling an alternate .ctor |
6122 | // Or target is the immediate parent of context |
6123 | |
6124 | return ((target == context) || (target == info.compCompHnd->getParentType(context))); |
6125 | } |
6126 | |
6127 | GenTree* Compiler::impImportLdvirtftn(GenTree* thisPtr, |
6128 | CORINFO_RESOLVED_TOKEN* pResolvedToken, |
6129 | CORINFO_CALL_INFO* pCallInfo) |
6130 | { |
6131 | if ((pCallInfo->methodFlags & CORINFO_FLG_EnC) && !(pCallInfo->classFlags & CORINFO_FLG_INTERFACE)) |
6132 | { |
6133 | NO_WAY("Virtual call to a function added via EnC is not supported" ); |
6134 | } |
6135 | |
6136 | // CoreRT generic virtual method |
6137 | if ((pCallInfo->sig.sigInst.methInstCount != 0) && IsTargetAbi(CORINFO_CORERT_ABI)) |
6138 | { |
6139 | GenTree* runtimeMethodHandle = nullptr; |
6140 | if (pCallInfo->exactContextNeedsRuntimeLookup) |
6141 | { |
6142 | runtimeMethodHandle = |
6143 | impRuntimeLookupToTree(pResolvedToken, &pCallInfo->codePointerLookup, pCallInfo->hMethod); |
6144 | } |
6145 | else |
6146 | { |
6147 | runtimeMethodHandle = gtNewIconEmbMethHndNode(pResolvedToken->hMethod); |
6148 | } |
6149 | return gtNewHelperCallNode(CORINFO_HELP_GVMLOOKUP_FOR_SLOT, TYP_I_IMPL, |
6150 | gtNewArgList(thisPtr, runtimeMethodHandle)); |
6151 | } |
6152 | |
6153 | #ifdef FEATURE_READYTORUN_COMPILER |
6154 | if (opts.IsReadyToRun()) |
6155 | { |
6156 | if (!pCallInfo->exactContextNeedsRuntimeLookup) |
6157 | { |
6158 | GenTreeCall* call = |
6159 | gtNewHelperCallNode(CORINFO_HELP_READYTORUN_VIRTUAL_FUNC_PTR, TYP_I_IMPL, gtNewArgList(thisPtr)); |
6160 | |
6161 | call->setEntryPoint(pCallInfo->codePointerLookup.constLookup); |
6162 | |
6163 | return call; |
6164 | } |
6165 | |
6166 | // We need a runtime lookup. CoreRT has a ReadyToRun helper for that too. |
6167 | if (IsTargetAbi(CORINFO_CORERT_ABI)) |
6168 | { |
6169 | GenTree* ctxTree = getRuntimeContextTree(pCallInfo->codePointerLookup.lookupKind.runtimeLookupKind); |
6170 | |
6171 | return impReadyToRunHelperToTree(pResolvedToken, CORINFO_HELP_READYTORUN_GENERIC_HANDLE, TYP_I_IMPL, |
6172 | gtNewArgList(ctxTree), &pCallInfo->codePointerLookup.lookupKind); |
6173 | } |
6174 | } |
6175 | #endif |
6176 | |
6177 | // Get the exact descriptor for the static callsite |
6178 | GenTree* exactTypeDesc = impParentClassTokenToHandle(pResolvedToken); |
6179 | if (exactTypeDesc == nullptr) |
6180 | { // compDonotInline() |
6181 | return nullptr; |
6182 | } |
6183 | |
6184 | GenTree* exactMethodDesc = impTokenToHandle(pResolvedToken); |
6185 | if (exactMethodDesc == nullptr) |
6186 | { // compDonotInline() |
6187 | return nullptr; |
6188 | } |
6189 | |
6190 | GenTreeArgList* helpArgs = gtNewArgList(exactMethodDesc); |
6191 | |
6192 | helpArgs = gtNewListNode(exactTypeDesc, helpArgs); |
6193 | |
6194 | helpArgs = gtNewListNode(thisPtr, helpArgs); |
6195 | |
6196 | // Call helper function. This gets the target address of the final destination callsite. |
6197 | |
6198 | return gtNewHelperCallNode(CORINFO_HELP_VIRTUAL_FUNC_PTR, TYP_I_IMPL, helpArgs); |
6199 | } |
6200 | |
6201 | //------------------------------------------------------------------------ |
6202 | // impImportAndPushBox: build and import a value-type box |
6203 | // |
6204 | // Arguments: |
6205 | // pResolvedToken - resolved token from the box operation |
6206 | // |
6207 | // Return Value: |
6208 | // None. |
6209 | // |
6210 | // Side Effects: |
6211 | // The value to be boxed is popped from the stack, and a tree for |
6212 | // the boxed value is pushed. This method may create upstream |
6213 | // statements, spill side effecting trees, and create new temps. |
6214 | // |
6215 | // If importing an inlinee, we may also discover the inline must |
6216 | // fail. If so there is no new value pushed on the stack. Callers |
6217 | // should use CompDoNotInline after calling this method to see if |
6218 | // ongoing importation should be aborted. |
6219 | // |
6220 | // Notes: |
6221 | // Boxing of ref classes results in the same value as the value on |
6222 | // the top of the stack, so is handled inline in impImportBlockCode |
6223 | // for the CEE_BOX case. Only value or primitive type boxes make it |
6224 | // here. |
6225 | // |
6226 | // Boxing for nullable types is done via a helper call; boxing |
6227 | // of other value types is expanded inline or handled via helper |
6228 | // call, depending on the jit's codegen mode. |
6229 | // |
6230 | // When the jit is operating in size and time constrained modes, |
6231 | // using a helper call here can save jit time and code size. But it |
6232 | // also may inhibit cleanup optimizations that could have also had a |
6233 | // even greater benefit effect on code size and jit time. An optimal |
6234 | // strategy may need to peek ahead and see if it is easy to tell how |
6235 | // the box is being used. For now, we defer. |
6236 | |
6237 | void Compiler::impImportAndPushBox(CORINFO_RESOLVED_TOKEN* pResolvedToken) |
6238 | { |
6239 | // Spill any special side effects |
6240 | impSpillSpecialSideEff(); |
6241 | |
6242 | // Get get the expression to box from the stack. |
6243 | GenTree* op1 = nullptr; |
6244 | GenTree* op2 = nullptr; |
6245 | StackEntry se = impPopStack(); |
6246 | CORINFO_CLASS_HANDLE operCls = se.seTypeInfo.GetClassHandle(); |
6247 | GenTree* exprToBox = se.val; |
6248 | |
6249 | // Look at what helper we should use. |
6250 | CorInfoHelpFunc boxHelper = info.compCompHnd->getBoxHelper(pResolvedToken->hClass); |
6251 | |
6252 | // Determine what expansion to prefer. |
6253 | // |
6254 | // In size/time/debuggable constrained modes, the helper call |
6255 | // expansion for box is generally smaller and is preferred, unless |
6256 | // the value to box is a struct that comes from a call. In that |
6257 | // case the call can construct its return value directly into the |
6258 | // box payload, saving possibly some up-front zeroing. |
6259 | // |
6260 | // Currently primitive type boxes always get inline expanded. We may |
6261 | // want to do the same for small structs if they don't come from |
6262 | // calls and don't have GC pointers, since explicitly copying such |
6263 | // structs is cheap. |
6264 | JITDUMP("\nCompiler::impImportAndPushBox -- handling BOX(value class) via" ); |
6265 | bool canExpandInline = (boxHelper == CORINFO_HELP_BOX); |
6266 | bool optForSize = !exprToBox->IsCall() && (operCls != nullptr) && opts.OptimizationDisabled(); |
6267 | bool expandInline = canExpandInline && !optForSize; |
6268 | |
6269 | if (expandInline) |
6270 | { |
6271 | JITDUMP(" inline allocate/copy sequence\n" ); |
6272 | |
6273 | // we are doing 'normal' boxing. This means that we can inline the box operation |
6274 | // Box(expr) gets morphed into |
6275 | // temp = new(clsHnd) |
6276 | // cpobj(temp+4, expr, clsHnd) |
6277 | // push temp |
6278 | // The code paths differ slightly below for structs and primitives because |
6279 | // "cpobj" differs in these cases. In one case you get |
6280 | // impAssignStructPtr(temp+4, expr, clsHnd) |
6281 | // and the other you get |
6282 | // *(temp+4) = expr |
6283 | |
6284 | if (opts.OptimizationDisabled()) |
6285 | { |
6286 | // For minopts/debug code, try and minimize the total number |
6287 | // of box temps by reusing an existing temp when possible. |
6288 | if (impBoxTempInUse || impBoxTemp == BAD_VAR_NUM) |
6289 | { |
6290 | impBoxTemp = lvaGrabTemp(true DEBUGARG("Reusable Box Helper" )); |
6291 | } |
6292 | } |
6293 | else |
6294 | { |
6295 | // When optimizing, use a new temp for each box operation |
6296 | // since we then know the exact class of the box temp. |
6297 | impBoxTemp = lvaGrabTemp(true DEBUGARG("Single-def Box Helper" )); |
6298 | lvaTable[impBoxTemp].lvType = TYP_REF; |
6299 | lvaTable[impBoxTemp].lvSingleDef = 1; |
6300 | JITDUMP("Marking V%02u as a single def local\n" , impBoxTemp); |
6301 | const bool isExact = true; |
6302 | lvaSetClass(impBoxTemp, pResolvedToken->hClass, isExact); |
6303 | } |
6304 | |
6305 | // needs to stay in use until this box expression is appended |
6306 | // some other node. We approximate this by keeping it alive until |
6307 | // the opcode stack becomes empty |
6308 | impBoxTempInUse = true; |
6309 | |
6310 | const BOOL useParent = FALSE; |
6311 | op1 = gtNewAllocObjNode(pResolvedToken, useParent); |
6312 | if (op1 == nullptr) |
6313 | { |
6314 | return; |
6315 | } |
6316 | |
6317 | /* Remember that this basic block contains 'new' of an object, and so does this method */ |
6318 | compCurBB->bbFlags |= BBF_HAS_NEWOBJ; |
6319 | optMethodFlags |= OMF_HAS_NEWOBJ; |
6320 | |
6321 | GenTree* asg = gtNewTempAssign(impBoxTemp, op1); |
6322 | |
6323 | GenTree* asgStmt = impAppendTree(asg, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); |
6324 | |
6325 | op1 = gtNewLclvNode(impBoxTemp, TYP_REF); |
6326 | op2 = gtNewIconNode(TARGET_POINTER_SIZE, TYP_I_IMPL); |
6327 | op1 = gtNewOperNode(GT_ADD, TYP_BYREF, op1, op2); |
6328 | |
6329 | if (varTypeIsStruct(exprToBox)) |
6330 | { |
6331 | assert(info.compCompHnd->getClassSize(pResolvedToken->hClass) == info.compCompHnd->getClassSize(operCls)); |
6332 | op1 = impAssignStructPtr(op1, exprToBox, operCls, (unsigned)CHECK_SPILL_ALL); |
6333 | } |
6334 | else |
6335 | { |
6336 | var_types lclTyp = exprToBox->TypeGet(); |
6337 | if (lclTyp == TYP_BYREF) |
6338 | { |
6339 | lclTyp = TYP_I_IMPL; |
6340 | } |
6341 | CorInfoType jitType = info.compCompHnd->asCorInfoType(pResolvedToken->hClass); |
6342 | if (impIsPrimitive(jitType)) |
6343 | { |
6344 | lclTyp = JITtype2varType(jitType); |
6345 | } |
6346 | assert(genActualType(exprToBox->TypeGet()) == genActualType(lclTyp) || |
6347 | varTypeIsFloating(lclTyp) == varTypeIsFloating(exprToBox->TypeGet())); |
6348 | var_types srcTyp = exprToBox->TypeGet(); |
6349 | var_types dstTyp = lclTyp; |
6350 | |
6351 | if (srcTyp != dstTyp) |
6352 | { |
6353 | assert((varTypeIsFloating(srcTyp) && varTypeIsFloating(dstTyp)) || |
6354 | (varTypeIsIntegral(srcTyp) && varTypeIsIntegral(dstTyp))); |
6355 | exprToBox = gtNewCastNode(dstTyp, exprToBox, false, dstTyp); |
6356 | } |
6357 | op1 = gtNewAssignNode(gtNewOperNode(GT_IND, lclTyp, op1), exprToBox); |
6358 | } |
6359 | |
6360 | // Spill eval stack to flush out any pending side effects. |
6361 | impSpillSideEffects(true, (unsigned)CHECK_SPILL_ALL DEBUGARG("impImportAndPushBox" )); |
6362 | |
6363 | // Set up this copy as a second assignment. |
6364 | GenTree* copyStmt = impAppendTree(op1, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); |
6365 | |
6366 | op1 = gtNewLclvNode(impBoxTemp, TYP_REF); |
6367 | |
6368 | // Record that this is a "box" node and keep track of the matching parts. |
6369 | op1 = new (this, GT_BOX) GenTreeBox(TYP_REF, op1, asgStmt, copyStmt); |
6370 | |
6371 | // If it is a value class, mark the "box" node. We can use this information |
6372 | // to optimise several cases: |
6373 | // "box(x) == null" --> false |
6374 | // "(box(x)).CallAnInterfaceMethod(...)" --> "(&x).CallAValueTypeMethod" |
6375 | // "(box(x)).CallAnObjectMethod(...)" --> "(&x).CallAValueTypeMethod" |
6376 | |
6377 | op1->gtFlags |= GTF_BOX_VALUE; |
6378 | assert(op1->IsBoxedValue()); |
6379 | assert(asg->gtOper == GT_ASG); |
6380 | } |
6381 | else |
6382 | { |
6383 | // Don't optimize, just call the helper and be done with it. |
6384 | JITDUMP(" helper call because: %s\n" , canExpandInline ? "optimizing for size" : "nullable" ); |
6385 | assert(operCls != nullptr); |
6386 | |
6387 | // Ensure that the value class is restored |
6388 | op2 = impTokenToHandle(pResolvedToken, nullptr, TRUE /* mustRestoreHandle */); |
6389 | if (op2 == nullptr) |
6390 | { |
6391 | // We must be backing out of an inline. |
6392 | assert(compDonotInline()); |
6393 | return; |
6394 | } |
6395 | |
6396 | GenTreeArgList* args = gtNewArgList(op2, impGetStructAddr(exprToBox, operCls, (unsigned)CHECK_SPILL_ALL, true)); |
6397 | op1 = gtNewHelperCallNode(boxHelper, TYP_REF, args); |
6398 | } |
6399 | |
6400 | /* Push the result back on the stack, */ |
6401 | /* even if clsHnd is a value class we want the TI_REF */ |
6402 | typeInfo tiRetVal = typeInfo(TI_REF, info.compCompHnd->getTypeForBox(pResolvedToken->hClass)); |
6403 | impPushOnStack(op1, tiRetVal); |
6404 | } |
6405 | |
6406 | //------------------------------------------------------------------------ |
6407 | // impImportNewObjArray: Build and import `new` of multi-dimmensional array |
6408 | // |
6409 | // Arguments: |
6410 | // pResolvedToken - The CORINFO_RESOLVED_TOKEN that has been initialized |
6411 | // by a call to CEEInfo::resolveToken(). |
6412 | // pCallInfo - The CORINFO_CALL_INFO that has been initialized |
6413 | // by a call to CEEInfo::getCallInfo(). |
6414 | // |
6415 | // Assumptions: |
6416 | // The multi-dimensional array constructor arguments (array dimensions) are |
6417 | // pushed on the IL stack on entry to this method. |
6418 | // |
6419 | // Notes: |
6420 | // Multi-dimensional array constructors are imported as calls to a JIT |
6421 | // helper, not as regular calls. |
6422 | |
6423 | void Compiler::impImportNewObjArray(CORINFO_RESOLVED_TOKEN* pResolvedToken, CORINFO_CALL_INFO* pCallInfo) |
6424 | { |
6425 | GenTree* classHandle = impParentClassTokenToHandle(pResolvedToken); |
6426 | if (classHandle == nullptr) |
6427 | { // compDonotInline() |
6428 | return; |
6429 | } |
6430 | |
6431 | assert(pCallInfo->sig.numArgs); |
6432 | |
6433 | GenTree* node; |
6434 | GenTreeArgList* args; |
6435 | |
6436 | // |
6437 | // There are two different JIT helpers that can be used to allocate |
6438 | // multi-dimensional arrays: |
6439 | // |
6440 | // - CORINFO_HELP_NEW_MDARR - takes the array dimensions as varargs. |
6441 | // This variant is deprecated. It should be eventually removed. |
6442 | // |
6443 | // - CORINFO_HELP_NEW_MDARR_NONVARARG - takes the array dimensions as |
6444 | // pointer to block of int32s. This variant is more portable. |
6445 | // |
6446 | // The non-varargs helper is enabled for CoreRT only for now. Enabling this |
6447 | // unconditionally would require ReadyToRun version bump. |
6448 | // |
6449 | CLANG_FORMAT_COMMENT_ANCHOR; |
6450 | |
6451 | if (!opts.IsReadyToRun() || IsTargetAbi(CORINFO_CORERT_ABI)) |
6452 | { |
6453 | |
6454 | // Reuse the temp used to pass the array dimensions to avoid bloating |
6455 | // the stack frame in case there are multiple calls to multi-dim array |
6456 | // constructors within a single method. |
6457 | if (lvaNewObjArrayArgs == BAD_VAR_NUM) |
6458 | { |
6459 | lvaNewObjArrayArgs = lvaGrabTemp(false DEBUGARG("NewObjArrayArgs" )); |
6460 | lvaTable[lvaNewObjArrayArgs].lvType = TYP_BLK; |
6461 | lvaTable[lvaNewObjArrayArgs].lvExactSize = 0; |
6462 | } |
6463 | |
6464 | // Increase size of lvaNewObjArrayArgs to be the largest size needed to hold 'numArgs' integers |
6465 | // for our call to CORINFO_HELP_NEW_MDARR_NONVARARG. |
6466 | lvaTable[lvaNewObjArrayArgs].lvExactSize = |
6467 | max(lvaTable[lvaNewObjArrayArgs].lvExactSize, pCallInfo->sig.numArgs * sizeof(INT32)); |
6468 | |
6469 | // The side-effects may include allocation of more multi-dimensional arrays. Spill all side-effects |
6470 | // to ensure that the shared lvaNewObjArrayArgs local variable is only ever used to pass arguments |
6471 | // to one allocation at a time. |
6472 | impSpillSideEffects(true, (unsigned)CHECK_SPILL_ALL DEBUGARG("impImportNewObjArray" )); |
6473 | |
6474 | // |
6475 | // The arguments of the CORINFO_HELP_NEW_MDARR_NONVARARG helper are: |
6476 | // - Array class handle |
6477 | // - Number of dimension arguments |
6478 | // - Pointer to block of int32 dimensions - address of lvaNewObjArrayArgs temp. |
6479 | // |
6480 | |
6481 | node = gtNewLclvNode(lvaNewObjArrayArgs, TYP_BLK); |
6482 | node = gtNewOperNode(GT_ADDR, TYP_I_IMPL, node); |
6483 | |
6484 | // Pop dimension arguments from the stack one at a time and store it |
6485 | // into lvaNewObjArrayArgs temp. |
6486 | for (int i = pCallInfo->sig.numArgs - 1; i >= 0; i--) |
6487 | { |
6488 | GenTree* arg = impImplicitIorI4Cast(impPopStack().val, TYP_INT); |
6489 | |
6490 | GenTree* dest = gtNewLclvNode(lvaNewObjArrayArgs, TYP_BLK); |
6491 | dest = gtNewOperNode(GT_ADDR, TYP_I_IMPL, dest); |
6492 | dest = gtNewOperNode(GT_ADD, TYP_I_IMPL, dest, |
6493 | new (this, GT_CNS_INT) GenTreeIntCon(TYP_I_IMPL, sizeof(INT32) * i)); |
6494 | dest = gtNewOperNode(GT_IND, TYP_INT, dest); |
6495 | |
6496 | node = gtNewOperNode(GT_COMMA, node->TypeGet(), gtNewAssignNode(dest, arg), node); |
6497 | } |
6498 | |
6499 | args = gtNewArgList(node); |
6500 | |
6501 | // pass number of arguments to the helper |
6502 | args = gtNewListNode(gtNewIconNode(pCallInfo->sig.numArgs), args); |
6503 | |
6504 | args = gtNewListNode(classHandle, args); |
6505 | |
6506 | node = gtNewHelperCallNode(CORINFO_HELP_NEW_MDARR_NONVARARG, TYP_REF, args); |
6507 | } |
6508 | else |
6509 | { |
6510 | // |
6511 | // The varargs helper needs the type and method handles as last |
6512 | // and last-1 param (this is a cdecl call, so args will be |
6513 | // pushed in reverse order on the CPU stack) |
6514 | // |
6515 | |
6516 | args = gtNewArgList(classHandle); |
6517 | |
6518 | // pass number of arguments to the helper |
6519 | args = gtNewListNode(gtNewIconNode(pCallInfo->sig.numArgs), args); |
6520 | |
6521 | unsigned argFlags = 0; |
6522 | args = impPopList(pCallInfo->sig.numArgs, &pCallInfo->sig, args); |
6523 | |
6524 | node = gtNewHelperCallNode(CORINFO_HELP_NEW_MDARR, TYP_REF, args); |
6525 | |
6526 | // varargs, so we pop the arguments |
6527 | node->gtFlags |= GTF_CALL_POP_ARGS; |
6528 | |
6529 | #ifdef DEBUG |
6530 | // At the present time we don't track Caller pop arguments |
6531 | // that have GC references in them |
6532 | for (GenTreeArgList* temp = args; temp; temp = temp->Rest()) |
6533 | { |
6534 | assert(temp->Current()->gtType != TYP_REF); |
6535 | } |
6536 | #endif |
6537 | } |
6538 | |
6539 | node->gtFlags |= args->gtFlags & GTF_GLOB_EFFECT; |
6540 | node->gtCall.compileTimeHelperArgumentHandle = (CORINFO_GENERIC_HANDLE)pResolvedToken->hClass; |
6541 | |
6542 | // Remember that this basic block contains 'new' of a md array |
6543 | compCurBB->bbFlags |= BBF_HAS_NEWARRAY; |
6544 | |
6545 | impPushOnStack(node, typeInfo(TI_REF, pResolvedToken->hClass)); |
6546 | } |
6547 | |
6548 | GenTree* Compiler::impTransformThis(GenTree* thisPtr, |
6549 | CORINFO_RESOLVED_TOKEN* pConstrainedResolvedToken, |
6550 | CORINFO_THIS_TRANSFORM transform) |
6551 | { |
6552 | switch (transform) |
6553 | { |
6554 | case CORINFO_DEREF_THIS: |
6555 | { |
6556 | GenTree* obj = thisPtr; |
6557 | |
6558 | // This does a LDIND on the obj, which should be a byref. pointing to a ref |
6559 | impBashVarAddrsToI(obj); |
6560 | assert(genActualType(obj->gtType) == TYP_I_IMPL || obj->gtType == TYP_BYREF); |
6561 | CorInfoType constraintTyp = info.compCompHnd->asCorInfoType(pConstrainedResolvedToken->hClass); |
6562 | |
6563 | obj = gtNewOperNode(GT_IND, JITtype2varType(constraintTyp), obj); |
6564 | // ldind could point anywhere, example a boxed class static int |
6565 | obj->gtFlags |= (GTF_EXCEPT | GTF_GLOB_REF | GTF_IND_TGTANYWHERE); |
6566 | |
6567 | return obj; |
6568 | } |
6569 | |
6570 | case CORINFO_BOX_THIS: |
6571 | { |
6572 | // Constraint calls where there might be no |
6573 | // unboxed entry point require us to implement the call via helper. |
6574 | // These only occur when a possible target of the call |
6575 | // may have inherited an implementation of an interface |
6576 | // method from System.Object or System.ValueType. The EE does not provide us with |
6577 | // "unboxed" versions of these methods. |
6578 | |
6579 | GenTree* obj = thisPtr; |
6580 | |
6581 | assert(obj->TypeGet() == TYP_BYREF || obj->TypeGet() == TYP_I_IMPL); |
6582 | obj = gtNewObjNode(pConstrainedResolvedToken->hClass, obj); |
6583 | obj->gtFlags |= GTF_EXCEPT; |
6584 | |
6585 | CorInfoType jitTyp = info.compCompHnd->asCorInfoType(pConstrainedResolvedToken->hClass); |
6586 | var_types objType = JITtype2varType(jitTyp); |
6587 | if (impIsPrimitive(jitTyp)) |
6588 | { |
6589 | if (obj->OperIsBlk()) |
6590 | { |
6591 | obj->ChangeOperUnchecked(GT_IND); |
6592 | |
6593 | // Obj could point anywhere, example a boxed class static int |
6594 | obj->gtFlags |= GTF_IND_TGTANYWHERE; |
6595 | obj->gtOp.gtOp2 = nullptr; // must be zero for tree walkers |
6596 | } |
6597 | |
6598 | obj->gtType = JITtype2varType(jitTyp); |
6599 | assert(varTypeIsArithmetic(obj->gtType)); |
6600 | } |
6601 | |
6602 | // This pushes on the dereferenced byref |
6603 | // This is then used immediately to box. |
6604 | impPushOnStack(obj, verMakeTypeInfo(pConstrainedResolvedToken->hClass).NormaliseForStack()); |
6605 | |
6606 | // This pops off the byref-to-a-value-type remaining on the stack and |
6607 | // replaces it with a boxed object. |
6608 | // This is then used as the object to the virtual call immediately below. |
6609 | impImportAndPushBox(pConstrainedResolvedToken); |
6610 | if (compDonotInline()) |
6611 | { |
6612 | return nullptr; |
6613 | } |
6614 | |
6615 | obj = impPopStack().val; |
6616 | return obj; |
6617 | } |
6618 | case CORINFO_NO_THIS_TRANSFORM: |
6619 | default: |
6620 | return thisPtr; |
6621 | } |
6622 | } |
6623 | |
6624 | //------------------------------------------------------------------------ |
6625 | // impCanPInvokeInline: check whether PInvoke inlining should enabled in current method. |
6626 | // |
6627 | // Return Value: |
6628 | // true if PInvoke inlining should be enabled in current method, false otherwise |
6629 | // |
6630 | // Notes: |
6631 | // Checks a number of ambient conditions where we could pinvoke but choose not to |
6632 | |
6633 | bool Compiler::impCanPInvokeInline() |
6634 | { |
6635 | return getInlinePInvokeEnabled() && (!opts.compDbgCode) && (compCodeOpt() != SMALL_CODE) && |
6636 | (!opts.compNoPInvokeInlineCB) // profiler is preventing inline pinvoke |
6637 | ; |
6638 | } |
6639 | |
6640 | //------------------------------------------------------------------------ |
6641 | // impCanPInvokeInlineCallSite: basic legality checks using information |
6642 | // from a call to see if the call qualifies as an inline pinvoke. |
6643 | // |
6644 | // Arguments: |
6645 | // block - block contaning the call, or for inlinees, block |
6646 | // containing the call being inlined |
6647 | // |
6648 | // Return Value: |
6649 | // true if this call can legally qualify as an inline pinvoke, false otherwise |
6650 | // |
6651 | // Notes: |
6652 | // For runtimes that support exception handling interop there are |
6653 | // restrictions on using inline pinvoke in handler regions. |
6654 | // |
6655 | // * We have to disable pinvoke inlining inside of filters because |
6656 | // in case the main execution (i.e. in the try block) is inside |
6657 | // unmanaged code, we cannot reuse the inlined stub (we still need |
6658 | // the original state until we are in the catch handler) |
6659 | // |
6660 | // * We disable pinvoke inlining inside handlers since the GSCookie |
6661 | // is in the inlined Frame (see |
6662 | // CORINFO_EE_INFO::InlinedCallFrameInfo::offsetOfGSCookie), but |
6663 | // this would not protect framelets/return-address of handlers. |
6664 | // |
6665 | // These restrictions are currently also in place for CoreCLR but |
6666 | // can be relaxed when coreclr/#8459 is addressed. |
6667 | |
6668 | bool Compiler::impCanPInvokeInlineCallSite(BasicBlock* block) |
6669 | { |
6670 | if (block->hasHndIndex()) |
6671 | { |
6672 | return false; |
6673 | } |
6674 | |
6675 | // The remaining limitations do not apply to CoreRT |
6676 | if (IsTargetAbi(CORINFO_CORERT_ABI)) |
6677 | { |
6678 | return true; |
6679 | } |
6680 | |
6681 | #ifdef _TARGET_AMD64_ |
6682 | // On x64, we disable pinvoke inlining inside of try regions. |
6683 | // Here is the comment from JIT64 explaining why: |
6684 | // |
6685 | // [VSWhidbey: 611015] - because the jitted code links in the |
6686 | // Frame (instead of the stub) we rely on the Frame not being |
6687 | // 'active' until inside the stub. This normally happens by the |
6688 | // stub setting the return address pointer in the Frame object |
6689 | // inside the stub. On a normal return, the return address |
6690 | // pointer is zeroed out so the Frame can be safely re-used, but |
6691 | // if an exception occurs, nobody zeros out the return address |
6692 | // pointer. Thus if we re-used the Frame object, it would go |
6693 | // 'active' as soon as we link it into the Frame chain. |
6694 | // |
6695 | // Technically we only need to disable PInvoke inlining if we're |
6696 | // in a handler or if we're in a try body with a catch or |
6697 | // filter/except where other non-handler code in this method |
6698 | // might run and try to re-use the dirty Frame object. |
6699 | // |
6700 | // A desktop test case where this seems to matter is |
6701 | // jit\jit64\ebvts\mcpp\sources2\ijw\__clrcall\vector_ctor_dtor.02\deldtor_clr.exe |
6702 | if (block->hasTryIndex()) |
6703 | { |
6704 | return false; |
6705 | } |
6706 | #endif // _TARGET_AMD64_ |
6707 | |
6708 | return true; |
6709 | } |
6710 | |
6711 | //------------------------------------------------------------------------ |
6712 | // impCheckForPInvokeCall examine call to see if it is a pinvoke and if so |
6713 | // if it can be expressed as an inline pinvoke. |
6714 | // |
6715 | // Arguments: |
6716 | // call - tree for the call |
6717 | // methHnd - handle for the method being called (may be null) |
6718 | // sig - signature of the method being called |
6719 | // mflags - method flags for the method being called |
6720 | // block - block contaning the call, or for inlinees, block |
6721 | // containing the call being inlined |
6722 | // |
6723 | // Notes: |
6724 | // Sets GTF_CALL_M_PINVOKE on the call for pinvokes. |
6725 | // |
6726 | // Also sets GTF_CALL_UNMANAGED on call for inline pinvokes if the |
6727 | // call passes a combination of legality and profitabilty checks. |
6728 | // |
6729 | // If GTF_CALL_UNMANAGED is set, increments info.compCallUnmanaged |
6730 | |
6731 | void Compiler::impCheckForPInvokeCall( |
6732 | GenTreeCall* call, CORINFO_METHOD_HANDLE methHnd, CORINFO_SIG_INFO* sig, unsigned mflags, BasicBlock* block) |
6733 | { |
6734 | CorInfoUnmanagedCallConv unmanagedCallConv; |
6735 | |
6736 | // If VM flagged it as Pinvoke, flag the call node accordingly |
6737 | if ((mflags & CORINFO_FLG_PINVOKE) != 0) |
6738 | { |
6739 | call->gtCallMoreFlags |= GTF_CALL_M_PINVOKE; |
6740 | } |
6741 | |
6742 | if (methHnd) |
6743 | { |
6744 | if ((mflags & CORINFO_FLG_PINVOKE) == 0 || (mflags & CORINFO_FLG_NOSECURITYWRAP) == 0) |
6745 | { |
6746 | return; |
6747 | } |
6748 | |
6749 | unmanagedCallConv = info.compCompHnd->getUnmanagedCallConv(methHnd); |
6750 | } |
6751 | else |
6752 | { |
6753 | CorInfoCallConv callConv = CorInfoCallConv(sig->callConv & CORINFO_CALLCONV_MASK); |
6754 | if (callConv == CORINFO_CALLCONV_NATIVEVARARG) |
6755 | { |
6756 | // Used by the IL Stubs. |
6757 | callConv = CORINFO_CALLCONV_C; |
6758 | } |
6759 | static_assert_no_msg((unsigned)CORINFO_CALLCONV_C == (unsigned)CORINFO_UNMANAGED_CALLCONV_C); |
6760 | static_assert_no_msg((unsigned)CORINFO_CALLCONV_STDCALL == (unsigned)CORINFO_UNMANAGED_CALLCONV_STDCALL); |
6761 | static_assert_no_msg((unsigned)CORINFO_CALLCONV_THISCALL == (unsigned)CORINFO_UNMANAGED_CALLCONV_THISCALL); |
6762 | unmanagedCallConv = CorInfoUnmanagedCallConv(callConv); |
6763 | |
6764 | assert(!call->gtCallCookie); |
6765 | } |
6766 | |
6767 | if (unmanagedCallConv != CORINFO_UNMANAGED_CALLCONV_C && unmanagedCallConv != CORINFO_UNMANAGED_CALLCONV_STDCALL && |
6768 | unmanagedCallConv != CORINFO_UNMANAGED_CALLCONV_THISCALL) |
6769 | { |
6770 | return; |
6771 | } |
6772 | optNativeCallCount++; |
6773 | |
6774 | if (methHnd == nullptr && (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_IL_STUB) || IsTargetAbi(CORINFO_CORERT_ABI))) |
6775 | { |
6776 | // PInvoke in CoreRT ABI must be always inlined. Non-inlineable CALLI cases have been |
6777 | // converted to regular method calls earlier using convertPInvokeCalliToCall. |
6778 | |
6779 | // PInvoke CALLI in IL stubs must be inlined |
6780 | } |
6781 | else |
6782 | { |
6783 | // Check legality |
6784 | if (!impCanPInvokeInlineCallSite(block)) |
6785 | { |
6786 | return; |
6787 | } |
6788 | |
6789 | // Legal PInvoke CALL in PInvoke IL stubs must be inlined to avoid infinite recursive |
6790 | // inlining in CoreRT. Skip the ambient conditions checks and profitability checks. |
6791 | if (!IsTargetAbi(CORINFO_CORERT_ABI) || (info.compFlags & CORINFO_FLG_PINVOKE) == 0) |
6792 | { |
6793 | if (!impCanPInvokeInline()) |
6794 | { |
6795 | return; |
6796 | } |
6797 | |
6798 | // Size-speed tradeoff: don't use inline pinvoke at rarely |
6799 | // executed call sites. The non-inline version is more |
6800 | // compact. |
6801 | if (block->isRunRarely()) |
6802 | { |
6803 | return; |
6804 | } |
6805 | } |
6806 | |
6807 | // The expensive check should be last |
6808 | if (info.compCompHnd->pInvokeMarshalingRequired(methHnd, sig)) |
6809 | { |
6810 | return; |
6811 | } |
6812 | } |
6813 | |
6814 | JITLOG((LL_INFO1000000, "\nInline a CALLI PINVOKE call from method %s" , info.compFullName)); |
6815 | |
6816 | call->gtFlags |= GTF_CALL_UNMANAGED; |
6817 | info.compCallUnmanaged++; |
6818 | |
6819 | // AMD64 convention is same for native and managed |
6820 | if (unmanagedCallConv == CORINFO_UNMANAGED_CALLCONV_C) |
6821 | { |
6822 | call->gtFlags |= GTF_CALL_POP_ARGS; |
6823 | } |
6824 | |
6825 | if (unmanagedCallConv == CORINFO_UNMANAGED_CALLCONV_THISCALL) |
6826 | { |
6827 | call->gtCallMoreFlags |= GTF_CALL_M_UNMGD_THISCALL; |
6828 | } |
6829 | } |
6830 | |
6831 | GenTreeCall* Compiler::impImportIndirectCall(CORINFO_SIG_INFO* sig, IL_OFFSETX ilOffset) |
6832 | { |
6833 | var_types callRetTyp = JITtype2varType(sig->retType); |
6834 | |
6835 | /* The function pointer is on top of the stack - It may be a |
6836 | * complex expression. As it is evaluated after the args, |
6837 | * it may cause registered args to be spilled. Simply spill it. |
6838 | */ |
6839 | |
6840 | // Ignore this trivial case. |
6841 | if (impStackTop().val->gtOper != GT_LCL_VAR) |
6842 | { |
6843 | impSpillStackEntry(verCurrentState.esStackDepth - 1, |
6844 | BAD_VAR_NUM DEBUGARG(false) DEBUGARG("impImportIndirectCall" )); |
6845 | } |
6846 | |
6847 | /* Get the function pointer */ |
6848 | |
6849 | GenTree* fptr = impPopStack().val; |
6850 | |
6851 | // The function pointer is typically a sized to match the target pointer size |
6852 | // However, stubgen IL optimization can change LDC.I8 to LDC.I4 |
6853 | // See ILCodeStream::LowerOpcode |
6854 | assert(genActualType(fptr->gtType) == TYP_I_IMPL || genActualType(fptr->gtType) == TYP_INT); |
6855 | |
6856 | #ifdef DEBUG |
6857 | // This temporary must never be converted to a double in stress mode, |
6858 | // because that can introduce a call to the cast helper after the |
6859 | // arguments have already been evaluated. |
6860 | |
6861 | if (fptr->OperGet() == GT_LCL_VAR) |
6862 | { |
6863 | lvaTable[fptr->gtLclVarCommon.gtLclNum].lvKeepType = 1; |
6864 | } |
6865 | #endif |
6866 | |
6867 | /* Create the call node */ |
6868 | |
6869 | GenTreeCall* call = gtNewIndCallNode(fptr, callRetTyp, nullptr, ilOffset); |
6870 | |
6871 | call->gtFlags |= GTF_EXCEPT | (fptr->gtFlags & GTF_GLOB_EFFECT); |
6872 | |
6873 | return call; |
6874 | } |
6875 | |
6876 | /*****************************************************************************/ |
6877 | |
6878 | void Compiler::impPopArgsForUnmanagedCall(GenTree* call, CORINFO_SIG_INFO* sig) |
6879 | { |
6880 | assert(call->gtFlags & GTF_CALL_UNMANAGED); |
6881 | |
6882 | /* Since we push the arguments in reverse order (i.e. right -> left) |
6883 | * spill any side effects from the stack |
6884 | * |
6885 | * OBS: If there is only one side effect we do not need to spill it |
6886 | * thus we have to spill all side-effects except last one |
6887 | */ |
6888 | |
6889 | unsigned lastLevelWithSideEffects = UINT_MAX; |
6890 | |
6891 | unsigned argsToReverse = sig->numArgs; |
6892 | |
6893 | // For "thiscall", the first argument goes in a register. Since its |
6894 | // order does not need to be changed, we do not need to spill it |
6895 | |
6896 | if (call->gtCall.gtCallMoreFlags & GTF_CALL_M_UNMGD_THISCALL) |
6897 | { |
6898 | assert(argsToReverse); |
6899 | argsToReverse--; |
6900 | } |
6901 | |
6902 | #ifndef _TARGET_X86_ |
6903 | // Don't reverse args on ARM or x64 - first four args always placed in regs in order |
6904 | argsToReverse = 0; |
6905 | #endif |
6906 | |
6907 | for (unsigned level = verCurrentState.esStackDepth - argsToReverse; level < verCurrentState.esStackDepth; level++) |
6908 | { |
6909 | if (verCurrentState.esStack[level].val->gtFlags & GTF_ORDER_SIDEEFF) |
6910 | { |
6911 | assert(lastLevelWithSideEffects == UINT_MAX); |
6912 | |
6913 | impSpillStackEntry(level, |
6914 | BAD_VAR_NUM DEBUGARG(false) DEBUGARG("impPopArgsForUnmanagedCall - other side effect" )); |
6915 | } |
6916 | else if (verCurrentState.esStack[level].val->gtFlags & GTF_SIDE_EFFECT) |
6917 | { |
6918 | if (lastLevelWithSideEffects != UINT_MAX) |
6919 | { |
6920 | /* We had a previous side effect - must spill it */ |
6921 | impSpillStackEntry(lastLevelWithSideEffects, |
6922 | BAD_VAR_NUM DEBUGARG(false) DEBUGARG("impPopArgsForUnmanagedCall - side effect" )); |
6923 | |
6924 | /* Record the level for the current side effect in case we will spill it */ |
6925 | lastLevelWithSideEffects = level; |
6926 | } |
6927 | else |
6928 | { |
6929 | /* This is the first side effect encountered - record its level */ |
6930 | |
6931 | lastLevelWithSideEffects = level; |
6932 | } |
6933 | } |
6934 | } |
6935 | |
6936 | /* The argument list is now "clean" - no out-of-order side effects |
6937 | * Pop the argument list in reverse order */ |
6938 | |
6939 | GenTree* args = call->gtCall.gtCallArgs = impPopRevList(sig->numArgs, sig, sig->numArgs - argsToReverse); |
6940 | |
6941 | if (call->gtCall.gtCallMoreFlags & GTF_CALL_M_UNMGD_THISCALL) |
6942 | { |
6943 | GenTree* thisPtr = args->Current(); |
6944 | impBashVarAddrsToI(thisPtr); |
6945 | assert(thisPtr->TypeGet() == TYP_I_IMPL || thisPtr->TypeGet() == TYP_BYREF); |
6946 | } |
6947 | |
6948 | if (args) |
6949 | { |
6950 | call->gtFlags |= args->gtFlags & GTF_GLOB_EFFECT; |
6951 | } |
6952 | } |
6953 | |
6954 | //------------------------------------------------------------------------ |
6955 | // impInitClass: Build a node to initialize the class before accessing the |
6956 | // field if necessary |
6957 | // |
6958 | // Arguments: |
6959 | // pResolvedToken - The CORINFO_RESOLVED_TOKEN that has been initialized |
6960 | // by a call to CEEInfo::resolveToken(). |
6961 | // |
6962 | // Return Value: If needed, a pointer to the node that will perform the class |
6963 | // initializtion. Otherwise, nullptr. |
6964 | // |
6965 | |
6966 | GenTree* Compiler::impInitClass(CORINFO_RESOLVED_TOKEN* pResolvedToken) |
6967 | { |
6968 | CorInfoInitClassResult initClassResult = |
6969 | info.compCompHnd->initClass(pResolvedToken->hField, info.compMethodHnd, impTokenLookupContextHandle); |
6970 | |
6971 | if ((initClassResult & CORINFO_INITCLASS_USE_HELPER) == 0) |
6972 | { |
6973 | return nullptr; |
6974 | } |
6975 | BOOL runtimeLookup; |
6976 | |
6977 | GenTree* node = impParentClassTokenToHandle(pResolvedToken, &runtimeLookup); |
6978 | |
6979 | if (node == nullptr) |
6980 | { |
6981 | assert(compDonotInline()); |
6982 | return nullptr; |
6983 | } |
6984 | |
6985 | if (runtimeLookup) |
6986 | { |
6987 | node = gtNewHelperCallNode(CORINFO_HELP_INITCLASS, TYP_VOID, gtNewArgList(node)); |
6988 | } |
6989 | else |
6990 | { |
6991 | // Call the shared non gc static helper, as its the fastest |
6992 | node = fgGetSharedCCtor(pResolvedToken->hClass); |
6993 | } |
6994 | |
6995 | return node; |
6996 | } |
6997 | |
6998 | GenTree* Compiler::impImportStaticReadOnlyField(void* fldAddr, var_types lclTyp) |
6999 | { |
7000 | GenTree* op1 = nullptr; |
7001 | |
7002 | switch (lclTyp) |
7003 | { |
7004 | int ival; |
7005 | __int64 lval; |
7006 | double dval; |
7007 | |
7008 | case TYP_BOOL: |
7009 | ival = *((bool*)fldAddr); |
7010 | goto IVAL_COMMON; |
7011 | |
7012 | case TYP_BYTE: |
7013 | ival = *((signed char*)fldAddr); |
7014 | goto IVAL_COMMON; |
7015 | |
7016 | case TYP_UBYTE: |
7017 | ival = *((unsigned char*)fldAddr); |
7018 | goto IVAL_COMMON; |
7019 | |
7020 | case TYP_SHORT: |
7021 | ival = *((short*)fldAddr); |
7022 | goto IVAL_COMMON; |
7023 | |
7024 | case TYP_USHORT: |
7025 | ival = *((unsigned short*)fldAddr); |
7026 | goto IVAL_COMMON; |
7027 | |
7028 | case TYP_UINT: |
7029 | case TYP_INT: |
7030 | ival = *((int*)fldAddr); |
7031 | IVAL_COMMON: |
7032 | op1 = gtNewIconNode(ival); |
7033 | break; |
7034 | |
7035 | case TYP_LONG: |
7036 | case TYP_ULONG: |
7037 | lval = *((__int64*)fldAddr); |
7038 | op1 = gtNewLconNode(lval); |
7039 | break; |
7040 | |
7041 | case TYP_FLOAT: |
7042 | dval = *((float*)fldAddr); |
7043 | op1 = gtNewDconNode(dval); |
7044 | op1->gtType = TYP_FLOAT; |
7045 | break; |
7046 | |
7047 | case TYP_DOUBLE: |
7048 | dval = *((double*)fldAddr); |
7049 | op1 = gtNewDconNode(dval); |
7050 | break; |
7051 | |
7052 | default: |
7053 | assert(!"Unexpected lclTyp" ); |
7054 | break; |
7055 | } |
7056 | |
7057 | return op1; |
7058 | } |
7059 | |
7060 | GenTree* Compiler::impImportStaticFieldAccess(CORINFO_RESOLVED_TOKEN* pResolvedToken, |
7061 | CORINFO_ACCESS_FLAGS access, |
7062 | CORINFO_FIELD_INFO* pFieldInfo, |
7063 | var_types lclTyp) |
7064 | { |
7065 | GenTree* op1; |
7066 | |
7067 | switch (pFieldInfo->fieldAccessor) |
7068 | { |
7069 | case CORINFO_FIELD_STATIC_GENERICS_STATIC_HELPER: |
7070 | { |
7071 | assert(!compIsForInlining()); |
7072 | |
7073 | // We first call a special helper to get the statics base pointer |
7074 | op1 = impParentClassTokenToHandle(pResolvedToken); |
7075 | |
7076 | // compIsForInlining() is false so we should not neve get NULL here |
7077 | assert(op1 != nullptr); |
7078 | |
7079 | var_types type = TYP_BYREF; |
7080 | |
7081 | switch (pFieldInfo->helper) |
7082 | { |
7083 | case CORINFO_HELP_GETGENERICS_NONGCTHREADSTATIC_BASE: |
7084 | type = TYP_I_IMPL; |
7085 | break; |
7086 | case CORINFO_HELP_GETGENERICS_GCSTATIC_BASE: |
7087 | case CORINFO_HELP_GETGENERICS_NONGCSTATIC_BASE: |
7088 | case CORINFO_HELP_GETGENERICS_GCTHREADSTATIC_BASE: |
7089 | break; |
7090 | default: |
7091 | assert(!"unknown generic statics helper" ); |
7092 | break; |
7093 | } |
7094 | |
7095 | op1 = gtNewHelperCallNode(pFieldInfo->helper, type, gtNewArgList(op1)); |
7096 | |
7097 | FieldSeqNode* fs = GetFieldSeqStore()->CreateSingleton(pResolvedToken->hField); |
7098 | op1 = gtNewOperNode(GT_ADD, type, op1, |
7099 | new (this, GT_CNS_INT) GenTreeIntCon(TYP_I_IMPL, pFieldInfo->offset, fs)); |
7100 | } |
7101 | break; |
7102 | |
7103 | case CORINFO_FIELD_STATIC_SHARED_STATIC_HELPER: |
7104 | { |
7105 | #ifdef FEATURE_READYTORUN_COMPILER |
7106 | if (opts.IsReadyToRun()) |
7107 | { |
7108 | unsigned callFlags = 0; |
7109 | |
7110 | if (info.compCompHnd->getClassAttribs(pResolvedToken->hClass) & CORINFO_FLG_BEFOREFIELDINIT) |
7111 | { |
7112 | callFlags |= GTF_CALL_HOISTABLE; |
7113 | } |
7114 | |
7115 | op1 = gtNewHelperCallNode(CORINFO_HELP_READYTORUN_STATIC_BASE, TYP_BYREF); |
7116 | op1->gtFlags |= callFlags; |
7117 | |
7118 | op1->gtCall.setEntryPoint(pFieldInfo->fieldLookup); |
7119 | } |
7120 | else |
7121 | #endif |
7122 | { |
7123 | op1 = fgGetStaticsCCtorHelper(pResolvedToken->hClass, pFieldInfo->helper); |
7124 | } |
7125 | |
7126 | { |
7127 | FieldSeqNode* fs = GetFieldSeqStore()->CreateSingleton(pResolvedToken->hField); |
7128 | op1 = gtNewOperNode(GT_ADD, op1->TypeGet(), op1, |
7129 | new (this, GT_CNS_INT) GenTreeIntCon(TYP_INT, pFieldInfo->offset, fs)); |
7130 | } |
7131 | break; |
7132 | } |
7133 | |
7134 | case CORINFO_FIELD_STATIC_READYTORUN_HELPER: |
7135 | { |
7136 | #ifdef FEATURE_READYTORUN_COMPILER |
7137 | noway_assert(opts.IsReadyToRun()); |
7138 | CORINFO_LOOKUP_KIND kind = info.compCompHnd->getLocationOfThisType(info.compMethodHnd); |
7139 | assert(kind.needsRuntimeLookup); |
7140 | |
7141 | GenTree* ctxTree = getRuntimeContextTree(kind.runtimeLookupKind); |
7142 | GenTreeArgList* args = gtNewArgList(ctxTree); |
7143 | |
7144 | unsigned callFlags = 0; |
7145 | |
7146 | if (info.compCompHnd->getClassAttribs(pResolvedToken->hClass) & CORINFO_FLG_BEFOREFIELDINIT) |
7147 | { |
7148 | callFlags |= GTF_CALL_HOISTABLE; |
7149 | } |
7150 | var_types type = TYP_BYREF; |
7151 | op1 = gtNewHelperCallNode(CORINFO_HELP_READYTORUN_GENERIC_STATIC_BASE, type, args); |
7152 | op1->gtFlags |= callFlags; |
7153 | |
7154 | op1->gtCall.setEntryPoint(pFieldInfo->fieldLookup); |
7155 | FieldSeqNode* fs = GetFieldSeqStore()->CreateSingleton(pResolvedToken->hField); |
7156 | op1 = gtNewOperNode(GT_ADD, type, op1, |
7157 | new (this, GT_CNS_INT) GenTreeIntCon(TYP_I_IMPL, pFieldInfo->offset, fs)); |
7158 | #else |
7159 | unreached(); |
7160 | #endif // FEATURE_READYTORUN_COMPILER |
7161 | } |
7162 | break; |
7163 | |
7164 | default: |
7165 | { |
7166 | if (!(access & CORINFO_ACCESS_ADDRESS)) |
7167 | { |
7168 | // In future, it may be better to just create the right tree here instead of folding it later. |
7169 | op1 = gtNewFieldRef(lclTyp, pResolvedToken->hField); |
7170 | |
7171 | if (pFieldInfo->fieldFlags & CORINFO_FLG_FIELD_INITCLASS) |
7172 | { |
7173 | op1->gtFlags |= GTF_FLD_INITCLASS; |
7174 | } |
7175 | |
7176 | if (pFieldInfo->fieldFlags & CORINFO_FLG_FIELD_STATIC_IN_HEAP) |
7177 | { |
7178 | op1->gtType = TYP_REF; // points at boxed object |
7179 | FieldSeqNode* firstElemFldSeq = |
7180 | GetFieldSeqStore()->CreateSingleton(FieldSeqStore::FirstElemPseudoField); |
7181 | op1 = gtNewOperNode(GT_ADD, TYP_BYREF, op1, |
7182 | new (this, GT_CNS_INT) |
7183 | GenTreeIntCon(TYP_I_IMPL, TARGET_POINTER_SIZE, firstElemFldSeq)); |
7184 | |
7185 | if (varTypeIsStruct(lclTyp)) |
7186 | { |
7187 | // Constructor adds GTF_GLOB_REF. Note that this is *not* GTF_EXCEPT. |
7188 | op1 = gtNewObjNode(pFieldInfo->structType, op1); |
7189 | } |
7190 | else |
7191 | { |
7192 | op1 = gtNewOperNode(GT_IND, lclTyp, op1); |
7193 | op1->gtFlags |= GTF_GLOB_REF | GTF_IND_NONFAULTING; |
7194 | } |
7195 | } |
7196 | |
7197 | return op1; |
7198 | } |
7199 | else |
7200 | { |
7201 | void** pFldAddr = nullptr; |
7202 | void* fldAddr = info.compCompHnd->getFieldAddress(pResolvedToken->hField, (void**)&pFldAddr); |
7203 | |
7204 | FieldSeqNode* fldSeq = GetFieldSeqStore()->CreateSingleton(pResolvedToken->hField); |
7205 | |
7206 | /* Create the data member node */ |
7207 | op1 = gtNewIconHandleNode(pFldAddr == nullptr ? (size_t)fldAddr : (size_t)pFldAddr, GTF_ICON_STATIC_HDL, |
7208 | fldSeq); |
7209 | |
7210 | if (pFieldInfo->fieldFlags & CORINFO_FLG_FIELD_INITCLASS) |
7211 | { |
7212 | op1->gtFlags |= GTF_ICON_INITCLASS; |
7213 | } |
7214 | |
7215 | if (pFldAddr != nullptr) |
7216 | { |
7217 | // There are two cases here, either the static is RVA based, |
7218 | // in which case the type of the FIELD node is not a GC type |
7219 | // and the handle to the RVA is a TYP_I_IMPL. Or the FIELD node is |
7220 | // a GC type and the handle to it is a TYP_BYREF in the GC heap |
7221 | // because handles to statics now go into the large object heap |
7222 | |
7223 | var_types handleTyp = (var_types)(varTypeIsGC(lclTyp) ? TYP_BYREF : TYP_I_IMPL); |
7224 | op1 = gtNewOperNode(GT_IND, handleTyp, op1); |
7225 | op1->gtFlags |= GTF_IND_INVARIANT | GTF_IND_NONFAULTING; |
7226 | } |
7227 | } |
7228 | break; |
7229 | } |
7230 | } |
7231 | |
7232 | if (pFieldInfo->fieldFlags & CORINFO_FLG_FIELD_STATIC_IN_HEAP) |
7233 | { |
7234 | op1 = gtNewOperNode(GT_IND, TYP_REF, op1); |
7235 | |
7236 | FieldSeqNode* fldSeq = GetFieldSeqStore()->CreateSingleton(FieldSeqStore::FirstElemPseudoField); |
7237 | |
7238 | op1 = gtNewOperNode(GT_ADD, TYP_BYREF, op1, |
7239 | new (this, GT_CNS_INT) GenTreeIntCon(TYP_I_IMPL, TARGET_POINTER_SIZE, fldSeq)); |
7240 | } |
7241 | |
7242 | if (!(access & CORINFO_ACCESS_ADDRESS)) |
7243 | { |
7244 | if (varTypeIsStruct(lclTyp)) |
7245 | { |
7246 | // Constructor adds GTF_GLOB_REF. Note that this is *not* GTF_EXCEPT. |
7247 | op1 = gtNewObjNode(pFieldInfo->structType, op1); |
7248 | } |
7249 | else |
7250 | { |
7251 | op1 = gtNewOperNode(GT_IND, lclTyp, op1); |
7252 | op1->gtFlags |= GTF_GLOB_REF; |
7253 | } |
7254 | } |
7255 | |
7256 | return op1; |
7257 | } |
7258 | |
7259 | // In general try to call this before most of the verification work. Most people expect the access |
7260 | // exceptions before the verification exceptions. If you do this after, that usually doesn't happen. Turns |
7261 | // out if you can't access something we also think that you're unverifiable for other reasons. |
7262 | void Compiler::impHandleAccessAllowed(CorInfoIsAccessAllowedResult result, CORINFO_HELPER_DESC* helperCall) |
7263 | { |
7264 | if (result != CORINFO_ACCESS_ALLOWED) |
7265 | { |
7266 | impHandleAccessAllowedInternal(result, helperCall); |
7267 | } |
7268 | } |
7269 | |
7270 | void Compiler::impHandleAccessAllowedInternal(CorInfoIsAccessAllowedResult result, CORINFO_HELPER_DESC* helperCall) |
7271 | { |
7272 | switch (result) |
7273 | { |
7274 | case CORINFO_ACCESS_ALLOWED: |
7275 | break; |
7276 | case CORINFO_ACCESS_ILLEGAL: |
7277 | // if we're verifying, then we need to reject the illegal access to ensure that we don't think the |
7278 | // method is verifiable. Otherwise, delay the exception to runtime. |
7279 | if (compIsForImportOnly()) |
7280 | { |
7281 | info.compCompHnd->ThrowExceptionForHelper(helperCall); |
7282 | } |
7283 | else |
7284 | { |
7285 | impInsertHelperCall(helperCall); |
7286 | } |
7287 | break; |
7288 | case CORINFO_ACCESS_RUNTIME_CHECK: |
7289 | impInsertHelperCall(helperCall); |
7290 | break; |
7291 | } |
7292 | } |
7293 | |
7294 | void Compiler::impInsertHelperCall(CORINFO_HELPER_DESC* helperInfo) |
7295 | { |
7296 | // Construct the argument list |
7297 | GenTreeArgList* args = nullptr; |
7298 | assert(helperInfo->helperNum != CORINFO_HELP_UNDEF); |
7299 | for (unsigned i = helperInfo->numArgs; i > 0; --i) |
7300 | { |
7301 | const CORINFO_HELPER_ARG& helperArg = helperInfo->args[i - 1]; |
7302 | GenTree* currentArg = nullptr; |
7303 | switch (helperArg.argType) |
7304 | { |
7305 | case CORINFO_HELPER_ARG_TYPE_Field: |
7306 | info.compCompHnd->classMustBeLoadedBeforeCodeIsRun( |
7307 | info.compCompHnd->getFieldClass(helperArg.fieldHandle)); |
7308 | currentArg = gtNewIconEmbFldHndNode(helperArg.fieldHandle); |
7309 | break; |
7310 | case CORINFO_HELPER_ARG_TYPE_Method: |
7311 | info.compCompHnd->methodMustBeLoadedBeforeCodeIsRun(helperArg.methodHandle); |
7312 | currentArg = gtNewIconEmbMethHndNode(helperArg.methodHandle); |
7313 | break; |
7314 | case CORINFO_HELPER_ARG_TYPE_Class: |
7315 | info.compCompHnd->classMustBeLoadedBeforeCodeIsRun(helperArg.classHandle); |
7316 | currentArg = gtNewIconEmbClsHndNode(helperArg.classHandle); |
7317 | break; |
7318 | case CORINFO_HELPER_ARG_TYPE_Module: |
7319 | currentArg = gtNewIconEmbScpHndNode(helperArg.moduleHandle); |
7320 | break; |
7321 | case CORINFO_HELPER_ARG_TYPE_Const: |
7322 | currentArg = gtNewIconNode(helperArg.constant); |
7323 | break; |
7324 | default: |
7325 | NO_WAY("Illegal helper arg type" ); |
7326 | } |
7327 | args = (currentArg == nullptr) ? gtNewArgList(currentArg) : gtNewListNode(currentArg, args); |
7328 | } |
7329 | |
7330 | /* TODO-Review: |
7331 | * Mark as CSE'able, and hoistable. Consider marking hoistable unless you're in the inlinee. |
7332 | * Also, consider sticking this in the first basic block. |
7333 | */ |
7334 | GenTree* callout = gtNewHelperCallNode(helperInfo->helperNum, TYP_VOID, args); |
7335 | impAppendTree(callout, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); |
7336 | } |
7337 | |
7338 | // Checks whether the return types of caller and callee are compatible |
7339 | // so that callee can be tail called. Note that here we don't check |
7340 | // compatibility in IL Verifier sense, but on the lines of return type |
7341 | // sizes are equal and get returned in the same return register. |
7342 | bool Compiler::impTailCallRetTypeCompatible(var_types callerRetType, |
7343 | CORINFO_CLASS_HANDLE callerRetTypeClass, |
7344 | var_types calleeRetType, |
7345 | CORINFO_CLASS_HANDLE calleeRetTypeClass) |
7346 | { |
7347 | // Note that we can not relax this condition with genActualType() as the |
7348 | // calling convention dictates that the caller of a function with a small |
7349 | // typed return value is responsible for normalizing the return val. |
7350 | if (callerRetType == calleeRetType) |
7351 | { |
7352 | return true; |
7353 | } |
7354 | |
7355 | // If the class handles are the same and not null, the return types are compatible. |
7356 | if ((callerRetTypeClass != nullptr) && (callerRetTypeClass == calleeRetTypeClass)) |
7357 | { |
7358 | return true; |
7359 | } |
7360 | |
7361 | #if defined(_TARGET_AMD64_) || defined(_TARGET_ARM64_) |
7362 | // Jit64 compat: |
7363 | if (callerRetType == TYP_VOID) |
7364 | { |
7365 | // This needs to be allowed to support the following IL pattern that Jit64 allows: |
7366 | // tail.call |
7367 | // pop |
7368 | // ret |
7369 | // |
7370 | // Note that the above IL pattern is not valid as per IL verification rules. |
7371 | // Therefore, only full trust code can take advantage of this pattern. |
7372 | return true; |
7373 | } |
7374 | |
7375 | // These checks return true if the return value type sizes are the same and |
7376 | // get returned in the same return register i.e. caller doesn't need to normalize |
7377 | // return value. Some of the tail calls permitted by below checks would have |
7378 | // been rejected by IL Verifier before we reached here. Therefore, only full |
7379 | // trust code can make those tail calls. |
7380 | unsigned callerRetTypeSize = 0; |
7381 | unsigned calleeRetTypeSize = 0; |
7382 | bool isCallerRetTypMBEnreg = |
7383 | VarTypeIsMultiByteAndCanEnreg(callerRetType, callerRetTypeClass, &callerRetTypeSize, true, info.compIsVarArgs); |
7384 | bool isCalleeRetTypMBEnreg = |
7385 | VarTypeIsMultiByteAndCanEnreg(calleeRetType, calleeRetTypeClass, &calleeRetTypeSize, true, info.compIsVarArgs); |
7386 | |
7387 | if (varTypeIsIntegral(callerRetType) || isCallerRetTypMBEnreg) |
7388 | { |
7389 | return (varTypeIsIntegral(calleeRetType) || isCalleeRetTypMBEnreg) && (callerRetTypeSize == calleeRetTypeSize); |
7390 | } |
7391 | #endif // _TARGET_AMD64_ || _TARGET_ARM64_ |
7392 | |
7393 | return false; |
7394 | } |
7395 | |
7396 | // For prefixFlags |
7397 | enum |
7398 | { |
7399 | PREFIX_TAILCALL_EXPLICIT = 0x00000001, // call has "tail" IL prefix |
7400 | PREFIX_TAILCALL_IMPLICIT = |
7401 | 0x00000010, // call is treated as having "tail" prefix even though there is no "tail" IL prefix |
7402 | PREFIX_TAILCALL = (PREFIX_TAILCALL_EXPLICIT | PREFIX_TAILCALL_IMPLICIT), |
7403 | PREFIX_VOLATILE = 0x00000100, |
7404 | PREFIX_UNALIGNED = 0x00001000, |
7405 | PREFIX_CONSTRAINED = 0x00010000, |
7406 | PREFIX_READONLY = 0x00100000 |
7407 | }; |
7408 | |
7409 | /******************************************************************************** |
7410 | * |
7411 | * Returns true if the current opcode and and the opcodes following it correspond |
7412 | * to a supported tail call IL pattern. |
7413 | * |
7414 | */ |
7415 | bool Compiler::impIsTailCallILPattern(bool tailPrefixed, |
7416 | OPCODE curOpcode, |
7417 | const BYTE* codeAddrOfNextOpcode, |
7418 | const BYTE* codeEnd, |
7419 | bool isRecursive, |
7420 | bool* isCallPopAndRet /* = nullptr */) |
7421 | { |
7422 | // Bail out if the current opcode is not a call. |
7423 | if (!impOpcodeIsCallOpcode(curOpcode)) |
7424 | { |
7425 | return false; |
7426 | } |
7427 | |
7428 | #if !FEATURE_TAILCALL_OPT_SHARED_RETURN |
7429 | // If shared ret tail opt is not enabled, we will enable |
7430 | // it for recursive methods. |
7431 | if (isRecursive) |
7432 | #endif |
7433 | { |
7434 | // we can actually handle if the ret is in a fallthrough block, as long as that is the only part of the |
7435 | // sequence. Make sure we don't go past the end of the IL however. |
7436 | codeEnd = min(codeEnd + 1, info.compCode + info.compILCodeSize); |
7437 | } |
7438 | |
7439 | // Bail out if there is no next opcode after call |
7440 | if (codeAddrOfNextOpcode >= codeEnd) |
7441 | { |
7442 | return false; |
7443 | } |
7444 | |
7445 | // Scan the opcodes to look for the following IL patterns if either |
7446 | // i) the call is not tail prefixed (i.e. implicit tail call) or |
7447 | // ii) if tail prefixed, IL verification is not needed for the method. |
7448 | // |
7449 | // Only in the above two cases we can allow the below tail call patterns |
7450 | // violating ECMA spec. |
7451 | // |
7452 | // Pattern1: |
7453 | // call |
7454 | // nop* |
7455 | // ret |
7456 | // |
7457 | // Pattern2: |
7458 | // call |
7459 | // nop* |
7460 | // pop |
7461 | // nop* |
7462 | // ret |
7463 | int cntPop = 0; |
7464 | OPCODE nextOpcode; |
7465 | |
7466 | #if !defined(FEATURE_CORECLR) && defined(_TARGET_AMD64_) |
7467 | do |
7468 | { |
7469 | nextOpcode = (OPCODE)getU1LittleEndian(codeAddrOfNextOpcode); |
7470 | codeAddrOfNextOpcode += sizeof(__int8); |
7471 | } while ((codeAddrOfNextOpcode < codeEnd) && // Haven't reached end of method |
7472 | (!tailPrefixed || !tiVerificationNeeded) && // Not ".tail" prefixed or method requires no IL verification |
7473 | ((nextOpcode == CEE_NOP) || ((nextOpcode == CEE_POP) && (++cntPop == 1)))); // Next opcode = nop or exactly |
7474 | // one pop seen so far. |
7475 | #else |
7476 | nextOpcode = (OPCODE)getU1LittleEndian(codeAddrOfNextOpcode); |
7477 | #endif // !FEATURE_CORECLR && _TARGET_AMD64_ |
7478 | |
7479 | if (isCallPopAndRet) |
7480 | { |
7481 | // Allow call+pop+ret to be tail call optimized if caller ret type is void |
7482 | *isCallPopAndRet = (nextOpcode == CEE_RET) && (cntPop == 1); |
7483 | } |
7484 | |
7485 | #if !defined(FEATURE_CORECLR) && defined(_TARGET_AMD64_) |
7486 | // Jit64 Compat: |
7487 | // Tail call IL pattern could be either of the following |
7488 | // 1) call/callvirt/calli + ret |
7489 | // 2) call/callvirt/calli + pop + ret in a method returning void. |
7490 | return (nextOpcode == CEE_RET) && ((cntPop == 0) || ((cntPop == 1) && (info.compRetType == TYP_VOID))); |
7491 | #else |
7492 | return (nextOpcode == CEE_RET) && (cntPop == 0); |
7493 | #endif // !FEATURE_CORECLR && _TARGET_AMD64_ |
7494 | } |
7495 | |
7496 | /***************************************************************************** |
7497 | * |
7498 | * Determine whether the call could be converted to an implicit tail call |
7499 | * |
7500 | */ |
7501 | bool Compiler::impIsImplicitTailCallCandidate( |
7502 | OPCODE opcode, const BYTE* codeAddrOfNextOpcode, const BYTE* codeEnd, int prefixFlags, bool isRecursive) |
7503 | { |
7504 | |
7505 | #if FEATURE_TAILCALL_OPT |
7506 | if (!opts.compTailCallOpt) |
7507 | { |
7508 | return false; |
7509 | } |
7510 | |
7511 | if (opts.OptimizationDisabled()) |
7512 | { |
7513 | return false; |
7514 | } |
7515 | |
7516 | // must not be tail prefixed |
7517 | if (prefixFlags & PREFIX_TAILCALL_EXPLICIT) |
7518 | { |
7519 | return false; |
7520 | } |
7521 | |
7522 | #if !FEATURE_TAILCALL_OPT_SHARED_RETURN |
7523 | // the block containing call is marked as BBJ_RETURN |
7524 | // We allow shared ret tail call optimization on recursive calls even under |
7525 | // !FEATURE_TAILCALL_OPT_SHARED_RETURN. |
7526 | if (!isRecursive && (compCurBB->bbJumpKind != BBJ_RETURN)) |
7527 | return false; |
7528 | #endif // !FEATURE_TAILCALL_OPT_SHARED_RETURN |
7529 | |
7530 | // must be call+ret or call+pop+ret |
7531 | if (!impIsTailCallILPattern(false, opcode, codeAddrOfNextOpcode, codeEnd, isRecursive)) |
7532 | { |
7533 | return false; |
7534 | } |
7535 | |
7536 | return true; |
7537 | #else |
7538 | return false; |
7539 | #endif // FEATURE_TAILCALL_OPT |
7540 | } |
7541 | |
7542 | //------------------------------------------------------------------------ |
7543 | // impImportCall: import a call-inspiring opcode |
7544 | // |
7545 | // Arguments: |
7546 | // opcode - opcode that inspires the call |
7547 | // pResolvedToken - resolved token for the call target |
7548 | // pConstrainedResolvedToken - resolved constraint token (or nullptr) |
7549 | // newObjThis - tree for this pointer or uninitalized newobj temp (or nullptr) |
7550 | // prefixFlags - IL prefix flags for the call |
7551 | // callInfo - EE supplied info for the call |
7552 | // rawILOffset - IL offset of the opcode |
7553 | // |
7554 | // Returns: |
7555 | // Type of the call's return value. |
7556 | // If we're importing an inlinee and have realized the inline must fail, the call return type should be TYP_UNDEF. |
7557 | // However we can't assert for this here yet because there are cases we miss. See issue #13272. |
7558 | // |
7559 | // |
7560 | // Notes: |
7561 | // opcode can be CEE_CALL, CEE_CALLI, CEE_CALLVIRT, or CEE_NEWOBJ. |
7562 | // |
7563 | // For CEE_NEWOBJ, newobjThis should be the temp grabbed for the allocated |
7564 | // uninitalized object. |
7565 | |
7566 | #ifdef _PREFAST_ |
7567 | #pragma warning(push) |
7568 | #pragma warning(disable : 21000) // Suppress PREFast warning about overly large function |
7569 | #endif |
7570 | |
7571 | var_types Compiler::impImportCall(OPCODE opcode, |
7572 | CORINFO_RESOLVED_TOKEN* pResolvedToken, |
7573 | CORINFO_RESOLVED_TOKEN* pConstrainedResolvedToken, |
7574 | GenTree* newobjThis, |
7575 | int prefixFlags, |
7576 | CORINFO_CALL_INFO* callInfo, |
7577 | IL_OFFSET rawILOffset) |
7578 | { |
7579 | assert(opcode == CEE_CALL || opcode == CEE_CALLVIRT || opcode == CEE_NEWOBJ || opcode == CEE_CALLI); |
7580 | |
7581 | IL_OFFSETX ilOffset = impCurILOffset(rawILOffset, true); |
7582 | var_types callRetTyp = TYP_COUNT; |
7583 | CORINFO_SIG_INFO* sig = nullptr; |
7584 | CORINFO_METHOD_HANDLE methHnd = nullptr; |
7585 | CORINFO_CLASS_HANDLE clsHnd = nullptr; |
7586 | unsigned clsFlags = 0; |
7587 | unsigned mflags = 0; |
7588 | unsigned argFlags = 0; |
7589 | GenTree* call = nullptr; |
7590 | GenTreeArgList* args = nullptr; |
7591 | CORINFO_THIS_TRANSFORM constraintCallThisTransform = CORINFO_NO_THIS_TRANSFORM; |
7592 | CORINFO_CONTEXT_HANDLE exactContextHnd = nullptr; |
7593 | bool exactContextNeedsRuntimeLookup = false; |
7594 | bool canTailCall = true; |
7595 | const char* szCanTailCallFailReason = nullptr; |
7596 | int tailCall = prefixFlags & PREFIX_TAILCALL; |
7597 | bool readonlyCall = (prefixFlags & PREFIX_READONLY) != 0; |
7598 | |
7599 | CORINFO_RESOLVED_TOKEN* ldftnToken = nullptr; |
7600 | |
7601 | // Synchronized methods need to call CORINFO_HELP_MON_EXIT at the end. We could |
7602 | // do that before tailcalls, but that is probably not the intended |
7603 | // semantic. So just disallow tailcalls from synchronized methods. |
7604 | // Also, popping arguments in a varargs function is more work and NYI |
7605 | // If we have a security object, we have to keep our frame around for callers |
7606 | // to see any imperative security. |
7607 | if (info.compFlags & CORINFO_FLG_SYNCH) |
7608 | { |
7609 | canTailCall = false; |
7610 | szCanTailCallFailReason = "Caller is synchronized" ; |
7611 | } |
7612 | #if !FEATURE_FIXED_OUT_ARGS |
7613 | else if (info.compIsVarArgs) |
7614 | { |
7615 | canTailCall = false; |
7616 | szCanTailCallFailReason = "Caller is varargs" ; |
7617 | } |
7618 | #endif // FEATURE_FIXED_OUT_ARGS |
7619 | else if (opts.compNeedSecurityCheck) |
7620 | { |
7621 | canTailCall = false; |
7622 | szCanTailCallFailReason = "Caller requires a security check." ; |
7623 | } |
7624 | |
7625 | // We only need to cast the return value of pinvoke inlined calls that return small types |
7626 | |
7627 | // TODO-AMD64-Cleanup: Remove this when we stop interoperating with JIT64, or if we decide to stop |
7628 | // widening everything! CoreCLR does not support JIT64 interoperation so no need to widen there. |
7629 | // The existing x64 JIT doesn't bother widening all types to int, so we have to assume for |
7630 | // the time being that the callee might be compiled by the other JIT and thus the return |
7631 | // value will need to be widened by us (or not widened at all...) |
7632 | |
7633 | // ReadyToRun code sticks with default calling convention that does not widen small return types. |
7634 | |
7635 | bool checkForSmallType = opts.IsJit64Compat() || opts.IsReadyToRun(); |
7636 | bool bIntrinsicImported = false; |
7637 | |
7638 | CORINFO_SIG_INFO calliSig; |
7639 | GenTreeArgList* = nullptr; |
7640 | |
7641 | /*------------------------------------------------------------------------- |
7642 | * First create the call node |
7643 | */ |
7644 | |
7645 | if (opcode == CEE_CALLI) |
7646 | { |
7647 | if (IsTargetAbi(CORINFO_CORERT_ABI)) |
7648 | { |
7649 | // See comment in impCheckForPInvokeCall |
7650 | BasicBlock* block = compIsForInlining() ? impInlineInfo->iciBlock : compCurBB; |
7651 | if (info.compCompHnd->convertPInvokeCalliToCall(pResolvedToken, !impCanPInvokeInlineCallSite(block))) |
7652 | { |
7653 | eeGetCallInfo(pResolvedToken, nullptr, CORINFO_CALLINFO_ALLOWINSTPARAM, callInfo); |
7654 | return impImportCall(CEE_CALL, pResolvedToken, nullptr, nullptr, prefixFlags, callInfo, rawILOffset); |
7655 | } |
7656 | } |
7657 | |
7658 | /* Get the call site sig */ |
7659 | eeGetSig(pResolvedToken->token, pResolvedToken->tokenScope, pResolvedToken->tokenContext, &calliSig); |
7660 | |
7661 | callRetTyp = JITtype2varType(calliSig.retType); |
7662 | |
7663 | call = impImportIndirectCall(&calliSig, ilOffset); |
7664 | |
7665 | // We don't know the target method, so we have to infer the flags, or |
7666 | // assume the worst-case. |
7667 | mflags = (calliSig.callConv & CORINFO_CALLCONV_HASTHIS) ? 0 : CORINFO_FLG_STATIC; |
7668 | |
7669 | #ifdef DEBUG |
7670 | if (verbose) |
7671 | { |
7672 | unsigned structSize = |
7673 | (callRetTyp == TYP_STRUCT) ? info.compCompHnd->getClassSize(calliSig.retTypeSigClass) : 0; |
7674 | printf("\nIn Compiler::impImportCall: opcode is %s, kind=%d, callRetType is %s, structSize is %d\n" , |
7675 | opcodeNames[opcode], callInfo->kind, varTypeName(callRetTyp), structSize); |
7676 | } |
7677 | #endif |
7678 | // This should be checked in impImportBlockCode. |
7679 | assert(!compIsForInlining() || !(impInlineInfo->inlineCandidateInfo->dwRestrictions & INLINE_RESPECT_BOUNDARY)); |
7680 | |
7681 | sig = &calliSig; |
7682 | |
7683 | #ifdef DEBUG |
7684 | // We cannot lazily obtain the signature of a CALLI call because it has no method |
7685 | // handle that we can use, so we need to save its full call signature here. |
7686 | assert(call->gtCall.callSig == nullptr); |
7687 | call->gtCall.callSig = new (this, CMK_CorSig) CORINFO_SIG_INFO; |
7688 | *call->gtCall.callSig = calliSig; |
7689 | #endif // DEBUG |
7690 | |
7691 | if (IsTargetAbi(CORINFO_CORERT_ABI)) |
7692 | { |
7693 | bool managedCall = (((calliSig.callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_STDCALL) && |
7694 | ((calliSig.callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_C) && |
7695 | ((calliSig.callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_THISCALL) && |
7696 | ((calliSig.callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_FASTCALL)); |
7697 | if (managedCall) |
7698 | { |
7699 | addFatPointerCandidate(call->AsCall()); |
7700 | } |
7701 | } |
7702 | } |
7703 | else // (opcode != CEE_CALLI) |
7704 | { |
7705 | CorInfoIntrinsics intrinsicID = CORINFO_INTRINSIC_Count; |
7706 | |
7707 | // Passing CORINFO_CALLINFO_ALLOWINSTPARAM indicates that this JIT is prepared to |
7708 | // supply the instantiation parameters necessary to make direct calls to underlying |
7709 | // shared generic code, rather than calling through instantiating stubs. If the |
7710 | // returned signature has CORINFO_CALLCONV_PARAMTYPE then this indicates that the JIT |
7711 | // must indeed pass an instantiation parameter. |
7712 | |
7713 | methHnd = callInfo->hMethod; |
7714 | |
7715 | sig = &(callInfo->sig); |
7716 | callRetTyp = JITtype2varType(sig->retType); |
7717 | |
7718 | mflags = callInfo->methodFlags; |
7719 | |
7720 | #ifdef DEBUG |
7721 | if (verbose) |
7722 | { |
7723 | unsigned structSize = (callRetTyp == TYP_STRUCT) ? info.compCompHnd->getClassSize(sig->retTypeSigClass) : 0; |
7724 | printf("\nIn Compiler::impImportCall: opcode is %s, kind=%d, callRetType is %s, structSize is %d\n" , |
7725 | opcodeNames[opcode], callInfo->kind, varTypeName(callRetTyp), structSize); |
7726 | } |
7727 | #endif |
7728 | if (compIsForInlining()) |
7729 | { |
7730 | /* Does this call site have security boundary restrictions? */ |
7731 | |
7732 | if (impInlineInfo->inlineCandidateInfo->dwRestrictions & INLINE_RESPECT_BOUNDARY) |
7733 | { |
7734 | compInlineResult->NoteFatal(InlineObservation::CALLSITE_CROSS_BOUNDARY_SECURITY); |
7735 | return TYP_UNDEF; |
7736 | } |
7737 | |
7738 | /* Does the inlinee need a security check token on the frame */ |
7739 | |
7740 | if (mflags & CORINFO_FLG_SECURITYCHECK) |
7741 | { |
7742 | compInlineResult->NoteFatal(InlineObservation::CALLEE_NEEDS_SECURITY_CHECK); |
7743 | return TYP_UNDEF; |
7744 | } |
7745 | |
7746 | /* Does the inlinee use StackCrawlMark */ |
7747 | |
7748 | if (mflags & CORINFO_FLG_DONT_INLINE_CALLER) |
7749 | { |
7750 | compInlineResult->NoteFatal(InlineObservation::CALLEE_STACK_CRAWL_MARK); |
7751 | return TYP_UNDEF; |
7752 | } |
7753 | |
7754 | /* For now ignore delegate invoke */ |
7755 | |
7756 | if (mflags & CORINFO_FLG_DELEGATE_INVOKE) |
7757 | { |
7758 | compInlineResult->NoteFatal(InlineObservation::CALLEE_HAS_DELEGATE_INVOKE); |
7759 | return TYP_UNDEF; |
7760 | } |
7761 | |
7762 | /* For now ignore varargs */ |
7763 | if ((sig->callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_NATIVEVARARG) |
7764 | { |
7765 | compInlineResult->NoteFatal(InlineObservation::CALLEE_HAS_NATIVE_VARARGS); |
7766 | return TYP_UNDEF; |
7767 | } |
7768 | |
7769 | if ((sig->callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_VARARG) |
7770 | { |
7771 | compInlineResult->NoteFatal(InlineObservation::CALLEE_HAS_MANAGED_VARARGS); |
7772 | return TYP_UNDEF; |
7773 | } |
7774 | |
7775 | if ((mflags & CORINFO_FLG_VIRTUAL) && (sig->sigInst.methInstCount != 0) && (opcode == CEE_CALLVIRT)) |
7776 | { |
7777 | compInlineResult->NoteFatal(InlineObservation::CALLEE_IS_GENERIC_VIRTUAL); |
7778 | return TYP_UNDEF; |
7779 | } |
7780 | } |
7781 | |
7782 | clsHnd = pResolvedToken->hClass; |
7783 | |
7784 | clsFlags = callInfo->classFlags; |
7785 | |
7786 | #ifdef DEBUG |
7787 | // If this is a call to JitTestLabel.Mark, do "early inlining", and record the test attribute. |
7788 | |
7789 | // This recognition should really be done by knowing the methHnd of the relevant Mark method(s). |
7790 | // These should be in mscorlib.h, and available through a JIT/EE interface call. |
7791 | const char* modName; |
7792 | const char* className; |
7793 | const char* methodName; |
7794 | if ((className = eeGetClassName(clsHnd)) != nullptr && |
7795 | strcmp(className, "System.Runtime.CompilerServices.JitTestLabel" ) == 0 && |
7796 | (methodName = eeGetMethodName(methHnd, &modName)) != nullptr && strcmp(methodName, "Mark" ) == 0) |
7797 | { |
7798 | return impImportJitTestLabelMark(sig->numArgs); |
7799 | } |
7800 | #endif // DEBUG |
7801 | |
7802 | // <NICE> Factor this into getCallInfo </NICE> |
7803 | bool isSpecialIntrinsic = false; |
7804 | if ((mflags & (CORINFO_FLG_INTRINSIC | CORINFO_FLG_JIT_INTRINSIC)) != 0) |
7805 | { |
7806 | const bool isTail = canTailCall && (tailCall != 0); |
7807 | |
7808 | call = impIntrinsic(newobjThis, clsHnd, methHnd, sig, mflags, pResolvedToken->token, readonlyCall, isTail, |
7809 | pConstrainedResolvedToken, callInfo->thisTransform, &intrinsicID, &isSpecialIntrinsic); |
7810 | |
7811 | if (compDonotInline()) |
7812 | { |
7813 | return TYP_UNDEF; |
7814 | } |
7815 | |
7816 | if (call != nullptr) |
7817 | { |
7818 | assert(!(mflags & CORINFO_FLG_VIRTUAL) || (mflags & CORINFO_FLG_FINAL) || |
7819 | (clsFlags & CORINFO_FLG_FINAL)); |
7820 | |
7821 | #ifdef FEATURE_READYTORUN_COMPILER |
7822 | if (call->OperGet() == GT_INTRINSIC) |
7823 | { |
7824 | if (opts.IsReadyToRun()) |
7825 | { |
7826 | noway_assert(callInfo->kind == CORINFO_CALL); |
7827 | call->gtIntrinsic.gtEntryPoint = callInfo->codePointerLookup.constLookup; |
7828 | } |
7829 | else |
7830 | { |
7831 | call->gtIntrinsic.gtEntryPoint.addr = nullptr; |
7832 | call->gtIntrinsic.gtEntryPoint.accessType = IAT_VALUE; |
7833 | } |
7834 | } |
7835 | #endif |
7836 | |
7837 | bIntrinsicImported = true; |
7838 | goto DONE_CALL; |
7839 | } |
7840 | } |
7841 | |
7842 | #ifdef FEATURE_SIMD |
7843 | if (featureSIMD) |
7844 | { |
7845 | call = impSIMDIntrinsic(opcode, newobjThis, clsHnd, methHnd, sig, mflags, pResolvedToken->token); |
7846 | if (call != nullptr) |
7847 | { |
7848 | bIntrinsicImported = true; |
7849 | goto DONE_CALL; |
7850 | } |
7851 | } |
7852 | #endif // FEATURE_SIMD |
7853 | |
7854 | if ((mflags & CORINFO_FLG_VIRTUAL) && (mflags & CORINFO_FLG_EnC) && (opcode == CEE_CALLVIRT)) |
7855 | { |
7856 | NO_WAY("Virtual call to a function added via EnC is not supported" ); |
7857 | } |
7858 | |
7859 | if ((sig->callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_DEFAULT && |
7860 | (sig->callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_VARARG && |
7861 | (sig->callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_NATIVEVARARG) |
7862 | { |
7863 | BADCODE("Bad calling convention" ); |
7864 | } |
7865 | |
7866 | //------------------------------------------------------------------------- |
7867 | // Construct the call node |
7868 | // |
7869 | // Work out what sort of call we're making. |
7870 | // Dispense with virtual calls implemented via LDVIRTFTN immediately. |
7871 | |
7872 | constraintCallThisTransform = callInfo->thisTransform; |
7873 | exactContextHnd = callInfo->contextHandle; |
7874 | exactContextNeedsRuntimeLookup = callInfo->exactContextNeedsRuntimeLookup == TRUE; |
7875 | |
7876 | // Recursive call is treated as a loop to the begining of the method. |
7877 | if (gtIsRecursiveCall(methHnd)) |
7878 | { |
7879 | #ifdef DEBUG |
7880 | if (verbose) |
7881 | { |
7882 | JITDUMP("\nFound recursive call in the method. Mark " FMT_BB " to " FMT_BB |
7883 | " as having a backward branch.\n" , |
7884 | fgFirstBB->bbNum, compCurBB->bbNum); |
7885 | } |
7886 | #endif |
7887 | fgMarkBackwardJump(fgFirstBB, compCurBB); |
7888 | } |
7889 | |
7890 | switch (callInfo->kind) |
7891 | { |
7892 | |
7893 | case CORINFO_VIRTUALCALL_STUB: |
7894 | { |
7895 | assert(!(mflags & CORINFO_FLG_STATIC)); // can't call a static method |
7896 | assert(!(clsFlags & CORINFO_FLG_VALUECLASS)); |
7897 | if (callInfo->stubLookup.lookupKind.needsRuntimeLookup) |
7898 | { |
7899 | |
7900 | if (compIsForInlining()) |
7901 | { |
7902 | // Don't import runtime lookups when inlining |
7903 | // Inlining has to be aborted in such a case |
7904 | /* XXX Fri 3/20/2009 |
7905 | * By the way, this would never succeed. If the handle lookup is into the generic |
7906 | * dictionary for a candidate, you'll generate different dictionary offsets and the |
7907 | * inlined code will crash. |
7908 | * |
7909 | * To anyone code reviewing this, when could this ever succeed in the future? It'll |
7910 | * always have a handle lookup. These lookups are safe intra-module, but we're just |
7911 | * failing here. |
7912 | */ |
7913 | compInlineResult->NoteFatal(InlineObservation::CALLSITE_HAS_COMPLEX_HANDLE); |
7914 | return TYP_UNDEF; |
7915 | } |
7916 | |
7917 | GenTree* stubAddr = impRuntimeLookupToTree(pResolvedToken, &callInfo->stubLookup, methHnd); |
7918 | assert(!compDonotInline()); |
7919 | |
7920 | // This is the rough code to set up an indirect stub call |
7921 | assert(stubAddr != nullptr); |
7922 | |
7923 | // The stubAddr may be a |
7924 | // complex expression. As it is evaluated after the args, |
7925 | // it may cause registered args to be spilled. Simply spill it. |
7926 | |
7927 | unsigned lclNum = lvaGrabTemp(true DEBUGARG("VirtualCall with runtime lookup" )); |
7928 | impAssignTempGen(lclNum, stubAddr, (unsigned)CHECK_SPILL_ALL); |
7929 | stubAddr = gtNewLclvNode(lclNum, TYP_I_IMPL); |
7930 | |
7931 | // Create the actual call node |
7932 | |
7933 | assert((sig->callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_VARARG && |
7934 | (sig->callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_NATIVEVARARG); |
7935 | |
7936 | call = gtNewIndCallNode(stubAddr, callRetTyp, nullptr); |
7937 | |
7938 | call->gtFlags |= GTF_EXCEPT | (stubAddr->gtFlags & GTF_GLOB_EFFECT); |
7939 | call->gtFlags |= GTF_CALL_VIRT_STUB; |
7940 | |
7941 | #ifdef _TARGET_X86_ |
7942 | // No tailcalls allowed for these yet... |
7943 | canTailCall = false; |
7944 | szCanTailCallFailReason = "VirtualCall with runtime lookup" ; |
7945 | #endif |
7946 | } |
7947 | else |
7948 | { |
7949 | // The stub address is known at compile time |
7950 | call = gtNewCallNode(CT_USER_FUNC, callInfo->hMethod, callRetTyp, nullptr, ilOffset); |
7951 | call->gtCall.gtStubCallStubAddr = callInfo->stubLookup.constLookup.addr; |
7952 | call->gtFlags |= GTF_CALL_VIRT_STUB; |
7953 | assert(callInfo->stubLookup.constLookup.accessType != IAT_PPVALUE && |
7954 | callInfo->stubLookup.constLookup.accessType != IAT_RELPVALUE); |
7955 | if (callInfo->stubLookup.constLookup.accessType == IAT_PVALUE) |
7956 | { |
7957 | call->gtCall.gtCallMoreFlags |= GTF_CALL_M_VIRTSTUB_REL_INDIRECT; |
7958 | } |
7959 | } |
7960 | |
7961 | #ifdef FEATURE_READYTORUN_COMPILER |
7962 | if (opts.IsReadyToRun()) |
7963 | { |
7964 | // Null check is sometimes needed for ready to run to handle |
7965 | // non-virtual <-> virtual changes between versions |
7966 | if (callInfo->nullInstanceCheck) |
7967 | { |
7968 | call->gtFlags |= GTF_CALL_NULLCHECK; |
7969 | } |
7970 | } |
7971 | #endif |
7972 | |
7973 | break; |
7974 | } |
7975 | |
7976 | case CORINFO_VIRTUALCALL_VTABLE: |
7977 | { |
7978 | assert(!(mflags & CORINFO_FLG_STATIC)); // can't call a static method |
7979 | assert(!(clsFlags & CORINFO_FLG_VALUECLASS)); |
7980 | call = gtNewCallNode(CT_USER_FUNC, callInfo->hMethod, callRetTyp, nullptr, ilOffset); |
7981 | call->gtFlags |= GTF_CALL_VIRT_VTABLE; |
7982 | break; |
7983 | } |
7984 | |
7985 | case CORINFO_VIRTUALCALL_LDVIRTFTN: |
7986 | { |
7987 | if (compIsForInlining()) |
7988 | { |
7989 | compInlineResult->NoteFatal(InlineObservation::CALLSITE_HAS_CALL_VIA_LDVIRTFTN); |
7990 | return TYP_UNDEF; |
7991 | } |
7992 | |
7993 | assert(!(mflags & CORINFO_FLG_STATIC)); // can't call a static method |
7994 | assert(!(clsFlags & CORINFO_FLG_VALUECLASS)); |
7995 | // OK, We've been told to call via LDVIRTFTN, so just |
7996 | // take the call now.... |
7997 | |
7998 | args = impPopList(sig->numArgs, sig); |
7999 | |
8000 | GenTree* thisPtr = impPopStack().val; |
8001 | thisPtr = impTransformThis(thisPtr, pConstrainedResolvedToken, callInfo->thisTransform); |
8002 | assert(thisPtr != nullptr); |
8003 | |
8004 | // Clone the (possibly transformed) "this" pointer |
8005 | GenTree* thisPtrCopy; |
8006 | thisPtr = impCloneExpr(thisPtr, &thisPtrCopy, NO_CLASS_HANDLE, (unsigned)CHECK_SPILL_ALL, |
8007 | nullptr DEBUGARG("LDVIRTFTN this pointer" )); |
8008 | |
8009 | GenTree* fptr = impImportLdvirtftn(thisPtr, pResolvedToken, callInfo); |
8010 | assert(fptr != nullptr); |
8011 | |
8012 | thisPtr = nullptr; // can't reuse it |
8013 | |
8014 | // Now make an indirect call through the function pointer |
8015 | |
8016 | unsigned lclNum = lvaGrabTemp(true DEBUGARG("VirtualCall through function pointer" )); |
8017 | impAssignTempGen(lclNum, fptr, (unsigned)CHECK_SPILL_ALL); |
8018 | fptr = gtNewLclvNode(lclNum, TYP_I_IMPL); |
8019 | |
8020 | // Create the actual call node |
8021 | |
8022 | call = gtNewIndCallNode(fptr, callRetTyp, args, ilOffset); |
8023 | call->gtCall.gtCallObjp = thisPtrCopy; |
8024 | call->gtFlags |= GTF_EXCEPT | (fptr->gtFlags & GTF_GLOB_EFFECT); |
8025 | |
8026 | if ((sig->sigInst.methInstCount != 0) && IsTargetAbi(CORINFO_CORERT_ABI)) |
8027 | { |
8028 | // CoreRT generic virtual method: need to handle potential fat function pointers |
8029 | addFatPointerCandidate(call->AsCall()); |
8030 | } |
8031 | #ifdef FEATURE_READYTORUN_COMPILER |
8032 | if (opts.IsReadyToRun()) |
8033 | { |
8034 | // Null check is needed for ready to run to handle |
8035 | // non-virtual <-> virtual changes between versions |
8036 | call->gtFlags |= GTF_CALL_NULLCHECK; |
8037 | } |
8038 | #endif |
8039 | |
8040 | // Sine we are jumping over some code, check that its OK to skip that code |
8041 | assert((sig->callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_VARARG && |
8042 | (sig->callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_NATIVEVARARG); |
8043 | goto DONE; |
8044 | } |
8045 | |
8046 | case CORINFO_CALL: |
8047 | { |
8048 | // This is for a non-virtual, non-interface etc. call |
8049 | call = gtNewCallNode(CT_USER_FUNC, callInfo->hMethod, callRetTyp, nullptr, ilOffset); |
8050 | |
8051 | // We remove the nullcheck for the GetType call instrinsic. |
8052 | // TODO-CQ: JIT64 does not introduce the null check for many more helper calls |
8053 | // and instrinsics. |
8054 | if (callInfo->nullInstanceCheck && |
8055 | !((mflags & CORINFO_FLG_INTRINSIC) != 0 && (intrinsicID == CORINFO_INTRINSIC_Object_GetType))) |
8056 | { |
8057 | call->gtFlags |= GTF_CALL_NULLCHECK; |
8058 | } |
8059 | |
8060 | #ifdef FEATURE_READYTORUN_COMPILER |
8061 | if (opts.IsReadyToRun()) |
8062 | { |
8063 | call->gtCall.setEntryPoint(callInfo->codePointerLookup.constLookup); |
8064 | } |
8065 | #endif |
8066 | break; |
8067 | } |
8068 | |
8069 | case CORINFO_CALL_CODE_POINTER: |
8070 | { |
8071 | // The EE has asked us to call by computing a code pointer and then doing an |
8072 | // indirect call. This is because a runtime lookup is required to get the code entry point. |
8073 | |
8074 | // These calls always follow a uniform calling convention, i.e. no extra hidden params |
8075 | assert((sig->callConv & CORINFO_CALLCONV_PARAMTYPE) == 0); |
8076 | |
8077 | assert((sig->callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_VARARG); |
8078 | assert((sig->callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_NATIVEVARARG); |
8079 | |
8080 | GenTree* fptr = |
8081 | impLookupToTree(pResolvedToken, &callInfo->codePointerLookup, GTF_ICON_FTN_ADDR, callInfo->hMethod); |
8082 | |
8083 | if (compDonotInline()) |
8084 | { |
8085 | return TYP_UNDEF; |
8086 | } |
8087 | |
8088 | // Now make an indirect call through the function pointer |
8089 | |
8090 | unsigned lclNum = lvaGrabTemp(true DEBUGARG("Indirect call through function pointer" )); |
8091 | impAssignTempGen(lclNum, fptr, (unsigned)CHECK_SPILL_ALL); |
8092 | fptr = gtNewLclvNode(lclNum, TYP_I_IMPL); |
8093 | |
8094 | call = gtNewIndCallNode(fptr, callRetTyp, nullptr, ilOffset); |
8095 | call->gtFlags |= GTF_EXCEPT | (fptr->gtFlags & GTF_GLOB_EFFECT); |
8096 | if (callInfo->nullInstanceCheck) |
8097 | { |
8098 | call->gtFlags |= GTF_CALL_NULLCHECK; |
8099 | } |
8100 | |
8101 | break; |
8102 | } |
8103 | |
8104 | default: |
8105 | assert(!"unknown call kind" ); |
8106 | break; |
8107 | } |
8108 | |
8109 | //------------------------------------------------------------------------- |
8110 | // Set more flags |
8111 | |
8112 | PREFIX_ASSUME(call != nullptr); |
8113 | |
8114 | if (mflags & CORINFO_FLG_NOGCCHECK) |
8115 | { |
8116 | call->gtCall.gtCallMoreFlags |= GTF_CALL_M_NOGCCHECK; |
8117 | } |
8118 | |
8119 | // Mark call if it's one of the ones we will maybe treat as an intrinsic |
8120 | if (isSpecialIntrinsic) |
8121 | { |
8122 | call->gtCall.gtCallMoreFlags |= GTF_CALL_M_SPECIAL_INTRINSIC; |
8123 | } |
8124 | } |
8125 | assert(sig); |
8126 | assert(clsHnd || (opcode == CEE_CALLI)); // We're never verifying for CALLI, so this is not set. |
8127 | |
8128 | /* Some sanity checks */ |
8129 | |
8130 | // CALL_VIRT and NEWOBJ must have a THIS pointer |
8131 | assert((opcode != CEE_CALLVIRT && opcode != CEE_NEWOBJ) || (sig->callConv & CORINFO_CALLCONV_HASTHIS)); |
8132 | // static bit and hasThis are negations of one another |
8133 | assert(((mflags & CORINFO_FLG_STATIC) != 0) == ((sig->callConv & CORINFO_CALLCONV_HASTHIS) == 0)); |
8134 | assert(call != nullptr); |
8135 | |
8136 | /*------------------------------------------------------------------------- |
8137 | * Check special-cases etc |
8138 | */ |
8139 | |
8140 | /* Special case - Check if it is a call to Delegate.Invoke(). */ |
8141 | |
8142 | if (mflags & CORINFO_FLG_DELEGATE_INVOKE) |
8143 | { |
8144 | assert(!compIsForInlining()); |
8145 | assert(!(mflags & CORINFO_FLG_STATIC)); // can't call a static method |
8146 | assert(mflags & CORINFO_FLG_FINAL); |
8147 | |
8148 | /* Set the delegate flag */ |
8149 | call->gtCall.gtCallMoreFlags |= GTF_CALL_M_DELEGATE_INV; |
8150 | |
8151 | if (callInfo->secureDelegateInvoke) |
8152 | { |
8153 | call->gtCall.gtCallMoreFlags |= GTF_CALL_M_SECURE_DELEGATE_INV; |
8154 | } |
8155 | |
8156 | if (opcode == CEE_CALLVIRT) |
8157 | { |
8158 | assert(mflags & CORINFO_FLG_FINAL); |
8159 | |
8160 | /* It should have the GTF_CALL_NULLCHECK flag set. Reset it */ |
8161 | assert(call->gtFlags & GTF_CALL_NULLCHECK); |
8162 | call->gtFlags &= ~GTF_CALL_NULLCHECK; |
8163 | } |
8164 | } |
8165 | |
8166 | CORINFO_CLASS_HANDLE actualMethodRetTypeSigClass; |
8167 | actualMethodRetTypeSigClass = sig->retTypeSigClass; |
8168 | if (varTypeIsStruct(callRetTyp)) |
8169 | { |
8170 | callRetTyp = impNormStructType(actualMethodRetTypeSigClass); |
8171 | call->gtType = callRetTyp; |
8172 | } |
8173 | |
8174 | #if !FEATURE_VARARG |
8175 | /* Check for varargs */ |
8176 | if ((sig->callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_VARARG || |
8177 | (sig->callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_NATIVEVARARG) |
8178 | { |
8179 | BADCODE("Varargs not supported." ); |
8180 | } |
8181 | #endif // !FEATURE_VARARG |
8182 | |
8183 | #ifdef UNIX_X86_ABI |
8184 | if (call->gtCall.callSig == nullptr) |
8185 | { |
8186 | call->gtCall.callSig = new (this, CMK_CorSig) CORINFO_SIG_INFO; |
8187 | *call->gtCall.callSig = *sig; |
8188 | } |
8189 | #endif // UNIX_X86_ABI |
8190 | |
8191 | if ((sig->callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_VARARG || |
8192 | (sig->callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_NATIVEVARARG) |
8193 | { |
8194 | assert(!compIsForInlining()); |
8195 | |
8196 | /* Set the right flags */ |
8197 | |
8198 | call->gtFlags |= GTF_CALL_POP_ARGS; |
8199 | call->gtCall.gtCallMoreFlags |= GTF_CALL_M_VARARGS; |
8200 | |
8201 | /* Can't allow tailcall for varargs as it is caller-pop. The caller |
8202 | will be expecting to pop a certain number of arguments, but if we |
8203 | tailcall to a function with a different number of arguments, we |
8204 | are hosed. There are ways around this (caller remembers esp value, |
8205 | varargs is not caller-pop, etc), but not worth it. */ |
8206 | CLANG_FORMAT_COMMENT_ANCHOR; |
8207 | |
8208 | #ifdef _TARGET_X86_ |
8209 | if (canTailCall) |
8210 | { |
8211 | canTailCall = false; |
8212 | szCanTailCallFailReason = "Callee is varargs" ; |
8213 | } |
8214 | #endif |
8215 | |
8216 | /* Get the total number of arguments - this is already correct |
8217 | * for CALLI - for methods we have to get it from the call site */ |
8218 | |
8219 | if (opcode != CEE_CALLI) |
8220 | { |
8221 | #ifdef DEBUG |
8222 | unsigned numArgsDef = sig->numArgs; |
8223 | #endif |
8224 | eeGetCallSiteSig(pResolvedToken->token, pResolvedToken->tokenScope, pResolvedToken->tokenContext, sig); |
8225 | |
8226 | #ifdef DEBUG |
8227 | // We cannot lazily obtain the signature of a vararg call because using its method |
8228 | // handle will give us only the declared argument list, not the full argument list. |
8229 | assert(call->gtCall.callSig == nullptr); |
8230 | call->gtCall.callSig = new (this, CMK_CorSig) CORINFO_SIG_INFO; |
8231 | *call->gtCall.callSig = *sig; |
8232 | #endif |
8233 | |
8234 | // For vararg calls we must be sure to load the return type of the |
8235 | // method actually being called, as well as the return types of the |
8236 | // specified in the vararg signature. With type equivalency, these types |
8237 | // may not be the same. |
8238 | if (sig->retTypeSigClass != actualMethodRetTypeSigClass) |
8239 | { |
8240 | if (actualMethodRetTypeSigClass != nullptr && sig->retType != CORINFO_TYPE_CLASS && |
8241 | sig->retType != CORINFO_TYPE_BYREF && sig->retType != CORINFO_TYPE_PTR && |
8242 | sig->retType != CORINFO_TYPE_VAR) |
8243 | { |
8244 | // Make sure that all valuetypes (including enums) that we push are loaded. |
8245 | // This is to guarantee that if a GC is triggerred from the prestub of this methods, |
8246 | // all valuetypes in the method signature are already loaded. |
8247 | // We need to be able to find the size of the valuetypes, but we cannot |
8248 | // do a class-load from within GC. |
8249 | info.compCompHnd->classMustBeLoadedBeforeCodeIsRun(actualMethodRetTypeSigClass); |
8250 | } |
8251 | } |
8252 | |
8253 | assert(numArgsDef <= sig->numArgs); |
8254 | } |
8255 | |
8256 | /* We will have "cookie" as the last argument but we cannot push |
8257 | * it on the operand stack because we may overflow, so we append it |
8258 | * to the arg list next after we pop them */ |
8259 | } |
8260 | |
8261 | if (mflags & CORINFO_FLG_SECURITYCHECK) |
8262 | { |
8263 | assert(!compIsForInlining()); |
8264 | |
8265 | // Need security prolog/epilog callouts when there is |
8266 | // imperative security in the method. This is to give security a |
8267 | // chance to do any setup in the prolog and cleanup in the epilog if needed. |
8268 | |
8269 | if (compIsForInlining()) |
8270 | { |
8271 | // Cannot handle this if the method being imported is an inlinee by itself. |
8272 | // Because inlinee method does not have its own frame. |
8273 | |
8274 | compInlineResult->NoteFatal(InlineObservation::CALLEE_NEEDS_SECURITY_CHECK); |
8275 | return TYP_UNDEF; |
8276 | } |
8277 | else |
8278 | { |
8279 | tiSecurityCalloutNeeded = true; |
8280 | |
8281 | // If the current method calls a method which needs a security check, |
8282 | // (i.e. the method being compiled has imperative security) |
8283 | // we need to reserve a slot for the security object in |
8284 | // the current method's stack frame |
8285 | opts.compNeedSecurityCheck = true; |
8286 | } |
8287 | } |
8288 | |
8289 | //--------------------------- Inline NDirect ------------------------------ |
8290 | |
8291 | // For inline cases we technically should look at both the current |
8292 | // block and the call site block (or just the latter if we've |
8293 | // fused the EH trees). However the block-related checks pertain to |
8294 | // EH and we currently won't inline a method with EH. So for |
8295 | // inlinees, just checking the call site block is sufficient. |
8296 | { |
8297 | // New lexical block here to avoid compilation errors because of GOTOs. |
8298 | BasicBlock* block = compIsForInlining() ? impInlineInfo->iciBlock : compCurBB; |
8299 | impCheckForPInvokeCall(call->AsCall(), methHnd, sig, mflags, block); |
8300 | } |
8301 | |
8302 | if (call->gtFlags & GTF_CALL_UNMANAGED) |
8303 | { |
8304 | // We set up the unmanaged call by linking the frame, disabling GC, etc |
8305 | // This needs to be cleaned up on return |
8306 | if (canTailCall) |
8307 | { |
8308 | canTailCall = false; |
8309 | szCanTailCallFailReason = "Callee is native" ; |
8310 | } |
8311 | |
8312 | checkForSmallType = true; |
8313 | |
8314 | impPopArgsForUnmanagedCall(call, sig); |
8315 | |
8316 | goto DONE; |
8317 | } |
8318 | else if ((opcode == CEE_CALLI) && (((sig->callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_STDCALL) || |
8319 | ((sig->callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_C) || |
8320 | ((sig->callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_THISCALL) || |
8321 | ((sig->callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_FASTCALL))) |
8322 | { |
8323 | if (!info.compCompHnd->canGetCookieForPInvokeCalliSig(sig)) |
8324 | { |
8325 | // Normally this only happens with inlining. |
8326 | // However, a generic method (or type) being NGENd into another module |
8327 | // can run into this issue as well. There's not an easy fall-back for NGEN |
8328 | // so instead we fallback to JIT. |
8329 | if (compIsForInlining()) |
8330 | { |
8331 | compInlineResult->NoteFatal(InlineObservation::CALLSITE_CANT_EMBED_PINVOKE_COOKIE); |
8332 | } |
8333 | else |
8334 | { |
8335 | IMPL_LIMITATION("Can't get PInvoke cookie (cross module generics)" ); |
8336 | } |
8337 | |
8338 | return TYP_UNDEF; |
8339 | } |
8340 | |
8341 | GenTree* cookie = eeGetPInvokeCookie(sig); |
8342 | |
8343 | // This cookie is required to be either a simple GT_CNS_INT or |
8344 | // an indirection of a GT_CNS_INT |
8345 | // |
8346 | GenTree* cookieConst = cookie; |
8347 | if (cookie->gtOper == GT_IND) |
8348 | { |
8349 | cookieConst = cookie->gtOp.gtOp1; |
8350 | } |
8351 | assert(cookieConst->gtOper == GT_CNS_INT); |
8352 | |
8353 | // Setting GTF_DONT_CSE on the GT_CNS_INT as well as on the GT_IND (if it exists) will ensure that |
8354 | // we won't allow this tree to participate in any CSE logic |
8355 | // |
8356 | cookie->gtFlags |= GTF_DONT_CSE; |
8357 | cookieConst->gtFlags |= GTF_DONT_CSE; |
8358 | |
8359 | call->gtCall.gtCallCookie = cookie; |
8360 | |
8361 | if (canTailCall) |
8362 | { |
8363 | canTailCall = false; |
8364 | szCanTailCallFailReason = "PInvoke calli" ; |
8365 | } |
8366 | } |
8367 | |
8368 | /*------------------------------------------------------------------------- |
8369 | * Create the argument list |
8370 | */ |
8371 | |
8372 | //------------------------------------------------------------------------- |
8373 | // Special case - for varargs we have an implicit last argument |
8374 | |
8375 | if ((sig->callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_VARARG) |
8376 | { |
8377 | assert(!compIsForInlining()); |
8378 | |
8379 | void *varCookie, *pVarCookie; |
8380 | if (!info.compCompHnd->canGetVarArgsHandle(sig)) |
8381 | { |
8382 | compInlineResult->NoteFatal(InlineObservation::CALLSITE_CANT_EMBED_VARARGS_COOKIE); |
8383 | return TYP_UNDEF; |
8384 | } |
8385 | |
8386 | varCookie = info.compCompHnd->getVarArgsHandle(sig, &pVarCookie); |
8387 | assert((!varCookie) != (!pVarCookie)); |
8388 | GenTree* cookie = gtNewIconEmbHndNode(varCookie, pVarCookie, GTF_ICON_VARG_HDL, sig); |
8389 | |
8390 | assert(extraArg == nullptr); |
8391 | extraArg = gtNewArgList(cookie); |
8392 | } |
8393 | |
8394 | //------------------------------------------------------------------------- |
8395 | // Extra arg for shared generic code and array methods |
8396 | // |
8397 | // Extra argument containing instantiation information is passed in the |
8398 | // following circumstances: |
8399 | // (a) To the "Address" method on array classes; the extra parameter is |
8400 | // the array's type handle (a TypeDesc) |
8401 | // (b) To shared-code instance methods in generic structs; the extra parameter |
8402 | // is the struct's type handle (a vtable ptr) |
8403 | // (c) To shared-code per-instantiation non-generic static methods in generic |
8404 | // classes and structs; the extra parameter is the type handle |
8405 | // (d) To shared-code generic methods; the extra parameter is an |
8406 | // exact-instantiation MethodDesc |
8407 | // |
8408 | // We also set the exact type context associated with the call so we can |
8409 | // inline the call correctly later on. |
8410 | |
8411 | if (sig->callConv & CORINFO_CALLCONV_PARAMTYPE) |
8412 | { |
8413 | assert(call->gtCall.gtCallType == CT_USER_FUNC); |
8414 | if (clsHnd == nullptr) |
8415 | { |
8416 | NO_WAY("CALLI on parameterized type" ); |
8417 | } |
8418 | |
8419 | assert(opcode != CEE_CALLI); |
8420 | |
8421 | GenTree* instParam; |
8422 | BOOL runtimeLookup; |
8423 | |
8424 | // Instantiated generic method |
8425 | if (((SIZE_T)exactContextHnd & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD) |
8426 | { |
8427 | CORINFO_METHOD_HANDLE exactMethodHandle = |
8428 | (CORINFO_METHOD_HANDLE)((SIZE_T)exactContextHnd & ~CORINFO_CONTEXTFLAGS_MASK); |
8429 | |
8430 | if (!exactContextNeedsRuntimeLookup) |
8431 | { |
8432 | #ifdef FEATURE_READYTORUN_COMPILER |
8433 | if (opts.IsReadyToRun()) |
8434 | { |
8435 | instParam = |
8436 | impReadyToRunLookupToTree(&callInfo->instParamLookup, GTF_ICON_METHOD_HDL, exactMethodHandle); |
8437 | if (instParam == nullptr) |
8438 | { |
8439 | assert(compDonotInline()); |
8440 | return TYP_UNDEF; |
8441 | } |
8442 | } |
8443 | else |
8444 | #endif |
8445 | { |
8446 | instParam = gtNewIconEmbMethHndNode(exactMethodHandle); |
8447 | info.compCompHnd->methodMustBeLoadedBeforeCodeIsRun(exactMethodHandle); |
8448 | } |
8449 | } |
8450 | else |
8451 | { |
8452 | instParam = impTokenToHandle(pResolvedToken, &runtimeLookup, TRUE /*mustRestoreHandle*/); |
8453 | if (instParam == nullptr) |
8454 | { |
8455 | assert(compDonotInline()); |
8456 | return TYP_UNDEF; |
8457 | } |
8458 | } |
8459 | } |
8460 | |
8461 | // otherwise must be an instance method in a generic struct, |
8462 | // a static method in a generic type, or a runtime-generated array method |
8463 | else |
8464 | { |
8465 | assert(((SIZE_T)exactContextHnd & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_CLASS); |
8466 | CORINFO_CLASS_HANDLE exactClassHandle = |
8467 | (CORINFO_CLASS_HANDLE)((SIZE_T)exactContextHnd & ~CORINFO_CONTEXTFLAGS_MASK); |
8468 | |
8469 | if (compIsForInlining() && (clsFlags & CORINFO_FLG_ARRAY) != 0) |
8470 | { |
8471 | compInlineResult->NoteFatal(InlineObservation::CALLEE_IS_ARRAY_METHOD); |
8472 | return TYP_UNDEF; |
8473 | } |
8474 | |
8475 | if ((clsFlags & CORINFO_FLG_ARRAY) && readonlyCall) |
8476 | { |
8477 | // We indicate "readonly" to the Address operation by using a null |
8478 | // instParam. |
8479 | instParam = gtNewIconNode(0, TYP_REF); |
8480 | } |
8481 | else if (!exactContextNeedsRuntimeLookup) |
8482 | { |
8483 | #ifdef FEATURE_READYTORUN_COMPILER |
8484 | if (opts.IsReadyToRun()) |
8485 | { |
8486 | instParam = |
8487 | impReadyToRunLookupToTree(&callInfo->instParamLookup, GTF_ICON_CLASS_HDL, exactClassHandle); |
8488 | if (instParam == nullptr) |
8489 | { |
8490 | assert(compDonotInline()); |
8491 | return TYP_UNDEF; |
8492 | } |
8493 | } |
8494 | else |
8495 | #endif |
8496 | { |
8497 | instParam = gtNewIconEmbClsHndNode(exactClassHandle); |
8498 | info.compCompHnd->classMustBeLoadedBeforeCodeIsRun(exactClassHandle); |
8499 | } |
8500 | } |
8501 | else |
8502 | { |
8503 | // If the EE was able to resolve a constrained call, the instantiating parameter to use is the type |
8504 | // by which the call was constrained with. We embed pConstrainedResolvedToken as the extra argument |
8505 | // because pResolvedToken is an interface method and interface types make a poor generic context. |
8506 | if (pConstrainedResolvedToken) |
8507 | { |
8508 | instParam = impTokenToHandle(pConstrainedResolvedToken, &runtimeLookup, TRUE /*mustRestoreHandle*/, |
8509 | FALSE /* importParent */); |
8510 | } |
8511 | else |
8512 | { |
8513 | instParam = impParentClassTokenToHandle(pResolvedToken, &runtimeLookup, TRUE /*mustRestoreHandle*/); |
8514 | } |
8515 | |
8516 | if (instParam == nullptr) |
8517 | { |
8518 | assert(compDonotInline()); |
8519 | return TYP_UNDEF; |
8520 | } |
8521 | } |
8522 | } |
8523 | |
8524 | assert(extraArg == nullptr); |
8525 | extraArg = gtNewArgList(instParam); |
8526 | } |
8527 | |
8528 | // Inlining may need the exact type context (exactContextHnd) if we're inlining shared generic code, in particular |
8529 | // to inline 'polytypic' operations such as static field accesses, type tests and method calls which |
8530 | // rely on the exact context. The exactContextHnd is passed back to the JitInterface at appropriate points. |
8531 | // exactContextHnd is not currently required when inlining shared generic code into shared |
8532 | // generic code, since the inliner aborts whenever shared code polytypic operations are encountered |
8533 | // (e.g. anything marked needsRuntimeLookup) |
8534 | if (exactContextNeedsRuntimeLookup) |
8535 | { |
8536 | exactContextHnd = nullptr; |
8537 | } |
8538 | |
8539 | if ((opcode == CEE_NEWOBJ) && ((clsFlags & CORINFO_FLG_DELEGATE) != 0)) |
8540 | { |
8541 | // Only verifiable cases are supported. |
8542 | // dup; ldvirtftn; newobj; or ldftn; newobj. |
8543 | // IL test could contain unverifiable sequence, in this case optimization should not be done. |
8544 | if (impStackHeight() > 0) |
8545 | { |
8546 | typeInfo delegateTypeInfo = impStackTop().seTypeInfo; |
8547 | if (delegateTypeInfo.IsToken()) |
8548 | { |
8549 | ldftnToken = delegateTypeInfo.GetToken(); |
8550 | } |
8551 | } |
8552 | } |
8553 | |
8554 | //------------------------------------------------------------------------- |
8555 | // The main group of arguments |
8556 | |
8557 | args = call->gtCall.gtCallArgs = impPopList(sig->numArgs, sig, extraArg); |
8558 | |
8559 | if (args) |
8560 | { |
8561 | call->gtFlags |= args->gtFlags & GTF_GLOB_EFFECT; |
8562 | } |
8563 | |
8564 | //------------------------------------------------------------------------- |
8565 | // The "this" pointer |
8566 | |
8567 | if (!(mflags & CORINFO_FLG_STATIC) && !((opcode == CEE_NEWOBJ) && (newobjThis == nullptr))) |
8568 | { |
8569 | GenTree* obj; |
8570 | |
8571 | if (opcode == CEE_NEWOBJ) |
8572 | { |
8573 | obj = newobjThis; |
8574 | } |
8575 | else |
8576 | { |
8577 | obj = impPopStack().val; |
8578 | obj = impTransformThis(obj, pConstrainedResolvedToken, constraintCallThisTransform); |
8579 | if (compDonotInline()) |
8580 | { |
8581 | return TYP_UNDEF; |
8582 | } |
8583 | } |
8584 | |
8585 | // Store the "this" value in the call |
8586 | call->gtFlags |= obj->gtFlags & GTF_GLOB_EFFECT; |
8587 | call->gtCall.gtCallObjp = obj; |
8588 | |
8589 | // Is this a virtual or interface call? |
8590 | if (call->gtCall.IsVirtual()) |
8591 | { |
8592 | // only true object pointers can be virtual |
8593 | assert(obj->gtType == TYP_REF); |
8594 | |
8595 | // See if we can devirtualize. |
8596 | const bool isLateDevirtualization = false; |
8597 | impDevirtualizeCall(call->AsCall(), &callInfo->hMethod, &callInfo->methodFlags, &callInfo->contextHandle, |
8598 | &exactContextHnd, isLateDevirtualization); |
8599 | } |
8600 | |
8601 | if (impIsThis(obj)) |
8602 | { |
8603 | call->gtCall.gtCallMoreFlags |= GTF_CALL_M_NONVIRT_SAME_THIS; |
8604 | } |
8605 | } |
8606 | |
8607 | //------------------------------------------------------------------------- |
8608 | // The "this" pointer for "newobj" |
8609 | |
8610 | if (opcode == CEE_NEWOBJ) |
8611 | { |
8612 | if (clsFlags & CORINFO_FLG_VAROBJSIZE) |
8613 | { |
8614 | assert(!(clsFlags & CORINFO_FLG_ARRAY)); // arrays handled separately |
8615 | // This is a 'new' of a variable sized object, wher |
8616 | // the constructor is to return the object. In this case |
8617 | // the constructor claims to return VOID but we know it |
8618 | // actually returns the new object |
8619 | assert(callRetTyp == TYP_VOID); |
8620 | callRetTyp = TYP_REF; |
8621 | call->gtType = TYP_REF; |
8622 | impSpillSpecialSideEff(); |
8623 | |
8624 | impPushOnStack(call, typeInfo(TI_REF, clsHnd)); |
8625 | } |
8626 | else |
8627 | { |
8628 | if (clsFlags & CORINFO_FLG_DELEGATE) |
8629 | { |
8630 | // New inliner morph it in impImportCall. |
8631 | // This will allow us to inline the call to the delegate constructor. |
8632 | call = fgOptimizeDelegateConstructor(call->AsCall(), &exactContextHnd, ldftnToken); |
8633 | } |
8634 | |
8635 | if (!bIntrinsicImported) |
8636 | { |
8637 | |
8638 | #if defined(DEBUG) || defined(INLINE_DATA) |
8639 | |
8640 | // Keep track of the raw IL offset of the call |
8641 | call->gtCall.gtRawILOffset = rawILOffset; |
8642 | |
8643 | #endif // defined(DEBUG) || defined(INLINE_DATA) |
8644 | |
8645 | // Is it an inline candidate? |
8646 | impMarkInlineCandidate(call, exactContextHnd, exactContextNeedsRuntimeLookup, callInfo); |
8647 | } |
8648 | |
8649 | // append the call node. |
8650 | impAppendTree(call, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs); |
8651 | |
8652 | // Now push the value of the 'new onto the stack |
8653 | |
8654 | // This is a 'new' of a non-variable sized object. |
8655 | // Append the new node (op1) to the statement list, |
8656 | // and then push the local holding the value of this |
8657 | // new instruction on the stack. |
8658 | |
8659 | if (clsFlags & CORINFO_FLG_VALUECLASS) |
8660 | { |
8661 | assert(newobjThis->gtOper == GT_ADDR && newobjThis->gtOp.gtOp1->gtOper == GT_LCL_VAR); |
8662 | |
8663 | unsigned tmp = newobjThis->gtOp.gtOp1->gtLclVarCommon.gtLclNum; |
8664 | impPushOnStack(gtNewLclvNode(tmp, lvaGetRealType(tmp)), verMakeTypeInfo(clsHnd).NormaliseForStack()); |
8665 | } |
8666 | else |
8667 | { |
8668 | if (newobjThis->gtOper == GT_COMMA) |
8669 | { |
8670 | // In coreclr the callout can be inserted even if verification is disabled |
8671 | // so we cannot rely on tiVerificationNeeded alone |
8672 | |
8673 | // We must have inserted the callout. Get the real newobj. |
8674 | newobjThis = newobjThis->gtOp.gtOp2; |
8675 | } |
8676 | |
8677 | assert(newobjThis->gtOper == GT_LCL_VAR); |
8678 | impPushOnStack(gtNewLclvNode(newobjThis->gtLclVarCommon.gtLclNum, TYP_REF), typeInfo(TI_REF, clsHnd)); |
8679 | } |
8680 | } |
8681 | return callRetTyp; |
8682 | } |
8683 | |
8684 | DONE: |
8685 | |
8686 | if (tailCall) |
8687 | { |
8688 | // This check cannot be performed for implicit tail calls for the reason |
8689 | // that impIsImplicitTailCallCandidate() is not checking whether return |
8690 | // types are compatible before marking a call node with PREFIX_TAILCALL_IMPLICIT. |
8691 | // As a result it is possible that in the following case, we find that |
8692 | // the type stack is non-empty if Callee() is considered for implicit |
8693 | // tail calling. |
8694 | // int Caller(..) { .... void Callee(); ret val; ... } |
8695 | // |
8696 | // Note that we cannot check return type compatibility before ImpImportCall() |
8697 | // as we don't have required info or need to duplicate some of the logic of |
8698 | // ImpImportCall(). |
8699 | // |
8700 | // For implicit tail calls, we perform this check after return types are |
8701 | // known to be compatible. |
8702 | if ((tailCall & PREFIX_TAILCALL_EXPLICIT) && (verCurrentState.esStackDepth != 0)) |
8703 | { |
8704 | BADCODE("Stack should be empty after tailcall" ); |
8705 | } |
8706 | |
8707 | // Note that we can not relax this condition with genActualType() as |
8708 | // the calling convention dictates that the caller of a function with |
8709 | // a small-typed return value is responsible for normalizing the return val |
8710 | |
8711 | if (canTailCall && |
8712 | !impTailCallRetTypeCompatible(info.compRetType, info.compMethodInfo->args.retTypeClass, callRetTyp, |
8713 | callInfo->sig.retTypeClass)) |
8714 | { |
8715 | canTailCall = false; |
8716 | szCanTailCallFailReason = "Return types are not tail call compatible" ; |
8717 | } |
8718 | |
8719 | // Stack empty check for implicit tail calls. |
8720 | if (canTailCall && (tailCall & PREFIX_TAILCALL_IMPLICIT) && (verCurrentState.esStackDepth != 0)) |
8721 | { |
8722 | #ifdef _TARGET_AMD64_ |
8723 | // JIT64 Compatibility: Opportunistic tail call stack mismatch throws a VerificationException |
8724 | // in JIT64, not an InvalidProgramException. |
8725 | Verify(false, "Stack should be empty after tailcall" ); |
8726 | #else // _TARGET_64BIT_ |
8727 | BADCODE("Stack should be empty after tailcall" ); |
8728 | #endif //!_TARGET_64BIT_ |
8729 | } |
8730 | |
8731 | // assert(compCurBB is not a catch, finally or filter block); |
8732 | // assert(compCurBB is not a try block protected by a finally block); |
8733 | |
8734 | // Check for permission to tailcall |
8735 | bool explicitTailCall = (tailCall & PREFIX_TAILCALL_EXPLICIT) != 0; |
8736 | |
8737 | assert(!explicitTailCall || compCurBB->bbJumpKind == BBJ_RETURN); |
8738 | |
8739 | if (canTailCall) |
8740 | { |
8741 | // True virtual or indirect calls, shouldn't pass in a callee handle. |
8742 | CORINFO_METHOD_HANDLE exactCalleeHnd = |
8743 | ((call->gtCall.gtCallType != CT_USER_FUNC) || call->gtCall.IsVirtual()) ? nullptr : methHnd; |
8744 | GenTree* thisArg = call->gtCall.gtCallObjp; |
8745 | |
8746 | if (info.compCompHnd->canTailCall(info.compMethodHnd, methHnd, exactCalleeHnd, explicitTailCall)) |
8747 | { |
8748 | canTailCall = true; |
8749 | if (explicitTailCall) |
8750 | { |
8751 | // In case of explicit tail calls, mark it so that it is not considered |
8752 | // for in-lining. |
8753 | call->gtCall.gtCallMoreFlags |= GTF_CALL_M_EXPLICIT_TAILCALL; |
8754 | #ifdef DEBUG |
8755 | if (verbose) |
8756 | { |
8757 | printf("\nGTF_CALL_M_EXPLICIT_TAILCALL bit set for call " ); |
8758 | printTreeID(call); |
8759 | printf("\n" ); |
8760 | } |
8761 | #endif |
8762 | } |
8763 | else |
8764 | { |
8765 | #if FEATURE_TAILCALL_OPT |
8766 | // Must be an implicit tail call. |
8767 | assert((tailCall & PREFIX_TAILCALL_IMPLICIT) != 0); |
8768 | |
8769 | // It is possible that a call node is both an inline candidate and marked |
8770 | // for opportunistic tail calling. In-lining happens before morhphing of |
8771 | // trees. If in-lining of an in-line candidate gets aborted for whatever |
8772 | // reason, it will survive to the morphing stage at which point it will be |
8773 | // transformed into a tail call after performing additional checks. |
8774 | |
8775 | call->gtCall.gtCallMoreFlags |= GTF_CALL_M_IMPLICIT_TAILCALL; |
8776 | #ifdef DEBUG |
8777 | if (verbose) |
8778 | { |
8779 | printf("\nGTF_CALL_M_IMPLICIT_TAILCALL bit set for call " ); |
8780 | printTreeID(call); |
8781 | printf("\n" ); |
8782 | } |
8783 | #endif |
8784 | |
8785 | #else //! FEATURE_TAILCALL_OPT |
8786 | NYI("Implicit tail call prefix on a target which doesn't support opportunistic tail calls" ); |
8787 | |
8788 | #endif // FEATURE_TAILCALL_OPT |
8789 | } |
8790 | |
8791 | // we can't report success just yet... |
8792 | } |
8793 | else |
8794 | { |
8795 | canTailCall = false; |
8796 | // canTailCall reported its reasons already |
8797 | #ifdef DEBUG |
8798 | if (verbose) |
8799 | { |
8800 | printf("\ninfo.compCompHnd->canTailCall returned false for call " ); |
8801 | printTreeID(call); |
8802 | printf("\n" ); |
8803 | } |
8804 | #endif |
8805 | } |
8806 | } |
8807 | else |
8808 | { |
8809 | // If this assert fires it means that canTailCall was set to false without setting a reason! |
8810 | assert(szCanTailCallFailReason != nullptr); |
8811 | |
8812 | #ifdef DEBUG |
8813 | if (verbose) |
8814 | { |
8815 | printf("\nRejecting %splicit tail call for call " , explicitTailCall ? "ex" : "im" ); |
8816 | printTreeID(call); |
8817 | printf(": %s\n" , szCanTailCallFailReason); |
8818 | } |
8819 | #endif |
8820 | info.compCompHnd->reportTailCallDecision(info.compMethodHnd, methHnd, explicitTailCall, TAILCALL_FAIL, |
8821 | szCanTailCallFailReason); |
8822 | } |
8823 | } |
8824 | |
8825 | // Note: we assume that small return types are already normalized by the managed callee |
8826 | // or by the pinvoke stub for calls to unmanaged code. |
8827 | |
8828 | if (!bIntrinsicImported) |
8829 | { |
8830 | // |
8831 | // Things needed to be checked when bIntrinsicImported is false. |
8832 | // |
8833 | |
8834 | assert(call->gtOper == GT_CALL); |
8835 | assert(sig != nullptr); |
8836 | |
8837 | // Tail calls require us to save the call site's sig info so we can obtain an argument |
8838 | // copying thunk from the EE later on. |
8839 | if (call->gtCall.callSig == nullptr) |
8840 | { |
8841 | call->gtCall.callSig = new (this, CMK_CorSig) CORINFO_SIG_INFO; |
8842 | *call->gtCall.callSig = *sig; |
8843 | } |
8844 | |
8845 | if (compIsForInlining() && opcode == CEE_CALLVIRT) |
8846 | { |
8847 | GenTree* callObj = call->gtCall.gtCallObjp; |
8848 | assert(callObj != nullptr); |
8849 | |
8850 | if ((call->gtCall.IsVirtual() || (call->gtFlags & GTF_CALL_NULLCHECK)) && |
8851 | impInlineIsGuaranteedThisDerefBeforeAnySideEffects(call->gtCall.gtCallArgs, callObj, |
8852 | impInlineInfo->inlArgInfo)) |
8853 | { |
8854 | impInlineInfo->thisDereferencedFirst = true; |
8855 | } |
8856 | } |
8857 | |
8858 | #if defined(DEBUG) || defined(INLINE_DATA) |
8859 | |
8860 | // Keep track of the raw IL offset of the call |
8861 | call->gtCall.gtRawILOffset = rawILOffset; |
8862 | |
8863 | #endif // defined(DEBUG) || defined(INLINE_DATA) |
8864 | |
8865 | // Is it an inline candidate? |
8866 | impMarkInlineCandidate(call, exactContextHnd, exactContextNeedsRuntimeLookup, callInfo); |
8867 | } |
8868 | |
8869 | DONE_CALL: |
8870 | // Push or append the result of the call |
8871 | if (callRetTyp == TYP_VOID) |
8872 | { |
8873 | if (opcode == CEE_NEWOBJ) |
8874 | { |
8875 | // we actually did push something, so don't spill the thing we just pushed. |
8876 | assert(verCurrentState.esStackDepth > 0); |
8877 | impAppendTree(call, verCurrentState.esStackDepth - 1, impCurStmtOffs); |
8878 | } |
8879 | else |
8880 | { |
8881 | impAppendTree(call, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs); |
8882 | } |
8883 | } |
8884 | else |
8885 | { |
8886 | impSpillSpecialSideEff(); |
8887 | |
8888 | if (clsFlags & CORINFO_FLG_ARRAY) |
8889 | { |
8890 | eeGetCallSiteSig(pResolvedToken->token, pResolvedToken->tokenScope, pResolvedToken->tokenContext, sig); |
8891 | } |
8892 | |
8893 | // Find the return type used for verification by interpreting the method signature. |
8894 | // NB: we are clobbering the already established sig. |
8895 | if (tiVerificationNeeded) |
8896 | { |
8897 | // Actually, we never get the sig for the original method. |
8898 | sig = &(callInfo->verSig); |
8899 | } |
8900 | |
8901 | typeInfo tiRetVal = verMakeTypeInfo(sig->retType, sig->retTypeClass); |
8902 | tiRetVal.NormaliseForStack(); |
8903 | |
8904 | // The CEE_READONLY prefix modifies the verification semantics of an Address |
8905 | // operation on an array type. |
8906 | if ((clsFlags & CORINFO_FLG_ARRAY) && readonlyCall && tiRetVal.IsByRef()) |
8907 | { |
8908 | tiRetVal.SetIsReadonlyByRef(); |
8909 | } |
8910 | |
8911 | if (tiVerificationNeeded) |
8912 | { |
8913 | // We assume all calls return permanent home byrefs. If they |
8914 | // didn't they wouldn't be verifiable. This is also covering |
8915 | // the Address() helper for multidimensional arrays. |
8916 | if (tiRetVal.IsByRef()) |
8917 | { |
8918 | tiRetVal.SetIsPermanentHomeByRef(); |
8919 | } |
8920 | } |
8921 | |
8922 | if (call->IsCall()) |
8923 | { |
8924 | // Sometimes "call" is not a GT_CALL (if we imported an intrinsic that didn't turn into a call) |
8925 | |
8926 | GenTreeCall* origCall = call->AsCall(); |
8927 | |
8928 | const bool isFatPointerCandidate = origCall->IsFatPointerCandidate(); |
8929 | const bool isInlineCandidate = origCall->IsInlineCandidate(); |
8930 | const bool isGuardedDevirtualizationCandidate = origCall->IsGuardedDevirtualizationCandidate(); |
8931 | |
8932 | if (varTypeIsStruct(callRetTyp)) |
8933 | { |
8934 | // Need to treat all "split tree" cases here, not just inline candidates |
8935 | call = impFixupCallStructReturn(call->AsCall(), sig->retTypeClass); |
8936 | } |
8937 | |
8938 | // TODO: consider handling fatcalli cases this way too...? |
8939 | if (isInlineCandidate || isGuardedDevirtualizationCandidate) |
8940 | { |
8941 | // We should not have made any adjustments in impFixupCallStructReturn |
8942 | // as we defer those until we know the fate of the call. |
8943 | assert(call == origCall); |
8944 | |
8945 | assert(opts.OptEnabled(CLFLG_INLINING)); |
8946 | assert(!isFatPointerCandidate); // We should not try to inline calli. |
8947 | |
8948 | // Make the call its own tree (spill the stack if needed). |
8949 | impAppendTree(call, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs); |
8950 | |
8951 | // TODO: Still using the widened type. |
8952 | GenTree* retExpr = gtNewInlineCandidateReturnExpr(call, genActualType(callRetTyp)); |
8953 | |
8954 | // Link the retExpr to the call so if necessary we can manipulate it later. |
8955 | origCall->gtInlineCandidateInfo->retExpr = retExpr; |
8956 | |
8957 | // Propagate retExpr as the placeholder for the call. |
8958 | call = retExpr; |
8959 | } |
8960 | else |
8961 | { |
8962 | if (isFatPointerCandidate) |
8963 | { |
8964 | // fatPointer candidates should be in statements of the form call() or var = call(). |
8965 | // Such form allows to find statements with fat calls without walking through whole trees |
8966 | // and removes problems with cutting trees. |
8967 | assert(!bIntrinsicImported); |
8968 | assert(IsTargetAbi(CORINFO_CORERT_ABI)); |
8969 | if (call->OperGet() != GT_LCL_VAR) // can be already converted by impFixupCallStructReturn. |
8970 | { |
8971 | unsigned calliSlot = lvaGrabTemp(true DEBUGARG("calli" )); |
8972 | LclVarDsc* varDsc = &lvaTable[calliSlot]; |
8973 | varDsc->lvVerTypeInfo = tiRetVal; |
8974 | impAssignTempGen(calliSlot, call, tiRetVal.GetClassHandle(), (unsigned)CHECK_SPILL_NONE); |
8975 | // impAssignTempGen can change src arg list and return type for call that returns struct. |
8976 | var_types type = genActualType(lvaTable[calliSlot].TypeGet()); |
8977 | call = gtNewLclvNode(calliSlot, type); |
8978 | } |
8979 | } |
8980 | |
8981 | // For non-candidates we must also spill, since we |
8982 | // might have locals live on the eval stack that this |
8983 | // call can modify. |
8984 | // |
8985 | // Suppress this for certain well-known call targets |
8986 | // that we know won't modify locals, eg calls that are |
8987 | // recognized in gtCanOptimizeTypeEquality. Otherwise |
8988 | // we may break key fragile pattern matches later on. |
8989 | bool spillStack = true; |
8990 | if (call->IsCall()) |
8991 | { |
8992 | GenTreeCall* callNode = call->AsCall(); |
8993 | if ((callNode->gtCallType == CT_HELPER) && (gtIsTypeHandleToRuntimeTypeHelper(callNode) || |
8994 | gtIsTypeHandleToRuntimeTypeHandleHelper(callNode))) |
8995 | { |
8996 | spillStack = false; |
8997 | } |
8998 | else if ((callNode->gtCallMoreFlags & GTF_CALL_M_SPECIAL_INTRINSIC) != 0) |
8999 | { |
9000 | spillStack = false; |
9001 | } |
9002 | } |
9003 | |
9004 | if (spillStack) |
9005 | { |
9006 | impSpillSideEffects(true, CHECK_SPILL_ALL DEBUGARG("non-inline candidate call" )); |
9007 | } |
9008 | } |
9009 | } |
9010 | |
9011 | if (!bIntrinsicImported) |
9012 | { |
9013 | //------------------------------------------------------------------------- |
9014 | // |
9015 | /* If the call is of a small type and the callee is managed, the callee will normalize the result |
9016 | before returning. |
9017 | However, we need to normalize small type values returned by unmanaged |
9018 | functions (pinvoke). The pinvoke stub does the normalization, but we need to do it here |
9019 | if we use the shorter inlined pinvoke stub. */ |
9020 | |
9021 | if (checkForSmallType && varTypeIsIntegral(callRetTyp) && genTypeSize(callRetTyp) < genTypeSize(TYP_INT)) |
9022 | { |
9023 | call = gtNewCastNode(genActualType(callRetTyp), call, false, callRetTyp); |
9024 | } |
9025 | } |
9026 | |
9027 | impPushOnStack(call, tiRetVal); |
9028 | } |
9029 | |
9030 | // VSD functions get a new call target each time we getCallInfo, so clear the cache. |
9031 | // Also, the call info cache for CALLI instructions is largely incomplete, so clear it out. |
9032 | // if ( (opcode == CEE_CALLI) || (callInfoCache.fetchCallInfo().kind == CORINFO_VIRTUALCALL_STUB)) |
9033 | // callInfoCache.uncacheCallInfo(); |
9034 | |
9035 | return callRetTyp; |
9036 | } |
9037 | #ifdef _PREFAST_ |
9038 | #pragma warning(pop) |
9039 | #endif |
9040 | |
9041 | bool Compiler::impMethodInfo_hasRetBuffArg(CORINFO_METHOD_INFO* methInfo) |
9042 | { |
9043 | CorInfoType corType = methInfo->args.retType; |
9044 | |
9045 | if ((corType == CORINFO_TYPE_VALUECLASS) || (corType == CORINFO_TYPE_REFANY)) |
9046 | { |
9047 | // We have some kind of STRUCT being returned |
9048 | |
9049 | structPassingKind howToReturnStruct = SPK_Unknown; |
9050 | |
9051 | var_types returnType = getReturnTypeForStruct(methInfo->args.retTypeClass, &howToReturnStruct); |
9052 | |
9053 | if (howToReturnStruct == SPK_ByReference) |
9054 | { |
9055 | return true; |
9056 | } |
9057 | } |
9058 | |
9059 | return false; |
9060 | } |
9061 | |
9062 | #ifdef DEBUG |
9063 | // |
9064 | var_types Compiler::impImportJitTestLabelMark(int numArgs) |
9065 | { |
9066 | TestLabelAndNum tlAndN; |
9067 | if (numArgs == 2) |
9068 | { |
9069 | tlAndN.m_num = 0; |
9070 | StackEntry se = impPopStack(); |
9071 | assert(se.seTypeInfo.GetType() == TI_INT); |
9072 | GenTree* val = se.val; |
9073 | assert(val->IsCnsIntOrI()); |
9074 | tlAndN.m_tl = (TestLabel)val->AsIntConCommon()->IconValue(); |
9075 | } |
9076 | else if (numArgs == 3) |
9077 | { |
9078 | StackEntry se = impPopStack(); |
9079 | assert(se.seTypeInfo.GetType() == TI_INT); |
9080 | GenTree* val = se.val; |
9081 | assert(val->IsCnsIntOrI()); |
9082 | tlAndN.m_num = val->AsIntConCommon()->IconValue(); |
9083 | se = impPopStack(); |
9084 | assert(se.seTypeInfo.GetType() == TI_INT); |
9085 | val = se.val; |
9086 | assert(val->IsCnsIntOrI()); |
9087 | tlAndN.m_tl = (TestLabel)val->AsIntConCommon()->IconValue(); |
9088 | } |
9089 | else |
9090 | { |
9091 | assert(false); |
9092 | } |
9093 | |
9094 | StackEntry expSe = impPopStack(); |
9095 | GenTree* node = expSe.val; |
9096 | |
9097 | // There are a small number of special cases, where we actually put the annotation on a subnode. |
9098 | if (tlAndN.m_tl == TL_LoopHoist && tlAndN.m_num >= 100) |
9099 | { |
9100 | // A loop hoist annotation with value >= 100 means that the expression should be a static field access, |
9101 | // a GT_IND of a static field address, which should be the sum of a (hoistable) helper call and possibly some |
9102 | // offset within the the static field block whose address is returned by the helper call. |
9103 | // The annotation is saying that this address calculation, but not the entire access, should be hoisted. |
9104 | GenTree* helperCall = nullptr; |
9105 | assert(node->OperGet() == GT_IND); |
9106 | tlAndN.m_num -= 100; |
9107 | GetNodeTestData()->Set(node->gtOp.gtOp1, tlAndN); |
9108 | GetNodeTestData()->Remove(node); |
9109 | } |
9110 | else |
9111 | { |
9112 | GetNodeTestData()->Set(node, tlAndN); |
9113 | } |
9114 | |
9115 | impPushOnStack(node, expSe.seTypeInfo); |
9116 | return node->TypeGet(); |
9117 | } |
9118 | #endif // DEBUG |
9119 | |
9120 | //----------------------------------------------------------------------------------- |
9121 | // impFixupCallStructReturn: For a call node that returns a struct type either |
9122 | // adjust the return type to an enregisterable type, or set the flag to indicate |
9123 | // struct return via retbuf arg. |
9124 | // |
9125 | // Arguments: |
9126 | // call - GT_CALL GenTree node |
9127 | // retClsHnd - Class handle of return type of the call |
9128 | // |
9129 | // Return Value: |
9130 | // Returns new GenTree node after fixing struct return of call node |
9131 | // |
9132 | GenTree* Compiler::impFixupCallStructReturn(GenTreeCall* call, CORINFO_CLASS_HANDLE retClsHnd) |
9133 | { |
9134 | if (!varTypeIsStruct(call)) |
9135 | { |
9136 | return call; |
9137 | } |
9138 | |
9139 | call->gtRetClsHnd = retClsHnd; |
9140 | |
9141 | #if FEATURE_MULTIREG_RET |
9142 | // Initialize Return type descriptor of call node |
9143 | ReturnTypeDesc* retTypeDesc = call->GetReturnTypeDesc(); |
9144 | retTypeDesc->InitializeStructReturnType(this, retClsHnd); |
9145 | #endif // FEATURE_MULTIREG_RET |
9146 | |
9147 | #ifdef UNIX_AMD64_ABI |
9148 | |
9149 | // Not allowed for FEATURE_CORCLR which is the only SKU available for System V OSs. |
9150 | assert(!call->IsVarargs() && "varargs not allowed for System V OSs." ); |
9151 | |
9152 | // The return type will remain as the incoming struct type unless normalized to a |
9153 | // single eightbyte return type below. |
9154 | call->gtReturnType = call->gtType; |
9155 | |
9156 | unsigned retRegCount = retTypeDesc->GetReturnRegCount(); |
9157 | if (retRegCount != 0) |
9158 | { |
9159 | if (retRegCount == 1) |
9160 | { |
9161 | // See if the struct size is smaller than the return |
9162 | // type size... |
9163 | if (retTypeDesc->IsEnclosingType()) |
9164 | { |
9165 | // If we know for sure this call will remain a call, |
9166 | // retype and return value via a suitable temp. |
9167 | if ((!call->CanTailCall()) && (!call->IsInlineCandidate())) |
9168 | { |
9169 | call->gtReturnType = retTypeDesc->GetReturnRegType(0); |
9170 | return impAssignSmallStructTypeToVar(call, retClsHnd); |
9171 | } |
9172 | } |
9173 | else |
9174 | { |
9175 | // Return type is same size as struct, so we can |
9176 | // simply retype the call. |
9177 | call->gtReturnType = retTypeDesc->GetReturnRegType(0); |
9178 | } |
9179 | } |
9180 | else |
9181 | { |
9182 | // must be a struct returned in two registers |
9183 | assert(retRegCount == 2); |
9184 | |
9185 | if ((!call->CanTailCall()) && (!call->IsInlineCandidate())) |
9186 | { |
9187 | // Force a call returning multi-reg struct to be always of the IR form |
9188 | // tmp = call |
9189 | // |
9190 | // No need to assign a multi-reg struct to a local var if: |
9191 | // - It is a tail call or |
9192 | // - The call is marked for in-lining later |
9193 | return impAssignMultiRegTypeToVar(call, retClsHnd); |
9194 | } |
9195 | } |
9196 | } |
9197 | else |
9198 | { |
9199 | // struct not returned in registers i.e returned via hiddden retbuf arg. |
9200 | call->gtCallMoreFlags |= GTF_CALL_M_RETBUFFARG; |
9201 | } |
9202 | |
9203 | #else // not UNIX_AMD64_ABI |
9204 | |
9205 | // Check for TYP_STRUCT type that wraps a primitive type |
9206 | // Such structs are returned using a single register |
9207 | // and we change the return type on those calls here. |
9208 | // |
9209 | structPassingKind howToReturnStruct; |
9210 | var_types returnType = getReturnTypeForStruct(retClsHnd, &howToReturnStruct); |
9211 | |
9212 | if (howToReturnStruct == SPK_ByReference) |
9213 | { |
9214 | assert(returnType == TYP_UNKNOWN); |
9215 | call->gtCallMoreFlags |= GTF_CALL_M_RETBUFFARG; |
9216 | } |
9217 | else |
9218 | { |
9219 | assert(returnType != TYP_UNKNOWN); |
9220 | |
9221 | // See if the struct size is smaller than the return |
9222 | // type size... |
9223 | if (howToReturnStruct == SPK_EnclosingType) |
9224 | { |
9225 | // If we know for sure this call will remain a call, |
9226 | // retype and return value via a suitable temp. |
9227 | if ((!call->CanTailCall()) && (!call->IsInlineCandidate())) |
9228 | { |
9229 | call->gtReturnType = returnType; |
9230 | return impAssignSmallStructTypeToVar(call, retClsHnd); |
9231 | } |
9232 | } |
9233 | else |
9234 | { |
9235 | // Return type is same size as struct, so we can |
9236 | // simply retype the call. |
9237 | call->gtReturnType = returnType; |
9238 | } |
9239 | |
9240 | // ToDo: Refactor this common code sequence into its own method as it is used 4+ times |
9241 | if ((returnType == TYP_LONG) && (compLongUsed == false)) |
9242 | { |
9243 | compLongUsed = true; |
9244 | } |
9245 | else if (((returnType == TYP_FLOAT) || (returnType == TYP_DOUBLE)) && (compFloatingPointUsed == false)) |
9246 | { |
9247 | compFloatingPointUsed = true; |
9248 | } |
9249 | |
9250 | #if FEATURE_MULTIREG_RET |
9251 | unsigned retRegCount = retTypeDesc->GetReturnRegCount(); |
9252 | assert(retRegCount != 0); |
9253 | |
9254 | if (retRegCount >= 2) |
9255 | { |
9256 | if ((!call->CanTailCall()) && (!call->IsInlineCandidate())) |
9257 | { |
9258 | // Force a call returning multi-reg struct to be always of the IR form |
9259 | // tmp = call |
9260 | // |
9261 | // No need to assign a multi-reg struct to a local var if: |
9262 | // - It is a tail call or |
9263 | // - The call is marked for in-lining later |
9264 | return impAssignMultiRegTypeToVar(call, retClsHnd); |
9265 | } |
9266 | } |
9267 | #endif // FEATURE_MULTIREG_RET |
9268 | } |
9269 | |
9270 | #endif // not UNIX_AMD64_ABI |
9271 | |
9272 | return call; |
9273 | } |
9274 | |
9275 | /***************************************************************************** |
9276 | For struct return values, re-type the operand in the case where the ABI |
9277 | does not use a struct return buffer |
9278 | Note that this method is only call for !_TARGET_X86_ |
9279 | */ |
9280 | |
9281 | GenTree* Compiler::impFixupStructReturnType(GenTree* op, CORINFO_CLASS_HANDLE retClsHnd) |
9282 | { |
9283 | assert(varTypeIsStruct(info.compRetType)); |
9284 | assert(info.compRetBuffArg == BAD_VAR_NUM); |
9285 | |
9286 | JITDUMP("\nimpFixupStructReturnType: retyping\n" ); |
9287 | DISPTREE(op); |
9288 | |
9289 | #if defined(_TARGET_XARCH_) |
9290 | |
9291 | #ifdef UNIX_AMD64_ABI |
9292 | // No VarArgs for CoreCLR on x64 Unix |
9293 | assert(!info.compIsVarArgs); |
9294 | |
9295 | // Is method returning a multi-reg struct? |
9296 | if (varTypeIsStruct(info.compRetNativeType) && IsMultiRegReturnedType(retClsHnd)) |
9297 | { |
9298 | // In case of multi-reg struct return, we force IR to be one of the following: |
9299 | // GT_RETURN(lclvar) or GT_RETURN(call). If op is anything other than a |
9300 | // lclvar or call, it is assigned to a temp to create: temp = op and GT_RETURN(tmp). |
9301 | |
9302 | if (op->gtOper == GT_LCL_VAR) |
9303 | { |
9304 | // Make sure that this struct stays in memory and doesn't get promoted. |
9305 | unsigned lclNum = op->gtLclVarCommon.gtLclNum; |
9306 | lvaTable[lclNum].lvIsMultiRegRet = true; |
9307 | |
9308 | // TODO-1stClassStructs: Handle constant propagation and CSE-ing of multireg returns. |
9309 | op->gtFlags |= GTF_DONT_CSE; |
9310 | |
9311 | return op; |
9312 | } |
9313 | |
9314 | if (op->gtOper == GT_CALL) |
9315 | { |
9316 | return op; |
9317 | } |
9318 | |
9319 | return impAssignMultiRegTypeToVar(op, retClsHnd); |
9320 | } |
9321 | #else // !UNIX_AMD64_ABI |
9322 | assert(info.compRetNativeType != TYP_STRUCT); |
9323 | #endif // !UNIX_AMD64_ABI |
9324 | |
9325 | #elif FEATURE_MULTIREG_RET && defined(_TARGET_ARM_) |
9326 | |
9327 | if (varTypeIsStruct(info.compRetNativeType) && !info.compIsVarArgs && IsHfa(retClsHnd)) |
9328 | { |
9329 | if (op->gtOper == GT_LCL_VAR) |
9330 | { |
9331 | // This LCL_VAR is an HFA return value, it stays as a TYP_STRUCT |
9332 | unsigned lclNum = op->gtLclVarCommon.gtLclNum; |
9333 | // Make sure this struct type stays as struct so that we can return it as an HFA |
9334 | lvaTable[lclNum].lvIsMultiRegRet = true; |
9335 | |
9336 | // TODO-1stClassStructs: Handle constant propagation and CSE-ing of multireg returns. |
9337 | op->gtFlags |= GTF_DONT_CSE; |
9338 | |
9339 | return op; |
9340 | } |
9341 | |
9342 | if (op->gtOper == GT_CALL) |
9343 | { |
9344 | if (op->gtCall.IsVarargs()) |
9345 | { |
9346 | // We cannot tail call because control needs to return to fixup the calling |
9347 | // convention for result return. |
9348 | op->gtCall.gtCallMoreFlags &= ~GTF_CALL_M_TAILCALL; |
9349 | op->gtCall.gtCallMoreFlags &= ~GTF_CALL_M_EXPLICIT_TAILCALL; |
9350 | } |
9351 | else |
9352 | { |
9353 | return op; |
9354 | } |
9355 | } |
9356 | return impAssignMultiRegTypeToVar(op, retClsHnd); |
9357 | } |
9358 | |
9359 | #elif FEATURE_MULTIREG_RET && defined(_TARGET_ARM64_) |
9360 | |
9361 | // Is method returning a multi-reg struct? |
9362 | if (IsMultiRegReturnedType(retClsHnd)) |
9363 | { |
9364 | if (op->gtOper == GT_LCL_VAR) |
9365 | { |
9366 | // This LCL_VAR stays as a TYP_STRUCT |
9367 | unsigned lclNum = op->gtLclVarCommon.gtLclNum; |
9368 | |
9369 | // Make sure this struct type is not struct promoted |
9370 | lvaTable[lclNum].lvIsMultiRegRet = true; |
9371 | |
9372 | // TODO-1stClassStructs: Handle constant propagation and CSE-ing of multireg returns. |
9373 | op->gtFlags |= GTF_DONT_CSE; |
9374 | |
9375 | return op; |
9376 | } |
9377 | |
9378 | if (op->gtOper == GT_CALL) |
9379 | { |
9380 | if (op->gtCall.IsVarargs()) |
9381 | { |
9382 | // We cannot tail call because control needs to return to fixup the calling |
9383 | // convention for result return. |
9384 | op->gtCall.gtCallMoreFlags &= ~GTF_CALL_M_TAILCALL; |
9385 | op->gtCall.gtCallMoreFlags &= ~GTF_CALL_M_EXPLICIT_TAILCALL; |
9386 | } |
9387 | else |
9388 | { |
9389 | return op; |
9390 | } |
9391 | } |
9392 | return impAssignMultiRegTypeToVar(op, retClsHnd); |
9393 | } |
9394 | |
9395 | #endif // FEATURE_MULTIREG_RET && FEATURE_HFA |
9396 | |
9397 | REDO_RETURN_NODE: |
9398 | // adjust the type away from struct to integral |
9399 | // and no normalizing |
9400 | if (op->gtOper == GT_LCL_VAR) |
9401 | { |
9402 | // It is possible that we now have a lclVar of scalar type. |
9403 | // If so, don't transform it to GT_LCL_FLD. |
9404 | if (varTypeIsStruct(lvaTable[op->AsLclVar()->gtLclNum].lvType)) |
9405 | { |
9406 | op->ChangeOper(GT_LCL_FLD); |
9407 | } |
9408 | } |
9409 | else if (op->gtOper == GT_OBJ) |
9410 | { |
9411 | GenTree* op1 = op->AsObj()->Addr(); |
9412 | |
9413 | // We will fold away OBJ/ADDR |
9414 | // except for OBJ/ADDR/INDEX |
9415 | // as the array type influences the array element's offset |
9416 | // Later in this method we change op->gtType to info.compRetNativeType |
9417 | // This is not correct when op is a GT_INDEX as the starting offset |
9418 | // for the array elements 'elemOffs' is different for an array of |
9419 | // TYP_REF than an array of TYP_STRUCT (which simply wraps a TYP_REF) |
9420 | // Also refer to the GTF_INX_REFARR_LAYOUT flag |
9421 | // |
9422 | if ((op1->gtOper == GT_ADDR) && (op1->gtOp.gtOp1->gtOper != GT_INDEX)) |
9423 | { |
9424 | // Change '*(&X)' to 'X' and see if we can do better |
9425 | op = op1->gtOp.gtOp1; |
9426 | goto REDO_RETURN_NODE; |
9427 | } |
9428 | op->gtObj.gtClass = NO_CLASS_HANDLE; |
9429 | op->ChangeOperUnchecked(GT_IND); |
9430 | op->gtFlags |= GTF_IND_TGTANYWHERE; |
9431 | } |
9432 | else if (op->gtOper == GT_CALL) |
9433 | { |
9434 | if (op->AsCall()->TreatAsHasRetBufArg(this)) |
9435 | { |
9436 | // This must be one of those 'special' helpers that don't |
9437 | // really have a return buffer, but instead use it as a way |
9438 | // to keep the trees cleaner with fewer address-taken temps. |
9439 | // |
9440 | // Well now we have to materialize the the return buffer as |
9441 | // an address-taken temp. Then we can return the temp. |
9442 | // |
9443 | // NOTE: this code assumes that since the call directly |
9444 | // feeds the return, then the call must be returning the |
9445 | // same structure/class/type. |
9446 | // |
9447 | unsigned tmpNum = lvaGrabTemp(true DEBUGARG("pseudo return buffer" )); |
9448 | |
9449 | // No need to spill anything as we're about to return. |
9450 | impAssignTempGen(tmpNum, op, info.compMethodInfo->args.retTypeClass, (unsigned)CHECK_SPILL_NONE); |
9451 | |
9452 | // Don't create both a GT_ADDR & GT_OBJ just to undo all of that; instead, |
9453 | // jump directly to a GT_LCL_FLD. |
9454 | op = gtNewLclvNode(tmpNum, info.compRetNativeType); |
9455 | op->ChangeOper(GT_LCL_FLD); |
9456 | } |
9457 | else |
9458 | { |
9459 | // Don't change the gtType of the call just yet, it will get changed later. |
9460 | return op; |
9461 | } |
9462 | } |
9463 | #if defined(FEATURE_HW_INTRINSICS) && defined(_TARGET_ARM64_) |
9464 | else if ((op->gtOper == GT_HWIntrinsic) && varTypeIsSIMD(op->gtType)) |
9465 | { |
9466 | // TODO-ARM64-FIXME Implement ARM64 ABI for Short Vectors properly |
9467 | // assert(op->gtType == info.compRetNativeType) |
9468 | if (op->gtType != info.compRetNativeType) |
9469 | { |
9470 | // Insert a register move to keep target type of SIMD intrinsic intact |
9471 | op = gtNewScalarHWIntrinsicNode(info.compRetNativeType, op, NI_ARM64_NONE_MOV); |
9472 | } |
9473 | } |
9474 | #endif |
9475 | else if (op->gtOper == GT_COMMA) |
9476 | { |
9477 | op->gtOp.gtOp2 = impFixupStructReturnType(op->gtOp.gtOp2, retClsHnd); |
9478 | } |
9479 | |
9480 | op->gtType = info.compRetNativeType; |
9481 | |
9482 | JITDUMP("\nimpFixupStructReturnType: result of retyping is\n" ); |
9483 | DISPTREE(op); |
9484 | |
9485 | return op; |
9486 | } |
9487 | |
9488 | /***************************************************************************** |
9489 | CEE_LEAVE may be jumping out of a protected block, viz, a catch or a |
9490 | finally-protected try. We find the finally blocks protecting the current |
9491 | offset (in order) by walking over the complete exception table and |
9492 | finding enclosing clauses. This assumes that the table is sorted. |
9493 | This will create a series of BBJ_CALLFINALLY -> BBJ_CALLFINALLY ... -> BBJ_ALWAYS. |
9494 | |
9495 | If we are leaving a catch handler, we need to attach the |
9496 | CPX_ENDCATCHes to the correct BBJ_CALLFINALLY blocks. |
9497 | |
9498 | After this function, the BBJ_LEAVE block has been converted to a different type. |
9499 | */ |
9500 | |
9501 | #if !FEATURE_EH_FUNCLETS |
9502 | |
9503 | void Compiler::impImportLeave(BasicBlock* block) |
9504 | { |
9505 | #ifdef DEBUG |
9506 | if (verbose) |
9507 | { |
9508 | printf("\nBefore import CEE_LEAVE:\n" ); |
9509 | fgDispBasicBlocks(); |
9510 | fgDispHandlerTab(); |
9511 | } |
9512 | #endif // DEBUG |
9513 | |
9514 | bool invalidatePreds = false; // If we create new blocks, invalidate the predecessor lists (if created) |
9515 | unsigned blkAddr = block->bbCodeOffs; |
9516 | BasicBlock* leaveTarget = block->bbJumpDest; |
9517 | unsigned jmpAddr = leaveTarget->bbCodeOffs; |
9518 | |
9519 | // LEAVE clears the stack, spill side effects, and set stack to 0 |
9520 | |
9521 | impSpillSideEffects(true, (unsigned)CHECK_SPILL_ALL DEBUGARG("impImportLeave" )); |
9522 | verCurrentState.esStackDepth = 0; |
9523 | |
9524 | assert(block->bbJumpKind == BBJ_LEAVE); |
9525 | assert(fgBBs == (BasicBlock**)0xCDCD || fgLookupBB(jmpAddr) != NULL); // should be a BB boundary |
9526 | |
9527 | BasicBlock* step = DUMMY_INIT(NULL); |
9528 | unsigned encFinallies = 0; // Number of enclosing finallies. |
9529 | GenTree* endCatches = NULL; |
9530 | GenTree* endLFin = NULL; // The statement tree to indicate the end of locally-invoked finally. |
9531 | |
9532 | unsigned XTnum; |
9533 | EHblkDsc* HBtab; |
9534 | |
9535 | for (XTnum = 0, HBtab = compHndBBtab; XTnum < compHndBBtabCount; XTnum++, HBtab++) |
9536 | { |
9537 | // Grab the handler offsets |
9538 | |
9539 | IL_OFFSET tryBeg = HBtab->ebdTryBegOffs(); |
9540 | IL_OFFSET tryEnd = HBtab->ebdTryEndOffs(); |
9541 | IL_OFFSET hndBeg = HBtab->ebdHndBegOffs(); |
9542 | IL_OFFSET hndEnd = HBtab->ebdHndEndOffs(); |
9543 | |
9544 | /* Is this a catch-handler we are CEE_LEAVEing out of? |
9545 | * If so, we need to call CORINFO_HELP_ENDCATCH. |
9546 | */ |
9547 | |
9548 | if (jitIsBetween(blkAddr, hndBeg, hndEnd) && !jitIsBetween(jmpAddr, hndBeg, hndEnd)) |
9549 | { |
9550 | // Can't CEE_LEAVE out of a finally/fault handler |
9551 | if (HBtab->HasFinallyOrFaultHandler()) |
9552 | BADCODE("leave out of fault/finally block" ); |
9553 | |
9554 | // Create the call to CORINFO_HELP_ENDCATCH |
9555 | GenTree* endCatch = gtNewHelperCallNode(CORINFO_HELP_ENDCATCH, TYP_VOID); |
9556 | |
9557 | // Make a list of all the currently pending endCatches |
9558 | if (endCatches) |
9559 | endCatches = gtNewOperNode(GT_COMMA, TYP_VOID, endCatches, endCatch); |
9560 | else |
9561 | endCatches = endCatch; |
9562 | |
9563 | #ifdef DEBUG |
9564 | if (verbose) |
9565 | { |
9566 | printf("impImportLeave - " FMT_BB " jumping out of catch handler EH#%u, adding call to " |
9567 | "CORINFO_HELP_ENDCATCH\n" , |
9568 | block->bbNum, XTnum); |
9569 | } |
9570 | #endif |
9571 | } |
9572 | else if (HBtab->HasFinallyHandler() && jitIsBetween(blkAddr, tryBeg, tryEnd) && |
9573 | !jitIsBetween(jmpAddr, tryBeg, tryEnd)) |
9574 | { |
9575 | /* This is a finally-protected try we are jumping out of */ |
9576 | |
9577 | /* If there are any pending endCatches, and we have already |
9578 | jumped out of a finally-protected try, then the endCatches |
9579 | have to be put in a block in an outer try for async |
9580 | exceptions to work correctly. |
9581 | Else, just use append to the original block */ |
9582 | |
9583 | BasicBlock* callBlock; |
9584 | |
9585 | assert(!encFinallies == !endLFin); // if we have finallies, we better have an endLFin tree, and vice-versa |
9586 | |
9587 | if (encFinallies == 0) |
9588 | { |
9589 | assert(step == DUMMY_INIT(NULL)); |
9590 | callBlock = block; |
9591 | callBlock->bbJumpKind = BBJ_CALLFINALLY; // convert the BBJ_LEAVE to BBJ_CALLFINALLY |
9592 | |
9593 | if (endCatches) |
9594 | impAppendTree(endCatches, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); |
9595 | |
9596 | #ifdef DEBUG |
9597 | if (verbose) |
9598 | { |
9599 | printf("impImportLeave - jumping out of a finally-protected try, convert block to BBJ_CALLFINALLY " |
9600 | "block %s\n" , |
9601 | callBlock->dspToString()); |
9602 | } |
9603 | #endif |
9604 | } |
9605 | else |
9606 | { |
9607 | assert(step != DUMMY_INIT(NULL)); |
9608 | |
9609 | /* Calling the finally block */ |
9610 | callBlock = fgNewBBinRegion(BBJ_CALLFINALLY, XTnum + 1, 0, step); |
9611 | assert(step->bbJumpKind == BBJ_ALWAYS); |
9612 | step->bbJumpDest = callBlock; // the previous call to a finally returns to this call (to the next |
9613 | // finally in the chain) |
9614 | step->bbJumpDest->bbRefs++; |
9615 | |
9616 | /* The new block will inherit this block's weight */ |
9617 | callBlock->setBBWeight(block->bbWeight); |
9618 | callBlock->bbFlags |= block->bbFlags & BBF_RUN_RARELY; |
9619 | |
9620 | #ifdef DEBUG |
9621 | if (verbose) |
9622 | { |
9623 | printf("impImportLeave - jumping out of a finally-protected try, new BBJ_CALLFINALLY block %s\n" , |
9624 | callBlock->dspToString()); |
9625 | } |
9626 | #endif |
9627 | |
9628 | GenTree* lastStmt; |
9629 | |
9630 | if (endCatches) |
9631 | { |
9632 | lastStmt = gtNewStmt(endCatches); |
9633 | endLFin->gtNext = lastStmt; |
9634 | lastStmt->gtPrev = endLFin; |
9635 | } |
9636 | else |
9637 | { |
9638 | lastStmt = endLFin; |
9639 | } |
9640 | |
9641 | // note that this sets BBF_IMPORTED on the block |
9642 | impEndTreeList(callBlock, endLFin, lastStmt); |
9643 | } |
9644 | |
9645 | step = fgNewBBafter(BBJ_ALWAYS, callBlock, true); |
9646 | /* The new block will inherit this block's weight */ |
9647 | step->setBBWeight(block->bbWeight); |
9648 | step->bbFlags |= (block->bbFlags & BBF_RUN_RARELY) | BBF_IMPORTED | BBF_KEEP_BBJ_ALWAYS; |
9649 | |
9650 | #ifdef DEBUG |
9651 | if (verbose) |
9652 | { |
9653 | printf("impImportLeave - jumping out of a finally-protected try, created step (BBJ_ALWAYS) block %s\n" , |
9654 | step->dspToString()); |
9655 | } |
9656 | #endif |
9657 | |
9658 | unsigned finallyNesting = compHndBBtab[XTnum].ebdHandlerNestingLevel; |
9659 | assert(finallyNesting <= compHndBBtabCount); |
9660 | |
9661 | callBlock->bbJumpDest = HBtab->ebdHndBeg; // This callBlock will call the "finally" handler. |
9662 | endLFin = new (this, GT_END_LFIN) GenTreeVal(GT_END_LFIN, TYP_VOID, finallyNesting); |
9663 | endLFin = gtNewStmt(endLFin); |
9664 | endCatches = NULL; |
9665 | |
9666 | encFinallies++; |
9667 | |
9668 | invalidatePreds = true; |
9669 | } |
9670 | } |
9671 | |
9672 | /* Append any remaining endCatches, if any */ |
9673 | |
9674 | assert(!encFinallies == !endLFin); |
9675 | |
9676 | if (encFinallies == 0) |
9677 | { |
9678 | assert(step == DUMMY_INIT(NULL)); |
9679 | block->bbJumpKind = BBJ_ALWAYS; // convert the BBJ_LEAVE to a BBJ_ALWAYS |
9680 | |
9681 | if (endCatches) |
9682 | impAppendTree(endCatches, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); |
9683 | |
9684 | #ifdef DEBUG |
9685 | if (verbose) |
9686 | { |
9687 | printf("impImportLeave - no enclosing finally-protected try blocks; convert CEE_LEAVE block to BBJ_ALWAYS " |
9688 | "block %s\n" , |
9689 | block->dspToString()); |
9690 | } |
9691 | #endif |
9692 | } |
9693 | else |
9694 | { |
9695 | // If leaveTarget is the start of another try block, we want to make sure that |
9696 | // we do not insert finalStep into that try block. Hence, we find the enclosing |
9697 | // try block. |
9698 | unsigned tryIndex = bbFindInnermostCommonTryRegion(step, leaveTarget); |
9699 | |
9700 | // Insert a new BB either in the try region indicated by tryIndex or |
9701 | // the handler region indicated by leaveTarget->bbHndIndex, |
9702 | // depending on which is the inner region. |
9703 | BasicBlock* finalStep = fgNewBBinRegion(BBJ_ALWAYS, tryIndex, leaveTarget->bbHndIndex, step); |
9704 | finalStep->bbFlags |= BBF_KEEP_BBJ_ALWAYS; |
9705 | step->bbJumpDest = finalStep; |
9706 | |
9707 | /* The new block will inherit this block's weight */ |
9708 | finalStep->setBBWeight(block->bbWeight); |
9709 | finalStep->bbFlags |= block->bbFlags & BBF_RUN_RARELY; |
9710 | |
9711 | #ifdef DEBUG |
9712 | if (verbose) |
9713 | { |
9714 | printf("impImportLeave - finalStep block required (encFinallies(%d) > 0), new block %s\n" , encFinallies, |
9715 | finalStep->dspToString()); |
9716 | } |
9717 | #endif |
9718 | |
9719 | GenTree* lastStmt; |
9720 | |
9721 | if (endCatches) |
9722 | { |
9723 | lastStmt = gtNewStmt(endCatches); |
9724 | endLFin->gtNext = lastStmt; |
9725 | lastStmt->gtPrev = endLFin; |
9726 | } |
9727 | else |
9728 | { |
9729 | lastStmt = endLFin; |
9730 | } |
9731 | |
9732 | impEndTreeList(finalStep, endLFin, lastStmt); |
9733 | |
9734 | finalStep->bbJumpDest = leaveTarget; // this is the ultimate destination of the LEAVE |
9735 | |
9736 | // Queue up the jump target for importing |
9737 | |
9738 | impImportBlockPending(leaveTarget); |
9739 | |
9740 | invalidatePreds = true; |
9741 | } |
9742 | |
9743 | if (invalidatePreds && fgComputePredsDone) |
9744 | { |
9745 | JITDUMP("\n**** impImportLeave - Removing preds after creating new blocks\n" ); |
9746 | fgRemovePreds(); |
9747 | } |
9748 | |
9749 | #ifdef DEBUG |
9750 | fgVerifyHandlerTab(); |
9751 | |
9752 | if (verbose) |
9753 | { |
9754 | printf("\nAfter import CEE_LEAVE:\n" ); |
9755 | fgDispBasicBlocks(); |
9756 | fgDispHandlerTab(); |
9757 | } |
9758 | #endif // DEBUG |
9759 | } |
9760 | |
9761 | #else // FEATURE_EH_FUNCLETS |
9762 | |
9763 | void Compiler::impImportLeave(BasicBlock* block) |
9764 | { |
9765 | #ifdef DEBUG |
9766 | if (verbose) |
9767 | { |
9768 | printf("\nBefore import CEE_LEAVE in " FMT_BB " (targetting " FMT_BB "):\n" , block->bbNum, |
9769 | block->bbJumpDest->bbNum); |
9770 | fgDispBasicBlocks(); |
9771 | fgDispHandlerTab(); |
9772 | } |
9773 | #endif // DEBUG |
9774 | |
9775 | bool invalidatePreds = false; // If we create new blocks, invalidate the predecessor lists (if created) |
9776 | unsigned blkAddr = block->bbCodeOffs; |
9777 | BasicBlock* leaveTarget = block->bbJumpDest; |
9778 | unsigned jmpAddr = leaveTarget->bbCodeOffs; |
9779 | |
9780 | // LEAVE clears the stack, spill side effects, and set stack to 0 |
9781 | |
9782 | impSpillSideEffects(true, (unsigned)CHECK_SPILL_ALL DEBUGARG("impImportLeave" )); |
9783 | verCurrentState.esStackDepth = 0; |
9784 | |
9785 | assert(block->bbJumpKind == BBJ_LEAVE); |
9786 | assert(fgBBs == (BasicBlock**)0xCDCD || fgLookupBB(jmpAddr) != nullptr); // should be a BB boundary |
9787 | |
9788 | BasicBlock* step = nullptr; |
9789 | |
9790 | enum StepType |
9791 | { |
9792 | // No step type; step == NULL. |
9793 | ST_None, |
9794 | |
9795 | // Is the step block the BBJ_ALWAYS block of a BBJ_CALLFINALLY/BBJ_ALWAYS pair? |
9796 | // That is, is step->bbJumpDest where a finally will return to? |
9797 | ST_FinallyReturn, |
9798 | |
9799 | // The step block is a catch return. |
9800 | ST_Catch, |
9801 | |
9802 | // The step block is in a "try", created as the target for a finally return or the target for a catch return. |
9803 | ST_Try |
9804 | }; |
9805 | StepType stepType = ST_None; |
9806 | |
9807 | unsigned XTnum; |
9808 | EHblkDsc* HBtab; |
9809 | |
9810 | for (XTnum = 0, HBtab = compHndBBtab; XTnum < compHndBBtabCount; XTnum++, HBtab++) |
9811 | { |
9812 | // Grab the handler offsets |
9813 | |
9814 | IL_OFFSET tryBeg = HBtab->ebdTryBegOffs(); |
9815 | IL_OFFSET tryEnd = HBtab->ebdTryEndOffs(); |
9816 | IL_OFFSET hndBeg = HBtab->ebdHndBegOffs(); |
9817 | IL_OFFSET hndEnd = HBtab->ebdHndEndOffs(); |
9818 | |
9819 | /* Is this a catch-handler we are CEE_LEAVEing out of? |
9820 | */ |
9821 | |
9822 | if (jitIsBetween(blkAddr, hndBeg, hndEnd) && !jitIsBetween(jmpAddr, hndBeg, hndEnd)) |
9823 | { |
9824 | // Can't CEE_LEAVE out of a finally/fault handler |
9825 | if (HBtab->HasFinallyOrFaultHandler()) |
9826 | { |
9827 | BADCODE("leave out of fault/finally block" ); |
9828 | } |
9829 | |
9830 | /* We are jumping out of a catch */ |
9831 | |
9832 | if (step == nullptr) |
9833 | { |
9834 | step = block; |
9835 | step->bbJumpKind = BBJ_EHCATCHRET; // convert the BBJ_LEAVE to BBJ_EHCATCHRET |
9836 | stepType = ST_Catch; |
9837 | |
9838 | #ifdef DEBUG |
9839 | if (verbose) |
9840 | { |
9841 | printf("impImportLeave - jumping out of a catch (EH#%u), convert block " FMT_BB |
9842 | " to BBJ_EHCATCHRET " |
9843 | "block\n" , |
9844 | XTnum, step->bbNum); |
9845 | } |
9846 | #endif |
9847 | } |
9848 | else |
9849 | { |
9850 | BasicBlock* exitBlock; |
9851 | |
9852 | /* Create a new catch exit block in the catch region for the existing step block to jump to in this |
9853 | * scope */ |
9854 | exitBlock = fgNewBBinRegion(BBJ_EHCATCHRET, 0, XTnum + 1, step); |
9855 | |
9856 | assert(step->bbJumpKind == BBJ_ALWAYS || step->bbJumpKind == BBJ_EHCATCHRET); |
9857 | step->bbJumpDest = exitBlock; // the previous step (maybe a call to a nested finally, or a nested catch |
9858 | // exit) returns to this block |
9859 | step->bbJumpDest->bbRefs++; |
9860 | |
9861 | #if defined(_TARGET_ARM_) |
9862 | if (stepType == ST_FinallyReturn) |
9863 | { |
9864 | assert(step->bbJumpKind == BBJ_ALWAYS); |
9865 | // Mark the target of a finally return |
9866 | step->bbJumpDest->bbFlags |= BBF_FINALLY_TARGET; |
9867 | } |
9868 | #endif // defined(_TARGET_ARM_) |
9869 | |
9870 | /* The new block will inherit this block's weight */ |
9871 | exitBlock->setBBWeight(block->bbWeight); |
9872 | exitBlock->bbFlags |= (block->bbFlags & BBF_RUN_RARELY) | BBF_IMPORTED; |
9873 | |
9874 | /* This exit block is the new step */ |
9875 | step = exitBlock; |
9876 | stepType = ST_Catch; |
9877 | |
9878 | invalidatePreds = true; |
9879 | |
9880 | #ifdef DEBUG |
9881 | if (verbose) |
9882 | { |
9883 | printf("impImportLeave - jumping out of a catch (EH#%u), new BBJ_EHCATCHRET block " FMT_BB "\n" , |
9884 | XTnum, exitBlock->bbNum); |
9885 | } |
9886 | #endif |
9887 | } |
9888 | } |
9889 | else if (HBtab->HasFinallyHandler() && jitIsBetween(blkAddr, tryBeg, tryEnd) && |
9890 | !jitIsBetween(jmpAddr, tryBeg, tryEnd)) |
9891 | { |
9892 | /* We are jumping out of a finally-protected try */ |
9893 | |
9894 | BasicBlock* callBlock; |
9895 | |
9896 | if (step == nullptr) |
9897 | { |
9898 | #if FEATURE_EH_CALLFINALLY_THUNKS |
9899 | |
9900 | // Put the call to the finally in the enclosing region. |
9901 | unsigned callFinallyTryIndex = |
9902 | (HBtab->ebdEnclosingTryIndex == EHblkDsc::NO_ENCLOSING_INDEX) ? 0 : HBtab->ebdEnclosingTryIndex + 1; |
9903 | unsigned callFinallyHndIndex = |
9904 | (HBtab->ebdEnclosingHndIndex == EHblkDsc::NO_ENCLOSING_INDEX) ? 0 : HBtab->ebdEnclosingHndIndex + 1; |
9905 | callBlock = fgNewBBinRegion(BBJ_CALLFINALLY, callFinallyTryIndex, callFinallyHndIndex, block); |
9906 | |
9907 | // Convert the BBJ_LEAVE to BBJ_ALWAYS, jumping to the new BBJ_CALLFINALLY. This is because |
9908 | // the new BBJ_CALLFINALLY is in a different EH region, thus it can't just replace the BBJ_LEAVE, |
9909 | // which might be in the middle of the "try". In most cases, the BBJ_ALWAYS will jump to the |
9910 | // next block, and flow optimizations will remove it. |
9911 | block->bbJumpKind = BBJ_ALWAYS; |
9912 | block->bbJumpDest = callBlock; |
9913 | block->bbJumpDest->bbRefs++; |
9914 | |
9915 | /* The new block will inherit this block's weight */ |
9916 | callBlock->setBBWeight(block->bbWeight); |
9917 | callBlock->bbFlags |= (block->bbFlags & BBF_RUN_RARELY) | BBF_IMPORTED; |
9918 | |
9919 | #ifdef DEBUG |
9920 | if (verbose) |
9921 | { |
9922 | printf("impImportLeave - jumping out of a finally-protected try (EH#%u), convert block " FMT_BB |
9923 | " to " |
9924 | "BBJ_ALWAYS, add BBJ_CALLFINALLY block " FMT_BB "\n" , |
9925 | XTnum, block->bbNum, callBlock->bbNum); |
9926 | } |
9927 | #endif |
9928 | |
9929 | #else // !FEATURE_EH_CALLFINALLY_THUNKS |
9930 | |
9931 | callBlock = block; |
9932 | callBlock->bbJumpKind = BBJ_CALLFINALLY; // convert the BBJ_LEAVE to BBJ_CALLFINALLY |
9933 | |
9934 | #ifdef DEBUG |
9935 | if (verbose) |
9936 | { |
9937 | printf("impImportLeave - jumping out of a finally-protected try (EH#%u), convert block " FMT_BB |
9938 | " to " |
9939 | "BBJ_CALLFINALLY block\n" , |
9940 | XTnum, callBlock->bbNum); |
9941 | } |
9942 | #endif |
9943 | |
9944 | #endif // !FEATURE_EH_CALLFINALLY_THUNKS |
9945 | } |
9946 | else |
9947 | { |
9948 | // Calling the finally block. We already have a step block that is either the call-to-finally from a |
9949 | // more nested try/finally (thus we are jumping out of multiple nested 'try' blocks, each protected by |
9950 | // a 'finally'), or the step block is the return from a catch. |
9951 | // |
9952 | // Due to ThreadAbortException, we can't have the catch return target the call-to-finally block |
9953 | // directly. Note that if a 'catch' ends without resetting the ThreadAbortException, the VM will |
9954 | // automatically re-raise the exception, using the return address of the catch (that is, the target |
9955 | // block of the BBJ_EHCATCHRET) as the re-raise address. If this address is in a finally, the VM will |
9956 | // refuse to do the re-raise, and the ThreadAbortException will get eaten (and lost). On AMD64/ARM64, |
9957 | // we put the call-to-finally thunk in a special "cloned finally" EH region that does look like a |
9958 | // finally clause to the VM. Thus, on these platforms, we can't have BBJ_EHCATCHRET target a |
9959 | // BBJ_CALLFINALLY directly. (Note that on ARM32, we don't mark the thunk specially -- it lives directly |
9960 | // within the 'try' region protected by the finally, since we generate code in such a way that execution |
9961 | // never returns to the call-to-finally call, and the finally-protected 'try' region doesn't appear on |
9962 | // stack walks.) |
9963 | |
9964 | assert(step->bbJumpKind == BBJ_ALWAYS || step->bbJumpKind == BBJ_EHCATCHRET); |
9965 | |
9966 | #if FEATURE_EH_CALLFINALLY_THUNKS |
9967 | if (step->bbJumpKind == BBJ_EHCATCHRET) |
9968 | { |
9969 | // Need to create another step block in the 'try' region that will actually branch to the |
9970 | // call-to-finally thunk. |
9971 | BasicBlock* step2 = fgNewBBinRegion(BBJ_ALWAYS, XTnum + 1, 0, step); |
9972 | step->bbJumpDest = step2; |
9973 | step->bbJumpDest->bbRefs++; |
9974 | step2->setBBWeight(block->bbWeight); |
9975 | step2->bbFlags |= (block->bbFlags & BBF_RUN_RARELY) | BBF_IMPORTED; |
9976 | |
9977 | #ifdef DEBUG |
9978 | if (verbose) |
9979 | { |
9980 | printf("impImportLeave - jumping out of a finally-protected try (EH#%u), step block is " |
9981 | "BBJ_EHCATCHRET (" FMT_BB "), new BBJ_ALWAYS step-step block " FMT_BB "\n" , |
9982 | XTnum, step->bbNum, step2->bbNum); |
9983 | } |
9984 | #endif |
9985 | |
9986 | step = step2; |
9987 | assert(stepType == ST_Catch); // Leave it as catch type for now. |
9988 | } |
9989 | #endif // FEATURE_EH_CALLFINALLY_THUNKS |
9990 | |
9991 | #if FEATURE_EH_CALLFINALLY_THUNKS |
9992 | unsigned callFinallyTryIndex = |
9993 | (HBtab->ebdEnclosingTryIndex == EHblkDsc::NO_ENCLOSING_INDEX) ? 0 : HBtab->ebdEnclosingTryIndex + 1; |
9994 | unsigned callFinallyHndIndex = |
9995 | (HBtab->ebdEnclosingHndIndex == EHblkDsc::NO_ENCLOSING_INDEX) ? 0 : HBtab->ebdEnclosingHndIndex + 1; |
9996 | #else // !FEATURE_EH_CALLFINALLY_THUNKS |
9997 | unsigned callFinallyTryIndex = XTnum + 1; |
9998 | unsigned callFinallyHndIndex = 0; // don't care |
9999 | #endif // !FEATURE_EH_CALLFINALLY_THUNKS |
10000 | |
10001 | callBlock = fgNewBBinRegion(BBJ_CALLFINALLY, callFinallyTryIndex, callFinallyHndIndex, step); |
10002 | step->bbJumpDest = callBlock; // the previous call to a finally returns to this call (to the next |
10003 | // finally in the chain) |
10004 | step->bbJumpDest->bbRefs++; |
10005 | |
10006 | #if defined(_TARGET_ARM_) |
10007 | if (stepType == ST_FinallyReturn) |
10008 | { |
10009 | assert(step->bbJumpKind == BBJ_ALWAYS); |
10010 | // Mark the target of a finally return |
10011 | step->bbJumpDest->bbFlags |= BBF_FINALLY_TARGET; |
10012 | } |
10013 | #endif // defined(_TARGET_ARM_) |
10014 | |
10015 | /* The new block will inherit this block's weight */ |
10016 | callBlock->setBBWeight(block->bbWeight); |
10017 | callBlock->bbFlags |= (block->bbFlags & BBF_RUN_RARELY) | BBF_IMPORTED; |
10018 | |
10019 | #ifdef DEBUG |
10020 | if (verbose) |
10021 | { |
10022 | printf("impImportLeave - jumping out of a finally-protected try (EH#%u), new BBJ_CALLFINALLY " |
10023 | "block " FMT_BB "\n" , |
10024 | XTnum, callBlock->bbNum); |
10025 | } |
10026 | #endif |
10027 | } |
10028 | |
10029 | step = fgNewBBafter(BBJ_ALWAYS, callBlock, true); |
10030 | stepType = ST_FinallyReturn; |
10031 | |
10032 | /* The new block will inherit this block's weight */ |
10033 | step->setBBWeight(block->bbWeight); |
10034 | step->bbFlags |= (block->bbFlags & BBF_RUN_RARELY) | BBF_IMPORTED | BBF_KEEP_BBJ_ALWAYS; |
10035 | |
10036 | #ifdef DEBUG |
10037 | if (verbose) |
10038 | { |
10039 | printf("impImportLeave - jumping out of a finally-protected try (EH#%u), created step (BBJ_ALWAYS) " |
10040 | "block " FMT_BB "\n" , |
10041 | XTnum, step->bbNum); |
10042 | } |
10043 | #endif |
10044 | |
10045 | callBlock->bbJumpDest = HBtab->ebdHndBeg; // This callBlock will call the "finally" handler. |
10046 | |
10047 | invalidatePreds = true; |
10048 | } |
10049 | else if (HBtab->HasCatchHandler() && jitIsBetween(blkAddr, tryBeg, tryEnd) && |
10050 | !jitIsBetween(jmpAddr, tryBeg, tryEnd)) |
10051 | { |
10052 | // We are jumping out of a catch-protected try. |
10053 | // |
10054 | // If we are returning from a call to a finally, then we must have a step block within a try |
10055 | // that is protected by a catch. This is so when unwinding from that finally (e.g., if code within the |
10056 | // finally raises an exception), the VM will find this step block, notice that it is in a protected region, |
10057 | // and invoke the appropriate catch. |
10058 | // |
10059 | // We also need to handle a special case with the handling of ThreadAbortException. If a try/catch |
10060 | // catches a ThreadAbortException (which might be because it catches a parent, e.g. System.Exception), |
10061 | // and the catch doesn't call System.Threading.Thread::ResetAbort(), then when the catch returns to the VM, |
10062 | // the VM will automatically re-raise the ThreadAbortException. When it does this, it uses the target |
10063 | // address of the catch return as the new exception address. That is, the re-raised exception appears to |
10064 | // occur at the catch return address. If this exception return address skips an enclosing try/catch that |
10065 | // catches ThreadAbortException, then the enclosing try/catch will not catch the exception, as it should. |
10066 | // For example: |
10067 | // |
10068 | // try { |
10069 | // try { |
10070 | // // something here raises ThreadAbortException |
10071 | // LEAVE LABEL_1; // no need to stop at LABEL_2 |
10072 | // } catch (Exception) { |
10073 | // // This catches ThreadAbortException, but doesn't call System.Threading.Thread::ResetAbort(), so |
10074 | // // ThreadAbortException is re-raised by the VM at the address specified by the LEAVE opcode. |
10075 | // // This is bad, since it means the outer try/catch won't get a chance to catch the re-raised |
10076 | // // ThreadAbortException. So, instead, create step block LABEL_2 and LEAVE to that. We only |
10077 | // // need to do this transformation if the current EH block is a try/catch that catches |
10078 | // // ThreadAbortException (or one of its parents), however we might not be able to find that |
10079 | // // information, so currently we do it for all catch types. |
10080 | // LEAVE LABEL_1; // Convert this to LEAVE LABEL2; |
10081 | // } |
10082 | // LABEL_2: LEAVE LABEL_1; // inserted by this step creation code |
10083 | // } catch (ThreadAbortException) { |
10084 | // } |
10085 | // LABEL_1: |
10086 | // |
10087 | // Note that this pattern isn't theoretical: it occurs in ASP.NET, in IL code generated by the Roslyn C# |
10088 | // compiler. |
10089 | |
10090 | if ((stepType == ST_FinallyReturn) || (stepType == ST_Catch)) |
10091 | { |
10092 | BasicBlock* catchStep; |
10093 | |
10094 | assert(step); |
10095 | |
10096 | if (stepType == ST_FinallyReturn) |
10097 | { |
10098 | assert(step->bbJumpKind == BBJ_ALWAYS); |
10099 | } |
10100 | else |
10101 | { |
10102 | assert(stepType == ST_Catch); |
10103 | assert(step->bbJumpKind == BBJ_EHCATCHRET); |
10104 | } |
10105 | |
10106 | /* Create a new exit block in the try region for the existing step block to jump to in this scope */ |
10107 | catchStep = fgNewBBinRegion(BBJ_ALWAYS, XTnum + 1, 0, step); |
10108 | step->bbJumpDest = catchStep; |
10109 | step->bbJumpDest->bbRefs++; |
10110 | |
10111 | #if defined(_TARGET_ARM_) |
10112 | if (stepType == ST_FinallyReturn) |
10113 | { |
10114 | // Mark the target of a finally return |
10115 | step->bbJumpDest->bbFlags |= BBF_FINALLY_TARGET; |
10116 | } |
10117 | #endif // defined(_TARGET_ARM_) |
10118 | |
10119 | /* The new block will inherit this block's weight */ |
10120 | catchStep->setBBWeight(block->bbWeight); |
10121 | catchStep->bbFlags |= (block->bbFlags & BBF_RUN_RARELY) | BBF_IMPORTED; |
10122 | |
10123 | #ifdef DEBUG |
10124 | if (verbose) |
10125 | { |
10126 | if (stepType == ST_FinallyReturn) |
10127 | { |
10128 | printf("impImportLeave - return from finally jumping out of a catch-protected try (EH#%u), new " |
10129 | "BBJ_ALWAYS block " FMT_BB "\n" , |
10130 | XTnum, catchStep->bbNum); |
10131 | } |
10132 | else |
10133 | { |
10134 | assert(stepType == ST_Catch); |
10135 | printf("impImportLeave - return from catch jumping out of a catch-protected try (EH#%u), new " |
10136 | "BBJ_ALWAYS block " FMT_BB "\n" , |
10137 | XTnum, catchStep->bbNum); |
10138 | } |
10139 | } |
10140 | #endif // DEBUG |
10141 | |
10142 | /* This block is the new step */ |
10143 | step = catchStep; |
10144 | stepType = ST_Try; |
10145 | |
10146 | invalidatePreds = true; |
10147 | } |
10148 | } |
10149 | } |
10150 | |
10151 | if (step == nullptr) |
10152 | { |
10153 | block->bbJumpKind = BBJ_ALWAYS; // convert the BBJ_LEAVE to a BBJ_ALWAYS |
10154 | |
10155 | #ifdef DEBUG |
10156 | if (verbose) |
10157 | { |
10158 | printf("impImportLeave - no enclosing finally-protected try blocks or catch handlers; convert CEE_LEAVE " |
10159 | "block " FMT_BB " to BBJ_ALWAYS\n" , |
10160 | block->bbNum); |
10161 | } |
10162 | #endif |
10163 | } |
10164 | else |
10165 | { |
10166 | step->bbJumpDest = leaveTarget; // this is the ultimate destination of the LEAVE |
10167 | |
10168 | #if defined(_TARGET_ARM_) |
10169 | if (stepType == ST_FinallyReturn) |
10170 | { |
10171 | assert(step->bbJumpKind == BBJ_ALWAYS); |
10172 | // Mark the target of a finally return |
10173 | step->bbJumpDest->bbFlags |= BBF_FINALLY_TARGET; |
10174 | } |
10175 | #endif // defined(_TARGET_ARM_) |
10176 | |
10177 | #ifdef DEBUG |
10178 | if (verbose) |
10179 | { |
10180 | printf("impImportLeave - final destination of step blocks set to " FMT_BB "\n" , leaveTarget->bbNum); |
10181 | } |
10182 | #endif |
10183 | |
10184 | // Queue up the jump target for importing |
10185 | |
10186 | impImportBlockPending(leaveTarget); |
10187 | } |
10188 | |
10189 | if (invalidatePreds && fgComputePredsDone) |
10190 | { |
10191 | JITDUMP("\n**** impImportLeave - Removing preds after creating new blocks\n" ); |
10192 | fgRemovePreds(); |
10193 | } |
10194 | |
10195 | #ifdef DEBUG |
10196 | fgVerifyHandlerTab(); |
10197 | |
10198 | if (verbose) |
10199 | { |
10200 | printf("\nAfter import CEE_LEAVE:\n" ); |
10201 | fgDispBasicBlocks(); |
10202 | fgDispHandlerTab(); |
10203 | } |
10204 | #endif // DEBUG |
10205 | } |
10206 | |
10207 | #endif // FEATURE_EH_FUNCLETS |
10208 | |
10209 | /*****************************************************************************/ |
10210 | // This is called when reimporting a leave block. It resets the JumpKind, |
10211 | // JumpDest, and bbNext to the original values |
10212 | |
10213 | void Compiler::impResetLeaveBlock(BasicBlock* block, unsigned jmpAddr) |
10214 | { |
10215 | #if FEATURE_EH_FUNCLETS |
10216 | // With EH Funclets, while importing leave opcode we create another block ending with BBJ_ALWAYS (call it B1) |
10217 | // and the block containing leave (say B0) is marked as BBJ_CALLFINALLY. Say for some reason we reimport B0, |
10218 | // it is reset (in this routine) by marking as ending with BBJ_LEAVE and further down when B0 is reimported, we |
10219 | // create another BBJ_ALWAYS (call it B2). In this process B1 gets orphaned and any blocks to which B1 is the |
10220 | // only predecessor are also considered orphans and attempted to be deleted. |
10221 | // |
10222 | // try { |
10223 | // .... |
10224 | // try |
10225 | // { |
10226 | // .... |
10227 | // leave OUTSIDE; // B0 is the block containing this leave, following this would be B1 |
10228 | // } finally { } |
10229 | // } finally { } |
10230 | // OUTSIDE: |
10231 | // |
10232 | // In the above nested try-finally example, we create a step block (call it Bstep) which in branches to a block |
10233 | // where a finally would branch to (and such block is marked as finally target). Block B1 branches to step block. |
10234 | // Because of re-import of B0, Bstep is also orphaned. Since Bstep is a finally target it cannot be removed. To |
10235 | // work around this we will duplicate B0 (call it B0Dup) before reseting. B0Dup is marked as BBJ_CALLFINALLY and |
10236 | // only serves to pair up with B1 (BBJ_ALWAYS) that got orphaned. Now during orphan block deletion B0Dup and B1 |
10237 | // will be treated as pair and handled correctly. |
10238 | if (block->bbJumpKind == BBJ_CALLFINALLY) |
10239 | { |
10240 | BasicBlock* dupBlock = bbNewBasicBlock(block->bbJumpKind); |
10241 | dupBlock->bbFlags = block->bbFlags; |
10242 | dupBlock->bbJumpDest = block->bbJumpDest; |
10243 | dupBlock->copyEHRegion(block); |
10244 | dupBlock->bbCatchTyp = block->bbCatchTyp; |
10245 | |
10246 | // Mark this block as |
10247 | // a) not referenced by any other block to make sure that it gets deleted |
10248 | // b) weight zero |
10249 | // c) prevent from being imported |
10250 | // d) as internal |
10251 | // e) as rarely run |
10252 | dupBlock->bbRefs = 0; |
10253 | dupBlock->bbWeight = 0; |
10254 | dupBlock->bbFlags |= BBF_IMPORTED | BBF_INTERNAL | BBF_RUN_RARELY; |
10255 | |
10256 | // Insert the block right after the block which is getting reset so that BBJ_CALLFINALLY and BBJ_ALWAYS |
10257 | // will be next to each other. |
10258 | fgInsertBBafter(block, dupBlock); |
10259 | |
10260 | #ifdef DEBUG |
10261 | if (verbose) |
10262 | { |
10263 | printf("New Basic Block " FMT_BB " duplicate of " FMT_BB " created.\n" , dupBlock->bbNum, block->bbNum); |
10264 | } |
10265 | #endif |
10266 | } |
10267 | #endif // FEATURE_EH_FUNCLETS |
10268 | |
10269 | block->bbJumpKind = BBJ_LEAVE; |
10270 | fgInitBBLookup(); |
10271 | block->bbJumpDest = fgLookupBB(jmpAddr); |
10272 | |
10273 | // We will leave the BBJ_ALWAYS block we introduced. When it's reimported |
10274 | // the BBJ_ALWAYS block will be unreachable, and will be removed after. The |
10275 | // reason we don't want to remove the block at this point is that if we call |
10276 | // fgInitBBLookup() again we will do it wrong as the BBJ_ALWAYS block won't be |
10277 | // added and the linked list length will be different than fgBBcount. |
10278 | } |
10279 | |
10280 | /*****************************************************************************/ |
10281 | // Get the first non-prefix opcode. Used for verification of valid combinations |
10282 | // of prefixes and actual opcodes. |
10283 | |
10284 | static OPCODE impGetNonPrefixOpcode(const BYTE* codeAddr, const BYTE* codeEndp) |
10285 | { |
10286 | while (codeAddr < codeEndp) |
10287 | { |
10288 | OPCODE opcode = (OPCODE)getU1LittleEndian(codeAddr); |
10289 | codeAddr += sizeof(__int8); |
10290 | |
10291 | if (opcode == CEE_PREFIX1) |
10292 | { |
10293 | if (codeAddr >= codeEndp) |
10294 | { |
10295 | break; |
10296 | } |
10297 | opcode = (OPCODE)(getU1LittleEndian(codeAddr) + 256); |
10298 | codeAddr += sizeof(__int8); |
10299 | } |
10300 | |
10301 | switch (opcode) |
10302 | { |
10303 | case CEE_UNALIGNED: |
10304 | case CEE_VOLATILE: |
10305 | case CEE_TAILCALL: |
10306 | case CEE_CONSTRAINED: |
10307 | case CEE_READONLY: |
10308 | break; |
10309 | default: |
10310 | return opcode; |
10311 | } |
10312 | |
10313 | codeAddr += opcodeSizes[opcode]; |
10314 | } |
10315 | |
10316 | return CEE_ILLEGAL; |
10317 | } |
10318 | |
10319 | /*****************************************************************************/ |
10320 | // Checks whether the opcode is a valid opcode for volatile. and unaligned. prefixes |
10321 | |
10322 | static void impValidateMemoryAccessOpcode(const BYTE* codeAddr, const BYTE* codeEndp, bool volatilePrefix) |
10323 | { |
10324 | OPCODE opcode = impGetNonPrefixOpcode(codeAddr, codeEndp); |
10325 | |
10326 | if (!( |
10327 | // Opcode of all ldind and stdind happen to be in continuous, except stind.i. |
10328 | ((CEE_LDIND_I1 <= opcode) && (opcode <= CEE_STIND_R8)) || (opcode == CEE_STIND_I) || |
10329 | (opcode == CEE_LDFLD) || (opcode == CEE_STFLD) || (opcode == CEE_LDOBJ) || (opcode == CEE_STOBJ) || |
10330 | (opcode == CEE_INITBLK) || (opcode == CEE_CPBLK) || |
10331 | // volatile. prefix is allowed with the ldsfld and stsfld |
10332 | (volatilePrefix && ((opcode == CEE_LDSFLD) || (opcode == CEE_STSFLD))))) |
10333 | { |
10334 | BADCODE("Invalid opcode for unaligned. or volatile. prefix" ); |
10335 | } |
10336 | } |
10337 | |
10338 | /*****************************************************************************/ |
10339 | |
10340 | #ifdef DEBUG |
10341 | |
10342 | #undef RETURN // undef contracts RETURN macro |
10343 | |
10344 | enum controlFlow_t |
10345 | { |
10346 | NEXT, |
10347 | CALL, |
10348 | RETURN, |
10349 | THROW, |
10350 | BRANCH, |
10351 | COND_BRANCH, |
10352 | BREAK, |
10353 | PHI, |
10354 | META, |
10355 | }; |
10356 | |
10357 | const static controlFlow_t controlFlow[] = { |
10358 | #define OPDEF(c, s, pop, push, args, type, l, s1, s2, flow) flow, |
10359 | #include "opcode.def" |
10360 | #undef OPDEF |
10361 | }; |
10362 | |
10363 | #endif // DEBUG |
10364 | |
10365 | /***************************************************************************** |
10366 | * Determine the result type of an arithemetic operation |
10367 | * On 64-bit inserts upcasts when native int is mixed with int32 |
10368 | */ |
10369 | var_types Compiler::impGetByRefResultType(genTreeOps oper, bool fUnsigned, GenTree** pOp1, GenTree** pOp2) |
10370 | { |
10371 | var_types type = TYP_UNDEF; |
10372 | GenTree* op1 = *pOp1; |
10373 | GenTree* op2 = *pOp2; |
10374 | |
10375 | // Arithemetic operations are generally only allowed with |
10376 | // primitive types, but certain operations are allowed |
10377 | // with byrefs |
10378 | |
10379 | if ((oper == GT_SUB) && (genActualType(op1->TypeGet()) == TYP_BYREF || genActualType(op2->TypeGet()) == TYP_BYREF)) |
10380 | { |
10381 | if ((genActualType(op1->TypeGet()) == TYP_BYREF) && (genActualType(op2->TypeGet()) == TYP_BYREF)) |
10382 | { |
10383 | // byref1-byref2 => gives a native int |
10384 | type = TYP_I_IMPL; |
10385 | } |
10386 | else if (genActualTypeIsIntOrI(op1->TypeGet()) && (genActualType(op2->TypeGet()) == TYP_BYREF)) |
10387 | { |
10388 | // [native] int - byref => gives a native int |
10389 | |
10390 | // |
10391 | // The reason is that it is possible, in managed C++, |
10392 | // to have a tree like this: |
10393 | // |
10394 | // - |
10395 | // / \ |
10396 | // / \ |
10397 | // / \ |
10398 | // / \ |
10399 | // const(h) int addr byref |
10400 | // |
10401 | // <BUGNUM> VSW 318822 </BUGNUM> |
10402 | // |
10403 | // So here we decide to make the resulting type to be a native int. |
10404 | CLANG_FORMAT_COMMENT_ANCHOR; |
10405 | |
10406 | #ifdef _TARGET_64BIT_ |
10407 | if (genActualType(op1->TypeGet()) != TYP_I_IMPL) |
10408 | { |
10409 | // insert an explicit upcast |
10410 | op1 = *pOp1 = gtNewCastNode(TYP_I_IMPL, op1, fUnsigned, fUnsigned ? TYP_U_IMPL : TYP_I_IMPL); |
10411 | } |
10412 | #endif // _TARGET_64BIT_ |
10413 | |
10414 | type = TYP_I_IMPL; |
10415 | } |
10416 | else |
10417 | { |
10418 | // byref - [native] int => gives a byref |
10419 | assert(genActualType(op1->TypeGet()) == TYP_BYREF && genActualTypeIsIntOrI(op2->TypeGet())); |
10420 | |
10421 | #ifdef _TARGET_64BIT_ |
10422 | if ((genActualType(op2->TypeGet()) != TYP_I_IMPL)) |
10423 | { |
10424 | // insert an explicit upcast |
10425 | op2 = *pOp2 = gtNewCastNode(TYP_I_IMPL, op2, fUnsigned, fUnsigned ? TYP_U_IMPL : TYP_I_IMPL); |
10426 | } |
10427 | #endif // _TARGET_64BIT_ |
10428 | |
10429 | type = TYP_BYREF; |
10430 | } |
10431 | } |
10432 | else if ((oper == GT_ADD) && |
10433 | (genActualType(op1->TypeGet()) == TYP_BYREF || genActualType(op2->TypeGet()) == TYP_BYREF)) |
10434 | { |
10435 | // byref + [native] int => gives a byref |
10436 | // (or) |
10437 | // [native] int + byref => gives a byref |
10438 | |
10439 | // only one can be a byref : byref op byref not allowed |
10440 | assert(genActualType(op1->TypeGet()) != TYP_BYREF || genActualType(op2->TypeGet()) != TYP_BYREF); |
10441 | assert(genActualTypeIsIntOrI(op1->TypeGet()) || genActualTypeIsIntOrI(op2->TypeGet())); |
10442 | |
10443 | #ifdef _TARGET_64BIT_ |
10444 | if (genActualType(op2->TypeGet()) == TYP_BYREF) |
10445 | { |
10446 | if (genActualType(op1->TypeGet()) != TYP_I_IMPL) |
10447 | { |
10448 | // insert an explicit upcast |
10449 | op1 = *pOp1 = gtNewCastNode(TYP_I_IMPL, op1, fUnsigned, fUnsigned ? TYP_U_IMPL : TYP_I_IMPL); |
10450 | } |
10451 | } |
10452 | else if (genActualType(op2->TypeGet()) != TYP_I_IMPL) |
10453 | { |
10454 | // insert an explicit upcast |
10455 | op2 = *pOp2 = gtNewCastNode(TYP_I_IMPL, op2, fUnsigned, fUnsigned ? TYP_U_IMPL : TYP_I_IMPL); |
10456 | } |
10457 | #endif // _TARGET_64BIT_ |
10458 | |
10459 | type = TYP_BYREF; |
10460 | } |
10461 | #ifdef _TARGET_64BIT_ |
10462 | else if (genActualType(op1->TypeGet()) == TYP_I_IMPL || genActualType(op2->TypeGet()) == TYP_I_IMPL) |
10463 | { |
10464 | assert(!varTypeIsFloating(op1->gtType) && !varTypeIsFloating(op2->gtType)); |
10465 | |
10466 | // int + long => gives long |
10467 | // long + int => gives long |
10468 | // we get this because in the IL the long isn't Int64, it's just IntPtr |
10469 | |
10470 | if (genActualType(op1->TypeGet()) != TYP_I_IMPL) |
10471 | { |
10472 | // insert an explicit upcast |
10473 | op1 = *pOp1 = gtNewCastNode(TYP_I_IMPL, op1, fUnsigned, fUnsigned ? TYP_U_IMPL : TYP_I_IMPL); |
10474 | } |
10475 | else if (genActualType(op2->TypeGet()) != TYP_I_IMPL) |
10476 | { |
10477 | // insert an explicit upcast |
10478 | op2 = *pOp2 = gtNewCastNode(TYP_I_IMPL, op2, fUnsigned, fUnsigned ? TYP_U_IMPL : TYP_I_IMPL); |
10479 | } |
10480 | |
10481 | type = TYP_I_IMPL; |
10482 | } |
10483 | #else // 32-bit TARGET |
10484 | else if (genActualType(op1->TypeGet()) == TYP_LONG || genActualType(op2->TypeGet()) == TYP_LONG) |
10485 | { |
10486 | assert(!varTypeIsFloating(op1->gtType) && !varTypeIsFloating(op2->gtType)); |
10487 | |
10488 | // int + long => gives long |
10489 | // long + int => gives long |
10490 | |
10491 | type = TYP_LONG; |
10492 | } |
10493 | #endif // _TARGET_64BIT_ |
10494 | else |
10495 | { |
10496 | // int + int => gives an int |
10497 | assert(genActualType(op1->TypeGet()) != TYP_BYREF && genActualType(op2->TypeGet()) != TYP_BYREF); |
10498 | |
10499 | assert(genActualType(op1->TypeGet()) == genActualType(op2->TypeGet()) || |
10500 | varTypeIsFloating(op1->gtType) && varTypeIsFloating(op2->gtType)); |
10501 | |
10502 | type = genActualType(op1->gtType); |
10503 | |
10504 | // If both operands are TYP_FLOAT, then leave it as TYP_FLOAT. |
10505 | // Otherwise, turn floats into doubles |
10506 | if ((type == TYP_FLOAT) && (genActualType(op2->gtType) != TYP_FLOAT)) |
10507 | { |
10508 | assert(genActualType(op2->gtType) == TYP_DOUBLE); |
10509 | type = TYP_DOUBLE; |
10510 | } |
10511 | } |
10512 | |
10513 | assert(type == TYP_BYREF || type == TYP_DOUBLE || type == TYP_FLOAT || type == TYP_LONG || type == TYP_INT); |
10514 | return type; |
10515 | } |
10516 | |
10517 | //------------------------------------------------------------------------ |
10518 | // impOptimizeCastClassOrIsInst: attempt to resolve a cast when jitting |
10519 | // |
10520 | // Arguments: |
10521 | // op1 - value to cast |
10522 | // pResolvedToken - resolved token for type to cast to |
10523 | // isCastClass - true if this is a castclass, false if isinst |
10524 | // |
10525 | // Return Value: |
10526 | // tree representing optimized cast, or null if no optimization possible |
10527 | |
10528 | GenTree* Compiler::impOptimizeCastClassOrIsInst(GenTree* op1, CORINFO_RESOLVED_TOKEN* pResolvedToken, bool isCastClass) |
10529 | { |
10530 | assert(op1->TypeGet() == TYP_REF); |
10531 | |
10532 | // Don't optimize for minopts or debug codegen. |
10533 | if (opts.OptimizationDisabled()) |
10534 | { |
10535 | return nullptr; |
10536 | } |
10537 | |
10538 | // See what we know about the type of the object being cast. |
10539 | bool isExact = false; |
10540 | bool isNonNull = false; |
10541 | CORINFO_CLASS_HANDLE fromClass = gtGetClassHandle(op1, &isExact, &isNonNull); |
10542 | GenTree* optResult = nullptr; |
10543 | |
10544 | if (fromClass != nullptr) |
10545 | { |
10546 | CORINFO_CLASS_HANDLE toClass = pResolvedToken->hClass; |
10547 | JITDUMP("\nConsidering optimization of %s from %s%p (%s) to %p (%s)\n" , isCastClass ? "castclass" : "isinst" , |
10548 | isExact ? "exact " : "" , dspPtr(fromClass), info.compCompHnd->getClassName(fromClass), dspPtr(toClass), |
10549 | info.compCompHnd->getClassName(toClass)); |
10550 | |
10551 | // Perhaps we know if the cast will succeed or fail. |
10552 | TypeCompareState castResult = info.compCompHnd->compareTypesForCast(fromClass, toClass); |
10553 | |
10554 | if (castResult == TypeCompareState::Must) |
10555 | { |
10556 | // Cast will succeed, result is simply op1. |
10557 | JITDUMP("Cast will succeed, optimizing to simply return input\n" ); |
10558 | return op1; |
10559 | } |
10560 | else if (castResult == TypeCompareState::MustNot) |
10561 | { |
10562 | // See if we can sharpen exactness by looking for final classes |
10563 | if (!isExact) |
10564 | { |
10565 | DWORD flags = info.compCompHnd->getClassAttribs(fromClass); |
10566 | DWORD flagsMask = CORINFO_FLG_FINAL | CORINFO_FLG_MARSHAL_BYREF | CORINFO_FLG_CONTEXTFUL | |
10567 | CORINFO_FLG_VARIANCE | CORINFO_FLG_ARRAY; |
10568 | isExact = ((flags & flagsMask) == CORINFO_FLG_FINAL); |
10569 | } |
10570 | |
10571 | // Cast to exact type will fail. Handle case where we have |
10572 | // an exact type (that is, fromClass is not a subtype) |
10573 | // and we're not going to throw on failure. |
10574 | if (isExact && !isCastClass) |
10575 | { |
10576 | JITDUMP("Cast will fail, optimizing to return null\n" ); |
10577 | GenTree* result = gtNewIconNode(0, TYP_REF); |
10578 | |
10579 | // If the cast was fed by a box, we can remove that too. |
10580 | if (op1->IsBoxedValue()) |
10581 | { |
10582 | JITDUMP("Also removing upstream box\n" ); |
10583 | gtTryRemoveBoxUpstreamEffects(op1); |
10584 | } |
10585 | |
10586 | return result; |
10587 | } |
10588 | else if (isExact) |
10589 | { |
10590 | JITDUMP("Not optimizing failing castclass (yet)\n" ); |
10591 | } |
10592 | else |
10593 | { |
10594 | JITDUMP("Can't optimize since fromClass is inexact\n" ); |
10595 | } |
10596 | } |
10597 | else |
10598 | { |
10599 | JITDUMP("Result of cast unknown, must generate runtime test\n" ); |
10600 | } |
10601 | } |
10602 | else |
10603 | { |
10604 | JITDUMP("\nCan't optimize since fromClass is unknown\n" ); |
10605 | } |
10606 | |
10607 | return nullptr; |
10608 | } |
10609 | |
10610 | //------------------------------------------------------------------------ |
10611 | // impCastClassOrIsInstToTree: build and import castclass/isinst |
10612 | // |
10613 | // Arguments: |
10614 | // op1 - value to cast |
10615 | // op2 - type handle for type to cast to |
10616 | // pResolvedToken - resolved token from the cast operation |
10617 | // isCastClass - true if this is castclass, false means isinst |
10618 | // |
10619 | // Return Value: |
10620 | // Tree representing the cast |
10621 | // |
10622 | // Notes: |
10623 | // May expand into a series of runtime checks or a helper call. |
10624 | |
10625 | GenTree* Compiler::impCastClassOrIsInstToTree(GenTree* op1, |
10626 | GenTree* op2, |
10627 | CORINFO_RESOLVED_TOKEN* pResolvedToken, |
10628 | bool isCastClass) |
10629 | { |
10630 | assert(op1->TypeGet() == TYP_REF); |
10631 | |
10632 | // Optimistically assume the jit should expand this as an inline test |
10633 | bool shouldExpandInline = true; |
10634 | |
10635 | // Profitability check. |
10636 | // |
10637 | // Don't bother with inline expansion when jit is trying to |
10638 | // generate code quickly, or the cast is in code that won't run very |
10639 | // often, or the method already is pretty big. |
10640 | if (compCurBB->isRunRarely() || opts.OptimizationDisabled()) |
10641 | { |
10642 | // not worth the code expansion if jitting fast or in a rarely run block |
10643 | shouldExpandInline = false; |
10644 | } |
10645 | else if ((op1->gtFlags & GTF_GLOB_EFFECT) && lvaHaveManyLocals()) |
10646 | { |
10647 | // not worth creating an untracked local variable |
10648 | shouldExpandInline = false; |
10649 | } |
10650 | |
10651 | // Pessimistically assume the jit cannot expand this as an inline test |
10652 | bool canExpandInline = false; |
10653 | const CorInfoHelpFunc helper = info.compCompHnd->getCastingHelper(pResolvedToken, isCastClass); |
10654 | |
10655 | // Legality check. |
10656 | // |
10657 | // Not all classclass/isinst operations can be inline expanded. |
10658 | // Check legality only if an inline expansion is desirable. |
10659 | if (shouldExpandInline) |
10660 | { |
10661 | if (isCastClass) |
10662 | { |
10663 | // Jit can only inline expand the normal CHKCASTCLASS helper. |
10664 | canExpandInline = (helper == CORINFO_HELP_CHKCASTCLASS); |
10665 | } |
10666 | else |
10667 | { |
10668 | if (helper == CORINFO_HELP_ISINSTANCEOFCLASS) |
10669 | { |
10670 | // Check the class attributes. |
10671 | DWORD flags = info.compCompHnd->getClassAttribs(pResolvedToken->hClass); |
10672 | |
10673 | // If the class is final and is not marshal byref or |
10674 | // contextful, the jit can expand the IsInst check inline. |
10675 | DWORD flagsMask = CORINFO_FLG_FINAL | CORINFO_FLG_MARSHAL_BYREF | CORINFO_FLG_CONTEXTFUL; |
10676 | canExpandInline = ((flags & flagsMask) == CORINFO_FLG_FINAL); |
10677 | } |
10678 | } |
10679 | } |
10680 | |
10681 | const bool expandInline = canExpandInline && shouldExpandInline; |
10682 | |
10683 | if (!expandInline) |
10684 | { |
10685 | JITDUMP("\nExpanding %s as call because %s\n" , isCastClass ? "castclass" : "isinst" , |
10686 | canExpandInline ? "want smaller code or faster jitting" : "inline expansion not legal" ); |
10687 | |
10688 | // If we CSE this class handle we prevent assertionProp from making SubType assertions |
10689 | // so instead we force the CSE logic to not consider CSE-ing this class handle. |
10690 | // |
10691 | op2->gtFlags |= GTF_DONT_CSE; |
10692 | |
10693 | return gtNewHelperCallNode(helper, TYP_REF, gtNewArgList(op2, op1)); |
10694 | } |
10695 | |
10696 | JITDUMP("\nExpanding %s inline\n" , isCastClass ? "castclass" : "isinst" ); |
10697 | |
10698 | impSpillSideEffects(true, CHECK_SPILL_ALL DEBUGARG("bubbling QMark2" )); |
10699 | |
10700 | GenTree* temp; |
10701 | GenTree* condMT; |
10702 | // |
10703 | // expand the methodtable match: |
10704 | // |
10705 | // condMT ==> GT_NE |
10706 | // / \ |
10707 | // GT_IND op2 (typically CNS_INT) |
10708 | // | |
10709 | // op1Copy |
10710 | // |
10711 | |
10712 | // This can replace op1 with a GT_COMMA that evaluates op1 into a local |
10713 | // |
10714 | op1 = impCloneExpr(op1, &temp, NO_CLASS_HANDLE, (unsigned)CHECK_SPILL_ALL, nullptr DEBUGARG("CASTCLASS eval op1" )); |
10715 | // |
10716 | // op1 is now known to be a non-complex tree |
10717 | // thus we can use gtClone(op1) from now on |
10718 | // |
10719 | |
10720 | GenTree* op2Var = op2; |
10721 | if (isCastClass) |
10722 | { |
10723 | op2Var = fgInsertCommaFormTemp(&op2); |
10724 | lvaTable[op2Var->AsLclVarCommon()->GetLclNum()].lvIsCSE = true; |
10725 | } |
10726 | temp = gtNewOperNode(GT_IND, TYP_I_IMPL, temp); |
10727 | temp->gtFlags |= GTF_EXCEPT; |
10728 | condMT = gtNewOperNode(GT_NE, TYP_INT, temp, op2); |
10729 | |
10730 | GenTree* condNull; |
10731 | // |
10732 | // expand the null check: |
10733 | // |
10734 | // condNull ==> GT_EQ |
10735 | // / \ |
10736 | // op1Copy CNS_INT |
10737 | // null |
10738 | // |
10739 | condNull = gtNewOperNode(GT_EQ, TYP_INT, gtClone(op1), gtNewIconNode(0, TYP_REF)); |
10740 | |
10741 | // |
10742 | // expand the true and false trees for the condMT |
10743 | // |
10744 | GenTree* condFalse = gtClone(op1); |
10745 | GenTree* condTrue; |
10746 | if (isCastClass) |
10747 | { |
10748 | // |
10749 | // use the special helper that skips the cases checked by our inlined cast |
10750 | // |
10751 | const CorInfoHelpFunc specialHelper = CORINFO_HELP_CHKCASTCLASS_SPECIAL; |
10752 | |
10753 | condTrue = gtNewHelperCallNode(specialHelper, TYP_REF, gtNewArgList(op2Var, gtClone(op1))); |
10754 | } |
10755 | else |
10756 | { |
10757 | condTrue = gtNewIconNode(0, TYP_REF); |
10758 | } |
10759 | |
10760 | #define USE_QMARK_TREES |
10761 | |
10762 | #ifdef USE_QMARK_TREES |
10763 | GenTree* qmarkMT; |
10764 | // |
10765 | // Generate first QMARK - COLON tree |
10766 | // |
10767 | // qmarkMT ==> GT_QMARK |
10768 | // / \ |
10769 | // condMT GT_COLON |
10770 | // / \ |
10771 | // condFalse condTrue |
10772 | // |
10773 | temp = new (this, GT_COLON) GenTreeColon(TYP_REF, condTrue, condFalse); |
10774 | qmarkMT = gtNewQmarkNode(TYP_REF, condMT, temp); |
10775 | |
10776 | GenTree* qmarkNull; |
10777 | // |
10778 | // Generate second QMARK - COLON tree |
10779 | // |
10780 | // qmarkNull ==> GT_QMARK |
10781 | // / \ |
10782 | // condNull GT_COLON |
10783 | // / \ |
10784 | // qmarkMT op1Copy |
10785 | // |
10786 | temp = new (this, GT_COLON) GenTreeColon(TYP_REF, gtClone(op1), qmarkMT); |
10787 | qmarkNull = gtNewQmarkNode(TYP_REF, condNull, temp); |
10788 | qmarkNull->gtFlags |= GTF_QMARK_CAST_INSTOF; |
10789 | |
10790 | // Make QMark node a top level node by spilling it. |
10791 | unsigned tmp = lvaGrabTemp(true DEBUGARG("spilling QMark2" )); |
10792 | impAssignTempGen(tmp, qmarkNull, (unsigned)CHECK_SPILL_NONE); |
10793 | |
10794 | // TODO-CQ: Is it possible op1 has a better type? |
10795 | // |
10796 | // See also gtGetHelperCallClassHandle where we make the same |
10797 | // determination for the helper call variants. |
10798 | LclVarDsc* lclDsc = lvaGetDesc(tmp); |
10799 | assert(lclDsc->lvSingleDef == 0); |
10800 | lclDsc->lvSingleDef = 1; |
10801 | JITDUMP("Marked V%02u as a single def temp\n" , tmp); |
10802 | lvaSetClass(tmp, pResolvedToken->hClass); |
10803 | return gtNewLclvNode(tmp, TYP_REF); |
10804 | #endif |
10805 | } |
10806 | |
10807 | #ifndef DEBUG |
10808 | #define assertImp(cond) ((void)0) |
10809 | #else |
10810 | #define assertImp(cond) \ |
10811 | do \ |
10812 | { \ |
10813 | if (!(cond)) \ |
10814 | { \ |
10815 | const int cchAssertImpBuf = 600; \ |
10816 | char* assertImpBuf = (char*)alloca(cchAssertImpBuf); \ |
10817 | _snprintf_s(assertImpBuf, cchAssertImpBuf, cchAssertImpBuf - 1, \ |
10818 | "%s : Possibly bad IL with CEE_%s at offset %04Xh (op1=%s op2=%s stkDepth=%d)", #cond, \ |
10819 | impCurOpcName, impCurOpcOffs, op1 ? varTypeName(op1->TypeGet()) : "NULL", \ |
10820 | op2 ? varTypeName(op2->TypeGet()) : "NULL", verCurrentState.esStackDepth); \ |
10821 | assertAbort(assertImpBuf, __FILE__, __LINE__); \ |
10822 | } \ |
10823 | } while (0) |
10824 | #endif // DEBUG |
10825 | |
10826 | #ifdef _PREFAST_ |
10827 | #pragma warning(push) |
10828 | #pragma warning(disable : 21000) // Suppress PREFast warning about overly large function |
10829 | #endif |
10830 | /***************************************************************************** |
10831 | * Import the instr for the given basic block |
10832 | */ |
10833 | void Compiler::impImportBlockCode(BasicBlock* block) |
10834 | { |
10835 | #define _impResolveToken(kind) impResolveToken(codeAddr, &resolvedToken, kind) |
10836 | |
10837 | #ifdef DEBUG |
10838 | |
10839 | if (verbose) |
10840 | { |
10841 | printf("\nImporting " FMT_BB " (PC=%03u) of '%s'" , block->bbNum, block->bbCodeOffs, info.compFullName); |
10842 | } |
10843 | #endif |
10844 | |
10845 | unsigned nxtStmtIndex = impInitBlockLineInfo(); |
10846 | IL_OFFSET nxtStmtOffs; |
10847 | |
10848 | GenTree* arrayNodeFrom; |
10849 | GenTree* arrayNodeTo; |
10850 | GenTree* arrayNodeToIndex; |
10851 | CorInfoHelpFunc helper; |
10852 | CorInfoIsAccessAllowedResult accessAllowedResult; |
10853 | CORINFO_HELPER_DESC calloutHelper; |
10854 | const BYTE* lastLoadToken = nullptr; |
10855 | |
10856 | // reject cyclic constraints |
10857 | if (tiVerificationNeeded) |
10858 | { |
10859 | Verify(!info.hasCircularClassConstraints, "Method parent has circular class type parameter constraints." ); |
10860 | Verify(!info.hasCircularMethodConstraints, "Method has circular method type parameter constraints." ); |
10861 | } |
10862 | |
10863 | /* Get the tree list started */ |
10864 | |
10865 | impBeginTreeList(); |
10866 | |
10867 | /* Walk the opcodes that comprise the basic block */ |
10868 | |
10869 | const BYTE* codeAddr = info.compCode + block->bbCodeOffs; |
10870 | const BYTE* codeEndp = info.compCode + block->bbCodeOffsEnd; |
10871 | |
10872 | IL_OFFSET opcodeOffs = block->bbCodeOffs; |
10873 | IL_OFFSET lastSpillOffs = opcodeOffs; |
10874 | |
10875 | signed jmpDist; |
10876 | |
10877 | /* remember the start of the delegate creation sequence (used for verification) */ |
10878 | const BYTE* delegateCreateStart = nullptr; |
10879 | |
10880 | int prefixFlags = 0; |
10881 | bool explicitTailCall, constraintCall, readonlyCall; |
10882 | |
10883 | typeInfo tiRetVal; |
10884 | |
10885 | unsigned numArgs = info.compArgsCount; |
10886 | |
10887 | /* Now process all the opcodes in the block */ |
10888 | |
10889 | var_types callTyp = TYP_COUNT; |
10890 | OPCODE prevOpcode = CEE_ILLEGAL; |
10891 | |
10892 | if (block->bbCatchTyp) |
10893 | { |
10894 | if (info.compStmtOffsetsImplicit & ICorDebugInfo::CALL_SITE_BOUNDARIES) |
10895 | { |
10896 | impCurStmtOffsSet(block->bbCodeOffs); |
10897 | } |
10898 | |
10899 | // We will spill the GT_CATCH_ARG and the input of the BB_QMARK block |
10900 | // to a temp. This is a trade off for code simplicity |
10901 | impSpillSpecialSideEff(); |
10902 | } |
10903 | |
10904 | while (codeAddr < codeEndp) |
10905 | { |
10906 | bool usingReadyToRunHelper = false; |
10907 | CORINFO_RESOLVED_TOKEN resolvedToken; |
10908 | CORINFO_RESOLVED_TOKEN constrainedResolvedToken; |
10909 | CORINFO_CALL_INFO callInfo; |
10910 | CORINFO_FIELD_INFO fieldInfo; |
10911 | |
10912 | tiRetVal = typeInfo(); // Default type info |
10913 | |
10914 | //--------------------------------------------------------------------- |
10915 | |
10916 | /* We need to restrict the max tree depth as many of the Compiler |
10917 | functions are recursive. We do this by spilling the stack */ |
10918 | |
10919 | if (verCurrentState.esStackDepth) |
10920 | { |
10921 | /* Has it been a while since we last saw a non-empty stack (which |
10922 | guarantees that the tree depth isnt accumulating. */ |
10923 | |
10924 | if ((opcodeOffs - lastSpillOffs) > MAX_TREE_SIZE && impCanSpillNow(prevOpcode)) |
10925 | { |
10926 | impSpillStackEnsure(); |
10927 | lastSpillOffs = opcodeOffs; |
10928 | } |
10929 | } |
10930 | else |
10931 | { |
10932 | lastSpillOffs = opcodeOffs; |
10933 | impBoxTempInUse = false; // nothing on the stack, box temp OK to use again |
10934 | } |
10935 | |
10936 | /* Compute the current instr offset */ |
10937 | |
10938 | opcodeOffs = (IL_OFFSET)(codeAddr - info.compCode); |
10939 | |
10940 | #ifndef DEBUG |
10941 | if (opts.compDbgInfo) |
10942 | #endif |
10943 | { |
10944 | if (!compIsForInlining()) |
10945 | { |
10946 | nxtStmtOffs = |
10947 | (nxtStmtIndex < info.compStmtOffsetsCount) ? info.compStmtOffsets[nxtStmtIndex] : BAD_IL_OFFSET; |
10948 | |
10949 | /* Have we reached the next stmt boundary ? */ |
10950 | |
10951 | if (nxtStmtOffs != BAD_IL_OFFSET && opcodeOffs >= nxtStmtOffs) |
10952 | { |
10953 | assert(nxtStmtOffs == info.compStmtOffsets[nxtStmtIndex]); |
10954 | |
10955 | if (verCurrentState.esStackDepth != 0 && opts.compDbgCode) |
10956 | { |
10957 | /* We need to provide accurate IP-mapping at this point. |
10958 | So spill anything on the stack so that it will form |
10959 | gtStmts with the correct stmt offset noted */ |
10960 | |
10961 | impSpillStackEnsure(true); |
10962 | } |
10963 | |
10964 | // Has impCurStmtOffs been reported in any tree? |
10965 | |
10966 | if (impCurStmtOffs != BAD_IL_OFFSET && opts.compDbgCode) |
10967 | { |
10968 | GenTree* placeHolder = new (this, GT_NO_OP) GenTree(GT_NO_OP, TYP_VOID); |
10969 | impAppendTree(placeHolder, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); |
10970 | |
10971 | assert(impCurStmtOffs == BAD_IL_OFFSET); |
10972 | } |
10973 | |
10974 | if (impCurStmtOffs == BAD_IL_OFFSET) |
10975 | { |
10976 | /* Make sure that nxtStmtIndex is in sync with opcodeOffs. |
10977 | If opcodeOffs has gone past nxtStmtIndex, catch up */ |
10978 | |
10979 | while ((nxtStmtIndex + 1) < info.compStmtOffsetsCount && |
10980 | info.compStmtOffsets[nxtStmtIndex + 1] <= opcodeOffs) |
10981 | { |
10982 | nxtStmtIndex++; |
10983 | } |
10984 | |
10985 | /* Go to the new stmt */ |
10986 | |
10987 | impCurStmtOffsSet(info.compStmtOffsets[nxtStmtIndex]); |
10988 | |
10989 | /* Update the stmt boundary index */ |
10990 | |
10991 | nxtStmtIndex++; |
10992 | assert(nxtStmtIndex <= info.compStmtOffsetsCount); |
10993 | |
10994 | /* Are there any more line# entries after this one? */ |
10995 | |
10996 | if (nxtStmtIndex < info.compStmtOffsetsCount) |
10997 | { |
10998 | /* Remember where the next line# starts */ |
10999 | |
11000 | nxtStmtOffs = info.compStmtOffsets[nxtStmtIndex]; |
11001 | } |
11002 | else |
11003 | { |
11004 | /* No more line# entries */ |
11005 | |
11006 | nxtStmtOffs = BAD_IL_OFFSET; |
11007 | } |
11008 | } |
11009 | } |
11010 | else if ((info.compStmtOffsetsImplicit & ICorDebugInfo::STACK_EMPTY_BOUNDARIES) && |
11011 | (verCurrentState.esStackDepth == 0)) |
11012 | { |
11013 | /* At stack-empty locations, we have already added the tree to |
11014 | the stmt list with the last offset. We just need to update |
11015 | impCurStmtOffs |
11016 | */ |
11017 | |
11018 | impCurStmtOffsSet(opcodeOffs); |
11019 | } |
11020 | else if ((info.compStmtOffsetsImplicit & ICorDebugInfo::CALL_SITE_BOUNDARIES) && |
11021 | impOpcodeIsCallSiteBoundary(prevOpcode)) |
11022 | { |
11023 | /* Make sure we have a type cached */ |
11024 | assert(callTyp != TYP_COUNT); |
11025 | |
11026 | if (callTyp == TYP_VOID) |
11027 | { |
11028 | impCurStmtOffsSet(opcodeOffs); |
11029 | } |
11030 | else if (opts.compDbgCode) |
11031 | { |
11032 | impSpillStackEnsure(true); |
11033 | impCurStmtOffsSet(opcodeOffs); |
11034 | } |
11035 | } |
11036 | else if ((info.compStmtOffsetsImplicit & ICorDebugInfo::NOP_BOUNDARIES) && (prevOpcode == CEE_NOP)) |
11037 | { |
11038 | if (opts.compDbgCode) |
11039 | { |
11040 | impSpillStackEnsure(true); |
11041 | } |
11042 | |
11043 | impCurStmtOffsSet(opcodeOffs); |
11044 | } |
11045 | |
11046 | assert(impCurStmtOffs == BAD_IL_OFFSET || nxtStmtOffs == BAD_IL_OFFSET || |
11047 | jitGetILoffs(impCurStmtOffs) <= nxtStmtOffs); |
11048 | } |
11049 | } |
11050 | |
11051 | CORINFO_CLASS_HANDLE clsHnd = DUMMY_INIT(NULL); |
11052 | CORINFO_CLASS_HANDLE ldelemClsHnd = DUMMY_INIT(NULL); |
11053 | CORINFO_CLASS_HANDLE stelemClsHnd = DUMMY_INIT(NULL); |
11054 | |
11055 | var_types lclTyp, ovflType = TYP_UNKNOWN; |
11056 | GenTree* op1 = DUMMY_INIT(NULL); |
11057 | GenTree* op2 = DUMMY_INIT(NULL); |
11058 | GenTreeArgList* args = nullptr; // What good do these "DUMMY_INIT"s do? |
11059 | GenTree* newObjThisPtr = DUMMY_INIT(NULL); |
11060 | bool uns = DUMMY_INIT(false); |
11061 | bool isLocal = false; |
11062 | |
11063 | /* Get the next opcode and the size of its parameters */ |
11064 | |
11065 | OPCODE opcode = (OPCODE)getU1LittleEndian(codeAddr); |
11066 | codeAddr += sizeof(__int8); |
11067 | |
11068 | #ifdef DEBUG |
11069 | impCurOpcOffs = (IL_OFFSET)(codeAddr - info.compCode - 1); |
11070 | JITDUMP("\n [%2u] %3u (0x%03x) " , verCurrentState.esStackDepth, impCurOpcOffs, impCurOpcOffs); |
11071 | #endif |
11072 | |
11073 | DECODE_OPCODE: |
11074 | |
11075 | // Return if any previous code has caused inline to fail. |
11076 | if (compDonotInline()) |
11077 | { |
11078 | return; |
11079 | } |
11080 | |
11081 | /* Get the size of additional parameters */ |
11082 | |
11083 | signed int sz = opcodeSizes[opcode]; |
11084 | |
11085 | #ifdef DEBUG |
11086 | clsHnd = NO_CLASS_HANDLE; |
11087 | lclTyp = TYP_COUNT; |
11088 | callTyp = TYP_COUNT; |
11089 | |
11090 | impCurOpcOffs = (IL_OFFSET)(codeAddr - info.compCode - 1); |
11091 | impCurOpcName = opcodeNames[opcode]; |
11092 | |
11093 | if (verbose && (opcode != CEE_PREFIX1)) |
11094 | { |
11095 | printf("%s" , impCurOpcName); |
11096 | } |
11097 | |
11098 | /* Use assertImp() to display the opcode */ |
11099 | |
11100 | op1 = op2 = nullptr; |
11101 | #endif |
11102 | |
11103 | /* See what kind of an opcode we have, then */ |
11104 | |
11105 | unsigned mflags = 0; |
11106 | unsigned clsFlags = 0; |
11107 | |
11108 | switch (opcode) |
11109 | { |
11110 | unsigned lclNum; |
11111 | var_types type; |
11112 | |
11113 | GenTree* op3; |
11114 | genTreeOps oper; |
11115 | unsigned size; |
11116 | |
11117 | int val; |
11118 | |
11119 | CORINFO_SIG_INFO sig; |
11120 | IL_OFFSET jmpAddr; |
11121 | bool ovfl, unordered, callNode; |
11122 | bool ldstruct; |
11123 | CORINFO_CLASS_HANDLE tokenType; |
11124 | |
11125 | union { |
11126 | int intVal; |
11127 | float fltVal; |
11128 | __int64 lngVal; |
11129 | double dblVal; |
11130 | } cval; |
11131 | |
11132 | case CEE_PREFIX1: |
11133 | opcode = (OPCODE)(getU1LittleEndian(codeAddr) + 256); |
11134 | opcodeOffs = (IL_OFFSET)(codeAddr - info.compCode); |
11135 | codeAddr += sizeof(__int8); |
11136 | goto DECODE_OPCODE; |
11137 | |
11138 | SPILL_APPEND: |
11139 | |
11140 | // We need to call impSpillLclRefs() for a struct type lclVar. |
11141 | // This is done for non-block assignments in the handling of stloc. |
11142 | if ((op1->OperGet() == GT_ASG) && varTypeIsStruct(op1->gtOp.gtOp1) && |
11143 | (op1->gtOp.gtOp1->gtOper == GT_LCL_VAR)) |
11144 | { |
11145 | impSpillLclRefs(op1->gtOp.gtOp1->AsLclVarCommon()->gtLclNum); |
11146 | } |
11147 | |
11148 | /* Append 'op1' to the list of statements */ |
11149 | impAppendTree(op1, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs); |
11150 | goto DONE_APPEND; |
11151 | |
11152 | APPEND: |
11153 | |
11154 | /* Append 'op1' to the list of statements */ |
11155 | |
11156 | impAppendTree(op1, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); |
11157 | goto DONE_APPEND; |
11158 | |
11159 | DONE_APPEND: |
11160 | |
11161 | #ifdef DEBUG |
11162 | // Remember at which BC offset the tree was finished |
11163 | impNoteLastILoffs(); |
11164 | #endif |
11165 | break; |
11166 | |
11167 | case CEE_LDNULL: |
11168 | impPushNullObjRefOnStack(); |
11169 | break; |
11170 | |
11171 | case CEE_LDC_I4_M1: |
11172 | case CEE_LDC_I4_0: |
11173 | case CEE_LDC_I4_1: |
11174 | case CEE_LDC_I4_2: |
11175 | case CEE_LDC_I4_3: |
11176 | case CEE_LDC_I4_4: |
11177 | case CEE_LDC_I4_5: |
11178 | case CEE_LDC_I4_6: |
11179 | case CEE_LDC_I4_7: |
11180 | case CEE_LDC_I4_8: |
11181 | cval.intVal = (opcode - CEE_LDC_I4_0); |
11182 | assert(-1 <= cval.intVal && cval.intVal <= 8); |
11183 | goto PUSH_I4CON; |
11184 | |
11185 | case CEE_LDC_I4_S: |
11186 | cval.intVal = getI1LittleEndian(codeAddr); |
11187 | goto PUSH_I4CON; |
11188 | case CEE_LDC_I4: |
11189 | cval.intVal = getI4LittleEndian(codeAddr); |
11190 | goto PUSH_I4CON; |
11191 | PUSH_I4CON: |
11192 | JITDUMP(" %d" , cval.intVal); |
11193 | impPushOnStack(gtNewIconNode(cval.intVal), typeInfo(TI_INT)); |
11194 | break; |
11195 | |
11196 | case CEE_LDC_I8: |
11197 | cval.lngVal = getI8LittleEndian(codeAddr); |
11198 | JITDUMP(" 0x%016llx" , cval.lngVal); |
11199 | impPushOnStack(gtNewLconNode(cval.lngVal), typeInfo(TI_LONG)); |
11200 | break; |
11201 | |
11202 | case CEE_LDC_R8: |
11203 | cval.dblVal = getR8LittleEndian(codeAddr); |
11204 | JITDUMP(" %#.17g" , cval.dblVal); |
11205 | impPushOnStack(gtNewDconNode(cval.dblVal), typeInfo(TI_DOUBLE)); |
11206 | break; |
11207 | |
11208 | case CEE_LDC_R4: |
11209 | cval.dblVal = getR4LittleEndian(codeAddr); |
11210 | JITDUMP(" %#.17g" , cval.dblVal); |
11211 | { |
11212 | GenTree* cnsOp = gtNewDconNode(cval.dblVal); |
11213 | cnsOp->gtType = TYP_FLOAT; |
11214 | impPushOnStack(cnsOp, typeInfo(TI_DOUBLE)); |
11215 | } |
11216 | break; |
11217 | |
11218 | case CEE_LDSTR: |
11219 | |
11220 | if (compIsForInlining()) |
11221 | { |
11222 | if (impInlineInfo->inlineCandidateInfo->dwRestrictions & INLINE_NO_CALLEE_LDSTR) |
11223 | { |
11224 | compInlineResult->NoteFatal(InlineObservation::CALLSITE_HAS_LDSTR_RESTRICTION); |
11225 | return; |
11226 | } |
11227 | } |
11228 | |
11229 | val = getU4LittleEndian(codeAddr); |
11230 | JITDUMP(" %08X" , val); |
11231 | if (tiVerificationNeeded) |
11232 | { |
11233 | Verify(info.compCompHnd->isValidStringRef(info.compScopeHnd, val), "bad string" ); |
11234 | tiRetVal = typeInfo(TI_REF, impGetStringClass()); |
11235 | } |
11236 | impPushOnStack(gtNewSconNode(val, info.compScopeHnd), tiRetVal); |
11237 | |
11238 | break; |
11239 | |
11240 | case CEE_LDARG: |
11241 | lclNum = getU2LittleEndian(codeAddr); |
11242 | JITDUMP(" %u" , lclNum); |
11243 | impLoadArg(lclNum, opcodeOffs + sz + 1); |
11244 | break; |
11245 | |
11246 | case CEE_LDARG_S: |
11247 | lclNum = getU1LittleEndian(codeAddr); |
11248 | JITDUMP(" %u" , lclNum); |
11249 | impLoadArg(lclNum, opcodeOffs + sz + 1); |
11250 | break; |
11251 | |
11252 | case CEE_LDARG_0: |
11253 | case CEE_LDARG_1: |
11254 | case CEE_LDARG_2: |
11255 | case CEE_LDARG_3: |
11256 | lclNum = (opcode - CEE_LDARG_0); |
11257 | assert(lclNum >= 0 && lclNum < 4); |
11258 | impLoadArg(lclNum, opcodeOffs + sz + 1); |
11259 | break; |
11260 | |
11261 | case CEE_LDLOC: |
11262 | lclNum = getU2LittleEndian(codeAddr); |
11263 | JITDUMP(" %u" , lclNum); |
11264 | impLoadLoc(lclNum, opcodeOffs + sz + 1); |
11265 | break; |
11266 | |
11267 | case CEE_LDLOC_S: |
11268 | lclNum = getU1LittleEndian(codeAddr); |
11269 | JITDUMP(" %u" , lclNum); |
11270 | impLoadLoc(lclNum, opcodeOffs + sz + 1); |
11271 | break; |
11272 | |
11273 | case CEE_LDLOC_0: |
11274 | case CEE_LDLOC_1: |
11275 | case CEE_LDLOC_2: |
11276 | case CEE_LDLOC_3: |
11277 | lclNum = (opcode - CEE_LDLOC_0); |
11278 | assert(lclNum >= 0 && lclNum < 4); |
11279 | impLoadLoc(lclNum, opcodeOffs + sz + 1); |
11280 | break; |
11281 | |
11282 | case CEE_STARG: |
11283 | lclNum = getU2LittleEndian(codeAddr); |
11284 | goto STARG; |
11285 | |
11286 | case CEE_STARG_S: |
11287 | lclNum = getU1LittleEndian(codeAddr); |
11288 | STARG: |
11289 | JITDUMP(" %u" , lclNum); |
11290 | |
11291 | if (tiVerificationNeeded) |
11292 | { |
11293 | Verify(lclNum < info.compILargsCount, "bad arg num" ); |
11294 | } |
11295 | |
11296 | if (compIsForInlining()) |
11297 | { |
11298 | op1 = impInlineFetchArg(lclNum, impInlineInfo->inlArgInfo, impInlineInfo->lclVarInfo); |
11299 | noway_assert(op1->gtOper == GT_LCL_VAR); |
11300 | lclNum = op1->AsLclVar()->gtLclNum; |
11301 | |
11302 | goto VAR_ST_VALID; |
11303 | } |
11304 | |
11305 | lclNum = compMapILargNum(lclNum); // account for possible hidden param |
11306 | assertImp(lclNum < numArgs); |
11307 | |
11308 | if (lclNum == info.compThisArg) |
11309 | { |
11310 | lclNum = lvaArg0Var; |
11311 | } |
11312 | |
11313 | // We should have seen this arg write in the prescan |
11314 | assert(lvaTable[lclNum].lvHasILStoreOp); |
11315 | |
11316 | if (tiVerificationNeeded) |
11317 | { |
11318 | typeInfo& tiLclVar = lvaTable[lclNum].lvVerTypeInfo; |
11319 | Verify(tiCompatibleWith(impStackTop().seTypeInfo, NormaliseForStack(tiLclVar), true), |
11320 | "type mismatch" ); |
11321 | |
11322 | if (verTrackObjCtorInitState && (verCurrentState.thisInitialized != TIS_Init)) |
11323 | { |
11324 | Verify(!tiLclVar.IsThisPtr(), "storing to uninit this ptr" ); |
11325 | } |
11326 | } |
11327 | |
11328 | goto VAR_ST; |
11329 | |
11330 | case CEE_STLOC: |
11331 | lclNum = getU2LittleEndian(codeAddr); |
11332 | isLocal = true; |
11333 | JITDUMP(" %u" , lclNum); |
11334 | goto LOC_ST; |
11335 | |
11336 | case CEE_STLOC_S: |
11337 | lclNum = getU1LittleEndian(codeAddr); |
11338 | isLocal = true; |
11339 | JITDUMP(" %u" , lclNum); |
11340 | goto LOC_ST; |
11341 | |
11342 | case CEE_STLOC_0: |
11343 | case CEE_STLOC_1: |
11344 | case CEE_STLOC_2: |
11345 | case CEE_STLOC_3: |
11346 | isLocal = true; |
11347 | lclNum = (opcode - CEE_STLOC_0); |
11348 | assert(lclNum >= 0 && lclNum < 4); |
11349 | |
11350 | LOC_ST: |
11351 | if (tiVerificationNeeded) |
11352 | { |
11353 | Verify(lclNum < info.compMethodInfo->locals.numArgs, "bad local num" ); |
11354 | Verify(tiCompatibleWith(impStackTop().seTypeInfo, |
11355 | NormaliseForStack(lvaTable[lclNum + numArgs].lvVerTypeInfo), true), |
11356 | "type mismatch" ); |
11357 | } |
11358 | |
11359 | if (compIsForInlining()) |
11360 | { |
11361 | lclTyp = impInlineInfo->lclVarInfo[lclNum + impInlineInfo->argCnt].lclTypeInfo; |
11362 | |
11363 | /* Have we allocated a temp for this local? */ |
11364 | |
11365 | lclNum = impInlineFetchLocal(lclNum DEBUGARG("Inline stloc first use temp" )); |
11366 | |
11367 | goto _PopValue; |
11368 | } |
11369 | |
11370 | lclNum += numArgs; |
11371 | |
11372 | VAR_ST: |
11373 | |
11374 | if (lclNum >= info.compLocalsCount && lclNum != lvaArg0Var) |
11375 | { |
11376 | assert(!tiVerificationNeeded); // We should have thrown the VerificationException before. |
11377 | BADCODE("Bad IL" ); |
11378 | } |
11379 | |
11380 | VAR_ST_VALID: |
11381 | |
11382 | /* if it is a struct assignment, make certain we don't overflow the buffer */ |
11383 | assert(lclTyp != TYP_STRUCT || lvaLclSize(lclNum) >= info.compCompHnd->getClassSize(clsHnd)); |
11384 | |
11385 | if (lvaTable[lclNum].lvNormalizeOnLoad()) |
11386 | { |
11387 | lclTyp = lvaGetRealType(lclNum); |
11388 | } |
11389 | else |
11390 | { |
11391 | lclTyp = lvaGetActualType(lclNum); |
11392 | } |
11393 | |
11394 | _PopValue: |
11395 | /* Pop the value being assigned */ |
11396 | |
11397 | { |
11398 | StackEntry se = impPopStack(); |
11399 | clsHnd = se.seTypeInfo.GetClassHandle(); |
11400 | op1 = se.val; |
11401 | tiRetVal = se.seTypeInfo; |
11402 | } |
11403 | |
11404 | #ifdef FEATURE_SIMD |
11405 | if (varTypeIsSIMD(lclTyp) && (lclTyp != op1->TypeGet())) |
11406 | { |
11407 | assert(op1->TypeGet() == TYP_STRUCT); |
11408 | op1->gtType = lclTyp; |
11409 | } |
11410 | #endif // FEATURE_SIMD |
11411 | |
11412 | op1 = impImplicitIorI4Cast(op1, lclTyp); |
11413 | |
11414 | #ifdef _TARGET_64BIT_ |
11415 | // Downcast the TYP_I_IMPL into a 32-bit Int for x86 JIT compatiblity |
11416 | if (varTypeIsI(op1->TypeGet()) && (genActualType(lclTyp) == TYP_INT)) |
11417 | { |
11418 | assert(!tiVerificationNeeded); // We should have thrown the VerificationException before. |
11419 | op1 = gtNewCastNode(TYP_INT, op1, false, TYP_INT); |
11420 | } |
11421 | #endif // _TARGET_64BIT_ |
11422 | |
11423 | // We had better assign it a value of the correct type |
11424 | assertImp( |
11425 | genActualType(lclTyp) == genActualType(op1->gtType) || |
11426 | genActualType(lclTyp) == TYP_I_IMPL && op1->IsVarAddr() || |
11427 | (genActualType(lclTyp) == TYP_I_IMPL && (op1->gtType == TYP_BYREF || op1->gtType == TYP_REF)) || |
11428 | (genActualType(op1->gtType) == TYP_I_IMPL && lclTyp == TYP_BYREF) || |
11429 | (varTypeIsFloating(lclTyp) && varTypeIsFloating(op1->TypeGet())) || |
11430 | ((genActualType(lclTyp) == TYP_BYREF) && genActualType(op1->TypeGet()) == TYP_REF)); |
11431 | |
11432 | /* If op1 is "&var" then its type is the transient "*" and it can |
11433 | be used either as TYP_BYREF or TYP_I_IMPL */ |
11434 | |
11435 | if (op1->IsVarAddr()) |
11436 | { |
11437 | assertImp(genActualType(lclTyp) == TYP_I_IMPL || lclTyp == TYP_BYREF); |
11438 | |
11439 | /* When "&var" is created, we assume it is a byref. If it is |
11440 | being assigned to a TYP_I_IMPL var, change the type to |
11441 | prevent unnecessary GC info */ |
11442 | |
11443 | if (genActualType(lclTyp) == TYP_I_IMPL) |
11444 | { |
11445 | op1->gtType = TYP_I_IMPL; |
11446 | } |
11447 | } |
11448 | |
11449 | // If this is a local and the local is a ref type, see |
11450 | // if we can improve type information based on the |
11451 | // value being assigned. |
11452 | if (isLocal && (lclTyp == TYP_REF)) |
11453 | { |
11454 | // We should have seen a stloc in our IL prescan. |
11455 | assert(lvaTable[lclNum].lvHasILStoreOp); |
11456 | |
11457 | // Is there just one place this local is defined? |
11458 | const bool isSingleDefLocal = lvaTable[lclNum].lvSingleDef; |
11459 | |
11460 | // Conservative check that there is just one |
11461 | // definition that reaches this store. |
11462 | const bool hasSingleReachingDef = (block->bbStackDepthOnEntry() == 0); |
11463 | |
11464 | if (isSingleDefLocal && hasSingleReachingDef) |
11465 | { |
11466 | lvaUpdateClass(lclNum, op1, clsHnd); |
11467 | } |
11468 | } |
11469 | |
11470 | /* Filter out simple assignments to itself */ |
11471 | |
11472 | if (op1->gtOper == GT_LCL_VAR && lclNum == op1->gtLclVarCommon.gtLclNum) |
11473 | { |
11474 | if (opts.compDbgCode) |
11475 | { |
11476 | op1 = gtNewNothingNode(); |
11477 | goto SPILL_APPEND; |
11478 | } |
11479 | else |
11480 | { |
11481 | break; |
11482 | } |
11483 | } |
11484 | |
11485 | /* Create the assignment node */ |
11486 | |
11487 | op2 = gtNewLclvNode(lclNum, lclTyp, opcodeOffs + sz + 1); |
11488 | |
11489 | /* If the local is aliased or pinned, we need to spill calls and |
11490 | indirections from the stack. */ |
11491 | |
11492 | if ((lvaTable[lclNum].lvAddrExposed || lvaTable[lclNum].lvHasLdAddrOp || lvaTable[lclNum].lvPinned) && |
11493 | (verCurrentState.esStackDepth > 0)) |
11494 | { |
11495 | impSpillSideEffects(false, |
11496 | (unsigned)CHECK_SPILL_ALL DEBUGARG("Local could be aliased or is pinned" )); |
11497 | } |
11498 | |
11499 | /* Spill any refs to the local from the stack */ |
11500 | |
11501 | impSpillLclRefs(lclNum); |
11502 | |
11503 | // We can generate an assignment to a TYP_FLOAT from a TYP_DOUBLE |
11504 | // We insert a cast to the dest 'op2' type |
11505 | // |
11506 | if ((op1->TypeGet() != op2->TypeGet()) && varTypeIsFloating(op1->gtType) && |
11507 | varTypeIsFloating(op2->gtType)) |
11508 | { |
11509 | op1 = gtNewCastNode(op2->TypeGet(), op1, false, op2->TypeGet()); |
11510 | } |
11511 | |
11512 | if (varTypeIsStruct(lclTyp)) |
11513 | { |
11514 | op1 = impAssignStruct(op2, op1, clsHnd, (unsigned)CHECK_SPILL_ALL); |
11515 | } |
11516 | else |
11517 | { |
11518 | // The code generator generates GC tracking information |
11519 | // based on the RHS of the assignment. Later the LHS (which is |
11520 | // is a BYREF) gets used and the emitter checks that that variable |
11521 | // is being tracked. It is not (since the RHS was an int and did |
11522 | // not need tracking). To keep this assert happy, we change the RHS |
11523 | if (lclTyp == TYP_BYREF && !varTypeIsGC(op1->gtType)) |
11524 | { |
11525 | op1->gtType = TYP_BYREF; |
11526 | } |
11527 | op1 = gtNewAssignNode(op2, op1); |
11528 | } |
11529 | |
11530 | goto SPILL_APPEND; |
11531 | |
11532 | case CEE_LDLOCA: |
11533 | lclNum = getU2LittleEndian(codeAddr); |
11534 | goto LDLOCA; |
11535 | |
11536 | case CEE_LDLOCA_S: |
11537 | lclNum = getU1LittleEndian(codeAddr); |
11538 | LDLOCA: |
11539 | JITDUMP(" %u" , lclNum); |
11540 | if (tiVerificationNeeded) |
11541 | { |
11542 | Verify(lclNum < info.compMethodInfo->locals.numArgs, "bad local num" ); |
11543 | Verify(info.compInitMem, "initLocals not set" ); |
11544 | } |
11545 | |
11546 | if (compIsForInlining()) |
11547 | { |
11548 | // Get the local type |
11549 | lclTyp = impInlineInfo->lclVarInfo[lclNum + impInlineInfo->argCnt].lclTypeInfo; |
11550 | |
11551 | /* Have we allocated a temp for this local? */ |
11552 | |
11553 | lclNum = impInlineFetchLocal(lclNum DEBUGARG("Inline ldloca(s) first use temp" )); |
11554 | |
11555 | op1 = gtNewLclvNode(lclNum, lvaGetActualType(lclNum)); |
11556 | |
11557 | goto _PUSH_ADRVAR; |
11558 | } |
11559 | |
11560 | lclNum += numArgs; |
11561 | assertImp(lclNum < info.compLocalsCount); |
11562 | goto ADRVAR; |
11563 | |
11564 | case CEE_LDARGA: |
11565 | lclNum = getU2LittleEndian(codeAddr); |
11566 | goto LDARGA; |
11567 | |
11568 | case CEE_LDARGA_S: |
11569 | lclNum = getU1LittleEndian(codeAddr); |
11570 | LDARGA: |
11571 | JITDUMP(" %u" , lclNum); |
11572 | Verify(lclNum < info.compILargsCount, "bad arg num" ); |
11573 | |
11574 | if (compIsForInlining()) |
11575 | { |
11576 | // In IL, LDARGA(_S) is used to load the byref managed pointer of struct argument, |
11577 | // followed by a ldfld to load the field. |
11578 | |
11579 | op1 = impInlineFetchArg(lclNum, impInlineInfo->inlArgInfo, impInlineInfo->lclVarInfo); |
11580 | if (op1->gtOper != GT_LCL_VAR) |
11581 | { |
11582 | compInlineResult->NoteFatal(InlineObservation::CALLSITE_LDARGA_NOT_LOCAL_VAR); |
11583 | return; |
11584 | } |
11585 | |
11586 | assert(op1->gtOper == GT_LCL_VAR); |
11587 | |
11588 | goto _PUSH_ADRVAR; |
11589 | } |
11590 | |
11591 | lclNum = compMapILargNum(lclNum); // account for possible hidden param |
11592 | assertImp(lclNum < numArgs); |
11593 | |
11594 | if (lclNum == info.compThisArg) |
11595 | { |
11596 | lclNum = lvaArg0Var; |
11597 | } |
11598 | |
11599 | goto ADRVAR; |
11600 | |
11601 | ADRVAR: |
11602 | |
11603 | op1 = gtNewLclvNode(lclNum, lvaGetActualType(lclNum), opcodeOffs + sz + 1); |
11604 | |
11605 | _PUSH_ADRVAR: |
11606 | assert(op1->gtOper == GT_LCL_VAR); |
11607 | |
11608 | /* Note that this is supposed to create the transient type "*" |
11609 | which may be used as a TYP_I_IMPL. However we catch places |
11610 | where it is used as a TYP_I_IMPL and change the node if needed. |
11611 | Thus we are pessimistic and may report byrefs in the GC info |
11612 | where it was not absolutely needed, but it is safer this way. |
11613 | */ |
11614 | op1 = gtNewOperNode(GT_ADDR, TYP_BYREF, op1); |
11615 | |
11616 | // &aliasedVar doesnt need GTF_GLOB_REF, though alisasedVar does |
11617 | assert((op1->gtFlags & GTF_GLOB_REF) == 0); |
11618 | |
11619 | tiRetVal = lvaTable[lclNum].lvVerTypeInfo; |
11620 | if (tiVerificationNeeded) |
11621 | { |
11622 | // Don't allow taking address of uninit this ptr. |
11623 | if (verTrackObjCtorInitState && (verCurrentState.thisInitialized != TIS_Init)) |
11624 | { |
11625 | Verify(!tiRetVal.IsThisPtr(), "address of uninit this ptr" ); |
11626 | } |
11627 | |
11628 | if (!tiRetVal.IsByRef()) |
11629 | { |
11630 | tiRetVal.MakeByRef(); |
11631 | } |
11632 | else |
11633 | { |
11634 | Verify(false, "byref to byref" ); |
11635 | } |
11636 | } |
11637 | |
11638 | impPushOnStack(op1, tiRetVal); |
11639 | break; |
11640 | |
11641 | case CEE_ARGLIST: |
11642 | |
11643 | if (!info.compIsVarArgs) |
11644 | { |
11645 | BADCODE("arglist in non-vararg method" ); |
11646 | } |
11647 | |
11648 | if (tiVerificationNeeded) |
11649 | { |
11650 | tiRetVal = typeInfo(TI_STRUCT, impGetRuntimeArgumentHandle()); |
11651 | } |
11652 | assertImp((info.compMethodInfo->args.callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_VARARG); |
11653 | |
11654 | /* The ARGLIST cookie is a hidden 'last' parameter, we have already |
11655 | adjusted the arg count cos this is like fetching the last param */ |
11656 | assertImp(0 < numArgs); |
11657 | assert(lvaTable[lvaVarargsHandleArg].lvAddrExposed); |
11658 | lclNum = lvaVarargsHandleArg; |
11659 | op1 = gtNewLclvNode(lclNum, TYP_I_IMPL, opcodeOffs + sz + 1); |
11660 | op1 = gtNewOperNode(GT_ADDR, TYP_BYREF, op1); |
11661 | impPushOnStack(op1, tiRetVal); |
11662 | break; |
11663 | |
11664 | case CEE_ENDFINALLY: |
11665 | |
11666 | if (compIsForInlining()) |
11667 | { |
11668 | assert(!"Shouldn't have exception handlers in the inliner!" ); |
11669 | compInlineResult->NoteFatal(InlineObservation::CALLEE_HAS_ENDFINALLY); |
11670 | return; |
11671 | } |
11672 | |
11673 | if (verCurrentState.esStackDepth > 0) |
11674 | { |
11675 | impEvalSideEffects(); |
11676 | } |
11677 | |
11678 | if (info.compXcptnsCount == 0) |
11679 | { |
11680 | BADCODE("endfinally outside finally" ); |
11681 | } |
11682 | |
11683 | assert(verCurrentState.esStackDepth == 0); |
11684 | |
11685 | op1 = gtNewOperNode(GT_RETFILT, TYP_VOID, nullptr); |
11686 | goto APPEND; |
11687 | |
11688 | case CEE_ENDFILTER: |
11689 | |
11690 | if (compIsForInlining()) |
11691 | { |
11692 | assert(!"Shouldn't have exception handlers in the inliner!" ); |
11693 | compInlineResult->NoteFatal(InlineObservation::CALLEE_HAS_ENDFILTER); |
11694 | return; |
11695 | } |
11696 | |
11697 | block->bbSetRunRarely(); // filters are rare |
11698 | |
11699 | if (info.compXcptnsCount == 0) |
11700 | { |
11701 | BADCODE("endfilter outside filter" ); |
11702 | } |
11703 | |
11704 | if (tiVerificationNeeded) |
11705 | { |
11706 | Verify(impStackTop().seTypeInfo.IsType(TI_INT), "bad endfilt arg" ); |
11707 | } |
11708 | |
11709 | op1 = impPopStack().val; |
11710 | assertImp(op1->gtType == TYP_INT); |
11711 | if (!bbInFilterILRange(block)) |
11712 | { |
11713 | BADCODE("EndFilter outside a filter handler" ); |
11714 | } |
11715 | |
11716 | /* Mark current bb as end of filter */ |
11717 | |
11718 | assert(compCurBB->bbFlags & BBF_DONT_REMOVE); |
11719 | assert(compCurBB->bbJumpKind == BBJ_EHFILTERRET); |
11720 | |
11721 | /* Mark catch handler as successor */ |
11722 | |
11723 | op1 = gtNewOperNode(GT_RETFILT, op1->TypeGet(), op1); |
11724 | if (verCurrentState.esStackDepth != 0) |
11725 | { |
11726 | verRaiseVerifyException(INDEBUG("stack must be 1 on end of filter" ) DEBUGARG(__FILE__) |
11727 | DEBUGARG(__LINE__)); |
11728 | } |
11729 | goto APPEND; |
11730 | |
11731 | case CEE_RET: |
11732 | prefixFlags &= ~PREFIX_TAILCALL; // ret without call before it |
11733 | RET: |
11734 | if (!impReturnInstruction(block, prefixFlags, opcode)) |
11735 | { |
11736 | return; // abort |
11737 | } |
11738 | else |
11739 | { |
11740 | break; |
11741 | } |
11742 | |
11743 | case CEE_JMP: |
11744 | |
11745 | assert(!compIsForInlining()); |
11746 | |
11747 | if (tiVerificationNeeded) |
11748 | { |
11749 | Verify(false, "Invalid opcode: CEE_JMP" ); |
11750 | } |
11751 | |
11752 | if ((info.compFlags & CORINFO_FLG_SYNCH) || block->hasTryIndex() || block->hasHndIndex()) |
11753 | { |
11754 | /* CEE_JMP does not make sense in some "protected" regions. */ |
11755 | |
11756 | BADCODE("Jmp not allowed in protected region" ); |
11757 | } |
11758 | |
11759 | if (verCurrentState.esStackDepth != 0) |
11760 | { |
11761 | BADCODE("Stack must be empty after CEE_JMPs" ); |
11762 | } |
11763 | |
11764 | _impResolveToken(CORINFO_TOKENKIND_Method); |
11765 | |
11766 | JITDUMP(" %08X" , resolvedToken.token); |
11767 | |
11768 | /* The signature of the target has to be identical to ours. |
11769 | At least check that argCnt and returnType match */ |
11770 | |
11771 | eeGetMethodSig(resolvedToken.hMethod, &sig); |
11772 | if (sig.numArgs != info.compMethodInfo->args.numArgs || |
11773 | sig.retType != info.compMethodInfo->args.retType || |
11774 | sig.callConv != info.compMethodInfo->args.callConv) |
11775 | { |
11776 | BADCODE("Incompatible target for CEE_JMPs" ); |
11777 | } |
11778 | |
11779 | op1 = new (this, GT_JMP) GenTreeVal(GT_JMP, TYP_VOID, (size_t)resolvedToken.hMethod); |
11780 | |
11781 | /* Mark the basic block as being a JUMP instead of RETURN */ |
11782 | |
11783 | block->bbFlags |= BBF_HAS_JMP; |
11784 | |
11785 | /* Set this flag to make sure register arguments have a location assigned |
11786 | * even if we don't use them inside the method */ |
11787 | |
11788 | compJmpOpUsed = true; |
11789 | |
11790 | fgNoStructPromotion = true; |
11791 | |
11792 | goto APPEND; |
11793 | |
11794 | case CEE_LDELEMA: |
11795 | assertImp(sz == sizeof(unsigned)); |
11796 | |
11797 | _impResolveToken(CORINFO_TOKENKIND_Class); |
11798 | |
11799 | JITDUMP(" %08X" , resolvedToken.token); |
11800 | |
11801 | ldelemClsHnd = resolvedToken.hClass; |
11802 | |
11803 | if (tiVerificationNeeded) |
11804 | { |
11805 | typeInfo tiArray = impStackTop(1).seTypeInfo; |
11806 | typeInfo tiIndex = impStackTop().seTypeInfo; |
11807 | |
11808 | // As per ECMA 'index' specified can be either int32 or native int. |
11809 | Verify(tiIndex.IsIntOrNativeIntType(), "bad index" ); |
11810 | |
11811 | typeInfo arrayElemType = verMakeTypeInfo(ldelemClsHnd); |
11812 | Verify(tiArray.IsNullObjRef() || |
11813 | typeInfo::AreEquivalent(verGetArrayElemType(tiArray), arrayElemType), |
11814 | "bad array" ); |
11815 | |
11816 | tiRetVal = arrayElemType; |
11817 | tiRetVal.MakeByRef(); |
11818 | if (prefixFlags & PREFIX_READONLY) |
11819 | { |
11820 | tiRetVal.SetIsReadonlyByRef(); |
11821 | } |
11822 | |
11823 | // an array interior pointer is always in the heap |
11824 | tiRetVal.SetIsPermanentHomeByRef(); |
11825 | } |
11826 | |
11827 | // If it's a value class array we just do a simple address-of |
11828 | if (eeIsValueClass(ldelemClsHnd)) |
11829 | { |
11830 | CorInfoType cit = info.compCompHnd->getTypeForPrimitiveValueClass(ldelemClsHnd); |
11831 | if (cit == CORINFO_TYPE_UNDEF) |
11832 | { |
11833 | lclTyp = TYP_STRUCT; |
11834 | } |
11835 | else |
11836 | { |
11837 | lclTyp = JITtype2varType(cit); |
11838 | } |
11839 | goto ARR_LD_POST_VERIFY; |
11840 | } |
11841 | |
11842 | // Similarly, if its a readonly access, we can do a simple address-of |
11843 | // without doing a runtime type-check |
11844 | if (prefixFlags & PREFIX_READONLY) |
11845 | { |
11846 | lclTyp = TYP_REF; |
11847 | goto ARR_LD_POST_VERIFY; |
11848 | } |
11849 | |
11850 | // Otherwise we need the full helper function with run-time type check |
11851 | op1 = impTokenToHandle(&resolvedToken); |
11852 | if (op1 == nullptr) |
11853 | { // compDonotInline() |
11854 | return; |
11855 | } |
11856 | |
11857 | args = gtNewArgList(op1); // Type |
11858 | args = gtNewListNode(impPopStack().val, args); // index |
11859 | args = gtNewListNode(impPopStack().val, args); // array |
11860 | op1 = gtNewHelperCallNode(CORINFO_HELP_LDELEMA_REF, TYP_BYREF, args); |
11861 | |
11862 | impPushOnStack(op1, tiRetVal); |
11863 | break; |
11864 | |
11865 | // ldelem for reference and value types |
11866 | case CEE_LDELEM: |
11867 | assertImp(sz == sizeof(unsigned)); |
11868 | |
11869 | _impResolveToken(CORINFO_TOKENKIND_Class); |
11870 | |
11871 | JITDUMP(" %08X" , resolvedToken.token); |
11872 | |
11873 | ldelemClsHnd = resolvedToken.hClass; |
11874 | |
11875 | if (tiVerificationNeeded) |
11876 | { |
11877 | typeInfo tiArray = impStackTop(1).seTypeInfo; |
11878 | typeInfo tiIndex = impStackTop().seTypeInfo; |
11879 | |
11880 | // As per ECMA 'index' specified can be either int32 or native int. |
11881 | Verify(tiIndex.IsIntOrNativeIntType(), "bad index" ); |
11882 | tiRetVal = verMakeTypeInfo(ldelemClsHnd); |
11883 | |
11884 | Verify(tiArray.IsNullObjRef() || tiCompatibleWith(verGetArrayElemType(tiArray), tiRetVal, false), |
11885 | "type of array incompatible with type operand" ); |
11886 | tiRetVal.NormaliseForStack(); |
11887 | } |
11888 | |
11889 | // If it's a reference type or generic variable type |
11890 | // then just generate code as though it's a ldelem.ref instruction |
11891 | if (!eeIsValueClass(ldelemClsHnd)) |
11892 | { |
11893 | lclTyp = TYP_REF; |
11894 | opcode = CEE_LDELEM_REF; |
11895 | } |
11896 | else |
11897 | { |
11898 | CorInfoType jitTyp = info.compCompHnd->asCorInfoType(ldelemClsHnd); |
11899 | lclTyp = JITtype2varType(jitTyp); |
11900 | tiRetVal = verMakeTypeInfo(ldelemClsHnd); // precise type always needed for struct |
11901 | tiRetVal.NormaliseForStack(); |
11902 | } |
11903 | goto ARR_LD_POST_VERIFY; |
11904 | |
11905 | case CEE_LDELEM_I1: |
11906 | lclTyp = TYP_BYTE; |
11907 | goto ARR_LD; |
11908 | case CEE_LDELEM_I2: |
11909 | lclTyp = TYP_SHORT; |
11910 | goto ARR_LD; |
11911 | case CEE_LDELEM_I: |
11912 | lclTyp = TYP_I_IMPL; |
11913 | goto ARR_LD; |
11914 | |
11915 | // Should be UINT, but since no platform widens 4->8 bytes it doesn't matter |
11916 | // and treating it as TYP_INT avoids other asserts. |
11917 | case CEE_LDELEM_U4: |
11918 | lclTyp = TYP_INT; |
11919 | goto ARR_LD; |
11920 | |
11921 | case CEE_LDELEM_I4: |
11922 | lclTyp = TYP_INT; |
11923 | goto ARR_LD; |
11924 | case CEE_LDELEM_I8: |
11925 | lclTyp = TYP_LONG; |
11926 | goto ARR_LD; |
11927 | case CEE_LDELEM_REF: |
11928 | lclTyp = TYP_REF; |
11929 | goto ARR_LD; |
11930 | case CEE_LDELEM_R4: |
11931 | lclTyp = TYP_FLOAT; |
11932 | goto ARR_LD; |
11933 | case CEE_LDELEM_R8: |
11934 | lclTyp = TYP_DOUBLE; |
11935 | goto ARR_LD; |
11936 | case CEE_LDELEM_U1: |
11937 | lclTyp = TYP_UBYTE; |
11938 | goto ARR_LD; |
11939 | case CEE_LDELEM_U2: |
11940 | lclTyp = TYP_USHORT; |
11941 | goto ARR_LD; |
11942 | |
11943 | ARR_LD: |
11944 | |
11945 | if (tiVerificationNeeded) |
11946 | { |
11947 | typeInfo tiArray = impStackTop(1).seTypeInfo; |
11948 | typeInfo tiIndex = impStackTop().seTypeInfo; |
11949 | |
11950 | // As per ECMA 'index' specified can be either int32 or native int. |
11951 | Verify(tiIndex.IsIntOrNativeIntType(), "bad index" ); |
11952 | if (tiArray.IsNullObjRef()) |
11953 | { |
11954 | if (lclTyp == TYP_REF) |
11955 | { // we will say a deref of a null array yields a null ref |
11956 | tiRetVal = typeInfo(TI_NULL); |
11957 | } |
11958 | else |
11959 | { |
11960 | tiRetVal = typeInfo(lclTyp); |
11961 | } |
11962 | } |
11963 | else |
11964 | { |
11965 | tiRetVal = verGetArrayElemType(tiArray); |
11966 | typeInfo arrayElemTi = typeInfo(lclTyp); |
11967 | #ifdef _TARGET_64BIT_ |
11968 | if (opcode == CEE_LDELEM_I) |
11969 | { |
11970 | arrayElemTi = typeInfo::nativeInt(); |
11971 | } |
11972 | |
11973 | if (lclTyp != TYP_REF && lclTyp != TYP_STRUCT) |
11974 | { |
11975 | Verify(typeInfo::AreEquivalent(tiRetVal, arrayElemTi), "bad array" ); |
11976 | } |
11977 | else |
11978 | #endif // _TARGET_64BIT_ |
11979 | { |
11980 | Verify(tiRetVal.IsType(arrayElemTi.GetType()), "bad array" ); |
11981 | } |
11982 | } |
11983 | tiRetVal.NormaliseForStack(); |
11984 | } |
11985 | ARR_LD_POST_VERIFY: |
11986 | |
11987 | /* Pull the index value and array address */ |
11988 | op2 = impPopStack().val; |
11989 | op1 = impPopStack().val; |
11990 | assertImp(op1->gtType == TYP_REF); |
11991 | |
11992 | /* Check for null pointer - in the inliner case we simply abort */ |
11993 | |
11994 | if (compIsForInlining()) |
11995 | { |
11996 | if (op1->gtOper == GT_CNS_INT) |
11997 | { |
11998 | compInlineResult->NoteFatal(InlineObservation::CALLEE_HAS_NULL_FOR_LDELEM); |
11999 | return; |
12000 | } |
12001 | } |
12002 | |
12003 | op1 = impCheckForNullPointer(op1); |
12004 | |
12005 | /* Mark the block as containing an index expression */ |
12006 | |
12007 | if (op1->gtOper == GT_LCL_VAR) |
12008 | { |
12009 | if (op2->gtOper == GT_LCL_VAR || op2->gtOper == GT_CNS_INT || op2->gtOper == GT_ADD) |
12010 | { |
12011 | block->bbFlags |= BBF_HAS_IDX_LEN; |
12012 | optMethodFlags |= OMF_HAS_ARRAYREF; |
12013 | } |
12014 | } |
12015 | |
12016 | /* Create the index node and push it on the stack */ |
12017 | |
12018 | op1 = gtNewIndexRef(lclTyp, op1, op2); |
12019 | |
12020 | ldstruct = (opcode == CEE_LDELEM && lclTyp == TYP_STRUCT); |
12021 | |
12022 | if ((opcode == CEE_LDELEMA) || ldstruct || |
12023 | (ldelemClsHnd != DUMMY_INIT(NULL) && eeIsValueClass(ldelemClsHnd))) |
12024 | { |
12025 | assert(ldelemClsHnd != DUMMY_INIT(NULL)); |
12026 | |
12027 | // remember the element size |
12028 | if (lclTyp == TYP_REF) |
12029 | { |
12030 | op1->gtIndex.gtIndElemSize = TARGET_POINTER_SIZE; |
12031 | } |
12032 | else |
12033 | { |
12034 | // If ldElemClass is precisely a primitive type, use that, otherwise, preserve the struct type. |
12035 | if (info.compCompHnd->getTypeForPrimitiveValueClass(ldelemClsHnd) == CORINFO_TYPE_UNDEF) |
12036 | { |
12037 | op1->gtIndex.gtStructElemClass = ldelemClsHnd; |
12038 | } |
12039 | assert(lclTyp != TYP_STRUCT || op1->gtIndex.gtStructElemClass != nullptr); |
12040 | if (lclTyp == TYP_STRUCT) |
12041 | { |
12042 | size = info.compCompHnd->getClassSize(ldelemClsHnd); |
12043 | op1->gtIndex.gtIndElemSize = size; |
12044 | op1->gtType = lclTyp; |
12045 | } |
12046 | } |
12047 | |
12048 | if ((opcode == CEE_LDELEMA) || ldstruct) |
12049 | { |
12050 | // wrap it in a & |
12051 | lclTyp = TYP_BYREF; |
12052 | |
12053 | op1 = gtNewOperNode(GT_ADDR, lclTyp, op1); |
12054 | } |
12055 | else |
12056 | { |
12057 | assert(lclTyp != TYP_STRUCT); |
12058 | } |
12059 | } |
12060 | |
12061 | if (ldstruct) |
12062 | { |
12063 | // Create an OBJ for the result |
12064 | op1 = gtNewObjNode(ldelemClsHnd, op1); |
12065 | op1->gtFlags |= GTF_EXCEPT; |
12066 | } |
12067 | impPushOnStack(op1, tiRetVal); |
12068 | break; |
12069 | |
12070 | // stelem for reference and value types |
12071 | case CEE_STELEM: |
12072 | |
12073 | assertImp(sz == sizeof(unsigned)); |
12074 | |
12075 | _impResolveToken(CORINFO_TOKENKIND_Class); |
12076 | |
12077 | JITDUMP(" %08X" , resolvedToken.token); |
12078 | |
12079 | stelemClsHnd = resolvedToken.hClass; |
12080 | |
12081 | if (tiVerificationNeeded) |
12082 | { |
12083 | typeInfo tiArray = impStackTop(2).seTypeInfo; |
12084 | typeInfo tiIndex = impStackTop(1).seTypeInfo; |
12085 | typeInfo tiValue = impStackTop().seTypeInfo; |
12086 | |
12087 | // As per ECMA 'index' specified can be either int32 or native int. |
12088 | Verify(tiIndex.IsIntOrNativeIntType(), "bad index" ); |
12089 | typeInfo arrayElem = verMakeTypeInfo(stelemClsHnd); |
12090 | |
12091 | Verify(tiArray.IsNullObjRef() || tiCompatibleWith(arrayElem, verGetArrayElemType(tiArray), false), |
12092 | "type operand incompatible with array element type" ); |
12093 | arrayElem.NormaliseForStack(); |
12094 | Verify(tiCompatibleWith(tiValue, arrayElem, true), "value incompatible with type operand" ); |
12095 | } |
12096 | |
12097 | // If it's a reference type just behave as though it's a stelem.ref instruction |
12098 | if (!eeIsValueClass(stelemClsHnd)) |
12099 | { |
12100 | goto STELEM_REF_POST_VERIFY; |
12101 | } |
12102 | |
12103 | // Otherwise extract the type |
12104 | { |
12105 | CorInfoType jitTyp = info.compCompHnd->asCorInfoType(stelemClsHnd); |
12106 | lclTyp = JITtype2varType(jitTyp); |
12107 | goto ARR_ST_POST_VERIFY; |
12108 | } |
12109 | |
12110 | case CEE_STELEM_REF: |
12111 | |
12112 | if (tiVerificationNeeded) |
12113 | { |
12114 | typeInfo tiArray = impStackTop(2).seTypeInfo; |
12115 | typeInfo tiIndex = impStackTop(1).seTypeInfo; |
12116 | typeInfo tiValue = impStackTop().seTypeInfo; |
12117 | |
12118 | // As per ECMA 'index' specified can be either int32 or native int. |
12119 | Verify(tiIndex.IsIntOrNativeIntType(), "bad index" ); |
12120 | Verify(tiValue.IsObjRef(), "bad value" ); |
12121 | |
12122 | // we only check that it is an object referece, The helper does additional checks |
12123 | Verify(tiArray.IsNullObjRef() || verGetArrayElemType(tiArray).IsType(TI_REF), "bad array" ); |
12124 | } |
12125 | |
12126 | STELEM_REF_POST_VERIFY: |
12127 | |
12128 | arrayNodeTo = impStackTop(2).val; |
12129 | arrayNodeToIndex = impStackTop(1).val; |
12130 | arrayNodeFrom = impStackTop().val; |
12131 | |
12132 | // |
12133 | // Note that it is not legal to optimize away CORINFO_HELP_ARRADDR_ST in a |
12134 | // lot of cases because of covariance. ie. foo[] can be cast to object[]. |
12135 | // |
12136 | |
12137 | // Check for assignment to same array, ie. arrLcl[i] = arrLcl[j] |
12138 | // This does not need CORINFO_HELP_ARRADDR_ST |
12139 | if (arrayNodeFrom->OperGet() == GT_INDEX && arrayNodeFrom->gtOp.gtOp1->gtOper == GT_LCL_VAR && |
12140 | arrayNodeTo->gtOper == GT_LCL_VAR && |
12141 | arrayNodeTo->gtLclVarCommon.gtLclNum == arrayNodeFrom->gtOp.gtOp1->gtLclVarCommon.gtLclNum && |
12142 | !lvaTable[arrayNodeTo->gtLclVarCommon.gtLclNum].lvAddrExposed) |
12143 | { |
12144 | JITDUMP("\nstelem of ref from same array: skipping covariant store check\n" ); |
12145 | lclTyp = TYP_REF; |
12146 | goto ARR_ST_POST_VERIFY; |
12147 | } |
12148 | |
12149 | // Check for assignment of NULL. This does not need CORINFO_HELP_ARRADDR_ST |
12150 | if (arrayNodeFrom->OperGet() == GT_CNS_INT) |
12151 | { |
12152 | JITDUMP("\nstelem of null: skipping covariant store check\n" ); |
12153 | assert(arrayNodeFrom->gtType == TYP_REF && arrayNodeFrom->gtIntCon.gtIconVal == 0); |
12154 | lclTyp = TYP_REF; |
12155 | goto ARR_ST_POST_VERIFY; |
12156 | } |
12157 | |
12158 | /* Call a helper function to do the assignment */ |
12159 | op1 = gtNewHelperCallNode(CORINFO_HELP_ARRADDR_ST, TYP_VOID, impPopList(3, nullptr)); |
12160 | |
12161 | goto SPILL_APPEND; |
12162 | |
12163 | case CEE_STELEM_I1: |
12164 | lclTyp = TYP_BYTE; |
12165 | goto ARR_ST; |
12166 | case CEE_STELEM_I2: |
12167 | lclTyp = TYP_SHORT; |
12168 | goto ARR_ST; |
12169 | case CEE_STELEM_I: |
12170 | lclTyp = TYP_I_IMPL; |
12171 | goto ARR_ST; |
12172 | case CEE_STELEM_I4: |
12173 | lclTyp = TYP_INT; |
12174 | goto ARR_ST; |
12175 | case CEE_STELEM_I8: |
12176 | lclTyp = TYP_LONG; |
12177 | goto ARR_ST; |
12178 | case CEE_STELEM_R4: |
12179 | lclTyp = TYP_FLOAT; |
12180 | goto ARR_ST; |
12181 | case CEE_STELEM_R8: |
12182 | lclTyp = TYP_DOUBLE; |
12183 | goto ARR_ST; |
12184 | |
12185 | ARR_ST: |
12186 | |
12187 | if (tiVerificationNeeded) |
12188 | { |
12189 | typeInfo tiArray = impStackTop(2).seTypeInfo; |
12190 | typeInfo tiIndex = impStackTop(1).seTypeInfo; |
12191 | typeInfo tiValue = impStackTop().seTypeInfo; |
12192 | |
12193 | // As per ECMA 'index' specified can be either int32 or native int. |
12194 | Verify(tiIndex.IsIntOrNativeIntType(), "bad index" ); |
12195 | typeInfo arrayElem = typeInfo(lclTyp); |
12196 | #ifdef _TARGET_64BIT_ |
12197 | if (opcode == CEE_STELEM_I) |
12198 | { |
12199 | arrayElem = typeInfo::nativeInt(); |
12200 | } |
12201 | #endif // _TARGET_64BIT_ |
12202 | Verify(tiArray.IsNullObjRef() || typeInfo::AreEquivalent(verGetArrayElemType(tiArray), arrayElem), |
12203 | "bad array" ); |
12204 | |
12205 | Verify(tiCompatibleWith(NormaliseForStack(tiValue), arrayElem.NormaliseForStack(), true), |
12206 | "bad value" ); |
12207 | } |
12208 | |
12209 | ARR_ST_POST_VERIFY: |
12210 | /* The strict order of evaluation is LHS-operands, RHS-operands, |
12211 | range-check, and then assignment. However, codegen currently |
12212 | does the range-check before evaluation the RHS-operands. So to |
12213 | maintain strict ordering, we spill the stack. */ |
12214 | |
12215 | if (impStackTop().val->gtFlags & GTF_SIDE_EFFECT) |
12216 | { |
12217 | impSpillSideEffects(false, (unsigned)CHECK_SPILL_ALL DEBUGARG( |
12218 | "Strict ordering of exceptions for Array store" )); |
12219 | } |
12220 | |
12221 | /* Pull the new value from the stack */ |
12222 | op2 = impPopStack().val; |
12223 | |
12224 | /* Pull the index value */ |
12225 | op1 = impPopStack().val; |
12226 | |
12227 | /* Pull the array address */ |
12228 | op3 = impPopStack().val; |
12229 | |
12230 | assertImp(op3->gtType == TYP_REF); |
12231 | if (op2->IsVarAddr()) |
12232 | { |
12233 | op2->gtType = TYP_I_IMPL; |
12234 | } |
12235 | |
12236 | op3 = impCheckForNullPointer(op3); |
12237 | |
12238 | // Mark the block as containing an index expression |
12239 | |
12240 | if (op3->gtOper == GT_LCL_VAR) |
12241 | { |
12242 | if (op1->gtOper == GT_LCL_VAR || op1->gtOper == GT_CNS_INT || op1->gtOper == GT_ADD) |
12243 | { |
12244 | block->bbFlags |= BBF_HAS_IDX_LEN; |
12245 | optMethodFlags |= OMF_HAS_ARRAYREF; |
12246 | } |
12247 | } |
12248 | |
12249 | /* Create the index node */ |
12250 | |
12251 | op1 = gtNewIndexRef(lclTyp, op3, op1); |
12252 | |
12253 | /* Create the assignment node and append it */ |
12254 | |
12255 | if (lclTyp == TYP_STRUCT) |
12256 | { |
12257 | assert(stelemClsHnd != DUMMY_INIT(NULL)); |
12258 | |
12259 | op1->gtIndex.gtStructElemClass = stelemClsHnd; |
12260 | op1->gtIndex.gtIndElemSize = info.compCompHnd->getClassSize(stelemClsHnd); |
12261 | } |
12262 | if (varTypeIsStruct(op1)) |
12263 | { |
12264 | op1 = impAssignStruct(op1, op2, stelemClsHnd, (unsigned)CHECK_SPILL_ALL); |
12265 | } |
12266 | else |
12267 | { |
12268 | op2 = impImplicitR4orR8Cast(op2, op1->TypeGet()); |
12269 | op1 = gtNewAssignNode(op1, op2); |
12270 | } |
12271 | |
12272 | /* Mark the expression as containing an assignment */ |
12273 | |
12274 | op1->gtFlags |= GTF_ASG; |
12275 | |
12276 | goto SPILL_APPEND; |
12277 | |
12278 | case CEE_ADD: |
12279 | oper = GT_ADD; |
12280 | goto MATH_OP2; |
12281 | |
12282 | case CEE_ADD_OVF: |
12283 | uns = false; |
12284 | goto ADD_OVF; |
12285 | case CEE_ADD_OVF_UN: |
12286 | uns = true; |
12287 | goto ADD_OVF; |
12288 | |
12289 | ADD_OVF: |
12290 | ovfl = true; |
12291 | callNode = false; |
12292 | oper = GT_ADD; |
12293 | goto MATH_OP2_FLAGS; |
12294 | |
12295 | case CEE_SUB: |
12296 | oper = GT_SUB; |
12297 | goto MATH_OP2; |
12298 | |
12299 | case CEE_SUB_OVF: |
12300 | uns = false; |
12301 | goto SUB_OVF; |
12302 | case CEE_SUB_OVF_UN: |
12303 | uns = true; |
12304 | goto SUB_OVF; |
12305 | |
12306 | SUB_OVF: |
12307 | ovfl = true; |
12308 | callNode = false; |
12309 | oper = GT_SUB; |
12310 | goto MATH_OP2_FLAGS; |
12311 | |
12312 | case CEE_MUL: |
12313 | oper = GT_MUL; |
12314 | goto MATH_MAYBE_CALL_NO_OVF; |
12315 | |
12316 | case CEE_MUL_OVF: |
12317 | uns = false; |
12318 | goto MUL_OVF; |
12319 | case CEE_MUL_OVF_UN: |
12320 | uns = true; |
12321 | goto MUL_OVF; |
12322 | |
12323 | MUL_OVF: |
12324 | ovfl = true; |
12325 | oper = GT_MUL; |
12326 | goto MATH_MAYBE_CALL_OVF; |
12327 | |
12328 | // Other binary math operations |
12329 | |
12330 | case CEE_DIV: |
12331 | oper = GT_DIV; |
12332 | goto MATH_MAYBE_CALL_NO_OVF; |
12333 | |
12334 | case CEE_DIV_UN: |
12335 | oper = GT_UDIV; |
12336 | goto MATH_MAYBE_CALL_NO_OVF; |
12337 | |
12338 | case CEE_REM: |
12339 | oper = GT_MOD; |
12340 | goto MATH_MAYBE_CALL_NO_OVF; |
12341 | |
12342 | case CEE_REM_UN: |
12343 | oper = GT_UMOD; |
12344 | goto MATH_MAYBE_CALL_NO_OVF; |
12345 | |
12346 | MATH_MAYBE_CALL_NO_OVF: |
12347 | ovfl = false; |
12348 | MATH_MAYBE_CALL_OVF: |
12349 | // Morpher has some complex logic about when to turn different |
12350 | // typed nodes on different platforms into helper calls. We |
12351 | // need to either duplicate that logic here, or just |
12352 | // pessimistically make all the nodes large enough to become |
12353 | // call nodes. Since call nodes aren't that much larger and |
12354 | // these opcodes are infrequent enough I chose the latter. |
12355 | callNode = true; |
12356 | goto MATH_OP2_FLAGS; |
12357 | |
12358 | case CEE_AND: |
12359 | oper = GT_AND; |
12360 | goto MATH_OP2; |
12361 | case CEE_OR: |
12362 | oper = GT_OR; |
12363 | goto MATH_OP2; |
12364 | case CEE_XOR: |
12365 | oper = GT_XOR; |
12366 | goto MATH_OP2; |
12367 | |
12368 | MATH_OP2: // For default values of 'ovfl' and 'callNode' |
12369 | |
12370 | ovfl = false; |
12371 | callNode = false; |
12372 | |
12373 | MATH_OP2_FLAGS: // If 'ovfl' and 'callNode' have already been set |
12374 | |
12375 | /* Pull two values and push back the result */ |
12376 | |
12377 | if (tiVerificationNeeded) |
12378 | { |
12379 | const typeInfo& tiOp1 = impStackTop(1).seTypeInfo; |
12380 | const typeInfo& tiOp2 = impStackTop().seTypeInfo; |
12381 | |
12382 | Verify(tiCompatibleWith(tiOp1, tiOp2, true), "different arg type" ); |
12383 | if (oper == GT_ADD || oper == GT_DIV || oper == GT_SUB || oper == GT_MUL || oper == GT_MOD) |
12384 | { |
12385 | Verify(tiOp1.IsNumberType(), "not number" ); |
12386 | } |
12387 | else |
12388 | { |
12389 | Verify(tiOp1.IsIntegerType(), "not integer" ); |
12390 | } |
12391 | |
12392 | Verify(!ovfl || tiOp1.IsIntegerType(), "not integer" ); |
12393 | |
12394 | tiRetVal = tiOp1; |
12395 | |
12396 | #ifdef _TARGET_64BIT_ |
12397 | if (tiOp2.IsNativeIntType()) |
12398 | { |
12399 | tiRetVal = tiOp2; |
12400 | } |
12401 | #endif // _TARGET_64BIT_ |
12402 | } |
12403 | |
12404 | op2 = impPopStack().val; |
12405 | op1 = impPopStack().val; |
12406 | |
12407 | #if !CPU_HAS_FP_SUPPORT |
12408 | if (varTypeIsFloating(op1->gtType)) |
12409 | { |
12410 | callNode = true; |
12411 | } |
12412 | #endif |
12413 | /* Can't do arithmetic with references */ |
12414 | assertImp(genActualType(op1->TypeGet()) != TYP_REF && genActualType(op2->TypeGet()) != TYP_REF); |
12415 | |
12416 | // Change both to TYP_I_IMPL (impBashVarAddrsToI won't change if its a true byref, only |
12417 | // if it is in the stack) |
12418 | impBashVarAddrsToI(op1, op2); |
12419 | |
12420 | type = impGetByRefResultType(oper, uns, &op1, &op2); |
12421 | |
12422 | assert(!ovfl || !varTypeIsFloating(op1->gtType)); |
12423 | |
12424 | /* Special case: "int+0", "int-0", "int*1", "int/1" */ |
12425 | |
12426 | if (op2->gtOper == GT_CNS_INT) |
12427 | { |
12428 | if ((op2->IsIntegralConst(0) && (oper == GT_ADD || oper == GT_SUB)) || |
12429 | (op2->IsIntegralConst(1) && (oper == GT_MUL || oper == GT_DIV))) |
12430 | |
12431 | { |
12432 | impPushOnStack(op1, tiRetVal); |
12433 | break; |
12434 | } |
12435 | } |
12436 | |
12437 | // We can generate a TYP_FLOAT operation that has a TYP_DOUBLE operand |
12438 | // |
12439 | if (varTypeIsFloating(type) && varTypeIsFloating(op1->gtType) && varTypeIsFloating(op2->gtType)) |
12440 | { |
12441 | if (op1->TypeGet() != type) |
12442 | { |
12443 | // We insert a cast of op1 to 'type' |
12444 | op1 = gtNewCastNode(type, op1, false, type); |
12445 | } |
12446 | if (op2->TypeGet() != type) |
12447 | { |
12448 | // We insert a cast of op2 to 'type' |
12449 | op2 = gtNewCastNode(type, op2, false, type); |
12450 | } |
12451 | } |
12452 | |
12453 | #if SMALL_TREE_NODES |
12454 | if (callNode) |
12455 | { |
12456 | /* These operators can later be transformed into 'GT_CALL' */ |
12457 | |
12458 | assert(GenTree::s_gtNodeSizes[GT_CALL] > GenTree::s_gtNodeSizes[GT_MUL]); |
12459 | #ifndef _TARGET_ARM_ |
12460 | assert(GenTree::s_gtNodeSizes[GT_CALL] > GenTree::s_gtNodeSizes[GT_DIV]); |
12461 | assert(GenTree::s_gtNodeSizes[GT_CALL] > GenTree::s_gtNodeSizes[GT_UDIV]); |
12462 | assert(GenTree::s_gtNodeSizes[GT_CALL] > GenTree::s_gtNodeSizes[GT_MOD]); |
12463 | assert(GenTree::s_gtNodeSizes[GT_CALL] > GenTree::s_gtNodeSizes[GT_UMOD]); |
12464 | #endif |
12465 | // It's tempting to use LargeOpOpcode() here, but this logic is *not* saying |
12466 | // that we'll need to transform into a general large node, but rather specifically |
12467 | // to a call: by doing it this way, things keep working if there are multiple sizes, |
12468 | // and a CALL is no longer the largest. |
12469 | // That said, as of now it *is* a large node, so we'll do this with an assert rather |
12470 | // than an "if". |
12471 | assert(GenTree::s_gtNodeSizes[GT_CALL] == TREE_NODE_SZ_LARGE); |
12472 | op1 = new (this, GT_CALL) GenTreeOp(oper, type, op1, op2 DEBUGARG(/*largeNode*/ true)); |
12473 | } |
12474 | else |
12475 | #endif // SMALL_TREE_NODES |
12476 | { |
12477 | op1 = gtNewOperNode(oper, type, op1, op2); |
12478 | } |
12479 | |
12480 | /* Special case: integer/long division may throw an exception */ |
12481 | |
12482 | if (varTypeIsIntegral(op1->TypeGet()) && op1->OperMayThrow(this)) |
12483 | { |
12484 | op1->gtFlags |= GTF_EXCEPT; |
12485 | } |
12486 | |
12487 | if (ovfl) |
12488 | { |
12489 | assert(oper == GT_ADD || oper == GT_SUB || oper == GT_MUL); |
12490 | if (ovflType != TYP_UNKNOWN) |
12491 | { |
12492 | op1->gtType = ovflType; |
12493 | } |
12494 | op1->gtFlags |= (GTF_EXCEPT | GTF_OVERFLOW); |
12495 | if (uns) |
12496 | { |
12497 | op1->gtFlags |= GTF_UNSIGNED; |
12498 | } |
12499 | } |
12500 | |
12501 | impPushOnStack(op1, tiRetVal); |
12502 | break; |
12503 | |
12504 | case CEE_SHL: |
12505 | oper = GT_LSH; |
12506 | goto CEE_SH_OP2; |
12507 | |
12508 | case CEE_SHR: |
12509 | oper = GT_RSH; |
12510 | goto CEE_SH_OP2; |
12511 | case CEE_SHR_UN: |
12512 | oper = GT_RSZ; |
12513 | goto CEE_SH_OP2; |
12514 | |
12515 | CEE_SH_OP2: |
12516 | if (tiVerificationNeeded) |
12517 | { |
12518 | const typeInfo& tiVal = impStackTop(1).seTypeInfo; |
12519 | const typeInfo& tiShift = impStackTop(0).seTypeInfo; |
12520 | Verify(tiVal.IsIntegerType() && tiShift.IsType(TI_INT), "Bad shift args" ); |
12521 | tiRetVal = tiVal; |
12522 | } |
12523 | op2 = impPopStack().val; |
12524 | op1 = impPopStack().val; // operand to be shifted |
12525 | impBashVarAddrsToI(op1, op2); |
12526 | |
12527 | type = genActualType(op1->TypeGet()); |
12528 | op1 = gtNewOperNode(oper, type, op1, op2); |
12529 | |
12530 | impPushOnStack(op1, tiRetVal); |
12531 | break; |
12532 | |
12533 | case CEE_NOT: |
12534 | if (tiVerificationNeeded) |
12535 | { |
12536 | tiRetVal = impStackTop().seTypeInfo; |
12537 | Verify(tiRetVal.IsIntegerType(), "bad int value" ); |
12538 | } |
12539 | |
12540 | op1 = impPopStack().val; |
12541 | impBashVarAddrsToI(op1, nullptr); |
12542 | type = genActualType(op1->TypeGet()); |
12543 | impPushOnStack(gtNewOperNode(GT_NOT, type, op1), tiRetVal); |
12544 | break; |
12545 | |
12546 | case CEE_CKFINITE: |
12547 | if (tiVerificationNeeded) |
12548 | { |
12549 | tiRetVal = impStackTop().seTypeInfo; |
12550 | Verify(tiRetVal.IsType(TI_DOUBLE), "bad R value" ); |
12551 | } |
12552 | op1 = impPopStack().val; |
12553 | type = op1->TypeGet(); |
12554 | op1 = gtNewOperNode(GT_CKFINITE, type, op1); |
12555 | op1->gtFlags |= GTF_EXCEPT; |
12556 | |
12557 | impPushOnStack(op1, tiRetVal); |
12558 | break; |
12559 | |
12560 | case CEE_LEAVE: |
12561 | |
12562 | val = getI4LittleEndian(codeAddr); // jump distance |
12563 | jmpAddr = (IL_OFFSET)((codeAddr - info.compCode + sizeof(__int32)) + val); |
12564 | goto LEAVE; |
12565 | |
12566 | case CEE_LEAVE_S: |
12567 | val = getI1LittleEndian(codeAddr); // jump distance |
12568 | jmpAddr = (IL_OFFSET)((codeAddr - info.compCode + sizeof(__int8)) + val); |
12569 | |
12570 | LEAVE: |
12571 | |
12572 | if (compIsForInlining()) |
12573 | { |
12574 | compInlineResult->NoteFatal(InlineObservation::CALLEE_HAS_LEAVE); |
12575 | return; |
12576 | } |
12577 | |
12578 | JITDUMP(" %04X" , jmpAddr); |
12579 | if (block->bbJumpKind != BBJ_LEAVE) |
12580 | { |
12581 | impResetLeaveBlock(block, jmpAddr); |
12582 | } |
12583 | |
12584 | assert(jmpAddr == block->bbJumpDest->bbCodeOffs); |
12585 | impImportLeave(block); |
12586 | impNoteBranchOffs(); |
12587 | |
12588 | break; |
12589 | |
12590 | case CEE_BR: |
12591 | case CEE_BR_S: |
12592 | jmpDist = (sz == 1) ? getI1LittleEndian(codeAddr) : getI4LittleEndian(codeAddr); |
12593 | |
12594 | if (compIsForInlining() && jmpDist == 0) |
12595 | { |
12596 | break; /* NOP */ |
12597 | } |
12598 | |
12599 | impNoteBranchOffs(); |
12600 | break; |
12601 | |
12602 | case CEE_BRTRUE: |
12603 | case CEE_BRTRUE_S: |
12604 | case CEE_BRFALSE: |
12605 | case CEE_BRFALSE_S: |
12606 | |
12607 | /* Pop the comparand (now there's a neat term) from the stack */ |
12608 | if (tiVerificationNeeded) |
12609 | { |
12610 | typeInfo& tiVal = impStackTop().seTypeInfo; |
12611 | Verify(tiVal.IsObjRef() || tiVal.IsByRef() || tiVal.IsIntegerType() || tiVal.IsMethod(), |
12612 | "bad value" ); |
12613 | } |
12614 | |
12615 | op1 = impPopStack().val; |
12616 | type = op1->TypeGet(); |
12617 | |
12618 | // brfalse and brtrue is only allowed on I4, refs, and byrefs. |
12619 | if (opts.OptimizationEnabled() && (block->bbJumpDest == block->bbNext)) |
12620 | { |
12621 | block->bbJumpKind = BBJ_NONE; |
12622 | |
12623 | if (op1->gtFlags & GTF_GLOB_EFFECT) |
12624 | { |
12625 | op1 = gtUnusedValNode(op1); |
12626 | goto SPILL_APPEND; |
12627 | } |
12628 | else |
12629 | { |
12630 | break; |
12631 | } |
12632 | } |
12633 | |
12634 | if (op1->OperIsCompare()) |
12635 | { |
12636 | if (opcode == CEE_BRFALSE || opcode == CEE_BRFALSE_S) |
12637 | { |
12638 | // Flip the sense of the compare |
12639 | |
12640 | op1 = gtReverseCond(op1); |
12641 | } |
12642 | } |
12643 | else |
12644 | { |
12645 | /* We'll compare against an equally-sized integer 0 */ |
12646 | /* For small types, we always compare against int */ |
12647 | op2 = gtNewZeroConNode(genActualType(op1->gtType)); |
12648 | |
12649 | /* Create the comparison operator and try to fold it */ |
12650 | |
12651 | oper = (opcode == CEE_BRTRUE || opcode == CEE_BRTRUE_S) ? GT_NE : GT_EQ; |
12652 | op1 = gtNewOperNode(oper, TYP_INT, op1, op2); |
12653 | } |
12654 | |
12655 | // fall through |
12656 | |
12657 | COND_JUMP: |
12658 | |
12659 | /* Fold comparison if we can */ |
12660 | |
12661 | op1 = gtFoldExpr(op1); |
12662 | |
12663 | /* Try to fold the really simple cases like 'iconst *, ifne/ifeq'*/ |
12664 | /* Don't make any blocks unreachable in import only mode */ |
12665 | |
12666 | if ((op1->gtOper == GT_CNS_INT) && !compIsForImportOnly()) |
12667 | { |
12668 | /* gtFoldExpr() should prevent this as we don't want to make any blocks |
12669 | unreachable under compDbgCode */ |
12670 | assert(!opts.compDbgCode); |
12671 | |
12672 | BBjumpKinds foldedJumpKind = (BBjumpKinds)(op1->gtIntCon.gtIconVal ? BBJ_ALWAYS : BBJ_NONE); |
12673 | assertImp((block->bbJumpKind == BBJ_COND) // normal case |
12674 | || (block->bbJumpKind == foldedJumpKind)); // this can happen if we are reimporting the |
12675 | // block for the second time |
12676 | |
12677 | block->bbJumpKind = foldedJumpKind; |
12678 | #ifdef DEBUG |
12679 | if (verbose) |
12680 | { |
12681 | if (op1->gtIntCon.gtIconVal) |
12682 | { |
12683 | printf("\nThe conditional jump becomes an unconditional jump to " FMT_BB "\n" , |
12684 | block->bbJumpDest->bbNum); |
12685 | } |
12686 | else |
12687 | { |
12688 | printf("\nThe block falls through into the next " FMT_BB "\n" , block->bbNext->bbNum); |
12689 | } |
12690 | } |
12691 | #endif |
12692 | break; |
12693 | } |
12694 | |
12695 | op1 = gtNewOperNode(GT_JTRUE, TYP_VOID, op1); |
12696 | |
12697 | /* GT_JTRUE is handled specially for non-empty stacks. See 'addStmt' |
12698 | in impImportBlock(block). For correct line numbers, spill stack. */ |
12699 | |
12700 | if (opts.compDbgCode && impCurStmtOffs != BAD_IL_OFFSET) |
12701 | { |
12702 | impSpillStackEnsure(true); |
12703 | } |
12704 | |
12705 | goto SPILL_APPEND; |
12706 | |
12707 | case CEE_CEQ: |
12708 | oper = GT_EQ; |
12709 | uns = false; |
12710 | goto CMP_2_OPs; |
12711 | case CEE_CGT_UN: |
12712 | oper = GT_GT; |
12713 | uns = true; |
12714 | goto CMP_2_OPs; |
12715 | case CEE_CGT: |
12716 | oper = GT_GT; |
12717 | uns = false; |
12718 | goto CMP_2_OPs; |
12719 | case CEE_CLT_UN: |
12720 | oper = GT_LT; |
12721 | uns = true; |
12722 | goto CMP_2_OPs; |
12723 | case CEE_CLT: |
12724 | oper = GT_LT; |
12725 | uns = false; |
12726 | goto CMP_2_OPs; |
12727 | |
12728 | CMP_2_OPs: |
12729 | if (tiVerificationNeeded) |
12730 | { |
12731 | verVerifyCond(impStackTop(1).seTypeInfo, impStackTop().seTypeInfo, opcode); |
12732 | tiRetVal = typeInfo(TI_INT); |
12733 | } |
12734 | |
12735 | op2 = impPopStack().val; |
12736 | op1 = impPopStack().val; |
12737 | |
12738 | #ifdef _TARGET_64BIT_ |
12739 | if (varTypeIsI(op1->TypeGet()) && (genActualType(op2->TypeGet()) == TYP_INT)) |
12740 | { |
12741 | op2 = gtNewCastNode(TYP_I_IMPL, op2, uns, uns ? TYP_U_IMPL : TYP_I_IMPL); |
12742 | } |
12743 | else if (varTypeIsI(op2->TypeGet()) && (genActualType(op1->TypeGet()) == TYP_INT)) |
12744 | { |
12745 | op1 = gtNewCastNode(TYP_I_IMPL, op1, uns, uns ? TYP_U_IMPL : TYP_I_IMPL); |
12746 | } |
12747 | #endif // _TARGET_64BIT_ |
12748 | |
12749 | assertImp(genActualType(op1->TypeGet()) == genActualType(op2->TypeGet()) || |
12750 | varTypeIsI(op1->TypeGet()) && varTypeIsI(op2->TypeGet()) || |
12751 | varTypeIsFloating(op1->gtType) && varTypeIsFloating(op2->gtType)); |
12752 | |
12753 | /* Create the comparison node */ |
12754 | |
12755 | op1 = gtNewOperNode(oper, TYP_INT, op1, op2); |
12756 | |
12757 | /* TODO: setting both flags when only one is appropriate */ |
12758 | if (opcode == CEE_CGT_UN || opcode == CEE_CLT_UN) |
12759 | { |
12760 | op1->gtFlags |= GTF_RELOP_NAN_UN | GTF_UNSIGNED; |
12761 | } |
12762 | |
12763 | // Fold result, if possible. |
12764 | op1 = gtFoldExpr(op1); |
12765 | |
12766 | impPushOnStack(op1, tiRetVal); |
12767 | break; |
12768 | |
12769 | case CEE_BEQ_S: |
12770 | case CEE_BEQ: |
12771 | oper = GT_EQ; |
12772 | goto CMP_2_OPs_AND_BR; |
12773 | |
12774 | case CEE_BGE_S: |
12775 | case CEE_BGE: |
12776 | oper = GT_GE; |
12777 | goto CMP_2_OPs_AND_BR; |
12778 | |
12779 | case CEE_BGE_UN_S: |
12780 | case CEE_BGE_UN: |
12781 | oper = GT_GE; |
12782 | goto CMP_2_OPs_AND_BR_UN; |
12783 | |
12784 | case CEE_BGT_S: |
12785 | case CEE_BGT: |
12786 | oper = GT_GT; |
12787 | goto CMP_2_OPs_AND_BR; |
12788 | |
12789 | case CEE_BGT_UN_S: |
12790 | case CEE_BGT_UN: |
12791 | oper = GT_GT; |
12792 | goto CMP_2_OPs_AND_BR_UN; |
12793 | |
12794 | case CEE_BLE_S: |
12795 | case CEE_BLE: |
12796 | oper = GT_LE; |
12797 | goto CMP_2_OPs_AND_BR; |
12798 | |
12799 | case CEE_BLE_UN_S: |
12800 | case CEE_BLE_UN: |
12801 | oper = GT_LE; |
12802 | goto CMP_2_OPs_AND_BR_UN; |
12803 | |
12804 | case CEE_BLT_S: |
12805 | case CEE_BLT: |
12806 | oper = GT_LT; |
12807 | goto CMP_2_OPs_AND_BR; |
12808 | |
12809 | case CEE_BLT_UN_S: |
12810 | case CEE_BLT_UN: |
12811 | oper = GT_LT; |
12812 | goto CMP_2_OPs_AND_BR_UN; |
12813 | |
12814 | case CEE_BNE_UN_S: |
12815 | case CEE_BNE_UN: |
12816 | oper = GT_NE; |
12817 | goto CMP_2_OPs_AND_BR_UN; |
12818 | |
12819 | CMP_2_OPs_AND_BR_UN: |
12820 | uns = true; |
12821 | unordered = true; |
12822 | goto CMP_2_OPs_AND_BR_ALL; |
12823 | CMP_2_OPs_AND_BR: |
12824 | uns = false; |
12825 | unordered = false; |
12826 | goto CMP_2_OPs_AND_BR_ALL; |
12827 | CMP_2_OPs_AND_BR_ALL: |
12828 | |
12829 | if (tiVerificationNeeded) |
12830 | { |
12831 | verVerifyCond(impStackTop(1).seTypeInfo, impStackTop().seTypeInfo, opcode); |
12832 | } |
12833 | |
12834 | /* Pull two values */ |
12835 | op2 = impPopStack().val; |
12836 | op1 = impPopStack().val; |
12837 | |
12838 | #ifdef _TARGET_64BIT_ |
12839 | if ((op1->TypeGet() == TYP_I_IMPL) && (genActualType(op2->TypeGet()) == TYP_INT)) |
12840 | { |
12841 | op2 = gtNewCastNode(TYP_I_IMPL, op2, uns, uns ? TYP_U_IMPL : TYP_I_IMPL); |
12842 | } |
12843 | else if ((op2->TypeGet() == TYP_I_IMPL) && (genActualType(op1->TypeGet()) == TYP_INT)) |
12844 | { |
12845 | op1 = gtNewCastNode(TYP_I_IMPL, op1, uns, uns ? TYP_U_IMPL : TYP_I_IMPL); |
12846 | } |
12847 | #endif // _TARGET_64BIT_ |
12848 | |
12849 | assertImp(genActualType(op1->TypeGet()) == genActualType(op2->TypeGet()) || |
12850 | varTypeIsI(op1->TypeGet()) && varTypeIsI(op2->TypeGet()) || |
12851 | varTypeIsFloating(op1->gtType) && varTypeIsFloating(op2->gtType)); |
12852 | |
12853 | if (opts.OptimizationEnabled() && (block->bbJumpDest == block->bbNext)) |
12854 | { |
12855 | block->bbJumpKind = BBJ_NONE; |
12856 | |
12857 | if (op1->gtFlags & GTF_GLOB_EFFECT) |
12858 | { |
12859 | impSpillSideEffects(false, (unsigned)CHECK_SPILL_ALL DEBUGARG( |
12860 | "Branch to next Optimization, op1 side effect" )); |
12861 | impAppendTree(gtUnusedValNode(op1), (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); |
12862 | } |
12863 | if (op2->gtFlags & GTF_GLOB_EFFECT) |
12864 | { |
12865 | impSpillSideEffects(false, (unsigned)CHECK_SPILL_ALL DEBUGARG( |
12866 | "Branch to next Optimization, op2 side effect" )); |
12867 | impAppendTree(gtUnusedValNode(op2), (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); |
12868 | } |
12869 | |
12870 | #ifdef DEBUG |
12871 | if ((op1->gtFlags | op2->gtFlags) & GTF_GLOB_EFFECT) |
12872 | { |
12873 | impNoteLastILoffs(); |
12874 | } |
12875 | #endif |
12876 | break; |
12877 | } |
12878 | |
12879 | // We can generate an compare of different sized floating point op1 and op2 |
12880 | // We insert a cast |
12881 | // |
12882 | if (varTypeIsFloating(op1->TypeGet())) |
12883 | { |
12884 | if (op1->TypeGet() != op2->TypeGet()) |
12885 | { |
12886 | assert(varTypeIsFloating(op2->TypeGet())); |
12887 | |
12888 | // say op1=double, op2=float. To avoid loss of precision |
12889 | // while comparing, op2 is converted to double and double |
12890 | // comparison is done. |
12891 | if (op1->TypeGet() == TYP_DOUBLE) |
12892 | { |
12893 | // We insert a cast of op2 to TYP_DOUBLE |
12894 | op2 = gtNewCastNode(TYP_DOUBLE, op2, false, TYP_DOUBLE); |
12895 | } |
12896 | else if (op2->TypeGet() == TYP_DOUBLE) |
12897 | { |
12898 | // We insert a cast of op1 to TYP_DOUBLE |
12899 | op1 = gtNewCastNode(TYP_DOUBLE, op1, false, TYP_DOUBLE); |
12900 | } |
12901 | } |
12902 | } |
12903 | |
12904 | /* Create and append the operator */ |
12905 | |
12906 | op1 = gtNewOperNode(oper, TYP_INT, op1, op2); |
12907 | |
12908 | if (uns) |
12909 | { |
12910 | op1->gtFlags |= GTF_UNSIGNED; |
12911 | } |
12912 | |
12913 | if (unordered) |
12914 | { |
12915 | op1->gtFlags |= GTF_RELOP_NAN_UN; |
12916 | } |
12917 | |
12918 | goto COND_JUMP; |
12919 | |
12920 | case CEE_SWITCH: |
12921 | assert(!compIsForInlining()); |
12922 | |
12923 | if (tiVerificationNeeded) |
12924 | { |
12925 | Verify(impStackTop().seTypeInfo.IsType(TI_INT), "Bad switch val" ); |
12926 | } |
12927 | /* Pop the switch value off the stack */ |
12928 | op1 = impPopStack().val; |
12929 | assertImp(genActualTypeIsIntOrI(op1->TypeGet())); |
12930 | |
12931 | /* We can create a switch node */ |
12932 | |
12933 | op1 = gtNewOperNode(GT_SWITCH, TYP_VOID, op1); |
12934 | |
12935 | val = (int)getU4LittleEndian(codeAddr); |
12936 | codeAddr += 4 + val * 4; // skip over the switch-table |
12937 | |
12938 | goto SPILL_APPEND; |
12939 | |
12940 | /************************** Casting OPCODES ***************************/ |
12941 | |
12942 | case CEE_CONV_OVF_I1: |
12943 | lclTyp = TYP_BYTE; |
12944 | goto CONV_OVF; |
12945 | case CEE_CONV_OVF_I2: |
12946 | lclTyp = TYP_SHORT; |
12947 | goto CONV_OVF; |
12948 | case CEE_CONV_OVF_I: |
12949 | lclTyp = TYP_I_IMPL; |
12950 | goto CONV_OVF; |
12951 | case CEE_CONV_OVF_I4: |
12952 | lclTyp = TYP_INT; |
12953 | goto CONV_OVF; |
12954 | case CEE_CONV_OVF_I8: |
12955 | lclTyp = TYP_LONG; |
12956 | goto CONV_OVF; |
12957 | |
12958 | case CEE_CONV_OVF_U1: |
12959 | lclTyp = TYP_UBYTE; |
12960 | goto CONV_OVF; |
12961 | case CEE_CONV_OVF_U2: |
12962 | lclTyp = TYP_USHORT; |
12963 | goto CONV_OVF; |
12964 | case CEE_CONV_OVF_U: |
12965 | lclTyp = TYP_U_IMPL; |
12966 | goto CONV_OVF; |
12967 | case CEE_CONV_OVF_U4: |
12968 | lclTyp = TYP_UINT; |
12969 | goto CONV_OVF; |
12970 | case CEE_CONV_OVF_U8: |
12971 | lclTyp = TYP_ULONG; |
12972 | goto CONV_OVF; |
12973 | |
12974 | case CEE_CONV_OVF_I1_UN: |
12975 | lclTyp = TYP_BYTE; |
12976 | goto CONV_OVF_UN; |
12977 | case CEE_CONV_OVF_I2_UN: |
12978 | lclTyp = TYP_SHORT; |
12979 | goto CONV_OVF_UN; |
12980 | case CEE_CONV_OVF_I_UN: |
12981 | lclTyp = TYP_I_IMPL; |
12982 | goto CONV_OVF_UN; |
12983 | case CEE_CONV_OVF_I4_UN: |
12984 | lclTyp = TYP_INT; |
12985 | goto CONV_OVF_UN; |
12986 | case CEE_CONV_OVF_I8_UN: |
12987 | lclTyp = TYP_LONG; |
12988 | goto CONV_OVF_UN; |
12989 | |
12990 | case CEE_CONV_OVF_U1_UN: |
12991 | lclTyp = TYP_UBYTE; |
12992 | goto CONV_OVF_UN; |
12993 | case CEE_CONV_OVF_U2_UN: |
12994 | lclTyp = TYP_USHORT; |
12995 | goto CONV_OVF_UN; |
12996 | case CEE_CONV_OVF_U_UN: |
12997 | lclTyp = TYP_U_IMPL; |
12998 | goto CONV_OVF_UN; |
12999 | case CEE_CONV_OVF_U4_UN: |
13000 | lclTyp = TYP_UINT; |
13001 | goto CONV_OVF_UN; |
13002 | case CEE_CONV_OVF_U8_UN: |
13003 | lclTyp = TYP_ULONG; |
13004 | goto CONV_OVF_UN; |
13005 | |
13006 | CONV_OVF_UN: |
13007 | uns = true; |
13008 | goto CONV_OVF_COMMON; |
13009 | CONV_OVF: |
13010 | uns = false; |
13011 | goto CONV_OVF_COMMON; |
13012 | |
13013 | CONV_OVF_COMMON: |
13014 | ovfl = true; |
13015 | goto _CONV; |
13016 | |
13017 | case CEE_CONV_I1: |
13018 | lclTyp = TYP_BYTE; |
13019 | goto CONV; |
13020 | case CEE_CONV_I2: |
13021 | lclTyp = TYP_SHORT; |
13022 | goto CONV; |
13023 | case CEE_CONV_I: |
13024 | lclTyp = TYP_I_IMPL; |
13025 | goto CONV; |
13026 | case CEE_CONV_I4: |
13027 | lclTyp = TYP_INT; |
13028 | goto CONV; |
13029 | case CEE_CONV_I8: |
13030 | lclTyp = TYP_LONG; |
13031 | goto CONV; |
13032 | |
13033 | case CEE_CONV_U1: |
13034 | lclTyp = TYP_UBYTE; |
13035 | goto CONV; |
13036 | case CEE_CONV_U2: |
13037 | lclTyp = TYP_USHORT; |
13038 | goto CONV; |
13039 | #if (REGSIZE_BYTES == 8) |
13040 | case CEE_CONV_U: |
13041 | lclTyp = TYP_U_IMPL; |
13042 | goto CONV_UN; |
13043 | #else |
13044 | case CEE_CONV_U: |
13045 | lclTyp = TYP_U_IMPL; |
13046 | goto CONV; |
13047 | #endif |
13048 | case CEE_CONV_U4: |
13049 | lclTyp = TYP_UINT; |
13050 | goto CONV; |
13051 | case CEE_CONV_U8: |
13052 | lclTyp = TYP_ULONG; |
13053 | goto CONV_UN; |
13054 | |
13055 | case CEE_CONV_R4: |
13056 | lclTyp = TYP_FLOAT; |
13057 | goto CONV; |
13058 | case CEE_CONV_R8: |
13059 | lclTyp = TYP_DOUBLE; |
13060 | goto CONV; |
13061 | |
13062 | case CEE_CONV_R_UN: |
13063 | lclTyp = TYP_DOUBLE; |
13064 | goto CONV_UN; |
13065 | |
13066 | CONV_UN: |
13067 | uns = true; |
13068 | ovfl = false; |
13069 | goto _CONV; |
13070 | |
13071 | CONV: |
13072 | uns = false; |
13073 | ovfl = false; |
13074 | goto _CONV; |
13075 | |
13076 | _CONV: |
13077 | // just check that we have a number on the stack |
13078 | if (tiVerificationNeeded) |
13079 | { |
13080 | const typeInfo& tiVal = impStackTop().seTypeInfo; |
13081 | Verify(tiVal.IsNumberType(), "bad arg" ); |
13082 | |
13083 | #ifdef _TARGET_64BIT_ |
13084 | bool isNative = false; |
13085 | |
13086 | switch (opcode) |
13087 | { |
13088 | case CEE_CONV_OVF_I: |
13089 | case CEE_CONV_OVF_I_UN: |
13090 | case CEE_CONV_I: |
13091 | case CEE_CONV_OVF_U: |
13092 | case CEE_CONV_OVF_U_UN: |
13093 | case CEE_CONV_U: |
13094 | isNative = true; |
13095 | default: |
13096 | // leave 'isNative' = false; |
13097 | break; |
13098 | } |
13099 | if (isNative) |
13100 | { |
13101 | tiRetVal = typeInfo::nativeInt(); |
13102 | } |
13103 | else |
13104 | #endif // _TARGET_64BIT_ |
13105 | { |
13106 | tiRetVal = typeInfo(lclTyp).NormaliseForStack(); |
13107 | } |
13108 | } |
13109 | |
13110 | // only converts from FLOAT or DOUBLE to an integer type |
13111 | // and converts from ULONG (or LONG on ARM) to DOUBLE are morphed to calls |
13112 | |
13113 | if (varTypeIsFloating(lclTyp)) |
13114 | { |
13115 | callNode = varTypeIsLong(impStackTop().val) || uns // uint->dbl gets turned into uint->long->dbl |
13116 | #ifdef _TARGET_64BIT_ |
13117 | // TODO-ARM64-Bug?: This was AMD64; I enabled it for ARM64 also. OK? |
13118 | // TYP_BYREF could be used as TYP_I_IMPL which is long. |
13119 | // TODO-CQ: remove this when we lower casts long/ulong --> float/double |
13120 | // and generate SSE2 code instead of going through helper calls. |
13121 | || (impStackTop().val->TypeGet() == TYP_BYREF) |
13122 | #endif |
13123 | ; |
13124 | } |
13125 | else |
13126 | { |
13127 | callNode = varTypeIsFloating(impStackTop().val->TypeGet()); |
13128 | } |
13129 | |
13130 | // At this point uns, ovf, callNode all set |
13131 | |
13132 | op1 = impPopStack().val; |
13133 | impBashVarAddrsToI(op1); |
13134 | |
13135 | if (varTypeIsSmall(lclTyp) && !ovfl && op1->gtType == TYP_INT && op1->gtOper == GT_AND) |
13136 | { |
13137 | op2 = op1->gtOp.gtOp2; |
13138 | |
13139 | if (op2->gtOper == GT_CNS_INT) |
13140 | { |
13141 | ssize_t ival = op2->gtIntCon.gtIconVal; |
13142 | ssize_t mask, umask; |
13143 | |
13144 | switch (lclTyp) |
13145 | { |
13146 | case TYP_BYTE: |
13147 | case TYP_UBYTE: |
13148 | mask = 0x00FF; |
13149 | umask = 0x007F; |
13150 | break; |
13151 | case TYP_USHORT: |
13152 | case TYP_SHORT: |
13153 | mask = 0xFFFF; |
13154 | umask = 0x7FFF; |
13155 | break; |
13156 | |
13157 | default: |
13158 | assert(!"unexpected type" ); |
13159 | return; |
13160 | } |
13161 | |
13162 | if (((ival & umask) == ival) || ((ival & mask) == ival && uns)) |
13163 | { |
13164 | /* Toss the cast, it's a waste of time */ |
13165 | |
13166 | impPushOnStack(op1, tiRetVal); |
13167 | break; |
13168 | } |
13169 | else if (ival == mask) |
13170 | { |
13171 | /* Toss the masking, it's a waste of time, since |
13172 | we sign-extend from the small value anyways */ |
13173 | |
13174 | op1 = op1->gtOp.gtOp1; |
13175 | } |
13176 | } |
13177 | } |
13178 | |
13179 | /* The 'op2' sub-operand of a cast is the 'real' type number, |
13180 | since the result of a cast to one of the 'small' integer |
13181 | types is an integer. |
13182 | */ |
13183 | |
13184 | type = genActualType(lclTyp); |
13185 | |
13186 | // If this is a no-op cast, just use op1. |
13187 | if (!ovfl && (type == op1->TypeGet()) && (genTypeSize(type) == genTypeSize(lclTyp))) |
13188 | { |
13189 | // Nothing needs to change |
13190 | } |
13191 | // Work is evidently required, add cast node |
13192 | else |
13193 | { |
13194 | #if SMALL_TREE_NODES |
13195 | if (callNode) |
13196 | { |
13197 | op1 = gtNewCastNodeL(type, op1, uns, lclTyp); |
13198 | } |
13199 | else |
13200 | #endif // SMALL_TREE_NODES |
13201 | { |
13202 | op1 = gtNewCastNode(type, op1, uns, lclTyp); |
13203 | } |
13204 | |
13205 | if (ovfl) |
13206 | { |
13207 | op1->gtFlags |= (GTF_OVERFLOW | GTF_EXCEPT); |
13208 | } |
13209 | } |
13210 | |
13211 | impPushOnStack(op1, tiRetVal); |
13212 | break; |
13213 | |
13214 | case CEE_NEG: |
13215 | if (tiVerificationNeeded) |
13216 | { |
13217 | tiRetVal = impStackTop().seTypeInfo; |
13218 | Verify(tiRetVal.IsNumberType(), "Bad arg" ); |
13219 | } |
13220 | |
13221 | op1 = impPopStack().val; |
13222 | impBashVarAddrsToI(op1, nullptr); |
13223 | impPushOnStack(gtNewOperNode(GT_NEG, genActualType(op1->gtType), op1), tiRetVal); |
13224 | break; |
13225 | |
13226 | case CEE_POP: |
13227 | { |
13228 | /* Pull the top value from the stack */ |
13229 | |
13230 | StackEntry se = impPopStack(); |
13231 | clsHnd = se.seTypeInfo.GetClassHandle(); |
13232 | op1 = se.val; |
13233 | |
13234 | /* Get hold of the type of the value being duplicated */ |
13235 | |
13236 | lclTyp = genActualType(op1->gtType); |
13237 | |
13238 | /* Does the value have any side effects? */ |
13239 | |
13240 | if ((op1->gtFlags & GTF_SIDE_EFFECT) || opts.compDbgCode) |
13241 | { |
13242 | // Since we are throwing away the value, just normalize |
13243 | // it to its address. This is more efficient. |
13244 | |
13245 | if (varTypeIsStruct(op1)) |
13246 | { |
13247 | JITDUMP("\n ... CEE_POP struct ...\n" ); |
13248 | DISPTREE(op1); |
13249 | #ifdef UNIX_AMD64_ABI |
13250 | // Non-calls, such as obj or ret_expr, have to go through this. |
13251 | // Calls with large struct return value have to go through this. |
13252 | // Helper calls with small struct return value also have to go |
13253 | // through this since they do not follow Unix calling convention. |
13254 | if (op1->gtOper != GT_CALL || !IsMultiRegReturnedType(clsHnd) || |
13255 | op1->AsCall()->gtCallType == CT_HELPER) |
13256 | #endif // UNIX_AMD64_ABI |
13257 | { |
13258 | // If the value being produced comes from loading |
13259 | // via an underlying address, just null check the address. |
13260 | if (op1->OperIs(GT_FIELD, GT_IND, GT_OBJ)) |
13261 | { |
13262 | op1->ChangeOper(GT_NULLCHECK); |
13263 | op1->gtType = TYP_BYTE; |
13264 | } |
13265 | else |
13266 | { |
13267 | op1 = impGetStructAddr(op1, clsHnd, (unsigned)CHECK_SPILL_ALL, false); |
13268 | } |
13269 | |
13270 | JITDUMP("\n ... optimized to ...\n" ); |
13271 | DISPTREE(op1); |
13272 | } |
13273 | } |
13274 | |
13275 | // If op1 is non-overflow cast, throw it away since it is useless. |
13276 | // Another reason for throwing away the useless cast is in the context of |
13277 | // implicit tail calls when the operand of pop is GT_CAST(GT_CALL(..)). |
13278 | // The cast gets added as part of importing GT_CALL, which gets in the way |
13279 | // of fgMorphCall() on the forms of tail call nodes that we assert. |
13280 | if ((op1->gtOper == GT_CAST) && !op1->gtOverflow()) |
13281 | { |
13282 | op1 = op1->gtOp.gtOp1; |
13283 | } |
13284 | |
13285 | // If 'op1' is an expression, create an assignment node. |
13286 | // Helps analyses (like CSE) to work fine. |
13287 | |
13288 | if (op1->gtOper != GT_CALL) |
13289 | { |
13290 | op1 = gtUnusedValNode(op1); |
13291 | } |
13292 | |
13293 | /* Append the value to the tree list */ |
13294 | goto SPILL_APPEND; |
13295 | } |
13296 | |
13297 | /* No side effects - just throw the <BEEP> thing away */ |
13298 | } |
13299 | break; |
13300 | |
13301 | case CEE_DUP: |
13302 | { |
13303 | if (tiVerificationNeeded) |
13304 | { |
13305 | // Dup could start the begining of delegate creation sequence, remember that |
13306 | delegateCreateStart = codeAddr - 1; |
13307 | impStackTop(0); |
13308 | } |
13309 | |
13310 | // If the expression to dup is simple, just clone it. |
13311 | // Otherwise spill it to a temp, and reload the temp |
13312 | // twice. |
13313 | StackEntry se = impPopStack(); |
13314 | GenTree* tree = se.val; |
13315 | tiRetVal = se.seTypeInfo; |
13316 | op1 = tree; |
13317 | |
13318 | if (!opts.compDbgCode && !op1->IsIntegralConst(0) && !op1->IsFPZero() && !op1->IsLocal()) |
13319 | { |
13320 | const unsigned tmpNum = lvaGrabTemp(true DEBUGARG("dup spill" )); |
13321 | impAssignTempGen(tmpNum, op1, tiRetVal.GetClassHandle(), (unsigned)CHECK_SPILL_ALL); |
13322 | var_types type = genActualType(lvaTable[tmpNum].TypeGet()); |
13323 | op1 = gtNewLclvNode(tmpNum, type); |
13324 | |
13325 | // Propagate type info to the temp from the stack and the original tree |
13326 | if (type == TYP_REF) |
13327 | { |
13328 | assert(lvaTable[tmpNum].lvSingleDef == 0); |
13329 | lvaTable[tmpNum].lvSingleDef = 1; |
13330 | JITDUMP("Marked V%02u as a single def local\n" , tmpNum); |
13331 | lvaSetClass(tmpNum, tree, tiRetVal.GetClassHandle()); |
13332 | } |
13333 | } |
13334 | |
13335 | op1 = impCloneExpr(op1, &op2, tiRetVal.GetClassHandle(), (unsigned)CHECK_SPILL_ALL, |
13336 | nullptr DEBUGARG("DUP instruction" )); |
13337 | |
13338 | assert(!(op1->gtFlags & GTF_GLOB_EFFECT) && !(op2->gtFlags & GTF_GLOB_EFFECT)); |
13339 | impPushOnStack(op1, tiRetVal); |
13340 | impPushOnStack(op2, tiRetVal); |
13341 | } |
13342 | break; |
13343 | |
13344 | case CEE_STIND_I1: |
13345 | lclTyp = TYP_BYTE; |
13346 | goto STIND; |
13347 | case CEE_STIND_I2: |
13348 | lclTyp = TYP_SHORT; |
13349 | goto STIND; |
13350 | case CEE_STIND_I4: |
13351 | lclTyp = TYP_INT; |
13352 | goto STIND; |
13353 | case CEE_STIND_I8: |
13354 | lclTyp = TYP_LONG; |
13355 | goto STIND; |
13356 | case CEE_STIND_I: |
13357 | lclTyp = TYP_I_IMPL; |
13358 | goto STIND; |
13359 | case CEE_STIND_REF: |
13360 | lclTyp = TYP_REF; |
13361 | goto STIND; |
13362 | case CEE_STIND_R4: |
13363 | lclTyp = TYP_FLOAT; |
13364 | goto STIND; |
13365 | case CEE_STIND_R8: |
13366 | lclTyp = TYP_DOUBLE; |
13367 | goto STIND; |
13368 | STIND: |
13369 | |
13370 | if (tiVerificationNeeded) |
13371 | { |
13372 | typeInfo instrType(lclTyp); |
13373 | #ifdef _TARGET_64BIT_ |
13374 | if (opcode == CEE_STIND_I) |
13375 | { |
13376 | instrType = typeInfo::nativeInt(); |
13377 | } |
13378 | #endif // _TARGET_64BIT_ |
13379 | verVerifySTIND(impStackTop(1).seTypeInfo, impStackTop(0).seTypeInfo, instrType); |
13380 | } |
13381 | else |
13382 | { |
13383 | compUnsafeCastUsed = true; // Have to go conservative |
13384 | } |
13385 | |
13386 | STIND_POST_VERIFY: |
13387 | |
13388 | op2 = impPopStack().val; // value to store |
13389 | op1 = impPopStack().val; // address to store to |
13390 | |
13391 | // you can indirect off of a TYP_I_IMPL (if we are in C) or a BYREF |
13392 | assertImp(genActualType(op1->gtType) == TYP_I_IMPL || op1->gtType == TYP_BYREF); |
13393 | |
13394 | impBashVarAddrsToI(op1, op2); |
13395 | |
13396 | op2 = impImplicitR4orR8Cast(op2, lclTyp); |
13397 | |
13398 | #ifdef _TARGET_64BIT_ |
13399 | // Automatic upcast for a GT_CNS_INT into TYP_I_IMPL |
13400 | if ((op2->OperGet() == GT_CNS_INT) && varTypeIsI(lclTyp) && !varTypeIsI(op2->gtType)) |
13401 | { |
13402 | op2->gtType = TYP_I_IMPL; |
13403 | } |
13404 | else |
13405 | { |
13406 | // Allow a downcast of op2 from TYP_I_IMPL into a 32-bit Int for x86 JIT compatiblity |
13407 | // |
13408 | if (varTypeIsI(op2->gtType) && (genActualType(lclTyp) == TYP_INT)) |
13409 | { |
13410 | assert(!tiVerificationNeeded); // We should have thrown the VerificationException before. |
13411 | op2 = gtNewCastNode(TYP_INT, op2, false, TYP_INT); |
13412 | } |
13413 | // Allow an upcast of op2 from a 32-bit Int into TYP_I_IMPL for x86 JIT compatiblity |
13414 | // |
13415 | if (varTypeIsI(lclTyp) && (genActualType(op2->gtType) == TYP_INT)) |
13416 | { |
13417 | assert(!tiVerificationNeeded); // We should have thrown the VerificationException before. |
13418 | op2 = gtNewCastNode(TYP_I_IMPL, op2, false, TYP_I_IMPL); |
13419 | } |
13420 | } |
13421 | #endif // _TARGET_64BIT_ |
13422 | |
13423 | if (opcode == CEE_STIND_REF) |
13424 | { |
13425 | // STIND_REF can be used to store TYP_INT, TYP_I_IMPL, TYP_REF, or TYP_BYREF |
13426 | assertImp(varTypeIsIntOrI(op2->gtType) || varTypeIsGC(op2->gtType)); |
13427 | lclTyp = genActualType(op2->TypeGet()); |
13428 | } |
13429 | |
13430 | // Check target type. |
13431 | #ifdef DEBUG |
13432 | if (op2->gtType == TYP_BYREF || lclTyp == TYP_BYREF) |
13433 | { |
13434 | if (op2->gtType == TYP_BYREF) |
13435 | { |
13436 | assertImp(lclTyp == TYP_BYREF || lclTyp == TYP_I_IMPL); |
13437 | } |
13438 | else if (lclTyp == TYP_BYREF) |
13439 | { |
13440 | assertImp(op2->gtType == TYP_BYREF || varTypeIsIntOrI(op2->gtType)); |
13441 | } |
13442 | } |
13443 | else |
13444 | { |
13445 | assertImp(genActualType(op2->gtType) == genActualType(lclTyp) || |
13446 | ((lclTyp == TYP_I_IMPL) && (genActualType(op2->gtType) == TYP_INT)) || |
13447 | (varTypeIsFloating(op2->gtType) && varTypeIsFloating(lclTyp))); |
13448 | } |
13449 | #endif |
13450 | |
13451 | op1 = gtNewOperNode(GT_IND, lclTyp, op1); |
13452 | |
13453 | // stind could point anywhere, example a boxed class static int |
13454 | op1->gtFlags |= GTF_IND_TGTANYWHERE; |
13455 | |
13456 | if (prefixFlags & PREFIX_VOLATILE) |
13457 | { |
13458 | assert(op1->OperGet() == GT_IND); |
13459 | op1->gtFlags |= GTF_DONT_CSE; // Can't CSE a volatile |
13460 | op1->gtFlags |= GTF_ORDER_SIDEEFF; // Prevent this from being reordered |
13461 | op1->gtFlags |= GTF_IND_VOLATILE; |
13462 | } |
13463 | |
13464 | if ((prefixFlags & PREFIX_UNALIGNED) && !varTypeIsByte(lclTyp)) |
13465 | { |
13466 | assert(op1->OperGet() == GT_IND); |
13467 | op1->gtFlags |= GTF_IND_UNALIGNED; |
13468 | } |
13469 | |
13470 | op1 = gtNewAssignNode(op1, op2); |
13471 | op1->gtFlags |= GTF_EXCEPT | GTF_GLOB_REF; |
13472 | |
13473 | // Spill side-effects AND global-data-accesses |
13474 | if (verCurrentState.esStackDepth > 0) |
13475 | { |
13476 | impSpillSideEffects(true, (unsigned)CHECK_SPILL_ALL DEBUGARG("spill side effects before STIND" )); |
13477 | } |
13478 | |
13479 | goto APPEND; |
13480 | |
13481 | case CEE_LDIND_I1: |
13482 | lclTyp = TYP_BYTE; |
13483 | goto LDIND; |
13484 | case CEE_LDIND_I2: |
13485 | lclTyp = TYP_SHORT; |
13486 | goto LDIND; |
13487 | case CEE_LDIND_U4: |
13488 | case CEE_LDIND_I4: |
13489 | lclTyp = TYP_INT; |
13490 | goto LDIND; |
13491 | case CEE_LDIND_I8: |
13492 | lclTyp = TYP_LONG; |
13493 | goto LDIND; |
13494 | case CEE_LDIND_REF: |
13495 | lclTyp = TYP_REF; |
13496 | goto LDIND; |
13497 | case CEE_LDIND_I: |
13498 | lclTyp = TYP_I_IMPL; |
13499 | goto LDIND; |
13500 | case CEE_LDIND_R4: |
13501 | lclTyp = TYP_FLOAT; |
13502 | goto LDIND; |
13503 | case CEE_LDIND_R8: |
13504 | lclTyp = TYP_DOUBLE; |
13505 | goto LDIND; |
13506 | case CEE_LDIND_U1: |
13507 | lclTyp = TYP_UBYTE; |
13508 | goto LDIND; |
13509 | case CEE_LDIND_U2: |
13510 | lclTyp = TYP_USHORT; |
13511 | goto LDIND; |
13512 | LDIND: |
13513 | |
13514 | if (tiVerificationNeeded) |
13515 | { |
13516 | typeInfo lclTiType(lclTyp); |
13517 | #ifdef _TARGET_64BIT_ |
13518 | if (opcode == CEE_LDIND_I) |
13519 | { |
13520 | lclTiType = typeInfo::nativeInt(); |
13521 | } |
13522 | #endif // _TARGET_64BIT_ |
13523 | tiRetVal = verVerifyLDIND(impStackTop().seTypeInfo, lclTiType); |
13524 | tiRetVal.NormaliseForStack(); |
13525 | } |
13526 | else |
13527 | { |
13528 | compUnsafeCastUsed = true; // Have to go conservative |
13529 | } |
13530 | |
13531 | LDIND_POST_VERIFY: |
13532 | |
13533 | op1 = impPopStack().val; // address to load from |
13534 | impBashVarAddrsToI(op1); |
13535 | |
13536 | #ifdef _TARGET_64BIT_ |
13537 | // Allow an upcast of op1 from a 32-bit Int into TYP_I_IMPL for x86 JIT compatiblity |
13538 | // |
13539 | if (genActualType(op1->gtType) == TYP_INT) |
13540 | { |
13541 | assert(!tiVerificationNeeded); // We should have thrown the VerificationException before. |
13542 | op1 = gtNewCastNode(TYP_I_IMPL, op1, false, TYP_I_IMPL); |
13543 | } |
13544 | #endif |
13545 | |
13546 | assertImp(genActualType(op1->gtType) == TYP_I_IMPL || op1->gtType == TYP_BYREF); |
13547 | |
13548 | op1 = gtNewOperNode(GT_IND, lclTyp, op1); |
13549 | |
13550 | // ldind could point anywhere, example a boxed class static int |
13551 | op1->gtFlags |= (GTF_EXCEPT | GTF_GLOB_REF | GTF_IND_TGTANYWHERE); |
13552 | |
13553 | if (prefixFlags & PREFIX_VOLATILE) |
13554 | { |
13555 | assert(op1->OperGet() == GT_IND); |
13556 | op1->gtFlags |= GTF_DONT_CSE; // Can't CSE a volatile |
13557 | op1->gtFlags |= GTF_ORDER_SIDEEFF; // Prevent this from being reordered |
13558 | op1->gtFlags |= GTF_IND_VOLATILE; |
13559 | } |
13560 | |
13561 | if ((prefixFlags & PREFIX_UNALIGNED) && !varTypeIsByte(lclTyp)) |
13562 | { |
13563 | assert(op1->OperGet() == GT_IND); |
13564 | op1->gtFlags |= GTF_IND_UNALIGNED; |
13565 | } |
13566 | |
13567 | impPushOnStack(op1, tiRetVal); |
13568 | |
13569 | break; |
13570 | |
13571 | case CEE_UNALIGNED: |
13572 | |
13573 | assert(sz == 1); |
13574 | val = getU1LittleEndian(codeAddr); |
13575 | ++codeAddr; |
13576 | JITDUMP(" %u" , val); |
13577 | if ((val != 1) && (val != 2) && (val != 4)) |
13578 | { |
13579 | BADCODE("Alignment unaligned. must be 1, 2, or 4" ); |
13580 | } |
13581 | |
13582 | Verify(!(prefixFlags & PREFIX_UNALIGNED), "Multiple unaligned. prefixes" ); |
13583 | prefixFlags |= PREFIX_UNALIGNED; |
13584 | |
13585 | impValidateMemoryAccessOpcode(codeAddr, codeEndp, false); |
13586 | |
13587 | PREFIX: |
13588 | opcode = (OPCODE)getU1LittleEndian(codeAddr); |
13589 | opcodeOffs = (IL_OFFSET)(codeAddr - info.compCode); |
13590 | codeAddr += sizeof(__int8); |
13591 | goto DECODE_OPCODE; |
13592 | |
13593 | case CEE_VOLATILE: |
13594 | |
13595 | Verify(!(prefixFlags & PREFIX_VOLATILE), "Multiple volatile. prefixes" ); |
13596 | prefixFlags |= PREFIX_VOLATILE; |
13597 | |
13598 | impValidateMemoryAccessOpcode(codeAddr, codeEndp, true); |
13599 | |
13600 | assert(sz == 0); |
13601 | goto PREFIX; |
13602 | |
13603 | case CEE_LDFTN: |
13604 | { |
13605 | // Need to do a lookup here so that we perform an access check |
13606 | // and do a NOWAY if protections are violated |
13607 | _impResolveToken(CORINFO_TOKENKIND_Method); |
13608 | |
13609 | JITDUMP(" %08X" , resolvedToken.token); |
13610 | |
13611 | eeGetCallInfo(&resolvedToken, nullptr /* constraint typeRef*/, |
13612 | addVerifyFlag(combine(CORINFO_CALLINFO_SECURITYCHECKS, CORINFO_CALLINFO_LDFTN)), |
13613 | &callInfo); |
13614 | |
13615 | // This check really only applies to intrinsic Array.Address methods |
13616 | if (callInfo.sig.callConv & CORINFO_CALLCONV_PARAMTYPE) |
13617 | { |
13618 | NO_WAY("Currently do not support LDFTN of Parameterized functions" ); |
13619 | } |
13620 | |
13621 | // Do this before DO_LDFTN since CEE_LDVIRTFN does it on its own. |
13622 | impHandleAccessAllowed(callInfo.accessAllowed, &callInfo.callsiteCalloutHelper); |
13623 | |
13624 | if (tiVerificationNeeded) |
13625 | { |
13626 | // LDFTN could start the begining of delegate creation sequence, remember that |
13627 | delegateCreateStart = codeAddr - 2; |
13628 | |
13629 | // check any constraints on the callee's class and type parameters |
13630 | VerifyOrReturn(info.compCompHnd->satisfiesClassConstraints(resolvedToken.hClass), |
13631 | "method has unsatisfied class constraints" ); |
13632 | VerifyOrReturn(info.compCompHnd->satisfiesMethodConstraints(resolvedToken.hClass, |
13633 | resolvedToken.hMethod), |
13634 | "method has unsatisfied method constraints" ); |
13635 | |
13636 | mflags = callInfo.verMethodFlags; |
13637 | Verify(!(mflags & CORINFO_FLG_CONSTRUCTOR), "LDFTN on a constructor" ); |
13638 | } |
13639 | |
13640 | DO_LDFTN: |
13641 | op1 = impMethodPointer(&resolvedToken, &callInfo); |
13642 | |
13643 | if (compDonotInline()) |
13644 | { |
13645 | return; |
13646 | } |
13647 | |
13648 | // Call info may have more precise information about the function than |
13649 | // the resolved token. |
13650 | CORINFO_RESOLVED_TOKEN* heapToken = impAllocateToken(resolvedToken); |
13651 | assert(callInfo.hMethod != nullptr); |
13652 | heapToken->hMethod = callInfo.hMethod; |
13653 | impPushOnStack(op1, typeInfo(heapToken)); |
13654 | |
13655 | break; |
13656 | } |
13657 | |
13658 | case CEE_LDVIRTFTN: |
13659 | { |
13660 | /* Get the method token */ |
13661 | |
13662 | _impResolveToken(CORINFO_TOKENKIND_Method); |
13663 | |
13664 | JITDUMP(" %08X" , resolvedToken.token); |
13665 | |
13666 | eeGetCallInfo(&resolvedToken, nullptr /* constraint typeRef */, |
13667 | addVerifyFlag(combine(combine(CORINFO_CALLINFO_SECURITYCHECKS, CORINFO_CALLINFO_LDFTN), |
13668 | CORINFO_CALLINFO_CALLVIRT)), |
13669 | &callInfo); |
13670 | |
13671 | // This check really only applies to intrinsic Array.Address methods |
13672 | if (callInfo.sig.callConv & CORINFO_CALLCONV_PARAMTYPE) |
13673 | { |
13674 | NO_WAY("Currently do not support LDFTN of Parameterized functions" ); |
13675 | } |
13676 | |
13677 | mflags = callInfo.methodFlags; |
13678 | |
13679 | impHandleAccessAllowed(callInfo.accessAllowed, &callInfo.callsiteCalloutHelper); |
13680 | |
13681 | if (compIsForInlining()) |
13682 | { |
13683 | if (mflags & (CORINFO_FLG_FINAL | CORINFO_FLG_STATIC) || !(mflags & CORINFO_FLG_VIRTUAL)) |
13684 | { |
13685 | compInlineResult->NoteFatal(InlineObservation::CALLSITE_LDVIRTFN_ON_NON_VIRTUAL); |
13686 | return; |
13687 | } |
13688 | } |
13689 | |
13690 | CORINFO_SIG_INFO& ftnSig = callInfo.sig; |
13691 | |
13692 | if (tiVerificationNeeded) |
13693 | { |
13694 | |
13695 | Verify(ftnSig.hasThis(), "ldvirtftn on a static method" ); |
13696 | Verify(!(mflags & CORINFO_FLG_CONSTRUCTOR), "LDVIRTFTN on a constructor" ); |
13697 | |
13698 | // JIT32 verifier rejects verifiable ldvirtftn pattern |
13699 | typeInfo declType = |
13700 | verMakeTypeInfo(resolvedToken.hClass, true); // Change TI_STRUCT to TI_REF when necessary |
13701 | |
13702 | typeInfo arg = impStackTop().seTypeInfo; |
13703 | Verify((arg.IsType(TI_REF) || arg.IsType(TI_NULL)) && tiCompatibleWith(arg, declType, true), |
13704 | "bad ldvirtftn" ); |
13705 | |
13706 | CORINFO_CLASS_HANDLE instanceClassHnd = info.compClassHnd; |
13707 | if (!(arg.IsType(TI_NULL) || (mflags & CORINFO_FLG_STATIC))) |
13708 | { |
13709 | instanceClassHnd = arg.GetClassHandleForObjRef(); |
13710 | } |
13711 | |
13712 | // check any constraints on the method's class and type parameters |
13713 | VerifyOrReturn(info.compCompHnd->satisfiesClassConstraints(resolvedToken.hClass), |
13714 | "method has unsatisfied class constraints" ); |
13715 | VerifyOrReturn(info.compCompHnd->satisfiesMethodConstraints(resolvedToken.hClass, |
13716 | resolvedToken.hMethod), |
13717 | "method has unsatisfied method constraints" ); |
13718 | |
13719 | if (mflags & CORINFO_FLG_PROTECTED) |
13720 | { |
13721 | Verify(info.compCompHnd->canAccessFamily(info.compMethodHnd, instanceClassHnd), |
13722 | "Accessing protected method through wrong type." ); |
13723 | } |
13724 | } |
13725 | |
13726 | /* Get the object-ref */ |
13727 | op1 = impPopStack().val; |
13728 | assertImp(op1->gtType == TYP_REF); |
13729 | |
13730 | if (opts.IsReadyToRun()) |
13731 | { |
13732 | if (callInfo.kind != CORINFO_VIRTUALCALL_LDVIRTFTN) |
13733 | { |
13734 | if (op1->gtFlags & GTF_SIDE_EFFECT) |
13735 | { |
13736 | op1 = gtUnusedValNode(op1); |
13737 | impAppendTree(op1, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs); |
13738 | } |
13739 | goto DO_LDFTN; |
13740 | } |
13741 | } |
13742 | else if (mflags & (CORINFO_FLG_FINAL | CORINFO_FLG_STATIC) || !(mflags & CORINFO_FLG_VIRTUAL)) |
13743 | { |
13744 | if (op1->gtFlags & GTF_SIDE_EFFECT) |
13745 | { |
13746 | op1 = gtUnusedValNode(op1); |
13747 | impAppendTree(op1, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs); |
13748 | } |
13749 | goto DO_LDFTN; |
13750 | } |
13751 | |
13752 | GenTree* fptr = impImportLdvirtftn(op1, &resolvedToken, &callInfo); |
13753 | if (compDonotInline()) |
13754 | { |
13755 | return; |
13756 | } |
13757 | |
13758 | CORINFO_RESOLVED_TOKEN* heapToken = impAllocateToken(resolvedToken); |
13759 | |
13760 | assert(heapToken->tokenType == CORINFO_TOKENKIND_Method); |
13761 | assert(callInfo.hMethod != nullptr); |
13762 | |
13763 | heapToken->tokenType = CORINFO_TOKENKIND_Ldvirtftn; |
13764 | heapToken->hMethod = callInfo.hMethod; |
13765 | impPushOnStack(fptr, typeInfo(heapToken)); |
13766 | |
13767 | break; |
13768 | } |
13769 | |
13770 | case CEE_CONSTRAINED: |
13771 | |
13772 | assertImp(sz == sizeof(unsigned)); |
13773 | impResolveToken(codeAddr, &constrainedResolvedToken, CORINFO_TOKENKIND_Constrained); |
13774 | codeAddr += sizeof(unsigned); // prefix instructions must increment codeAddr manually |
13775 | JITDUMP(" (%08X) " , constrainedResolvedToken.token); |
13776 | |
13777 | Verify(!(prefixFlags & PREFIX_CONSTRAINED), "Multiple constrained. prefixes" ); |
13778 | prefixFlags |= PREFIX_CONSTRAINED; |
13779 | |
13780 | { |
13781 | OPCODE actualOpcode = impGetNonPrefixOpcode(codeAddr, codeEndp); |
13782 | if (actualOpcode != CEE_CALLVIRT) |
13783 | { |
13784 | BADCODE("constrained. has to be followed by callvirt" ); |
13785 | } |
13786 | } |
13787 | |
13788 | goto PREFIX; |
13789 | |
13790 | case CEE_READONLY: |
13791 | JITDUMP(" readonly." ); |
13792 | |
13793 | Verify(!(prefixFlags & PREFIX_READONLY), "Multiple readonly. prefixes" ); |
13794 | prefixFlags |= PREFIX_READONLY; |
13795 | |
13796 | { |
13797 | OPCODE actualOpcode = impGetNonPrefixOpcode(codeAddr, codeEndp); |
13798 | if (actualOpcode != CEE_LDELEMA && !impOpcodeIsCallOpcode(actualOpcode)) |
13799 | { |
13800 | BADCODE("readonly. has to be followed by ldelema or call" ); |
13801 | } |
13802 | } |
13803 | |
13804 | assert(sz == 0); |
13805 | goto PREFIX; |
13806 | |
13807 | case CEE_TAILCALL: |
13808 | JITDUMP(" tail." ); |
13809 | |
13810 | Verify(!(prefixFlags & PREFIX_TAILCALL_EXPLICIT), "Multiple tailcall. prefixes" ); |
13811 | prefixFlags |= PREFIX_TAILCALL_EXPLICIT; |
13812 | |
13813 | { |
13814 | OPCODE actualOpcode = impGetNonPrefixOpcode(codeAddr, codeEndp); |
13815 | if (!impOpcodeIsCallOpcode(actualOpcode)) |
13816 | { |
13817 | BADCODE("tailcall. has to be followed by call, callvirt or calli" ); |
13818 | } |
13819 | } |
13820 | assert(sz == 0); |
13821 | goto PREFIX; |
13822 | |
13823 | case CEE_NEWOBJ: |
13824 | |
13825 | /* Since we will implicitly insert newObjThisPtr at the start of the |
13826 | argument list, spill any GTF_ORDER_SIDEEFF */ |
13827 | impSpillSpecialSideEff(); |
13828 | |
13829 | /* NEWOBJ does not respond to TAIL */ |
13830 | prefixFlags &= ~PREFIX_TAILCALL_EXPLICIT; |
13831 | |
13832 | /* NEWOBJ does not respond to CONSTRAINED */ |
13833 | prefixFlags &= ~PREFIX_CONSTRAINED; |
13834 | |
13835 | _impResolveToken(CORINFO_TOKENKIND_NewObj); |
13836 | |
13837 | eeGetCallInfo(&resolvedToken, nullptr /* constraint typeRef*/, |
13838 | addVerifyFlag(combine(CORINFO_CALLINFO_SECURITYCHECKS, CORINFO_CALLINFO_ALLOWINSTPARAM)), |
13839 | &callInfo); |
13840 | |
13841 | if (compIsForInlining()) |
13842 | { |
13843 | if (impInlineInfo->inlineCandidateInfo->dwRestrictions & INLINE_RESPECT_BOUNDARY) |
13844 | { |
13845 | // Check to see if this call violates the boundary. |
13846 | compInlineResult->NoteFatal(InlineObservation::CALLSITE_CROSS_BOUNDARY_SECURITY); |
13847 | return; |
13848 | } |
13849 | } |
13850 | |
13851 | mflags = callInfo.methodFlags; |
13852 | |
13853 | if ((mflags & (CORINFO_FLG_STATIC | CORINFO_FLG_ABSTRACT)) != 0) |
13854 | { |
13855 | BADCODE("newobj on static or abstract method" ); |
13856 | } |
13857 | |
13858 | // Insert the security callout before any actual code is generated |
13859 | impHandleAccessAllowed(callInfo.accessAllowed, &callInfo.callsiteCalloutHelper); |
13860 | |
13861 | // There are three different cases for new |
13862 | // Object size is variable (depends on arguments) |
13863 | // 1) Object is an array (arrays treated specially by the EE) |
13864 | // 2) Object is some other variable sized object (e.g. String) |
13865 | // 3) Class Size can be determined beforehand (normal case) |
13866 | // In the first case, we need to call a NEWOBJ helper (multinewarray) |
13867 | // in the second case we call the constructor with a '0' this pointer |
13868 | // In the third case we alloc the memory, then call the constuctor |
13869 | |
13870 | clsFlags = callInfo.classFlags; |
13871 | if (clsFlags & CORINFO_FLG_ARRAY) |
13872 | { |
13873 | if (tiVerificationNeeded) |
13874 | { |
13875 | CORINFO_CLASS_HANDLE elemTypeHnd; |
13876 | INDEBUG(CorInfoType corType =) |
13877 | info.compCompHnd->getChildType(resolvedToken.hClass, &elemTypeHnd); |
13878 | assert(!(elemTypeHnd == nullptr && corType == CORINFO_TYPE_VALUECLASS)); |
13879 | Verify(elemTypeHnd == nullptr || |
13880 | !(info.compCompHnd->getClassAttribs(elemTypeHnd) & CORINFO_FLG_CONTAINS_STACK_PTR), |
13881 | "newarr of byref-like objects" ); |
13882 | verVerifyCall(opcode, &resolvedToken, nullptr, ((prefixFlags & PREFIX_TAILCALL_EXPLICIT) != 0), |
13883 | ((prefixFlags & PREFIX_READONLY) != 0), delegateCreateStart, codeAddr - 1, |
13884 | &callInfo DEBUGARG(info.compFullName)); |
13885 | } |
13886 | // Arrays need to call the NEWOBJ helper. |
13887 | assertImp(clsFlags & CORINFO_FLG_VAROBJSIZE); |
13888 | |
13889 | impImportNewObjArray(&resolvedToken, &callInfo); |
13890 | if (compDonotInline()) |
13891 | { |
13892 | return; |
13893 | } |
13894 | |
13895 | callTyp = TYP_REF; |
13896 | break; |
13897 | } |
13898 | // At present this can only be String |
13899 | else if (clsFlags & CORINFO_FLG_VAROBJSIZE) |
13900 | { |
13901 | if (IsTargetAbi(CORINFO_CORERT_ABI)) |
13902 | { |
13903 | // The dummy argument does not exist in CoreRT |
13904 | newObjThisPtr = nullptr; |
13905 | } |
13906 | else |
13907 | { |
13908 | // This is the case for variable-sized objects that are not |
13909 | // arrays. In this case, call the constructor with a null 'this' |
13910 | // pointer |
13911 | newObjThisPtr = gtNewIconNode(0, TYP_REF); |
13912 | } |
13913 | |
13914 | /* Remember that this basic block contains 'new' of an object */ |
13915 | block->bbFlags |= BBF_HAS_NEWOBJ; |
13916 | optMethodFlags |= OMF_HAS_NEWOBJ; |
13917 | } |
13918 | else |
13919 | { |
13920 | // This is the normal case where the size of the object is |
13921 | // fixed. Allocate the memory and call the constructor. |
13922 | |
13923 | // Note: We cannot add a peep to avoid use of temp here |
13924 | // becase we don't have enough interference info to detect when |
13925 | // sources and destination interfere, example: s = new S(ref); |
13926 | |
13927 | // TODO: We find the correct place to introduce a general |
13928 | // reverse copy prop for struct return values from newobj or |
13929 | // any function returning structs. |
13930 | |
13931 | /* get a temporary for the new object */ |
13932 | lclNum = lvaGrabTemp(true DEBUGARG("NewObj constructor temp" )); |
13933 | if (compDonotInline()) |
13934 | { |
13935 | // Fail fast if lvaGrabTemp fails with CALLSITE_TOO_MANY_LOCALS. |
13936 | assert(compInlineResult->GetObservation() == InlineObservation::CALLSITE_TOO_MANY_LOCALS); |
13937 | return; |
13938 | } |
13939 | |
13940 | // In the value class case we only need clsHnd for size calcs. |
13941 | // |
13942 | // The lookup of the code pointer will be handled by CALL in this case |
13943 | if (clsFlags & CORINFO_FLG_VALUECLASS) |
13944 | { |
13945 | if (compIsForInlining()) |
13946 | { |
13947 | // If value class has GC fields, inform the inliner. It may choose to |
13948 | // bail out on the inline. |
13949 | DWORD typeFlags = info.compCompHnd->getClassAttribs(resolvedToken.hClass); |
13950 | if ((typeFlags & CORINFO_FLG_CONTAINS_GC_PTR) != 0) |
13951 | { |
13952 | compInlineResult->Note(InlineObservation::CALLEE_HAS_GC_STRUCT); |
13953 | if (compInlineResult->IsFailure()) |
13954 | { |
13955 | return; |
13956 | } |
13957 | |
13958 | // Do further notification in the case where the call site is rare; |
13959 | // some policies do not track the relative hotness of call sites for |
13960 | // "always" inline cases. |
13961 | if (impInlineInfo->iciBlock->isRunRarely()) |
13962 | { |
13963 | compInlineResult->Note(InlineObservation::CALLSITE_RARE_GC_STRUCT); |
13964 | if (compInlineResult->IsFailure()) |
13965 | { |
13966 | return; |
13967 | } |
13968 | } |
13969 | } |
13970 | } |
13971 | |
13972 | CorInfoType jitTyp = info.compCompHnd->asCorInfoType(resolvedToken.hClass); |
13973 | unsigned size = info.compCompHnd->getClassSize(resolvedToken.hClass); |
13974 | |
13975 | if (impIsPrimitive(jitTyp)) |
13976 | { |
13977 | lvaTable[lclNum].lvType = JITtype2varType(jitTyp); |
13978 | } |
13979 | else |
13980 | { |
13981 | // The local variable itself is the allocated space. |
13982 | // Here we need unsafe value cls check, since the address of struct is taken for further use |
13983 | // and potentially exploitable. |
13984 | lvaSetStruct(lclNum, resolvedToken.hClass, true /* unsafe value cls check */); |
13985 | } |
13986 | if (compIsForInlining() || fgStructTempNeedsExplicitZeroInit(lvaTable + lclNum, block)) |
13987 | { |
13988 | // Append a tree to zero-out the temp |
13989 | newObjThisPtr = gtNewLclvNode(lclNum, lvaTable[lclNum].TypeGet()); |
13990 | |
13991 | newObjThisPtr = gtNewBlkOpNode(newObjThisPtr, // Dest |
13992 | gtNewIconNode(0), // Value |
13993 | size, // Size |
13994 | false, // isVolatile |
13995 | false); // not copyBlock |
13996 | impAppendTree(newObjThisPtr, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); |
13997 | } |
13998 | |
13999 | // Obtain the address of the temp |
14000 | newObjThisPtr = |
14001 | gtNewOperNode(GT_ADDR, TYP_BYREF, gtNewLclvNode(lclNum, lvaTable[lclNum].TypeGet())); |
14002 | } |
14003 | else |
14004 | { |
14005 | const BOOL useParent = TRUE; |
14006 | op1 = gtNewAllocObjNode(&resolvedToken, useParent); |
14007 | if (op1 == nullptr) |
14008 | { |
14009 | return; |
14010 | } |
14011 | |
14012 | // Remember that this basic block contains 'new' of an object |
14013 | block->bbFlags |= BBF_HAS_NEWOBJ; |
14014 | optMethodFlags |= OMF_HAS_NEWOBJ; |
14015 | |
14016 | // Append the assignment to the temp/local. Dont need to spill |
14017 | // at all as we are just calling an EE-Jit helper which can only |
14018 | // cause an (async) OutOfMemoryException. |
14019 | |
14020 | // We assign the newly allocated object (by a GT_ALLOCOBJ node) |
14021 | // to a temp. Note that the pattern "temp = allocObj" is required |
14022 | // by ObjectAllocator phase to be able to determine GT_ALLOCOBJ nodes |
14023 | // without exhaustive walk over all expressions. |
14024 | |
14025 | impAssignTempGen(lclNum, op1, (unsigned)CHECK_SPILL_NONE); |
14026 | |
14027 | assert(lvaTable[lclNum].lvSingleDef == 0); |
14028 | lvaTable[lclNum].lvSingleDef = 1; |
14029 | JITDUMP("Marked V%02u as a single def local\n" , lclNum); |
14030 | lvaSetClass(lclNum, resolvedToken.hClass, true /* is Exact */); |
14031 | |
14032 | newObjThisPtr = gtNewLclvNode(lclNum, TYP_REF); |
14033 | } |
14034 | } |
14035 | goto CALL; |
14036 | |
14037 | case CEE_CALLI: |
14038 | |
14039 | /* CALLI does not respond to CONSTRAINED */ |
14040 | prefixFlags &= ~PREFIX_CONSTRAINED; |
14041 | |
14042 | if (compIsForInlining()) |
14043 | { |
14044 | // CALLI doesn't have a method handle, so assume the worst. |
14045 | if (impInlineInfo->inlineCandidateInfo->dwRestrictions & INLINE_RESPECT_BOUNDARY) |
14046 | { |
14047 | compInlineResult->NoteFatal(InlineObservation::CALLSITE_CROSS_BOUNDARY_CALLI); |
14048 | return; |
14049 | } |
14050 | } |
14051 | |
14052 | // fall through |
14053 | |
14054 | case CEE_CALLVIRT: |
14055 | case CEE_CALL: |
14056 | |
14057 | // We can't call getCallInfo on the token from a CALLI, but we need it in |
14058 | // many other places. We unfortunately embed that knowledge here. |
14059 | if (opcode != CEE_CALLI) |
14060 | { |
14061 | _impResolveToken(CORINFO_TOKENKIND_Method); |
14062 | |
14063 | eeGetCallInfo(&resolvedToken, |
14064 | (prefixFlags & PREFIX_CONSTRAINED) ? &constrainedResolvedToken : nullptr, |
14065 | // this is how impImportCall invokes getCallInfo |
14066 | addVerifyFlag( |
14067 | combine(combine(CORINFO_CALLINFO_ALLOWINSTPARAM, CORINFO_CALLINFO_SECURITYCHECKS), |
14068 | (opcode == CEE_CALLVIRT) ? CORINFO_CALLINFO_CALLVIRT |
14069 | : CORINFO_CALLINFO_NONE)), |
14070 | &callInfo); |
14071 | } |
14072 | else |
14073 | { |
14074 | // Suppress uninitialized use warning. |
14075 | memset(&resolvedToken, 0, sizeof(resolvedToken)); |
14076 | memset(&callInfo, 0, sizeof(callInfo)); |
14077 | |
14078 | resolvedToken.token = getU4LittleEndian(codeAddr); |
14079 | resolvedToken.tokenContext = impTokenLookupContextHandle; |
14080 | resolvedToken.tokenScope = info.compScopeHnd; |
14081 | } |
14082 | |
14083 | CALL: // memberRef should be set. |
14084 | // newObjThisPtr should be set for CEE_NEWOBJ |
14085 | |
14086 | JITDUMP(" %08X" , resolvedToken.token); |
14087 | constraintCall = (prefixFlags & PREFIX_CONSTRAINED) != 0; |
14088 | |
14089 | bool newBBcreatedForTailcallStress; |
14090 | |
14091 | newBBcreatedForTailcallStress = false; |
14092 | |
14093 | if (compIsForInlining()) |
14094 | { |
14095 | if (compDonotInline()) |
14096 | { |
14097 | return; |
14098 | } |
14099 | // We rule out inlinees with explicit tail calls in fgMakeBasicBlocks. |
14100 | assert((prefixFlags & PREFIX_TAILCALL_EXPLICIT) == 0); |
14101 | } |
14102 | else |
14103 | { |
14104 | if (compTailCallStress()) |
14105 | { |
14106 | // Have we created a new BB after the "call" instruction in fgMakeBasicBlocks()? |
14107 | // Tail call stress only recognizes call+ret patterns and forces them to be |
14108 | // explicit tail prefixed calls. Also fgMakeBasicBlocks() under tail call stress |
14109 | // doesn't import 'ret' opcode following the call into the basic block containing |
14110 | // the call instead imports it to a new basic block. Note that fgMakeBasicBlocks() |
14111 | // is already checking that there is an opcode following call and hence it is |
14112 | // safe here to read next opcode without bounds check. |
14113 | newBBcreatedForTailcallStress = |
14114 | impOpcodeIsCallOpcode(opcode) && // Current opcode is a CALL, (not a CEE_NEWOBJ). So, don't |
14115 | // make it jump to RET. |
14116 | (OPCODE)getU1LittleEndian(codeAddr + sz) == CEE_RET; // Next opcode is a CEE_RET |
14117 | |
14118 | bool hasTailPrefix = (prefixFlags & PREFIX_TAILCALL_EXPLICIT); |
14119 | if (newBBcreatedForTailcallStress && !hasTailPrefix && // User hasn't set "tail." prefix yet. |
14120 | verCheckTailCallConstraint(opcode, &resolvedToken, |
14121 | constraintCall ? &constrainedResolvedToken : nullptr, |
14122 | true) // Is it legal to do tailcall? |
14123 | ) |
14124 | { |
14125 | CORINFO_METHOD_HANDLE declaredCalleeHnd = callInfo.hMethod; |
14126 | bool isVirtual = (callInfo.kind == CORINFO_VIRTUALCALL_STUB) || |
14127 | (callInfo.kind == CORINFO_VIRTUALCALL_VTABLE); |
14128 | CORINFO_METHOD_HANDLE exactCalleeHnd = isVirtual ? nullptr : declaredCalleeHnd; |
14129 | if (info.compCompHnd->canTailCall(info.compMethodHnd, declaredCalleeHnd, exactCalleeHnd, |
14130 | hasTailPrefix)) // Is it legal to do tailcall? |
14131 | { |
14132 | // Stress the tailcall. |
14133 | JITDUMP(" (Tailcall stress: prefixFlags |= PREFIX_TAILCALL_EXPLICIT)" ); |
14134 | prefixFlags |= PREFIX_TAILCALL_EXPLICIT; |
14135 | } |
14136 | } |
14137 | } |
14138 | } |
14139 | |
14140 | // This is split up to avoid goto flow warnings. |
14141 | bool isRecursive; |
14142 | isRecursive = !compIsForInlining() && (callInfo.hMethod == info.compMethodHnd); |
14143 | |
14144 | // Note that when running under tail call stress, a call will be marked as explicit tail prefixed |
14145 | // hence will not be considered for implicit tail calling. |
14146 | if (impIsImplicitTailCallCandidate(opcode, codeAddr + sz, codeEndp, prefixFlags, isRecursive)) |
14147 | { |
14148 | if (compIsForInlining()) |
14149 | { |
14150 | #if FEATURE_TAILCALL_OPT_SHARED_RETURN |
14151 | // Are we inlining at an implicit tail call site? If so the we can flag |
14152 | // implicit tail call sites in the inline body. These call sites |
14153 | // often end up in non BBJ_RETURN blocks, so only flag them when |
14154 | // we're able to handle shared returns. |
14155 | if (impInlineInfo->iciCall->IsImplicitTailCall()) |
14156 | { |
14157 | JITDUMP(" (Inline Implicit Tail call: prefixFlags |= PREFIX_TAILCALL_IMPLICIT)" ); |
14158 | prefixFlags |= PREFIX_TAILCALL_IMPLICIT; |
14159 | } |
14160 | #endif // FEATURE_TAILCALL_OPT_SHARED_RETURN |
14161 | } |
14162 | else |
14163 | { |
14164 | JITDUMP(" (Implicit Tail call: prefixFlags |= PREFIX_TAILCALL_IMPLICIT)" ); |
14165 | prefixFlags |= PREFIX_TAILCALL_IMPLICIT; |
14166 | } |
14167 | } |
14168 | |
14169 | // Treat this call as tail call for verification only if "tail" prefixed (i.e. explicit tail call). |
14170 | explicitTailCall = (prefixFlags & PREFIX_TAILCALL_EXPLICIT) != 0; |
14171 | readonlyCall = (prefixFlags & PREFIX_READONLY) != 0; |
14172 | |
14173 | if (opcode != CEE_CALLI && opcode != CEE_NEWOBJ) |
14174 | { |
14175 | // All calls and delegates need a security callout. |
14176 | // For delegates, this is the call to the delegate constructor, not the access check on the |
14177 | // LD(virt)FTN. |
14178 | impHandleAccessAllowed(callInfo.accessAllowed, &callInfo.callsiteCalloutHelper); |
14179 | } |
14180 | |
14181 | if (tiVerificationNeeded) |
14182 | { |
14183 | verVerifyCall(opcode, &resolvedToken, constraintCall ? &constrainedResolvedToken : nullptr, |
14184 | explicitTailCall, readonlyCall, delegateCreateStart, codeAddr - 1, |
14185 | &callInfo DEBUGARG(info.compFullName)); |
14186 | } |
14187 | |
14188 | callTyp = impImportCall(opcode, &resolvedToken, constraintCall ? &constrainedResolvedToken : nullptr, |
14189 | newObjThisPtr, prefixFlags, &callInfo, opcodeOffs); |
14190 | if (compDonotInline()) |
14191 | { |
14192 | // We do not check fails after lvaGrabTemp. It is covered with CoreCLR_13272 issue. |
14193 | assert((callTyp == TYP_UNDEF) || |
14194 | (compInlineResult->GetObservation() == InlineObservation::CALLSITE_TOO_MANY_LOCALS)); |
14195 | return; |
14196 | } |
14197 | |
14198 | if (explicitTailCall || newBBcreatedForTailcallStress) // If newBBcreatedForTailcallStress is true, we |
14199 | // have created a new BB after the "call" |
14200 | // instruction in fgMakeBasicBlocks(). So we need to jump to RET regardless. |
14201 | { |
14202 | assert(!compIsForInlining()); |
14203 | goto RET; |
14204 | } |
14205 | |
14206 | break; |
14207 | |
14208 | case CEE_LDFLD: |
14209 | case CEE_LDSFLD: |
14210 | case CEE_LDFLDA: |
14211 | case CEE_LDSFLDA: |
14212 | { |
14213 | |
14214 | BOOL isLoadAddress = (opcode == CEE_LDFLDA || opcode == CEE_LDSFLDA); |
14215 | BOOL isLoadStatic = (opcode == CEE_LDSFLD || opcode == CEE_LDSFLDA); |
14216 | |
14217 | /* Get the CP_Fieldref index */ |
14218 | assertImp(sz == sizeof(unsigned)); |
14219 | |
14220 | _impResolveToken(CORINFO_TOKENKIND_Field); |
14221 | |
14222 | JITDUMP(" %08X" , resolvedToken.token); |
14223 | |
14224 | int aflags = isLoadAddress ? CORINFO_ACCESS_ADDRESS : CORINFO_ACCESS_GET; |
14225 | |
14226 | GenTree* obj = nullptr; |
14227 | typeInfo* tiObj = nullptr; |
14228 | CORINFO_CLASS_HANDLE objType = nullptr; // used for fields |
14229 | |
14230 | if (opcode == CEE_LDFLD || opcode == CEE_LDFLDA) |
14231 | { |
14232 | tiObj = &impStackTop().seTypeInfo; |
14233 | StackEntry se = impPopStack(); |
14234 | objType = se.seTypeInfo.GetClassHandle(); |
14235 | obj = se.val; |
14236 | |
14237 | if (impIsThis(obj)) |
14238 | { |
14239 | aflags |= CORINFO_ACCESS_THIS; |
14240 | |
14241 | // An optimization for Contextful classes: |
14242 | // we unwrap the proxy when we have a 'this reference' |
14243 | |
14244 | if (info.compUnwrapContextful) |
14245 | { |
14246 | aflags |= CORINFO_ACCESS_UNWRAP; |
14247 | } |
14248 | } |
14249 | } |
14250 | |
14251 | eeGetFieldInfo(&resolvedToken, (CORINFO_ACCESS_FLAGS)aflags, &fieldInfo); |
14252 | |
14253 | // Figure out the type of the member. We always call canAccessField, so you always need this |
14254 | // handle |
14255 | CorInfoType ciType = fieldInfo.fieldType; |
14256 | clsHnd = fieldInfo.structType; |
14257 | |
14258 | lclTyp = JITtype2varType(ciType); |
14259 | |
14260 | #ifdef _TARGET_AMD64 |
14261 | noway_assert(varTypeIsIntegralOrI(lclTyp) || varTypeIsFloating(lclTyp) || lclTyp == TYP_STRUCT); |
14262 | #endif // _TARGET_AMD64 |
14263 | |
14264 | if (compIsForInlining()) |
14265 | { |
14266 | switch (fieldInfo.fieldAccessor) |
14267 | { |
14268 | case CORINFO_FIELD_INSTANCE_HELPER: |
14269 | case CORINFO_FIELD_INSTANCE_ADDR_HELPER: |
14270 | case CORINFO_FIELD_STATIC_ADDR_HELPER: |
14271 | case CORINFO_FIELD_STATIC_TLS: |
14272 | |
14273 | compInlineResult->NoteFatal(InlineObservation::CALLEE_LDFLD_NEEDS_HELPER); |
14274 | return; |
14275 | |
14276 | case CORINFO_FIELD_STATIC_GENERICS_STATIC_HELPER: |
14277 | case CORINFO_FIELD_STATIC_READYTORUN_HELPER: |
14278 | /* We may be able to inline the field accessors in specific instantiations of generic |
14279 | * methods */ |
14280 | compInlineResult->NoteFatal(InlineObservation::CALLSITE_LDFLD_NEEDS_HELPER); |
14281 | return; |
14282 | |
14283 | default: |
14284 | break; |
14285 | } |
14286 | |
14287 | if (!isLoadAddress && (fieldInfo.fieldFlags & CORINFO_FLG_FIELD_STATIC) && lclTyp == TYP_STRUCT && |
14288 | clsHnd) |
14289 | { |
14290 | if ((info.compCompHnd->getTypeForPrimitiveValueClass(clsHnd) == CORINFO_TYPE_UNDEF) && |
14291 | !(info.compFlags & CORINFO_FLG_FORCEINLINE)) |
14292 | { |
14293 | // Loading a static valuetype field usually will cause a JitHelper to be called |
14294 | // for the static base. This will bloat the code. |
14295 | compInlineResult->Note(InlineObservation::CALLEE_LDFLD_STATIC_VALUECLASS); |
14296 | |
14297 | if (compInlineResult->IsFailure()) |
14298 | { |
14299 | return; |
14300 | } |
14301 | } |
14302 | } |
14303 | } |
14304 | |
14305 | tiRetVal = verMakeTypeInfo(ciType, clsHnd); |
14306 | if (isLoadAddress) |
14307 | { |
14308 | tiRetVal.MakeByRef(); |
14309 | } |
14310 | else |
14311 | { |
14312 | tiRetVal.NormaliseForStack(); |
14313 | } |
14314 | |
14315 | // Perform this check always to ensure that we get field access exceptions even with |
14316 | // SkipVerification. |
14317 | impHandleAccessAllowed(fieldInfo.accessAllowed, &fieldInfo.accessCalloutHelper); |
14318 | |
14319 | if (tiVerificationNeeded) |
14320 | { |
14321 | // You can also pass the unboxed struct to LDFLD |
14322 | BOOL bAllowPlainValueTypeAsThis = FALSE; |
14323 | if (opcode == CEE_LDFLD && impIsValueType(tiObj)) |
14324 | { |
14325 | bAllowPlainValueTypeAsThis = TRUE; |
14326 | } |
14327 | |
14328 | verVerifyField(&resolvedToken, fieldInfo, tiObj, isLoadAddress, bAllowPlainValueTypeAsThis); |
14329 | |
14330 | // If we're doing this on a heap object or from a 'safe' byref |
14331 | // then the result is a safe byref too |
14332 | if (isLoadAddress) // load address |
14333 | { |
14334 | if (fieldInfo.fieldFlags & |
14335 | CORINFO_FLG_FIELD_STATIC) // statics marked as safe will have permanent home |
14336 | { |
14337 | if (fieldInfo.fieldFlags & CORINFO_FLG_FIELD_SAFESTATIC_BYREF_RETURN) |
14338 | { |
14339 | tiRetVal.SetIsPermanentHomeByRef(); |
14340 | } |
14341 | } |
14342 | else if (tiObj->IsObjRef() || tiObj->IsPermanentHomeByRef()) |
14343 | { |
14344 | // ldflda of byref is safe if done on a gc object or on a |
14345 | // safe byref |
14346 | tiRetVal.SetIsPermanentHomeByRef(); |
14347 | } |
14348 | } |
14349 | } |
14350 | else |
14351 | { |
14352 | // tiVerificationNeeded is false. |
14353 | // Raise InvalidProgramException if static load accesses non-static field |
14354 | if (isLoadStatic && ((fieldInfo.fieldFlags & CORINFO_FLG_FIELD_STATIC) == 0)) |
14355 | { |
14356 | BADCODE("static access on an instance field" ); |
14357 | } |
14358 | } |
14359 | |
14360 | // We are using ldfld/a on a static field. We allow it, but need to get side-effect from obj. |
14361 | if ((fieldInfo.fieldFlags & CORINFO_FLG_FIELD_STATIC) && obj != nullptr) |
14362 | { |
14363 | if (obj->gtFlags & GTF_SIDE_EFFECT) |
14364 | { |
14365 | obj = gtUnusedValNode(obj); |
14366 | impAppendTree(obj, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs); |
14367 | } |
14368 | obj = nullptr; |
14369 | } |
14370 | |
14371 | /* Preserve 'small' int types */ |
14372 | if (!varTypeIsSmall(lclTyp)) |
14373 | { |
14374 | lclTyp = genActualType(lclTyp); |
14375 | } |
14376 | |
14377 | bool usesHelper = false; |
14378 | |
14379 | switch (fieldInfo.fieldAccessor) |
14380 | { |
14381 | case CORINFO_FIELD_INSTANCE: |
14382 | #ifdef FEATURE_READYTORUN_COMPILER |
14383 | case CORINFO_FIELD_INSTANCE_WITH_BASE: |
14384 | #endif |
14385 | { |
14386 | obj = impCheckForNullPointer(obj); |
14387 | |
14388 | // If the object is a struct, what we really want is |
14389 | // for the field to operate on the address of the struct. |
14390 | if (!varTypeGCtype(obj->TypeGet()) && impIsValueType(tiObj)) |
14391 | { |
14392 | assert(opcode == CEE_LDFLD && objType != nullptr); |
14393 | |
14394 | obj = impGetStructAddr(obj, objType, (unsigned)CHECK_SPILL_ALL, true); |
14395 | } |
14396 | |
14397 | /* Create the data member node */ |
14398 | op1 = gtNewFieldRef(lclTyp, resolvedToken.hField, obj, fieldInfo.offset); |
14399 | |
14400 | #ifdef FEATURE_READYTORUN_COMPILER |
14401 | if (fieldInfo.fieldAccessor == CORINFO_FIELD_INSTANCE_WITH_BASE) |
14402 | { |
14403 | op1->gtField.gtFieldLookup = fieldInfo.fieldLookup; |
14404 | } |
14405 | #endif |
14406 | |
14407 | op1->gtFlags |= (obj->gtFlags & GTF_GLOB_EFFECT); |
14408 | |
14409 | if (fgAddrCouldBeNull(obj)) |
14410 | { |
14411 | op1->gtFlags |= GTF_EXCEPT; |
14412 | } |
14413 | |
14414 | // If gtFldObj is a BYREF then our target is a value class and |
14415 | // it could point anywhere, example a boxed class static int |
14416 | if (obj->gtType == TYP_BYREF) |
14417 | { |
14418 | op1->gtFlags |= GTF_IND_TGTANYWHERE; |
14419 | } |
14420 | |
14421 | DWORD typeFlags = info.compCompHnd->getClassAttribs(resolvedToken.hClass); |
14422 | if (StructHasOverlappingFields(typeFlags)) |
14423 | { |
14424 | op1->gtField.gtFldMayOverlap = true; |
14425 | } |
14426 | |
14427 | // wrap it in a address of operator if necessary |
14428 | if (isLoadAddress) |
14429 | { |
14430 | op1 = gtNewOperNode(GT_ADDR, |
14431 | (var_types)(varTypeIsGC(obj->TypeGet()) ? TYP_BYREF : TYP_I_IMPL), op1); |
14432 | } |
14433 | else |
14434 | { |
14435 | if (compIsForInlining() && |
14436 | impInlineIsGuaranteedThisDerefBeforeAnySideEffects(nullptr, obj, |
14437 | impInlineInfo->inlArgInfo)) |
14438 | { |
14439 | impInlineInfo->thisDereferencedFirst = true; |
14440 | } |
14441 | } |
14442 | } |
14443 | break; |
14444 | |
14445 | case CORINFO_FIELD_STATIC_TLS: |
14446 | #ifdef _TARGET_X86_ |
14447 | // Legacy TLS access is implemented as intrinsic on x86 only |
14448 | |
14449 | /* Create the data member node */ |
14450 | op1 = gtNewFieldRef(lclTyp, resolvedToken.hField, NULL, fieldInfo.offset); |
14451 | op1->gtFlags |= GTF_IND_TLS_REF; // fgMorphField will handle the transformation |
14452 | |
14453 | if (isLoadAddress) |
14454 | { |
14455 | op1 = gtNewOperNode(GT_ADDR, (var_types)TYP_I_IMPL, op1); |
14456 | } |
14457 | break; |
14458 | #else |
14459 | fieldInfo.fieldAccessor = CORINFO_FIELD_STATIC_ADDR_HELPER; |
14460 | |
14461 | __fallthrough; |
14462 | #endif |
14463 | |
14464 | case CORINFO_FIELD_STATIC_ADDR_HELPER: |
14465 | case CORINFO_FIELD_INSTANCE_HELPER: |
14466 | case CORINFO_FIELD_INSTANCE_ADDR_HELPER: |
14467 | op1 = gtNewRefCOMfield(obj, &resolvedToken, (CORINFO_ACCESS_FLAGS)aflags, &fieldInfo, lclTyp, |
14468 | clsHnd, nullptr); |
14469 | usesHelper = true; |
14470 | break; |
14471 | |
14472 | case CORINFO_FIELD_STATIC_ADDRESS: |
14473 | // Replace static read-only fields with constant if possible |
14474 | if ((aflags & CORINFO_ACCESS_GET) && (fieldInfo.fieldFlags & CORINFO_FLG_FIELD_FINAL) && |
14475 | !(fieldInfo.fieldFlags & CORINFO_FLG_FIELD_STATIC_IN_HEAP) && |
14476 | (varTypeIsIntegral(lclTyp) || varTypeIsFloating(lclTyp))) |
14477 | { |
14478 | CorInfoInitClassResult initClassResult = |
14479 | info.compCompHnd->initClass(resolvedToken.hField, info.compMethodHnd, |
14480 | impTokenLookupContextHandle); |
14481 | |
14482 | if (initClassResult & CORINFO_INITCLASS_INITIALIZED) |
14483 | { |
14484 | void** pFldAddr = nullptr; |
14485 | void* fldAddr = |
14486 | info.compCompHnd->getFieldAddress(resolvedToken.hField, (void**)&pFldAddr); |
14487 | |
14488 | // We should always be able to access this static's address directly |
14489 | assert(pFldAddr == nullptr); |
14490 | |
14491 | op1 = impImportStaticReadOnlyField(fldAddr, lclTyp); |
14492 | goto FIELD_DONE; |
14493 | } |
14494 | } |
14495 | |
14496 | __fallthrough; |
14497 | |
14498 | case CORINFO_FIELD_STATIC_RVA_ADDRESS: |
14499 | case CORINFO_FIELD_STATIC_SHARED_STATIC_HELPER: |
14500 | case CORINFO_FIELD_STATIC_GENERICS_STATIC_HELPER: |
14501 | case CORINFO_FIELD_STATIC_READYTORUN_HELPER: |
14502 | op1 = impImportStaticFieldAccess(&resolvedToken, (CORINFO_ACCESS_FLAGS)aflags, &fieldInfo, |
14503 | lclTyp); |
14504 | break; |
14505 | |
14506 | case CORINFO_FIELD_INTRINSIC_ZERO: |
14507 | { |
14508 | assert(aflags & CORINFO_ACCESS_GET); |
14509 | op1 = gtNewIconNode(0, lclTyp); |
14510 | goto FIELD_DONE; |
14511 | } |
14512 | break; |
14513 | |
14514 | case CORINFO_FIELD_INTRINSIC_EMPTY_STRING: |
14515 | { |
14516 | assert(aflags & CORINFO_ACCESS_GET); |
14517 | |
14518 | LPVOID pValue; |
14519 | InfoAccessType iat = info.compCompHnd->emptyStringLiteral(&pValue); |
14520 | op1 = gtNewStringLiteralNode(iat, pValue); |
14521 | goto FIELD_DONE; |
14522 | } |
14523 | break; |
14524 | |
14525 | case CORINFO_FIELD_INTRINSIC_ISLITTLEENDIAN: |
14526 | { |
14527 | assert(aflags & CORINFO_ACCESS_GET); |
14528 | #if BIGENDIAN |
14529 | op1 = gtNewIconNode(0, lclTyp); |
14530 | #else |
14531 | op1 = gtNewIconNode(1, lclTyp); |
14532 | #endif |
14533 | goto FIELD_DONE; |
14534 | } |
14535 | break; |
14536 | |
14537 | default: |
14538 | assert(!"Unexpected fieldAccessor" ); |
14539 | } |
14540 | |
14541 | if (!isLoadAddress) |
14542 | { |
14543 | |
14544 | if (prefixFlags & PREFIX_VOLATILE) |
14545 | { |
14546 | op1->gtFlags |= GTF_DONT_CSE; // Can't CSE a volatile |
14547 | op1->gtFlags |= GTF_ORDER_SIDEEFF; // Prevent this from being reordered |
14548 | |
14549 | if (!usesHelper) |
14550 | { |
14551 | assert((op1->OperGet() == GT_FIELD) || (op1->OperGet() == GT_IND) || |
14552 | (op1->OperGet() == GT_OBJ)); |
14553 | op1->gtFlags |= GTF_IND_VOLATILE; |
14554 | } |
14555 | } |
14556 | |
14557 | if ((prefixFlags & PREFIX_UNALIGNED) && !varTypeIsByte(lclTyp)) |
14558 | { |
14559 | if (!usesHelper) |
14560 | { |
14561 | assert((op1->OperGet() == GT_FIELD) || (op1->OperGet() == GT_IND) || |
14562 | (op1->OperGet() == GT_OBJ)); |
14563 | op1->gtFlags |= GTF_IND_UNALIGNED; |
14564 | } |
14565 | } |
14566 | } |
14567 | |
14568 | /* Check if the class needs explicit initialization */ |
14569 | |
14570 | if (fieldInfo.fieldFlags & CORINFO_FLG_FIELD_INITCLASS) |
14571 | { |
14572 | GenTree* helperNode = impInitClass(&resolvedToken); |
14573 | if (compDonotInline()) |
14574 | { |
14575 | return; |
14576 | } |
14577 | if (helperNode != nullptr) |
14578 | { |
14579 | op1 = gtNewOperNode(GT_COMMA, op1->TypeGet(), helperNode, op1); |
14580 | } |
14581 | } |
14582 | |
14583 | FIELD_DONE: |
14584 | impPushOnStack(op1, tiRetVal); |
14585 | } |
14586 | break; |
14587 | |
14588 | case CEE_STFLD: |
14589 | case CEE_STSFLD: |
14590 | { |
14591 | |
14592 | BOOL isStoreStatic = (opcode == CEE_STSFLD); |
14593 | |
14594 | CORINFO_CLASS_HANDLE fieldClsHnd; // class of the field (if it's a ref type) |
14595 | |
14596 | /* Get the CP_Fieldref index */ |
14597 | |
14598 | assertImp(sz == sizeof(unsigned)); |
14599 | |
14600 | _impResolveToken(CORINFO_TOKENKIND_Field); |
14601 | |
14602 | JITDUMP(" %08X" , resolvedToken.token); |
14603 | |
14604 | int aflags = CORINFO_ACCESS_SET; |
14605 | GenTree* obj = nullptr; |
14606 | typeInfo* tiObj = nullptr; |
14607 | typeInfo tiVal; |
14608 | |
14609 | /* Pull the value from the stack */ |
14610 | StackEntry se = impPopStack(); |
14611 | op2 = se.val; |
14612 | tiVal = se.seTypeInfo; |
14613 | clsHnd = tiVal.GetClassHandle(); |
14614 | |
14615 | if (opcode == CEE_STFLD) |
14616 | { |
14617 | tiObj = &impStackTop().seTypeInfo; |
14618 | obj = impPopStack().val; |
14619 | |
14620 | if (impIsThis(obj)) |
14621 | { |
14622 | aflags |= CORINFO_ACCESS_THIS; |
14623 | |
14624 | // An optimization for Contextful classes: |
14625 | // we unwrap the proxy when we have a 'this reference' |
14626 | |
14627 | if (info.compUnwrapContextful) |
14628 | { |
14629 | aflags |= CORINFO_ACCESS_UNWRAP; |
14630 | } |
14631 | } |
14632 | } |
14633 | |
14634 | eeGetFieldInfo(&resolvedToken, (CORINFO_ACCESS_FLAGS)aflags, &fieldInfo); |
14635 | |
14636 | // Figure out the type of the member. We always call canAccessField, so you always need this |
14637 | // handle |
14638 | CorInfoType ciType = fieldInfo.fieldType; |
14639 | fieldClsHnd = fieldInfo.structType; |
14640 | |
14641 | lclTyp = JITtype2varType(ciType); |
14642 | |
14643 | if (compIsForInlining()) |
14644 | { |
14645 | /* Is this a 'special' (COM) field? or a TLS ref static field?, field stored int GC heap? or |
14646 | * per-inst static? */ |
14647 | |
14648 | switch (fieldInfo.fieldAccessor) |
14649 | { |
14650 | case CORINFO_FIELD_INSTANCE_HELPER: |
14651 | case CORINFO_FIELD_INSTANCE_ADDR_HELPER: |
14652 | case CORINFO_FIELD_STATIC_ADDR_HELPER: |
14653 | case CORINFO_FIELD_STATIC_TLS: |
14654 | |
14655 | compInlineResult->NoteFatal(InlineObservation::CALLEE_STFLD_NEEDS_HELPER); |
14656 | return; |
14657 | |
14658 | case CORINFO_FIELD_STATIC_GENERICS_STATIC_HELPER: |
14659 | case CORINFO_FIELD_STATIC_READYTORUN_HELPER: |
14660 | /* We may be able to inline the field accessors in specific instantiations of generic |
14661 | * methods */ |
14662 | compInlineResult->NoteFatal(InlineObservation::CALLSITE_STFLD_NEEDS_HELPER); |
14663 | return; |
14664 | |
14665 | default: |
14666 | break; |
14667 | } |
14668 | } |
14669 | |
14670 | impHandleAccessAllowed(fieldInfo.accessAllowed, &fieldInfo.accessCalloutHelper); |
14671 | |
14672 | if (tiVerificationNeeded) |
14673 | { |
14674 | verVerifyField(&resolvedToken, fieldInfo, tiObj, TRUE); |
14675 | typeInfo fieldType = verMakeTypeInfo(ciType, fieldClsHnd); |
14676 | Verify(tiCompatibleWith(tiVal, fieldType.NormaliseForStack(), true), "type mismatch" ); |
14677 | } |
14678 | else |
14679 | { |
14680 | // tiVerificationNeed is false. |
14681 | // Raise InvalidProgramException if static store accesses non-static field |
14682 | if (isStoreStatic && ((fieldInfo.fieldFlags & CORINFO_FLG_FIELD_STATIC) == 0)) |
14683 | { |
14684 | BADCODE("static access on an instance field" ); |
14685 | } |
14686 | } |
14687 | |
14688 | // We are using stfld on a static field. |
14689 | // We allow it, but need to eval any side-effects for obj |
14690 | if ((fieldInfo.fieldFlags & CORINFO_FLG_FIELD_STATIC) && obj != nullptr) |
14691 | { |
14692 | if (obj->gtFlags & GTF_SIDE_EFFECT) |
14693 | { |
14694 | obj = gtUnusedValNode(obj); |
14695 | impAppendTree(obj, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs); |
14696 | } |
14697 | obj = nullptr; |
14698 | } |
14699 | |
14700 | /* Preserve 'small' int types */ |
14701 | if (!varTypeIsSmall(lclTyp)) |
14702 | { |
14703 | lclTyp = genActualType(lclTyp); |
14704 | } |
14705 | |
14706 | switch (fieldInfo.fieldAccessor) |
14707 | { |
14708 | case CORINFO_FIELD_INSTANCE: |
14709 | #ifdef FEATURE_READYTORUN_COMPILER |
14710 | case CORINFO_FIELD_INSTANCE_WITH_BASE: |
14711 | #endif |
14712 | { |
14713 | obj = impCheckForNullPointer(obj); |
14714 | |
14715 | /* Create the data member node */ |
14716 | op1 = gtNewFieldRef(lclTyp, resolvedToken.hField, obj, fieldInfo.offset); |
14717 | DWORD typeFlags = info.compCompHnd->getClassAttribs(resolvedToken.hClass); |
14718 | if (StructHasOverlappingFields(typeFlags)) |
14719 | { |
14720 | op1->gtField.gtFldMayOverlap = true; |
14721 | } |
14722 | |
14723 | #ifdef FEATURE_READYTORUN_COMPILER |
14724 | if (fieldInfo.fieldAccessor == CORINFO_FIELD_INSTANCE_WITH_BASE) |
14725 | { |
14726 | op1->gtField.gtFieldLookup = fieldInfo.fieldLookup; |
14727 | } |
14728 | #endif |
14729 | |
14730 | op1->gtFlags |= (obj->gtFlags & GTF_GLOB_EFFECT); |
14731 | |
14732 | if (fgAddrCouldBeNull(obj)) |
14733 | { |
14734 | op1->gtFlags |= GTF_EXCEPT; |
14735 | } |
14736 | |
14737 | // If gtFldObj is a BYREF then our target is a value class and |
14738 | // it could point anywhere, example a boxed class static int |
14739 | if (obj->gtType == TYP_BYREF) |
14740 | { |
14741 | op1->gtFlags |= GTF_IND_TGTANYWHERE; |
14742 | } |
14743 | |
14744 | if (compIsForInlining() && |
14745 | impInlineIsGuaranteedThisDerefBeforeAnySideEffects(op2, obj, impInlineInfo->inlArgInfo)) |
14746 | { |
14747 | impInlineInfo->thisDereferencedFirst = true; |
14748 | } |
14749 | } |
14750 | break; |
14751 | |
14752 | case CORINFO_FIELD_STATIC_TLS: |
14753 | #ifdef _TARGET_X86_ |
14754 | // Legacy TLS access is implemented as intrinsic on x86 only |
14755 | |
14756 | /* Create the data member node */ |
14757 | op1 = gtNewFieldRef(lclTyp, resolvedToken.hField, NULL, fieldInfo.offset); |
14758 | op1->gtFlags |= GTF_IND_TLS_REF; // fgMorphField will handle the transformation |
14759 | |
14760 | break; |
14761 | #else |
14762 | fieldInfo.fieldAccessor = CORINFO_FIELD_STATIC_ADDR_HELPER; |
14763 | |
14764 | __fallthrough; |
14765 | #endif |
14766 | |
14767 | case CORINFO_FIELD_STATIC_ADDR_HELPER: |
14768 | case CORINFO_FIELD_INSTANCE_HELPER: |
14769 | case CORINFO_FIELD_INSTANCE_ADDR_HELPER: |
14770 | op1 = gtNewRefCOMfield(obj, &resolvedToken, (CORINFO_ACCESS_FLAGS)aflags, &fieldInfo, lclTyp, |
14771 | clsHnd, op2); |
14772 | goto SPILL_APPEND; |
14773 | |
14774 | case CORINFO_FIELD_STATIC_ADDRESS: |
14775 | case CORINFO_FIELD_STATIC_RVA_ADDRESS: |
14776 | case CORINFO_FIELD_STATIC_SHARED_STATIC_HELPER: |
14777 | case CORINFO_FIELD_STATIC_GENERICS_STATIC_HELPER: |
14778 | case CORINFO_FIELD_STATIC_READYTORUN_HELPER: |
14779 | op1 = impImportStaticFieldAccess(&resolvedToken, (CORINFO_ACCESS_FLAGS)aflags, &fieldInfo, |
14780 | lclTyp); |
14781 | break; |
14782 | |
14783 | default: |
14784 | assert(!"Unexpected fieldAccessor" ); |
14785 | } |
14786 | |
14787 | // Create the member assignment, unless we have a struct. |
14788 | // TODO-1stClassStructs: This could be limited to TYP_STRUCT, to avoid extra copies. |
14789 | bool deferStructAssign = varTypeIsStruct(lclTyp); |
14790 | |
14791 | if (!deferStructAssign) |
14792 | { |
14793 | if (prefixFlags & PREFIX_VOLATILE) |
14794 | { |
14795 | assert((op1->OperGet() == GT_FIELD) || (op1->OperGet() == GT_IND)); |
14796 | op1->gtFlags |= GTF_DONT_CSE; // Can't CSE a volatile |
14797 | op1->gtFlags |= GTF_ORDER_SIDEEFF; // Prevent this from being reordered |
14798 | op1->gtFlags |= GTF_IND_VOLATILE; |
14799 | } |
14800 | if ((prefixFlags & PREFIX_UNALIGNED) && !varTypeIsByte(lclTyp)) |
14801 | { |
14802 | assert((op1->OperGet() == GT_FIELD) || (op1->OperGet() == GT_IND)); |
14803 | op1->gtFlags |= GTF_IND_UNALIGNED; |
14804 | } |
14805 | |
14806 | /* V4.0 allows assignment of i4 constant values to i8 type vars when IL verifier is bypassed (full |
14807 | trust apps). The reason this works is that JIT stores an i4 constant in Gentree union during |
14808 | importation and reads from the union as if it were a long during code generation. Though this |
14809 | can potentially read garbage, one can get lucky to have this working correctly. |
14810 | |
14811 | This code pattern is generated by Dev10 MC++ compiler while storing to fields when compiled with |
14812 | /O2 switch (default when compiling retail configs in Dev10) and a customer app has taken a |
14813 | dependency on it. To be backward compatible, we will explicitly add an upward cast here so that |
14814 | it works correctly always. |
14815 | |
14816 | Note that this is limited to x86 alone as there is no back compat to be addressed for Arm JIT |
14817 | for V4.0. |
14818 | */ |
14819 | CLANG_FORMAT_COMMENT_ANCHOR; |
14820 | |
14821 | #ifndef _TARGET_64BIT_ |
14822 | // In UWP6.0 and beyond (post-.NET Core 2.0), we decided to let this cast from int to long be |
14823 | // generated for ARM as well as x86, so the following IR will be accepted: |
14824 | // * STMT void |
14825 | // | /--* CNS_INT int 2 |
14826 | // \--* ASG long |
14827 | // \--* CLS_VAR long |
14828 | |
14829 | if ((op1->TypeGet() != op2->TypeGet()) && op2->OperIsConst() && varTypeIsIntOrI(op2->TypeGet()) && |
14830 | varTypeIsLong(op1->TypeGet())) |
14831 | { |
14832 | op2 = gtNewCastNode(op1->TypeGet(), op2, false, op1->TypeGet()); |
14833 | } |
14834 | #endif |
14835 | |
14836 | #ifdef _TARGET_64BIT_ |
14837 | // Automatic upcast for a GT_CNS_INT into TYP_I_IMPL |
14838 | if ((op2->OperGet() == GT_CNS_INT) && varTypeIsI(lclTyp) && !varTypeIsI(op2->gtType)) |
14839 | { |
14840 | op2->gtType = TYP_I_IMPL; |
14841 | } |
14842 | else |
14843 | { |
14844 | // Allow a downcast of op2 from TYP_I_IMPL into a 32-bit Int for x86 JIT compatiblity |
14845 | // |
14846 | if (varTypeIsI(op2->gtType) && (genActualType(lclTyp) == TYP_INT)) |
14847 | { |
14848 | op2 = gtNewCastNode(TYP_INT, op2, false, TYP_INT); |
14849 | } |
14850 | // Allow an upcast of op2 from a 32-bit Int into TYP_I_IMPL for x86 JIT compatiblity |
14851 | // |
14852 | if (varTypeIsI(lclTyp) && (genActualType(op2->gtType) == TYP_INT)) |
14853 | { |
14854 | op2 = gtNewCastNode(TYP_I_IMPL, op2, false, TYP_I_IMPL); |
14855 | } |
14856 | } |
14857 | #endif |
14858 | |
14859 | // We can generate an assignment to a TYP_FLOAT from a TYP_DOUBLE |
14860 | // We insert a cast to the dest 'op1' type |
14861 | // |
14862 | if ((op1->TypeGet() != op2->TypeGet()) && varTypeIsFloating(op1->gtType) && |
14863 | varTypeIsFloating(op2->gtType)) |
14864 | { |
14865 | op2 = gtNewCastNode(op1->TypeGet(), op2, false, op1->TypeGet()); |
14866 | } |
14867 | |
14868 | op1 = gtNewAssignNode(op1, op2); |
14869 | |
14870 | /* Mark the expression as containing an assignment */ |
14871 | |
14872 | op1->gtFlags |= GTF_ASG; |
14873 | } |
14874 | |
14875 | /* Check if the class needs explicit initialization */ |
14876 | |
14877 | if (fieldInfo.fieldFlags & CORINFO_FLG_FIELD_INITCLASS) |
14878 | { |
14879 | GenTree* helperNode = impInitClass(&resolvedToken); |
14880 | if (compDonotInline()) |
14881 | { |
14882 | return; |
14883 | } |
14884 | if (helperNode != nullptr) |
14885 | { |
14886 | op1 = gtNewOperNode(GT_COMMA, op1->TypeGet(), helperNode, op1); |
14887 | } |
14888 | } |
14889 | |
14890 | /* stfld can interfere with value classes (consider the sequence |
14891 | ldloc, ldloca, ..., stfld, stloc). We will be conservative and |
14892 | spill all value class references from the stack. */ |
14893 | |
14894 | if (obj && ((obj->gtType == TYP_BYREF) || (obj->gtType == TYP_I_IMPL))) |
14895 | { |
14896 | assert(tiObj); |
14897 | |
14898 | if (impIsValueType(tiObj)) |
14899 | { |
14900 | impSpillEvalStack(); |
14901 | } |
14902 | else |
14903 | { |
14904 | impSpillValueClasses(); |
14905 | } |
14906 | } |
14907 | |
14908 | /* Spill any refs to the same member from the stack */ |
14909 | |
14910 | impSpillLclRefs((ssize_t)resolvedToken.hField); |
14911 | |
14912 | /* stsfld also interferes with indirect accesses (for aliased |
14913 | statics) and calls. But don't need to spill other statics |
14914 | as we have explicitly spilled this particular static field. */ |
14915 | |
14916 | impSpillSideEffects(false, (unsigned)CHECK_SPILL_ALL DEBUGARG("spill side effects before STFLD" )); |
14917 | |
14918 | if (deferStructAssign) |
14919 | { |
14920 | op1 = impAssignStruct(op1, op2, clsHnd, (unsigned)CHECK_SPILL_ALL); |
14921 | } |
14922 | } |
14923 | goto APPEND; |
14924 | |
14925 | case CEE_NEWARR: |
14926 | { |
14927 | |
14928 | /* Get the class type index operand */ |
14929 | |
14930 | _impResolveToken(CORINFO_TOKENKIND_Newarr); |
14931 | |
14932 | JITDUMP(" %08X" , resolvedToken.token); |
14933 | |
14934 | if (!opts.IsReadyToRun()) |
14935 | { |
14936 | // Need to restore array classes before creating array objects on the heap |
14937 | op1 = impTokenToHandle(&resolvedToken, nullptr, TRUE /*mustRestoreHandle*/); |
14938 | if (op1 == nullptr) |
14939 | { // compDonotInline() |
14940 | return; |
14941 | } |
14942 | } |
14943 | |
14944 | if (tiVerificationNeeded) |
14945 | { |
14946 | // As per ECMA 'numElems' specified can be either int32 or native int. |
14947 | Verify(impStackTop().seTypeInfo.IsIntOrNativeIntType(), "bad bound" ); |
14948 | |
14949 | CORINFO_CLASS_HANDLE elemTypeHnd; |
14950 | info.compCompHnd->getChildType(resolvedToken.hClass, &elemTypeHnd); |
14951 | Verify(elemTypeHnd == nullptr || |
14952 | !(info.compCompHnd->getClassAttribs(elemTypeHnd) & CORINFO_FLG_CONTAINS_STACK_PTR), |
14953 | "array of byref-like type" ); |
14954 | } |
14955 | |
14956 | tiRetVal = verMakeTypeInfo(resolvedToken.hClass); |
14957 | |
14958 | accessAllowedResult = |
14959 | info.compCompHnd->canAccessClass(&resolvedToken, info.compMethodHnd, &calloutHelper); |
14960 | impHandleAccessAllowed(accessAllowedResult, &calloutHelper); |
14961 | |
14962 | /* Form the arglist: array class handle, size */ |
14963 | op2 = impPopStack().val; |
14964 | assertImp(genActualTypeIsIntOrI(op2->gtType)); |
14965 | |
14966 | #ifdef _TARGET_64BIT_ |
14967 | // The array helper takes a native int for array length. |
14968 | // So if we have an int, explicitly extend it to be a native int. |
14969 | if (genActualType(op2->TypeGet()) != TYP_I_IMPL) |
14970 | { |
14971 | if (op2->IsIntegralConst()) |
14972 | { |
14973 | op2->gtType = TYP_I_IMPL; |
14974 | } |
14975 | else |
14976 | { |
14977 | bool isUnsigned = false; |
14978 | op2 = gtNewCastNode(TYP_I_IMPL, op2, isUnsigned, TYP_I_IMPL); |
14979 | } |
14980 | } |
14981 | #endif // _TARGET_64BIT_ |
14982 | |
14983 | #ifdef FEATURE_READYTORUN_COMPILER |
14984 | if (opts.IsReadyToRun()) |
14985 | { |
14986 | op1 = impReadyToRunHelperToTree(&resolvedToken, CORINFO_HELP_READYTORUN_NEWARR_1, TYP_REF, |
14987 | gtNewArgList(op2)); |
14988 | usingReadyToRunHelper = (op1 != nullptr); |
14989 | |
14990 | if (!usingReadyToRunHelper) |
14991 | { |
14992 | // TODO: ReadyToRun: When generic dictionary lookups are necessary, replace the lookup call |
14993 | // and the newarr call with a single call to a dynamic R2R cell that will: |
14994 | // 1) Load the context |
14995 | // 2) Perform the generic dictionary lookup and caching, and generate the appropriate stub |
14996 | // 3) Allocate the new array |
14997 | // Reason: performance (today, we'll always use the slow helper for the R2R generics case) |
14998 | |
14999 | // Need to restore array classes before creating array objects on the heap |
15000 | op1 = impTokenToHandle(&resolvedToken, nullptr, TRUE /*mustRestoreHandle*/); |
15001 | if (op1 == nullptr) |
15002 | { // compDonotInline() |
15003 | return; |
15004 | } |
15005 | } |
15006 | } |
15007 | |
15008 | if (!usingReadyToRunHelper) |
15009 | #endif |
15010 | { |
15011 | args = gtNewArgList(op1, op2); |
15012 | |
15013 | /* Create a call to 'new' */ |
15014 | |
15015 | // Note that this only works for shared generic code because the same helper is used for all |
15016 | // reference array types |
15017 | op1 = gtNewHelperCallNode(info.compCompHnd->getNewArrHelper(resolvedToken.hClass), TYP_REF, args); |
15018 | } |
15019 | |
15020 | op1->gtCall.compileTimeHelperArgumentHandle = (CORINFO_GENERIC_HANDLE)resolvedToken.hClass; |
15021 | |
15022 | /* Remember that this basic block contains 'new' of an sd array */ |
15023 | |
15024 | block->bbFlags |= BBF_HAS_NEWARRAY; |
15025 | optMethodFlags |= OMF_HAS_NEWARRAY; |
15026 | |
15027 | /* Push the result of the call on the stack */ |
15028 | |
15029 | impPushOnStack(op1, tiRetVal); |
15030 | |
15031 | callTyp = TYP_REF; |
15032 | } |
15033 | break; |
15034 | |
15035 | case CEE_LOCALLOC: |
15036 | if (tiVerificationNeeded) |
15037 | { |
15038 | Verify(false, "bad opcode" ); |
15039 | } |
15040 | |
15041 | // We don't allow locallocs inside handlers |
15042 | if (block->hasHndIndex()) |
15043 | { |
15044 | BADCODE("Localloc can't be inside handler" ); |
15045 | } |
15046 | |
15047 | // Get the size to allocate |
15048 | |
15049 | op2 = impPopStack().val; |
15050 | assertImp(genActualTypeIsIntOrI(op2->gtType)); |
15051 | |
15052 | if (verCurrentState.esStackDepth != 0) |
15053 | { |
15054 | BADCODE("Localloc can only be used when the stack is empty" ); |
15055 | } |
15056 | |
15057 | // If the localloc is not in a loop and its size is a small constant, |
15058 | // create a new local var of TYP_BLK and return its address. |
15059 | { |
15060 | bool convertedToLocal = false; |
15061 | |
15062 | // Need to aggressively fold here, as even fixed-size locallocs |
15063 | // will have casts in the way. |
15064 | op2 = gtFoldExpr(op2); |
15065 | |
15066 | if (op2->IsIntegralConst()) |
15067 | { |
15068 | const ssize_t allocSize = op2->AsIntCon()->IconValue(); |
15069 | |
15070 | if (allocSize == 0) |
15071 | { |
15072 | // Result is nullptr |
15073 | JITDUMP("Converting stackalloc of 0 bytes to push null unmanaged pointer\n" ); |
15074 | op1 = gtNewIconNode(0, TYP_I_IMPL); |
15075 | convertedToLocal = true; |
15076 | } |
15077 | else if ((allocSize > 0) && ((compCurBB->bbFlags & BBF_BACKWARD_JUMP) == 0)) |
15078 | { |
15079 | // Get the size threshold for local conversion |
15080 | ssize_t maxSize = DEFAULT_MAX_LOCALLOC_TO_LOCAL_SIZE; |
15081 | |
15082 | #ifdef DEBUG |
15083 | // Optionally allow this to be modified |
15084 | maxSize = JitConfig.JitStackAllocToLocalSize(); |
15085 | #endif // DEBUG |
15086 | |
15087 | if (allocSize <= maxSize) |
15088 | { |
15089 | const unsigned stackallocAsLocal = lvaGrabTemp(false DEBUGARG("stackallocLocal" )); |
15090 | JITDUMP("Converting stackalloc of %lld bytes to new local V%02u\n" , allocSize, |
15091 | stackallocAsLocal); |
15092 | lvaTable[stackallocAsLocal].lvType = TYP_BLK; |
15093 | lvaTable[stackallocAsLocal].lvExactSize = (unsigned)allocSize; |
15094 | lvaTable[stackallocAsLocal].lvIsUnsafeBuffer = true; |
15095 | op1 = gtNewLclvNode(stackallocAsLocal, TYP_BLK); |
15096 | op1 = gtNewOperNode(GT_ADDR, TYP_I_IMPL, op1); |
15097 | convertedToLocal = true; |
15098 | |
15099 | if (!this->opts.compDbgEnC) |
15100 | { |
15101 | // Ensure we have stack security for this method. |
15102 | // Reorder layout since the converted localloc is treated as an unsafe buffer. |
15103 | setNeedsGSSecurityCookie(); |
15104 | compGSReorderStackLayout = true; |
15105 | } |
15106 | } |
15107 | } |
15108 | } |
15109 | |
15110 | if (!convertedToLocal) |
15111 | { |
15112 | // Bail out if inlining and the localloc was not converted. |
15113 | // |
15114 | // Note we might consider allowing the inline, if the call |
15115 | // site is not in a loop. |
15116 | if (compIsForInlining()) |
15117 | { |
15118 | InlineObservation obs = op2->IsIntegralConst() |
15119 | ? InlineObservation::CALLEE_LOCALLOC_TOO_LARGE |
15120 | : InlineObservation::CALLSITE_LOCALLOC_SIZE_UNKNOWN; |
15121 | compInlineResult->NoteFatal(obs); |
15122 | return; |
15123 | } |
15124 | |
15125 | op1 = gtNewOperNode(GT_LCLHEAP, TYP_I_IMPL, op2); |
15126 | // May throw a stack overflow exception. Obviously, we don't want locallocs to be CSE'd. |
15127 | op1->gtFlags |= (GTF_EXCEPT | GTF_DONT_CSE); |
15128 | |
15129 | // Ensure we have stack security for this method. |
15130 | setNeedsGSSecurityCookie(); |
15131 | |
15132 | /* The FP register may not be back to the original value at the end |
15133 | of the method, even if the frame size is 0, as localloc may |
15134 | have modified it. So we will HAVE to reset it */ |
15135 | compLocallocUsed = true; |
15136 | } |
15137 | else |
15138 | { |
15139 | compLocallocOptimized = true; |
15140 | } |
15141 | } |
15142 | |
15143 | impPushOnStack(op1, tiRetVal); |
15144 | break; |
15145 | |
15146 | case CEE_ISINST: |
15147 | { |
15148 | /* Get the type token */ |
15149 | assertImp(sz == sizeof(unsigned)); |
15150 | |
15151 | _impResolveToken(CORINFO_TOKENKIND_Casting); |
15152 | |
15153 | JITDUMP(" %08X" , resolvedToken.token); |
15154 | |
15155 | if (!opts.IsReadyToRun()) |
15156 | { |
15157 | op2 = impTokenToHandle(&resolvedToken, nullptr, FALSE); |
15158 | if (op2 == nullptr) |
15159 | { // compDonotInline() |
15160 | return; |
15161 | } |
15162 | } |
15163 | |
15164 | if (tiVerificationNeeded) |
15165 | { |
15166 | Verify(impStackTop().seTypeInfo.IsObjRef(), "obj reference needed" ); |
15167 | // Even if this is a value class, we know it is boxed. |
15168 | tiRetVal = typeInfo(TI_REF, resolvedToken.hClass); |
15169 | } |
15170 | accessAllowedResult = |
15171 | info.compCompHnd->canAccessClass(&resolvedToken, info.compMethodHnd, &calloutHelper); |
15172 | impHandleAccessAllowed(accessAllowedResult, &calloutHelper); |
15173 | |
15174 | op1 = impPopStack().val; |
15175 | |
15176 | GenTree* optTree = impOptimizeCastClassOrIsInst(op1, &resolvedToken, false); |
15177 | |
15178 | if (optTree != nullptr) |
15179 | { |
15180 | impPushOnStack(optTree, tiRetVal); |
15181 | } |
15182 | else |
15183 | { |
15184 | |
15185 | #ifdef FEATURE_READYTORUN_COMPILER |
15186 | if (opts.IsReadyToRun()) |
15187 | { |
15188 | GenTreeCall* opLookup = |
15189 | impReadyToRunHelperToTree(&resolvedToken, CORINFO_HELP_READYTORUN_ISINSTANCEOF, TYP_REF, |
15190 | gtNewArgList(op1)); |
15191 | usingReadyToRunHelper = (opLookup != nullptr); |
15192 | op1 = (usingReadyToRunHelper ? opLookup : op1); |
15193 | |
15194 | if (!usingReadyToRunHelper) |
15195 | { |
15196 | // TODO: ReadyToRun: When generic dictionary lookups are necessary, replace the lookup call |
15197 | // and the isinstanceof_any call with a single call to a dynamic R2R cell that will: |
15198 | // 1) Load the context |
15199 | // 2) Perform the generic dictionary lookup and caching, and generate the appropriate |
15200 | // stub |
15201 | // 3) Perform the 'is instance' check on the input object |
15202 | // Reason: performance (today, we'll always use the slow helper for the R2R generics case) |
15203 | |
15204 | op2 = impTokenToHandle(&resolvedToken, nullptr, FALSE); |
15205 | if (op2 == nullptr) |
15206 | { // compDonotInline() |
15207 | return; |
15208 | } |
15209 | } |
15210 | } |
15211 | |
15212 | if (!usingReadyToRunHelper) |
15213 | #endif |
15214 | { |
15215 | op1 = impCastClassOrIsInstToTree(op1, op2, &resolvedToken, false); |
15216 | } |
15217 | if (compDonotInline()) |
15218 | { |
15219 | return; |
15220 | } |
15221 | |
15222 | impPushOnStack(op1, tiRetVal); |
15223 | } |
15224 | break; |
15225 | } |
15226 | |
15227 | case CEE_REFANYVAL: |
15228 | |
15229 | // get the class handle and make a ICON node out of it |
15230 | |
15231 | _impResolveToken(CORINFO_TOKENKIND_Class); |
15232 | |
15233 | JITDUMP(" %08X" , resolvedToken.token); |
15234 | |
15235 | op2 = impTokenToHandle(&resolvedToken); |
15236 | if (op2 == nullptr) |
15237 | { // compDonotInline() |
15238 | return; |
15239 | } |
15240 | |
15241 | if (tiVerificationNeeded) |
15242 | { |
15243 | Verify(typeInfo::AreEquivalent(impStackTop().seTypeInfo, verMakeTypeInfo(impGetRefAnyClass())), |
15244 | "need refany" ); |
15245 | tiRetVal = verMakeTypeInfo(resolvedToken.hClass).MakeByRef(); |
15246 | } |
15247 | |
15248 | op1 = impPopStack().val; |
15249 | // make certain it is normalized; |
15250 | op1 = impNormStructVal(op1, impGetRefAnyClass(), (unsigned)CHECK_SPILL_ALL); |
15251 | |
15252 | // Call helper GETREFANY(classHandle, op1); |
15253 | args = gtNewArgList(op2, op1); |
15254 | op1 = gtNewHelperCallNode(CORINFO_HELP_GETREFANY, TYP_BYREF, args); |
15255 | |
15256 | impPushOnStack(op1, tiRetVal); |
15257 | break; |
15258 | |
15259 | case CEE_REFANYTYPE: |
15260 | |
15261 | if (tiVerificationNeeded) |
15262 | { |
15263 | Verify(typeInfo::AreEquivalent(impStackTop().seTypeInfo, verMakeTypeInfo(impGetRefAnyClass())), |
15264 | "need refany" ); |
15265 | } |
15266 | |
15267 | op1 = impPopStack().val; |
15268 | |
15269 | // make certain it is normalized; |
15270 | op1 = impNormStructVal(op1, impGetRefAnyClass(), (unsigned)CHECK_SPILL_ALL); |
15271 | |
15272 | if (op1->gtOper == GT_OBJ) |
15273 | { |
15274 | // Get the address of the refany |
15275 | op1 = op1->gtOp.gtOp1; |
15276 | |
15277 | // Fetch the type from the correct slot |
15278 | op1 = gtNewOperNode(GT_ADD, TYP_BYREF, op1, |
15279 | gtNewIconNode(OFFSETOF__CORINFO_TypedReference__type, TYP_I_IMPL)); |
15280 | op1 = gtNewOperNode(GT_IND, TYP_BYREF, op1); |
15281 | } |
15282 | else |
15283 | { |
15284 | assertImp(op1->gtOper == GT_MKREFANY); |
15285 | |
15286 | // The pointer may have side-effects |
15287 | if (op1->gtOp.gtOp1->gtFlags & GTF_SIDE_EFFECT) |
15288 | { |
15289 | impAppendTree(op1->gtOp.gtOp1, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs); |
15290 | #ifdef DEBUG |
15291 | impNoteLastILoffs(); |
15292 | #endif |
15293 | } |
15294 | |
15295 | // We already have the class handle |
15296 | op1 = op1->gtOp.gtOp2; |
15297 | } |
15298 | |
15299 | // convert native TypeHandle to RuntimeTypeHandle |
15300 | { |
15301 | GenTreeArgList* helperArgs = gtNewArgList(op1); |
15302 | |
15303 | op1 = gtNewHelperCallNode(CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPEHANDLE_MAYBENULL, TYP_STRUCT, |
15304 | helperArgs); |
15305 | |
15306 | // The handle struct is returned in register |
15307 | op1->gtCall.gtReturnType = GetRuntimeHandleUnderlyingType(); |
15308 | |
15309 | tiRetVal = typeInfo(TI_STRUCT, impGetTypeHandleClass()); |
15310 | } |
15311 | |
15312 | impPushOnStack(op1, tiRetVal); |
15313 | break; |
15314 | |
15315 | case CEE_LDTOKEN: |
15316 | { |
15317 | /* Get the Class index */ |
15318 | assertImp(sz == sizeof(unsigned)); |
15319 | lastLoadToken = codeAddr; |
15320 | _impResolveToken(CORINFO_TOKENKIND_Ldtoken); |
15321 | |
15322 | tokenType = info.compCompHnd->getTokenTypeAsHandle(&resolvedToken); |
15323 | |
15324 | op1 = impTokenToHandle(&resolvedToken, nullptr, TRUE); |
15325 | if (op1 == nullptr) |
15326 | { // compDonotInline() |
15327 | return; |
15328 | } |
15329 | |
15330 | helper = CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPEHANDLE; |
15331 | assert(resolvedToken.hClass != nullptr); |
15332 | |
15333 | if (resolvedToken.hMethod != nullptr) |
15334 | { |
15335 | helper = CORINFO_HELP_METHODDESC_TO_STUBRUNTIMEMETHOD; |
15336 | } |
15337 | else if (resolvedToken.hField != nullptr) |
15338 | { |
15339 | helper = CORINFO_HELP_FIELDDESC_TO_STUBRUNTIMEFIELD; |
15340 | } |
15341 | |
15342 | GenTreeArgList* helperArgs = gtNewArgList(op1); |
15343 | |
15344 | op1 = gtNewHelperCallNode(helper, TYP_STRUCT, helperArgs); |
15345 | |
15346 | // The handle struct is returned in register |
15347 | op1->gtCall.gtReturnType = GetRuntimeHandleUnderlyingType(); |
15348 | |
15349 | tiRetVal = verMakeTypeInfo(tokenType); |
15350 | impPushOnStack(op1, tiRetVal); |
15351 | } |
15352 | break; |
15353 | |
15354 | case CEE_UNBOX: |
15355 | case CEE_UNBOX_ANY: |
15356 | { |
15357 | /* Get the Class index */ |
15358 | assertImp(sz == sizeof(unsigned)); |
15359 | |
15360 | _impResolveToken(CORINFO_TOKENKIND_Class); |
15361 | |
15362 | JITDUMP(" %08X" , resolvedToken.token); |
15363 | |
15364 | BOOL runtimeLookup; |
15365 | op2 = impTokenToHandle(&resolvedToken, &runtimeLookup); |
15366 | if (op2 == nullptr) |
15367 | { |
15368 | assert(compDonotInline()); |
15369 | return; |
15370 | } |
15371 | |
15372 | // Run this always so we can get access exceptions even with SkipVerification. |
15373 | accessAllowedResult = |
15374 | info.compCompHnd->canAccessClass(&resolvedToken, info.compMethodHnd, &calloutHelper); |
15375 | impHandleAccessAllowed(accessAllowedResult, &calloutHelper); |
15376 | |
15377 | if (opcode == CEE_UNBOX_ANY && !eeIsValueClass(resolvedToken.hClass)) |
15378 | { |
15379 | if (tiVerificationNeeded) |
15380 | { |
15381 | typeInfo tiUnbox = impStackTop().seTypeInfo; |
15382 | Verify(tiUnbox.IsObjRef(), "bad unbox.any arg" ); |
15383 | tiRetVal = verMakeTypeInfo(resolvedToken.hClass); |
15384 | tiRetVal.NormaliseForStack(); |
15385 | } |
15386 | JITDUMP("\n Importing UNBOX.ANY(refClass) as CASTCLASS\n" ); |
15387 | op1 = impPopStack().val; |
15388 | goto CASTCLASS; |
15389 | } |
15390 | |
15391 | /* Pop the object and create the unbox helper call */ |
15392 | /* You might think that for UNBOX_ANY we need to push a different */ |
15393 | /* (non-byref) type, but here we're making the tiRetVal that is used */ |
15394 | /* for the intermediate pointer which we then transfer onto the OBJ */ |
15395 | /* instruction. OBJ then creates the appropriate tiRetVal. */ |
15396 | if (tiVerificationNeeded) |
15397 | { |
15398 | typeInfo tiUnbox = impStackTop().seTypeInfo; |
15399 | Verify(tiUnbox.IsObjRef(), "Bad unbox arg" ); |
15400 | |
15401 | tiRetVal = verMakeTypeInfo(resolvedToken.hClass); |
15402 | Verify(tiRetVal.IsValueClass(), "not value class" ); |
15403 | tiRetVal.MakeByRef(); |
15404 | |
15405 | // We always come from an objref, so this is safe byref |
15406 | tiRetVal.SetIsPermanentHomeByRef(); |
15407 | tiRetVal.SetIsReadonlyByRef(); |
15408 | } |
15409 | |
15410 | op1 = impPopStack().val; |
15411 | assertImp(op1->gtType == TYP_REF); |
15412 | |
15413 | helper = info.compCompHnd->getUnBoxHelper(resolvedToken.hClass); |
15414 | assert(helper == CORINFO_HELP_UNBOX || helper == CORINFO_HELP_UNBOX_NULLABLE); |
15415 | |
15416 | // Check legality and profitability of inline expansion for unboxing. |
15417 | const bool canExpandInline = (helper == CORINFO_HELP_UNBOX); |
15418 | const bool shouldExpandInline = !compCurBB->isRunRarely() && opts.OptimizationEnabled(); |
15419 | |
15420 | if (canExpandInline && shouldExpandInline) |
15421 | { |
15422 | // See if we know anything about the type of op1, the object being unboxed. |
15423 | bool isExact = false; |
15424 | bool isNonNull = false; |
15425 | CORINFO_CLASS_HANDLE clsHnd = gtGetClassHandle(op1, &isExact, &isNonNull); |
15426 | |
15427 | // We can skip the "exact" bit here as we are comparing to a value class. |
15428 | // compareTypesForEquality should bail on comparisions for shared value classes. |
15429 | if (clsHnd != NO_CLASS_HANDLE) |
15430 | { |
15431 | const TypeCompareState compare = |
15432 | info.compCompHnd->compareTypesForEquality(resolvedToken.hClass, clsHnd); |
15433 | |
15434 | if (compare == TypeCompareState::Must) |
15435 | { |
15436 | JITDUMP("\nOptimizing %s (%s) -- type test will succeed\n" , |
15437 | opcode == CEE_UNBOX ? "UNBOX" : "UNBOX.ANY" , eeGetClassName(clsHnd)); |
15438 | |
15439 | // For UNBOX, null check (if necessary), and then leave the box payload byref on the stack. |
15440 | if (opcode == CEE_UNBOX) |
15441 | { |
15442 | GenTree* cloneOperand; |
15443 | op1 = impCloneExpr(op1, &cloneOperand, NO_CLASS_HANDLE, (unsigned)CHECK_SPILL_ALL, |
15444 | nullptr DEBUGARG("optimized unbox clone" )); |
15445 | |
15446 | GenTree* boxPayloadOffset = gtNewIconNode(TARGET_POINTER_SIZE, TYP_I_IMPL); |
15447 | GenTree* boxPayloadAddress = |
15448 | gtNewOperNode(GT_ADD, TYP_BYREF, cloneOperand, boxPayloadOffset); |
15449 | GenTree* nullcheck = gtNewOperNode(GT_NULLCHECK, TYP_I_IMPL, op1); |
15450 | GenTree* result = gtNewOperNode(GT_COMMA, TYP_BYREF, nullcheck, boxPayloadAddress); |
15451 | impPushOnStack(result, tiRetVal); |
15452 | break; |
15453 | } |
15454 | |
15455 | // For UNBOX.ANY load the struct from the box payload byref (the load will nullcheck) |
15456 | assert(opcode == CEE_UNBOX_ANY); |
15457 | GenTree* boxPayloadOffset = gtNewIconNode(TARGET_POINTER_SIZE, TYP_I_IMPL); |
15458 | GenTree* boxPayloadAddress = gtNewOperNode(GT_ADD, TYP_BYREF, op1, boxPayloadOffset); |
15459 | impPushOnStack(boxPayloadAddress, tiRetVal); |
15460 | oper = GT_OBJ; |
15461 | goto OBJ; |
15462 | } |
15463 | else |
15464 | { |
15465 | JITDUMP("\nUnable to optimize %s -- can't resolve type comparison\n" , |
15466 | opcode == CEE_UNBOX ? "UNBOX" : "UNBOX.ANY" ); |
15467 | } |
15468 | } |
15469 | else |
15470 | { |
15471 | JITDUMP("\nUnable to optimize %s -- class for [%06u] not known\n" , |
15472 | opcode == CEE_UNBOX ? "UNBOX" : "UNBOX.ANY" , dspTreeID(op1)); |
15473 | } |
15474 | |
15475 | JITDUMP("\n Importing %s as inline sequence\n" , opcode == CEE_UNBOX ? "UNBOX" : "UNBOX.ANY" ); |
15476 | // we are doing normal unboxing |
15477 | // inline the common case of the unbox helper |
15478 | // UNBOX(exp) morphs into |
15479 | // clone = pop(exp); |
15480 | // ((*clone == typeToken) ? nop : helper(clone, typeToken)); |
15481 | // push(clone + TARGET_POINTER_SIZE) |
15482 | // |
15483 | GenTree* cloneOperand; |
15484 | op1 = impCloneExpr(op1, &cloneOperand, NO_CLASS_HANDLE, (unsigned)CHECK_SPILL_ALL, |
15485 | nullptr DEBUGARG("inline UNBOX clone1" )); |
15486 | op1 = gtNewOperNode(GT_IND, TYP_I_IMPL, op1); |
15487 | |
15488 | GenTree* condBox = gtNewOperNode(GT_EQ, TYP_INT, op1, op2); |
15489 | |
15490 | op1 = impCloneExpr(cloneOperand, &cloneOperand, NO_CLASS_HANDLE, (unsigned)CHECK_SPILL_ALL, |
15491 | nullptr DEBUGARG("inline UNBOX clone2" )); |
15492 | op2 = impTokenToHandle(&resolvedToken); |
15493 | if (op2 == nullptr) |
15494 | { // compDonotInline() |
15495 | return; |
15496 | } |
15497 | args = gtNewArgList(op2, op1); |
15498 | op1 = gtNewHelperCallNode(helper, TYP_VOID, args); |
15499 | |
15500 | op1 = new (this, GT_COLON) GenTreeColon(TYP_VOID, gtNewNothingNode(), op1); |
15501 | op1 = gtNewQmarkNode(TYP_VOID, condBox, op1); |
15502 | |
15503 | // QMARK nodes cannot reside on the evaluation stack. Because there |
15504 | // may be other trees on the evaluation stack that side-effect the |
15505 | // sources of the UNBOX operation we must spill the stack. |
15506 | |
15507 | impAppendTree(op1, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs); |
15508 | |
15509 | // Create the address-expression to reference past the object header |
15510 | // to the beginning of the value-type. Today this means adjusting |
15511 | // past the base of the objects vtable field which is pointer sized. |
15512 | |
15513 | op2 = gtNewIconNode(TARGET_POINTER_SIZE, TYP_I_IMPL); |
15514 | op1 = gtNewOperNode(GT_ADD, TYP_BYREF, cloneOperand, op2); |
15515 | } |
15516 | else |
15517 | { |
15518 | JITDUMP("\n Importing %s as helper call because %s\n" , opcode == CEE_UNBOX ? "UNBOX" : "UNBOX.ANY" , |
15519 | canExpandInline ? "want smaller code or faster jitting" : "inline expansion not legal" ); |
15520 | |
15521 | // Don't optimize, just call the helper and be done with it |
15522 | args = gtNewArgList(op2, op1); |
15523 | op1 = |
15524 | gtNewHelperCallNode(helper, |
15525 | (var_types)((helper == CORINFO_HELP_UNBOX) ? TYP_BYREF : TYP_STRUCT), args); |
15526 | } |
15527 | |
15528 | assert(helper == CORINFO_HELP_UNBOX && op1->gtType == TYP_BYREF || // Unbox helper returns a byref. |
15529 | helper == CORINFO_HELP_UNBOX_NULLABLE && |
15530 | varTypeIsStruct(op1) // UnboxNullable helper returns a struct. |
15531 | ); |
15532 | |
15533 | /* |
15534 | ---------------------------------------------------------------------- |
15535 | | \ helper | | | |
15536 | | \ | | | |
15537 | | \ | CORINFO_HELP_UNBOX | CORINFO_HELP_UNBOX_NULLABLE | |
15538 | | \ | (which returns a BYREF) | (which returns a STRUCT) | | |
15539 | | opcode \ | | | |
15540 | |--------------------------------------------------------------------- |
15541 | | UNBOX | push the BYREF | spill the STRUCT to a local, | |
15542 | | | | push the BYREF to this local | |
15543 | |--------------------------------------------------------------------- |
15544 | | UNBOX_ANY | push a GT_OBJ of | push the STRUCT | |
15545 | | | the BYREF | For Linux when the | |
15546 | | | | struct is returned in two | |
15547 | | | | registers create a temp | |
15548 | | | | which address is passed to | |
15549 | | | | the unbox_nullable helper. | |
15550 | |--------------------------------------------------------------------- |
15551 | */ |
15552 | |
15553 | if (opcode == CEE_UNBOX) |
15554 | { |
15555 | if (helper == CORINFO_HELP_UNBOX_NULLABLE) |
15556 | { |
15557 | // Unbox nullable helper returns a struct type. |
15558 | // We need to spill it to a temp so than can take the address of it. |
15559 | // Here we need unsafe value cls check, since the address of struct is taken to be used |
15560 | // further along and potetially be exploitable. |
15561 | |
15562 | unsigned tmp = lvaGrabTemp(true DEBUGARG("UNBOXing a nullable" )); |
15563 | lvaSetStruct(tmp, resolvedToken.hClass, true /* unsafe value cls check */); |
15564 | |
15565 | op2 = gtNewLclvNode(tmp, TYP_STRUCT); |
15566 | op1 = impAssignStruct(op2, op1, resolvedToken.hClass, (unsigned)CHECK_SPILL_ALL); |
15567 | assert(op1->gtType == TYP_VOID); // We must be assigning the return struct to the temp. |
15568 | |
15569 | op2 = gtNewLclvNode(tmp, TYP_STRUCT); |
15570 | op2 = gtNewOperNode(GT_ADDR, TYP_BYREF, op2); |
15571 | op1 = gtNewOperNode(GT_COMMA, TYP_BYREF, op1, op2); |
15572 | } |
15573 | |
15574 | assert(op1->gtType == TYP_BYREF); |
15575 | assert(!tiVerificationNeeded || tiRetVal.IsByRef()); |
15576 | } |
15577 | else |
15578 | { |
15579 | assert(opcode == CEE_UNBOX_ANY); |
15580 | |
15581 | if (helper == CORINFO_HELP_UNBOX) |
15582 | { |
15583 | // Normal unbox helper returns a TYP_BYREF. |
15584 | impPushOnStack(op1, tiRetVal); |
15585 | oper = GT_OBJ; |
15586 | goto OBJ; |
15587 | } |
15588 | |
15589 | assert(helper == CORINFO_HELP_UNBOX_NULLABLE && "Make sure the helper is nullable!" ); |
15590 | |
15591 | #if FEATURE_MULTIREG_RET |
15592 | |
15593 | if (varTypeIsStruct(op1) && IsMultiRegReturnedType(resolvedToken.hClass)) |
15594 | { |
15595 | // Unbox nullable helper returns a TYP_STRUCT. |
15596 | // For the multi-reg case we need to spill it to a temp so that |
15597 | // we can pass the address to the unbox_nullable jit helper. |
15598 | |
15599 | unsigned tmp = lvaGrabTemp(true DEBUGARG("UNBOXing a register returnable nullable" )); |
15600 | lvaTable[tmp].lvIsMultiRegArg = true; |
15601 | lvaSetStruct(tmp, resolvedToken.hClass, true /* unsafe value cls check */); |
15602 | |
15603 | op2 = gtNewLclvNode(tmp, TYP_STRUCT); |
15604 | op1 = impAssignStruct(op2, op1, resolvedToken.hClass, (unsigned)CHECK_SPILL_ALL); |
15605 | assert(op1->gtType == TYP_VOID); // We must be assigning the return struct to the temp. |
15606 | |
15607 | op2 = gtNewLclvNode(tmp, TYP_STRUCT); |
15608 | op2 = gtNewOperNode(GT_ADDR, TYP_BYREF, op2); |
15609 | op1 = gtNewOperNode(GT_COMMA, TYP_BYREF, op1, op2); |
15610 | |
15611 | // In this case the return value of the unbox helper is TYP_BYREF. |
15612 | // Make sure the right type is placed on the operand type stack. |
15613 | impPushOnStack(op1, tiRetVal); |
15614 | |
15615 | // Load the struct. |
15616 | oper = GT_OBJ; |
15617 | |
15618 | assert(op1->gtType == TYP_BYREF); |
15619 | assert(!tiVerificationNeeded || tiRetVal.IsByRef()); |
15620 | |
15621 | goto OBJ; |
15622 | } |
15623 | else |
15624 | |
15625 | #endif // !FEATURE_MULTIREG_RET |
15626 | |
15627 | { |
15628 | // If non register passable struct we have it materialized in the RetBuf. |
15629 | assert(op1->gtType == TYP_STRUCT); |
15630 | tiRetVal = verMakeTypeInfo(resolvedToken.hClass); |
15631 | assert(tiRetVal.IsValueClass()); |
15632 | } |
15633 | } |
15634 | |
15635 | impPushOnStack(op1, tiRetVal); |
15636 | } |
15637 | break; |
15638 | |
15639 | case CEE_BOX: |
15640 | { |
15641 | /* Get the Class index */ |
15642 | assertImp(sz == sizeof(unsigned)); |
15643 | |
15644 | _impResolveToken(CORINFO_TOKENKIND_Box); |
15645 | |
15646 | JITDUMP(" %08X" , resolvedToken.token); |
15647 | |
15648 | if (tiVerificationNeeded) |
15649 | { |
15650 | typeInfo tiActual = impStackTop().seTypeInfo; |
15651 | typeInfo tiBox = verMakeTypeInfo(resolvedToken.hClass); |
15652 | |
15653 | Verify(verIsBoxable(tiBox), "boxable type expected" ); |
15654 | |
15655 | // check the class constraints of the boxed type in case we are boxing an uninitialized value |
15656 | Verify(info.compCompHnd->satisfiesClassConstraints(resolvedToken.hClass), |
15657 | "boxed type has unsatisfied class constraints" ); |
15658 | |
15659 | Verify(tiCompatibleWith(tiActual, tiBox.NormaliseForStack(), true), "type mismatch" ); |
15660 | |
15661 | // Observation: the following code introduces a boxed value class on the stack, but, |
15662 | // according to the ECMA spec, one would simply expect: tiRetVal = |
15663 | // typeInfo(TI_REF,impGetObjectClass()); |
15664 | |
15665 | // Push the result back on the stack, |
15666 | // even if clsHnd is a value class we want the TI_REF |
15667 | // we call back to the EE to get find out what hte type we should push (for nullable<T> we push T) |
15668 | tiRetVal = typeInfo(TI_REF, info.compCompHnd->getTypeForBox(resolvedToken.hClass)); |
15669 | } |
15670 | |
15671 | accessAllowedResult = |
15672 | info.compCompHnd->canAccessClass(&resolvedToken, info.compMethodHnd, &calloutHelper); |
15673 | impHandleAccessAllowed(accessAllowedResult, &calloutHelper); |
15674 | |
15675 | // Note BOX can be used on things that are not value classes, in which |
15676 | // case we get a NOP. However the verifier's view of the type on the |
15677 | // stack changes (in generic code a 'T' becomes a 'boxed T') |
15678 | if (!eeIsValueClass(resolvedToken.hClass)) |
15679 | { |
15680 | JITDUMP("\n Importing BOX(refClass) as NOP\n" ); |
15681 | verCurrentState.esStack[verCurrentState.esStackDepth - 1].seTypeInfo = tiRetVal; |
15682 | break; |
15683 | } |
15684 | |
15685 | // Look ahead for unbox.any |
15686 | if (codeAddr + (sz + 1 + sizeof(mdToken)) <= codeEndp && codeAddr[sz] == CEE_UNBOX_ANY) |
15687 | { |
15688 | CORINFO_RESOLVED_TOKEN unboxResolvedToken; |
15689 | |
15690 | impResolveToken(codeAddr + (sz + 1), &unboxResolvedToken, CORINFO_TOKENKIND_Class); |
15691 | |
15692 | // See if the resolved tokens describe types that are equal. |
15693 | const TypeCompareState compare = |
15694 | info.compCompHnd->compareTypesForEquality(unboxResolvedToken.hClass, resolvedToken.hClass); |
15695 | |
15696 | // If so, box/unbox.any is a nop. |
15697 | if (compare == TypeCompareState::Must) |
15698 | { |
15699 | JITDUMP("\n Importing BOX; UNBOX.ANY as NOP\n" ); |
15700 | // Skip the next unbox.any instruction |
15701 | sz += sizeof(mdToken) + 1; |
15702 | break; |
15703 | } |
15704 | } |
15705 | |
15706 | impImportAndPushBox(&resolvedToken); |
15707 | if (compDonotInline()) |
15708 | { |
15709 | return; |
15710 | } |
15711 | } |
15712 | break; |
15713 | |
15714 | case CEE_SIZEOF: |
15715 | |
15716 | /* Get the Class index */ |
15717 | assertImp(sz == sizeof(unsigned)); |
15718 | |
15719 | _impResolveToken(CORINFO_TOKENKIND_Class); |
15720 | |
15721 | JITDUMP(" %08X" , resolvedToken.token); |
15722 | |
15723 | if (tiVerificationNeeded) |
15724 | { |
15725 | tiRetVal = typeInfo(TI_INT); |
15726 | } |
15727 | |
15728 | op1 = gtNewIconNode(info.compCompHnd->getClassSize(resolvedToken.hClass)); |
15729 | impPushOnStack(op1, tiRetVal); |
15730 | break; |
15731 | |
15732 | case CEE_CASTCLASS: |
15733 | |
15734 | /* Get the Class index */ |
15735 | |
15736 | assertImp(sz == sizeof(unsigned)); |
15737 | |
15738 | _impResolveToken(CORINFO_TOKENKIND_Casting); |
15739 | |
15740 | JITDUMP(" %08X" , resolvedToken.token); |
15741 | |
15742 | if (!opts.IsReadyToRun()) |
15743 | { |
15744 | op2 = impTokenToHandle(&resolvedToken, nullptr, FALSE); |
15745 | if (op2 == nullptr) |
15746 | { // compDonotInline() |
15747 | return; |
15748 | } |
15749 | } |
15750 | |
15751 | if (tiVerificationNeeded) |
15752 | { |
15753 | Verify(impStackTop().seTypeInfo.IsObjRef(), "object ref expected" ); |
15754 | // box it |
15755 | tiRetVal = typeInfo(TI_REF, resolvedToken.hClass); |
15756 | } |
15757 | |
15758 | accessAllowedResult = |
15759 | info.compCompHnd->canAccessClass(&resolvedToken, info.compMethodHnd, &calloutHelper); |
15760 | impHandleAccessAllowed(accessAllowedResult, &calloutHelper); |
15761 | |
15762 | op1 = impPopStack().val; |
15763 | |
15764 | /* Pop the address and create the 'checked cast' helper call */ |
15765 | |
15766 | // At this point we expect typeRef to contain the token, op1 to contain the value being cast, |
15767 | // and op2 to contain code that creates the type handle corresponding to typeRef |
15768 | CASTCLASS: |
15769 | { |
15770 | GenTree* optTree = impOptimizeCastClassOrIsInst(op1, &resolvedToken, true); |
15771 | |
15772 | if (optTree != nullptr) |
15773 | { |
15774 | impPushOnStack(optTree, tiRetVal); |
15775 | } |
15776 | else |
15777 | { |
15778 | |
15779 | #ifdef FEATURE_READYTORUN_COMPILER |
15780 | if (opts.IsReadyToRun()) |
15781 | { |
15782 | GenTreeCall* opLookup = |
15783 | impReadyToRunHelperToTree(&resolvedToken, CORINFO_HELP_READYTORUN_CHKCAST, TYP_REF, |
15784 | gtNewArgList(op1)); |
15785 | usingReadyToRunHelper = (opLookup != nullptr); |
15786 | op1 = (usingReadyToRunHelper ? opLookup : op1); |
15787 | |
15788 | if (!usingReadyToRunHelper) |
15789 | { |
15790 | // TODO: ReadyToRun: When generic dictionary lookups are necessary, replace the lookup call |
15791 | // and the chkcastany call with a single call to a dynamic R2R cell that will: |
15792 | // 1) Load the context |
15793 | // 2) Perform the generic dictionary lookup and caching, and generate the appropriate |
15794 | // stub |
15795 | // 3) Check the object on the stack for the type-cast |
15796 | // Reason: performance (today, we'll always use the slow helper for the R2R generics case) |
15797 | |
15798 | op2 = impTokenToHandle(&resolvedToken, nullptr, FALSE); |
15799 | if (op2 == nullptr) |
15800 | { // compDonotInline() |
15801 | return; |
15802 | } |
15803 | } |
15804 | } |
15805 | |
15806 | if (!usingReadyToRunHelper) |
15807 | #endif |
15808 | { |
15809 | op1 = impCastClassOrIsInstToTree(op1, op2, &resolvedToken, true); |
15810 | } |
15811 | if (compDonotInline()) |
15812 | { |
15813 | return; |
15814 | } |
15815 | |
15816 | /* Push the result back on the stack */ |
15817 | impPushOnStack(op1, tiRetVal); |
15818 | } |
15819 | } |
15820 | break; |
15821 | |
15822 | case CEE_THROW: |
15823 | |
15824 | if (compIsForInlining()) |
15825 | { |
15826 | // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! |
15827 | // TODO: Will this be too strict, given that we will inline many basic blocks? |
15828 | // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! |
15829 | |
15830 | /* Do we have just the exception on the stack ?*/ |
15831 | |
15832 | if (verCurrentState.esStackDepth != 1) |
15833 | { |
15834 | /* if not, just don't inline the method */ |
15835 | |
15836 | compInlineResult->NoteFatal(InlineObservation::CALLEE_THROW_WITH_INVALID_STACK); |
15837 | return; |
15838 | } |
15839 | } |
15840 | |
15841 | if (tiVerificationNeeded) |
15842 | { |
15843 | tiRetVal = impStackTop().seTypeInfo; |
15844 | Verify(tiRetVal.IsObjRef(), "object ref expected" ); |
15845 | if (verTrackObjCtorInitState && (verCurrentState.thisInitialized != TIS_Init)) |
15846 | { |
15847 | Verify(!tiRetVal.IsThisPtr(), "throw uninitialized this" ); |
15848 | } |
15849 | } |
15850 | |
15851 | block->bbSetRunRarely(); // any block with a throw is rare |
15852 | /* Pop the exception object and create the 'throw' helper call */ |
15853 | |
15854 | op1 = gtNewHelperCallNode(CORINFO_HELP_THROW, TYP_VOID, gtNewArgList(impPopStack().val)); |
15855 | |
15856 | EVAL_APPEND: |
15857 | if (verCurrentState.esStackDepth > 0) |
15858 | { |
15859 | impEvalSideEffects(); |
15860 | } |
15861 | |
15862 | assert(verCurrentState.esStackDepth == 0); |
15863 | |
15864 | goto APPEND; |
15865 | |
15866 | case CEE_RETHROW: |
15867 | |
15868 | assert(!compIsForInlining()); |
15869 | |
15870 | if (info.compXcptnsCount == 0) |
15871 | { |
15872 | BADCODE("rethrow outside catch" ); |
15873 | } |
15874 | |
15875 | if (tiVerificationNeeded) |
15876 | { |
15877 | Verify(block->hasHndIndex(), "rethrow outside catch" ); |
15878 | if (block->hasHndIndex()) |
15879 | { |
15880 | EHblkDsc* HBtab = ehGetDsc(block->getHndIndex()); |
15881 | Verify(!HBtab->HasFinallyOrFaultHandler(), "rethrow in finally or fault" ); |
15882 | if (HBtab->HasFilter()) |
15883 | { |
15884 | // we better be in the handler clause part, not the filter part |
15885 | Verify(jitIsBetween(compCurBB->bbCodeOffs, HBtab->ebdHndBegOffs(), HBtab->ebdHndEndOffs()), |
15886 | "rethrow in filter" ); |
15887 | } |
15888 | } |
15889 | } |
15890 | |
15891 | /* Create the 'rethrow' helper call */ |
15892 | |
15893 | op1 = gtNewHelperCallNode(CORINFO_HELP_RETHROW, TYP_VOID); |
15894 | |
15895 | goto EVAL_APPEND; |
15896 | |
15897 | case CEE_INITOBJ: |
15898 | |
15899 | assertImp(sz == sizeof(unsigned)); |
15900 | |
15901 | _impResolveToken(CORINFO_TOKENKIND_Class); |
15902 | |
15903 | JITDUMP(" %08X" , resolvedToken.token); |
15904 | |
15905 | if (tiVerificationNeeded) |
15906 | { |
15907 | typeInfo tiTo = impStackTop().seTypeInfo; |
15908 | typeInfo tiInstr = verMakeTypeInfo(resolvedToken.hClass); |
15909 | |
15910 | Verify(tiTo.IsByRef(), "byref expected" ); |
15911 | Verify(!tiTo.IsReadonlyByRef(), "write to readonly byref" ); |
15912 | |
15913 | Verify(tiCompatibleWith(tiInstr, tiTo.DereferenceByRef(), false), |
15914 | "type operand incompatible with type of address" ); |
15915 | } |
15916 | |
15917 | size = info.compCompHnd->getClassSize(resolvedToken.hClass); // Size |
15918 | op2 = gtNewIconNode(0); // Value |
15919 | op1 = impPopStack().val; // Dest |
15920 | op1 = gtNewBlockVal(op1, size); |
15921 | op1 = gtNewBlkOpNode(op1, op2, size, (prefixFlags & PREFIX_VOLATILE) != 0, false); |
15922 | goto SPILL_APPEND; |
15923 | |
15924 | case CEE_INITBLK: |
15925 | |
15926 | if (tiVerificationNeeded) |
15927 | { |
15928 | Verify(false, "bad opcode" ); |
15929 | } |
15930 | |
15931 | op3 = impPopStack().val; // Size |
15932 | op2 = impPopStack().val; // Value |
15933 | op1 = impPopStack().val; // Dest |
15934 | |
15935 | if (op3->IsCnsIntOrI()) |
15936 | { |
15937 | size = (unsigned)op3->AsIntConCommon()->IconValue(); |
15938 | op1 = new (this, GT_BLK) GenTreeBlk(GT_BLK, TYP_STRUCT, op1, size); |
15939 | } |
15940 | else |
15941 | { |
15942 | op1 = new (this, GT_DYN_BLK) GenTreeDynBlk(op1, op3); |
15943 | size = 0; |
15944 | } |
15945 | op1 = gtNewBlkOpNode(op1, op2, size, (prefixFlags & PREFIX_VOLATILE) != 0, false); |
15946 | |
15947 | goto SPILL_APPEND; |
15948 | |
15949 | case CEE_CPBLK: |
15950 | |
15951 | if (tiVerificationNeeded) |
15952 | { |
15953 | Verify(false, "bad opcode" ); |
15954 | } |
15955 | op3 = impPopStack().val; // Size |
15956 | op2 = impPopStack().val; // Src |
15957 | op1 = impPopStack().val; // Dest |
15958 | |
15959 | if (op3->IsCnsIntOrI()) |
15960 | { |
15961 | size = (unsigned)op3->AsIntConCommon()->IconValue(); |
15962 | op1 = new (this, GT_BLK) GenTreeBlk(GT_BLK, TYP_STRUCT, op1, size); |
15963 | } |
15964 | else |
15965 | { |
15966 | op1 = new (this, GT_DYN_BLK) GenTreeDynBlk(op1, op3); |
15967 | size = 0; |
15968 | } |
15969 | if (op2->OperGet() == GT_ADDR) |
15970 | { |
15971 | op2 = op2->gtOp.gtOp1; |
15972 | } |
15973 | else |
15974 | { |
15975 | op2 = gtNewOperNode(GT_IND, TYP_STRUCT, op2); |
15976 | } |
15977 | |
15978 | op1 = gtNewBlkOpNode(op1, op2, size, (prefixFlags & PREFIX_VOLATILE) != 0, true); |
15979 | goto SPILL_APPEND; |
15980 | |
15981 | case CEE_CPOBJ: |
15982 | |
15983 | assertImp(sz == sizeof(unsigned)); |
15984 | |
15985 | _impResolveToken(CORINFO_TOKENKIND_Class); |
15986 | |
15987 | JITDUMP(" %08X" , resolvedToken.token); |
15988 | |
15989 | if (tiVerificationNeeded) |
15990 | { |
15991 | typeInfo tiFrom = impStackTop().seTypeInfo; |
15992 | typeInfo tiTo = impStackTop(1).seTypeInfo; |
15993 | typeInfo tiInstr = verMakeTypeInfo(resolvedToken.hClass); |
15994 | |
15995 | Verify(tiFrom.IsByRef(), "expected byref source" ); |
15996 | Verify(tiTo.IsByRef(), "expected byref destination" ); |
15997 | |
15998 | Verify(tiCompatibleWith(tiFrom.DereferenceByRef(), tiInstr, false), |
15999 | "type of source address incompatible with type operand" ); |
16000 | Verify(!tiTo.IsReadonlyByRef(), "write to readonly byref" ); |
16001 | Verify(tiCompatibleWith(tiInstr, tiTo.DereferenceByRef(), false), |
16002 | "type operand incompatible with type of destination address" ); |
16003 | } |
16004 | |
16005 | if (!eeIsValueClass(resolvedToken.hClass)) |
16006 | { |
16007 | op1 = impPopStack().val; // address to load from |
16008 | |
16009 | impBashVarAddrsToI(op1); |
16010 | |
16011 | assertImp(genActualType(op1->gtType) == TYP_I_IMPL || op1->gtType == TYP_BYREF); |
16012 | |
16013 | op1 = gtNewOperNode(GT_IND, TYP_REF, op1); |
16014 | op1->gtFlags |= GTF_EXCEPT | GTF_GLOB_REF; |
16015 | |
16016 | impPushOnStack(op1, typeInfo()); |
16017 | opcode = CEE_STIND_REF; |
16018 | lclTyp = TYP_REF; |
16019 | goto STIND_POST_VERIFY; |
16020 | } |
16021 | |
16022 | op2 = impPopStack().val; // Src |
16023 | op1 = impPopStack().val; // Dest |
16024 | op1 = gtNewCpObjNode(op1, op2, resolvedToken.hClass, ((prefixFlags & PREFIX_VOLATILE) != 0)); |
16025 | goto SPILL_APPEND; |
16026 | |
16027 | case CEE_STOBJ: |
16028 | { |
16029 | assertImp(sz == sizeof(unsigned)); |
16030 | |
16031 | _impResolveToken(CORINFO_TOKENKIND_Class); |
16032 | |
16033 | JITDUMP(" %08X" , resolvedToken.token); |
16034 | |
16035 | if (eeIsValueClass(resolvedToken.hClass)) |
16036 | { |
16037 | lclTyp = TYP_STRUCT; |
16038 | } |
16039 | else |
16040 | { |
16041 | lclTyp = TYP_REF; |
16042 | } |
16043 | |
16044 | if (tiVerificationNeeded) |
16045 | { |
16046 | |
16047 | typeInfo tiPtr = impStackTop(1).seTypeInfo; |
16048 | |
16049 | // Make sure we have a good looking byref |
16050 | Verify(tiPtr.IsByRef(), "pointer not byref" ); |
16051 | Verify(!tiPtr.IsReadonlyByRef(), "write to readonly byref" ); |
16052 | if (!tiPtr.IsByRef() || tiPtr.IsReadonlyByRef()) |
16053 | { |
16054 | compUnsafeCastUsed = true; |
16055 | } |
16056 | |
16057 | typeInfo ptrVal = DereferenceByRef(tiPtr); |
16058 | typeInfo argVal = verMakeTypeInfo(resolvedToken.hClass); |
16059 | |
16060 | if (!tiCompatibleWith(impStackTop(0).seTypeInfo, NormaliseForStack(argVal), true)) |
16061 | { |
16062 | Verify(false, "type of value incompatible with type operand" ); |
16063 | compUnsafeCastUsed = true; |
16064 | } |
16065 | |
16066 | if (!tiCompatibleWith(argVal, ptrVal, false)) |
16067 | { |
16068 | Verify(false, "type operand incompatible with type of address" ); |
16069 | compUnsafeCastUsed = true; |
16070 | } |
16071 | } |
16072 | else |
16073 | { |
16074 | compUnsafeCastUsed = true; |
16075 | } |
16076 | |
16077 | if (lclTyp == TYP_REF) |
16078 | { |
16079 | opcode = CEE_STIND_REF; |
16080 | goto STIND_POST_VERIFY; |
16081 | } |
16082 | |
16083 | CorInfoType jitTyp = info.compCompHnd->asCorInfoType(resolvedToken.hClass); |
16084 | if (impIsPrimitive(jitTyp)) |
16085 | { |
16086 | lclTyp = JITtype2varType(jitTyp); |
16087 | goto STIND_POST_VERIFY; |
16088 | } |
16089 | |
16090 | op2 = impPopStack().val; // Value |
16091 | op1 = impPopStack().val; // Ptr |
16092 | |
16093 | assertImp(varTypeIsStruct(op2)); |
16094 | |
16095 | op1 = impAssignStructPtr(op1, op2, resolvedToken.hClass, (unsigned)CHECK_SPILL_ALL); |
16096 | |
16097 | if (op1->OperIsBlkOp() && (prefixFlags & PREFIX_UNALIGNED)) |
16098 | { |
16099 | op1->gtFlags |= GTF_BLK_UNALIGNED; |
16100 | } |
16101 | goto SPILL_APPEND; |
16102 | } |
16103 | |
16104 | case CEE_MKREFANY: |
16105 | |
16106 | assert(!compIsForInlining()); |
16107 | |
16108 | // Being lazy here. Refanys are tricky in terms of gc tracking. |
16109 | // Since it is uncommon, just don't perform struct promotion in any method that contains mkrefany. |
16110 | |
16111 | JITDUMP("disabling struct promotion because of mkrefany\n" ); |
16112 | fgNoStructPromotion = true; |
16113 | |
16114 | oper = GT_MKREFANY; |
16115 | assertImp(sz == sizeof(unsigned)); |
16116 | |
16117 | _impResolveToken(CORINFO_TOKENKIND_Class); |
16118 | |
16119 | JITDUMP(" %08X" , resolvedToken.token); |
16120 | |
16121 | op2 = impTokenToHandle(&resolvedToken, nullptr, TRUE); |
16122 | if (op2 == nullptr) |
16123 | { // compDonotInline() |
16124 | return; |
16125 | } |
16126 | |
16127 | if (tiVerificationNeeded) |
16128 | { |
16129 | typeInfo tiPtr = impStackTop().seTypeInfo; |
16130 | typeInfo tiInstr = verMakeTypeInfo(resolvedToken.hClass); |
16131 | |
16132 | Verify(!verIsByRefLike(tiInstr), "mkrefany of byref-like class" ); |
16133 | Verify(!tiPtr.IsReadonlyByRef(), "readonly byref used with mkrefany" ); |
16134 | Verify(typeInfo::AreEquivalent(tiPtr.DereferenceByRef(), tiInstr), "type mismatch" ); |
16135 | } |
16136 | |
16137 | accessAllowedResult = |
16138 | info.compCompHnd->canAccessClass(&resolvedToken, info.compMethodHnd, &calloutHelper); |
16139 | impHandleAccessAllowed(accessAllowedResult, &calloutHelper); |
16140 | |
16141 | op1 = impPopStack().val; |
16142 | |
16143 | // @SPECVIOLATION: TYP_INT should not be allowed here by a strict reading of the spec. |
16144 | // But JIT32 allowed it, so we continue to allow it. |
16145 | assertImp(op1->TypeGet() == TYP_BYREF || op1->TypeGet() == TYP_I_IMPL || op1->TypeGet() == TYP_INT); |
16146 | |
16147 | // MKREFANY returns a struct. op2 is the class token. |
16148 | op1 = gtNewOperNode(oper, TYP_STRUCT, op1, op2); |
16149 | |
16150 | impPushOnStack(op1, verMakeTypeInfo(impGetRefAnyClass())); |
16151 | break; |
16152 | |
16153 | case CEE_LDOBJ: |
16154 | { |
16155 | oper = GT_OBJ; |
16156 | assertImp(sz == sizeof(unsigned)); |
16157 | |
16158 | _impResolveToken(CORINFO_TOKENKIND_Class); |
16159 | |
16160 | JITDUMP(" %08X" , resolvedToken.token); |
16161 | |
16162 | OBJ: |
16163 | |
16164 | tiRetVal = verMakeTypeInfo(resolvedToken.hClass); |
16165 | |
16166 | if (tiVerificationNeeded) |
16167 | { |
16168 | typeInfo tiPtr = impStackTop().seTypeInfo; |
16169 | |
16170 | // Make sure we have a byref |
16171 | if (!tiPtr.IsByRef()) |
16172 | { |
16173 | Verify(false, "pointer not byref" ); |
16174 | compUnsafeCastUsed = true; |
16175 | } |
16176 | typeInfo tiPtrVal = DereferenceByRef(tiPtr); |
16177 | |
16178 | if (!tiCompatibleWith(tiPtrVal, tiRetVal, false)) |
16179 | { |
16180 | Verify(false, "type of address incompatible with type operand" ); |
16181 | compUnsafeCastUsed = true; |
16182 | } |
16183 | tiRetVal.NormaliseForStack(); |
16184 | } |
16185 | else |
16186 | { |
16187 | compUnsafeCastUsed = true; |
16188 | } |
16189 | |
16190 | if (eeIsValueClass(resolvedToken.hClass)) |
16191 | { |
16192 | lclTyp = TYP_STRUCT; |
16193 | } |
16194 | else |
16195 | { |
16196 | lclTyp = TYP_REF; |
16197 | opcode = CEE_LDIND_REF; |
16198 | goto LDIND_POST_VERIFY; |
16199 | } |
16200 | |
16201 | op1 = impPopStack().val; |
16202 | |
16203 | assertImp(op1->TypeGet() == TYP_BYREF || op1->TypeGet() == TYP_I_IMPL); |
16204 | |
16205 | CorInfoType jitTyp = info.compCompHnd->asCorInfoType(resolvedToken.hClass); |
16206 | if (impIsPrimitive(jitTyp)) |
16207 | { |
16208 | op1 = gtNewOperNode(GT_IND, JITtype2varType(jitTyp), op1); |
16209 | |
16210 | // Could point anywhere, example a boxed class static int |
16211 | op1->gtFlags |= GTF_IND_TGTANYWHERE | GTF_GLOB_REF; |
16212 | assertImp(varTypeIsArithmetic(op1->gtType)); |
16213 | } |
16214 | else |
16215 | { |
16216 | // OBJ returns a struct |
16217 | // and an inline argument which is the class token of the loaded obj |
16218 | op1 = gtNewObjNode(resolvedToken.hClass, op1); |
16219 | } |
16220 | op1->gtFlags |= GTF_EXCEPT; |
16221 | |
16222 | if (prefixFlags & PREFIX_UNALIGNED) |
16223 | { |
16224 | op1->gtFlags |= GTF_IND_UNALIGNED; |
16225 | } |
16226 | |
16227 | impPushOnStack(op1, tiRetVal); |
16228 | break; |
16229 | } |
16230 | |
16231 | case CEE_LDLEN: |
16232 | if (tiVerificationNeeded) |
16233 | { |
16234 | typeInfo tiArray = impStackTop().seTypeInfo; |
16235 | Verify(verIsSDArray(tiArray), "bad array" ); |
16236 | tiRetVal = typeInfo(TI_INT); |
16237 | } |
16238 | |
16239 | op1 = impPopStack().val; |
16240 | if (opts.OptimizationEnabled()) |
16241 | { |
16242 | /* Use GT_ARR_LENGTH operator so rng check opts see this */ |
16243 | GenTreeArrLen* arrLen = gtNewArrLen(TYP_INT, op1, OFFSETOF__CORINFO_Array__length); |
16244 | |
16245 | /* Mark the block as containing a length expression */ |
16246 | |
16247 | if (op1->gtOper == GT_LCL_VAR) |
16248 | { |
16249 | block->bbFlags |= BBF_HAS_IDX_LEN; |
16250 | } |
16251 | |
16252 | op1 = arrLen; |
16253 | } |
16254 | else |
16255 | { |
16256 | /* Create the expression "*(array_addr + ArrLenOffs)" */ |
16257 | op1 = gtNewOperNode(GT_ADD, TYP_BYREF, op1, |
16258 | gtNewIconNode(OFFSETOF__CORINFO_Array__length, TYP_I_IMPL)); |
16259 | op1 = gtNewIndir(TYP_INT, op1); |
16260 | } |
16261 | |
16262 | /* Push the result back on the stack */ |
16263 | impPushOnStack(op1, tiRetVal); |
16264 | break; |
16265 | |
16266 | case CEE_BREAK: |
16267 | op1 = gtNewHelperCallNode(CORINFO_HELP_USER_BREAKPOINT, TYP_VOID); |
16268 | goto SPILL_APPEND; |
16269 | |
16270 | case CEE_NOP: |
16271 | if (opts.compDbgCode) |
16272 | { |
16273 | op1 = new (this, GT_NO_OP) GenTree(GT_NO_OP, TYP_VOID); |
16274 | goto SPILL_APPEND; |
16275 | } |
16276 | break; |
16277 | |
16278 | /******************************** NYI *******************************/ |
16279 | |
16280 | case 0xCC: |
16281 | OutputDebugStringA("CLR: Invalid x86 breakpoint in IL stream\n" ); |
16282 | |
16283 | case CEE_ILLEGAL: |
16284 | case CEE_MACRO_END: |
16285 | |
16286 | default: |
16287 | BADCODE3("unknown opcode" , ": %02X" , (int)opcode); |
16288 | } |
16289 | |
16290 | codeAddr += sz; |
16291 | prevOpcode = opcode; |
16292 | |
16293 | prefixFlags = 0; |
16294 | } |
16295 | |
16296 | return; |
16297 | #undef _impResolveToken |
16298 | } |
16299 | #ifdef _PREFAST_ |
16300 | #pragma warning(pop) |
16301 | #endif |
16302 | |
16303 | // Push a local/argument treeon the operand stack |
16304 | void Compiler::impPushVar(GenTree* op, typeInfo tiRetVal) |
16305 | { |
16306 | tiRetVal.NormaliseForStack(); |
16307 | |
16308 | if (verTrackObjCtorInitState && (verCurrentState.thisInitialized != TIS_Init) && tiRetVal.IsThisPtr()) |
16309 | { |
16310 | tiRetVal.SetUninitialisedObjRef(); |
16311 | } |
16312 | |
16313 | impPushOnStack(op, tiRetVal); |
16314 | } |
16315 | |
16316 | // Load a local/argument on the operand stack |
16317 | // lclNum is an index into lvaTable *NOT* the arg/lcl index in the IL |
16318 | void Compiler::impLoadVar(unsigned lclNum, IL_OFFSET offset, typeInfo tiRetVal) |
16319 | { |
16320 | var_types lclTyp; |
16321 | |
16322 | if (lvaTable[lclNum].lvNormalizeOnLoad()) |
16323 | { |
16324 | lclTyp = lvaGetRealType(lclNum); |
16325 | } |
16326 | else |
16327 | { |
16328 | lclTyp = lvaGetActualType(lclNum); |
16329 | } |
16330 | |
16331 | impPushVar(gtNewLclvNode(lclNum, lclTyp, offset), tiRetVal); |
16332 | } |
16333 | |
16334 | // Load an argument on the operand stack |
16335 | // Shared by the various CEE_LDARG opcodes |
16336 | // ilArgNum is the argument index as specified in IL. |
16337 | // It will be mapped to the correct lvaTable index |
16338 | void Compiler::impLoadArg(unsigned ilArgNum, IL_OFFSET offset) |
16339 | { |
16340 | Verify(ilArgNum < info.compILargsCount, "bad arg num" ); |
16341 | |
16342 | if (compIsForInlining()) |
16343 | { |
16344 | if (ilArgNum >= info.compArgsCount) |
16345 | { |
16346 | compInlineResult->NoteFatal(InlineObservation::CALLEE_BAD_ARGUMENT_NUMBER); |
16347 | return; |
16348 | } |
16349 | |
16350 | impPushVar(impInlineFetchArg(ilArgNum, impInlineInfo->inlArgInfo, impInlineInfo->lclVarInfo), |
16351 | impInlineInfo->lclVarInfo[ilArgNum].lclVerTypeInfo); |
16352 | } |
16353 | else |
16354 | { |
16355 | if (ilArgNum >= info.compArgsCount) |
16356 | { |
16357 | BADCODE("Bad IL" ); |
16358 | } |
16359 | |
16360 | unsigned lclNum = compMapILargNum(ilArgNum); // account for possible hidden param |
16361 | |
16362 | if (lclNum == info.compThisArg) |
16363 | { |
16364 | lclNum = lvaArg0Var; |
16365 | } |
16366 | |
16367 | impLoadVar(lclNum, offset); |
16368 | } |
16369 | } |
16370 | |
16371 | // Load a local on the operand stack |
16372 | // Shared by the various CEE_LDLOC opcodes |
16373 | // ilLclNum is the local index as specified in IL. |
16374 | // It will be mapped to the correct lvaTable index |
16375 | void Compiler::impLoadLoc(unsigned ilLclNum, IL_OFFSET offset) |
16376 | { |
16377 | if (tiVerificationNeeded) |
16378 | { |
16379 | Verify(ilLclNum < info.compMethodInfo->locals.numArgs, "bad loc num" ); |
16380 | Verify(info.compInitMem, "initLocals not set" ); |
16381 | } |
16382 | |
16383 | if (compIsForInlining()) |
16384 | { |
16385 | if (ilLclNum >= info.compMethodInfo->locals.numArgs) |
16386 | { |
16387 | compInlineResult->NoteFatal(InlineObservation::CALLEE_BAD_LOCAL_NUMBER); |
16388 | return; |
16389 | } |
16390 | |
16391 | // Get the local type |
16392 | var_types lclTyp = impInlineInfo->lclVarInfo[ilLclNum + impInlineInfo->argCnt].lclTypeInfo; |
16393 | |
16394 | typeInfo tiRetVal = impInlineInfo->lclVarInfo[ilLclNum + impInlineInfo->argCnt].lclVerTypeInfo; |
16395 | |
16396 | /* Have we allocated a temp for this local? */ |
16397 | |
16398 | unsigned lclNum = impInlineFetchLocal(ilLclNum DEBUGARG("Inline ldloc first use temp" )); |
16399 | |
16400 | // All vars of inlined methods should be !lvNormalizeOnLoad() |
16401 | |
16402 | assert(!lvaTable[lclNum].lvNormalizeOnLoad()); |
16403 | lclTyp = genActualType(lclTyp); |
16404 | |
16405 | impPushVar(gtNewLclvNode(lclNum, lclTyp), tiRetVal); |
16406 | } |
16407 | else |
16408 | { |
16409 | if (ilLclNum >= info.compMethodInfo->locals.numArgs) |
16410 | { |
16411 | BADCODE("Bad IL" ); |
16412 | } |
16413 | |
16414 | unsigned lclNum = info.compArgsCount + ilLclNum; |
16415 | |
16416 | impLoadVar(lclNum, offset); |
16417 | } |
16418 | } |
16419 | |
16420 | #ifdef _TARGET_ARM_ |
16421 | /************************************************************************************** |
16422 | * |
16423 | * When assigning a vararg call src to a HFA lcl dest, mark that we cannot promote the |
16424 | * dst struct, because struct promotion will turn it into a float/double variable while |
16425 | * the rhs will be an int/long variable. We don't code generate assignment of int into |
16426 | * a float, but there is nothing that might prevent us from doing so. The tree however |
16427 | * would like: (=, (typ_float, typ_int)) or (GT_TRANSFER, (typ_float, typ_int)) |
16428 | * |
16429 | * tmpNum - the lcl dst variable num that is a struct. |
16430 | * src - the src tree assigned to the dest that is a struct/int (when varargs call.) |
16431 | * hClass - the type handle for the struct variable. |
16432 | * |
16433 | * TODO-ARM-CQ: [301608] This is a rare scenario with varargs and struct promotion coming into play, |
16434 | * however, we could do a codegen of transferring from int to float registers |
16435 | * (transfer, not a cast.) |
16436 | * |
16437 | */ |
16438 | void Compiler::impMarkLclDstNotPromotable(unsigned tmpNum, GenTree* src, CORINFO_CLASS_HANDLE hClass) |
16439 | { |
16440 | if (src->gtOper == GT_CALL && src->gtCall.IsVarargs() && IsHfa(hClass)) |
16441 | { |
16442 | int hfaSlots = GetHfaCount(hClass); |
16443 | var_types hfaType = GetHfaType(hClass); |
16444 | |
16445 | // If we have varargs we morph the method's return type to be "int" irrespective of its original |
16446 | // type: struct/float at importer because the ABI calls out return in integer registers. |
16447 | // We don't want struct promotion to replace an expression like this: |
16448 | // lclFld_int = callvar_int() into lclFld_float = callvar_int(); |
16449 | // This means an int is getting assigned to a float without a cast. Prevent the promotion. |
16450 | if ((hfaType == TYP_DOUBLE && hfaSlots == sizeof(double) / REGSIZE_BYTES) || |
16451 | (hfaType == TYP_FLOAT && hfaSlots == sizeof(float) / REGSIZE_BYTES)) |
16452 | { |
16453 | // Make sure this struct type stays as struct so we can receive the call in a struct. |
16454 | lvaTable[tmpNum].lvIsMultiRegRet = true; |
16455 | } |
16456 | } |
16457 | } |
16458 | #endif // _TARGET_ARM_ |
16459 | |
16460 | //------------------------------------------------------------------------ |
16461 | // impAssignSmallStructTypeToVar: ensure calls that return small structs whose |
16462 | // sizes are not supported integral type sizes return values to temps. |
16463 | // |
16464 | // Arguments: |
16465 | // op -- call returning a small struct in a register |
16466 | // hClass -- class handle for struct |
16467 | // |
16468 | // Returns: |
16469 | // Tree with reference to struct local to use as call return value. |
16470 | // |
16471 | // Remarks: |
16472 | // The call will be spilled into a preceding statement. |
16473 | // Currently handles struct returns for 3, 5, 6, and 7 byte structs. |
16474 | |
16475 | GenTree* Compiler::impAssignSmallStructTypeToVar(GenTree* op, CORINFO_CLASS_HANDLE hClass) |
16476 | { |
16477 | unsigned tmpNum = lvaGrabTemp(true DEBUGARG("Return value temp for small struct return." )); |
16478 | impAssignTempGen(tmpNum, op, hClass, (unsigned)CHECK_SPILL_ALL); |
16479 | GenTree* ret = gtNewLclvNode(tmpNum, lvaTable[tmpNum].lvType); |
16480 | |
16481 | // TODO-1stClassStructs: Handle constant propagation and CSE-ing of small struct returns. |
16482 | ret->gtFlags |= GTF_DONT_CSE; |
16483 | |
16484 | return ret; |
16485 | } |
16486 | |
16487 | #if FEATURE_MULTIREG_RET |
16488 | //------------------------------------------------------------------------ |
16489 | // impAssignMultiRegTypeToVar: ensure calls that return structs in multiple |
16490 | // registers return values to suitable temps. |
16491 | // |
16492 | // Arguments: |
16493 | // op -- call returning a struct in a registers |
16494 | // hClass -- class handle for struct |
16495 | // |
16496 | // Returns: |
16497 | // Tree with reference to struct local to use as call return value. |
16498 | |
16499 | GenTree* Compiler::impAssignMultiRegTypeToVar(GenTree* op, CORINFO_CLASS_HANDLE hClass) |
16500 | { |
16501 | unsigned tmpNum = lvaGrabTemp(true DEBUGARG("Return value temp for multireg return." )); |
16502 | impAssignTempGen(tmpNum, op, hClass, (unsigned)CHECK_SPILL_ALL); |
16503 | GenTree* ret = gtNewLclvNode(tmpNum, lvaTable[tmpNum].lvType); |
16504 | |
16505 | // TODO-1stClassStructs: Handle constant propagation and CSE-ing of multireg returns. |
16506 | ret->gtFlags |= GTF_DONT_CSE; |
16507 | |
16508 | assert(IsMultiRegReturnedType(hClass)); |
16509 | |
16510 | // Mark the var so that fields are not promoted and stay together. |
16511 | lvaTable[tmpNum].lvIsMultiRegRet = true; |
16512 | |
16513 | return ret; |
16514 | } |
16515 | #endif // FEATURE_MULTIREG_RET |
16516 | |
16517 | // do import for a return |
16518 | // returns false if inlining was aborted |
16519 | // opcode can be ret or call in the case of a tail.call |
16520 | bool Compiler::impReturnInstruction(BasicBlock* block, int prefixFlags, OPCODE& opcode) |
16521 | { |
16522 | if (tiVerificationNeeded) |
16523 | { |
16524 | verVerifyThisPtrInitialised(); |
16525 | |
16526 | unsigned expectedStack = 0; |
16527 | if (info.compRetType != TYP_VOID) |
16528 | { |
16529 | typeInfo tiVal = impStackTop().seTypeInfo; |
16530 | typeInfo tiDeclared = |
16531 | verMakeTypeInfo(info.compMethodInfo->args.retType, info.compMethodInfo->args.retTypeClass); |
16532 | |
16533 | Verify(!verIsByRefLike(tiDeclared) || verIsSafeToReturnByRef(tiVal), "byref return" ); |
16534 | |
16535 | Verify(tiCompatibleWith(tiVal, tiDeclared.NormaliseForStack(), true), "type mismatch" ); |
16536 | expectedStack = 1; |
16537 | } |
16538 | Verify(verCurrentState.esStackDepth == expectedStack, "stack non-empty on return" ); |
16539 | } |
16540 | |
16541 | #ifdef DEBUG |
16542 | // If we are importing an inlinee and have GC ref locals we always |
16543 | // need to have a spill temp for the return value. This temp |
16544 | // should have been set up in advance, over in fgFindBasicBlocks. |
16545 | if (compIsForInlining() && impInlineInfo->HasGcRefLocals() && (info.compRetType != TYP_VOID)) |
16546 | { |
16547 | assert(lvaInlineeReturnSpillTemp != BAD_VAR_NUM); |
16548 | } |
16549 | #endif // DEBUG |
16550 | |
16551 | GenTree* op2 = nullptr; |
16552 | GenTree* op1 = nullptr; |
16553 | CORINFO_CLASS_HANDLE retClsHnd = nullptr; |
16554 | |
16555 | if (info.compRetType != TYP_VOID) |
16556 | { |
16557 | StackEntry se = impPopStack(); |
16558 | retClsHnd = se.seTypeInfo.GetClassHandle(); |
16559 | op2 = se.val; |
16560 | |
16561 | if (!compIsForInlining()) |
16562 | { |
16563 | impBashVarAddrsToI(op2); |
16564 | op2 = impImplicitIorI4Cast(op2, info.compRetType); |
16565 | op2 = impImplicitR4orR8Cast(op2, info.compRetType); |
16566 | assertImp((genActualType(op2->TypeGet()) == genActualType(info.compRetType)) || |
16567 | ((op2->TypeGet() == TYP_I_IMPL) && (info.compRetType == TYP_BYREF)) || |
16568 | ((op2->TypeGet() == TYP_BYREF) && (info.compRetType == TYP_I_IMPL)) || |
16569 | (varTypeIsFloating(op2->gtType) && varTypeIsFloating(info.compRetType)) || |
16570 | (varTypeIsStruct(op2) && varTypeIsStruct(info.compRetType))); |
16571 | |
16572 | #ifdef DEBUG |
16573 | if (opts.compGcChecks && info.compRetType == TYP_REF) |
16574 | { |
16575 | // DDB 3483 : JIT Stress: early termination of GC ref's life time in exception code path |
16576 | // VSW 440513: Incorrect gcinfo on the return value under COMPlus_JitGCChecks=1 for methods with |
16577 | // one-return BB. |
16578 | |
16579 | assert(op2->gtType == TYP_REF); |
16580 | |
16581 | // confirm that the argument is a GC pointer (for debugging (GC stress)) |
16582 | GenTreeArgList* args = gtNewArgList(op2); |
16583 | op2 = gtNewHelperCallNode(CORINFO_HELP_CHECK_OBJ, TYP_REF, args); |
16584 | |
16585 | if (verbose) |
16586 | { |
16587 | printf("\ncompGcChecks tree:\n" ); |
16588 | gtDispTree(op2); |
16589 | } |
16590 | } |
16591 | #endif |
16592 | } |
16593 | else |
16594 | { |
16595 | // inlinee's stack should be empty now. |
16596 | assert(verCurrentState.esStackDepth == 0); |
16597 | |
16598 | #ifdef DEBUG |
16599 | if (verbose) |
16600 | { |
16601 | printf("\n\n Inlinee Return expression (before normalization) =>\n" ); |
16602 | gtDispTree(op2); |
16603 | } |
16604 | #endif |
16605 | |
16606 | // Make sure the type matches the original call. |
16607 | |
16608 | var_types returnType = genActualType(op2->gtType); |
16609 | var_types originalCallType = impInlineInfo->inlineCandidateInfo->fncRetType; |
16610 | if ((returnType != originalCallType) && (originalCallType == TYP_STRUCT)) |
16611 | { |
16612 | originalCallType = impNormStructType(impInlineInfo->inlineCandidateInfo->methInfo.args.retTypeClass); |
16613 | } |
16614 | |
16615 | if (returnType != originalCallType) |
16616 | { |
16617 | // Allow TYP_BYREF to be returned as TYP_I_IMPL and vice versa |
16618 | if (((returnType == TYP_BYREF) && (originalCallType == TYP_I_IMPL)) || |
16619 | ((returnType == TYP_I_IMPL) && (originalCallType == TYP_BYREF))) |
16620 | { |
16621 | JITDUMP("Allowing return type mismatch: have %s, needed %s\n" , varTypeName(returnType), |
16622 | varTypeName(originalCallType)); |
16623 | } |
16624 | else |
16625 | { |
16626 | JITDUMP("Return type mismatch: have %s, needed %s\n" , varTypeName(returnType), |
16627 | varTypeName(originalCallType)); |
16628 | compInlineResult->NoteFatal(InlineObservation::CALLSITE_RETURN_TYPE_MISMATCH); |
16629 | return false; |
16630 | } |
16631 | } |
16632 | |
16633 | // Below, we are going to set impInlineInfo->retExpr to the tree with the return |
16634 | // expression. At this point, retExpr could already be set if there are multiple |
16635 | // return blocks (meaning fgNeedReturnSpillTemp() == true) and one of |
16636 | // the other blocks already set it. If there is only a single return block, |
16637 | // retExpr shouldn't be set. However, this is not true if we reimport a block |
16638 | // with a return. In that case, retExpr will be set, then the block will be |
16639 | // reimported, but retExpr won't get cleared as part of setting the block to |
16640 | // be reimported. The reimported retExpr value should be the same, so even if |
16641 | // we don't unconditionally overwrite it, it shouldn't matter. |
16642 | if (info.compRetNativeType != TYP_STRUCT) |
16643 | { |
16644 | // compRetNativeType is not TYP_STRUCT. |
16645 | // This implies it could be either a scalar type or SIMD vector type or |
16646 | // a struct type that can be normalized to a scalar type. |
16647 | |
16648 | if (varTypeIsStruct(info.compRetType)) |
16649 | { |
16650 | noway_assert(info.compRetBuffArg == BAD_VAR_NUM); |
16651 | // adjust the type away from struct to integral |
16652 | // and no normalizing |
16653 | op2 = impFixupStructReturnType(op2, retClsHnd); |
16654 | } |
16655 | else |
16656 | { |
16657 | // Do we have to normalize? |
16658 | var_types fncRealRetType = JITtype2varType(info.compMethodInfo->args.retType); |
16659 | if ((varTypeIsSmall(op2->TypeGet()) || varTypeIsSmall(fncRealRetType)) && |
16660 | fgCastNeeded(op2, fncRealRetType)) |
16661 | { |
16662 | // Small-typed return values are normalized by the callee |
16663 | op2 = gtNewCastNode(TYP_INT, op2, false, fncRealRetType); |
16664 | } |
16665 | } |
16666 | |
16667 | if (fgNeedReturnSpillTemp()) |
16668 | { |
16669 | assert(info.compRetNativeType != TYP_VOID && |
16670 | (fgMoreThanOneReturnBlock() || impInlineInfo->HasGcRefLocals())); |
16671 | |
16672 | // If this method returns a ref type, track the actual types seen |
16673 | // in the returns. |
16674 | if (info.compRetType == TYP_REF) |
16675 | { |
16676 | bool isExact = false; |
16677 | bool isNonNull = false; |
16678 | CORINFO_CLASS_HANDLE returnClsHnd = gtGetClassHandle(op2, &isExact, &isNonNull); |
16679 | |
16680 | if (impInlineInfo->retExpr == nullptr) |
16681 | { |
16682 | // This is the first return, so best known type is the type |
16683 | // of this return value. |
16684 | impInlineInfo->retExprClassHnd = returnClsHnd; |
16685 | impInlineInfo->retExprClassHndIsExact = isExact; |
16686 | } |
16687 | else if (impInlineInfo->retExprClassHnd != returnClsHnd) |
16688 | { |
16689 | // This return site type differs from earlier seen sites, |
16690 | // so reset the info and we'll fall back to using the method's |
16691 | // declared return type for the return spill temp. |
16692 | impInlineInfo->retExprClassHnd = nullptr; |
16693 | impInlineInfo->retExprClassHndIsExact = false; |
16694 | } |
16695 | } |
16696 | |
16697 | // This is a bit of a workaround... |
16698 | // If we are inlining a call that returns a struct, where the actual "native" return type is |
16699 | // not a struct (for example, the struct is composed of exactly one int, and the native |
16700 | // return type is thus an int), and the inlinee has multiple return blocks (thus, |
16701 | // fgNeedReturnSpillTemp() == true, and is the index of a local var that is set |
16702 | // to the *native* return type), and at least one of the return blocks is the result of |
16703 | // a call, then we have a problem. The situation is like this (from a failed test case): |
16704 | // |
16705 | // inliner: |
16706 | // // Note: valuetype plinq_devtests.LazyTests/LIX is a struct with only a single int |
16707 | // call !!0 [mscorlib]System.Threading.LazyInitializer::EnsureInitialized<valuetype |
16708 | // plinq_devtests.LazyTests/LIX>(!!0&, bool&, object&, class [mscorlib]System.Func`1<!!0>) |
16709 | // |
16710 | // inlinee: |
16711 | // ... |
16712 | // ldobj !!T // this gets bashed to a GT_LCL_FLD, type TYP_INT |
16713 | // ret |
16714 | // ... |
16715 | // call !!0 System.Threading.LazyInitializer::EnsureInitializedCore<!!0>(!!0&, bool&, |
16716 | // object&, class System.Func`1<!!0>) |
16717 | // ret |
16718 | // |
16719 | // In the code above, when we call impFixupStructReturnType(), we will change the op2 return type |
16720 | // of the inlinee return node, but we don't do that for GT_CALL nodes, which we delay until |
16721 | // morphing when we call fgFixupStructReturn(). We do this, apparently, to handle nested |
16722 | // inlining properly by leaving the correct type on the GT_CALL node through importing. |
16723 | // |
16724 | // To fix this, for this case, we temporarily change the GT_CALL node type to the |
16725 | // native return type, which is what it will be set to eventually. We generate the |
16726 | // assignment to the return temp, using the correct type, and then restore the GT_CALL |
16727 | // node type. During morphing, the GT_CALL will get the correct, final, native return type. |
16728 | |
16729 | bool restoreType = false; |
16730 | if ((op2->OperGet() == GT_CALL) && (info.compRetType == TYP_STRUCT)) |
16731 | { |
16732 | noway_assert(op2->TypeGet() == TYP_STRUCT); |
16733 | op2->gtType = info.compRetNativeType; |
16734 | restoreType = true; |
16735 | } |
16736 | |
16737 | impAssignTempGen(lvaInlineeReturnSpillTemp, op2, se.seTypeInfo.GetClassHandle(), |
16738 | (unsigned)CHECK_SPILL_ALL); |
16739 | |
16740 | GenTree* tmpOp2 = gtNewLclvNode(lvaInlineeReturnSpillTemp, op2->TypeGet()); |
16741 | |
16742 | if (restoreType) |
16743 | { |
16744 | op2->gtType = TYP_STRUCT; // restore it to what it was |
16745 | } |
16746 | |
16747 | op2 = tmpOp2; |
16748 | |
16749 | #ifdef DEBUG |
16750 | if (impInlineInfo->retExpr) |
16751 | { |
16752 | // Some other block(s) have seen the CEE_RET first. |
16753 | // Better they spilled to the same temp. |
16754 | assert(impInlineInfo->retExpr->gtOper == GT_LCL_VAR); |
16755 | assert(impInlineInfo->retExpr->gtLclVarCommon.gtLclNum == op2->gtLclVarCommon.gtLclNum); |
16756 | } |
16757 | #endif |
16758 | } |
16759 | |
16760 | #ifdef DEBUG |
16761 | if (verbose) |
16762 | { |
16763 | printf("\n\n Inlinee Return expression (after normalization) =>\n" ); |
16764 | gtDispTree(op2); |
16765 | } |
16766 | #endif |
16767 | |
16768 | // Report the return expression |
16769 | impInlineInfo->retExpr = op2; |
16770 | } |
16771 | else |
16772 | { |
16773 | // compRetNativeType is TYP_STRUCT. |
16774 | // This implies that struct return via RetBuf arg or multi-reg struct return |
16775 | |
16776 | GenTreeCall* iciCall = impInlineInfo->iciCall->AsCall(); |
16777 | |
16778 | // Assign the inlinee return into a spill temp. |
16779 | // spill temp only exists if there are multiple return points |
16780 | if (lvaInlineeReturnSpillTemp != BAD_VAR_NUM) |
16781 | { |
16782 | // in this case we have to insert multiple struct copies to the temp |
16783 | // and the retexpr is just the temp. |
16784 | assert(info.compRetNativeType != TYP_VOID); |
16785 | assert(fgMoreThanOneReturnBlock() || impInlineInfo->HasGcRefLocals()); |
16786 | |
16787 | impAssignTempGen(lvaInlineeReturnSpillTemp, op2, se.seTypeInfo.GetClassHandle(), |
16788 | (unsigned)CHECK_SPILL_ALL); |
16789 | } |
16790 | |
16791 | #if defined(_TARGET_ARM_) || defined(UNIX_AMD64_ABI) |
16792 | #if defined(_TARGET_ARM_) |
16793 | // TODO-ARM64-NYI: HFA |
16794 | // TODO-AMD64-Unix and TODO-ARM once the ARM64 functionality is implemented the |
16795 | // next ifdefs could be refactored in a single method with the ifdef inside. |
16796 | if (IsHfa(retClsHnd)) |
16797 | { |
16798 | // Same as !IsHfa but just don't bother with impAssignStructPtr. |
16799 | #else // defined(UNIX_AMD64_ABI) |
16800 | ReturnTypeDesc retTypeDesc; |
16801 | retTypeDesc.InitializeStructReturnType(this, retClsHnd); |
16802 | unsigned retRegCount = retTypeDesc.GetReturnRegCount(); |
16803 | |
16804 | if (retRegCount != 0) |
16805 | { |
16806 | // If single eightbyte, the return type would have been normalized and there won't be a temp var. |
16807 | // This code will be called only if the struct return has not been normalized (i.e. 2 eightbytes - |
16808 | // max allowed.) |
16809 | assert(retRegCount == MAX_RET_REG_COUNT); |
16810 | // Same as !structDesc.passedInRegisters but just don't bother with impAssignStructPtr. |
16811 | CLANG_FORMAT_COMMENT_ANCHOR; |
16812 | #endif // defined(UNIX_AMD64_ABI) |
16813 | |
16814 | if (fgNeedReturnSpillTemp()) |
16815 | { |
16816 | if (!impInlineInfo->retExpr) |
16817 | { |
16818 | #if defined(_TARGET_ARM_) |
16819 | impInlineInfo->retExpr = gtNewLclvNode(lvaInlineeReturnSpillTemp, info.compRetType); |
16820 | #else // defined(UNIX_AMD64_ABI) |
16821 | // The inlinee compiler has figured out the type of the temp already. Use it here. |
16822 | impInlineInfo->retExpr = |
16823 | gtNewLclvNode(lvaInlineeReturnSpillTemp, lvaTable[lvaInlineeReturnSpillTemp].lvType); |
16824 | #endif // defined(UNIX_AMD64_ABI) |
16825 | } |
16826 | } |
16827 | else |
16828 | { |
16829 | impInlineInfo->retExpr = op2; |
16830 | } |
16831 | } |
16832 | else |
16833 | #elif defined(_TARGET_ARM64_) |
16834 | ReturnTypeDesc retTypeDesc; |
16835 | retTypeDesc.InitializeStructReturnType(this, retClsHnd); |
16836 | unsigned retRegCount = retTypeDesc.GetReturnRegCount(); |
16837 | |
16838 | if (retRegCount != 0) |
16839 | { |
16840 | assert(!iciCall->HasRetBufArg()); |
16841 | assert(retRegCount >= 2); |
16842 | if (fgNeedReturnSpillTemp()) |
16843 | { |
16844 | if (!impInlineInfo->retExpr) |
16845 | { |
16846 | // The inlinee compiler has figured out the type of the temp already. Use it here. |
16847 | impInlineInfo->retExpr = |
16848 | gtNewLclvNode(lvaInlineeReturnSpillTemp, lvaTable[lvaInlineeReturnSpillTemp].lvType); |
16849 | } |
16850 | } |
16851 | else |
16852 | { |
16853 | impInlineInfo->retExpr = op2; |
16854 | } |
16855 | } |
16856 | else |
16857 | #endif // defined(_TARGET_ARM64_) |
16858 | { |
16859 | assert(iciCall->HasRetBufArg()); |
16860 | GenTree* dest = gtCloneExpr(iciCall->gtCallArgs->gtOp.gtOp1); |
16861 | // spill temp only exists if there are multiple return points |
16862 | if (fgNeedReturnSpillTemp()) |
16863 | { |
16864 | // if this is the first return we have seen set the retExpr |
16865 | if (!impInlineInfo->retExpr) |
16866 | { |
16867 | impInlineInfo->retExpr = |
16868 | impAssignStructPtr(dest, gtNewLclvNode(lvaInlineeReturnSpillTemp, info.compRetType), |
16869 | retClsHnd, (unsigned)CHECK_SPILL_ALL); |
16870 | } |
16871 | } |
16872 | else |
16873 | { |
16874 | impInlineInfo->retExpr = impAssignStructPtr(dest, op2, retClsHnd, (unsigned)CHECK_SPILL_ALL); |
16875 | } |
16876 | } |
16877 | } |
16878 | } |
16879 | } |
16880 | |
16881 | if (compIsForInlining()) |
16882 | { |
16883 | return true; |
16884 | } |
16885 | |
16886 | if (info.compRetType == TYP_VOID) |
16887 | { |
16888 | // return void |
16889 | op1 = new (this, GT_RETURN) GenTreeOp(GT_RETURN, TYP_VOID); |
16890 | } |
16891 | else if (info.compRetBuffArg != BAD_VAR_NUM) |
16892 | { |
16893 | // Assign value to return buff (first param) |
16894 | GenTree* retBuffAddr = gtNewLclvNode(info.compRetBuffArg, TYP_BYREF, impCurStmtOffs); |
16895 | |
16896 | op2 = impAssignStructPtr(retBuffAddr, op2, retClsHnd, (unsigned)CHECK_SPILL_ALL); |
16897 | impAppendTree(op2, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); |
16898 | |
16899 | // There are cases where the address of the implicit RetBuf should be returned explicitly (in RAX). |
16900 | CLANG_FORMAT_COMMENT_ANCHOR; |
16901 | |
16902 | #if defined(_TARGET_AMD64_) |
16903 | |
16904 | // x64 (System V and Win64) calling convention requires to |
16905 | // return the implicit return buffer explicitly (in RAX). |
16906 | // Change the return type to be BYREF. |
16907 | op1 = gtNewOperNode(GT_RETURN, TYP_BYREF, gtNewLclvNode(info.compRetBuffArg, TYP_BYREF)); |
16908 | #else // !defined(_TARGET_AMD64_) |
16909 | // In case of non-AMD64 targets the profiler hook requires to return the implicit RetBuf explicitly (in RAX). |
16910 | // In such case the return value of the function is changed to BYREF. |
16911 | // If profiler hook is not needed the return type of the function is TYP_VOID. |
16912 | if (compIsProfilerHookNeeded()) |
16913 | { |
16914 | op1 = gtNewOperNode(GT_RETURN, TYP_BYREF, gtNewLclvNode(info.compRetBuffArg, TYP_BYREF)); |
16915 | } |
16916 | else |
16917 | { |
16918 | // return void |
16919 | op1 = new (this, GT_RETURN) GenTreeOp(GT_RETURN, TYP_VOID); |
16920 | } |
16921 | #endif // !defined(_TARGET_AMD64_) |
16922 | } |
16923 | else if (varTypeIsStruct(info.compRetType)) |
16924 | { |
16925 | #if !FEATURE_MULTIREG_RET |
16926 | // For both ARM architectures the HFA native types are maintained as structs. |
16927 | // Also on System V AMD64 the multireg structs returns are also left as structs. |
16928 | noway_assert(info.compRetNativeType != TYP_STRUCT); |
16929 | #endif |
16930 | op2 = impFixupStructReturnType(op2, retClsHnd); |
16931 | // return op2 |
16932 | op1 = gtNewOperNode(GT_RETURN, genActualType(info.compRetNativeType), op2); |
16933 | } |
16934 | else |
16935 | { |
16936 | // return op2 |
16937 | op1 = gtNewOperNode(GT_RETURN, genActualType(info.compRetType), op2); |
16938 | } |
16939 | |
16940 | // We must have imported a tailcall and jumped to RET |
16941 | if (prefixFlags & PREFIX_TAILCALL) |
16942 | { |
16943 | #if defined(FEATURE_CORECLR) || !defined(_TARGET_AMD64_) |
16944 | // Jit64 compat: |
16945 | // This cannot be asserted on Amd64 since we permit the following IL pattern: |
16946 | // tail.call |
16947 | // pop |
16948 | // ret |
16949 | assert(verCurrentState.esStackDepth == 0 && impOpcodeIsCallOpcode(opcode)); |
16950 | #endif // FEATURE_CORECLR || !_TARGET_AMD64_ |
16951 | |
16952 | opcode = CEE_RET; // To prevent trying to spill if CALL_SITE_BOUNDARIES |
16953 | |
16954 | // impImportCall() would have already appended TYP_VOID calls |
16955 | if (info.compRetType == TYP_VOID) |
16956 | { |
16957 | return true; |
16958 | } |
16959 | } |
16960 | |
16961 | impAppendTree(op1, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); |
16962 | #ifdef DEBUG |
16963 | // Remember at which BC offset the tree was finished |
16964 | impNoteLastILoffs(); |
16965 | #endif |
16966 | return true; |
16967 | } |
16968 | |
16969 | /***************************************************************************** |
16970 | * Mark the block as unimported. |
16971 | * Note that the caller is responsible for calling impImportBlockPending(), |
16972 | * with the appropriate stack-state |
16973 | */ |
16974 | |
16975 | inline void Compiler::impReimportMarkBlock(BasicBlock* block) |
16976 | { |
16977 | #ifdef DEBUG |
16978 | if (verbose && (block->bbFlags & BBF_IMPORTED)) |
16979 | { |
16980 | printf("\n" FMT_BB " will be reimported\n" , block->bbNum); |
16981 | } |
16982 | #endif |
16983 | |
16984 | block->bbFlags &= ~BBF_IMPORTED; |
16985 | } |
16986 | |
16987 | /***************************************************************************** |
16988 | * Mark the successors of the given block as unimported. |
16989 | * Note that the caller is responsible for calling impImportBlockPending() |
16990 | * for all the successors, with the appropriate stack-state. |
16991 | */ |
16992 | |
16993 | void Compiler::impReimportMarkSuccessors(BasicBlock* block) |
16994 | { |
16995 | const unsigned numSuccs = block->NumSucc(); |
16996 | for (unsigned i = 0; i < numSuccs; i++) |
16997 | { |
16998 | impReimportMarkBlock(block->GetSucc(i)); |
16999 | } |
17000 | } |
17001 | |
17002 | /***************************************************************************** |
17003 | * |
17004 | * Filter wrapper to handle only passed in exception code |
17005 | * from it). |
17006 | */ |
17007 | |
17008 | LONG FilterVerificationExceptions(PEXCEPTION_POINTERS pExceptionPointers, LPVOID lpvParam) |
17009 | { |
17010 | if (pExceptionPointers->ExceptionRecord->ExceptionCode == SEH_VERIFICATION_EXCEPTION) |
17011 | { |
17012 | return EXCEPTION_EXECUTE_HANDLER; |
17013 | } |
17014 | |
17015 | return EXCEPTION_CONTINUE_SEARCH; |
17016 | } |
17017 | |
17018 | void Compiler::impVerifyEHBlock(BasicBlock* block, bool isTryStart) |
17019 | { |
17020 | assert(block->hasTryIndex()); |
17021 | assert(!compIsForInlining()); |
17022 | |
17023 | unsigned tryIndex = block->getTryIndex(); |
17024 | EHblkDsc* HBtab = ehGetDsc(tryIndex); |
17025 | |
17026 | if (isTryStart) |
17027 | { |
17028 | assert(block->bbFlags & BBF_TRY_BEG); |
17029 | |
17030 | // The Stack must be empty |
17031 | // |
17032 | if (block->bbStkDepth != 0) |
17033 | { |
17034 | BADCODE("Evaluation stack must be empty on entry into a try block" ); |
17035 | } |
17036 | } |
17037 | |
17038 | // Save the stack contents, we'll need to restore it later |
17039 | // |
17040 | SavedStack blockState; |
17041 | impSaveStackState(&blockState, false); |
17042 | |
17043 | while (HBtab != nullptr) |
17044 | { |
17045 | if (isTryStart) |
17046 | { |
17047 | // Are we verifying that an instance constructor properly initializes it's 'this' pointer once? |
17048 | // We do not allow the 'this' pointer to be uninitialized when entering most kinds try regions |
17049 | // |
17050 | if (verTrackObjCtorInitState && (verCurrentState.thisInitialized != TIS_Init)) |
17051 | { |
17052 | // We trigger an invalid program exception here unless we have a try/fault region. |
17053 | // |
17054 | if (HBtab->HasCatchHandler() || HBtab->HasFinallyHandler() || HBtab->HasFilter()) |
17055 | { |
17056 | BADCODE( |
17057 | "The 'this' pointer of an instance constructor is not intialized upon entry to a try region" ); |
17058 | } |
17059 | else |
17060 | { |
17061 | // Allow a try/fault region to proceed. |
17062 | assert(HBtab->HasFaultHandler()); |
17063 | } |
17064 | } |
17065 | |
17066 | /* Recursively process the handler block */ |
17067 | BasicBlock* hndBegBB = HBtab->ebdHndBeg; |
17068 | |
17069 | // Construct the proper verification stack state |
17070 | // either empty or one that contains just |
17071 | // the Exception Object that we are dealing with |
17072 | // |
17073 | verCurrentState.esStackDepth = 0; |
17074 | |
17075 | if (handlerGetsXcptnObj(hndBegBB->bbCatchTyp)) |
17076 | { |
17077 | CORINFO_CLASS_HANDLE clsHnd; |
17078 | |
17079 | if (HBtab->HasFilter()) |
17080 | { |
17081 | clsHnd = impGetObjectClass(); |
17082 | } |
17083 | else |
17084 | { |
17085 | CORINFO_RESOLVED_TOKEN resolvedToken; |
17086 | |
17087 | resolvedToken.tokenContext = impTokenLookupContextHandle; |
17088 | resolvedToken.tokenScope = info.compScopeHnd; |
17089 | resolvedToken.token = HBtab->ebdTyp; |
17090 | resolvedToken.tokenType = CORINFO_TOKENKIND_Class; |
17091 | info.compCompHnd->resolveToken(&resolvedToken); |
17092 | |
17093 | clsHnd = resolvedToken.hClass; |
17094 | } |
17095 | |
17096 | // push catch arg the stack, spill to a temp if necessary |
17097 | // Note: can update HBtab->ebdHndBeg! |
17098 | hndBegBB = impPushCatchArgOnStack(hndBegBB, clsHnd, false); |
17099 | } |
17100 | |
17101 | // Queue up the handler for importing |
17102 | // |
17103 | impImportBlockPending(hndBegBB); |
17104 | |
17105 | if (HBtab->HasFilter()) |
17106 | { |
17107 | /* @VERIFICATION : Ideally the end of filter state should get |
17108 | propagated to the catch handler, this is an incompleteness, |
17109 | but is not a security/compliance issue, since the only |
17110 | interesting state is the 'thisInit' state. |
17111 | */ |
17112 | |
17113 | verCurrentState.esStackDepth = 0; |
17114 | |
17115 | BasicBlock* filterBB = HBtab->ebdFilter; |
17116 | |
17117 | // push catch arg the stack, spill to a temp if necessary |
17118 | // Note: can update HBtab->ebdFilter! |
17119 | const bool isSingleBlockFilter = (filterBB->bbNext == hndBegBB); |
17120 | filterBB = impPushCatchArgOnStack(filterBB, impGetObjectClass(), isSingleBlockFilter); |
17121 | |
17122 | impImportBlockPending(filterBB); |
17123 | } |
17124 | } |
17125 | else if (verTrackObjCtorInitState && HBtab->HasFaultHandler()) |
17126 | { |
17127 | /* Recursively process the handler block */ |
17128 | |
17129 | verCurrentState.esStackDepth = 0; |
17130 | |
17131 | // Queue up the fault handler for importing |
17132 | // |
17133 | impImportBlockPending(HBtab->ebdHndBeg); |
17134 | } |
17135 | |
17136 | // Now process our enclosing try index (if any) |
17137 | // |
17138 | tryIndex = HBtab->ebdEnclosingTryIndex; |
17139 | if (tryIndex == EHblkDsc::NO_ENCLOSING_INDEX) |
17140 | { |
17141 | HBtab = nullptr; |
17142 | } |
17143 | else |
17144 | { |
17145 | HBtab = ehGetDsc(tryIndex); |
17146 | } |
17147 | } |
17148 | |
17149 | // Restore the stack contents |
17150 | impRestoreStackState(&blockState); |
17151 | } |
17152 | |
17153 | //*************************************************************** |
17154 | // Import the instructions for the given basic block. Perform |
17155 | // verification, throwing an exception on failure. Push any successor blocks that are enabled for the first |
17156 | // time, or whose verification pre-state is changed. |
17157 | |
17158 | #ifdef _PREFAST_ |
17159 | #pragma warning(push) |
17160 | #pragma warning(disable : 21000) // Suppress PREFast warning about overly large function |
17161 | #endif |
17162 | void Compiler::impImportBlock(BasicBlock* block) |
17163 | { |
17164 | // BBF_INTERNAL blocks only exist during importation due to EH canonicalization. We need to |
17165 | // handle them specially. In particular, there is no IL to import for them, but we do need |
17166 | // to mark them as imported and put their successors on the pending import list. |
17167 | if (block->bbFlags & BBF_INTERNAL) |
17168 | { |
17169 | JITDUMP("Marking BBF_INTERNAL block " FMT_BB " as BBF_IMPORTED\n" , block->bbNum); |
17170 | block->bbFlags |= BBF_IMPORTED; |
17171 | |
17172 | const unsigned numSuccs = block->NumSucc(); |
17173 | for (unsigned i = 0; i < numSuccs; i++) |
17174 | { |
17175 | impImportBlockPending(block->GetSucc(i)); |
17176 | } |
17177 | |
17178 | return; |
17179 | } |
17180 | |
17181 | bool markImport; |
17182 | |
17183 | assert(block); |
17184 | |
17185 | /* Make the block globaly available */ |
17186 | |
17187 | compCurBB = block; |
17188 | |
17189 | #ifdef DEBUG |
17190 | /* Initialize the debug variables */ |
17191 | impCurOpcName = "unknown" ; |
17192 | impCurOpcOffs = block->bbCodeOffs; |
17193 | #endif |
17194 | |
17195 | /* Set the current stack state to the merged result */ |
17196 | verResetCurrentState(block, &verCurrentState); |
17197 | |
17198 | /* Now walk the code and import the IL into GenTrees */ |
17199 | |
17200 | struct FilterVerificationExceptionsParam |
17201 | { |
17202 | Compiler* pThis; |
17203 | BasicBlock* block; |
17204 | }; |
17205 | FilterVerificationExceptionsParam param; |
17206 | |
17207 | param.pThis = this; |
17208 | param.block = block; |
17209 | |
17210 | PAL_TRY(FilterVerificationExceptionsParam*, pParam, ¶m) |
17211 | { |
17212 | /* @VERIFICATION : For now, the only state propagation from try |
17213 | to it's handler is "thisInit" state (stack is empty at start of try). |
17214 | In general, for state that we track in verification, we need to |
17215 | model the possibility that an exception might happen at any IL |
17216 | instruction, so we really need to merge all states that obtain |
17217 | between IL instructions in a try block into the start states of |
17218 | all handlers. |
17219 | |
17220 | However we do not allow the 'this' pointer to be uninitialized when |
17221 | entering most kinds try regions (only try/fault are allowed to have |
17222 | an uninitialized this pointer on entry to the try) |
17223 | |
17224 | Fortunately, the stack is thrown away when an exception |
17225 | leads to a handler, so we don't have to worry about that. |
17226 | We DO, however, have to worry about the "thisInit" state. |
17227 | But only for the try/fault case. |
17228 | |
17229 | The only allowed transition is from TIS_Uninit to TIS_Init. |
17230 | |
17231 | So for a try/fault region for the fault handler block |
17232 | we will merge the start state of the try begin |
17233 | and the post-state of each block that is part of this try region |
17234 | */ |
17235 | |
17236 | // merge the start state of the try begin |
17237 | // |
17238 | if (pParam->block->bbFlags & BBF_TRY_BEG) |
17239 | { |
17240 | pParam->pThis->impVerifyEHBlock(pParam->block, true); |
17241 | } |
17242 | |
17243 | pParam->pThis->impImportBlockCode(pParam->block); |
17244 | |
17245 | // As discussed above: |
17246 | // merge the post-state of each block that is part of this try region |
17247 | // |
17248 | if (pParam->block->hasTryIndex()) |
17249 | { |
17250 | pParam->pThis->impVerifyEHBlock(pParam->block, false); |
17251 | } |
17252 | } |
17253 | PAL_EXCEPT_FILTER(FilterVerificationExceptions) |
17254 | { |
17255 | verHandleVerificationFailure(block DEBUGARG(false)); |
17256 | } |
17257 | PAL_ENDTRY |
17258 | |
17259 | if (compDonotInline()) |
17260 | { |
17261 | return; |
17262 | } |
17263 | |
17264 | assert(!compDonotInline()); |
17265 | |
17266 | markImport = false; |
17267 | |
17268 | SPILLSTACK: |
17269 | |
17270 | unsigned baseTmp = NO_BASE_TMP; // input temps assigned to successor blocks |
17271 | bool reimportSpillClique = false; |
17272 | BasicBlock* tgtBlock = nullptr; |
17273 | |
17274 | /* If the stack is non-empty, we might have to spill its contents */ |
17275 | |
17276 | if (verCurrentState.esStackDepth != 0) |
17277 | { |
17278 | impBoxTemp = BAD_VAR_NUM; // if a box temp is used in a block that leaves something |
17279 | // on the stack, its lifetime is hard to determine, simply |
17280 | // don't reuse such temps. |
17281 | |
17282 | GenTree* addStmt = nullptr; |
17283 | |
17284 | /* Do the successors of 'block' have any other predecessors ? |
17285 | We do not want to do some of the optimizations related to multiRef |
17286 | if we can reimport blocks */ |
17287 | |
17288 | unsigned multRef = impCanReimport ? unsigned(~0) : 0; |
17289 | |
17290 | switch (block->bbJumpKind) |
17291 | { |
17292 | case BBJ_COND: |
17293 | |
17294 | /* Temporarily remove the 'jtrue' from the end of the tree list */ |
17295 | |
17296 | assert(impTreeLast); |
17297 | assert(impTreeLast->gtOper == GT_STMT); |
17298 | assert(impTreeLast->gtStmt.gtStmtExpr->gtOper == GT_JTRUE); |
17299 | |
17300 | addStmt = impTreeLast; |
17301 | impTreeLast = impTreeLast->gtPrev; |
17302 | |
17303 | /* Note if the next block has more than one ancestor */ |
17304 | |
17305 | multRef |= block->bbNext->bbRefs; |
17306 | |
17307 | /* Does the next block have temps assigned? */ |
17308 | |
17309 | baseTmp = block->bbNext->bbStkTempsIn; |
17310 | tgtBlock = block->bbNext; |
17311 | |
17312 | if (baseTmp != NO_BASE_TMP) |
17313 | { |
17314 | break; |
17315 | } |
17316 | |
17317 | /* Try the target of the jump then */ |
17318 | |
17319 | multRef |= block->bbJumpDest->bbRefs; |
17320 | baseTmp = block->bbJumpDest->bbStkTempsIn; |
17321 | tgtBlock = block->bbJumpDest; |
17322 | break; |
17323 | |
17324 | case BBJ_ALWAYS: |
17325 | multRef |= block->bbJumpDest->bbRefs; |
17326 | baseTmp = block->bbJumpDest->bbStkTempsIn; |
17327 | tgtBlock = block->bbJumpDest; |
17328 | break; |
17329 | |
17330 | case BBJ_NONE: |
17331 | multRef |= block->bbNext->bbRefs; |
17332 | baseTmp = block->bbNext->bbStkTempsIn; |
17333 | tgtBlock = block->bbNext; |
17334 | break; |
17335 | |
17336 | case BBJ_SWITCH: |
17337 | |
17338 | BasicBlock** jmpTab; |
17339 | unsigned jmpCnt; |
17340 | |
17341 | /* Temporarily remove the GT_SWITCH from the end of the tree list */ |
17342 | |
17343 | assert(impTreeLast); |
17344 | assert(impTreeLast->gtOper == GT_STMT); |
17345 | assert(impTreeLast->gtStmt.gtStmtExpr->gtOper == GT_SWITCH); |
17346 | |
17347 | addStmt = impTreeLast; |
17348 | impTreeLast = impTreeLast->gtPrev; |
17349 | |
17350 | jmpCnt = block->bbJumpSwt->bbsCount; |
17351 | jmpTab = block->bbJumpSwt->bbsDstTab; |
17352 | |
17353 | do |
17354 | { |
17355 | tgtBlock = (*jmpTab); |
17356 | |
17357 | multRef |= tgtBlock->bbRefs; |
17358 | |
17359 | // Thanks to spill cliques, we should have assigned all or none |
17360 | assert((baseTmp == NO_BASE_TMP) || (baseTmp == tgtBlock->bbStkTempsIn)); |
17361 | baseTmp = tgtBlock->bbStkTempsIn; |
17362 | if (multRef > 1) |
17363 | { |
17364 | break; |
17365 | } |
17366 | } while (++jmpTab, --jmpCnt); |
17367 | |
17368 | break; |
17369 | |
17370 | case BBJ_CALLFINALLY: |
17371 | case BBJ_EHCATCHRET: |
17372 | case BBJ_RETURN: |
17373 | case BBJ_EHFINALLYRET: |
17374 | case BBJ_EHFILTERRET: |
17375 | case BBJ_THROW: |
17376 | NO_WAY("can't have 'unreached' end of BB with non-empty stack" ); |
17377 | break; |
17378 | |
17379 | default: |
17380 | noway_assert(!"Unexpected bbJumpKind" ); |
17381 | break; |
17382 | } |
17383 | |
17384 | assert(multRef >= 1); |
17385 | |
17386 | /* Do we have a base temp number? */ |
17387 | |
17388 | bool newTemps = (baseTmp == NO_BASE_TMP); |
17389 | |
17390 | if (newTemps) |
17391 | { |
17392 | /* Grab enough temps for the whole stack */ |
17393 | baseTmp = impGetSpillTmpBase(block); |
17394 | } |
17395 | |
17396 | /* Spill all stack entries into temps */ |
17397 | unsigned level, tempNum; |
17398 | |
17399 | JITDUMP("\nSpilling stack entries into temps\n" ); |
17400 | for (level = 0, tempNum = baseTmp; level < verCurrentState.esStackDepth; level++, tempNum++) |
17401 | { |
17402 | GenTree* tree = verCurrentState.esStack[level].val; |
17403 | |
17404 | /* VC generates code where it pushes a byref from one branch, and an int (ldc.i4 0) from |
17405 | the other. This should merge to a byref in unverifiable code. |
17406 | However, if the branch which leaves the TYP_I_IMPL on the stack is imported first, the |
17407 | successor would be imported assuming there was a TYP_I_IMPL on |
17408 | the stack. Thus the value would not get GC-tracked. Hence, |
17409 | change the temp to TYP_BYREF and reimport the successors. |
17410 | Note: We should only allow this in unverifiable code. |
17411 | */ |
17412 | if (tree->gtType == TYP_BYREF && lvaTable[tempNum].lvType == TYP_I_IMPL && !verNeedsVerification()) |
17413 | { |
17414 | lvaTable[tempNum].lvType = TYP_BYREF; |
17415 | impReimportMarkSuccessors(block); |
17416 | markImport = true; |
17417 | } |
17418 | |
17419 | #ifdef _TARGET_64BIT_ |
17420 | if (genActualType(tree->gtType) == TYP_I_IMPL && lvaTable[tempNum].lvType == TYP_INT) |
17421 | { |
17422 | if (tiVerificationNeeded && tgtBlock->bbEntryState != nullptr && |
17423 | (tgtBlock->bbFlags & BBF_FAILED_VERIFICATION) == 0) |
17424 | { |
17425 | // Merge the current state into the entry state of block; |
17426 | // the call to verMergeEntryStates must have changed |
17427 | // the entry state of the block by merging the int local var |
17428 | // and the native-int stack entry. |
17429 | bool changed = false; |
17430 | if (verMergeEntryStates(tgtBlock, &changed)) |
17431 | { |
17432 | impRetypeEntryStateTemps(tgtBlock); |
17433 | impReimportBlockPending(tgtBlock); |
17434 | assert(changed); |
17435 | } |
17436 | else |
17437 | { |
17438 | tgtBlock->bbFlags |= BBF_FAILED_VERIFICATION; |
17439 | break; |
17440 | } |
17441 | } |
17442 | |
17443 | // Some other block in the spill clique set this to "int", but now we have "native int". |
17444 | // Change the type and go back to re-import any blocks that used the wrong type. |
17445 | lvaTable[tempNum].lvType = TYP_I_IMPL; |
17446 | reimportSpillClique = true; |
17447 | } |
17448 | else if (genActualType(tree->gtType) == TYP_INT && lvaTable[tempNum].lvType == TYP_I_IMPL) |
17449 | { |
17450 | // Spill clique has decided this should be "native int", but this block only pushes an "int". |
17451 | // Insert a sign-extension to "native int" so we match the clique. |
17452 | verCurrentState.esStack[level].val = gtNewCastNode(TYP_I_IMPL, tree, false, TYP_I_IMPL); |
17453 | } |
17454 | |
17455 | // Consider the case where one branch left a 'byref' on the stack and the other leaves |
17456 | // an 'int'. On 32-bit, this is allowed (in non-verifiable code) since they are the same |
17457 | // size. JIT64 managed to make this work on 64-bit. For compatibility, we support JIT64 |
17458 | // behavior instead of asserting and then generating bad code (where we save/restore the |
17459 | // low 32 bits of a byref pointer to an 'int' sized local). If the 'int' side has been |
17460 | // imported already, we need to change the type of the local and reimport the spill clique. |
17461 | // If the 'byref' side has imported, we insert a cast from int to 'native int' to match |
17462 | // the 'byref' size. |
17463 | if (!tiVerificationNeeded) |
17464 | { |
17465 | if (genActualType(tree->gtType) == TYP_BYREF && lvaTable[tempNum].lvType == TYP_INT) |
17466 | { |
17467 | // Some other block in the spill clique set this to "int", but now we have "byref". |
17468 | // Change the type and go back to re-import any blocks that used the wrong type. |
17469 | lvaTable[tempNum].lvType = TYP_BYREF; |
17470 | reimportSpillClique = true; |
17471 | } |
17472 | else if (genActualType(tree->gtType) == TYP_INT && lvaTable[tempNum].lvType == TYP_BYREF) |
17473 | { |
17474 | // Spill clique has decided this should be "byref", but this block only pushes an "int". |
17475 | // Insert a sign-extension to "native int" so we match the clique size. |
17476 | verCurrentState.esStack[level].val = gtNewCastNode(TYP_I_IMPL, tree, false, TYP_I_IMPL); |
17477 | } |
17478 | } |
17479 | #endif // _TARGET_64BIT_ |
17480 | |
17481 | if (tree->gtType == TYP_DOUBLE && lvaTable[tempNum].lvType == TYP_FLOAT) |
17482 | { |
17483 | // Some other block in the spill clique set this to "float", but now we have "double". |
17484 | // Change the type and go back to re-import any blocks that used the wrong type. |
17485 | lvaTable[tempNum].lvType = TYP_DOUBLE; |
17486 | reimportSpillClique = true; |
17487 | } |
17488 | else if (tree->gtType == TYP_FLOAT && lvaTable[tempNum].lvType == TYP_DOUBLE) |
17489 | { |
17490 | // Spill clique has decided this should be "double", but this block only pushes a "float". |
17491 | // Insert a cast to "double" so we match the clique. |
17492 | verCurrentState.esStack[level].val = gtNewCastNode(TYP_DOUBLE, tree, false, TYP_DOUBLE); |
17493 | } |
17494 | |
17495 | /* If addStmt has a reference to tempNum (can only happen if we |
17496 | are spilling to the temps already used by a previous block), |
17497 | we need to spill addStmt */ |
17498 | |
17499 | if (addStmt && !newTemps && gtHasRef(addStmt->gtStmt.gtStmtExpr, tempNum, false)) |
17500 | { |
17501 | GenTree* addTree = addStmt->gtStmt.gtStmtExpr; |
17502 | |
17503 | if (addTree->gtOper == GT_JTRUE) |
17504 | { |
17505 | GenTree* relOp = addTree->gtOp.gtOp1; |
17506 | assert(relOp->OperIsCompare()); |
17507 | |
17508 | var_types type = genActualType(relOp->gtOp.gtOp1->TypeGet()); |
17509 | |
17510 | if (gtHasRef(relOp->gtOp.gtOp1, tempNum, false)) |
17511 | { |
17512 | unsigned temp = lvaGrabTemp(true DEBUGARG("spill addStmt JTRUE ref Op1" )); |
17513 | impAssignTempGen(temp, relOp->gtOp.gtOp1, level); |
17514 | type = genActualType(lvaTable[temp].TypeGet()); |
17515 | relOp->gtOp.gtOp1 = gtNewLclvNode(temp, type); |
17516 | } |
17517 | |
17518 | if (gtHasRef(relOp->gtOp.gtOp2, tempNum, false)) |
17519 | { |
17520 | unsigned temp = lvaGrabTemp(true DEBUGARG("spill addStmt JTRUE ref Op2" )); |
17521 | impAssignTempGen(temp, relOp->gtOp.gtOp2, level); |
17522 | type = genActualType(lvaTable[temp].TypeGet()); |
17523 | relOp->gtOp.gtOp2 = gtNewLclvNode(temp, type); |
17524 | } |
17525 | } |
17526 | else |
17527 | { |
17528 | assert(addTree->gtOper == GT_SWITCH && genActualTypeIsIntOrI(addTree->gtOp.gtOp1->TypeGet())); |
17529 | |
17530 | unsigned temp = lvaGrabTemp(true DEBUGARG("spill addStmt SWITCH" )); |
17531 | impAssignTempGen(temp, addTree->gtOp.gtOp1, level); |
17532 | addTree->gtOp.gtOp1 = gtNewLclvNode(temp, genActualType(addTree->gtOp.gtOp1->TypeGet())); |
17533 | } |
17534 | } |
17535 | |
17536 | /* Spill the stack entry, and replace with the temp */ |
17537 | |
17538 | if (!impSpillStackEntry(level, tempNum |
17539 | #ifdef DEBUG |
17540 | , |
17541 | true, "Spill Stack Entry" |
17542 | #endif |
17543 | )) |
17544 | { |
17545 | if (markImport) |
17546 | { |
17547 | BADCODE("bad stack state" ); |
17548 | } |
17549 | |
17550 | // Oops. Something went wrong when spilling. Bad code. |
17551 | verHandleVerificationFailure(block DEBUGARG(true)); |
17552 | |
17553 | goto SPILLSTACK; |
17554 | } |
17555 | } |
17556 | |
17557 | /* Put back the 'jtrue'/'switch' if we removed it earlier */ |
17558 | |
17559 | if (addStmt) |
17560 | { |
17561 | impAppendStmt(addStmt, (unsigned)CHECK_SPILL_NONE); |
17562 | } |
17563 | } |
17564 | |
17565 | // Some of the append/spill logic works on compCurBB |
17566 | |
17567 | assert(compCurBB == block); |
17568 | |
17569 | /* Save the tree list in the block */ |
17570 | impEndTreeList(block); |
17571 | |
17572 | // impEndTreeList sets BBF_IMPORTED on the block |
17573 | // We do *NOT* want to set it later than this because |
17574 | // impReimportSpillClique might clear it if this block is both a |
17575 | // predecessor and successor in the current spill clique |
17576 | assert(block->bbFlags & BBF_IMPORTED); |
17577 | |
17578 | // If we had a int/native int, or float/double collision, we need to re-import |
17579 | if (reimportSpillClique) |
17580 | { |
17581 | // This will re-import all the successors of block (as well as each of their predecessors) |
17582 | impReimportSpillClique(block); |
17583 | |
17584 | // For blocks that haven't been imported yet, we still need to mark them as pending import. |
17585 | const unsigned numSuccs = block->NumSucc(); |
17586 | for (unsigned i = 0; i < numSuccs; i++) |
17587 | { |
17588 | BasicBlock* succ = block->GetSucc(i); |
17589 | if ((succ->bbFlags & BBF_IMPORTED) == 0) |
17590 | { |
17591 | impImportBlockPending(succ); |
17592 | } |
17593 | } |
17594 | } |
17595 | else // the normal case |
17596 | { |
17597 | // otherwise just import the successors of block |
17598 | |
17599 | /* Does this block jump to any other blocks? */ |
17600 | const unsigned numSuccs = block->NumSucc(); |
17601 | for (unsigned i = 0; i < numSuccs; i++) |
17602 | { |
17603 | impImportBlockPending(block->GetSucc(i)); |
17604 | } |
17605 | } |
17606 | } |
17607 | #ifdef _PREFAST_ |
17608 | #pragma warning(pop) |
17609 | #endif |
17610 | |
17611 | /*****************************************************************************/ |
17612 | // |
17613 | // Ensures that "block" is a member of the list of BBs waiting to be imported, pushing it on the list if |
17614 | // necessary (and ensures that it is a member of the set of BB's on the list, by setting its byte in |
17615 | // impPendingBlockMembers). Merges the current verification state into the verification state of "block" |
17616 | // (its "pre-state"). |
17617 | |
17618 | void Compiler::impImportBlockPending(BasicBlock* block) |
17619 | { |
17620 | #ifdef DEBUG |
17621 | if (verbose) |
17622 | { |
17623 | printf("\nimpImportBlockPending for " FMT_BB "\n" , block->bbNum); |
17624 | } |
17625 | #endif |
17626 | |
17627 | // We will add a block to the pending set if it has not already been imported (or needs to be re-imported), |
17628 | // or if it has, but merging in a predecessor's post-state changes the block's pre-state. |
17629 | // (When we're doing verification, we always attempt the merge to detect verification errors.) |
17630 | |
17631 | // If the block has not been imported, add to pending set. |
17632 | bool addToPending = ((block->bbFlags & BBF_IMPORTED) == 0); |
17633 | |
17634 | // Initialize bbEntryState just the first time we try to add this block to the pending list |
17635 | // Just because bbEntryState is NULL, doesn't mean the pre-state wasn't previously set |
17636 | // We use NULL to indicate the 'common' state to avoid memory allocation |
17637 | if ((block->bbEntryState == nullptr) && ((block->bbFlags & (BBF_IMPORTED | BBF_FAILED_VERIFICATION)) == 0) && |
17638 | (impGetPendingBlockMember(block) == 0)) |
17639 | { |
17640 | verInitBBEntryState(block, &verCurrentState); |
17641 | assert(block->bbStkDepth == 0); |
17642 | block->bbStkDepth = static_cast<unsigned short>(verCurrentState.esStackDepth); |
17643 | assert(addToPending); |
17644 | assert(impGetPendingBlockMember(block) == 0); |
17645 | } |
17646 | else |
17647 | { |
17648 | // The stack should have the same height on entry to the block from all its predecessors. |
17649 | if (block->bbStkDepth != verCurrentState.esStackDepth) |
17650 | { |
17651 | #ifdef DEBUG |
17652 | char buffer[400]; |
17653 | sprintf_s(buffer, sizeof(buffer), |
17654 | "Block at offset %4.4x to %4.4x in %s entered with different stack depths.\n" |
17655 | "Previous depth was %d, current depth is %d" , |
17656 | block->bbCodeOffs, block->bbCodeOffsEnd, info.compFullName, block->bbStkDepth, |
17657 | verCurrentState.esStackDepth); |
17658 | buffer[400 - 1] = 0; |
17659 | NO_WAY(buffer); |
17660 | #else |
17661 | NO_WAY("Block entered with different stack depths" ); |
17662 | #endif |
17663 | } |
17664 | |
17665 | // Additionally, if we need to verify, merge the verification state. |
17666 | if (tiVerificationNeeded) |
17667 | { |
17668 | // Merge the current state into the entry state of block; if this does not change the entry state |
17669 | // by merging, do not add the block to the pending-list. |
17670 | bool changed = false; |
17671 | if (!verMergeEntryStates(block, &changed)) |
17672 | { |
17673 | block->bbFlags |= BBF_FAILED_VERIFICATION; |
17674 | addToPending = true; // We will pop it off, and check the flag set above. |
17675 | } |
17676 | else if (changed) |
17677 | { |
17678 | addToPending = true; |
17679 | |
17680 | JITDUMP("Adding " FMT_BB " to pending set due to new merge result\n" , block->bbNum); |
17681 | } |
17682 | } |
17683 | |
17684 | if (!addToPending) |
17685 | { |
17686 | return; |
17687 | } |
17688 | |
17689 | if (block->bbStkDepth > 0) |
17690 | { |
17691 | // We need to fix the types of any spill temps that might have changed: |
17692 | // int->native int, float->double, int->byref, etc. |
17693 | impRetypeEntryStateTemps(block); |
17694 | } |
17695 | |
17696 | // OK, we must add to the pending list, if it's not already in it. |
17697 | if (impGetPendingBlockMember(block) != 0) |
17698 | { |
17699 | return; |
17700 | } |
17701 | } |
17702 | |
17703 | // Get an entry to add to the pending list |
17704 | |
17705 | PendingDsc* dsc; |
17706 | |
17707 | if (impPendingFree) |
17708 | { |
17709 | // We can reuse one of the freed up dscs. |
17710 | dsc = impPendingFree; |
17711 | impPendingFree = dsc->pdNext; |
17712 | } |
17713 | else |
17714 | { |
17715 | // We have to create a new dsc |
17716 | dsc = new (this, CMK_Unknown) PendingDsc; |
17717 | } |
17718 | |
17719 | dsc->pdBB = block; |
17720 | dsc->pdSavedStack.ssDepth = verCurrentState.esStackDepth; |
17721 | dsc->pdThisPtrInit = verCurrentState.thisInitialized; |
17722 | |
17723 | // Save the stack trees for later |
17724 | |
17725 | if (verCurrentState.esStackDepth) |
17726 | { |
17727 | impSaveStackState(&dsc->pdSavedStack, false); |
17728 | } |
17729 | |
17730 | // Add the entry to the pending list |
17731 | |
17732 | dsc->pdNext = impPendingList; |
17733 | impPendingList = dsc; |
17734 | impSetPendingBlockMember(block, 1); // And indicate that it's now a member of the set. |
17735 | |
17736 | // Various assertions require us to now to consider the block as not imported (at least for |
17737 | // the final time...) |
17738 | block->bbFlags &= ~BBF_IMPORTED; |
17739 | |
17740 | #ifdef DEBUG |
17741 | if (verbose && 0) |
17742 | { |
17743 | printf("Added PendingDsc - %08p for " FMT_BB "\n" , dspPtr(dsc), block->bbNum); |
17744 | } |
17745 | #endif |
17746 | } |
17747 | |
17748 | /*****************************************************************************/ |
17749 | // |
17750 | // Ensures that "block" is a member of the list of BBs waiting to be imported, pushing it on the list if |
17751 | // necessary (and ensures that it is a member of the set of BB's on the list, by setting its byte in |
17752 | // impPendingBlockMembers). Does *NOT* change the existing "pre-state" of the block. |
17753 | |
17754 | void Compiler::impReimportBlockPending(BasicBlock* block) |
17755 | { |
17756 | JITDUMP("\nimpReimportBlockPending for " FMT_BB, block->bbNum); |
17757 | |
17758 | assert(block->bbFlags & BBF_IMPORTED); |
17759 | |
17760 | // OK, we must add to the pending list, if it's not already in it. |
17761 | if (impGetPendingBlockMember(block) != 0) |
17762 | { |
17763 | return; |
17764 | } |
17765 | |
17766 | // Get an entry to add to the pending list |
17767 | |
17768 | PendingDsc* dsc; |
17769 | |
17770 | if (impPendingFree) |
17771 | { |
17772 | // We can reuse one of the freed up dscs. |
17773 | dsc = impPendingFree; |
17774 | impPendingFree = dsc->pdNext; |
17775 | } |
17776 | else |
17777 | { |
17778 | // We have to create a new dsc |
17779 | dsc = new (this, CMK_ImpStack) PendingDsc; |
17780 | } |
17781 | |
17782 | dsc->pdBB = block; |
17783 | |
17784 | if (block->bbEntryState) |
17785 | { |
17786 | dsc->pdThisPtrInit = block->bbEntryState->thisInitialized; |
17787 | dsc->pdSavedStack.ssDepth = block->bbEntryState->esStackDepth; |
17788 | dsc->pdSavedStack.ssTrees = block->bbEntryState->esStack; |
17789 | } |
17790 | else |
17791 | { |
17792 | dsc->pdThisPtrInit = TIS_Bottom; |
17793 | dsc->pdSavedStack.ssDepth = 0; |
17794 | dsc->pdSavedStack.ssTrees = nullptr; |
17795 | } |
17796 | |
17797 | // Add the entry to the pending list |
17798 | |
17799 | dsc->pdNext = impPendingList; |
17800 | impPendingList = dsc; |
17801 | impSetPendingBlockMember(block, 1); // And indicate that it's now a member of the set. |
17802 | |
17803 | // Various assertions require us to now to consider the block as not imported (at least for |
17804 | // the final time...) |
17805 | block->bbFlags &= ~BBF_IMPORTED; |
17806 | |
17807 | #ifdef DEBUG |
17808 | if (verbose && 0) |
17809 | { |
17810 | printf("Added PendingDsc - %08p for " FMT_BB "\n" , dspPtr(dsc), block->bbNum); |
17811 | } |
17812 | #endif |
17813 | } |
17814 | |
17815 | void* Compiler::BlockListNode::operator new(size_t sz, Compiler* comp) |
17816 | { |
17817 | if (comp->impBlockListNodeFreeList == nullptr) |
17818 | { |
17819 | return comp->getAllocator(CMK_BasicBlock).allocate<BlockListNode>(1); |
17820 | } |
17821 | else |
17822 | { |
17823 | BlockListNode* res = comp->impBlockListNodeFreeList; |
17824 | comp->impBlockListNodeFreeList = res->m_next; |
17825 | return res; |
17826 | } |
17827 | } |
17828 | |
17829 | void Compiler::FreeBlockListNode(Compiler::BlockListNode* node) |
17830 | { |
17831 | node->m_next = impBlockListNodeFreeList; |
17832 | impBlockListNodeFreeList = node; |
17833 | } |
17834 | |
17835 | void Compiler::impWalkSpillCliqueFromPred(BasicBlock* block, SpillCliqueWalker* callback) |
17836 | { |
17837 | bool toDo = true; |
17838 | |
17839 | noway_assert(!fgComputePredsDone); |
17840 | if (!fgCheapPredsValid) |
17841 | { |
17842 | fgComputeCheapPreds(); |
17843 | } |
17844 | |
17845 | BlockListNode* succCliqueToDo = nullptr; |
17846 | BlockListNode* predCliqueToDo = new (this) BlockListNode(block); |
17847 | while (toDo) |
17848 | { |
17849 | toDo = false; |
17850 | // Look at the successors of every member of the predecessor to-do list. |
17851 | while (predCliqueToDo != nullptr) |
17852 | { |
17853 | BlockListNode* node = predCliqueToDo; |
17854 | predCliqueToDo = node->m_next; |
17855 | BasicBlock* blk = node->m_blk; |
17856 | FreeBlockListNode(node); |
17857 | |
17858 | const unsigned numSuccs = blk->NumSucc(); |
17859 | for (unsigned succNum = 0; succNum < numSuccs; succNum++) |
17860 | { |
17861 | BasicBlock* succ = blk->GetSucc(succNum); |
17862 | // If it's not already in the clique, add it, and also add it |
17863 | // as a member of the successor "toDo" set. |
17864 | if (impSpillCliqueGetMember(SpillCliqueSucc, succ) == 0) |
17865 | { |
17866 | callback->Visit(SpillCliqueSucc, succ); |
17867 | impSpillCliqueSetMember(SpillCliqueSucc, succ, 1); |
17868 | succCliqueToDo = new (this) BlockListNode(succ, succCliqueToDo); |
17869 | toDo = true; |
17870 | } |
17871 | } |
17872 | } |
17873 | // Look at the predecessors of every member of the successor to-do list. |
17874 | while (succCliqueToDo != nullptr) |
17875 | { |
17876 | BlockListNode* node = succCliqueToDo; |
17877 | succCliqueToDo = node->m_next; |
17878 | BasicBlock* blk = node->m_blk; |
17879 | FreeBlockListNode(node); |
17880 | |
17881 | for (BasicBlockList* pred = blk->bbCheapPreds; pred != nullptr; pred = pred->next) |
17882 | { |
17883 | BasicBlock* predBlock = pred->block; |
17884 | // If it's not already in the clique, add it, and also add it |
17885 | // as a member of the predecessor "toDo" set. |
17886 | if (impSpillCliqueGetMember(SpillCliquePred, predBlock) == 0) |
17887 | { |
17888 | callback->Visit(SpillCliquePred, predBlock); |
17889 | impSpillCliqueSetMember(SpillCliquePred, predBlock, 1); |
17890 | predCliqueToDo = new (this) BlockListNode(predBlock, predCliqueToDo); |
17891 | toDo = true; |
17892 | } |
17893 | } |
17894 | } |
17895 | } |
17896 | |
17897 | // If this fails, it means we didn't walk the spill clique properly and somehow managed |
17898 | // miss walking back to include the predecessor we started from. |
17899 | // This most likely cause: missing or out of date bbPreds |
17900 | assert(impSpillCliqueGetMember(SpillCliquePred, block) != 0); |
17901 | } |
17902 | |
17903 | void Compiler::SetSpillTempsBase::Visit(SpillCliqueDir predOrSucc, BasicBlock* blk) |
17904 | { |
17905 | if (predOrSucc == SpillCliqueSucc) |
17906 | { |
17907 | assert(blk->bbStkTempsIn == NO_BASE_TMP); // Should not already be a member of a clique as a successor. |
17908 | blk->bbStkTempsIn = m_baseTmp; |
17909 | } |
17910 | else |
17911 | { |
17912 | assert(predOrSucc == SpillCliquePred); |
17913 | assert(blk->bbStkTempsOut == NO_BASE_TMP); // Should not already be a member of a clique as a predecessor. |
17914 | blk->bbStkTempsOut = m_baseTmp; |
17915 | } |
17916 | } |
17917 | |
17918 | void Compiler::ReimportSpillClique::Visit(SpillCliqueDir predOrSucc, BasicBlock* blk) |
17919 | { |
17920 | // For Preds we could be a little smarter and just find the existing store |
17921 | // and re-type it/add a cast, but that is complicated and hopefully very rare, so |
17922 | // just re-import the whole block (just like we do for successors) |
17923 | |
17924 | if (((blk->bbFlags & BBF_IMPORTED) == 0) && (m_pComp->impGetPendingBlockMember(blk) == 0)) |
17925 | { |
17926 | // If we haven't imported this block and we're not going to (because it isn't on |
17927 | // the pending list) then just ignore it for now. |
17928 | |
17929 | // This block has either never been imported (EntryState == NULL) or it failed |
17930 | // verification. Neither state requires us to force it to be imported now. |
17931 | assert((blk->bbEntryState == nullptr) || (blk->bbFlags & BBF_FAILED_VERIFICATION)); |
17932 | return; |
17933 | } |
17934 | |
17935 | // For successors we have a valid verCurrentState, so just mark them for reimport |
17936 | // the 'normal' way |
17937 | // Unlike predecessors, we *DO* need to reimport the current block because the |
17938 | // initial import had the wrong entry state types. |
17939 | // Similarly, blocks that are currently on the pending list, still need to call |
17940 | // impImportBlockPending to fixup their entry state. |
17941 | if (predOrSucc == SpillCliqueSucc) |
17942 | { |
17943 | m_pComp->impReimportMarkBlock(blk); |
17944 | |
17945 | // Set the current stack state to that of the blk->bbEntryState |
17946 | m_pComp->verResetCurrentState(blk, &m_pComp->verCurrentState); |
17947 | assert(m_pComp->verCurrentState.thisInitialized == blk->bbThisOnEntry()); |
17948 | |
17949 | m_pComp->impImportBlockPending(blk); |
17950 | } |
17951 | else if ((blk != m_pComp->compCurBB) && ((blk->bbFlags & BBF_IMPORTED) != 0)) |
17952 | { |
17953 | // As described above, we are only visiting predecessors so they can |
17954 | // add the appropriate casts, since we have already done that for the current |
17955 | // block, it does not need to be reimported. |
17956 | // Nor do we need to reimport blocks that are still pending, but not yet |
17957 | // imported. |
17958 | // |
17959 | // For predecessors, we have no state to seed the EntryState, so we just have |
17960 | // to assume the existing one is correct. |
17961 | // If the block is also a successor, it will get the EntryState properly |
17962 | // updated when it is visited as a successor in the above "if" block. |
17963 | assert(predOrSucc == SpillCliquePred); |
17964 | m_pComp->impReimportBlockPending(blk); |
17965 | } |
17966 | } |
17967 | |
17968 | // Re-type the incoming lclVar nodes to match the varDsc. |
17969 | void Compiler::impRetypeEntryStateTemps(BasicBlock* blk) |
17970 | { |
17971 | if (blk->bbEntryState != nullptr) |
17972 | { |
17973 | EntryState* es = blk->bbEntryState; |
17974 | for (unsigned level = 0; level < es->esStackDepth; level++) |
17975 | { |
17976 | GenTree* tree = es->esStack[level].val; |
17977 | if ((tree->gtOper == GT_LCL_VAR) || (tree->gtOper == GT_LCL_FLD)) |
17978 | { |
17979 | unsigned lclNum = tree->gtLclVarCommon.gtLclNum; |
17980 | noway_assert(lclNum < lvaCount); |
17981 | LclVarDsc* varDsc = lvaTable + lclNum; |
17982 | es->esStack[level].val->gtType = varDsc->TypeGet(); |
17983 | } |
17984 | } |
17985 | } |
17986 | } |
17987 | |
17988 | unsigned Compiler::impGetSpillTmpBase(BasicBlock* block) |
17989 | { |
17990 | if (block->bbStkTempsOut != NO_BASE_TMP) |
17991 | { |
17992 | return block->bbStkTempsOut; |
17993 | } |
17994 | |
17995 | #ifdef DEBUG |
17996 | if (verbose) |
17997 | { |
17998 | printf("\n*************** In impGetSpillTmpBase(" FMT_BB ")\n" , block->bbNum); |
17999 | } |
18000 | #endif // DEBUG |
18001 | |
18002 | // Otherwise, choose one, and propagate to all members of the spill clique. |
18003 | // Grab enough temps for the whole stack. |
18004 | unsigned baseTmp = lvaGrabTemps(verCurrentState.esStackDepth DEBUGARG("IL Stack Entries" )); |
18005 | SetSpillTempsBase callback(baseTmp); |
18006 | |
18007 | // We do *NOT* need to reset the SpillClique*Members because a block can only be the predecessor |
18008 | // to one spill clique, and similarly can only be the sucessor to one spill clique |
18009 | impWalkSpillCliqueFromPred(block, &callback); |
18010 | |
18011 | return baseTmp; |
18012 | } |
18013 | |
18014 | void Compiler::impReimportSpillClique(BasicBlock* block) |
18015 | { |
18016 | #ifdef DEBUG |
18017 | if (verbose) |
18018 | { |
18019 | printf("\n*************** In impReimportSpillClique(" FMT_BB ")\n" , block->bbNum); |
18020 | } |
18021 | #endif // DEBUG |
18022 | |
18023 | // If we get here, it is because this block is already part of a spill clique |
18024 | // and one predecessor had an outgoing live stack slot of type int, and this |
18025 | // block has an outgoing live stack slot of type native int. |
18026 | // We need to reset these before traversal because they have already been set |
18027 | // by the previous walk to determine all the members of the spill clique. |
18028 | impInlineRoot()->impSpillCliquePredMembers.Reset(); |
18029 | impInlineRoot()->impSpillCliqueSuccMembers.Reset(); |
18030 | |
18031 | ReimportSpillClique callback(this); |
18032 | |
18033 | impWalkSpillCliqueFromPred(block, &callback); |
18034 | } |
18035 | |
18036 | // Set the pre-state of "block" (which should not have a pre-state allocated) to |
18037 | // a copy of "srcState", cloning tree pointers as required. |
18038 | void Compiler::verInitBBEntryState(BasicBlock* block, EntryState* srcState) |
18039 | { |
18040 | if (srcState->esStackDepth == 0 && srcState->thisInitialized == TIS_Bottom) |
18041 | { |
18042 | block->bbEntryState = nullptr; |
18043 | return; |
18044 | } |
18045 | |
18046 | block->bbEntryState = getAllocator(CMK_Unknown).allocate<EntryState>(1); |
18047 | |
18048 | // block->bbEntryState.esRefcount = 1; |
18049 | |
18050 | block->bbEntryState->esStackDepth = srcState->esStackDepth; |
18051 | block->bbEntryState->thisInitialized = TIS_Bottom; |
18052 | |
18053 | if (srcState->esStackDepth > 0) |
18054 | { |
18055 | block->bbSetStack(new (this, CMK_Unknown) StackEntry[srcState->esStackDepth]); |
18056 | unsigned stackSize = srcState->esStackDepth * sizeof(StackEntry); |
18057 | |
18058 | memcpy(block->bbEntryState->esStack, srcState->esStack, stackSize); |
18059 | for (unsigned level = 0; level < srcState->esStackDepth; level++) |
18060 | { |
18061 | GenTree* tree = srcState->esStack[level].val; |
18062 | block->bbEntryState->esStack[level].val = gtCloneExpr(tree); |
18063 | } |
18064 | } |
18065 | |
18066 | if (verTrackObjCtorInitState) |
18067 | { |
18068 | verSetThisInit(block, srcState->thisInitialized); |
18069 | } |
18070 | |
18071 | return; |
18072 | } |
18073 | |
18074 | void Compiler::verSetThisInit(BasicBlock* block, ThisInitState tis) |
18075 | { |
18076 | assert(tis != TIS_Bottom); // Precondition. |
18077 | if (block->bbEntryState == nullptr) |
18078 | { |
18079 | block->bbEntryState = new (this, CMK_Unknown) EntryState(); |
18080 | } |
18081 | |
18082 | block->bbEntryState->thisInitialized = tis; |
18083 | } |
18084 | |
18085 | /* |
18086 | * Resets the current state to the state at the start of the basic block |
18087 | */ |
18088 | void Compiler::verResetCurrentState(BasicBlock* block, EntryState* destState) |
18089 | { |
18090 | |
18091 | if (block->bbEntryState == nullptr) |
18092 | { |
18093 | destState->esStackDepth = 0; |
18094 | destState->thisInitialized = TIS_Bottom; |
18095 | return; |
18096 | } |
18097 | |
18098 | destState->esStackDepth = block->bbEntryState->esStackDepth; |
18099 | |
18100 | if (destState->esStackDepth > 0) |
18101 | { |
18102 | unsigned stackSize = destState->esStackDepth * sizeof(StackEntry); |
18103 | |
18104 | memcpy(destState->esStack, block->bbStackOnEntry(), stackSize); |
18105 | } |
18106 | |
18107 | destState->thisInitialized = block->bbThisOnEntry(); |
18108 | |
18109 | return; |
18110 | } |
18111 | |
18112 | ThisInitState BasicBlock::bbThisOnEntry() |
18113 | { |
18114 | return bbEntryState ? bbEntryState->thisInitialized : TIS_Bottom; |
18115 | } |
18116 | |
18117 | unsigned BasicBlock::bbStackDepthOnEntry() |
18118 | { |
18119 | return (bbEntryState ? bbEntryState->esStackDepth : 0); |
18120 | } |
18121 | |
18122 | void BasicBlock::bbSetStack(void* stackBuffer) |
18123 | { |
18124 | assert(bbEntryState); |
18125 | assert(stackBuffer); |
18126 | bbEntryState->esStack = (StackEntry*)stackBuffer; |
18127 | } |
18128 | |
18129 | StackEntry* BasicBlock::bbStackOnEntry() |
18130 | { |
18131 | assert(bbEntryState); |
18132 | return bbEntryState->esStack; |
18133 | } |
18134 | |
18135 | void Compiler::verInitCurrentState() |
18136 | { |
18137 | verTrackObjCtorInitState = FALSE; |
18138 | verCurrentState.thisInitialized = TIS_Bottom; |
18139 | |
18140 | if (tiVerificationNeeded) |
18141 | { |
18142 | // Track this ptr initialization |
18143 | if (!info.compIsStatic && (info.compFlags & CORINFO_FLG_CONSTRUCTOR) && lvaTable[0].lvVerTypeInfo.IsObjRef()) |
18144 | { |
18145 | verTrackObjCtorInitState = TRUE; |
18146 | verCurrentState.thisInitialized = TIS_Uninit; |
18147 | } |
18148 | } |
18149 | |
18150 | // initialize stack info |
18151 | |
18152 | verCurrentState.esStackDepth = 0; |
18153 | assert(verCurrentState.esStack != nullptr); |
18154 | |
18155 | // copy current state to entry state of first BB |
18156 | verInitBBEntryState(fgFirstBB, &verCurrentState); |
18157 | } |
18158 | |
18159 | Compiler* Compiler::impInlineRoot() |
18160 | { |
18161 | if (impInlineInfo == nullptr) |
18162 | { |
18163 | return this; |
18164 | } |
18165 | else |
18166 | { |
18167 | return impInlineInfo->InlineRoot; |
18168 | } |
18169 | } |
18170 | |
18171 | BYTE Compiler::impSpillCliqueGetMember(SpillCliqueDir predOrSucc, BasicBlock* blk) |
18172 | { |
18173 | if (predOrSucc == SpillCliquePred) |
18174 | { |
18175 | return impInlineRoot()->impSpillCliquePredMembers.Get(blk->bbInd()); |
18176 | } |
18177 | else |
18178 | { |
18179 | assert(predOrSucc == SpillCliqueSucc); |
18180 | return impInlineRoot()->impSpillCliqueSuccMembers.Get(blk->bbInd()); |
18181 | } |
18182 | } |
18183 | |
18184 | void Compiler::impSpillCliqueSetMember(SpillCliqueDir predOrSucc, BasicBlock* blk, BYTE val) |
18185 | { |
18186 | if (predOrSucc == SpillCliquePred) |
18187 | { |
18188 | impInlineRoot()->impSpillCliquePredMembers.Set(blk->bbInd(), val); |
18189 | } |
18190 | else |
18191 | { |
18192 | assert(predOrSucc == SpillCliqueSucc); |
18193 | impInlineRoot()->impSpillCliqueSuccMembers.Set(blk->bbInd(), val); |
18194 | } |
18195 | } |
18196 | |
18197 | /***************************************************************************** |
18198 | * |
18199 | * Convert the instrs ("import") into our internal format (trees). The |
18200 | * basic flowgraph has already been constructed and is passed in. |
18201 | */ |
18202 | |
18203 | void Compiler::impImport(BasicBlock* method) |
18204 | { |
18205 | #ifdef DEBUG |
18206 | if (verbose) |
18207 | { |
18208 | printf("*************** In impImport() for %s\n" , info.compFullName); |
18209 | } |
18210 | #endif |
18211 | |
18212 | Compiler* inlineRoot = impInlineRoot(); |
18213 | |
18214 | if (info.compMaxStack <= SMALL_STACK_SIZE) |
18215 | { |
18216 | impStkSize = SMALL_STACK_SIZE; |
18217 | } |
18218 | else |
18219 | { |
18220 | impStkSize = info.compMaxStack; |
18221 | } |
18222 | |
18223 | if (this == inlineRoot) |
18224 | { |
18225 | // Allocate the stack contents |
18226 | verCurrentState.esStack = new (this, CMK_ImpStack) StackEntry[impStkSize]; |
18227 | } |
18228 | else |
18229 | { |
18230 | // This is the inlinee compiler, steal the stack from the inliner compiler |
18231 | // (after ensuring that it is large enough). |
18232 | if (inlineRoot->impStkSize < impStkSize) |
18233 | { |
18234 | inlineRoot->impStkSize = impStkSize; |
18235 | inlineRoot->verCurrentState.esStack = new (this, CMK_ImpStack) StackEntry[impStkSize]; |
18236 | } |
18237 | |
18238 | verCurrentState.esStack = inlineRoot->verCurrentState.esStack; |
18239 | } |
18240 | |
18241 | // initialize the entry state at start of method |
18242 | verInitCurrentState(); |
18243 | |
18244 | // Initialize stuff related to figuring "spill cliques" (see spec comment for impGetSpillTmpBase). |
18245 | if (this == inlineRoot) // These are only used on the root of the inlining tree. |
18246 | { |
18247 | // We have initialized these previously, but to size 0. Make them larger. |
18248 | impPendingBlockMembers.Init(getAllocator(), fgBBNumMax * 2); |
18249 | impSpillCliquePredMembers.Init(getAllocator(), fgBBNumMax * 2); |
18250 | impSpillCliqueSuccMembers.Init(getAllocator(), fgBBNumMax * 2); |
18251 | } |
18252 | inlineRoot->impPendingBlockMembers.Reset(fgBBNumMax * 2); |
18253 | inlineRoot->impSpillCliquePredMembers.Reset(fgBBNumMax * 2); |
18254 | inlineRoot->impSpillCliqueSuccMembers.Reset(fgBBNumMax * 2); |
18255 | impBlockListNodeFreeList = nullptr; |
18256 | |
18257 | #ifdef DEBUG |
18258 | impLastILoffsStmt = nullptr; |
18259 | impNestedStackSpill = false; |
18260 | #endif |
18261 | impBoxTemp = BAD_VAR_NUM; |
18262 | |
18263 | impPendingList = impPendingFree = nullptr; |
18264 | |
18265 | /* Add the entry-point to the worker-list */ |
18266 | |
18267 | // Skip leading internal blocks. There can be one as a leading scratch BB, and more |
18268 | // from EH normalization. |
18269 | // NOTE: It might be possible to always just put fgFirstBB on the pending list, and let everything else just fall |
18270 | // out. |
18271 | for (; method->bbFlags & BBF_INTERNAL; method = method->bbNext) |
18272 | { |
18273 | // Treat these as imported. |
18274 | assert(method->bbJumpKind == BBJ_NONE); // We assume all the leading ones are fallthrough. |
18275 | JITDUMP("Marking leading BBF_INTERNAL block " FMT_BB " as BBF_IMPORTED\n" , method->bbNum); |
18276 | method->bbFlags |= BBF_IMPORTED; |
18277 | } |
18278 | |
18279 | impImportBlockPending(method); |
18280 | |
18281 | /* Import blocks in the worker-list until there are no more */ |
18282 | |
18283 | while (impPendingList) |
18284 | { |
18285 | /* Remove the entry at the front of the list */ |
18286 | |
18287 | PendingDsc* dsc = impPendingList; |
18288 | impPendingList = impPendingList->pdNext; |
18289 | impSetPendingBlockMember(dsc->pdBB, 0); |
18290 | |
18291 | /* Restore the stack state */ |
18292 | |
18293 | verCurrentState.thisInitialized = dsc->pdThisPtrInit; |
18294 | verCurrentState.esStackDepth = dsc->pdSavedStack.ssDepth; |
18295 | if (verCurrentState.esStackDepth) |
18296 | { |
18297 | impRestoreStackState(&dsc->pdSavedStack); |
18298 | } |
18299 | |
18300 | /* Add the entry to the free list for reuse */ |
18301 | |
18302 | dsc->pdNext = impPendingFree; |
18303 | impPendingFree = dsc; |
18304 | |
18305 | /* Now import the block */ |
18306 | |
18307 | if (dsc->pdBB->bbFlags & BBF_FAILED_VERIFICATION) |
18308 | { |
18309 | |
18310 | #ifdef _TARGET_64BIT_ |
18311 | // On AMD64, during verification we have to match JIT64 behavior since the VM is very tighly |
18312 | // coupled with the JIT64 IL Verification logic. Look inside verHandleVerificationFailure |
18313 | // method for further explanation on why we raise this exception instead of making the jitted |
18314 | // code throw the verification exception during execution. |
18315 | if (tiVerificationNeeded && opts.jitFlags->IsSet(JitFlags::JIT_FLAG_IMPORT_ONLY)) |
18316 | { |
18317 | BADCODE("Basic block marked as not verifiable" ); |
18318 | } |
18319 | else |
18320 | #endif // _TARGET_64BIT_ |
18321 | { |
18322 | verConvertBBToThrowVerificationException(dsc->pdBB DEBUGARG(true)); |
18323 | impEndTreeList(dsc->pdBB); |
18324 | } |
18325 | } |
18326 | else |
18327 | { |
18328 | impImportBlock(dsc->pdBB); |
18329 | |
18330 | if (compDonotInline()) |
18331 | { |
18332 | return; |
18333 | } |
18334 | if (compIsForImportOnly() && !tiVerificationNeeded) |
18335 | { |
18336 | return; |
18337 | } |
18338 | } |
18339 | } |
18340 | |
18341 | #ifdef DEBUG |
18342 | if (verbose && info.compXcptnsCount) |
18343 | { |
18344 | printf("\nAfter impImport() added block for try,catch,finally" ); |
18345 | fgDispBasicBlocks(); |
18346 | printf("\n" ); |
18347 | } |
18348 | |
18349 | // Used in impImportBlockPending() for STRESS_CHK_REIMPORT |
18350 | for (BasicBlock* block = fgFirstBB; block; block = block->bbNext) |
18351 | { |
18352 | block->bbFlags &= ~BBF_VISITED; |
18353 | } |
18354 | #endif |
18355 | |
18356 | assert(!compIsForInlining() || !tiVerificationNeeded); |
18357 | } |
18358 | |
18359 | // Checks if a typeinfo (usually stored in the type stack) is a struct. |
18360 | // The invariant here is that if it's not a ref or a method and has a class handle |
18361 | // it's a valuetype |
18362 | bool Compiler::impIsValueType(typeInfo* pTypeInfo) |
18363 | { |
18364 | if (pTypeInfo && pTypeInfo->IsValueClassWithClsHnd()) |
18365 | { |
18366 | return true; |
18367 | } |
18368 | else |
18369 | { |
18370 | return false; |
18371 | } |
18372 | } |
18373 | |
18374 | /***************************************************************************** |
18375 | * Check to see if the tree is the address of a local or |
18376 | the address of a field in a local. |
18377 | |
18378 | *lclVarTreeOut will contain the GT_LCL_VAR tree when it returns TRUE. |
18379 | |
18380 | */ |
18381 | |
18382 | BOOL Compiler::impIsAddressInLocal(GenTree* tree, GenTree** lclVarTreeOut) |
18383 | { |
18384 | if (tree->gtOper != GT_ADDR) |
18385 | { |
18386 | return FALSE; |
18387 | } |
18388 | |
18389 | GenTree* op = tree->gtOp.gtOp1; |
18390 | while (op->gtOper == GT_FIELD) |
18391 | { |
18392 | op = op->gtField.gtFldObj; |
18393 | if (op && op->gtOper == GT_ADDR) // Skip static fields where op will be NULL. |
18394 | { |
18395 | op = op->gtOp.gtOp1; |
18396 | } |
18397 | else |
18398 | { |
18399 | return false; |
18400 | } |
18401 | } |
18402 | |
18403 | if (op->gtOper == GT_LCL_VAR) |
18404 | { |
18405 | *lclVarTreeOut = op; |
18406 | return TRUE; |
18407 | } |
18408 | else |
18409 | { |
18410 | return FALSE; |
18411 | } |
18412 | } |
18413 | |
18414 | //------------------------------------------------------------------------ |
18415 | // impMakeDiscretionaryInlineObservations: make observations that help |
18416 | // determine the profitability of a discretionary inline |
18417 | // |
18418 | // Arguments: |
18419 | // pInlineInfo -- InlineInfo for the inline, or null for the prejit root |
18420 | // inlineResult -- InlineResult accumulating information about this inline |
18421 | // |
18422 | // Notes: |
18423 | // If inlining or prejitting the root, this method also makes |
18424 | // various observations about the method that factor into inline |
18425 | // decisions. It sets `compNativeSizeEstimate` as a side effect. |
18426 | |
18427 | void Compiler::impMakeDiscretionaryInlineObservations(InlineInfo* pInlineInfo, InlineResult* inlineResult) |
18428 | { |
18429 | assert(pInlineInfo != nullptr && compIsForInlining() || // Perform the actual inlining. |
18430 | pInlineInfo == nullptr && !compIsForInlining() // Calculate the static inlining hint for ngen. |
18431 | ); |
18432 | |
18433 | // If we're really inlining, we should just have one result in play. |
18434 | assert((pInlineInfo == nullptr) || (inlineResult == pInlineInfo->inlineResult)); |
18435 | |
18436 | // If this is a "forceinline" method, the JIT probably shouldn't have gone |
18437 | // to the trouble of estimating the native code size. Even if it did, it |
18438 | // shouldn't be relying on the result of this method. |
18439 | assert(inlineResult->GetObservation() == InlineObservation::CALLEE_IS_DISCRETIONARY_INLINE); |
18440 | |
18441 | // Note if the caller contains NEWOBJ or NEWARR. |
18442 | Compiler* rootCompiler = impInlineRoot(); |
18443 | |
18444 | if ((rootCompiler->optMethodFlags & OMF_HAS_NEWARRAY) != 0) |
18445 | { |
18446 | inlineResult->Note(InlineObservation::CALLER_HAS_NEWARRAY); |
18447 | } |
18448 | |
18449 | if ((rootCompiler->optMethodFlags & OMF_HAS_NEWOBJ) != 0) |
18450 | { |
18451 | inlineResult->Note(InlineObservation::CALLER_HAS_NEWOBJ); |
18452 | } |
18453 | |
18454 | bool calleeIsStatic = (info.compFlags & CORINFO_FLG_STATIC) != 0; |
18455 | bool isSpecialMethod = (info.compFlags & CORINFO_FLG_CONSTRUCTOR) != 0; |
18456 | |
18457 | if (isSpecialMethod) |
18458 | { |
18459 | if (calleeIsStatic) |
18460 | { |
18461 | inlineResult->Note(InlineObservation::CALLEE_IS_CLASS_CTOR); |
18462 | } |
18463 | else |
18464 | { |
18465 | inlineResult->Note(InlineObservation::CALLEE_IS_INSTANCE_CTOR); |
18466 | } |
18467 | } |
18468 | else if (!calleeIsStatic) |
18469 | { |
18470 | // Callee is an instance method. |
18471 | // |
18472 | // Check if the callee has the same 'this' as the root. |
18473 | if (pInlineInfo != nullptr) |
18474 | { |
18475 | GenTree* thisArg = pInlineInfo->iciCall->gtCall.gtCallObjp; |
18476 | assert(thisArg); |
18477 | bool isSameThis = impIsThis(thisArg); |
18478 | inlineResult->NoteBool(InlineObservation::CALLSITE_IS_SAME_THIS, isSameThis); |
18479 | } |
18480 | } |
18481 | |
18482 | // Note if the callee's class is a promotable struct |
18483 | if ((info.compClassAttr & CORINFO_FLG_VALUECLASS) != 0) |
18484 | { |
18485 | assert(structPromotionHelper != nullptr); |
18486 | if (structPromotionHelper->CanPromoteStructType(info.compClassHnd)) |
18487 | { |
18488 | inlineResult->Note(InlineObservation::CALLEE_CLASS_PROMOTABLE); |
18489 | } |
18490 | } |
18491 | |
18492 | #ifdef FEATURE_SIMD |
18493 | |
18494 | // Note if this method is has SIMD args or return value |
18495 | if (pInlineInfo != nullptr && pInlineInfo->hasSIMDTypeArgLocalOrReturn) |
18496 | { |
18497 | inlineResult->Note(InlineObservation::CALLEE_HAS_SIMD); |
18498 | } |
18499 | |
18500 | #endif // FEATURE_SIMD |
18501 | |
18502 | // Roughly classify callsite frequency. |
18503 | InlineCallsiteFrequency frequency = InlineCallsiteFrequency::UNUSED; |
18504 | |
18505 | // If this is a prejit root, or a maximally hot block... |
18506 | if ((pInlineInfo == nullptr) || (pInlineInfo->iciBlock->bbWeight >= BB_MAX_WEIGHT)) |
18507 | { |
18508 | frequency = InlineCallsiteFrequency::HOT; |
18509 | } |
18510 | // No training data. Look for loop-like things. |
18511 | // We consider a recursive call loop-like. Do not give the inlining boost to the method itself. |
18512 | // However, give it to things nearby. |
18513 | else if ((pInlineInfo->iciBlock->bbFlags & BBF_BACKWARD_JUMP) && |
18514 | (pInlineInfo->fncHandle != pInlineInfo->inlineCandidateInfo->ilCallerHandle)) |
18515 | { |
18516 | frequency = InlineCallsiteFrequency::LOOP; |
18517 | } |
18518 | else if (pInlineInfo->iciBlock->hasProfileWeight() && (pInlineInfo->iciBlock->bbWeight > BB_ZERO_WEIGHT)) |
18519 | { |
18520 | frequency = InlineCallsiteFrequency::WARM; |
18521 | } |
18522 | // Now modify the multiplier based on where we're called from. |
18523 | else if (pInlineInfo->iciBlock->isRunRarely() || ((info.compFlags & FLG_CCTOR) == FLG_CCTOR)) |
18524 | { |
18525 | frequency = InlineCallsiteFrequency::RARE; |
18526 | } |
18527 | else |
18528 | { |
18529 | frequency = InlineCallsiteFrequency::BORING; |
18530 | } |
18531 | |
18532 | // Also capture the block weight of the call site. In the prejit |
18533 | // root case, assume there's some hot call site for this method. |
18534 | unsigned weight = 0; |
18535 | |
18536 | if (pInlineInfo != nullptr) |
18537 | { |
18538 | weight = pInlineInfo->iciBlock->bbWeight; |
18539 | } |
18540 | else |
18541 | { |
18542 | weight = BB_MAX_WEIGHT; |
18543 | } |
18544 | |
18545 | inlineResult->NoteInt(InlineObservation::CALLSITE_FREQUENCY, static_cast<int>(frequency)); |
18546 | inlineResult->NoteInt(InlineObservation::CALLSITE_WEIGHT, static_cast<int>(weight)); |
18547 | } |
18548 | |
18549 | /***************************************************************************** |
18550 | This method makes STATIC inlining decision based on the IL code. |
18551 | It should not make any inlining decision based on the context. |
18552 | If forceInline is true, then the inlining decision should not depend on |
18553 | performance heuristics (code size, etc.). |
18554 | */ |
18555 | |
18556 | void Compiler::impCanInlineIL(CORINFO_METHOD_HANDLE fncHandle, |
18557 | CORINFO_METHOD_INFO* methInfo, |
18558 | bool forceInline, |
18559 | InlineResult* inlineResult) |
18560 | { |
18561 | unsigned codeSize = methInfo->ILCodeSize; |
18562 | |
18563 | // We shouldn't have made up our minds yet... |
18564 | assert(!inlineResult->IsDecided()); |
18565 | |
18566 | if (methInfo->EHcount) |
18567 | { |
18568 | inlineResult->NoteFatal(InlineObservation::CALLEE_HAS_EH); |
18569 | return; |
18570 | } |
18571 | |
18572 | if ((methInfo->ILCode == nullptr) || (codeSize == 0)) |
18573 | { |
18574 | inlineResult->NoteFatal(InlineObservation::CALLEE_HAS_NO_BODY); |
18575 | return; |
18576 | } |
18577 | |
18578 | // For now we don't inline varargs (import code can't handle it) |
18579 | |
18580 | if (methInfo->args.isVarArg()) |
18581 | { |
18582 | inlineResult->NoteFatal(InlineObservation::CALLEE_HAS_MANAGED_VARARGS); |
18583 | return; |
18584 | } |
18585 | |
18586 | // Reject if it has too many locals. |
18587 | // This is currently an implementation limit due to fixed-size arrays in the |
18588 | // inline info, rather than a performance heuristic. |
18589 | |
18590 | inlineResult->NoteInt(InlineObservation::CALLEE_NUMBER_OF_LOCALS, methInfo->locals.numArgs); |
18591 | |
18592 | if (methInfo->locals.numArgs > MAX_INL_LCLS) |
18593 | { |
18594 | inlineResult->NoteFatal(InlineObservation::CALLEE_TOO_MANY_LOCALS); |
18595 | return; |
18596 | } |
18597 | |
18598 | // Make sure there aren't too many arguments. |
18599 | // This is currently an implementation limit due to fixed-size arrays in the |
18600 | // inline info, rather than a performance heuristic. |
18601 | |
18602 | inlineResult->NoteInt(InlineObservation::CALLEE_NUMBER_OF_ARGUMENTS, methInfo->args.numArgs); |
18603 | |
18604 | if (methInfo->args.numArgs > MAX_INL_ARGS) |
18605 | { |
18606 | inlineResult->NoteFatal(InlineObservation::CALLEE_TOO_MANY_ARGUMENTS); |
18607 | return; |
18608 | } |
18609 | |
18610 | // Note force inline state |
18611 | |
18612 | inlineResult->NoteBool(InlineObservation::CALLEE_IS_FORCE_INLINE, forceInline); |
18613 | |
18614 | // Note IL code size |
18615 | |
18616 | inlineResult->NoteInt(InlineObservation::CALLEE_IL_CODE_SIZE, codeSize); |
18617 | |
18618 | if (inlineResult->IsFailure()) |
18619 | { |
18620 | return; |
18621 | } |
18622 | |
18623 | // Make sure maxstack is not too big |
18624 | |
18625 | inlineResult->NoteInt(InlineObservation::CALLEE_MAXSTACK, methInfo->maxStack); |
18626 | |
18627 | if (inlineResult->IsFailure()) |
18628 | { |
18629 | return; |
18630 | } |
18631 | } |
18632 | |
18633 | /***************************************************************************** |
18634 | */ |
18635 | |
18636 | void Compiler::impCheckCanInline(GenTreeCall* call, |
18637 | CORINFO_METHOD_HANDLE fncHandle, |
18638 | unsigned methAttr, |
18639 | CORINFO_CONTEXT_HANDLE exactContextHnd, |
18640 | InlineCandidateInfo** ppInlineCandidateInfo, |
18641 | InlineResult* inlineResult) |
18642 | { |
18643 | // Either EE or JIT might throw exceptions below. |
18644 | // If that happens, just don't inline the method. |
18645 | |
18646 | struct Param |
18647 | { |
18648 | Compiler* pThis; |
18649 | GenTreeCall* call; |
18650 | CORINFO_METHOD_HANDLE fncHandle; |
18651 | unsigned methAttr; |
18652 | CORINFO_CONTEXT_HANDLE exactContextHnd; |
18653 | InlineResult* result; |
18654 | InlineCandidateInfo** ppInlineCandidateInfo; |
18655 | } param; |
18656 | memset(¶m, 0, sizeof(param)); |
18657 | |
18658 | param.pThis = this; |
18659 | param.call = call; |
18660 | param.fncHandle = fncHandle; |
18661 | param.methAttr = methAttr; |
18662 | param.exactContextHnd = (exactContextHnd != nullptr) ? exactContextHnd : MAKE_METHODCONTEXT(fncHandle); |
18663 | param.result = inlineResult; |
18664 | param.ppInlineCandidateInfo = ppInlineCandidateInfo; |
18665 | |
18666 | bool success = eeRunWithErrorTrap<Param>( |
18667 | [](Param* pParam) { |
18668 | DWORD dwRestrictions = 0; |
18669 | CorInfoInitClassResult initClassResult; |
18670 | |
18671 | #ifdef DEBUG |
18672 | const char* methodName; |
18673 | const char* className; |
18674 | methodName = pParam->pThis->eeGetMethodName(pParam->fncHandle, &className); |
18675 | |
18676 | if (JitConfig.JitNoInline()) |
18677 | { |
18678 | pParam->result->NoteFatal(InlineObservation::CALLEE_IS_JIT_NOINLINE); |
18679 | goto _exit; |
18680 | } |
18681 | #endif |
18682 | |
18683 | /* Try to get the code address/size for the method */ |
18684 | |
18685 | CORINFO_METHOD_INFO methInfo; |
18686 | if (!pParam->pThis->info.compCompHnd->getMethodInfo(pParam->fncHandle, &methInfo)) |
18687 | { |
18688 | pParam->result->NoteFatal(InlineObservation::CALLEE_NO_METHOD_INFO); |
18689 | goto _exit; |
18690 | } |
18691 | |
18692 | bool forceInline; |
18693 | forceInline = !!(pParam->methAttr & CORINFO_FLG_FORCEINLINE); |
18694 | |
18695 | pParam->pThis->impCanInlineIL(pParam->fncHandle, &methInfo, forceInline, pParam->result); |
18696 | |
18697 | if (pParam->result->IsFailure()) |
18698 | { |
18699 | assert(pParam->result->IsNever()); |
18700 | goto _exit; |
18701 | } |
18702 | |
18703 | // Speculatively check if initClass() can be done. |
18704 | // If it can be done, we will try to inline the method. If inlining |
18705 | // succeeds, then we will do the non-speculative initClass() and commit it. |
18706 | // If this speculative call to initClass() fails, there is no point |
18707 | // trying to inline this method. |
18708 | initClassResult = |
18709 | pParam->pThis->info.compCompHnd->initClass(nullptr /* field */, pParam->fncHandle /* method */, |
18710 | pParam->exactContextHnd /* context */, |
18711 | TRUE /* speculative */); |
18712 | |
18713 | if (initClassResult & CORINFO_INITCLASS_DONT_INLINE) |
18714 | { |
18715 | pParam->result->NoteFatal(InlineObservation::CALLSITE_CLASS_INIT_FAILURE_SPEC); |
18716 | goto _exit; |
18717 | } |
18718 | |
18719 | // Given the EE the final say in whether to inline or not. |
18720 | // This should be last since for verifiable code, this can be expensive |
18721 | |
18722 | /* VM Inline check also ensures that the method is verifiable if needed */ |
18723 | CorInfoInline vmResult; |
18724 | vmResult = pParam->pThis->info.compCompHnd->canInline(pParam->pThis->info.compMethodHnd, pParam->fncHandle, |
18725 | &dwRestrictions); |
18726 | |
18727 | if (vmResult == INLINE_FAIL) |
18728 | { |
18729 | pParam->result->NoteFatal(InlineObservation::CALLSITE_IS_VM_NOINLINE); |
18730 | } |
18731 | else if (vmResult == INLINE_NEVER) |
18732 | { |
18733 | pParam->result->NoteFatal(InlineObservation::CALLEE_IS_VM_NOINLINE); |
18734 | } |
18735 | |
18736 | if (pParam->result->IsFailure()) |
18737 | { |
18738 | // Make sure not to report this one. It was already reported by the VM. |
18739 | pParam->result->SetReported(); |
18740 | goto _exit; |
18741 | } |
18742 | |
18743 | // check for unsupported inlining restrictions |
18744 | assert((dwRestrictions & ~(INLINE_RESPECT_BOUNDARY | INLINE_NO_CALLEE_LDSTR | INLINE_SAME_THIS)) == 0); |
18745 | |
18746 | if (dwRestrictions & INLINE_SAME_THIS) |
18747 | { |
18748 | GenTree* thisArg = pParam->call->gtCall.gtCallObjp; |
18749 | assert(thisArg); |
18750 | |
18751 | if (!pParam->pThis->impIsThis(thisArg)) |
18752 | { |
18753 | pParam->result->NoteFatal(InlineObservation::CALLSITE_REQUIRES_SAME_THIS); |
18754 | goto _exit; |
18755 | } |
18756 | } |
18757 | |
18758 | /* Get the method properties */ |
18759 | |
18760 | CORINFO_CLASS_HANDLE clsHandle; |
18761 | clsHandle = pParam->pThis->info.compCompHnd->getMethodClass(pParam->fncHandle); |
18762 | unsigned clsAttr; |
18763 | clsAttr = pParam->pThis->info.compCompHnd->getClassAttribs(clsHandle); |
18764 | |
18765 | /* Get the return type */ |
18766 | |
18767 | var_types fncRetType; |
18768 | fncRetType = pParam->call->TypeGet(); |
18769 | |
18770 | #ifdef DEBUG |
18771 | var_types fncRealRetType; |
18772 | fncRealRetType = JITtype2varType(methInfo.args.retType); |
18773 | |
18774 | assert((genActualType(fncRealRetType) == genActualType(fncRetType)) || |
18775 | // <BUGNUM> VSW 288602 </BUGNUM> |
18776 | // In case of IJW, we allow to assign a native pointer to a BYREF. |
18777 | (fncRetType == TYP_BYREF && methInfo.args.retType == CORINFO_TYPE_PTR) || |
18778 | (varTypeIsStruct(fncRetType) && (fncRealRetType == TYP_STRUCT))); |
18779 | #endif |
18780 | |
18781 | // Allocate an InlineCandidateInfo structure, |
18782 | // |
18783 | // Or, reuse the existing GuardedDevirtualizationCandidateInfo, |
18784 | // which was pre-allocated to have extra room. |
18785 | // |
18786 | InlineCandidateInfo* pInfo; |
18787 | |
18788 | if (pParam->call->IsGuardedDevirtualizationCandidate()) |
18789 | { |
18790 | pInfo = pParam->call->gtInlineCandidateInfo; |
18791 | } |
18792 | else |
18793 | { |
18794 | pInfo = new (pParam->pThis, CMK_Inlining) InlineCandidateInfo; |
18795 | |
18796 | // Null out bits we don't use when we're just inlining |
18797 | pInfo->guardedClassHandle = nullptr; |
18798 | pInfo->guardedMethodHandle = nullptr; |
18799 | pInfo->stubAddr = nullptr; |
18800 | } |
18801 | |
18802 | pInfo->methInfo = methInfo; |
18803 | pInfo->ilCallerHandle = pParam->pThis->info.compMethodHnd; |
18804 | pInfo->clsHandle = clsHandle; |
18805 | pInfo->exactContextHnd = pParam->exactContextHnd; |
18806 | pInfo->retExpr = nullptr; |
18807 | pInfo->dwRestrictions = dwRestrictions; |
18808 | pInfo->preexistingSpillTemp = BAD_VAR_NUM; |
18809 | pInfo->clsAttr = clsAttr; |
18810 | pInfo->methAttr = pParam->methAttr; |
18811 | pInfo->initClassResult = initClassResult; |
18812 | pInfo->fncRetType = fncRetType; |
18813 | pInfo->exactContextNeedsRuntimeLookup = false; |
18814 | |
18815 | // Note exactContextNeedsRuntimeLookup is reset later on, |
18816 | // over in impMarkInlineCandidate. |
18817 | |
18818 | *(pParam->ppInlineCandidateInfo) = pInfo; |
18819 | |
18820 | _exit:; |
18821 | }, |
18822 | ¶m); |
18823 | if (!success) |
18824 | { |
18825 | param.result->NoteFatal(InlineObservation::CALLSITE_COMPILATION_ERROR); |
18826 | } |
18827 | } |
18828 | |
18829 | //------------------------------------------------------------------------ |
18830 | // impInlineRecordArgInfo: record information about an inline candidate argument |
18831 | // |
18832 | // Arguments: |
18833 | // pInlineInfo - inline info for the inline candidate |
18834 | // curArgVal - tree for the caller actual argument value |
18835 | // argNum - logical index of this argument |
18836 | // inlineResult - result of ongoing inline evaluation |
18837 | // |
18838 | // Notes: |
18839 | // |
18840 | // Checks for various inline blocking conditions and makes notes in |
18841 | // the inline info arg table about the properties of the actual. These |
18842 | // properties are used later by impFetchArg to determine how best to |
18843 | // pass the argument into the inlinee. |
18844 | |
18845 | void Compiler::impInlineRecordArgInfo(InlineInfo* pInlineInfo, |
18846 | GenTree* curArgVal, |
18847 | unsigned argNum, |
18848 | InlineResult* inlineResult) |
18849 | { |
18850 | InlArgInfo* inlCurArgInfo = &pInlineInfo->inlArgInfo[argNum]; |
18851 | |
18852 | if (curArgVal->gtOper == GT_MKREFANY) |
18853 | { |
18854 | inlineResult->NoteFatal(InlineObservation::CALLSITE_ARG_IS_MKREFANY); |
18855 | return; |
18856 | } |
18857 | |
18858 | inlCurArgInfo->argNode = curArgVal; |
18859 | |
18860 | GenTree* lclVarTree; |
18861 | if (impIsAddressInLocal(curArgVal, &lclVarTree) && varTypeIsStruct(lclVarTree)) |
18862 | { |
18863 | inlCurArgInfo->argIsByRefToStructLocal = true; |
18864 | #ifdef FEATURE_SIMD |
18865 | if (lvaTable[lclVarTree->AsLclVarCommon()->gtLclNum].lvSIMDType) |
18866 | { |
18867 | pInlineInfo->hasSIMDTypeArgLocalOrReturn = true; |
18868 | } |
18869 | #endif // FEATURE_SIMD |
18870 | } |
18871 | |
18872 | if (curArgVal->gtFlags & GTF_ALL_EFFECT) |
18873 | { |
18874 | inlCurArgInfo->argHasGlobRef = (curArgVal->gtFlags & GTF_GLOB_REF) != 0; |
18875 | inlCurArgInfo->argHasSideEff = (curArgVal->gtFlags & (GTF_ALL_EFFECT & ~GTF_GLOB_REF)) != 0; |
18876 | } |
18877 | |
18878 | if (curArgVal->gtOper == GT_LCL_VAR) |
18879 | { |
18880 | inlCurArgInfo->argIsLclVar = true; |
18881 | |
18882 | /* Remember the "original" argument number */ |
18883 | curArgVal->gtLclVar.gtLclILoffs = argNum; |
18884 | } |
18885 | |
18886 | if ((curArgVal->OperKind() & GTK_CONST) || |
18887 | ((curArgVal->gtOper == GT_ADDR) && (curArgVal->gtOp.gtOp1->gtOper == GT_LCL_VAR))) |
18888 | { |
18889 | inlCurArgInfo->argIsInvariant = true; |
18890 | if (inlCurArgInfo->argIsThis && (curArgVal->gtOper == GT_CNS_INT) && (curArgVal->gtIntCon.gtIconVal == 0)) |
18891 | { |
18892 | // Abort inlining at this call site |
18893 | inlineResult->NoteFatal(InlineObservation::CALLSITE_ARG_HAS_NULL_THIS); |
18894 | return; |
18895 | } |
18896 | } |
18897 | |
18898 | // If the arg is a local that is address-taken, we can't safely |
18899 | // directly substitute it into the inlinee. |
18900 | // |
18901 | // Previously we'd accomplish this by setting "argHasLdargaOp" but |
18902 | // that has a stronger meaning: that the arg value can change in |
18903 | // the method body. Using that flag prevents type propagation, |
18904 | // which is safe in this case. |
18905 | // |
18906 | // Instead mark the arg as having a caller local ref. |
18907 | if (!inlCurArgInfo->argIsInvariant && gtHasLocalsWithAddrOp(curArgVal)) |
18908 | { |
18909 | inlCurArgInfo->argHasCallerLocalRef = true; |
18910 | } |
18911 | |
18912 | #ifdef DEBUG |
18913 | if (verbose) |
18914 | { |
18915 | if (inlCurArgInfo->argIsThis) |
18916 | { |
18917 | printf("thisArg:" ); |
18918 | } |
18919 | else |
18920 | { |
18921 | printf("\nArgument #%u:" , argNum); |
18922 | } |
18923 | if (inlCurArgInfo->argIsLclVar) |
18924 | { |
18925 | printf(" is a local var" ); |
18926 | } |
18927 | if (inlCurArgInfo->argIsInvariant) |
18928 | { |
18929 | printf(" is a constant" ); |
18930 | } |
18931 | if (inlCurArgInfo->argHasGlobRef) |
18932 | { |
18933 | printf(" has global refs" ); |
18934 | } |
18935 | if (inlCurArgInfo->argHasCallerLocalRef) |
18936 | { |
18937 | printf(" has caller local ref" ); |
18938 | } |
18939 | if (inlCurArgInfo->argHasSideEff) |
18940 | { |
18941 | printf(" has side effects" ); |
18942 | } |
18943 | if (inlCurArgInfo->argHasLdargaOp) |
18944 | { |
18945 | printf(" has ldarga effect" ); |
18946 | } |
18947 | if (inlCurArgInfo->argHasStargOp) |
18948 | { |
18949 | printf(" has starg effect" ); |
18950 | } |
18951 | if (inlCurArgInfo->argIsByRefToStructLocal) |
18952 | { |
18953 | printf(" is byref to a struct local" ); |
18954 | } |
18955 | |
18956 | printf("\n" ); |
18957 | gtDispTree(curArgVal); |
18958 | printf("\n" ); |
18959 | } |
18960 | #endif |
18961 | } |
18962 | |
18963 | //------------------------------------------------------------------------ |
18964 | // impInlineInitVars: setup inline information for inlinee args and locals |
18965 | // |
18966 | // Arguments: |
18967 | // pInlineInfo - inline info for the inline candidate |
18968 | // |
18969 | // Notes: |
18970 | // This method primarily adds caller-supplied info to the inlArgInfo |
18971 | // and sets up the lclVarInfo table. |
18972 | // |
18973 | // For args, the inlArgInfo records properties of the actual argument |
18974 | // including the tree node that produces the arg value. This node is |
18975 | // usually the tree node present at the call, but may also differ in |
18976 | // various ways: |
18977 | // - when the call arg is a GT_RET_EXPR, we search back through the ret |
18978 | // expr chain for the actual node. Note this will either be the original |
18979 | // call (which will be a failed inline by this point), or the return |
18980 | // expression from some set of inlines. |
18981 | // - when argument type casting is needed the necessary casts are added |
18982 | // around the argument node. |
18983 | // - if an argment can be simplified by folding then the node here is the |
18984 | // folded value. |
18985 | // |
18986 | // The method may make observations that lead to marking this candidate as |
18987 | // a failed inline. If this happens the initialization is abandoned immediately |
18988 | // to try and reduce the jit time cost for a failed inline. |
18989 | |
18990 | void Compiler::impInlineInitVars(InlineInfo* pInlineInfo) |
18991 | { |
18992 | assert(!compIsForInlining()); |
18993 | |
18994 | GenTree* call = pInlineInfo->iciCall; |
18995 | CORINFO_METHOD_INFO* methInfo = &pInlineInfo->inlineCandidateInfo->methInfo; |
18996 | unsigned clsAttr = pInlineInfo->inlineCandidateInfo->clsAttr; |
18997 | InlArgInfo* inlArgInfo = pInlineInfo->inlArgInfo; |
18998 | InlLclVarInfo* lclVarInfo = pInlineInfo->lclVarInfo; |
18999 | InlineResult* inlineResult = pInlineInfo->inlineResult; |
19000 | |
19001 | const bool hasRetBuffArg = impMethodInfo_hasRetBuffArg(methInfo); |
19002 | |
19003 | /* init the argument stuct */ |
19004 | |
19005 | memset(inlArgInfo, 0, (MAX_INL_ARGS + 1) * sizeof(inlArgInfo[0])); |
19006 | |
19007 | /* Get hold of the 'this' pointer and the argument list proper */ |
19008 | |
19009 | GenTree* thisArg = call->gtCall.gtCallObjp; |
19010 | GenTree* argList = call->gtCall.gtCallArgs; |
19011 | unsigned argCnt = 0; // Count of the arguments |
19012 | |
19013 | assert((methInfo->args.hasThis()) == (thisArg != nullptr)); |
19014 | |
19015 | if (thisArg) |
19016 | { |
19017 | inlArgInfo[0].argIsThis = true; |
19018 | GenTree* actualThisArg = thisArg->gtRetExprVal(); |
19019 | impInlineRecordArgInfo(pInlineInfo, actualThisArg, argCnt, inlineResult); |
19020 | |
19021 | if (inlineResult->IsFailure()) |
19022 | { |
19023 | return; |
19024 | } |
19025 | |
19026 | /* Increment the argument count */ |
19027 | argCnt++; |
19028 | } |
19029 | |
19030 | /* Record some information about each of the arguments */ |
19031 | bool hasTypeCtxtArg = (methInfo->args.callConv & CORINFO_CALLCONV_PARAMTYPE) != 0; |
19032 | |
19033 | #if USER_ARGS_COME_LAST |
19034 | unsigned typeCtxtArg = thisArg ? 1 : 0; |
19035 | #else // USER_ARGS_COME_LAST |
19036 | unsigned typeCtxtArg = methInfo->args.totalILArgs(); |
19037 | #endif // USER_ARGS_COME_LAST |
19038 | |
19039 | for (GenTree* argTmp = argList; argTmp; argTmp = argTmp->gtOp.gtOp2) |
19040 | { |
19041 | if (argTmp == argList && hasRetBuffArg) |
19042 | { |
19043 | continue; |
19044 | } |
19045 | |
19046 | // Ignore the type context argument |
19047 | if (hasTypeCtxtArg && (argCnt == typeCtxtArg)) |
19048 | { |
19049 | pInlineInfo->typeContextArg = typeCtxtArg; |
19050 | typeCtxtArg = 0xFFFFFFFF; |
19051 | continue; |
19052 | } |
19053 | |
19054 | assert(argTmp->gtOper == GT_LIST); |
19055 | GenTree* arg = argTmp->gtOp.gtOp1; |
19056 | GenTree* actualArg = arg->gtRetExprVal(); |
19057 | impInlineRecordArgInfo(pInlineInfo, actualArg, argCnt, inlineResult); |
19058 | |
19059 | if (inlineResult->IsFailure()) |
19060 | { |
19061 | return; |
19062 | } |
19063 | |
19064 | /* Increment the argument count */ |
19065 | argCnt++; |
19066 | } |
19067 | |
19068 | /* Make sure we got the arg number right */ |
19069 | assert(argCnt == methInfo->args.totalILArgs()); |
19070 | |
19071 | #ifdef FEATURE_SIMD |
19072 | bool foundSIMDType = pInlineInfo->hasSIMDTypeArgLocalOrReturn; |
19073 | #endif // FEATURE_SIMD |
19074 | |
19075 | /* We have typeless opcodes, get type information from the signature */ |
19076 | |
19077 | if (thisArg) |
19078 | { |
19079 | var_types sigType; |
19080 | |
19081 | if (clsAttr & CORINFO_FLG_VALUECLASS) |
19082 | { |
19083 | sigType = TYP_BYREF; |
19084 | } |
19085 | else |
19086 | { |
19087 | sigType = TYP_REF; |
19088 | } |
19089 | |
19090 | lclVarInfo[0].lclVerTypeInfo = verMakeTypeInfo(pInlineInfo->inlineCandidateInfo->clsHandle); |
19091 | lclVarInfo[0].lclHasLdlocaOp = false; |
19092 | |
19093 | #ifdef FEATURE_SIMD |
19094 | // We always want to check isSIMDClass, since we want to set foundSIMDType (to increase |
19095 | // the inlining multiplier) for anything in that assembly. |
19096 | // But we only need to normalize it if it is a TYP_STRUCT |
19097 | // (which we need to do even if we have already set foundSIMDType). |
19098 | if ((!foundSIMDType || (sigType == TYP_STRUCT)) && isSIMDorHWSIMDClass(&(lclVarInfo[0].lclVerTypeInfo))) |
19099 | { |
19100 | if (sigType == TYP_STRUCT) |
19101 | { |
19102 | sigType = impNormStructType(lclVarInfo[0].lclVerTypeInfo.GetClassHandle()); |
19103 | } |
19104 | foundSIMDType = true; |
19105 | } |
19106 | #endif // FEATURE_SIMD |
19107 | lclVarInfo[0].lclTypeInfo = sigType; |
19108 | |
19109 | assert(varTypeIsGC(thisArg->gtType) || // "this" is managed |
19110 | (thisArg->gtType == TYP_I_IMPL && // "this" is unmgd but the method's class doesnt care |
19111 | (clsAttr & CORINFO_FLG_VALUECLASS))); |
19112 | |
19113 | if (genActualType(thisArg->gtType) != genActualType(sigType)) |
19114 | { |
19115 | if (sigType == TYP_REF) |
19116 | { |
19117 | /* The argument cannot be bashed into a ref (see bug 750871) */ |
19118 | inlineResult->NoteFatal(InlineObservation::CALLSITE_ARG_NO_BASH_TO_REF); |
19119 | return; |
19120 | } |
19121 | |
19122 | /* This can only happen with byrefs <-> ints/shorts */ |
19123 | |
19124 | assert(genActualType(sigType) == TYP_I_IMPL || sigType == TYP_BYREF); |
19125 | assert(genActualType(thisArg->gtType) == TYP_I_IMPL || thisArg->gtType == TYP_BYREF); |
19126 | |
19127 | if (sigType == TYP_BYREF) |
19128 | { |
19129 | lclVarInfo[0].lclVerTypeInfo = typeInfo(varType2tiType(TYP_I_IMPL)); |
19130 | } |
19131 | else if (thisArg->gtType == TYP_BYREF) |
19132 | { |
19133 | assert(sigType == TYP_I_IMPL); |
19134 | |
19135 | /* If possible change the BYREF to an int */ |
19136 | if (thisArg->IsVarAddr()) |
19137 | { |
19138 | thisArg->gtType = TYP_I_IMPL; |
19139 | lclVarInfo[0].lclVerTypeInfo = typeInfo(varType2tiType(TYP_I_IMPL)); |
19140 | } |
19141 | else |
19142 | { |
19143 | /* Arguments 'int <- byref' cannot be bashed */ |
19144 | inlineResult->NoteFatal(InlineObservation::CALLSITE_ARG_NO_BASH_TO_INT); |
19145 | return; |
19146 | } |
19147 | } |
19148 | } |
19149 | } |
19150 | |
19151 | /* Init the types of the arguments and make sure the types |
19152 | * from the trees match the types in the signature */ |
19153 | |
19154 | CORINFO_ARG_LIST_HANDLE argLst; |
19155 | argLst = methInfo->args.args; |
19156 | |
19157 | unsigned i; |
19158 | for (i = (thisArg ? 1 : 0); i < argCnt; i++, argLst = info.compCompHnd->getArgNext(argLst)) |
19159 | { |
19160 | var_types sigType = (var_types)eeGetArgType(argLst, &methInfo->args); |
19161 | |
19162 | lclVarInfo[i].lclVerTypeInfo = verParseArgSigToTypeInfo(&methInfo->args, argLst); |
19163 | |
19164 | #ifdef FEATURE_SIMD |
19165 | if ((!foundSIMDType || (sigType == TYP_STRUCT)) && isSIMDorHWSIMDClass(&(lclVarInfo[i].lclVerTypeInfo))) |
19166 | { |
19167 | // If this is a SIMD class (i.e. in the SIMD assembly), then we will consider that we've |
19168 | // found a SIMD type, even if this may not be a type we recognize (the assumption is that |
19169 | // it is likely to use a SIMD type, and therefore we want to increase the inlining multiplier). |
19170 | foundSIMDType = true; |
19171 | if (sigType == TYP_STRUCT) |
19172 | { |
19173 | var_types structType = impNormStructType(lclVarInfo[i].lclVerTypeInfo.GetClassHandle()); |
19174 | sigType = structType; |
19175 | } |
19176 | } |
19177 | #endif // FEATURE_SIMD |
19178 | |
19179 | lclVarInfo[i].lclTypeInfo = sigType; |
19180 | lclVarInfo[i].lclHasLdlocaOp = false; |
19181 | |
19182 | /* Does the tree type match the signature type? */ |
19183 | |
19184 | GenTree* inlArgNode = inlArgInfo[i].argNode; |
19185 | |
19186 | if (sigType != inlArgNode->gtType) |
19187 | { |
19188 | /* In valid IL, this can only happen for short integer types or byrefs <-> [native] ints, |
19189 | but in bad IL cases with caller-callee signature mismatches we can see other types. |
19190 | Intentionally reject cases with mismatches so the jit is more flexible when |
19191 | encountering bad IL. */ |
19192 | |
19193 | bool isPlausibleTypeMatch = (genActualType(sigType) == genActualType(inlArgNode->gtType)) || |
19194 | (genActualTypeIsIntOrI(sigType) && inlArgNode->gtType == TYP_BYREF) || |
19195 | (sigType == TYP_BYREF && genActualTypeIsIntOrI(inlArgNode->gtType)); |
19196 | |
19197 | if (!isPlausibleTypeMatch) |
19198 | { |
19199 | inlineResult->NoteFatal(InlineObservation::CALLSITE_ARG_TYPES_INCOMPATIBLE); |
19200 | return; |
19201 | } |
19202 | |
19203 | /* Is it a narrowing or widening cast? |
19204 | * Widening casts are ok since the value computed is already |
19205 | * normalized to an int (on the IL stack) */ |
19206 | |
19207 | if (genTypeSize(inlArgNode->gtType) >= genTypeSize(sigType)) |
19208 | { |
19209 | if (sigType == TYP_BYREF) |
19210 | { |
19211 | lclVarInfo[i].lclVerTypeInfo = typeInfo(varType2tiType(TYP_I_IMPL)); |
19212 | } |
19213 | else if (inlArgNode->gtType == TYP_BYREF) |
19214 | { |
19215 | assert(varTypeIsIntOrI(sigType)); |
19216 | |
19217 | /* If possible bash the BYREF to an int */ |
19218 | if (inlArgNode->IsVarAddr()) |
19219 | { |
19220 | inlArgNode->gtType = TYP_I_IMPL; |
19221 | lclVarInfo[i].lclVerTypeInfo = typeInfo(varType2tiType(TYP_I_IMPL)); |
19222 | } |
19223 | else |
19224 | { |
19225 | /* Arguments 'int <- byref' cannot be changed */ |
19226 | inlineResult->NoteFatal(InlineObservation::CALLSITE_ARG_NO_BASH_TO_INT); |
19227 | return; |
19228 | } |
19229 | } |
19230 | else if (genTypeSize(sigType) < EA_PTRSIZE) |
19231 | { |
19232 | /* Narrowing cast */ |
19233 | |
19234 | if (inlArgNode->gtOper == GT_LCL_VAR && |
19235 | !lvaTable[inlArgNode->gtLclVarCommon.gtLclNum].lvNormalizeOnLoad() && |
19236 | sigType == lvaGetRealType(inlArgNode->gtLclVarCommon.gtLclNum)) |
19237 | { |
19238 | /* We don't need to insert a cast here as the variable |
19239 | was assigned a normalized value of the right type */ |
19240 | |
19241 | continue; |
19242 | } |
19243 | |
19244 | inlArgNode = inlArgInfo[i].argNode = gtNewCastNode(TYP_INT, inlArgNode, false, sigType); |
19245 | |
19246 | inlArgInfo[i].argIsLclVar = false; |
19247 | |
19248 | /* Try to fold the node in case we have constant arguments */ |
19249 | |
19250 | if (inlArgInfo[i].argIsInvariant) |
19251 | { |
19252 | inlArgNode = gtFoldExprConst(inlArgNode); |
19253 | inlArgInfo[i].argNode = inlArgNode; |
19254 | assert(inlArgNode->OperIsConst()); |
19255 | } |
19256 | } |
19257 | #ifdef _TARGET_64BIT_ |
19258 | else if (genTypeSize(genActualType(inlArgNode->gtType)) < genTypeSize(sigType)) |
19259 | { |
19260 | // This should only happen for int -> native int widening |
19261 | inlArgNode = inlArgInfo[i].argNode = |
19262 | gtNewCastNode(genActualType(sigType), inlArgNode, false, sigType); |
19263 | |
19264 | inlArgInfo[i].argIsLclVar = false; |
19265 | |
19266 | /* Try to fold the node in case we have constant arguments */ |
19267 | |
19268 | if (inlArgInfo[i].argIsInvariant) |
19269 | { |
19270 | inlArgNode = gtFoldExprConst(inlArgNode); |
19271 | inlArgInfo[i].argNode = inlArgNode; |
19272 | assert(inlArgNode->OperIsConst()); |
19273 | } |
19274 | } |
19275 | #endif // _TARGET_64BIT_ |
19276 | } |
19277 | } |
19278 | } |
19279 | |
19280 | /* Init the types of the local variables */ |
19281 | |
19282 | CORINFO_ARG_LIST_HANDLE localsSig; |
19283 | localsSig = methInfo->locals.args; |
19284 | |
19285 | for (i = 0; i < methInfo->locals.numArgs; i++) |
19286 | { |
19287 | bool isPinned; |
19288 | var_types type = (var_types)eeGetArgType(localsSig, &methInfo->locals, &isPinned); |
19289 | |
19290 | lclVarInfo[i + argCnt].lclHasLdlocaOp = false; |
19291 | lclVarInfo[i + argCnt].lclIsPinned = isPinned; |
19292 | lclVarInfo[i + argCnt].lclTypeInfo = type; |
19293 | |
19294 | if (varTypeIsGC(type)) |
19295 | { |
19296 | pInlineInfo->numberOfGcRefLocals++; |
19297 | } |
19298 | |
19299 | if (isPinned) |
19300 | { |
19301 | // Pinned locals may cause inlines to fail. |
19302 | inlineResult->Note(InlineObservation::CALLEE_HAS_PINNED_LOCALS); |
19303 | if (inlineResult->IsFailure()) |
19304 | { |
19305 | return; |
19306 | } |
19307 | } |
19308 | |
19309 | lclVarInfo[i + argCnt].lclVerTypeInfo = verParseArgSigToTypeInfo(&methInfo->locals, localsSig); |
19310 | |
19311 | // If this local is a struct type with GC fields, inform the inliner. It may choose to bail |
19312 | // out on the inline. |
19313 | if (type == TYP_STRUCT) |
19314 | { |
19315 | CORINFO_CLASS_HANDLE lclHandle = lclVarInfo[i + argCnt].lclVerTypeInfo.GetClassHandle(); |
19316 | DWORD typeFlags = info.compCompHnd->getClassAttribs(lclHandle); |
19317 | if ((typeFlags & CORINFO_FLG_CONTAINS_GC_PTR) != 0) |
19318 | { |
19319 | inlineResult->Note(InlineObservation::CALLEE_HAS_GC_STRUCT); |
19320 | if (inlineResult->IsFailure()) |
19321 | { |
19322 | return; |
19323 | } |
19324 | |
19325 | // Do further notification in the case where the call site is rare; some policies do |
19326 | // not track the relative hotness of call sites for "always" inline cases. |
19327 | if (pInlineInfo->iciBlock->isRunRarely()) |
19328 | { |
19329 | inlineResult->Note(InlineObservation::CALLSITE_RARE_GC_STRUCT); |
19330 | if (inlineResult->IsFailure()) |
19331 | { |
19332 | |
19333 | return; |
19334 | } |
19335 | } |
19336 | } |
19337 | } |
19338 | |
19339 | localsSig = info.compCompHnd->getArgNext(localsSig); |
19340 | |
19341 | #ifdef FEATURE_SIMD |
19342 | if ((!foundSIMDType || (type == TYP_STRUCT)) && isSIMDorHWSIMDClass(&(lclVarInfo[i + argCnt].lclVerTypeInfo))) |
19343 | { |
19344 | foundSIMDType = true; |
19345 | if (featureSIMD && type == TYP_STRUCT) |
19346 | { |
19347 | var_types structType = impNormStructType(lclVarInfo[i + argCnt].lclVerTypeInfo.GetClassHandle()); |
19348 | lclVarInfo[i + argCnt].lclTypeInfo = structType; |
19349 | } |
19350 | } |
19351 | #endif // FEATURE_SIMD |
19352 | } |
19353 | |
19354 | #ifdef FEATURE_SIMD |
19355 | if (!foundSIMDType && (call->AsCall()->gtRetClsHnd != nullptr) && isSIMDorHWSIMDClass(call->AsCall()->gtRetClsHnd)) |
19356 | { |
19357 | foundSIMDType = true; |
19358 | } |
19359 | pInlineInfo->hasSIMDTypeArgLocalOrReturn = foundSIMDType; |
19360 | #endif // FEATURE_SIMD |
19361 | } |
19362 | |
19363 | //------------------------------------------------------------------------ |
19364 | // impInlineFetchLocal: get a local var that represents an inlinee local |
19365 | // |
19366 | // Arguments: |
19367 | // lclNum -- number of the inlinee local |
19368 | // reason -- debug string describing purpose of the local var |
19369 | // |
19370 | // Returns: |
19371 | // Number of the local to use |
19372 | // |
19373 | // Notes: |
19374 | // This method is invoked only for locals actually used in the |
19375 | // inlinee body. |
19376 | // |
19377 | // Allocates a new temp if necessary, and copies key properties |
19378 | // over from the inlinee local var info. |
19379 | |
19380 | unsigned Compiler::impInlineFetchLocal(unsigned lclNum DEBUGARG(const char* reason)) |
19381 | { |
19382 | assert(compIsForInlining()); |
19383 | |
19384 | unsigned tmpNum = impInlineInfo->lclTmpNum[lclNum]; |
19385 | |
19386 | if (tmpNum == BAD_VAR_NUM) |
19387 | { |
19388 | const InlLclVarInfo& inlineeLocal = impInlineInfo->lclVarInfo[lclNum + impInlineInfo->argCnt]; |
19389 | const var_types lclTyp = inlineeLocal.lclTypeInfo; |
19390 | |
19391 | // The lifetime of this local might span multiple BBs. |
19392 | // So it is a long lifetime local. |
19393 | impInlineInfo->lclTmpNum[lclNum] = tmpNum = lvaGrabTemp(false DEBUGARG(reason)); |
19394 | |
19395 | // Copy over key info |
19396 | lvaTable[tmpNum].lvType = lclTyp; |
19397 | lvaTable[tmpNum].lvHasLdAddrOp = inlineeLocal.lclHasLdlocaOp; |
19398 | lvaTable[tmpNum].lvPinned = inlineeLocal.lclIsPinned; |
19399 | lvaTable[tmpNum].lvHasILStoreOp = inlineeLocal.lclHasStlocOp; |
19400 | lvaTable[tmpNum].lvHasMultipleILStoreOp = inlineeLocal.lclHasMultipleStlocOp; |
19401 | |
19402 | // Copy over class handle for ref types. Note this may be a |
19403 | // shared type -- someday perhaps we can get the exact |
19404 | // signature and pass in a more precise type. |
19405 | if (lclTyp == TYP_REF) |
19406 | { |
19407 | assert(lvaTable[tmpNum].lvSingleDef == 0); |
19408 | |
19409 | lvaTable[tmpNum].lvSingleDef = !inlineeLocal.lclHasMultipleStlocOp && !inlineeLocal.lclHasLdlocaOp; |
19410 | if (lvaTable[tmpNum].lvSingleDef) |
19411 | { |
19412 | JITDUMP("Marked V%02u as a single def temp\n" , tmpNum); |
19413 | } |
19414 | |
19415 | lvaSetClass(tmpNum, inlineeLocal.lclVerTypeInfo.GetClassHandleForObjRef()); |
19416 | } |
19417 | |
19418 | if (inlineeLocal.lclVerTypeInfo.IsStruct()) |
19419 | { |
19420 | if (varTypeIsStruct(lclTyp)) |
19421 | { |
19422 | lvaSetStruct(tmpNum, inlineeLocal.lclVerTypeInfo.GetClassHandle(), true /* unsafe value cls check */); |
19423 | } |
19424 | else |
19425 | { |
19426 | // This is a wrapped primitive. Make sure the verstate knows that |
19427 | lvaTable[tmpNum].lvVerTypeInfo = inlineeLocal.lclVerTypeInfo; |
19428 | } |
19429 | } |
19430 | |
19431 | #ifdef DEBUG |
19432 | // Sanity check that we're properly prepared for gc ref locals. |
19433 | if (varTypeIsGC(lclTyp)) |
19434 | { |
19435 | // Since there are gc locals we should have seen them earlier |
19436 | // and if there was a return value, set up the spill temp. |
19437 | assert(impInlineInfo->HasGcRefLocals()); |
19438 | assert((info.compRetNativeType == TYP_VOID) || fgNeedReturnSpillTemp()); |
19439 | } |
19440 | else |
19441 | { |
19442 | // Make sure all pinned locals count as gc refs. |
19443 | assert(!inlineeLocal.lclIsPinned); |
19444 | } |
19445 | #endif // DEBUG |
19446 | } |
19447 | |
19448 | return tmpNum; |
19449 | } |
19450 | |
19451 | //------------------------------------------------------------------------ |
19452 | // impInlineFetchArg: return tree node for argument value in an inlinee |
19453 | // |
19454 | // Arguments: |
19455 | // lclNum -- argument number in inlinee IL |
19456 | // inlArgInfo -- argument info for inlinee |
19457 | // lclVarInfo -- var info for inlinee |
19458 | // |
19459 | // Returns: |
19460 | // Tree for the argument's value. Often an inlinee-scoped temp |
19461 | // GT_LCL_VAR but can be other tree kinds, if the argument |
19462 | // expression from the caller can be directly substituted into the |
19463 | // inlinee body. |
19464 | // |
19465 | // Notes: |
19466 | // Must be used only for arguments -- use impInlineFetchLocal for |
19467 | // inlinee locals. |
19468 | // |
19469 | // Direct substitution is performed when the formal argument cannot |
19470 | // change value in the inlinee body (no starg or ldarga), and the |
19471 | // actual argument expression's value cannot be changed if it is |
19472 | // substituted it into the inlinee body. |
19473 | // |
19474 | // Even if an inlinee-scoped temp is returned here, it may later be |
19475 | // "bashed" to a caller-supplied tree when arguments are actually |
19476 | // passed (see fgInlinePrependStatements). Bashing can happen if |
19477 | // the argument ends up being single use and other conditions are |
19478 | // met. So the contents of the tree returned here may not end up |
19479 | // being the ones ultimately used for the argument. |
19480 | // |
19481 | // This method will side effect inlArgInfo. It should only be called |
19482 | // for actual uses of the argument in the inlinee. |
19483 | |
19484 | GenTree* Compiler::impInlineFetchArg(unsigned lclNum, InlArgInfo* inlArgInfo, InlLclVarInfo* lclVarInfo) |
19485 | { |
19486 | // Cache the relevant arg and lcl info for this argument. |
19487 | // We will modify argInfo but not lclVarInfo. |
19488 | InlArgInfo& argInfo = inlArgInfo[lclNum]; |
19489 | const InlLclVarInfo& lclInfo = lclVarInfo[lclNum]; |
19490 | const bool argCanBeModified = argInfo.argHasLdargaOp || argInfo.argHasStargOp; |
19491 | const var_types lclTyp = lclInfo.lclTypeInfo; |
19492 | GenTree* op1 = nullptr; |
19493 | |
19494 | if (argInfo.argIsInvariant && !argCanBeModified) |
19495 | { |
19496 | // Directly substitute constants or addresses of locals |
19497 | // |
19498 | // Clone the constant. Note that we cannot directly use |
19499 | // argNode in the trees even if !argInfo.argIsUsed as this |
19500 | // would introduce aliasing between inlArgInfo[].argNode and |
19501 | // impInlineExpr. Then gtFoldExpr() could change it, causing |
19502 | // further references to the argument working off of the |
19503 | // bashed copy. |
19504 | op1 = gtCloneExpr(argInfo.argNode); |
19505 | PREFIX_ASSUME(op1 != nullptr); |
19506 | argInfo.argTmpNum = BAD_VAR_NUM; |
19507 | |
19508 | // We may need to retype to ensure we match the callee's view of the type. |
19509 | // Otherwise callee-pass throughs of arguments can create return type |
19510 | // mismatches that block inlining. |
19511 | // |
19512 | // Note argument type mismatches that prevent inlining should |
19513 | // have been caught in impInlineInitVars. |
19514 | if (op1->TypeGet() != lclTyp) |
19515 | { |
19516 | op1->gtType = genActualType(lclTyp); |
19517 | } |
19518 | } |
19519 | else if (argInfo.argIsLclVar && !argCanBeModified && !argInfo.argHasCallerLocalRef) |
19520 | { |
19521 | // Directly substitute unaliased caller locals for args that cannot be modified |
19522 | // |
19523 | // Use the caller-supplied node if this is the first use. |
19524 | op1 = argInfo.argNode; |
19525 | argInfo.argTmpNum = op1->gtLclVarCommon.gtLclNum; |
19526 | |
19527 | // Use an equivalent copy if this is the second or subsequent |
19528 | // use, or if we need to retype. |
19529 | // |
19530 | // Note argument type mismatches that prevent inlining should |
19531 | // have been caught in impInlineInitVars. |
19532 | if (argInfo.argIsUsed || (op1->TypeGet() != lclTyp)) |
19533 | { |
19534 | assert(op1->gtOper == GT_LCL_VAR); |
19535 | assert(lclNum == op1->gtLclVar.gtLclILoffs); |
19536 | |
19537 | var_types newTyp = lclTyp; |
19538 | |
19539 | if (!lvaTable[op1->gtLclVarCommon.gtLclNum].lvNormalizeOnLoad()) |
19540 | { |
19541 | newTyp = genActualType(lclTyp); |
19542 | } |
19543 | |
19544 | // Create a new lcl var node - remember the argument lclNum |
19545 | op1 = gtNewLclvNode(op1->gtLclVarCommon.gtLclNum, newTyp, op1->gtLclVar.gtLclILoffs); |
19546 | } |
19547 | } |
19548 | else if (argInfo.argIsByRefToStructLocal && !argInfo.argHasStargOp) |
19549 | { |
19550 | /* Argument is a by-ref address to a struct, a normed struct, or its field. |
19551 | In these cases, don't spill the byref to a local, simply clone the tree and use it. |
19552 | This way we will increase the chance for this byref to be optimized away by |
19553 | a subsequent "dereference" operation. |
19554 | |
19555 | From Dev11 bug #139955: Argument node can also be TYP_I_IMPL if we've bashed the tree |
19556 | (in impInlineInitVars()), if the arg has argHasLdargaOp as well as argIsByRefToStructLocal. |
19557 | For example, if the caller is: |
19558 | ldloca.s V_1 // V_1 is a local struct |
19559 | call void Test.ILPart::RunLdargaOnPointerArg(int32*) |
19560 | and the callee being inlined has: |
19561 | .method public static void RunLdargaOnPointerArg(int32* ptrToInts) cil managed |
19562 | ldarga.s ptrToInts |
19563 | call void Test.FourInts::NotInlined_SetExpectedValuesThroughPointerToPointer(int32**) |
19564 | then we change the argument tree (of "ldloca.s V_1") to TYP_I_IMPL to match the callee signature. We'll |
19565 | soon afterwards reject the inlining anyway, since the tree we return isn't a GT_LCL_VAR. |
19566 | */ |
19567 | assert(argInfo.argNode->TypeGet() == TYP_BYREF || argInfo.argNode->TypeGet() == TYP_I_IMPL); |
19568 | op1 = gtCloneExpr(argInfo.argNode); |
19569 | } |
19570 | else |
19571 | { |
19572 | /* Argument is a complex expression - it must be evaluated into a temp */ |
19573 | |
19574 | if (argInfo.argHasTmp) |
19575 | { |
19576 | assert(argInfo.argIsUsed); |
19577 | assert(argInfo.argTmpNum < lvaCount); |
19578 | |
19579 | /* Create a new lcl var node - remember the argument lclNum */ |
19580 | op1 = gtNewLclvNode(argInfo.argTmpNum, genActualType(lclTyp)); |
19581 | |
19582 | /* This is the second or later use of the this argument, |
19583 | so we have to use the temp (instead of the actual arg) */ |
19584 | argInfo.argBashTmpNode = nullptr; |
19585 | } |
19586 | else |
19587 | { |
19588 | /* First time use */ |
19589 | assert(!argInfo.argIsUsed); |
19590 | |
19591 | /* Reserve a temp for the expression. |
19592 | * Use a large size node as we may change it later */ |
19593 | |
19594 | const unsigned tmpNum = lvaGrabTemp(true DEBUGARG("Inlining Arg" )); |
19595 | |
19596 | lvaTable[tmpNum].lvType = lclTyp; |
19597 | |
19598 | // For ref types, determine the type of the temp. |
19599 | if (lclTyp == TYP_REF) |
19600 | { |
19601 | if (!argCanBeModified) |
19602 | { |
19603 | // If the arg can't be modified in the method |
19604 | // body, use the type of the value, if |
19605 | // known. Otherwise, use the declared type. |
19606 | assert(lvaTable[tmpNum].lvSingleDef == 0); |
19607 | lvaTable[tmpNum].lvSingleDef = 1; |
19608 | JITDUMP("Marked V%02u as a single def temp\n" , tmpNum); |
19609 | lvaSetClass(tmpNum, argInfo.argNode, lclInfo.lclVerTypeInfo.GetClassHandleForObjRef()); |
19610 | } |
19611 | else |
19612 | { |
19613 | // Arg might be modified, use the declared type of |
19614 | // the argument. |
19615 | lvaSetClass(tmpNum, lclInfo.lclVerTypeInfo.GetClassHandleForObjRef()); |
19616 | } |
19617 | } |
19618 | |
19619 | assert(lvaTable[tmpNum].lvAddrExposed == 0); |
19620 | if (argInfo.argHasLdargaOp) |
19621 | { |
19622 | lvaTable[tmpNum].lvHasLdAddrOp = 1; |
19623 | } |
19624 | |
19625 | if (lclInfo.lclVerTypeInfo.IsStruct()) |
19626 | { |
19627 | if (varTypeIsStruct(lclTyp)) |
19628 | { |
19629 | lvaSetStruct(tmpNum, lclInfo.lclVerTypeInfo.GetClassHandle(), true /* unsafe value cls check */); |
19630 | if (info.compIsVarArgs) |
19631 | { |
19632 | lvaSetStructUsedAsVarArg(tmpNum); |
19633 | } |
19634 | } |
19635 | else |
19636 | { |
19637 | // This is a wrapped primitive. Make sure the verstate knows that |
19638 | lvaTable[tmpNum].lvVerTypeInfo = lclInfo.lclVerTypeInfo; |
19639 | } |
19640 | } |
19641 | |
19642 | argInfo.argHasTmp = true; |
19643 | argInfo.argTmpNum = tmpNum; |
19644 | |
19645 | // If we require strict exception order, then arguments must |
19646 | // be evaluated in sequence before the body of the inlined method. |
19647 | // So we need to evaluate them to a temp. |
19648 | // Also, if arguments have global or local references, we need to |
19649 | // evaluate them to a temp before the inlined body as the |
19650 | // inlined body may be modifying the global ref. |
19651 | // TODO-1stClassStructs: We currently do not reuse an existing lclVar |
19652 | // if it is a struct, because it requires some additional handling. |
19653 | |
19654 | if (!varTypeIsStruct(lclTyp) && !argInfo.argHasSideEff && !argInfo.argHasGlobRef && |
19655 | !argInfo.argHasCallerLocalRef) |
19656 | { |
19657 | /* Get a *LARGE* LCL_VAR node */ |
19658 | op1 = gtNewLclLNode(tmpNum, genActualType(lclTyp), lclNum); |
19659 | |
19660 | /* Record op1 as the very first use of this argument. |
19661 | If there are no further uses of the arg, we may be |
19662 | able to use the actual arg node instead of the temp. |
19663 | If we do see any further uses, we will clear this. */ |
19664 | argInfo.argBashTmpNode = op1; |
19665 | } |
19666 | else |
19667 | { |
19668 | /* Get a small LCL_VAR node */ |
19669 | op1 = gtNewLclvNode(tmpNum, genActualType(lclTyp)); |
19670 | /* No bashing of this argument */ |
19671 | argInfo.argBashTmpNode = nullptr; |
19672 | } |
19673 | } |
19674 | } |
19675 | |
19676 | // Mark this argument as used. |
19677 | argInfo.argIsUsed = true; |
19678 | |
19679 | return op1; |
19680 | } |
19681 | |
19682 | /****************************************************************************** |
19683 | Is this the original "this" argument to the call being inlined? |
19684 | |
19685 | Note that we do not inline methods with "starg 0", and so we do not need to |
19686 | worry about it. |
19687 | */ |
19688 | |
19689 | BOOL Compiler::impInlineIsThis(GenTree* tree, InlArgInfo* inlArgInfo) |
19690 | { |
19691 | assert(compIsForInlining()); |
19692 | return (tree->gtOper == GT_LCL_VAR && tree->gtLclVarCommon.gtLclNum == inlArgInfo[0].argTmpNum); |
19693 | } |
19694 | |
19695 | //----------------------------------------------------------------------------- |
19696 | // This function checks if a dereference in the inlinee can guarantee that |
19697 | // the "this" is non-NULL. |
19698 | // If we haven't hit a branch or a side effect, and we are dereferencing |
19699 | // from 'this' to access a field or make GTF_CALL_NULLCHECK call, |
19700 | // then we can avoid a separate null pointer check. |
19701 | // |
19702 | // "additionalTreesToBeEvaluatedBefore" |
19703 | // is the set of pending trees that have not yet been added to the statement list, |
19704 | // and which have been removed from verCurrentState.esStack[] |
19705 | |
19706 | BOOL Compiler::impInlineIsGuaranteedThisDerefBeforeAnySideEffects(GenTree* additionalTreesToBeEvaluatedBefore, |
19707 | GenTree* variableBeingDereferenced, |
19708 | InlArgInfo* inlArgInfo) |
19709 | { |
19710 | assert(compIsForInlining()); |
19711 | assert(opts.OptEnabled(CLFLG_INLINING)); |
19712 | |
19713 | BasicBlock* block = compCurBB; |
19714 | |
19715 | GenTree* stmt; |
19716 | GenTree* expr; |
19717 | |
19718 | if (block != fgFirstBB) |
19719 | { |
19720 | return FALSE; |
19721 | } |
19722 | |
19723 | if (!impInlineIsThis(variableBeingDereferenced, inlArgInfo)) |
19724 | { |
19725 | return FALSE; |
19726 | } |
19727 | |
19728 | if (additionalTreesToBeEvaluatedBefore && |
19729 | GTF_GLOBALLY_VISIBLE_SIDE_EFFECTS(additionalTreesToBeEvaluatedBefore->gtFlags)) |
19730 | { |
19731 | return FALSE; |
19732 | } |
19733 | |
19734 | for (stmt = impTreeList->gtNext; stmt; stmt = stmt->gtNext) |
19735 | { |
19736 | expr = stmt->gtStmt.gtStmtExpr; |
19737 | |
19738 | if (GTF_GLOBALLY_VISIBLE_SIDE_EFFECTS(expr->gtFlags)) |
19739 | { |
19740 | return FALSE; |
19741 | } |
19742 | } |
19743 | |
19744 | for (unsigned level = 0; level < verCurrentState.esStackDepth; level++) |
19745 | { |
19746 | unsigned stackTreeFlags = verCurrentState.esStack[level].val->gtFlags; |
19747 | if (GTF_GLOBALLY_VISIBLE_SIDE_EFFECTS(stackTreeFlags)) |
19748 | { |
19749 | return FALSE; |
19750 | } |
19751 | } |
19752 | |
19753 | return TRUE; |
19754 | } |
19755 | |
19756 | //------------------------------------------------------------------------ |
19757 | // impMarkInlineCandidate: determine if this call can be subsequently inlined |
19758 | // |
19759 | // Arguments: |
19760 | // callNode -- call under scrutiny |
19761 | // exactContextHnd -- context handle for inlining |
19762 | // exactContextNeedsRuntimeLookup -- true if context required runtime lookup |
19763 | // callInfo -- call info from VM |
19764 | // |
19765 | // Notes: |
19766 | // Mostly a wrapper for impMarkInlineCandidateHelper that also undoes |
19767 | // guarded devirtualization for virtual calls where the method we'd |
19768 | // devirtualize to cannot be inlined. |
19769 | |
19770 | void Compiler::impMarkInlineCandidate(GenTree* callNode, |
19771 | CORINFO_CONTEXT_HANDLE exactContextHnd, |
19772 | bool exactContextNeedsRuntimeLookup, |
19773 | CORINFO_CALL_INFO* callInfo) |
19774 | { |
19775 | GenTreeCall* call = callNode->AsCall(); |
19776 | |
19777 | // Do the actual evaluation |
19778 | impMarkInlineCandidateHelper(call, exactContextHnd, exactContextNeedsRuntimeLookup, callInfo); |
19779 | |
19780 | // If this call is an inline candidate or is not a guarded devirtualization |
19781 | // candidate, we're done. |
19782 | if (call->IsInlineCandidate() || !call->IsGuardedDevirtualizationCandidate()) |
19783 | { |
19784 | return; |
19785 | } |
19786 | |
19787 | // If we can't inline the call we'd guardedly devirtualize to, |
19788 | // we undo the guarded devirtualization, as the benefit from |
19789 | // just guarded devirtualization alone is likely not worth the |
19790 | // extra jit time and code size. |
19791 | // |
19792 | // TODO: it is possibly interesting to allow this, but requires |
19793 | // fixes elsewhere too... |
19794 | JITDUMP("Revoking guarded devirtualization candidacy for call [%06u]: target method can't be inlined\n" , |
19795 | dspTreeID(call)); |
19796 | |
19797 | call->ClearGuardedDevirtualizationCandidate(); |
19798 | |
19799 | // If we have a stub address, restore it back into the union that it shares |
19800 | // with the candidate info. |
19801 | if (call->IsVirtualStub()) |
19802 | { |
19803 | JITDUMP("Restoring stub addr %p from guarded devirt candidate info\n" , |
19804 | call->gtGuardedDevirtualizationCandidateInfo->stubAddr); |
19805 | call->gtStubCallStubAddr = call->gtGuardedDevirtualizationCandidateInfo->stubAddr; |
19806 | } |
19807 | } |
19808 | |
19809 | //------------------------------------------------------------------------ |
19810 | // impMarkInlineCandidateHelper: determine if this call can be subsequently |
19811 | // inlined |
19812 | // |
19813 | // Arguments: |
19814 | // callNode -- call under scrutiny |
19815 | // exactContextHnd -- context handle for inlining |
19816 | // exactContextNeedsRuntimeLookup -- true if context required runtime lookup |
19817 | // callInfo -- call info from VM |
19818 | // |
19819 | // Notes: |
19820 | // If callNode is an inline candidate, this method sets the flag |
19821 | // GTF_CALL_INLINE_CANDIDATE, and ensures that helper methods have |
19822 | // filled in the associated InlineCandidateInfo. |
19823 | // |
19824 | // If callNode is not an inline candidate, and the reason is |
19825 | // something that is inherent to the method being called, the |
19826 | // method may be marked as "noinline" to short-circuit any |
19827 | // future assessments of calls to this method. |
19828 | |
19829 | void Compiler::impMarkInlineCandidateHelper(GenTreeCall* call, |
19830 | CORINFO_CONTEXT_HANDLE exactContextHnd, |
19831 | bool exactContextNeedsRuntimeLookup, |
19832 | CORINFO_CALL_INFO* callInfo) |
19833 | { |
19834 | // Let the strategy know there's another call |
19835 | impInlineRoot()->m_inlineStrategy->NoteCall(); |
19836 | |
19837 | if (!opts.OptEnabled(CLFLG_INLINING)) |
19838 | { |
19839 | /* XXX Mon 8/18/2008 |
19840 | * This assert is misleading. The caller does not ensure that we have CLFLG_INLINING set before |
19841 | * calling impMarkInlineCandidate. However, if this assert trips it means that we're an inlinee and |
19842 | * CLFLG_MINOPT is set. That doesn't make a lot of sense. If you hit this assert, work back and |
19843 | * figure out why we did not set MAXOPT for this compile. |
19844 | */ |
19845 | assert(!compIsForInlining()); |
19846 | return; |
19847 | } |
19848 | |
19849 | if (compIsForImportOnly()) |
19850 | { |
19851 | // Don't bother creating the inline candidate during verification. |
19852 | // Otherwise the call to info.compCompHnd->canInline will trigger a recursive verification |
19853 | // that leads to the creation of multiple instances of Compiler. |
19854 | return; |
19855 | } |
19856 | |
19857 | InlineResult inlineResult(this, call, nullptr, "impMarkInlineCandidate" ); |
19858 | |
19859 | // Don't inline if not optimizing root method |
19860 | if (opts.compDbgCode) |
19861 | { |
19862 | inlineResult.NoteFatal(InlineObservation::CALLER_DEBUG_CODEGEN); |
19863 | return; |
19864 | } |
19865 | |
19866 | // Don't inline if inlining into root method is disabled. |
19867 | if (InlineStrategy::IsNoInline(info.compCompHnd, info.compMethodHnd)) |
19868 | { |
19869 | inlineResult.NoteFatal(InlineObservation::CALLER_IS_JIT_NOINLINE); |
19870 | return; |
19871 | } |
19872 | |
19873 | // Inlining candidate determination needs to honor only IL tail prefix. |
19874 | // Inlining takes precedence over implicit tail call optimization (if the call is not directly recursive). |
19875 | if (call->IsTailPrefixedCall()) |
19876 | { |
19877 | inlineResult.NoteFatal(InlineObservation::CALLSITE_EXPLICIT_TAIL_PREFIX); |
19878 | return; |
19879 | } |
19880 | |
19881 | // Tail recursion elimination takes precedence over inlining. |
19882 | // TODO: We may want to do some of the additional checks from fgMorphCall |
19883 | // here to reduce the chance we don't inline a call that won't be optimized |
19884 | // as a fast tail call or turned into a loop. |
19885 | if (gtIsRecursiveCall(call) && call->IsImplicitTailCall()) |
19886 | { |
19887 | inlineResult.NoteFatal(InlineObservation::CALLSITE_IMPLICIT_REC_TAIL_CALL); |
19888 | return; |
19889 | } |
19890 | |
19891 | if (call->IsVirtual()) |
19892 | { |
19893 | // Allow guarded devirt calls to be treated as inline candidates, |
19894 | // but reject all other virtual calls. |
19895 | if (!call->IsGuardedDevirtualizationCandidate()) |
19896 | { |
19897 | inlineResult.NoteFatal(InlineObservation::CALLSITE_IS_NOT_DIRECT); |
19898 | return; |
19899 | } |
19900 | } |
19901 | |
19902 | /* Ignore helper calls */ |
19903 | |
19904 | if (call->gtCallType == CT_HELPER) |
19905 | { |
19906 | assert(!call->IsGuardedDevirtualizationCandidate()); |
19907 | inlineResult.NoteFatal(InlineObservation::CALLSITE_IS_CALL_TO_HELPER); |
19908 | return; |
19909 | } |
19910 | |
19911 | /* Ignore indirect calls */ |
19912 | if (call->gtCallType == CT_INDIRECT) |
19913 | { |
19914 | inlineResult.NoteFatal(InlineObservation::CALLSITE_IS_NOT_DIRECT_MANAGED); |
19915 | return; |
19916 | } |
19917 | |
19918 | /* I removed the check for BBJ_THROW. BBJ_THROW is usually marked as rarely run. This more or less |
19919 | * restricts the inliner to non-expanding inlines. I removed the check to allow for non-expanding |
19920 | * inlining in throw blocks. I should consider the same thing for catch and filter regions. */ |
19921 | |
19922 | CORINFO_METHOD_HANDLE fncHandle; |
19923 | unsigned methAttr; |
19924 | |
19925 | if (call->IsGuardedDevirtualizationCandidate()) |
19926 | { |
19927 | fncHandle = call->gtGuardedDevirtualizationCandidateInfo->guardedMethodHandle; |
19928 | methAttr = info.compCompHnd->getMethodAttribs(fncHandle); |
19929 | } |
19930 | else |
19931 | { |
19932 | fncHandle = call->gtCallMethHnd; |
19933 | |
19934 | // Reuse method flags from the original callInfo if possible |
19935 | if (fncHandle == callInfo->hMethod) |
19936 | { |
19937 | methAttr = callInfo->methodFlags; |
19938 | } |
19939 | else |
19940 | { |
19941 | methAttr = info.compCompHnd->getMethodAttribs(fncHandle); |
19942 | } |
19943 | } |
19944 | |
19945 | #ifdef DEBUG |
19946 | if (compStressCompile(STRESS_FORCE_INLINE, 0)) |
19947 | { |
19948 | methAttr |= CORINFO_FLG_FORCEINLINE; |
19949 | } |
19950 | #endif |
19951 | |
19952 | // Check for COMPlus_AggressiveInlining |
19953 | if (compDoAggressiveInlining) |
19954 | { |
19955 | methAttr |= CORINFO_FLG_FORCEINLINE; |
19956 | } |
19957 | |
19958 | if (!(methAttr & CORINFO_FLG_FORCEINLINE)) |
19959 | { |
19960 | /* Don't bother inline blocks that are in the filter region */ |
19961 | if (bbInCatchHandlerILRange(compCurBB)) |
19962 | { |
19963 | #ifdef DEBUG |
19964 | if (verbose) |
19965 | { |
19966 | printf("\nWill not inline blocks that are in the catch handler region\n" ); |
19967 | } |
19968 | |
19969 | #endif |
19970 | |
19971 | inlineResult.NoteFatal(InlineObservation::CALLSITE_IS_WITHIN_CATCH); |
19972 | return; |
19973 | } |
19974 | |
19975 | if (bbInFilterILRange(compCurBB)) |
19976 | { |
19977 | #ifdef DEBUG |
19978 | if (verbose) |
19979 | { |
19980 | printf("\nWill not inline blocks that are in the filter region\n" ); |
19981 | } |
19982 | #endif |
19983 | |
19984 | inlineResult.NoteFatal(InlineObservation::CALLSITE_IS_WITHIN_FILTER); |
19985 | return; |
19986 | } |
19987 | } |
19988 | |
19989 | /* If the caller's stack frame is marked, then we can't do any inlining. Period. */ |
19990 | |
19991 | if (opts.compNeedSecurityCheck) |
19992 | { |
19993 | inlineResult.NoteFatal(InlineObservation::CALLER_NEEDS_SECURITY_CHECK); |
19994 | return; |
19995 | } |
19996 | |
19997 | /* Check if we tried to inline this method before */ |
19998 | |
19999 | if (methAttr & CORINFO_FLG_DONT_INLINE) |
20000 | { |
20001 | inlineResult.NoteFatal(InlineObservation::CALLEE_IS_NOINLINE); |
20002 | return; |
20003 | } |
20004 | |
20005 | /* Cannot inline synchronized methods */ |
20006 | |
20007 | if (methAttr & CORINFO_FLG_SYNCH) |
20008 | { |
20009 | inlineResult.NoteFatal(InlineObservation::CALLEE_IS_SYNCHRONIZED); |
20010 | return; |
20011 | } |
20012 | |
20013 | /* Do not inline if callee needs security checks (since they would then mark the wrong frame) */ |
20014 | |
20015 | if (methAttr & CORINFO_FLG_SECURITYCHECK) |
20016 | { |
20017 | inlineResult.NoteFatal(InlineObservation::CALLEE_NEEDS_SECURITY_CHECK); |
20018 | return; |
20019 | } |
20020 | |
20021 | /* Check legality of PInvoke callsite (for inlining of marshalling code) */ |
20022 | |
20023 | if (methAttr & CORINFO_FLG_PINVOKE) |
20024 | { |
20025 | // See comment in impCheckForPInvokeCall |
20026 | BasicBlock* block = compIsForInlining() ? impInlineInfo->iciBlock : compCurBB; |
20027 | if (!impCanPInvokeInlineCallSite(block)) |
20028 | { |
20029 | inlineResult.NoteFatal(InlineObservation::CALLSITE_PINVOKE_EH); |
20030 | return; |
20031 | } |
20032 | } |
20033 | |
20034 | InlineCandidateInfo* inlineCandidateInfo = nullptr; |
20035 | impCheckCanInline(call, fncHandle, methAttr, exactContextHnd, &inlineCandidateInfo, &inlineResult); |
20036 | |
20037 | if (inlineResult.IsFailure()) |
20038 | { |
20039 | return; |
20040 | } |
20041 | |
20042 | // The old value should be null OR this call should be a guarded devirtualization candidate. |
20043 | assert((call->gtInlineCandidateInfo == nullptr) || call->IsGuardedDevirtualizationCandidate()); |
20044 | |
20045 | // The new value should not be null. |
20046 | assert(inlineCandidateInfo != nullptr); |
20047 | inlineCandidateInfo->exactContextNeedsRuntimeLookup = exactContextNeedsRuntimeLookup; |
20048 | call->gtInlineCandidateInfo = inlineCandidateInfo; |
20049 | |
20050 | // Mark the call node as inline candidate. |
20051 | call->gtFlags |= GTF_CALL_INLINE_CANDIDATE; |
20052 | |
20053 | // Let the strategy know there's another candidate. |
20054 | impInlineRoot()->m_inlineStrategy->NoteCandidate(); |
20055 | |
20056 | // Since we're not actually inlining yet, and this call site is |
20057 | // still just an inline candidate, there's nothing to report. |
20058 | inlineResult.SetReported(); |
20059 | } |
20060 | |
20061 | /******************************************************************************/ |
20062 | // Returns true if the given intrinsic will be implemented by target-specific |
20063 | // instructions |
20064 | |
20065 | bool Compiler::IsTargetIntrinsic(CorInfoIntrinsics intrinsicId) |
20066 | { |
20067 | #if defined(_TARGET_XARCH_) |
20068 | switch (intrinsicId) |
20069 | { |
20070 | // AMD64/x86 has SSE2 instructions to directly compute sqrt/abs and SSE4.1 |
20071 | // instructions to directly compute round/ceiling/floor. |
20072 | // |
20073 | // TODO: Because the x86 backend only targets SSE for floating-point code, |
20074 | // it does not treat Sine, Cosine, or Round as intrinsics (JIT32 |
20075 | // implemented those intrinsics as x87 instructions). If this poses |
20076 | // a CQ problem, it may be necessary to change the implementation of |
20077 | // the helper calls to decrease call overhead or switch back to the |
20078 | // x87 instructions. This is tracked by #7097. |
20079 | case CORINFO_INTRINSIC_Sqrt: |
20080 | case CORINFO_INTRINSIC_Abs: |
20081 | return true; |
20082 | |
20083 | case CORINFO_INTRINSIC_Round: |
20084 | case CORINFO_INTRINSIC_Ceiling: |
20085 | case CORINFO_INTRINSIC_Floor: |
20086 | return compSupports(InstructionSet_SSE41); |
20087 | |
20088 | default: |
20089 | return false; |
20090 | } |
20091 | #elif defined(_TARGET_ARM64_) |
20092 | switch (intrinsicId) |
20093 | { |
20094 | case CORINFO_INTRINSIC_Sqrt: |
20095 | case CORINFO_INTRINSIC_Abs: |
20096 | case CORINFO_INTRINSIC_Round: |
20097 | case CORINFO_INTRINSIC_Floor: |
20098 | case CORINFO_INTRINSIC_Ceiling: |
20099 | return true; |
20100 | |
20101 | default: |
20102 | return false; |
20103 | } |
20104 | #elif defined(_TARGET_ARM_) |
20105 | switch (intrinsicId) |
20106 | { |
20107 | case CORINFO_INTRINSIC_Sqrt: |
20108 | case CORINFO_INTRINSIC_Abs: |
20109 | case CORINFO_INTRINSIC_Round: |
20110 | return true; |
20111 | |
20112 | default: |
20113 | return false; |
20114 | } |
20115 | #else |
20116 | // TODO: This portion of logic is not implemented for other arch. |
20117 | // The reason for returning true is that on all other arch the only intrinsic |
20118 | // enabled are target intrinsics. |
20119 | return true; |
20120 | #endif |
20121 | } |
20122 | |
20123 | /******************************************************************************/ |
20124 | // Returns true if the given intrinsic will be implemented by calling System.Math |
20125 | // methods. |
20126 | |
20127 | bool Compiler::IsIntrinsicImplementedByUserCall(CorInfoIntrinsics intrinsicId) |
20128 | { |
20129 | // Currently, if a math intrinsic is not implemented by target-specific |
20130 | // instructions, it will be implemented by a System.Math call. In the |
20131 | // future, if we turn to implementing some of them with helper calls, |
20132 | // this predicate needs to be revisited. |
20133 | return !IsTargetIntrinsic(intrinsicId); |
20134 | } |
20135 | |
20136 | bool Compiler::IsMathIntrinsic(CorInfoIntrinsics intrinsicId) |
20137 | { |
20138 | switch (intrinsicId) |
20139 | { |
20140 | case CORINFO_INTRINSIC_Sin: |
20141 | case CORINFO_INTRINSIC_Cbrt: |
20142 | case CORINFO_INTRINSIC_Sqrt: |
20143 | case CORINFO_INTRINSIC_Abs: |
20144 | case CORINFO_INTRINSIC_Cos: |
20145 | case CORINFO_INTRINSIC_Round: |
20146 | case CORINFO_INTRINSIC_Cosh: |
20147 | case CORINFO_INTRINSIC_Sinh: |
20148 | case CORINFO_INTRINSIC_Tan: |
20149 | case CORINFO_INTRINSIC_Tanh: |
20150 | case CORINFO_INTRINSIC_Asin: |
20151 | case CORINFO_INTRINSIC_Asinh: |
20152 | case CORINFO_INTRINSIC_Acos: |
20153 | case CORINFO_INTRINSIC_Acosh: |
20154 | case CORINFO_INTRINSIC_Atan: |
20155 | case CORINFO_INTRINSIC_Atan2: |
20156 | case CORINFO_INTRINSIC_Atanh: |
20157 | case CORINFO_INTRINSIC_Log10: |
20158 | case CORINFO_INTRINSIC_Pow: |
20159 | case CORINFO_INTRINSIC_Exp: |
20160 | case CORINFO_INTRINSIC_Ceiling: |
20161 | case CORINFO_INTRINSIC_Floor: |
20162 | return true; |
20163 | default: |
20164 | return false; |
20165 | } |
20166 | } |
20167 | |
20168 | bool Compiler::IsMathIntrinsic(GenTree* tree) |
20169 | { |
20170 | return (tree->OperGet() == GT_INTRINSIC) && IsMathIntrinsic(tree->gtIntrinsic.gtIntrinsicId); |
20171 | } |
20172 | |
20173 | //------------------------------------------------------------------------ |
20174 | // impDevirtualizeCall: Attempt to change a virtual vtable call into a |
20175 | // normal call |
20176 | // |
20177 | // Arguments: |
20178 | // call -- the call node to examine/modify |
20179 | // method -- [IN/OUT] the method handle for call. Updated iff call devirtualized. |
20180 | // methodFlags -- [IN/OUT] flags for the method to call. Updated iff call devirtualized. |
20181 | // contextHandle -- [IN/OUT] context handle for the call. Updated iff call devirtualized. |
20182 | // exactContextHnd -- [OUT] updated context handle iff call devirtualized |
20183 | // isLateDevirtualization -- if devirtualization is happening after importation |
20184 | // |
20185 | // Notes: |
20186 | // Virtual calls in IL will always "invoke" the base class method. |
20187 | // |
20188 | // This transformation looks for evidence that the type of 'this' |
20189 | // in the call is exactly known, is a final class or would invoke |
20190 | // a final method, and if that and other safety checks pan out, |
20191 | // modifies the call and the call info to create a direct call. |
20192 | // |
20193 | // This transformation is initially done in the importer and not |
20194 | // in some subsequent optimization pass because we want it to be |
20195 | // upstream of inline candidate identification. |
20196 | // |
20197 | // However, later phases may supply improved type information that |
20198 | // can enable further devirtualization. We currently reinvoke this |
20199 | // code after inlining, if the return value of the inlined call is |
20200 | // the 'this obj' of a subsequent virtual call. |
20201 | // |
20202 | // If devirtualization succeeds and the call's this object is the |
20203 | // result of a box, the jit will ask the EE for the unboxed entry |
20204 | // point. If this exists, the jit will see if it can rework the box |
20205 | // to instead make a local copy. If that is doable, the call is |
20206 | // updated to invoke the unboxed entry on the local copy. |
20207 | // |
20208 | // When guarded devirtualization is enabled, this method will mark |
20209 | // calls as guarded devirtualization candidates, if the type of `this` |
20210 | // is not exactly known, and there is a plausible guess for the type. |
20211 | |
20212 | void Compiler::impDevirtualizeCall(GenTreeCall* call, |
20213 | CORINFO_METHOD_HANDLE* method, |
20214 | unsigned* methodFlags, |
20215 | CORINFO_CONTEXT_HANDLE* contextHandle, |
20216 | CORINFO_CONTEXT_HANDLE* exactContextHandle, |
20217 | bool isLateDevirtualization) |
20218 | { |
20219 | assert(call != nullptr); |
20220 | assert(method != nullptr); |
20221 | assert(methodFlags != nullptr); |
20222 | assert(contextHandle != nullptr); |
20223 | |
20224 | // This should be a virtual vtable or virtual stub call. |
20225 | assert(call->IsVirtual()); |
20226 | |
20227 | // Bail if not optimizing |
20228 | if (opts.OptimizationDisabled()) |
20229 | { |
20230 | return; |
20231 | } |
20232 | |
20233 | #if defined(DEBUG) |
20234 | // Bail if devirt is disabled. |
20235 | if (JitConfig.JitEnableDevirtualization() == 0) |
20236 | { |
20237 | return; |
20238 | } |
20239 | |
20240 | const bool doPrint = JitConfig.JitPrintDevirtualizedMethods() == 1; |
20241 | #endif // DEBUG |
20242 | |
20243 | // Fetch information about the virtual method we're calling. |
20244 | CORINFO_METHOD_HANDLE baseMethod = *method; |
20245 | unsigned baseMethodAttribs = *methodFlags; |
20246 | |
20247 | if (baseMethodAttribs == 0) |
20248 | { |
20249 | // For late devirt we may not have method attributes, so fetch them. |
20250 | baseMethodAttribs = info.compCompHnd->getMethodAttribs(baseMethod); |
20251 | } |
20252 | else |
20253 | { |
20254 | #if defined(DEBUG) |
20255 | // Validate that callInfo has up to date method flags |
20256 | const DWORD freshBaseMethodAttribs = info.compCompHnd->getMethodAttribs(baseMethod); |
20257 | |
20258 | // All the base method attributes should agree, save that |
20259 | // CORINFO_FLG_DONT_INLINE may have changed from 0 to 1 |
20260 | // because of concurrent jitting activity. |
20261 | // |
20262 | // Note we don't look at this particular flag bit below, and |
20263 | // later on (if we do try and inline) we will rediscover why |
20264 | // the method can't be inlined, so there's no danger here in |
20265 | // seeing this particular flag bit in different states between |
20266 | // the cached and fresh values. |
20267 | if ((freshBaseMethodAttribs & ~CORINFO_FLG_DONT_INLINE) != (baseMethodAttribs & ~CORINFO_FLG_DONT_INLINE)) |
20268 | { |
20269 | assert(!"mismatched method attributes" ); |
20270 | } |
20271 | #endif // DEBUG |
20272 | } |
20273 | |
20274 | // In R2R mode, we might see virtual stub calls to |
20275 | // non-virtuals. For instance cases where the non-virtual method |
20276 | // is in a different assembly but is called via CALLVIRT. For |
20277 | // verison resilience we must allow for the fact that the method |
20278 | // might become virtual in some update. |
20279 | // |
20280 | // In non-R2R modes CALLVIRT <nonvirtual> will be turned into a |
20281 | // regular call+nullcheck upstream, so we won't reach this |
20282 | // point. |
20283 | if ((baseMethodAttribs & CORINFO_FLG_VIRTUAL) == 0) |
20284 | { |
20285 | assert(call->IsVirtualStub()); |
20286 | assert(opts.IsReadyToRun()); |
20287 | JITDUMP("\nimpDevirtualizeCall: [R2R] base method not virtual, sorry\n" ); |
20288 | return; |
20289 | } |
20290 | |
20291 | // See what we know about the type of 'this' in the call. |
20292 | GenTree* thisObj = call->gtCallObjp->gtEffectiveVal(false); |
20293 | GenTree* actualThisObj = nullptr; |
20294 | bool isExact = false; |
20295 | bool objIsNonNull = false; |
20296 | CORINFO_CLASS_HANDLE objClass = gtGetClassHandle(thisObj, &isExact, &objIsNonNull); |
20297 | |
20298 | // See if we have special knowlege that can get us a type or a better type. |
20299 | if ((objClass == nullptr) || !isExact) |
20300 | { |
20301 | // Walk back through any return expression placeholders |
20302 | actualThisObj = thisObj->gtRetExprVal(); |
20303 | |
20304 | // See if we landed on a call to a special intrinsic method |
20305 | if (actualThisObj->IsCall()) |
20306 | { |
20307 | GenTreeCall* thisObjCall = actualThisObj->AsCall(); |
20308 | if ((thisObjCall->gtCallMoreFlags & GTF_CALL_M_SPECIAL_INTRINSIC) != 0) |
20309 | { |
20310 | assert(thisObjCall->gtCallType == CT_USER_FUNC); |
20311 | CORINFO_METHOD_HANDLE specialIntrinsicHandle = thisObjCall->gtCallMethHnd; |
20312 | CORINFO_CLASS_HANDLE specialObjClass = impGetSpecialIntrinsicExactReturnType(specialIntrinsicHandle); |
20313 | if (specialObjClass != nullptr) |
20314 | { |
20315 | objClass = specialObjClass; |
20316 | isExact = true; |
20317 | objIsNonNull = true; |
20318 | } |
20319 | } |
20320 | } |
20321 | } |
20322 | |
20323 | // Bail if we know nothing. |
20324 | if (objClass == nullptr) |
20325 | { |
20326 | JITDUMP("\nimpDevirtualizeCall: no type available (op=%s)\n" , GenTree::OpName(thisObj->OperGet())); |
20327 | return; |
20328 | } |
20329 | |
20330 | // Fetch information about the class that introduced the virtual method. |
20331 | CORINFO_CLASS_HANDLE baseClass = info.compCompHnd->getMethodClass(baseMethod); |
20332 | const DWORD baseClassAttribs = info.compCompHnd->getClassAttribs(baseClass); |
20333 | |
20334 | #if !defined(FEATURE_CORECLR) |
20335 | // If base class is not beforefieldinit then devirtualizing may |
20336 | // cause us to miss a base class init trigger. Spec says we don't |
20337 | // need a trigger for ref class callvirts but desktop seems to |
20338 | // have one anyways. So defer. |
20339 | if ((baseClassAttribs & CORINFO_FLG_BEFOREFIELDINIT) == 0) |
20340 | { |
20341 | JITDUMP("\nimpDevirtualizeCall: base class has precise initialization, sorry\n" ); |
20342 | return; |
20343 | } |
20344 | #endif // FEATURE_CORECLR |
20345 | |
20346 | // Is the call an interface call? |
20347 | const bool isInterface = (baseClassAttribs & CORINFO_FLG_INTERFACE) != 0; |
20348 | |
20349 | // If the objClass is sealed (final), then we may be able to devirtualize. |
20350 | const DWORD objClassAttribs = info.compCompHnd->getClassAttribs(objClass); |
20351 | const bool objClassIsFinal = (objClassAttribs & CORINFO_FLG_FINAL) != 0; |
20352 | |
20353 | #if defined(DEBUG) |
20354 | const char* callKind = isInterface ? "interface" : "virtual" ; |
20355 | const char* objClassNote = "[?]" ; |
20356 | const char* objClassName = "?objClass" ; |
20357 | const char* baseClassName = "?baseClass" ; |
20358 | const char* baseMethodName = "?baseMethod" ; |
20359 | |
20360 | if (verbose || doPrint) |
20361 | { |
20362 | objClassNote = isExact ? " [exact]" : objClassIsFinal ? " [final]" : "" ; |
20363 | objClassName = info.compCompHnd->getClassName(objClass); |
20364 | baseClassName = info.compCompHnd->getClassName(baseClass); |
20365 | baseMethodName = eeGetMethodName(baseMethod, nullptr); |
20366 | |
20367 | if (verbose) |
20368 | { |
20369 | printf("\nimpDevirtualizeCall: Trying to devirtualize %s call:\n" |
20370 | " class for 'this' is %s%s (attrib %08x)\n" |
20371 | " base method is %s::%s\n" , |
20372 | callKind, objClassName, objClassNote, objClassAttribs, baseClassName, baseMethodName); |
20373 | } |
20374 | } |
20375 | #endif // defined(DEBUG) |
20376 | |
20377 | // See if the jit's best type for `obj` is an interface. |
20378 | // See for instance System.ValueTuple`8::GetHashCode, where lcl 0 is System.IValueTupleInternal |
20379 | // IL_021d: ldloc.0 |
20380 | // IL_021e: callvirt instance int32 System.Object::GetHashCode() |
20381 | // |
20382 | // If so, we can't devirtualize, but we may be able to do guarded devirtualization. |
20383 | if ((objClassAttribs & CORINFO_FLG_INTERFACE) != 0) |
20384 | { |
20385 | // If we're called during early devirtualiztion, attempt guarded devirtualization |
20386 | // if there's currently just one implementing class. |
20387 | if (exactContextHandle == nullptr) |
20388 | { |
20389 | JITDUMP("--- obj class is interface...unable to dervirtualize, sorry\n" ); |
20390 | return; |
20391 | } |
20392 | |
20393 | CORINFO_CLASS_HANDLE uniqueImplementingClass = NO_CLASS_HANDLE; |
20394 | |
20395 | // info.compCompHnd->getUniqueImplementingClass(objClass); |
20396 | |
20397 | if (uniqueImplementingClass == NO_CLASS_HANDLE) |
20398 | { |
20399 | JITDUMP("No unique implementor of interface %p (%s), sorry\n" , objClass, objClassName); |
20400 | return; |
20401 | } |
20402 | |
20403 | JITDUMP("Only known implementor of interface %p (%s) is %p (%s)!\n" , objClass, objClassName, |
20404 | uniqueImplementingClass, eeGetClassName(uniqueImplementingClass)); |
20405 | |
20406 | bool guessUniqueInterface = true; |
20407 | |
20408 | INDEBUG(guessUniqueInterface = (JitConfig.JitGuardedDevirtualizationGuessUniqueInterface() > 0);); |
20409 | |
20410 | if (!guessUniqueInterface) |
20411 | { |
20412 | JITDUMP("Guarded devirt for unique interface implementor is not enabled, sorry\n" ); |
20413 | return; |
20414 | } |
20415 | |
20416 | // Ask the runtime to determine the method that would be called based on the guessed-for type. |
20417 | CORINFO_CONTEXT_HANDLE ownerType = *contextHandle; |
20418 | CORINFO_METHOD_HANDLE uniqueImplementingMethod = |
20419 | info.compCompHnd->resolveVirtualMethod(baseMethod, uniqueImplementingClass, ownerType); |
20420 | |
20421 | if (uniqueImplementingMethod == nullptr) |
20422 | { |
20423 | JITDUMP("Can't figure out which method would be invoked, sorry\n" ); |
20424 | return; |
20425 | } |
20426 | |
20427 | JITDUMP("Interface call would invoke method %s\n" , eeGetMethodName(uniqueImplementingMethod, nullptr)); |
20428 | DWORD uniqueMethodAttribs = info.compCompHnd->getMethodAttribs(uniqueImplementingMethod); |
20429 | DWORD uniqueClassAttribs = info.compCompHnd->getClassAttribs(uniqueImplementingClass); |
20430 | |
20431 | addGuardedDevirtualizationCandidate(call, uniqueImplementingMethod, uniqueImplementingClass, |
20432 | uniqueMethodAttribs, uniqueClassAttribs); |
20433 | return; |
20434 | } |
20435 | |
20436 | // If we get this far, the jit has a lower bound class type for the `this` object being used for dispatch. |
20437 | // It may or may not know enough to devirtualize... |
20438 | if (isInterface) |
20439 | { |
20440 | assert(call->IsVirtualStub()); |
20441 | JITDUMP("--- base class is interface\n" ); |
20442 | } |
20443 | |
20444 | // Fetch the method that would be called based on the declared type of 'this' |
20445 | CORINFO_CONTEXT_HANDLE ownerType = *contextHandle; |
20446 | CORINFO_METHOD_HANDLE derivedMethod = info.compCompHnd->resolveVirtualMethod(baseMethod, objClass, ownerType); |
20447 | |
20448 | // If we failed to get a handle, we can't devirtualize. This can |
20449 | // happen when prejitting, if the devirtualization crosses |
20450 | // servicing bubble boundaries. |
20451 | // |
20452 | // Note if we have some way of guessing a better and more likely type we can do something similar to the code |
20453 | // above for the case where the best jit type is an interface type. |
20454 | if (derivedMethod == nullptr) |
20455 | { |
20456 | JITDUMP("--- no derived method, sorry\n" ); |
20457 | return; |
20458 | } |
20459 | |
20460 | // Fetch method attributes to see if method is marked final. |
20461 | DWORD derivedMethodAttribs = info.compCompHnd->getMethodAttribs(derivedMethod); |
20462 | const bool derivedMethodIsFinal = ((derivedMethodAttribs & CORINFO_FLG_FINAL) != 0); |
20463 | |
20464 | #if defined(DEBUG) |
20465 | const char* derivedClassName = "?derivedClass" ; |
20466 | const char* derivedMethodName = "?derivedMethod" ; |
20467 | |
20468 | const char* note = "inexact or not final" ; |
20469 | if (isExact) |
20470 | { |
20471 | note = "exact" ; |
20472 | } |
20473 | else if (objClassIsFinal) |
20474 | { |
20475 | note = "final class" ; |
20476 | } |
20477 | else if (derivedMethodIsFinal) |
20478 | { |
20479 | note = "final method" ; |
20480 | } |
20481 | |
20482 | if (verbose || doPrint) |
20483 | { |
20484 | derivedMethodName = eeGetMethodName(derivedMethod, &derivedClassName); |
20485 | if (verbose) |
20486 | { |
20487 | printf(" devirt to %s::%s -- %s\n" , derivedClassName, derivedMethodName, note); |
20488 | gtDispTree(call); |
20489 | } |
20490 | } |
20491 | #endif // defined(DEBUG) |
20492 | |
20493 | const bool canDevirtualize = isExact || objClassIsFinal || (!isInterface && derivedMethodIsFinal); |
20494 | |
20495 | if (!canDevirtualize) |
20496 | { |
20497 | JITDUMP(" Class not final or exact%s\n" , isInterface ? "" : ", and method not final" ); |
20498 | |
20499 | // Have we enabled guarded devirtualization by guessing the jit's best class? |
20500 | bool guessJitBestClass = true; |
20501 | INDEBUG(guessJitBestClass = (JitConfig.JitGuardedDevirtualizationGuessBestClass() > 0);); |
20502 | |
20503 | if (!guessJitBestClass) |
20504 | { |
20505 | JITDUMP("No guarded devirt: guessing for jit best class disabled\n" ); |
20506 | return; |
20507 | } |
20508 | |
20509 | // Don't try guarded devirtualiztion when we're doing late devirtualization. |
20510 | if (isLateDevirtualization) |
20511 | { |
20512 | JITDUMP("No guarded devirt during late devirtualization\n" ); |
20513 | return; |
20514 | } |
20515 | |
20516 | // We will use the class that introduced the method as our guess |
20517 | // for the runtime class of othe object. |
20518 | CORINFO_CLASS_HANDLE derivedClass = info.compCompHnd->getMethodClass(derivedMethod); |
20519 | |
20520 | // Try guarded devirtualization. |
20521 | addGuardedDevirtualizationCandidate(call, derivedMethod, derivedClass, derivedMethodAttribs, objClassAttribs); |
20522 | return; |
20523 | } |
20524 | |
20525 | // All checks done. Time to transform the call. |
20526 | assert(canDevirtualize); |
20527 | |
20528 | JITDUMP(" %s; can devirtualize\n" , note); |
20529 | |
20530 | // Make the updates. |
20531 | call->gtFlags &= ~GTF_CALL_VIRT_VTABLE; |
20532 | call->gtFlags &= ~GTF_CALL_VIRT_STUB; |
20533 | call->gtCallMethHnd = derivedMethod; |
20534 | call->gtCallType = CT_USER_FUNC; |
20535 | call->gtCallMoreFlags |= GTF_CALL_M_DEVIRTUALIZED; |
20536 | |
20537 | // Virtual calls include an implicit null check, which we may |
20538 | // now need to make explicit. |
20539 | if (!objIsNonNull) |
20540 | { |
20541 | call->gtFlags |= GTF_CALL_NULLCHECK; |
20542 | } |
20543 | |
20544 | // Clear the inline candidate info (may be non-null since |
20545 | // it's a union field used for other things by virtual |
20546 | // stubs) |
20547 | call->gtInlineCandidateInfo = nullptr; |
20548 | |
20549 | #if defined(DEBUG) |
20550 | if (verbose) |
20551 | { |
20552 | printf("... after devirt...\n" ); |
20553 | gtDispTree(call); |
20554 | } |
20555 | |
20556 | if (doPrint) |
20557 | { |
20558 | printf("Devirtualized %s call to %s:%s; now direct call to %s:%s [%s]\n" , callKind, baseClassName, |
20559 | baseMethodName, derivedClassName, derivedMethodName, note); |
20560 | } |
20561 | #endif // defined(DEBUG) |
20562 | |
20563 | // If the 'this' object is a box, see if we can find the unboxed entry point for the call. |
20564 | if (thisObj->IsBoxedValue()) |
20565 | { |
20566 | JITDUMP("Now have direct call to boxed entry point, looking for unboxed entry point\n" ); |
20567 | |
20568 | // Note for some shared methods the unboxed entry point requires an extra parameter. |
20569 | bool requiresInstMethodTableArg = false; |
20570 | CORINFO_METHOD_HANDLE unboxedEntryMethod = |
20571 | info.compCompHnd->getUnboxedEntry(derivedMethod, &requiresInstMethodTableArg); |
20572 | |
20573 | if (unboxedEntryMethod != nullptr) |
20574 | { |
20575 | // Since the call is the only consumer of the box, we know the box can't escape |
20576 | // since it is being passed an interior pointer. |
20577 | // |
20578 | // So, revise the box to simply create a local copy, use the address of that copy |
20579 | // as the this pointer, and update the entry point to the unboxed entry. |
20580 | // |
20581 | // Ideally, we then inline the boxed method and and if it turns out not to modify |
20582 | // the copy, we can undo the copy too. |
20583 | if (requiresInstMethodTableArg) |
20584 | { |
20585 | // Perform a trial box removal and ask for the type handle tree. |
20586 | JITDUMP("Unboxed entry needs method table arg...\n" ); |
20587 | GenTree* methodTableArg = gtTryRemoveBoxUpstreamEffects(thisObj, BR_DONT_REMOVE_WANT_TYPE_HANDLE); |
20588 | |
20589 | if (methodTableArg != nullptr) |
20590 | { |
20591 | // If that worked, turn the box into a copy to a local var |
20592 | JITDUMP("Found suitable method table arg tree [%06u]\n" , dspTreeID(methodTableArg)); |
20593 | GenTree* localCopyThis = gtTryRemoveBoxUpstreamEffects(thisObj, BR_MAKE_LOCAL_COPY); |
20594 | |
20595 | if (localCopyThis != nullptr) |
20596 | { |
20597 | // Pass the local var as this and the type handle as a new arg |
20598 | JITDUMP("Success! invoking unboxed entry point on local copy, and passing method table arg\n" ); |
20599 | call->gtCallObjp = localCopyThis; |
20600 | call->gtCallMoreFlags |= GTF_CALL_M_UNBOXED; |
20601 | |
20602 | // Prepend for R2L arg passing or empty L2R passing |
20603 | if ((Target::g_tgtArgOrder == Target::ARG_ORDER_R2L) || (call->gtCallArgs == nullptr)) |
20604 | { |
20605 | call->gtCallArgs = gtNewListNode(methodTableArg, call->gtCallArgs); |
20606 | } |
20607 | // Append for non-empty L2R |
20608 | else |
20609 | { |
20610 | GenTreeArgList* beforeArg = call->gtCallArgs; |
20611 | while (beforeArg->Rest() != nullptr) |
20612 | { |
20613 | beforeArg = beforeArg->Rest(); |
20614 | } |
20615 | |
20616 | beforeArg->Rest() = gtNewListNode(methodTableArg, nullptr); |
20617 | } |
20618 | |
20619 | call->gtCallMethHnd = unboxedEntryMethod; |
20620 | derivedMethod = unboxedEntryMethod; |
20621 | |
20622 | // Method attributes will differ because unboxed entry point is shared |
20623 | const DWORD unboxedMethodAttribs = info.compCompHnd->getMethodAttribs(unboxedEntryMethod); |
20624 | JITDUMP("Updating method attribs from 0x%08x to 0x%08x\n" , derivedMethodAttribs, |
20625 | unboxedMethodAttribs); |
20626 | derivedMethodAttribs = unboxedMethodAttribs; |
20627 | } |
20628 | else |
20629 | { |
20630 | JITDUMP("Sorry, failed to undo the box -- can't convert to local copy\n" ); |
20631 | } |
20632 | } |
20633 | else |
20634 | { |
20635 | JITDUMP("Sorry, failed to undo the box -- can't find method table arg\n" ); |
20636 | } |
20637 | } |
20638 | else |
20639 | { |
20640 | JITDUMP("Found unboxed entry point, trying to simplify box to a local copy\n" ); |
20641 | GenTree* localCopyThis = gtTryRemoveBoxUpstreamEffects(thisObj, BR_MAKE_LOCAL_COPY); |
20642 | |
20643 | if (localCopyThis != nullptr) |
20644 | { |
20645 | JITDUMP("Success! invoking unboxed entry point on local copy\n" ); |
20646 | call->gtCallObjp = localCopyThis; |
20647 | call->gtCallMethHnd = unboxedEntryMethod; |
20648 | call->gtCallMoreFlags |= GTF_CALL_M_UNBOXED; |
20649 | derivedMethod = unboxedEntryMethod; |
20650 | } |
20651 | else |
20652 | { |
20653 | JITDUMP("Sorry, failed to undo the box\n" ); |
20654 | } |
20655 | } |
20656 | } |
20657 | else |
20658 | { |
20659 | // Many of the low-level methods on value classes won't have unboxed entries, |
20660 | // as they need access to the type of the object. |
20661 | // |
20662 | // Note this may be a cue for us to stack allocate the boxed object, since |
20663 | // we probably know that these objects don't escape. |
20664 | JITDUMP("Sorry, failed to find unboxed entry point\n" ); |
20665 | } |
20666 | } |
20667 | |
20668 | // Fetch the class that introduced the derived method. |
20669 | // |
20670 | // Note this may not equal objClass, if there is a |
20671 | // final method that objClass inherits. |
20672 | CORINFO_CLASS_HANDLE derivedClass = info.compCompHnd->getMethodClass(derivedMethod); |
20673 | |
20674 | // Need to update call info too. This is fragile |
20675 | // but hopefully the derived method conforms to |
20676 | // the base in most other ways. |
20677 | *method = derivedMethod; |
20678 | *methodFlags = derivedMethodAttribs; |
20679 | *contextHandle = MAKE_METHODCONTEXT(derivedMethod); |
20680 | |
20681 | // Update context handle. |
20682 | if ((exactContextHandle != nullptr) && (*exactContextHandle != nullptr)) |
20683 | { |
20684 | *exactContextHandle = MAKE_METHODCONTEXT(derivedMethod); |
20685 | } |
20686 | |
20687 | #ifdef FEATURE_READYTORUN_COMPILER |
20688 | if (opts.IsReadyToRun()) |
20689 | { |
20690 | // For R2R, getCallInfo triggers bookkeeping on the zap |
20691 | // side so we need to call it here. |
20692 | // |
20693 | // First, cons up a suitable resolved token. |
20694 | CORINFO_RESOLVED_TOKEN derivedResolvedToken = {}; |
20695 | |
20696 | derivedResolvedToken.tokenScope = info.compScopeHnd; |
20697 | derivedResolvedToken.tokenContext = *contextHandle; |
20698 | derivedResolvedToken.token = info.compCompHnd->getMethodDefFromMethod(derivedMethod); |
20699 | derivedResolvedToken.tokenType = CORINFO_TOKENKIND_Method; |
20700 | derivedResolvedToken.hClass = derivedClass; |
20701 | derivedResolvedToken.hMethod = derivedMethod; |
20702 | |
20703 | // Look up the new call info. |
20704 | CORINFO_CALL_INFO derivedCallInfo; |
20705 | eeGetCallInfo(&derivedResolvedToken, nullptr, addVerifyFlag(CORINFO_CALLINFO_ALLOWINSTPARAM), &derivedCallInfo); |
20706 | |
20707 | // Update the call. |
20708 | call->gtCallMoreFlags &= ~GTF_CALL_M_VIRTSTUB_REL_INDIRECT; |
20709 | call->gtCallMoreFlags &= ~GTF_CALL_M_R2R_REL_INDIRECT; |
20710 | call->setEntryPoint(derivedCallInfo.codePointerLookup.constLookup); |
20711 | } |
20712 | #endif // FEATURE_READYTORUN_COMPILER |
20713 | } |
20714 | |
20715 | //------------------------------------------------------------------------ |
20716 | // impGetSpecialIntrinsicExactReturnType: Look for special cases where a call |
20717 | // to an intrinsic returns an exact type |
20718 | // |
20719 | // Arguments: |
20720 | // methodHnd -- handle for the special intrinsic method |
20721 | // |
20722 | // Returns: |
20723 | // Exact class handle returned by the intrinsic call, if known. |
20724 | // Nullptr if not known, or not likely to lead to beneficial optimization. |
20725 | |
20726 | CORINFO_CLASS_HANDLE Compiler::impGetSpecialIntrinsicExactReturnType(CORINFO_METHOD_HANDLE methodHnd) |
20727 | { |
20728 | JITDUMP("Special intrinsic: looking for exact type returned by %s\n" , eeGetMethodFullName(methodHnd)); |
20729 | |
20730 | CORINFO_CLASS_HANDLE result = nullptr; |
20731 | |
20732 | // See what intrinisc we have... |
20733 | const NamedIntrinsic ni = lookupNamedIntrinsic(methodHnd); |
20734 | switch (ni) |
20735 | { |
20736 | case NI_System_Collections_Generic_EqualityComparer_get_Default: |
20737 | { |
20738 | // Expect one class generic parameter; figure out which it is. |
20739 | CORINFO_SIG_INFO sig; |
20740 | info.compCompHnd->getMethodSig(methodHnd, &sig); |
20741 | assert(sig.sigInst.classInstCount == 1); |
20742 | CORINFO_CLASS_HANDLE typeHnd = sig.sigInst.classInst[0]; |
20743 | assert(typeHnd != nullptr); |
20744 | |
20745 | // Lookup can incorrect when we have __Canon as it won't appear |
20746 | // to implement any interface types. |
20747 | // |
20748 | // And if we do not have a final type, devirt & inlining is |
20749 | // unlikely to result in much simplification. |
20750 | // |
20751 | // We can use CORINFO_FLG_FINAL to screen out both of these cases. |
20752 | const DWORD typeAttribs = info.compCompHnd->getClassAttribs(typeHnd); |
20753 | const bool isFinalType = ((typeAttribs & CORINFO_FLG_FINAL) != 0); |
20754 | |
20755 | if (isFinalType) |
20756 | { |
20757 | result = info.compCompHnd->getDefaultEqualityComparerClass(typeHnd); |
20758 | JITDUMP("Special intrinsic for type %s: return type is %s\n" , eeGetClassName(typeHnd), |
20759 | result != nullptr ? eeGetClassName(result) : "unknown" ); |
20760 | } |
20761 | else |
20762 | { |
20763 | JITDUMP("Special intrinsic for type %s: type not final, so deferring opt\n" , eeGetClassName(typeHnd)); |
20764 | } |
20765 | |
20766 | break; |
20767 | } |
20768 | |
20769 | default: |
20770 | { |
20771 | JITDUMP("This special intrinsic not handled, sorry...\n" ); |
20772 | break; |
20773 | } |
20774 | } |
20775 | |
20776 | return result; |
20777 | } |
20778 | |
20779 | //------------------------------------------------------------------------ |
20780 | // impAllocateToken: create CORINFO_RESOLVED_TOKEN into jit-allocated memory and init it. |
20781 | // |
20782 | // Arguments: |
20783 | // token - init value for the allocated token. |
20784 | // |
20785 | // Return Value: |
20786 | // pointer to token into jit-allocated memory. |
20787 | CORINFO_RESOLVED_TOKEN* Compiler::impAllocateToken(CORINFO_RESOLVED_TOKEN token) |
20788 | { |
20789 | CORINFO_RESOLVED_TOKEN* memory = getAllocator(CMK_Unknown).allocate<CORINFO_RESOLVED_TOKEN>(1); |
20790 | *memory = token; |
20791 | return memory; |
20792 | } |
20793 | |
20794 | //------------------------------------------------------------------------ |
20795 | // SpillRetExprHelper: iterate through arguments tree and spill ret_expr to local variables. |
20796 | // |
20797 | class SpillRetExprHelper |
20798 | { |
20799 | public: |
20800 | SpillRetExprHelper(Compiler* comp) : comp(comp) |
20801 | { |
20802 | } |
20803 | |
20804 | void StoreRetExprResultsInArgs(GenTreeCall* call) |
20805 | { |
20806 | GenTreeArgList** pArgs = &call->gtCallArgs; |
20807 | if (*pArgs != nullptr) |
20808 | { |
20809 | comp->fgWalkTreePre((GenTree**)pArgs, SpillRetExprVisitor, this); |
20810 | } |
20811 | |
20812 | GenTree** pThisArg = &call->gtCallObjp; |
20813 | if (*pThisArg != nullptr) |
20814 | { |
20815 | comp->fgWalkTreePre(pThisArg, SpillRetExprVisitor, this); |
20816 | } |
20817 | } |
20818 | |
20819 | private: |
20820 | static Compiler::fgWalkResult SpillRetExprVisitor(GenTree** pTree, Compiler::fgWalkData* fgWalkPre) |
20821 | { |
20822 | assert((pTree != nullptr) && (*pTree != nullptr)); |
20823 | GenTree* tree = *pTree; |
20824 | if ((tree->gtFlags & GTF_CALL) == 0) |
20825 | { |
20826 | // Trees with ret_expr are marked as GTF_CALL. |
20827 | return Compiler::WALK_SKIP_SUBTREES; |
20828 | } |
20829 | if (tree->OperGet() == GT_RET_EXPR) |
20830 | { |
20831 | SpillRetExprHelper* walker = static_cast<SpillRetExprHelper*>(fgWalkPre->pCallbackData); |
20832 | walker->StoreRetExprAsLocalVar(pTree); |
20833 | } |
20834 | return Compiler::WALK_CONTINUE; |
20835 | } |
20836 | |
20837 | void StoreRetExprAsLocalVar(GenTree** pRetExpr) |
20838 | { |
20839 | GenTree* retExpr = *pRetExpr; |
20840 | assert(retExpr->OperGet() == GT_RET_EXPR); |
20841 | const unsigned tmp = comp->lvaGrabTemp(true DEBUGARG("spilling ret_expr" )); |
20842 | JITDUMP("Storing return expression [%06u] to a local var V%02u.\n" , comp->dspTreeID(retExpr), tmp); |
20843 | comp->impAssignTempGen(tmp, retExpr, (unsigned)Compiler::CHECK_SPILL_NONE); |
20844 | *pRetExpr = comp->gtNewLclvNode(tmp, retExpr->TypeGet()); |
20845 | |
20846 | if (retExpr->TypeGet() == TYP_REF) |
20847 | { |
20848 | assert(comp->lvaTable[tmp].lvSingleDef == 0); |
20849 | comp->lvaTable[tmp].lvSingleDef = 1; |
20850 | JITDUMP("Marked V%02u as a single def temp\n" , tmp); |
20851 | |
20852 | bool isExact = false; |
20853 | bool isNonNull = false; |
20854 | CORINFO_CLASS_HANDLE retClsHnd = comp->gtGetClassHandle(retExpr, &isExact, &isNonNull); |
20855 | if (retClsHnd != nullptr) |
20856 | { |
20857 | comp->lvaSetClass(tmp, retClsHnd, isExact); |
20858 | } |
20859 | } |
20860 | } |
20861 | |
20862 | private: |
20863 | Compiler* comp; |
20864 | }; |
20865 | |
20866 | //------------------------------------------------------------------------ |
20867 | // addFatPointerCandidate: mark the call and the method, that they have a fat pointer candidate. |
20868 | // Spill ret_expr in the call node, because they can't be cloned. |
20869 | // |
20870 | // Arguments: |
20871 | // call - fat calli candidate |
20872 | // |
20873 | void Compiler::addFatPointerCandidate(GenTreeCall* call) |
20874 | { |
20875 | JITDUMP("Marking call [%06u] as fat pointer candidate\n" , dspTreeID(call)); |
20876 | setMethodHasFatPointer(); |
20877 | call->SetFatPointerCandidate(); |
20878 | SpillRetExprHelper helper(this); |
20879 | helper.StoreRetExprResultsInArgs(call); |
20880 | } |
20881 | |
20882 | //------------------------------------------------------------------------ |
20883 | // addGuardedDevirtualizationCandidate: potentially mark the call as a guarded |
20884 | // devirtualization candidate |
20885 | // |
20886 | // Notes: |
20887 | // |
20888 | // We currently do not mark calls as candidates when prejitting. This was done |
20889 | // to simplify bringing up the associated transformation. It is worth revisiting |
20890 | // if we think we can come up with a good guess for the class when prejitting. |
20891 | // |
20892 | // Call sites in rare or unoptimized code, and calls that require cookies are |
20893 | // also not marked as candidates. |
20894 | // |
20895 | // As part of marking the candidate, the code spills GT_RET_EXPRs anywhere in any |
20896 | // child tree, because and we need to clone all these trees when we clone the call |
20897 | // as part of guarded devirtualization, and these IR nodes can't be cloned. |
20898 | // |
20899 | // Arguments: |
20900 | // call - potentual guarded devirtialization candidate |
20901 | // methodHandle - method that will be invoked if the class test succeeds |
20902 | // classHandle - class that will be tested for at runtime |
20903 | // methodAttr - attributes of the method |
20904 | // classAttr - attributes of the class |
20905 | // |
20906 | void Compiler::addGuardedDevirtualizationCandidate(GenTreeCall* call, |
20907 | CORINFO_METHOD_HANDLE methodHandle, |
20908 | CORINFO_CLASS_HANDLE classHandle, |
20909 | unsigned methodAttr, |
20910 | unsigned classAttr) |
20911 | { |
20912 | // This transformation only makes sense for virtual calls |
20913 | assert(call->IsVirtual()); |
20914 | |
20915 | // Only mark calls if the feature is enabled. |
20916 | const bool isEnabled = JitConfig.JitEnableGuardedDevirtualization() > 0; |
20917 | |
20918 | if (!isEnabled) |
20919 | { |
20920 | JITDUMP("NOT Marking call [%06u] as guarded devirtualization candidate -- disabled by jit config\n" , |
20921 | dspTreeID(call)); |
20922 | return; |
20923 | } |
20924 | |
20925 | // Bail when prejitting. We only do this for jitted code. |
20926 | // We shoud revisit this if we think we can come up with good class guesses when prejitting. |
20927 | if (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_PREJIT)) |
20928 | { |
20929 | JITDUMP("NOT Marking call [%06u] as guarded devirtualization candidate -- prejitting" , dspTreeID(call)); |
20930 | return; |
20931 | } |
20932 | |
20933 | // Bail if not optimizing or the call site is very likely cold |
20934 | if (compCurBB->isRunRarely() || opts.OptimizationDisabled()) |
20935 | { |
20936 | JITDUMP("NOT Marking call [%06u] as guarded devirtualization candidate -- rare / dbg / minopts\n" , |
20937 | dspTreeID(call)); |
20938 | return; |
20939 | } |
20940 | |
20941 | // CT_INDRECT calls may use the cookie, bail if so... |
20942 | // |
20943 | // If transforming these provides a benefit, we could save this off in the same way |
20944 | // we save the stub address below. |
20945 | if ((call->gtCallType == CT_INDIRECT) && (call->gtCall.gtCallCookie != nullptr)) |
20946 | { |
20947 | return; |
20948 | } |
20949 | |
20950 | // We're all set, proceed with candidate creation. |
20951 | JITDUMP("Marking call [%06u] as guarded devirtualization candidate; will guess for class %s\n" , dspTreeID(call), |
20952 | eeGetClassName(classHandle)); |
20953 | setMethodHasGuardedDevirtualization(); |
20954 | call->SetGuardedDevirtualizationCandidate(); |
20955 | |
20956 | // Spill off any GT_RET_EXPR subtrees so we can clone the call. |
20957 | SpillRetExprHelper helper(this); |
20958 | helper.StoreRetExprResultsInArgs(call); |
20959 | |
20960 | // Gather some information for later. Note we actually allocate InlineCandidateInfo |
20961 | // here, as the devirtualized half of this call will likely become an inline candidate. |
20962 | GuardedDevirtualizationCandidateInfo* pInfo = new (this, CMK_Inlining) InlineCandidateInfo; |
20963 | |
20964 | pInfo->guardedMethodHandle = methodHandle; |
20965 | pInfo->guardedClassHandle = classHandle; |
20966 | |
20967 | // Save off the stub address since it shares a union with the candidate info. |
20968 | if (call->IsVirtualStub()) |
20969 | { |
20970 | JITDUMP("Saving stub addr %p in candidate info\n" , call->gtStubCallStubAddr); |
20971 | pInfo->stubAddr = call->gtStubCallStubAddr; |
20972 | } |
20973 | else |
20974 | { |
20975 | pInfo->stubAddr = nullptr; |
20976 | } |
20977 | |
20978 | call->gtGuardedDevirtualizationCandidateInfo = pInfo; |
20979 | } |
20980 | |