summaryrefslogtreecommitdiff
path: root/src/jit/importer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/jit/importer.cpp')
-rw-r--r--src/jit/importer.cpp760
1 files changed, 480 insertions, 280 deletions
diff --git a/src/jit/importer.cpp b/src/jit/importer.cpp
index d04ded78fa..cb09ff8b8c 100644
--- a/src/jit/importer.cpp
+++ b/src/jit/importer.cpp
@@ -63,15 +63,12 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
void Compiler::impInit()
{
-#ifdef DEBUG
- impTreeList = impTreeLast = nullptr;
-#endif
-#if defined(DEBUG)
+#ifdef DEBUG
+ impTreeList = nullptr;
+ impTreeLast = nullptr;
impInlinedCodeSize = 0;
#endif
-
- seenConditionalJump = false;
}
/*****************************************************************************
@@ -600,13 +597,9 @@ inline void Compiler::impAppendStmt(GenTreePtr stmt, unsigned chkLevel)
// 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))
+ !(expr->gtOp.gtOp1->gtFlags & GTF_GLOB_REF) && !gtHasLocalsWithAddrOp(expr->gtOp.gtOp2))
{
unsigned op2Flags = expr->gtOp.gtOp2->gtFlags & GTF_GLOB_EFFECT;
assert(flags == (op2Flags | GTF_ASG));
@@ -673,8 +666,6 @@ inline void Compiler::impAppendStmt(GenTreePtr stmt, unsigned chkLevel)
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 */
@@ -683,8 +674,6 @@ inline void Compiler::impAppendStmt(GenTreePtr stmt, unsigned chkLevel)
impCurStmtOffsSet(BAD_IL_OFFSET);
}
-#endif
-
#ifdef DEBUG
if (impLastILoffsStmt == nullptr)
{
@@ -1143,9 +1132,13 @@ GenTreePtr Compiler::impAssignStructPtr(GenTreePtr destAddr,
if (destAddr->OperGet() == GT_ADDR)
{
GenTree* destNode = destAddr->gtGetOp1();
- // If the actual destination is already a block node, or is a node that
+ // If the actual destination is a local (for non-LEGACY_BACKEND), or 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())
+ if (destNode->gtOper == GT_INDEX || destNode->OperIsBlk()
+#ifndef LEGACY_BACKEND
+ || ((destNode->OperGet() == GT_LCL_VAR) && (destNode->TypeGet() == src->TypeGet()))
+#endif // !LEGACY_BACKEND
+ )
{
dest = destNode;
}
@@ -1194,6 +1187,9 @@ GenTreePtr Compiler::impAssignStructPtr(GenTreePtr destAddr,
{
// Mark the struct LclVar as used in a MultiReg return context
// which currently makes it non promotable.
+ // TODO-1stClassStructs: Eliminate this pessimization when we can more generally
+ // handle multireg returns.
+ lcl->gtFlags |= GTF_DONT_CSE;
lvaTable[lcl->gtLclVarCommon.gtLclNum].lvIsMultiRegRet = true;
}
else // The call result is not a multireg return
@@ -1208,12 +1204,20 @@ GenTreePtr Compiler::impAssignStructPtr(GenTreePtr destAddr,
dest = lcl;
#if defined(_TARGET_ARM_)
+ // TODO-Cleanup: This should have been taken care of in the above HasMultiRegRetVal() case,
+ // but that method has not been updadted to include ARM.
impMarkLclDstNotPromotable(lcl->gtLclVarCommon.gtLclNum, src, structHnd);
+ lcl->gtFlags |= GTF_DONT_CSE;
#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.
+ // TODO-1stClassStructs: Eliminate this pessimization when we can more generally
+ // handle multireg returns.
+ // TODO-Cleanup: Why is this needed here? This seems that it will set this even for
+ // non-multireg returns.
+ lcl->gtFlags |= GTF_DONT_CSE;
lvaTable[lcl->gtLclVarCommon.gtLclNum].lvIsMultiRegRet = true;
#endif
}
@@ -1255,10 +1259,11 @@ GenTreePtr Compiler::impAssignStructPtr(GenTreePtr destAddr,
src->gtType = genActualType(returnType);
call->gtType = src->gtType;
- // 1stClassStructToDo: We shouldn't necessarily need this.
- if (dest != nullptr)
+ // If we've changed the type, and it no longer matches a local destination,
+ // we must use an indirection.
+ if ((dest != nullptr) && (dest->OperGet() == GT_LCL_VAR) && (dest->TypeGet() != asgType))
{
- dest = gtNewOperNode(GT_IND, returnType, gtNewOperNode(GT_ADDR, TYP_BYREF, dest));
+ dest = nullptr;
}
// !!! The destination could be on stack. !!!
@@ -1329,21 +1334,19 @@ GenTreePtr Compiler::impAssignStructPtr(GenTreePtr destAddr,
}
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;
+#ifdef LEGACY_BACKEND
+ if (asgType == TYP_STRUCT)
+ {
+ GenTree* srcAddr = gtNewOperNode(GT_ADDR, TYP_BYREF, src);
+ src = gtNewOperNode(GT_IND, TYP_STRUCT, srcAddr);
+ }
+#endif
}
if (dest == nullptr)
{
@@ -1459,6 +1462,8 @@ GenTreePtr Compiler::impGetStructAddr(GenTreePtr structVal,
// 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.
+// pSimdBaseType - (optional, default nullptr) - if non-null, and the struct is a SIMD
+// type, set to the SIMD base type
//
// Return Value:
// The JIT type for the struct (e.g. TYP_STRUCT, or TYP_SIMD*).
@@ -1480,53 +1485,69 @@ var_types Compiler::impNormStructType(CORINFO_CLASS_HANDLE structHnd,
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
+ const DWORD structFlags = info.compCompHnd->getClassAttribs(structHnd);
+ var_types structType = TYP_STRUCT;
+
+#ifdef FEATURE_CORECLR
+ const bool hasGCPtrs = (structFlags & CORINFO_FLG_CONTAINS_GC_PTR) != 0;
+#else
+ // Desktop CLR won't report FLG_CONTAINS_GC_PTR for RefAnyClass - need to check explicitly.
+ const bool isRefAny = (structHnd == impGetRefAnyClass());
+ const bool hasGCPtrs = isRefAny || ((structFlags & CORINFO_FLG_CONTAINS_GC_PTR) != 0);
+#endif
- 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)
+ if (featureSIMD && !hasGCPtrs)
{
- unsigned int sizeBytes;
- simdBaseType = getBaseTypeAndSizeOfSIMDType(structHnd, &sizeBytes);
- if (simdBaseType != TYP_UNKNOWN)
+ unsigned originalSize = info.compCompHnd->getClassSize(structHnd);
+
+ if ((originalSize >= minSIMDStructBytes()) && (originalSize <= maxSIMDStructBytes()))
{
- assert(sizeBytes == originalSize);
- structType = getSIMDTypeForSize(sizeBytes);
- if (pSimdBaseType != nullptr)
+ unsigned int sizeBytes;
+ var_types simdBaseType = getBaseTypeAndSizeOfSIMDType(structHnd, &sizeBytes);
+ if (simdBaseType != TYP_UNKNOWN)
{
- *pSimdBaseType = simdBaseType;
- }
+ 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;
+ // Amd64: also indicate that we use floating point registers
+ compFloatingPointUsed = true;
#endif
+ }
}
}
#endif // FEATURE_SIMD
- if (pNumGCVars != nullptr)
+
+ // Fetch GC layout info if requested
+ if (gcLayout != nullptr)
+ {
+ unsigned numGCVars = info.compCompHnd->getClassGClayout(structHnd, gcLayout);
+
+ // Verify that the quick test up above via the class attributes gave a
+ // safe view of the type's GCness.
+ //
+ // Note there are cases where hasGCPtrs is true but getClassGClayout
+ // does not report any gc fields.
+ assert(hasGCPtrs || (numGCVars == 0));
+
+ if (pNumGCVars != nullptr)
+ {
+ *pNumGCVars = numGCVars;
+ }
+ }
+ else
{
- *pNumGCVars = numGCVars;
+ // Can't safely ask for number of GC pointers without also
+ // asking for layout.
+ assert(pNumGCVars == nullptr);
}
+
return structType;
}
@@ -1777,15 +1798,19 @@ GenTreePtr Compiler::impReadyToRunLookupToTree(CORINFO_CONST_LOOKUP* pLookup,
unsigned handleFlags,
void* compileTimeHandle)
{
- CORINFO_GENERIC_HANDLE handle = 0;
- void* pIndirection = 0;
+ CORINFO_GENERIC_HANDLE handle = nullptr;
+ void* pIndirection = nullptr;
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);
+ }
+ return gtNewIconEmbHndNode(handle, pIndirection, handleFlags, 0, nullptr, compileTimeHandle);
}
GenTreePtr Compiler::impReadyToRunHelperToTree(
@@ -1798,7 +1823,9 @@ GenTreePtr Compiler::impReadyToRunHelperToTree(
CORINFO_CONST_LOOKUP lookup;
#if COR_JIT_EE_VERSION > 460
if (!info.compCompHnd->getReadyToRunHelper(pResolvedToken, pGenericLookupKind, helper, &lookup))
- return NULL;
+ {
+ return nullptr;
+ }
#else
info.compCompHnd->getReadyToRunHelper(pResolvedToken, helper, &lookup);
#endif
@@ -1828,7 +1855,9 @@ GenTreePtr Compiler::impMethodPointer(CORINFO_RESOLVED_TOKEN* pResolvedToken, CO
*op1->gtFptrVal.gtLdftnResolvedToken = *pResolvedToken;
}
else
+ {
op1->gtFptrVal.gtEntryPoint.addr = nullptr;
+ }
#endif
break;
@@ -1852,6 +1881,46 @@ GenTreePtr Compiler::impMethodPointer(CORINFO_RESOLVED_TOKEN* pResolvedToken, CO
return op1;
}
+//------------------------------------------------------------------------
+// getRuntimeContextTree: find pointer to context for runtime lookup.
+//
+// Arguments:
+// kind - lookup kind.
+//
+// Return Value:
+// Return GenTree pointer to generic shared context.
+//
+// Notes:
+// Reports about generic context using.
+
+GenTreePtr Compiler::getRuntimeContextTree(CORINFO_RUNTIME_LOOKUP_KIND kind)
+{
+ GenTreePtr ctxTree = nullptr;
+
+ // 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
+ }
+ return ctxTree;
+}
+
/*****************************************************************************/
/* Import a dictionary lookup to access a handle in code shared between
generic instantiations.
@@ -1874,36 +1943,12 @@ GenTreePtr Compiler::impRuntimeLookupToTree(CORINFO_RESOLVED_TOKEN* pResolvedTok
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
- }
+ GenTreePtr ctxTree = getRuntimeContextTree(pLookup->lookupKind.runtimeLookupKind);
#ifdef FEATURE_READYTORUN_COMPILER
if (opts.IsReadyToRun())
@@ -1913,6 +1958,7 @@ GenTreePtr Compiler::impRuntimeLookupToTree(CORINFO_RESOLVED_TOKEN* pResolvedTok
}
#endif
+ CORINFO_RUNTIME_LOOKUP* pRuntimeLookup = &pLookup->runtimeLookup;
// It's available only via the run-time helper function
if (pRuntimeLookup->indirections == CORINFO_USEHELPER)
{
@@ -2083,8 +2129,6 @@ bool Compiler::impSpillStackEntry(unsigned level,
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 */
@@ -2179,8 +2223,6 @@ void Compiler::impSpillStackEnsure(bool spillLeaves)
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"));
@@ -2318,8 +2360,6 @@ Compiler::fgWalkResult Compiler::impFindValueClasses(GenTreePtr* pTree, fgWalkDa
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 */
@@ -2676,7 +2716,6 @@ static inline bool impOpcodeIsCallOpcode(OPCODE opcode)
}
/*****************************************************************************/
-#ifdef DEBUGGING_SUPPORT
static inline bool impOpcodeIsCallSiteBoundary(OPCODE opcode)
{
@@ -2695,8 +2734,6 @@ static inline bool impOpcodeIsCallSiteBoundary(OPCODE opcode)
}
}
-#endif // DEBUGGING_SUPPORT
-
/*****************************************************************************/
// One might think it is worth caching these values, but results indicate
@@ -2816,27 +2853,6 @@ GenTreePtr Compiler::impImplicitR4orR8Cast(GenTreePtr tree, var_types dstTyp)
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.
@@ -3236,7 +3252,7 @@ GenTreePtr Compiler::impIntrinsic(CORINFO_CLASS_HANDLE clsHnd,
#if COR_JIT_EE_VERSION > 460
CorInfoIntrinsics intrinsicID = info.compCompHnd->getIntrinsicID(method, &mustExpand);
#else
- CorInfoIntrinsics intrinsicID = info.compCompHnd->getIntrinsicID(method);
+ CorInfoIntrinsics intrinsicID = info.compCompHnd->getIntrinsicID(method);
#endif
*pIntrinsicID = intrinsicID;
@@ -3307,9 +3323,9 @@ GenTreePtr Compiler::impIntrinsic(CORINFO_CLASS_HANDLE clsHnd,
op1 = nullptr;
-#ifdef LEGACY_BACKEND
+#if defined(LEGACY_BACKEND)
if (IsTargetIntrinsic(intrinsicID))
-#else
+#elif !defined(_TARGET_X86_)
// 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
@@ -3317,6 +3333,11 @@ GenTreePtr Compiler::impIntrinsic(CORINFO_CLASS_HANDLE clsHnd,
// 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)
+#else
+ // On x86 RyuJIT, importing intrinsics that are implemented as user calls can cause incorrect calculation
+ // of the depth of the stack if these intrinsics are used as arguments to another call. This causes bad
+ // code generation for certain EH constructs.
+ if (!IsIntrinsicImplementedByUserCall(intrinsicID))
#endif
{
switch (sig->numArgs)
@@ -3534,7 +3555,7 @@ GenTreePtr Compiler::impIntrinsic(CORINFO_CLASS_HANDLE clsHnd,
// Get native TypeHandle argument to old helper
op1 = op1->gtCall.gtCallArgs;
- assert(op1->IsList());
+ assert(op1->OperIsList());
assert(op1->gtOp.gtOp2 == nullptr);
op1 = op1->gtOp.gtOp1;
retNode = op1;
@@ -3886,7 +3907,7 @@ void Compiler::verHandleVerificationFailure(BasicBlock* block DEBUGARG(bool logM
#endif // DEBUG
// Add the non verifiable flag to the compiler
- if ((opts.eeFlags & CORJIT_FLG_IMPORT_ONLY) != 0)
+ if (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_IMPORT_ONLY))
{
tiIsVerifiableCode = FALSE;
}
@@ -4913,14 +4934,26 @@ GenTreePtr Compiler::impImportLdvirtftn(GenTreePtr thisPtr,
}
#ifdef FEATURE_READYTORUN_COMPILER
- if (opts.IsReadyToRun() && !pCallInfo->exactContextNeedsRuntimeLookup)
+ if (opts.IsReadyToRun())
{
- GenTreeCall* call = gtNewHelperCallNode(CORINFO_HELP_READYTORUN_VIRTUAL_FUNC_PTR, TYP_I_IMPL, GTF_EXCEPT,
- gtNewArgList(thisPtr));
+ if (!pCallInfo->exactContextNeedsRuntimeLookup)
+ {
+ GenTreeCall* call = gtNewHelperCallNode(CORINFO_HELP_READYTORUN_VIRTUAL_FUNC_PTR, TYP_I_IMPL, GTF_EXCEPT,
+ gtNewArgList(thisPtr));
- call->setEntryPoint(pCallInfo->codePointerLookup.constLookup);
+ call->setEntryPoint(pCallInfo->codePointerLookup.constLookup);
- return call;
+ return call;
+ }
+
+ // We need a runtime lookup. CoreRT has a ReadyToRun helper for that too.
+ if (IsTargetAbi(CORINFO_CORERT_ABI))
+ {
+ GenTreePtr ctxTree = getRuntimeContextTree(pCallInfo->codePointerLookup.lookupKind.runtimeLookupKind);
+
+ return impReadyToRunHelperToTree(pResolvedToken, CORINFO_HELP_READYTORUN_GENERIC_HANDLE, TYP_I_IMPL,
+ gtNewArgList(ctxTree), &pCallInfo->codePointerLookup.lookupKind);
+ }
}
#endif
@@ -5001,7 +5034,7 @@ void Compiler::impImportAndPushBox(CORINFO_RESOLVED_TOKEN* pResolvedToken)
if (opts.IsReadyToRun())
{
op1 = impReadyToRunHelperToTree(pResolvedToken, CORINFO_HELP_READYTORUN_NEW, TYP_REF);
- usingReadyToRunHelper = (op1 != NULL);
+ usingReadyToRunHelper = (op1 != nullptr);
}
if (!usingReadyToRunHelper)
@@ -5150,7 +5183,7 @@ void Compiler::impImportNewObjArray(CORINFO_RESOLVED_TOKEN* pResolvedToken, CORI
CLANG_FORMAT_COMMENT_ANCHOR;
#if COR_JIT_EE_VERSION > 460
- if (!opts.IsReadyToRun() || (eeGetEEInfo()->targetAbi == CORINFO_CORERT_ABI))
+ if (!opts.IsReadyToRun() || IsTargetAbi(CORINFO_CORERT_ABI))
{
LclVarDsc* newObjArrayArgsVar;
@@ -5325,61 +5358,110 @@ GenTreePtr Compiler::impTransformThis(GenTreePtr thisPtr,
}
}
-bool Compiler::impCanPInvokeInline(var_types callRetTyp)
+//------------------------------------------------------------------------
+// impCanPInvokeInline: examine information from a call to see if the call
+// qualifies as an inline pinvoke.
+//
+// Arguments:
+// block - block contaning the call, or for inlinees, block
+// containing the call being inlined
+//
+// Return Value:
+// true if this call qualifies as an inline pinvoke, false otherwise
+//
+// Notes:
+// Checks basic legality and then a number of ambient conditions
+// where we could pinvoke but choose not to
+
+bool Compiler::impCanPInvokeInline(BasicBlock* block)
{
- return impCanPInvokeInlineCallSite(callRetTyp) && getInlinePInvokeEnabled() && (!opts.compDbgCode) &&
+ return impCanPInvokeInlineCallSite(block) && 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)
+//------------------------------------------------------------------------
+// impCanPInvokeInlineSallSite: basic legality checks using information
+// from a call to see if the call qualifies as an inline pinvoke.
+//
+// Arguments:
+// block - block contaning the call, or for inlinees, block
+// containing the call being inlined
+//
+// Return Value:
+// true if this call can legally qualify as an inline pinvoke, false otherwise
+//
+// Notes:
+// For runtimes that support exception handling interop there are
+// restrictions on using inline pinvoke in handler regions.
+//
+// * 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)
+//
+// * 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.
+//
+// These restrictions are currently also in place for CoreCLR but
+// can be relaxed when coreclr/#8459 is addressed.
+
+bool Compiler::impCanPInvokeInlineCallSite(BasicBlock* block)
{
- 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);
+ // On x64, we disable pinvoke inlining inside of try regions.
+ // Here is 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.
+ //
+ // A desktop test case where this seems to matter is
+ // jit\jit64\ebvts\mcpp\sources2\ijw\__clrcall\vector_ctor_dtor.02\deldtor_clr.exe
+ const bool inX64Try = block->hasTryIndex();
+#else
+ const bool inX64Try = false;
+#endif // _TARGET_AMD64_
+
+ return !inX64Try && !block->hasHndIndex();
}
-void Compiler::impCheckForPInvokeCall(GenTreePtr call,
- CORINFO_METHOD_HANDLE methHnd,
- CORINFO_SIG_INFO* sig,
- unsigned mflags)
+//------------------------------------------------------------------------
+// impCheckForPInvokeCall examine call to see if it is a pinvoke and if so
+// if it can be expressed as an inline pinvoke.
+//
+// Arguments:
+// call - tree for the call
+// methHnd - handle for the method being called (may be null)
+// sig - signature of the method being called
+// mflags - method flags for the method being called
+// block - block contaning the call, or for inlinees, block
+// containing the call being inlined
+//
+// Notes:
+// Sets GTF_CALL_M_PINVOKE on the call for pinvokes.
+//
+// Also sets GTF_CALL_UNMANAGED on call for inline pinvokes if the
+// call passes a combination of legality and profitabilty checks.
+//
+// If GTF_CALL_UNMANAGED is set, increments info.compCallUnmanaged
+
+void Compiler::impCheckForPInvokeCall(
+ GenTreePtr call, CORINFO_METHOD_HANDLE methHnd, CORINFO_SIG_INFO* sig, unsigned mflags, BasicBlock* block)
{
- var_types callRetTyp = JITtype2varType(sig->retType);
CorInfoUnmanagedCallConv unmanagedCallConv;
// If VM flagged it as Pinvoke, flag the call node accordingly
@@ -5422,15 +5504,12 @@ void Compiler::impCheckForPInvokeCall(GenTreePtr call,
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_
+ // Always inline pinvoke.
}
else
{
- if (!impCanPInvokeInline(callRetTyp))
+ // Check legality and profitability.
+ if (!impCanPInvokeInline(block))
{
return;
}
@@ -5439,6 +5518,14 @@ void Compiler::impCheckForPInvokeCall(GenTreePtr call,
{
return;
}
+
+ // Size-speed tradeoff: don't use inline pinvoke at rarely
+ // executed call sites. The non-inline version is more
+ // compact.
+ if (block->isRunRarely())
+ {
+ return;
+ }
}
JITLOG((LL_INFO1000000, "\nInline a CALLI PINVOKE call from method %s", info.compFullName));
@@ -5446,8 +5533,6 @@ void Compiler::impCheckForPInvokeCall(GenTreePtr call,
call->gtFlags |= GTF_CALL_UNMANAGED;
info.compCallUnmanaged++;
- assert(!compIsForInlining());
-
// AMD64 convention is same for native and managed
if (unmanagedCallConv == CORINFO_UNMANAGED_CALLCONV_C)
{
@@ -5736,6 +5821,7 @@ GenTreePtr Compiler::impImportStaticFieldAccess(CORINFO_RESOLVED_TOKEN* pResolve
break;
case CORINFO_FIELD_STATIC_SHARED_STATIC_HELPER:
+ {
#ifdef FEATURE_READYTORUN_COMPILER
if (opts.IsReadyToRun())
{
@@ -5762,8 +5848,39 @@ GenTreePtr Compiler::impImportStaticFieldAccess(CORINFO_RESOLVED_TOKEN* pResolve
new (this, GT_CNS_INT) GenTreeIntCon(TYP_INT, pFieldInfo->offset, fs));
}
break;
+ }
+#if COR_JIT_EE_VERSION > 460
+ case CORINFO_FIELD_STATIC_READYTORUN_HELPER:
+ {
+#ifdef FEATURE_READYTORUN_COMPILER
+ noway_assert(opts.IsReadyToRun());
+ CORINFO_LOOKUP_KIND kind = info.compCompHnd->getLocationOfThisType(info.compMethodHnd);
+ assert(kind.needsRuntimeLookup);
+
+ GenTreePtr ctxTree = getRuntimeContextTree(kind.runtimeLookupKind);
+ GenTreeArgList* args = gtNewArgList(ctxTree);
+
+ unsigned callFlags = 0;
+
+ if (info.compCompHnd->getClassAttribs(pResolvedToken->hClass) & CORINFO_FLG_BEFOREFIELDINIT)
+ {
+ callFlags |= GTF_CALL_HOISTABLE;
+ }
+ var_types type = TYP_BYREF;
+ op1 = gtNewHelperCallNode(CORINFO_HELP_READYTORUN_GENERIC_STATIC_BASE, type, callFlags, args);
+ op1->gtCall.setEntryPoint(pFieldInfo->fieldLookup);
+ FieldSeqNode* fs = GetFieldSeqStore()->CreateSingleton(pResolvedToken->hField);
+ op1 = gtNewOperNode(GT_ADD, type, op1,
+ new (this, GT_CNS_INT) GenTreeIntCon(TYP_I_IMPL, pFieldInfo->offset, fs));
+#else
+ unreached();
+#endif // FEATURE_READYTORUN_COMPILER
+ }
+ break;
+#endif // COR_JIT_EE_VERSION > 460
default:
+ {
if (!(access & CORINFO_ACCESS_ADDRESS))
{
// In future, it may be better to just create the right tree here instead of folding it later.
@@ -5820,6 +5937,7 @@ GenTreePtr Compiler::impImportStaticFieldAccess(CORINFO_RESOLVED_TOKEN* pResolve
}
}
break;
+ }
}
if (pFieldInfo->fieldFlags & CORINFO_FLG_FIELD_STATIC_IN_HEAP)
@@ -6071,7 +6189,7 @@ bool Compiler::impIsTailCallILPattern(bool tailPrefixed,
((nextOpcode == CEE_NOP) || ((nextOpcode == CEE_POP) && (++cntPop == 1)))); // Next opcode = nop or exactly
// one pop seen so far.
#else
- nextOpcode = (OPCODE)getU1LittleEndian(codeAddrOfNextOpcode);
+ nextOpcode = (OPCODE)getU1LittleEndian(codeAddrOfNextOpcode);
#endif
if (isCallPopAndRet)
@@ -6845,9 +6963,15 @@ var_types Compiler::impImportCall(OPCODE opcode,
//--------------------------- Inline NDirect ------------------------------
- if (!compIsForInlining())
+ // For inline cases we technically should look at both the current
+ // block and the call site block (or just the latter if we've
+ // fused the EH trees). However the block-related checks pertain to
+ // EH and we currently won't inline a method with EH. So for
+ // inlinees, just checking the call site block is sufficient.
{
- impCheckForPInvokeCall(call, methHnd, sig, mflags);
+ // New lexical block here to avoid compilation errors because of GOTOs.
+ BasicBlock* block = compIsForInlining() ? impInlineInfo->iciBlock : compCurBB;
+ impCheckForPInvokeCall(call, methHnd, sig, mflags, block);
}
if (call->gtFlags & GTF_CALL_UNMANAGED)
@@ -7035,7 +7159,7 @@ var_types Compiler::impImportCall(OPCODE opcode,
{
instParam =
impReadyToRunLookupToTree(&callInfo->instParamLookup, GTF_ICON_CLASS_HDL, exactClassHandle);
- if (instParam == NULL)
+ if (instParam == nullptr)
{
return callRetTyp;
}
@@ -7452,10 +7576,6 @@ DONE_CALL:
{
call = impFixupCallStructReturn(call, sig->retTypeClass);
}
- else if (varTypeIsLong(callRetTyp))
- {
- call = impInitCallLongReturn(call);
- }
if ((call->gtFlags & GTF_CALL_INLINE_CANDIDATE) != 0)
{
@@ -7467,6 +7587,13 @@ DONE_CALL:
// TODO: Still using the widened type.
call = gtNewInlineCandidateReturnExpr(call, genActualType(callRetTyp));
}
+ else
+ {
+ // For non-candidates we must also spill, since we
+ // might have locals live on the eval stack that this
+ // call can modify.
+ impSpillSideEffects(true, CHECK_SPILL_ALL DEBUGARG("non-inline candidate call"));
+ }
}
if (!bIntrinsicImported)
@@ -7738,42 +7865,6 @@ GenTreePtr Compiler::impFixupCallStructReturn(GenTreePtr call, CORINFO_CLASS_HAN
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
@@ -7804,6 +7895,9 @@ GenTreePtr Compiler::impFixupStructReturnType(GenTreePtr op, CORINFO_CLASS_HANDL
unsigned lclNum = op->gtLclVarCommon.gtLclNum;
lvaTable[lclNum].lvIsMultiRegRet = true;
+ // TODO-1stClassStructs: Handle constant propagation and CSE-ing of multireg returns.
+ op->gtFlags |= GTF_DONT_CSE;
+
return op;
}
@@ -7828,6 +7922,10 @@ GenTreePtr Compiler::impFixupStructReturnType(GenTreePtr op, CORINFO_CLASS_HANDL
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;
+
+ // TODO-1stClassStructs: Handle constant propagation and CSE-ing of multireg returns.
+ op->gtFlags |= GTF_DONT_CSE;
+
return op;
}
@@ -7860,6 +7958,10 @@ GenTreePtr Compiler::impFixupStructReturnType(GenTreePtr op, CORINFO_CLASS_HANDL
// Make sure this struct type is not struct promoted
lvaTable[lclNum].lvIsMultiRegRet = true;
+
+ // TODO-1stClassStructs: Handle constant propagation and CSE-ing of multireg returns.
+ op->gtFlags |= GTF_DONT_CSE;
+
return op;
}
@@ -9311,8 +9413,6 @@ void Compiler::impImportBlockCode(BasicBlock* block)
opcodeOffs = (IL_OFFSET)(codeAddr - info.compCode);
-#if defined(DEBUGGING_SUPPORT) || defined(DEBUG)
-
#ifndef DEBUG
if (opts.compDbgInfo)
#endif
@@ -9424,8 +9524,6 @@ void Compiler::impImportBlockCode(BasicBlock* block)
}
}
-#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);
@@ -9515,6 +9613,14 @@ void Compiler::impImportBlockCode(BasicBlock* block)
SPILL_APPEND:
+ // We need to call impSpillLclRefs() for a struct type lclVar.
+ // This is done for non-block assignments in the handling of stloc.
+ if ((op1->OperGet() == GT_ASG) && varTypeIsStruct(op1->gtOp.gtOp1) &&
+ (op1->gtOp.gtOp1->gtOper == GT_LCL_VAR))
+ {
+ impSpillLclRefs(op1->gtOp.gtOp1->AsLclVarCommon()->gtLclNum);
+ }
+
/* Append 'op1' to the list of statements */
impAppendTree(op1, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs);
goto DONE_APPEND;
@@ -11087,8 +11193,6 @@ void Compiler::impImportBlockCode(BasicBlock* block)
COND_JUMP:
- seenConditionalJump = true;
-
/* Fold comparison if we can */
op1 = gtFoldExpr(op1);
@@ -12328,14 +12432,12 @@ void Compiler::impImportBlockCode(BasicBlock* block)
// 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)
+ if (IsTargetAbi(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'
@@ -12368,6 +12470,33 @@ void Compiler::impImportBlockCode(BasicBlock* block)
// The lookup of the code pointer will be handled by CALL in this case
if (clsFlags & CORINFO_FLG_VALUECLASS)
{
+ if (compIsForInlining())
+ {
+ // If value class has GC fields, inform the inliner. It may choose to
+ // bail out on the inline.
+ DWORD typeFlags = info.compCompHnd->getClassAttribs(resolvedToken.hClass);
+ if ((typeFlags & CORINFO_FLG_CONTAINS_GC_PTR) != 0)
+ {
+ compInlineResult->Note(InlineObservation::CALLEE_HAS_GC_STRUCT);
+ if (compInlineResult->IsFailure())
+ {
+ return;
+ }
+
+ // Do further notification in the case where the call site is rare;
+ // some policies do not track the relative hotness of call sites for
+ // "always" inline cases.
+ if (impInlineInfo->iciBlock->isRunRarely())
+ {
+ compInlineResult->Note(InlineObservation::CALLSITE_RARE_GC_STRUCT);
+ if (compInlineResult->IsFailure())
+ {
+ return;
+ }
+ }
+ }
+ }
+
CorInfoType jitTyp = info.compCompHnd->asCorInfoType(resolvedToken.hClass);
unsigned size = info.compCompHnd->getClassSize(resolvedToken.hClass);
@@ -12403,7 +12532,7 @@ void Compiler::impImportBlockCode(BasicBlock* block)
if (opts.IsReadyToRun())
{
op1 = impReadyToRunHelperToTree(&resolvedToken, CORINFO_HELP_READYTORUN_NEW, TYP_REF);
- usingReadyToRunHelper = (op1 != NULL);
+ usingReadyToRunHelper = (op1 != nullptr);
}
if (!usingReadyToRunHelper)
@@ -12503,6 +12632,10 @@ void Compiler::impImportBlockCode(BasicBlock* block)
if (compIsForInlining())
{
+ if (compDonotInline())
+ {
+ return;
+ }
// We rule out inlinees with explicit tail calls in fgMakeBasicBlocks.
assert((prefixFlags & PREFIX_TAILCALL_EXPLICIT) == 0);
}
@@ -12696,7 +12829,9 @@ void Compiler::impImportBlockCode(BasicBlock* block)
return;
case CORINFO_FIELD_STATIC_GENERICS_STATIC_HELPER:
-
+#if COR_JIT_EE_VERSION > 460
+ case CORINFO_FIELD_STATIC_READYTORUN_HELPER:
+#endif
/* We may be able to inline the field accessors in specific instantiations of generic
* methods */
compInlineResult->NoteFatal(InlineObservation::CALLSITE_LDFLD_NEEDS_HELPER);
@@ -12828,7 +12963,9 @@ void Compiler::impImportBlockCode(BasicBlock* block)
#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);
@@ -12925,6 +13062,9 @@ void Compiler::impImportBlockCode(BasicBlock* block)
case CORINFO_FIELD_STATIC_RVA_ADDRESS:
case CORINFO_FIELD_STATIC_SHARED_STATIC_HELPER:
case CORINFO_FIELD_STATIC_GENERICS_STATIC_HELPER:
+#if COR_JIT_EE_VERSION > 460
+ case CORINFO_FIELD_STATIC_READYTORUN_HELPER:
+#endif
op1 = impImportStaticFieldAccess(&resolvedToken, (CORINFO_ACCESS_FLAGS)aflags, &fieldInfo,
lclTyp);
break;
@@ -13068,6 +13208,9 @@ void Compiler::impImportBlockCode(BasicBlock* block)
return;
case CORINFO_FIELD_STATIC_GENERICS_STATIC_HELPER:
+#if COR_JIT_EE_VERSION > 460
+ case CORINFO_FIELD_STATIC_READYTORUN_HELPER:
+#endif
/* We may be able to inline the field accessors in specific instantiations of generic
* methods */
@@ -13134,7 +13277,9 @@ void Compiler::impImportBlockCode(BasicBlock* block)
#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);
@@ -13185,6 +13330,9 @@ void Compiler::impImportBlockCode(BasicBlock* block)
case CORINFO_FIELD_STATIC_RVA_ADDRESS:
case CORINFO_FIELD_STATIC_SHARED_STATIC_HELPER:
case CORINFO_FIELD_STATIC_GENERICS_STATIC_HELPER:
+#if COR_JIT_EE_VERSION > 460
+ case CORINFO_FIELD_STATIC_READYTORUN_HELPER:
+#endif
op1 = impImportStaticFieldAccess(&resolvedToken, (CORINFO_ACCESS_FLAGS)aflags, &fieldInfo,
lclTyp);
break;
@@ -13376,7 +13524,7 @@ void Compiler::impImportBlockCode(BasicBlock* block)
{
op1 = impReadyToRunHelperToTree(&resolvedToken, CORINFO_HELP_READYTORUN_NEWARR_1, TYP_REF,
gtNewArgList(op2));
- usingReadyToRunHelper = (op1 != NULL);
+ usingReadyToRunHelper = (op1 != nullptr);
if (!usingReadyToRunHelper)
{
@@ -13388,9 +13536,11 @@ void Compiler::impImportBlockCode(BasicBlock* block)
// 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()
+ op1 = impTokenToHandle(&resolvedToken, nullptr, TRUE /*mustRestoreHandle*/);
+ if (op1 == nullptr)
+ { // compDonotInline()
return;
+ }
}
}
@@ -13498,7 +13648,7 @@ void Compiler::impImportBlockCode(BasicBlock* block)
GenTreePtr opLookup =
impReadyToRunHelperToTree(&resolvedToken, CORINFO_HELP_READYTORUN_ISINSTANCEOF, TYP_REF,
gtNewArgList(op1));
- usingReadyToRunHelper = (opLookup != NULL);
+ usingReadyToRunHelper = (opLookup != nullptr);
op1 = (usingReadyToRunHelper ? opLookup : op1);
if (!usingReadyToRunHelper)
@@ -13510,9 +13660,11 @@ void Compiler::impImportBlockCode(BasicBlock* block)
// 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()
+ op2 = impTokenToHandle(&resolvedToken, nullptr, FALSE);
+ if (op2 == nullptr)
+ { // compDonotInline()
return;
+ }
}
}
@@ -14026,7 +14178,7 @@ void Compiler::impImportBlockCode(BasicBlock* block)
{
GenTreePtr opLookup = impReadyToRunHelperToTree(&resolvedToken, CORINFO_HELP_READYTORUN_CHKCAST,
TYP_REF, gtNewArgList(op1));
- usingReadyToRunHelper = (opLookup != NULL);
+ usingReadyToRunHelper = (opLookup != nullptr);
op1 = (usingReadyToRunHelper ? opLookup : op1);
if (!usingReadyToRunHelper)
@@ -14038,9 +14190,11 @@ void Compiler::impImportBlockCode(BasicBlock* block)
// 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()
+ op2 = impTokenToHandle(&resolvedToken, nullptr, FALSE);
+ if (op2 == nullptr)
+ { // compDonotInline()
return;
+ }
}
}
@@ -14075,20 +14229,6 @@ void Compiler::impImportBlockCode(BasicBlock* block)
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)
@@ -14714,6 +14854,10 @@ GenTreePtr Compiler::impAssignMultiRegTypeToVar(GenTreePtr op, CORINFO_CLASS_HAN
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);
+
+ // TODO-1stClassStructs: Handle constant propagation and CSE-ing of multireg returns.
+ ret->gtFlags |= GTF_DONT_CSE;
+
assert(IsMultiRegReturnedType(hClass));
// Mark the var so that fields are not promoted and stay together.
@@ -14852,7 +14996,8 @@ bool Compiler::impReturnInstruction(BasicBlock* block, int prefixFlags, OPCODE&
if (lvaInlineeReturnSpillTemp != BAD_VAR_NUM)
{
- assert(info.compRetNativeType != TYP_VOID && fgMoreThanOneReturnBlock());
+ assert(info.compRetNativeType != TYP_VOID &&
+ (fgMoreThanOneReturnBlock() || impInlineInfo->hasPinnedLocals));
// This is a bit of a workaround...
// If we are inlining a call that returns a struct, where the actual "native" return type is
@@ -14943,7 +15088,7 @@ bool Compiler::impReturnInstruction(BasicBlock* block, int prefixFlags, OPCODE&
// 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());
+ assert(fgMoreThanOneReturnBlock() || impInlineInfo->hasPinnedLocals);
impAssignTempGen(lvaInlineeReturnSpillTemp, op2, se.seTypeInfo.GetClassHandle(),
(unsigned)CHECK_SPILL_ALL);
@@ -16469,7 +16614,7 @@ void Compiler::impImport(BasicBlock* method)
// 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)
+ if (tiVerificationNeeded && opts.jitFlags->IsSet(JitFlags::JIT_FLAG_IMPORT_ONLY))
{
BADCODE("Basic block marked as not verifiable");
}
@@ -16989,18 +17134,10 @@ void Compiler::impInlineRecordArgInfo(InlineInfo* pInlineInfo,
#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)
+ if (curArgVal->gtFlags & GTF_ALL_EFFECT)
{
inlCurArgInfo->argHasGlobRef = (curArgVal->gtFlags & GTF_GLOB_REF) != 0;
- inlCurArgInfo->argHasSideEff = (curArgVal->gtFlags & GTF_SIDE_EFFECT) != 0;
+ inlCurArgInfo->argHasSideEff = (curArgVal->gtFlags & (GTF_ALL_EFFECT & ~GTF_GLOB_REF)) != 0;
}
if (curArgVal->gtOper == GT_LCL_VAR)
@@ -17251,6 +17388,7 @@ void Compiler::impInlineInitVars(InlineInfo* pInlineInfo)
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)))
{
@@ -17377,16 +17515,49 @@ void Compiler::impInlineInitVars(InlineInfo* pInlineInfo)
var_types type = (var_types)eeGetArgType(localsSig, &methInfo->locals, &isPinned);
lclVarInfo[i + argCnt].lclHasLdlocaOp = false;
+ lclVarInfo[i + argCnt].lclIsPinned = isPinned;
lclVarInfo[i + argCnt].lclTypeInfo = type;
if (isPinned)
{
- inlineResult->NoteFatal(InlineObservation::CALLEE_HAS_PINNED_LOCALS);
- return;
+ // Pinned locals may cause inlines to fail.
+ inlineResult->Note(InlineObservation::CALLEE_HAS_PINNED_LOCALS);
+ if (inlineResult->IsFailure())
+ {
+ return;
+ }
}
lclVarInfo[i + argCnt].lclVerTypeInfo = verParseArgSigToTypeInfo(&methInfo->locals, localsSig);
+ // If this local is a struct type with GC fields, inform the inliner. It may choose to bail
+ // out on the inline.
+ if (type == TYP_STRUCT)
+ {
+ CORINFO_CLASS_HANDLE lclHandle = lclVarInfo[i + argCnt].lclVerTypeInfo.GetClassHandle();
+ DWORD typeFlags = info.compCompHnd->getClassAttribs(lclHandle);
+ if ((typeFlags & CORINFO_FLG_CONTAINS_GC_PTR) != 0)
+ {
+ inlineResult->Note(InlineObservation::CALLEE_HAS_GC_STRUCT);
+ if (inlineResult->IsFailure())
+ {
+ return;
+ }
+
+ // Do further notification in the case where the call site is rare; some policies do
+ // not track the relative hotness of call sites for "always" inline cases.
+ if (pInlineInfo->iciBlock->isRunRarely())
+ {
+ inlineResult->Note(InlineObservation::CALLSITE_RARE_GC_STRUCT);
+ if (inlineResult->IsFailure())
+ {
+
+ return;
+ }
+ }
+ }
+ }
+
localsSig = info.compCompHnd->getArgNext(localsSig);
#ifdef FEATURE_SIMD
@@ -17431,6 +17602,28 @@ unsigned Compiler::impInlineFetchLocal(unsigned lclNum DEBUGARG(const char* reas
lvaTable[tmpNum].lvHasLdAddrOp = 1;
}
+ if (impInlineInfo->lclVarInfo[lclNum + impInlineInfo->argCnt].lclIsPinned)
+ {
+ lvaTable[tmpNum].lvPinned = 1;
+
+ if (!impInlineInfo->hasPinnedLocals)
+ {
+ // If the inlinee returns a value, use a spill temp
+ // for the return value to ensure that even in case
+ // where the return expression refers to one of the
+ // pinned locals, we can unpin the local right after
+ // the inlined method body.
+ if ((info.compRetNativeType != TYP_VOID) && (lvaInlineeReturnSpillTemp == BAD_VAR_NUM))
+ {
+ lvaInlineeReturnSpillTemp =
+ lvaGrabTemp(false DEBUGARG("Inline candidate pinned local return spill temp"));
+ lvaTable[lvaInlineeReturnSpillTemp].lvType = info.compRetNativeType;
+ }
+ }
+
+ impInlineInfo->hasPinnedLocals = true;
+ }
+
if (impInlineInfo->lclVarInfo[lclNum + impInlineInfo->argCnt].lclVerTypeInfo.IsStruct())
{
if (varTypeIsStruct(lclTyp))
@@ -17895,10 +18088,17 @@ void Compiler::impMarkInlineCandidate(GenTreePtr callNode,
bool Compiler::IsTargetIntrinsic(CorInfoIntrinsics intrinsicId)
{
-#if defined(_TARGET_AMD64_)
+#if defined(_TARGET_AMD64_) || (defined(_TARGET_X86_) && !defined(LEGACY_BACKEND))
switch (intrinsicId)
{
// Amd64 only has SSE2 instruction to directly compute sqrt/abs.
+ //
+ // TODO: Because the x86 backend only targets SSE for floating-point code,
+ // it does not treat Sine, Cosine, or Round as intrinsics (JIT32
+ // implemented those intrinsics as x87 instructions). If this poses
+ // a CQ problem, it may be necessary to change the implementation of
+ // the helper calls to decrease call overhead or switch back to the
+ // x87 instructions. This is tracked by #7097.
case CORINFO_INTRINSIC_Sqrt:
case CORINFO_INTRINSIC_Abs:
return true;