| 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 | |