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.cpp1101
1 files changed, 781 insertions, 320 deletions
diff --git a/src/jit/importer.cpp b/src/jit/importer.cpp
index b1e0f487ef..54427ba4dd 100644
--- a/src/jit/importer.cpp
+++ b/src/jit/importer.cpp
@@ -350,6 +350,12 @@ StackEntry& Compiler::impStackTop(unsigned n)
return verCurrentState.esStack[verCurrentState.esStackDepth - n - 1];
}
+
+unsigned Compiler::impStackHeight()
+{
+ return verCurrentState.esStackDepth;
+}
+
/*****************************************************************************
* Some of the trees are spilled specially. While unspilling them, or
* making a copy, these need to be handled specially. The function
@@ -1232,13 +1238,13 @@ GenTreePtr Compiler::impAssignStructPtr(GenTreePtr destAddr,
}
else if (src->gtOper == GT_RET_EXPR)
{
- GenTreePtr call = src->gtRetExpr.gtInlineCandidate;
+ GenTreeCall* call = src->gtRetExpr.gtInlineCandidate->AsCall();
noway_assert(call->gtOper == GT_CALL);
- if (call->AsCall()->HasRetBufArg())
+ if (call->HasRetBufArg())
{
// insert the return value buffer into the argument list as first byref parameter
- call->gtCall.gtCallArgs = gtNewListNode(destAddr, call->gtCall.gtCallArgs);
+ call->gtCallArgs = gtNewListNode(destAddr, call->gtCallArgs);
// now returns void, not a struct
src->gtType = TYP_VOID;
@@ -1252,7 +1258,7 @@ GenTreePtr Compiler::impAssignStructPtr(GenTreePtr destAddr,
{
// Case of inline method returning a struct in one or more registers.
//
- var_types returnType = (var_types)call->gtCall.gtReturnType;
+ var_types returnType = (var_types)call->gtReturnType;
// We won't need a return buffer
asgType = returnType;
@@ -1842,7 +1848,7 @@ GenTreePtr Compiler::impReadyToRunLookupToTree(CORINFO_CONST_LOOKUP* pLookup,
return gtNewIconEmbHndNode(handle, pIndirection, handleFlags, 0, nullptr, compileTimeHandle);
}
-GenTreePtr Compiler::impReadyToRunHelperToTree(
+GenTreeCall* Compiler::impReadyToRunHelperToTree(
CORINFO_RESOLVED_TOKEN* pResolvedToken,
CorInfoHelpFunc helper,
var_types type,
@@ -1850,18 +1856,14 @@ GenTreePtr Compiler::impReadyToRunHelperToTree(
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 nullptr;
}
-#else
- info.compCompHnd->getReadyToRunHelper(pResolvedToken, helper, &lookup);
-#endif
- GenTreePtr op1 = gtNewHelperCallNode(helper, type, GTF_EXCEPT, args);
+ GenTreeCall* op1 = gtNewHelperCallNode(helper, type, GTF_EXCEPT, args);
- op1->gtCall.setEntryPoint(lookup);
+ op1->setEntryPoint(lookup);
return op1;
}
@@ -1879,9 +1881,7 @@ GenTreePtr Compiler::impMethodPointer(CORINFO_RESOLVED_TOKEN* pResolvedToken, CO
#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;
+ op1->gtFptrVal.gtEntryPoint = pCallInfo->codePointerLookup.constLookup;
}
else
{
@@ -1929,7 +1929,7 @@ GenTreePtr Compiler::getRuntimeContextTree(CORINFO_RUNTIME_LOOKUP_KIND kind)
// 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;
+ lvaGenericsContextUseCount++;
if (kind == CORINFO_LOOKUP_THISOBJ)
{
@@ -2179,9 +2179,12 @@ bool Compiler::impSpillStackEntry(unsigned level,
}
}
+ bool isNewTemp = false;
+
if (tnum == BAD_VAR_NUM)
{
- tnum = lvaGrabTemp(true DEBUGARG(reason));
+ tnum = lvaGrabTemp(true DEBUGARG(reason));
+ isNewTemp = true;
}
else if (tiVerificationNeeded && lvaTable[tnum].TypeGet() != TYP_UNDEF)
{
@@ -2211,6 +2214,13 @@ bool Compiler::impSpillStackEntry(unsigned level,
/* Assign the spilled entry to the temp */
impAssignTempGen(tnum, tree, verCurrentState.esStack[level].seTypeInfo.GetClassHandle(), level);
+ // If temp is newly introduced and a ref type, grab what type info we can.
+ if (isNewTemp && (lvaTable[tnum].lvType == TYP_REF))
+ {
+ CORINFO_CLASS_HANDLE stkHnd = verCurrentState.esStack[level].seTypeInfo.GetClassHandle();
+ lvaSetClass(tnum, tree, stkHnd);
+ }
+
// 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);
@@ -2584,6 +2594,21 @@ inline IL_OFFSETX Compiler::impCurILOffset(IL_OFFSET offs, bool callInstruction)
}
}
+//------------------------------------------------------------------------
+// impCanSpillNow: check is it possible to spill all values from eeStack to local variables.
+//
+// Arguments:
+// prevOpcode - last importer opcode
+//
+// Return Value:
+// true if it is legal, false if it could be a sequence that we do not want to divide.
+bool Compiler::impCanSpillNow(OPCODE prevOpcode)
+{
+ // Don't spill after ldtoken, because it could be a part of the InitializeArray sequence.
+ // Avoid breaking up to guarantee that impInitializeArrayIntrinsic can succeed.
+ return prevOpcode != CEE_LDTOKEN;
+}
+
/*****************************************************************************
*
* Remember the instr offset for the statements
@@ -2997,14 +3022,12 @@ GenTreePtr Compiler::impInitializeArrayIntrinsic(CORINFO_SIG_INFO* sig)
#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;
@@ -3278,13 +3301,9 @@ GenTreePtr Compiler::impIntrinsic(GenTreePtr newobjThis,
bool tailCall,
CorInfoIntrinsics* pIntrinsicID)
{
- bool mustExpand = false;
-#if COR_JIT_EE_VERSION > 460
+ bool mustExpand = false;
CorInfoIntrinsics intrinsicID = info.compCompHnd->getIntrinsicID(method, &mustExpand);
-#else
- CorInfoIntrinsics intrinsicID = info.compCompHnd->getIntrinsicID(method);
-#endif
- *pIntrinsicID = intrinsicID;
+ *pIntrinsicID = intrinsicID;
#ifndef _TARGET_ARM_
genTreeOps interlockedOperator;
@@ -3557,7 +3576,7 @@ GenTreePtr Compiler::impIntrinsic(GenTreePtr newobjThis,
case CORINFO_INTRINSIC_GetTypeFromHandle:
op1 = impStackTop(0).val;
if (op1->gtOper == GT_CALL && (op1->gtCall.gtCallType == CT_HELPER) &&
- gtIsTypeHandleToRuntimeTypeHelper(op1))
+ gtIsTypeHandleToRuntimeTypeHelper(op1->AsCall()))
{
op1 = impPopStack().val;
// Change call to return RuntimeType directly.
@@ -3570,7 +3589,7 @@ GenTreePtr Compiler::impIntrinsic(GenTreePtr newobjThis,
case CORINFO_INTRINSIC_RTH_GetValueInternal:
op1 = impStackTop(0).val;
if (op1->gtOper == GT_CALL && (op1->gtCall.gtCallType == CT_HELPER) &&
- gtIsTypeHandleToRuntimeTypeHelper(op1))
+ gtIsTypeHandleToRuntimeTypeHelper(op1->AsCall()))
{
// Old tree
// Helper-RuntimeTypeHandle -> TreeToGetNativeTypeHandle
@@ -4989,6 +5008,23 @@ GenTreePtr Compiler::impImportLdvirtftn(GenTreePtr thisPtr,
NO_WAY("Virtual call to a function added via EnC is not supported");
}
+ // CoreRT generic virtual method
+ if (((pCallInfo->sig.callConv & CORINFO_CALLCONV_GENERIC) != 0) && IsTargetAbi(CORINFO_CORERT_ABI))
+ {
+ GenTreePtr runtimeMethodHandle = nullptr;
+ if (pCallInfo->exactContextNeedsRuntimeLookup)
+ {
+ runtimeMethodHandle =
+ impRuntimeLookupToTree(pResolvedToken, &pCallInfo->codePointerLookup, pCallInfo->hMethod);
+ }
+ else
+ {
+ runtimeMethodHandle = gtNewIconEmbMethHndNode(pResolvedToken->hMethod);
+ }
+ return gtNewHelperCallNode(CORINFO_HELP_GVMLOOKUP_FOR_SLOT, TYP_I_IMPL, GTF_EXCEPT,
+ gtNewArgList(thisPtr, runtimeMethodHandle));
+ }
+
#ifdef FEATURE_READYTORUN_COMPILER
if (opts.IsReadyToRun())
{
@@ -5238,7 +5274,6 @@ void Compiler::impImportNewObjArray(CORINFO_RESOLVED_TOKEN* pResolvedToken, CORI
//
CLANG_FORMAT_COMMENT_ANCHOR;
-#if COR_JIT_EE_VERSION > 460
if (!opts.IsReadyToRun() || IsTargetAbi(CORINFO_CORERT_ABI))
{
LclVarDsc* newObjArrayArgsVar;
@@ -5298,7 +5333,6 @@ void Compiler::impImportNewObjArray(CORINFO_RESOLVED_TOKEN* pResolvedToken, CORI
node = gtNewHelperCallNode(CORINFO_HELP_NEW_MDARR_NONVARARG, TYP_REF, 0, args);
}
else
-#endif
{
//
// The varargs helper needs the type and method handles as last
@@ -5522,14 +5556,14 @@ bool Compiler::impCanPInvokeInlineCallSite(BasicBlock* block)
// 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)
+ GenTreeCall* call, CORINFO_METHOD_HANDLE methHnd, CORINFO_SIG_INFO* sig, unsigned mflags, BasicBlock* block)
{
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;
+ call->gtCallMoreFlags |= GTF_CALL_M_PINVOKE;
}
if (methHnd)
@@ -5554,7 +5588,7 @@ void Compiler::impCheckForPInvokeCall(
static_assert_no_msg((unsigned)CORINFO_CALLCONV_THISCALL == (unsigned)CORINFO_UNMANAGED_CALLCONV_THISCALL);
unmanagedCallConv = CorInfoUnmanagedCallConv(callConv);
- assert(!call->gtCall.gtCallCookie);
+ assert(!call->gtCallCookie);
}
if (unmanagedCallConv != CORINFO_UNMANAGED_CALLCONV_C && unmanagedCallConv != CORINFO_UNMANAGED_CALLCONV_STDCALL &&
@@ -5614,11 +5648,11 @@ void Compiler::impCheckForPInvokeCall(
if (unmanagedCallConv == CORINFO_UNMANAGED_CALLCONV_THISCALL)
{
- call->gtCall.gtCallMoreFlags |= GTF_CALL_M_UNMGD_THISCALL;
+ call->gtCallMoreFlags |= GTF_CALL_M_UNMGD_THISCALL;
}
}
-GenTreePtr Compiler::impImportIndirectCall(CORINFO_SIG_INFO* sig, IL_OFFSETX ilOffset)
+GenTreeCall* Compiler::impImportIndirectCall(CORINFO_SIG_INFO* sig, IL_OFFSETX ilOffset)
{
var_types callRetTyp = JITtype2varType(sig->retType);
@@ -5637,7 +5671,11 @@ GenTreePtr Compiler::impImportIndirectCall(CORINFO_SIG_INFO* sig, IL_OFFSETX ilO
/* Get the function pointer */
GenTreePtr fptr = impPopStack().val;
- assert(genActualType(fptr->gtType) == TYP_I_IMPL);
+
+ // The function pointer is typically a sized to match the target pointer size
+ // However, stubgen IL optimization can change LDC.I8 to LDC.I4
+ // See ILCodeStream::LowerOpcode
+ assert(genActualType(fptr->gtType) == TYP_I_IMPL || genActualType(fptr->gtType) == TYP_INT);
#ifdef DEBUG
// This temporary must never be converted to a double in stress mode,
@@ -5652,7 +5690,7 @@ GenTreePtr Compiler::impImportIndirectCall(CORINFO_SIG_INFO* sig, IL_OFFSETX ilO
/* Create the call node */
- GenTreePtr call = gtNewIndCallNode(fptr, callRetTyp, nullptr, ilOffset);
+ GenTreeCall* call = gtNewIndCallNode(fptr, callRetTyp, nullptr, ilOffset);
call->gtFlags |= GTF_EXCEPT | (fptr->gtFlags & GTF_GLOB_EFFECT);
@@ -5922,7 +5960,7 @@ GenTreePtr Compiler::impImportStaticFieldAccess(CORINFO_RESOLVED_TOKEN* pResolve
}
break;
}
-#if COR_JIT_EE_VERSION > 460
+
case CORINFO_FIELD_STATIC_READYTORUN_HELPER:
{
#ifdef FEATURE_READYTORUN_COMPILER
@@ -5951,7 +5989,7 @@ GenTreePtr Compiler::impImportStaticFieldAccess(CORINFO_RESOLVED_TOKEN* pResolve
#endif // FEATURE_READYTORUN_COMPILER
}
break;
-#endif // COR_JIT_EE_VERSION > 460
+
default:
{
if (!(access & CORINFO_ACCESS_ADDRESS))
@@ -6111,25 +6149,6 @@ void Compiler::impInsertHelperCall(CORINFO_HELPER_DESC* helperInfo)
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
@@ -6376,12 +6395,14 @@ var_types Compiler::impImportCall(OPCODE opcode,
GenTreeArgList* args = nullptr;
CORINFO_THIS_TRANSFORM constraintCallThisTransform = CORINFO_NO_THIS_TRANSFORM;
CORINFO_CONTEXT_HANDLE exactContextHnd = nullptr;
- BOOL exactContextNeedsRuntimeLookup = FALSE;
+ bool exactContextNeedsRuntimeLookup = false;
bool canTailCall = true;
const char* szCanTailCallFailReason = nullptr;
int tailCall = prefixFlags & PREFIX_TAILCALL;
bool readonlyCall = (prefixFlags & PREFIX_READONLY) != 0;
+ CORINFO_RESOLVED_TOKEN* ldftnToken = nullptr;
+
// 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.
@@ -6432,7 +6453,6 @@ var_types Compiler::impImportCall(OPCODE opcode,
eeGetSig(pResolvedToken->token, info.compScopeHnd, impTokenLookupContextHandle, &calliSig);
callRetTyp = JITtype2varType(calliSig.retType);
- clsHnd = calliSig.retTypeClass;
call = impImportIndirectCall(&calliSig, ilOffset);
@@ -6464,11 +6484,13 @@ var_types Compiler::impImportCall(OPCODE opcode,
if (IsTargetAbi(CORINFO_CORERT_ABI))
{
- bool managedCall = (calliSig.callConv & GTF_CALL_UNMANAGED) == 0;
+ bool managedCall = (((calliSig.callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_STDCALL) &&
+ ((calliSig.callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_C) &&
+ ((calliSig.callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_THISCALL) &&
+ ((calliSig.callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_FASTCALL));
if (managedCall)
{
- call->AsCall()->SetFatPointerCandidate();
- setMethodHasFatPointer();
+ addFatPointerCandidate(call->AsCall());
}
}
}
@@ -6519,7 +6541,7 @@ var_types Compiler::impImportCall(OPCODE opcode,
if (mflags & CORINFO_FLG_DONT_INLINE_CALLER)
{
- compInlineResult->NoteFatal(InlineObservation::CALLEE_HAS_NOINLINE_CALLEE);
+ compInlineResult->NoteFatal(InlineObservation::CALLEE_STACK_CRAWL_MARK);
return callRetTyp;
}
@@ -6632,10 +6654,9 @@ var_types Compiler::impImportCall(OPCODE opcode,
// Work out what sort of call we're making.
// Dispense with virtual calls implemented via LDVIRTFTN immediately.
- constraintCallThisTransform = callInfo->thisTransform;
-
+ constraintCallThisTransform = callInfo->thisTransform;
exactContextHnd = callInfo->contextHandle;
- exactContextNeedsRuntimeLookup = callInfo->exactContextNeedsRuntimeLookup;
+ exactContextNeedsRuntimeLookup = callInfo->exactContextNeedsRuntimeLookup == TRUE;
// Recursive call is treaded as a loop to the begining of the method.
if (methHnd == info.compMethodHnd)
@@ -6773,6 +6794,7 @@ var_types Compiler::impImportCall(OPCODE opcode,
nullptr DEBUGARG("LDVIRTFTN this pointer"));
GenTreePtr fptr = impImportLdvirtftn(thisPtr, pResolvedToken, callInfo);
+
if (compDonotInline())
{
return callRetTyp;
@@ -6792,6 +6814,11 @@ var_types Compiler::impImportCall(OPCODE opcode,
call->gtCall.gtCallObjp = thisPtrCopy;
call->gtFlags |= GTF_EXCEPT | (fptr->gtFlags & GTF_GLOB_EFFECT);
+ if (((sig->callConv & CORINFO_CALLCONV_GENERIC) != 0) && IsTargetAbi(CORINFO_CORERT_ABI))
+ {
+ // CoreRT generic virtual method: need to handle potential fat function pointers
+ addFatPointerCandidate(call->AsCall());
+ }
#ifdef FEATURE_READYTORUN_COMPILER
if (opts.IsReadyToRun())
{
@@ -6946,6 +6973,14 @@ var_types Compiler::impImportCall(OPCODE opcode,
}
#endif // !FEATURE_VARARG
+#ifdef UNIX_X86_ABI
+ if (call->gtCall.callSig == nullptr)
+ {
+ call->gtCall.callSig = new (this, CMK_CorSig) CORINFO_SIG_INFO;
+ *call->gtCall.callSig = *sig;
+ }
+#endif // UNIX_X86_ABI
+
if ((sig->callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_VARARG ||
(sig->callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_NATIVEVARARG)
{
@@ -7054,7 +7089,7 @@ var_types Compiler::impImportCall(OPCODE opcode,
{
// New lexical block here to avoid compilation errors because of GOTOs.
BasicBlock* block = compIsForInlining() ? impInlineInfo->iciBlock : compCurBB;
- impCheckForPInvokeCall(call, methHnd, sig, mflags, block);
+ impCheckForPInvokeCall(call->AsCall(), methHnd, sig, mflags, block);
}
if (call->gtFlags & GTF_CALL_UNMANAGED)
@@ -7279,6 +7314,21 @@ var_types Compiler::impImportCall(OPCODE opcode,
exactContextHnd = nullptr;
}
+ if ((opcode == CEE_NEWOBJ) && ((clsFlags & CORINFO_FLG_DELEGATE) != 0))
+ {
+ // Only verifiable cases are supported.
+ // dup; ldvirtftn; newobj; or ldftn; newobj.
+ // IL test could contain unverifiable sequence, in this case optimization should not be done.
+ if (impStackHeight() > 0)
+ {
+ typeInfo delegateTypeInfo = impStackTop().seTypeInfo;
+ if (delegateTypeInfo.IsToken())
+ {
+ ldftnToken = delegateTypeInfo.GetToken();
+ }
+ }
+ }
+
//-------------------------------------------------------------------------
// The main group of arguments
@@ -7315,8 +7365,10 @@ var_types Compiler::impImportCall(OPCODE opcode,
if ((call->gtFlags & GTF_CALL_VIRT_KIND_MASK) != GTF_CALL_NONVIRT)
{
/* only true object pointers can be virtual */
-
assert(obj->gtType == TYP_REF);
+
+ // See if we can devirtualize.
+ impDevirtualizeCall(call->AsCall(), obj, callInfo, &exactContextHnd);
}
else
{
@@ -7357,7 +7409,7 @@ var_types Compiler::impImportCall(OPCODE opcode,
{
// New inliner morph it in impImportCall.
// This will allow us to inline the call to the delegate constructor.
- call = fgOptimizeDelegateConstructor(call, &exactContextHnd);
+ call = fgOptimizeDelegateConstructor(call->AsCall(), &exactContextHnd, ldftnToken);
}
if (!bIntrinsicImported)
@@ -7371,7 +7423,7 @@ var_types Compiler::impImportCall(OPCODE opcode,
#endif // defined(DEBUG) || defined(INLINE_DATA)
// Is it an inline candidate?
- impMarkInlineCandidate(call, exactContextHnd, callInfo);
+ impMarkInlineCandidate(call, exactContextHnd, exactContextNeedsRuntimeLookup, callInfo);
}
// append the call node.
@@ -7595,7 +7647,7 @@ DONE:
#endif // defined(DEBUG) || defined(INLINE_DATA)
// Is it an inline candidate?
- impMarkInlineCandidate(call, exactContextHnd, callInfo);
+ impMarkInlineCandidate(call, exactContextHnd, exactContextNeedsRuntimeLookup, callInfo);
}
DONE_CALL:
@@ -7658,7 +7710,7 @@ DONE_CALL:
bool fatPointerCandidate = call->AsCall()->IsFatPointerCandidate();
if (varTypeIsStruct(callRetTyp))
{
- call = impFixupCallStructReturn(call, sig->retTypeClass);
+ call = impFixupCallStructReturn(call->AsCall(), sig->retTypeClass);
}
if ((call->gtFlags & GTF_CALL_INLINE_CANDIDATE) != 0)
@@ -7686,16 +7738,39 @@ DONE_CALL:
unsigned calliSlot = lvaGrabTemp(true DEBUGARG("calli"));
LclVarDsc* varDsc = &lvaTable[calliSlot];
varDsc->lvVerTypeInfo = tiRetVal;
- impAssignTempGen(calliSlot, call, clsHnd, (unsigned)CHECK_SPILL_NONE);
+ impAssignTempGen(calliSlot, call, tiRetVal.GetClassHandle(), (unsigned)CHECK_SPILL_NONE);
// impAssignTempGen can change src arg list and return type for call that returns struct.
var_types type = genActualType(lvaTable[calliSlot].TypeGet());
call = gtNewLclvNode(calliSlot, type);
}
}
+
// 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"));
+ //
+ // Suppress this for certain well-known call targets
+ // that we know won't modify locals, eg calls that are
+ // recognized in gtCanOptimizeTypeEquality. Otherwise
+ // we may break key fragile pattern matches later on.
+ bool spillStack = true;
+ if (call->IsCall())
+ {
+ GenTreeCall* callNode = call->AsCall();
+ if ((callNode->gtCallType == CT_HELPER) && gtIsTypeHandleToRuntimeTypeHelper(callNode))
+ {
+ spillStack = false;
+ }
+ else if ((callNode->gtCallMoreFlags & GTF_CALL_M_SPECIAL_INTRINSIC) != 0)
+ {
+ spillStack = false;
+ }
+ }
+
+ if (spillStack)
+ {
+ impSpillSideEffects(true, CHECK_SPILL_ALL DEBUGARG("non-inline candidate call"));
+ }
}
}
@@ -7820,33 +7895,29 @@ var_types Compiler::impImportJitTestLabelMark(int numArgs)
// Return Value:
// Returns new GenTree node after fixing struct return of call node
//
-GenTreePtr Compiler::impFixupCallStructReturn(GenTreePtr call, CORINFO_CLASS_HANDLE retClsHnd)
+GenTreePtr Compiler::impFixupCallStructReturn(GenTreeCall* call, CORINFO_CLASS_HANDLE retClsHnd)
{
- assert(call->gtOper == GT_CALL);
-
if (!varTypeIsStruct(call))
{
return call;
}
- call->gtCall.gtRetClsHnd = retClsHnd;
-
- GenTreeCall* callNode = call->AsCall();
+ call->gtRetClsHnd = retClsHnd;
#if FEATURE_MULTIREG_RET
// Initialize Return type descriptor of call node
- ReturnTypeDesc* retTypeDesc = callNode->GetReturnTypeDesc();
+ ReturnTypeDesc* retTypeDesc = call->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.");
+ assert(!call->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;
+ call->gtReturnType = call->gtType;
unsigned retRegCount = retTypeDesc->GetReturnRegCount();
if (retRegCount != 0)
@@ -7854,14 +7925,14 @@ GenTreePtr Compiler::impFixupCallStructReturn(GenTreePtr call, CORINFO_CLASS_HAN
if (retRegCount == 1)
{
// struct returned in a single register
- callNode->gtReturnType = retTypeDesc->GetReturnRegType(0);
+ call->gtReturnType = retTypeDesc->GetReturnRegType(0);
}
else
{
// must be a struct returned in two registers
assert(retRegCount == 2);
- if ((!callNode->CanTailCall()) && (!callNode->IsInlineCandidate()))
+ if ((!call->CanTailCall()) && (!call->IsInlineCandidate()))
{
// Force a call returning multi-reg struct to be always of the IR form
// tmp = call
@@ -7876,7 +7947,7 @@ GenTreePtr Compiler::impFixupCallStructReturn(GenTreePtr call, CORINFO_CLASS_HAN
else
{
// struct not returned in registers i.e returned via hiddden retbuf arg.
- callNode->gtCallMoreFlags |= GTF_CALL_M_RETBUFFARG;
+ call->gtCallMoreFlags |= GTF_CALL_M_RETBUFFARG;
}
#else // not FEATURE_UNIX_AMD64_STRUCT_PASSING
@@ -7885,15 +7956,15 @@ GenTreePtr Compiler::impFixupCallStructReturn(GenTreePtr call, CORINFO_CLASS_HAN
// 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->IsVarargs() && IsHfa(retClsHnd))
{
- if (call->gtCall.CanTailCall())
+ if (call->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;
+ call->gtCallMoreFlags &= ~GTF_CALL_M_EXPLICIT_TAILCALL;
}
else
{
@@ -7926,12 +7997,12 @@ GenTreePtr Compiler::impFixupCallStructReturn(GenTreePtr call, CORINFO_CLASS_HAN
if (howToReturnStruct == SPK_ByReference)
{
assert(returnType == TYP_UNKNOWN);
- call->gtCall.gtCallMoreFlags |= GTF_CALL_M_RETBUFFARG;
+ call->gtCallMoreFlags |= GTF_CALL_M_RETBUFFARG;
}
else
{
assert(returnType != TYP_UNKNOWN);
- call->gtCall.gtReturnType = returnType;
+ call->gtReturnType = returnType;
// ToDo: Refactor this common code sequence into its own method as it is used 4+ times
if ((returnType == TYP_LONG) && (compLongUsed == false))
@@ -7949,7 +8020,7 @@ GenTreePtr Compiler::impFixupCallStructReturn(GenTreePtr call, CORINFO_CLASS_HAN
if (retRegCount >= 2)
{
- if ((!callNode->CanTailCall()) && (!callNode->IsInlineCandidate()))
+ if ((!call->CanTailCall()) && (!call->IsInlineCandidate()))
{
// Force a call returning multi-reg struct to be always of the IR form
// tmp = call
@@ -9379,6 +9450,9 @@ GenTreePtr Compiler::impCastClassOrIsInstToTree(GenTreePtr op1,
// Make QMark node a top level node by spilling it.
unsigned tmp = lvaGrabTemp(true DEBUGARG("spilling QMark2"));
impAssignTempGen(tmp, qmarkNull, (unsigned)CHECK_SPILL_NONE);
+
+ // TODO: Is it possible op1 has a better type?
+ lvaSetClass(tmp, pResolvedToken->hClass);
return gtNewLclvNode(tmp, TYP_REF);
#endif
}
@@ -9458,7 +9532,6 @@ void Compiler::impImportBlockCode(BasicBlock* block)
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;
@@ -9500,7 +9573,7 @@ void Compiler::impImportBlockCode(BasicBlock* block)
/* 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)
+ if ((opcodeOffs - lastSpillOffs) > MAX_TREE_SIZE && impCanSpillNow(prevOpcode))
{
impSpillStackEnsure();
lastSpillOffs = opcodeOffs;
@@ -9637,6 +9710,7 @@ void Compiler::impImportBlockCode(BasicBlock* block)
GenTreeArgList* args = nullptr; // What good do these "DUMMY_INIT"s do?
GenTreePtr newObjThisPtr = DUMMY_INIT(NULL);
bool uns = DUMMY_INIT(false);
+ bool isLocal = false;
/* Get the next opcode and the size of its parameters */
@@ -9892,7 +9966,9 @@ void Compiler::impImportBlockCode(BasicBlock* block)
{
lclNum = lvaArg0Var;
}
- lvaTable[lclNum].lvArgWrite = 1;
+
+ // We should have seen this arg write in the prescan
+ assert(lvaTable[lclNum].lvHasILStoreOp);
if (tiVerificationNeeded)
{
@@ -9909,12 +9985,14 @@ void Compiler::impImportBlockCode(BasicBlock* block)
goto VAR_ST;
case CEE_STLOC:
- lclNum = getU2LittleEndian(codeAddr);
+ lclNum = getU2LittleEndian(codeAddr);
+ isLocal = true;
JITDUMP(" %u", lclNum);
goto LOC_ST;
case CEE_STLOC_S:
- lclNum = getU1LittleEndian(codeAddr);
+ lclNum = getU1LittleEndian(codeAddr);
+ isLocal = true;
JITDUMP(" %u", lclNum);
goto LOC_ST;
@@ -9922,7 +10000,8 @@ void Compiler::impImportBlockCode(BasicBlock* block)
case CEE_STLOC_1:
case CEE_STLOC_2:
case CEE_STLOC_3:
- lclNum = (opcode - CEE_STLOC_0);
+ isLocal = true;
+ lclNum = (opcode - CEE_STLOC_0);
assert(lclNum >= 0 && lclNum < 4);
LOC_ST:
@@ -10023,31 +10102,32 @@ void Compiler::impImportBlockCode(BasicBlock* block)
}
}
- /* Filter out simple assignments to itself */
-
- if (op1->gtOper == GT_LCL_VAR && lclNum == op1->gtLclVarCommon.gtLclNum)
+ // If this is a local and the local is a ref type, see
+ // if we can improve type information based on the
+ // value being assigned.
+ if (isLocal && (lclTyp == TYP_REF))
{
- 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;
+ // We should have seen a stloc in our IL prescan.
+ assert(lvaTable[lclNum].lvHasILStoreOp);
-#ifdef DEBUG
- if (tiVerificationNeeded)
- {
- assert(
- typeInfo::AreEquivalent(tiRetVal, NormaliseForStack(lvaTable[lclNum].lvVerTypeInfo)));
- }
-#endif
+ const bool isSingleILStoreLocal =
+ !lvaTable[lclNum].lvHasMultipleILStoreOp && !lvaTable[lclNum].lvHasLdAddrOp;
- op1 = nullptr;
- insertLdloc = false;
+ // Conservative check that there is just one
+ // definition that reaches this store.
+ const bool hasSingleReachingDef = (block->bbStackDepthOnEntry() == 0);
- impLoadVar(lclNum, opcodeOffs + sz + 1);
- break;
+ if (isSingleILStoreLocal && hasSingleReachingDef)
+ {
+ lvaUpdateClass(lclNum, op1, clsHnd);
}
- else if (opts.compDbgCode)
+ }
+
+ /* Filter out simple assignments to itself */
+
+ if (op1->gtOper == GT_LCL_VAR && lclNum == op1->gtLclVarCommon.gtLclNum)
+ {
+ if (opts.compDbgCode)
{
op1 = gtNewNothingNode();
goto SPILL_APPEND;
@@ -10104,26 +10184,6 @@ void Compiler::impImportBlockCode(BasicBlock* block)
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:
@@ -11566,22 +11626,6 @@ void Compiler::impImportBlockCode(BasicBlock* block)
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);
@@ -11941,48 +11985,30 @@ void Compiler::impImportBlockCode(BasicBlock* block)
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 the expression to dup is simple, just clone it.
+ // Otherwise spill it to a temp, and reload the temp
+ // twice.
+ op1 = impPopStack(tiRetVal);
- if (codeAddr < codeEndp)
+ if (!opts.compDbgCode && !op1->IsIntegralConst(0) && !op1->IsFPZero() && !op1->IsLocal())
{
- OPCODE nextOpcode = (OPCODE)getU1LittleEndian(codeAddr);
- if (impIsAnySTLOC(nextOpcode))
+ const unsigned tmpNum = lvaGrabTemp(true DEBUGARG("dup spill"));
+ impAssignTempGen(tmpNum, op1, tiRetVal.GetClassHandle(), (unsigned)CHECK_SPILL_ALL);
+ var_types type = genActualType(lvaTable[tmpNum].TypeGet());
+ op1 = gtNewLclvNode(tmpNum, type);
+
+ // Propagate type info to the temp
+ if (type == TYP_REF)
{
- if (!opts.compDbgCode)
- {
- insertLdloc = true;
- break;
- }
- GenTree* stackTop = impStackTop().val;
- if (!stackTop->IsIntegralConst(0) && !stackTop->IsFPZero() && !stackTop->IsLocal())
- {
- insertLdloc = true;
- break;
- }
+ lvaSetClass(tmpNum, op1, tiRetVal.GetClassHandle());
}
}
- /* 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;
@@ -12290,7 +12316,8 @@ void Compiler::impImportBlockCode(BasicBlock* block)
return;
}
- impPushOnStack(op1, typeInfo(resolvedToken.hMethod));
+ CORINFO_RESOLVED_TOKEN* heapToken = impAllocateToken(resolvedToken);
+ impPushOnStack(op1, typeInfo(heapToken));
break;
}
@@ -12395,7 +12422,10 @@ void Compiler::impImportBlockCode(BasicBlock* block)
return;
}
- impPushOnStack(fptr, typeInfo(resolvedToken.hMethod));
+ CORINFO_RESOLVED_TOKEN* heapToken = impAllocateToken(resolvedToken);
+ assert(heapToken->tokenType == CORINFO_TOKENKIND_Method);
+ heapToken->tokenType = CORINFO_TOKENKIND_Ldvirtftn;
+ impPushOnStack(fptr, typeInfo(heapToken));
break;
}
@@ -12465,11 +12495,7 @@ void Compiler::impImportBlockCode(BasicBlock* block)
/* 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)),
@@ -12673,6 +12699,7 @@ void Compiler::impImportBlockCode(BasicBlock* block)
// without exhaustive walk over all expressions.
impAssignTempGen(lclNum, op1, (unsigned)CHECK_SPILL_NONE);
+ lvaSetClass(lclNum, resolvedToken.hClass, true /* is Exact */);
newObjThisPtr = gtNewLclvNode(lclNum, TYP_REF);
}
@@ -12770,11 +12797,31 @@ void Compiler::impImportBlockCode(BasicBlock* block)
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))
+ // This is split up to avoid goto flow warnings.
+ bool isRecursive;
+ isRecursive = !compIsForInlining() && (callInfo.hMethod == info.compMethodHnd);
+
+ // 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.
+ if (impIsImplicitTailCallCandidate(opcode, codeAddr + sz, codeEndp, prefixFlags, isRecursive))
+ {
+ if (compIsForInlining())
+ {
+#if FEATURE_TAILCALL_OPT_SHARED_RETURN
+ // Are we inlining at an implicit tail call site? If so the we can flag
+ // implicit tail call sites in the inline body. These call sites
+ // often end up in non BBJ_RETURN blocks, so only flag them when
+ // we're able to handle shared returns.
+ if (impInlineInfo->iciCall->IsImplicitTailCall())
+ {
+ JITDUMP(" (Inline Implicit Tail call: prefixFlags |= PREFIX_TAILCALL_IMPLICIT)");
+ prefixFlags |= PREFIX_TAILCALL_IMPLICIT;
+ }
+#endif // FEATURE_TAILCALL_OPT_SHARED_RETURN
+ }
+ else
{
JITDUMP(" (Implicit Tail call: prefixFlags |= PREFIX_TAILCALL_IMPLICIT)");
prefixFlags |= PREFIX_TAILCALL_IMPLICIT;
@@ -12793,7 +12840,7 @@ void Compiler::impImportBlockCode(BasicBlock* block)
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
@@ -12838,14 +12885,6 @@ void Compiler::impImportBlockCode(BasicBlock* block)
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,
@@ -12932,9 +12971,7 @@ 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);
@@ -13165,9 +13202,7 @@ 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;
@@ -13191,6 +13226,18 @@ void Compiler::impImportBlockCode(BasicBlock* block)
}
break;
+ case CORINFO_FIELD_INTRINSIC_ISLITTLEENDIAN:
+ {
+ assert(aflags & CORINFO_ACCESS_GET);
+#if BIGENDIAN
+ op1 = gtNewIconNode(0, lclTyp);
+#else
+ op1 = gtNewIconNode(1, lclTyp);
+#endif
+ goto FIELD_DONE;
+ }
+ break;
+
default:
assert(!"Unexpected fieldAccessor");
}
@@ -13311,10 +13358,7 @@ 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_STFLD_NEEDS_HELPER);
@@ -13433,9 +13477,7 @@ 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;
@@ -13611,9 +13653,10 @@ void Compiler::impImportBlockCode(BasicBlock* block)
Verify(elemTypeHnd == nullptr ||
!(info.compCompHnd->getClassAttribs(elemTypeHnd) & CORINFO_FLG_CONTAINS_STACK_PTR),
"array of byref-like type");
- tiRetVal = verMakeTypeInfo(resolvedToken.hClass);
}
+ tiRetVal = verMakeTypeInfo(resolvedToken.hClass);
+
accessAllowedResult =
info.compCompHnd->canAccessClass(&resolvedToken, info.compMethodHnd, &calloutHelper);
impHandleAccessAllowed(accessAllowedResult, &calloutHelper);
@@ -13748,7 +13791,7 @@ void Compiler::impImportBlockCode(BasicBlock* block)
#ifdef FEATURE_READYTORUN_COMPILER
if (opts.IsReadyToRun())
{
- GenTreePtr opLookup =
+ GenTreeCall* opLookup =
impReadyToRunHelperToTree(&resolvedToken, CORINFO_HELP_READYTORUN_ISINSTANCEOF, TYP_REF,
gtNewArgList(op1));
usingReadyToRunHelper = (opLookup != nullptr);
@@ -14279,8 +14322,8 @@ void Compiler::impImportBlockCode(BasicBlock* block)
#ifdef FEATURE_READYTORUN_COMPILER
if (opts.IsReadyToRun())
{
- GenTreePtr opLookup = impReadyToRunHelperToTree(&resolvedToken, CORINFO_HELP_READYTORUN_CHKCAST,
- TYP_REF, gtNewArgList(op1));
+ GenTreeCall* opLookup = impReadyToRunHelperToTree(&resolvedToken, CORINFO_HELP_READYTORUN_CHKCAST,
+ TYP_REF, gtNewArgList(op1));
usingReadyToRunHelper = (opLookup != nullptr);
op1 = (usingReadyToRunHelper ? opLookup : op1);
@@ -14782,11 +14825,8 @@ void Compiler::impImportBlockCode(BasicBlock* block)
prevOpcode = opcode;
prefixFlags = 0;
- assert(!insertLdloc || opcode == CEE_DUP);
}
- assert(!insertLdloc);
-
return;
#undef _impResolveToken
}
@@ -14994,6 +15034,16 @@ bool Compiler::impReturnInstruction(BasicBlock* block, int prefixFlags, OPCODE&
Verify(verCurrentState.esStackDepth == expectedStack, "stack non-empty on return");
}
+#ifdef DEBUG
+ // If we are importing an inlinee and have GC ref locals we always
+ // need to have a spill temp for the return value. This temp
+ // should have been set up in advance, over in fgFindBasicBlocks.
+ if (compIsForInlining() && impInlineInfo->HasGcRefLocals() && (info.compRetType != TYP_VOID))
+ {
+ assert(lvaInlineeReturnSpillTemp != BAD_VAR_NUM);
+ }
+#endif // DEBUG
+
GenTree* op2 = nullptr;
GenTree* op1 = nullptr;
CORINFO_CLASS_HANDLE retClsHnd = nullptr;
@@ -15100,7 +15150,7 @@ bool Compiler::impReturnInstruction(BasicBlock* block, int prefixFlags, OPCODE&
if (lvaInlineeReturnSpillTemp != BAD_VAR_NUM)
{
assert(info.compRetNativeType != TYP_VOID &&
- (fgMoreThanOneReturnBlock() || impInlineInfo->hasPinnedLocals));
+ (fgMoreThanOneReturnBlock() || impInlineInfo->HasGcRefLocals()));
// This is a bit of a workaround...
// If we are inlining a call that returns a struct, where the actual "native" return type is
@@ -15181,8 +15231,7 @@ bool Compiler::impReturnInstruction(BasicBlock* block, int prefixFlags, OPCODE&
// 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);
+ GenTreeCall* iciCall = impInlineInfo->iciCall->AsCall();
// Assign the inlinee return into a spill temp.
// spill temp only exists if there are multiple return points
@@ -15191,7 +15240,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() || impInlineInfo->hasPinnedLocals);
+ assert(fgMoreThanOneReturnBlock() || impInlineInfo->HasGcRefLocals());
impAssignTempGen(lvaInlineeReturnSpillTemp, op2, se.seTypeInfo.GetClassHandle(),
(unsigned)CHECK_SPILL_ALL);
@@ -15246,7 +15295,7 @@ bool Compiler::impReturnInstruction(BasicBlock* block, int prefixFlags, OPCODE&
if (retRegCount != 0)
{
- assert(!iciCall->AsCall()->HasRetBufArg());
+ assert(!iciCall->HasRetBufArg());
assert(retRegCount >= 2);
if (lvaInlineeReturnSpillTemp != BAD_VAR_NUM)
{
@@ -15265,8 +15314,8 @@ bool Compiler::impReturnInstruction(BasicBlock* block, int prefixFlags, OPCODE&
else
#endif // defined(_TARGET_ARM64_)
{
- assert(iciCall->AsCall()->HasRetBufArg());
- GenTreePtr dest = gtCloneExpr(iciCall->gtCall.gtCallArgs->gtOp.gtOp1);
+ assert(iciCall->HasRetBufArg());
+ GenTreePtr dest = gtCloneExpr(iciCall->gtCallArgs->gtOp.gtOp1);
// spill temp only exists if there are multiple return points
if (lvaInlineeReturnSpillTemp != BAD_VAR_NUM)
{
@@ -15946,11 +15995,11 @@ SPILLSTACK:
}
else
{
- assert(addTree->gtOper == GT_SWITCH && genActualType(addTree->gtOp.gtOp1->gtType) == TYP_I_IMPL);
+ assert(addTree->gtOper == GT_SWITCH && genActualTypeIsIntOrI(addTree->gtOp.gtOp1->TypeGet()));
unsigned temp = lvaGrabTemp(true DEBUGARG("spill addStmt SWITCH"));
impAssignTempGen(temp, addTree->gtOp.gtOp1, level);
- addTree->gtOp.gtOp1 = gtNewLclvNode(temp, TYP_I_IMPL);
+ addTree->gtOp.gtOp1 = gtNewLclvNode(temp, genActualType(addTree->gtOp.gtOp1->TypeGet()));
}
}
@@ -16921,7 +16970,7 @@ void Compiler::impMakeDiscretionaryInlineObservations(InlineInfo* pInlineInfo, I
{
frequency = InlineCallsiteFrequency::LOOP;
}
- else if ((pInlineInfo->iciBlock->bbFlags & BBF_PROF_WEIGHT) && (pInlineInfo->iciBlock->bbWeight > BB_ZERO_WEIGHT))
+ else if (pInlineInfo->iciBlock->hasProfileWeight() && (pInlineInfo->iciBlock->bbWeight > BB_ZERO_WEIGHT))
{
frequency = InlineCallsiteFrequency::WARM;
}
@@ -17378,7 +17427,8 @@ void Compiler::impInlineInitVars(InlineInfo* pInlineInfo)
// Ignore the type context argument
if (hasTypeCtxtArg && (argCnt == typeCtxtArg))
{
- typeCtxtArg = 0xFFFFFFFF;
+ pInlineInfo->typeContextArg = typeCtxtArg;
+ typeCtxtArg = 0xFFFFFFFF;
continue;
}
@@ -17621,6 +17671,11 @@ void Compiler::impInlineInitVars(InlineInfo* pInlineInfo)
lclVarInfo[i + argCnt].lclIsPinned = isPinned;
lclVarInfo[i + argCnt].lclTypeInfo = type;
+ if (varTypeIsGC(type))
+ {
+ pInlineInfo->numberOfGcRefLocals++;
+ }
+
if (isPinned)
{
// Pinned locals may cause inlines to fail.
@@ -17685,6 +17740,23 @@ void Compiler::impInlineInitVars(InlineInfo* pInlineInfo)
#endif // FEATURE_SIMD
}
+//------------------------------------------------------------------------
+// impInlineFetchLocal: get a local var that represents an inlinee local
+//
+// Arguments:
+// lclNum -- number of the inlinee local
+// reason -- debug string describing purpose of the local var
+//
+// Returns:
+// Number of the local to use
+//
+// Notes:
+// This method is invoked only for locals actually used in the
+// inlinee body.
+//
+// Allocates a new temp if necessary, and copies key properties
+// over from the inlinee local var info.
+
unsigned Compiler::impInlineFetchLocal(unsigned lclNum DEBUGARG(const char* reason))
{
assert(compIsForInlining());
@@ -17693,107 +17765,144 @@ unsigned Compiler::impInlineFetchLocal(unsigned lclNum DEBUGARG(const char* reas
if (tmpNum == BAD_VAR_NUM)
{
- var_types lclTyp = impInlineInfo->lclVarInfo[lclNum + impInlineInfo->argCnt].lclTypeInfo;
+ const InlLclVarInfo& inlineeLocal = impInlineInfo->lclVarInfo[lclNum + impInlineInfo->argCnt];
+ const var_types lclTyp = inlineeLocal.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;
- }
+ // Copy over key info
+ lvaTable[tmpNum].lvType = lclTyp;
+ lvaTable[tmpNum].lvHasLdAddrOp = inlineeLocal.lclHasLdlocaOp;
+ lvaTable[tmpNum].lvPinned = inlineeLocal.lclIsPinned;
+ lvaTable[tmpNum].lvHasILStoreOp = inlineeLocal.lclHasStlocOp;
+ lvaTable[tmpNum].lvHasMultipleILStoreOp = inlineeLocal.lclHasMultipleStlocOp;
- if (impInlineInfo->lclVarInfo[lclNum + impInlineInfo->argCnt].lclIsPinned)
+ // Copy over class handle for ref types. Note this may be a
+ // shared type -- someday perhaps we can get the exact
+ // signature and pass in a more precise type.
+ if (lclTyp == TYP_REF)
{
- 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;
+ lvaSetClass(tmpNum, inlineeLocal.lclVerTypeInfo.GetClassHandleForObjRef());
}
- if (impInlineInfo->lclVarInfo[lclNum + impInlineInfo->argCnt].lclVerTypeInfo.IsStruct())
+ if (inlineeLocal.lclVerTypeInfo.IsStruct())
{
if (varTypeIsStruct(lclTyp))
{
- lvaSetStruct(tmpNum,
- impInlineInfo->lclVarInfo[lclNum + impInlineInfo->argCnt].lclVerTypeInfo.GetClassHandle(),
- true /* unsafe value cls check */);
+ lvaSetStruct(tmpNum, inlineeLocal.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;
+ lvaTable[tmpNum].lvVerTypeInfo = inlineeLocal.lclVerTypeInfo;
}
}
+
+#ifdef DEBUG
+ // Sanity check that we're properly prepared for gc ref locals.
+ if (varTypeIsGC(lclTyp))
+ {
+ // Since there are gc locals we should have seen them earlier
+ // and if there was a return value, set up the spill temp.
+ assert(impInlineInfo->HasGcRefLocals());
+ assert((info.compRetNativeType == TYP_VOID) || (lvaInlineeReturnSpillTemp != BAD_VAR_NUM));
+ }
+ else
+ {
+ // Make sure all pinned locals count as gc refs.
+ assert(!inlineeLocal.lclIsPinned);
+ }
+#endif // DEBUG
}
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. !!!!
+//------------------------------------------------------------------------
+// impInlineFetchArg: return tree node for argument value in an inlinee
+//
+// Arguments:
+// lclNum -- argument number in inlinee IL
+// inlArgInfo -- argument info for inlinee
+// lclVarInfo -- var info for inlinee
+//
+// Returns:
+// Tree for the argument's value. Often an inlinee-scoped temp
+// GT_LCL_VAR but can be other tree kinds, if the argument
+// expression from the caller can be directly substituted into the
+// inlinee body.
+//
+// Notes:
+// Must be used only for arguments -- use impInlineFetchLocal for
+// inlinee locals.
+//
+// Direct substitution is performed when the formal argument cannot
+// change value in the inlinee body (no starg or ldarga), and the
+// actual argument expression's value cannot be changed if it is
+// substituted it into the inlinee body.
+//
+// Even if an inlinee-scoped temp is returned here, it may later be
+// "bashed" to a caller-supplied tree when arguments are actually
+// passed (see fgInlinePrependStatements). Bashing can happen if
+// the argument ends up being single use and other conditions are
+// met. So the contents of the tree returned here may not end up
+// being the ones ultimately used for the argument.
+//
+// This method will side effect inlArgInfo. It should only be called
+// for actual uses of the argument in the inlinee.
GenTreePtr Compiler::impInlineFetchArg(unsigned lclNum, InlArgInfo* inlArgInfo, InlLclVarInfo* lclVarInfo)
{
- /* Get the argument type */
- var_types lclTyp = lclVarInfo[lclNum].lclTypeInfo;
+ // Cache the relevant arg and lcl info for this argument.
+ // We will modify argInfo but not lclVarInfo.
+ InlArgInfo& argInfo = inlArgInfo[lclNum];
+ const InlLclVarInfo& lclInfo = lclVarInfo[lclNum];
+ const bool argCanBeModified = argInfo.argHasLdargaOp || argInfo.argHasStargOp;
+ const var_types lclTyp = lclInfo.lclTypeInfo;
+ GenTreePtr op1 = nullptr;
- GenTreePtr op1 = nullptr;
-
- // constant or address of local
- if (inlArgInfo[lclNum].argIsInvariant && !inlArgInfo[lclNum].argHasLdargaOp && !inlArgInfo[lclNum].argHasStargOp)
+ if (argInfo.argIsInvariant && !argCanBeModified)
{
- /* 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);
+ // Directly substitute constants or addresses of locals
+ //
+ // Clone the constant. Note that we cannot directly use
+ // argNode in the trees even if !argInfo.argIsUsed 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(argInfo.argNode);
PREFIX_ASSUME(op1 != nullptr);
- inlArgInfo[lclNum].argTmpNum = (unsigned)-1; // illegal temp
+ argInfo.argTmpNum = BAD_VAR_NUM;
}
- else if (inlArgInfo[lclNum].argIsLclVar && !inlArgInfo[lclNum].argHasLdargaOp && !inlArgInfo[lclNum].argHasStargOp)
+ else if (argInfo.argIsLclVar && !argCanBeModified)
{
- /* 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;
+ // Directly substitute caller locals
+ //
+ // Use the caller-supplied node if this is the first use.
+ op1 = argInfo.argNode;
+ argInfo.argTmpNum = op1->gtLclVarCommon.gtLclNum;
- if (inlArgInfo[lclNum].argIsUsed)
+ // Use an equivalent copy if this is the second or subsequent use.
+ if (argInfo.argIsUsed)
{
assert(op1->gtOper == GT_LCL_VAR);
assert(lclNum == op1->gtLclVar.gtLclILoffs);
+ var_types newTyp = lclTyp;
+
if (!lvaTable[op1->gtLclVarCommon.gtLclNum].lvNormalizeOnLoad())
{
- lclTyp = genActualType(lclTyp);
+ newTyp = genActualType(lclTyp);
}
- /* Create a new lcl var node - remember the argument lclNum */
- op1 = gtNewLclvNode(op1->gtLclVarCommon.gtLclNum, lclTyp, op1->gtLclVar.gtLclILoffs);
+ // Create a new lcl var node - remember the argument lclNum
+ op1 = gtNewLclvNode(op1->gtLclVarCommon.gtLclNum, newTyp, op1->gtLclVar.gtLclILoffs);
}
}
- else if (inlArgInfo[lclNum].argIsByRefToStructLocal && !inlArgInfo[lclNum].argHasStargOp)
+ else if (argInfo.argIsByRefToStructLocal && !argInfo.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.
@@ -17812,59 +17921,65 @@ GenTreePtr Compiler::impInlineFetchArg(unsigned lclNum, InlArgInfo* inlArgInfo,
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);
+ assert(argInfo.argNode->TypeGet() == TYP_BYREF || argInfo.argNode->TypeGet() == TYP_I_IMPL);
+ op1 = gtCloneExpr(argInfo.argNode);
}
else
{
/* Argument is a complex expression - it must be evaluated into a temp */
- if (inlArgInfo[lclNum].argHasTmp)
+ if (argInfo.argHasTmp)
{
- assert(inlArgInfo[lclNum].argIsUsed);
- assert(inlArgInfo[lclNum].argTmpNum < lvaCount);
+ assert(argInfo.argIsUsed);
+ assert(argInfo.argTmpNum < lvaCount);
/* Create a new lcl var node - remember the argument lclNum */
- op1 = gtNewLclvNode(inlArgInfo[lclNum].argTmpNum, genActualType(lclTyp));
+ op1 = gtNewLclvNode(argInfo.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;
+ argInfo.argBashTmpNode = nullptr;
}
else
{
/* First time use */
- assert(inlArgInfo[lclNum].argIsUsed == false);
+ assert(!argInfo.argIsUsed);
/* Reserve a temp for the expression.
* Use a large size node as we may change it later */
- unsigned tmpNum = lvaGrabTemp(true DEBUGARG("Inlining Arg"));
+ const unsigned tmpNum = lvaGrabTemp(true DEBUGARG("Inlining Arg"));
lvaTable[tmpNum].lvType = lclTyp;
+
+ // Copy over class handle for ref types. Note this may be
+ // further improved if it is a shared type and we know the exact context.
+ if (lclTyp == TYP_REF)
+ {
+ lvaSetClass(tmpNum, lclInfo.lclVerTypeInfo.GetClassHandleForObjRef());
+ }
+
assert(lvaTable[tmpNum].lvAddrExposed == 0);
- if (inlArgInfo[lclNum].argHasLdargaOp)
+ if (argInfo.argHasLdargaOp)
{
lvaTable[tmpNum].lvHasLdAddrOp = 1;
}
- if (lclVarInfo[lclNum].lclVerTypeInfo.IsStruct())
+ if (lclInfo.lclVerTypeInfo.IsStruct())
{
if (varTypeIsStruct(lclTyp))
{
- lvaSetStruct(tmpNum, impInlineInfo->lclVarInfo[lclNum].lclVerTypeInfo.GetClassHandle(),
- true /* unsafe value cls check */);
+ lvaSetStruct(tmpNum, lclInfo.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;
+ lvaTable[tmpNum].lvVerTypeInfo = lclInfo.lclVerTypeInfo;
}
}
- inlArgInfo[lclNum].argHasTmp = true;
- inlArgInfo[lclNum].argTmpNum = tmpNum;
+ argInfo.argHasTmp = true;
+ argInfo.argTmpNum = tmpNum;
// If we require strict exception order, then arguments must
// be evaluated in sequence before the body of the inlined method.
@@ -17875,7 +17990,7 @@ GenTreePtr Compiler::impInlineFetchArg(unsigned lclNum, InlArgInfo* inlArgInfo,
// 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))
+ if (!varTypeIsStruct(lclTyp) && !argInfo.argHasSideEff && !argInfo.argHasGlobRef)
{
/* Get a *LARGE* LCL_VAR node */
op1 = gtNewLclLNode(tmpNum, genActualType(lclTyp), lclNum);
@@ -17884,21 +17999,20 @@ GenTreePtr Compiler::impInlineFetchArg(unsigned lclNum, InlArgInfo* inlArgInfo,
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;
+ argInfo.argBashTmpNode = op1;
}
else
{
/* Get a small LCL_VAR node */
op1 = gtNewLclvNode(tmpNum, genActualType(lclTyp));
/* No bashing of this argument */
- inlArgInfo[lclNum].argBashTmpNode = nullptr;
+ argInfo.argBashTmpNode = nullptr;
}
}
}
- /* Mark the argument as used */
-
- inlArgInfo[lclNum].argIsUsed = true;
+ // Mark this argument as used.
+ argInfo.argIsUsed = true;
return op1;
}
@@ -17977,16 +18091,28 @@ BOOL Compiler::impInlineIsGuaranteedThisDerefBeforeAnySideEffects(GenTreePtr ad
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
+//------------------------------------------------------------------------
+// impMarkInlineCandidate: determine if this call can be subsequently inlined
+//
+// Arguments:
+// callNode -- call under scrutiny
+// exactContextHnd -- context handle for inlining
+// exactContextNeedsRuntimeLookup -- true if context required runtime lookup
+// callInfo -- call info from VM
+//
+// Notes:
+// If callNode is an inline candidate, this method sets the flag
+// GTF_CALL_INLINE_CANDIDATE, and ensures that helper methods have
+// filled in the associated InlineCandidateInfo.
+//
+// If callNode is not an inline candidate, and the reason is
+// something that is inherent to the method being called, the
+// method may be marked as "noinline" to short-circuit any
+// future assessments of calls to this method.
void Compiler::impMarkInlineCandidate(GenTreePtr callNode,
CORINFO_CONTEXT_HANDLE exactContextHnd,
+ bool exactContextNeedsRuntimeLookup,
CORINFO_CALL_INFO* callInfo)
{
// Let the strategy know there's another call
@@ -18172,6 +18298,10 @@ void Compiler::impMarkInlineCandidate(GenTreePtr callNode,
// The old value should be NULL
assert(call->gtInlineCandidateInfo == nullptr);
+ // The new value should not be NULL.
+ assert(inlineCandidateInfo != nullptr);
+ inlineCandidateInfo->exactContextNeedsRuntimeLookup = exactContextNeedsRuntimeLookup;
+
call->gtInlineCandidateInfo = inlineCandidateInfo;
// Mark the call node as inline candidate.
@@ -18297,4 +18427,335 @@ bool Compiler::IsMathIntrinsic(GenTreePtr tree)
{
return (tree->OperGet() == GT_INTRINSIC) && IsMathIntrinsic(tree->gtIntrinsic.gtIntrinsicId);
}
-/*****************************************************************************/
+
+//------------------------------------------------------------------------
+// impDevirtualizeCall: Attempt to change a virtual vtable call into a
+// normal call
+//
+// Arguments:
+// call -- the call node to examine/modify
+// thisObj -- the value of 'this' for the call
+// callInfo -- [IN/OUT] info about the call from the VM
+// exactContextHnd -- [OUT] updated context handle iff call devirtualized
+//
+// Notes:
+// Virtual calls in IL will always "invoke" the base class method.
+//
+// This transformation looks for evidence that the type of 'this'
+// in the call is exactly known, is a final class or would invoke
+// a final method, and if that and other safety checks pan out,
+// modifies the call and the call info to create a direct call.
+//
+// This transformation is initially done in the importer and not
+// in some subsequent optimization pass because we want it to be
+// upstream of inline candidate identification.
+//
+// However, later phases may supply improved type information that
+// can enable further devirtualization. We currently reinvoke this
+// code after inlining, if the return value of the inlined call is
+// the 'this obj' of a subsequent virtual call.
+//
+void Compiler::impDevirtualizeCall(GenTreeCall* call,
+ GenTreePtr thisObj,
+ CORINFO_CALL_INFO* callInfo,
+ CORINFO_CONTEXT_HANDLE* exactContextHandle)
+{
+ // This should be a virtual vtable or virtual stub call.
+ assert(call->IsVirtual());
+
+ // Bail if not optimizing
+ if (opts.MinOpts())
+ {
+ return;
+ }
+
+ // Bail if debuggable codegen
+ if (opts.compDbgCode)
+ {
+ return;
+ }
+
+#if defined(DEBUG)
+ // Bail if devirt is disabled.
+ if (JitConfig.JitEnableDevirtualization() == 0)
+ {
+ return;
+ }
+
+ const bool doPrint = JitConfig.JitPrintDevirtualizedMethods() == 1;
+#endif // DEBUG
+
+ // Fetch information about the virtual method we're calling.
+ CORINFO_METHOD_HANDLE baseMethod = callInfo->hMethod;
+ unsigned baseMethodAttribs = callInfo->methodFlags;
+
+ if (baseMethodAttribs == 0)
+ {
+ // For late devirt we may not have method attributes, so fetch them.
+ baseMethodAttribs = info.compCompHnd->getMethodAttribs(baseMethod);
+ }
+ else
+ {
+#if defined(DEBUG)
+ // Validate that callInfo has up to date method flags
+ const DWORD freshBaseMethodAttribs = info.compCompHnd->getMethodAttribs(baseMethod);
+ assert(freshBaseMethodAttribs == baseMethodAttribs);
+#endif // DEBUG
+ }
+
+ // In R2R mode, we might see virtual stub calls to
+ // non-virtuals. For instance cases where the non-virtual method
+ // is in a different assembly but is called via CALLVIRT. For
+ // verison resilience we must allow for the fact that the method
+ // might become virtual in some update.
+ //
+ // In non-R2R modes CALLVIRT <nonvirtual> will be turned into a
+ // regular call+nullcheck upstream, so we won't reach this
+ // point.
+ if ((baseMethodAttribs & CORINFO_FLG_VIRTUAL) == 0)
+ {
+ assert(call->IsVirtualStub());
+ assert(opts.IsReadyToRun());
+ JITDUMP("\nimpDevirtualizeCall: [R2R] base method not virtual, sorry\n");
+ return;
+ }
+
+ // See what we know about the type of 'this' in the call.
+ bool isExact = false;
+ bool objIsNonNull = false;
+ CORINFO_CLASS_HANDLE objClass = gtGetClassHandle(thisObj, &isExact, &objIsNonNull);
+
+ // Bail if we know nothing.
+ if (objClass == nullptr)
+ {
+ JITDUMP("\nimpDevirtualizeCall: no type available (op=%s)\n", GenTree::OpName(thisObj->OperGet()));
+ return;
+ }
+
+ // Fetch information about the class that introduced the virtual method.
+ CORINFO_CLASS_HANDLE baseClass = info.compCompHnd->getMethodClass(baseMethod);
+ const DWORD baseClassAttribs = info.compCompHnd->getClassAttribs(baseClass);
+
+#if !defined(FEATURE_CORECLR)
+ // If base class is not beforefieldinit then devirtualizing may
+ // cause us to miss a base class init trigger. Spec says we don't
+ // need a trigger for ref class callvirts but desktop seems to
+ // have one anyways. So defer.
+ if ((baseClassAttribs & CORINFO_FLG_BEFOREFIELDINIT) == 0)
+ {
+ JITDUMP("\nimpDevirtualizeCall: base class has precise initialization, sorry\n");
+ return;
+ }
+#endif // FEATURE_CORECLR
+
+ // Is the call an interface call?
+ const bool isInterface = (baseClassAttribs & CORINFO_FLG_INTERFACE) != 0;
+
+ // If the objClass is sealed (final), then we may be able to devirtualize.
+ const DWORD objClassAttribs = info.compCompHnd->getClassAttribs(objClass);
+ const bool objClassIsFinal = (objClassAttribs & CORINFO_FLG_FINAL) != 0;
+
+#if defined(DEBUG)
+ const char* callKind = isInterface ? "interface" : "virtual";
+ const char* objClassNote = "[?]";
+ const char* objClassName = "?objClass";
+ const char* baseClassName = "?baseClass";
+ const char* baseMethodName = "?baseMethod";
+
+ if (verbose || doPrint)
+ {
+ objClassNote = isExact ? " [exact]" : objClassIsFinal ? " [final]" : "";
+ objClassName = info.compCompHnd->getClassName(objClass);
+ baseClassName = info.compCompHnd->getClassName(baseClass);
+ baseMethodName = eeGetMethodName(baseMethod, nullptr);
+
+ if (verbose)
+ {
+ printf("\nimpDevirtualizeCall: Trying to devirtualize %s call:\n"
+ " class for 'this' is %s%s (attrib %08x)\n"
+ " base method is %s::%s\n",
+ callKind, objClassName, objClassNote, objClassAttribs, baseClassName, baseMethodName);
+ }
+ }
+#endif // defined(DEBUG)
+
+ // Bail if obj class is an interface.
+ // See for instance System.ValueTuple`8::GetHashCode, where lcl 0 is System.IValueTupleInternal
+ // IL_021d: ldloc.0
+ // IL_021e: callvirt instance int32 System.Object::GetHashCode()
+ if ((objClassAttribs & CORINFO_FLG_INTERFACE) != 0)
+ {
+ JITDUMP("--- obj class is interface, sorry\n");
+ return;
+ }
+
+ if (isInterface)
+ {
+ assert(call->IsVirtualStub());
+ JITDUMP("--- base class is interface\n");
+ }
+
+ // Fetch the method that would be called based on the declared type of 'this'
+ CORINFO_CONTEXT_HANDLE ownerType = callInfo->contextHandle;
+ CORINFO_METHOD_HANDLE derivedMethod = info.compCompHnd->resolveVirtualMethod(baseMethod, objClass, ownerType);
+
+ // If we failed to get a handle, we can't devirtualize. This can
+ // happen when prejitting, if the devirtualization crosses
+ // servicing bubble boundaries.
+ if (derivedMethod == nullptr)
+ {
+ JITDUMP("--- no derived method, sorry\n");
+ return;
+ }
+
+ // Fetch method attributes to see if method is marked final.
+ const DWORD derivedMethodAttribs = info.compCompHnd->getMethodAttribs(derivedMethod);
+ const bool derivedMethodIsFinal = ((derivedMethodAttribs & CORINFO_FLG_FINAL) != 0);
+
+#if defined(DEBUG)
+ const char* derivedClassName = "?derivedClass";
+ const char* derivedMethodName = "?derivedMethod";
+
+ const char* note = "speculative";
+ if (isExact)
+ {
+ note = "exact";
+ }
+ else if (objClassIsFinal)
+ {
+ note = "final class";
+ }
+ else if (derivedMethodIsFinal)
+ {
+ note = "final method";
+ }
+
+ if (verbose || doPrint)
+ {
+ derivedMethodName = eeGetMethodName(derivedMethod, &derivedClassName);
+ if (verbose)
+ {
+ printf(" devirt to %s::%s -- %s\n", derivedClassName, derivedMethodName, note);
+ gtDispTree(call);
+ }
+ }
+#endif // defined(DEBUG)
+
+ if (!isExact && !objClassIsFinal && !derivedMethodIsFinal)
+ {
+ // Type is not exact, and neither class or method is final.
+ //
+ // We could speculatively devirtualize, but there's no
+ // reason to believe the derived method is the one that
+ // is likely to be invoked.
+ //
+ // If there's currently no further overriding (that is, at
+ // the time of jitting, objClass has no subclasses that
+ // override this method), then perhaps we'd be willing to
+ // make a bet...?
+ JITDUMP(" Class not final or exact, method not final, no devirtualization\n");
+ return;
+ }
+
+ // For interface calls we must have an exact type or final class.
+ if (isInterface && !isExact && !objClassIsFinal)
+ {
+ JITDUMP(" Class not final or exact for interface, no devirtualization\n");
+ return;
+ }
+
+ JITDUMP(" %s; can devirtualize\n", note);
+
+ // Make the updates.
+ call->gtFlags &= ~GTF_CALL_VIRT_VTABLE;
+ call->gtFlags &= ~GTF_CALL_VIRT_STUB;
+ call->gtCallMethHnd = derivedMethod;
+ call->gtCallType = CT_USER_FUNC;
+
+ // Virtual calls include an implicit null check, which we may
+ // now need to make explicit.
+ if (!objIsNonNull)
+ {
+ call->gtFlags |= GTF_CALL_NULLCHECK;
+ }
+
+ // Clear the inline candidate info (may be non-null since
+ // it's a union field used for other things by virtual
+ // stubs)
+ call->gtInlineCandidateInfo = nullptr;
+
+ // Fetch the class that introduced the derived method.
+ //
+ // Note this may not equal objClass, if there is a
+ // final method that objClass inherits.
+ CORINFO_CLASS_HANDLE derivedClass = info.compCompHnd->getMethodClass(derivedMethod);
+
+#ifdef FEATURE_READYTORUN_COMPILER
+ if (opts.IsReadyToRun())
+ {
+ // For R2R, getCallInfo triggers bookkeeping on the zap
+ // side so we need to call it here.
+ //
+ // First, cons up a suitable resolved token.
+ CORINFO_RESOLVED_TOKEN derivedResolvedToken = {};
+
+ derivedResolvedToken.tokenScope = info.compScopeHnd;
+ derivedResolvedToken.tokenContext = callInfo->contextHandle;
+ derivedResolvedToken.token = info.compCompHnd->getMethodDefFromMethod(derivedMethod);
+ derivedResolvedToken.tokenType = CORINFO_TOKENKIND_Method;
+ derivedResolvedToken.hClass = derivedClass;
+ derivedResolvedToken.hMethod = derivedMethod;
+
+ // Look up the new call info.
+ CORINFO_CALL_INFO derivedCallInfo;
+ eeGetCallInfo(&derivedResolvedToken, nullptr, addVerifyFlag(CORINFO_CALLINFO_ALLOWINSTPARAM), &derivedCallInfo);
+
+ // Update the call.
+ call->gtCallMoreFlags &= ~GTF_CALL_M_VIRTSTUB_REL_INDIRECT;
+ call->gtCallMoreFlags &= ~GTF_CALL_M_R2R_REL_INDIRECT;
+ call->setEntryPoint(derivedCallInfo.codePointerLookup.constLookup);
+ }
+#endif // FEATURE_READYTORUN_COMPILER
+
+ // Need to update call info too. This is fragile
+ // but hopefully the derived method conforms to
+ // the base in most other ways.
+ callInfo->hMethod = derivedMethod;
+ callInfo->methodFlags = derivedMethodAttribs;
+ callInfo->contextHandle = MAKE_METHODCONTEXT(derivedMethod);
+
+ // Update context handle.
+ if ((exactContextHandle != nullptr) && (*exactContextHandle != nullptr))
+ {
+ *exactContextHandle = MAKE_METHODCONTEXT(derivedMethod);
+ }
+
+#if defined(DEBUG)
+ if (verbose)
+ {
+ printf("... after devirt...\n");
+ gtDispTree(call);
+ }
+
+ if (doPrint)
+ {
+ printf("Devirtualized %s call to %s:%s; now direct call to %s:%s [%s]\n", callKind, baseClassName,
+ baseMethodName, derivedClassName, derivedMethodName, note);
+ }
+#endif // defined(DEBUG)
+}
+
+//------------------------------------------------------------------------
+// impAllocateToken: create CORINFO_RESOLVED_TOKEN into jit-allocated memory and init it.
+//
+// Arguments:
+// token - init value for the allocated token.
+//
+// Return Value:
+// pointer to token into jit-allocated memory.
+CORINFO_RESOLVED_TOKEN* Compiler::impAllocateToken(CORINFO_RESOLVED_TOKEN token)
+{
+ CORINFO_RESOLVED_TOKEN* memory = (CORINFO_RESOLVED_TOKEN*)compGetMem(sizeof(token));
+ *memory = token;
+ return memory;
+}