diff options
Diffstat (limited to 'src/jit/importer.cpp')
-rw-r--r-- | src/jit/importer.cpp | 17997 |
1 files changed, 17997 insertions, 0 deletions
diff --git a/src/jit/importer.cpp b/src/jit/importer.cpp new file mode 100644 index 0000000000..d04ded78fa --- /dev/null +++ b/src/jit/importer.cpp @@ -0,0 +1,17997 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +XX XX +XX Importer XX +XX XX +XX Imports the given method and converts it to semantic trees XX +XX XX +XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +*/ + +#include "jitpch.h" +#ifdef _MSC_VER +#pragma hdrstop +#endif + +#include "corexcep.h" + +#define Verify(cond, msg) \ + do \ + { \ + if (!(cond)) \ + { \ + verRaiseVerifyExceptionIfNeeded(INDEBUG(msg) DEBUGARG(__FILE__) DEBUGARG(__LINE__)); \ + } \ + } while (0) + +#define VerifyOrReturn(cond, msg) \ + do \ + { \ + if (!(cond)) \ + { \ + verRaiseVerifyExceptionIfNeeded(INDEBUG(msg) DEBUGARG(__FILE__) DEBUGARG(__LINE__)); \ + return; \ + } \ + } while (0) + +#define VerifyOrReturnSpeculative(cond, msg, speculative) \ + do \ + { \ + if (speculative) \ + { \ + if (!(cond)) \ + { \ + return false; \ + } \ + } \ + else \ + { \ + if (!(cond)) \ + { \ + verRaiseVerifyExceptionIfNeeded(INDEBUG(msg) DEBUGARG(__FILE__) DEBUGARG(__LINE__)); \ + return false; \ + } \ + } \ + } while (0) + +/*****************************************************************************/ + +void Compiler::impInit() +{ +#ifdef DEBUG + impTreeList = impTreeLast = nullptr; +#endif + +#if defined(DEBUG) + impInlinedCodeSize = 0; +#endif + + seenConditionalJump = false; +} + +/***************************************************************************** + * + * Pushes the given tree on the stack. + */ + +void Compiler::impPushOnStack(GenTreePtr tree, typeInfo ti) +{ + /* Check for overflow. If inlining, we may be using a bigger stack */ + + if ((verCurrentState.esStackDepth >= info.compMaxStack) && + (verCurrentState.esStackDepth >= impStkSize || ((compCurBB->bbFlags & BBF_IMPORTED) == 0))) + { + BADCODE("stack overflow"); + } + +#ifdef DEBUG + // If we are pushing a struct, make certain we know the precise type! + if (tree->TypeGet() == TYP_STRUCT) + { + assert(ti.IsType(TI_STRUCT)); + CORINFO_CLASS_HANDLE clsHnd = ti.GetClassHandle(); + assert(clsHnd != NO_CLASS_HANDLE); + } + + if (tiVerificationNeeded && !ti.IsDead()) + { + assert(typeInfo::AreEquivalent(NormaliseForStack(ti), ti)); // types are normalized + + // The ti type is consistent with the tree type. + // + + // On 64-bit systems, nodes whose "proper" type is "native int" get labeled TYP_LONG. + // In the verification type system, we always transform "native int" to "TI_INT". + // Ideally, we would keep track of which nodes labeled "TYP_LONG" are really "native int", but + // attempts to do that have proved too difficult. Instead, we'll assume that in checks like this, + // when there's a mismatch, it's because of this reason -- the typeInfo::AreEquivalentModuloNativeInt + // method used in the last disjunct allows exactly this mismatch. + assert(ti.IsDead() || ti.IsByRef() && (tree->TypeGet() == TYP_I_IMPL || tree->TypeGet() == TYP_BYREF) || + ti.IsUnboxedGenericTypeVar() && tree->TypeGet() == TYP_REF || + ti.IsObjRef() && tree->TypeGet() == TYP_REF || ti.IsMethod() && tree->TypeGet() == TYP_I_IMPL || + ti.IsType(TI_STRUCT) && tree->TypeGet() != TYP_REF || + typeInfo::AreEquivalentModuloNativeInt(NormaliseForStack(ti), + NormaliseForStack(typeInfo(tree->TypeGet())))); + + // If it is a struct type, make certain we normalized the primitive types + assert(!ti.IsType(TI_STRUCT) || + info.compCompHnd->getTypeForPrimitiveValueClass(ti.GetClassHandle()) == CORINFO_TYPE_UNDEF); + } + +#if VERBOSE_VERIFY + if (VERBOSE && tiVerificationNeeded) + { + printf("\n"); + printf(TI_DUMP_PADDING); + printf("About to push to stack: "); + ti.Dump(); + } +#endif // VERBOSE_VERIFY + +#endif // DEBUG + + verCurrentState.esStack[verCurrentState.esStackDepth].seTypeInfo = ti; + verCurrentState.esStack[verCurrentState.esStackDepth++].val = tree; + + if ((tree->gtType == TYP_LONG) && (compLongUsed == false)) + { + compLongUsed = true; + } + else if (((tree->gtType == TYP_FLOAT) || (tree->gtType == TYP_DOUBLE)) && (compFloatingPointUsed == false)) + { + compFloatingPointUsed = true; + } +} + +/******************************************************************************/ +// used in the inliner, where we can assume typesafe code. please don't use in the importer!! +inline void Compiler::impPushOnStackNoType(GenTreePtr tree) +{ + assert(verCurrentState.esStackDepth < impStkSize); + INDEBUG(verCurrentState.esStack[verCurrentState.esStackDepth].seTypeInfo = typeInfo()); + verCurrentState.esStack[verCurrentState.esStackDepth++].val = tree; + + if ((tree->gtType == TYP_LONG) && (compLongUsed == false)) + { + compLongUsed = true; + } + else if (((tree->gtType == TYP_FLOAT) || (tree->gtType == TYP_DOUBLE)) && (compFloatingPointUsed == false)) + { + compFloatingPointUsed = true; + } +} + +inline void Compiler::impPushNullObjRefOnStack() +{ + impPushOnStack(gtNewIconNode(0, TYP_REF), typeInfo(TI_NULL)); +} + +// This method gets called when we run into unverifiable code +// (and we are verifying the method) + +inline void Compiler::verRaiseVerifyExceptionIfNeeded(INDEBUG(const char* msg) DEBUGARG(const char* file) + DEBUGARG(unsigned line)) +{ + // Remember that the code is not verifiable + // Note that the method may yet pass canSkipMethodVerification(), + // and so the presence of unverifiable code may not be an issue. + tiIsVerifiableCode = FALSE; + +#ifdef DEBUG + const char* tail = strrchr(file, '\\'); + if (tail) + { + file = tail + 1; + } + + if (JitConfig.JitBreakOnUnsafeCode()) + { + assert(!"Unsafe code detected"); + } +#endif + + JITLOG((LL_INFO10000, "Detected unsafe code: %s:%d : %s, while compiling %s opcode %s, IL offset %x\n", file, line, + msg, info.compFullName, impCurOpcName, impCurOpcOffs)); + + if (verNeedsVerification() || compIsForImportOnly()) + { + JITLOG((LL_ERROR, "Verification failure: %s:%d : %s, while compiling %s opcode %s, IL offset %x\n", file, line, + msg, info.compFullName, impCurOpcName, impCurOpcOffs)); + verRaiseVerifyException(INDEBUG(msg) DEBUGARG(file) DEBUGARG(line)); + } +} + +inline void DECLSPEC_NORETURN Compiler::verRaiseVerifyException(INDEBUG(const char* msg) DEBUGARG(const char* file) + DEBUGARG(unsigned line)) +{ + JITLOG((LL_ERROR, "Verification failure: %s:%d : %s, while compiling %s opcode %s, IL offset %x\n", file, line, + msg, info.compFullName, impCurOpcName, impCurOpcOffs)); + +#ifdef DEBUG + // BreakIfDebuggerPresent(); + if (getBreakOnBadCode()) + { + assert(!"Typechecking error"); + } +#endif + + RaiseException(SEH_VERIFICATION_EXCEPTION, EXCEPTION_NONCONTINUABLE, 0, nullptr); + UNREACHABLE(); +} + +// helper function that will tell us if the IL instruction at the addr passed +// by param consumes an address at the top of the stack. We use it to save +// us lvAddrTaken +bool Compiler::impILConsumesAddr(const BYTE* codeAddr, CORINFO_METHOD_HANDLE fncHandle, CORINFO_MODULE_HANDLE scpHandle) +{ + assert(!compIsForInlining()); + + OPCODE opcode; + + opcode = (OPCODE)getU1LittleEndian(codeAddr); + + switch (opcode) + { + // case CEE_LDFLDA: We're taking this one out as if you have a sequence + // like + // + // ldloca.0 + // ldflda whatever + // + // of a primitivelike struct, you end up after morphing with addr of a local + // that's not marked as addrtaken, which is wrong. Also ldflda is usually used + // for structs that contain other structs, which isnt a case we handle very + // well now for other reasons. + + case CEE_LDFLD: + { + // We won't collapse small fields. This is probably not the right place to have this + // check, but we're only using the function for this purpose, and is easy to factor + // out if we need to do so. + + CORINFO_RESOLVED_TOKEN resolvedToken; + impResolveToken(codeAddr + sizeof(__int8), &resolvedToken, CORINFO_TOKENKIND_Field); + + CORINFO_CLASS_HANDLE clsHnd; + var_types lclTyp = JITtype2varType(info.compCompHnd->getFieldType(resolvedToken.hField, &clsHnd)); + + // Preserve 'small' int types + if (lclTyp > TYP_INT) + { + lclTyp = genActualType(lclTyp); + } + + if (varTypeIsSmall(lclTyp)) + { + return false; + } + + return true; + } + default: + break; + } + + return false; +} + +void Compiler::impResolveToken(const BYTE* addr, CORINFO_RESOLVED_TOKEN* pResolvedToken, CorInfoTokenKind kind) +{ + pResolvedToken->tokenContext = impTokenLookupContextHandle; + pResolvedToken->tokenScope = info.compScopeHnd; + pResolvedToken->token = getU4LittleEndian(addr); + pResolvedToken->tokenType = kind; + + if (!tiVerificationNeeded) + { + info.compCompHnd->resolveToken(pResolvedToken); + } + else + { + Verify(eeTryResolveToken(pResolvedToken), "Token resolution failed"); + } +} + +/***************************************************************************** + * + * Pop one tree from the stack. + */ + +StackEntry Compiler::impPopStack() +{ + if (verCurrentState.esStackDepth == 0) + { + BADCODE("stack underflow"); + } + +#ifdef DEBUG +#if VERBOSE_VERIFY + if (VERBOSE && tiVerificationNeeded) + { + JITDUMP("\n"); + printf(TI_DUMP_PADDING); + printf("About to pop from the stack: "); + const typeInfo& ti = verCurrentState.esStack[verCurrentState.esStackDepth - 1].seTypeInfo; + ti.Dump(); + } +#endif // VERBOSE_VERIFY +#endif // DEBUG + + return verCurrentState.esStack[--verCurrentState.esStackDepth]; +} + +StackEntry Compiler::impPopStack(CORINFO_CLASS_HANDLE& structType) +{ + StackEntry ret = impPopStack(); + structType = verCurrentState.esStack[verCurrentState.esStackDepth].seTypeInfo.GetClassHandle(); + return (ret); +} + +GenTreePtr Compiler::impPopStack(typeInfo& ti) +{ + StackEntry ret = impPopStack(); + ti = ret.seTypeInfo; + return (ret.val); +} + +/***************************************************************************** + * + * Peep at n'th (0-based) tree on the top of the stack. + */ + +StackEntry& Compiler::impStackTop(unsigned n) +{ + if (verCurrentState.esStackDepth <= n) + { + BADCODE("stack underflow"); + } + + return verCurrentState.esStack[verCurrentState.esStackDepth - n - 1]; +} +/***************************************************************************** + * Some of the trees are spilled specially. While unspilling them, or + * making a copy, these need to be handled specially. The function + * enumerates the operators possible after spilling. + */ + +#ifdef DEBUG // only used in asserts +static bool impValidSpilledStackEntry(GenTreePtr tree) +{ + if (tree->gtOper == GT_LCL_VAR) + { + return true; + } + + if (tree->OperIsConst()) + { + return true; + } + + return false; +} +#endif + +/***************************************************************************** + * + * The following logic is used to save/restore stack contents. + * If 'copy' is true, then we make a copy of the trees on the stack. These + * have to all be cloneable/spilled values. + */ + +void Compiler::impSaveStackState(SavedStack* savePtr, bool copy) +{ + savePtr->ssDepth = verCurrentState.esStackDepth; + + if (verCurrentState.esStackDepth) + { + savePtr->ssTrees = new (this, CMK_ImpStack) StackEntry[verCurrentState.esStackDepth]; + size_t saveSize = verCurrentState.esStackDepth * sizeof(*savePtr->ssTrees); + + if (copy) + { + StackEntry* table = savePtr->ssTrees; + + /* Make a fresh copy of all the stack entries */ + + for (unsigned level = 0; level < verCurrentState.esStackDepth; level++, table++) + { + table->seTypeInfo = verCurrentState.esStack[level].seTypeInfo; + GenTreePtr tree = verCurrentState.esStack[level].val; + + assert(impValidSpilledStackEntry(tree)); + + switch (tree->gtOper) + { + case GT_CNS_INT: + case GT_CNS_LNG: + case GT_CNS_DBL: + case GT_CNS_STR: + case GT_LCL_VAR: + table->val = gtCloneExpr(tree); + break; + + default: + assert(!"Bad oper - Not covered by impValidSpilledStackEntry()"); + break; + } + } + } + else + { + memcpy(savePtr->ssTrees, verCurrentState.esStack, saveSize); + } + } +} + +void Compiler::impRestoreStackState(SavedStack* savePtr) +{ + verCurrentState.esStackDepth = savePtr->ssDepth; + + if (verCurrentState.esStackDepth) + { + memcpy(verCurrentState.esStack, savePtr->ssTrees, + verCurrentState.esStackDepth * sizeof(*verCurrentState.esStack)); + } +} + +/***************************************************************************** + * + * Get the tree list started for a new basic block. + */ +inline void Compiler::impBeginTreeList() +{ + assert(impTreeList == nullptr && impTreeLast == nullptr); + + impTreeList = impTreeLast = new (this, GT_BEG_STMTS) GenTree(GT_BEG_STMTS, TYP_VOID); +} + +/***************************************************************************** + * + * Store the given start and end stmt in the given basic block. This is + * mostly called by impEndTreeList(BasicBlock *block). It is called + * directly only for handling CEE_LEAVEs out of finally-protected try's. + */ + +inline void Compiler::impEndTreeList(BasicBlock* block, GenTreePtr firstStmt, GenTreePtr lastStmt) +{ + assert(firstStmt->gtOper == GT_STMT); + assert(lastStmt->gtOper == GT_STMT); + + /* Make the list circular, so that we can easily walk it backwards */ + + firstStmt->gtPrev = lastStmt; + + /* Store the tree list in the basic block */ + + block->bbTreeList = firstStmt; + + /* The block should not already be marked as imported */ + assert((block->bbFlags & BBF_IMPORTED) == 0); + + block->bbFlags |= BBF_IMPORTED; +} + +/***************************************************************************** + * + * Store the current tree list in the given basic block. + */ + +inline void Compiler::impEndTreeList(BasicBlock* block) +{ + assert(impTreeList->gtOper == GT_BEG_STMTS); + + GenTreePtr firstTree = impTreeList->gtNext; + + if (!firstTree) + { + /* The block should not already be marked as imported */ + assert((block->bbFlags & BBF_IMPORTED) == 0); + + // Empty block. Just mark it as imported + block->bbFlags |= BBF_IMPORTED; + } + else + { + // Ignore the GT_BEG_STMTS + assert(firstTree->gtPrev == impTreeList); + + impEndTreeList(block, firstTree, impTreeLast); + } + +#ifdef DEBUG + if (impLastILoffsStmt != nullptr) + { + impLastILoffsStmt->gtStmt.gtStmtLastILoffs = compIsForInlining() ? BAD_IL_OFFSET : impCurOpcOffs; + impLastILoffsStmt = nullptr; + } + + impTreeList = impTreeLast = nullptr; +#endif +} + +/***************************************************************************** + * + * Check that storing the given tree doesnt mess up the semantic order. Note + * that this has only limited value as we can only check [0..chkLevel). + */ + +inline void Compiler::impAppendStmtCheck(GenTreePtr stmt, unsigned chkLevel) +{ +#ifndef DEBUG + return; +#else + assert(stmt->gtOper == GT_STMT); + + if (chkLevel == (unsigned)CHECK_SPILL_ALL) + { + chkLevel = verCurrentState.esStackDepth; + } + + if (verCurrentState.esStackDepth == 0 || chkLevel == 0 || chkLevel == (unsigned)CHECK_SPILL_NONE) + { + return; + } + + GenTreePtr tree = stmt->gtStmt.gtStmtExpr; + + // Calls can only be appended if there are no GTF_GLOB_EFFECT on the stack + + if (tree->gtFlags & GTF_CALL) + { + for (unsigned level = 0; level < chkLevel; level++) + { + assert((verCurrentState.esStack[level].val->gtFlags & GTF_GLOB_EFFECT) == 0); + } + } + + if (tree->gtOper == GT_ASG) + { + // For an assignment to a local variable, all references of that + // variable have to be spilled. If it is aliased, all calls and + // indirect accesses have to be spilled + + if (tree->gtOp.gtOp1->gtOper == GT_LCL_VAR) + { + unsigned lclNum = tree->gtOp.gtOp1->gtLclVarCommon.gtLclNum; + for (unsigned level = 0; level < chkLevel; level++) + { + assert(!gtHasRef(verCurrentState.esStack[level].val, lclNum, false)); + assert(!lvaTable[lclNum].lvAddrExposed || + (verCurrentState.esStack[level].val->gtFlags & GTF_SIDE_EFFECT) == 0); + } + } + + // If the access may be to global memory, all side effects have to be spilled. + + else if (tree->gtOp.gtOp1->gtFlags & GTF_GLOB_REF) + { + for (unsigned level = 0; level < chkLevel; level++) + { + assert((verCurrentState.esStack[level].val->gtFlags & GTF_GLOB_REF) == 0); + } + } + } +#endif +} + +/***************************************************************************** + * + * Append the given GT_STMT node to the current block's tree list. + * [0..chkLevel) is the portion of the stack which we will check for + * interference with stmt and spill if needed. + */ + +inline void Compiler::impAppendStmt(GenTreePtr stmt, unsigned chkLevel) +{ + assert(stmt->gtOper == GT_STMT); + noway_assert(impTreeLast != nullptr); + + /* If the statement being appended has any side-effects, check the stack + to see if anything needs to be spilled to preserve correct ordering. */ + + GenTreePtr expr = stmt->gtStmt.gtStmtExpr; + unsigned flags = expr->gtFlags & GTF_GLOB_EFFECT; + + // Assignment to (unaliased) locals don't count as a side-effect as + // we handle them specially using impSpillLclRefs(). Temp locals should + // be fine too. + // TODO-1stClassStructs: The check below should apply equally to struct assignments, + // but previously the block ops were always being marked GTF_GLOB_REF, even if + // the operands could not be global refs. + + if ((expr->gtOper == GT_ASG) && (expr->gtOp.gtOp1->gtOper == GT_LCL_VAR) && + !(expr->gtOp.gtOp1->gtFlags & GTF_GLOB_REF) && !gtHasLocalsWithAddrOp(expr->gtOp.gtOp2) && + !varTypeIsStruct(expr->gtOp.gtOp1)) + { + unsigned op2Flags = expr->gtOp.gtOp2->gtFlags & GTF_GLOB_EFFECT; + assert(flags == (op2Flags | GTF_ASG)); + flags = op2Flags; + } + + if (chkLevel == (unsigned)CHECK_SPILL_ALL) + { + chkLevel = verCurrentState.esStackDepth; + } + + if (chkLevel && chkLevel != (unsigned)CHECK_SPILL_NONE) + { + assert(chkLevel <= verCurrentState.esStackDepth); + + if (flags) + { + // If there is a call, we have to spill global refs + bool spillGlobEffects = (flags & GTF_CALL) ? true : false; + + if (expr->gtOper == GT_ASG) + { + GenTree* lhs = expr->gtGetOp1(); + // If we are assigning to a global ref, we have to spill global refs on stack. + // TODO-1stClassStructs: Previously, spillGlobEffects was set to true for + // GT_INITBLK and GT_COPYBLK, but this is overly conservative, and should be + // revisited. (Note that it was NOT set to true for GT_COPYOBJ.) + if (!expr->OperIsBlkOp()) + { + // If we are assigning to a global ref, we have to spill global refs on stack + if ((lhs->gtFlags & GTF_GLOB_REF) != 0) + { + spillGlobEffects = true; + } + } + else if ((lhs->OperIsBlk() && !lhs->AsBlk()->HasGCPtr()) || + ((lhs->OperGet() == GT_LCL_VAR) && + (lvaTable[lhs->AsLclVarCommon()->gtLclNum].lvStructGcCount == 0))) + { + spillGlobEffects = true; + } + } + + impSpillSideEffects(spillGlobEffects, chkLevel DEBUGARG("impAppendStmt")); + } + else + { + impSpillSpecialSideEff(); + } + } + + impAppendStmtCheck(stmt, chkLevel); + + /* Point 'prev' at the previous node, so that we can walk backwards */ + + stmt->gtPrev = impTreeLast; + + /* Append the expression statement to the list */ + + impTreeLast->gtNext = stmt; + impTreeLast = stmt; + +#ifdef FEATURE_SIMD + impMarkContiguousSIMDFieldAssignments(stmt); +#endif + +#ifdef DEBUGGING_SUPPORT + + /* Once we set impCurStmtOffs in an appended tree, we are ready to + report the following offsets. So reset impCurStmtOffs */ + + if (impTreeLast->gtStmt.gtStmtILoffsx == impCurStmtOffs) + { + impCurStmtOffsSet(BAD_IL_OFFSET); + } + +#endif + +#ifdef DEBUG + if (impLastILoffsStmt == nullptr) + { + impLastILoffsStmt = stmt; + } + + if (verbose) + { + printf("\n\n"); + gtDispTree(stmt); + } +#endif +} + +/***************************************************************************** + * + * Insert the given GT_STMT "stmt" before GT_STMT "stmtBefore" + */ + +inline void Compiler::impInsertStmtBefore(GenTreePtr stmt, GenTreePtr stmtBefore) +{ + assert(stmt->gtOper == GT_STMT); + assert(stmtBefore->gtOper == GT_STMT); + + GenTreePtr stmtPrev = stmtBefore->gtPrev; + stmt->gtPrev = stmtPrev; + stmt->gtNext = stmtBefore; + stmtPrev->gtNext = stmt; + stmtBefore->gtPrev = stmt; +} + +/***************************************************************************** + * + * Append the given expression tree to the current block's tree list. + * Return the newly created statement. + */ + +GenTreePtr Compiler::impAppendTree(GenTreePtr tree, unsigned chkLevel, IL_OFFSETX offset) +{ + assert(tree); + + /* Allocate an 'expression statement' node */ + + GenTreePtr expr = gtNewStmt(tree, offset); + + /* Append the statement to the current block's stmt list */ + + impAppendStmt(expr, chkLevel); + + return expr; +} + +/***************************************************************************** + * + * Insert the given exression tree before GT_STMT "stmtBefore" + */ + +void Compiler::impInsertTreeBefore(GenTreePtr tree, IL_OFFSETX offset, GenTreePtr stmtBefore) +{ + assert(stmtBefore->gtOper == GT_STMT); + + /* Allocate an 'expression statement' node */ + + GenTreePtr expr = gtNewStmt(tree, offset); + + /* Append the statement to the current block's stmt list */ + + impInsertStmtBefore(expr, stmtBefore); +} + +/***************************************************************************** + * + * Append an assignment of the given value to a temp to the current tree list. + * curLevel is the stack level for which the spill to the temp is being done. + */ + +void Compiler::impAssignTempGen(unsigned tmp, + GenTreePtr val, + unsigned curLevel, + GenTreePtr* pAfterStmt, /* = NULL */ + IL_OFFSETX ilOffset, /* = BAD_IL_OFFSET */ + BasicBlock* block /* = NULL */ + ) +{ + GenTreePtr asg = gtNewTempAssign(tmp, val); + + if (!asg->IsNothingNode()) + { + if (pAfterStmt) + { + GenTreePtr asgStmt = gtNewStmt(asg, ilOffset); + *pAfterStmt = fgInsertStmtAfter(block, *pAfterStmt, asgStmt); + } + else + { + impAppendTree(asg, curLevel, impCurStmtOffs); + } + } +} + +/***************************************************************************** + * same as above, but handle the valueclass case too + */ + +void Compiler::impAssignTempGen(unsigned tmpNum, + GenTreePtr val, + CORINFO_CLASS_HANDLE structType, + unsigned curLevel, + GenTreePtr* pAfterStmt, /* = NULL */ + IL_OFFSETX ilOffset, /* = BAD_IL_OFFSET */ + BasicBlock* block /* = NULL */ + ) +{ + GenTreePtr asg; + + if (varTypeIsStruct(val)) + { + assert(tmpNum < lvaCount); + assert(structType != NO_CLASS_HANDLE); + + // if the method is non-verifiable the assert is not true + // so at least ignore it in the case when verification is turned on + // since any block that tries to use the temp would have failed verification. + var_types varType = lvaTable[tmpNum].lvType; + assert(tiVerificationNeeded || varType == TYP_UNDEF || varTypeIsStruct(varType)); + lvaSetStruct(tmpNum, structType, false); + + // Now, set the type of the struct value. Note that lvaSetStruct may modify the type + // of the lclVar to a specialized type (e.g. TYP_SIMD), based on the handle (structType) + // that has been passed in for the value being assigned to the temp, in which case we + // need to set 'val' to that same type. + // Note also that if we always normalized the types of any node that might be a struct + // type, this would not be necessary - but that requires additional JIT/EE interface + // calls that may not actually be required - e.g. if we only access a field of a struct. + + val->gtType = lvaTable[tmpNum].lvType; + + GenTreePtr dst = gtNewLclvNode(tmpNum, val->gtType); + asg = impAssignStruct(dst, val, structType, curLevel, pAfterStmt, block); + } + else + { + asg = gtNewTempAssign(tmpNum, val); + } + + if (!asg->IsNothingNode()) + { + if (pAfterStmt) + { + GenTreePtr asgStmt = gtNewStmt(asg, ilOffset); + *pAfterStmt = fgInsertStmtAfter(block, *pAfterStmt, asgStmt); + } + else + { + impAppendTree(asg, curLevel, impCurStmtOffs); + } + } +} + +/***************************************************************************** + * + * Pop the given number of values from the stack and return a list node with + * their values. + * The 'prefixTree' argument may optionally contain an argument + * list that is prepended to the list returned from this function. + * + * The notion of prepended is a bit misleading in that the list is backwards + * from the way I would expect: The first element popped is at the end of + * the returned list, and prefixTree is 'before' that, meaning closer to + * the end of the list. To get to prefixTree, you have to walk to the + * end of the list. + * + * For ARG_ORDER_R2L prefixTree is only used to insert extra arguments, as + * such we reverse its meaning such that returnValue has a reversed + * prefixTree at the head of the list. + */ + +GenTreeArgList* Compiler::impPopList(unsigned count, + unsigned* flagsPtr, + CORINFO_SIG_INFO* sig, + GenTreeArgList* prefixTree) +{ + assert(sig == nullptr || count == sig->numArgs); + + unsigned flags = 0; + CORINFO_CLASS_HANDLE structType; + GenTreeArgList* treeList; + + if (Target::g_tgtArgOrder == Target::ARG_ORDER_R2L) + { + treeList = nullptr; + } + else + { // ARG_ORDER_L2R + treeList = prefixTree; + } + + while (count--) + { + StackEntry se = impPopStack(); + typeInfo ti = se.seTypeInfo; + GenTreePtr temp = se.val; + + if (varTypeIsStruct(temp)) + { + // Morph trees that aren't already OBJs or MKREFANY to be OBJs + assert(ti.IsType(TI_STRUCT)); + structType = ti.GetClassHandleForValueClass(); + temp = impNormStructVal(temp, structType, (unsigned)CHECK_SPILL_ALL); + } + + /* NOTE: we defer bashing the type for I_IMPL to fgMorphArgs */ + flags |= temp->gtFlags; + treeList = gtNewListNode(temp, treeList); + } + + *flagsPtr = flags; + + if (sig != nullptr) + { + if (sig->retTypeSigClass != nullptr && sig->retType != CORINFO_TYPE_CLASS && + sig->retType != CORINFO_TYPE_BYREF && sig->retType != CORINFO_TYPE_PTR && sig->retType != CORINFO_TYPE_VAR) + { + // Make sure that all valuetypes (including enums) that we push are loaded. + // This is to guarantee that if a GC is triggerred from the prestub of this methods, + // all valuetypes in the method signature are already loaded. + // We need to be able to find the size of the valuetypes, but we cannot + // do a class-load from within GC. + info.compCompHnd->classMustBeLoadedBeforeCodeIsRun(sig->retTypeSigClass); + } + + CORINFO_ARG_LIST_HANDLE argLst = sig->args; + CORINFO_CLASS_HANDLE argClass; + CORINFO_CLASS_HANDLE argRealClass; + GenTreeArgList* args; + unsigned sigSize; + + for (args = treeList, count = sig->numArgs; count > 0; args = args->Rest(), count--) + { + PREFIX_ASSUME(args != nullptr); + + CorInfoType corType = strip(info.compCompHnd->getArgType(sig, argLst, &argClass)); + + // insert implied casts (from float to double or double to float) + + if (corType == CORINFO_TYPE_DOUBLE && args->Current()->TypeGet() == TYP_FLOAT) + { + args->Current() = gtNewCastNode(TYP_DOUBLE, args->Current(), TYP_DOUBLE); + } + else if (corType == CORINFO_TYPE_FLOAT && args->Current()->TypeGet() == TYP_DOUBLE) + { + args->Current() = gtNewCastNode(TYP_FLOAT, args->Current(), TYP_FLOAT); + } + + // insert any widening or narrowing casts for backwards compatibility + + args->Current() = impImplicitIorI4Cast(args->Current(), JITtype2varType(corType)); + + if (corType != CORINFO_TYPE_CLASS && corType != CORINFO_TYPE_BYREF && corType != CORINFO_TYPE_PTR && + corType != CORINFO_TYPE_VAR && (argRealClass = info.compCompHnd->getArgClass(sig, argLst)) != nullptr) + { + // Everett MC++ could generate IL with a mismatched valuetypes. It used to work with Everett JIT, + // but it stopped working in Whidbey when we have started passing simple valuetypes as underlying + // primitive types. + // We will try to adjust for this case here to avoid breaking customers code (see VSW 485789 for + // details). + if (corType == CORINFO_TYPE_VALUECLASS && !varTypeIsStruct(args->Current())) + { + args->Current() = impNormStructVal(args->Current(), argRealClass, (unsigned)CHECK_SPILL_ALL, true); + } + + // Make sure that all valuetypes (including enums) that we push are loaded. + // This is to guarantee that if a GC is triggered from the prestub of this methods, + // all valuetypes in the method signature are already loaded. + // We need to be able to find the size of the valuetypes, but we cannot + // do a class-load from within GC. + info.compCompHnd->classMustBeLoadedBeforeCodeIsRun(argRealClass); + } + + argLst = info.compCompHnd->getArgNext(argLst); + } + } + + if (Target::g_tgtArgOrder == Target::ARG_ORDER_R2L) + { + // Prepend the prefixTree + + // Simple in-place reversal to place treeList + // at the end of a reversed prefixTree + while (prefixTree != nullptr) + { + GenTreeArgList* next = prefixTree->Rest(); + prefixTree->Rest() = treeList; + treeList = prefixTree; + prefixTree = next; + } + } + return treeList; +} + +/***************************************************************************** + * + * Pop the given number of values from the stack in reverse order (STDCALL/CDECL etc.) + * The first "skipReverseCount" items are not reversed. + */ + +GenTreeArgList* Compiler::impPopRevList(unsigned count, + unsigned* flagsPtr, + CORINFO_SIG_INFO* sig, + unsigned skipReverseCount) + +{ + assert(skipReverseCount <= count); + + GenTreeArgList* list = impPopList(count, flagsPtr, sig); + + // reverse the list + if (list == nullptr || skipReverseCount == count) + { + return list; + } + + GenTreeArgList* ptr = nullptr; // Initialized to the first node that needs to be reversed + GenTreeArgList* lastSkipNode = nullptr; // Will be set to the last node that does not need to be reversed + + if (skipReverseCount == 0) + { + ptr = list; + } + else + { + lastSkipNode = list; + // Get to the first node that needs to be reversed + for (unsigned i = 0; i < skipReverseCount - 1; i++) + { + lastSkipNode = lastSkipNode->Rest(); + } + + PREFIX_ASSUME(lastSkipNode != nullptr); + ptr = lastSkipNode->Rest(); + } + + GenTreeArgList* reversedList = nullptr; + + do + { + GenTreeArgList* tmp = ptr->Rest(); + ptr->Rest() = reversedList; + reversedList = ptr; + ptr = tmp; + } while (ptr != nullptr); + + if (skipReverseCount) + { + lastSkipNode->Rest() = reversedList; + return list; + } + else + { + return reversedList; + } +} + +/***************************************************************************** + Assign (copy) the structure from 'src' to 'dest'. The structure is a value + class of type 'clsHnd'. It returns the tree that should be appended to the + statement list that represents the assignment. + Temp assignments may be appended to impTreeList if spilling is necessary. + curLevel is the stack level for which a spill may be being done. + */ + +GenTreePtr Compiler::impAssignStruct(GenTreePtr dest, + GenTreePtr src, + CORINFO_CLASS_HANDLE structHnd, + unsigned curLevel, + GenTreePtr* pAfterStmt, /* = NULL */ + BasicBlock* block /* = NULL */ + ) +{ + assert(varTypeIsStruct(dest)); + + while (dest->gtOper == GT_COMMA) + { + assert(varTypeIsStruct(dest->gtOp.gtOp2)); // Second thing is the struct + + // Append all the op1 of GT_COMMA trees before we evaluate op2 of the GT_COMMA tree. + if (pAfterStmt) + { + *pAfterStmt = fgInsertStmtAfter(block, *pAfterStmt, gtNewStmt(dest->gtOp.gtOp1, impCurStmtOffs)); + } + else + { + impAppendTree(dest->gtOp.gtOp1, curLevel, impCurStmtOffs); // do the side effect + } + + // set dest to the second thing + dest = dest->gtOp.gtOp2; + } + + assert(dest->gtOper == GT_LCL_VAR || dest->gtOper == GT_RETURN || dest->gtOper == GT_FIELD || + dest->gtOper == GT_IND || dest->gtOper == GT_OBJ || dest->gtOper == GT_INDEX); + + if (dest->OperGet() == GT_LCL_VAR && src->OperGet() == GT_LCL_VAR && + src->gtLclVarCommon.gtLclNum == dest->gtLclVarCommon.gtLclNum) + { + // Make this a NOP + return gtNewNothingNode(); + } + + // TODO-1stClassStructs: Avoid creating an address if it is not needed, + // or re-creating a Blk node if it is. + GenTreePtr destAddr; + + if (dest->gtOper == GT_IND || dest->OperIsBlk()) + { + destAddr = dest->gtOp.gtOp1; + } + else + { + destAddr = gtNewOperNode(GT_ADDR, TYP_BYREF, dest); + } + + return (impAssignStructPtr(destAddr, src, structHnd, curLevel, pAfterStmt, block)); +} + +/*****************************************************************************/ + +GenTreePtr Compiler::impAssignStructPtr(GenTreePtr destAddr, + GenTreePtr src, + CORINFO_CLASS_HANDLE structHnd, + unsigned curLevel, + GenTreePtr* pAfterStmt, /* = NULL */ + BasicBlock* block /* = NULL */ + ) +{ + var_types destType; + GenTreePtr dest = nullptr; + unsigned destFlags = 0; + +#if defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) + assert(varTypeIsStruct(src) || (src->gtOper == GT_ADDR && src->TypeGet() == TYP_BYREF)); + // TODO-ARM-BUG: Does ARM need this? + // TODO-ARM64-BUG: Does ARM64 need this? + assert(src->gtOper == GT_LCL_VAR || src->gtOper == GT_FIELD || src->gtOper == GT_IND || src->gtOper == GT_OBJ || + src->gtOper == GT_CALL || src->gtOper == GT_MKREFANY || src->gtOper == GT_RET_EXPR || + src->gtOper == GT_COMMA || src->gtOper == GT_ADDR || + (src->TypeGet() != TYP_STRUCT && (GenTree::OperIsSIMD(src->gtOper) || src->gtOper == GT_LCL_FLD))); +#else // !defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) + assert(varTypeIsStruct(src)); + + assert(src->gtOper == GT_LCL_VAR || src->gtOper == GT_FIELD || src->gtOper == GT_IND || src->gtOper == GT_OBJ || + src->gtOper == GT_CALL || src->gtOper == GT_MKREFANY || src->gtOper == GT_RET_EXPR || + src->gtOper == GT_COMMA || + (src->TypeGet() != TYP_STRUCT && (GenTree::OperIsSIMD(src->gtOper) || src->gtOper == GT_LCL_FLD))); +#endif // !defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) + if (destAddr->OperGet() == GT_ADDR) + { + GenTree* destNode = destAddr->gtGetOp1(); + // If the actual destination is already a block node, or is a node that + // will be morphed, don't insert an OBJ(ADDR). + if (destNode->gtOper == GT_INDEX || destNode->OperIsBlk()) + { + dest = destNode; + } + destType = destNode->TypeGet(); + } + else + { + destType = src->TypeGet(); + } + + var_types asgType = src->TypeGet(); + + if (src->gtOper == GT_CALL) + { + if (src->AsCall()->TreatAsHasRetBufArg(this)) + { + // Case of call returning a struct via hidden retbuf arg + + // insert the return value buffer into the argument list as first byref parameter + src->gtCall.gtCallArgs = gtNewListNode(destAddr, src->gtCall.gtCallArgs); + + // now returns void, not a struct + src->gtType = TYP_VOID; + + // return the morphed call node + return src; + } + else + { + // Case of call returning a struct in one or more registers. + + var_types returnType = (var_types)src->gtCall.gtReturnType; + + // We won't use a return buffer, so change the type of src->gtType to 'returnType' + src->gtType = genActualType(returnType); + + // First we try to change this to "LclVar/LclFld = call" + // + if ((destAddr->gtOper == GT_ADDR) && (destAddr->gtOp.gtOp1->gtOper == GT_LCL_VAR)) + { + // If it is a multi-reg struct return, don't change the oper to GT_LCL_FLD. + // That is, the IR will be of the form lclVar = call for multi-reg return + // + GenTreePtr lcl = destAddr->gtOp.gtOp1; + if (src->AsCall()->HasMultiRegRetVal()) + { + // Mark the struct LclVar as used in a MultiReg return context + // which currently makes it non promotable. + lvaTable[lcl->gtLclVarCommon.gtLclNum].lvIsMultiRegRet = true; + } + else // The call result is not a multireg return + { + // We change this to a GT_LCL_FLD (from a GT_ADDR of a GT_LCL_VAR) + lcl->ChangeOper(GT_LCL_FLD); + fgLclFldAssign(lcl->gtLclVarCommon.gtLclNum); + } + + lcl->gtType = src->gtType; + asgType = src->gtType; + dest = lcl; + +#if defined(_TARGET_ARM_) + impMarkLclDstNotPromotable(lcl->gtLclVarCommon.gtLclNum, src, structHnd); +#elif defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) + // Not allowed for FEATURE_CORCLR which is the only SKU available for System V OSs. + assert(!src->gtCall.IsVarargs() && "varargs not allowed for System V OSs."); + + // Make the struct non promotable. The eightbytes could contain multiple fields. + lvaTable[lcl->gtLclVarCommon.gtLclNum].lvIsMultiRegRet = true; +#endif + } + else // we don't have a GT_ADDR of a GT_LCL_VAR + { + // !!! The destination could be on stack. !!! + // This flag will let us choose the correct write barrier. + asgType = returnType; + destFlags = GTF_IND_TGTANYWHERE; + } + } + } + else if (src->gtOper == GT_RET_EXPR) + { + GenTreePtr call = src->gtRetExpr.gtInlineCandidate; + noway_assert(call->gtOper == GT_CALL); + + if (call->AsCall()->HasRetBufArg()) + { + // insert the return value buffer into the argument list as first byref parameter + call->gtCall.gtCallArgs = gtNewListNode(destAddr, call->gtCall.gtCallArgs); + + // now returns void, not a struct + src->gtType = TYP_VOID; + call->gtType = TYP_VOID; + + // We already have appended the write to 'dest' GT_CALL's args + // So now we just return an empty node (pruning the GT_RET_EXPR) + return src; + } + else + { + // Case of inline method returning a struct in one or more registers. + // + var_types returnType = (var_types)call->gtCall.gtReturnType; + + // We won't need a return buffer + asgType = returnType; + src->gtType = genActualType(returnType); + call->gtType = src->gtType; + + // 1stClassStructToDo: We shouldn't necessarily need this. + if (dest != nullptr) + { + dest = gtNewOperNode(GT_IND, returnType, gtNewOperNode(GT_ADDR, TYP_BYREF, dest)); + } + + // !!! The destination could be on stack. !!! + // This flag will let us choose the correct write barrier. + destFlags = GTF_IND_TGTANYWHERE; + } + } + else if (src->OperIsBlk()) + { + asgType = impNormStructType(structHnd); + if (src->gtOper == GT_OBJ) + { + assert(src->gtObj.gtClass == structHnd); + } + } + else if (src->gtOper == GT_INDEX) + { + asgType = impNormStructType(structHnd); + assert(src->gtIndex.gtStructElemClass == structHnd); + } + else if (src->gtOper == GT_MKREFANY) + { + // Since we are assigning the result of a GT_MKREFANY, + // "destAddr" must point to a refany. + + GenTreePtr destAddrClone; + destAddr = + impCloneExpr(destAddr, &destAddrClone, structHnd, curLevel, pAfterStmt DEBUGARG("MKREFANY assignment")); + + assert(offsetof(CORINFO_RefAny, dataPtr) == 0); + assert(destAddr->gtType == TYP_I_IMPL || destAddr->gtType == TYP_BYREF); + GetZeroOffsetFieldMap()->Set(destAddr, GetFieldSeqStore()->CreateSingleton(GetRefanyDataField())); + GenTreePtr ptrSlot = gtNewOperNode(GT_IND, TYP_I_IMPL, destAddr); + GenTreeIntCon* typeFieldOffset = gtNewIconNode(offsetof(CORINFO_RefAny, type), TYP_I_IMPL); + typeFieldOffset->gtFieldSeq = GetFieldSeqStore()->CreateSingleton(GetRefanyTypeField()); + GenTreePtr typeSlot = + gtNewOperNode(GT_IND, TYP_I_IMPL, gtNewOperNode(GT_ADD, destAddr->gtType, destAddrClone, typeFieldOffset)); + + // append the assign of the pointer value + GenTreePtr asg = gtNewAssignNode(ptrSlot, src->gtOp.gtOp1); + if (pAfterStmt) + { + *pAfterStmt = fgInsertStmtAfter(block, *pAfterStmt, gtNewStmt(asg, impCurStmtOffs)); + } + else + { + impAppendTree(asg, curLevel, impCurStmtOffs); + } + + // return the assign of the type value, to be appended + return gtNewAssignNode(typeSlot, src->gtOp.gtOp2); + } + else if (src->gtOper == GT_COMMA) + { + // The second thing is the struct or its address. + assert(varTypeIsStruct(src->gtOp.gtOp2) || src->gtOp.gtOp2->gtType == TYP_BYREF); + if (pAfterStmt) + { + *pAfterStmt = fgInsertStmtAfter(block, *pAfterStmt, gtNewStmt(src->gtOp.gtOp1, impCurStmtOffs)); + } + else + { + impAppendTree(src->gtOp.gtOp1, curLevel, impCurStmtOffs); // do the side effect + } + + // Evaluate the second thing using recursion. + return impAssignStructPtr(destAddr, src->gtOp.gtOp2, structHnd, curLevel, pAfterStmt, block); + } + else if (src->IsLocal()) + { + // TODO-1stClassStructs: Eliminate this; it is only here to minimize diffs in the + // initial implementation. Previously the source would have been under a GT_ADDR, which + // would cause it to be marked GTF_DONT_CSE. + asgType = src->TypeGet(); + src->gtFlags |= GTF_DONT_CSE; + if (asgType == TYP_STRUCT) + { + GenTree* srcAddr = gtNewOperNode(GT_ADDR, TYP_BYREF, src); + src = gtNewOperNode(GT_IND, TYP_STRUCT, srcAddr); + } + } + else if (asgType == TYP_STRUCT) + { + asgType = impNormStructType(structHnd); + src->gtType = asgType; + } + if (dest == nullptr) + { + // TODO-1stClassStructs: We shouldn't really need a block node as the destination + // if this is a known struct type. + if (asgType == TYP_STRUCT) + { + dest = gtNewObjNode(structHnd, destAddr); + gtSetObjGcInfo(dest->AsObj()); + // Although an obj as a call argument was always assumed to be a globRef + // (which is itself overly conservative), that is not true of the operands + // of a block assignment. + dest->gtFlags &= ~GTF_GLOB_REF; + dest->gtFlags |= (destAddr->gtFlags & GTF_GLOB_REF); + } + else if (varTypeIsStruct(asgType)) + { + dest = new (this, GT_BLK) GenTreeBlk(GT_BLK, asgType, destAddr, genTypeSize(asgType)); + } + else + { + dest = gtNewOperNode(GT_IND, asgType, destAddr); + } + } + else + { + dest->gtType = asgType; + } + + dest->gtFlags |= destFlags; + destFlags = dest->gtFlags; + + // return an assignment node, to be appended + GenTree* asgNode = gtNewAssignNode(dest, src); + gtBlockOpInit(asgNode, dest, src, false); + + // TODO-1stClassStructs: Clean up the settings of GTF_DONT_CSE on the lhs + // of assignments. + if ((destFlags & GTF_DONT_CSE) == 0) + { + dest->gtFlags &= ~(GTF_DONT_CSE); + } + return asgNode; +} + +/***************************************************************************** + Given a struct value, and the class handle for that structure, return + the expression for the address for that structure value. + + willDeref - does the caller guarantee to dereference the pointer. +*/ + +GenTreePtr Compiler::impGetStructAddr(GenTreePtr structVal, + CORINFO_CLASS_HANDLE structHnd, + unsigned curLevel, + bool willDeref) +{ + assert(varTypeIsStruct(structVal) || eeIsValueClass(structHnd)); + + var_types type = structVal->TypeGet(); + + genTreeOps oper = structVal->gtOper; + + if (oper == GT_OBJ && willDeref) + { + assert(structVal->gtObj.gtClass == structHnd); + return (structVal->gtObj.Addr()); + } + else if (oper == GT_CALL || oper == GT_RET_EXPR || oper == GT_OBJ || oper == GT_MKREFANY) + { + unsigned tmpNum = lvaGrabTemp(true DEBUGARG("struct address for call/obj")); + + impAssignTempGen(tmpNum, structVal, structHnd, curLevel); + + // The 'return value' is now the temp itself + + type = genActualType(lvaTable[tmpNum].TypeGet()); + GenTreePtr temp = gtNewLclvNode(tmpNum, type); + temp = gtNewOperNode(GT_ADDR, TYP_BYREF, temp); + return temp; + } + else if (oper == GT_COMMA) + { + assert(structVal->gtOp.gtOp2->gtType == type); // Second thing is the struct + + GenTreePtr oldTreeLast = impTreeLast; + structVal->gtOp.gtOp2 = impGetStructAddr(structVal->gtOp.gtOp2, structHnd, curLevel, willDeref); + structVal->gtType = TYP_BYREF; + + if (oldTreeLast != impTreeLast) + { + // Some temp assignment statement was placed on the statement list + // for Op2, but that would be out of order with op1, so we need to + // spill op1 onto the statement list after whatever was last + // before we recursed on Op2 (i.e. before whatever Op2 appended). + impInsertTreeBefore(structVal->gtOp.gtOp1, impCurStmtOffs, oldTreeLast->gtNext); + structVal->gtOp.gtOp1 = gtNewNothingNode(); + } + + return (structVal); + } + + return (gtNewOperNode(GT_ADDR, TYP_BYREF, structVal)); +} + +//------------------------------------------------------------------------ +// impNormStructType: Given a (known to be) struct class handle structHnd, normalize its type, +// and optionally determine the GC layout of the struct. +// +// Arguments: +// structHnd - The class handle for the struct type of interest. +// gcLayout - (optional, default nullptr) - a BYTE pointer, allocated by the caller, +// into which the gcLayout will be written. +// pNumGCVars - (optional, default nullptr) - if non-null, a pointer to an unsigned, +// which will be set to the number of GC fields in the struct. +// +// Return Value: +// The JIT type for the struct (e.g. TYP_STRUCT, or TYP_SIMD*). +// The gcLayout will be returned using the pointers provided by the caller, if non-null. +// It may also modify the compFloatingPointUsed flag if the type is a SIMD type. +// +// Assumptions: +// The caller must set gcLayout to nullptr OR ensure that it is large enough +// (see ICorStaticInfo::getClassGClayout in corinfo.h). +// +// Notes: +// Normalizing the type involves examining the struct type to determine if it should +// be modified to one that is handled specially by the JIT, possibly being a candidate +// for full enregistration, e.g. TYP_SIMD16. + +var_types Compiler::impNormStructType(CORINFO_CLASS_HANDLE structHnd, + BYTE* gcLayout, + unsigned* pNumGCVars, + var_types* pSimdBaseType) +{ + assert(structHnd != NO_CLASS_HANDLE); + unsigned originalSize = info.compCompHnd->getClassSize(structHnd); + unsigned numGCVars = 0; + var_types structType = TYP_STRUCT; + var_types simdBaseType = TYP_UNKNOWN; + bool definitelyHasGCPtrs = false; + +#ifdef FEATURE_SIMD + // We don't want to consider this as a possible SIMD type if it has GC pointers. + // (Saves querying about the SIMD assembly.) + BYTE gcBytes[maxPossibleSIMDStructBytes / TARGET_POINTER_SIZE]; + if ((gcLayout == nullptr) && (originalSize >= minSIMDStructBytes()) && (originalSize <= maxSIMDStructBytes())) + { + gcLayout = gcBytes; + } +#endif // FEATURE_SIMD + + if (gcLayout != nullptr) + { + numGCVars = info.compCompHnd->getClassGClayout(structHnd, gcLayout); + definitelyHasGCPtrs = (numGCVars != 0); + } +#ifdef FEATURE_SIMD + // Check to see if this is a SIMD type. + if (featureSIMD && (originalSize <= getSIMDVectorRegisterByteLength()) && (originalSize >= TARGET_POINTER_SIZE) && + !definitelyHasGCPtrs) + { + unsigned int sizeBytes; + simdBaseType = getBaseTypeAndSizeOfSIMDType(structHnd, &sizeBytes); + if (simdBaseType != TYP_UNKNOWN) + { + assert(sizeBytes == originalSize); + structType = getSIMDTypeForSize(sizeBytes); + if (pSimdBaseType != nullptr) + { + *pSimdBaseType = simdBaseType; + } +#ifdef _TARGET_AMD64_ + // Amd64: also indicate that we use floating point registers + compFloatingPointUsed = true; +#endif + } + } +#endif // FEATURE_SIMD + if (pNumGCVars != nullptr) + { + *pNumGCVars = numGCVars; + } + return structType; +} + +//**************************************************************************** +// Given TYP_STRUCT value 'structVal', make sure it is 'canonical', that is +// it is either an OBJ or a MKREFANY node, or a node (e.g. GT_INDEX) that will be morphed. +// +GenTreePtr Compiler::impNormStructVal(GenTreePtr structVal, + CORINFO_CLASS_HANDLE structHnd, + unsigned curLevel, + bool forceNormalization /*=false*/) +{ + assert(forceNormalization || varTypeIsStruct(structVal)); + assert(structHnd != NO_CLASS_HANDLE); + var_types structType = structVal->TypeGet(); + bool makeTemp = false; + if (structType == TYP_STRUCT) + { + structType = impNormStructType(structHnd); + } + bool alreadyNormalized = false; + GenTreeLclVarCommon* structLcl = nullptr; + + genTreeOps oper = structVal->OperGet(); + switch (oper) + { + // GT_RETURN and GT_MKREFANY don't capture the handle. + case GT_RETURN: + break; + case GT_MKREFANY: + alreadyNormalized = true; + break; + + case GT_CALL: + structVal->gtCall.gtRetClsHnd = structHnd; + makeTemp = true; + break; + + case GT_RET_EXPR: + structVal->gtRetExpr.gtRetClsHnd = structHnd; + makeTemp = true; + break; + + case GT_ARGPLACE: + structVal->gtArgPlace.gtArgPlaceClsHnd = structHnd; + break; + + case GT_INDEX: + // This will be transformed to an OBJ later. + alreadyNormalized = true; + structVal->gtIndex.gtStructElemClass = structHnd; + structVal->gtIndex.gtIndElemSize = info.compCompHnd->getClassSize(structHnd); + break; + + case GT_FIELD: + // Wrap it in a GT_OBJ. + structVal->gtType = structType; + structVal = gtNewObjNode(structHnd, gtNewOperNode(GT_ADDR, TYP_BYREF, structVal)); + break; + + case GT_LCL_VAR: + case GT_LCL_FLD: + structLcl = structVal->AsLclVarCommon(); + // Wrap it in a GT_OBJ. + structVal = gtNewObjNode(structHnd, gtNewOperNode(GT_ADDR, TYP_BYREF, structVal)); + __fallthrough; + + case GT_OBJ: + case GT_BLK: + case GT_DYN_BLK: + case GT_ASG: + // These should already have the appropriate type. + assert(structVal->gtType == structType); + alreadyNormalized = true; + break; + + case GT_IND: + assert(structVal->gtType == structType); + structVal = gtNewObjNode(structHnd, structVal->gtGetOp1()); + alreadyNormalized = true; + break; + +#ifdef FEATURE_SIMD + case GT_SIMD: + assert(varTypeIsSIMD(structVal) && (structVal->gtType == structType)); + break; +#endif // FEATURE_SIMD + + case GT_COMMA: + { + // The second thing is the block node. + GenTree* blockNode = structVal->gtOp.gtOp2; + assert(blockNode->gtType == structType); + // It had better be a block node - any others should not occur here. + assert(blockNode->OperIsBlk()); + + // Sink the GT_COMMA below the blockNode addr. + GenTree* blockNodeAddr = blockNode->gtOp.gtOp1; + assert(blockNodeAddr->gtType == TYP_BYREF); + GenTree* commaNode = structVal; + commaNode->gtType = TYP_BYREF; + commaNode->gtOp.gtOp2 = blockNodeAddr; + blockNode->gtOp.gtOp1 = commaNode; + structVal = blockNode; + alreadyNormalized = true; + } + break; + + default: + assert(!"Unexpected node in impNormStructVal()"); + break; + } + structVal->gtType = structType; + GenTree* structObj = structVal; + + if (!alreadyNormalized || forceNormalization) + { + if (makeTemp) + { + unsigned tmpNum = lvaGrabTemp(true DEBUGARG("struct address for call/obj")); + + impAssignTempGen(tmpNum, structVal, structHnd, curLevel); + + // The structVal is now the temp itself + + structLcl = gtNewLclvNode(tmpNum, structType)->AsLclVarCommon(); + // TODO-1stClassStructs: Avoid always wrapping in GT_OBJ. + structObj = gtNewObjNode(structHnd, gtNewOperNode(GT_ADDR, TYP_BYREF, structLcl)); + } + else if (varTypeIsStruct(structType) && !structVal->OperIsBlk()) + { + // Wrap it in a GT_OBJ + structObj = gtNewObjNode(structHnd, gtNewOperNode(GT_ADDR, TYP_BYREF, structVal)); + } + } + + if (structLcl != nullptr) + { + // A OBJ on a ADDR(LCL_VAR) can never raise an exception + // so we don't set GTF_EXCEPT here. + if (!lvaIsImplicitByRefLocal(structLcl->gtLclNum)) + { + structObj->gtFlags &= ~GTF_GLOB_REF; + } + } + else + { + // In general a OBJ is an indirection and could raise an exception. + structObj->gtFlags |= GTF_EXCEPT; + } + return (structObj); +} + +/******************************************************************************/ +// Given a type token, generate code that will evaluate to the correct +// handle representation of that token (type handle, field handle, or method handle) +// +// For most cases, the handle is determined at compile-time, and the code +// generated is simply an embedded handle. +// +// Run-time lookup is required if the enclosing method is shared between instantiations +// and the token refers to formal type parameters whose instantiation is not known +// at compile-time. +// +GenTreePtr Compiler::impTokenToHandle(CORINFO_RESOLVED_TOKEN* pResolvedToken, + BOOL* pRuntimeLookup /* = NULL */, + BOOL mustRestoreHandle /* = FALSE */, + BOOL importParent /* = FALSE */) +{ + assert(!fgGlobalMorph); + + CORINFO_GENERICHANDLE_RESULT embedInfo; + info.compCompHnd->embedGenericHandle(pResolvedToken, importParent, &embedInfo); + + if (pRuntimeLookup) + { + *pRuntimeLookup = embedInfo.lookup.lookupKind.needsRuntimeLookup; + } + + if (mustRestoreHandle && !embedInfo.lookup.lookupKind.needsRuntimeLookup) + { + switch (embedInfo.handleType) + { + case CORINFO_HANDLETYPE_CLASS: + info.compCompHnd->classMustBeLoadedBeforeCodeIsRun((CORINFO_CLASS_HANDLE)embedInfo.compileTimeHandle); + break; + + case CORINFO_HANDLETYPE_METHOD: + info.compCompHnd->methodMustBeLoadedBeforeCodeIsRun((CORINFO_METHOD_HANDLE)embedInfo.compileTimeHandle); + break; + + case CORINFO_HANDLETYPE_FIELD: + info.compCompHnd->classMustBeLoadedBeforeCodeIsRun( + info.compCompHnd->getFieldClass((CORINFO_FIELD_HANDLE)embedInfo.compileTimeHandle)); + break; + + default: + break; + } + } + + return impLookupToTree(pResolvedToken, &embedInfo.lookup, gtTokenToIconFlags(pResolvedToken->token), + embedInfo.compileTimeHandle); +} + +GenTreePtr Compiler::impLookupToTree(CORINFO_RESOLVED_TOKEN* pResolvedToken, + CORINFO_LOOKUP* pLookup, + unsigned handleFlags, + void* compileTimeHandle) +{ + if (!pLookup->lookupKind.needsRuntimeLookup) + { + // No runtime lookup is required. + // Access is direct or memory-indirect (of a fixed address) reference + + CORINFO_GENERIC_HANDLE handle = nullptr; + void* pIndirection = nullptr; + assert(pLookup->constLookup.accessType != IAT_PPVALUE); + + if (pLookup->constLookup.accessType == IAT_VALUE) + { + handle = pLookup->constLookup.handle; + } + else if (pLookup->constLookup.accessType == IAT_PVALUE) + { + pIndirection = pLookup->constLookup.addr; + } + return gtNewIconEmbHndNode(handle, pIndirection, handleFlags, 0, nullptr, compileTimeHandle); + } + else if (compIsForInlining()) + { + // Don't import runtime lookups when inlining + // Inlining has to be aborted in such a case + compInlineResult->NoteFatal(InlineObservation::CALLSITE_GENERIC_DICTIONARY_LOOKUP); + return nullptr; + } + else + { + // Need to use dictionary-based access which depends on the typeContext + // which is only available at runtime, not at compile-time. + + return impRuntimeLookupToTree(pResolvedToken, pLookup, compileTimeHandle); + } +} + +#ifdef FEATURE_READYTORUN_COMPILER +GenTreePtr Compiler::impReadyToRunLookupToTree(CORINFO_CONST_LOOKUP* pLookup, + unsigned handleFlags, + void* compileTimeHandle) +{ + CORINFO_GENERIC_HANDLE handle = 0; + void* pIndirection = 0; + assert(pLookup->accessType != IAT_PPVALUE); + + if (pLookup->accessType == IAT_VALUE) + handle = pLookup->handle; + else if (pLookup->accessType == IAT_PVALUE) + pIndirection = pLookup->addr; + return gtNewIconEmbHndNode(handle, pIndirection, handleFlags, 0, 0, compileTimeHandle); +} + +GenTreePtr Compiler::impReadyToRunHelperToTree( + CORINFO_RESOLVED_TOKEN* pResolvedToken, + CorInfoHelpFunc helper, + var_types type, + GenTreeArgList* args /* =NULL*/, + CORINFO_LOOKUP_KIND* pGenericLookupKind /* =NULL. Only used with generics */) +{ + CORINFO_CONST_LOOKUP lookup; +#if COR_JIT_EE_VERSION > 460 + if (!info.compCompHnd->getReadyToRunHelper(pResolvedToken, pGenericLookupKind, helper, &lookup)) + return NULL; +#else + info.compCompHnd->getReadyToRunHelper(pResolvedToken, helper, &lookup); +#endif + + GenTreePtr op1 = gtNewHelperCallNode(helper, type, GTF_EXCEPT, args); + + op1->gtCall.setEntryPoint(lookup); + + return op1; +} +#endif + +GenTreePtr Compiler::impMethodPointer(CORINFO_RESOLVED_TOKEN* pResolvedToken, CORINFO_CALL_INFO* pCallInfo) +{ + GenTreePtr op1 = nullptr; + + switch (pCallInfo->kind) + { + case CORINFO_CALL: + op1 = new (this, GT_FTN_ADDR) GenTreeFptrVal(TYP_I_IMPL, pCallInfo->hMethod); + +#ifdef FEATURE_READYTORUN_COMPILER + if (opts.IsReadyToRun()) + { + op1->gtFptrVal.gtEntryPoint = pCallInfo->codePointerLookup.constLookup; + op1->gtFptrVal.gtLdftnResolvedToken = new (this, CMK_Unknown) CORINFO_RESOLVED_TOKEN; + *op1->gtFptrVal.gtLdftnResolvedToken = *pResolvedToken; + } + else + op1->gtFptrVal.gtEntryPoint.addr = nullptr; +#endif + break; + + case CORINFO_CALL_CODE_POINTER: + if (compIsForInlining()) + { + // Don't import runtime lookups when inlining + // Inlining has to be aborted in such a case + compInlineResult->NoteFatal(InlineObservation::CALLSITE_GENERIC_DICTIONARY_LOOKUP); + return nullptr; + } + + op1 = impLookupToTree(pResolvedToken, &pCallInfo->codePointerLookup, GTF_ICON_FTN_ADDR, pCallInfo->hMethod); + break; + + default: + noway_assert(!"unknown call kind"); + break; + } + + return op1; +} + +/*****************************************************************************/ +/* Import a dictionary lookup to access a handle in code shared between + generic instantiations. + The lookup depends on the typeContext which is only available at + runtime, and not at compile-time. + pLookup->token1 and pLookup->token2 specify the handle that is needed. + The cases are: + + 1. pLookup->indirections == CORINFO_USEHELPER : Call a helper passing it the + instantiation-specific handle, and the tokens to lookup the handle. + 2. pLookup->indirections != CORINFO_USEHELPER : + 2a. pLookup->testForNull == false : Dereference the instantiation-specific handle + to get the handle. + 2b. pLookup->testForNull == true : Dereference the instantiation-specific handle. + If it is non-NULL, it is the handle required. Else, call a helper + to lookup the handle. + */ + +GenTreePtr Compiler::impRuntimeLookupToTree(CORINFO_RESOLVED_TOKEN* pResolvedToken, + CORINFO_LOOKUP* pLookup, + void* compileTimeHandle) +{ + CORINFO_RUNTIME_LOOKUP_KIND kind = pLookup->lookupKind.runtimeLookupKind; + CORINFO_RUNTIME_LOOKUP* pRuntimeLookup = &pLookup->runtimeLookup; + + // This method can only be called from the importer instance of the Compiler. + // In other word, it cannot be called by the instance of the Compiler for the inlinee. + assert(!compIsForInlining()); + + GenTreePtr ctxTree; + + // Collectible types requires that for shared generic code, if we use the generic context parameter + // that we report it. (This is a conservative approach, we could detect some cases particularly when the + // context parameter is this that we don't need the eager reporting logic.) + lvaGenericsContextUsed = true; + + if (kind == CORINFO_LOOKUP_THISOBJ) + { + // this Object + ctxTree = gtNewLclvNode(info.compThisArg, TYP_REF); + + // Vtable pointer of this object + ctxTree = gtNewOperNode(GT_IND, TYP_I_IMPL, ctxTree); + ctxTree->gtFlags |= GTF_EXCEPT; // Null-pointer exception + ctxTree->gtFlags |= GTF_IND_INVARIANT; + } + else + { + assert(kind == CORINFO_LOOKUP_METHODPARAM || kind == CORINFO_LOOKUP_CLASSPARAM); + + ctxTree = gtNewLclvNode(info.compTypeCtxtArg, TYP_I_IMPL); // Exact method descriptor as passed in as last arg + } + +#ifdef FEATURE_READYTORUN_COMPILER + if (opts.IsReadyToRun()) + { + return impReadyToRunHelperToTree(pResolvedToken, CORINFO_HELP_READYTORUN_GENERIC_HANDLE, TYP_I_IMPL, + gtNewArgList(ctxTree), &pLookup->lookupKind); + } +#endif + + // It's available only via the run-time helper function + if (pRuntimeLookup->indirections == CORINFO_USEHELPER) + { + GenTreeArgList* helperArgs = + gtNewArgList(ctxTree, gtNewIconEmbHndNode(pRuntimeLookup->signature, nullptr, GTF_ICON_TOKEN_HDL, 0, + nullptr, compileTimeHandle)); + + return gtNewHelperCallNode(pRuntimeLookup->helper, TYP_I_IMPL, GTF_EXCEPT, helperArgs); + } + + // Slot pointer + GenTreePtr slotPtrTree = ctxTree; + + if (pRuntimeLookup->testForNull) + { + slotPtrTree = impCloneExpr(ctxTree, &ctxTree, NO_CLASS_HANDLE, (unsigned)CHECK_SPILL_ALL, + nullptr DEBUGARG("impRuntimeLookup slot")); + } + + // Applied repeated indirections + for (WORD i = 0; i < pRuntimeLookup->indirections; i++) + { + if (i != 0) + { + slotPtrTree = gtNewOperNode(GT_IND, TYP_I_IMPL, slotPtrTree); + slotPtrTree->gtFlags |= GTF_IND_NONFAULTING; + slotPtrTree->gtFlags |= GTF_IND_INVARIANT; + } + if (pRuntimeLookup->offsets[i] != 0) + { + slotPtrTree = + gtNewOperNode(GT_ADD, TYP_I_IMPL, slotPtrTree, gtNewIconNode(pRuntimeLookup->offsets[i], TYP_I_IMPL)); + } + } + + // No null test required + if (!pRuntimeLookup->testForNull) + { + if (pRuntimeLookup->indirections == 0) + { + return slotPtrTree; + } + + slotPtrTree = gtNewOperNode(GT_IND, TYP_I_IMPL, slotPtrTree); + slotPtrTree->gtFlags |= GTF_IND_NONFAULTING; + + if (!pRuntimeLookup->testForFixup) + { + return slotPtrTree; + } + + impSpillSideEffects(true, CHECK_SPILL_ALL DEBUGARG("bubbling QMark0")); + + GenTreePtr op1 = impCloneExpr(slotPtrTree, &slotPtrTree, NO_CLASS_HANDLE, (unsigned)CHECK_SPILL_ALL, + nullptr DEBUGARG("impRuntimeLookup test")); + op1 = impImplicitIorI4Cast(op1, TYP_INT); // downcast the pointer to a TYP_INT on 64-bit targets + + // Use a GT_AND to check for the lowest bit and indirect if it is set + GenTreePtr testTree = gtNewOperNode(GT_AND, TYP_INT, op1, gtNewIconNode(1)); + GenTreePtr relop = gtNewOperNode(GT_EQ, TYP_INT, testTree, gtNewIconNode(0)); + relop->gtFlags |= GTF_RELOP_QMARK; + + op1 = impCloneExpr(slotPtrTree, &slotPtrTree, NO_CLASS_HANDLE, (unsigned)CHECK_SPILL_ALL, + nullptr DEBUGARG("impRuntimeLookup indir")); + op1 = gtNewOperNode(GT_ADD, TYP_I_IMPL, op1, gtNewIconNode(-1, TYP_I_IMPL)); // subtract 1 from the pointer + GenTreePtr indirTree = gtNewOperNode(GT_IND, TYP_I_IMPL, op1); + GenTreePtr colon = new (this, GT_COLON) GenTreeColon(TYP_I_IMPL, slotPtrTree, indirTree); + + GenTreePtr qmark = gtNewQmarkNode(TYP_I_IMPL, relop, colon); + + unsigned tmp = lvaGrabTemp(true DEBUGARG("spilling QMark0")); + impAssignTempGen(tmp, qmark, (unsigned)CHECK_SPILL_NONE); + return gtNewLclvNode(tmp, TYP_I_IMPL); + } + + assert(pRuntimeLookup->indirections != 0); + + impSpillSideEffects(true, CHECK_SPILL_ALL DEBUGARG("bubbling QMark1")); + + // Extract the handle + GenTreePtr handle = gtNewOperNode(GT_IND, TYP_I_IMPL, slotPtrTree); + handle->gtFlags |= GTF_IND_NONFAULTING; + + GenTreePtr handleCopy = impCloneExpr(handle, &handle, NO_CLASS_HANDLE, (unsigned)CHECK_SPILL_ALL, + nullptr DEBUGARG("impRuntimeLookup typehandle")); + + // Call to helper + GenTreeArgList* helperArgs = + gtNewArgList(ctxTree, gtNewIconEmbHndNode(pRuntimeLookup->signature, nullptr, GTF_ICON_TOKEN_HDL, 0, nullptr, + compileTimeHandle)); + GenTreePtr helperCall = gtNewHelperCallNode(pRuntimeLookup->helper, TYP_I_IMPL, GTF_EXCEPT, helperArgs); + + // Check for null and possibly call helper + GenTreePtr relop = gtNewOperNode(GT_NE, TYP_INT, handle, gtNewIconNode(0, TYP_I_IMPL)); + relop->gtFlags |= GTF_RELOP_QMARK; + + GenTreePtr colon = new (this, GT_COLON) GenTreeColon(TYP_I_IMPL, + gtNewNothingNode(), // do nothing if nonnull + helperCall); + + GenTreePtr qmark = gtNewQmarkNode(TYP_I_IMPL, relop, colon); + + unsigned tmp; + if (handleCopy->IsLocal()) + { + tmp = handleCopy->gtLclVarCommon.gtLclNum; + } + else + { + tmp = lvaGrabTemp(true DEBUGARG("spilling QMark1")); + } + + impAssignTempGen(tmp, qmark, (unsigned)CHECK_SPILL_NONE); + return gtNewLclvNode(tmp, TYP_I_IMPL); +} + +/****************************************************************************** + * Spills the stack at verCurrentState.esStack[level] and replaces it with a temp. + * If tnum!=BAD_VAR_NUM, the temp var used to replace the tree is tnum, + * else, grab a new temp. + * For structs (which can be pushed on the stack using obj, etc), + * special handling is needed + */ + +struct RecursiveGuard +{ +public: + RecursiveGuard() + { + m_pAddress = nullptr; + } + + ~RecursiveGuard() + { + if (m_pAddress) + { + *m_pAddress = false; + } + } + + void Init(bool* pAddress, bool bInitialize) + { + assert(pAddress && *pAddress == false && "Recursive guard violation"); + m_pAddress = pAddress; + + if (bInitialize) + { + *m_pAddress = true; + } + } + +protected: + bool* m_pAddress; +}; + +bool Compiler::impSpillStackEntry(unsigned level, + unsigned tnum +#ifdef DEBUG + , + bool bAssertOnRecursion, + const char* reason +#endif + ) +{ + +#ifdef DEBUG + RecursiveGuard guard; + guard.Init(&impNestedStackSpill, bAssertOnRecursion); +#endif + + assert(!fgGlobalMorph); // use impInlineSpillStackEntry() during inlining + + GenTreePtr tree = verCurrentState.esStack[level].val; + + /* Allocate a temp if we haven't been asked to use a particular one */ + + if (tiVerificationNeeded) + { + // Ignore bad temp requests (they will happen with bad code and will be + // catched when importing the destblock) + if ((tnum != BAD_VAR_NUM && tnum >= lvaCount) && verNeedsVerification()) + { + return false; + } + } + else + { + if (tnum != BAD_VAR_NUM && (tnum >= lvaCount)) + { + return false; + } + } + + if (tnum == BAD_VAR_NUM) + { + tnum = lvaGrabTemp(true DEBUGARG(reason)); + } + else if (tiVerificationNeeded && lvaTable[tnum].TypeGet() != TYP_UNDEF) + { + // if verification is needed and tnum's type is incompatible with + // type on that stack, we grab a new temp. This is safe since + // we will throw a verification exception in the dest block. + + var_types valTyp = tree->TypeGet(); + var_types dstTyp = lvaTable[tnum].TypeGet(); + + // if the two types are different, we return. This will only happen with bad code and will + // be catched when importing the destblock. We still allow int/byrefs and float/double differences. + if ((genActualType(valTyp) != genActualType(dstTyp)) && + !( +#ifndef _TARGET_64BIT_ + (valTyp == TYP_I_IMPL && dstTyp == TYP_BYREF) || (valTyp == TYP_BYREF && dstTyp == TYP_I_IMPL) || +#endif // !_TARGET_64BIT_ + (varTypeIsFloating(dstTyp) && varTypeIsFloating(valTyp)))) + { + if (verNeedsVerification()) + { + return false; + } + } + } + + /* Assign the spilled entry to the temp */ + impAssignTempGen(tnum, tree, verCurrentState.esStack[level].seTypeInfo.GetClassHandle(), level); + + // The tree type may be modified by impAssignTempGen, so use the type of the lclVar. + var_types type = genActualType(lvaTable[tnum].TypeGet()); + GenTreePtr temp = gtNewLclvNode(tnum, type); + verCurrentState.esStack[level].val = temp; + + return true; +} + +/***************************************************************************** + * + * Ensure that the stack has only spilled values + */ + +void Compiler::impSpillStackEnsure(bool spillLeaves) +{ + assert(!spillLeaves || opts.compDbgCode); + + for (unsigned level = 0; level < verCurrentState.esStackDepth; level++) + { + GenTreePtr tree = verCurrentState.esStack[level].val; + + if (!spillLeaves && tree->OperIsLeaf()) + { + continue; + } + + // Temps introduced by the importer itself don't need to be spilled + + bool isTempLcl = (tree->OperGet() == GT_LCL_VAR) && (tree->gtLclVarCommon.gtLclNum >= info.compLocalsCount); + + if (isTempLcl) + { + continue; + } + + impSpillStackEntry(level, BAD_VAR_NUM DEBUGARG(false) DEBUGARG("impSpillStackEnsure")); + } +} + +void Compiler::impSpillEvalStack() +{ + assert(!fgGlobalMorph); // use impInlineSpillEvalStack() during inlining + + for (unsigned level = 0; level < verCurrentState.esStackDepth; level++) + { + impSpillStackEntry(level, BAD_VAR_NUM DEBUGARG(false) DEBUGARG("impSpillEvalStack")); + } +} + +/***************************************************************************** + * + * If the stack contains any trees with side effects in them, assign those + * trees to temps and append the assignments to the statement list. + * On return the stack is guaranteed to be empty. + */ + +inline void Compiler::impEvalSideEffects() +{ + impSpillSideEffects(false, (unsigned)CHECK_SPILL_ALL DEBUGARG("impEvalSideEffects")); + verCurrentState.esStackDepth = 0; +} + +/***************************************************************************** + * + * If the stack contains any trees with side effects in them, assign those + * trees to temps and replace them on the stack with refs to their temps. + * [0..chkLevel) is the portion of the stack which will be checked and spilled. + */ + +inline void Compiler::impSpillSideEffects(bool spillGlobEffects, unsigned chkLevel DEBUGARG(const char* reason)) +{ + assert(chkLevel != (unsigned)CHECK_SPILL_NONE); + + /* Before we make any appends to the tree list we must spill the + * "special" side effects (GTF_ORDER_SIDEEFF on a GT_CATCH_ARG) */ + + impSpillSpecialSideEff(); + + if (chkLevel == (unsigned)CHECK_SPILL_ALL) + { + chkLevel = verCurrentState.esStackDepth; + } + + assert(chkLevel <= verCurrentState.esStackDepth); + + unsigned spillFlags = spillGlobEffects ? GTF_GLOB_EFFECT : GTF_SIDE_EFFECT; + + for (unsigned i = 0; i < chkLevel; i++) + { + GenTreePtr tree = verCurrentState.esStack[i].val; + + GenTreePtr lclVarTree; + + if ((tree->gtFlags & spillFlags) != 0 || + (spillGlobEffects && // Only consider the following when spillGlobEffects == TRUE + !impIsAddressInLocal(tree, &lclVarTree) && // No need to spill the GT_ADDR node on a local. + gtHasLocalsWithAddrOp(tree))) // Spill if we still see GT_LCL_VAR that contains lvHasLdAddrOp or + // lvAddrTaken flag. + { + impSpillStackEntry(i, BAD_VAR_NUM DEBUGARG(false) DEBUGARG(reason)); + } + } +} + +/***************************************************************************** + * + * If the stack contains any trees with special side effects in them, assign + * those trees to temps and replace them on the stack with refs to their temps. + */ + +inline void Compiler::impSpillSpecialSideEff() +{ + // Only exception objects need to be carefully handled + + if (!compCurBB->bbCatchTyp) + { + return; + } + + for (unsigned level = 0; level < verCurrentState.esStackDepth; level++) + { + GenTreePtr tree = verCurrentState.esStack[level].val; + // Make sure if we have an exception object in the sub tree we spill ourselves. + if (gtHasCatchArg(tree)) + { + impSpillStackEntry(level, BAD_VAR_NUM DEBUGARG(false) DEBUGARG("impSpillSpecialSideEff")); + } + } +} + +/***************************************************************************** + * + * Spill all stack references to value classes (TYP_STRUCT nodes) + */ + +void Compiler::impSpillValueClasses() +{ + for (unsigned level = 0; level < verCurrentState.esStackDepth; level++) + { + GenTreePtr tree = verCurrentState.esStack[level].val; + + if (fgWalkTreePre(&tree, impFindValueClasses) == WALK_ABORT) + { + // Tree walk was aborted, which means that we found a + // value class on the stack. Need to spill that + // stack entry. + + impSpillStackEntry(level, BAD_VAR_NUM DEBUGARG(false) DEBUGARG("impSpillValueClasses")); + } + } +} + +/***************************************************************************** + * + * Callback that checks if a tree node is TYP_STRUCT + */ + +Compiler::fgWalkResult Compiler::impFindValueClasses(GenTreePtr* pTree, fgWalkData* data) +{ + fgWalkResult walkResult = WALK_CONTINUE; + + if ((*pTree)->gtType == TYP_STRUCT) + { + // Abort the walk and indicate that we found a value class + + walkResult = WALK_ABORT; + } + + return walkResult; +} + +/***************************************************************************** + * + * If the stack contains any trees with references to local #lclNum, assign + * those trees to temps and replace their place on the stack with refs to + * their temps. + */ + +void Compiler::impSpillLclRefs(ssize_t lclNum) +{ + assert(!fgGlobalMorph); // use impInlineSpillLclRefs() during inlining + + /* Before we make any appends to the tree list we must spill the + * "special" side effects (GTF_ORDER_SIDEEFF) - GT_CATCH_ARG */ + + impSpillSpecialSideEff(); + + for (unsigned level = 0; level < verCurrentState.esStackDepth; level++) + { + GenTreePtr tree = verCurrentState.esStack[level].val; + + /* If the tree may throw an exception, and the block has a handler, + then we need to spill assignments to the local if the local is + live on entry to the handler. + Just spill 'em all without considering the liveness */ + + bool xcptnCaught = ehBlockHasExnFlowDsc(compCurBB) && (tree->gtFlags & (GTF_CALL | GTF_EXCEPT)); + + /* Skip the tree if it doesn't have an affected reference, + unless xcptnCaught */ + + if (xcptnCaught || gtHasRef(tree, lclNum, false)) + { + impSpillStackEntry(level, BAD_VAR_NUM DEBUGARG(false) DEBUGARG("impSpillLclRefs")); + } + } +} + +/***************************************************************************** + * + * Push catch arg onto the stack. + * If there are jumps to the beginning of the handler, insert basic block + * and spill catch arg to a temp. Update the handler block if necessary. + * + * Returns the basic block of the actual handler. + */ + +BasicBlock* Compiler::impPushCatchArgOnStack(BasicBlock* hndBlk, CORINFO_CLASS_HANDLE clsHnd) +{ + // Do not inject the basic block twice on reimport. This should be + // hit only under JIT stress. See if the block is the one we injected. + // Note that EH canonicalization can inject internal blocks here. We might + // be able to re-use such a block (but we don't, right now). + if ((hndBlk->bbFlags & (BBF_IMPORTED | BBF_INTERNAL | BBF_DONT_REMOVE | BBF_HAS_LABEL | BBF_JMP_TARGET)) == + (BBF_IMPORTED | BBF_INTERNAL | BBF_DONT_REMOVE | BBF_HAS_LABEL | BBF_JMP_TARGET)) + { + GenTreePtr tree = hndBlk->bbTreeList; + + if (tree != nullptr && tree->gtOper == GT_STMT) + { + tree = tree->gtStmt.gtStmtExpr; + assert(tree != nullptr); + + if ((tree->gtOper == GT_ASG) && (tree->gtOp.gtOp1->gtOper == GT_LCL_VAR) && + (tree->gtOp.gtOp2->gtOper == GT_CATCH_ARG)) + { + tree = gtNewLclvNode(tree->gtOp.gtOp1->gtLclVarCommon.gtLclNum, TYP_REF); + + impPushOnStack(tree, typeInfo(TI_REF, clsHnd)); + + return hndBlk->bbNext; + } + } + + // If we get here, it must have been some other kind of internal block. It's possible that + // someone prepended something to our injected block, but that's unlikely. + } + + /* Push the exception address value on the stack */ + GenTreePtr arg = new (this, GT_CATCH_ARG) GenTree(GT_CATCH_ARG, TYP_REF); + + /* Mark the node as having a side-effect - i.e. cannot be + * moved around since it is tied to a fixed location (EAX) */ + arg->gtFlags |= GTF_ORDER_SIDEEFF; + + /* Spill GT_CATCH_ARG to a temp if there are jumps to the beginning of the handler */ + if (hndBlk->bbRefs > 1 || compStressCompile(STRESS_CATCH_ARG, 5)) + { + if (hndBlk->bbRefs == 1) + { + hndBlk->bbRefs++; + } + + /* Create extra basic block for the spill */ + BasicBlock* newBlk = fgNewBBbefore(BBJ_NONE, hndBlk, /* extendRegion */ true); + newBlk->bbFlags |= BBF_IMPORTED | BBF_DONT_REMOVE | BBF_HAS_LABEL | BBF_JMP_TARGET; + newBlk->setBBWeight(hndBlk->bbWeight); + newBlk->bbCodeOffs = hndBlk->bbCodeOffs; + + /* Account for the new link we are about to create */ + hndBlk->bbRefs++; + + /* Spill into a temp */ + unsigned tempNum = lvaGrabTemp(false DEBUGARG("SpillCatchArg")); + lvaTable[tempNum].lvType = TYP_REF; + arg = gtNewTempAssign(tempNum, arg); + + hndBlk->bbStkTempsIn = tempNum; + + /* Report the debug info. impImportBlockCode won't treat + * the actual handler as exception block and thus won't do it for us. */ + if (info.compStmtOffsetsImplicit & ICorDebugInfo::CALL_SITE_BOUNDARIES) + { + impCurStmtOffs = newBlk->bbCodeOffs | IL_OFFSETX_STKBIT; + arg = gtNewStmt(arg, impCurStmtOffs); + } + + fgInsertStmtAtEnd(newBlk, arg); + + arg = gtNewLclvNode(tempNum, TYP_REF); + } + + impPushOnStack(arg, typeInfo(TI_REF, clsHnd)); + + return hndBlk; +} + +/***************************************************************************** + * + * Given a tree, clone it. *pClone is set to the cloned tree. + * Returns the original tree if the cloning was easy, + * else returns the temp to which the tree had to be spilled to. + * If the tree has side-effects, it will be spilled to a temp. + */ + +GenTreePtr Compiler::impCloneExpr(GenTreePtr tree, + GenTreePtr* pClone, + CORINFO_CLASS_HANDLE structHnd, + unsigned curLevel, + GenTreePtr* pAfterStmt DEBUGARG(const char* reason)) +{ + if (!(tree->gtFlags & GTF_GLOB_EFFECT)) + { + GenTreePtr clone = gtClone(tree, true); + + if (clone) + { + *pClone = clone; + return tree; + } + } + + /* Store the operand in a temp and return the temp */ + + unsigned temp = lvaGrabTemp(true DEBUGARG(reason)); + + // impAssignTempGen() may change tree->gtType to TYP_VOID for calls which + // return a struct type. It also may modify the struct type to a more + // specialized type (e.g. a SIMD type). So we will get the type from + // the lclVar AFTER calling impAssignTempGen(). + + impAssignTempGen(temp, tree, structHnd, curLevel, pAfterStmt, impCurStmtOffs); + var_types type = genActualType(lvaTable[temp].TypeGet()); + + *pClone = gtNewLclvNode(temp, type); + return gtNewLclvNode(temp, type); +} + +/***************************************************************************** + * Remember the IL offset (including stack-empty info) for the trees we will + * generate now. + */ + +inline void Compiler::impCurStmtOffsSet(IL_OFFSET offs) +{ + if (compIsForInlining()) + { + GenTreePtr callStmt = impInlineInfo->iciStmt; + assert(callStmt->gtOper == GT_STMT); + impCurStmtOffs = callStmt->gtStmt.gtStmtILoffsx; + } + else + { + assert(offs == BAD_IL_OFFSET || (offs & IL_OFFSETX_BITS) == 0); + IL_OFFSETX stkBit = (verCurrentState.esStackDepth > 0) ? IL_OFFSETX_STKBIT : 0; + impCurStmtOffs = offs | stkBit; + } +} + +/***************************************************************************** + * Returns current IL offset with stack-empty and call-instruction info incorporated + */ +inline IL_OFFSETX Compiler::impCurILOffset(IL_OFFSET offs, bool callInstruction) +{ + if (compIsForInlining()) + { + return BAD_IL_OFFSET; + } + else + { + assert(offs == BAD_IL_OFFSET || (offs & IL_OFFSETX_BITS) == 0); + IL_OFFSETX stkBit = (verCurrentState.esStackDepth > 0) ? IL_OFFSETX_STKBIT : 0; + IL_OFFSETX callInstructionBit = callInstruction ? IL_OFFSETX_CALLINSTRUCTIONBIT : 0; + return offs | stkBit | callInstructionBit; + } +} + +/***************************************************************************** + * + * Remember the instr offset for the statements + * + * When we do impAppendTree(tree), we can't set tree->gtStmtLastILoffs to + * impCurOpcOffs, if the append was done because of a partial stack spill, + * as some of the trees corresponding to code up to impCurOpcOffs might + * still be sitting on the stack. + * So we delay marking of gtStmtLastILoffs until impNoteLastILoffs(). + * This should be called when an opcode finally/explicitly causes + * impAppendTree(tree) to be called (as opposed to being called because of + * a spill caused by the opcode) + */ + +#ifdef DEBUG + +void Compiler::impNoteLastILoffs() +{ + if (impLastILoffsStmt == nullptr) + { + // We should have added a statement for the current basic block + // Is this assert correct ? + + assert(impTreeLast); + assert(impTreeLast->gtOper == GT_STMT); + + impTreeLast->gtStmt.gtStmtLastILoffs = compIsForInlining() ? BAD_IL_OFFSET : impCurOpcOffs; + } + else + { + impLastILoffsStmt->gtStmt.gtStmtLastILoffs = compIsForInlining() ? BAD_IL_OFFSET : impCurOpcOffs; + impLastILoffsStmt = nullptr; + } +} + +#endif // DEBUG + +/***************************************************************************** + * We don't create any GenTree (excluding spills) for a branch. + * For debugging info, we need a placeholder so that we can note + * the IL offset in gtStmt.gtStmtOffs. So append an empty statement. + */ + +void Compiler::impNoteBranchOffs() +{ + if (opts.compDbgCode) + { + impAppendTree(gtNewNothingNode(), (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); + } +} + +/***************************************************************************** + * Locate the next stmt boundary for which we need to record info. + * We will have to spill the stack at such boundaries if it is not + * already empty. + * Returns the next stmt boundary (after the start of the block) + */ + +unsigned Compiler::impInitBlockLineInfo() +{ + /* Assume the block does not correspond with any IL offset. This prevents + us from reporting extra offsets. Extra mappings can cause confusing + stepping, especially if the extra mapping is a jump-target, and the + debugger does not ignore extra mappings, but instead rewinds to the + nearest known offset */ + + impCurStmtOffsSet(BAD_IL_OFFSET); + + if (compIsForInlining()) + { + return ~0; + } + + IL_OFFSET blockOffs = compCurBB->bbCodeOffs; + + if ((verCurrentState.esStackDepth == 0) && (info.compStmtOffsetsImplicit & ICorDebugInfo::STACK_EMPTY_BOUNDARIES)) + { + impCurStmtOffsSet(blockOffs); + } + + if (false && (info.compStmtOffsetsImplicit & ICorDebugInfo::CALL_SITE_BOUNDARIES)) + { + impCurStmtOffsSet(blockOffs); + } + + /* Always report IL offset 0 or some tests get confused. + Probably a good idea anyways */ + + if (blockOffs == 0) + { + impCurStmtOffsSet(blockOffs); + } + + if (!info.compStmtOffsetsCount) + { + return ~0; + } + + /* Find the lowest explicit stmt boundary within the block */ + + /* Start looking at an entry that is based on our instr offset */ + + unsigned index = (info.compStmtOffsetsCount * blockOffs) / info.compILCodeSize; + + if (index >= info.compStmtOffsetsCount) + { + index = info.compStmtOffsetsCount - 1; + } + + /* If we've guessed too far, back up */ + + while (index > 0 && info.compStmtOffsets[index - 1] >= blockOffs) + { + index--; + } + + /* If we guessed short, advance ahead */ + + while (info.compStmtOffsets[index] < blockOffs) + { + index++; + + if (index == info.compStmtOffsetsCount) + { + return info.compStmtOffsetsCount; + } + } + + assert(index < info.compStmtOffsetsCount); + + if (info.compStmtOffsets[index] == blockOffs) + { + /* There is an explicit boundary for the start of this basic block. + So we will start with bbCodeOffs. Else we will wait until we + get to the next explicit boundary */ + + impCurStmtOffsSet(blockOffs); + + index++; + } + + return index; +} + +/*****************************************************************************/ + +static inline bool impOpcodeIsCallOpcode(OPCODE opcode) +{ + switch (opcode) + { + case CEE_CALL: + case CEE_CALLI: + case CEE_CALLVIRT: + return true; + + default: + return false; + } +} + +/*****************************************************************************/ +#ifdef DEBUGGING_SUPPORT + +static inline bool impOpcodeIsCallSiteBoundary(OPCODE opcode) +{ + switch (opcode) + { + case CEE_CALL: + case CEE_CALLI: + case CEE_CALLVIRT: + case CEE_JMP: + case CEE_NEWOBJ: + case CEE_NEWARR: + return true; + + default: + return false; + } +} + +#endif // DEBUGGING_SUPPORT + +/*****************************************************************************/ + +// One might think it is worth caching these values, but results indicate +// that it isn't. +// In addition, caching them causes SuperPMI to be unable to completely +// encapsulate an individual method context. +CORINFO_CLASS_HANDLE Compiler::impGetRefAnyClass() +{ + CORINFO_CLASS_HANDLE refAnyClass = info.compCompHnd->getBuiltinClass(CLASSID_TYPED_BYREF); + assert(refAnyClass != (CORINFO_CLASS_HANDLE) nullptr); + return refAnyClass; +} + +CORINFO_CLASS_HANDLE Compiler::impGetTypeHandleClass() +{ + CORINFO_CLASS_HANDLE typeHandleClass = info.compCompHnd->getBuiltinClass(CLASSID_TYPE_HANDLE); + assert(typeHandleClass != (CORINFO_CLASS_HANDLE) nullptr); + return typeHandleClass; +} + +CORINFO_CLASS_HANDLE Compiler::impGetRuntimeArgumentHandle() +{ + CORINFO_CLASS_HANDLE argIteratorClass = info.compCompHnd->getBuiltinClass(CLASSID_ARGUMENT_HANDLE); + assert(argIteratorClass != (CORINFO_CLASS_HANDLE) nullptr); + return argIteratorClass; +} + +CORINFO_CLASS_HANDLE Compiler::impGetStringClass() +{ + CORINFO_CLASS_HANDLE stringClass = info.compCompHnd->getBuiltinClass(CLASSID_STRING); + assert(stringClass != (CORINFO_CLASS_HANDLE) nullptr); + return stringClass; +} + +CORINFO_CLASS_HANDLE Compiler::impGetObjectClass() +{ + CORINFO_CLASS_HANDLE objectClass = info.compCompHnd->getBuiltinClass(CLASSID_SYSTEM_OBJECT); + assert(objectClass != (CORINFO_CLASS_HANDLE) nullptr); + return objectClass; +} + +/***************************************************************************** + * "&var" can be used either as TYP_BYREF or TYP_I_IMPL, but we + * set its type to TYP_BYREF when we create it. We know if it can be + * changed to TYP_I_IMPL only at the point where we use it + */ + +/* static */ +void Compiler::impBashVarAddrsToI(GenTreePtr tree1, GenTreePtr tree2) +{ + if (tree1->IsVarAddr()) + { + tree1->gtType = TYP_I_IMPL; + } + + if (tree2 && tree2->IsVarAddr()) + { + tree2->gtType = TYP_I_IMPL; + } +} + +/***************************************************************************** + * TYP_INT and TYP_I_IMPL can be used almost interchangeably, but we want + * to make that an explicit cast in our trees, so any implicit casts that + * exist in the IL (at least on 64-bit where TYP_I_IMPL != TYP_INT) are + * turned into explicit casts here. + * We also allow an implicit conversion of a ldnull into a TYP_I_IMPL(0) + */ + +GenTreePtr Compiler::impImplicitIorI4Cast(GenTreePtr tree, var_types dstTyp) +{ + var_types currType = genActualType(tree->gtType); + var_types wantedType = genActualType(dstTyp); + + if (wantedType != currType) + { + // Automatic upcast for a GT_CNS_INT into TYP_I_IMPL + if ((tree->OperGet() == GT_CNS_INT) && varTypeIsI(dstTyp)) + { + if (!varTypeIsI(tree->gtType) || ((tree->gtType == TYP_REF) && (tree->gtIntCon.gtIconVal == 0))) + { + tree->gtType = TYP_I_IMPL; + } + } +#ifdef _TARGET_64BIT_ + else if (varTypeIsI(wantedType) && (currType == TYP_INT)) + { + // Note that this allows TYP_INT to be cast to a TYP_I_IMPL when wantedType is a TYP_BYREF or TYP_REF + tree = gtNewCastNode(TYP_I_IMPL, tree, TYP_I_IMPL); + } + else if ((wantedType == TYP_INT) && varTypeIsI(currType)) + { + // Note that this allows TYP_BYREF or TYP_REF to be cast to a TYP_INT + tree = gtNewCastNode(TYP_INT, tree, TYP_INT); + } +#endif // _TARGET_64BIT_ + } + + return tree; +} + +/***************************************************************************** + * TYP_FLOAT and TYP_DOUBLE can be used almost interchangeably in some cases, + * but we want to make that an explicit cast in our trees, so any implicit casts + * that exist in the IL are turned into explicit casts here. + */ + +GenTreePtr Compiler::impImplicitR4orR8Cast(GenTreePtr tree, var_types dstTyp) +{ +#ifndef LEGACY_BACKEND + if (varTypeIsFloating(tree) && varTypeIsFloating(dstTyp) && (dstTyp != tree->gtType)) + { + tree = gtNewCastNode(dstTyp, tree, dstTyp); + } +#endif // !LEGACY_BACKEND + + return tree; +} + +/*****************************************************************************/ +BOOL Compiler::impLocAllocOnStack() +{ + if (!compLocallocUsed) + { + return (FALSE); + } + + // Returns true if a GT_LCLHEAP node is encountered in any of the trees + // that have been pushed on the importer evaluatuion stack. + // + for (unsigned i = 0; i < verCurrentState.esStackDepth; i++) + { + if (fgWalkTreePre(&verCurrentState.esStack[i].val, Compiler::fgChkLocAllocCB) == WALK_ABORT) + { + return (TRUE); + } + } + return (FALSE); +} + +//------------------------------------------------------------------------ +// impInitializeArrayIntrinsic: Attempts to replace a call to InitializeArray +// with a GT_COPYBLK node. +// +// Arguments: +// sig - The InitializeArray signature. +// +// Return Value: +// A pointer to the newly created GT_COPYBLK node if the replacement succeeds or +// nullptr otherwise. +// +// Notes: +// The function recognizes the following IL pattern: +// ldc <length> or a list of ldc <lower bound>/<length> +// newarr or newobj +// dup +// ldtoken <field handle> +// call InitializeArray +// The lower bounds need not be constant except when the array rank is 1. +// The function recognizes all kinds of arrays thus enabling a small runtime +// such as CoreRT to skip providing an implementation for InitializeArray. + +GenTreePtr Compiler::impInitializeArrayIntrinsic(CORINFO_SIG_INFO* sig) +{ + assert(sig->numArgs == 2); + + GenTreePtr fieldTokenNode = impStackTop(0).val; + GenTreePtr arrayLocalNode = impStackTop(1).val; + + // + // Verify that the field token is known and valid. Note that It's also + // possible for the token to come from reflection, in which case we cannot do + // the optimization and must therefore revert to calling the helper. You can + // see an example of this in bvt\DynIL\initarray2.exe (in Main). + // + + // Check to see if the ldtoken helper call is what we see here. + if (fieldTokenNode->gtOper != GT_CALL || (fieldTokenNode->gtCall.gtCallType != CT_HELPER) || + (fieldTokenNode->gtCall.gtCallMethHnd != eeFindHelper(CORINFO_HELP_FIELDDESC_TO_STUBRUNTIMEFIELD))) + { + return nullptr; + } + + // Strip helper call away + fieldTokenNode = fieldTokenNode->gtCall.gtCallArgs->Current(); + + if (fieldTokenNode->gtOper == GT_IND) + { + fieldTokenNode = fieldTokenNode->gtOp.gtOp1; + } + + // Check for constant + if (fieldTokenNode->gtOper != GT_CNS_INT) + { + return nullptr; + } + + CORINFO_FIELD_HANDLE fieldToken = (CORINFO_FIELD_HANDLE)fieldTokenNode->gtIntCon.gtCompileTimeHandle; + if (!fieldTokenNode->IsIconHandle(GTF_ICON_FIELD_HDL) || (fieldToken == nullptr)) + { + return nullptr; + } + + // + // We need to get the number of elements in the array and the size of each element. + // We verify that the newarr statement is exactly what we expect it to be. + // If it's not then we just return NULL and we don't optimize this call + // + + // + // It is possible the we don't have any statements in the block yet + // + if (impTreeLast->gtOper != GT_STMT) + { + assert(impTreeLast->gtOper == GT_BEG_STMTS); + return nullptr; + } + + // + // We start by looking at the last statement, making sure it's an assignment, and + // that the target of the assignment is the array passed to InitializeArray. + // + GenTreePtr arrayAssignment = impTreeLast->gtStmt.gtStmtExpr; + if ((arrayAssignment->gtOper != GT_ASG) || (arrayAssignment->gtOp.gtOp1->gtOper != GT_LCL_VAR) || + (arrayLocalNode->gtOper != GT_LCL_VAR) || + (arrayAssignment->gtOp.gtOp1->gtLclVarCommon.gtLclNum != arrayLocalNode->gtLclVarCommon.gtLclNum)) + { + return nullptr; + } + + // + // Make sure that the object being assigned is a helper call. + // + + GenTreePtr newArrayCall = arrayAssignment->gtOp.gtOp2; + if ((newArrayCall->gtOper != GT_CALL) || (newArrayCall->gtCall.gtCallType != CT_HELPER)) + { + return nullptr; + } + + // + // Verify that it is one of the new array helpers. + // + + bool isMDArray = false; + + if (newArrayCall->gtCall.gtCallMethHnd != eeFindHelper(CORINFO_HELP_NEWARR_1_DIRECT) && + newArrayCall->gtCall.gtCallMethHnd != eeFindHelper(CORINFO_HELP_NEWARR_1_OBJ) && + newArrayCall->gtCall.gtCallMethHnd != eeFindHelper(CORINFO_HELP_NEWARR_1_VC) && + newArrayCall->gtCall.gtCallMethHnd != eeFindHelper(CORINFO_HELP_NEWARR_1_ALIGN8) +#ifdef FEATURE_READYTORUN_COMPILER + && newArrayCall->gtCall.gtCallMethHnd != eeFindHelper(CORINFO_HELP_READYTORUN_NEWARR_1) +#endif + ) + { +#if COR_JIT_EE_VERSION > 460 + if (newArrayCall->gtCall.gtCallMethHnd != eeFindHelper(CORINFO_HELP_NEW_MDARR_NONVARARG)) + { + return nullptr; + } + + isMDArray = true; +#endif + } + + CORINFO_CLASS_HANDLE arrayClsHnd = (CORINFO_CLASS_HANDLE)newArrayCall->gtCall.compileTimeHelperArgumentHandle; + + // + // Make sure we found a compile time handle to the array + // + + if (!arrayClsHnd) + { + return nullptr; + } + + unsigned rank = 0; + S_UINT32 numElements; + + if (isMDArray) + { + rank = info.compCompHnd->getArrayRank(arrayClsHnd); + + if (rank == 0) + { + return nullptr; + } + + GenTreeArgList* tokenArg = newArrayCall->gtCall.gtCallArgs; + assert(tokenArg != nullptr); + GenTreeArgList* numArgsArg = tokenArg->Rest(); + assert(numArgsArg != nullptr); + GenTreeArgList* argsArg = numArgsArg->Rest(); + assert(argsArg != nullptr); + + // + // The number of arguments should be a constant between 1 and 64. The rank can't be 0 + // so at least one length must be present and the rank can't exceed 32 so there can + // be at most 64 arguments - 32 lengths and 32 lower bounds. + // + + if ((!numArgsArg->Current()->IsCnsIntOrI()) || (numArgsArg->Current()->AsIntCon()->IconValue() < 1) || + (numArgsArg->Current()->AsIntCon()->IconValue() > 64)) + { + return nullptr; + } + + unsigned numArgs = static_cast<unsigned>(numArgsArg->Current()->AsIntCon()->IconValue()); + bool lowerBoundsSpecified; + + if (numArgs == rank * 2) + { + lowerBoundsSpecified = true; + } + else if (numArgs == rank) + { + lowerBoundsSpecified = false; + + // + // If the rank is 1 and a lower bound isn't specified then the runtime creates + // a SDArray. Note that even if a lower bound is specified it can be 0 and then + // we get a SDArray as well, see the for loop below. + // + + if (rank == 1) + { + isMDArray = false; + } + } + else + { + return nullptr; + } + + // + // The rank is known to be at least 1 so we can start with numElements being 1 + // to avoid the need to special case the first dimension. + // + + numElements = S_UINT32(1); + + struct Match + { + static bool IsArgsFieldInit(GenTree* tree, unsigned index, unsigned lvaNewObjArrayArgs) + { + return (tree->OperGet() == GT_ASG) && IsArgsFieldIndir(tree->gtGetOp1(), index, lvaNewObjArrayArgs) && + IsArgsAddr(tree->gtGetOp1()->gtGetOp1()->gtGetOp1(), lvaNewObjArrayArgs); + } + + static bool IsArgsFieldIndir(GenTree* tree, unsigned index, unsigned lvaNewObjArrayArgs) + { + return (tree->OperGet() == GT_IND) && (tree->gtGetOp1()->OperGet() == GT_ADD) && + (tree->gtGetOp1()->gtGetOp2()->IsIntegralConst(sizeof(INT32) * index)) && + IsArgsAddr(tree->gtGetOp1()->gtGetOp1(), lvaNewObjArrayArgs); + } + + static bool IsArgsAddr(GenTree* tree, unsigned lvaNewObjArrayArgs) + { + return (tree->OperGet() == GT_ADDR) && (tree->gtGetOp1()->OperGet() == GT_LCL_VAR) && + (tree->gtGetOp1()->AsLclVar()->GetLclNum() == lvaNewObjArrayArgs); + } + + static bool IsComma(GenTree* tree) + { + return (tree != nullptr) && (tree->OperGet() == GT_COMMA); + } + }; + + unsigned argIndex = 0; + GenTree* comma; + + for (comma = argsArg->Current(); Match::IsComma(comma); comma = comma->gtGetOp2()) + { + if (lowerBoundsSpecified) + { + // + // In general lower bounds can be ignored because they're not needed to + // calculate the total number of elements. But for single dimensional arrays + // we need to know if the lower bound is 0 because in this case the runtime + // creates a SDArray and this affects the way the array data offset is calculated. + // + + if (rank == 1) + { + GenTree* lowerBoundAssign = comma->gtGetOp1(); + assert(Match::IsArgsFieldInit(lowerBoundAssign, argIndex, lvaNewObjArrayArgs)); + GenTree* lowerBoundNode = lowerBoundAssign->gtGetOp2(); + + if (lowerBoundNode->IsIntegralConst(0)) + { + isMDArray = false; + } + } + + comma = comma->gtGetOp2(); + argIndex++; + } + + GenTree* lengthNodeAssign = comma->gtGetOp1(); + assert(Match::IsArgsFieldInit(lengthNodeAssign, argIndex, lvaNewObjArrayArgs)); + GenTree* lengthNode = lengthNodeAssign->gtGetOp2(); + + if (!lengthNode->IsCnsIntOrI()) + { + return nullptr; + } + + numElements *= S_SIZE_T(lengthNode->AsIntCon()->IconValue()); + argIndex++; + } + + assert((comma != nullptr) && Match::IsArgsAddr(comma, lvaNewObjArrayArgs)); + + if (argIndex != numArgs) + { + return nullptr; + } + } + else + { + // + // Make sure there are exactly two arguments: the array class and + // the number of elements. + // + + GenTreePtr arrayLengthNode; + + GenTreeArgList* args = newArrayCall->gtCall.gtCallArgs; +#ifdef FEATURE_READYTORUN_COMPILER + if (newArrayCall->gtCall.gtCallMethHnd == eeFindHelper(CORINFO_HELP_READYTORUN_NEWARR_1)) + { + // Array length is 1st argument for readytorun helper + arrayLengthNode = args->Current(); + } + else +#endif + { + // Array length is 2nd argument for regular helper + arrayLengthNode = args->Rest()->Current(); + } + + // + // Make sure that the number of elements look valid. + // + if (arrayLengthNode->gtOper != GT_CNS_INT) + { + return nullptr; + } + + numElements = S_SIZE_T(arrayLengthNode->gtIntCon.gtIconVal); + + if (!info.compCompHnd->isSDArray(arrayClsHnd)) + { + return nullptr; + } + } + + CORINFO_CLASS_HANDLE elemClsHnd; + var_types elementType = JITtype2varType(info.compCompHnd->getChildType(arrayClsHnd, &elemClsHnd)); + + // + // Note that genTypeSize will return zero for non primitive types, which is exactly + // what we want (size will then be 0, and we will catch this in the conditional below). + // Note that we don't expect this to fail for valid binaries, so we assert in the + // non-verification case (the verification case should not assert but rather correctly + // handle bad binaries). This assert is not guarding any specific invariant, but rather + // saying that we don't expect this to happen, and if it is hit, we need to investigate + // why. + // + + S_UINT32 elemSize(genTypeSize(elementType)); + S_UINT32 size = elemSize * S_UINT32(numElements); + + if (size.IsOverflow()) + { + return nullptr; + } + + if ((size.Value() == 0) || (varTypeIsGC(elementType))) + { + assert(verNeedsVerification()); + return nullptr; + } + + void* initData = info.compCompHnd->getArrayInitializationData(fieldToken, size.Value()); + if (!initData) + { + return nullptr; + } + + // + // At this point we are ready to commit to implementing the InitializeArray + // intrinsic using a struct assignment. Pop the arguments from the stack and + // return the struct assignment node. + // + + impPopStack(); + impPopStack(); + + const unsigned blkSize = size.Value(); + GenTreePtr dst; + + if (isMDArray) + { + unsigned dataOffset = eeGetMDArrayDataOffset(elementType, rank); + + dst = gtNewOperNode(GT_ADD, TYP_BYREF, arrayLocalNode, gtNewIconNode(dataOffset, TYP_I_IMPL)); + } + else + { + dst = gtNewOperNode(GT_ADDR, TYP_BYREF, gtNewIndexRef(elementType, arrayLocalNode, gtNewIconNode(0))); + } + GenTreePtr blk = gtNewBlockVal(dst, blkSize); + GenTreePtr srcAddr = gtNewIconHandleNode((size_t)initData, GTF_ICON_STATIC_HDL); + GenTreePtr src = gtNewOperNode(GT_IND, TYP_STRUCT, srcAddr); + + return gtNewBlkOpNode(blk, // dst + src, // src + blkSize, // size + false, // volatil + true); // copyBlock +} + +/*****************************************************************************/ +// Returns the GenTree that should be used to do the intrinsic instead of the call. +// Returns NULL if an intrinsic cannot be used + +GenTreePtr Compiler::impIntrinsic(CORINFO_CLASS_HANDLE clsHnd, + CORINFO_METHOD_HANDLE method, + CORINFO_SIG_INFO* sig, + int memberRef, + bool readonlyCall, + bool tailCall, + CorInfoIntrinsics* pIntrinsicID) +{ + bool mustExpand = false; +#if COR_JIT_EE_VERSION > 460 + CorInfoIntrinsics intrinsicID = info.compCompHnd->getIntrinsicID(method, &mustExpand); +#else + CorInfoIntrinsics intrinsicID = info.compCompHnd->getIntrinsicID(method); +#endif + *pIntrinsicID = intrinsicID; + +#ifndef _TARGET_ARM_ + genTreeOps interlockedOperator; +#endif + + if (intrinsicID == CORINFO_INTRINSIC_StubHelpers_GetStubContext) + { + // must be done regardless of DbgCode and MinOpts + return gtNewLclvNode(lvaStubArgumentVar, TYP_I_IMPL); + } +#ifdef _TARGET_64BIT_ + if (intrinsicID == CORINFO_INTRINSIC_StubHelpers_GetStubContextAddr) + { + // must be done regardless of DbgCode and MinOpts + return gtNewOperNode(GT_ADDR, TYP_I_IMPL, gtNewLclvNode(lvaStubArgumentVar, TYP_I_IMPL)); + } +#else + assert(intrinsicID != CORINFO_INTRINSIC_StubHelpers_GetStubContextAddr); +#endif + + GenTreePtr retNode = nullptr; + + // + // We disable the inlining of instrinsics for MinOpts. + // + if (!mustExpand && (opts.compDbgCode || opts.MinOpts())) + { + *pIntrinsicID = CORINFO_INTRINSIC_Illegal; + return retNode; + } + + // Currently we don't have CORINFO_INTRINSIC_Exp because it does not + // seem to work properly for Infinity values, we don't do + // CORINFO_INTRINSIC_Pow because it needs a Helper which we currently don't have + + var_types callType = JITtype2varType(sig->retType); + + /* First do the intrinsics which are always smaller than a call */ + + switch (intrinsicID) + { + GenTreePtr op1, op2; + + case CORINFO_INTRINSIC_Sin: + case CORINFO_INTRINSIC_Sqrt: + case CORINFO_INTRINSIC_Abs: + case CORINFO_INTRINSIC_Cos: + case CORINFO_INTRINSIC_Round: + case CORINFO_INTRINSIC_Cosh: + case CORINFO_INTRINSIC_Sinh: + case CORINFO_INTRINSIC_Tan: + case CORINFO_INTRINSIC_Tanh: + case CORINFO_INTRINSIC_Asin: + case CORINFO_INTRINSIC_Acos: + case CORINFO_INTRINSIC_Atan: + case CORINFO_INTRINSIC_Atan2: + case CORINFO_INTRINSIC_Log10: + case CORINFO_INTRINSIC_Pow: + case CORINFO_INTRINSIC_Exp: + case CORINFO_INTRINSIC_Ceiling: + case CORINFO_INTRINSIC_Floor: + + // These are math intrinsics + + assert(callType != TYP_STRUCT); + + op1 = nullptr; + +#ifdef LEGACY_BACKEND + if (IsTargetIntrinsic(intrinsicID)) +#else + // Intrinsics that are not implemented directly by target instructions will + // be re-materialized as users calls in rationalizer. For prefixed tail calls, + // don't do this optimization, because + // a) For back compatibility reasons on desktop.Net 4.6 / 4.6.1 + // b) It will be non-trivial task or too late to re-materialize a surviving + // tail prefixed GT_INTRINSIC as tail call in rationalizer. + if (!IsIntrinsicImplementedByUserCall(intrinsicID) || !tailCall) +#endif + { + switch (sig->numArgs) + { + case 1: + op1 = impPopStack().val; + +#if FEATURE_X87_DOUBLES + + // X87 stack doesn't differentiate between float/double + // so it doesn't need a cast, but everybody else does + // Just double check it is at least a FP type + noway_assert(varTypeIsFloating(op1)); + +#else // FEATURE_X87_DOUBLES + + if (op1->TypeGet() != callType) + { + op1 = gtNewCastNode(callType, op1, callType); + } + +#endif // FEATURE_X87_DOUBLES + + op1 = new (this, GT_INTRINSIC) + GenTreeIntrinsic(genActualType(callType), op1, intrinsicID, method); + break; + + case 2: + op2 = impPopStack().val; + op1 = impPopStack().val; + +#if FEATURE_X87_DOUBLES + + // X87 stack doesn't differentiate between float/double + // so it doesn't need a cast, but everybody else does + // Just double check it is at least a FP type + noway_assert(varTypeIsFloating(op2)); + noway_assert(varTypeIsFloating(op1)); + +#else // FEATURE_X87_DOUBLES + + if (op2->TypeGet() != callType) + { + op2 = gtNewCastNode(callType, op2, callType); + } + if (op1->TypeGet() != callType) + { + op1 = gtNewCastNode(callType, op1, callType); + } + +#endif // FEATURE_X87_DOUBLES + + op1 = new (this, GT_INTRINSIC) + GenTreeIntrinsic(genActualType(callType), op1, op2, intrinsicID, method); + break; + + default: + NO_WAY("Unsupported number of args for Math Instrinsic"); + } + +#ifndef LEGACY_BACKEND + if (IsIntrinsicImplementedByUserCall(intrinsicID)) + { + op1->gtFlags |= GTF_CALL; + } +#endif + } + + retNode = op1; + break; + +#ifdef _TARGET_XARCH_ + // TODO-ARM-CQ: reenable treating Interlocked operation as intrinsic + case CORINFO_INTRINSIC_InterlockedAdd32: + interlockedOperator = GT_LOCKADD; + goto InterlockedBinOpCommon; + case CORINFO_INTRINSIC_InterlockedXAdd32: + interlockedOperator = GT_XADD; + goto InterlockedBinOpCommon; + case CORINFO_INTRINSIC_InterlockedXchg32: + interlockedOperator = GT_XCHG; + goto InterlockedBinOpCommon; + +#ifdef _TARGET_AMD64_ + case CORINFO_INTRINSIC_InterlockedAdd64: + interlockedOperator = GT_LOCKADD; + goto InterlockedBinOpCommon; + case CORINFO_INTRINSIC_InterlockedXAdd64: + interlockedOperator = GT_XADD; + goto InterlockedBinOpCommon; + case CORINFO_INTRINSIC_InterlockedXchg64: + interlockedOperator = GT_XCHG; + goto InterlockedBinOpCommon; +#endif // _TARGET_AMD64_ + + InterlockedBinOpCommon: + assert(callType != TYP_STRUCT); + assert(sig->numArgs == 2); + + op2 = impPopStack().val; + op1 = impPopStack().val; + + // This creates: + // val + // XAdd + // addr + // field (for example) + // + // In the case where the first argument is the address of a local, we might + // want to make this *not* make the var address-taken -- but atomic instructions + // on a local are probably pretty useless anyway, so we probably don't care. + + op1 = gtNewOperNode(interlockedOperator, genActualType(callType), op1, op2); + op1->gtFlags |= GTF_GLOB_EFFECT; + retNode = op1; + break; +#endif // _TARGET_XARCH_ + + case CORINFO_INTRINSIC_MemoryBarrier: + + assert(sig->numArgs == 0); + + op1 = new (this, GT_MEMORYBARRIER) GenTree(GT_MEMORYBARRIER, TYP_VOID); + op1->gtFlags |= GTF_GLOB_EFFECT; + retNode = op1; + break; + +#ifdef _TARGET_XARCH_ + // TODO-ARM-CQ: reenable treating InterlockedCmpXchg32 operation as intrinsic + case CORINFO_INTRINSIC_InterlockedCmpXchg32: +#ifdef _TARGET_AMD64_ + case CORINFO_INTRINSIC_InterlockedCmpXchg64: +#endif + { + assert(callType != TYP_STRUCT); + assert(sig->numArgs == 3); + GenTreePtr op3; + + op3 = impPopStack().val; // comparand + op2 = impPopStack().val; // value + op1 = impPopStack().val; // location + + GenTreePtr node = new (this, GT_CMPXCHG) GenTreeCmpXchg(genActualType(callType), op1, op2, op3); + + node->gtCmpXchg.gtOpLocation->gtFlags |= GTF_DONT_CSE; + retNode = node; + break; + } +#endif + + case CORINFO_INTRINSIC_StringLength: + op1 = impPopStack().val; + if (!opts.MinOpts() && !opts.compDbgCode) + { + GenTreeArrLen* arrLen = + new (this, GT_ARR_LENGTH) GenTreeArrLen(TYP_INT, op1, offsetof(CORINFO_String, stringLen)); + op1 = arrLen; + } + else + { + /* Create the expression "*(str_addr + stringLengthOffset)" */ + op1 = gtNewOperNode(GT_ADD, TYP_BYREF, op1, + gtNewIconNode(offsetof(CORINFO_String, stringLen), TYP_I_IMPL)); + op1 = gtNewOperNode(GT_IND, TYP_INT, op1); + } + retNode = op1; + break; + + case CORINFO_INTRINSIC_StringGetChar: + op2 = impPopStack().val; + op1 = impPopStack().val; + op1 = gtNewIndexRef(TYP_CHAR, op1, op2); + op1->gtFlags |= GTF_INX_STRING_LAYOUT; + retNode = op1; + break; + + case CORINFO_INTRINSIC_InitializeArray: + retNode = impInitializeArrayIntrinsic(sig); + break; + + case CORINFO_INTRINSIC_Array_Address: + case CORINFO_INTRINSIC_Array_Get: + case CORINFO_INTRINSIC_Array_Set: + retNode = impArrayAccessIntrinsic(clsHnd, sig, memberRef, readonlyCall, intrinsicID); + break; + + case CORINFO_INTRINSIC_GetTypeFromHandle: + op1 = impStackTop(0).val; + if (op1->gtOper == GT_CALL && (op1->gtCall.gtCallType == CT_HELPER) && + gtIsTypeHandleToRuntimeTypeHelper(op1)) + { + op1 = impPopStack().val; + // Change call to return RuntimeType directly. + op1->gtType = TYP_REF; + retNode = op1; + } + // Call the regular function. + break; + + case CORINFO_INTRINSIC_RTH_GetValueInternal: + op1 = impStackTop(0).val; + if (op1->gtOper == GT_CALL && (op1->gtCall.gtCallType == CT_HELPER) && + gtIsTypeHandleToRuntimeTypeHelper(op1)) + { + // Old tree + // Helper-RuntimeTypeHandle -> TreeToGetNativeTypeHandle + // + // New tree + // TreeToGetNativeTypeHandle + + // Remove call to helper and return the native TypeHandle pointer that was the parameter + // to that helper. + + op1 = impPopStack().val; + + // Get native TypeHandle argument to old helper + op1 = op1->gtCall.gtCallArgs; + assert(op1->IsList()); + assert(op1->gtOp.gtOp2 == nullptr); + op1 = op1->gtOp.gtOp1; + retNode = op1; + } + // Call the regular function. + break; + +#ifndef LEGACY_BACKEND + case CORINFO_INTRINSIC_Object_GetType: + + op1 = impPopStack().val; + op1 = new (this, GT_INTRINSIC) GenTreeIntrinsic(genActualType(callType), op1, intrinsicID, method); + + // Set the CALL flag to indicate that the operator is implemented by a call. + // Set also the EXCEPTION flag because the native implementation of + // CORINFO_INTRINSIC_Object_GetType intrinsic can throw NullReferenceException. + op1->gtFlags |= (GTF_CALL | GTF_EXCEPT); + retNode = op1; + break; +#endif + + default: + /* Unknown intrinsic */ + break; + } + + if (mustExpand) + { + if (retNode == nullptr) + { + NO_WAY("JIT must expand the intrinsic!"); + } + } + + return retNode; +} + +/*****************************************************************************/ + +GenTreePtr Compiler::impArrayAccessIntrinsic( + CORINFO_CLASS_HANDLE clsHnd, CORINFO_SIG_INFO* sig, int memberRef, bool readonlyCall, CorInfoIntrinsics intrinsicID) +{ + /* If we are generating SMALL_CODE, we don't want to use intrinsics for + the following, as it generates fatter code. + */ + + if (compCodeOpt() == SMALL_CODE) + { + return nullptr; + } + + /* These intrinsics generate fatter (but faster) code and are only + done if we don't need SMALL_CODE */ + + unsigned rank = (intrinsicID == CORINFO_INTRINSIC_Array_Set) ? (sig->numArgs - 1) : sig->numArgs; + + // The rank 1 case is special because it has to handle two array formats + // we will simply not do that case + if (rank > GT_ARR_MAX_RANK || rank <= 1) + { + return nullptr; + } + + CORINFO_CLASS_HANDLE arrElemClsHnd = nullptr; + var_types elemType = JITtype2varType(info.compCompHnd->getChildType(clsHnd, &arrElemClsHnd)); + + // For the ref case, we will only be able to inline if the types match + // (verifier checks for this, we don't care for the nonverified case and the + // type is final (so we don't need to do the cast) + if ((intrinsicID != CORINFO_INTRINSIC_Array_Get) && !readonlyCall && varTypeIsGC(elemType)) + { + // Get the call site signature + CORINFO_SIG_INFO LocalSig; + eeGetCallSiteSig(memberRef, info.compScopeHnd, impTokenLookupContextHandle, &LocalSig); + assert(LocalSig.hasThis()); + + CORINFO_CLASS_HANDLE actualElemClsHnd; + + if (intrinsicID == CORINFO_INTRINSIC_Array_Set) + { + // Fetch the last argument, the one that indicates the type we are setting. + CORINFO_ARG_LIST_HANDLE argType = LocalSig.args; + for (unsigned r = 0; r < rank; r++) + { + argType = info.compCompHnd->getArgNext(argType); + } + + typeInfo argInfo = verParseArgSigToTypeInfo(&LocalSig, argType); + actualElemClsHnd = argInfo.GetClassHandle(); + } + else + { + assert(intrinsicID == CORINFO_INTRINSIC_Array_Address); + + // Fetch the return type + typeInfo retInfo = verMakeTypeInfo(LocalSig.retType, LocalSig.retTypeClass); + assert(retInfo.IsByRef()); + actualElemClsHnd = retInfo.GetClassHandle(); + } + + // if it's not final, we can't do the optimization + if (!(info.compCompHnd->getClassAttribs(actualElemClsHnd) & CORINFO_FLG_FINAL)) + { + return nullptr; + } + } + + unsigned arrayElemSize; + if (elemType == TYP_STRUCT) + { + assert(arrElemClsHnd); + + arrayElemSize = info.compCompHnd->getClassSize(arrElemClsHnd); + } + else + { + arrayElemSize = genTypeSize(elemType); + } + + if ((unsigned char)arrayElemSize != arrayElemSize) + { + // arrayElemSize would be truncated as an unsigned char. + // This means the array element is too large. Don't do the optimization. + return nullptr; + } + + GenTreePtr val = nullptr; + + if (intrinsicID == CORINFO_INTRINSIC_Array_Set) + { + // Assignment of a struct is more work, and there are more gets than sets. + if (elemType == TYP_STRUCT) + { + return nullptr; + } + + val = impPopStack().val; + assert(genActualType(elemType) == genActualType(val->gtType) || + (elemType == TYP_FLOAT && val->gtType == TYP_DOUBLE) || + (elemType == TYP_INT && val->gtType == TYP_BYREF) || + (elemType == TYP_DOUBLE && val->gtType == TYP_FLOAT)); + } + + noway_assert((unsigned char)GT_ARR_MAX_RANK == GT_ARR_MAX_RANK); + + GenTreePtr inds[GT_ARR_MAX_RANK]; + for (unsigned k = rank; k > 0; k--) + { + inds[k - 1] = impPopStack().val; + } + + GenTreePtr arr = impPopStack().val; + assert(arr->gtType == TYP_REF); + + GenTreePtr arrElem = + new (this, GT_ARR_ELEM) GenTreeArrElem(TYP_BYREF, arr, static_cast<unsigned char>(rank), + static_cast<unsigned char>(arrayElemSize), elemType, &inds[0]); + + if (intrinsicID != CORINFO_INTRINSIC_Array_Address) + { + arrElem = gtNewOperNode(GT_IND, elemType, arrElem); + } + + if (intrinsicID == CORINFO_INTRINSIC_Array_Set) + { + assert(val != nullptr); + return gtNewAssignNode(arrElem, val); + } + else + { + return arrElem; + } +} + +BOOL Compiler::verMergeEntryStates(BasicBlock* block, bool* changed) +{ + unsigned i; + + // do some basic checks first + if (block->bbStackDepthOnEntry() != verCurrentState.esStackDepth) + { + return FALSE; + } + + if (verCurrentState.esStackDepth > 0) + { + // merge stack types + StackEntry* parentStack = block->bbStackOnEntry(); + StackEntry* childStack = verCurrentState.esStack; + + for (i = 0; i < verCurrentState.esStackDepth; i++, parentStack++, childStack++) + { + if (tiMergeToCommonParent(&parentStack->seTypeInfo, &childStack->seTypeInfo, changed) == FALSE) + { + return FALSE; + } + } + } + + // merge initialization status of this ptr + + if (verTrackObjCtorInitState) + { + // If we're tracking the CtorInitState, then it must not be unknown in the current state. + assert(verCurrentState.thisInitialized != TIS_Bottom); + + // If the successor block's thisInit state is unknown, copy it from the current state. + if (block->bbThisOnEntry() == TIS_Bottom) + { + *changed = true; + verSetThisInit(block, verCurrentState.thisInitialized); + } + else if (verCurrentState.thisInitialized != block->bbThisOnEntry()) + { + if (block->bbThisOnEntry() != TIS_Top) + { + *changed = true; + verSetThisInit(block, TIS_Top); + + if (block->bbFlags & BBF_FAILED_VERIFICATION) + { + // The block is bad. Control can flow through the block to any handler that catches the + // verification exception, but the importer ignores bad blocks and therefore won't model + // this flow in the normal way. To complete the merge into the bad block, the new state + // needs to be manually pushed to the handlers that may be reached after the verification + // exception occurs. + // + // Usually, the new state was already propagated to the relevant handlers while processing + // the predecessors of the bad block. The exception is when the bad block is at the start + // of a try region, meaning it is protected by additional handlers that do not protect its + // predecessors. + // + if (block->hasTryIndex() && ((block->bbFlags & BBF_TRY_BEG) != 0)) + { + // Push TIS_Top to the handlers that protect the bad block. Note that this can cause + // recursive calls back into this code path (if successors of the current bad block are + // also bad blocks). + // + ThisInitState origTIS = verCurrentState.thisInitialized; + verCurrentState.thisInitialized = TIS_Top; + impVerifyEHBlock(block, true); + verCurrentState.thisInitialized = origTIS; + } + } + } + } + } + else + { + assert(verCurrentState.thisInitialized == TIS_Bottom && block->bbThisOnEntry() == TIS_Bottom); + } + + return TRUE; +} + +/***************************************************************************** + * 'logMsg' is true if a log message needs to be logged. false if the caller has + * already logged it (presumably in a more detailed fashion than done here) + * 'bVerificationException' is true for a verification exception, false for a + * "call unauthorized by host" exception. + */ + +void Compiler::verConvertBBToThrowVerificationException(BasicBlock* block DEBUGARG(bool logMsg)) +{ + block->bbJumpKind = BBJ_THROW; + block->bbFlags |= BBF_FAILED_VERIFICATION; + + impCurStmtOffsSet(block->bbCodeOffs); + +#ifdef DEBUG + // we need this since BeginTreeList asserts otherwise + impTreeList = impTreeLast = nullptr; + block->bbFlags &= ~BBF_IMPORTED; + + if (logMsg) + { + JITLOG((LL_ERROR, "Verification failure: while compiling %s near IL offset %x..%xh \n", info.compFullName, + block->bbCodeOffs, block->bbCodeOffsEnd)); + if (verbose) + { + printf("\n\nVerification failure: %s near IL %xh \n", info.compFullName, block->bbCodeOffs); + } + } + + if (JitConfig.DebugBreakOnVerificationFailure()) + { + DebugBreak(); + } +#endif + + impBeginTreeList(); + + // if the stack is non-empty evaluate all the side-effects + if (verCurrentState.esStackDepth > 0) + { + impEvalSideEffects(); + } + assert(verCurrentState.esStackDepth == 0); + + GenTreePtr op1 = gtNewHelperCallNode(CORINFO_HELP_VERIFICATION, TYP_VOID, GTF_EXCEPT, + gtNewArgList(gtNewIconNode(block->bbCodeOffs))); + // verCurrentState.esStackDepth = 0; + impAppendTree(op1, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); + + // The inliner is not able to handle methods that require throw block, so + // make sure this methods never gets inlined. + info.compCompHnd->setMethodAttribs(info.compMethodHnd, CORINFO_FLG_BAD_INLINEE); +} + +/***************************************************************************** + * + */ +void Compiler::verHandleVerificationFailure(BasicBlock* block DEBUGARG(bool logMsg)) + +{ + // In AMD64, for historical reasons involving design limitations of JIT64, the VM has a + // slightly different mechanism in which it calls the JIT to perform IL verification: + // in the case of transparent methods the VM calls for a predicate IsVerifiable() + // that consists of calling the JIT with the IMPORT_ONLY flag and with the IL verify flag on. + // If the JIT determines the method is not verifiable, it should raise the exception to the VM and let + // it bubble up until reported by the runtime. Currently in RyuJIT, this method doesn't bubble + // up the exception, instead it embeds a throw inside the offending basic block and lets this + // to fail upon runtime of the jitted method. + // + // For AMD64 we don't want this behavior when the JIT has been called only for verification (i.e. + // with the IMPORT_ONLY and IL Verification flag set) because this won't actually generate code, + // just try to find out whether to fail this method before even actually jitting it. So, in case + // we detect these two conditions, instead of generating a throw statement inside the offending + // basic block, we immediately fail to JIT and notify the VM to make the IsVerifiable() predicate + // to return false and make RyuJIT behave the same way JIT64 does. + // + // The rationale behind this workaround is to avoid modifying the VM and maintain compatibility between JIT64 and + // RyuJIT for the time being until we completely replace JIT64. + // TODO-ARM64-Cleanup: We probably want to actually modify the VM in the future to avoid the unnecesary two passes. + + // In AMD64 we must make sure we're behaving the same way as JIT64, meaning we should only raise the verification + // exception if we are only importing and verifying. The method verNeedsVerification() can also modify the + // tiVerificationNeeded flag in the case it determines it can 'skip verification' during importation and defer it + // to a runtime check. That's why we must assert one or the other (since the flag tiVerificationNeeded can + // be turned off during importation). + CLANG_FORMAT_COMMENT_ANCHOR; + +#ifdef _TARGET_64BIT_ + +#ifdef DEBUG + bool canSkipVerificationResult = + info.compCompHnd->canSkipMethodVerification(info.compMethodHnd) != CORINFO_VERIFICATION_CANNOT_SKIP; + assert(tiVerificationNeeded || canSkipVerificationResult); +#endif // DEBUG + + // Add the non verifiable flag to the compiler + if ((opts.eeFlags & CORJIT_FLG_IMPORT_ONLY) != 0) + { + tiIsVerifiableCode = FALSE; + } +#endif //_TARGET_64BIT_ + verResetCurrentState(block, &verCurrentState); + verConvertBBToThrowVerificationException(block DEBUGARG(logMsg)); + +#ifdef DEBUG + impNoteLastILoffs(); // Remember at which BC offset the tree was finished +#endif // DEBUG +} + +/******************************************************************************/ +typeInfo Compiler::verMakeTypeInfo(CorInfoType ciType, CORINFO_CLASS_HANDLE clsHnd) +{ + assert(ciType < CORINFO_TYPE_COUNT); + + typeInfo tiResult; + switch (ciType) + { + case CORINFO_TYPE_STRING: + case CORINFO_TYPE_CLASS: + tiResult = verMakeTypeInfo(clsHnd); + if (!tiResult.IsType(TI_REF)) + { // type must be consistent with element type + return typeInfo(); + } + break; + +#ifdef _TARGET_64BIT_ + case CORINFO_TYPE_NATIVEINT: + case CORINFO_TYPE_NATIVEUINT: + if (clsHnd) + { + // If we have more precise information, use it + return verMakeTypeInfo(clsHnd); + } + else + { + return typeInfo::nativeInt(); + } + break; +#endif // _TARGET_64BIT_ + + case CORINFO_TYPE_VALUECLASS: + case CORINFO_TYPE_REFANY: + tiResult = verMakeTypeInfo(clsHnd); + // type must be constant with element type; + if (!tiResult.IsValueClass()) + { + return typeInfo(); + } + break; + case CORINFO_TYPE_VAR: + return verMakeTypeInfo(clsHnd); + + case CORINFO_TYPE_PTR: // for now, pointers are treated as an error + case CORINFO_TYPE_VOID: + return typeInfo(); + break; + + case CORINFO_TYPE_BYREF: + { + CORINFO_CLASS_HANDLE childClassHandle; + CorInfoType childType = info.compCompHnd->getChildType(clsHnd, &childClassHandle); + return ByRef(verMakeTypeInfo(childType, childClassHandle)); + } + break; + + default: + if (clsHnd) + { // If we have more precise information, use it + return typeInfo(TI_STRUCT, clsHnd); + } + else + { + return typeInfo(JITtype2tiType(ciType)); + } + } + return tiResult; +} + +/******************************************************************************/ + +typeInfo Compiler::verMakeTypeInfo(CORINFO_CLASS_HANDLE clsHnd, bool bashStructToRef /* = false */) +{ + if (clsHnd == nullptr) + { + return typeInfo(); + } + + // Byrefs should only occur in method and local signatures, which are accessed + // using ICorClassInfo and ICorClassInfo.getChildType. + // So findClass() and getClassAttribs() should not be called for byrefs + + if (JITtype2varType(info.compCompHnd->asCorInfoType(clsHnd)) == TYP_BYREF) + { + assert(!"Did findClass() return a Byref?"); + return typeInfo(); + } + + unsigned attribs = info.compCompHnd->getClassAttribs(clsHnd); + + if (attribs & CORINFO_FLG_VALUECLASS) + { + CorInfoType t = info.compCompHnd->getTypeForPrimitiveValueClass(clsHnd); + + // Meta-data validation should ensure that CORINF_TYPE_BYREF should + // not occur here, so we may want to change this to an assert instead. + if (t == CORINFO_TYPE_VOID || t == CORINFO_TYPE_BYREF || t == CORINFO_TYPE_PTR) + { + return typeInfo(); + } + +#ifdef _TARGET_64BIT_ + if (t == CORINFO_TYPE_NATIVEINT || t == CORINFO_TYPE_NATIVEUINT) + { + return typeInfo::nativeInt(); + } +#endif // _TARGET_64BIT_ + + if (t != CORINFO_TYPE_UNDEF) + { + return (typeInfo(JITtype2tiType(t))); + } + else if (bashStructToRef) + { + return (typeInfo(TI_REF, clsHnd)); + } + else + { + return (typeInfo(TI_STRUCT, clsHnd)); + } + } + else if (attribs & CORINFO_FLG_GENERIC_TYPE_VARIABLE) + { + // See comment in _typeInfo.h for why we do it this way. + return (typeInfo(TI_REF, clsHnd, true)); + } + else + { + return (typeInfo(TI_REF, clsHnd)); + } +} + +/******************************************************************************/ +BOOL Compiler::verIsSDArray(typeInfo ti) +{ + if (ti.IsNullObjRef()) + { // nulls are SD arrays + return TRUE; + } + + if (!ti.IsType(TI_REF)) + { + return FALSE; + } + + if (!info.compCompHnd->isSDArray(ti.GetClassHandleForObjRef())) + { + return FALSE; + } + return TRUE; +} + +/******************************************************************************/ +/* Given 'arrayObjectType' which is an array type, fetch the element type. */ +/* Returns an error type if anything goes wrong */ + +typeInfo Compiler::verGetArrayElemType(typeInfo arrayObjectType) +{ + assert(!arrayObjectType.IsNullObjRef()); // you need to check for null explictly since that is a success case + + if (!verIsSDArray(arrayObjectType)) + { + return typeInfo(); + } + + CORINFO_CLASS_HANDLE childClassHandle = nullptr; + CorInfoType ciType = info.compCompHnd->getChildType(arrayObjectType.GetClassHandleForObjRef(), &childClassHandle); + + return verMakeTypeInfo(ciType, childClassHandle); +} + +/***************************************************************************** + */ +typeInfo Compiler::verParseArgSigToTypeInfo(CORINFO_SIG_INFO* sig, CORINFO_ARG_LIST_HANDLE args) +{ + CORINFO_CLASS_HANDLE classHandle; + CorInfoType ciType = strip(info.compCompHnd->getArgType(sig, args, &classHandle)); + + var_types type = JITtype2varType(ciType); + if (varTypeIsGC(type)) + { + // For efficiency, getArgType only returns something in classHandle for + // value types. For other types that have addition type info, you + // have to call back explicitly + classHandle = info.compCompHnd->getArgClass(sig, args); + if (!classHandle) + { + NO_WAY("Could not figure out Class specified in argument or local signature"); + } + } + + return verMakeTypeInfo(ciType, classHandle); +} + +/*****************************************************************************/ + +// This does the expensive check to figure out whether the method +// needs to be verified. It is called only when we fail verification, +// just before throwing the verification exception. + +BOOL Compiler::verNeedsVerification() +{ + // If we have previously determined that verification is NOT needed + // (for example in Compiler::compCompile), that means verification is really not needed. + // Return the same decision we made before. + // (Note: This literally means that tiVerificationNeeded can never go from 0 to 1.) + + if (!tiVerificationNeeded) + { + return tiVerificationNeeded; + } + + assert(tiVerificationNeeded); + + // Ok, we haven't concluded that verification is NOT needed. Consult the EE now to + // obtain the answer. + CorInfoCanSkipVerificationResult canSkipVerificationResult = + info.compCompHnd->canSkipMethodVerification(info.compMethodHnd); + + // canSkipVerification will return one of the following three values: + // CORINFO_VERIFICATION_CANNOT_SKIP = 0, // Cannot skip verification during jit time. + // CORINFO_VERIFICATION_CAN_SKIP = 1, // Can skip verification during jit time. + // CORINFO_VERIFICATION_RUNTIME_CHECK = 2, // Skip verification during jit time, + // but need to insert a callout to the VM to ask during runtime + // whether to skip verification or not. + + // Set tiRuntimeCalloutNeeded if canSkipVerification() instructs us to insert a callout for runtime check + if (canSkipVerificationResult == CORINFO_VERIFICATION_RUNTIME_CHECK) + { + tiRuntimeCalloutNeeded = true; + } + + if (canSkipVerificationResult == CORINFO_VERIFICATION_DONT_JIT) + { + // Dev10 706080 - Testers don't like the assert, so just silence it + // by not using the macros that invoke debugAssert. + badCode(); + } + + // When tiVerificationNeeded is true, JIT will do the verification during JIT time. + // The following line means we will NOT do jit time verification if canSkipVerification + // returns CORINFO_VERIFICATION_CAN_SKIP or CORINFO_VERIFICATION_RUNTIME_CHECK. + tiVerificationNeeded = (canSkipVerificationResult == CORINFO_VERIFICATION_CANNOT_SKIP); + return tiVerificationNeeded; +} + +BOOL Compiler::verIsByRefLike(const typeInfo& ti) +{ + if (ti.IsByRef()) + { + return TRUE; + } + if (!ti.IsType(TI_STRUCT)) + { + return FALSE; + } + return info.compCompHnd->getClassAttribs(ti.GetClassHandleForValueClass()) & CORINFO_FLG_CONTAINS_STACK_PTR; +} + +BOOL Compiler::verIsSafeToReturnByRef(const typeInfo& ti) +{ + if (ti.IsPermanentHomeByRef()) + { + return TRUE; + } + else + { + return FALSE; + } +} + +BOOL Compiler::verIsBoxable(const typeInfo& ti) +{ + return (ti.IsPrimitiveType() || ti.IsObjRef() // includes boxed generic type variables + || ti.IsUnboxedGenericTypeVar() || + (ti.IsType(TI_STRUCT) && + // exclude byreflike structs + !(info.compCompHnd->getClassAttribs(ti.GetClassHandleForValueClass()) & CORINFO_FLG_CONTAINS_STACK_PTR))); +} + +// Is it a boxed value type? +bool Compiler::verIsBoxedValueType(typeInfo ti) +{ + if (ti.GetType() == TI_REF) + { + CORINFO_CLASS_HANDLE clsHnd = ti.GetClassHandleForObjRef(); + return !!eeIsValueClass(clsHnd); + } + else + { + return false; + } +} + +/***************************************************************************** + * + * Check if a TailCall is legal. + */ + +bool Compiler::verCheckTailCallConstraint( + OPCODE opcode, + CORINFO_RESOLVED_TOKEN* pResolvedToken, + CORINFO_RESOLVED_TOKEN* pConstrainedResolvedToken, // Is this a "constrained." call on a type parameter? + bool speculative // If true, won't throw if verificatoin fails. Instead it will + // return false to the caller. + // If false, it will throw. + ) +{ + DWORD mflags; + CORINFO_SIG_INFO sig; + unsigned int popCount = 0; // we can't pop the stack since impImportCall needs it, so + // this counter is used to keep track of how many items have been + // virtually popped + + CORINFO_METHOD_HANDLE methodHnd = nullptr; + CORINFO_CLASS_HANDLE methodClassHnd = nullptr; + unsigned methodClassFlgs = 0; + + assert(impOpcodeIsCallOpcode(opcode)); + + if (compIsForInlining()) + { + return false; + } + + // for calli, VerifyOrReturn that this is not a virtual method + if (opcode == CEE_CALLI) + { + /* Get the call sig */ + eeGetSig(pResolvedToken->token, info.compScopeHnd, impTokenLookupContextHandle, &sig); + + // We don't know the target method, so we have to infer the flags, or + // assume the worst-case. + mflags = (sig.callConv & CORINFO_CALLCONV_HASTHIS) ? 0 : CORINFO_FLG_STATIC; + } + else + { + methodHnd = pResolvedToken->hMethod; + + mflags = info.compCompHnd->getMethodAttribs(methodHnd); + + // When verifying generic code we pair the method handle with its + // owning class to get the exact method signature. + methodClassHnd = pResolvedToken->hClass; + assert(methodClassHnd); + + eeGetMethodSig(methodHnd, &sig, methodClassHnd); + + // opcode specific check + methodClassFlgs = info.compCompHnd->getClassAttribs(methodClassHnd); + } + + // We must have got the methodClassHnd if opcode is not CEE_CALLI + assert((methodHnd != nullptr && methodClassHnd != nullptr) || opcode == CEE_CALLI); + + if ((sig.callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_VARARG) + { + eeGetCallSiteSig(pResolvedToken->token, info.compScopeHnd, impTokenLookupContextHandle, &sig); + } + + // check compatibility of the arguments + unsigned int argCount; + argCount = sig.numArgs; + CORINFO_ARG_LIST_HANDLE args; + args = sig.args; + while (argCount--) + { + typeInfo tiDeclared = verParseArgSigToTypeInfo(&sig, args).NormaliseForStack(); + + // check that the argument is not a byref for tailcalls + VerifyOrReturnSpeculative(!verIsByRefLike(tiDeclared), "tailcall on byrefs", speculative); + + // For unsafe code, we might have parameters containing pointer to the stack location. + // Disallow the tailcall for this kind. + CORINFO_CLASS_HANDLE classHandle; + CorInfoType ciType = strip(info.compCompHnd->getArgType(&sig, args, &classHandle)); + VerifyOrReturnSpeculative(ciType != CORINFO_TYPE_PTR, "tailcall on CORINFO_TYPE_PTR", speculative); + + args = info.compCompHnd->getArgNext(args); + } + + // update popCount + popCount += sig.numArgs; + + // check for 'this' which is on non-static methods, not called via NEWOBJ + if (!(mflags & CORINFO_FLG_STATIC)) + { + // Always update the popCount. + // This is crucial for the stack calculation to be correct. + typeInfo tiThis = impStackTop(popCount).seTypeInfo; + popCount++; + + if (opcode == CEE_CALLI) + { + // For CALLI, we don't know the methodClassHnd. Therefore, let's check the "this" object + // on the stack. + if (tiThis.IsValueClass()) + { + tiThis.MakeByRef(); + } + VerifyOrReturnSpeculative(!verIsByRefLike(tiThis), "byref in tailcall", speculative); + } + else + { + // Check type compatibility of the this argument + typeInfo tiDeclaredThis = verMakeTypeInfo(methodClassHnd); + if (tiDeclaredThis.IsValueClass()) + { + tiDeclaredThis.MakeByRef(); + } + + VerifyOrReturnSpeculative(!verIsByRefLike(tiDeclaredThis), "byref in tailcall", speculative); + } + } + + // Tail calls on constrained calls should be illegal too: + // when instantiated at a value type, a constrained call may pass the address of a stack allocated value + VerifyOrReturnSpeculative(!pConstrainedResolvedToken, "byref in constrained tailcall", speculative); + + // Get the exact view of the signature for an array method + if (sig.retType != CORINFO_TYPE_VOID) + { + if (methodClassFlgs & CORINFO_FLG_ARRAY) + { + assert(opcode != CEE_CALLI); + eeGetCallSiteSig(pResolvedToken->token, info.compScopeHnd, impTokenLookupContextHandle, &sig); + } + } + + typeInfo tiCalleeRetType = verMakeTypeInfo(sig.retType, sig.retTypeClass); + typeInfo tiCallerRetType = + verMakeTypeInfo(info.compMethodInfo->args.retType, info.compMethodInfo->args.retTypeClass); + + // void return type gets morphed into the error type, so we have to treat them specially here + if (sig.retType == CORINFO_TYPE_VOID) + { + VerifyOrReturnSpeculative(info.compMethodInfo->args.retType == CORINFO_TYPE_VOID, "tailcall return mismatch", + speculative); + } + else + { + VerifyOrReturnSpeculative(tiCompatibleWith(NormaliseForStack(tiCalleeRetType), + NormaliseForStack(tiCallerRetType), true), + "tailcall return mismatch", speculative); + } + + // for tailcall, stack must be empty + VerifyOrReturnSpeculative(verCurrentState.esStackDepth == popCount, "stack non-empty on tailcall", speculative); + + return true; // Yes, tailcall is legal +} + +/***************************************************************************** + * + * Checks the IL verification rules for the call + */ + +void Compiler::verVerifyCall(OPCODE opcode, + CORINFO_RESOLVED_TOKEN* pResolvedToken, + CORINFO_RESOLVED_TOKEN* pConstrainedResolvedToken, + bool tailCall, + bool readonlyCall, + const BYTE* delegateCreateStart, + const BYTE* codeAddr, + CORINFO_CALL_INFO* callInfo DEBUGARG(const char* methodName)) +{ + DWORD mflags; + CORINFO_SIG_INFO* sig = nullptr; + unsigned int popCount = 0; // we can't pop the stack since impImportCall needs it, so + // this counter is used to keep track of how many items have been + // virtually popped + + // for calli, VerifyOrReturn that this is not a virtual method + if (opcode == CEE_CALLI) + { + Verify(false, "Calli not verifiable"); + return; + } + + //<NICE> It would be nice to cache the rest of it, but eeFindMethod is the big ticket item. + mflags = callInfo->verMethodFlags; + + sig = &callInfo->verSig; + + if ((sig->callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_VARARG) + { + eeGetCallSiteSig(pResolvedToken->token, pResolvedToken->tokenScope, pResolvedToken->tokenContext, sig); + } + + // opcode specific check + unsigned methodClassFlgs = callInfo->classFlags; + switch (opcode) + { + case CEE_CALLVIRT: + // cannot do callvirt on valuetypes + VerifyOrReturn(!(methodClassFlgs & CORINFO_FLG_VALUECLASS), "callVirt on value class"); + VerifyOrReturn(sig->hasThis(), "CallVirt on static method"); + break; + + case CEE_NEWOBJ: + { + assert(!tailCall); // Importer should not allow this + VerifyOrReturn((mflags & CORINFO_FLG_CONSTRUCTOR) && !(mflags & CORINFO_FLG_STATIC), + "newobj must be on instance"); + + if (methodClassFlgs & CORINFO_FLG_DELEGATE) + { + VerifyOrReturn(sig->numArgs == 2, "wrong number args to delegate ctor"); + typeInfo tiDeclaredObj = verParseArgSigToTypeInfo(sig, sig->args).NormaliseForStack(); + typeInfo tiDeclaredFtn = + verParseArgSigToTypeInfo(sig, info.compCompHnd->getArgNext(sig->args)).NormaliseForStack(); + VerifyOrReturn(tiDeclaredFtn.IsNativeIntType(), "ftn arg needs to be a native int type"); + + assert(popCount == 0); + typeInfo tiActualObj = impStackTop(1).seTypeInfo; + typeInfo tiActualFtn = impStackTop(0).seTypeInfo; + + VerifyOrReturn(tiActualFtn.IsMethod(), "delegate needs method as first arg"); + VerifyOrReturn(tiCompatibleWith(tiActualObj, tiDeclaredObj, true), "delegate object type mismatch"); + VerifyOrReturn(tiActualObj.IsNullObjRef() || tiActualObj.IsType(TI_REF), + "delegate object type mismatch"); + + CORINFO_CLASS_HANDLE objTypeHandle = + tiActualObj.IsNullObjRef() ? nullptr : tiActualObj.GetClassHandleForObjRef(); + + // the method signature must be compatible with the delegate's invoke method + + // check that for virtual functions, the type of the object used to get the + // ftn ptr is the same as the type of the object passed to the delegate ctor. + // since this is a bit of work to determine in general, we pattern match stylized + // code sequences + + // the delegate creation code check, which used to be done later, is now done here + // so we can read delegateMethodRef directly from + // from the preceding LDFTN or CEE_LDVIRTFN instruction sequence; + // we then use it in our call to isCompatibleDelegate(). + + mdMemberRef delegateMethodRef = mdMemberRefNil; + VerifyOrReturn(verCheckDelegateCreation(delegateCreateStart, codeAddr, delegateMethodRef), + "must create delegates with certain IL"); + + CORINFO_RESOLVED_TOKEN delegateResolvedToken; + delegateResolvedToken.tokenContext = impTokenLookupContextHandle; + delegateResolvedToken.tokenScope = info.compScopeHnd; + delegateResolvedToken.token = delegateMethodRef; + delegateResolvedToken.tokenType = CORINFO_TOKENKIND_Method; + info.compCompHnd->resolveToken(&delegateResolvedToken); + + CORINFO_CALL_INFO delegateCallInfo; + eeGetCallInfo(&delegateResolvedToken, nullptr /* constraint typeRef */, + addVerifyFlag(CORINFO_CALLINFO_SECURITYCHECKS), &delegateCallInfo); + + BOOL isOpenDelegate = FALSE; + VerifyOrReturn(info.compCompHnd->isCompatibleDelegate(objTypeHandle, delegateResolvedToken.hClass, + tiActualFtn.GetMethod(), pResolvedToken->hClass, + &isOpenDelegate), + "function incompatible with delegate"); + + // check the constraints on the target method + VerifyOrReturn(info.compCompHnd->satisfiesClassConstraints(delegateResolvedToken.hClass), + "delegate target has unsatisfied class constraints"); + VerifyOrReturn(info.compCompHnd->satisfiesMethodConstraints(delegateResolvedToken.hClass, + tiActualFtn.GetMethod()), + "delegate target has unsatisfied method constraints"); + + // See ECMA spec section 1.8.1.5.2 (Delegating via instance dispatch) + // for additional verification rules for delegates + CORINFO_METHOD_HANDLE actualMethodHandle = tiActualFtn.GetMethod(); + DWORD actualMethodAttribs = info.compCompHnd->getMethodAttribs(actualMethodHandle); + if (impIsLDFTN_TOKEN(delegateCreateStart, codeAddr)) + { + + if ((actualMethodAttribs & CORINFO_FLG_VIRTUAL) && ((actualMethodAttribs & CORINFO_FLG_FINAL) == 0) +#ifdef DEBUG + && StrictCheckForNonVirtualCallToVirtualMethod() +#endif + ) + { + if (info.compCompHnd->shouldEnforceCallvirtRestriction(info.compScopeHnd)) + { + VerifyOrReturn(tiActualObj.IsThisPtr() && lvaIsOriginalThisReadOnly() || + verIsBoxedValueType(tiActualObj), + "The 'this' parameter to the call must be either the calling method's " + "'this' parameter or " + "a boxed value type."); + } + } + } + + if (actualMethodAttribs & CORINFO_FLG_PROTECTED) + { + BOOL targetIsStatic = actualMethodAttribs & CORINFO_FLG_STATIC; + + Verify(targetIsStatic || !isOpenDelegate, + "Unverifiable creation of an open instance delegate for a protected member."); + + CORINFO_CLASS_HANDLE instanceClassHnd = (tiActualObj.IsNullObjRef() || targetIsStatic) + ? info.compClassHnd + : tiActualObj.GetClassHandleForObjRef(); + + // In the case of protected methods, it is a requirement that the 'this' + // pointer be a subclass of the current context. Perform this check. + Verify(info.compCompHnd->canAccessFamily(info.compMethodHnd, instanceClassHnd), + "Accessing protected method through wrong type."); + } + goto DONE_ARGS; + } + } + // fall thru to default checks + default: + VerifyOrReturn(!(mflags & CORINFO_FLG_ABSTRACT), "method abstract"); + } + VerifyOrReturn(!((mflags & CORINFO_FLG_CONSTRUCTOR) && (methodClassFlgs & CORINFO_FLG_DELEGATE)), + "can only newobj a delegate constructor"); + + // check compatibility of the arguments + unsigned int argCount; + argCount = sig->numArgs; + CORINFO_ARG_LIST_HANDLE args; + args = sig->args; + while (argCount--) + { + typeInfo tiActual = impStackTop(popCount + argCount).seTypeInfo; + + typeInfo tiDeclared = verParseArgSigToTypeInfo(sig, args).NormaliseForStack(); + VerifyOrReturn(tiCompatibleWith(tiActual, tiDeclared, true), "type mismatch"); + + args = info.compCompHnd->getArgNext(args); + } + +DONE_ARGS: + + // update popCount + popCount += sig->numArgs; + + // check for 'this' which are is non-static methods, not called via NEWOBJ + CORINFO_CLASS_HANDLE instanceClassHnd = info.compClassHnd; + if (!(mflags & CORINFO_FLG_STATIC) && (opcode != CEE_NEWOBJ)) + { + typeInfo tiThis = impStackTop(popCount).seTypeInfo; + popCount++; + + // If it is null, we assume we can access it (since it will AV shortly) + // If it is anything but a reference class, there is no hierarchy, so + // again, we don't need the precise instance class to compute 'protected' access + if (tiThis.IsType(TI_REF)) + { + instanceClassHnd = tiThis.GetClassHandleForObjRef(); + } + + // Check type compatibility of the this argument + typeInfo tiDeclaredThis = verMakeTypeInfo(pResolvedToken->hClass); + if (tiDeclaredThis.IsValueClass()) + { + tiDeclaredThis.MakeByRef(); + } + + // If this is a call to the base class .ctor, set thisPtr Init for + // this block. + if (mflags & CORINFO_FLG_CONSTRUCTOR) + { + if (verTrackObjCtorInitState && tiThis.IsThisPtr() && + verIsCallToInitThisPtr(info.compClassHnd, pResolvedToken->hClass)) + { + assert(verCurrentState.thisInitialized != + TIS_Bottom); // This should never be the case just from the logic of the verifier. + VerifyOrReturn(verCurrentState.thisInitialized == TIS_Uninit, + "Call to base class constructor when 'this' is possibly initialized"); + // Otherwise, 'this' is now initialized. + verCurrentState.thisInitialized = TIS_Init; + tiThis.SetInitialisedObjRef(); + } + else + { + // We allow direct calls to value type constructors + // NB: we have to check that the contents of tiThis is a value type, otherwise we could use a + // constrained callvirt to illegally re-enter a .ctor on a value of reference type. + VerifyOrReturn(tiThis.IsByRef() && DereferenceByRef(tiThis).IsValueClass(), + "Bad call to a constructor"); + } + } + + if (pConstrainedResolvedToken != nullptr) + { + VerifyOrReturn(tiThis.IsByRef(), "non-byref this type in constrained call"); + + typeInfo tiConstraint = verMakeTypeInfo(pConstrainedResolvedToken->hClass); + + // We just dereference this and test for equality + tiThis.DereferenceByRef(); + VerifyOrReturn(typeInfo::AreEquivalent(tiThis, tiConstraint), + "this type mismatch with constrained type operand"); + + // Now pretend the this type is the boxed constrained type, for the sake of subsequent checks + tiThis = typeInfo(TI_REF, pConstrainedResolvedToken->hClass); + } + + // To support direct calls on readonly byrefs, just pretend tiDeclaredThis is readonly too + if (tiDeclaredThis.IsByRef() && tiThis.IsReadonlyByRef()) + { + tiDeclaredThis.SetIsReadonlyByRef(); + } + + VerifyOrReturn(tiCompatibleWith(tiThis, tiDeclaredThis, true), "this type mismatch"); + + if (tiThis.IsByRef()) + { + // Find the actual type where the method exists (as opposed to what is declared + // in the metadata). This is to prevent passing a byref as the "this" argument + // while calling methods like System.ValueType.GetHashCode() which expect boxed objects. + + CORINFO_CLASS_HANDLE actualClassHnd = info.compCompHnd->getMethodClass(pResolvedToken->hMethod); + VerifyOrReturn(eeIsValueClass(actualClassHnd), + "Call to base type of valuetype (which is never a valuetype)"); + } + + // Rules for non-virtual call to a non-final virtual method: + + // Define: + // The "this" pointer is considered to be "possibly written" if + // 1. Its address have been taken (LDARGA 0) anywhere in the method. + // (or) + // 2. It has been stored to (STARG.0) anywhere in the method. + + // A non-virtual call to a non-final virtual method is only allowed if + // 1. The this pointer passed to the callee is an instance of a boxed value type. + // (or) + // 2. The this pointer passed to the callee is the current method's this pointer. + // (and) The current method's this pointer is not "possibly written". + + // Thus the rule is that if you assign to this ANYWHERE you can't make "base" calls to + // virtual methods. (Luckily this does affect .ctors, since they are not virtual). + // This is stronger that is strictly needed, but implementing a laxer rule is significantly + // hard and more error prone. + + if (opcode == CEE_CALL && (mflags & CORINFO_FLG_VIRTUAL) && ((mflags & CORINFO_FLG_FINAL) == 0) +#ifdef DEBUG + && StrictCheckForNonVirtualCallToVirtualMethod() +#endif + ) + { + if (info.compCompHnd->shouldEnforceCallvirtRestriction(info.compScopeHnd)) + { + VerifyOrReturn( + tiThis.IsThisPtr() && lvaIsOriginalThisReadOnly() || verIsBoxedValueType(tiThis), + "The 'this' parameter to the call must be either the calling method's 'this' parameter or " + "a boxed value type."); + } + } + } + + // check any constraints on the callee's class and type parameters + VerifyOrReturn(info.compCompHnd->satisfiesClassConstraints(pResolvedToken->hClass), + "method has unsatisfied class constraints"); + VerifyOrReturn(info.compCompHnd->satisfiesMethodConstraints(pResolvedToken->hClass, pResolvedToken->hMethod), + "method has unsatisfied method constraints"); + + if (mflags & CORINFO_FLG_PROTECTED) + { + VerifyOrReturn(info.compCompHnd->canAccessFamily(info.compMethodHnd, instanceClassHnd), + "Can't access protected method"); + } + + // Get the exact view of the signature for an array method + if (sig->retType != CORINFO_TYPE_VOID) + { + eeGetMethodSig(pResolvedToken->hMethod, sig, pResolvedToken->hClass); + } + + // "readonly." prefixed calls only allowed for the Address operation on arrays. + // The methods supported by array types are under the control of the EE + // so we can trust that only the Address operation returns a byref. + if (readonlyCall) + { + typeInfo tiCalleeRetType = verMakeTypeInfo(sig->retType, sig->retTypeClass); + VerifyOrReturn((methodClassFlgs & CORINFO_FLG_ARRAY) && tiCalleeRetType.IsByRef(), + "unexpected use of readonly prefix"); + } + + // Verify the tailcall + if (tailCall) + { + verCheckTailCallConstraint(opcode, pResolvedToken, pConstrainedResolvedToken, false); + } +} + +/***************************************************************************** + * Checks that a delegate creation is done using the following pattern: + * dup + * ldvirtftn targetMemberRef + * OR + * ldftn targetMemberRef + * + * 'delegateCreateStart' points at the last dup or ldftn in this basic block (null if + * not in this basic block) + * + * targetMemberRef is read from the code sequence. + * targetMemberRef is validated iff verificationNeeded. + */ + +BOOL Compiler::verCheckDelegateCreation(const BYTE* delegateCreateStart, + const BYTE* codeAddr, + mdMemberRef& targetMemberRef) +{ + if (impIsLDFTN_TOKEN(delegateCreateStart, codeAddr)) + { + targetMemberRef = getU4LittleEndian(&delegateCreateStart[2]); + return TRUE; + } + else if (impIsDUP_LDVIRTFTN_TOKEN(delegateCreateStart, codeAddr)) + { + targetMemberRef = getU4LittleEndian(&delegateCreateStart[3]); + return TRUE; + } + + return FALSE; +} + +typeInfo Compiler::verVerifySTIND(const typeInfo& tiTo, const typeInfo& value, const typeInfo& instrType) +{ + Verify(!tiTo.IsReadonlyByRef(), "write to readonly byref"); + typeInfo ptrVal = verVerifyLDIND(tiTo, instrType); + typeInfo normPtrVal = typeInfo(ptrVal).NormaliseForStack(); + if (!tiCompatibleWith(value, normPtrVal, true)) + { + Verify(tiCompatibleWith(value, normPtrVal, true), "type mismatch"); + compUnsafeCastUsed = true; + } + return ptrVal; +} + +typeInfo Compiler::verVerifyLDIND(const typeInfo& ptr, const typeInfo& instrType) +{ + assert(!instrType.IsStruct()); + + typeInfo ptrVal; + if (ptr.IsByRef()) + { + ptrVal = DereferenceByRef(ptr); + if (instrType.IsObjRef() && !ptrVal.IsObjRef()) + { + Verify(false, "bad pointer"); + compUnsafeCastUsed = true; + } + else if (!instrType.IsObjRef() && !typeInfo::AreEquivalent(instrType, ptrVal)) + { + Verify(false, "pointer not consistent with instr"); + compUnsafeCastUsed = true; + } + } + else + { + Verify(false, "pointer not byref"); + compUnsafeCastUsed = true; + } + + return ptrVal; +} + +// Verify that the field is used properly. 'tiThis' is NULL for statics, +// 'fieldFlags' is the fields attributes, and mutator is TRUE if it is a +// ld*flda or a st*fld. +// 'enclosingClass' is given if we are accessing a field in some specific type. + +void Compiler::verVerifyField(CORINFO_RESOLVED_TOKEN* pResolvedToken, + const CORINFO_FIELD_INFO& fieldInfo, + const typeInfo* tiThis, + BOOL mutator, + BOOL allowPlainStructAsThis) +{ + CORINFO_CLASS_HANDLE enclosingClass = pResolvedToken->hClass; + unsigned fieldFlags = fieldInfo.fieldFlags; + CORINFO_CLASS_HANDLE instanceClass = + info.compClassHnd; // for statics, we imagine the instance is the current class. + + bool isStaticField = ((fieldFlags & CORINFO_FLG_FIELD_STATIC) != 0); + if (mutator) + { + Verify(!(fieldFlags & CORINFO_FLG_FIELD_UNMANAGED), "mutating an RVA bases static"); + if ((fieldFlags & CORINFO_FLG_FIELD_FINAL)) + { + Verify((info.compFlags & CORINFO_FLG_CONSTRUCTOR) && enclosingClass == info.compClassHnd && + info.compIsStatic == isStaticField, + "bad use of initonly field (set or address taken)"); + } + } + + if (tiThis == nullptr) + { + Verify(isStaticField, "used static opcode with non-static field"); + } + else + { + typeInfo tThis = *tiThis; + + if (allowPlainStructAsThis && tThis.IsValueClass()) + { + tThis.MakeByRef(); + } + + // If it is null, we assume we can access it (since it will AV shortly) + // If it is anything but a refernce class, there is no hierarchy, so + // again, we don't need the precise instance class to compute 'protected' access + if (tiThis->IsType(TI_REF)) + { + instanceClass = tiThis->GetClassHandleForObjRef(); + } + + // Note that even if the field is static, we require that the this pointer + // satisfy the same constraints as a non-static field This happens to + // be simpler and seems reasonable + typeInfo tiDeclaredThis = verMakeTypeInfo(enclosingClass); + if (tiDeclaredThis.IsValueClass()) + { + tiDeclaredThis.MakeByRef(); + + // we allow read-only tThis, on any field access (even stores!), because if the + // class implementor wants to prohibit stores he should make the field private. + // we do this by setting the read-only bit on the type we compare tThis to. + tiDeclaredThis.SetIsReadonlyByRef(); + } + else if (verTrackObjCtorInitState && tThis.IsThisPtr()) + { + // Any field access is legal on "uninitialized" this pointers. + // The easiest way to implement this is to simply set the + // initialized bit for the duration of the type check on the + // field access only. It does not change the state of the "this" + // for the function as a whole. Note that the "tThis" is a copy + // of the original "this" type (*tiThis) passed in. + tThis.SetInitialisedObjRef(); + } + + Verify(tiCompatibleWith(tThis, tiDeclaredThis, true), "this type mismatch"); + } + + // Presently the JIT does not check that we don't store or take the address of init-only fields + // since we cannot guarantee their immutability and it is not a security issue. + + // check any constraints on the fields's class --- accessing the field might cause a class constructor to run. + VerifyOrReturn(info.compCompHnd->satisfiesClassConstraints(enclosingClass), + "field has unsatisfied class constraints"); + if (fieldFlags & CORINFO_FLG_FIELD_PROTECTED) + { + Verify(info.compCompHnd->canAccessFamily(info.compMethodHnd, instanceClass), + "Accessing protected method through wrong type."); + } +} + +void Compiler::verVerifyCond(const typeInfo& tiOp1, const typeInfo& tiOp2, unsigned opcode) +{ + if (tiOp1.IsNumberType()) + { +#ifdef _TARGET_64BIT_ + Verify(tiCompatibleWith(tiOp1, tiOp2, true), "Cond type mismatch"); +#else // _TARGET_64BIT + // [10/17/2013] Consider changing this: to put on my verification lawyer hat, + // this is non-conforming to the ECMA Spec: types don't have to be equivalent, + // but compatible, since we can coalesce native int with int32 (see section III.1.5). + Verify(typeInfo::AreEquivalent(tiOp1, tiOp2), "Cond type mismatch"); +#endif // !_TARGET_64BIT_ + } + else if (tiOp1.IsObjRef()) + { + switch (opcode) + { + case CEE_BEQ_S: + case CEE_BEQ: + case CEE_BNE_UN_S: + case CEE_BNE_UN: + case CEE_CEQ: + case CEE_CGT_UN: + break; + default: + Verify(FALSE, "Cond not allowed on object types"); + } + Verify(tiOp2.IsObjRef(), "Cond type mismatch"); + } + else if (tiOp1.IsByRef()) + { + Verify(tiOp2.IsByRef(), "Cond type mismatch"); + } + else + { + Verify(tiOp1.IsMethod() && tiOp2.IsMethod(), "Cond type mismatch"); + } +} + +void Compiler::verVerifyThisPtrInitialised() +{ + if (verTrackObjCtorInitState) + { + Verify(verCurrentState.thisInitialized == TIS_Init, "this ptr is not initialized"); + } +} + +BOOL Compiler::verIsCallToInitThisPtr(CORINFO_CLASS_HANDLE context, CORINFO_CLASS_HANDLE target) +{ + // Either target == context, in this case calling an alternate .ctor + // Or target is the immediate parent of context + + return ((target == context) || (target == info.compCompHnd->getParentType(context))); +} + +GenTreePtr Compiler::impImportLdvirtftn(GenTreePtr thisPtr, + CORINFO_RESOLVED_TOKEN* pResolvedToken, + CORINFO_CALL_INFO* pCallInfo) +{ + if ((pCallInfo->methodFlags & CORINFO_FLG_EnC) && !(pCallInfo->classFlags & CORINFO_FLG_INTERFACE)) + { + NO_WAY("Virtual call to a function added via EnC is not supported"); + } + +#ifdef FEATURE_READYTORUN_COMPILER + if (opts.IsReadyToRun() && !pCallInfo->exactContextNeedsRuntimeLookup) + { + GenTreeCall* call = gtNewHelperCallNode(CORINFO_HELP_READYTORUN_VIRTUAL_FUNC_PTR, TYP_I_IMPL, GTF_EXCEPT, + gtNewArgList(thisPtr)); + + call->setEntryPoint(pCallInfo->codePointerLookup.constLookup); + + return call; + } +#endif + + // Get the exact descriptor for the static callsite + GenTreePtr exactTypeDesc = impParentClassTokenToHandle(pResolvedToken); + if (exactTypeDesc == nullptr) + { // compDonotInline() + return nullptr; + } + + GenTreePtr exactMethodDesc = impTokenToHandle(pResolvedToken); + if (exactMethodDesc == nullptr) + { // compDonotInline() + return nullptr; + } + + GenTreeArgList* helpArgs = gtNewArgList(exactMethodDesc); + + helpArgs = gtNewListNode(exactTypeDesc, helpArgs); + + helpArgs = gtNewListNode(thisPtr, helpArgs); + + // Call helper function. This gets the target address of the final destination callsite. + + return gtNewHelperCallNode(CORINFO_HELP_VIRTUAL_FUNC_PTR, TYP_I_IMPL, GTF_EXCEPT, helpArgs); +} + +/***************************************************************************** + * + * Build and import a box node + */ + +void Compiler::impImportAndPushBox(CORINFO_RESOLVED_TOKEN* pResolvedToken) +{ + // Get the tree for the type handle for the boxed object. In the case + // of shared generic code or ngen'd code this might be an embedded + // computation. + // Note we can only box do it if the class construtor has been called + // We can always do it on primitive types + + GenTreePtr op1 = nullptr; + GenTreePtr op2 = nullptr; + var_types lclTyp; + + impSpillSpecialSideEff(); + + // Now get the expression to box from the stack. + CORINFO_CLASS_HANDLE operCls; + GenTreePtr exprToBox = impPopStack(operCls).val; + + CorInfoHelpFunc boxHelper = info.compCompHnd->getBoxHelper(pResolvedToken->hClass); + if (boxHelper == CORINFO_HELP_BOX) + { + // we are doing 'normal' boxing. This means that we can inline the box operation + // Box(expr) gets morphed into + // temp = new(clsHnd) + // cpobj(temp+4, expr, clsHnd) + // push temp + // The code paths differ slightly below for structs and primitives because + // "cpobj" differs in these cases. In one case you get + // impAssignStructPtr(temp+4, expr, clsHnd) + // and the other you get + // *(temp+4) = expr + + if (impBoxTempInUse || impBoxTemp == BAD_VAR_NUM) + { + impBoxTemp = lvaGrabTemp(true DEBUGARG("Box Helper")); + } + + // needs to stay in use until this box expression is appended + // some other node. We approximate this by keeping it alive until + // the opcode stack becomes empty + impBoxTempInUse = true; + +#ifdef FEATURE_READYTORUN_COMPILER + bool usingReadyToRunHelper = false; + + if (opts.IsReadyToRun()) + { + op1 = impReadyToRunHelperToTree(pResolvedToken, CORINFO_HELP_READYTORUN_NEW, TYP_REF); + usingReadyToRunHelper = (op1 != NULL); + } + + if (!usingReadyToRunHelper) +#endif + { + // TODO: ReadyToRun: When generic dictionary lookups are necessary, replace the lookup call + // and the newfast call with a single call to a dynamic R2R cell that will: + // 1) Load the context + // 2) Perform the generic dictionary lookup and caching, and generate the appropriate stub + // 3) Allocate and return the new object for boxing + // Reason: performance (today, we'll always use the slow helper for the R2R generics case) + + // Ensure that the value class is restored + op2 = impTokenToHandle(pResolvedToken, nullptr, TRUE /* mustRestoreHandle */); + if (op2 == nullptr) + { // compDonotInline() + return; + } + + op1 = gtNewHelperCallNode(info.compCompHnd->getNewHelper(pResolvedToken, info.compMethodHnd), TYP_REF, 0, + gtNewArgList(op2)); + } + + /* Remember that this basic block contains 'new' of an array */ + compCurBB->bbFlags |= BBF_HAS_NEWOBJ; + + GenTreePtr asg = gtNewTempAssign(impBoxTemp, op1); + + GenTreePtr asgStmt = impAppendTree(asg, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); + + op1 = gtNewLclvNode(impBoxTemp, TYP_REF); + op2 = gtNewIconNode(sizeof(void*), TYP_I_IMPL); + op1 = gtNewOperNode(GT_ADD, TYP_BYREF, op1, op2); + + if (varTypeIsStruct(exprToBox)) + { + assert(info.compCompHnd->getClassSize(pResolvedToken->hClass) == info.compCompHnd->getClassSize(operCls)); + op1 = impAssignStructPtr(op1, exprToBox, operCls, (unsigned)CHECK_SPILL_ALL); + } + else + { + lclTyp = exprToBox->TypeGet(); + if (lclTyp == TYP_BYREF) + { + lclTyp = TYP_I_IMPL; + } + CorInfoType jitType = info.compCompHnd->asCorInfoType(pResolvedToken->hClass); + if (impIsPrimitive(jitType)) + { + lclTyp = JITtype2varType(jitType); + } + assert(genActualType(exprToBox->TypeGet()) == genActualType(lclTyp) || + varTypeIsFloating(lclTyp) == varTypeIsFloating(exprToBox->TypeGet())); + var_types srcTyp = exprToBox->TypeGet(); + var_types dstTyp = lclTyp; + + if (srcTyp != dstTyp) + { + assert((varTypeIsFloating(srcTyp) && varTypeIsFloating(dstTyp)) || + (varTypeIsIntegral(srcTyp) && varTypeIsIntegral(dstTyp))); + exprToBox = gtNewCastNode(dstTyp, exprToBox, dstTyp); + } + op1 = gtNewAssignNode(gtNewOperNode(GT_IND, lclTyp, op1), exprToBox); + } + + op2 = gtNewLclvNode(impBoxTemp, TYP_REF); + op1 = gtNewOperNode(GT_COMMA, TYP_REF, op1, op2); + + // Record that this is a "box" node. + op1 = new (this, GT_BOX) GenTreeBox(TYP_REF, op1, asgStmt); + + // If it is a value class, mark the "box" node. We can use this information + // to optimise several cases: + // "box(x) == null" --> false + // "(box(x)).CallAnInterfaceMethod(...)" --> "(&x).CallAValueTypeMethod" + // "(box(x)).CallAnObjectMethod(...)" --> "(&x).CallAValueTypeMethod" + + op1->gtFlags |= GTF_BOX_VALUE; + assert(op1->IsBoxedValue()); + assert(asg->gtOper == GT_ASG); + } + else + { + // Don't optimize, just call the helper and be done with it + + // Ensure that the value class is restored + op2 = impTokenToHandle(pResolvedToken, nullptr, TRUE /* mustRestoreHandle */); + if (op2 == nullptr) + { // compDonotInline() + return; + } + + GenTreeArgList* args = gtNewArgList(op2, impGetStructAddr(exprToBox, operCls, (unsigned)CHECK_SPILL_ALL, true)); + op1 = gtNewHelperCallNode(boxHelper, TYP_REF, GTF_EXCEPT, args); + } + + /* Push the result back on the stack, */ + /* even if clsHnd is a value class we want the TI_REF */ + typeInfo tiRetVal = typeInfo(TI_REF, info.compCompHnd->getTypeForBox(pResolvedToken->hClass)); + impPushOnStack(op1, tiRetVal); +} + +//------------------------------------------------------------------------ +// impImportNewObjArray: Build and import `new` of multi-dimmensional array +// +// Arguments: +// pResolvedToken - The CORINFO_RESOLVED_TOKEN that has been initialized +// by a call to CEEInfo::resolveToken(). +// pCallInfo - The CORINFO_CALL_INFO that has been initialized +// by a call to CEEInfo::getCallInfo(). +// +// Assumptions: +// The multi-dimensional array constructor arguments (array dimensions) are +// pushed on the IL stack on entry to this method. +// +// Notes: +// Multi-dimensional array constructors are imported as calls to a JIT +// helper, not as regular calls. + +void Compiler::impImportNewObjArray(CORINFO_RESOLVED_TOKEN* pResolvedToken, CORINFO_CALL_INFO* pCallInfo) +{ + GenTreePtr classHandle = impParentClassTokenToHandle(pResolvedToken); + if (classHandle == nullptr) + { // compDonotInline() + return; + } + + assert(pCallInfo->sig.numArgs); + + GenTreePtr node; + GenTreeArgList* args; + + // + // There are two different JIT helpers that can be used to allocate + // multi-dimensional arrays: + // + // - CORINFO_HELP_NEW_MDARR - takes the array dimensions as varargs. + // This variant is deprecated. It should be eventually removed. + // + // - CORINFO_HELP_NEW_MDARR_NONVARARG - takes the array dimensions as + // pointer to block of int32s. This variant is more portable. + // + // The non-varargs helper is enabled for CoreRT only for now. Enabling this + // unconditionally would require ReadyToRun version bump. + // + CLANG_FORMAT_COMMENT_ANCHOR; + +#if COR_JIT_EE_VERSION > 460 + if (!opts.IsReadyToRun() || (eeGetEEInfo()->targetAbi == CORINFO_CORERT_ABI)) + { + LclVarDsc* newObjArrayArgsVar; + + // Reuse the temp used to pass the array dimensions to avoid bloating + // the stack frame in case there are multiple calls to multi-dim array + // constructors within a single method. + if (lvaNewObjArrayArgs == BAD_VAR_NUM) + { + lvaNewObjArrayArgs = lvaGrabTemp(false DEBUGARG("NewObjArrayArgs")); + lvaTable[lvaNewObjArrayArgs].lvType = TYP_BLK; + lvaTable[lvaNewObjArrayArgs].lvExactSize = 0; + } + + // Increase size of lvaNewObjArrayArgs to be the largest size needed to hold 'numArgs' integers + // for our call to CORINFO_HELP_NEW_MDARR_NONVARARG. + lvaTable[lvaNewObjArrayArgs].lvExactSize = + max(lvaTable[lvaNewObjArrayArgs].lvExactSize, pCallInfo->sig.numArgs * sizeof(INT32)); + + // The side-effects may include allocation of more multi-dimensional arrays. Spill all side-effects + // to ensure that the shared lvaNewObjArrayArgs local variable is only ever used to pass arguments + // to one allocation at a time. + impSpillSideEffects(true, (unsigned)CHECK_SPILL_ALL DEBUGARG("impImportNewObjArray")); + + // + // The arguments of the CORINFO_HELP_NEW_MDARR_NONVARARG helper are: + // - Array class handle + // - Number of dimension arguments + // - Pointer to block of int32 dimensions - address of lvaNewObjArrayArgs temp. + // + + node = gtNewLclvNode(lvaNewObjArrayArgs, TYP_BLK); + node = gtNewOperNode(GT_ADDR, TYP_I_IMPL, node); + + // Pop dimension arguments from the stack one at a time and store it + // into lvaNewObjArrayArgs temp. + for (int i = pCallInfo->sig.numArgs - 1; i >= 0; i--) + { + GenTreePtr arg = impImplicitIorI4Cast(impPopStack().val, TYP_INT); + + GenTreePtr dest = gtNewLclvNode(lvaNewObjArrayArgs, TYP_BLK); + dest = gtNewOperNode(GT_ADDR, TYP_I_IMPL, dest); + dest = gtNewOperNode(GT_ADD, TYP_I_IMPL, dest, + new (this, GT_CNS_INT) GenTreeIntCon(TYP_I_IMPL, sizeof(INT32) * i)); + dest = gtNewOperNode(GT_IND, TYP_INT, dest); + + node = gtNewOperNode(GT_COMMA, node->TypeGet(), gtNewAssignNode(dest, arg), node); + } + + args = gtNewArgList(node); + + // pass number of arguments to the helper + args = gtNewListNode(gtNewIconNode(pCallInfo->sig.numArgs), args); + + args = gtNewListNode(classHandle, args); + + node = gtNewHelperCallNode(CORINFO_HELP_NEW_MDARR_NONVARARG, TYP_REF, 0, args); + } + else +#endif + { + // + // The varargs helper needs the type and method handles as last + // and last-1 param (this is a cdecl call, so args will be + // pushed in reverse order on the CPU stack) + // + + args = gtNewArgList(classHandle); + + // pass number of arguments to the helper + args = gtNewListNode(gtNewIconNode(pCallInfo->sig.numArgs), args); + + unsigned argFlags = 0; + args = impPopList(pCallInfo->sig.numArgs, &argFlags, &pCallInfo->sig, args); + + node = gtNewHelperCallNode(CORINFO_HELP_NEW_MDARR, TYP_REF, 0, args); + + // varargs, so we pop the arguments + node->gtFlags |= GTF_CALL_POP_ARGS; + +#ifdef DEBUG + // At the present time we don't track Caller pop arguments + // that have GC references in them + for (GenTreeArgList* temp = args; temp; temp = temp->Rest()) + { + assert(temp->Current()->gtType != TYP_REF); + } +#endif + } + + node->gtFlags |= args->gtFlags & GTF_GLOB_EFFECT; + node->gtCall.compileTimeHelperArgumentHandle = (CORINFO_GENERIC_HANDLE)pResolvedToken->hClass; + + // Remember that this basic block contains 'new' of a md array + compCurBB->bbFlags |= BBF_HAS_NEWARRAY; + + impPushOnStack(node, typeInfo(TI_REF, pResolvedToken->hClass)); +} + +GenTreePtr Compiler::impTransformThis(GenTreePtr thisPtr, + CORINFO_RESOLVED_TOKEN* pConstrainedResolvedToken, + CORINFO_THIS_TRANSFORM transform) +{ + switch (transform) + { + case CORINFO_DEREF_THIS: + { + GenTreePtr obj = thisPtr; + + // This does a LDIND on the obj, which should be a byref. pointing to a ref + impBashVarAddrsToI(obj); + assert(genActualType(obj->gtType) == TYP_I_IMPL || obj->gtType == TYP_BYREF); + CorInfoType constraintTyp = info.compCompHnd->asCorInfoType(pConstrainedResolvedToken->hClass); + + obj = gtNewOperNode(GT_IND, JITtype2varType(constraintTyp), obj); + // ldind could point anywhere, example a boxed class static int + obj->gtFlags |= (GTF_EXCEPT | GTF_GLOB_REF | GTF_IND_TGTANYWHERE); + + return obj; + } + + case CORINFO_BOX_THIS: + { + // Constraint calls where there might be no + // unboxed entry point require us to implement the call via helper. + // These only occur when a possible target of the call + // may have inherited an implementation of an interface + // method from System.Object or System.ValueType. The EE does not provide us with + // "unboxed" versions of these methods. + + GenTreePtr obj = thisPtr; + + assert(obj->TypeGet() == TYP_BYREF || obj->TypeGet() == TYP_I_IMPL); + obj = gtNewObjNode(pConstrainedResolvedToken->hClass, obj); + obj->gtFlags |= GTF_EXCEPT; + + CorInfoType jitTyp = info.compCompHnd->asCorInfoType(pConstrainedResolvedToken->hClass); + var_types objType = JITtype2varType(jitTyp); + if (impIsPrimitive(jitTyp)) + { + if (obj->OperIsBlk()) + { + obj->ChangeOperUnchecked(GT_IND); + + // Obj could point anywhere, example a boxed class static int + obj->gtFlags |= GTF_IND_TGTANYWHERE; + obj->gtOp.gtOp2 = nullptr; // must be zero for tree walkers + } + + obj->gtType = JITtype2varType(jitTyp); + assert(varTypeIsArithmetic(obj->gtType)); + } + + // This pushes on the dereferenced byref + // This is then used immediately to box. + impPushOnStack(obj, verMakeTypeInfo(pConstrainedResolvedToken->hClass).NormaliseForStack()); + + // This pops off the byref-to-a-value-type remaining on the stack and + // replaces it with a boxed object. + // This is then used as the object to the virtual call immediately below. + impImportAndPushBox(pConstrainedResolvedToken); + if (compDonotInline()) + { + return nullptr; + } + + obj = impPopStack().val; + return obj; + } + case CORINFO_NO_THIS_TRANSFORM: + default: + return thisPtr; + } +} + +bool Compiler::impCanPInvokeInline(var_types callRetTyp) +{ + return impCanPInvokeInlineCallSite(callRetTyp) && getInlinePInvokeEnabled() && (!opts.compDbgCode) && + (compCodeOpt() != SMALL_CODE) && (!opts.compNoPInvokeInlineCB) // profiler is preventing inline pinvoke + ; +} + +// Returns false only if the callsite really cannot be inlined. Ignores global variables +// like debugger, profiler etc. +bool Compiler::impCanPInvokeInlineCallSite(var_types callRetTyp) +{ + return + // We have to disable pinvoke inlining inside of filters + // because in case the main execution (i.e. in the try block) is inside + // unmanaged code, we cannot reuse the inlined stub (we still need the + // original state until we are in the catch handler) + (!bbInFilterILRange(compCurBB)) && + // We disable pinvoke inlining inside handlers since the GSCookie is + // in the inlined Frame (see CORINFO_EE_INFO::InlinedCallFrameInfo::offsetOfGSCookie), + // but this would not protect framelets/return-address of handlers. + !compCurBB->hasHndIndex() && +#ifdef _TARGET_AMD64_ + // Turns out JIT64 doesn't perform PInvoke inlining inside try regions, here's an excerpt of + // the comment from JIT64 explaining why: + // + //// [VSWhidbey: 611015] - because the jitted code links in the Frame (instead + //// of the stub) we rely on the Frame not being 'active' until inside the + //// stub. This normally happens by the stub setting the return address + //// pointer in the Frame object inside the stub. On a normal return, the + //// return address pointer is zeroed out so the Frame can be safely re-used, + //// but if an exception occurs, nobody zeros out the return address pointer. + //// Thus if we re-used the Frame object, it would go 'active' as soon as we + //// link it into the Frame chain. + //// + //// Technically we only need to disable PInvoke inlining if we're in a + //// handler or if we're + //// in a try body with a catch or filter/except where other non-handler code + //// in this method might run and try to re-use the dirty Frame object. + // + // Now, because of this, the VM actually assumes that in 64 bit we never PInvoke + // inline calls on any EH construct, you can verify that on VM\ExceptionHandling.cpp:203 + // The method responsible for resuming execution is UpdateObjectRefInResumeContextCallback + // you can see how it aligns with JIT64 policy of not inlining PInvoke calls almost right + // at the beginning of the body of the method. + !compCurBB->hasTryIndex() && +#endif + (!impLocAllocOnStack()) && (callRetTyp != TYP_STRUCT); +} + +void Compiler::impCheckForPInvokeCall(GenTreePtr call, + CORINFO_METHOD_HANDLE methHnd, + CORINFO_SIG_INFO* sig, + unsigned mflags) +{ + var_types callRetTyp = JITtype2varType(sig->retType); + CorInfoUnmanagedCallConv unmanagedCallConv; + + // If VM flagged it as Pinvoke, flag the call node accordingly + if ((mflags & CORINFO_FLG_PINVOKE) != 0) + { + call->gtCall.gtCallMoreFlags |= GTF_CALL_M_PINVOKE; + } + + if (methHnd) + { + if ((mflags & CORINFO_FLG_PINVOKE) == 0 || (mflags & CORINFO_FLG_NOSECURITYWRAP) == 0) + { + return; + } + + unmanagedCallConv = info.compCompHnd->getUnmanagedCallConv(methHnd); + } + else + { + CorInfoCallConv callConv = CorInfoCallConv(sig->callConv & CORINFO_CALLCONV_MASK); + if (callConv == CORINFO_CALLCONV_NATIVEVARARG) + { + // Used by the IL Stubs. + callConv = CORINFO_CALLCONV_C; + } + static_assert_no_msg((unsigned)CORINFO_CALLCONV_C == (unsigned)CORINFO_UNMANAGED_CALLCONV_C); + static_assert_no_msg((unsigned)CORINFO_CALLCONV_STDCALL == (unsigned)CORINFO_UNMANAGED_CALLCONV_STDCALL); + static_assert_no_msg((unsigned)CORINFO_CALLCONV_THISCALL == (unsigned)CORINFO_UNMANAGED_CALLCONV_THISCALL); + unmanagedCallConv = CorInfoUnmanagedCallConv(callConv); + + assert(!call->gtCall.gtCallCookie); + } + + if (unmanagedCallConv != CORINFO_UNMANAGED_CALLCONV_C && unmanagedCallConv != CORINFO_UNMANAGED_CALLCONV_STDCALL && + unmanagedCallConv != CORINFO_UNMANAGED_CALLCONV_THISCALL) + { + return; + } + optNativeCallCount++; + + if (opts.compMustInlinePInvokeCalli && methHnd == nullptr) + { +#ifdef _TARGET_X86_ + // CALLI in IL stubs must be inlined + assert(impCanPInvokeInlineCallSite(callRetTyp)); + assert(!info.compCompHnd->pInvokeMarshalingRequired(methHnd, sig)); +#endif // _TARGET_X86_ + } + else + { + if (!impCanPInvokeInline(callRetTyp)) + { + return; + } + + if (info.compCompHnd->pInvokeMarshalingRequired(methHnd, sig)) + { + return; + } + } + + JITLOG((LL_INFO1000000, "\nInline a CALLI PINVOKE call from method %s", info.compFullName)); + + call->gtFlags |= GTF_CALL_UNMANAGED; + info.compCallUnmanaged++; + + assert(!compIsForInlining()); + + // AMD64 convention is same for native and managed + if (unmanagedCallConv == CORINFO_UNMANAGED_CALLCONV_C) + { + call->gtFlags |= GTF_CALL_POP_ARGS; + } + + if (unmanagedCallConv == CORINFO_UNMANAGED_CALLCONV_THISCALL) + { + call->gtCall.gtCallMoreFlags |= GTF_CALL_M_UNMGD_THISCALL; + } +} + +GenTreePtr Compiler::impImportIndirectCall(CORINFO_SIG_INFO* sig, IL_OFFSETX ilOffset) +{ + var_types callRetTyp = JITtype2varType(sig->retType); + + /* The function pointer is on top of the stack - It may be a + * complex expression. As it is evaluated after the args, + * it may cause registered args to be spilled. Simply spill it. + */ + + // Ignore this trivial case. + if (impStackTop().val->gtOper != GT_LCL_VAR) + { + impSpillStackEntry(verCurrentState.esStackDepth - 1, + BAD_VAR_NUM DEBUGARG(false) DEBUGARG("impImportIndirectCall")); + } + + /* Get the function pointer */ + + GenTreePtr fptr = impPopStack().val; + assert(genActualType(fptr->gtType) == TYP_I_IMPL); + +#ifdef DEBUG + // This temporary must never be converted to a double in stress mode, + // because that can introduce a call to the cast helper after the + // arguments have already been evaluated. + + if (fptr->OperGet() == GT_LCL_VAR) + { + lvaTable[fptr->gtLclVarCommon.gtLclNum].lvKeepType = 1; + } +#endif + + /* Create the call node */ + + GenTreePtr call = gtNewIndCallNode(fptr, callRetTyp, nullptr, ilOffset); + + call->gtFlags |= GTF_EXCEPT | (fptr->gtFlags & GTF_GLOB_EFFECT); + + return call; +} + +/*****************************************************************************/ + +void Compiler::impPopArgsForUnmanagedCall(GenTreePtr call, CORINFO_SIG_INFO* sig) +{ + assert(call->gtFlags & GTF_CALL_UNMANAGED); + + /* Since we push the arguments in reverse order (i.e. right -> left) + * spill any side effects from the stack + * + * OBS: If there is only one side effect we do not need to spill it + * thus we have to spill all side-effects except last one + */ + + unsigned lastLevelWithSideEffects = UINT_MAX; + + unsigned argsToReverse = sig->numArgs; + + // For "thiscall", the first argument goes in a register. Since its + // order does not need to be changed, we do not need to spill it + + if (call->gtCall.gtCallMoreFlags & GTF_CALL_M_UNMGD_THISCALL) + { + assert(argsToReverse); + argsToReverse--; + } + +#ifndef _TARGET_X86_ + // Don't reverse args on ARM or x64 - first four args always placed in regs in order + argsToReverse = 0; +#endif + + for (unsigned level = verCurrentState.esStackDepth - argsToReverse; level < verCurrentState.esStackDepth; level++) + { + if (verCurrentState.esStack[level].val->gtFlags & GTF_ORDER_SIDEEFF) + { + assert(lastLevelWithSideEffects == UINT_MAX); + + impSpillStackEntry(level, + BAD_VAR_NUM DEBUGARG(false) DEBUGARG("impPopArgsForUnmanagedCall - other side effect")); + } + else if (verCurrentState.esStack[level].val->gtFlags & GTF_SIDE_EFFECT) + { + if (lastLevelWithSideEffects != UINT_MAX) + { + /* We had a previous side effect - must spill it */ + impSpillStackEntry(lastLevelWithSideEffects, + BAD_VAR_NUM DEBUGARG(false) DEBUGARG("impPopArgsForUnmanagedCall - side effect")); + + /* Record the level for the current side effect in case we will spill it */ + lastLevelWithSideEffects = level; + } + else + { + /* This is the first side effect encountered - record its level */ + + lastLevelWithSideEffects = level; + } + } + } + + /* The argument list is now "clean" - no out-of-order side effects + * Pop the argument list in reverse order */ + + unsigned argFlags = 0; + GenTreePtr args = call->gtCall.gtCallArgs = + impPopRevList(sig->numArgs, &argFlags, sig, sig->numArgs - argsToReverse); + + if (call->gtCall.gtCallMoreFlags & GTF_CALL_M_UNMGD_THISCALL) + { + GenTreePtr thisPtr = args->Current(); + impBashVarAddrsToI(thisPtr); + assert(thisPtr->TypeGet() == TYP_I_IMPL || thisPtr->TypeGet() == TYP_BYREF); + } + + if (args) + { + call->gtFlags |= args->gtFlags & GTF_GLOB_EFFECT; + } +} + +//------------------------------------------------------------------------ +// impInitClass: Build a node to initialize the class before accessing the +// field if necessary +// +// Arguments: +// pResolvedToken - The CORINFO_RESOLVED_TOKEN that has been initialized +// by a call to CEEInfo::resolveToken(). +// +// Return Value: If needed, a pointer to the node that will perform the class +// initializtion. Otherwise, nullptr. +// + +GenTreePtr Compiler::impInitClass(CORINFO_RESOLVED_TOKEN* pResolvedToken) +{ + CorInfoInitClassResult initClassResult = + info.compCompHnd->initClass(pResolvedToken->hField, info.compMethodHnd, impTokenLookupContextHandle); + + if ((initClassResult & CORINFO_INITCLASS_USE_HELPER) == 0) + { + return nullptr; + } + BOOL runtimeLookup; + + GenTreePtr node = impParentClassTokenToHandle(pResolvedToken, &runtimeLookup); + + if (node == nullptr) + { + assert(compDonotInline()); + return nullptr; + } + + if (runtimeLookup) + { + node = gtNewHelperCallNode(CORINFO_HELP_INITCLASS, TYP_VOID, 0, gtNewArgList(node)); + } + else + { + // Call the shared non gc static helper, as its the fastest + node = fgGetSharedCCtor(pResolvedToken->hClass); + } + + return node; +} + +GenTreePtr Compiler::impImportStaticReadOnlyField(void* fldAddr, var_types lclTyp) +{ + GenTreePtr op1 = nullptr; + + switch (lclTyp) + { + int ival; + __int64 lval; + double dval; + + case TYP_BOOL: + ival = *((bool*)fldAddr); + goto IVAL_COMMON; + + case TYP_BYTE: + ival = *((signed char*)fldAddr); + goto IVAL_COMMON; + + case TYP_UBYTE: + ival = *((unsigned char*)fldAddr); + goto IVAL_COMMON; + + case TYP_SHORT: + ival = *((short*)fldAddr); + goto IVAL_COMMON; + + case TYP_CHAR: + case TYP_USHORT: + ival = *((unsigned short*)fldAddr); + goto IVAL_COMMON; + + case TYP_UINT: + case TYP_INT: + ival = *((int*)fldAddr); + IVAL_COMMON: + op1 = gtNewIconNode(ival); + break; + + case TYP_LONG: + case TYP_ULONG: + lval = *((__int64*)fldAddr); + op1 = gtNewLconNode(lval); + break; + + case TYP_FLOAT: + dval = *((float*)fldAddr); + op1 = gtNewDconNode(dval); +#if !FEATURE_X87_DOUBLES + // X87 stack doesn't differentiate between float/double + // so R4 is treated as R8, but everybody else does + op1->gtType = TYP_FLOAT; +#endif // FEATURE_X87_DOUBLES + break; + + case TYP_DOUBLE: + dval = *((double*)fldAddr); + op1 = gtNewDconNode(dval); + break; + + default: + assert(!"Unexpected lclTyp"); + break; + } + + return op1; +} + +GenTreePtr Compiler::impImportStaticFieldAccess(CORINFO_RESOLVED_TOKEN* pResolvedToken, + CORINFO_ACCESS_FLAGS access, + CORINFO_FIELD_INFO* pFieldInfo, + var_types lclTyp) +{ + GenTreePtr op1; + + switch (pFieldInfo->fieldAccessor) + { + case CORINFO_FIELD_STATIC_GENERICS_STATIC_HELPER: + { + assert(!compIsForInlining()); + + // We first call a special helper to get the statics base pointer + op1 = impParentClassTokenToHandle(pResolvedToken); + + // compIsForInlining() is false so we should not neve get NULL here + assert(op1 != nullptr); + + var_types type = TYP_BYREF; + + switch (pFieldInfo->helper) + { + case CORINFO_HELP_GETGENERICS_NONGCTHREADSTATIC_BASE: + type = TYP_I_IMPL; + break; + case CORINFO_HELP_GETGENERICS_GCSTATIC_BASE: + case CORINFO_HELP_GETGENERICS_NONGCSTATIC_BASE: + case CORINFO_HELP_GETGENERICS_GCTHREADSTATIC_BASE: + break; + default: + assert(!"unknown generic statics helper"); + break; + } + + op1 = gtNewHelperCallNode(pFieldInfo->helper, type, 0, gtNewArgList(op1)); + + FieldSeqNode* fs = GetFieldSeqStore()->CreateSingleton(pResolvedToken->hField); + op1 = gtNewOperNode(GT_ADD, type, op1, + new (this, GT_CNS_INT) GenTreeIntCon(TYP_I_IMPL, pFieldInfo->offset, fs)); + } + break; + + case CORINFO_FIELD_STATIC_SHARED_STATIC_HELPER: +#ifdef FEATURE_READYTORUN_COMPILER + if (opts.IsReadyToRun()) + { + unsigned callFlags = 0; + + if (info.compCompHnd->getClassAttribs(pResolvedToken->hClass) & CORINFO_FLG_BEFOREFIELDINIT) + { + callFlags |= GTF_CALL_HOISTABLE; + } + + op1 = gtNewHelperCallNode(CORINFO_HELP_READYTORUN_STATIC_BASE, TYP_BYREF, callFlags); + + op1->gtCall.setEntryPoint(pFieldInfo->fieldLookup); + } + else +#endif + { + op1 = fgGetStaticsCCtorHelper(pResolvedToken->hClass, pFieldInfo->helper); + } + + { + FieldSeqNode* fs = GetFieldSeqStore()->CreateSingleton(pResolvedToken->hField); + op1 = gtNewOperNode(GT_ADD, op1->TypeGet(), op1, + new (this, GT_CNS_INT) GenTreeIntCon(TYP_INT, pFieldInfo->offset, fs)); + } + break; + + default: + if (!(access & CORINFO_ACCESS_ADDRESS)) + { + // In future, it may be better to just create the right tree here instead of folding it later. + op1 = gtNewFieldRef(lclTyp, pResolvedToken->hField); + + if (pFieldInfo->fieldFlags & CORINFO_FLG_FIELD_STATIC_IN_HEAP) + { + op1->gtType = TYP_REF; // points at boxed object + FieldSeqNode* firstElemFldSeq = + GetFieldSeqStore()->CreateSingleton(FieldSeqStore::FirstElemPseudoField); + op1 = + gtNewOperNode(GT_ADD, TYP_BYREF, op1, + new (this, GT_CNS_INT) GenTreeIntCon(TYP_I_IMPL, sizeof(void*), firstElemFldSeq)); + + if (varTypeIsStruct(lclTyp)) + { + // Constructor adds GTF_GLOB_REF. Note that this is *not* GTF_EXCEPT. + op1 = gtNewObjNode(pFieldInfo->structType, op1); + } + else + { + op1 = gtNewOperNode(GT_IND, lclTyp, op1); + op1->gtFlags |= GTF_GLOB_REF | GTF_IND_NONFAULTING; + } + } + + return op1; + } + else + { + void** pFldAddr = nullptr; + void* fldAddr = info.compCompHnd->getFieldAddress(pResolvedToken->hField, (void**)&pFldAddr); + + FieldSeqNode* fldSeq = GetFieldSeqStore()->CreateSingleton(pResolvedToken->hField); + + /* Create the data member node */ + if (pFldAddr == nullptr) + { + op1 = gtNewIconHandleNode((size_t)fldAddr, GTF_ICON_STATIC_HDL, fldSeq); + } + else + { + op1 = gtNewIconHandleNode((size_t)pFldAddr, GTF_ICON_STATIC_HDL, fldSeq); + + // There are two cases here, either the static is RVA based, + // in which case the type of the FIELD node is not a GC type + // and the handle to the RVA is a TYP_I_IMPL. Or the FIELD node is + // a GC type and the handle to it is a TYP_BYREF in the GC heap + // because handles to statics now go into the large object heap + + var_types handleTyp = (var_types)(varTypeIsGC(lclTyp) ? TYP_BYREF : TYP_I_IMPL); + op1 = gtNewOperNode(GT_IND, handleTyp, op1); + op1->gtFlags |= GTF_IND_INVARIANT | GTF_IND_NONFAULTING; + } + } + break; + } + + if (pFieldInfo->fieldFlags & CORINFO_FLG_FIELD_STATIC_IN_HEAP) + { + op1 = gtNewOperNode(GT_IND, TYP_REF, op1); + + FieldSeqNode* fldSeq = GetFieldSeqStore()->CreateSingleton(FieldSeqStore::FirstElemPseudoField); + + op1 = gtNewOperNode(GT_ADD, TYP_BYREF, op1, + new (this, GT_CNS_INT) GenTreeIntCon(TYP_I_IMPL, sizeof(void*), fldSeq)); + } + + if (!(access & CORINFO_ACCESS_ADDRESS)) + { + op1 = gtNewOperNode(GT_IND, lclTyp, op1); + op1->gtFlags |= GTF_GLOB_REF; + } + + return op1; +} + +// In general try to call this before most of the verification work. Most people expect the access +// exceptions before the verification exceptions. If you do this after, that usually doesn't happen. Turns +// out if you can't access something we also think that you're unverifiable for other reasons. +void Compiler::impHandleAccessAllowed(CorInfoIsAccessAllowedResult result, CORINFO_HELPER_DESC* helperCall) +{ + if (result != CORINFO_ACCESS_ALLOWED) + { + impHandleAccessAllowedInternal(result, helperCall); + } +} + +void Compiler::impHandleAccessAllowedInternal(CorInfoIsAccessAllowedResult result, CORINFO_HELPER_DESC* helperCall) +{ + switch (result) + { + case CORINFO_ACCESS_ALLOWED: + break; + case CORINFO_ACCESS_ILLEGAL: + // if we're verifying, then we need to reject the illegal access to ensure that we don't think the + // method is verifiable. Otherwise, delay the exception to runtime. + if (compIsForImportOnly()) + { + info.compCompHnd->ThrowExceptionForHelper(helperCall); + } + else + { + impInsertHelperCall(helperCall); + } + break; + case CORINFO_ACCESS_RUNTIME_CHECK: + impInsertHelperCall(helperCall); + break; + } +} + +void Compiler::impInsertHelperCall(CORINFO_HELPER_DESC* helperInfo) +{ + // Construct the argument list + GenTreeArgList* args = nullptr; + assert(helperInfo->helperNum != CORINFO_HELP_UNDEF); + for (unsigned i = helperInfo->numArgs; i > 0; --i) + { + const CORINFO_HELPER_ARG& helperArg = helperInfo->args[i - 1]; + GenTreePtr currentArg = nullptr; + switch (helperArg.argType) + { + case CORINFO_HELPER_ARG_TYPE_Field: + info.compCompHnd->classMustBeLoadedBeforeCodeIsRun( + info.compCompHnd->getFieldClass(helperArg.fieldHandle)); + currentArg = gtNewIconEmbFldHndNode(helperArg.fieldHandle); + break; + case CORINFO_HELPER_ARG_TYPE_Method: + info.compCompHnd->methodMustBeLoadedBeforeCodeIsRun(helperArg.methodHandle); + currentArg = gtNewIconEmbMethHndNode(helperArg.methodHandle); + break; + case CORINFO_HELPER_ARG_TYPE_Class: + info.compCompHnd->classMustBeLoadedBeforeCodeIsRun(helperArg.classHandle); + currentArg = gtNewIconEmbClsHndNode(helperArg.classHandle); + break; + case CORINFO_HELPER_ARG_TYPE_Module: + currentArg = gtNewIconEmbScpHndNode(helperArg.moduleHandle); + break; + case CORINFO_HELPER_ARG_TYPE_Const: + currentArg = gtNewIconNode(helperArg.constant); + break; + default: + NO_WAY("Illegal helper arg type"); + } + args = (currentArg == nullptr) ? gtNewArgList(currentArg) : gtNewListNode(currentArg, args); + } + + /* TODO-Review: + * Mark as CSE'able, and hoistable. Consider marking hoistable unless you're in the inlinee. + * Also, consider sticking this in the first basic block. + */ + GenTreePtr callout = gtNewHelperCallNode(helperInfo->helperNum, TYP_VOID, GTF_EXCEPT, args); + impAppendTree(callout, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); +} + +void Compiler::impInsertCalloutForDelegate(CORINFO_METHOD_HANDLE callerMethodHnd, + CORINFO_METHOD_HANDLE calleeMethodHnd, + CORINFO_CLASS_HANDLE delegateTypeHnd) +{ +#ifdef FEATURE_CORECLR + if (!info.compCompHnd->isDelegateCreationAllowed(delegateTypeHnd, calleeMethodHnd)) + { + // Call the JIT_DelegateSecurityCheck helper before calling the actual function. + // This helper throws an exception if the CLR host disallows the call. + + GenTreePtr helper = gtNewHelperCallNode(CORINFO_HELP_DELEGATE_SECURITY_CHECK, TYP_VOID, GTF_EXCEPT, + gtNewArgList(gtNewIconEmbClsHndNode(delegateTypeHnd), + gtNewIconEmbMethHndNode(calleeMethodHnd))); + // Append the callout statement + impAppendTree(helper, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); + } +#endif // FEATURE_CORECLR +} + +// Checks whether the return types of caller and callee are compatible +// so that callee can be tail called. Note that here we don't check +// compatibility in IL Verifier sense, but on the lines of return type +// sizes are equal and get returned in the same return register. +bool Compiler::impTailCallRetTypeCompatible(var_types callerRetType, + CORINFO_CLASS_HANDLE callerRetTypeClass, + var_types calleeRetType, + CORINFO_CLASS_HANDLE calleeRetTypeClass) +{ + // Note that we can not relax this condition with genActualType() as the + // calling convention dictates that the caller of a function with a small + // typed return value is responsible for normalizing the return val. + if (callerRetType == calleeRetType) + { + return true; + } + +#if defined(_TARGET_AMD64_) || defined(_TARGET_ARM64_) + // Jit64 compat: + if (callerRetType == TYP_VOID) + { + // This needs to be allowed to support the following IL pattern that Jit64 allows: + // tail.call + // pop + // ret + // + // Note that the above IL pattern is not valid as per IL verification rules. + // Therefore, only full trust code can take advantage of this pattern. + return true; + } + + // These checks return true if the return value type sizes are the same and + // get returned in the same return register i.e. caller doesn't need to normalize + // return value. Some of the tail calls permitted by below checks would have + // been rejected by IL Verifier before we reached here. Therefore, only full + // trust code can make those tail calls. + unsigned callerRetTypeSize = 0; + unsigned calleeRetTypeSize = 0; + bool isCallerRetTypMBEnreg = + VarTypeIsMultiByteAndCanEnreg(callerRetType, callerRetTypeClass, &callerRetTypeSize, true); + bool isCalleeRetTypMBEnreg = + VarTypeIsMultiByteAndCanEnreg(calleeRetType, calleeRetTypeClass, &calleeRetTypeSize, true); + + if (varTypeIsIntegral(callerRetType) || isCallerRetTypMBEnreg) + { + return (varTypeIsIntegral(calleeRetType) || isCalleeRetTypMBEnreg) && (callerRetTypeSize == calleeRetTypeSize); + } +#endif // _TARGET_AMD64_ || _TARGET_ARM64_ + + return false; +} + +// For prefixFlags +enum +{ + PREFIX_TAILCALL_EXPLICIT = 0x00000001, // call has "tail" IL prefix + PREFIX_TAILCALL_IMPLICIT = + 0x00000010, // call is treated as having "tail" prefix even though there is no "tail" IL prefix + PREFIX_TAILCALL = (PREFIX_TAILCALL_EXPLICIT | PREFIX_TAILCALL_IMPLICIT), + PREFIX_VOLATILE = 0x00000100, + PREFIX_UNALIGNED = 0x00001000, + PREFIX_CONSTRAINED = 0x00010000, + PREFIX_READONLY = 0x00100000 +}; + +/******************************************************************************** + * + * Returns true if the current opcode and and the opcodes following it correspond + * to a supported tail call IL pattern. + * + */ +bool Compiler::impIsTailCallILPattern(bool tailPrefixed, + OPCODE curOpcode, + const BYTE* codeAddrOfNextOpcode, + const BYTE* codeEnd, + bool isRecursive, + bool* isCallPopAndRet /* = nullptr */) +{ + // Bail out if the current opcode is not a call. + if (!impOpcodeIsCallOpcode(curOpcode)) + { + return false; + } + +#if !FEATURE_TAILCALL_OPT_SHARED_RETURN + // If shared ret tail opt is not enabled, we will enable + // it for recursive methods. + if (isRecursive) +#endif + { + // we can actually handle if the ret is in a fallthrough block, as long as that is the only part of the + // sequence. Make sure we don't go past the end of the IL however. + codeEnd = min(codeEnd + 1, info.compCode + info.compILCodeSize); + } + + // Bail out if there is no next opcode after call + if (codeAddrOfNextOpcode >= codeEnd) + { + return false; + } + + // Scan the opcodes to look for the following IL patterns if either + // i) the call is not tail prefixed (i.e. implicit tail call) or + // ii) if tail prefixed, IL verification is not needed for the method. + // + // Only in the above two cases we can allow the below tail call patterns + // violating ECMA spec. + // + // Pattern1: + // call + // nop* + // ret + // + // Pattern2: + // call + // nop* + // pop + // nop* + // ret + int cntPop = 0; + OPCODE nextOpcode; + +#ifdef _TARGET_AMD64_ + do + { + nextOpcode = (OPCODE)getU1LittleEndian(codeAddrOfNextOpcode); + codeAddrOfNextOpcode += sizeof(__int8); + } while ((codeAddrOfNextOpcode < codeEnd) && // Haven't reached end of method + (!tailPrefixed || !tiVerificationNeeded) && // Not ".tail" prefixed or method requires no IL verification + ((nextOpcode == CEE_NOP) || ((nextOpcode == CEE_POP) && (++cntPop == 1)))); // Next opcode = nop or exactly + // one pop seen so far. +#else + nextOpcode = (OPCODE)getU1LittleEndian(codeAddrOfNextOpcode); +#endif + + if (isCallPopAndRet) + { + // Allow call+pop+ret to be tail call optimized if caller ret type is void + *isCallPopAndRet = (nextOpcode == CEE_RET) && (cntPop == 1); + } + +#ifdef _TARGET_AMD64_ + // Jit64 Compat: + // Tail call IL pattern could be either of the following + // 1) call/callvirt/calli + ret + // 2) call/callvirt/calli + pop + ret in a method returning void. + return (nextOpcode == CEE_RET) && ((cntPop == 0) || ((cntPop == 1) && (info.compRetType == TYP_VOID))); +#else //!_TARGET_AMD64_ + return (nextOpcode == CEE_RET) && (cntPop == 0); +#endif +} + +/***************************************************************************** + * + * Determine whether the call could be converted to an implicit tail call + * + */ +bool Compiler::impIsImplicitTailCallCandidate( + OPCODE opcode, const BYTE* codeAddrOfNextOpcode, const BYTE* codeEnd, int prefixFlags, bool isRecursive) +{ + +#if FEATURE_TAILCALL_OPT + if (!opts.compTailCallOpt) + { + return false; + } + + if (opts.compDbgCode || opts.MinOpts()) + { + return false; + } + + // must not be tail prefixed + if (prefixFlags & PREFIX_TAILCALL_EXPLICIT) + { + return false; + } + +#if !FEATURE_TAILCALL_OPT_SHARED_RETURN + // the block containing call is marked as BBJ_RETURN + // We allow shared ret tail call optimization on recursive calls even under + // !FEATURE_TAILCALL_OPT_SHARED_RETURN. + if (!isRecursive && (compCurBB->bbJumpKind != BBJ_RETURN)) + return false; +#endif // !FEATURE_TAILCALL_OPT_SHARED_RETURN + + // must be call+ret or call+pop+ret + if (!impIsTailCallILPattern(false, opcode, codeAddrOfNextOpcode, codeEnd, isRecursive)) + { + return false; + } + + return true; +#else + return false; +#endif // FEATURE_TAILCALL_OPT +} + +//------------------------------------------------------------------------ +// impImportCall: import a call-inspiring opcode +// +// Arguments: +// opcode - opcode that inspires the call +// pResolvedToken - resolved token for the call target +// pConstrainedResolvedToken - resolved constraint token (or nullptr) +// newObjThis - tree for this pointer or uninitalized newobj temp (or nullptr) +// prefixFlags - IL prefix flags for the call +// callInfo - EE supplied info for the call +// rawILOffset - IL offset of the opcode +// +// Returns: +// Type of the call's return value. +// +// Notes: +// opcode can be CEE_CALL, CEE_CALLI, CEE_CALLVIRT, or CEE_NEWOBJ. +// +// For CEE_NEWOBJ, newobjThis should be the temp grabbed for the allocated +// uninitalized object. + +#ifdef _PREFAST_ +#pragma warning(push) +#pragma warning(disable : 21000) // Suppress PREFast warning about overly large function +#endif + +var_types Compiler::impImportCall(OPCODE opcode, + CORINFO_RESOLVED_TOKEN* pResolvedToken, + CORINFO_RESOLVED_TOKEN* pConstrainedResolvedToken, + GenTreePtr newobjThis, + int prefixFlags, + CORINFO_CALL_INFO* callInfo, + IL_OFFSET rawILOffset) +{ + assert(opcode == CEE_CALL || opcode == CEE_CALLVIRT || opcode == CEE_NEWOBJ || opcode == CEE_CALLI); + + IL_OFFSETX ilOffset = impCurILOffset(rawILOffset, true); + var_types callRetTyp = TYP_COUNT; + CORINFO_SIG_INFO* sig = nullptr; + CORINFO_METHOD_HANDLE methHnd = nullptr; + CORINFO_CLASS_HANDLE clsHnd = nullptr; + unsigned clsFlags = 0; + unsigned mflags = 0; + unsigned argFlags = 0; + GenTreePtr call = nullptr; + GenTreeArgList* args = nullptr; + CORINFO_THIS_TRANSFORM constraintCallThisTransform = CORINFO_NO_THIS_TRANSFORM; + CORINFO_CONTEXT_HANDLE exactContextHnd = nullptr; + BOOL exactContextNeedsRuntimeLookup = FALSE; + bool canTailCall = true; + const char* szCanTailCallFailReason = nullptr; + int tailCall = prefixFlags & PREFIX_TAILCALL; + bool readonlyCall = (prefixFlags & PREFIX_READONLY) != 0; + + // Synchronized methods need to call CORINFO_HELP_MON_EXIT at the end. We could + // do that before tailcalls, but that is probably not the intended + // semantic. So just disallow tailcalls from synchronized methods. + // Also, popping arguments in a varargs function is more work and NYI + // If we have a security object, we have to keep our frame around for callers + // to see any imperative security. + if (info.compFlags & CORINFO_FLG_SYNCH) + { + canTailCall = false; + szCanTailCallFailReason = "Caller is synchronized"; + } +#if !FEATURE_FIXED_OUT_ARGS + else if (info.compIsVarArgs) + { + canTailCall = false; + szCanTailCallFailReason = "Caller is varargs"; + } +#endif // FEATURE_FIXED_OUT_ARGS + else if (opts.compNeedSecurityCheck) + { + canTailCall = false; + szCanTailCallFailReason = "Caller requires a security check."; + } + + // We only need to cast the return value of pinvoke inlined calls that return small types + + // TODO-AMD64-Cleanup: Remove this when we stop interoperating with JIT64, or if we decide to stop + // widening everything! CoreCLR does not support JIT64 interoperation so no need to widen there. + // The existing x64 JIT doesn't bother widening all types to int, so we have to assume for + // the time being that the callee might be compiled by the other JIT and thus the return + // value will need to be widened by us (or not widened at all...) + + // ReadyToRun code sticks with default calling convention that does not widen small return types. + + bool checkForSmallType = opts.IsJit64Compat() || opts.IsReadyToRun(); + bool bIntrinsicImported = false; + + CORINFO_SIG_INFO calliSig; + GenTreeArgList* extraArg = nullptr; + + /*------------------------------------------------------------------------- + * First create the call node + */ + + if (opcode == CEE_CALLI) + { + /* Get the call site sig */ + eeGetSig(pResolvedToken->token, info.compScopeHnd, impTokenLookupContextHandle, &calliSig); + + callRetTyp = JITtype2varType(calliSig.retType); + + call = impImportIndirectCall(&calliSig, ilOffset); + + // We don't know the target method, so we have to infer the flags, or + // assume the worst-case. + mflags = (calliSig.callConv & CORINFO_CALLCONV_HASTHIS) ? 0 : CORINFO_FLG_STATIC; + +#ifdef DEBUG + if (verbose) + { + unsigned structSize = + (callRetTyp == TYP_STRUCT) ? info.compCompHnd->getClassSize(calliSig.retTypeSigClass) : 0; + printf("\nIn Compiler::impImportCall: opcode is %s, kind=%d, callRetType is %s, structSize is %d\n", + opcodeNames[opcode], callInfo->kind, varTypeName(callRetTyp), structSize); + } +#endif + // This should be checked in impImportBlockCode. + assert(!compIsForInlining() || !(impInlineInfo->inlineCandidateInfo->dwRestrictions & INLINE_RESPECT_BOUNDARY)); + + sig = &calliSig; + +#ifdef DEBUG + // We cannot lazily obtain the signature of a CALLI call because it has no method + // handle that we can use, so we need to save its full call signature here. + assert(call->gtCall.callSig == nullptr); + call->gtCall.callSig = new (this, CMK_CorSig) CORINFO_SIG_INFO; + *call->gtCall.callSig = calliSig; +#endif // DEBUG + } + else // (opcode != CEE_CALLI) + { + CorInfoIntrinsics intrinsicID = CORINFO_INTRINSIC_Count; + + // Passing CORINFO_CALLINFO_ALLOWINSTPARAM indicates that this JIT is prepared to + // supply the instantiation parameters necessary to make direct calls to underlying + // shared generic code, rather than calling through instantiating stubs. If the + // returned signature has CORINFO_CALLCONV_PARAMTYPE then this indicates that the JIT + // must indeed pass an instantiation parameter. + + methHnd = callInfo->hMethod; + + sig = &(callInfo->sig); + callRetTyp = JITtype2varType(sig->retType); + + mflags = callInfo->methodFlags; + +#ifdef DEBUG + if (verbose) + { + unsigned structSize = (callRetTyp == TYP_STRUCT) ? info.compCompHnd->getClassSize(sig->retTypeSigClass) : 0; + printf("\nIn Compiler::impImportCall: opcode is %s, kind=%d, callRetType is %s, structSize is %d\n", + opcodeNames[opcode], callInfo->kind, varTypeName(callRetTyp), structSize); + } +#endif + if (compIsForInlining()) + { + /* Does this call site have security boundary restrictions? */ + + if (impInlineInfo->inlineCandidateInfo->dwRestrictions & INLINE_RESPECT_BOUNDARY) + { + compInlineResult->NoteFatal(InlineObservation::CALLSITE_CROSS_BOUNDARY_SECURITY); + return callRetTyp; + } + + /* Does the inlinee need a security check token on the frame */ + + if (mflags & CORINFO_FLG_SECURITYCHECK) + { + compInlineResult->NoteFatal(InlineObservation::CALLEE_NEEDS_SECURITY_CHECK); + return callRetTyp; + } + + /* Does the inlinee use StackCrawlMark */ + + if (mflags & CORINFO_FLG_DONT_INLINE_CALLER) + { + compInlineResult->NoteFatal(InlineObservation::CALLEE_STACK_CRAWL_MARK); + return callRetTyp; + } + + /* For now ignore delegate invoke */ + + if (mflags & CORINFO_FLG_DELEGATE_INVOKE) + { + compInlineResult->NoteFatal(InlineObservation::CALLEE_HAS_DELEGATE_INVOKE); + return callRetTyp; + } + + /* For now ignore varargs */ + if ((sig->callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_NATIVEVARARG) + { + compInlineResult->NoteFatal(InlineObservation::CALLEE_HAS_NATIVE_VARARGS); + return callRetTyp; + } + + if ((sig->callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_VARARG) + { + compInlineResult->NoteFatal(InlineObservation::CALLEE_HAS_MANAGED_VARARGS); + return callRetTyp; + } + + if ((mflags & CORINFO_FLG_VIRTUAL) && (sig->sigInst.methInstCount != 0) && (opcode == CEE_CALLVIRT)) + { + compInlineResult->NoteFatal(InlineObservation::CALLEE_IS_GENERIC_VIRTUAL); + return callRetTyp; + } + } + + clsHnd = pResolvedToken->hClass; + + clsFlags = callInfo->classFlags; + +#ifdef DEBUG + // If this is a call to JitTestLabel.Mark, do "early inlining", and record the test attribute. + + // This recognition should really be done by knowing the methHnd of the relevant Mark method(s). + // These should be in mscorlib.h, and available through a JIT/EE interface call. + const char* modName; + const char* className; + const char* methodName; + if ((className = eeGetClassName(clsHnd)) != nullptr && + strcmp(className, "System.Runtime.CompilerServices.JitTestLabel") == 0 && + (methodName = eeGetMethodName(methHnd, &modName)) != nullptr && strcmp(methodName, "Mark") == 0) + { + return impImportJitTestLabelMark(sig->numArgs); + } +#endif // DEBUG + + // <NICE> Factor this into getCallInfo </NICE> + if ((mflags & CORINFO_FLG_INTRINSIC) && !pConstrainedResolvedToken) + { + call = impIntrinsic(clsHnd, methHnd, sig, pResolvedToken->token, readonlyCall, + (canTailCall && (tailCall != 0)), &intrinsicID); + + if (call != nullptr) + { + assert(!(mflags & CORINFO_FLG_VIRTUAL) || (mflags & CORINFO_FLG_FINAL) || + (clsFlags & CORINFO_FLG_FINAL)); + +#ifdef FEATURE_READYTORUN_COMPILER + if (call->OperGet() == GT_INTRINSIC) + { + if (opts.IsReadyToRun()) + { + noway_assert(callInfo->kind == CORINFO_CALL); + call->gtIntrinsic.gtEntryPoint = callInfo->codePointerLookup.constLookup; + } + else + { + call->gtIntrinsic.gtEntryPoint.addr = nullptr; + } + } +#endif + + bIntrinsicImported = true; + goto DONE_CALL; + } + } + +#ifdef FEATURE_SIMD + if (featureSIMD) + { + call = impSIMDIntrinsic(opcode, newobjThis, clsHnd, methHnd, sig, pResolvedToken->token); + if (call != nullptr) + { + bIntrinsicImported = true; + goto DONE_CALL; + } + } +#endif // FEATURE_SIMD + + if ((mflags & CORINFO_FLG_VIRTUAL) && (mflags & CORINFO_FLG_EnC) && (opcode == CEE_CALLVIRT)) + { + NO_WAY("Virtual call to a function added via EnC is not supported"); + goto DONE_CALL; + } + + if ((sig->callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_DEFAULT && + (sig->callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_VARARG && + (sig->callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_NATIVEVARARG) + { + BADCODE("Bad calling convention"); + } + + //------------------------------------------------------------------------- + // Construct the call node + // + // Work out what sort of call we're making. + // Dispense with virtual calls implemented via LDVIRTFTN immediately. + + constraintCallThisTransform = callInfo->thisTransform; + + exactContextHnd = callInfo->contextHandle; + exactContextNeedsRuntimeLookup = callInfo->exactContextNeedsRuntimeLookup; + + // Recursive call is treaded as a loop to the begining of the method. + if (methHnd == info.compMethodHnd) + { +#ifdef DEBUG + if (verbose) + { + JITDUMP("\nFound recursive call in the method. Mark BB%02u to BB%02u as having a backward branch.\n", + fgFirstBB->bbNum, compCurBB->bbNum); + } +#endif + fgMarkBackwardJump(fgFirstBB, compCurBB); + } + + switch (callInfo->kind) + { + + case CORINFO_VIRTUALCALL_STUB: + { + assert(!(mflags & CORINFO_FLG_STATIC)); // can't call a static method + assert(!(clsFlags & CORINFO_FLG_VALUECLASS)); + if (callInfo->stubLookup.lookupKind.needsRuntimeLookup) + { + + if (compIsForInlining()) + { + // Don't import runtime lookups when inlining + // Inlining has to be aborted in such a case + /* XXX Fri 3/20/2009 + * By the way, this would never succeed. If the handle lookup is into the generic + * dictionary for a candidate, you'll generate different dictionary offsets and the + * inlined code will crash. + * + * To anyone code reviewing this, when could this ever succeed in the future? It'll + * always have a handle lookup. These lookups are safe intra-module, but we're just + * failing here. + */ + compInlineResult->NoteFatal(InlineObservation::CALLSITE_HAS_COMPLEX_HANDLE); + return callRetTyp; + } + + GenTreePtr stubAddr = impRuntimeLookupToTree(pResolvedToken, &callInfo->stubLookup, methHnd); + assert(!compDonotInline()); + + // This is the rough code to set up an indirect stub call + assert(stubAddr != nullptr); + + // The stubAddr may be a + // complex expression. As it is evaluated after the args, + // it may cause registered args to be spilled. Simply spill it. + + unsigned lclNum = lvaGrabTemp(true DEBUGARG("VirtualCall with runtime lookup")); + impAssignTempGen(lclNum, stubAddr, (unsigned)CHECK_SPILL_ALL); + stubAddr = gtNewLclvNode(lclNum, TYP_I_IMPL); + + // Create the actual call node + + assert((sig->callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_VARARG && + (sig->callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_NATIVEVARARG); + + call = gtNewIndCallNode(stubAddr, callRetTyp, nullptr); + + call->gtFlags |= GTF_EXCEPT | (stubAddr->gtFlags & GTF_GLOB_EFFECT); + call->gtFlags |= GTF_CALL_VIRT_STUB; + +#ifdef _TARGET_X86_ + // No tailcalls allowed for these yet... + canTailCall = false; + szCanTailCallFailReason = "VirtualCall with runtime lookup"; +#endif + } + else + { + // ok, the stub is available at compile type. + + call = gtNewCallNode(CT_USER_FUNC, callInfo->hMethod, callRetTyp, nullptr, ilOffset); + call->gtCall.gtStubCallStubAddr = callInfo->stubLookup.constLookup.addr; + call->gtFlags |= GTF_CALL_VIRT_STUB; + assert(callInfo->stubLookup.constLookup.accessType != IAT_PPVALUE); + if (callInfo->stubLookup.constLookup.accessType == IAT_PVALUE) + { + call->gtCall.gtCallMoreFlags |= GTF_CALL_M_VIRTSTUB_REL_INDIRECT; + } + } + +#ifdef FEATURE_READYTORUN_COMPILER + if (opts.IsReadyToRun()) + { + // Null check is sometimes needed for ready to run to handle + // non-virtual <-> virtual changes between versions + if (callInfo->nullInstanceCheck) + { + call->gtFlags |= GTF_CALL_NULLCHECK; + } + } +#endif + + break; + } + + case CORINFO_VIRTUALCALL_VTABLE: + { + assert(!(mflags & CORINFO_FLG_STATIC)); // can't call a static method + assert(!(clsFlags & CORINFO_FLG_VALUECLASS)); + call = gtNewCallNode(CT_USER_FUNC, callInfo->hMethod, callRetTyp, nullptr, ilOffset); + call->gtFlags |= GTF_CALL_VIRT_VTABLE; + break; + } + + case CORINFO_VIRTUALCALL_LDVIRTFTN: + { + if (compIsForInlining()) + { + compInlineResult->NoteFatal(InlineObservation::CALLSITE_HAS_CALL_VIA_LDVIRTFTN); + return callRetTyp; + } + + assert(!(mflags & CORINFO_FLG_STATIC)); // can't call a static method + assert(!(clsFlags & CORINFO_FLG_VALUECLASS)); + // OK, We've been told to call via LDVIRTFTN, so just + // take the call now.... + + args = impPopList(sig->numArgs, &argFlags, sig); + + GenTreePtr thisPtr = impPopStack().val; + thisPtr = impTransformThis(thisPtr, pConstrainedResolvedToken, callInfo->thisTransform); + if (compDonotInline()) + { + return callRetTyp; + } + + // Clone the (possibly transformed) "this" pointer + GenTreePtr thisPtrCopy; + thisPtr = impCloneExpr(thisPtr, &thisPtrCopy, NO_CLASS_HANDLE, (unsigned)CHECK_SPILL_ALL, + nullptr DEBUGARG("LDVIRTFTN this pointer")); + + GenTreePtr fptr = impImportLdvirtftn(thisPtr, pResolvedToken, callInfo); + if (compDonotInline()) + { + return callRetTyp; + } + + thisPtr = nullptr; // can't reuse it + + // Now make an indirect call through the function pointer + + unsigned lclNum = lvaGrabTemp(true DEBUGARG("VirtualCall through function pointer")); + impAssignTempGen(lclNum, fptr, (unsigned)CHECK_SPILL_ALL); + fptr = gtNewLclvNode(lclNum, TYP_I_IMPL); + + // Create the actual call node + + call = gtNewIndCallNode(fptr, callRetTyp, args, ilOffset); + call->gtCall.gtCallObjp = thisPtrCopy; + call->gtFlags |= GTF_EXCEPT | (fptr->gtFlags & GTF_GLOB_EFFECT); + +#ifdef FEATURE_READYTORUN_COMPILER + if (opts.IsReadyToRun()) + { + // Null check is needed for ready to run to handle + // non-virtual <-> virtual changes between versions + call->gtFlags |= GTF_CALL_NULLCHECK; + } +#endif + + // Sine we are jumping over some code, check that its OK to skip that code + assert((sig->callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_VARARG && + (sig->callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_NATIVEVARARG); + goto DONE; + } + + case CORINFO_CALL: + { + // This is for a non-virtual, non-interface etc. call + call = gtNewCallNode(CT_USER_FUNC, callInfo->hMethod, callRetTyp, nullptr, ilOffset); + + // We remove the nullcheck for the GetType call instrinsic. + // TODO-CQ: JIT64 does not introduce the null check for many more helper calls + // and instrinsics. + if (callInfo->nullInstanceCheck && + !((mflags & CORINFO_FLG_INTRINSIC) != 0 && (intrinsicID == CORINFO_INTRINSIC_Object_GetType))) + { + call->gtFlags |= GTF_CALL_NULLCHECK; + } + +#ifdef FEATURE_READYTORUN_COMPILER + if (opts.IsReadyToRun()) + { + call->gtCall.setEntryPoint(callInfo->codePointerLookup.constLookup); + } +#endif + break; + } + + case CORINFO_CALL_CODE_POINTER: + { + // The EE has asked us to call by computing a code pointer and then doing an + // indirect call. This is because a runtime lookup is required to get the code entry point. + + // These calls always follow a uniform calling convention, i.e. no extra hidden params + assert((sig->callConv & CORINFO_CALLCONV_PARAMTYPE) == 0); + + assert((sig->callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_VARARG); + assert((sig->callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_NATIVEVARARG); + + GenTreePtr fptr = + impLookupToTree(pResolvedToken, &callInfo->codePointerLookup, GTF_ICON_FTN_ADDR, callInfo->hMethod); + + if (compDonotInline()) + { + return callRetTyp; + } + + // Now make an indirect call through the function pointer + + unsigned lclNum = lvaGrabTemp(true DEBUGARG("Indirect call through function pointer")); + impAssignTempGen(lclNum, fptr, (unsigned)CHECK_SPILL_ALL); + fptr = gtNewLclvNode(lclNum, TYP_I_IMPL); + + call = gtNewIndCallNode(fptr, callRetTyp, nullptr, ilOffset); + call->gtFlags |= GTF_EXCEPT | (fptr->gtFlags & GTF_GLOB_EFFECT); + if (callInfo->nullInstanceCheck) + { + call->gtFlags |= GTF_CALL_NULLCHECK; + } + + break; + } + + default: + assert(!"unknown call kind"); + break; + } + + //------------------------------------------------------------------------- + // Set more flags + + PREFIX_ASSUME(call != nullptr); + + if (mflags & CORINFO_FLG_NOGCCHECK) + { + call->gtCall.gtCallMoreFlags |= GTF_CALL_M_NOGCCHECK; + } + + // Mark call if it's one of the ones we will maybe treat as an intrinsic + if (intrinsicID == CORINFO_INTRINSIC_Object_GetType || intrinsicID == CORINFO_INTRINSIC_TypeEQ || + intrinsicID == CORINFO_INTRINSIC_TypeNEQ || intrinsicID == CORINFO_INTRINSIC_GetCurrentManagedThread || + intrinsicID == CORINFO_INTRINSIC_GetManagedThreadId) + { + call->gtCall.gtCallMoreFlags |= GTF_CALL_M_SPECIAL_INTRINSIC; + } + } + assert(sig); + assert(clsHnd || (opcode == CEE_CALLI)); // We're never verifying for CALLI, so this is not set. + + /* Some sanity checks */ + + // CALL_VIRT and NEWOBJ must have a THIS pointer + assert((opcode != CEE_CALLVIRT && opcode != CEE_NEWOBJ) || (sig->callConv & CORINFO_CALLCONV_HASTHIS)); + // static bit and hasThis are negations of one another + assert(((mflags & CORINFO_FLG_STATIC) != 0) == ((sig->callConv & CORINFO_CALLCONV_HASTHIS) == 0)); + assert(call != nullptr); + + /*------------------------------------------------------------------------- + * Check special-cases etc + */ + + /* Special case - Check if it is a call to Delegate.Invoke(). */ + + if (mflags & CORINFO_FLG_DELEGATE_INVOKE) + { + assert(!compIsForInlining()); + assert(!(mflags & CORINFO_FLG_STATIC)); // can't call a static method + assert(mflags & CORINFO_FLG_FINAL); + + /* Set the delegate flag */ + call->gtCall.gtCallMoreFlags |= GTF_CALL_M_DELEGATE_INV; + + if (callInfo->secureDelegateInvoke) + { + call->gtCall.gtCallMoreFlags |= GTF_CALL_M_SECURE_DELEGATE_INV; + } + + if (opcode == CEE_CALLVIRT) + { + assert(mflags & CORINFO_FLG_FINAL); + + /* It should have the GTF_CALL_NULLCHECK flag set. Reset it */ + assert(call->gtFlags & GTF_CALL_NULLCHECK); + call->gtFlags &= ~GTF_CALL_NULLCHECK; + } + } + + CORINFO_CLASS_HANDLE actualMethodRetTypeSigClass; + actualMethodRetTypeSigClass = sig->retTypeSigClass; + if (varTypeIsStruct(callRetTyp)) + { + callRetTyp = impNormStructType(actualMethodRetTypeSigClass); + call->gtType = callRetTyp; + } + +#if !FEATURE_VARARG + /* Check for varargs */ + if ((sig->callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_VARARG || + (sig->callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_NATIVEVARARG) + { + BADCODE("Varargs not supported."); + } +#endif // !FEATURE_VARARG + + if ((sig->callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_VARARG || + (sig->callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_NATIVEVARARG) + { + assert(!compIsForInlining()); + + /* Set the right flags */ + + call->gtFlags |= GTF_CALL_POP_ARGS; + call->gtCall.gtCallMoreFlags |= GTF_CALL_M_VARARGS; + + /* Can't allow tailcall for varargs as it is caller-pop. The caller + will be expecting to pop a certain number of arguments, but if we + tailcall to a function with a different number of arguments, we + are hosed. There are ways around this (caller remembers esp value, + varargs is not caller-pop, etc), but not worth it. */ + CLANG_FORMAT_COMMENT_ANCHOR; + +#ifdef _TARGET_X86_ + if (canTailCall) + { + canTailCall = false; + szCanTailCallFailReason = "Callee is varargs"; + } +#endif + + /* Get the total number of arguments - this is already correct + * for CALLI - for methods we have to get it from the call site */ + + if (opcode != CEE_CALLI) + { +#ifdef DEBUG + unsigned numArgsDef = sig->numArgs; +#endif + eeGetCallSiteSig(pResolvedToken->token, info.compScopeHnd, impTokenLookupContextHandle, sig); + +#ifdef DEBUG + // We cannot lazily obtain the signature of a vararg call because using its method + // handle will give us only the declared argument list, not the full argument list. + assert(call->gtCall.callSig == nullptr); + call->gtCall.callSig = new (this, CMK_CorSig) CORINFO_SIG_INFO; + *call->gtCall.callSig = *sig; +#endif + + // For vararg calls we must be sure to load the return type of the + // method actually being called, as well as the return types of the + // specified in the vararg signature. With type equivalency, these types + // may not be the same. + if (sig->retTypeSigClass != actualMethodRetTypeSigClass) + { + if (actualMethodRetTypeSigClass != nullptr && sig->retType != CORINFO_TYPE_CLASS && + sig->retType != CORINFO_TYPE_BYREF && sig->retType != CORINFO_TYPE_PTR && + sig->retType != CORINFO_TYPE_VAR) + { + // Make sure that all valuetypes (including enums) that we push are loaded. + // This is to guarantee that if a GC is triggerred from the prestub of this methods, + // all valuetypes in the method signature are already loaded. + // We need to be able to find the size of the valuetypes, but we cannot + // do a class-load from within GC. + info.compCompHnd->classMustBeLoadedBeforeCodeIsRun(actualMethodRetTypeSigClass); + } + } + + assert(numArgsDef <= sig->numArgs); + } + + /* We will have "cookie" as the last argument but we cannot push + * it on the operand stack because we may overflow, so we append it + * to the arg list next after we pop them */ + } + + if (mflags & CORINFO_FLG_SECURITYCHECK) + { + assert(!compIsForInlining()); + + // Need security prolog/epilog callouts when there is + // imperative security in the method. This is to give security a + // chance to do any setup in the prolog and cleanup in the epilog if needed. + + if (compIsForInlining()) + { + // Cannot handle this if the method being imported is an inlinee by itself. + // Because inlinee method does not have its own frame. + + compInlineResult->NoteFatal(InlineObservation::CALLEE_NEEDS_SECURITY_CHECK); + return callRetTyp; + } + else + { + tiSecurityCalloutNeeded = true; + + // If the current method calls a method which needs a security check, + // (i.e. the method being compiled has imperative security) + // we need to reserve a slot for the security object in + // the current method's stack frame + opts.compNeedSecurityCheck = true; + } + } + + //--------------------------- Inline NDirect ------------------------------ + + if (!compIsForInlining()) + { + impCheckForPInvokeCall(call, methHnd, sig, mflags); + } + + if (call->gtFlags & GTF_CALL_UNMANAGED) + { + // We set up the unmanaged call by linking the frame, disabling GC, etc + // This needs to be cleaned up on return + if (canTailCall) + { + canTailCall = false; + szCanTailCallFailReason = "Callee is native"; + } + + checkForSmallType = true; + + impPopArgsForUnmanagedCall(call, sig); + + goto DONE; + } + else if ((opcode == CEE_CALLI) && (((sig->callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_STDCALL) || + ((sig->callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_C) || + ((sig->callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_THISCALL) || + ((sig->callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_FASTCALL))) + { + if (!info.compCompHnd->canGetCookieForPInvokeCalliSig(sig)) + { + // Normally this only happens with inlining. + // However, a generic method (or type) being NGENd into another module + // can run into this issue as well. There's not an easy fall-back for NGEN + // so instead we fallback to JIT. + if (compIsForInlining()) + { + compInlineResult->NoteFatal(InlineObservation::CALLSITE_CANT_EMBED_PINVOKE_COOKIE); + } + else + { + IMPL_LIMITATION("Can't get PInvoke cookie (cross module generics)"); + } + + return callRetTyp; + } + + GenTreePtr cookie = eeGetPInvokeCookie(sig); + + // This cookie is required to be either a simple GT_CNS_INT or + // an indirection of a GT_CNS_INT + // + GenTreePtr cookieConst = cookie; + if (cookie->gtOper == GT_IND) + { + cookieConst = cookie->gtOp.gtOp1; + } + assert(cookieConst->gtOper == GT_CNS_INT); + + // Setting GTF_DONT_CSE on the GT_CNS_INT as well as on the GT_IND (if it exists) will ensure that + // we won't allow this tree to participate in any CSE logic + // + cookie->gtFlags |= GTF_DONT_CSE; + cookieConst->gtFlags |= GTF_DONT_CSE; + + call->gtCall.gtCallCookie = cookie; + + if (canTailCall) + { + canTailCall = false; + szCanTailCallFailReason = "PInvoke calli"; + } + } + + /*------------------------------------------------------------------------- + * Create the argument list + */ + + //------------------------------------------------------------------------- + // Special case - for varargs we have an implicit last argument + + if ((sig->callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_VARARG) + { + assert(!compIsForInlining()); + + void *varCookie, *pVarCookie; + if (!info.compCompHnd->canGetVarArgsHandle(sig)) + { + compInlineResult->NoteFatal(InlineObservation::CALLSITE_CANT_EMBED_VARARGS_COOKIE); + return callRetTyp; + } + + varCookie = info.compCompHnd->getVarArgsHandle(sig, &pVarCookie); + assert((!varCookie) != (!pVarCookie)); + GenTreePtr cookie = gtNewIconEmbHndNode(varCookie, pVarCookie, GTF_ICON_VARG_HDL); + + assert(extraArg == nullptr); + extraArg = gtNewArgList(cookie); + } + + //------------------------------------------------------------------------- + // Extra arg for shared generic code and array methods + // + // Extra argument containing instantiation information is passed in the + // following circumstances: + // (a) To the "Address" method on array classes; the extra parameter is + // the array's type handle (a TypeDesc) + // (b) To shared-code instance methods in generic structs; the extra parameter + // is the struct's type handle (a vtable ptr) + // (c) To shared-code per-instantiation non-generic static methods in generic + // classes and structs; the extra parameter is the type handle + // (d) To shared-code generic methods; the extra parameter is an + // exact-instantiation MethodDesc + // + // We also set the exact type context associated with the call so we can + // inline the call correctly later on. + + if (sig->callConv & CORINFO_CALLCONV_PARAMTYPE) + { + assert(call->gtCall.gtCallType == CT_USER_FUNC); + if (clsHnd == nullptr) + { + NO_WAY("CALLI on parameterized type"); + } + + assert(opcode != CEE_CALLI); + + GenTreePtr instParam; + BOOL runtimeLookup; + + // Instantiated generic method + if (((SIZE_T)exactContextHnd & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD) + { + CORINFO_METHOD_HANDLE exactMethodHandle = + (CORINFO_METHOD_HANDLE)((SIZE_T)exactContextHnd & ~CORINFO_CONTEXTFLAGS_MASK); + + if (!exactContextNeedsRuntimeLookup) + { +#ifdef FEATURE_READYTORUN_COMPILER + if (opts.IsReadyToRun()) + { + instParam = + impReadyToRunLookupToTree(&callInfo->instParamLookup, GTF_ICON_METHOD_HDL, exactMethodHandle); + if (instParam == nullptr) + { + return callRetTyp; + } + } + else +#endif + { + instParam = gtNewIconEmbMethHndNode(exactMethodHandle); + info.compCompHnd->methodMustBeLoadedBeforeCodeIsRun(exactMethodHandle); + } + } + else + { + instParam = impTokenToHandle(pResolvedToken, &runtimeLookup, TRUE /*mustRestoreHandle*/); + if (instParam == nullptr) + { + return callRetTyp; + } + } + } + + // otherwise must be an instance method in a generic struct, + // a static method in a generic type, or a runtime-generated array method + else + { + assert(((SIZE_T)exactContextHnd & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_CLASS); + CORINFO_CLASS_HANDLE exactClassHandle = + (CORINFO_CLASS_HANDLE)((SIZE_T)exactContextHnd & ~CORINFO_CONTEXTFLAGS_MASK); + + if (compIsForInlining() && (clsFlags & CORINFO_FLG_ARRAY) != 0) + { + compInlineResult->NoteFatal(InlineObservation::CALLEE_IS_ARRAY_METHOD); + return callRetTyp; + } + + if ((clsFlags & CORINFO_FLG_ARRAY) && readonlyCall) + { + // We indicate "readonly" to the Address operation by using a null + // instParam. + instParam = gtNewIconNode(0, TYP_REF); + } + + if (!exactContextNeedsRuntimeLookup) + { +#ifdef FEATURE_READYTORUN_COMPILER + if (opts.IsReadyToRun()) + { + instParam = + impReadyToRunLookupToTree(&callInfo->instParamLookup, GTF_ICON_CLASS_HDL, exactClassHandle); + if (instParam == NULL) + { + return callRetTyp; + } + } + else +#endif + { + instParam = gtNewIconEmbClsHndNode(exactClassHandle); + info.compCompHnd->classMustBeLoadedBeforeCodeIsRun(exactClassHandle); + } + } + else + { + instParam = impParentClassTokenToHandle(pResolvedToken, &runtimeLookup, TRUE /*mustRestoreHandle*/); + if (instParam == nullptr) + { + return callRetTyp; + } + } + } + + assert(extraArg == nullptr); + extraArg = gtNewArgList(instParam); + } + + // Inlining may need the exact type context (exactContextHnd) if we're inlining shared generic code, in particular + // to inline 'polytypic' operations such as static field accesses, type tests and method calls which + // rely on the exact context. The exactContextHnd is passed back to the JitInterface at appropriate points. + // exactContextHnd is not currently required when inlining shared generic code into shared + // generic code, since the inliner aborts whenever shared code polytypic operations are encountered + // (e.g. anything marked needsRuntimeLookup) + if (exactContextNeedsRuntimeLookup) + { + exactContextHnd = nullptr; + } + + //------------------------------------------------------------------------- + // The main group of arguments + + args = call->gtCall.gtCallArgs = impPopList(sig->numArgs, &argFlags, sig, extraArg); + + if (args) + { + call->gtFlags |= args->gtFlags & GTF_GLOB_EFFECT; + } + + //------------------------------------------------------------------------- + // The "this" pointer + + if (!(mflags & CORINFO_FLG_STATIC) && !((opcode == CEE_NEWOBJ) && (newobjThis == nullptr))) + { + GenTreePtr obj; + + if (opcode == CEE_NEWOBJ) + { + obj = newobjThis; + } + else + { + obj = impPopStack().val; + obj = impTransformThis(obj, pConstrainedResolvedToken, constraintCallThisTransform); + if (compDonotInline()) + { + return callRetTyp; + } + } + + /* Is this a virtual or interface call? */ + + if ((call->gtFlags & GTF_CALL_VIRT_KIND_MASK) != GTF_CALL_NONVIRT) + { + /* only true object pointers can be virtual */ + + assert(obj->gtType == TYP_REF); + } + else + { + if (impIsThis(obj)) + { + call->gtCall.gtCallMoreFlags |= GTF_CALL_M_NONVIRT_SAME_THIS; + } + } + + /* Store the "this" value in the call */ + + call->gtFlags |= obj->gtFlags & GTF_GLOB_EFFECT; + call->gtCall.gtCallObjp = obj; + } + + //------------------------------------------------------------------------- + // The "this" pointer for "newobj" + + if (opcode == CEE_NEWOBJ) + { + if (clsFlags & CORINFO_FLG_VAROBJSIZE) + { + assert(!(clsFlags & CORINFO_FLG_ARRAY)); // arrays handled separately + // This is a 'new' of a variable sized object, wher + // the constructor is to return the object. In this case + // the constructor claims to return VOID but we know it + // actually returns the new object + assert(callRetTyp == TYP_VOID); + callRetTyp = TYP_REF; + call->gtType = TYP_REF; + impSpillSpecialSideEff(); + + impPushOnStack(call, typeInfo(TI_REF, clsHnd)); + } + else + { + if (clsFlags & CORINFO_FLG_DELEGATE) + { + // New inliner morph it in impImportCall. + // This will allow us to inline the call to the delegate constructor. + call = fgOptimizeDelegateConstructor(call, &exactContextHnd); + } + + if (!bIntrinsicImported) + { + +#if defined(DEBUG) || defined(INLINE_DATA) + + // Keep track of the raw IL offset of the call + call->gtCall.gtRawILOffset = rawILOffset; + +#endif // defined(DEBUG) || defined(INLINE_DATA) + + // Is it an inline candidate? + impMarkInlineCandidate(call, exactContextHnd, callInfo); + } + + // append the call node. + impAppendTree(call, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs); + + // Now push the value of the 'new onto the stack + + // This is a 'new' of a non-variable sized object. + // Append the new node (op1) to the statement list, + // and then push the local holding the value of this + // new instruction on the stack. + + if (clsFlags & CORINFO_FLG_VALUECLASS) + { + assert(newobjThis->gtOper == GT_ADDR && newobjThis->gtOp.gtOp1->gtOper == GT_LCL_VAR); + + unsigned tmp = newobjThis->gtOp.gtOp1->gtLclVarCommon.gtLclNum; + impPushOnStack(gtNewLclvNode(tmp, lvaGetRealType(tmp)), verMakeTypeInfo(clsHnd).NormaliseForStack()); + } + else + { + if (newobjThis->gtOper == GT_COMMA) + { + // In coreclr the callout can be inserted even if verification is disabled + // so we cannot rely on tiVerificationNeeded alone + + // We must have inserted the callout. Get the real newobj. + newobjThis = newobjThis->gtOp.gtOp2; + } + + assert(newobjThis->gtOper == GT_LCL_VAR); + impPushOnStack(gtNewLclvNode(newobjThis->gtLclVarCommon.gtLclNum, TYP_REF), typeInfo(TI_REF, clsHnd)); + } + } + return callRetTyp; + } + +DONE: + + if (tailCall) + { + // This check cannot be performed for implicit tail calls for the reason + // that impIsImplicitTailCallCandidate() is not checking whether return + // types are compatible before marking a call node with PREFIX_TAILCALL_IMPLICIT. + // As a result it is possible that in the following case, we find that + // the type stack is non-empty if Callee() is considered for implicit + // tail calling. + // int Caller(..) { .... void Callee(); ret val; ... } + // + // Note that we cannot check return type compatibility before ImpImportCall() + // as we don't have required info or need to duplicate some of the logic of + // ImpImportCall(). + // + // For implicit tail calls, we perform this check after return types are + // known to be compatible. + if ((tailCall & PREFIX_TAILCALL_EXPLICIT) && (verCurrentState.esStackDepth != 0)) + { + BADCODE("Stack should be empty after tailcall"); + } + + // Note that we can not relax this condition with genActualType() as + // the calling convention dictates that the caller of a function with + // a small-typed return value is responsible for normalizing the return val + + if (canTailCall && + !impTailCallRetTypeCompatible(info.compRetType, info.compMethodInfo->args.retTypeClass, callRetTyp, + callInfo->sig.retTypeClass)) + { + canTailCall = false; + szCanTailCallFailReason = "Return types are not tail call compatible"; + } + + // Stack empty check for implicit tail calls. + if (canTailCall && (tailCall & PREFIX_TAILCALL_IMPLICIT) && (verCurrentState.esStackDepth != 0)) + { +#ifdef _TARGET_AMD64_ + // JIT64 Compatibility: Opportunistic tail call stack mismatch throws a VerificationException + // in JIT64, not an InvalidProgramException. + Verify(false, "Stack should be empty after tailcall"); +#else // _TARGET_64BIT_ + BADCODE("Stack should be empty after tailcall"); +#endif //!_TARGET_64BIT_ + } + + // assert(compCurBB is not a catch, finally or filter block); + // assert(compCurBB is not a try block protected by a finally block); + + // Check for permission to tailcall + bool explicitTailCall = (tailCall & PREFIX_TAILCALL_EXPLICIT) != 0; + + assert(!explicitTailCall || compCurBB->bbJumpKind == BBJ_RETURN); + + if (canTailCall) + { + // True virtual or indirect calls, shouldn't pass in a callee handle. + CORINFO_METHOD_HANDLE exactCalleeHnd = ((call->gtCall.gtCallType != CT_USER_FUNC) || + ((call->gtFlags & GTF_CALL_VIRT_KIND_MASK) != GTF_CALL_NONVIRT)) + ? nullptr + : methHnd; + GenTreePtr thisArg = call->gtCall.gtCallObjp; + + if (info.compCompHnd->canTailCall(info.compMethodHnd, methHnd, exactCalleeHnd, explicitTailCall)) + { + canTailCall = true; + if (explicitTailCall) + { + // In case of explicit tail calls, mark it so that it is not considered + // for in-lining. + call->gtCall.gtCallMoreFlags |= GTF_CALL_M_EXPLICIT_TAILCALL; +#ifdef DEBUG + if (verbose) + { + printf("\nGTF_CALL_M_EXPLICIT_TAILCALL bit set for call "); + printTreeID(call); + printf("\n"); + } +#endif + } + else + { +#if FEATURE_TAILCALL_OPT + // Must be an implicit tail call. + assert((tailCall & PREFIX_TAILCALL_IMPLICIT) != 0); + + // It is possible that a call node is both an inline candidate and marked + // for opportunistic tail calling. In-lining happens before morhphing of + // trees. If in-lining of an in-line candidate gets aborted for whatever + // reason, it will survive to the morphing stage at which point it will be + // transformed into a tail call after performing additional checks. + + call->gtCall.gtCallMoreFlags |= GTF_CALL_M_IMPLICIT_TAILCALL; +#ifdef DEBUG + if (verbose) + { + printf("\nGTF_CALL_M_IMPLICIT_TAILCALL bit set for call "); + printTreeID(call); + printf("\n"); + } +#endif + +#else //! FEATURE_TAILCALL_OPT + NYI("Implicit tail call prefix on a target which doesn't support opportunistic tail calls"); + +#endif // FEATURE_TAILCALL_OPT + } + + // we can't report success just yet... + } + else + { + canTailCall = false; +// canTailCall reported its reasons already +#ifdef DEBUG + if (verbose) + { + printf("\ninfo.compCompHnd->canTailCall returned false for call "); + printTreeID(call); + printf("\n"); + } +#endif + } + } + else + { + // If this assert fires it means that canTailCall was set to false without setting a reason! + assert(szCanTailCallFailReason != nullptr); + +#ifdef DEBUG + if (verbose) + { + printf("\nRejecting %splicit tail call for call ", explicitTailCall ? "ex" : "im"); + printTreeID(call); + printf(": %s\n", szCanTailCallFailReason); + } +#endif + info.compCompHnd->reportTailCallDecision(info.compMethodHnd, methHnd, explicitTailCall, TAILCALL_FAIL, + szCanTailCallFailReason); + } + } + +// Note: we assume that small return types are already normalized by the managed callee +// or by the pinvoke stub for calls to unmanaged code. + +DONE_CALL: + + if (!bIntrinsicImported) + { + // + // Things needed to be checked when bIntrinsicImported is false. + // + + assert(call->gtOper == GT_CALL); + assert(sig != nullptr); + + // Tail calls require us to save the call site's sig info so we can obtain an argument + // copying thunk from the EE later on. + if (call->gtCall.callSig == nullptr) + { + call->gtCall.callSig = new (this, CMK_CorSig) CORINFO_SIG_INFO; + *call->gtCall.callSig = *sig; + } + + if (compIsForInlining() && opcode == CEE_CALLVIRT) + { + GenTreePtr callObj = call->gtCall.gtCallObjp; + assert(callObj != nullptr); + + unsigned callKind = call->gtFlags & GTF_CALL_VIRT_KIND_MASK; + + if (((callKind != GTF_CALL_NONVIRT) || (call->gtFlags & GTF_CALL_NULLCHECK)) && + impInlineIsGuaranteedThisDerefBeforeAnySideEffects(call->gtCall.gtCallArgs, callObj, + impInlineInfo->inlArgInfo)) + { + impInlineInfo->thisDereferencedFirst = true; + } + } + +#if defined(DEBUG) || defined(INLINE_DATA) + + // Keep track of the raw IL offset of the call + call->gtCall.gtRawILOffset = rawILOffset; + +#endif // defined(DEBUG) || defined(INLINE_DATA) + + // Is it an inline candidate? + impMarkInlineCandidate(call, exactContextHnd, callInfo); + } + + // Push or append the result of the call + if (callRetTyp == TYP_VOID) + { + if (opcode == CEE_NEWOBJ) + { + // we actually did push something, so don't spill the thing we just pushed. + assert(verCurrentState.esStackDepth > 0); + impAppendTree(call, verCurrentState.esStackDepth - 1, impCurStmtOffs); + } + else + { + impAppendTree(call, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs); + } + } + else + { + impSpillSpecialSideEff(); + + if (clsFlags & CORINFO_FLG_ARRAY) + { + eeGetCallSiteSig(pResolvedToken->token, pResolvedToken->tokenScope, pResolvedToken->tokenContext, sig); + } + + // Find the return type used for verification by interpreting the method signature. + // NB: we are clobbering the already established sig. + if (tiVerificationNeeded) + { + // Actually, we never get the sig for the original method. + sig = &(callInfo->verSig); + } + + typeInfo tiRetVal = verMakeTypeInfo(sig->retType, sig->retTypeClass); + tiRetVal.NormaliseForStack(); + + // The CEE_READONLY prefix modifies the verification semantics of an Address + // operation on an array type. + if ((clsFlags & CORINFO_FLG_ARRAY) && readonlyCall && tiRetVal.IsByRef()) + { + tiRetVal.SetIsReadonlyByRef(); + } + + if (tiVerificationNeeded) + { + // We assume all calls return permanent home byrefs. If they + // didn't they wouldn't be verifiable. This is also covering + // the Address() helper for multidimensional arrays. + if (tiRetVal.IsByRef()) + { + tiRetVal.SetIsPermanentHomeByRef(); + } + } + + if (call->gtOper == GT_CALL) + { + // Sometimes "call" is not a GT_CALL (if we imported an intrinsic that didn't turn into a call) + if (varTypeIsStruct(callRetTyp)) + { + call = impFixupCallStructReturn(call, sig->retTypeClass); + } + else if (varTypeIsLong(callRetTyp)) + { + call = impInitCallLongReturn(call); + } + + if ((call->gtFlags & GTF_CALL_INLINE_CANDIDATE) != 0) + { + assert(opts.OptEnabled(CLFLG_INLINING)); + + // Make the call its own tree (spill the stack if needed). + impAppendTree(call, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs); + + // TODO: Still using the widened type. + call = gtNewInlineCandidateReturnExpr(call, genActualType(callRetTyp)); + } + } + + if (!bIntrinsicImported) + { + //------------------------------------------------------------------------- + // + /* If the call is of a small type and the callee is managed, the callee will normalize the result + before returning. + However, we need to normalize small type values returned by unmanaged + functions (pinvoke). The pinvoke stub does the normalization, but we need to do it here + if we use the shorter inlined pinvoke stub. */ + + if (checkForSmallType && varTypeIsIntegral(callRetTyp) && genTypeSize(callRetTyp) < genTypeSize(TYP_INT)) + { + call = gtNewCastNode(genActualType(callRetTyp), call, callRetTyp); + } + } + + impPushOnStack(call, tiRetVal); + } + + // VSD functions get a new call target each time we getCallInfo, so clear the cache. + // Also, the call info cache for CALLI instructions is largely incomplete, so clear it out. + // if ( (opcode == CEE_CALLI) || (callInfoCache.fetchCallInfo().kind == CORINFO_VIRTUALCALL_STUB)) + // callInfoCache.uncacheCallInfo(); + + return callRetTyp; +} +#ifdef _PREFAST_ +#pragma warning(pop) +#endif + +bool Compiler::impMethodInfo_hasRetBuffArg(CORINFO_METHOD_INFO* methInfo) +{ + CorInfoType corType = methInfo->args.retType; + + if ((corType == CORINFO_TYPE_VALUECLASS) || (corType == CORINFO_TYPE_REFANY)) + { + // We have some kind of STRUCT being returned + + structPassingKind howToReturnStruct = SPK_Unknown; + + var_types returnType = getReturnTypeForStruct(methInfo->args.retTypeClass, &howToReturnStruct); + + if (howToReturnStruct == SPK_ByReference) + { + return true; + } + } + + return false; +} + +#ifdef DEBUG +// +var_types Compiler::impImportJitTestLabelMark(int numArgs) +{ + TestLabelAndNum tlAndN; + if (numArgs == 2) + { + tlAndN.m_num = 0; + StackEntry se = impPopStack(); + assert(se.seTypeInfo.GetType() == TI_INT); + GenTreePtr val = se.val; + assert(val->IsCnsIntOrI()); + tlAndN.m_tl = (TestLabel)val->AsIntConCommon()->IconValue(); + } + else if (numArgs == 3) + { + StackEntry se = impPopStack(); + assert(se.seTypeInfo.GetType() == TI_INT); + GenTreePtr val = se.val; + assert(val->IsCnsIntOrI()); + tlAndN.m_num = val->AsIntConCommon()->IconValue(); + se = impPopStack(); + assert(se.seTypeInfo.GetType() == TI_INT); + val = se.val; + assert(val->IsCnsIntOrI()); + tlAndN.m_tl = (TestLabel)val->AsIntConCommon()->IconValue(); + } + else + { + assert(false); + } + + StackEntry expSe = impPopStack(); + GenTreePtr node = expSe.val; + + // There are a small number of special cases, where we actually put the annotation on a subnode. + if (tlAndN.m_tl == TL_LoopHoist && tlAndN.m_num >= 100) + { + // A loop hoist annotation with value >= 100 means that the expression should be a static field access, + // a GT_IND of a static field address, which should be the sum of a (hoistable) helper call and possibly some + // offset within the the static field block whose address is returned by the helper call. + // The annotation is saying that this address calculation, but not the entire access, should be hoisted. + GenTreePtr helperCall = nullptr; + assert(node->OperGet() == GT_IND); + tlAndN.m_num -= 100; + GetNodeTestData()->Set(node->gtOp.gtOp1, tlAndN); + GetNodeTestData()->Remove(node); + } + else + { + GetNodeTestData()->Set(node, tlAndN); + } + + impPushOnStack(node, expSe.seTypeInfo); + return node->TypeGet(); +} +#endif // DEBUG + +//----------------------------------------------------------------------------------- +// impFixupCallStructReturn: For a call node that returns a struct type either +// adjust the return type to an enregisterable type, or set the flag to indicate +// struct return via retbuf arg. +// +// Arguments: +// call - GT_CALL GenTree node +// retClsHnd - Class handle of return type of the call +// +// Return Value: +// Returns new GenTree node after fixing struct return of call node +// +GenTreePtr Compiler::impFixupCallStructReturn(GenTreePtr call, CORINFO_CLASS_HANDLE retClsHnd) +{ + assert(call->gtOper == GT_CALL); + + if (!varTypeIsStruct(call)) + { + return call; + } + + call->gtCall.gtRetClsHnd = retClsHnd; + + GenTreeCall* callNode = call->AsCall(); + +#if FEATURE_MULTIREG_RET + // Initialize Return type descriptor of call node + ReturnTypeDesc* retTypeDesc = callNode->GetReturnTypeDesc(); + retTypeDesc->InitializeStructReturnType(this, retClsHnd); +#endif // FEATURE_MULTIREG_RET + +#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING + + // Not allowed for FEATURE_CORCLR which is the only SKU available for System V OSs. + assert(!callNode->IsVarargs() && "varargs not allowed for System V OSs."); + + // The return type will remain as the incoming struct type unless normalized to a + // single eightbyte return type below. + callNode->gtReturnType = call->gtType; + + unsigned retRegCount = retTypeDesc->GetReturnRegCount(); + if (retRegCount != 0) + { + if (retRegCount == 1) + { + // struct returned in a single register + callNode->gtReturnType = retTypeDesc->GetReturnRegType(0); + } + else + { + // must be a struct returned in two registers + assert(retRegCount == 2); + + if ((!callNode->CanTailCall()) && (!callNode->IsInlineCandidate())) + { + // Force a call returning multi-reg struct to be always of the IR form + // tmp = call + // + // No need to assign a multi-reg struct to a local var if: + // - It is a tail call or + // - The call is marked for in-lining later + return impAssignMultiRegTypeToVar(call, retClsHnd); + } + } + } + else + { + // struct not returned in registers i.e returned via hiddden retbuf arg. + callNode->gtCallMoreFlags |= GTF_CALL_M_RETBUFFARG; + } + +#else // not FEATURE_UNIX_AMD64_STRUCT_PASSING + +#if FEATURE_MULTIREG_RET && defined(_TARGET_ARM_) + // There is no fixup necessary if the return type is a HFA struct. + // HFA structs are returned in registers for ARM32 and ARM64 + // + if (!call->gtCall.IsVarargs() && IsHfa(retClsHnd)) + { + if (call->gtCall.CanTailCall()) + { + if (info.compIsVarArgs) + { + // We cannot tail call because control needs to return to fixup the calling + // convention for result return. + call->gtCall.gtCallMoreFlags &= ~GTF_CALL_M_EXPLICIT_TAILCALL; + } + else + { + // If we can tail call returning HFA, then don't assign it to + // a variable back and forth. + return call; + } + } + + if (call->gtFlags & GTF_CALL_INLINE_CANDIDATE) + { + return call; + } + + unsigned retRegCount = retTypeDesc->GetReturnRegCount(); + if (retRegCount >= 2) + { + return impAssignMultiRegTypeToVar(call, retClsHnd); + } + } +#endif // _TARGET_ARM_ + + // Check for TYP_STRUCT type that wraps a primitive type + // Such structs are returned using a single register + // and we change the return type on those calls here. + // + structPassingKind howToReturnStruct; + var_types returnType = getReturnTypeForStruct(retClsHnd, &howToReturnStruct); + + if (howToReturnStruct == SPK_ByReference) + { + assert(returnType == TYP_UNKNOWN); + call->gtCall.gtCallMoreFlags |= GTF_CALL_M_RETBUFFARG; + } + else + { + assert(returnType != TYP_UNKNOWN); + call->gtCall.gtReturnType = returnType; + + // ToDo: Refactor this common code sequence into its own method as it is used 4+ times + if ((returnType == TYP_LONG) && (compLongUsed == false)) + { + compLongUsed = true; + } + else if (((returnType == TYP_FLOAT) || (returnType == TYP_DOUBLE)) && (compFloatingPointUsed == false)) + { + compFloatingPointUsed = true; + } + +#if FEATURE_MULTIREG_RET + unsigned retRegCount = retTypeDesc->GetReturnRegCount(); + assert(retRegCount != 0); + + if (retRegCount >= 2) + { + if ((!callNode->CanTailCall()) && (!callNode->IsInlineCandidate())) + { + // Force a call returning multi-reg struct to be always of the IR form + // tmp = call + // + // No need to assign a multi-reg struct to a local var if: + // - It is a tail call or + // - The call is marked for in-lining later + return impAssignMultiRegTypeToVar(call, retClsHnd); + } + } +#endif // FEATURE_MULTIREG_RET + } + +#endif // not FEATURE_UNIX_AMD64_STRUCT_PASSING + + return call; +} + +//------------------------------------------------------------------------------------- +// impInitCallLongReturn: +// Initialize the ReturnTypDesc for a call that returns a TYP_LONG +// +// Arguments: +// call - GT_CALL GenTree node +// +// Return Value: +// Returns new GenTree node after initializing the ReturnTypeDesc of call node +// +GenTreePtr Compiler::impInitCallLongReturn(GenTreePtr call) +{ + assert(call->gtOper == GT_CALL); + +#if defined(_TARGET_X86_) && !defined(LEGACY_BACKEND) + // LEGACY_BACKEND does not use multi reg returns for calls with long return types + + if (varTypeIsLong(call)) + { + GenTreeCall* callNode = call->AsCall(); + + // The return type will remain as the incoming long type + callNode->gtReturnType = call->gtType; + + // Initialize Return type descriptor of call node + ReturnTypeDesc* retTypeDesc = callNode->GetReturnTypeDesc(); + retTypeDesc->InitializeLongReturnType(this); + + // must be a long returned in two registers + assert(retTypeDesc->GetReturnRegCount() == 2); + } +#endif // _TARGET_X86_ && !LEGACY_BACKEND + + return call; +} + +/***************************************************************************** + For struct return values, re-type the operand in the case where the ABI + does not use a struct return buffer + Note that this method is only call for !_TARGET_X86_ + */ + +GenTreePtr Compiler::impFixupStructReturnType(GenTreePtr op, CORINFO_CLASS_HANDLE retClsHnd) +{ + assert(varTypeIsStruct(info.compRetType)); + assert(info.compRetBuffArg == BAD_VAR_NUM); + +#if defined(_TARGET_XARCH_) + +#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING + // No VarArgs for CoreCLR on x64 Unix + assert(!info.compIsVarArgs); + + // Is method returning a multi-reg struct? + if (varTypeIsStruct(info.compRetNativeType) && IsMultiRegReturnedType(retClsHnd)) + { + // In case of multi-reg struct return, we force IR to be one of the following: + // GT_RETURN(lclvar) or GT_RETURN(call). If op is anything other than a + // lclvar or call, it is assigned to a temp to create: temp = op and GT_RETURN(tmp). + + if (op->gtOper == GT_LCL_VAR) + { + // Make sure that this struct stays in memory and doesn't get promoted. + unsigned lclNum = op->gtLclVarCommon.gtLclNum; + lvaTable[lclNum].lvIsMultiRegRet = true; + + return op; + } + + if (op->gtOper == GT_CALL) + { + return op; + } + + return impAssignMultiRegTypeToVar(op, retClsHnd); + } +#else // !FEATURE_UNIX_AMD64_STRUCT_PASSING + assert(info.compRetNativeType != TYP_STRUCT); +#endif // !FEATURE_UNIX_AMD64_STRUCT_PASSING + +#elif FEATURE_MULTIREG_RET && defined(_TARGET_ARM_) + + if (varTypeIsStruct(info.compRetNativeType) && !info.compIsVarArgs && IsHfa(retClsHnd)) + { + if (op->gtOper == GT_LCL_VAR) + { + // This LCL_VAR is an HFA return value, it stays as a TYP_STRUCT + unsigned lclNum = op->gtLclVarCommon.gtLclNum; + // Make sure this struct type stays as struct so that we can return it as an HFA + lvaTable[lclNum].lvIsMultiRegRet = true; + return op; + } + + if (op->gtOper == GT_CALL) + { + if (op->gtCall.IsVarargs()) + { + // We cannot tail call because control needs to return to fixup the calling + // convention for result return. + op->gtCall.gtCallMoreFlags &= ~GTF_CALL_M_TAILCALL; + op->gtCall.gtCallMoreFlags &= ~GTF_CALL_M_EXPLICIT_TAILCALL; + } + else + { + return op; + } + } + return impAssignMultiRegTypeToVar(op, retClsHnd); + } + +#elif FEATURE_MULTIREG_RET && defined(_TARGET_ARM64_) + + // Is method returning a multi-reg struct? + if (IsMultiRegReturnedType(retClsHnd)) + { + if (op->gtOper == GT_LCL_VAR) + { + // This LCL_VAR stays as a TYP_STRUCT + unsigned lclNum = op->gtLclVarCommon.gtLclNum; + + // Make sure this struct type is not struct promoted + lvaTable[lclNum].lvIsMultiRegRet = true; + return op; + } + + if (op->gtOper == GT_CALL) + { + if (op->gtCall.IsVarargs()) + { + // We cannot tail call because control needs to return to fixup the calling + // convention for result return. + op->gtCall.gtCallMoreFlags &= ~GTF_CALL_M_TAILCALL; + op->gtCall.gtCallMoreFlags &= ~GTF_CALL_M_EXPLICIT_TAILCALL; + } + else + { + return op; + } + } + return impAssignMultiRegTypeToVar(op, retClsHnd); + } + +#endif // FEATURE_MULTIREG_RET && FEATURE_HFA + +REDO_RETURN_NODE: + // adjust the type away from struct to integral + // and no normalizing + if (op->gtOper == GT_LCL_VAR) + { + op->ChangeOper(GT_LCL_FLD); + } + else if (op->gtOper == GT_OBJ) + { + GenTreePtr op1 = op->AsObj()->Addr(); + + // We will fold away OBJ/ADDR + // except for OBJ/ADDR/INDEX + // as the array type influences the array element's offset + // Later in this method we change op->gtType to info.compRetNativeType + // This is not correct when op is a GT_INDEX as the starting offset + // for the array elements 'elemOffs' is different for an array of + // TYP_REF than an array of TYP_STRUCT (which simply wraps a TYP_REF) + // Also refer to the GTF_INX_REFARR_LAYOUT flag + // + if ((op1->gtOper == GT_ADDR) && (op1->gtOp.gtOp1->gtOper != GT_INDEX)) + { + // Change '*(&X)' to 'X' and see if we can do better + op = op1->gtOp.gtOp1; + goto REDO_RETURN_NODE; + } + op->gtObj.gtClass = NO_CLASS_HANDLE; + op->ChangeOperUnchecked(GT_IND); + op->gtFlags |= GTF_IND_TGTANYWHERE; + } + else if (op->gtOper == GT_CALL) + { + if (op->AsCall()->TreatAsHasRetBufArg(this)) + { + // This must be one of those 'special' helpers that don't + // really have a return buffer, but instead use it as a way + // to keep the trees cleaner with fewer address-taken temps. + // + // Well now we have to materialize the the return buffer as + // an address-taken temp. Then we can return the temp. + // + // NOTE: this code assumes that since the call directly + // feeds the return, then the call must be returning the + // same structure/class/type. + // + unsigned tmpNum = lvaGrabTemp(true DEBUGARG("pseudo return buffer")); + + // No need to spill anything as we're about to return. + impAssignTempGen(tmpNum, op, info.compMethodInfo->args.retTypeClass, (unsigned)CHECK_SPILL_NONE); + + // Don't create both a GT_ADDR & GT_OBJ just to undo all of that; instead, + // jump directly to a GT_LCL_FLD. + op = gtNewLclvNode(tmpNum, info.compRetNativeType); + op->ChangeOper(GT_LCL_FLD); + } + else + { + assert(info.compRetNativeType == op->gtCall.gtReturnType); + + // Don't change the gtType of the node just yet, it will get changed later. + return op; + } + } + else if (op->gtOper == GT_COMMA) + { + op->gtOp.gtOp2 = impFixupStructReturnType(op->gtOp.gtOp2, retClsHnd); + } + + op->gtType = info.compRetNativeType; + + return op; +} + +/***************************************************************************** + CEE_LEAVE may be jumping out of a protected block, viz, a catch or a + finally-protected try. We find the finally blocks protecting the current + offset (in order) by walking over the complete exception table and + finding enclosing clauses. This assumes that the table is sorted. + This will create a series of BBJ_CALLFINALLY -> BBJ_CALLFINALLY ... -> BBJ_ALWAYS. + + If we are leaving a catch handler, we need to attach the + CPX_ENDCATCHes to the correct BBJ_CALLFINALLY blocks. + + After this function, the BBJ_LEAVE block has been converted to a different type. + */ + +#if !FEATURE_EH_FUNCLETS + +void Compiler::impImportLeave(BasicBlock* block) +{ +#ifdef DEBUG + if (verbose) + { + printf("\nBefore import CEE_LEAVE:\n"); + fgDispBasicBlocks(); + fgDispHandlerTab(); + } +#endif // DEBUG + + bool invalidatePreds = false; // If we create new blocks, invalidate the predecessor lists (if created) + unsigned blkAddr = block->bbCodeOffs; + BasicBlock* leaveTarget = block->bbJumpDest; + unsigned jmpAddr = leaveTarget->bbCodeOffs; + + // LEAVE clears the stack, spill side effects, and set stack to 0 + + impSpillSideEffects(true, (unsigned)CHECK_SPILL_ALL DEBUGARG("impImportLeave")); + verCurrentState.esStackDepth = 0; + + assert(block->bbJumpKind == BBJ_LEAVE); + assert(fgBBs == (BasicBlock**)0xCDCD || fgLookupBB(jmpAddr) != NULL); // should be a BB boundary + + BasicBlock* step = DUMMY_INIT(NULL); + unsigned encFinallies = 0; // Number of enclosing finallies. + GenTreePtr endCatches = NULL; + GenTreePtr endLFin = NULL; // The statement tree to indicate the end of locally-invoked finally. + + unsigned XTnum; + EHblkDsc* HBtab; + + for (XTnum = 0, HBtab = compHndBBtab; XTnum < compHndBBtabCount; XTnum++, HBtab++) + { + // Grab the handler offsets + + IL_OFFSET tryBeg = HBtab->ebdTryBegOffs(); + IL_OFFSET tryEnd = HBtab->ebdTryEndOffs(); + IL_OFFSET hndBeg = HBtab->ebdHndBegOffs(); + IL_OFFSET hndEnd = HBtab->ebdHndEndOffs(); + + /* Is this a catch-handler we are CEE_LEAVEing out of? + * If so, we need to call CORINFO_HELP_ENDCATCH. + */ + + if (jitIsBetween(blkAddr, hndBeg, hndEnd) && !jitIsBetween(jmpAddr, hndBeg, hndEnd)) + { + // Can't CEE_LEAVE out of a finally/fault handler + if (HBtab->HasFinallyOrFaultHandler()) + BADCODE("leave out of fault/finally block"); + + // Create the call to CORINFO_HELP_ENDCATCH + GenTreePtr endCatch = gtNewHelperCallNode(CORINFO_HELP_ENDCATCH, TYP_VOID); + + // Make a list of all the currently pending endCatches + if (endCatches) + endCatches = gtNewOperNode(GT_COMMA, TYP_VOID, endCatches, endCatch); + else + endCatches = endCatch; + +#ifdef DEBUG + if (verbose) + { + printf("impImportLeave - BB%02u jumping out of catch handler EH#%u, adding call to " + "CORINFO_HELP_ENDCATCH\n", + block->bbNum, XTnum); + } +#endif + } + else if (HBtab->HasFinallyHandler() && jitIsBetween(blkAddr, tryBeg, tryEnd) && + !jitIsBetween(jmpAddr, tryBeg, tryEnd)) + { + /* This is a finally-protected try we are jumping out of */ + + /* If there are any pending endCatches, and we have already + jumped out of a finally-protected try, then the endCatches + have to be put in a block in an outer try for async + exceptions to work correctly. + Else, just use append to the original block */ + + BasicBlock* callBlock; + + assert(!encFinallies == !endLFin); // if we have finallies, we better have an endLFin tree, and vice-versa + + if (encFinallies == 0) + { + assert(step == DUMMY_INIT(NULL)); + callBlock = block; + callBlock->bbJumpKind = BBJ_CALLFINALLY; // convert the BBJ_LEAVE to BBJ_CALLFINALLY + + if (endCatches) + impAppendTree(endCatches, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); + +#ifdef DEBUG + if (verbose) + { + printf("impImportLeave - jumping out of a finally-protected try, convert block to BBJ_CALLFINALLY " + "block BB%02u [%08p]\n", + callBlock->bbNum, dspPtr(callBlock)); + } +#endif + } + else + { + assert(step != DUMMY_INIT(NULL)); + + /* Calling the finally block */ + callBlock = fgNewBBinRegion(BBJ_CALLFINALLY, XTnum + 1, 0, step); + assert(step->bbJumpKind == BBJ_ALWAYS); + step->bbJumpDest = callBlock; // the previous call to a finally returns to this call (to the next + // finally in the chain) + step->bbJumpDest->bbRefs++; + + /* The new block will inherit this block's weight */ + callBlock->setBBWeight(block->bbWeight); + callBlock->bbFlags |= block->bbFlags & BBF_RUN_RARELY; + +#ifdef DEBUG + if (verbose) + { + printf("impImportLeave - jumping out of a finally-protected try, new BBJ_CALLFINALLY block BB%02u " + "[%08p]\n", + callBlock->bbNum, dspPtr(callBlock)); + } +#endif + + GenTreePtr lastStmt; + + if (endCatches) + { + lastStmt = gtNewStmt(endCatches); + endLFin->gtNext = lastStmt; + lastStmt->gtPrev = endLFin; + } + else + { + lastStmt = endLFin; + } + + // note that this sets BBF_IMPORTED on the block + impEndTreeList(callBlock, endLFin, lastStmt); + } + + step = fgNewBBafter(BBJ_ALWAYS, callBlock, true); + /* The new block will inherit this block's weight */ + step->setBBWeight(block->bbWeight); + step->bbFlags |= (block->bbFlags & BBF_RUN_RARELY) | BBF_IMPORTED | BBF_KEEP_BBJ_ALWAYS; + +#ifdef DEBUG + if (verbose) + { + printf("impImportLeave - jumping out of a finally-protected try, created step (BBJ_ALWAYS) block " + "BB%02u [%08p]\n", + step->bbNum, dspPtr(step)); + } +#endif + + unsigned finallyNesting = compHndBBtab[XTnum].ebdHandlerNestingLevel; + assert(finallyNesting <= compHndBBtabCount); + + callBlock->bbJumpDest = HBtab->ebdHndBeg; // This callBlock will call the "finally" handler. + endLFin = new (this, GT_END_LFIN) GenTreeVal(GT_END_LFIN, TYP_VOID, finallyNesting); + endLFin = gtNewStmt(endLFin); + endCatches = NULL; + + encFinallies++; + + invalidatePreds = true; + } + } + + /* Append any remaining endCatches, if any */ + + assert(!encFinallies == !endLFin); + + if (encFinallies == 0) + { + assert(step == DUMMY_INIT(NULL)); + block->bbJumpKind = BBJ_ALWAYS; // convert the BBJ_LEAVE to a BBJ_ALWAYS + + if (endCatches) + impAppendTree(endCatches, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); + +#ifdef DEBUG + if (verbose) + { + printf("impImportLeave - no enclosing finally-protected try blocks; convert CEE_LEAVE block to BBJ_ALWAYS " + "block BB%02u [%08p]\n", + block->bbNum, dspPtr(block)); + } +#endif + } + else + { + // If leaveTarget is the start of another try block, we want to make sure that + // we do not insert finalStep into that try block. Hence, we find the enclosing + // try block. + unsigned tryIndex = bbFindInnermostCommonTryRegion(step, leaveTarget); + + // Insert a new BB either in the try region indicated by tryIndex or + // the handler region indicated by leaveTarget->bbHndIndex, + // depending on which is the inner region. + BasicBlock* finalStep = fgNewBBinRegion(BBJ_ALWAYS, tryIndex, leaveTarget->bbHndIndex, step); + finalStep->bbFlags |= BBF_KEEP_BBJ_ALWAYS; + step->bbJumpDest = finalStep; + + /* The new block will inherit this block's weight */ + finalStep->setBBWeight(block->bbWeight); + finalStep->bbFlags |= block->bbFlags & BBF_RUN_RARELY; + +#ifdef DEBUG + if (verbose) + { + printf("impImportLeave - finalStep block required (encFinallies(%d) > 0), new block BB%02u [%08p]\n", + encFinallies, finalStep->bbNum, dspPtr(finalStep)); + } +#endif + + GenTreePtr lastStmt; + + if (endCatches) + { + lastStmt = gtNewStmt(endCatches); + endLFin->gtNext = lastStmt; + lastStmt->gtPrev = endLFin; + } + else + { + lastStmt = endLFin; + } + + impEndTreeList(finalStep, endLFin, lastStmt); + + finalStep->bbJumpDest = leaveTarget; // this is the ultimate destination of the LEAVE + + // Queue up the jump target for importing + + impImportBlockPending(leaveTarget); + + invalidatePreds = true; + } + + if (invalidatePreds && fgComputePredsDone) + { + JITDUMP("\n**** impImportLeave - Removing preds after creating new blocks\n"); + fgRemovePreds(); + } + +#ifdef DEBUG + fgVerifyHandlerTab(); + + if (verbose) + { + printf("\nAfter import CEE_LEAVE:\n"); + fgDispBasicBlocks(); + fgDispHandlerTab(); + } +#endif // DEBUG +} + +#else // FEATURE_EH_FUNCLETS + +void Compiler::impImportLeave(BasicBlock* block) +{ +#ifdef DEBUG + if (verbose) + { + printf("\nBefore import CEE_LEAVE in BB%02u (targetting BB%02u):\n", block->bbNum, block->bbJumpDest->bbNum); + fgDispBasicBlocks(); + fgDispHandlerTab(); + } +#endif // DEBUG + + bool invalidatePreds = false; // If we create new blocks, invalidate the predecessor lists (if created) + unsigned blkAddr = block->bbCodeOffs; + BasicBlock* leaveTarget = block->bbJumpDest; + unsigned jmpAddr = leaveTarget->bbCodeOffs; + + // LEAVE clears the stack, spill side effects, and set stack to 0 + + impSpillSideEffects(true, (unsigned)CHECK_SPILL_ALL DEBUGARG("impImportLeave")); + verCurrentState.esStackDepth = 0; + + assert(block->bbJumpKind == BBJ_LEAVE); + assert(fgBBs == (BasicBlock**)0xCDCD || fgLookupBB(jmpAddr) != nullptr); // should be a BB boundary + + BasicBlock* step = nullptr; + + enum StepType + { + // No step type; step == NULL. + ST_None, + + // Is the step block the BBJ_ALWAYS block of a BBJ_CALLFINALLY/BBJ_ALWAYS pair? + // That is, is step->bbJumpDest where a finally will return to? + ST_FinallyReturn, + + // The step block is a catch return. + ST_Catch, + + // The step block is in a "try", created as the target for a finally return or the target for a catch return. + ST_Try + }; + StepType stepType = ST_None; + + unsigned XTnum; + EHblkDsc* HBtab; + + for (XTnum = 0, HBtab = compHndBBtab; XTnum < compHndBBtabCount; XTnum++, HBtab++) + { + // Grab the handler offsets + + IL_OFFSET tryBeg = HBtab->ebdTryBegOffs(); + IL_OFFSET tryEnd = HBtab->ebdTryEndOffs(); + IL_OFFSET hndBeg = HBtab->ebdHndBegOffs(); + IL_OFFSET hndEnd = HBtab->ebdHndEndOffs(); + + /* Is this a catch-handler we are CEE_LEAVEing out of? + */ + + if (jitIsBetween(blkAddr, hndBeg, hndEnd) && !jitIsBetween(jmpAddr, hndBeg, hndEnd)) + { + // Can't CEE_LEAVE out of a finally/fault handler + if (HBtab->HasFinallyOrFaultHandler()) + { + BADCODE("leave out of fault/finally block"); + } + + /* We are jumping out of a catch */ + + if (step == nullptr) + { + step = block; + step->bbJumpKind = BBJ_EHCATCHRET; // convert the BBJ_LEAVE to BBJ_EHCATCHRET + stepType = ST_Catch; + +#ifdef DEBUG + if (verbose) + { + printf("impImportLeave - jumping out of a catch (EH#%u), convert block BB%02u to BBJ_EHCATCHRET " + "block\n", + XTnum, step->bbNum); + } +#endif + } + else + { + BasicBlock* exitBlock; + + /* Create a new catch exit block in the catch region for the existing step block to jump to in this + * scope */ + exitBlock = fgNewBBinRegion(BBJ_EHCATCHRET, 0, XTnum + 1, step); + + assert(step->bbJumpKind == BBJ_ALWAYS || step->bbJumpKind == BBJ_EHCATCHRET); + step->bbJumpDest = exitBlock; // the previous step (maybe a call to a nested finally, or a nested catch + // exit) returns to this block + step->bbJumpDest->bbRefs++; + +#if defined(_TARGET_ARM_) + if (stepType == ST_FinallyReturn) + { + assert(step->bbJumpKind == BBJ_ALWAYS); + // Mark the target of a finally return + step->bbJumpDest->bbFlags |= BBF_FINALLY_TARGET; + } +#endif // defined(_TARGET_ARM_) + + /* The new block will inherit this block's weight */ + exitBlock->setBBWeight(block->bbWeight); + exitBlock->bbFlags |= (block->bbFlags & BBF_RUN_RARELY) | BBF_IMPORTED; + + /* This exit block is the new step */ + step = exitBlock; + stepType = ST_Catch; + + invalidatePreds = true; + +#ifdef DEBUG + if (verbose) + { + printf("impImportLeave - jumping out of a catch (EH#%u), new BBJ_EHCATCHRET block BB%02u\n", XTnum, + exitBlock->bbNum); + } +#endif + } + } + else if (HBtab->HasFinallyHandler() && jitIsBetween(blkAddr, tryBeg, tryEnd) && + !jitIsBetween(jmpAddr, tryBeg, tryEnd)) + { + /* We are jumping out of a finally-protected try */ + + BasicBlock* callBlock; + + if (step == nullptr) + { +#if FEATURE_EH_CALLFINALLY_THUNKS + + // Put the call to the finally in the enclosing region. + unsigned callFinallyTryIndex = + (HBtab->ebdEnclosingTryIndex == EHblkDsc::NO_ENCLOSING_INDEX) ? 0 : HBtab->ebdEnclosingTryIndex + 1; + unsigned callFinallyHndIndex = + (HBtab->ebdEnclosingHndIndex == EHblkDsc::NO_ENCLOSING_INDEX) ? 0 : HBtab->ebdEnclosingHndIndex + 1; + callBlock = fgNewBBinRegion(BBJ_CALLFINALLY, callFinallyTryIndex, callFinallyHndIndex, block); + + // Convert the BBJ_LEAVE to BBJ_ALWAYS, jumping to the new BBJ_CALLFINALLY. This is because + // the new BBJ_CALLFINALLY is in a different EH region, thus it can't just replace the BBJ_LEAVE, + // which might be in the middle of the "try". In most cases, the BBJ_ALWAYS will jump to the + // next block, and flow optimizations will remove it. + block->bbJumpKind = BBJ_ALWAYS; + block->bbJumpDest = callBlock; + block->bbJumpDest->bbRefs++; + + /* The new block will inherit this block's weight */ + callBlock->setBBWeight(block->bbWeight); + callBlock->bbFlags |= (block->bbFlags & BBF_RUN_RARELY) | BBF_IMPORTED; + +#ifdef DEBUG + if (verbose) + { + printf("impImportLeave - jumping out of a finally-protected try (EH#%u), convert block BB%02u to " + "BBJ_ALWAYS, add BBJ_CALLFINALLY block BB%02u\n", + XTnum, block->bbNum, callBlock->bbNum); + } +#endif + +#else // !FEATURE_EH_CALLFINALLY_THUNKS + + callBlock = block; + callBlock->bbJumpKind = BBJ_CALLFINALLY; // convert the BBJ_LEAVE to BBJ_CALLFINALLY + +#ifdef DEBUG + if (verbose) + { + printf("impImportLeave - jumping out of a finally-protected try (EH#%u), convert block BB%02u to " + "BBJ_CALLFINALLY block\n", + XTnum, callBlock->bbNum); + } +#endif + +#endif // !FEATURE_EH_CALLFINALLY_THUNKS + } + else + { + // Calling the finally block. We already have a step block that is either the call-to-finally from a + // more nested try/finally (thus we are jumping out of multiple nested 'try' blocks, each protected by + // a 'finally'), or the step block is the return from a catch. + // + // Due to ThreadAbortException, we can't have the catch return target the call-to-finally block + // directly. Note that if a 'catch' ends without resetting the ThreadAbortException, the VM will + // automatically re-raise the exception, using the return address of the catch (that is, the target + // block of the BBJ_EHCATCHRET) as the re-raise address. If this address is in a finally, the VM will + // refuse to do the re-raise, and the ThreadAbortException will get eaten (and lost). On AMD64/ARM64, + // we put the call-to-finally thunk in a special "cloned finally" EH region that does look like a + // finally clause to the VM. Thus, on these platforms, we can't have BBJ_EHCATCHRET target a + // BBJ_CALLFINALLY directly. (Note that on ARM32, we don't mark the thunk specially -- it lives directly + // within the 'try' region protected by the finally, since we generate code in such a way that execution + // never returns to the call-to-finally call, and the finally-protected 'try' region doesn't appear on + // stack walks.) + + assert(step->bbJumpKind == BBJ_ALWAYS || step->bbJumpKind == BBJ_EHCATCHRET); + +#if FEATURE_EH_CALLFINALLY_THUNKS + if (step->bbJumpKind == BBJ_EHCATCHRET) + { + // Need to create another step block in the 'try' region that will actually branch to the + // call-to-finally thunk. + BasicBlock* step2 = fgNewBBinRegion(BBJ_ALWAYS, XTnum + 1, 0, step); + step->bbJumpDest = step2; + step->bbJumpDest->bbRefs++; + step2->setBBWeight(block->bbWeight); + step2->bbFlags |= (block->bbFlags & BBF_RUN_RARELY) | BBF_IMPORTED; + +#ifdef DEBUG + if (verbose) + { + printf("impImportLeave - jumping out of a finally-protected try (EH#%u), step block is " + "BBJ_EHCATCHRET (BB%02u), new BBJ_ALWAYS step-step block BB%02u\n", + XTnum, step->bbNum, step2->bbNum); + } +#endif + + step = step2; + assert(stepType == ST_Catch); // Leave it as catch type for now. + } +#endif // FEATURE_EH_CALLFINALLY_THUNKS + +#if FEATURE_EH_CALLFINALLY_THUNKS + unsigned callFinallyTryIndex = + (HBtab->ebdEnclosingTryIndex == EHblkDsc::NO_ENCLOSING_INDEX) ? 0 : HBtab->ebdEnclosingTryIndex + 1; + unsigned callFinallyHndIndex = + (HBtab->ebdEnclosingHndIndex == EHblkDsc::NO_ENCLOSING_INDEX) ? 0 : HBtab->ebdEnclosingHndIndex + 1; +#else // !FEATURE_EH_CALLFINALLY_THUNKS + unsigned callFinallyTryIndex = XTnum + 1; + unsigned callFinallyHndIndex = 0; // don't care +#endif // !FEATURE_EH_CALLFINALLY_THUNKS + + callBlock = fgNewBBinRegion(BBJ_CALLFINALLY, callFinallyTryIndex, callFinallyHndIndex, step); + step->bbJumpDest = callBlock; // the previous call to a finally returns to this call (to the next + // finally in the chain) + step->bbJumpDest->bbRefs++; + +#if defined(_TARGET_ARM_) + if (stepType == ST_FinallyReturn) + { + assert(step->bbJumpKind == BBJ_ALWAYS); + // Mark the target of a finally return + step->bbJumpDest->bbFlags |= BBF_FINALLY_TARGET; + } +#endif // defined(_TARGET_ARM_) + + /* The new block will inherit this block's weight */ + callBlock->setBBWeight(block->bbWeight); + callBlock->bbFlags |= (block->bbFlags & BBF_RUN_RARELY) | BBF_IMPORTED; + +#ifdef DEBUG + if (verbose) + { + printf("impImportLeave - jumping out of a finally-protected try (EH#%u), new BBJ_CALLFINALLY block " + "BB%02u\n", + XTnum, callBlock->bbNum); + } +#endif + } + + step = fgNewBBafter(BBJ_ALWAYS, callBlock, true); + stepType = ST_FinallyReturn; + + /* The new block will inherit this block's weight */ + step->setBBWeight(block->bbWeight); + step->bbFlags |= (block->bbFlags & BBF_RUN_RARELY) | BBF_IMPORTED | BBF_KEEP_BBJ_ALWAYS; + +#ifdef DEBUG + if (verbose) + { + printf("impImportLeave - jumping out of a finally-protected try (EH#%u), created step (BBJ_ALWAYS) " + "block BB%02u\n", + XTnum, step->bbNum); + } +#endif + + callBlock->bbJumpDest = HBtab->ebdHndBeg; // This callBlock will call the "finally" handler. + + invalidatePreds = true; + } + else if (HBtab->HasCatchHandler() && jitIsBetween(blkAddr, tryBeg, tryEnd) && + !jitIsBetween(jmpAddr, tryBeg, tryEnd)) + { + // We are jumping out of a catch-protected try. + // + // If we are returning from a call to a finally, then we must have a step block within a try + // that is protected by a catch. This is so when unwinding from that finally (e.g., if code within the + // finally raises an exception), the VM will find this step block, notice that it is in a protected region, + // and invoke the appropriate catch. + // + // We also need to handle a special case with the handling of ThreadAbortException. If a try/catch + // catches a ThreadAbortException (which might be because it catches a parent, e.g. System.Exception), + // and the catch doesn't call System.Threading.Thread::ResetAbort(), then when the catch returns to the VM, + // the VM will automatically re-raise the ThreadAbortException. When it does this, it uses the target + // address of the catch return as the new exception address. That is, the re-raised exception appears to + // occur at the catch return address. If this exception return address skips an enclosing try/catch that + // catches ThreadAbortException, then the enclosing try/catch will not catch the exception, as it should. + // For example: + // + // try { + // try { + // // something here raises ThreadAbortException + // LEAVE LABEL_1; // no need to stop at LABEL_2 + // } catch (Exception) { + // // This catches ThreadAbortException, but doesn't call System.Threading.Thread::ResetAbort(), so + // // ThreadAbortException is re-raised by the VM at the address specified by the LEAVE opcode. + // // This is bad, since it means the outer try/catch won't get a chance to catch the re-raised + // // ThreadAbortException. So, instead, create step block LABEL_2 and LEAVE to that. We only + // // need to do this transformation if the current EH block is a try/catch that catches + // // ThreadAbortException (or one of its parents), however we might not be able to find that + // // information, so currently we do it for all catch types. + // LEAVE LABEL_1; // Convert this to LEAVE LABEL2; + // } + // LABEL_2: LEAVE LABEL_1; // inserted by this step creation code + // } catch (ThreadAbortException) { + // } + // LABEL_1: + // + // Note that this pattern isn't theoretical: it occurs in ASP.NET, in IL code generated by the Roslyn C# + // compiler. + + if ((stepType == ST_FinallyReturn) || (stepType == ST_Catch)) + { + BasicBlock* catchStep; + + assert(step); + + if (stepType == ST_FinallyReturn) + { + assert(step->bbJumpKind == BBJ_ALWAYS); + } + else + { + assert(stepType == ST_Catch); + assert(step->bbJumpKind == BBJ_EHCATCHRET); + } + + /* Create a new exit block in the try region for the existing step block to jump to in this scope */ + catchStep = fgNewBBinRegion(BBJ_ALWAYS, XTnum + 1, 0, step); + step->bbJumpDest = catchStep; + step->bbJumpDest->bbRefs++; + +#if defined(_TARGET_ARM_) + if (stepType == ST_FinallyReturn) + { + // Mark the target of a finally return + step->bbJumpDest->bbFlags |= BBF_FINALLY_TARGET; + } +#endif // defined(_TARGET_ARM_) + + /* The new block will inherit this block's weight */ + catchStep->setBBWeight(block->bbWeight); + catchStep->bbFlags |= (block->bbFlags & BBF_RUN_RARELY) | BBF_IMPORTED; + +#ifdef DEBUG + if (verbose) + { + if (stepType == ST_FinallyReturn) + { + printf("impImportLeave - return from finally jumping out of a catch-protected try (EH#%u), new " + "BBJ_ALWAYS block BB%02u\n", + XTnum, catchStep->bbNum); + } + else + { + assert(stepType == ST_Catch); + printf("impImportLeave - return from catch jumping out of a catch-protected try (EH#%u), new " + "BBJ_ALWAYS block BB%02u\n", + XTnum, catchStep->bbNum); + } + } +#endif // DEBUG + + /* This block is the new step */ + step = catchStep; + stepType = ST_Try; + + invalidatePreds = true; + } + } + } + + if (step == nullptr) + { + block->bbJumpKind = BBJ_ALWAYS; // convert the BBJ_LEAVE to a BBJ_ALWAYS + +#ifdef DEBUG + if (verbose) + { + printf("impImportLeave - no enclosing finally-protected try blocks or catch handlers; convert CEE_LEAVE " + "block BB%02u to BBJ_ALWAYS\n", + block->bbNum); + } +#endif + } + else + { + step->bbJumpDest = leaveTarget; // this is the ultimate destination of the LEAVE + +#if defined(_TARGET_ARM_) + if (stepType == ST_FinallyReturn) + { + assert(step->bbJumpKind == BBJ_ALWAYS); + // Mark the target of a finally return + step->bbJumpDest->bbFlags |= BBF_FINALLY_TARGET; + } +#endif // defined(_TARGET_ARM_) + +#ifdef DEBUG + if (verbose) + { + printf("impImportLeave - final destination of step blocks set to BB%02u\n", leaveTarget->bbNum); + } +#endif + + // Queue up the jump target for importing + + impImportBlockPending(leaveTarget); + } + + if (invalidatePreds && fgComputePredsDone) + { + JITDUMP("\n**** impImportLeave - Removing preds after creating new blocks\n"); + fgRemovePreds(); + } + +#ifdef DEBUG + fgVerifyHandlerTab(); + + if (verbose) + { + printf("\nAfter import CEE_LEAVE:\n"); + fgDispBasicBlocks(); + fgDispHandlerTab(); + } +#endif // DEBUG +} + +#endif // FEATURE_EH_FUNCLETS + +/*****************************************************************************/ +// This is called when reimporting a leave block. It resets the JumpKind, +// JumpDest, and bbNext to the original values + +void Compiler::impResetLeaveBlock(BasicBlock* block, unsigned jmpAddr) +{ +#if FEATURE_EH_FUNCLETS + // With EH Funclets, while importing leave opcode we create another block ending with BBJ_ALWAYS (call it B1) + // and the block containing leave (say B0) is marked as BBJ_CALLFINALLY. Say for some reason we reimport B0, + // it is reset (in this routine) by marking as ending with BBJ_LEAVE and further down when B0 is reimported, we + // create another BBJ_ALWAYS (call it B2). In this process B1 gets orphaned and any blocks to which B1 is the + // only predecessor are also considered orphans and attempted to be deleted. + // + // try { + // .... + // try + // { + // .... + // leave OUTSIDE; // B0 is the block containing this leave, following this would be B1 + // } finally { } + // } finally { } + // OUTSIDE: + // + // In the above nested try-finally example, we create a step block (call it Bstep) which in branches to a block + // where a finally would branch to (and such block is marked as finally target). Block B1 branches to step block. + // Because of re-import of B0, Bstep is also orphaned. Since Bstep is a finally target it cannot be removed. To + // work around this we will duplicate B0 (call it B0Dup) before reseting. B0Dup is marked as BBJ_CALLFINALLY and + // only serves to pair up with B1 (BBJ_ALWAYS) that got orphaned. Now during orphan block deletion B0Dup and B1 + // will be treated as pair and handled correctly. + if (block->bbJumpKind == BBJ_CALLFINALLY) + { + BasicBlock* dupBlock = bbNewBasicBlock(block->bbJumpKind); + dupBlock->bbFlags = block->bbFlags; + dupBlock->bbJumpDest = block->bbJumpDest; + dupBlock->copyEHRegion(block); + dupBlock->bbCatchTyp = block->bbCatchTyp; + + // Mark this block as + // a) not referenced by any other block to make sure that it gets deleted + // b) weight zero + // c) prevent from being imported + // d) as internal + // e) as rarely run + dupBlock->bbRefs = 0; + dupBlock->bbWeight = 0; + dupBlock->bbFlags |= BBF_IMPORTED | BBF_INTERNAL | BBF_RUN_RARELY; + + // Insert the block right after the block which is getting reset so that BBJ_CALLFINALLY and BBJ_ALWAYS + // will be next to each other. + fgInsertBBafter(block, dupBlock); + +#ifdef DEBUG + if (verbose) + { + printf("New Basic Block BB%02u duplicate of BB%02u created.\n", dupBlock->bbNum, block->bbNum); + } +#endif + } +#endif // FEATURE_EH_FUNCLETS + + block->bbJumpKind = BBJ_LEAVE; + fgInitBBLookup(); + block->bbJumpDest = fgLookupBB(jmpAddr); + + // We will leave the BBJ_ALWAYS block we introduced. When it's reimported + // the BBJ_ALWAYS block will be unreachable, and will be removed after. The + // reason we don't want to remove the block at this point is that if we call + // fgInitBBLookup() again we will do it wrong as the BBJ_ALWAYS block won't be + // added and the linked list length will be different than fgBBcount. +} + +/*****************************************************************************/ +// Get the first non-prefix opcode. Used for verification of valid combinations +// of prefixes and actual opcodes. + +static OPCODE impGetNonPrefixOpcode(const BYTE* codeAddr, const BYTE* codeEndp) +{ + while (codeAddr < codeEndp) + { + OPCODE opcode = (OPCODE)getU1LittleEndian(codeAddr); + codeAddr += sizeof(__int8); + + if (opcode == CEE_PREFIX1) + { + if (codeAddr >= codeEndp) + { + break; + } + opcode = (OPCODE)(getU1LittleEndian(codeAddr) + 256); + codeAddr += sizeof(__int8); + } + + switch (opcode) + { + case CEE_UNALIGNED: + case CEE_VOLATILE: + case CEE_TAILCALL: + case CEE_CONSTRAINED: + case CEE_READONLY: + break; + default: + return opcode; + } + + codeAddr += opcodeSizes[opcode]; + } + + return CEE_ILLEGAL; +} + +/*****************************************************************************/ +// Checks whether the opcode is a valid opcode for volatile. and unaligned. prefixes + +static void impValidateMemoryAccessOpcode(const BYTE* codeAddr, const BYTE* codeEndp, bool volatilePrefix) +{ + OPCODE opcode = impGetNonPrefixOpcode(codeAddr, codeEndp); + + if (!( + // Opcode of all ldind and stdind happen to be in continuous, except stind.i. + ((CEE_LDIND_I1 <= opcode) && (opcode <= CEE_STIND_R8)) || (opcode == CEE_STIND_I) || + (opcode == CEE_LDFLD) || (opcode == CEE_STFLD) || (opcode == CEE_LDOBJ) || (opcode == CEE_STOBJ) || + (opcode == CEE_INITBLK) || (opcode == CEE_CPBLK) || + // volatile. prefix is allowed with the ldsfld and stsfld + (volatilePrefix && ((opcode == CEE_LDSFLD) || (opcode == CEE_STSFLD))))) + { + BADCODE("Invalid opcode for unaligned. or volatile. prefix"); + } +} + +/*****************************************************************************/ + +#ifdef DEBUG + +#undef RETURN // undef contracts RETURN macro + +enum controlFlow_t +{ + NEXT, + CALL, + RETURN, + THROW, + BRANCH, + COND_BRANCH, + BREAK, + PHI, + META, +}; + +const static controlFlow_t controlFlow[] = { +#define OPDEF(c, s, pop, push, args, type, l, s1, s2, flow) flow, +#include "opcode.def" +#undef OPDEF +}; + +#endif // DEBUG + +/***************************************************************************** + * Determine the result type of an arithemetic operation + * On 64-bit inserts upcasts when native int is mixed with int32 + */ +var_types Compiler::impGetByRefResultType(genTreeOps oper, bool fUnsigned, GenTreePtr* pOp1, GenTreePtr* pOp2) +{ + var_types type = TYP_UNDEF; + GenTreePtr op1 = *pOp1, op2 = *pOp2; + + // Arithemetic operations are generally only allowed with + // primitive types, but certain operations are allowed + // with byrefs + + if ((oper == GT_SUB) && (genActualType(op1->TypeGet()) == TYP_BYREF || genActualType(op2->TypeGet()) == TYP_BYREF)) + { + if ((genActualType(op1->TypeGet()) == TYP_BYREF) && (genActualType(op2->TypeGet()) == TYP_BYREF)) + { + // byref1-byref2 => gives a native int + type = TYP_I_IMPL; + } + else if (genActualTypeIsIntOrI(op1->TypeGet()) && (genActualType(op2->TypeGet()) == TYP_BYREF)) + { + // [native] int - byref => gives a native int + + // + // The reason is that it is possible, in managed C++, + // to have a tree like this: + // + // - + // / \ + // / \ + // / \ + // / \ + // const(h) int addr byref + // + // <BUGNUM> VSW 318822 </BUGNUM> + // + // So here we decide to make the resulting type to be a native int. + CLANG_FORMAT_COMMENT_ANCHOR; + +#ifdef _TARGET_64BIT_ + if (genActualType(op1->TypeGet()) != TYP_I_IMPL) + { + // insert an explicit upcast + op1 = *pOp1 = gtNewCastNode(TYP_I_IMPL, op1, (var_types)(fUnsigned ? TYP_U_IMPL : TYP_I_IMPL)); + } +#endif // _TARGET_64BIT_ + + type = TYP_I_IMPL; + } + else + { + // byref - [native] int => gives a byref + assert(genActualType(op1->TypeGet()) == TYP_BYREF && genActualTypeIsIntOrI(op2->TypeGet())); + +#ifdef _TARGET_64BIT_ + if ((genActualType(op2->TypeGet()) != TYP_I_IMPL)) + { + // insert an explicit upcast + op2 = *pOp2 = gtNewCastNode(TYP_I_IMPL, op2, (var_types)(fUnsigned ? TYP_U_IMPL : TYP_I_IMPL)); + } +#endif // _TARGET_64BIT_ + + type = TYP_BYREF; + } + } + else if ((oper == GT_ADD) && + (genActualType(op1->TypeGet()) == TYP_BYREF || genActualType(op2->TypeGet()) == TYP_BYREF)) + { + // byref + [native] int => gives a byref + // (or) + // [native] int + byref => gives a byref + + // only one can be a byref : byref op byref not allowed + assert(genActualType(op1->TypeGet()) != TYP_BYREF || genActualType(op2->TypeGet()) != TYP_BYREF); + assert(genActualTypeIsIntOrI(op1->TypeGet()) || genActualTypeIsIntOrI(op2->TypeGet())); + +#ifdef _TARGET_64BIT_ + if (genActualType(op2->TypeGet()) == TYP_BYREF) + { + if (genActualType(op1->TypeGet()) != TYP_I_IMPL) + { + // insert an explicit upcast + op1 = *pOp1 = gtNewCastNode(TYP_I_IMPL, op1, (var_types)(fUnsigned ? TYP_U_IMPL : TYP_I_IMPL)); + } + } + else if (genActualType(op2->TypeGet()) != TYP_I_IMPL) + { + // insert an explicit upcast + op2 = *pOp2 = gtNewCastNode(TYP_I_IMPL, op2, (var_types)(fUnsigned ? TYP_U_IMPL : TYP_I_IMPL)); + } +#endif // _TARGET_64BIT_ + + type = TYP_BYREF; + } +#ifdef _TARGET_64BIT_ + else if (genActualType(op1->TypeGet()) == TYP_I_IMPL || genActualType(op2->TypeGet()) == TYP_I_IMPL) + { + assert(!varTypeIsFloating(op1->gtType) && !varTypeIsFloating(op2->gtType)); + + // int + long => gives long + // long + int => gives long + // we get this because in the IL the long isn't Int64, it's just IntPtr + + if (genActualType(op1->TypeGet()) != TYP_I_IMPL) + { + // insert an explicit upcast + op1 = *pOp1 = gtNewCastNode(TYP_I_IMPL, op1, (var_types)(fUnsigned ? TYP_U_IMPL : TYP_I_IMPL)); + } + else if (genActualType(op2->TypeGet()) != TYP_I_IMPL) + { + // insert an explicit upcast + op2 = *pOp2 = gtNewCastNode(TYP_I_IMPL, op2, (var_types)(fUnsigned ? TYP_U_IMPL : TYP_I_IMPL)); + } + + type = TYP_I_IMPL; + } +#else // 32-bit TARGET + else if (genActualType(op1->TypeGet()) == TYP_LONG || genActualType(op2->TypeGet()) == TYP_LONG) + { + assert(!varTypeIsFloating(op1->gtType) && !varTypeIsFloating(op2->gtType)); + + // int + long => gives long + // long + int => gives long + + type = TYP_LONG; + } +#endif // _TARGET_64BIT_ + else + { + // int + int => gives an int + assert(genActualType(op1->TypeGet()) != TYP_BYREF && genActualType(op2->TypeGet()) != TYP_BYREF); + + assert(genActualType(op1->TypeGet()) == genActualType(op2->TypeGet()) || + varTypeIsFloating(op1->gtType) && varTypeIsFloating(op2->gtType)); + + type = genActualType(op1->gtType); + +#if FEATURE_X87_DOUBLES + + // For x87, since we only have 1 size of registers, prefer double + // For everybody else, be more precise + if (type == TYP_FLOAT) + type = TYP_DOUBLE; + +#else // !FEATURE_X87_DOUBLES + + // If both operands are TYP_FLOAT, then leave it as TYP_FLOAT. + // Otherwise, turn floats into doubles + if ((type == TYP_FLOAT) && (genActualType(op2->gtType) != TYP_FLOAT)) + { + assert(genActualType(op2->gtType) == TYP_DOUBLE); + type = TYP_DOUBLE; + } + +#endif // FEATURE_X87_DOUBLES + } + +#if FEATURE_X87_DOUBLES + assert(type == TYP_BYREF || type == TYP_DOUBLE || type == TYP_LONG || type == TYP_INT); +#else // FEATURE_X87_DOUBLES + assert(type == TYP_BYREF || type == TYP_DOUBLE || type == TYP_FLOAT || type == TYP_LONG || type == TYP_INT); +#endif // FEATURE_X87_DOUBLES + + return type; +} + +/***************************************************************************** + * Casting Helper Function to service both CEE_CASTCLASS and CEE_ISINST + * + * typeRef contains the token, op1 to contain the value being cast, + * and op2 to contain code that creates the type handle corresponding to typeRef + * isCastClass = true means CEE_CASTCLASS, false means CEE_ISINST + */ +GenTreePtr Compiler::impCastClassOrIsInstToTree(GenTreePtr op1, + GenTreePtr op2, + CORINFO_RESOLVED_TOKEN* pResolvedToken, + bool isCastClass) +{ + bool expandInline; + + assert(op1->TypeGet() == TYP_REF); + + CorInfoHelpFunc helper = info.compCompHnd->getCastingHelper(pResolvedToken, isCastClass); + + if (isCastClass) + { + // We only want to expand inline the normal CHKCASTCLASS helper; + expandInline = (helper == CORINFO_HELP_CHKCASTCLASS); + } + else + { + if (helper == CORINFO_HELP_ISINSTANCEOFCLASS) + { + // Get the Class Handle abd class attributes for the type we are casting to + // + DWORD flags = info.compCompHnd->getClassAttribs(pResolvedToken->hClass); + + // + // If the class handle is marked as final we can also expand the IsInst check inline + // + expandInline = ((flags & CORINFO_FLG_FINAL) != 0); + + // + // But don't expand inline these two cases + // + if (flags & CORINFO_FLG_MARSHAL_BYREF) + { + expandInline = false; + } + else if (flags & CORINFO_FLG_CONTEXTFUL) + { + expandInline = false; + } + } + else + { + // + // We can't expand inline any other helpers + // + expandInline = false; + } + } + + if (expandInline) + { + if (compCurBB->isRunRarely()) + { + expandInline = false; // not worth the code expansion in a rarely run block + } + + if ((op1->gtFlags & GTF_GLOB_EFFECT) && lvaHaveManyLocals()) + { + expandInline = false; // not worth creating an untracked local variable + } + } + + if (!expandInline) + { + // If we CSE this class handle we prevent assertionProp from making SubType assertions + // so instead we force the CSE logic to not consider CSE-ing this class handle. + // + op2->gtFlags |= GTF_DONT_CSE; + + return gtNewHelperCallNode(helper, TYP_REF, 0, gtNewArgList(op2, op1)); + } + + impSpillSideEffects(true, CHECK_SPILL_ALL DEBUGARG("bubbling QMark2")); + + GenTreePtr temp; + GenTreePtr condMT; + // + // expand the methodtable match: + // + // condMT ==> GT_NE + // / \ + // GT_IND op2 (typically CNS_INT) + // | + // op1Copy + // + + // This can replace op1 with a GT_COMMA that evaluates op1 into a local + // + op1 = impCloneExpr(op1, &temp, NO_CLASS_HANDLE, (unsigned)CHECK_SPILL_ALL, nullptr DEBUGARG("CASTCLASS eval op1")); + // + // op1 is now known to be a non-complex tree + // thus we can use gtClone(op1) from now on + // + + GenTreePtr op2Var = op2; + if (isCastClass) + { + op2Var = fgInsertCommaFormTemp(&op2); + lvaTable[op2Var->AsLclVarCommon()->GetLclNum()].lvIsCSE = true; + } + temp = gtNewOperNode(GT_IND, TYP_I_IMPL, temp); + temp->gtFlags |= GTF_EXCEPT; + condMT = gtNewOperNode(GT_NE, TYP_INT, temp, op2); + + GenTreePtr condNull; + // + // expand the null check: + // + // condNull ==> GT_EQ + // / \ + // op1Copy CNS_INT + // null + // + condNull = gtNewOperNode(GT_EQ, TYP_INT, gtClone(op1), gtNewIconNode(0, TYP_REF)); + + // + // expand the true and false trees for the condMT + // + GenTreePtr condFalse = gtClone(op1); + GenTreePtr condTrue; + if (isCastClass) + { + // + // use the special helper that skips the cases checked by our inlined cast + // + helper = CORINFO_HELP_CHKCASTCLASS_SPECIAL; + + condTrue = gtNewHelperCallNode(helper, TYP_REF, 0, gtNewArgList(op2Var, gtClone(op1))); + } + else + { + condTrue = gtNewIconNode(0, TYP_REF); + } + +#define USE_QMARK_TREES + +#ifdef USE_QMARK_TREES + GenTreePtr qmarkMT; + // + // Generate first QMARK - COLON tree + // + // qmarkMT ==> GT_QMARK + // / \ + // condMT GT_COLON + // / \ + // condFalse condTrue + // + temp = new (this, GT_COLON) GenTreeColon(TYP_REF, condTrue, condFalse); + qmarkMT = gtNewQmarkNode(TYP_REF, condMT, temp); + condMT->gtFlags |= GTF_RELOP_QMARK; + + GenTreePtr qmarkNull; + // + // Generate second QMARK - COLON tree + // + // qmarkNull ==> GT_QMARK + // / \ + // condNull GT_COLON + // / \ + // qmarkMT op1Copy + // + temp = new (this, GT_COLON) GenTreeColon(TYP_REF, gtClone(op1), qmarkMT); + qmarkNull = gtNewQmarkNode(TYP_REF, condNull, temp); + qmarkNull->gtFlags |= GTF_QMARK_CAST_INSTOF; + condNull->gtFlags |= GTF_RELOP_QMARK; + + // Make QMark node a top level node by spilling it. + unsigned tmp = lvaGrabTemp(true DEBUGARG("spilling QMark2")); + impAssignTempGen(tmp, qmarkNull, (unsigned)CHECK_SPILL_NONE); + return gtNewLclvNode(tmp, TYP_REF); +#endif +} + +#ifndef DEBUG +#define assertImp(cond) ((void)0) +#else +#define assertImp(cond) \ + do \ + { \ + if (!(cond)) \ + { \ + const int cchAssertImpBuf = 600; \ + char* assertImpBuf = (char*)alloca(cchAssertImpBuf); \ + _snprintf_s(assertImpBuf, cchAssertImpBuf, cchAssertImpBuf - 1, \ + "%s : Possibly bad IL with CEE_%s at offset %04Xh (op1=%s op2=%s stkDepth=%d)", #cond, \ + impCurOpcName, impCurOpcOffs, op1 ? varTypeName(op1->TypeGet()) : "NULL", \ + op2 ? varTypeName(op2->TypeGet()) : "NULL", verCurrentState.esStackDepth); \ + assertAbort(assertImpBuf, __FILE__, __LINE__); \ + } \ + } while (0) +#endif // DEBUG + +#ifdef _PREFAST_ +#pragma warning(push) +#pragma warning(disable : 21000) // Suppress PREFast warning about overly large function +#endif +/***************************************************************************** + * Import the instr for the given basic block + */ +void Compiler::impImportBlockCode(BasicBlock* block) +{ +#define _impResolveToken(kind) impResolveToken(codeAddr, &resolvedToken, kind) + +#ifdef DEBUG + + if (verbose) + { + printf("\nImporting BB%02u (PC=%03u) of '%s'", block->bbNum, block->bbCodeOffs, info.compFullName); + } +#endif + + unsigned nxtStmtIndex = impInitBlockLineInfo(); + IL_OFFSET nxtStmtOffs; + + GenTreePtr arrayNodeFrom, arrayNodeTo, arrayNodeToIndex; + bool expandInline; + CorInfoHelpFunc helper; + CorInfoIsAccessAllowedResult accessAllowedResult; + CORINFO_HELPER_DESC calloutHelper; + const BYTE* lastLoadToken = nullptr; + + // reject cyclic constraints + if (tiVerificationNeeded) + { + Verify(!info.hasCircularClassConstraints, "Method parent has circular class type parameter constraints."); + Verify(!info.hasCircularMethodConstraints, "Method has circular method type parameter constraints."); + } + + /* Get the tree list started */ + + impBeginTreeList(); + + /* Walk the opcodes that comprise the basic block */ + + const BYTE* codeAddr = info.compCode + block->bbCodeOffs; + const BYTE* codeEndp = info.compCode + block->bbCodeOffsEnd; + + IL_OFFSET opcodeOffs = block->bbCodeOffs; + IL_OFFSET lastSpillOffs = opcodeOffs; + + signed jmpDist; + + /* remember the start of the delegate creation sequence (used for verification) */ + const BYTE* delegateCreateStart = nullptr; + + int prefixFlags = 0; + bool explicitTailCall, constraintCall, readonlyCall; + + bool insertLdloc = false; // set by CEE_DUP and cleared by following store + typeInfo tiRetVal; + + unsigned numArgs = info.compArgsCount; + + /* Now process all the opcodes in the block */ + + var_types callTyp = TYP_COUNT; + OPCODE prevOpcode = CEE_ILLEGAL; + + if (block->bbCatchTyp) + { + if (info.compStmtOffsetsImplicit & ICorDebugInfo::CALL_SITE_BOUNDARIES) + { + impCurStmtOffsSet(block->bbCodeOffs); + } + + // We will spill the GT_CATCH_ARG and the input of the BB_QMARK block + // to a temp. This is a trade off for code simplicity + impSpillSpecialSideEff(); + } + + while (codeAddr < codeEndp) + { + bool usingReadyToRunHelper = false; + CORINFO_RESOLVED_TOKEN resolvedToken; + CORINFO_RESOLVED_TOKEN constrainedResolvedToken; + CORINFO_CALL_INFO callInfo; + CORINFO_FIELD_INFO fieldInfo; + + tiRetVal = typeInfo(); // Default type info + + //--------------------------------------------------------------------- + + /* We need to restrict the max tree depth as many of the Compiler + functions are recursive. We do this by spilling the stack */ + + if (verCurrentState.esStackDepth) + { + /* Has it been a while since we last saw a non-empty stack (which + guarantees that the tree depth isnt accumulating. */ + + if ((opcodeOffs - lastSpillOffs) > 200) + { + impSpillStackEnsure(); + lastSpillOffs = opcodeOffs; + } + } + else + { + lastSpillOffs = opcodeOffs; + impBoxTempInUse = false; // nothing on the stack, box temp OK to use again + } + + /* Compute the current instr offset */ + + opcodeOffs = (IL_OFFSET)(codeAddr - info.compCode); + +#if defined(DEBUGGING_SUPPORT) || defined(DEBUG) + +#ifndef DEBUG + if (opts.compDbgInfo) +#endif + { + if (!compIsForInlining()) + { + nxtStmtOffs = + (nxtStmtIndex < info.compStmtOffsetsCount) ? info.compStmtOffsets[nxtStmtIndex] : BAD_IL_OFFSET; + + /* Have we reached the next stmt boundary ? */ + + if (nxtStmtOffs != BAD_IL_OFFSET && opcodeOffs >= nxtStmtOffs) + { + assert(nxtStmtOffs == info.compStmtOffsets[nxtStmtIndex]); + + if (verCurrentState.esStackDepth != 0 && opts.compDbgCode) + { + /* We need to provide accurate IP-mapping at this point. + So spill anything on the stack so that it will form + gtStmts with the correct stmt offset noted */ + + impSpillStackEnsure(true); + } + + // Has impCurStmtOffs been reported in any tree? + + if (impCurStmtOffs != BAD_IL_OFFSET && opts.compDbgCode) + { + GenTreePtr placeHolder = new (this, GT_NO_OP) GenTree(GT_NO_OP, TYP_VOID); + impAppendTree(placeHolder, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); + + assert(impCurStmtOffs == BAD_IL_OFFSET); + } + + if (impCurStmtOffs == BAD_IL_OFFSET) + { + /* Make sure that nxtStmtIndex is in sync with opcodeOffs. + If opcodeOffs has gone past nxtStmtIndex, catch up */ + + while ((nxtStmtIndex + 1) < info.compStmtOffsetsCount && + info.compStmtOffsets[nxtStmtIndex + 1] <= opcodeOffs) + { + nxtStmtIndex++; + } + + /* Go to the new stmt */ + + impCurStmtOffsSet(info.compStmtOffsets[nxtStmtIndex]); + + /* Update the stmt boundary index */ + + nxtStmtIndex++; + assert(nxtStmtIndex <= info.compStmtOffsetsCount); + + /* Are there any more line# entries after this one? */ + + if (nxtStmtIndex < info.compStmtOffsetsCount) + { + /* Remember where the next line# starts */ + + nxtStmtOffs = info.compStmtOffsets[nxtStmtIndex]; + } + else + { + /* No more line# entries */ + + nxtStmtOffs = BAD_IL_OFFSET; + } + } + } + else if ((info.compStmtOffsetsImplicit & ICorDebugInfo::STACK_EMPTY_BOUNDARIES) && + (verCurrentState.esStackDepth == 0)) + { + /* At stack-empty locations, we have already added the tree to + the stmt list with the last offset. We just need to update + impCurStmtOffs + */ + + impCurStmtOffsSet(opcodeOffs); + } + else if ((info.compStmtOffsetsImplicit & ICorDebugInfo::CALL_SITE_BOUNDARIES) && + impOpcodeIsCallSiteBoundary(prevOpcode)) + { + /* Make sure we have a type cached */ + assert(callTyp != TYP_COUNT); + + if (callTyp == TYP_VOID) + { + impCurStmtOffsSet(opcodeOffs); + } + else if (opts.compDbgCode) + { + impSpillStackEnsure(true); + impCurStmtOffsSet(opcodeOffs); + } + } + else if ((info.compStmtOffsetsImplicit & ICorDebugInfo::NOP_BOUNDARIES) && (prevOpcode == CEE_NOP)) + { + if (opts.compDbgCode) + { + impSpillStackEnsure(true); + } + + impCurStmtOffsSet(opcodeOffs); + } + + assert(impCurStmtOffs == BAD_IL_OFFSET || nxtStmtOffs == BAD_IL_OFFSET || + jitGetILoffs(impCurStmtOffs) <= nxtStmtOffs); + } + } + +#endif // defined(DEBUGGING_SUPPORT) || defined(DEBUG) + + CORINFO_CLASS_HANDLE clsHnd = DUMMY_INIT(NULL); + CORINFO_CLASS_HANDLE ldelemClsHnd = DUMMY_INIT(NULL); + CORINFO_CLASS_HANDLE stelemClsHnd = DUMMY_INIT(NULL); + + var_types lclTyp, ovflType = TYP_UNKNOWN; + GenTreePtr op1 = DUMMY_INIT(NULL); + GenTreePtr op2 = DUMMY_INIT(NULL); + GenTreeArgList* args = nullptr; // What good do these "DUMMY_INIT"s do? + GenTreePtr newObjThisPtr = DUMMY_INIT(NULL); + bool uns = DUMMY_INIT(false); + + /* Get the next opcode and the size of its parameters */ + + OPCODE opcode = (OPCODE)getU1LittleEndian(codeAddr); + codeAddr += sizeof(__int8); + +#ifdef DEBUG + impCurOpcOffs = (IL_OFFSET)(codeAddr - info.compCode - 1); + JITDUMP("\n [%2u] %3u (0x%03x) ", verCurrentState.esStackDepth, impCurOpcOffs, impCurOpcOffs); +#endif + + DECODE_OPCODE: + + // Return if any previous code has caused inline to fail. + if (compDonotInline()) + { + return; + } + + /* Get the size of additional parameters */ + + signed int sz = opcodeSizes[opcode]; + +#ifdef DEBUG + clsHnd = NO_CLASS_HANDLE; + lclTyp = TYP_COUNT; + callTyp = TYP_COUNT; + + impCurOpcOffs = (IL_OFFSET)(codeAddr - info.compCode - 1); + impCurOpcName = opcodeNames[opcode]; + + if (verbose && (opcode != CEE_PREFIX1)) + { + printf("%s", impCurOpcName); + } + + /* Use assertImp() to display the opcode */ + + op1 = op2 = nullptr; +#endif + + /* See what kind of an opcode we have, then */ + + unsigned mflags = 0; + unsigned clsFlags = 0; + + switch (opcode) + { + unsigned lclNum; + var_types type; + + GenTreePtr op3; + genTreeOps oper; + unsigned size; + + int val; + + CORINFO_SIG_INFO sig; + unsigned flags; + IL_OFFSET jmpAddr; + bool ovfl, unordered, callNode; + bool ldstruct; + CORINFO_CLASS_HANDLE tokenType; + + union { + int intVal; + float fltVal; + __int64 lngVal; + double dblVal; + } cval; + + case CEE_PREFIX1: + opcode = (OPCODE)(getU1LittleEndian(codeAddr) + 256); + codeAddr += sizeof(__int8); + opcodeOffs = (IL_OFFSET)(codeAddr - info.compCode); + goto DECODE_OPCODE; + + SPILL_APPEND: + + /* Append 'op1' to the list of statements */ + impAppendTree(op1, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs); + goto DONE_APPEND; + + APPEND: + + /* Append 'op1' to the list of statements */ + + impAppendTree(op1, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); + goto DONE_APPEND; + + DONE_APPEND: + +#ifdef DEBUG + // Remember at which BC offset the tree was finished + impNoteLastILoffs(); +#endif + break; + + case CEE_LDNULL: + impPushNullObjRefOnStack(); + break; + + case CEE_LDC_I4_M1: + case CEE_LDC_I4_0: + case CEE_LDC_I4_1: + case CEE_LDC_I4_2: + case CEE_LDC_I4_3: + case CEE_LDC_I4_4: + case CEE_LDC_I4_5: + case CEE_LDC_I4_6: + case CEE_LDC_I4_7: + case CEE_LDC_I4_8: + cval.intVal = (opcode - CEE_LDC_I4_0); + assert(-1 <= cval.intVal && cval.intVal <= 8); + goto PUSH_I4CON; + + case CEE_LDC_I4_S: + cval.intVal = getI1LittleEndian(codeAddr); + goto PUSH_I4CON; + case CEE_LDC_I4: + cval.intVal = getI4LittleEndian(codeAddr); + goto PUSH_I4CON; + PUSH_I4CON: + JITDUMP(" %d", cval.intVal); + impPushOnStack(gtNewIconNode(cval.intVal), typeInfo(TI_INT)); + break; + + case CEE_LDC_I8: + cval.lngVal = getI8LittleEndian(codeAddr); + JITDUMP(" 0x%016llx", cval.lngVal); + impPushOnStack(gtNewLconNode(cval.lngVal), typeInfo(TI_LONG)); + break; + + case CEE_LDC_R8: + cval.dblVal = getR8LittleEndian(codeAddr); + JITDUMP(" %#.17g", cval.dblVal); + impPushOnStack(gtNewDconNode(cval.dblVal), typeInfo(TI_DOUBLE)); + break; + + case CEE_LDC_R4: + cval.dblVal = getR4LittleEndian(codeAddr); + JITDUMP(" %#.17g", cval.dblVal); + { + GenTreePtr cnsOp = gtNewDconNode(cval.dblVal); +#if !FEATURE_X87_DOUBLES + // X87 stack doesn't differentiate between float/double + // so R4 is treated as R8, but everybody else does + cnsOp->gtType = TYP_FLOAT; +#endif // FEATURE_X87_DOUBLES + impPushOnStack(cnsOp, typeInfo(TI_DOUBLE)); + } + break; + + case CEE_LDSTR: + + if (compIsForInlining()) + { + if (impInlineInfo->inlineCandidateInfo->dwRestrictions & INLINE_NO_CALLEE_LDSTR) + { + compInlineResult->NoteFatal(InlineObservation::CALLSITE_HAS_LDSTR_RESTRICTION); + return; + } + } + + val = getU4LittleEndian(codeAddr); + JITDUMP(" %08X", val); + if (tiVerificationNeeded) + { + Verify(info.compCompHnd->isValidStringRef(info.compScopeHnd, val), "bad string"); + tiRetVal = typeInfo(TI_REF, impGetStringClass()); + } + impPushOnStack(gtNewSconNode(val, info.compScopeHnd), tiRetVal); + + break; + + case CEE_LDARG: + lclNum = getU2LittleEndian(codeAddr); + JITDUMP(" %u", lclNum); + impLoadArg(lclNum, opcodeOffs + sz + 1); + break; + + case CEE_LDARG_S: + lclNum = getU1LittleEndian(codeAddr); + JITDUMP(" %u", lclNum); + impLoadArg(lclNum, opcodeOffs + sz + 1); + break; + + case CEE_LDARG_0: + case CEE_LDARG_1: + case CEE_LDARG_2: + case CEE_LDARG_3: + lclNum = (opcode - CEE_LDARG_0); + assert(lclNum >= 0 && lclNum < 4); + impLoadArg(lclNum, opcodeOffs + sz + 1); + break; + + case CEE_LDLOC: + lclNum = getU2LittleEndian(codeAddr); + JITDUMP(" %u", lclNum); + impLoadLoc(lclNum, opcodeOffs + sz + 1); + break; + + case CEE_LDLOC_S: + lclNum = getU1LittleEndian(codeAddr); + JITDUMP(" %u", lclNum); + impLoadLoc(lclNum, opcodeOffs + sz + 1); + break; + + case CEE_LDLOC_0: + case CEE_LDLOC_1: + case CEE_LDLOC_2: + case CEE_LDLOC_3: + lclNum = (opcode - CEE_LDLOC_0); + assert(lclNum >= 0 && lclNum < 4); + impLoadLoc(lclNum, opcodeOffs + sz + 1); + break; + + case CEE_STARG: + lclNum = getU2LittleEndian(codeAddr); + goto STARG; + + case CEE_STARG_S: + lclNum = getU1LittleEndian(codeAddr); + STARG: + JITDUMP(" %u", lclNum); + + if (tiVerificationNeeded) + { + Verify(lclNum < info.compILargsCount, "bad arg num"); + } + + if (compIsForInlining()) + { + op1 = impInlineFetchArg(lclNum, impInlineInfo->inlArgInfo, impInlineInfo->lclVarInfo); + noway_assert(op1->gtOper == GT_LCL_VAR); + lclNum = op1->AsLclVar()->gtLclNum; + + goto VAR_ST_VALID; + } + + lclNum = compMapILargNum(lclNum); // account for possible hidden param + assertImp(lclNum < numArgs); + + if (lclNum == info.compThisArg) + { + lclNum = lvaArg0Var; + } + lvaTable[lclNum].lvArgWrite = 1; + + if (tiVerificationNeeded) + { + typeInfo& tiLclVar = lvaTable[lclNum].lvVerTypeInfo; + Verify(tiCompatibleWith(impStackTop().seTypeInfo, NormaliseForStack(tiLclVar), true), + "type mismatch"); + + if (verTrackObjCtorInitState && (verCurrentState.thisInitialized != TIS_Init)) + { + Verify(!tiLclVar.IsThisPtr(), "storing to uninit this ptr"); + } + } + + goto VAR_ST; + + case CEE_STLOC: + lclNum = getU2LittleEndian(codeAddr); + JITDUMP(" %u", lclNum); + goto LOC_ST; + + case CEE_STLOC_S: + lclNum = getU1LittleEndian(codeAddr); + JITDUMP(" %u", lclNum); + goto LOC_ST; + + case CEE_STLOC_0: + case CEE_STLOC_1: + case CEE_STLOC_2: + case CEE_STLOC_3: + lclNum = (opcode - CEE_STLOC_0); + assert(lclNum >= 0 && lclNum < 4); + + LOC_ST: + if (tiVerificationNeeded) + { + Verify(lclNum < info.compMethodInfo->locals.numArgs, "bad local num"); + Verify(tiCompatibleWith(impStackTop().seTypeInfo, + NormaliseForStack(lvaTable[lclNum + numArgs].lvVerTypeInfo), true), + "type mismatch"); + } + + if (compIsForInlining()) + { + lclTyp = impInlineInfo->lclVarInfo[lclNum + impInlineInfo->argCnt].lclTypeInfo; + + /* Have we allocated a temp for this local? */ + + lclNum = impInlineFetchLocal(lclNum DEBUGARG("Inline stloc first use temp")); + + goto _PopValue; + } + + lclNum += numArgs; + + VAR_ST: + + if (lclNum >= info.compLocalsCount && lclNum != lvaArg0Var) + { + assert(!tiVerificationNeeded); // We should have thrown the VerificationException before. + BADCODE("Bad IL"); + } + + VAR_ST_VALID: + + /* if it is a struct assignment, make certain we don't overflow the buffer */ + assert(lclTyp != TYP_STRUCT || lvaLclSize(lclNum) >= info.compCompHnd->getClassSize(clsHnd)); + + if (lvaTable[lclNum].lvNormalizeOnLoad()) + { + lclTyp = lvaGetRealType(lclNum); + } + else + { + lclTyp = lvaGetActualType(lclNum); + } + + _PopValue: + /* Pop the value being assigned */ + + { + StackEntry se = impPopStack(clsHnd); + op1 = se.val; + tiRetVal = se.seTypeInfo; + } + +#ifdef FEATURE_SIMD + if (varTypeIsSIMD(lclTyp) && (lclTyp != op1->TypeGet())) + { + assert(op1->TypeGet() == TYP_STRUCT); + op1->gtType = lclTyp; + } +#endif // FEATURE_SIMD + + op1 = impImplicitIorI4Cast(op1, lclTyp); + +#ifdef _TARGET_64BIT_ + // Downcast the TYP_I_IMPL into a 32-bit Int for x86 JIT compatiblity + if (varTypeIsI(op1->TypeGet()) && (genActualType(lclTyp) == TYP_INT)) + { + assert(!tiVerificationNeeded); // We should have thrown the VerificationException before. + op1 = gtNewCastNode(TYP_INT, op1, TYP_INT); + } +#endif // _TARGET_64BIT_ + + // We had better assign it a value of the correct type + assertImp( + genActualType(lclTyp) == genActualType(op1->gtType) || + genActualType(lclTyp) == TYP_I_IMPL && op1->IsVarAddr() || + (genActualType(lclTyp) == TYP_I_IMPL && (op1->gtType == TYP_BYREF || op1->gtType == TYP_REF)) || + (genActualType(op1->gtType) == TYP_I_IMPL && lclTyp == TYP_BYREF) || + (varTypeIsFloating(lclTyp) && varTypeIsFloating(op1->TypeGet())) || + ((genActualType(lclTyp) == TYP_BYREF) && genActualType(op1->TypeGet()) == TYP_REF)); + + /* If op1 is "&var" then its type is the transient "*" and it can + be used either as TYP_BYREF or TYP_I_IMPL */ + + if (op1->IsVarAddr()) + { + assertImp(genActualType(lclTyp) == TYP_I_IMPL || lclTyp == TYP_BYREF); + + /* When "&var" is created, we assume it is a byref. If it is + being assigned to a TYP_I_IMPL var, change the type to + prevent unnecessary GC info */ + + if (genActualType(lclTyp) == TYP_I_IMPL) + { + op1->gtType = TYP_I_IMPL; + } + } + + /* Filter out simple assignments to itself */ + + if (op1->gtOper == GT_LCL_VAR && lclNum == op1->gtLclVarCommon.gtLclNum) + { + if (insertLdloc) + { + // This is a sequence of (ldloc, dup, stloc). Can simplify + // to (ldloc, stloc). Goto LDVAR to reconstruct the ldloc node. + CLANG_FORMAT_COMMENT_ANCHOR; + +#ifdef DEBUG + if (tiVerificationNeeded) + { + assert( + typeInfo::AreEquivalent(tiRetVal, NormaliseForStack(lvaTable[lclNum].lvVerTypeInfo))); + } +#endif + + op1 = nullptr; + insertLdloc = false; + + impLoadVar(lclNum, opcodeOffs + sz + 1); + break; + } + else if (opts.compDbgCode) + { + op1 = gtNewNothingNode(); + goto SPILL_APPEND; + } + else + { + break; + } + } + + /* Create the assignment node */ + + op2 = gtNewLclvNode(lclNum, lclTyp, opcodeOffs + sz + 1); + + /* If the local is aliased, we need to spill calls and + indirections from the stack. */ + + if ((lvaTable[lclNum].lvAddrExposed || lvaTable[lclNum].lvHasLdAddrOp) && + verCurrentState.esStackDepth > 0) + { + impSpillSideEffects(false, (unsigned)CHECK_SPILL_ALL DEBUGARG("Local could be aliased")); + } + + /* Spill any refs to the local from the stack */ + + impSpillLclRefs(lclNum); + +#if !FEATURE_X87_DOUBLES + // We can generate an assignment to a TYP_FLOAT from a TYP_DOUBLE + // We insert a cast to the dest 'op2' type + // + if ((op1->TypeGet() != op2->TypeGet()) && varTypeIsFloating(op1->gtType) && + varTypeIsFloating(op2->gtType)) + { + op1 = gtNewCastNode(op2->TypeGet(), op1, op2->TypeGet()); + } +#endif // !FEATURE_X87_DOUBLES + + if (varTypeIsStruct(lclTyp)) + { + op1 = impAssignStruct(op2, op1, clsHnd, (unsigned)CHECK_SPILL_ALL); + } + else + { + // The code generator generates GC tracking information + // based on the RHS of the assignment. Later the LHS (which is + // is a BYREF) gets used and the emitter checks that that variable + // is being tracked. It is not (since the RHS was an int and did + // not need tracking). To keep this assert happy, we change the RHS + if (lclTyp == TYP_BYREF && !varTypeIsGC(op1->gtType)) + { + op1->gtType = TYP_BYREF; + } + op1 = gtNewAssignNode(op2, op1); + } + + /* If insertLdloc is true, then we need to insert a ldloc following the + stloc. This is done when converting a (dup, stloc) sequence into + a (stloc, ldloc) sequence. */ + + if (insertLdloc) + { + // From SPILL_APPEND + impAppendTree(op1, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs); + +#ifdef DEBUG + // From DONE_APPEND + impNoteLastILoffs(); +#endif + op1 = nullptr; + insertLdloc = false; + + impLoadVar(lclNum, opcodeOffs + sz + 1, tiRetVal); + break; + } + + goto SPILL_APPEND; + + case CEE_LDLOCA: + lclNum = getU2LittleEndian(codeAddr); + goto LDLOCA; + + case CEE_LDLOCA_S: + lclNum = getU1LittleEndian(codeAddr); + LDLOCA: + JITDUMP(" %u", lclNum); + if (tiVerificationNeeded) + { + Verify(lclNum < info.compMethodInfo->locals.numArgs, "bad local num"); + Verify(info.compInitMem, "initLocals not set"); + } + + if (compIsForInlining()) + { + // Get the local type + lclTyp = impInlineInfo->lclVarInfo[lclNum + impInlineInfo->argCnt].lclTypeInfo; + + /* Have we allocated a temp for this local? */ + + lclNum = impInlineFetchLocal(lclNum DEBUGARG("Inline ldloca(s) first use temp")); + + op1 = gtNewLclvNode(lclNum, lvaGetActualType(lclNum)); + + goto _PUSH_ADRVAR; + } + + lclNum += numArgs; + assertImp(lclNum < info.compLocalsCount); + goto ADRVAR; + + case CEE_LDARGA: + lclNum = getU2LittleEndian(codeAddr); + goto LDARGA; + + case CEE_LDARGA_S: + lclNum = getU1LittleEndian(codeAddr); + LDARGA: + JITDUMP(" %u", lclNum); + Verify(lclNum < info.compILargsCount, "bad arg num"); + + if (compIsForInlining()) + { + // In IL, LDARGA(_S) is used to load the byref managed pointer of struct argument, + // followed by a ldfld to load the field. + + op1 = impInlineFetchArg(lclNum, impInlineInfo->inlArgInfo, impInlineInfo->lclVarInfo); + if (op1->gtOper != GT_LCL_VAR) + { + compInlineResult->NoteFatal(InlineObservation::CALLSITE_LDARGA_NOT_LOCAL_VAR); + return; + } + + assert(op1->gtOper == GT_LCL_VAR); + + goto _PUSH_ADRVAR; + } + + lclNum = compMapILargNum(lclNum); // account for possible hidden param + assertImp(lclNum < numArgs); + + if (lclNum == info.compThisArg) + { + lclNum = lvaArg0Var; + } + + goto ADRVAR; + + ADRVAR: + + op1 = gtNewLclvNode(lclNum, lvaGetActualType(lclNum), opcodeOffs + sz + 1); + + _PUSH_ADRVAR: + assert(op1->gtOper == GT_LCL_VAR); + + /* Note that this is supposed to create the transient type "*" + which may be used as a TYP_I_IMPL. However we catch places + where it is used as a TYP_I_IMPL and change the node if needed. + Thus we are pessimistic and may report byrefs in the GC info + where it was not absolutely needed, but it is safer this way. + */ + op1 = gtNewOperNode(GT_ADDR, TYP_BYREF, op1); + + // &aliasedVar doesnt need GTF_GLOB_REF, though alisasedVar does + assert((op1->gtFlags & GTF_GLOB_REF) == 0); + + tiRetVal = lvaTable[lclNum].lvVerTypeInfo; + if (tiVerificationNeeded) + { + // Don't allow taking address of uninit this ptr. + if (verTrackObjCtorInitState && (verCurrentState.thisInitialized != TIS_Init)) + { + Verify(!tiRetVal.IsThisPtr(), "address of uninit this ptr"); + } + + if (!tiRetVal.IsByRef()) + { + tiRetVal.MakeByRef(); + } + else + { + Verify(false, "byref to byref"); + } + } + + impPushOnStack(op1, tiRetVal); + break; + + case CEE_ARGLIST: + + if (!info.compIsVarArgs) + { + BADCODE("arglist in non-vararg method"); + } + + if (tiVerificationNeeded) + { + tiRetVal = typeInfo(TI_STRUCT, impGetRuntimeArgumentHandle()); + } + assertImp((info.compMethodInfo->args.callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_VARARG); + + /* The ARGLIST cookie is a hidden 'last' parameter, we have already + adjusted the arg count cos this is like fetching the last param */ + assertImp(0 < numArgs); + assert(lvaTable[lvaVarargsHandleArg].lvAddrExposed); + lclNum = lvaVarargsHandleArg; + op1 = gtNewLclvNode(lclNum, TYP_I_IMPL, opcodeOffs + sz + 1); + op1 = gtNewOperNode(GT_ADDR, TYP_BYREF, op1); + impPushOnStack(op1, tiRetVal); + break; + + case CEE_ENDFINALLY: + + if (compIsForInlining()) + { + assert(!"Shouldn't have exception handlers in the inliner!"); + compInlineResult->NoteFatal(InlineObservation::CALLEE_HAS_ENDFINALLY); + return; + } + + if (verCurrentState.esStackDepth > 0) + { + impEvalSideEffects(); + } + + if (info.compXcptnsCount == 0) + { + BADCODE("endfinally outside finally"); + } + + assert(verCurrentState.esStackDepth == 0); + + op1 = gtNewOperNode(GT_RETFILT, TYP_VOID, nullptr); + goto APPEND; + + case CEE_ENDFILTER: + + if (compIsForInlining()) + { + assert(!"Shouldn't have exception handlers in the inliner!"); + compInlineResult->NoteFatal(InlineObservation::CALLEE_HAS_ENDFILTER); + return; + } + + block->bbSetRunRarely(); // filters are rare + + if (info.compXcptnsCount == 0) + { + BADCODE("endfilter outside filter"); + } + + if (tiVerificationNeeded) + { + Verify(impStackTop().seTypeInfo.IsType(TI_INT), "bad endfilt arg"); + } + + op1 = impPopStack().val; + assertImp(op1->gtType == TYP_INT); + if (!bbInFilterILRange(block)) + { + BADCODE("EndFilter outside a filter handler"); + } + + /* Mark current bb as end of filter */ + + assert(compCurBB->bbFlags & BBF_DONT_REMOVE); + assert(compCurBB->bbJumpKind == BBJ_EHFILTERRET); + + /* Mark catch handler as successor */ + + op1 = gtNewOperNode(GT_RETFILT, op1->TypeGet(), op1); + if (verCurrentState.esStackDepth != 0) + { + verRaiseVerifyException(INDEBUG("stack must be 1 on end of filter") DEBUGARG(__FILE__) + DEBUGARG(__LINE__)); + } + goto APPEND; + + case CEE_RET: + prefixFlags &= ~PREFIX_TAILCALL; // ret without call before it + RET: + if (!impReturnInstruction(block, prefixFlags, opcode)) + { + return; // abort + } + else + { + break; + } + + case CEE_JMP: + + assert(!compIsForInlining()); + + if (tiVerificationNeeded) + { + Verify(false, "Invalid opcode: CEE_JMP"); + } + + if ((info.compFlags & CORINFO_FLG_SYNCH) || block->hasTryIndex() || block->hasHndIndex()) + { + /* CEE_JMP does not make sense in some "protected" regions. */ + + BADCODE("Jmp not allowed in protected region"); + } + + if (verCurrentState.esStackDepth != 0) + { + BADCODE("Stack must be empty after CEE_JMPs"); + } + + _impResolveToken(CORINFO_TOKENKIND_Method); + + JITDUMP(" %08X", resolvedToken.token); + + /* The signature of the target has to be identical to ours. + At least check that argCnt and returnType match */ + + eeGetMethodSig(resolvedToken.hMethod, &sig); + if (sig.numArgs != info.compMethodInfo->args.numArgs || + sig.retType != info.compMethodInfo->args.retType || + sig.callConv != info.compMethodInfo->args.callConv) + { + BADCODE("Incompatible target for CEE_JMPs"); + } + +#if defined(_TARGET_XARCH_) || defined(_TARGET_ARMARCH_) + + op1 = new (this, GT_JMP) GenTreeVal(GT_JMP, TYP_VOID, (size_t)resolvedToken.hMethod); + + /* Mark the basic block as being a JUMP instead of RETURN */ + + block->bbFlags |= BBF_HAS_JMP; + + /* Set this flag to make sure register arguments have a location assigned + * even if we don't use them inside the method */ + + compJmpOpUsed = true; + + fgNoStructPromotion = true; + + goto APPEND; + +#else // !_TARGET_XARCH_ && !_TARGET_ARMARCH_ + + // Import this just like a series of LDARGs + tail. + call + ret + + if (info.compIsVarArgs) + { + // For now we don't implement true tail calls, so this breaks varargs. + // So warn the user instead of generating bad code. + // This is a semi-temporary workaround for DevDiv 173860, until we can properly + // implement true tail calls. + IMPL_LIMITATION("varags + CEE_JMP doesn't work yet"); + } + + // First load up the arguments (0 - N) + for (unsigned argNum = 0; argNum < info.compILargsCount; argNum++) + { + impLoadArg(argNum, opcodeOffs + sz + 1); + } + + // Now generate the tail call + noway_assert(prefixFlags == 0); + prefixFlags = PREFIX_TAILCALL_EXPLICIT; + opcode = CEE_CALL; + + eeGetCallInfo(&resolvedToken, NULL, + combine(CORINFO_CALLINFO_ALLOWINSTPARAM, CORINFO_CALLINFO_SECURITYCHECKS), &callInfo); + + // All calls and delegates need a security callout. + impHandleAccessAllowed(callInfo.accessAllowed, &callInfo.callsiteCalloutHelper); + + callTyp = impImportCall(CEE_CALL, &resolvedToken, NULL, NULL, PREFIX_TAILCALL_EXPLICIT, &callInfo, + opcodeOffs); + + // And finish with the ret + goto RET; + +#endif // _TARGET_XARCH_ || _TARGET_ARMARCH_ + + case CEE_LDELEMA: + assertImp(sz == sizeof(unsigned)); + + _impResolveToken(CORINFO_TOKENKIND_Class); + + JITDUMP(" %08X", resolvedToken.token); + + ldelemClsHnd = resolvedToken.hClass; + + if (tiVerificationNeeded) + { + typeInfo tiArray = impStackTop(1).seTypeInfo; + typeInfo tiIndex = impStackTop().seTypeInfo; + + // As per ECMA 'index' specified can be either int32 or native int. + Verify(tiIndex.IsIntOrNativeIntType(), "bad index"); + + typeInfo arrayElemType = verMakeTypeInfo(ldelemClsHnd); + Verify(tiArray.IsNullObjRef() || + typeInfo::AreEquivalent(verGetArrayElemType(tiArray), arrayElemType), + "bad array"); + + tiRetVal = arrayElemType; + tiRetVal.MakeByRef(); + if (prefixFlags & PREFIX_READONLY) + { + tiRetVal.SetIsReadonlyByRef(); + } + + // an array interior pointer is always in the heap + tiRetVal.SetIsPermanentHomeByRef(); + } + + // If it's a value class array we just do a simple address-of + if (eeIsValueClass(ldelemClsHnd)) + { + CorInfoType cit = info.compCompHnd->getTypeForPrimitiveValueClass(ldelemClsHnd); + if (cit == CORINFO_TYPE_UNDEF) + { + lclTyp = TYP_STRUCT; + } + else + { + lclTyp = JITtype2varType(cit); + } + goto ARR_LD_POST_VERIFY; + } + + // Similarly, if its a readonly access, we can do a simple address-of + // without doing a runtime type-check + if (prefixFlags & PREFIX_READONLY) + { + lclTyp = TYP_REF; + goto ARR_LD_POST_VERIFY; + } + + // Otherwise we need the full helper function with run-time type check + op1 = impTokenToHandle(&resolvedToken); + if (op1 == nullptr) + { // compDonotInline() + return; + } + + args = gtNewArgList(op1); // Type + args = gtNewListNode(impPopStack().val, args); // index + args = gtNewListNode(impPopStack().val, args); // array + op1 = gtNewHelperCallNode(CORINFO_HELP_LDELEMA_REF, TYP_BYREF, GTF_EXCEPT, args); + + impPushOnStack(op1, tiRetVal); + break; + + // ldelem for reference and value types + case CEE_LDELEM: + assertImp(sz == sizeof(unsigned)); + + _impResolveToken(CORINFO_TOKENKIND_Class); + + JITDUMP(" %08X", resolvedToken.token); + + ldelemClsHnd = resolvedToken.hClass; + + if (tiVerificationNeeded) + { + typeInfo tiArray = impStackTop(1).seTypeInfo; + typeInfo tiIndex = impStackTop().seTypeInfo; + + // As per ECMA 'index' specified can be either int32 or native int. + Verify(tiIndex.IsIntOrNativeIntType(), "bad index"); + tiRetVal = verMakeTypeInfo(ldelemClsHnd); + + Verify(tiArray.IsNullObjRef() || tiCompatibleWith(verGetArrayElemType(tiArray), tiRetVal, false), + "type of array incompatible with type operand"); + tiRetVal.NormaliseForStack(); + } + + // If it's a reference type or generic variable type + // then just generate code as though it's a ldelem.ref instruction + if (!eeIsValueClass(ldelemClsHnd)) + { + lclTyp = TYP_REF; + opcode = CEE_LDELEM_REF; + } + else + { + CorInfoType jitTyp = info.compCompHnd->asCorInfoType(ldelemClsHnd); + lclTyp = JITtype2varType(jitTyp); + tiRetVal = verMakeTypeInfo(ldelemClsHnd); // precise type always needed for struct + tiRetVal.NormaliseForStack(); + } + goto ARR_LD_POST_VERIFY; + + case CEE_LDELEM_I1: + lclTyp = TYP_BYTE; + goto ARR_LD; + case CEE_LDELEM_I2: + lclTyp = TYP_SHORT; + goto ARR_LD; + case CEE_LDELEM_I: + lclTyp = TYP_I_IMPL; + goto ARR_LD; + + // Should be UINT, but since no platform widens 4->8 bytes it doesn't matter + // and treating it as TYP_INT avoids other asserts. + case CEE_LDELEM_U4: + lclTyp = TYP_INT; + goto ARR_LD; + + case CEE_LDELEM_I4: + lclTyp = TYP_INT; + goto ARR_LD; + case CEE_LDELEM_I8: + lclTyp = TYP_LONG; + goto ARR_LD; + case CEE_LDELEM_REF: + lclTyp = TYP_REF; + goto ARR_LD; + case CEE_LDELEM_R4: + lclTyp = TYP_FLOAT; + goto ARR_LD; + case CEE_LDELEM_R8: + lclTyp = TYP_DOUBLE; + goto ARR_LD; + case CEE_LDELEM_U1: + lclTyp = TYP_UBYTE; + goto ARR_LD; + case CEE_LDELEM_U2: + lclTyp = TYP_CHAR; + goto ARR_LD; + + ARR_LD: + + if (tiVerificationNeeded) + { + typeInfo tiArray = impStackTop(1).seTypeInfo; + typeInfo tiIndex = impStackTop().seTypeInfo; + + // As per ECMA 'index' specified can be either int32 or native int. + Verify(tiIndex.IsIntOrNativeIntType(), "bad index"); + if (tiArray.IsNullObjRef()) + { + if (lclTyp == TYP_REF) + { // we will say a deref of a null array yields a null ref + tiRetVal = typeInfo(TI_NULL); + } + else + { + tiRetVal = typeInfo(lclTyp); + } + } + else + { + tiRetVal = verGetArrayElemType(tiArray); + typeInfo arrayElemTi = typeInfo(lclTyp); +#ifdef _TARGET_64BIT_ + if (opcode == CEE_LDELEM_I) + { + arrayElemTi = typeInfo::nativeInt(); + } + + if (lclTyp != TYP_REF && lclTyp != TYP_STRUCT) + { + Verify(typeInfo::AreEquivalent(tiRetVal, arrayElemTi), "bad array"); + } + else +#endif // _TARGET_64BIT_ + { + Verify(tiRetVal.IsType(arrayElemTi.GetType()), "bad array"); + } + } + tiRetVal.NormaliseForStack(); + } + ARR_LD_POST_VERIFY: + + /* Pull the index value and array address */ + op2 = impPopStack().val; + op1 = impPopStack().val; + assertImp(op1->gtType == TYP_REF); + + /* Check for null pointer - in the inliner case we simply abort */ + + if (compIsForInlining()) + { + if (op1->gtOper == GT_CNS_INT) + { + compInlineResult->NoteFatal(InlineObservation::CALLEE_HAS_NULL_FOR_LDELEM); + return; + } + } + + op1 = impCheckForNullPointer(op1); + + /* Mark the block as containing an index expression */ + + if (op1->gtOper == GT_LCL_VAR) + { + if (op2->gtOper == GT_LCL_VAR || op2->gtOper == GT_CNS_INT || op2->gtOper == GT_ADD) + { + block->bbFlags |= BBF_HAS_IDX_LEN; + optMethodFlags |= OMF_HAS_ARRAYREF; + } + } + + /* Create the index node and push it on the stack */ + + op1 = gtNewIndexRef(lclTyp, op1, op2); + + ldstruct = (opcode == CEE_LDELEM && lclTyp == TYP_STRUCT); + + if ((opcode == CEE_LDELEMA) || ldstruct || + (ldelemClsHnd != DUMMY_INIT(NULL) && eeIsValueClass(ldelemClsHnd))) + { + assert(ldelemClsHnd != DUMMY_INIT(NULL)); + + // remember the element size + if (lclTyp == TYP_REF) + { + op1->gtIndex.gtIndElemSize = sizeof(void*); + } + else + { + // If ldElemClass is precisely a primitive type, use that, otherwise, preserve the struct type. + if (info.compCompHnd->getTypeForPrimitiveValueClass(ldelemClsHnd) == CORINFO_TYPE_UNDEF) + { + op1->gtIndex.gtStructElemClass = ldelemClsHnd; + } + assert(lclTyp != TYP_STRUCT || op1->gtIndex.gtStructElemClass != nullptr); + if (lclTyp == TYP_STRUCT) + { + size = info.compCompHnd->getClassSize(ldelemClsHnd); + op1->gtIndex.gtIndElemSize = size; + op1->gtType = lclTyp; + } + } + + if ((opcode == CEE_LDELEMA) || ldstruct) + { + // wrap it in a & + lclTyp = TYP_BYREF; + + op1 = gtNewOperNode(GT_ADDR, lclTyp, op1); + } + else + { + assert(lclTyp != TYP_STRUCT); + } + } + + if (ldstruct) + { + // Create an OBJ for the result + op1 = gtNewObjNode(ldelemClsHnd, op1); + op1->gtFlags |= GTF_EXCEPT; + } + impPushOnStack(op1, tiRetVal); + break; + + // stelem for reference and value types + case CEE_STELEM: + + assertImp(sz == sizeof(unsigned)); + + _impResolveToken(CORINFO_TOKENKIND_Class); + + JITDUMP(" %08X", resolvedToken.token); + + stelemClsHnd = resolvedToken.hClass; + + if (tiVerificationNeeded) + { + typeInfo tiArray = impStackTop(2).seTypeInfo; + typeInfo tiIndex = impStackTop(1).seTypeInfo; + typeInfo tiValue = impStackTop().seTypeInfo; + + // As per ECMA 'index' specified can be either int32 or native int. + Verify(tiIndex.IsIntOrNativeIntType(), "bad index"); + typeInfo arrayElem = verMakeTypeInfo(stelemClsHnd); + + Verify(tiArray.IsNullObjRef() || tiCompatibleWith(arrayElem, verGetArrayElemType(tiArray), false), + "type operand incompatible with array element type"); + arrayElem.NormaliseForStack(); + Verify(tiCompatibleWith(tiValue, arrayElem, true), "value incompatible with type operand"); + } + + // If it's a reference type just behave as though it's a stelem.ref instruction + if (!eeIsValueClass(stelemClsHnd)) + { + goto STELEM_REF_POST_VERIFY; + } + + // Otherwise extract the type + { + CorInfoType jitTyp = info.compCompHnd->asCorInfoType(stelemClsHnd); + lclTyp = JITtype2varType(jitTyp); + goto ARR_ST_POST_VERIFY; + } + + case CEE_STELEM_REF: + + if (tiVerificationNeeded) + { + typeInfo tiArray = impStackTop(2).seTypeInfo; + typeInfo tiIndex = impStackTop(1).seTypeInfo; + typeInfo tiValue = impStackTop().seTypeInfo; + + // As per ECMA 'index' specified can be either int32 or native int. + Verify(tiIndex.IsIntOrNativeIntType(), "bad index"); + Verify(tiValue.IsObjRef(), "bad value"); + + // we only check that it is an object referece, The helper does additional checks + Verify(tiArray.IsNullObjRef() || verGetArrayElemType(tiArray).IsType(TI_REF), "bad array"); + } + + arrayNodeTo = impStackTop(2).val; + arrayNodeToIndex = impStackTop(1).val; + arrayNodeFrom = impStackTop().val; + + // + // Note that it is not legal to optimize away CORINFO_HELP_ARRADDR_ST in a + // lot of cases because of covariance. ie. foo[] can be cast to object[]. + // + + // Check for assignment to same array, ie. arrLcl[i] = arrLcl[j] + // This does not need CORINFO_HELP_ARRADDR_ST + + if (arrayNodeFrom->OperGet() == GT_INDEX && arrayNodeFrom->gtOp.gtOp1->gtOper == GT_LCL_VAR && + arrayNodeTo->gtOper == GT_LCL_VAR && + arrayNodeTo->gtLclVarCommon.gtLclNum == arrayNodeFrom->gtOp.gtOp1->gtLclVarCommon.gtLclNum && + !lvaTable[arrayNodeTo->gtLclVarCommon.gtLclNum].lvAddrExposed) + { + lclTyp = TYP_REF; + goto ARR_ST_POST_VERIFY; + } + + // Check for assignment of NULL. This does not need CORINFO_HELP_ARRADDR_ST + + if (arrayNodeFrom->OperGet() == GT_CNS_INT) + { + assert(arrayNodeFrom->gtType == TYP_REF && arrayNodeFrom->gtIntCon.gtIconVal == 0); + + lclTyp = TYP_REF; + goto ARR_ST_POST_VERIFY; + } + + STELEM_REF_POST_VERIFY: + + /* Call a helper function to do the assignment */ + op1 = gtNewHelperCallNode(CORINFO_HELP_ARRADDR_ST, TYP_VOID, 0, impPopList(3, &flags, nullptr)); + + goto SPILL_APPEND; + + case CEE_STELEM_I1: + lclTyp = TYP_BYTE; + goto ARR_ST; + case CEE_STELEM_I2: + lclTyp = TYP_SHORT; + goto ARR_ST; + case CEE_STELEM_I: + lclTyp = TYP_I_IMPL; + goto ARR_ST; + case CEE_STELEM_I4: + lclTyp = TYP_INT; + goto ARR_ST; + case CEE_STELEM_I8: + lclTyp = TYP_LONG; + goto ARR_ST; + case CEE_STELEM_R4: + lclTyp = TYP_FLOAT; + goto ARR_ST; + case CEE_STELEM_R8: + lclTyp = TYP_DOUBLE; + goto ARR_ST; + + ARR_ST: + + if (tiVerificationNeeded) + { + typeInfo tiArray = impStackTop(2).seTypeInfo; + typeInfo tiIndex = impStackTop(1).seTypeInfo; + typeInfo tiValue = impStackTop().seTypeInfo; + + // As per ECMA 'index' specified can be either int32 or native int. + Verify(tiIndex.IsIntOrNativeIntType(), "bad index"); + typeInfo arrayElem = typeInfo(lclTyp); +#ifdef _TARGET_64BIT_ + if (opcode == CEE_STELEM_I) + { + arrayElem = typeInfo::nativeInt(); + } +#endif // _TARGET_64BIT_ + Verify(tiArray.IsNullObjRef() || typeInfo::AreEquivalent(verGetArrayElemType(tiArray), arrayElem), + "bad array"); + + Verify(tiCompatibleWith(NormaliseForStack(tiValue), arrayElem.NormaliseForStack(), true), + "bad value"); + } + + ARR_ST_POST_VERIFY: + /* The strict order of evaluation is LHS-operands, RHS-operands, + range-check, and then assignment. However, codegen currently + does the range-check before evaluation the RHS-operands. So to + maintain strict ordering, we spill the stack. */ + + if (impStackTop().val->gtFlags & GTF_SIDE_EFFECT) + { + impSpillSideEffects(false, (unsigned)CHECK_SPILL_ALL DEBUGARG( + "Strict ordering of exceptions for Array store")); + } + + /* Pull the new value from the stack */ + op2 = impPopStack().val; + + /* Pull the index value */ + op1 = impPopStack().val; + + /* Pull the array address */ + op3 = impPopStack().val; + + assertImp(op3->gtType == TYP_REF); + if (op2->IsVarAddr()) + { + op2->gtType = TYP_I_IMPL; + } + + op3 = impCheckForNullPointer(op3); + + // Mark the block as containing an index expression + + if (op3->gtOper == GT_LCL_VAR) + { + if (op1->gtOper == GT_LCL_VAR || op1->gtOper == GT_CNS_INT || op1->gtOper == GT_ADD) + { + block->bbFlags |= BBF_HAS_IDX_LEN; + optMethodFlags |= OMF_HAS_ARRAYREF; + } + } + + /* Create the index node */ + + op1 = gtNewIndexRef(lclTyp, op3, op1); + + /* Create the assignment node and append it */ + + if (lclTyp == TYP_STRUCT) + { + assert(stelemClsHnd != DUMMY_INIT(NULL)); + + op1->gtIndex.gtStructElemClass = stelemClsHnd; + op1->gtIndex.gtIndElemSize = info.compCompHnd->getClassSize(stelemClsHnd); + } + if (varTypeIsStruct(op1)) + { + op1 = impAssignStruct(op1, op2, stelemClsHnd, (unsigned)CHECK_SPILL_ALL); + } + else + { + op2 = impImplicitR4orR8Cast(op2, op1->TypeGet()); + op1 = gtNewAssignNode(op1, op2); + } + + /* Mark the expression as containing an assignment */ + + op1->gtFlags |= GTF_ASG; + + goto SPILL_APPEND; + + case CEE_ADD: + oper = GT_ADD; + goto MATH_OP2; + + case CEE_ADD_OVF: + uns = false; + goto ADD_OVF; + case CEE_ADD_OVF_UN: + uns = true; + goto ADD_OVF; + + ADD_OVF: + ovfl = true; + callNode = false; + oper = GT_ADD; + goto MATH_OP2_FLAGS; + + case CEE_SUB: + oper = GT_SUB; + goto MATH_OP2; + + case CEE_SUB_OVF: + uns = false; + goto SUB_OVF; + case CEE_SUB_OVF_UN: + uns = true; + goto SUB_OVF; + + SUB_OVF: + ovfl = true; + callNode = false; + oper = GT_SUB; + goto MATH_OP2_FLAGS; + + case CEE_MUL: + oper = GT_MUL; + goto MATH_MAYBE_CALL_NO_OVF; + + case CEE_MUL_OVF: + uns = false; + goto MUL_OVF; + case CEE_MUL_OVF_UN: + uns = true; + goto MUL_OVF; + + MUL_OVF: + ovfl = true; + oper = GT_MUL; + goto MATH_MAYBE_CALL_OVF; + + // Other binary math operations + + case CEE_DIV: + oper = GT_DIV; + goto MATH_MAYBE_CALL_NO_OVF; + + case CEE_DIV_UN: + oper = GT_UDIV; + goto MATH_MAYBE_CALL_NO_OVF; + + case CEE_REM: + oper = GT_MOD; + goto MATH_MAYBE_CALL_NO_OVF; + + case CEE_REM_UN: + oper = GT_UMOD; + goto MATH_MAYBE_CALL_NO_OVF; + + MATH_MAYBE_CALL_NO_OVF: + ovfl = false; + MATH_MAYBE_CALL_OVF: + // Morpher has some complex logic about when to turn different + // typed nodes on different platforms into helper calls. We + // need to either duplicate that logic here, or just + // pessimistically make all the nodes large enough to become + // call nodes. Since call nodes aren't that much larger and + // these opcodes are infrequent enough I chose the latter. + callNode = true; + goto MATH_OP2_FLAGS; + + case CEE_AND: + oper = GT_AND; + goto MATH_OP2; + case CEE_OR: + oper = GT_OR; + goto MATH_OP2; + case CEE_XOR: + oper = GT_XOR; + goto MATH_OP2; + + MATH_OP2: // For default values of 'ovfl' and 'callNode' + + ovfl = false; + callNode = false; + + MATH_OP2_FLAGS: // If 'ovfl' and 'callNode' have already been set + + /* Pull two values and push back the result */ + + if (tiVerificationNeeded) + { + const typeInfo& tiOp1 = impStackTop(1).seTypeInfo; + const typeInfo& tiOp2 = impStackTop().seTypeInfo; + + Verify(tiCompatibleWith(tiOp1, tiOp2, true), "different arg type"); + if (oper == GT_ADD || oper == GT_DIV || oper == GT_SUB || oper == GT_MUL || oper == GT_MOD) + { + Verify(tiOp1.IsNumberType(), "not number"); + } + else + { + Verify(tiOp1.IsIntegerType(), "not integer"); + } + + Verify(!ovfl || tiOp1.IsIntegerType(), "not integer"); + + tiRetVal = tiOp1; + +#ifdef _TARGET_64BIT_ + if (tiOp2.IsNativeIntType()) + { + tiRetVal = tiOp2; + } +#endif // _TARGET_64BIT_ + } + + op2 = impPopStack().val; + op1 = impPopStack().val; + +#if !CPU_HAS_FP_SUPPORT + if (varTypeIsFloating(op1->gtType)) + { + callNode = true; + } +#endif + /* Can't do arithmetic with references */ + assertImp(genActualType(op1->TypeGet()) != TYP_REF && genActualType(op2->TypeGet()) != TYP_REF); + + // Change both to TYP_I_IMPL (impBashVarAddrsToI won't change if its a true byref, only + // if it is in the stack) + impBashVarAddrsToI(op1, op2); + + type = impGetByRefResultType(oper, uns, &op1, &op2); + + assert(!ovfl || !varTypeIsFloating(op1->gtType)); + + /* Special case: "int+0", "int-0", "int*1", "int/1" */ + + if (op2->gtOper == GT_CNS_INT) + { + if ((op2->IsIntegralConst(0) && (oper == GT_ADD || oper == GT_SUB)) || + (op2->IsIntegralConst(1) && (oper == GT_MUL || oper == GT_DIV))) + + { + impPushOnStack(op1, tiRetVal); + break; + } + } + +#if !FEATURE_X87_DOUBLES + // We can generate a TYP_FLOAT operation that has a TYP_DOUBLE operand + // + if (varTypeIsFloating(type) && varTypeIsFloating(op1->gtType) && varTypeIsFloating(op2->gtType)) + { + if (op1->TypeGet() != type) + { + // We insert a cast of op1 to 'type' + op1 = gtNewCastNode(type, op1, type); + } + if (op2->TypeGet() != type) + { + // We insert a cast of op2 to 'type' + op2 = gtNewCastNode(type, op2, type); + } + } +#endif // !FEATURE_X87_DOUBLES + +#if SMALL_TREE_NODES + if (callNode) + { + /* These operators can later be transformed into 'GT_CALL' */ + + assert(GenTree::s_gtNodeSizes[GT_CALL] > GenTree::s_gtNodeSizes[GT_MUL]); +#ifndef _TARGET_ARM_ + assert(GenTree::s_gtNodeSizes[GT_CALL] > GenTree::s_gtNodeSizes[GT_DIV]); + assert(GenTree::s_gtNodeSizes[GT_CALL] > GenTree::s_gtNodeSizes[GT_UDIV]); + assert(GenTree::s_gtNodeSizes[GT_CALL] > GenTree::s_gtNodeSizes[GT_MOD]); + assert(GenTree::s_gtNodeSizes[GT_CALL] > GenTree::s_gtNodeSizes[GT_UMOD]); +#endif + // It's tempting to use LargeOpOpcode() here, but this logic is *not* saying + // that we'll need to transform into a general large node, but rather specifically + // to a call: by doing it this way, things keep working if there are multiple sizes, + // and a CALL is no longer the largest. + // That said, as of now it *is* a large node, so we'll do this with an assert rather + // than an "if". + assert(GenTree::s_gtNodeSizes[GT_CALL] == TREE_NODE_SZ_LARGE); + op1 = new (this, GT_CALL) GenTreeOp(oper, type, op1, op2 DEBUGARG(/*largeNode*/ true)); + } + else +#endif // SMALL_TREE_NODES + { + op1 = gtNewOperNode(oper, type, op1, op2); + } + + /* Special case: integer/long division may throw an exception */ + + if (varTypeIsIntegral(op1->TypeGet()) && op1->OperMayThrow()) + { + op1->gtFlags |= GTF_EXCEPT; + } + + if (ovfl) + { + assert(oper == GT_ADD || oper == GT_SUB || oper == GT_MUL); + if (ovflType != TYP_UNKNOWN) + { + op1->gtType = ovflType; + } + op1->gtFlags |= (GTF_EXCEPT | GTF_OVERFLOW); + if (uns) + { + op1->gtFlags |= GTF_UNSIGNED; + } + } + + impPushOnStack(op1, tiRetVal); + break; + + case CEE_SHL: + oper = GT_LSH; + goto CEE_SH_OP2; + + case CEE_SHR: + oper = GT_RSH; + goto CEE_SH_OP2; + case CEE_SHR_UN: + oper = GT_RSZ; + goto CEE_SH_OP2; + + CEE_SH_OP2: + if (tiVerificationNeeded) + { + const typeInfo& tiVal = impStackTop(1).seTypeInfo; + const typeInfo& tiShift = impStackTop(0).seTypeInfo; + Verify(tiVal.IsIntegerType() && tiShift.IsType(TI_INT), "Bad shift args"); + tiRetVal = tiVal; + } + op2 = impPopStack().val; + op1 = impPopStack().val; // operand to be shifted + impBashVarAddrsToI(op1, op2); + + type = genActualType(op1->TypeGet()); + op1 = gtNewOperNode(oper, type, op1, op2); + + impPushOnStack(op1, tiRetVal); + break; + + case CEE_NOT: + if (tiVerificationNeeded) + { + tiRetVal = impStackTop().seTypeInfo; + Verify(tiRetVal.IsIntegerType(), "bad int value"); + } + + op1 = impPopStack().val; + impBashVarAddrsToI(op1, nullptr); + type = genActualType(op1->TypeGet()); + impPushOnStack(gtNewOperNode(GT_NOT, type, op1), tiRetVal); + break; + + case CEE_CKFINITE: + if (tiVerificationNeeded) + { + tiRetVal = impStackTop().seTypeInfo; + Verify(tiRetVal.IsType(TI_DOUBLE), "bad R value"); + } + op1 = impPopStack().val; + type = op1->TypeGet(); + op1 = gtNewOperNode(GT_CKFINITE, type, op1); + op1->gtFlags |= GTF_EXCEPT; + + impPushOnStack(op1, tiRetVal); + break; + + case CEE_LEAVE: + + val = getI4LittleEndian(codeAddr); // jump distance + jmpAddr = (IL_OFFSET)((codeAddr - info.compCode + sizeof(__int32)) + val); + goto LEAVE; + + case CEE_LEAVE_S: + val = getI1LittleEndian(codeAddr); // jump distance + jmpAddr = (IL_OFFSET)((codeAddr - info.compCode + sizeof(__int8)) + val); + + LEAVE: + + if (compIsForInlining()) + { + compInlineResult->NoteFatal(InlineObservation::CALLEE_HAS_LEAVE); + return; + } + + JITDUMP(" %04X", jmpAddr); + if (block->bbJumpKind != BBJ_LEAVE) + { + impResetLeaveBlock(block, jmpAddr); + } + + assert(jmpAddr == block->bbJumpDest->bbCodeOffs); + impImportLeave(block); + impNoteBranchOffs(); + + break; + + case CEE_BR: + case CEE_BR_S: + jmpDist = (sz == 1) ? getI1LittleEndian(codeAddr) : getI4LittleEndian(codeAddr); + + if (compIsForInlining() && jmpDist == 0) + { + break; /* NOP */ + } + + impNoteBranchOffs(); + break; + + case CEE_BRTRUE: + case CEE_BRTRUE_S: + case CEE_BRFALSE: + case CEE_BRFALSE_S: + + /* Pop the comparand (now there's a neat term) from the stack */ + if (tiVerificationNeeded) + { + typeInfo& tiVal = impStackTop().seTypeInfo; + Verify(tiVal.IsObjRef() || tiVal.IsByRef() || tiVal.IsIntegerType() || tiVal.IsMethod(), + "bad value"); + } + + op1 = impPopStack().val; + type = op1->TypeGet(); + + // brfalse and brtrue is only allowed on I4, refs, and byrefs. + if (!opts.MinOpts() && !opts.compDbgCode && block->bbJumpDest == block->bbNext) + { + block->bbJumpKind = BBJ_NONE; + + if (op1->gtFlags & GTF_GLOB_EFFECT) + { + op1 = gtUnusedValNode(op1); + goto SPILL_APPEND; + } + else + { + break; + } + } + + if (op1->OperIsCompare()) + { + if (opcode == CEE_BRFALSE || opcode == CEE_BRFALSE_S) + { + // Flip the sense of the compare + + op1 = gtReverseCond(op1); + } + } + else + { + /* We'll compare against an equally-sized integer 0 */ + /* For small types, we always compare against int */ + op2 = gtNewZeroConNode(genActualType(op1->gtType)); + + /* Create the comparison operator and try to fold it */ + + oper = (opcode == CEE_BRTRUE || opcode == CEE_BRTRUE_S) ? GT_NE : GT_EQ; + op1 = gtNewOperNode(oper, TYP_INT, op1, op2); + } + + // fall through + + COND_JUMP: + + seenConditionalJump = true; + + /* Fold comparison if we can */ + + op1 = gtFoldExpr(op1); + + /* Try to fold the really simple cases like 'iconst *, ifne/ifeq'*/ + /* Don't make any blocks unreachable in import only mode */ + + if ((op1->gtOper == GT_CNS_INT) && !compIsForImportOnly()) + { + /* gtFoldExpr() should prevent this as we don't want to make any blocks + unreachable under compDbgCode */ + assert(!opts.compDbgCode); + + BBjumpKinds foldedJumpKind = (BBjumpKinds)(op1->gtIntCon.gtIconVal ? BBJ_ALWAYS : BBJ_NONE); + assertImp((block->bbJumpKind == BBJ_COND) // normal case + || (block->bbJumpKind == foldedJumpKind)); // this can happen if we are reimporting the + // block for the second time + + block->bbJumpKind = foldedJumpKind; +#ifdef DEBUG + if (verbose) + { + if (op1->gtIntCon.gtIconVal) + { + printf("\nThe conditional jump becomes an unconditional jump to BB%02u\n", + block->bbJumpDest->bbNum); + } + else + { + printf("\nThe block falls through into the next BB%02u\n", block->bbNext->bbNum); + } + } +#endif + break; + } + + op1 = gtNewOperNode(GT_JTRUE, TYP_VOID, op1); + + /* GT_JTRUE is handled specially for non-empty stacks. See 'addStmt' + in impImportBlock(block). For correct line numbers, spill stack. */ + + if (opts.compDbgCode && impCurStmtOffs != BAD_IL_OFFSET) + { + impSpillStackEnsure(true); + } + + goto SPILL_APPEND; + + case CEE_CEQ: + oper = GT_EQ; + uns = false; + goto CMP_2_OPs; + case CEE_CGT_UN: + oper = GT_GT; + uns = true; + goto CMP_2_OPs; + case CEE_CGT: + oper = GT_GT; + uns = false; + goto CMP_2_OPs; + case CEE_CLT_UN: + oper = GT_LT; + uns = true; + goto CMP_2_OPs; + case CEE_CLT: + oper = GT_LT; + uns = false; + goto CMP_2_OPs; + + CMP_2_OPs: + if (tiVerificationNeeded) + { + verVerifyCond(impStackTop(1).seTypeInfo, impStackTop().seTypeInfo, opcode); + tiRetVal = typeInfo(TI_INT); + } + + op2 = impPopStack().val; + op1 = impPopStack().val; + +#ifdef _TARGET_64BIT_ + if (varTypeIsI(op1->TypeGet()) && (genActualType(op2->TypeGet()) == TYP_INT)) + { + op2 = gtNewCastNode(TYP_I_IMPL, op2, (var_types)(uns ? TYP_U_IMPL : TYP_I_IMPL)); + } + else if (varTypeIsI(op2->TypeGet()) && (genActualType(op1->TypeGet()) == TYP_INT)) + { + op1 = gtNewCastNode(TYP_I_IMPL, op1, (var_types)(uns ? TYP_U_IMPL : TYP_I_IMPL)); + } +#endif // _TARGET_64BIT_ + + assertImp(genActualType(op1->TypeGet()) == genActualType(op2->TypeGet()) || + varTypeIsI(op1->TypeGet()) && varTypeIsI(op2->TypeGet()) || + varTypeIsFloating(op1->gtType) && varTypeIsFloating(op2->gtType)); + + /* Create the comparison node */ + + op1 = gtNewOperNode(oper, TYP_INT, op1, op2); + + /* TODO: setting both flags when only one is appropriate */ + if (opcode == CEE_CGT_UN || opcode == CEE_CLT_UN) + { + op1->gtFlags |= GTF_RELOP_NAN_UN | GTF_UNSIGNED; + } + + impPushOnStack(op1, tiRetVal); + break; + + case CEE_BEQ_S: + case CEE_BEQ: + oper = GT_EQ; + goto CMP_2_OPs_AND_BR; + + case CEE_BGE_S: + case CEE_BGE: + oper = GT_GE; + goto CMP_2_OPs_AND_BR; + + case CEE_BGE_UN_S: + case CEE_BGE_UN: + oper = GT_GE; + goto CMP_2_OPs_AND_BR_UN; + + case CEE_BGT_S: + case CEE_BGT: + oper = GT_GT; + goto CMP_2_OPs_AND_BR; + + case CEE_BGT_UN_S: + case CEE_BGT_UN: + oper = GT_GT; + goto CMP_2_OPs_AND_BR_UN; + + case CEE_BLE_S: + case CEE_BLE: + oper = GT_LE; + goto CMP_2_OPs_AND_BR; + + case CEE_BLE_UN_S: + case CEE_BLE_UN: + oper = GT_LE; + goto CMP_2_OPs_AND_BR_UN; + + case CEE_BLT_S: + case CEE_BLT: + oper = GT_LT; + goto CMP_2_OPs_AND_BR; + + case CEE_BLT_UN_S: + case CEE_BLT_UN: + oper = GT_LT; + goto CMP_2_OPs_AND_BR_UN; + + case CEE_BNE_UN_S: + case CEE_BNE_UN: + oper = GT_NE; + goto CMP_2_OPs_AND_BR_UN; + + CMP_2_OPs_AND_BR_UN: + uns = true; + unordered = true; + goto CMP_2_OPs_AND_BR_ALL; + CMP_2_OPs_AND_BR: + uns = false; + unordered = false; + goto CMP_2_OPs_AND_BR_ALL; + CMP_2_OPs_AND_BR_ALL: + + if (tiVerificationNeeded) + { + verVerifyCond(impStackTop(1).seTypeInfo, impStackTop().seTypeInfo, opcode); + } + + /* Pull two values */ + op2 = impPopStack().val; + op1 = impPopStack().val; + +#ifdef _TARGET_64BIT_ + if ((op1->TypeGet() == TYP_I_IMPL) && (genActualType(op2->TypeGet()) == TYP_INT)) + { + op2 = gtNewCastNode(TYP_I_IMPL, op2, (var_types)(uns ? TYP_U_IMPL : TYP_I_IMPL)); + } + else if ((op2->TypeGet() == TYP_I_IMPL) && (genActualType(op1->TypeGet()) == TYP_INT)) + { + op1 = gtNewCastNode(TYP_I_IMPL, op1, (var_types)(uns ? TYP_U_IMPL : TYP_I_IMPL)); + } +#endif // _TARGET_64BIT_ + + assertImp(genActualType(op1->TypeGet()) == genActualType(op2->TypeGet()) || + varTypeIsI(op1->TypeGet()) && varTypeIsI(op2->TypeGet()) || + varTypeIsFloating(op1->gtType) && varTypeIsFloating(op2->gtType)); + + if (!opts.MinOpts() && !opts.compDbgCode && block->bbJumpDest == block->bbNext) + { + block->bbJumpKind = BBJ_NONE; + + if (op1->gtFlags & GTF_GLOB_EFFECT) + { + impSpillSideEffects(false, (unsigned)CHECK_SPILL_ALL DEBUGARG( + "Branch to next Optimization, op1 side effect")); + impAppendTree(gtUnusedValNode(op1), (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); + } + if (op2->gtFlags & GTF_GLOB_EFFECT) + { + impSpillSideEffects(false, (unsigned)CHECK_SPILL_ALL DEBUGARG( + "Branch to next Optimization, op2 side effect")); + impAppendTree(gtUnusedValNode(op2), (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); + } + +#ifdef DEBUG + if ((op1->gtFlags | op2->gtFlags) & GTF_GLOB_EFFECT) + { + impNoteLastILoffs(); + } +#endif + break; + } +#if !FEATURE_X87_DOUBLES + // We can generate an compare of different sized floating point op1 and op2 + // We insert a cast + // + if (varTypeIsFloating(op1->TypeGet())) + { + if (op1->TypeGet() != op2->TypeGet()) + { + assert(varTypeIsFloating(op2->TypeGet())); + + // say op1=double, op2=float. To avoid loss of precision + // while comparing, op2 is converted to double and double + // comparison is done. + if (op1->TypeGet() == TYP_DOUBLE) + { + // We insert a cast of op2 to TYP_DOUBLE + op2 = gtNewCastNode(TYP_DOUBLE, op2, TYP_DOUBLE); + } + else if (op2->TypeGet() == TYP_DOUBLE) + { + // We insert a cast of op1 to TYP_DOUBLE + op1 = gtNewCastNode(TYP_DOUBLE, op1, TYP_DOUBLE); + } + } + } +#endif // !FEATURE_X87_DOUBLES + + /* Create and append the operator */ + + op1 = gtNewOperNode(oper, TYP_INT, op1, op2); + + if (uns) + { + op1->gtFlags |= GTF_UNSIGNED; + } + + if (unordered) + { + op1->gtFlags |= GTF_RELOP_NAN_UN; + } + + goto COND_JUMP; + + case CEE_SWITCH: + assert(!compIsForInlining()); + + if (tiVerificationNeeded) + { + Verify(impStackTop().seTypeInfo.IsType(TI_INT), "Bad switch val"); + } + /* Pop the switch value off the stack */ + op1 = impPopStack().val; + assertImp(genActualTypeIsIntOrI(op1->TypeGet())); + +#ifdef _TARGET_64BIT_ + // Widen 'op1' on 64-bit targets + if (op1->TypeGet() != TYP_I_IMPL) + { + if (op1->OperGet() == GT_CNS_INT) + { + op1->gtType = TYP_I_IMPL; + } + else + { + op1 = gtNewCastNode(TYP_I_IMPL, op1, TYP_I_IMPL); + } + } +#endif // _TARGET_64BIT_ + assert(genActualType(op1->TypeGet()) == TYP_I_IMPL); + + /* We can create a switch node */ + + op1 = gtNewOperNode(GT_SWITCH, TYP_VOID, op1); + + val = (int)getU4LittleEndian(codeAddr); + codeAddr += 4 + val * 4; // skip over the switch-table + + goto SPILL_APPEND; + + /************************** Casting OPCODES ***************************/ + + case CEE_CONV_OVF_I1: + lclTyp = TYP_BYTE; + goto CONV_OVF; + case CEE_CONV_OVF_I2: + lclTyp = TYP_SHORT; + goto CONV_OVF; + case CEE_CONV_OVF_I: + lclTyp = TYP_I_IMPL; + goto CONV_OVF; + case CEE_CONV_OVF_I4: + lclTyp = TYP_INT; + goto CONV_OVF; + case CEE_CONV_OVF_I8: + lclTyp = TYP_LONG; + goto CONV_OVF; + + case CEE_CONV_OVF_U1: + lclTyp = TYP_UBYTE; + goto CONV_OVF; + case CEE_CONV_OVF_U2: + lclTyp = TYP_CHAR; + goto CONV_OVF; + case CEE_CONV_OVF_U: + lclTyp = TYP_U_IMPL; + goto CONV_OVF; + case CEE_CONV_OVF_U4: + lclTyp = TYP_UINT; + goto CONV_OVF; + case CEE_CONV_OVF_U8: + lclTyp = TYP_ULONG; + goto CONV_OVF; + + case CEE_CONV_OVF_I1_UN: + lclTyp = TYP_BYTE; + goto CONV_OVF_UN; + case CEE_CONV_OVF_I2_UN: + lclTyp = TYP_SHORT; + goto CONV_OVF_UN; + case CEE_CONV_OVF_I_UN: + lclTyp = TYP_I_IMPL; + goto CONV_OVF_UN; + case CEE_CONV_OVF_I4_UN: + lclTyp = TYP_INT; + goto CONV_OVF_UN; + case CEE_CONV_OVF_I8_UN: + lclTyp = TYP_LONG; + goto CONV_OVF_UN; + + case CEE_CONV_OVF_U1_UN: + lclTyp = TYP_UBYTE; + goto CONV_OVF_UN; + case CEE_CONV_OVF_U2_UN: + lclTyp = TYP_CHAR; + goto CONV_OVF_UN; + case CEE_CONV_OVF_U_UN: + lclTyp = TYP_U_IMPL; + goto CONV_OVF_UN; + case CEE_CONV_OVF_U4_UN: + lclTyp = TYP_UINT; + goto CONV_OVF_UN; + case CEE_CONV_OVF_U8_UN: + lclTyp = TYP_ULONG; + goto CONV_OVF_UN; + + CONV_OVF_UN: + uns = true; + goto CONV_OVF_COMMON; + CONV_OVF: + uns = false; + goto CONV_OVF_COMMON; + + CONV_OVF_COMMON: + ovfl = true; + goto _CONV; + + case CEE_CONV_I1: + lclTyp = TYP_BYTE; + goto CONV; + case CEE_CONV_I2: + lclTyp = TYP_SHORT; + goto CONV; + case CEE_CONV_I: + lclTyp = TYP_I_IMPL; + goto CONV; + case CEE_CONV_I4: + lclTyp = TYP_INT; + goto CONV; + case CEE_CONV_I8: + lclTyp = TYP_LONG; + goto CONV; + + case CEE_CONV_U1: + lclTyp = TYP_UBYTE; + goto CONV; + case CEE_CONV_U2: + lclTyp = TYP_CHAR; + goto CONV; +#if (REGSIZE_BYTES == 8) + case CEE_CONV_U: + lclTyp = TYP_U_IMPL; + goto CONV_UN; +#else + case CEE_CONV_U: + lclTyp = TYP_U_IMPL; + goto CONV; +#endif + case CEE_CONV_U4: + lclTyp = TYP_UINT; + goto CONV; + case CEE_CONV_U8: + lclTyp = TYP_ULONG; + goto CONV_UN; + + case CEE_CONV_R4: + lclTyp = TYP_FLOAT; + goto CONV; + case CEE_CONV_R8: + lclTyp = TYP_DOUBLE; + goto CONV; + + case CEE_CONV_R_UN: + lclTyp = TYP_DOUBLE; + goto CONV_UN; + + CONV_UN: + uns = true; + ovfl = false; + goto _CONV; + + CONV: + uns = false; + ovfl = false; + goto _CONV; + + _CONV: + // just check that we have a number on the stack + if (tiVerificationNeeded) + { + const typeInfo& tiVal = impStackTop().seTypeInfo; + Verify(tiVal.IsNumberType(), "bad arg"); + +#ifdef _TARGET_64BIT_ + bool isNative = false; + + switch (opcode) + { + case CEE_CONV_OVF_I: + case CEE_CONV_OVF_I_UN: + case CEE_CONV_I: + case CEE_CONV_OVF_U: + case CEE_CONV_OVF_U_UN: + case CEE_CONV_U: + isNative = true; + default: + // leave 'isNative' = false; + break; + } + if (isNative) + { + tiRetVal = typeInfo::nativeInt(); + } + else +#endif // _TARGET_64BIT_ + { + tiRetVal = typeInfo(lclTyp).NormaliseForStack(); + } + } + + // only converts from FLOAT or DOUBLE to an integer type + // and converts from ULONG (or LONG on ARM) to DOUBLE are morphed to calls + + if (varTypeIsFloating(lclTyp)) + { + callNode = varTypeIsLong(impStackTop().val) || uns // uint->dbl gets turned into uint->long->dbl +#ifdef _TARGET_64BIT_ + // TODO-ARM64-Bug?: This was AMD64; I enabled it for ARM64 also. OK? + // TYP_BYREF could be used as TYP_I_IMPL which is long. + // TODO-CQ: remove this when we lower casts long/ulong --> float/double + // and generate SSE2 code instead of going through helper calls. + || (impStackTop().val->TypeGet() == TYP_BYREF) +#endif + ; + } + else + { + callNode = varTypeIsFloating(impStackTop().val->TypeGet()); + } + + // At this point uns, ovf, callNode all set + + op1 = impPopStack().val; + impBashVarAddrsToI(op1); + + if (varTypeIsSmall(lclTyp) && !ovfl && op1->gtType == TYP_INT && op1->gtOper == GT_AND) + { + op2 = op1->gtOp.gtOp2; + + if (op2->gtOper == GT_CNS_INT) + { + ssize_t ival = op2->gtIntCon.gtIconVal; + ssize_t mask, umask; + + switch (lclTyp) + { + case TYP_BYTE: + case TYP_UBYTE: + mask = 0x00FF; + umask = 0x007F; + break; + case TYP_CHAR: + case TYP_SHORT: + mask = 0xFFFF; + umask = 0x7FFF; + break; + + default: + assert(!"unexpected type"); + return; + } + + if (((ival & umask) == ival) || ((ival & mask) == ival && uns)) + { + /* Toss the cast, it's a waste of time */ + + impPushOnStack(op1, tiRetVal); + break; + } + else if (ival == mask) + { + /* Toss the masking, it's a waste of time, since + we sign-extend from the small value anyways */ + + op1 = op1->gtOp.gtOp1; + } + } + } + + /* The 'op2' sub-operand of a cast is the 'real' type number, + since the result of a cast to one of the 'small' integer + types is an integer. + */ + + type = genActualType(lclTyp); + +#if SMALL_TREE_NODES + if (callNode) + { + op1 = gtNewCastNodeL(type, op1, lclTyp); + } + else +#endif // SMALL_TREE_NODES + { + op1 = gtNewCastNode(type, op1, lclTyp); + } + + if (ovfl) + { + op1->gtFlags |= (GTF_OVERFLOW | GTF_EXCEPT); + } + if (uns) + { + op1->gtFlags |= GTF_UNSIGNED; + } + impPushOnStack(op1, tiRetVal); + break; + + case CEE_NEG: + if (tiVerificationNeeded) + { + tiRetVal = impStackTop().seTypeInfo; + Verify(tiRetVal.IsNumberType(), "Bad arg"); + } + + op1 = impPopStack().val; + impBashVarAddrsToI(op1, nullptr); + impPushOnStack(gtNewOperNode(GT_NEG, genActualType(op1->gtType), op1), tiRetVal); + break; + + case CEE_POP: + if (tiVerificationNeeded) + { + impStackTop(0); + } + + /* Pull the top value from the stack */ + + op1 = impPopStack(clsHnd).val; + + /* Get hold of the type of the value being duplicated */ + + lclTyp = genActualType(op1->gtType); + + /* Does the value have any side effects? */ + + if ((op1->gtFlags & GTF_SIDE_EFFECT) || opts.compDbgCode) + { + // Since we are throwing away the value, just normalize + // it to its address. This is more efficient. + + if (varTypeIsStruct(op1)) + { +#ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING + // Non-calls, such as obj or ret_expr, have to go through this. + // Calls with large struct return value have to go through this. + // Helper calls with small struct return value also have to go + // through this since they do not follow Unix calling convention. + if (op1->gtOper != GT_CALL || !IsMultiRegReturnedType(clsHnd) || + op1->AsCall()->gtCallType == CT_HELPER) +#endif // FEATURE_UNIX_AMD64_STRUCT_PASSING + { + op1 = impGetStructAddr(op1, clsHnd, (unsigned)CHECK_SPILL_ALL, false); + } + } + + // If op1 is non-overflow cast, throw it away since it is useless. + // Another reason for throwing away the useless cast is in the context of + // implicit tail calls when the operand of pop is GT_CAST(GT_CALL(..)). + // The cast gets added as part of importing GT_CALL, which gets in the way + // of fgMorphCall() on the forms of tail call nodes that we assert. + if ((op1->gtOper == GT_CAST) && !op1->gtOverflow()) + { + op1 = op1->gtOp.gtOp1; + } + + // If 'op1' is an expression, create an assignment node. + // Helps analyses (like CSE) to work fine. + + if (op1->gtOper != GT_CALL) + { + op1 = gtUnusedValNode(op1); + } + + /* Append the value to the tree list */ + goto SPILL_APPEND; + } + + /* No side effects - just throw the <BEEP> thing away */ + break; + + case CEE_DUP: + + if (tiVerificationNeeded) + { + // Dup could start the begining of delegate creation sequence, remember that + delegateCreateStart = codeAddr - 1; + impStackTop(0); + } + + // Convert a (dup, stloc) sequence into a (stloc, ldloc) sequence in the following cases: + // - If this is non-debug code - so that CSE will recognize the two as equal. + // This helps eliminate a redundant bounds check in cases such as: + // ariba[i+3] += some_value; + // - If the top of the stack is a non-leaf that may be expensive to clone. + + if (codeAddr < codeEndp) + { + OPCODE nextOpcode = (OPCODE)getU1LittleEndian(codeAddr); + if (impIsAnySTLOC(nextOpcode)) + { + if (!opts.compDbgCode) + { + insertLdloc = true; + break; + } + GenTree* stackTop = impStackTop().val; + if (!stackTop->IsIntegralConst(0) && !stackTop->IsFPZero() && !stackTop->IsLocal()) + { + insertLdloc = true; + break; + } + } + } + + /* Pull the top value from the stack */ + op1 = impPopStack(tiRetVal); + + /* Clone the value */ + op1 = impCloneExpr(op1, &op2, tiRetVal.GetClassHandle(), (unsigned)CHECK_SPILL_ALL, + nullptr DEBUGARG("DUP instruction")); + + /* Either the tree started with no global effects, or impCloneExpr + evaluated the tree to a temp and returned two copies of that + temp. Either way, neither op1 nor op2 should have side effects. + */ + assert(!(op1->gtFlags & GTF_GLOB_EFFECT) && !(op2->gtFlags & GTF_GLOB_EFFECT)); + + /* Push the tree/temp back on the stack */ + impPushOnStack(op1, tiRetVal); + + /* Push the copy on the stack */ + impPushOnStack(op2, tiRetVal); + + break; + + case CEE_STIND_I1: + lclTyp = TYP_BYTE; + goto STIND; + case CEE_STIND_I2: + lclTyp = TYP_SHORT; + goto STIND; + case CEE_STIND_I4: + lclTyp = TYP_INT; + goto STIND; + case CEE_STIND_I8: + lclTyp = TYP_LONG; + goto STIND; + case CEE_STIND_I: + lclTyp = TYP_I_IMPL; + goto STIND; + case CEE_STIND_REF: + lclTyp = TYP_REF; + goto STIND; + case CEE_STIND_R4: + lclTyp = TYP_FLOAT; + goto STIND; + case CEE_STIND_R8: + lclTyp = TYP_DOUBLE; + goto STIND; + STIND: + + if (tiVerificationNeeded) + { + typeInfo instrType(lclTyp); +#ifdef _TARGET_64BIT_ + if (opcode == CEE_STIND_I) + { + instrType = typeInfo::nativeInt(); + } +#endif // _TARGET_64BIT_ + verVerifySTIND(impStackTop(1).seTypeInfo, impStackTop(0).seTypeInfo, instrType); + } + else + { + compUnsafeCastUsed = true; // Have to go conservative + } + + STIND_POST_VERIFY: + + op2 = impPopStack().val; // value to store + op1 = impPopStack().val; // address to store to + + // you can indirect off of a TYP_I_IMPL (if we are in C) or a BYREF + assertImp(genActualType(op1->gtType) == TYP_I_IMPL || op1->gtType == TYP_BYREF); + + impBashVarAddrsToI(op1, op2); + + op2 = impImplicitR4orR8Cast(op2, lclTyp); + +#ifdef _TARGET_64BIT_ + // Automatic upcast for a GT_CNS_INT into TYP_I_IMPL + if ((op2->OperGet() == GT_CNS_INT) && varTypeIsI(lclTyp) && !varTypeIsI(op2->gtType)) + { + op2->gtType = TYP_I_IMPL; + } + else + { + // Allow a downcast of op2 from TYP_I_IMPL into a 32-bit Int for x86 JIT compatiblity + // + if (varTypeIsI(op2->gtType) && (genActualType(lclTyp) == TYP_INT)) + { + assert(!tiVerificationNeeded); // We should have thrown the VerificationException before. + op2 = gtNewCastNode(TYP_INT, op2, TYP_INT); + } + // Allow an upcast of op2 from a 32-bit Int into TYP_I_IMPL for x86 JIT compatiblity + // + if (varTypeIsI(lclTyp) && (genActualType(op2->gtType) == TYP_INT)) + { + assert(!tiVerificationNeeded); // We should have thrown the VerificationException before. + op2 = gtNewCastNode(TYP_I_IMPL, op2, TYP_I_IMPL); + } + } +#endif // _TARGET_64BIT_ + + if (opcode == CEE_STIND_REF) + { + // STIND_REF can be used to store TYP_INT, TYP_I_IMPL, TYP_REF, or TYP_BYREF + assertImp(varTypeIsIntOrI(op2->gtType) || varTypeIsGC(op2->gtType)); + lclTyp = genActualType(op2->TypeGet()); + } + +// Check target type. +#ifdef DEBUG + if (op2->gtType == TYP_BYREF || lclTyp == TYP_BYREF) + { + if (op2->gtType == TYP_BYREF) + { + assertImp(lclTyp == TYP_BYREF || lclTyp == TYP_I_IMPL); + } + else if (lclTyp == TYP_BYREF) + { + assertImp(op2->gtType == TYP_BYREF || varTypeIsIntOrI(op2->gtType)); + } + } + else + { + assertImp(genActualType(op2->gtType) == genActualType(lclTyp) || + ((lclTyp == TYP_I_IMPL) && (genActualType(op2->gtType) == TYP_INT)) || + (varTypeIsFloating(op2->gtType) && varTypeIsFloating(lclTyp))); + } +#endif + + op1 = gtNewOperNode(GT_IND, lclTyp, op1); + + // stind could point anywhere, example a boxed class static int + op1->gtFlags |= GTF_IND_TGTANYWHERE; + + if (prefixFlags & PREFIX_VOLATILE) + { + assert(op1->OperGet() == GT_IND); + op1->gtFlags |= GTF_DONT_CSE; // Can't CSE a volatile + op1->gtFlags |= GTF_ORDER_SIDEEFF; // Prevent this from being reordered + op1->gtFlags |= GTF_IND_VOLATILE; + } + + if (prefixFlags & PREFIX_UNALIGNED) + { + assert(op1->OperGet() == GT_IND); + op1->gtFlags |= GTF_IND_UNALIGNED; + } + + op1 = gtNewAssignNode(op1, op2); + op1->gtFlags |= GTF_EXCEPT | GTF_GLOB_REF; + + // Spill side-effects AND global-data-accesses + if (verCurrentState.esStackDepth > 0) + { + impSpillSideEffects(true, (unsigned)CHECK_SPILL_ALL DEBUGARG("spill side effects before STIND")); + } + + goto APPEND; + + case CEE_LDIND_I1: + lclTyp = TYP_BYTE; + goto LDIND; + case CEE_LDIND_I2: + lclTyp = TYP_SHORT; + goto LDIND; + case CEE_LDIND_U4: + case CEE_LDIND_I4: + lclTyp = TYP_INT; + goto LDIND; + case CEE_LDIND_I8: + lclTyp = TYP_LONG; + goto LDIND; + case CEE_LDIND_REF: + lclTyp = TYP_REF; + goto LDIND; + case CEE_LDIND_I: + lclTyp = TYP_I_IMPL; + goto LDIND; + case CEE_LDIND_R4: + lclTyp = TYP_FLOAT; + goto LDIND; + case CEE_LDIND_R8: + lclTyp = TYP_DOUBLE; + goto LDIND; + case CEE_LDIND_U1: + lclTyp = TYP_UBYTE; + goto LDIND; + case CEE_LDIND_U2: + lclTyp = TYP_CHAR; + goto LDIND; + LDIND: + + if (tiVerificationNeeded) + { + typeInfo lclTiType(lclTyp); +#ifdef _TARGET_64BIT_ + if (opcode == CEE_LDIND_I) + { + lclTiType = typeInfo::nativeInt(); + } +#endif // _TARGET_64BIT_ + tiRetVal = verVerifyLDIND(impStackTop().seTypeInfo, lclTiType); + tiRetVal.NormaliseForStack(); + } + else + { + compUnsafeCastUsed = true; // Have to go conservative + } + + LDIND_POST_VERIFY: + + op1 = impPopStack().val; // address to load from + impBashVarAddrsToI(op1); + +#ifdef _TARGET_64BIT_ + // Allow an upcast of op1 from a 32-bit Int into TYP_I_IMPL for x86 JIT compatiblity + // + if (genActualType(op1->gtType) == TYP_INT) + { + assert(!tiVerificationNeeded); // We should have thrown the VerificationException before. + op1 = gtNewCastNode(TYP_I_IMPL, op1, TYP_I_IMPL); + } +#endif + + assertImp(genActualType(op1->gtType) == TYP_I_IMPL || op1->gtType == TYP_BYREF); + + op1 = gtNewOperNode(GT_IND, lclTyp, op1); + + // ldind could point anywhere, example a boxed class static int + op1->gtFlags |= (GTF_EXCEPT | GTF_GLOB_REF | GTF_IND_TGTANYWHERE); + + if (prefixFlags & PREFIX_VOLATILE) + { + assert(op1->OperGet() == GT_IND); + op1->gtFlags |= GTF_DONT_CSE; // Can't CSE a volatile + op1->gtFlags |= GTF_ORDER_SIDEEFF; // Prevent this from being reordered + op1->gtFlags |= GTF_IND_VOLATILE; + } + + if (prefixFlags & PREFIX_UNALIGNED) + { + assert(op1->OperGet() == GT_IND); + op1->gtFlags |= GTF_IND_UNALIGNED; + } + + impPushOnStack(op1, tiRetVal); + + break; + + case CEE_UNALIGNED: + + assert(sz == 1); + val = getU1LittleEndian(codeAddr); + ++codeAddr; + JITDUMP(" %u", val); + if ((val != 1) && (val != 2) && (val != 4)) + { + BADCODE("Alignment unaligned. must be 1, 2, or 4"); + } + + Verify(!(prefixFlags & PREFIX_UNALIGNED), "Multiple unaligned. prefixes"); + prefixFlags |= PREFIX_UNALIGNED; + + impValidateMemoryAccessOpcode(codeAddr, codeEndp, false); + + PREFIX: + opcode = (OPCODE)getU1LittleEndian(codeAddr); + codeAddr += sizeof(__int8); + opcodeOffs = (IL_OFFSET)(codeAddr - info.compCode); + goto DECODE_OPCODE; + + case CEE_VOLATILE: + + Verify(!(prefixFlags & PREFIX_VOLATILE), "Multiple volatile. prefixes"); + prefixFlags |= PREFIX_VOLATILE; + + impValidateMemoryAccessOpcode(codeAddr, codeEndp, true); + + assert(sz == 0); + goto PREFIX; + + case CEE_LDFTN: + { + // Need to do a lookup here so that we perform an access check + // and do a NOWAY if protections are violated + _impResolveToken(CORINFO_TOKENKIND_Method); + + JITDUMP(" %08X", resolvedToken.token); + + eeGetCallInfo(&resolvedToken, nullptr /* constraint typeRef*/, + addVerifyFlag(combine(CORINFO_CALLINFO_SECURITYCHECKS, CORINFO_CALLINFO_LDFTN)), + &callInfo); + + // This check really only applies to intrinsic Array.Address methods + if (callInfo.sig.callConv & CORINFO_CALLCONV_PARAMTYPE) + { + NO_WAY("Currently do not support LDFTN of Parameterized functions"); + } + + // Do this before DO_LDFTN since CEE_LDVIRTFN does it on its own. + impHandleAccessAllowed(callInfo.accessAllowed, &callInfo.callsiteCalloutHelper); + + if (tiVerificationNeeded) + { + // LDFTN could start the begining of delegate creation sequence, remember that + delegateCreateStart = codeAddr - 2; + + // check any constraints on the callee's class and type parameters + VerifyOrReturn(info.compCompHnd->satisfiesClassConstraints(resolvedToken.hClass), + "method has unsatisfied class constraints"); + VerifyOrReturn(info.compCompHnd->satisfiesMethodConstraints(resolvedToken.hClass, + resolvedToken.hMethod), + "method has unsatisfied method constraints"); + + mflags = callInfo.verMethodFlags; + Verify(!(mflags & CORINFO_FLG_CONSTRUCTOR), "LDFTN on a constructor"); + } + + DO_LDFTN: + op1 = impMethodPointer(&resolvedToken, &callInfo); + if (compDonotInline()) + { + return; + } + + impPushOnStack(op1, typeInfo(resolvedToken.hMethod)); + + break; + } + + case CEE_LDVIRTFTN: + { + /* Get the method token */ + + _impResolveToken(CORINFO_TOKENKIND_Method); + + JITDUMP(" %08X", resolvedToken.token); + + eeGetCallInfo(&resolvedToken, nullptr /* constraint typeRef */, + addVerifyFlag(combine(combine(CORINFO_CALLINFO_SECURITYCHECKS, CORINFO_CALLINFO_LDFTN), + CORINFO_CALLINFO_CALLVIRT)), + &callInfo); + + // This check really only applies to intrinsic Array.Address methods + if (callInfo.sig.callConv & CORINFO_CALLCONV_PARAMTYPE) + { + NO_WAY("Currently do not support LDFTN of Parameterized functions"); + } + + mflags = callInfo.methodFlags; + + impHandleAccessAllowed(callInfo.accessAllowed, &callInfo.callsiteCalloutHelper); + + if (compIsForInlining()) + { + if (mflags & (CORINFO_FLG_FINAL | CORINFO_FLG_STATIC) || !(mflags & CORINFO_FLG_VIRTUAL)) + { + compInlineResult->NoteFatal(InlineObservation::CALLSITE_LDVIRTFN_ON_NON_VIRTUAL); + return; + } + } + + CORINFO_SIG_INFO& ftnSig = callInfo.sig; + + if (tiVerificationNeeded) + { + + Verify(ftnSig.hasThis(), "ldvirtftn on a static method"); + Verify(!(mflags & CORINFO_FLG_CONSTRUCTOR), "LDVIRTFTN on a constructor"); + + // JIT32 verifier rejects verifiable ldvirtftn pattern + typeInfo declType = + verMakeTypeInfo(resolvedToken.hClass, true); // Change TI_STRUCT to TI_REF when necessary + + typeInfo arg = impStackTop().seTypeInfo; + Verify((arg.IsType(TI_REF) || arg.IsType(TI_NULL)) && tiCompatibleWith(arg, declType, true), + "bad ldvirtftn"); + + CORINFO_CLASS_HANDLE instanceClassHnd = info.compClassHnd; + if (!(arg.IsType(TI_NULL) || (mflags & CORINFO_FLG_STATIC))) + { + instanceClassHnd = arg.GetClassHandleForObjRef(); + } + + // check any constraints on the method's class and type parameters + VerifyOrReturn(info.compCompHnd->satisfiesClassConstraints(resolvedToken.hClass), + "method has unsatisfied class constraints"); + VerifyOrReturn(info.compCompHnd->satisfiesMethodConstraints(resolvedToken.hClass, + resolvedToken.hMethod), + "method has unsatisfied method constraints"); + + if (mflags & CORINFO_FLG_PROTECTED) + { + Verify(info.compCompHnd->canAccessFamily(info.compMethodHnd, instanceClassHnd), + "Accessing protected method through wrong type."); + } + } + + /* Get the object-ref */ + op1 = impPopStack().val; + assertImp(op1->gtType == TYP_REF); + + if (opts.IsReadyToRun()) + { + if (callInfo.kind != CORINFO_VIRTUALCALL_LDVIRTFTN) + { + if (op1->gtFlags & GTF_SIDE_EFFECT) + { + op1 = gtUnusedValNode(op1); + impAppendTree(op1, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs); + } + goto DO_LDFTN; + } + } + else if (mflags & (CORINFO_FLG_FINAL | CORINFO_FLG_STATIC) || !(mflags & CORINFO_FLG_VIRTUAL)) + { + if (op1->gtFlags & GTF_SIDE_EFFECT) + { + op1 = gtUnusedValNode(op1); + impAppendTree(op1, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs); + } + goto DO_LDFTN; + } + + GenTreePtr fptr = impImportLdvirtftn(op1, &resolvedToken, &callInfo); + if (compDonotInline()) + { + return; + } + + impPushOnStack(fptr, typeInfo(resolvedToken.hMethod)); + + break; + } + + case CEE_CONSTRAINED: + + assertImp(sz == sizeof(unsigned)); + impResolveToken(codeAddr, &constrainedResolvedToken, CORINFO_TOKENKIND_Constrained); + codeAddr += sizeof(unsigned); // prefix instructions must increment codeAddr manually + JITDUMP(" (%08X) ", constrainedResolvedToken.token); + + Verify(!(prefixFlags & PREFIX_CONSTRAINED), "Multiple constrained. prefixes"); + prefixFlags |= PREFIX_CONSTRAINED; + + { + OPCODE actualOpcode = impGetNonPrefixOpcode(codeAddr, codeEndp); + if (actualOpcode != CEE_CALLVIRT) + { + BADCODE("constrained. has to be followed by callvirt"); + } + } + + goto PREFIX; + + case CEE_READONLY: + JITDUMP(" readonly."); + + Verify(!(prefixFlags & PREFIX_READONLY), "Multiple readonly. prefixes"); + prefixFlags |= PREFIX_READONLY; + + { + OPCODE actualOpcode = impGetNonPrefixOpcode(codeAddr, codeEndp); + if (actualOpcode != CEE_LDELEMA && !impOpcodeIsCallOpcode(actualOpcode)) + { + BADCODE("readonly. has to be followed by ldelema or call"); + } + } + + assert(sz == 0); + goto PREFIX; + + case CEE_TAILCALL: + JITDUMP(" tail."); + + Verify(!(prefixFlags & PREFIX_TAILCALL_EXPLICIT), "Multiple tailcall. prefixes"); + prefixFlags |= PREFIX_TAILCALL_EXPLICIT; + + { + OPCODE actualOpcode = impGetNonPrefixOpcode(codeAddr, codeEndp); + if (!impOpcodeIsCallOpcode(actualOpcode)) + { + BADCODE("tailcall. has to be followed by call, callvirt or calli"); + } + } + assert(sz == 0); + goto PREFIX; + + case CEE_NEWOBJ: + + /* Since we will implicitly insert newObjThisPtr at the start of the + argument list, spill any GTF_ORDER_SIDEEFF */ + impSpillSpecialSideEff(); + + /* NEWOBJ does not respond to TAIL */ + prefixFlags &= ~PREFIX_TAILCALL_EXPLICIT; + + /* NEWOBJ does not respond to CONSTRAINED */ + prefixFlags &= ~PREFIX_CONSTRAINED; + +#if COR_JIT_EE_VERSION > 460 + _impResolveToken(CORINFO_TOKENKIND_NewObj); +#else + _impResolveToken(CORINFO_TOKENKIND_Method); +#endif + + eeGetCallInfo(&resolvedToken, nullptr /* constraint typeRef*/, + addVerifyFlag(combine(CORINFO_CALLINFO_SECURITYCHECKS, CORINFO_CALLINFO_ALLOWINSTPARAM)), + &callInfo); + + if (compIsForInlining()) + { + if (impInlineInfo->inlineCandidateInfo->dwRestrictions & INLINE_RESPECT_BOUNDARY) + { + // Check to see if this call violates the boundary. + compInlineResult->NoteFatal(InlineObservation::CALLSITE_CROSS_BOUNDARY_SECURITY); + return; + } + } + + mflags = callInfo.methodFlags; + + if ((mflags & (CORINFO_FLG_STATIC | CORINFO_FLG_ABSTRACT)) != 0) + { + BADCODE("newobj on static or abstract method"); + } + + // Insert the security callout before any actual code is generated + impHandleAccessAllowed(callInfo.accessAllowed, &callInfo.callsiteCalloutHelper); + + // There are three different cases for new + // Object size is variable (depends on arguments) + // 1) Object is an array (arrays treated specially by the EE) + // 2) Object is some other variable sized object (e.g. String) + // 3) Class Size can be determined beforehand (normal case) + // In the first case, we need to call a NEWOBJ helper (multinewarray) + // in the second case we call the constructor with a '0' this pointer + // In the third case we alloc the memory, then call the constuctor + + clsFlags = callInfo.classFlags; + if (clsFlags & CORINFO_FLG_ARRAY) + { + if (tiVerificationNeeded) + { + CORINFO_CLASS_HANDLE elemTypeHnd; + INDEBUG(CorInfoType corType =) + info.compCompHnd->getChildType(resolvedToken.hClass, &elemTypeHnd); + assert(!(elemTypeHnd == nullptr && corType == CORINFO_TYPE_VALUECLASS)); + Verify(elemTypeHnd == nullptr || + !(info.compCompHnd->getClassAttribs(elemTypeHnd) & CORINFO_FLG_CONTAINS_STACK_PTR), + "newarr of byref-like objects"); + verVerifyCall(opcode, &resolvedToken, nullptr, ((prefixFlags & PREFIX_TAILCALL_EXPLICIT) != 0), + ((prefixFlags & PREFIX_READONLY) != 0), delegateCreateStart, codeAddr - 1, + &callInfo DEBUGARG(info.compFullName)); + } + // Arrays need to call the NEWOBJ helper. + assertImp(clsFlags & CORINFO_FLG_VAROBJSIZE); + + impImportNewObjArray(&resolvedToken, &callInfo); + if (compDonotInline()) + { + return; + } + + callTyp = TYP_REF; + break; + } + // At present this can only be String + else if (clsFlags & CORINFO_FLG_VAROBJSIZE) + { +#if COR_JIT_EE_VERSION > 460 + if (eeGetEEInfo()->targetAbi == CORINFO_CORERT_ABI) + { + // The dummy argument does not exist in CoreRT + newObjThisPtr = nullptr; + } + else +#endif + { + // This is the case for variable-sized objects that are not + // arrays. In this case, call the constructor with a null 'this' + // pointer + newObjThisPtr = gtNewIconNode(0, TYP_REF); + } + + /* Remember that this basic block contains 'new' of an object */ + block->bbFlags |= BBF_HAS_NEWOBJ; + optMethodFlags |= OMF_HAS_NEWOBJ; + } + else + { + // This is the normal case where the size of the object is + // fixed. Allocate the memory and call the constructor. + + // Note: We cannot add a peep to avoid use of temp here + // becase we don't have enough interference info to detect when + // sources and destination interfere, example: s = new S(ref); + + // TODO: We find the correct place to introduce a general + // reverse copy prop for struct return values from newobj or + // any function returning structs. + + /* get a temporary for the new object */ + lclNum = lvaGrabTemp(true DEBUGARG("NewObj constructor temp")); + + // In the value class case we only need clsHnd for size calcs. + // + // The lookup of the code pointer will be handled by CALL in this case + if (clsFlags & CORINFO_FLG_VALUECLASS) + { + CorInfoType jitTyp = info.compCompHnd->asCorInfoType(resolvedToken.hClass); + unsigned size = info.compCompHnd->getClassSize(resolvedToken.hClass); + + if (impIsPrimitive(jitTyp)) + { + lvaTable[lclNum].lvType = JITtype2varType(jitTyp); + } + else + { + // The local variable itself is the allocated space. + // Here we need unsafe value cls check, since the address of struct is taken for further use + // and potentially exploitable. + lvaSetStruct(lclNum, resolvedToken.hClass, true /* unsafe value cls check */); + } + + // Append a tree to zero-out the temp + newObjThisPtr = gtNewLclvNode(lclNum, lvaTable[lclNum].TypeGet()); + + newObjThisPtr = gtNewBlkOpNode(newObjThisPtr, // Dest + gtNewIconNode(0), // Value + size, // Size + false, // isVolatile + false); // not copyBlock + impAppendTree(newObjThisPtr, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); + + // Obtain the address of the temp + newObjThisPtr = + gtNewOperNode(GT_ADDR, TYP_BYREF, gtNewLclvNode(lclNum, lvaTable[lclNum].TypeGet())); + } + else + { +#ifdef FEATURE_READYTORUN_COMPILER + if (opts.IsReadyToRun()) + { + op1 = impReadyToRunHelperToTree(&resolvedToken, CORINFO_HELP_READYTORUN_NEW, TYP_REF); + usingReadyToRunHelper = (op1 != NULL); + } + + if (!usingReadyToRunHelper) +#endif + { + op1 = impParentClassTokenToHandle(&resolvedToken, nullptr, TRUE); + if (op1 == nullptr) + { // compDonotInline() + return; + } + + // TODO: ReadyToRun: When generic dictionary lookups are necessary, replace the lookup call + // and the newfast call with a single call to a dynamic R2R cell that will: + // 1) Load the context + // 2) Perform the generic dictionary lookup and caching, and generate the appropriate + // stub + // 3) Allocate and return the new object + // Reason: performance (today, we'll always use the slow helper for the R2R generics case) + + op1 = gtNewAllocObjNode(info.compCompHnd->getNewHelper(&resolvedToken, info.compMethodHnd), + resolvedToken.hClass, TYP_REF, op1); + } + + // Remember that this basic block contains 'new' of an object + block->bbFlags |= BBF_HAS_NEWOBJ; + optMethodFlags |= OMF_HAS_NEWOBJ; + + // Append the assignment to the temp/local. Dont need to spill + // at all as we are just calling an EE-Jit helper which can only + // cause an (async) OutOfMemoryException. + + // We assign the newly allocated object (by a GT_ALLOCOBJ node) + // to a temp. Note that the pattern "temp = allocObj" is required + // by ObjectAllocator phase to be able to determine GT_ALLOCOBJ nodes + // without exhaustive walk over all expressions. + + impAssignTempGen(lclNum, op1, (unsigned)CHECK_SPILL_NONE); + + newObjThisPtr = gtNewLclvNode(lclNum, TYP_REF); + } + } + goto CALL; + + case CEE_CALLI: + + /* CALLI does not respond to CONSTRAINED */ + prefixFlags &= ~PREFIX_CONSTRAINED; + + if (compIsForInlining()) + { + // CALLI doesn't have a method handle, so assume the worst. + if (impInlineInfo->inlineCandidateInfo->dwRestrictions & INLINE_RESPECT_BOUNDARY) + { + compInlineResult->NoteFatal(InlineObservation::CALLSITE_CROSS_BOUNDARY_CALLI); + return; + } + } + + // fall through + + case CEE_CALLVIRT: + case CEE_CALL: + + // We can't call getCallInfo on the token from a CALLI, but we need it in + // many other places. We unfortunately embed that knowledge here. + if (opcode != CEE_CALLI) + { + _impResolveToken(CORINFO_TOKENKIND_Method); + + eeGetCallInfo(&resolvedToken, + (prefixFlags & PREFIX_CONSTRAINED) ? &constrainedResolvedToken : nullptr, + // this is how impImportCall invokes getCallInfo + addVerifyFlag( + combine(combine(CORINFO_CALLINFO_ALLOWINSTPARAM, CORINFO_CALLINFO_SECURITYCHECKS), + (opcode == CEE_CALLVIRT) ? CORINFO_CALLINFO_CALLVIRT + : CORINFO_CALLINFO_NONE)), + &callInfo); + } + else + { + // Suppress uninitialized use warning. + memset(&resolvedToken, 0, sizeof(resolvedToken)); + memset(&callInfo, 0, sizeof(callInfo)); + + resolvedToken.token = getU4LittleEndian(codeAddr); + } + + CALL: // memberRef should be set. + // newObjThisPtr should be set for CEE_NEWOBJ + + JITDUMP(" %08X", resolvedToken.token); + constraintCall = (prefixFlags & PREFIX_CONSTRAINED) != 0; + + bool newBBcreatedForTailcallStress; + + newBBcreatedForTailcallStress = false; + + if (compIsForInlining()) + { + // We rule out inlinees with explicit tail calls in fgMakeBasicBlocks. + assert((prefixFlags & PREFIX_TAILCALL_EXPLICIT) == 0); + } + else + { + if (compTailCallStress()) + { + // Have we created a new BB after the "call" instruction in fgMakeBasicBlocks()? + // Tail call stress only recognizes call+ret patterns and forces them to be + // explicit tail prefixed calls. Also fgMakeBasicBlocks() under tail call stress + // doesn't import 'ret' opcode following the call into the basic block containing + // the call instead imports it to a new basic block. Note that fgMakeBasicBlocks() + // is already checking that there is an opcode following call and hence it is + // safe here to read next opcode without bounds check. + newBBcreatedForTailcallStress = + impOpcodeIsCallOpcode(opcode) && // Current opcode is a CALL, (not a CEE_NEWOBJ). So, don't + // make it jump to RET. + (OPCODE)getU1LittleEndian(codeAddr + sz) == CEE_RET; // Next opcode is a CEE_RET + + if (newBBcreatedForTailcallStress && + !(prefixFlags & PREFIX_TAILCALL_EXPLICIT) && // User hasn't set "tail." prefix yet. + verCheckTailCallConstraint(opcode, &resolvedToken, + constraintCall ? &constrainedResolvedToken : nullptr, + true) // Is it legal to do talcall? + ) + { + // Stress the tailcall. + JITDUMP(" (Tailcall stress: prefixFlags |= PREFIX_TAILCALL_EXPLICIT)"); + prefixFlags |= PREFIX_TAILCALL_EXPLICIT; + } + } + + // Note that when running under tail call stress, a call will be marked as explicit tail prefixed + // hence will not be considered for implicit tail calling. + bool isRecursive = (callInfo.hMethod == info.compMethodHnd); + if (impIsImplicitTailCallCandidate(opcode, codeAddr + sz, codeEndp, prefixFlags, isRecursive)) + { + JITDUMP(" (Implicit Tail call: prefixFlags |= PREFIX_TAILCALL_IMPLICIT)"); + prefixFlags |= PREFIX_TAILCALL_IMPLICIT; + } + } + + // Treat this call as tail call for verification only if "tail" prefixed (i.e. explicit tail call). + explicitTailCall = (prefixFlags & PREFIX_TAILCALL_EXPLICIT) != 0; + readonlyCall = (prefixFlags & PREFIX_READONLY) != 0; + + if (opcode != CEE_CALLI && opcode != CEE_NEWOBJ) + { + // All calls and delegates need a security callout. + // For delegates, this is the call to the delegate constructor, not the access check on the + // LD(virt)FTN. + impHandleAccessAllowed(callInfo.accessAllowed, &callInfo.callsiteCalloutHelper); + +#if 0 // DevDiv 410397 - This breaks too many obfuscated apps to do this in an in-place release + + // DevDiv 291703 - we need to check for accessibility between the caller of InitializeArray + // and the field it is reading, thus it is now unverifiable to not immediately precede with + // ldtoken <filed token>, and we now check accessibility + if ((callInfo.methodFlags & CORINFO_FLG_INTRINSIC) && + (info.compCompHnd->getIntrinsicID(callInfo.hMethod) == CORINFO_INTRINSIC_InitializeArray)) + { + if (prevOpcode != CEE_LDTOKEN) + { + Verify(prevOpcode == CEE_LDTOKEN, "Need ldtoken for InitializeArray"); + } + else + { + assert(lastLoadToken != NULL); + // Now that we know we have a token, verify that it is accessible for loading + CORINFO_RESOLVED_TOKEN resolvedLoadField; + impResolveToken(lastLoadToken, &resolvedLoadField, CORINFO_TOKENKIND_Field); + eeGetFieldInfo(&resolvedLoadField, CORINFO_ACCESS_INIT_ARRAY, &fieldInfo); + impHandleAccessAllowed(fieldInfo.accessAllowed, &fieldInfo.accessCalloutHelper); + } + } + +#endif // DevDiv 410397 + } + + if (tiVerificationNeeded) + { + verVerifyCall(opcode, &resolvedToken, constraintCall ? &constrainedResolvedToken : nullptr, + explicitTailCall, readonlyCall, delegateCreateStart, codeAddr - 1, + &callInfo DEBUGARG(info.compFullName)); + } + + // Insert delegate callout here. + if (opcode == CEE_NEWOBJ && (mflags & CORINFO_FLG_CONSTRUCTOR) && (clsFlags & CORINFO_FLG_DELEGATE)) + { +#ifdef DEBUG + // We should do this only if verification is enabled + // If verification is disabled, delegateCreateStart will not be initialized correctly + if (tiVerificationNeeded) + { + mdMemberRef delegateMethodRef = mdMemberRefNil; + // We should get here only for well formed delegate creation. + assert(verCheckDelegateCreation(delegateCreateStart, codeAddr - 1, delegateMethodRef)); + } +#endif + +#ifdef FEATURE_CORECLR + // In coreclr the delegate transparency rule needs to be enforced even if verification is disabled + typeInfo tiActualFtn = impStackTop(0).seTypeInfo; + CORINFO_METHOD_HANDLE delegateMethodHandle = tiActualFtn.GetMethod2(); + + impInsertCalloutForDelegate(info.compMethodHnd, delegateMethodHandle, resolvedToken.hClass); +#endif // FEATURE_CORECLR + } + + callTyp = impImportCall(opcode, &resolvedToken, constraintCall ? &constrainedResolvedToken : nullptr, + newObjThisPtr, prefixFlags, &callInfo, opcodeOffs); + if (compDonotInline()) + { + return; + } + + if (explicitTailCall || newBBcreatedForTailcallStress) // If newBBcreatedForTailcallStress is true, we + // have created a new BB after the "call" + // instruction in fgMakeBasicBlocks(). So we need to jump to RET regardless. + { + assert(!compIsForInlining()); + goto RET; + } + + break; + + case CEE_LDFLD: + case CEE_LDSFLD: + case CEE_LDFLDA: + case CEE_LDSFLDA: + { + + BOOL isLoadAddress = (opcode == CEE_LDFLDA || opcode == CEE_LDSFLDA); + BOOL isLoadStatic = (opcode == CEE_LDSFLD || opcode == CEE_LDSFLDA); + + /* Get the CP_Fieldref index */ + assertImp(sz == sizeof(unsigned)); + + _impResolveToken(CORINFO_TOKENKIND_Field); + + JITDUMP(" %08X", resolvedToken.token); + + int aflags = isLoadAddress ? CORINFO_ACCESS_ADDRESS : CORINFO_ACCESS_GET; + + GenTreePtr obj = nullptr; + typeInfo* tiObj = nullptr; + CORINFO_CLASS_HANDLE objType = nullptr; // used for fields + + if (opcode == CEE_LDFLD || opcode == CEE_LDFLDA) + { + tiObj = &impStackTop().seTypeInfo; + obj = impPopStack(objType).val; + + if (impIsThis(obj)) + { + aflags |= CORINFO_ACCESS_THIS; + + // An optimization for Contextful classes: + // we unwrap the proxy when we have a 'this reference' + + if (info.compUnwrapContextful) + { + aflags |= CORINFO_ACCESS_UNWRAP; + } + } + } + + eeGetFieldInfo(&resolvedToken, (CORINFO_ACCESS_FLAGS)aflags, &fieldInfo); + + // Figure out the type of the member. We always call canAccessField, so you always need this + // handle + CorInfoType ciType = fieldInfo.fieldType; + clsHnd = fieldInfo.structType; + + lclTyp = JITtype2varType(ciType); + +#ifdef _TARGET_AMD64 + noway_assert(varTypeIsIntegralOrI(lclTyp) || varTypeIsFloating(lclTyp) || lclTyp == TYP_STRUCT); +#endif // _TARGET_AMD64 + + if (compIsForInlining()) + { + switch (fieldInfo.fieldAccessor) + { + case CORINFO_FIELD_INSTANCE_HELPER: + case CORINFO_FIELD_INSTANCE_ADDR_HELPER: + case CORINFO_FIELD_STATIC_ADDR_HELPER: + case CORINFO_FIELD_STATIC_TLS: + + compInlineResult->NoteFatal(InlineObservation::CALLEE_LDFLD_NEEDS_HELPER); + return; + + case CORINFO_FIELD_STATIC_GENERICS_STATIC_HELPER: + + /* We may be able to inline the field accessors in specific instantiations of generic + * methods */ + compInlineResult->NoteFatal(InlineObservation::CALLSITE_LDFLD_NEEDS_HELPER); + return; + + default: + break; + } + + if (!isLoadAddress && (fieldInfo.fieldFlags & CORINFO_FLG_FIELD_STATIC) && lclTyp == TYP_STRUCT && + clsHnd) + { + if ((info.compCompHnd->getTypeForPrimitiveValueClass(clsHnd) == CORINFO_TYPE_UNDEF) && + !(info.compFlags & CORINFO_FLG_FORCEINLINE)) + { + // Loading a static valuetype field usually will cause a JitHelper to be called + // for the static base. This will bloat the code. + compInlineResult->Note(InlineObservation::CALLEE_LDFLD_STATIC_VALUECLASS); + + if (compInlineResult->IsFailure()) + { + return; + } + } + } + } + + tiRetVal = verMakeTypeInfo(ciType, clsHnd); + if (isLoadAddress) + { + tiRetVal.MakeByRef(); + } + else + { + tiRetVal.NormaliseForStack(); + } + + // Perform this check always to ensure that we get field access exceptions even with + // SkipVerification. + impHandleAccessAllowed(fieldInfo.accessAllowed, &fieldInfo.accessCalloutHelper); + + if (tiVerificationNeeded) + { + // You can also pass the unboxed struct to LDFLD + BOOL bAllowPlainValueTypeAsThis = FALSE; + if (opcode == CEE_LDFLD && impIsValueType(tiObj)) + { + bAllowPlainValueTypeAsThis = TRUE; + } + + verVerifyField(&resolvedToken, fieldInfo, tiObj, isLoadAddress, bAllowPlainValueTypeAsThis); + + // If we're doing this on a heap object or from a 'safe' byref + // then the result is a safe byref too + if (isLoadAddress) // load address + { + if (fieldInfo.fieldFlags & + CORINFO_FLG_FIELD_STATIC) // statics marked as safe will have permanent home + { + if (fieldInfo.fieldFlags & CORINFO_FLG_FIELD_SAFESTATIC_BYREF_RETURN) + { + tiRetVal.SetIsPermanentHomeByRef(); + } + } + else if (tiObj->IsObjRef() || tiObj->IsPermanentHomeByRef()) + { + // ldflda of byref is safe if done on a gc object or on a + // safe byref + tiRetVal.SetIsPermanentHomeByRef(); + } + } + } + else + { + // tiVerificationNeeded is false. + // Raise InvalidProgramException if static load accesses non-static field + if (isLoadStatic && ((fieldInfo.fieldFlags & CORINFO_FLG_FIELD_STATIC) == 0)) + { + BADCODE("static access on an instance field"); + } + } + + // We are using ldfld/a on a static field. We allow it, but need to get side-effect from obj. + if ((fieldInfo.fieldFlags & CORINFO_FLG_FIELD_STATIC) && obj != nullptr) + { + if (obj->gtFlags & GTF_SIDE_EFFECT) + { + obj = gtUnusedValNode(obj); + impAppendTree(obj, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs); + } + obj = nullptr; + } + + /* Preserve 'small' int types */ + if (lclTyp > TYP_INT) + { + lclTyp = genActualType(lclTyp); + } + + bool usesHelper = false; + + switch (fieldInfo.fieldAccessor) + { + case CORINFO_FIELD_INSTANCE: +#ifdef FEATURE_READYTORUN_COMPILER + case CORINFO_FIELD_INSTANCE_WITH_BASE: +#endif + { + bool nullcheckNeeded = false; + + obj = impCheckForNullPointer(obj); + + if (isLoadAddress && (obj->gtType == TYP_BYREF) && fgAddrCouldBeNull(obj)) + { + nullcheckNeeded = true; + } + + // If the object is a struct, what we really want is + // for the field to operate on the address of the struct. + if (!varTypeGCtype(obj->TypeGet()) && impIsValueType(tiObj)) + { + assert(opcode == CEE_LDFLD && objType != nullptr); + + obj = impGetStructAddr(obj, objType, (unsigned)CHECK_SPILL_ALL, true); + } + + /* Create the data member node */ + op1 = gtNewFieldRef(lclTyp, resolvedToken.hField, obj, fieldInfo.offset, nullcheckNeeded); + +#ifdef FEATURE_READYTORUN_COMPILER + if (fieldInfo.fieldAccessor == CORINFO_FIELD_INSTANCE_WITH_BASE) + op1->gtField.gtFieldLookup = fieldInfo.fieldLookup; +#endif + + op1->gtFlags |= (obj->gtFlags & GTF_GLOB_EFFECT); + + if (fgAddrCouldBeNull(obj)) + { + op1->gtFlags |= GTF_EXCEPT; + } + + // If gtFldObj is a BYREF then our target is a value class and + // it could point anywhere, example a boxed class static int + if (obj->gtType == TYP_BYREF) + { + op1->gtFlags |= GTF_IND_TGTANYWHERE; + } + + DWORD typeFlags = info.compCompHnd->getClassAttribs(resolvedToken.hClass); + if (StructHasOverlappingFields(typeFlags)) + { + op1->gtField.gtFldMayOverlap = true; + } + + // wrap it in a address of operator if necessary + if (isLoadAddress) + { + op1 = gtNewOperNode(GT_ADDR, + (var_types)(varTypeIsGC(obj->TypeGet()) ? TYP_BYREF : TYP_I_IMPL), op1); + } + else + { + if (compIsForInlining() && + impInlineIsGuaranteedThisDerefBeforeAnySideEffects(nullptr, obj, + impInlineInfo->inlArgInfo)) + { + impInlineInfo->thisDereferencedFirst = true; + } + } + } + break; + + case CORINFO_FIELD_STATIC_TLS: +#ifdef _TARGET_X86_ + // Legacy TLS access is implemented as intrinsic on x86 only + + /* Create the data member node */ + op1 = gtNewFieldRef(lclTyp, resolvedToken.hField, NULL, fieldInfo.offset); + op1->gtFlags |= GTF_IND_TLS_REF; // fgMorphField will handle the transformation + + if (isLoadAddress) + { + op1 = gtNewOperNode(GT_ADDR, (var_types)TYP_I_IMPL, op1); + } + break; +#else + fieldInfo.fieldAccessor = CORINFO_FIELD_STATIC_ADDR_HELPER; + + __fallthrough; +#endif + + case CORINFO_FIELD_STATIC_ADDR_HELPER: + case CORINFO_FIELD_INSTANCE_HELPER: + case CORINFO_FIELD_INSTANCE_ADDR_HELPER: + op1 = gtNewRefCOMfield(obj, &resolvedToken, (CORINFO_ACCESS_FLAGS)aflags, &fieldInfo, lclTyp, + clsHnd, nullptr); + usesHelper = true; + break; + + case CORINFO_FIELD_STATIC_ADDRESS: + // Replace static read-only fields with constant if possible + if ((aflags & CORINFO_ACCESS_GET) && (fieldInfo.fieldFlags & CORINFO_FLG_FIELD_FINAL) && + !(fieldInfo.fieldFlags & CORINFO_FLG_FIELD_STATIC_IN_HEAP) && + (varTypeIsIntegral(lclTyp) || varTypeIsFloating(lclTyp))) + { + CorInfoInitClassResult initClassResult = + info.compCompHnd->initClass(resolvedToken.hField, info.compMethodHnd, + impTokenLookupContextHandle); + + if (initClassResult & CORINFO_INITCLASS_INITIALIZED) + { + void** pFldAddr = nullptr; + void* fldAddr = + info.compCompHnd->getFieldAddress(resolvedToken.hField, (void**)&pFldAddr); + + // We should always be able to access this static's address directly + assert(pFldAddr == nullptr); + + op1 = impImportStaticReadOnlyField(fldAddr, lclTyp); + goto FIELD_DONE; + } + } + + __fallthrough; + + case CORINFO_FIELD_STATIC_RVA_ADDRESS: + case CORINFO_FIELD_STATIC_SHARED_STATIC_HELPER: + case CORINFO_FIELD_STATIC_GENERICS_STATIC_HELPER: + op1 = impImportStaticFieldAccess(&resolvedToken, (CORINFO_ACCESS_FLAGS)aflags, &fieldInfo, + lclTyp); + break; + + case CORINFO_FIELD_INTRINSIC_ZERO: + { + assert(aflags & CORINFO_ACCESS_GET); + op1 = gtNewIconNode(0, lclTyp); + goto FIELD_DONE; + } + break; + + case CORINFO_FIELD_INTRINSIC_EMPTY_STRING: + { + assert(aflags & CORINFO_ACCESS_GET); + + LPVOID pValue; + InfoAccessType iat = info.compCompHnd->emptyStringLiteral(&pValue); + op1 = gtNewStringLiteralNode(iat, pValue); + goto FIELD_DONE; + } + break; + + default: + assert(!"Unexpected fieldAccessor"); + } + + if (!isLoadAddress) + { + + if (prefixFlags & PREFIX_VOLATILE) + { + op1->gtFlags |= GTF_DONT_CSE; // Can't CSE a volatile + op1->gtFlags |= GTF_ORDER_SIDEEFF; // Prevent this from being reordered + + if (!usesHelper) + { + assert((op1->OperGet() == GT_FIELD) || (op1->OperGet() == GT_IND) || + (op1->OperGet() == GT_OBJ)); + op1->gtFlags |= GTF_IND_VOLATILE; + } + } + + if (prefixFlags & PREFIX_UNALIGNED) + { + if (!usesHelper) + { + assert((op1->OperGet() == GT_FIELD) || (op1->OperGet() == GT_IND) || + (op1->OperGet() == GT_OBJ)); + op1->gtFlags |= GTF_IND_UNALIGNED; + } + } + } + + /* Check if the class needs explicit initialization */ + + if (fieldInfo.fieldFlags & CORINFO_FLG_FIELD_INITCLASS) + { + GenTreePtr helperNode = impInitClass(&resolvedToken); + if (compDonotInline()) + { + return; + } + if (helperNode != nullptr) + { + op1 = gtNewOperNode(GT_COMMA, op1->TypeGet(), helperNode, op1); + } + } + + FIELD_DONE: + impPushOnStack(op1, tiRetVal); + } + break; + + case CEE_STFLD: + case CEE_STSFLD: + { + + BOOL isStoreStatic = (opcode == CEE_STSFLD); + + CORINFO_CLASS_HANDLE fieldClsHnd; // class of the field (if it's a ref type) + + /* Get the CP_Fieldref index */ + + assertImp(sz == sizeof(unsigned)); + + _impResolveToken(CORINFO_TOKENKIND_Field); + + JITDUMP(" %08X", resolvedToken.token); + + int aflags = CORINFO_ACCESS_SET; + GenTreePtr obj = nullptr; + typeInfo* tiObj = nullptr; + typeInfo tiVal; + + /* Pull the value from the stack */ + op2 = impPopStack(tiVal); + clsHnd = tiVal.GetClassHandle(); + + if (opcode == CEE_STFLD) + { + tiObj = &impStackTop().seTypeInfo; + obj = impPopStack().val; + + if (impIsThis(obj)) + { + aflags |= CORINFO_ACCESS_THIS; + + // An optimization for Contextful classes: + // we unwrap the proxy when we have a 'this reference' + + if (info.compUnwrapContextful) + { + aflags |= CORINFO_ACCESS_UNWRAP; + } + } + } + + eeGetFieldInfo(&resolvedToken, (CORINFO_ACCESS_FLAGS)aflags, &fieldInfo); + + // Figure out the type of the member. We always call canAccessField, so you always need this + // handle + CorInfoType ciType = fieldInfo.fieldType; + fieldClsHnd = fieldInfo.structType; + + lclTyp = JITtype2varType(ciType); + + if (compIsForInlining()) + { + /* Is this a 'special' (COM) field? or a TLS ref static field?, field stored int GC heap? or + * per-inst static? */ + + switch (fieldInfo.fieldAccessor) + { + case CORINFO_FIELD_INSTANCE_HELPER: + case CORINFO_FIELD_INSTANCE_ADDR_HELPER: + case CORINFO_FIELD_STATIC_ADDR_HELPER: + case CORINFO_FIELD_STATIC_TLS: + + compInlineResult->NoteFatal(InlineObservation::CALLEE_STFLD_NEEDS_HELPER); + return; + + case CORINFO_FIELD_STATIC_GENERICS_STATIC_HELPER: + + /* We may be able to inline the field accessors in specific instantiations of generic + * methods */ + compInlineResult->NoteFatal(InlineObservation::CALLSITE_STFLD_NEEDS_HELPER); + return; + + default: + break; + } + } + + impHandleAccessAllowed(fieldInfo.accessAllowed, &fieldInfo.accessCalloutHelper); + + if (tiVerificationNeeded) + { + verVerifyField(&resolvedToken, fieldInfo, tiObj, TRUE); + typeInfo fieldType = verMakeTypeInfo(ciType, fieldClsHnd); + Verify(tiCompatibleWith(tiVal, fieldType.NormaliseForStack(), true), "type mismatch"); + } + else + { + // tiVerificationNeed is false. + // Raise InvalidProgramException if static store accesses non-static field + if (isStoreStatic && ((fieldInfo.fieldFlags & CORINFO_FLG_FIELD_STATIC) == 0)) + { + BADCODE("static access on an instance field"); + } + } + + // We are using stfld on a static field. + // We allow it, but need to eval any side-effects for obj + if ((fieldInfo.fieldFlags & CORINFO_FLG_FIELD_STATIC) && obj != nullptr) + { + if (obj->gtFlags & GTF_SIDE_EFFECT) + { + obj = gtUnusedValNode(obj); + impAppendTree(obj, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs); + } + obj = nullptr; + } + + /* Preserve 'small' int types */ + if (lclTyp > TYP_INT) + { + lclTyp = genActualType(lclTyp); + } + + switch (fieldInfo.fieldAccessor) + { + case CORINFO_FIELD_INSTANCE: +#ifdef FEATURE_READYTORUN_COMPILER + case CORINFO_FIELD_INSTANCE_WITH_BASE: +#endif + { + obj = impCheckForNullPointer(obj); + + /* Create the data member node */ + op1 = gtNewFieldRef(lclTyp, resolvedToken.hField, obj, fieldInfo.offset); + DWORD typeFlags = info.compCompHnd->getClassAttribs(resolvedToken.hClass); + if (StructHasOverlappingFields(typeFlags)) + { + op1->gtField.gtFldMayOverlap = true; + } + +#ifdef FEATURE_READYTORUN_COMPILER + if (fieldInfo.fieldAccessor == CORINFO_FIELD_INSTANCE_WITH_BASE) + op1->gtField.gtFieldLookup = fieldInfo.fieldLookup; +#endif + + op1->gtFlags |= (obj->gtFlags & GTF_GLOB_EFFECT); + + if (fgAddrCouldBeNull(obj)) + { + op1->gtFlags |= GTF_EXCEPT; + } + + // If gtFldObj is a BYREF then our target is a value class and + // it could point anywhere, example a boxed class static int + if (obj->gtType == TYP_BYREF) + { + op1->gtFlags |= GTF_IND_TGTANYWHERE; + } + + if (compIsForInlining() && + impInlineIsGuaranteedThisDerefBeforeAnySideEffects(op2, obj, impInlineInfo->inlArgInfo)) + { + impInlineInfo->thisDereferencedFirst = true; + } + } + break; + + case CORINFO_FIELD_STATIC_TLS: +#ifdef _TARGET_X86_ + // Legacy TLS access is implemented as intrinsic on x86 only + + /* Create the data member node */ + op1 = gtNewFieldRef(lclTyp, resolvedToken.hField, NULL, fieldInfo.offset); + op1->gtFlags |= GTF_IND_TLS_REF; // fgMorphField will handle the transformation + + break; +#else + fieldInfo.fieldAccessor = CORINFO_FIELD_STATIC_ADDR_HELPER; + + __fallthrough; +#endif + + case CORINFO_FIELD_STATIC_ADDR_HELPER: + case CORINFO_FIELD_INSTANCE_HELPER: + case CORINFO_FIELD_INSTANCE_ADDR_HELPER: + op1 = gtNewRefCOMfield(obj, &resolvedToken, (CORINFO_ACCESS_FLAGS)aflags, &fieldInfo, lclTyp, + clsHnd, op2); + goto SPILL_APPEND; + + case CORINFO_FIELD_STATIC_ADDRESS: + case CORINFO_FIELD_STATIC_RVA_ADDRESS: + case CORINFO_FIELD_STATIC_SHARED_STATIC_HELPER: + case CORINFO_FIELD_STATIC_GENERICS_STATIC_HELPER: + op1 = impImportStaticFieldAccess(&resolvedToken, (CORINFO_ACCESS_FLAGS)aflags, &fieldInfo, + lclTyp); + break; + + default: + assert(!"Unexpected fieldAccessor"); + } + + // Create the member assignment, unless we have a struct. + // TODO-1stClassStructs: This could be limited to TYP_STRUCT, to avoid extra copies. + bool deferStructAssign = varTypeIsStruct(lclTyp); + + if (!deferStructAssign) + { + if (prefixFlags & PREFIX_VOLATILE) + { + assert((op1->OperGet() == GT_FIELD) || (op1->OperGet() == GT_IND)); + op1->gtFlags |= GTF_DONT_CSE; // Can't CSE a volatile + op1->gtFlags |= GTF_ORDER_SIDEEFF; // Prevent this from being reordered + op1->gtFlags |= GTF_IND_VOLATILE; + } + if (prefixFlags & PREFIX_UNALIGNED) + { + assert((op1->OperGet() == GT_FIELD) || (op1->OperGet() == GT_IND)); + op1->gtFlags |= GTF_IND_UNALIGNED; + } + + /* V4.0 allows assignment of i4 constant values to i8 type vars when IL verifier is bypassed (full + trust + apps). The reason this works is that JIT stores an i4 constant in Gentree union during + importation + and reads from the union as if it were a long during code generation. Though this can potentially + read garbage, one can get lucky to have this working correctly. + + This code pattern is generated by Dev10 MC++ compiler while storing to fields when compiled with + /O2 + switch (default when compiling retail configs in Dev10) and a customer app has taken a dependency + on + it. To be backward compatible, we will explicitly add an upward cast here so that it works + correctly + always. + + Note that this is limited to x86 alone as thereis no back compat to be addressed for Arm JIT for + V4.0. + */ + CLANG_FORMAT_COMMENT_ANCHOR; + +#ifdef _TARGET_X86_ + if (op1->TypeGet() != op2->TypeGet() && op2->OperIsConst() && varTypeIsIntOrI(op2->TypeGet()) && + varTypeIsLong(op1->TypeGet())) + { + op2 = gtNewCastNode(op1->TypeGet(), op2, op1->TypeGet()); + } +#endif + +#ifdef _TARGET_64BIT_ + // Automatic upcast for a GT_CNS_INT into TYP_I_IMPL + if ((op2->OperGet() == GT_CNS_INT) && varTypeIsI(lclTyp) && !varTypeIsI(op2->gtType)) + { + op2->gtType = TYP_I_IMPL; + } + else + { + // Allow a downcast of op2 from TYP_I_IMPL into a 32-bit Int for x86 JIT compatiblity + // + if (varTypeIsI(op2->gtType) && (genActualType(lclTyp) == TYP_INT)) + { + op2 = gtNewCastNode(TYP_INT, op2, TYP_INT); + } + // Allow an upcast of op2 from a 32-bit Int into TYP_I_IMPL for x86 JIT compatiblity + // + if (varTypeIsI(lclTyp) && (genActualType(op2->gtType) == TYP_INT)) + { + op2 = gtNewCastNode(TYP_I_IMPL, op2, TYP_I_IMPL); + } + } +#endif + +#if !FEATURE_X87_DOUBLES + // We can generate an assignment to a TYP_FLOAT from a TYP_DOUBLE + // We insert a cast to the dest 'op1' type + // + if ((op1->TypeGet() != op2->TypeGet()) && varTypeIsFloating(op1->gtType) && + varTypeIsFloating(op2->gtType)) + { + op2 = gtNewCastNode(op1->TypeGet(), op2, op1->TypeGet()); + } +#endif // !FEATURE_X87_DOUBLES + + op1 = gtNewAssignNode(op1, op2); + + /* Mark the expression as containing an assignment */ + + op1->gtFlags |= GTF_ASG; + } + + /* Check if the class needs explicit initialization */ + + if (fieldInfo.fieldFlags & CORINFO_FLG_FIELD_INITCLASS) + { + GenTreePtr helperNode = impInitClass(&resolvedToken); + if (compDonotInline()) + { + return; + } + if (helperNode != nullptr) + { + op1 = gtNewOperNode(GT_COMMA, op1->TypeGet(), helperNode, op1); + } + } + + /* stfld can interfere with value classes (consider the sequence + ldloc, ldloca, ..., stfld, stloc). We will be conservative and + spill all value class references from the stack. */ + + if (obj && ((obj->gtType == TYP_BYREF) || (obj->gtType == TYP_I_IMPL))) + { + assert(tiObj); + + if (impIsValueType(tiObj)) + { + impSpillEvalStack(); + } + else + { + impSpillValueClasses(); + } + } + + /* Spill any refs to the same member from the stack */ + + impSpillLclRefs((ssize_t)resolvedToken.hField); + + /* stsfld also interferes with indirect accesses (for aliased + statics) and calls. But don't need to spill other statics + as we have explicitly spilled this particular static field. */ + + impSpillSideEffects(false, (unsigned)CHECK_SPILL_ALL DEBUGARG("spill side effects before STFLD")); + + if (deferStructAssign) + { + op1 = impAssignStruct(op1, op2, clsHnd, (unsigned)CHECK_SPILL_ALL); + } + } + goto APPEND; + + case CEE_NEWARR: + { + + /* Get the class type index operand */ + + _impResolveToken(CORINFO_TOKENKIND_Newarr); + + JITDUMP(" %08X", resolvedToken.token); + + if (!opts.IsReadyToRun()) + { + // Need to restore array classes before creating array objects on the heap + op1 = impTokenToHandle(&resolvedToken, nullptr, TRUE /*mustRestoreHandle*/); + if (op1 == nullptr) + { // compDonotInline() + return; + } + } + + if (tiVerificationNeeded) + { + // As per ECMA 'numElems' specified can be either int32 or native int. + Verify(impStackTop().seTypeInfo.IsIntOrNativeIntType(), "bad bound"); + + CORINFO_CLASS_HANDLE elemTypeHnd; + info.compCompHnd->getChildType(resolvedToken.hClass, &elemTypeHnd); + Verify(elemTypeHnd == nullptr || + !(info.compCompHnd->getClassAttribs(elemTypeHnd) & CORINFO_FLG_CONTAINS_STACK_PTR), + "array of byref-like type"); + tiRetVal = verMakeTypeInfo(resolvedToken.hClass); + } + + accessAllowedResult = + info.compCompHnd->canAccessClass(&resolvedToken, info.compMethodHnd, &calloutHelper); + impHandleAccessAllowed(accessAllowedResult, &calloutHelper); + + /* Form the arglist: array class handle, size */ + op2 = impPopStack().val; + assertImp(genActualTypeIsIntOrI(op2->gtType)); + +#ifdef FEATURE_READYTORUN_COMPILER + if (opts.IsReadyToRun()) + { + op1 = impReadyToRunHelperToTree(&resolvedToken, CORINFO_HELP_READYTORUN_NEWARR_1, TYP_REF, + gtNewArgList(op2)); + usingReadyToRunHelper = (op1 != NULL); + + if (!usingReadyToRunHelper) + { + // TODO: ReadyToRun: When generic dictionary lookups are necessary, replace the lookup call + // and the newarr call with a single call to a dynamic R2R cell that will: + // 1) Load the context + // 2) Perform the generic dictionary lookup and caching, and generate the appropriate stub + // 3) Allocate the new array + // Reason: performance (today, we'll always use the slow helper for the R2R generics case) + + // Need to restore array classes before creating array objects on the heap + op1 = impTokenToHandle(&resolvedToken, NULL, TRUE /*mustRestoreHandle*/); + if (op1 == NULL) // compDonotInline() + return; + } + } + + if (!usingReadyToRunHelper) +#endif + { + args = gtNewArgList(op1, op2); + + /* Create a call to 'new' */ + + // Note that this only works for shared generic code because the same helper is used for all + // reference array types + op1 = + gtNewHelperCallNode(info.compCompHnd->getNewArrHelper(resolvedToken.hClass), TYP_REF, 0, args); + } + + op1->gtCall.compileTimeHelperArgumentHandle = (CORINFO_GENERIC_HANDLE)resolvedToken.hClass; + + /* Remember that this basic block contains 'new' of an sd array */ + + block->bbFlags |= BBF_HAS_NEWARRAY; + optMethodFlags |= OMF_HAS_NEWARRAY; + + /* Push the result of the call on the stack */ + + impPushOnStack(op1, tiRetVal); + + callTyp = TYP_REF; + } + break; + + case CEE_LOCALLOC: + assert(!compIsForInlining()); + + if (tiVerificationNeeded) + { + Verify(false, "bad opcode"); + } + + // We don't allow locallocs inside handlers + if (block->hasHndIndex()) + { + BADCODE("Localloc can't be inside handler"); + } + + /* The FP register may not be back to the original value at the end + of the method, even if the frame size is 0, as localloc may + have modified it. So we will HAVE to reset it */ + + compLocallocUsed = true; + setNeedsGSSecurityCookie(); + + // Get the size to allocate + + op2 = impPopStack().val; + assertImp(genActualTypeIsIntOrI(op2->gtType)); + + if (verCurrentState.esStackDepth != 0) + { + BADCODE("Localloc can only be used when the stack is empty"); + } + + op1 = gtNewOperNode(GT_LCLHEAP, TYP_I_IMPL, op2); + + // May throw a stack overflow exception. Obviously, we don't want locallocs to be CSE'd. + + op1->gtFlags |= (GTF_EXCEPT | GTF_DONT_CSE); + + impPushOnStack(op1, tiRetVal); + break; + + case CEE_ISINST: + + /* Get the type token */ + assertImp(sz == sizeof(unsigned)); + + _impResolveToken(CORINFO_TOKENKIND_Casting); + + JITDUMP(" %08X", resolvedToken.token); + + if (!opts.IsReadyToRun()) + { + op2 = impTokenToHandle(&resolvedToken, nullptr, FALSE); + if (op2 == nullptr) + { // compDonotInline() + return; + } + } + + if (tiVerificationNeeded) + { + Verify(impStackTop().seTypeInfo.IsObjRef(), "obj reference needed"); + // Even if this is a value class, we know it is boxed. + tiRetVal = typeInfo(TI_REF, resolvedToken.hClass); + } + accessAllowedResult = + info.compCompHnd->canAccessClass(&resolvedToken, info.compMethodHnd, &calloutHelper); + impHandleAccessAllowed(accessAllowedResult, &calloutHelper); + + op1 = impPopStack().val; + +#ifdef FEATURE_READYTORUN_COMPILER + if (opts.IsReadyToRun()) + { + GenTreePtr opLookup = + impReadyToRunHelperToTree(&resolvedToken, CORINFO_HELP_READYTORUN_ISINSTANCEOF, TYP_REF, + gtNewArgList(op1)); + usingReadyToRunHelper = (opLookup != NULL); + op1 = (usingReadyToRunHelper ? opLookup : op1); + + if (!usingReadyToRunHelper) + { + // TODO: ReadyToRun: When generic dictionary lookups are necessary, replace the lookup call + // and the isinstanceof_any call with a single call to a dynamic R2R cell that will: + // 1) Load the context + // 2) Perform the generic dictionary lookup and caching, and generate the appropriate stub + // 3) Perform the 'is instance' check on the input object + // Reason: performance (today, we'll always use the slow helper for the R2R generics case) + + op2 = impTokenToHandle(&resolvedToken, NULL, FALSE); + if (op2 == NULL) // compDonotInline() + return; + } + } + + if (!usingReadyToRunHelper) +#endif + { + op1 = impCastClassOrIsInstToTree(op1, op2, &resolvedToken, false); + } + if (compDonotInline()) + { + return; + } + + impPushOnStack(op1, tiRetVal); + + break; + + case CEE_REFANYVAL: + + // get the class handle and make a ICON node out of it + + _impResolveToken(CORINFO_TOKENKIND_Class); + + JITDUMP(" %08X", resolvedToken.token); + + op2 = impTokenToHandle(&resolvedToken); + if (op2 == nullptr) + { // compDonotInline() + return; + } + + if (tiVerificationNeeded) + { + Verify(typeInfo::AreEquivalent(impStackTop().seTypeInfo, verMakeTypeInfo(impGetRefAnyClass())), + "need refany"); + tiRetVal = verMakeTypeInfo(resolvedToken.hClass).MakeByRef(); + } + + op1 = impPopStack().val; + // make certain it is normalized; + op1 = impNormStructVal(op1, impGetRefAnyClass(), (unsigned)CHECK_SPILL_ALL); + + // Call helper GETREFANY(classHandle, op1); + args = gtNewArgList(op2, op1); + op1 = gtNewHelperCallNode(CORINFO_HELP_GETREFANY, TYP_BYREF, 0, args); + + impPushOnStack(op1, tiRetVal); + break; + + case CEE_REFANYTYPE: + + if (tiVerificationNeeded) + { + Verify(typeInfo::AreEquivalent(impStackTop().seTypeInfo, verMakeTypeInfo(impGetRefAnyClass())), + "need refany"); + } + + op1 = impPopStack().val; + + // make certain it is normalized; + op1 = impNormStructVal(op1, impGetRefAnyClass(), (unsigned)CHECK_SPILL_ALL); + + if (op1->gtOper == GT_OBJ) + { + // Get the address of the refany + op1 = op1->gtOp.gtOp1; + + // Fetch the type from the correct slot + op1 = gtNewOperNode(GT_ADD, TYP_BYREF, op1, + gtNewIconNode(offsetof(CORINFO_RefAny, type), TYP_I_IMPL)); + op1 = gtNewOperNode(GT_IND, TYP_BYREF, op1); + } + else + { + assertImp(op1->gtOper == GT_MKREFANY); + + // The pointer may have side-effects + if (op1->gtOp.gtOp1->gtFlags & GTF_SIDE_EFFECT) + { + impAppendTree(op1->gtOp.gtOp1, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs); +#ifdef DEBUG + impNoteLastILoffs(); +#endif + } + + // We already have the class handle + op1 = op1->gtOp.gtOp2; + } + + // convert native TypeHandle to RuntimeTypeHandle + { + GenTreeArgList* helperArgs = gtNewArgList(op1); + + op1 = gtNewHelperCallNode(CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE_MAYBENULL, TYP_STRUCT, GTF_EXCEPT, + helperArgs); + + // The handle struct is returned in register + op1->gtCall.gtReturnType = TYP_REF; + + tiRetVal = typeInfo(TI_STRUCT, impGetTypeHandleClass()); + } + + impPushOnStack(op1, tiRetVal); + break; + + case CEE_LDTOKEN: + { + /* Get the Class index */ + assertImp(sz == sizeof(unsigned)); + lastLoadToken = codeAddr; + _impResolveToken(CORINFO_TOKENKIND_Ldtoken); + + tokenType = info.compCompHnd->getTokenTypeAsHandle(&resolvedToken); + + op1 = impTokenToHandle(&resolvedToken, nullptr, TRUE); + if (op1 == nullptr) + { // compDonotInline() + return; + } + + helper = CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE; + assert(resolvedToken.hClass != nullptr); + + if (resolvedToken.hMethod != nullptr) + { + helper = CORINFO_HELP_METHODDESC_TO_STUBRUNTIMEMETHOD; + } + else if (resolvedToken.hField != nullptr) + { + helper = CORINFO_HELP_FIELDDESC_TO_STUBRUNTIMEFIELD; + } + + GenTreeArgList* helperArgs = gtNewArgList(op1); + + op1 = gtNewHelperCallNode(helper, TYP_STRUCT, GTF_EXCEPT, helperArgs); + + // The handle struct is returned in register + op1->gtCall.gtReturnType = TYP_REF; + + tiRetVal = verMakeTypeInfo(tokenType); + impPushOnStack(op1, tiRetVal); + } + break; + + case CEE_UNBOX: + case CEE_UNBOX_ANY: + { + /* Get the Class index */ + assertImp(sz == sizeof(unsigned)); + + _impResolveToken(CORINFO_TOKENKIND_Class); + + JITDUMP(" %08X", resolvedToken.token); + + BOOL runtimeLookup; + op2 = impTokenToHandle(&resolvedToken, &runtimeLookup); + if (op2 == nullptr) + { // compDonotInline() + return; + } + + // Run this always so we can get access exceptions even with SkipVerification. + accessAllowedResult = + info.compCompHnd->canAccessClass(&resolvedToken, info.compMethodHnd, &calloutHelper); + impHandleAccessAllowed(accessAllowedResult, &calloutHelper); + + if (opcode == CEE_UNBOX_ANY && !eeIsValueClass(resolvedToken.hClass)) + { + if (tiVerificationNeeded) + { + typeInfo tiUnbox = impStackTop().seTypeInfo; + Verify(tiUnbox.IsObjRef(), "bad unbox.any arg"); + tiRetVal = verMakeTypeInfo(resolvedToken.hClass); + tiRetVal.NormaliseForStack(); + } + op1 = impPopStack().val; + goto CASTCLASS; + } + + /* Pop the object and create the unbox helper call */ + /* You might think that for UNBOX_ANY we need to push a different */ + /* (non-byref) type, but here we're making the tiRetVal that is used */ + /* for the intermediate pointer which we then transfer onto the OBJ */ + /* instruction. OBJ then creates the appropriate tiRetVal. */ + if (tiVerificationNeeded) + { + typeInfo tiUnbox = impStackTop().seTypeInfo; + Verify(tiUnbox.IsObjRef(), "Bad unbox arg"); + + tiRetVal = verMakeTypeInfo(resolvedToken.hClass); + Verify(tiRetVal.IsValueClass(), "not value class"); + tiRetVal.MakeByRef(); + + // We always come from an objref, so this is safe byref + tiRetVal.SetIsPermanentHomeByRef(); + tiRetVal.SetIsReadonlyByRef(); + } + + op1 = impPopStack().val; + assertImp(op1->gtType == TYP_REF); + + helper = info.compCompHnd->getUnBoxHelper(resolvedToken.hClass); + assert(helper == CORINFO_HELP_UNBOX || helper == CORINFO_HELP_UNBOX_NULLABLE); + + // We only want to expand inline the normal UNBOX helper; + expandInline = (helper == CORINFO_HELP_UNBOX); + + if (expandInline) + { + if (compCurBB->isRunRarely()) + { + expandInline = false; // not worth the code expansion + } + } + + if (expandInline) + { + // we are doing normal unboxing + // inline the common case of the unbox helper + // UNBOX(exp) morphs into + // clone = pop(exp); + // ((*clone == typeToken) ? nop : helper(clone, typeToken)); + // push(clone + sizeof(void*)) + // + GenTreePtr cloneOperand; + op1 = impCloneExpr(op1, &cloneOperand, NO_CLASS_HANDLE, (unsigned)CHECK_SPILL_ALL, + nullptr DEBUGARG("inline UNBOX clone1")); + op1 = gtNewOperNode(GT_IND, TYP_I_IMPL, op1); + + GenTreePtr condBox = gtNewOperNode(GT_EQ, TYP_INT, op1, op2); + + op1 = impCloneExpr(cloneOperand, &cloneOperand, NO_CLASS_HANDLE, (unsigned)CHECK_SPILL_ALL, + nullptr DEBUGARG("inline UNBOX clone2")); + op2 = impTokenToHandle(&resolvedToken); + if (op2 == nullptr) + { // compDonotInline() + return; + } + args = gtNewArgList(op2, op1); + op1 = gtNewHelperCallNode(helper, TYP_VOID, 0, args); + + op1 = new (this, GT_COLON) GenTreeColon(TYP_VOID, gtNewNothingNode(), op1); + op1 = gtNewQmarkNode(TYP_VOID, condBox, op1); + condBox->gtFlags |= GTF_RELOP_QMARK; + + // QMARK nodes cannot reside on the evaluation stack. Because there + // may be other trees on the evaluation stack that side-effect the + // sources of the UNBOX operation we must spill the stack. + + impAppendTree(op1, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs); + + // Create the address-expression to reference past the object header + // to the beginning of the value-type. Today this means adjusting + // past the base of the objects vtable field which is pointer sized. + + op2 = gtNewIconNode(sizeof(void*), TYP_I_IMPL); + op1 = gtNewOperNode(GT_ADD, TYP_BYREF, cloneOperand, op2); + } + else + { + unsigned callFlags = (helper == CORINFO_HELP_UNBOX) ? 0 : GTF_EXCEPT; + + // Don't optimize, just call the helper and be done with it + args = gtNewArgList(op2, op1); + op1 = gtNewHelperCallNode(helper, + (var_types)((helper == CORINFO_HELP_UNBOX) ? TYP_BYREF : TYP_STRUCT), + callFlags, args); + } + + assert(helper == CORINFO_HELP_UNBOX && op1->gtType == TYP_BYREF || // Unbox helper returns a byref. + helper == CORINFO_HELP_UNBOX_NULLABLE && + varTypeIsStruct(op1) // UnboxNullable helper returns a struct. + ); + + /* + ---------------------------------------------------------------------- + | \ helper | | | + | \ | | | + | \ | CORINFO_HELP_UNBOX | CORINFO_HELP_UNBOX_NULLABLE | + | \ | (which returns a BYREF) | (which returns a STRUCT) | | + | opcode \ | | | + |--------------------------------------------------------------------- + | UNBOX | push the BYREF | spill the STRUCT to a local, | + | | | push the BYREF to this local | + |--------------------------------------------------------------------- + | UNBOX_ANY | push a GT_OBJ of | push the STRUCT | + | | the BYREF | For Linux when the | + | | | struct is returned in two | + | | | registers create a temp | + | | | which address is passed to | + | | | the unbox_nullable helper. | + |--------------------------------------------------------------------- + */ + + if (opcode == CEE_UNBOX) + { + if (helper == CORINFO_HELP_UNBOX_NULLABLE) + { + // Unbox nullable helper returns a struct type. + // We need to spill it to a temp so than can take the address of it. + // Here we need unsafe value cls check, since the address of struct is taken to be used + // further along and potetially be exploitable. + + unsigned tmp = lvaGrabTemp(true DEBUGARG("UNBOXing a nullable")); + lvaSetStruct(tmp, resolvedToken.hClass, true /* unsafe value cls check */); + + op2 = gtNewLclvNode(tmp, TYP_STRUCT); + op1 = impAssignStruct(op2, op1, resolvedToken.hClass, (unsigned)CHECK_SPILL_ALL); + assert(op1->gtType == TYP_VOID); // We must be assigning the return struct to the temp. + + op2 = gtNewLclvNode(tmp, TYP_STRUCT); + op2 = gtNewOperNode(GT_ADDR, TYP_BYREF, op2); + op1 = gtNewOperNode(GT_COMMA, TYP_BYREF, op1, op2); + } + + assert(op1->gtType == TYP_BYREF); + assert(!tiVerificationNeeded || tiRetVal.IsByRef()); + } + else + { + assert(opcode == CEE_UNBOX_ANY); + + if (helper == CORINFO_HELP_UNBOX) + { + // Normal unbox helper returns a TYP_BYREF. + impPushOnStack(op1, tiRetVal); + oper = GT_OBJ; + goto OBJ; + } + + assert(helper == CORINFO_HELP_UNBOX_NULLABLE && "Make sure the helper is nullable!"); + +#if FEATURE_MULTIREG_RET + + if (varTypeIsStruct(op1) && IsMultiRegReturnedType(resolvedToken.hClass)) + { + // Unbox nullable helper returns a TYP_STRUCT. + // For the multi-reg case we need to spill it to a temp so that + // we can pass the address to the unbox_nullable jit helper. + + unsigned tmp = lvaGrabTemp(true DEBUGARG("UNBOXing a register returnable nullable")); + lvaTable[tmp].lvIsMultiRegArg = true; + lvaSetStruct(tmp, resolvedToken.hClass, true /* unsafe value cls check */); + + op2 = gtNewLclvNode(tmp, TYP_STRUCT); + op1 = impAssignStruct(op2, op1, resolvedToken.hClass, (unsigned)CHECK_SPILL_ALL); + assert(op1->gtType == TYP_VOID); // We must be assigning the return struct to the temp. + + op2 = gtNewLclvNode(tmp, TYP_STRUCT); + op2 = gtNewOperNode(GT_ADDR, TYP_BYREF, op2); + op1 = gtNewOperNode(GT_COMMA, TYP_BYREF, op1, op2); + + // In this case the return value of the unbox helper is TYP_BYREF. + // Make sure the right type is placed on the operand type stack. + impPushOnStack(op1, tiRetVal); + + // Load the struct. + oper = GT_OBJ; + + assert(op1->gtType == TYP_BYREF); + assert(!tiVerificationNeeded || tiRetVal.IsByRef()); + + goto OBJ; + } + else + +#endif // !FEATURE_MULTIREG_RET + + { + // If non register passable struct we have it materialized in the RetBuf. + assert(op1->gtType == TYP_STRUCT); + tiRetVal = verMakeTypeInfo(resolvedToken.hClass); + assert(tiRetVal.IsValueClass()); + } + } + + impPushOnStack(op1, tiRetVal); + } + break; + + case CEE_BOX: + { + /* Get the Class index */ + assertImp(sz == sizeof(unsigned)); + + _impResolveToken(CORINFO_TOKENKIND_Box); + + JITDUMP(" %08X", resolvedToken.token); + + if (tiVerificationNeeded) + { + typeInfo tiActual = impStackTop().seTypeInfo; + typeInfo tiBox = verMakeTypeInfo(resolvedToken.hClass); + + Verify(verIsBoxable(tiBox), "boxable type expected"); + + // check the class constraints of the boxed type in case we are boxing an uninitialized value + Verify(info.compCompHnd->satisfiesClassConstraints(resolvedToken.hClass), + "boxed type has unsatisfied class constraints"); + + Verify(tiCompatibleWith(tiActual, tiBox.NormaliseForStack(), true), "type mismatch"); + + // Observation: the following code introduces a boxed value class on the stack, but, + // according to the ECMA spec, one would simply expect: tiRetVal = + // typeInfo(TI_REF,impGetObjectClass()); + + // Push the result back on the stack, + // even if clsHnd is a value class we want the TI_REF + // we call back to the EE to get find out what hte type we should push (for nullable<T> we push T) + tiRetVal = typeInfo(TI_REF, info.compCompHnd->getTypeForBox(resolvedToken.hClass)); + } + + accessAllowedResult = + info.compCompHnd->canAccessClass(&resolvedToken, info.compMethodHnd, &calloutHelper); + impHandleAccessAllowed(accessAllowedResult, &calloutHelper); + + // Note BOX can be used on things that are not value classes, in which + // case we get a NOP. However the verifier's view of the type on the + // stack changes (in generic code a 'T' becomes a 'boxed T') + if (!eeIsValueClass(resolvedToken.hClass)) + { + verCurrentState.esStack[verCurrentState.esStackDepth - 1].seTypeInfo = tiRetVal; + break; + } + + // Look ahead for unbox.any + if (codeAddr + (sz + 1 + sizeof(mdToken)) <= codeEndp && codeAddr[sz] == CEE_UNBOX_ANY) + { + DWORD classAttribs = info.compCompHnd->getClassAttribs(resolvedToken.hClass); + if (!(classAttribs & CORINFO_FLG_SHAREDINST)) + { + CORINFO_RESOLVED_TOKEN unboxResolvedToken; + + impResolveToken(codeAddr + (sz + 1), &unboxResolvedToken, CORINFO_TOKENKIND_Class); + + if (unboxResolvedToken.hClass == resolvedToken.hClass) + { + // Skip the next unbox.any instruction + sz += sizeof(mdToken) + 1; + break; + } + } + } + + impImportAndPushBox(&resolvedToken); + if (compDonotInline()) + { + return; + } + } + break; + + case CEE_SIZEOF: + + /* Get the Class index */ + assertImp(sz == sizeof(unsigned)); + + _impResolveToken(CORINFO_TOKENKIND_Class); + + JITDUMP(" %08X", resolvedToken.token); + + if (tiVerificationNeeded) + { + tiRetVal = typeInfo(TI_INT); + } + + op1 = gtNewIconNode(info.compCompHnd->getClassSize(resolvedToken.hClass)); + impPushOnStack(op1, tiRetVal); + break; + + case CEE_CASTCLASS: + + /* Get the Class index */ + + assertImp(sz == sizeof(unsigned)); + + _impResolveToken(CORINFO_TOKENKIND_Casting); + + JITDUMP(" %08X", resolvedToken.token); + + if (!opts.IsReadyToRun()) + { + op2 = impTokenToHandle(&resolvedToken, nullptr, FALSE); + if (op2 == nullptr) + { // compDonotInline() + return; + } + } + + if (tiVerificationNeeded) + { + Verify(impStackTop().seTypeInfo.IsObjRef(), "object ref expected"); + // box it + tiRetVal = typeInfo(TI_REF, resolvedToken.hClass); + } + + accessAllowedResult = + info.compCompHnd->canAccessClass(&resolvedToken, info.compMethodHnd, &calloutHelper); + impHandleAccessAllowed(accessAllowedResult, &calloutHelper); + + op1 = impPopStack().val; + + /* Pop the address and create the 'checked cast' helper call */ + + // At this point we expect typeRef to contain the token, op1 to contain the value being cast, + // and op2 to contain code that creates the type handle corresponding to typeRef + CASTCLASS: + +#ifdef FEATURE_READYTORUN_COMPILER + if (opts.IsReadyToRun()) + { + GenTreePtr opLookup = impReadyToRunHelperToTree(&resolvedToken, CORINFO_HELP_READYTORUN_CHKCAST, + TYP_REF, gtNewArgList(op1)); + usingReadyToRunHelper = (opLookup != NULL); + op1 = (usingReadyToRunHelper ? opLookup : op1); + + if (!usingReadyToRunHelper) + { + // TODO: ReadyToRun: When generic dictionary lookups are necessary, replace the lookup call + // and the chkcastany call with a single call to a dynamic R2R cell that will: + // 1) Load the context + // 2) Perform the generic dictionary lookup and caching, and generate the appropriate stub + // 3) Check the object on the stack for the type-cast + // Reason: performance (today, we'll always use the slow helper for the R2R generics case) + + op2 = impTokenToHandle(&resolvedToken, NULL, FALSE); + if (op2 == NULL) // compDonotInline() + return; + } + } + + if (!usingReadyToRunHelper) +#endif + { + op1 = impCastClassOrIsInstToTree(op1, op2, &resolvedToken, true); + } + if (compDonotInline()) + { + return; + } + + /* Push the result back on the stack */ + impPushOnStack(op1, tiRetVal); + break; + + case CEE_THROW: + + if (compIsForInlining()) + { + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // TODO: Will this be too strict, given that we will inline many basic blocks? + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + /* Do we have just the exception on the stack ?*/ + + if (verCurrentState.esStackDepth != 1) + { + /* if not, just don't inline the method */ + + compInlineResult->NoteFatal(InlineObservation::CALLEE_THROW_WITH_INVALID_STACK); + return; + } + + /* Don't inline non-void conditionals that have a throw in one of the branches */ + + /* NOTE: If we do allow this, note that we can't simply do a + checkLiveness() to match the liveness at the end of the "then" + and "else" branches of the GT_COLON. The branch with the throw + will keep nothing live, so we should use the liveness at the + end of the non-throw branch. */ + + if (seenConditionalJump && (impInlineInfo->inlineCandidateInfo->fncRetType != TYP_VOID)) + { + compInlineResult->NoteFatal(InlineObservation::CALLSITE_CONDITIONAL_THROW); + return; + } + } + + if (tiVerificationNeeded) + { + tiRetVal = impStackTop().seTypeInfo; + Verify(tiRetVal.IsObjRef(), "object ref expected"); + if (verTrackObjCtorInitState && (verCurrentState.thisInitialized != TIS_Init)) + { + Verify(!tiRetVal.IsThisPtr(), "throw uninitialized this"); + } + } + + block->bbSetRunRarely(); // any block with a throw is rare + /* Pop the exception object and create the 'throw' helper call */ + + op1 = gtNewHelperCallNode(CORINFO_HELP_THROW, TYP_VOID, GTF_EXCEPT, gtNewArgList(impPopStack().val)); + + EVAL_APPEND: + if (verCurrentState.esStackDepth > 0) + { + impEvalSideEffects(); + } + + assert(verCurrentState.esStackDepth == 0); + + goto APPEND; + + case CEE_RETHROW: + + assert(!compIsForInlining()); + + if (info.compXcptnsCount == 0) + { + BADCODE("rethrow outside catch"); + } + + if (tiVerificationNeeded) + { + Verify(block->hasHndIndex(), "rethrow outside catch"); + if (block->hasHndIndex()) + { + EHblkDsc* HBtab = ehGetDsc(block->getHndIndex()); + Verify(!HBtab->HasFinallyOrFaultHandler(), "rethrow in finally or fault"); + if (HBtab->HasFilter()) + { + // we better be in the handler clause part, not the filter part + Verify(jitIsBetween(compCurBB->bbCodeOffs, HBtab->ebdHndBegOffs(), HBtab->ebdHndEndOffs()), + "rethrow in filter"); + } + } + } + + /* Create the 'rethrow' helper call */ + + op1 = gtNewHelperCallNode(CORINFO_HELP_RETHROW, TYP_VOID, GTF_EXCEPT); + + goto EVAL_APPEND; + + case CEE_INITOBJ: + + assertImp(sz == sizeof(unsigned)); + + _impResolveToken(CORINFO_TOKENKIND_Class); + + JITDUMP(" %08X", resolvedToken.token); + + if (tiVerificationNeeded) + { + typeInfo tiTo = impStackTop().seTypeInfo; + typeInfo tiInstr = verMakeTypeInfo(resolvedToken.hClass); + + Verify(tiTo.IsByRef(), "byref expected"); + Verify(!tiTo.IsReadonlyByRef(), "write to readonly byref"); + + Verify(tiCompatibleWith(tiInstr, tiTo.DereferenceByRef(), false), + "type operand incompatible with type of address"); + } + + size = info.compCompHnd->getClassSize(resolvedToken.hClass); // Size + op2 = gtNewIconNode(0); // Value + op1 = impPopStack().val; // Dest + op1 = gtNewBlockVal(op1, size); + op1 = gtNewBlkOpNode(op1, op2, size, (prefixFlags & PREFIX_VOLATILE) != 0, false); + goto SPILL_APPEND; + + case CEE_INITBLK: + + if (tiVerificationNeeded) + { + Verify(false, "bad opcode"); + } + + op3 = impPopStack().val; // Size + op2 = impPopStack().val; // Value + op1 = impPopStack().val; // Dest + + if (op3->IsCnsIntOrI()) + { + size = (unsigned)op3->AsIntConCommon()->IconValue(); + op1 = new (this, GT_BLK) GenTreeBlk(GT_BLK, TYP_STRUCT, op1, size); + } + else + { + op1 = new (this, GT_DYN_BLK) GenTreeDynBlk(op1, op3); + size = 0; + } + op1 = gtNewBlkOpNode(op1, op2, size, (prefixFlags & PREFIX_VOLATILE) != 0, false); + + goto SPILL_APPEND; + + case CEE_CPBLK: + + if (tiVerificationNeeded) + { + Verify(false, "bad opcode"); + } + op3 = impPopStack().val; // Size + op2 = impPopStack().val; // Src + op1 = impPopStack().val; // Dest + + if (op3->IsCnsIntOrI()) + { + size = (unsigned)op3->AsIntConCommon()->IconValue(); + op1 = new (this, GT_BLK) GenTreeBlk(GT_BLK, TYP_STRUCT, op1, size); + } + else + { + op1 = new (this, GT_DYN_BLK) GenTreeDynBlk(op1, op3); + size = 0; + } + if (op2->OperGet() == GT_ADDR) + { + op2 = op2->gtOp.gtOp1; + } + else + { + op2 = gtNewOperNode(GT_IND, TYP_STRUCT, op2); + } + + op1 = gtNewBlkOpNode(op1, op2, size, (prefixFlags & PREFIX_VOLATILE) != 0, true); + goto SPILL_APPEND; + + case CEE_CPOBJ: + + assertImp(sz == sizeof(unsigned)); + + _impResolveToken(CORINFO_TOKENKIND_Class); + + JITDUMP(" %08X", resolvedToken.token); + + if (tiVerificationNeeded) + { + typeInfo tiFrom = impStackTop().seTypeInfo; + typeInfo tiTo = impStackTop(1).seTypeInfo; + typeInfo tiInstr = verMakeTypeInfo(resolvedToken.hClass); + + Verify(tiFrom.IsByRef(), "expected byref source"); + Verify(tiTo.IsByRef(), "expected byref destination"); + + Verify(tiCompatibleWith(tiFrom.DereferenceByRef(), tiInstr, false), + "type of source address incompatible with type operand"); + Verify(!tiTo.IsReadonlyByRef(), "write to readonly byref"); + Verify(tiCompatibleWith(tiInstr, tiTo.DereferenceByRef(), false), + "type operand incompatible with type of destination address"); + } + + if (!eeIsValueClass(resolvedToken.hClass)) + { + op1 = impPopStack().val; // address to load from + + impBashVarAddrsToI(op1); + + assertImp(genActualType(op1->gtType) == TYP_I_IMPL || op1->gtType == TYP_BYREF); + + op1 = gtNewOperNode(GT_IND, TYP_REF, op1); + op1->gtFlags |= GTF_EXCEPT | GTF_GLOB_REF; + + impPushOnStackNoType(op1); + opcode = CEE_STIND_REF; + lclTyp = TYP_REF; + goto STIND_POST_VERIFY; + } + + op2 = impPopStack().val; // Src + op1 = impPopStack().val; // Dest + op1 = gtNewCpObjNode(op1, op2, resolvedToken.hClass, ((prefixFlags & PREFIX_VOLATILE) != 0)); + goto SPILL_APPEND; + + case CEE_STOBJ: + { + assertImp(sz == sizeof(unsigned)); + + _impResolveToken(CORINFO_TOKENKIND_Class); + + JITDUMP(" %08X", resolvedToken.token); + + if (eeIsValueClass(resolvedToken.hClass)) + { + lclTyp = TYP_STRUCT; + } + else + { + lclTyp = TYP_REF; + } + + if (tiVerificationNeeded) + { + + typeInfo tiPtr = impStackTop(1).seTypeInfo; + + // Make sure we have a good looking byref + Verify(tiPtr.IsByRef(), "pointer not byref"); + Verify(!tiPtr.IsReadonlyByRef(), "write to readonly byref"); + if (!tiPtr.IsByRef() || tiPtr.IsReadonlyByRef()) + { + compUnsafeCastUsed = true; + } + + typeInfo ptrVal = DereferenceByRef(tiPtr); + typeInfo argVal = verMakeTypeInfo(resolvedToken.hClass); + + if (!tiCompatibleWith(impStackTop(0).seTypeInfo, NormaliseForStack(argVal), true)) + { + Verify(false, "type of value incompatible with type operand"); + compUnsafeCastUsed = true; + } + + if (!tiCompatibleWith(argVal, ptrVal, false)) + { + Verify(false, "type operand incompatible with type of address"); + compUnsafeCastUsed = true; + } + } + else + { + compUnsafeCastUsed = true; + } + + if (lclTyp == TYP_REF) + { + opcode = CEE_STIND_REF; + goto STIND_POST_VERIFY; + } + + CorInfoType jitTyp = info.compCompHnd->asCorInfoType(resolvedToken.hClass); + if (impIsPrimitive(jitTyp)) + { + lclTyp = JITtype2varType(jitTyp); + goto STIND_POST_VERIFY; + } + + op2 = impPopStack().val; // Value + op1 = impPopStack().val; // Ptr + + assertImp(varTypeIsStruct(op2)); + + op1 = impAssignStructPtr(op1, op2, resolvedToken.hClass, (unsigned)CHECK_SPILL_ALL); + goto SPILL_APPEND; + } + + case CEE_MKREFANY: + + assert(!compIsForInlining()); + + // Being lazy here. Refanys are tricky in terms of gc tracking. + // Since it is uncommon, just don't perform struct promotion in any method that contains mkrefany. + + JITDUMP("disabling struct promotion because of mkrefany\n"); + fgNoStructPromotion = true; + + oper = GT_MKREFANY; + assertImp(sz == sizeof(unsigned)); + + _impResolveToken(CORINFO_TOKENKIND_Class); + + JITDUMP(" %08X", resolvedToken.token); + + op2 = impTokenToHandle(&resolvedToken, nullptr, TRUE); + if (op2 == nullptr) + { // compDonotInline() + return; + } + + if (tiVerificationNeeded) + { + typeInfo tiPtr = impStackTop().seTypeInfo; + typeInfo tiInstr = verMakeTypeInfo(resolvedToken.hClass); + + Verify(!verIsByRefLike(tiInstr), "mkrefany of byref-like class"); + Verify(!tiPtr.IsReadonlyByRef(), "readonly byref used with mkrefany"); + Verify(typeInfo::AreEquivalent(tiPtr.DereferenceByRef(), tiInstr), "type mismatch"); + } + + accessAllowedResult = + info.compCompHnd->canAccessClass(&resolvedToken, info.compMethodHnd, &calloutHelper); + impHandleAccessAllowed(accessAllowedResult, &calloutHelper); + + op1 = impPopStack().val; + + // @SPECVIOLATION: TYP_INT should not be allowed here by a strict reading of the spec. + // But JIT32 allowed it, so we continue to allow it. + assertImp(op1->TypeGet() == TYP_BYREF || op1->TypeGet() == TYP_I_IMPL || op1->TypeGet() == TYP_INT); + + // MKREFANY returns a struct. op2 is the class token. + op1 = gtNewOperNode(oper, TYP_STRUCT, op1, op2); + + impPushOnStack(op1, verMakeTypeInfo(impGetRefAnyClass())); + break; + + case CEE_LDOBJ: + { + oper = GT_OBJ; + assertImp(sz == sizeof(unsigned)); + + _impResolveToken(CORINFO_TOKENKIND_Class); + + JITDUMP(" %08X", resolvedToken.token); + + OBJ: + + tiRetVal = verMakeTypeInfo(resolvedToken.hClass); + + if (tiVerificationNeeded) + { + typeInfo tiPtr = impStackTop().seTypeInfo; + + // Make sure we have a byref + if (!tiPtr.IsByRef()) + { + Verify(false, "pointer not byref"); + compUnsafeCastUsed = true; + } + typeInfo tiPtrVal = DereferenceByRef(tiPtr); + + if (!tiCompatibleWith(tiPtrVal, tiRetVal, false)) + { + Verify(false, "type of address incompatible with type operand"); + compUnsafeCastUsed = true; + } + tiRetVal.NormaliseForStack(); + } + else + { + compUnsafeCastUsed = true; + } + + if (eeIsValueClass(resolvedToken.hClass)) + { + lclTyp = TYP_STRUCT; + } + else + { + lclTyp = TYP_REF; + opcode = CEE_LDIND_REF; + goto LDIND_POST_VERIFY; + } + + op1 = impPopStack().val; + + assertImp(op1->TypeGet() == TYP_BYREF || op1->TypeGet() == TYP_I_IMPL); + + CorInfoType jitTyp = info.compCompHnd->asCorInfoType(resolvedToken.hClass); + if (impIsPrimitive(jitTyp)) + { + op1 = gtNewOperNode(GT_IND, JITtype2varType(jitTyp), op1); + + // Could point anywhere, example a boxed class static int + op1->gtFlags |= GTF_IND_TGTANYWHERE | GTF_GLOB_REF; + assertImp(varTypeIsArithmetic(op1->gtType)); + } + else + { + // OBJ returns a struct + // and an inline argument which is the class token of the loaded obj + op1 = gtNewObjNode(resolvedToken.hClass, op1); + } + op1->gtFlags |= GTF_EXCEPT; + + impPushOnStack(op1, tiRetVal); + break; + } + + case CEE_LDLEN: + if (tiVerificationNeeded) + { + typeInfo tiArray = impStackTop().seTypeInfo; + Verify(verIsSDArray(tiArray), "bad array"); + tiRetVal = typeInfo(TI_INT); + } + + op1 = impPopStack().val; + if (!opts.MinOpts() && !opts.compDbgCode) + { + /* Use GT_ARR_LENGTH operator so rng check opts see this */ + GenTreeArrLen* arrLen = + new (this, GT_ARR_LENGTH) GenTreeArrLen(TYP_INT, op1, offsetof(CORINFO_Array, length)); + + /* Mark the block as containing a length expression */ + + if (op1->gtOper == GT_LCL_VAR) + { + block->bbFlags |= BBF_HAS_IDX_LEN; + } + + op1 = arrLen; + } + else + { + /* Create the expression "*(array_addr + ArrLenOffs)" */ + op1 = gtNewOperNode(GT_ADD, TYP_BYREF, op1, + gtNewIconNode(offsetof(CORINFO_Array, length), TYP_I_IMPL)); + op1 = gtNewOperNode(GT_IND, TYP_INT, op1); + op1->gtFlags |= GTF_IND_ARR_LEN; + } + + /* An indirection will cause a GPF if the address is null */ + op1->gtFlags |= GTF_EXCEPT; + + /* Push the result back on the stack */ + impPushOnStack(op1, tiRetVal); + break; + + case CEE_BREAK: + op1 = gtNewHelperCallNode(CORINFO_HELP_USER_BREAKPOINT, TYP_VOID); + goto SPILL_APPEND; + + case CEE_NOP: + if (opts.compDbgCode) + { + op1 = new (this, GT_NO_OP) GenTree(GT_NO_OP, TYP_VOID); + goto SPILL_APPEND; + } + break; + + /******************************** NYI *******************************/ + + case 0xCC: + OutputDebugStringA("CLR: Invalid x86 breakpoint in IL stream\n"); + + case CEE_ILLEGAL: + case CEE_MACRO_END: + + default: + BADCODE3("unknown opcode", ": %02X", (int)opcode); + } + + codeAddr += sz; + prevOpcode = opcode; + + prefixFlags = 0; + assert(!insertLdloc || opcode == CEE_DUP); + } + + assert(!insertLdloc); + + return; +#undef _impResolveToken +} +#ifdef _PREFAST_ +#pragma warning(pop) +#endif + +// Push a local/argument treeon the operand stack +void Compiler::impPushVar(GenTree* op, typeInfo tiRetVal) +{ + tiRetVal.NormaliseForStack(); + + if (verTrackObjCtorInitState && (verCurrentState.thisInitialized != TIS_Init) && tiRetVal.IsThisPtr()) + { + tiRetVal.SetUninitialisedObjRef(); + } + + impPushOnStack(op, tiRetVal); +} + +// Load a local/argument on the operand stack +// lclNum is an index into lvaTable *NOT* the arg/lcl index in the IL +void Compiler::impLoadVar(unsigned lclNum, IL_OFFSET offset, typeInfo tiRetVal) +{ + var_types lclTyp; + + if (lvaTable[lclNum].lvNormalizeOnLoad()) + { + lclTyp = lvaGetRealType(lclNum); + } + else + { + lclTyp = lvaGetActualType(lclNum); + } + + impPushVar(gtNewLclvNode(lclNum, lclTyp, offset), tiRetVal); +} + +// Load an argument on the operand stack +// Shared by the various CEE_LDARG opcodes +// ilArgNum is the argument index as specified in IL. +// It will be mapped to the correct lvaTable index +void Compiler::impLoadArg(unsigned ilArgNum, IL_OFFSET offset) +{ + Verify(ilArgNum < info.compILargsCount, "bad arg num"); + + if (compIsForInlining()) + { + if (ilArgNum >= info.compArgsCount) + { + compInlineResult->NoteFatal(InlineObservation::CALLEE_BAD_ARGUMENT_NUMBER); + return; + } + + impPushVar(impInlineFetchArg(ilArgNum, impInlineInfo->inlArgInfo, impInlineInfo->lclVarInfo), + impInlineInfo->lclVarInfo[ilArgNum].lclVerTypeInfo); + } + else + { + if (ilArgNum >= info.compArgsCount) + { + BADCODE("Bad IL"); + } + + unsigned lclNum = compMapILargNum(ilArgNum); // account for possible hidden param + + if (lclNum == info.compThisArg) + { + lclNum = lvaArg0Var; + } + + impLoadVar(lclNum, offset); + } +} + +// Load a local on the operand stack +// Shared by the various CEE_LDLOC opcodes +// ilLclNum is the local index as specified in IL. +// It will be mapped to the correct lvaTable index +void Compiler::impLoadLoc(unsigned ilLclNum, IL_OFFSET offset) +{ + if (tiVerificationNeeded) + { + Verify(ilLclNum < info.compMethodInfo->locals.numArgs, "bad loc num"); + Verify(info.compInitMem, "initLocals not set"); + } + + if (compIsForInlining()) + { + if (ilLclNum >= info.compMethodInfo->locals.numArgs) + { + compInlineResult->NoteFatal(InlineObservation::CALLEE_BAD_LOCAL_NUMBER); + return; + } + + // Get the local type + var_types lclTyp = impInlineInfo->lclVarInfo[ilLclNum + impInlineInfo->argCnt].lclTypeInfo; + + typeInfo tiRetVal = impInlineInfo->lclVarInfo[ilLclNum + impInlineInfo->argCnt].lclVerTypeInfo; + + /* Have we allocated a temp for this local? */ + + unsigned lclNum = impInlineFetchLocal(ilLclNum DEBUGARG("Inline ldloc first use temp")); + + // All vars of inlined methods should be !lvNormalizeOnLoad() + + assert(!lvaTable[lclNum].lvNormalizeOnLoad()); + lclTyp = genActualType(lclTyp); + + impPushVar(gtNewLclvNode(lclNum, lclTyp), tiRetVal); + } + else + { + if (ilLclNum >= info.compMethodInfo->locals.numArgs) + { + BADCODE("Bad IL"); + } + + unsigned lclNum = info.compArgsCount + ilLclNum; + + impLoadVar(lclNum, offset); + } +} + +#ifdef _TARGET_ARM_ +/************************************************************************************** + * + * When assigning a vararg call src to a HFA lcl dest, mark that we cannot promote the + * dst struct, because struct promotion will turn it into a float/double variable while + * the rhs will be an int/long variable. We don't code generate assignment of int into + * a float, but there is nothing that might prevent us from doing so. The tree however + * would like: (=, (typ_float, typ_int)) or (GT_TRANSFER, (typ_float, typ_int)) + * + * tmpNum - the lcl dst variable num that is a struct. + * src - the src tree assigned to the dest that is a struct/int (when varargs call.) + * hClass - the type handle for the struct variable. + * + * TODO-ARM-CQ: [301608] This is a rare scenario with varargs and struct promotion coming into play, + * however, we could do a codegen of transferring from int to float registers + * (transfer, not a cast.) + * + */ +void Compiler::impMarkLclDstNotPromotable(unsigned tmpNum, GenTreePtr src, CORINFO_CLASS_HANDLE hClass) +{ + if (src->gtOper == GT_CALL && src->gtCall.IsVarargs() && IsHfa(hClass)) + { + int hfaSlots = GetHfaCount(hClass); + var_types hfaType = GetHfaType(hClass); + + // If we have varargs we morph the method's return type to be "int" irrespective of its original + // type: struct/float at importer because the ABI calls out return in integer registers. + // We don't want struct promotion to replace an expression like this: + // lclFld_int = callvar_int() into lclFld_float = callvar_int(); + // This means an int is getting assigned to a float without a cast. Prevent the promotion. + if ((hfaType == TYP_DOUBLE && hfaSlots == sizeof(double) / REGSIZE_BYTES) || + (hfaType == TYP_FLOAT && hfaSlots == sizeof(float) / REGSIZE_BYTES)) + { + // Make sure this struct type stays as struct so we can receive the call in a struct. + lvaTable[tmpNum].lvIsMultiRegRet = true; + } + } +} +#endif // _TARGET_ARM_ + +#if FEATURE_MULTIREG_RET +GenTreePtr Compiler::impAssignMultiRegTypeToVar(GenTreePtr op, CORINFO_CLASS_HANDLE hClass) +{ + unsigned tmpNum = lvaGrabTemp(true DEBUGARG("Return value temp for multireg return.")); + impAssignTempGen(tmpNum, op, hClass, (unsigned)CHECK_SPILL_NONE); + GenTreePtr ret = gtNewLclvNode(tmpNum, op->gtType); + assert(IsMultiRegReturnedType(hClass)); + + // Mark the var so that fields are not promoted and stay together. + lvaTable[tmpNum].lvIsMultiRegRet = true; + + return ret; +} +#endif // FEATURE_MULTIREG_RET + +// do import for a return +// returns false if inlining was aborted +// opcode can be ret or call in the case of a tail.call +bool Compiler::impReturnInstruction(BasicBlock* block, int prefixFlags, OPCODE& opcode) +{ + if (tiVerificationNeeded) + { + verVerifyThisPtrInitialised(); + + unsigned expectedStack = 0; + if (info.compRetType != TYP_VOID) + { + typeInfo tiVal = impStackTop().seTypeInfo; + typeInfo tiDeclared = + verMakeTypeInfo(info.compMethodInfo->args.retType, info.compMethodInfo->args.retTypeClass); + + Verify(!verIsByRefLike(tiDeclared) || verIsSafeToReturnByRef(tiVal), "byref return"); + + Verify(tiCompatibleWith(tiVal, tiDeclared.NormaliseForStack(), true), "type mismatch"); + expectedStack = 1; + } + Verify(verCurrentState.esStackDepth == expectedStack, "stack non-empty on return"); + } + + GenTree* op2 = nullptr; + GenTree* op1 = nullptr; + CORINFO_CLASS_HANDLE retClsHnd = nullptr; + + if (info.compRetType != TYP_VOID) + { + StackEntry se = impPopStack(retClsHnd); + op2 = se.val; + + if (!compIsForInlining()) + { + impBashVarAddrsToI(op2); + op2 = impImplicitIorI4Cast(op2, info.compRetType); + op2 = impImplicitR4orR8Cast(op2, info.compRetType); + assertImp((genActualType(op2->TypeGet()) == genActualType(info.compRetType)) || + ((op2->TypeGet() == TYP_I_IMPL) && (info.compRetType == TYP_BYREF)) || + ((op2->TypeGet() == TYP_BYREF) && (info.compRetType == TYP_I_IMPL)) || + (varTypeIsFloating(op2->gtType) && varTypeIsFloating(info.compRetType)) || + (varTypeIsStruct(op2) && varTypeIsStruct(info.compRetType))); + +#ifdef DEBUG + if (opts.compGcChecks && info.compRetType == TYP_REF) + { + // DDB 3483 : JIT Stress: early termination of GC ref's life time in exception code path + // VSW 440513: Incorrect gcinfo on the return value under COMPlus_JitGCChecks=1 for methods with + // one-return BB. + + assert(op2->gtType == TYP_REF); + + // confirm that the argument is a GC pointer (for debugging (GC stress)) + GenTreeArgList* args = gtNewArgList(op2); + op2 = gtNewHelperCallNode(CORINFO_HELP_CHECK_OBJ, TYP_REF, 0, args); + + if (verbose) + { + printf("\ncompGcChecks tree:\n"); + gtDispTree(op2); + } + } +#endif + } + else + { + // inlinee's stack should be empty now. + assert(verCurrentState.esStackDepth == 0); + +#ifdef DEBUG + if (verbose) + { + printf("\n\n Inlinee Return expression (before normalization) =>\n"); + gtDispTree(op2); + } +#endif + + // Make sure the type matches the original call. + + var_types returnType = genActualType(op2->gtType); + var_types originalCallType = impInlineInfo->inlineCandidateInfo->fncRetType; + if ((returnType != originalCallType) && (originalCallType == TYP_STRUCT)) + { + originalCallType = impNormStructType(impInlineInfo->inlineCandidateInfo->methInfo.args.retTypeClass); + } + + if (returnType != originalCallType) + { + compInlineResult->NoteFatal(InlineObservation::CALLSITE_RETURN_TYPE_MISMATCH); + return false; + } + + // Below, we are going to set impInlineInfo->retExpr to the tree with the return + // expression. At this point, retExpr could already be set if there are multiple + // return blocks (meaning lvaInlineeReturnSpillTemp != BAD_VAR_NUM) and one of + // the other blocks already set it. If there is only a single return block, + // retExpr shouldn't be set. However, this is not true if we reimport a block + // with a return. In that case, retExpr will be set, then the block will be + // reimported, but retExpr won't get cleared as part of setting the block to + // be reimported. The reimported retExpr value should be the same, so even if + // we don't unconditionally overwrite it, it shouldn't matter. + if (info.compRetNativeType != TYP_STRUCT) + { + // compRetNativeType is not TYP_STRUCT. + // This implies it could be either a scalar type or SIMD vector type or + // a struct type that can be normalized to a scalar type. + + if (varTypeIsStruct(info.compRetType)) + { + noway_assert(info.compRetBuffArg == BAD_VAR_NUM); + // adjust the type away from struct to integral + // and no normalizing + op2 = impFixupStructReturnType(op2, retClsHnd); + } + else + { + // Do we have to normalize? + var_types fncRealRetType = JITtype2varType(info.compMethodInfo->args.retType); + if ((varTypeIsSmall(op2->TypeGet()) || varTypeIsSmall(fncRealRetType)) && + fgCastNeeded(op2, fncRealRetType)) + { + // Small-typed return values are normalized by the callee + op2 = gtNewCastNode(TYP_INT, op2, fncRealRetType); + } + } + + if (lvaInlineeReturnSpillTemp != BAD_VAR_NUM) + { + assert(info.compRetNativeType != TYP_VOID && fgMoreThanOneReturnBlock()); + + // This is a bit of a workaround... + // If we are inlining a call that returns a struct, where the actual "native" return type is + // not a struct (for example, the struct is composed of exactly one int, and the native + // return type is thus an int), and the inlinee has multiple return blocks (thus, + // lvaInlineeReturnSpillTemp is != BAD_VAR_NUM, and is the index of a local var that is set + // to the *native* return type), and at least one of the return blocks is the result of + // a call, then we have a problem. The situation is like this (from a failed test case): + // + // inliner: + // // Note: valuetype plinq_devtests.LazyTests/LIX is a struct with only a single int + // call !!0 [mscorlib]System.Threading.LazyInitializer::EnsureInitialized<valuetype + // plinq_devtests.LazyTests/LIX>(!!0&, bool&, object&, class [mscorlib]System.Func`1<!!0>) + // + // inlinee: + // ... + // ldobj !!T // this gets bashed to a GT_LCL_FLD, type TYP_INT + // ret + // ... + // call !!0 System.Threading.LazyInitializer::EnsureInitializedCore<!!0>(!!0&, bool&, + // object&, class System.Func`1<!!0>) + // ret + // + // In the code above, when we call impFixupStructReturnType(), we will change the op2 return type + // of the inlinee return node, but we don't do that for GT_CALL nodes, which we delay until + // morphing when we call fgFixupStructReturn(). We do this, apparently, to handle nested + // inlining properly by leaving the correct type on the GT_CALL node through importing. + // + // To fix this, for this case, we temporarily change the GT_CALL node type to the + // native return type, which is what it will be set to eventually. We generate the + // assignment to the return temp, using the correct type, and then restore the GT_CALL + // node type. During morphing, the GT_CALL will get the correct, final, native return type. + + bool restoreType = false; + if ((op2->OperGet() == GT_CALL) && (info.compRetType == TYP_STRUCT)) + { + noway_assert(op2->TypeGet() == TYP_STRUCT); + op2->gtType = info.compRetNativeType; + restoreType = true; + } + + impAssignTempGen(lvaInlineeReturnSpillTemp, op2, se.seTypeInfo.GetClassHandle(), + (unsigned)CHECK_SPILL_ALL); + + GenTreePtr tmpOp2 = gtNewLclvNode(lvaInlineeReturnSpillTemp, op2->TypeGet()); + + if (restoreType) + { + op2->gtType = TYP_STRUCT; // restore it to what it was + } + + op2 = tmpOp2; + +#ifdef DEBUG + if (impInlineInfo->retExpr) + { + // Some other block(s) have seen the CEE_RET first. + // Better they spilled to the same temp. + assert(impInlineInfo->retExpr->gtOper == GT_LCL_VAR); + assert(impInlineInfo->retExpr->gtLclVarCommon.gtLclNum == op2->gtLclVarCommon.gtLclNum); + } +#endif + } + +#ifdef DEBUG + if (verbose) + { + printf("\n\n Inlinee Return expression (after normalization) =>\n"); + gtDispTree(op2); + } +#endif + + // Report the return expression + impInlineInfo->retExpr = op2; + } + else + { + // compRetNativeType is TYP_STRUCT. + // This implies that struct return via RetBuf arg or multi-reg struct return + + GenTreePtr iciCall = impInlineInfo->iciCall; + assert(iciCall->gtOper == GT_CALL); + + // Assign the inlinee return into a spill temp. + // spill temp only exists if there are multiple return points + if (lvaInlineeReturnSpillTemp != BAD_VAR_NUM) + { + // in this case we have to insert multiple struct copies to the temp + // and the retexpr is just the temp. + assert(info.compRetNativeType != TYP_VOID); + assert(fgMoreThanOneReturnBlock()); + + impAssignTempGen(lvaInlineeReturnSpillTemp, op2, se.seTypeInfo.GetClassHandle(), + (unsigned)CHECK_SPILL_ALL); + } + +#if defined(_TARGET_ARM_) || defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) +#if defined(_TARGET_ARM_) + // TODO-ARM64-NYI: HFA + // TODO-AMD64-Unix and TODO-ARM once the ARM64 functionality is implemented the + // next ifdefs could be refactored in a single method with the ifdef inside. + if (IsHfa(retClsHnd)) + { +// Same as !IsHfa but just don't bother with impAssignStructPtr. +#else // defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) + ReturnTypeDesc retTypeDesc; + retTypeDesc.InitializeStructReturnType(this, retClsHnd); + unsigned retRegCount = retTypeDesc.GetReturnRegCount(); + + if (retRegCount != 0) + { + // If single eightbyte, the return type would have been normalized and there won't be a temp var. + // This code will be called only if the struct return has not been normalized (i.e. 2 eightbytes - + // max allowed.) + assert(retRegCount == MAX_RET_REG_COUNT); + // Same as !structDesc.passedInRegisters but just don't bother with impAssignStructPtr. + CLANG_FORMAT_COMMENT_ANCHOR; +#endif // defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) + + if (lvaInlineeReturnSpillTemp != BAD_VAR_NUM) + { + if (!impInlineInfo->retExpr) + { +#if defined(_TARGET_ARM_) + impInlineInfo->retExpr = gtNewLclvNode(lvaInlineeReturnSpillTemp, info.compRetType); +#else // defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) + // The inlinee compiler has figured out the type of the temp already. Use it here. + impInlineInfo->retExpr = + gtNewLclvNode(lvaInlineeReturnSpillTemp, lvaTable[lvaInlineeReturnSpillTemp].lvType); +#endif // defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) + } + } + else + { + impInlineInfo->retExpr = op2; + } + } + else +#elif defined(_TARGET_ARM64_) + ReturnTypeDesc retTypeDesc; + retTypeDesc.InitializeStructReturnType(this, retClsHnd); + unsigned retRegCount = retTypeDesc.GetReturnRegCount(); + + if (retRegCount != 0) + { + assert(!iciCall->AsCall()->HasRetBufArg()); + assert(retRegCount >= 2); + if (lvaInlineeReturnSpillTemp != BAD_VAR_NUM) + { + if (!impInlineInfo->retExpr) + { + // The inlinee compiler has figured out the type of the temp already. Use it here. + impInlineInfo->retExpr = + gtNewLclvNode(lvaInlineeReturnSpillTemp, lvaTable[lvaInlineeReturnSpillTemp].lvType); + } + } + else + { + impInlineInfo->retExpr = op2; + } + } + else +#endif // defined(_TARGET_ARM64_) + { + assert(iciCall->AsCall()->HasRetBufArg()); + GenTreePtr dest = gtCloneExpr(iciCall->gtCall.gtCallArgs->gtOp.gtOp1); + // spill temp only exists if there are multiple return points + if (lvaInlineeReturnSpillTemp != BAD_VAR_NUM) + { + // if this is the first return we have seen set the retExpr + if (!impInlineInfo->retExpr) + { + impInlineInfo->retExpr = + impAssignStructPtr(dest, gtNewLclvNode(lvaInlineeReturnSpillTemp, info.compRetType), + retClsHnd, (unsigned)CHECK_SPILL_ALL); + } + } + else + { + impInlineInfo->retExpr = impAssignStructPtr(dest, op2, retClsHnd, (unsigned)CHECK_SPILL_ALL); + } + } + } + } + } + + if (compIsForInlining()) + { + return true; + } + + if (info.compRetType == TYP_VOID) + { + // return void + op1 = new (this, GT_RETURN) GenTreeOp(GT_RETURN, TYP_VOID); + } + else if (info.compRetBuffArg != BAD_VAR_NUM) + { + // Assign value to return buff (first param) + GenTreePtr retBuffAddr = gtNewLclvNode(info.compRetBuffArg, TYP_BYREF, impCurStmtOffs); + + op2 = impAssignStructPtr(retBuffAddr, op2, retClsHnd, (unsigned)CHECK_SPILL_ALL); + impAppendTree(op2, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); + + // There are cases where the address of the implicit RetBuf should be returned explicitly (in RAX). + CLANG_FORMAT_COMMENT_ANCHOR; + +#if defined(_TARGET_AMD64_) + + // x64 (System V and Win64) calling convention requires to + // return the implicit return buffer explicitly (in RAX). + // Change the return type to be BYREF. + op1 = gtNewOperNode(GT_RETURN, TYP_BYREF, gtNewLclvNode(info.compRetBuffArg, TYP_BYREF)); +#else // !defined(_TARGET_AMD64_) + // In case of non-AMD64 targets the profiler hook requires to return the implicit RetBuf explicitly (in RAX). + // In such case the return value of the function is changed to BYREF. + // If profiler hook is not needed the return type of the function is TYP_VOID. + if (compIsProfilerHookNeeded()) + { + op1 = gtNewOperNode(GT_RETURN, TYP_BYREF, gtNewLclvNode(info.compRetBuffArg, TYP_BYREF)); + } + else + { + // return void + op1 = new (this, GT_RETURN) GenTreeOp(GT_RETURN, TYP_VOID); + } +#endif // !defined(_TARGET_AMD64_) + } + else if (varTypeIsStruct(info.compRetType)) + { +#if !FEATURE_MULTIREG_RET + // For both ARM architectures the HFA native types are maintained as structs. + // Also on System V AMD64 the multireg structs returns are also left as structs. + noway_assert(info.compRetNativeType != TYP_STRUCT); +#endif + op2 = impFixupStructReturnType(op2, retClsHnd); + // return op2 + op1 = gtNewOperNode(GT_RETURN, genActualType(info.compRetNativeType), op2); + } + else + { + // return op2 + op1 = gtNewOperNode(GT_RETURN, genActualType(info.compRetType), op2); + } + + // We must have imported a tailcall and jumped to RET + if (prefixFlags & PREFIX_TAILCALL) + { +#ifndef _TARGET_AMD64_ + // Jit64 compat: + // This cannot be asserted on Amd64 since we permit the following IL pattern: + // tail.call + // pop + // ret + assert(verCurrentState.esStackDepth == 0 && impOpcodeIsCallOpcode(opcode)); +#endif + + opcode = CEE_RET; // To prevent trying to spill if CALL_SITE_BOUNDARIES + + // impImportCall() would have already appended TYP_VOID calls + if (info.compRetType == TYP_VOID) + { + return true; + } + } + + impAppendTree(op1, (unsigned)CHECK_SPILL_NONE, impCurStmtOffs); +#ifdef DEBUG + // Remember at which BC offset the tree was finished + impNoteLastILoffs(); +#endif + return true; +} + +/***************************************************************************** + * Mark the block as unimported. + * Note that the caller is responsible for calling impImportBlockPending(), + * with the appropriate stack-state + */ + +inline void Compiler::impReimportMarkBlock(BasicBlock* block) +{ +#ifdef DEBUG + if (verbose && (block->bbFlags & BBF_IMPORTED)) + { + printf("\nBB%02u will be reimported\n", block->bbNum); + } +#endif + + block->bbFlags &= ~BBF_IMPORTED; +} + +/***************************************************************************** + * Mark the successors of the given block as unimported. + * Note that the caller is responsible for calling impImportBlockPending() + * for all the successors, with the appropriate stack-state. + */ + +void Compiler::impReimportMarkSuccessors(BasicBlock* block) +{ + for (unsigned i = 0; i < block->NumSucc(); i++) + { + impReimportMarkBlock(block->GetSucc(i)); + } +} + +/***************************************************************************** + * + * Filter wrapper to handle only passed in exception code + * from it). + */ + +LONG FilterVerificationExceptions(PEXCEPTION_POINTERS pExceptionPointers, LPVOID lpvParam) +{ + if (pExceptionPointers->ExceptionRecord->ExceptionCode == SEH_VERIFICATION_EXCEPTION) + { + return EXCEPTION_EXECUTE_HANDLER; + } + + return EXCEPTION_CONTINUE_SEARCH; +} + +void Compiler::impVerifyEHBlock(BasicBlock* block, bool isTryStart) +{ + assert(block->hasTryIndex()); + assert(!compIsForInlining()); + + unsigned tryIndex = block->getTryIndex(); + EHblkDsc* HBtab = ehGetDsc(tryIndex); + + if (isTryStart) + { + assert(block->bbFlags & BBF_TRY_BEG); + + // The Stack must be empty + // + if (block->bbStkDepth != 0) + { + BADCODE("Evaluation stack must be empty on entry into a try block"); + } + } + + // Save the stack contents, we'll need to restore it later + // + SavedStack blockState; + impSaveStackState(&blockState, false); + + while (HBtab != nullptr) + { + if (isTryStart) + { + // Are we verifying that an instance constructor properly initializes it's 'this' pointer once? + // We do not allow the 'this' pointer to be uninitialized when entering most kinds try regions + // + if (verTrackObjCtorInitState && (verCurrentState.thisInitialized != TIS_Init)) + { + // We trigger an invalid program exception here unless we have a try/fault region. + // + if (HBtab->HasCatchHandler() || HBtab->HasFinallyHandler() || HBtab->HasFilter()) + { + BADCODE( + "The 'this' pointer of an instance constructor is not intialized upon entry to a try region"); + } + else + { + // Allow a try/fault region to proceed. + assert(HBtab->HasFaultHandler()); + } + } + + /* Recursively process the handler block */ + BasicBlock* hndBegBB = HBtab->ebdHndBeg; + + // Construct the proper verification stack state + // either empty or one that contains just + // the Exception Object that we are dealing with + // + verCurrentState.esStackDepth = 0; + + if (handlerGetsXcptnObj(hndBegBB->bbCatchTyp)) + { + CORINFO_CLASS_HANDLE clsHnd; + + if (HBtab->HasFilter()) + { + clsHnd = impGetObjectClass(); + } + else + { + CORINFO_RESOLVED_TOKEN resolvedToken; + + resolvedToken.tokenContext = impTokenLookupContextHandle; + resolvedToken.tokenScope = info.compScopeHnd; + resolvedToken.token = HBtab->ebdTyp; + resolvedToken.tokenType = CORINFO_TOKENKIND_Class; + info.compCompHnd->resolveToken(&resolvedToken); + + clsHnd = resolvedToken.hClass; + } + + // push catch arg the stack, spill to a temp if necessary + // Note: can update HBtab->ebdHndBeg! + hndBegBB = impPushCatchArgOnStack(hndBegBB, clsHnd); + } + + // Queue up the handler for importing + // + impImportBlockPending(hndBegBB); + + if (HBtab->HasFilter()) + { + /* @VERIFICATION : Ideally the end of filter state should get + propagated to the catch handler, this is an incompleteness, + but is not a security/compliance issue, since the only + interesting state is the 'thisInit' state. + */ + + verCurrentState.esStackDepth = 0; + + BasicBlock* filterBB = HBtab->ebdFilter; + + // push catch arg the stack, spill to a temp if necessary + // Note: can update HBtab->ebdFilter! + filterBB = impPushCatchArgOnStack(filterBB, impGetObjectClass()); + + impImportBlockPending(filterBB); + } + } + else if (verTrackObjCtorInitState && HBtab->HasFaultHandler()) + { + /* Recursively process the handler block */ + + verCurrentState.esStackDepth = 0; + + // Queue up the fault handler for importing + // + impImportBlockPending(HBtab->ebdHndBeg); + } + + // Now process our enclosing try index (if any) + // + tryIndex = HBtab->ebdEnclosingTryIndex; + if (tryIndex == EHblkDsc::NO_ENCLOSING_INDEX) + { + HBtab = nullptr; + } + else + { + HBtab = ehGetDsc(tryIndex); + } + } + + // Restore the stack contents + impRestoreStackState(&blockState); +} + +//*************************************************************** +// Import the instructions for the given basic block. Perform +// verification, throwing an exception on failure. Push any successor blocks that are enabled for the first +// time, or whose verification pre-state is changed. + +#ifdef _PREFAST_ +#pragma warning(push) +#pragma warning(disable : 21000) // Suppress PREFast warning about overly large function +#endif +void Compiler::impImportBlock(BasicBlock* block) +{ + // BBF_INTERNAL blocks only exist during importation due to EH canonicalization. We need to + // handle them specially. In particular, there is no IL to import for them, but we do need + // to mark them as imported and put their successors on the pending import list. + if (block->bbFlags & BBF_INTERNAL) + { + JITDUMP("Marking BBF_INTERNAL block BB%02u as BBF_IMPORTED\n", block->bbNum); + block->bbFlags |= BBF_IMPORTED; + + for (unsigned i = 0; i < block->NumSucc(); i++) + { + impImportBlockPending(block->GetSucc(i)); + } + + return; + } + + bool markImport; + + assert(block); + + /* Make the block globaly available */ + + compCurBB = block; + +#ifdef DEBUG + /* Initialize the debug variables */ + impCurOpcName = "unknown"; + impCurOpcOffs = block->bbCodeOffs; +#endif + + /* Set the current stack state to the merged result */ + verResetCurrentState(block, &verCurrentState); + + /* Now walk the code and import the IL into GenTrees */ + + struct FilterVerificationExceptionsParam + { + Compiler* pThis; + BasicBlock* block; + }; + FilterVerificationExceptionsParam param; + + param.pThis = this; + param.block = block; + + PAL_TRY(FilterVerificationExceptionsParam*, pParam, ¶m) + { + /* @VERIFICATION : For now, the only state propagation from try + to it's handler is "thisInit" state (stack is empty at start of try). + In general, for state that we track in verification, we need to + model the possibility that an exception might happen at any IL + instruction, so we really need to merge all states that obtain + between IL instructions in a try block into the start states of + all handlers. + + However we do not allow the 'this' pointer to be uninitialized when + entering most kinds try regions (only try/fault are allowed to have + an uninitialized this pointer on entry to the try) + + Fortunately, the stack is thrown away when an exception + leads to a handler, so we don't have to worry about that. + We DO, however, have to worry about the "thisInit" state. + But only for the try/fault case. + + The only allowed transition is from TIS_Uninit to TIS_Init. + + So for a try/fault region for the fault handler block + we will merge the start state of the try begin + and the post-state of each block that is part of this try region + */ + + // merge the start state of the try begin + // + if (pParam->block->bbFlags & BBF_TRY_BEG) + { + pParam->pThis->impVerifyEHBlock(pParam->block, true); + } + + pParam->pThis->impImportBlockCode(pParam->block); + + // As discussed above: + // merge the post-state of each block that is part of this try region + // + if (pParam->block->hasTryIndex()) + { + pParam->pThis->impVerifyEHBlock(pParam->block, false); + } + } + PAL_EXCEPT_FILTER(FilterVerificationExceptions) + { + verHandleVerificationFailure(block DEBUGARG(false)); + } + PAL_ENDTRY + + if (compDonotInline()) + { + return; + } + + assert(!compDonotInline()); + + markImport = false; + +SPILLSTACK: + + unsigned baseTmp = NO_BASE_TMP; // input temps assigned to successor blocks + bool reimportSpillClique = false; + BasicBlock* tgtBlock = nullptr; + + /* If the stack is non-empty, we might have to spill its contents */ + + if (verCurrentState.esStackDepth != 0) + { + impBoxTemp = BAD_VAR_NUM; // if a box temp is used in a block that leaves something + // on the stack, its lifetime is hard to determine, simply + // don't reuse such temps. + + GenTreePtr addStmt = nullptr; + + /* Do the successors of 'block' have any other predecessors ? + We do not want to do some of the optimizations related to multiRef + if we can reimport blocks */ + + unsigned multRef = impCanReimport ? unsigned(~0) : 0; + + switch (block->bbJumpKind) + { + case BBJ_COND: + + /* Temporarily remove the 'jtrue' from the end of the tree list */ + + assert(impTreeLast); + assert(impTreeLast->gtOper == GT_STMT); + assert(impTreeLast->gtStmt.gtStmtExpr->gtOper == GT_JTRUE); + + addStmt = impTreeLast; + impTreeLast = impTreeLast->gtPrev; + + /* Note if the next block has more than one ancestor */ + + multRef |= block->bbNext->bbRefs; + + /* Does the next block have temps assigned? */ + + baseTmp = block->bbNext->bbStkTempsIn; + tgtBlock = block->bbNext; + + if (baseTmp != NO_BASE_TMP) + { + break; + } + + /* Try the target of the jump then */ + + multRef |= block->bbJumpDest->bbRefs; + baseTmp = block->bbJumpDest->bbStkTempsIn; + tgtBlock = block->bbJumpDest; + break; + + case BBJ_ALWAYS: + multRef |= block->bbJumpDest->bbRefs; + baseTmp = block->bbJumpDest->bbStkTempsIn; + tgtBlock = block->bbJumpDest; + break; + + case BBJ_NONE: + multRef |= block->bbNext->bbRefs; + baseTmp = block->bbNext->bbStkTempsIn; + tgtBlock = block->bbNext; + break; + + case BBJ_SWITCH: + + BasicBlock** jmpTab; + unsigned jmpCnt; + + /* Temporarily remove the GT_SWITCH from the end of the tree list */ + + assert(impTreeLast); + assert(impTreeLast->gtOper == GT_STMT); + assert(impTreeLast->gtStmt.gtStmtExpr->gtOper == GT_SWITCH); + + addStmt = impTreeLast; + impTreeLast = impTreeLast->gtPrev; + + jmpCnt = block->bbJumpSwt->bbsCount; + jmpTab = block->bbJumpSwt->bbsDstTab; + + do + { + tgtBlock = (*jmpTab); + + multRef |= tgtBlock->bbRefs; + + // Thanks to spill cliques, we should have assigned all or none + assert((baseTmp == NO_BASE_TMP) || (baseTmp == tgtBlock->bbStkTempsIn)); + baseTmp = tgtBlock->bbStkTempsIn; + if (multRef > 1) + { + break; + } + } while (++jmpTab, --jmpCnt); + + break; + + case BBJ_CALLFINALLY: + case BBJ_EHCATCHRET: + case BBJ_RETURN: + case BBJ_EHFINALLYRET: + case BBJ_EHFILTERRET: + case BBJ_THROW: + NO_WAY("can't have 'unreached' end of BB with non-empty stack"); + break; + + default: + noway_assert(!"Unexpected bbJumpKind"); + break; + } + + assert(multRef >= 1); + + /* Do we have a base temp number? */ + + bool newTemps = (baseTmp == NO_BASE_TMP); + + if (newTemps) + { + /* Grab enough temps for the whole stack */ + baseTmp = impGetSpillTmpBase(block); + } + + /* Spill all stack entries into temps */ + unsigned level, tempNum; + + JITDUMP("\nSpilling stack entries into temps\n"); + for (level = 0, tempNum = baseTmp; level < verCurrentState.esStackDepth; level++, tempNum++) + { + GenTreePtr tree = verCurrentState.esStack[level].val; + + /* VC generates code where it pushes a byref from one branch, and an int (ldc.i4 0) from + the other. This should merge to a byref in unverifiable code. + However, if the branch which leaves the TYP_I_IMPL on the stack is imported first, the + successor would be imported assuming there was a TYP_I_IMPL on + the stack. Thus the value would not get GC-tracked. Hence, + change the temp to TYP_BYREF and reimport the successors. + Note: We should only allow this in unverifiable code. + */ + if (tree->gtType == TYP_BYREF && lvaTable[tempNum].lvType == TYP_I_IMPL && !verNeedsVerification()) + { + lvaTable[tempNum].lvType = TYP_BYREF; + impReimportMarkSuccessors(block); + markImport = true; + } + +#ifdef _TARGET_64BIT_ + if (genActualType(tree->gtType) == TYP_I_IMPL && lvaTable[tempNum].lvType == TYP_INT) + { + if (tiVerificationNeeded && tgtBlock->bbEntryState != nullptr && + (tgtBlock->bbFlags & BBF_FAILED_VERIFICATION) == 0) + { + // Merge the current state into the entry state of block; + // the call to verMergeEntryStates must have changed + // the entry state of the block by merging the int local var + // and the native-int stack entry. + bool changed = false; + if (verMergeEntryStates(tgtBlock, &changed)) + { + impRetypeEntryStateTemps(tgtBlock); + impReimportBlockPending(tgtBlock); + assert(changed); + } + else + { + tgtBlock->bbFlags |= BBF_FAILED_VERIFICATION; + break; + } + } + + // Some other block in the spill clique set this to "int", but now we have "native int". + // Change the type and go back to re-import any blocks that used the wrong type. + lvaTable[tempNum].lvType = TYP_I_IMPL; + reimportSpillClique = true; + } + else if (genActualType(tree->gtType) == TYP_INT && lvaTable[tempNum].lvType == TYP_I_IMPL) + { + // Spill clique has decided this should be "native int", but this block only pushes an "int". + // Insert a sign-extension to "native int" so we match the clique. + verCurrentState.esStack[level].val = gtNewCastNode(TYP_I_IMPL, tree, TYP_I_IMPL); + } + + // Consider the case where one branch left a 'byref' on the stack and the other leaves + // an 'int'. On 32-bit, this is allowed (in non-verifiable code) since they are the same + // size. JIT64 managed to make this work on 64-bit. For compatibility, we support JIT64 + // behavior instead of asserting and then generating bad code (where we save/restore the + // low 32 bits of a byref pointer to an 'int' sized local). If the 'int' side has been + // imported already, we need to change the type of the local and reimport the spill clique. + // If the 'byref' side has imported, we insert a cast from int to 'native int' to match + // the 'byref' size. + if (!tiVerificationNeeded) + { + if (genActualType(tree->gtType) == TYP_BYREF && lvaTable[tempNum].lvType == TYP_INT) + { + // Some other block in the spill clique set this to "int", but now we have "byref". + // Change the type and go back to re-import any blocks that used the wrong type. + lvaTable[tempNum].lvType = TYP_BYREF; + reimportSpillClique = true; + } + else if (genActualType(tree->gtType) == TYP_INT && lvaTable[tempNum].lvType == TYP_BYREF) + { + // Spill clique has decided this should be "byref", but this block only pushes an "int". + // Insert a sign-extension to "native int" so we match the clique size. + verCurrentState.esStack[level].val = gtNewCastNode(TYP_I_IMPL, tree, TYP_I_IMPL); + } + } +#endif // _TARGET_64BIT_ + +#if FEATURE_X87_DOUBLES + // X87 stack doesn't differentiate between float/double + // so promoting is no big deal. + // For everybody else keep it as float until we have a collision and then promote + // Just like for x64's TYP_INT<->TYP_I_IMPL + + if (multRef > 1 && tree->gtType == TYP_FLOAT) + { + verCurrentState.esStack[level].val = gtNewCastNode(TYP_DOUBLE, tree, TYP_DOUBLE); + } + +#else // !FEATURE_X87_DOUBLES + + if (tree->gtType == TYP_DOUBLE && lvaTable[tempNum].lvType == TYP_FLOAT) + { + // Some other block in the spill clique set this to "float", but now we have "double". + // Change the type and go back to re-import any blocks that used the wrong type. + lvaTable[tempNum].lvType = TYP_DOUBLE; + reimportSpillClique = true; + } + else if (tree->gtType == TYP_FLOAT && lvaTable[tempNum].lvType == TYP_DOUBLE) + { + // Spill clique has decided this should be "double", but this block only pushes a "float". + // Insert a cast to "double" so we match the clique. + verCurrentState.esStack[level].val = gtNewCastNode(TYP_DOUBLE, tree, TYP_DOUBLE); + } + +#endif // FEATURE_X87_DOUBLES + + /* If addStmt has a reference to tempNum (can only happen if we + are spilling to the temps already used by a previous block), + we need to spill addStmt */ + + if (addStmt && !newTemps && gtHasRef(addStmt->gtStmt.gtStmtExpr, tempNum, false)) + { + GenTreePtr addTree = addStmt->gtStmt.gtStmtExpr; + + if (addTree->gtOper == GT_JTRUE) + { + GenTreePtr relOp = addTree->gtOp.gtOp1; + assert(relOp->OperIsCompare()); + + var_types type = genActualType(relOp->gtOp.gtOp1->TypeGet()); + + if (gtHasRef(relOp->gtOp.gtOp1, tempNum, false)) + { + unsigned temp = lvaGrabTemp(true DEBUGARG("spill addStmt JTRUE ref Op1")); + impAssignTempGen(temp, relOp->gtOp.gtOp1, level); + type = genActualType(lvaTable[temp].TypeGet()); + relOp->gtOp.gtOp1 = gtNewLclvNode(temp, type); + } + + if (gtHasRef(relOp->gtOp.gtOp2, tempNum, false)) + { + unsigned temp = lvaGrabTemp(true DEBUGARG("spill addStmt JTRUE ref Op2")); + impAssignTempGen(temp, relOp->gtOp.gtOp2, level); + type = genActualType(lvaTable[temp].TypeGet()); + relOp->gtOp.gtOp2 = gtNewLclvNode(temp, type); + } + } + else + { + assert(addTree->gtOper == GT_SWITCH && genActualType(addTree->gtOp.gtOp1->gtType) == TYP_I_IMPL); + + unsigned temp = lvaGrabTemp(true DEBUGARG("spill addStmt SWITCH")); + impAssignTempGen(temp, addTree->gtOp.gtOp1, level); + addTree->gtOp.gtOp1 = gtNewLclvNode(temp, TYP_I_IMPL); + } + } + + /* Spill the stack entry, and replace with the temp */ + + if (!impSpillStackEntry(level, tempNum +#ifdef DEBUG + , + true, "Spill Stack Entry" +#endif + )) + { + if (markImport) + { + BADCODE("bad stack state"); + } + + // Oops. Something went wrong when spilling. Bad code. + verHandleVerificationFailure(block DEBUGARG(true)); + + goto SPILLSTACK; + } + } + + /* Put back the 'jtrue'/'switch' if we removed it earlier */ + + if (addStmt) + { + impAppendStmt(addStmt, (unsigned)CHECK_SPILL_NONE); + } + } + + // Some of the append/spill logic works on compCurBB + + assert(compCurBB == block); + + /* Save the tree list in the block */ + impEndTreeList(block); + + // impEndTreeList sets BBF_IMPORTED on the block + // We do *NOT* want to set it later than this because + // impReimportSpillClique might clear it if this block is both a + // predecessor and successor in the current spill clique + assert(block->bbFlags & BBF_IMPORTED); + + // If we had a int/native int, or float/double collision, we need to re-import + if (reimportSpillClique) + { + // This will re-import all the successors of block (as well as each of their predecessors) + impReimportSpillClique(block); + + // For blocks that haven't been imported yet, we still need to mark them as pending import. + for (unsigned i = 0; i < block->NumSucc(); i++) + { + BasicBlock* succ = block->GetSucc(i); + if ((succ->bbFlags & BBF_IMPORTED) == 0) + { + impImportBlockPending(succ); + } + } + } + else // the normal case + { + // otherwise just import the successors of block + + /* Does this block jump to any other blocks? */ + for (unsigned i = 0; i < block->NumSucc(); i++) + { + impImportBlockPending(block->GetSucc(i)); + } + } +} +#ifdef _PREFAST_ +#pragma warning(pop) +#endif + +/*****************************************************************************/ +// +// Ensures that "block" is a member of the list of BBs waiting to be imported, pushing it on the list if +// necessary (and ensures that it is a member of the set of BB's on the list, by setting its byte in +// impPendingBlockMembers). Merges the current verification state into the verification state of "block" +// (its "pre-state"). + +void Compiler::impImportBlockPending(BasicBlock* block) +{ +#ifdef DEBUG + if (verbose) + { + printf("\nimpImportBlockPending for BB%02u\n", block->bbNum); + } +#endif + + // We will add a block to the pending set if it has not already been imported (or needs to be re-imported), + // or if it has, but merging in a predecessor's post-state changes the block's pre-state. + // (When we're doing verification, we always attempt the merge to detect verification errors.) + + // If the block has not been imported, add to pending set. + bool addToPending = ((block->bbFlags & BBF_IMPORTED) == 0); + + // Initialize bbEntryState just the first time we try to add this block to the pending list + // Just because bbEntryState is NULL, doesn't mean the pre-state wasn't previously set + // We use NULL to indicate the 'common' state to avoid memory allocation + if ((block->bbEntryState == nullptr) && ((block->bbFlags & (BBF_IMPORTED | BBF_FAILED_VERIFICATION)) == 0) && + (impGetPendingBlockMember(block) == 0)) + { + verInitBBEntryState(block, &verCurrentState); + assert(block->bbStkDepth == 0); + block->bbStkDepth = static_cast<unsigned short>(verCurrentState.esStackDepth); + assert(addToPending); + assert(impGetPendingBlockMember(block) == 0); + } + else + { + // The stack should have the same height on entry to the block from all its predecessors. + if (block->bbStkDepth != verCurrentState.esStackDepth) + { +#ifdef DEBUG + char buffer[400]; + sprintf_s(buffer, sizeof(buffer), + "Block at offset %4.4x to %4.4x in %s entered with different stack depths.\n" + "Previous depth was %d, current depth is %d", + block->bbCodeOffs, block->bbCodeOffsEnd, info.compFullName, block->bbStkDepth, + verCurrentState.esStackDepth); + buffer[400 - 1] = 0; + NO_WAY(buffer); +#else + NO_WAY("Block entered with different stack depths"); +#endif + } + + // Additionally, if we need to verify, merge the verification state. + if (tiVerificationNeeded) + { + // Merge the current state into the entry state of block; if this does not change the entry state + // by merging, do not add the block to the pending-list. + bool changed = false; + if (!verMergeEntryStates(block, &changed)) + { + block->bbFlags |= BBF_FAILED_VERIFICATION; + addToPending = true; // We will pop it off, and check the flag set above. + } + else if (changed) + { + addToPending = true; + + JITDUMP("Adding BB%02u to pending set due to new merge result\n", block->bbNum); + } + } + + if (!addToPending) + { + return; + } + + if (block->bbStkDepth > 0) + { + // We need to fix the types of any spill temps that might have changed: + // int->native int, float->double, int->byref, etc. + impRetypeEntryStateTemps(block); + } + + // OK, we must add to the pending list, if it's not already in it. + if (impGetPendingBlockMember(block) != 0) + { + return; + } + } + + // Get an entry to add to the pending list + + PendingDsc* dsc; + + if (impPendingFree) + { + // We can reuse one of the freed up dscs. + dsc = impPendingFree; + impPendingFree = dsc->pdNext; + } + else + { + // We have to create a new dsc + dsc = new (this, CMK_Unknown) PendingDsc; + } + + dsc->pdBB = block; + dsc->pdSavedStack.ssDepth = verCurrentState.esStackDepth; + dsc->pdThisPtrInit = verCurrentState.thisInitialized; + + // Save the stack trees for later + + if (verCurrentState.esStackDepth) + { + impSaveStackState(&dsc->pdSavedStack, false); + } + + // Add the entry to the pending list + + dsc->pdNext = impPendingList; + impPendingList = dsc; + impSetPendingBlockMember(block, 1); // And indicate that it's now a member of the set. + + // Various assertions require us to now to consider the block as not imported (at least for + // the final time...) + block->bbFlags &= ~BBF_IMPORTED; + +#ifdef DEBUG + if (verbose && 0) + { + printf("Added PendingDsc - %08p for BB%02u\n", dspPtr(dsc), block->bbNum); + } +#endif +} + +/*****************************************************************************/ +// +// Ensures that "block" is a member of the list of BBs waiting to be imported, pushing it on the list if +// necessary (and ensures that it is a member of the set of BB's on the list, by setting its byte in +// impPendingBlockMembers). Does *NOT* change the existing "pre-state" of the block. + +void Compiler::impReimportBlockPending(BasicBlock* block) +{ + JITDUMP("\nimpReimportBlockPending for BB%02u", block->bbNum); + + assert(block->bbFlags & BBF_IMPORTED); + + // OK, we must add to the pending list, if it's not already in it. + if (impGetPendingBlockMember(block) != 0) + { + return; + } + + // Get an entry to add to the pending list + + PendingDsc* dsc; + + if (impPendingFree) + { + // We can reuse one of the freed up dscs. + dsc = impPendingFree; + impPendingFree = dsc->pdNext; + } + else + { + // We have to create a new dsc + dsc = new (this, CMK_ImpStack) PendingDsc; + } + + dsc->pdBB = block; + + if (block->bbEntryState) + { + dsc->pdThisPtrInit = block->bbEntryState->thisInitialized; + dsc->pdSavedStack.ssDepth = block->bbEntryState->esStackDepth; + dsc->pdSavedStack.ssTrees = block->bbEntryState->esStack; + } + else + { + dsc->pdThisPtrInit = TIS_Bottom; + dsc->pdSavedStack.ssDepth = 0; + dsc->pdSavedStack.ssTrees = nullptr; + } + + // Add the entry to the pending list + + dsc->pdNext = impPendingList; + impPendingList = dsc; + impSetPendingBlockMember(block, 1); // And indicate that it's now a member of the set. + + // Various assertions require us to now to consider the block as not imported (at least for + // the final time...) + block->bbFlags &= ~BBF_IMPORTED; + +#ifdef DEBUG + if (verbose && 0) + { + printf("Added PendingDsc - %08p for BB%02u\n", dspPtr(dsc), block->bbNum); + } +#endif +} + +void* Compiler::BlockListNode::operator new(size_t sz, Compiler* comp) +{ + if (comp->impBlockListNodeFreeList == nullptr) + { + return (BlockListNode*)comp->compGetMem(sizeof(BlockListNode), CMK_BasicBlock); + } + else + { + BlockListNode* res = comp->impBlockListNodeFreeList; + comp->impBlockListNodeFreeList = res->m_next; + return res; + } +} + +void Compiler::FreeBlockListNode(Compiler::BlockListNode* node) +{ + node->m_next = impBlockListNodeFreeList; + impBlockListNodeFreeList = node; +} + +void Compiler::impWalkSpillCliqueFromPred(BasicBlock* block, SpillCliqueWalker* callback) +{ + bool toDo = true; + + noway_assert(!fgComputePredsDone); + if (!fgCheapPredsValid) + { + fgComputeCheapPreds(); + } + + BlockListNode* succCliqueToDo = nullptr; + BlockListNode* predCliqueToDo = new (this) BlockListNode(block); + while (toDo) + { + toDo = false; + // Look at the successors of every member of the predecessor to-do list. + while (predCliqueToDo != nullptr) + { + BlockListNode* node = predCliqueToDo; + predCliqueToDo = node->m_next; + BasicBlock* blk = node->m_blk; + FreeBlockListNode(node); + + for (unsigned succNum = 0; succNum < blk->NumSucc(); succNum++) + { + BasicBlock* succ = blk->GetSucc(succNum); + // If it's not already in the clique, add it, and also add it + // as a member of the successor "toDo" set. + if (impSpillCliqueGetMember(SpillCliqueSucc, succ) == 0) + { + callback->Visit(SpillCliqueSucc, succ); + impSpillCliqueSetMember(SpillCliqueSucc, succ, 1); + succCliqueToDo = new (this) BlockListNode(succ, succCliqueToDo); + toDo = true; + } + } + } + // Look at the predecessors of every member of the successor to-do list. + while (succCliqueToDo != nullptr) + { + BlockListNode* node = succCliqueToDo; + succCliqueToDo = node->m_next; + BasicBlock* blk = node->m_blk; + FreeBlockListNode(node); + + for (BasicBlockList* pred = blk->bbCheapPreds; pred != nullptr; pred = pred->next) + { + BasicBlock* predBlock = pred->block; + // If it's not already in the clique, add it, and also add it + // as a member of the predecessor "toDo" set. + if (impSpillCliqueGetMember(SpillCliquePred, predBlock) == 0) + { + callback->Visit(SpillCliquePred, predBlock); + impSpillCliqueSetMember(SpillCliquePred, predBlock, 1); + predCliqueToDo = new (this) BlockListNode(predBlock, predCliqueToDo); + toDo = true; + } + } + } + } + + // If this fails, it means we didn't walk the spill clique properly and somehow managed + // miss walking back to include the predecessor we started from. + // This most likely cause: missing or out of date bbPreds + assert(impSpillCliqueGetMember(SpillCliquePred, block) != 0); +} + +void Compiler::SetSpillTempsBase::Visit(SpillCliqueDir predOrSucc, BasicBlock* blk) +{ + if (predOrSucc == SpillCliqueSucc) + { + assert(blk->bbStkTempsIn == NO_BASE_TMP); // Should not already be a member of a clique as a successor. + blk->bbStkTempsIn = m_baseTmp; + } + else + { + assert(predOrSucc == SpillCliquePred); + assert(blk->bbStkTempsOut == NO_BASE_TMP); // Should not already be a member of a clique as a predecessor. + blk->bbStkTempsOut = m_baseTmp; + } +} + +void Compiler::ReimportSpillClique::Visit(SpillCliqueDir predOrSucc, BasicBlock* blk) +{ + // For Preds we could be a little smarter and just find the existing store + // and re-type it/add a cast, but that is complicated and hopefully very rare, so + // just re-import the whole block (just like we do for successors) + + if (((blk->bbFlags & BBF_IMPORTED) == 0) && (m_pComp->impGetPendingBlockMember(blk) == 0)) + { + // If we haven't imported this block and we're not going to (because it isn't on + // the pending list) then just ignore it for now. + + // This block has either never been imported (EntryState == NULL) or it failed + // verification. Neither state requires us to force it to be imported now. + assert((blk->bbEntryState == nullptr) || (blk->bbFlags & BBF_FAILED_VERIFICATION)); + return; + } + + // For successors we have a valid verCurrentState, so just mark them for reimport + // the 'normal' way + // Unlike predecessors, we *DO* need to reimport the current block because the + // initial import had the wrong entry state types. + // Similarly, blocks that are currently on the pending list, still need to call + // impImportBlockPending to fixup their entry state. + if (predOrSucc == SpillCliqueSucc) + { + m_pComp->impReimportMarkBlock(blk); + + // Set the current stack state to that of the blk->bbEntryState + m_pComp->verResetCurrentState(blk, &m_pComp->verCurrentState); + assert(m_pComp->verCurrentState.thisInitialized == blk->bbThisOnEntry()); + + m_pComp->impImportBlockPending(blk); + } + else if ((blk != m_pComp->compCurBB) && ((blk->bbFlags & BBF_IMPORTED) != 0)) + { + // As described above, we are only visiting predecessors so they can + // add the appropriate casts, since we have already done that for the current + // block, it does not need to be reimported. + // Nor do we need to reimport blocks that are still pending, but not yet + // imported. + // + // For predecessors, we have no state to seed the EntryState, so we just have + // to assume the existing one is correct. + // If the block is also a successor, it will get the EntryState properly + // updated when it is visited as a successor in the above "if" block. + assert(predOrSucc == SpillCliquePred); + m_pComp->impReimportBlockPending(blk); + } +} + +// Re-type the incoming lclVar nodes to match the varDsc. +void Compiler::impRetypeEntryStateTemps(BasicBlock* blk) +{ + if (blk->bbEntryState != nullptr) + { + EntryState* es = blk->bbEntryState; + for (unsigned level = 0; level < es->esStackDepth; level++) + { + GenTreePtr tree = es->esStack[level].val; + if ((tree->gtOper == GT_LCL_VAR) || (tree->gtOper == GT_LCL_FLD)) + { + unsigned lclNum = tree->gtLclVarCommon.gtLclNum; + noway_assert(lclNum < lvaCount); + LclVarDsc* varDsc = lvaTable + lclNum; + es->esStack[level].val->gtType = varDsc->TypeGet(); + } + } + } +} + +unsigned Compiler::impGetSpillTmpBase(BasicBlock* block) +{ + if (block->bbStkTempsOut != NO_BASE_TMP) + { + return block->bbStkTempsOut; + } + +#ifdef DEBUG + if (verbose) + { + printf("\n*************** In impGetSpillTmpBase(BB%02u)\n", block->bbNum); + } +#endif // DEBUG + + // Otherwise, choose one, and propagate to all members of the spill clique. + // Grab enough temps for the whole stack. + unsigned baseTmp = lvaGrabTemps(verCurrentState.esStackDepth DEBUGARG("IL Stack Entries")); + SetSpillTempsBase callback(baseTmp); + + // We do *NOT* need to reset the SpillClique*Members because a block can only be the predecessor + // to one spill clique, and similarly can only be the sucessor to one spill clique + impWalkSpillCliqueFromPred(block, &callback); + + return baseTmp; +} + +void Compiler::impReimportSpillClique(BasicBlock* block) +{ +#ifdef DEBUG + if (verbose) + { + printf("\n*************** In impReimportSpillClique(BB%02u)\n", block->bbNum); + } +#endif // DEBUG + + // If we get here, it is because this block is already part of a spill clique + // and one predecessor had an outgoing live stack slot of type int, and this + // block has an outgoing live stack slot of type native int. + // We need to reset these before traversal because they have already been set + // by the previous walk to determine all the members of the spill clique. + impInlineRoot()->impSpillCliquePredMembers.Reset(); + impInlineRoot()->impSpillCliqueSuccMembers.Reset(); + + ReimportSpillClique callback(this); + + impWalkSpillCliqueFromPred(block, &callback); +} + +// Set the pre-state of "block" (which should not have a pre-state allocated) to +// a copy of "srcState", cloning tree pointers as required. +void Compiler::verInitBBEntryState(BasicBlock* block, EntryState* srcState) +{ + if (srcState->esStackDepth == 0 && srcState->thisInitialized == TIS_Bottom) + { + block->bbEntryState = nullptr; + return; + } + + block->bbEntryState = (EntryState*)compGetMemA(sizeof(EntryState)); + + // block->bbEntryState.esRefcount = 1; + + block->bbEntryState->esStackDepth = srcState->esStackDepth; + block->bbEntryState->thisInitialized = TIS_Bottom; + + if (srcState->esStackDepth > 0) + { + block->bbSetStack(new (this, CMK_Unknown) StackEntry[srcState->esStackDepth]); + unsigned stackSize = srcState->esStackDepth * sizeof(StackEntry); + + memcpy(block->bbEntryState->esStack, srcState->esStack, stackSize); + for (unsigned level = 0; level < srcState->esStackDepth; level++) + { + GenTreePtr tree = srcState->esStack[level].val; + block->bbEntryState->esStack[level].val = gtCloneExpr(tree); + } + } + + if (verTrackObjCtorInitState) + { + verSetThisInit(block, srcState->thisInitialized); + } + + return; +} + +void Compiler::verSetThisInit(BasicBlock* block, ThisInitState tis) +{ + assert(tis != TIS_Bottom); // Precondition. + if (block->bbEntryState == nullptr) + { + block->bbEntryState = new (this, CMK_Unknown) EntryState(); + } + + block->bbEntryState->thisInitialized = tis; +} + +/* + * Resets the current state to the state at the start of the basic block + */ +void Compiler::verResetCurrentState(BasicBlock* block, EntryState* destState) +{ + + if (block->bbEntryState == nullptr) + { + destState->esStackDepth = 0; + destState->thisInitialized = TIS_Bottom; + return; + } + + destState->esStackDepth = block->bbEntryState->esStackDepth; + + if (destState->esStackDepth > 0) + { + unsigned stackSize = destState->esStackDepth * sizeof(StackEntry); + + memcpy(destState->esStack, block->bbStackOnEntry(), stackSize); + } + + destState->thisInitialized = block->bbThisOnEntry(); + + return; +} + +ThisInitState BasicBlock::bbThisOnEntry() +{ + return bbEntryState ? bbEntryState->thisInitialized : TIS_Bottom; +} + +unsigned BasicBlock::bbStackDepthOnEntry() +{ + return (bbEntryState ? bbEntryState->esStackDepth : 0); +} + +void BasicBlock::bbSetStack(void* stackBuffer) +{ + assert(bbEntryState); + assert(stackBuffer); + bbEntryState->esStack = (StackEntry*)stackBuffer; +} + +StackEntry* BasicBlock::bbStackOnEntry() +{ + assert(bbEntryState); + return bbEntryState->esStack; +} + +void Compiler::verInitCurrentState() +{ + verTrackObjCtorInitState = FALSE; + verCurrentState.thisInitialized = TIS_Bottom; + + if (tiVerificationNeeded) + { + // Track this ptr initialization + if (!info.compIsStatic && (info.compFlags & CORINFO_FLG_CONSTRUCTOR) && lvaTable[0].lvVerTypeInfo.IsObjRef()) + { + verTrackObjCtorInitState = TRUE; + verCurrentState.thisInitialized = TIS_Uninit; + } + } + + // initialize stack info + + verCurrentState.esStackDepth = 0; + assert(verCurrentState.esStack != nullptr); + + // copy current state to entry state of first BB + verInitBBEntryState(fgFirstBB, &verCurrentState); +} + +Compiler* Compiler::impInlineRoot() +{ + if (impInlineInfo == nullptr) + { + return this; + } + else + { + return impInlineInfo->InlineRoot; + } +} + +BYTE Compiler::impSpillCliqueGetMember(SpillCliqueDir predOrSucc, BasicBlock* blk) +{ + if (predOrSucc == SpillCliquePred) + { + return impInlineRoot()->impSpillCliquePredMembers.Get(blk->bbInd()); + } + else + { + assert(predOrSucc == SpillCliqueSucc); + return impInlineRoot()->impSpillCliqueSuccMembers.Get(blk->bbInd()); + } +} + +void Compiler::impSpillCliqueSetMember(SpillCliqueDir predOrSucc, BasicBlock* blk, BYTE val) +{ + if (predOrSucc == SpillCliquePred) + { + impInlineRoot()->impSpillCliquePredMembers.Set(blk->bbInd(), val); + } + else + { + assert(predOrSucc == SpillCliqueSucc); + impInlineRoot()->impSpillCliqueSuccMembers.Set(blk->bbInd(), val); + } +} + +/***************************************************************************** + * + * Convert the instrs ("import") into our internal format (trees). The + * basic flowgraph has already been constructed and is passed in. + */ + +void Compiler::impImport(BasicBlock* method) +{ +#ifdef DEBUG + if (verbose) + { + printf("*************** In impImport() for %s\n", info.compFullName); + } +#endif + + /* Allocate the stack contents */ + + if (info.compMaxStack <= sizeof(impSmallStack) / sizeof(impSmallStack[0])) + { + /* Use local variable, don't waste time allocating on the heap */ + + impStkSize = sizeof(impSmallStack) / sizeof(impSmallStack[0]); + verCurrentState.esStack = impSmallStack; + } + else + { + impStkSize = info.compMaxStack; + verCurrentState.esStack = new (this, CMK_ImpStack) StackEntry[impStkSize]; + } + + // initialize the entry state at start of method + verInitCurrentState(); + + // Initialize stuff related to figuring "spill cliques" (see spec comment for impGetSpillTmpBase). + Compiler* inlineRoot = impInlineRoot(); + if (this == inlineRoot) // These are only used on the root of the inlining tree. + { + // We have initialized these previously, but to size 0. Make them larger. + impPendingBlockMembers.Init(getAllocator(), fgBBNumMax * 2); + impSpillCliquePredMembers.Init(getAllocator(), fgBBNumMax * 2); + impSpillCliqueSuccMembers.Init(getAllocator(), fgBBNumMax * 2); + } + inlineRoot->impPendingBlockMembers.Reset(fgBBNumMax * 2); + inlineRoot->impSpillCliquePredMembers.Reset(fgBBNumMax * 2); + inlineRoot->impSpillCliqueSuccMembers.Reset(fgBBNumMax * 2); + impBlockListNodeFreeList = nullptr; + +#ifdef DEBUG + impLastILoffsStmt = nullptr; + impNestedStackSpill = false; +#endif + impBoxTemp = BAD_VAR_NUM; + + impPendingList = impPendingFree = nullptr; + + /* Add the entry-point to the worker-list */ + + // Skip leading internal blocks. There can be one as a leading scratch BB, and more + // from EH normalization. + // NOTE: It might be possible to always just put fgFirstBB on the pending list, and let everything else just fall + // out. + for (; method->bbFlags & BBF_INTERNAL; method = method->bbNext) + { + // Treat these as imported. + assert(method->bbJumpKind == BBJ_NONE); // We assume all the leading ones are fallthrough. + JITDUMP("Marking leading BBF_INTERNAL block BB%02u as BBF_IMPORTED\n", method->bbNum); + method->bbFlags |= BBF_IMPORTED; + } + + impImportBlockPending(method); + + /* Import blocks in the worker-list until there are no more */ + + while (impPendingList) + { + /* Remove the entry at the front of the list */ + + PendingDsc* dsc = impPendingList; + impPendingList = impPendingList->pdNext; + impSetPendingBlockMember(dsc->pdBB, 0); + + /* Restore the stack state */ + + verCurrentState.thisInitialized = dsc->pdThisPtrInit; + verCurrentState.esStackDepth = dsc->pdSavedStack.ssDepth; + if (verCurrentState.esStackDepth) + { + impRestoreStackState(&dsc->pdSavedStack); + } + + /* Add the entry to the free list for reuse */ + + dsc->pdNext = impPendingFree; + impPendingFree = dsc; + + /* Now import the block */ + + if (dsc->pdBB->bbFlags & BBF_FAILED_VERIFICATION) + { + +#ifdef _TARGET_64BIT_ + // On AMD64, during verification we have to match JIT64 behavior since the VM is very tighly + // coupled with the JIT64 IL Verification logic. Look inside verHandleVerificationFailure + // method for further explanation on why we raise this exception instead of making the jitted + // code throw the verification exception during execution. + if (tiVerificationNeeded && (opts.eeFlags & CORJIT_FLG_IMPORT_ONLY) != 0) + { + BADCODE("Basic block marked as not verifiable"); + } + else +#endif // _TARGET_64BIT_ + { + verConvertBBToThrowVerificationException(dsc->pdBB DEBUGARG(true)); + impEndTreeList(dsc->pdBB); + } + } + else + { + impImportBlock(dsc->pdBB); + + if (compDonotInline()) + { + return; + } + if (compIsForImportOnly() && !tiVerificationNeeded) + { + return; + } + } + } + +#ifdef DEBUG + if (verbose && info.compXcptnsCount) + { + printf("\nAfter impImport() added block for try,catch,finally"); + fgDispBasicBlocks(); + printf("\n"); + } + + // Used in impImportBlockPending() for STRESS_CHK_REIMPORT + for (BasicBlock* block = fgFirstBB; block; block = block->bbNext) + { + block->bbFlags &= ~BBF_VISITED; + } +#endif + + assert(!compIsForInlining() || !tiVerificationNeeded); +} + +// Checks if a typeinfo (usually stored in the type stack) is a struct. +// The invariant here is that if it's not a ref or a method and has a class handle +// it's a valuetype +bool Compiler::impIsValueType(typeInfo* pTypeInfo) +{ + if (pTypeInfo && pTypeInfo->IsValueClassWithClsHnd()) + { + return true; + } + else + { + return false; + } +} + +/***************************************************************************** + * Check to see if the tree is the address of a local or + the address of a field in a local. + + *lclVarTreeOut will contain the GT_LCL_VAR tree when it returns TRUE. + + */ + +BOOL Compiler::impIsAddressInLocal(GenTreePtr tree, GenTreePtr* lclVarTreeOut) +{ + if (tree->gtOper != GT_ADDR) + { + return FALSE; + } + + GenTreePtr op = tree->gtOp.gtOp1; + while (op->gtOper == GT_FIELD) + { + op = op->gtField.gtFldObj; + if (op && op->gtOper == GT_ADDR) // Skip static fields where op will be NULL. + { + op = op->gtOp.gtOp1; + } + else + { + return false; + } + } + + if (op->gtOper == GT_LCL_VAR) + { + *lclVarTreeOut = op; + return TRUE; + } + else + { + return FALSE; + } +} + +//------------------------------------------------------------------------ +// impMakeDiscretionaryInlineObservations: make observations that help +// determine the profitability of a discretionary inline +// +// Arguments: +// pInlineInfo -- InlineInfo for the inline, or null for the prejit root +// inlineResult -- InlineResult accumulating information about this inline +// +// Notes: +// If inlining or prejitting the root, this method also makes +// various observations about the method that factor into inline +// decisions. It sets `compNativeSizeEstimate` as a side effect. + +void Compiler::impMakeDiscretionaryInlineObservations(InlineInfo* pInlineInfo, InlineResult* inlineResult) +{ + assert(pInlineInfo != nullptr && compIsForInlining() || // Perform the actual inlining. + pInlineInfo == nullptr && !compIsForInlining() // Calculate the static inlining hint for ngen. + ); + + // If we're really inlining, we should just have one result in play. + assert((pInlineInfo == nullptr) || (inlineResult == pInlineInfo->inlineResult)); + + // If this is a "forceinline" method, the JIT probably shouldn't have gone + // to the trouble of estimating the native code size. Even if it did, it + // shouldn't be relying on the result of this method. + assert(inlineResult->GetObservation() == InlineObservation::CALLEE_IS_DISCRETIONARY_INLINE); + + // Note if the caller contains NEWOBJ or NEWARR. + Compiler* rootCompiler = impInlineRoot(); + + if ((rootCompiler->optMethodFlags & OMF_HAS_NEWARRAY) != 0) + { + inlineResult->Note(InlineObservation::CALLER_HAS_NEWARRAY); + } + + if ((rootCompiler->optMethodFlags & OMF_HAS_NEWOBJ) != 0) + { + inlineResult->Note(InlineObservation::CALLER_HAS_NEWOBJ); + } + + bool calleeIsStatic = (info.compFlags & CORINFO_FLG_STATIC) != 0; + bool isSpecialMethod = (info.compFlags & CORINFO_FLG_CONSTRUCTOR) != 0; + + if (isSpecialMethod) + { + if (calleeIsStatic) + { + inlineResult->Note(InlineObservation::CALLEE_IS_CLASS_CTOR); + } + else + { + inlineResult->Note(InlineObservation::CALLEE_IS_INSTANCE_CTOR); + } + } + else if (!calleeIsStatic) + { + // Callee is an instance method. + // + // Check if the callee has the same 'this' as the root. + if (pInlineInfo != nullptr) + { + GenTreePtr thisArg = pInlineInfo->iciCall->gtCall.gtCallObjp; + assert(thisArg); + bool isSameThis = impIsThis(thisArg); + inlineResult->NoteBool(InlineObservation::CALLSITE_IS_SAME_THIS, isSameThis); + } + } + + // Note if the callee's class is a promotable struct + if ((info.compClassAttr & CORINFO_FLG_VALUECLASS) != 0) + { + lvaStructPromotionInfo structPromotionInfo; + lvaCanPromoteStructType(info.compClassHnd, &structPromotionInfo, false); + if (structPromotionInfo.canPromote) + { + inlineResult->Note(InlineObservation::CALLEE_CLASS_PROMOTABLE); + } + } + +#ifdef FEATURE_SIMD + + // Note if this method is has SIMD args or return value + if (pInlineInfo != nullptr && pInlineInfo->hasSIMDTypeArgLocalOrReturn) + { + inlineResult->Note(InlineObservation::CALLEE_HAS_SIMD); + } + +#endif // FEATURE_SIMD + + // Roughly classify callsite frequency. + InlineCallsiteFrequency frequency = InlineCallsiteFrequency::UNUSED; + + // If this is a prejit root, or a maximally hot block... + if ((pInlineInfo == nullptr) || (pInlineInfo->iciBlock->bbWeight >= BB_MAX_WEIGHT)) + { + frequency = InlineCallsiteFrequency::HOT; + } + // No training data. Look for loop-like things. + // We consider a recursive call loop-like. Do not give the inlining boost to the method itself. + // However, give it to things nearby. + else if ((pInlineInfo->iciBlock->bbFlags & BBF_BACKWARD_JUMP) && + (pInlineInfo->fncHandle != pInlineInfo->inlineCandidateInfo->ilCallerHandle)) + { + frequency = InlineCallsiteFrequency::LOOP; + } + else if ((pInlineInfo->iciBlock->bbFlags & BBF_PROF_WEIGHT) && (pInlineInfo->iciBlock->bbWeight > BB_ZERO_WEIGHT)) + { + frequency = InlineCallsiteFrequency::WARM; + } + // Now modify the multiplier based on where we're called from. + else if (pInlineInfo->iciBlock->isRunRarely() || ((info.compFlags & FLG_CCTOR) == FLG_CCTOR)) + { + frequency = InlineCallsiteFrequency::RARE; + } + else + { + frequency = InlineCallsiteFrequency::BORING; + } + + // Also capture the block weight of the call site. In the prejit + // root case, assume there's some hot call site for this method. + unsigned weight = 0; + + if (pInlineInfo != nullptr) + { + weight = pInlineInfo->iciBlock->bbWeight; + } + else + { + weight = BB_MAX_WEIGHT; + } + + inlineResult->NoteInt(InlineObservation::CALLSITE_FREQUENCY, static_cast<int>(frequency)); + inlineResult->NoteInt(InlineObservation::CALLSITE_WEIGHT, static_cast<int>(weight)); +} + +/***************************************************************************** + This method makes STATIC inlining decision based on the IL code. + It should not make any inlining decision based on the context. + If forceInline is true, then the inlining decision should not depend on + performance heuristics (code size, etc.). + */ + +void Compiler::impCanInlineIL(CORINFO_METHOD_HANDLE fncHandle, + CORINFO_METHOD_INFO* methInfo, + bool forceInline, + InlineResult* inlineResult) +{ + unsigned codeSize = methInfo->ILCodeSize; + + // We shouldn't have made up our minds yet... + assert(!inlineResult->IsDecided()); + + if (methInfo->EHcount) + { + inlineResult->NoteFatal(InlineObservation::CALLEE_HAS_EH); + return; + } + + if ((methInfo->ILCode == nullptr) || (codeSize == 0)) + { + inlineResult->NoteFatal(InlineObservation::CALLEE_HAS_NO_BODY); + return; + } + + // For now we don't inline varargs (import code can't handle it) + + if (methInfo->args.isVarArg()) + { + inlineResult->NoteFatal(InlineObservation::CALLEE_HAS_MANAGED_VARARGS); + return; + } + + // Reject if it has too many locals. + // This is currently an implementation limit due to fixed-size arrays in the + // inline info, rather than a performance heuristic. + + inlineResult->NoteInt(InlineObservation::CALLEE_NUMBER_OF_LOCALS, methInfo->locals.numArgs); + + if (methInfo->locals.numArgs > MAX_INL_LCLS) + { + inlineResult->NoteFatal(InlineObservation::CALLEE_TOO_MANY_LOCALS); + return; + } + + // Make sure there aren't too many arguments. + // This is currently an implementation limit due to fixed-size arrays in the + // inline info, rather than a performance heuristic. + + inlineResult->NoteInt(InlineObservation::CALLEE_NUMBER_OF_ARGUMENTS, methInfo->args.numArgs); + + if (methInfo->args.numArgs > MAX_INL_ARGS) + { + inlineResult->NoteFatal(InlineObservation::CALLEE_TOO_MANY_ARGUMENTS); + return; + } + + // Note force inline state + + inlineResult->NoteBool(InlineObservation::CALLEE_IS_FORCE_INLINE, forceInline); + + // Note IL code size + + inlineResult->NoteInt(InlineObservation::CALLEE_IL_CODE_SIZE, codeSize); + + if (inlineResult->IsFailure()) + { + return; + } + + // Make sure maxstack is not too big + + inlineResult->NoteInt(InlineObservation::CALLEE_MAXSTACK, methInfo->maxStack); + + if (inlineResult->IsFailure()) + { + return; + } +} + +/***************************************************************************** + */ + +void Compiler::impCheckCanInline(GenTreePtr call, + CORINFO_METHOD_HANDLE fncHandle, + unsigned methAttr, + CORINFO_CONTEXT_HANDLE exactContextHnd, + InlineCandidateInfo** ppInlineCandidateInfo, + InlineResult* inlineResult) +{ + // Either EE or JIT might throw exceptions below. + // If that happens, just don't inline the method. + + struct Param + { + Compiler* pThis; + GenTreePtr call; + CORINFO_METHOD_HANDLE fncHandle; + unsigned methAttr; + CORINFO_CONTEXT_HANDLE exactContextHnd; + InlineResult* result; + InlineCandidateInfo** ppInlineCandidateInfo; + } param = {nullptr}; + + param.pThis = this; + param.call = call; + param.fncHandle = fncHandle; + param.methAttr = methAttr; + param.exactContextHnd = (exactContextHnd != nullptr) ? exactContextHnd : MAKE_METHODCONTEXT(fncHandle); + param.result = inlineResult; + param.ppInlineCandidateInfo = ppInlineCandidateInfo; + + bool success = eeRunWithErrorTrap<Param>( + [](Param* pParam) { + DWORD dwRestrictions = 0; + CorInfoInitClassResult initClassResult; + +#ifdef DEBUG + const char* methodName; + const char* className; + methodName = pParam->pThis->eeGetMethodName(pParam->fncHandle, &className); + + if (JitConfig.JitNoInline()) + { + pParam->result->NoteFatal(InlineObservation::CALLEE_IS_JIT_NOINLINE); + goto _exit; + } +#endif + + /* Try to get the code address/size for the method */ + + CORINFO_METHOD_INFO methInfo; + if (!pParam->pThis->info.compCompHnd->getMethodInfo(pParam->fncHandle, &methInfo)) + { + pParam->result->NoteFatal(InlineObservation::CALLEE_NO_METHOD_INFO); + goto _exit; + } + + bool forceInline; + forceInline = !!(pParam->methAttr & CORINFO_FLG_FORCEINLINE); + + pParam->pThis->impCanInlineIL(pParam->fncHandle, &methInfo, forceInline, pParam->result); + + if (pParam->result->IsFailure()) + { + assert(pParam->result->IsNever()); + goto _exit; + } + + // Speculatively check if initClass() can be done. + // If it can be done, we will try to inline the method. If inlining + // succeeds, then we will do the non-speculative initClass() and commit it. + // If this speculative call to initClass() fails, there is no point + // trying to inline this method. + initClassResult = + pParam->pThis->info.compCompHnd->initClass(nullptr /* field */, pParam->fncHandle /* method */, + pParam->exactContextHnd /* context */, + TRUE /* speculative */); + + if (initClassResult & CORINFO_INITCLASS_DONT_INLINE) + { + pParam->result->NoteFatal(InlineObservation::CALLSITE_CLASS_INIT_FAILURE_SPEC); + goto _exit; + } + + // Given the EE the final say in whether to inline or not. + // This should be last since for verifiable code, this can be expensive + + /* VM Inline check also ensures that the method is verifiable if needed */ + CorInfoInline vmResult; + vmResult = pParam->pThis->info.compCompHnd->canInline(pParam->pThis->info.compMethodHnd, pParam->fncHandle, + &dwRestrictions); + + if (vmResult == INLINE_FAIL) + { + pParam->result->NoteFatal(InlineObservation::CALLSITE_IS_VM_NOINLINE); + } + else if (vmResult == INLINE_NEVER) + { + pParam->result->NoteFatal(InlineObservation::CALLEE_IS_VM_NOINLINE); + } + + if (pParam->result->IsFailure()) + { + // Make sure not to report this one. It was already reported by the VM. + pParam->result->SetReported(); + goto _exit; + } + + // check for unsupported inlining restrictions + assert((dwRestrictions & ~(INLINE_RESPECT_BOUNDARY | INLINE_NO_CALLEE_LDSTR | INLINE_SAME_THIS)) == 0); + + if (dwRestrictions & INLINE_SAME_THIS) + { + GenTreePtr thisArg = pParam->call->gtCall.gtCallObjp; + assert(thisArg); + + if (!pParam->pThis->impIsThis(thisArg)) + { + pParam->result->NoteFatal(InlineObservation::CALLSITE_REQUIRES_SAME_THIS); + goto _exit; + } + } + + /* Get the method properties */ + + CORINFO_CLASS_HANDLE clsHandle; + clsHandle = pParam->pThis->info.compCompHnd->getMethodClass(pParam->fncHandle); + unsigned clsAttr; + clsAttr = pParam->pThis->info.compCompHnd->getClassAttribs(clsHandle); + + /* Get the return type */ + + var_types fncRetType; + fncRetType = pParam->call->TypeGet(); + +#ifdef DEBUG + var_types fncRealRetType; + fncRealRetType = JITtype2varType(methInfo.args.retType); + + assert((genActualType(fncRealRetType) == genActualType(fncRetType)) || + // <BUGNUM> VSW 288602 </BUGNUM> + // In case of IJW, we allow to assign a native pointer to a BYREF. + (fncRetType == TYP_BYREF && methInfo.args.retType == CORINFO_TYPE_PTR) || + (varTypeIsStruct(fncRetType) && (fncRealRetType == TYP_STRUCT))); +#endif + + // + // Allocate an InlineCandidateInfo structure + // + InlineCandidateInfo* pInfo; + pInfo = new (pParam->pThis, CMK_Inlining) InlineCandidateInfo; + + pInfo->dwRestrictions = dwRestrictions; + pInfo->methInfo = methInfo; + pInfo->methAttr = pParam->methAttr; + pInfo->clsHandle = clsHandle; + pInfo->clsAttr = clsAttr; + pInfo->fncRetType = fncRetType; + pInfo->exactContextHnd = pParam->exactContextHnd; + pInfo->ilCallerHandle = pParam->pThis->info.compMethodHnd; + pInfo->initClassResult = initClassResult; + + *(pParam->ppInlineCandidateInfo) = pInfo; + + _exit:; + }, + ¶m); + if (!success) + { + param.result->NoteFatal(InlineObservation::CALLSITE_COMPILATION_ERROR); + } +} + +void Compiler::impInlineRecordArgInfo(InlineInfo* pInlineInfo, + GenTreePtr curArgVal, + unsigned argNum, + InlineResult* inlineResult) +{ + InlArgInfo* inlCurArgInfo = &pInlineInfo->inlArgInfo[argNum]; + + if (curArgVal->gtOper == GT_MKREFANY) + { + inlineResult->NoteFatal(InlineObservation::CALLSITE_ARG_IS_MKREFANY); + return; + } + + inlCurArgInfo->argNode = curArgVal; + + GenTreePtr lclVarTree; + if (impIsAddressInLocal(curArgVal, &lclVarTree) && varTypeIsStruct(lclVarTree)) + { + inlCurArgInfo->argIsByRefToStructLocal = true; +#ifdef FEATURE_SIMD + if (lvaTable[lclVarTree->AsLclVarCommon()->gtLclNum].lvSIMDType) + { + pInlineInfo->hasSIMDTypeArgLocalOrReturn = true; + } +#endif // FEATURE_SIMD + } + + if (curArgVal->gtFlags & GTF_ORDER_SIDEEFF) + { + // Right now impInlineSpillLclRefs and impInlineSpillGlobEffects don't take + // into account special side effects, so we disallow them during inlining. + inlineResult->NoteFatal(InlineObservation::CALLSITE_ARG_HAS_SIDE_EFFECT); + return; + } + + if (curArgVal->gtFlags & GTF_GLOB_EFFECT) + { + inlCurArgInfo->argHasGlobRef = (curArgVal->gtFlags & GTF_GLOB_REF) != 0; + inlCurArgInfo->argHasSideEff = (curArgVal->gtFlags & GTF_SIDE_EFFECT) != 0; + } + + if (curArgVal->gtOper == GT_LCL_VAR) + { + inlCurArgInfo->argIsLclVar = true; + + /* Remember the "original" argument number */ + curArgVal->gtLclVar.gtLclILoffs = argNum; + } + + if ((curArgVal->OperKind() & GTK_CONST) || + ((curArgVal->gtOper == GT_ADDR) && (curArgVal->gtOp.gtOp1->gtOper == GT_LCL_VAR))) + { + inlCurArgInfo->argIsInvariant = true; + if (inlCurArgInfo->argIsThis && (curArgVal->gtOper == GT_CNS_INT) && (curArgVal->gtIntCon.gtIconVal == 0)) + { + /* Abort, but do not mark as not inlinable */ + inlineResult->NoteFatal(InlineObservation::CALLSITE_ARG_HAS_NULL_THIS); + return; + } + } + + if (!inlCurArgInfo->argIsInvariant && gtHasLocalsWithAddrOp(curArgVal)) + { + inlCurArgInfo->argHasLdargaOp = true; + } + +#ifdef DEBUG + if (verbose) + { + if (inlCurArgInfo->argIsThis) + { + printf("thisArg:"); + } + else + { + printf("\nArgument #%u:", argNum); + } + if (inlCurArgInfo->argIsLclVar) + { + printf(" is a local var"); + } + if (inlCurArgInfo->argIsInvariant) + { + printf(" is a constant"); + } + if (inlCurArgInfo->argHasGlobRef) + { + printf(" has global refs"); + } + if (inlCurArgInfo->argHasSideEff) + { + printf(" has side effects"); + } + if (inlCurArgInfo->argHasLdargaOp) + { + printf(" has ldarga effect"); + } + if (inlCurArgInfo->argHasStargOp) + { + printf(" has starg effect"); + } + if (inlCurArgInfo->argIsByRefToStructLocal) + { + printf(" is byref to a struct local"); + } + + printf("\n"); + gtDispTree(curArgVal); + printf("\n"); + } +#endif +} + +/***************************************************************************** + * + */ + +void Compiler::impInlineInitVars(InlineInfo* pInlineInfo) +{ + assert(!compIsForInlining()); + + GenTreePtr call = pInlineInfo->iciCall; + CORINFO_METHOD_INFO* methInfo = &pInlineInfo->inlineCandidateInfo->methInfo; + unsigned clsAttr = pInlineInfo->inlineCandidateInfo->clsAttr; + InlArgInfo* inlArgInfo = pInlineInfo->inlArgInfo; + InlLclVarInfo* lclVarInfo = pInlineInfo->lclVarInfo; + InlineResult* inlineResult = pInlineInfo->inlineResult; + + const bool hasRetBuffArg = impMethodInfo_hasRetBuffArg(methInfo); + + /* init the argument stuct */ + + memset(inlArgInfo, 0, (MAX_INL_ARGS + 1) * sizeof(inlArgInfo[0])); + + /* Get hold of the 'this' pointer and the argument list proper */ + + GenTreePtr thisArg = call->gtCall.gtCallObjp; + GenTreePtr argList = call->gtCall.gtCallArgs; + unsigned argCnt = 0; // Count of the arguments + + assert((methInfo->args.hasThis()) == (thisArg != nullptr)); + + if (thisArg) + { + inlArgInfo[0].argIsThis = true; + + impInlineRecordArgInfo(pInlineInfo, thisArg, argCnt, inlineResult); + + if (inlineResult->IsFailure()) + { + return; + } + + /* Increment the argument count */ + argCnt++; + } + + /* Record some information about each of the arguments */ + bool hasTypeCtxtArg = (methInfo->args.callConv & CORINFO_CALLCONV_PARAMTYPE) != 0; + +#if USER_ARGS_COME_LAST + unsigned typeCtxtArg = thisArg ? 1 : 0; +#else // USER_ARGS_COME_LAST + unsigned typeCtxtArg = methInfo->args.totalILArgs(); +#endif // USER_ARGS_COME_LAST + + for (GenTreePtr argTmp = argList; argTmp; argTmp = argTmp->gtOp.gtOp2) + { + if (argTmp == argList && hasRetBuffArg) + { + continue; + } + + // Ignore the type context argument + if (hasTypeCtxtArg && (argCnt == typeCtxtArg)) + { + typeCtxtArg = 0xFFFFFFFF; + continue; + } + + assert(argTmp->gtOper == GT_LIST); + GenTreePtr argVal = argTmp->gtOp.gtOp1; + + impInlineRecordArgInfo(pInlineInfo, argVal, argCnt, inlineResult); + + if (inlineResult->IsFailure()) + { + return; + } + + /* Increment the argument count */ + argCnt++; + } + + /* Make sure we got the arg number right */ + assert(argCnt == methInfo->args.totalILArgs()); + +#ifdef FEATURE_SIMD + bool foundSIMDType = pInlineInfo->hasSIMDTypeArgLocalOrReturn; +#endif // FEATURE_SIMD + + /* We have typeless opcodes, get type information from the signature */ + + if (thisArg) + { + var_types sigType; + + if (clsAttr & CORINFO_FLG_VALUECLASS) + { + sigType = TYP_BYREF; + } + else + { + sigType = TYP_REF; + } + + lclVarInfo[0].lclVerTypeInfo = verMakeTypeInfo(pInlineInfo->inlineCandidateInfo->clsHandle); + lclVarInfo[0].lclHasLdlocaOp = false; + +#ifdef FEATURE_SIMD + // We always want to check isSIMDClass, since we want to set foundSIMDType (to increase + // the inlining multiplier) for anything in that assembly. + // But we only need to normalize it if it is a TYP_STRUCT + // (which we need to do even if we have already set foundSIMDType). + if ((!foundSIMDType || (sigType == TYP_STRUCT)) && isSIMDClass(&(lclVarInfo[0].lclVerTypeInfo))) + { + if (sigType == TYP_STRUCT) + { + sigType = impNormStructType(lclVarInfo[0].lclVerTypeInfo.GetClassHandle()); + } + foundSIMDType = true; + } +#endif // FEATURE_SIMD + lclVarInfo[0].lclTypeInfo = sigType; + + assert(varTypeIsGC(thisArg->gtType) || // "this" is managed + (thisArg->gtType == TYP_I_IMPL && // "this" is unmgd but the method's class doesnt care + (clsAttr & CORINFO_FLG_VALUECLASS))); + + if (genActualType(thisArg->gtType) != genActualType(sigType)) + { + if (sigType == TYP_REF) + { + /* The argument cannot be bashed into a ref (see bug 750871) */ + inlineResult->NoteFatal(InlineObservation::CALLSITE_ARG_NO_BASH_TO_REF); + return; + } + + /* This can only happen with byrefs <-> ints/shorts */ + + assert(genActualType(sigType) == TYP_I_IMPL || sigType == TYP_BYREF); + assert(genActualType(thisArg->gtType) == TYP_I_IMPL || thisArg->gtType == TYP_BYREF); + + if (sigType == TYP_BYREF) + { + lclVarInfo[0].lclVerTypeInfo = typeInfo(varType2tiType(TYP_I_IMPL)); + } + else if (thisArg->gtType == TYP_BYREF) + { + assert(sigType == TYP_I_IMPL); + + /* If possible change the BYREF to an int */ + if (thisArg->IsVarAddr()) + { + thisArg->gtType = TYP_I_IMPL; + lclVarInfo[0].lclVerTypeInfo = typeInfo(varType2tiType(TYP_I_IMPL)); + } + else + { + /* Arguments 'int <- byref' cannot be bashed */ + inlineResult->NoteFatal(InlineObservation::CALLSITE_ARG_NO_BASH_TO_INT); + return; + } + } + } + } + + /* Init the types of the arguments and make sure the types + * from the trees match the types in the signature */ + + CORINFO_ARG_LIST_HANDLE argLst; + argLst = methInfo->args.args; + + unsigned i; + for (i = (thisArg ? 1 : 0); i < argCnt; i++, argLst = info.compCompHnd->getArgNext(argLst)) + { + var_types sigType = (var_types)eeGetArgType(argLst, &methInfo->args); + + lclVarInfo[i].lclVerTypeInfo = verParseArgSigToTypeInfo(&methInfo->args, argLst); +#ifdef FEATURE_SIMD + if ((!foundSIMDType || (sigType == TYP_STRUCT)) && isSIMDClass(&(lclVarInfo[i].lclVerTypeInfo))) + { + // If this is a SIMD class (i.e. in the SIMD assembly), then we will consider that we've + // found a SIMD type, even if this may not be a type we recognize (the assumption is that + // it is likely to use a SIMD type, and therefore we want to increase the inlining multiplier). + foundSIMDType = true; + if (sigType == TYP_STRUCT) + { + var_types structType = impNormStructType(lclVarInfo[i].lclVerTypeInfo.GetClassHandle()); + sigType = structType; + } + } +#endif // FEATURE_SIMD + + lclVarInfo[i].lclTypeInfo = sigType; + lclVarInfo[i].lclHasLdlocaOp = false; + + /* Does the tree type match the signature type? */ + + GenTreePtr inlArgNode = inlArgInfo[i].argNode; + + if (sigType != inlArgNode->gtType) + { + /* In valid IL, this can only happen for short integer types or byrefs <-> [native] ints, + but in bad IL cases with caller-callee signature mismatches we can see other types. + Intentionally reject cases with mismatches so the jit is more flexible when + encountering bad IL. */ + + bool isPlausibleTypeMatch = (genActualType(sigType) == genActualType(inlArgNode->gtType)) || + (genActualTypeIsIntOrI(sigType) && inlArgNode->gtType == TYP_BYREF) || + (sigType == TYP_BYREF && genActualTypeIsIntOrI(inlArgNode->gtType)); + + if (!isPlausibleTypeMatch) + { + inlineResult->NoteFatal(InlineObservation::CALLSITE_ARG_TYPES_INCOMPATIBLE); + return; + } + + /* Is it a narrowing or widening cast? + * Widening casts are ok since the value computed is already + * normalized to an int (on the IL stack) */ + + if (genTypeSize(inlArgNode->gtType) >= genTypeSize(sigType)) + { + if (sigType == TYP_BYREF) + { + lclVarInfo[i].lclVerTypeInfo = typeInfo(varType2tiType(TYP_I_IMPL)); + } + else if (inlArgNode->gtType == TYP_BYREF) + { + assert(varTypeIsIntOrI(sigType)); + + /* If possible bash the BYREF to an int */ + if (inlArgNode->IsVarAddr()) + { + inlArgNode->gtType = TYP_I_IMPL; + lclVarInfo[i].lclVerTypeInfo = typeInfo(varType2tiType(TYP_I_IMPL)); + } + else + { + /* Arguments 'int <- byref' cannot be changed */ + inlineResult->NoteFatal(InlineObservation::CALLSITE_ARG_NO_BASH_TO_INT); + return; + } + } + else if (genTypeSize(sigType) < EA_PTRSIZE) + { + /* Narrowing cast */ + + if (inlArgNode->gtOper == GT_LCL_VAR && + !lvaTable[inlArgNode->gtLclVarCommon.gtLclNum].lvNormalizeOnLoad() && + sigType == lvaGetRealType(inlArgNode->gtLclVarCommon.gtLclNum)) + { + /* We don't need to insert a cast here as the variable + was assigned a normalized value of the right type */ + + continue; + } + + inlArgNode = inlArgInfo[i].argNode = gtNewCastNode(TYP_INT, inlArgNode, sigType); + + inlArgInfo[i].argIsLclVar = false; + + /* Try to fold the node in case we have constant arguments */ + + if (inlArgInfo[i].argIsInvariant) + { + inlArgNode = gtFoldExprConst(inlArgNode); + inlArgInfo[i].argNode = inlArgNode; + assert(inlArgNode->OperIsConst()); + } + } +#ifdef _TARGET_64BIT_ + else if (genTypeSize(genActualType(inlArgNode->gtType)) < genTypeSize(sigType)) + { + // This should only happen for int -> native int widening + inlArgNode = inlArgInfo[i].argNode = gtNewCastNode(genActualType(sigType), inlArgNode, sigType); + + inlArgInfo[i].argIsLclVar = false; + + /* Try to fold the node in case we have constant arguments */ + + if (inlArgInfo[i].argIsInvariant) + { + inlArgNode = gtFoldExprConst(inlArgNode); + inlArgInfo[i].argNode = inlArgNode; + assert(inlArgNode->OperIsConst()); + } + } +#endif // _TARGET_64BIT_ + } + } + } + + /* Init the types of the local variables */ + + CORINFO_ARG_LIST_HANDLE localsSig; + localsSig = methInfo->locals.args; + + for (i = 0; i < methInfo->locals.numArgs; i++) + { + bool isPinned; + var_types type = (var_types)eeGetArgType(localsSig, &methInfo->locals, &isPinned); + + lclVarInfo[i + argCnt].lclHasLdlocaOp = false; + lclVarInfo[i + argCnt].lclTypeInfo = type; + + if (isPinned) + { + inlineResult->NoteFatal(InlineObservation::CALLEE_HAS_PINNED_LOCALS); + return; + } + + lclVarInfo[i + argCnt].lclVerTypeInfo = verParseArgSigToTypeInfo(&methInfo->locals, localsSig); + + localsSig = info.compCompHnd->getArgNext(localsSig); + +#ifdef FEATURE_SIMD + if ((!foundSIMDType || (type == TYP_STRUCT)) && isSIMDClass(&(lclVarInfo[i + argCnt].lclVerTypeInfo))) + { + foundSIMDType = true; + if (featureSIMD && type == TYP_STRUCT) + { + var_types structType = impNormStructType(lclVarInfo[i + argCnt].lclVerTypeInfo.GetClassHandle()); + lclVarInfo[i + argCnt].lclTypeInfo = structType; + } + } +#endif // FEATURE_SIMD + } + +#ifdef FEATURE_SIMD + if (!foundSIMDType && (call->AsCall()->gtRetClsHnd != nullptr) && isSIMDClass(call->AsCall()->gtRetClsHnd)) + { + foundSIMDType = true; + } + pInlineInfo->hasSIMDTypeArgLocalOrReturn = foundSIMDType; +#endif // FEATURE_SIMD +} + +unsigned Compiler::impInlineFetchLocal(unsigned lclNum DEBUGARG(const char* reason)) +{ + assert(compIsForInlining()); + + unsigned tmpNum = impInlineInfo->lclTmpNum[lclNum]; + + if (tmpNum == BAD_VAR_NUM) + { + var_types lclTyp = impInlineInfo->lclVarInfo[lclNum + impInlineInfo->argCnt].lclTypeInfo; + + // The lifetime of this local might span multiple BBs. + // So it is a long lifetime local. + impInlineInfo->lclTmpNum[lclNum] = tmpNum = lvaGrabTemp(false DEBUGARG(reason)); + + lvaTable[tmpNum].lvType = lclTyp; + if (impInlineInfo->lclVarInfo[lclNum + impInlineInfo->argCnt].lclHasLdlocaOp) + { + lvaTable[tmpNum].lvHasLdAddrOp = 1; + } + + if (impInlineInfo->lclVarInfo[lclNum + impInlineInfo->argCnt].lclVerTypeInfo.IsStruct()) + { + if (varTypeIsStruct(lclTyp)) + { + lvaSetStruct(tmpNum, + impInlineInfo->lclVarInfo[lclNum + impInlineInfo->argCnt].lclVerTypeInfo.GetClassHandle(), + true /* unsafe value cls check */); + } + else + { + // This is a wrapped primitive. Make sure the verstate knows that + lvaTable[tmpNum].lvVerTypeInfo = + impInlineInfo->lclVarInfo[lclNum + impInlineInfo->argCnt].lclVerTypeInfo; + } + } + } + + return tmpNum; +} + +// A method used to return the GenTree (usually a GT_LCL_VAR) representing the arguments of the inlined method. +// Only use this method for the arguments of the inlinee method. +// !!! Do not use it for the locals of the inlinee method. !!!! + +GenTreePtr Compiler::impInlineFetchArg(unsigned lclNum, InlArgInfo* inlArgInfo, InlLclVarInfo* lclVarInfo) +{ + /* Get the argument type */ + var_types lclTyp = lclVarInfo[lclNum].lclTypeInfo; + + GenTreePtr op1 = nullptr; + + // constant or address of local + if (inlArgInfo[lclNum].argIsInvariant && !inlArgInfo[lclNum].argHasLdargaOp && !inlArgInfo[lclNum].argHasStargOp) + { + /* Clone the constant. Note that we cannot directly use argNode + in the trees even if inlArgInfo[lclNum].argIsUsed==false as this + would introduce aliasing between inlArgInfo[].argNode and + impInlineExpr. Then gtFoldExpr() could change it, causing further + references to the argument working off of the bashed copy. */ + + op1 = gtCloneExpr(inlArgInfo[lclNum].argNode); + PREFIX_ASSUME(op1 != nullptr); + inlArgInfo[lclNum].argTmpNum = (unsigned)-1; // illegal temp + } + else if (inlArgInfo[lclNum].argIsLclVar && !inlArgInfo[lclNum].argHasLdargaOp && !inlArgInfo[lclNum].argHasStargOp) + { + /* Argument is a local variable (of the caller) + * Can we re-use the passed argument node? */ + + op1 = inlArgInfo[lclNum].argNode; + inlArgInfo[lclNum].argTmpNum = op1->gtLclVarCommon.gtLclNum; + + if (inlArgInfo[lclNum].argIsUsed) + { + assert(op1->gtOper == GT_LCL_VAR); + assert(lclNum == op1->gtLclVar.gtLclILoffs); + + if (!lvaTable[op1->gtLclVarCommon.gtLclNum].lvNormalizeOnLoad()) + { + lclTyp = genActualType(lclTyp); + } + + /* Create a new lcl var node - remember the argument lclNum */ + op1 = gtNewLclvNode(op1->gtLclVarCommon.gtLclNum, lclTyp, op1->gtLclVar.gtLclILoffs); + } + } + else if (inlArgInfo[lclNum].argIsByRefToStructLocal && !inlArgInfo[lclNum].argHasStargOp) + { + /* Argument is a by-ref address to a struct, a normed struct, or its field. + In these cases, don't spill the byref to a local, simply clone the tree and use it. + This way we will increase the chance for this byref to be optimized away by + a subsequent "dereference" operation. + + From Dev11 bug #139955: Argument node can also be TYP_I_IMPL if we've bashed the tree + (in impInlineInitVars()), if the arg has argHasLdargaOp as well as argIsByRefToStructLocal. + For example, if the caller is: + ldloca.s V_1 // V_1 is a local struct + call void Test.ILPart::RunLdargaOnPointerArg(int32*) + and the callee being inlined has: + .method public static void RunLdargaOnPointerArg(int32* ptrToInts) cil managed + ldarga.s ptrToInts + call void Test.FourInts::NotInlined_SetExpectedValuesThroughPointerToPointer(int32**) + then we change the argument tree (of "ldloca.s V_1") to TYP_I_IMPL to match the callee signature. We'll + soon afterwards reject the inlining anyway, since the tree we return isn't a GT_LCL_VAR. + */ + assert(inlArgInfo[lclNum].argNode->TypeGet() == TYP_BYREF || + inlArgInfo[lclNum].argNode->TypeGet() == TYP_I_IMPL); + op1 = gtCloneExpr(inlArgInfo[lclNum].argNode); + } + else + { + /* Argument is a complex expression - it must be evaluated into a temp */ + + if (inlArgInfo[lclNum].argHasTmp) + { + assert(inlArgInfo[lclNum].argIsUsed); + assert(inlArgInfo[lclNum].argTmpNum < lvaCount); + + /* Create a new lcl var node - remember the argument lclNum */ + op1 = gtNewLclvNode(inlArgInfo[lclNum].argTmpNum, genActualType(lclTyp)); + + /* This is the second or later use of the this argument, + so we have to use the temp (instead of the actual arg) */ + inlArgInfo[lclNum].argBashTmpNode = nullptr; + } + else + { + /* First time use */ + assert(inlArgInfo[lclNum].argIsUsed == false); + + /* Reserve a temp for the expression. + * Use a large size node as we may change it later */ + + unsigned tmpNum = lvaGrabTemp(true DEBUGARG("Inlining Arg")); + + lvaTable[tmpNum].lvType = lclTyp; + assert(lvaTable[tmpNum].lvAddrExposed == 0); + if (inlArgInfo[lclNum].argHasLdargaOp) + { + lvaTable[tmpNum].lvHasLdAddrOp = 1; + } + + if (lclVarInfo[lclNum].lclVerTypeInfo.IsStruct()) + { + if (varTypeIsStruct(lclTyp)) + { + lvaSetStruct(tmpNum, impInlineInfo->lclVarInfo[lclNum].lclVerTypeInfo.GetClassHandle(), + true /* unsafe value cls check */); + } + else + { + // This is a wrapped primitive. Make sure the verstate knows that + lvaTable[tmpNum].lvVerTypeInfo = impInlineInfo->lclVarInfo[lclNum].lclVerTypeInfo; + } + } + + inlArgInfo[lclNum].argHasTmp = true; + inlArgInfo[lclNum].argTmpNum = tmpNum; + + // If we require strict exception order, then arguments must + // be evaluated in sequence before the body of the inlined method. + // So we need to evaluate them to a temp. + // Also, if arguments have global references, we need to + // evaluate them to a temp before the inlined body as the + // inlined body may be modifying the global ref. + // TODO-1stClassStructs: We currently do not reuse an existing lclVar + // if it is a struct, because it requires some additional handling. + + if (!varTypeIsStruct(lclTyp) && (!inlArgInfo[lclNum].argHasSideEff) && (!inlArgInfo[lclNum].argHasGlobRef)) + { + /* Get a *LARGE* LCL_VAR node */ + op1 = gtNewLclLNode(tmpNum, genActualType(lclTyp), lclNum); + + /* Record op1 as the very first use of this argument. + If there are no further uses of the arg, we may be + able to use the actual arg node instead of the temp. + If we do see any further uses, we will clear this. */ + inlArgInfo[lclNum].argBashTmpNode = op1; + } + else + { + /* Get a small LCL_VAR node */ + op1 = gtNewLclvNode(tmpNum, genActualType(lclTyp)); + /* No bashing of this argument */ + inlArgInfo[lclNum].argBashTmpNode = nullptr; + } + } + } + + /* Mark the argument as used */ + + inlArgInfo[lclNum].argIsUsed = true; + + return op1; +} + +/****************************************************************************** + Is this the original "this" argument to the call being inlined? + + Note that we do not inline methods with "starg 0", and so we do not need to + worry about it. +*/ + +BOOL Compiler::impInlineIsThis(GenTreePtr tree, InlArgInfo* inlArgInfo) +{ + assert(compIsForInlining()); + return (tree->gtOper == GT_LCL_VAR && tree->gtLclVarCommon.gtLclNum == inlArgInfo[0].argTmpNum); +} + +//----------------------------------------------------------------------------- +// This function checks if a dereference in the inlinee can guarantee that +// the "this" is non-NULL. +// If we haven't hit a branch or a side effect, and we are dereferencing +// from 'this' to access a field or make GTF_CALL_NULLCHECK call, +// then we can avoid a separate null pointer check. +// +// "additionalTreesToBeEvaluatedBefore" +// is the set of pending trees that have not yet been added to the statement list, +// and which have been removed from verCurrentState.esStack[] + +BOOL Compiler::impInlineIsGuaranteedThisDerefBeforeAnySideEffects(GenTreePtr additionalTreesToBeEvaluatedBefore, + GenTreePtr variableBeingDereferenced, + InlArgInfo* inlArgInfo) +{ + assert(compIsForInlining()); + assert(opts.OptEnabled(CLFLG_INLINING)); + + BasicBlock* block = compCurBB; + + GenTreePtr stmt; + GenTreePtr expr; + + if (block != fgFirstBB) + { + return FALSE; + } + + if (!impInlineIsThis(variableBeingDereferenced, inlArgInfo)) + { + return FALSE; + } + + if (additionalTreesToBeEvaluatedBefore && + GTF_GLOBALLY_VISIBLE_SIDE_EFFECTS(additionalTreesToBeEvaluatedBefore->gtFlags)) + { + return FALSE; + } + + for (stmt = impTreeList->gtNext; stmt; stmt = stmt->gtNext) + { + expr = stmt->gtStmt.gtStmtExpr; + + if (GTF_GLOBALLY_VISIBLE_SIDE_EFFECTS(expr->gtFlags)) + { + return FALSE; + } + } + + for (unsigned level = 0; level < verCurrentState.esStackDepth; level++) + { + unsigned stackTreeFlags = verCurrentState.esStack[level].val->gtFlags; + if (GTF_GLOBALLY_VISIBLE_SIDE_EFFECTS(stackTreeFlags)) + { + return FALSE; + } + } + + return TRUE; +} + +/******************************************************************************/ +// Check the inlining eligibility of this GT_CALL node. +// Mark GTF_CALL_INLINE_CANDIDATE on the GT_CALL node + +// Todo: find a way to record the failure reasons in the IR (or +// otherwise build tree context) so when we do the inlining pass we +// can capture these reasons + +void Compiler::impMarkInlineCandidate(GenTreePtr callNode, + CORINFO_CONTEXT_HANDLE exactContextHnd, + CORINFO_CALL_INFO* callInfo) +{ + // Let the strategy know there's another call + impInlineRoot()->m_inlineStrategy->NoteCall(); + + if (!opts.OptEnabled(CLFLG_INLINING)) + { + /* XXX Mon 8/18/2008 + * This assert is misleading. The caller does not ensure that we have CLFLG_INLINING set before + * calling impMarkInlineCandidate. However, if this assert trips it means that we're an inlinee and + * CLFLG_MINOPT is set. That doesn't make a lot of sense. If you hit this assert, work back and + * figure out why we did not set MAXOPT for this compile. + */ + assert(!compIsForInlining()); + return; + } + + if (compIsForImportOnly()) + { + // Don't bother creating the inline candidate during verification. + // Otherwise the call to info.compCompHnd->canInline will trigger a recursive verification + // that leads to the creation of multiple instances of Compiler. + return; + } + + GenTreeCall* call = callNode->AsCall(); + InlineResult inlineResult(this, call, nullptr, "impMarkInlineCandidate"); + + // Don't inline if not optimizing root method + if (opts.compDbgCode) + { + inlineResult.NoteFatal(InlineObservation::CALLER_DEBUG_CODEGEN); + return; + } + + // Don't inline if inlining into root method is disabled. + if (InlineStrategy::IsNoInline(info.compCompHnd, info.compMethodHnd)) + { + inlineResult.NoteFatal(InlineObservation::CALLER_IS_JIT_NOINLINE); + return; + } + + // Inlining candidate determination needs to honor only IL tail prefix. + // Inlining takes precedence over implicit tail call optimization (if the call is not directly recursive). + if (call->IsTailPrefixedCall()) + { + inlineResult.NoteFatal(InlineObservation::CALLSITE_EXPLICIT_TAIL_PREFIX); + return; + } + + // Tail recursion elimination takes precedence over inlining. + // TODO: We may want to do some of the additional checks from fgMorphCall + // here to reduce the chance we don't inline a call that won't be optimized + // as a fast tail call or turned into a loop. + if (gtIsRecursiveCall(call) && call->IsImplicitTailCall()) + { + inlineResult.NoteFatal(InlineObservation::CALLSITE_IMPLICIT_REC_TAIL_CALL); + return; + } + + if ((call->gtFlags & GTF_CALL_VIRT_KIND_MASK) != GTF_CALL_NONVIRT) + { + inlineResult.NoteFatal(InlineObservation::CALLSITE_IS_NOT_DIRECT); + return; + } + + /* Ignore helper calls */ + + if (call->gtCallType == CT_HELPER) + { + inlineResult.NoteFatal(InlineObservation::CALLSITE_IS_CALL_TO_HELPER); + return; + } + + /* Ignore indirect calls */ + if (call->gtCallType == CT_INDIRECT) + { + inlineResult.NoteFatal(InlineObservation::CALLSITE_IS_NOT_DIRECT_MANAGED); + return; + } + + /* I removed the check for BBJ_THROW. BBJ_THROW is usually marked as rarely run. This more or less + * restricts the inliner to non-expanding inlines. I removed the check to allow for non-expanding + * inlining in throw blocks. I should consider the same thing for catch and filter regions. */ + + CORINFO_METHOD_HANDLE fncHandle = call->gtCallMethHnd; + unsigned methAttr; + + // Reuse method flags from the original callInfo if possible + if (fncHandle == callInfo->hMethod) + { + methAttr = callInfo->methodFlags; + } + else + { + methAttr = info.compCompHnd->getMethodAttribs(fncHandle); + } + +#ifdef DEBUG + if (compStressCompile(STRESS_FORCE_INLINE, 0)) + { + methAttr |= CORINFO_FLG_FORCEINLINE; + } +#endif + + // Check for COMPlus_AggressiveInlining + if (compDoAggressiveInlining) + { + methAttr |= CORINFO_FLG_FORCEINLINE; + } + + if (!(methAttr & CORINFO_FLG_FORCEINLINE)) + { + /* Don't bother inline blocks that are in the filter region */ + if (bbInCatchHandlerILRange(compCurBB)) + { +#ifdef DEBUG + if (verbose) + { + printf("\nWill not inline blocks that are in the catch handler region\n"); + } + +#endif + + inlineResult.NoteFatal(InlineObservation::CALLSITE_IS_WITHIN_CATCH); + return; + } + + if (bbInFilterILRange(compCurBB)) + { +#ifdef DEBUG + if (verbose) + { + printf("\nWill not inline blocks that are in the filter region\n"); + } +#endif + + inlineResult.NoteFatal(InlineObservation::CALLSITE_IS_WITHIN_FILTER); + return; + } + } + + /* If the caller's stack frame is marked, then we can't do any inlining. Period. */ + + if (opts.compNeedSecurityCheck) + { + inlineResult.NoteFatal(InlineObservation::CALLER_NEEDS_SECURITY_CHECK); + return; + } + + /* Check if we tried to inline this method before */ + + if (methAttr & CORINFO_FLG_DONT_INLINE) + { + inlineResult.NoteFatal(InlineObservation::CALLEE_IS_NOINLINE); + return; + } + + /* Cannot inline synchronized methods */ + + if (methAttr & CORINFO_FLG_SYNCH) + { + inlineResult.NoteFatal(InlineObservation::CALLEE_IS_SYNCHRONIZED); + return; + } + + /* Do not inline if callee needs security checks (since they would then mark the wrong frame) */ + + if (methAttr & CORINFO_FLG_SECURITYCHECK) + { + inlineResult.NoteFatal(InlineObservation::CALLEE_NEEDS_SECURITY_CHECK); + return; + } + + InlineCandidateInfo* inlineCandidateInfo = nullptr; + impCheckCanInline(call, fncHandle, methAttr, exactContextHnd, &inlineCandidateInfo, &inlineResult); + + if (inlineResult.IsFailure()) + { + return; + } + + // The old value should be NULL + assert(call->gtInlineCandidateInfo == nullptr); + + call->gtInlineCandidateInfo = inlineCandidateInfo; + + // Mark the call node as inline candidate. + call->gtFlags |= GTF_CALL_INLINE_CANDIDATE; + + // Let the strategy know there's another candidate. + impInlineRoot()->m_inlineStrategy->NoteCandidate(); + + // Since we're not actually inlining yet, and this call site is + // still just an inline candidate, there's nothing to report. + inlineResult.SetReported(); +} + +/******************************************************************************/ +// Returns true if the given intrinsic will be implemented by target-specific +// instructions + +bool Compiler::IsTargetIntrinsic(CorInfoIntrinsics intrinsicId) +{ +#if defined(_TARGET_AMD64_) + switch (intrinsicId) + { + // Amd64 only has SSE2 instruction to directly compute sqrt/abs. + case CORINFO_INTRINSIC_Sqrt: + case CORINFO_INTRINSIC_Abs: + return true; + + default: + return false; + } +#elif defined(_TARGET_ARM64_) + switch (intrinsicId) + { + case CORINFO_INTRINSIC_Sqrt: + case CORINFO_INTRINSIC_Abs: + case CORINFO_INTRINSIC_Round: + return true; + + default: + return false; + } +#elif defined(_TARGET_ARM_) + switch (intrinsicId) + { + case CORINFO_INTRINSIC_Sqrt: + case CORINFO_INTRINSIC_Abs: + case CORINFO_INTRINSIC_Round: + return true; + + default: + return false; + } +#elif defined(_TARGET_X86_) + switch (intrinsicId) + { + case CORINFO_INTRINSIC_Sin: + case CORINFO_INTRINSIC_Cos: + case CORINFO_INTRINSIC_Sqrt: + case CORINFO_INTRINSIC_Abs: + case CORINFO_INTRINSIC_Round: + return true; + + default: + return false; + } +#else + // TODO: This portion of logic is not implemented for other arch. + // The reason for returning true is that on all other arch the only intrinsic + // enabled are target intrinsics. + return true; +#endif //_TARGET_AMD64_ +} + +/******************************************************************************/ +// Returns true if the given intrinsic will be implemented by calling System.Math +// methods. + +bool Compiler::IsIntrinsicImplementedByUserCall(CorInfoIntrinsics intrinsicId) +{ + // Currently, if an math intrisic is not implemented by target-specific + // intructions, it will be implemented by a System.Math call. In the + // future, if we turn to implementing some of them with helper callers, + // this predicate needs to be revisited. + return !IsTargetIntrinsic(intrinsicId); +} + +bool Compiler::IsMathIntrinsic(CorInfoIntrinsics intrinsicId) +{ + switch (intrinsicId) + { + case CORINFO_INTRINSIC_Sin: + case CORINFO_INTRINSIC_Sqrt: + case CORINFO_INTRINSIC_Abs: + case CORINFO_INTRINSIC_Cos: + case CORINFO_INTRINSIC_Round: + case CORINFO_INTRINSIC_Cosh: + case CORINFO_INTRINSIC_Sinh: + case CORINFO_INTRINSIC_Tan: + case CORINFO_INTRINSIC_Tanh: + case CORINFO_INTRINSIC_Asin: + case CORINFO_INTRINSIC_Acos: + case CORINFO_INTRINSIC_Atan: + case CORINFO_INTRINSIC_Atan2: + case CORINFO_INTRINSIC_Log10: + case CORINFO_INTRINSIC_Pow: + case CORINFO_INTRINSIC_Exp: + case CORINFO_INTRINSIC_Ceiling: + case CORINFO_INTRINSIC_Floor: + return true; + default: + return false; + } +} + +bool Compiler::IsMathIntrinsic(GenTreePtr tree) +{ + return (tree->OperGet() == GT_INTRINSIC) && IsMathIntrinsic(tree->gtIntrinsic.gtIntrinsicId); +} +/*****************************************************************************/ |