compHndBBtabCount = impInlineInfo->InlinerCompiler->compHndBBtabCount;
info.compXcptnsCount = impInlineInfo->InlinerCompiler->info.compXcptnsCount;
- // Use a spill temp for the return value if there are multiple return blocks.
- if ((info.compRetNativeType != TYP_VOID) && (retBlocks > 1))
+ // Use a spill temp for the return value if there are multiple return blocks,
+ // or if the inlinee has GC ref locals.
+ if ((info.compRetNativeType != TYP_VOID) && ((retBlocks > 1) || impInlineInfo->HasGcRefLocals()))
{
// The lifetime of this var might expand multiple BBs. So it is a long lifetime compiler temp.
- lvaInlineeReturnSpillTemp = lvaGrabTemp(false DEBUGARG("Inline candidate multiple BBJ_RETURN spill temp"));
+ lvaInlineeReturnSpillTemp = lvaGrabTemp(false DEBUGARG("Inline return value spill temp"));
lvaTable[lvaInlineeReturnSpillTemp].lvType = info.compRetNativeType;
}
}
#endif // DEBUG
- // Append statements to unpin, if necessary.
+ // Append statements to null out gc ref locals, if necessary.
fgInlineAppendStatements(pInlineInfo, iciBlock, stmtAfter);
goto _Done;
//
fgBBcount += InlineeCompiler->fgBBcount;
- // Append statements to unpin if necessary.
+ // Append statements to null out gc ref locals, if necessary.
fgInlineAppendStatements(pInlineInfo, bottomBlock, nullptr);
#ifdef DEBUG
// inlineInfo - information about the inline
// block - basic block for the new statements
// stmtAfter - (optional) insertion point for mid-block cases
+//
+// Notes:
+// If the call we're inlining is in tail position then
+// we skip nulling the locals, since it can interfere
+// with tail calls introduced by the local.
void Compiler::fgInlineAppendStatements(InlineInfo* inlineInfo, BasicBlock* block, GenTreePtr stmtAfter)
{
- // Null out any inline pinned locals
- if (!inlineInfo->hasPinnedLocals)
+ // Null out any gc ref locals
+ if (!inlineInfo->HasGcRefLocals())
{
- // No pins, nothing to do
+ // No ref locals, nothing to do.
+ JITDUMP("fgInlineAppendStatements: no gc ref inline locals.\n");
return;
}
- JITDUMP("Unpin inlinee locals:\n");
+ if (inlineInfo->iciCall->IsImplicitTailCall())
+ {
+ JITDUMP("fgInlineAppendStatements: implicit tail call; skipping nulling.\n");
+ return;
+ }
+
+ JITDUMP("fgInlineAppendStatements: nulling out gc ref inlinee locals.\n");
GenTreePtr callStmt = inlineInfo->iciStmt;
IL_OFFSETX callILOffset = callStmt->gtStmt.gtStmtILoffsx;
CORINFO_METHOD_INFO* InlineeMethodInfo = InlineeCompiler->info.compMethodInfo;
- unsigned lclCnt = InlineeMethodInfo->locals.numArgs;
+ const unsigned lclCnt = InlineeMethodInfo->locals.numArgs;
InlLclVarInfo* lclVarInfo = inlineInfo->lclVarInfo;
+ unsigned gcRefLclCnt = inlineInfo->numberOfGcRefLocals;
+ const unsigned argCnt = inlineInfo->argCnt;
noway_assert(callStmt->gtOper == GT_STMT);
for (unsigned lclNum = 0; lclNum < lclCnt; lclNum++)
{
- unsigned tmpNum = inlineInfo->lclTmpNum[lclNum];
+ // Is the local a gc ref type? Need to look at the
+ // inline info for this since we will not have local
+ // temps for unused inlinee locals.
+ const var_types lclTyp = lclVarInfo[argCnt + lclNum].lclTypeInfo;
- // Is the local used at all?
- if (tmpNum == BAD_VAR_NUM)
+ if (!varTypeIsGC(lclTyp))
{
- // Nope, nothing to unpin.
+ // Nope, nothing to null out.
continue;
}
- // Is the local pinned?
- if (!lvaTable[tmpNum].lvPinned)
+ // Ensure we're examining just the right number of locals.
+ assert(gcRefLclCnt > 0);
+ gcRefLclCnt--;
+
+ // Fetch the temp for this inline local
+ const unsigned tmpNum = inlineInfo->lclTmpNum[lclNum];
+
+ // Is the local used at all?
+ if (tmpNum == BAD_VAR_NUM)
{
- // Nope, nothing to unpin.
+ // Nope, nothing to null out.
continue;
}
- // Does the local we're about to unpin appear in the return
+ // Local was used, make sure the type is consistent.
+ assert(lvaTable[tmpNum].lvType == lclTyp);
+
+ // Does the local we're about to null out appear in the return
// expression? If so we somehow messed up and didn't properly
// spill the return value. See impInlineFetchLocal.
GenTreePtr retExpr = inlineInfo->retExpr;
noway_assert(!interferesWithReturn);
}
- // Emit the unpin, by assigning null to the local.
- var_types lclTyp = (var_types)lvaTable[tmpNum].lvType;
- noway_assert(lclTyp == lclVarInfo[lclNum + inlineInfo->argCnt].lclTypeInfo);
- noway_assert(!varTypeIsStruct(lclTyp));
- GenTreePtr unpinExpr = gtNewTempAssign(tmpNum, gtNewZeroConNode(genActualType(lclTyp)));
- GenTreePtr unpinStmt = gtNewStmt(unpinExpr, callILOffset);
+ // Assign null to the local.
+ GenTreePtr nullExpr = gtNewTempAssign(tmpNum, gtNewZeroConNode(lclTyp));
+ GenTreePtr nullStmt = gtNewStmt(nullExpr, callILOffset);
if (stmtAfter == nullptr)
{
- stmtAfter = fgInsertStmtAtBeg(block, unpinStmt);
+ stmtAfter = fgInsertStmtAtBeg(block, nullStmt);
}
else
{
- stmtAfter = fgInsertStmtAfter(block, stmtAfter, unpinStmt);
+ stmtAfter = fgInsertStmtAfter(block, stmtAfter, nullStmt);
}
#ifdef DEBUG
if (verbose)
{
- gtDispTree(unpinStmt);
+ gtDispTree(nullStmt);
}
#endif // DEBUG
}
+
+ // There should not be any GC ref locals left to null out.
+ assert(gcRefLclCnt == 0);
}
/*****************************************************************************/
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;
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
// 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);
lclVarInfo[i + argCnt].lclIsPinned = isPinned;
lclVarInfo[i + argCnt].lclTypeInfo = type;
+ if (varTypeIsGC(type))
+ {
+ pInlineInfo->numberOfGcRefLocals++;
+ }
+
if (isPinned)
{
// Pinned locals may cause inlines to fail.
#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());
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;
- }
-
- 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;
- }
+ // Copy over key info
+ lvaTable[tmpNum].lvType = lclTyp;
+ lvaTable[tmpNum].lvHasLdAddrOp = inlineeLocal.lclHasLdlocaOp;
+ lvaTable[tmpNum].lvPinned = inlineeLocal.lclIsPinned;
- 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;