Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit c60e68e

Browse files
committed
Inliner: support inlining of methods with pinned locals
The inliner now can inline a subset of methods with pinned locals -- namely those where the language compiler (eg CSC) can determine that a try/finally is not necessary to ensure unpinning, and where the jit likewise an determines that a try/finally is likewise not needed in the root method. Generally speaking this allows inlining in cases where the inline method is fairly simple and does not any contain exception handling, and the call site is not within a try region. When inlining methods that have pinned locals and also return a value, the jit ensures that the return value is spilled to a temp so that the unpins can be placed just after the inlined method body and won't alter the return value. Mark FixedAddressValueType as GCStressIncompatible since the "unfixed" class static may get re-allocated at the same address. This seems to happen even without these changes but happens much more frequently with them. Closes #7774.
1 parent e76da56 commit c60e68e

File tree

8 files changed

+170
-18
lines changed

8 files changed

+170
-18
lines changed

src/jit/compiler.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4664,6 +4664,7 @@ class Compiler
46644664
void fgInvokeInlineeCompiler(GenTreeCall* call, InlineResult* result);
46654665
void fgInsertInlineeBlocks(InlineInfo* pInlineInfo);
46664666
GenTreePtr fgInlinePrependStatements(InlineInfo* inlineInfo);
4667+
void fgInlineAppendStatements(InlineInfo* inlineInfo, BasicBlock* block, GenTreePtr stmt);
46674668

46684669
#if FEATURE_MULTIREG_RET
46694670
GenTreePtr fgGetStructAsStructPtr(GenTreePtr tree);

src/jit/flowgraph.cpp

Lines changed: 111 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4777,6 +4777,12 @@ void Compiler::fgFindJumpTargets(const BYTE* codeAddr, IL_OFFSET codeSize, BYTE*
47774777
}
47784778
}
47794779

4780+
// Determine if call site is within a try.
4781+
if (isInlining && impInlineInfo->iciBlock->hasTryIndex())
4782+
{
4783+
compInlineResult->Note(InlineObservation::CALLSITE_IN_TRY_REGION);
4784+
}
4785+
47804786
// If the inline is viable and discretionary, do the
47814787
// profitability screening.
47824788
if (compInlineResult->IsDiscretionaryCandidate())
@@ -5774,12 +5780,14 @@ void Compiler::fgFindBasicBlocks()
57745780
compHndBBtabCount = impInlineInfo->InlinerCompiler->compHndBBtabCount;
57755781
info.compXcptnsCount = impInlineInfo->InlinerCompiler->info.compXcptnsCount;
57765782

5777-
if (info.compRetNativeType != TYP_VOID && retBlocks > 1)
5783+
// Use a spill temp for the return value if there are multiple return blocks.
5784+
if ((info.compRetNativeType != TYP_VOID) && (retBlocks > 1))
57785785
{
57795786
// The lifetime of this var might expand multiple BBs. So it is a long lifetime compiler temp.
57805787
lvaInlineeReturnSpillTemp = lvaGrabTemp(false DEBUGARG("Inline candidate multiple BBJ_RETURN spill temp"));
57815788
lvaTable[lvaInlineeReturnSpillTemp].lvType = info.compRetNativeType;
57825789
}
5790+
57835791
return;
57845792
}
57855793

@@ -21696,11 +21704,8 @@ void Compiler::fgInsertInlineeBlocks(InlineInfo* pInlineInfo)
2169621704
}
2169721705
}
2169821706

21699-
//
2170021707
// Prepend statements.
21701-
//
21702-
GenTreePtr stmtAfter;
21703-
stmtAfter = fgInlinePrependStatements(pInlineInfo);
21708+
GenTreePtr stmtAfter = fgInlinePrependStatements(pInlineInfo);
2170421709

2170521710
#ifdef DEBUG
2170621711
if (verbose)
@@ -21710,6 +21715,9 @@ void Compiler::fgInsertInlineeBlocks(InlineInfo* pInlineInfo)
2171021715
}
2171121716
#endif // DEBUG
2171221717

21718+
BasicBlock* topBlock = iciBlock;
21719+
BasicBlock* bottomBlock = nullptr;
21720+
2171321721
if (InlineeCompiler->fgBBcount == 1)
2171421722
{
2171521723
// When fgBBCount is 1 we will always have a non-NULL fgFirstBB
@@ -21734,6 +21742,7 @@ void Compiler::fgInsertInlineeBlocks(InlineInfo* pInlineInfo)
2173421742
noway_assert((inlineeBlockFlags & BBF_KEEP_BBJ_ALWAYS) == 0);
2173521743
iciBlock->bbFlags |= inlineeBlockFlags;
2173621744
}
21745+
2173721746
#ifdef DEBUG
2173821747
if (verbose)
2173921748
{
@@ -21756,6 +21765,10 @@ void Compiler::fgInsertInlineeBlocks(InlineInfo* pInlineInfo)
2175621765
}
2175721766
}
2175821767
#endif // DEBUG
21768+
21769+
// Append statements to unpin, if necessary.
21770+
fgInlineAppendStatements(pInlineInfo, iciBlock, stmtAfter);
21771+
2175921772
goto _Done;
2176021773
}
2176121774
}
@@ -21764,11 +21777,6 @@ void Compiler::fgInsertInlineeBlocks(InlineInfo* pInlineInfo)
2176421777
// ======= Inserting inlinee's basic blocks ===============
2176521778
//
2176621779

21767-
BasicBlock* topBlock;
21768-
BasicBlock* bottomBlock;
21769-
21770-
topBlock = iciBlock;
21771-
2177221780
bottomBlock = fgNewBBafter(topBlock->bbJumpKind, topBlock, true);
2177321781
bottomBlock->bbRefs = 1;
2177421782
bottomBlock->bbJumpDest = topBlock->bbJumpDest;
@@ -21929,6 +21937,9 @@ void Compiler::fgInsertInlineeBlocks(InlineInfo* pInlineInfo)
2192921937
//
2193021938
fgBBcount += InlineeCompiler->fgBBcount;
2193121939

21940+
// Append statements to unpin if necessary.
21941+
fgInlineAppendStatements(pInlineInfo, bottomBlock, nullptr);
21942+
2193221943
#ifdef DEBUG
2193321944
if (verbose)
2193421945
{
@@ -21994,8 +22005,15 @@ void Compiler::fgInsertInlineeBlocks(InlineInfo* pInlineInfo)
2199422005
iciStmt->gtStmt.gtStmtExpr = gtNewNothingNode();
2199522006
}
2199622007

21997-
// Prepend the statements that are needed before the inlined call.
21998-
// Return the last statement that is prepended.
22008+
//------------------------------------------------------------------------
22009+
// fgInlinePrependStatements: Prepend statements that are needed
22010+
// before the inlined call.
22011+
//
22012+
// Arguments:
22013+
// inlineInfo - information about the inline
22014+
//
22015+
// Return Value:
22016+
// The last statement that was prepended.
2199922017

2200022018
GenTreePtr Compiler::fgInlinePrependStatements(InlineInfo* inlineInfo)
2200122019
{
@@ -22276,6 +22294,87 @@ GenTreePtr Compiler::fgInlinePrependStatements(InlineInfo* inlineInfo)
2227622294
return afterStmt;
2227722295
}
2227822296

22297+
//------------------------------------------------------------------------
22298+
// fgInlineAppendStatements: Append statements that are needed
22299+
// after the inlined call.
22300+
//
22301+
// Arguments:
22302+
// inlineInfo - information about the inline
22303+
// block - basic block for the new statements
22304+
// stmtAfter - (optional) insertion point for mid-block cases
22305+
22306+
void Compiler::fgInlineAppendStatements(InlineInfo* inlineInfo, BasicBlock* block, GenTreePtr stmtAfter)
22307+
{
22308+
// Null out any inline pinned locals
22309+
if (!inlineInfo->hasPinnedLocals)
22310+
{
22311+
// No pins, nothing to do
22312+
return;
22313+
}
22314+
22315+
JITDUMP("Unpin inlinee locals:\n");
22316+
22317+
GenTreePtr callStmt = inlineInfo->iciStmt;
22318+
IL_OFFSETX callILOffset = callStmt->gtStmt.gtStmtILoffsx;
22319+
CORINFO_METHOD_INFO* InlineeMethodInfo = InlineeCompiler->info.compMethodInfo;
22320+
unsigned lclCnt = InlineeMethodInfo->locals.numArgs;
22321+
InlLclVarInfo* lclVarInfo = inlineInfo->lclVarInfo;
22322+
22323+
noway_assert(callStmt->gtOper == GT_STMT);
22324+
22325+
for (unsigned lclNum = 0; lclNum < lclCnt; lclNum++)
22326+
{
22327+
unsigned tmpNum = inlineInfo->lclTmpNum[lclNum];
22328+
22329+
// Is the local used at all?
22330+
if (tmpNum == BAD_VAR_NUM)
22331+
{
22332+
// Nope, nothing to unpin.
22333+
continue;
22334+
}
22335+
22336+
// Is the local pinned?
22337+
if (!lvaTable[tmpNum].lvPinned)
22338+
{
22339+
// Nope, nothing to unpin.
22340+
continue;
22341+
}
22342+
22343+
// Does the local we're about to unpin appear in the return
22344+
// expression? If so we somehow messed up and didn't properly
22345+
// spill the return value. See impInlineFetchLocal.
22346+
GenTreePtr retExpr = inlineInfo->retExpr;
22347+
if (retExpr != nullptr)
22348+
{
22349+
const bool interferesWithReturn = gtHasRef(inlineInfo->retExpr, tmpNum, false);
22350+
noway_assert(!interferesWithReturn);
22351+
}
22352+
22353+
// Emit the unpin, by assigning null to the local.
22354+
var_types lclTyp = (var_types)lvaTable[tmpNum].lvType;
22355+
noway_assert(lclTyp == lclVarInfo[lclNum + inlineInfo->argCnt].lclTypeInfo);
22356+
noway_assert(!varTypeIsStruct(lclTyp));
22357+
GenTreePtr unpinExpr = gtNewTempAssign(tmpNum, gtNewZeroConNode(genActualType(lclTyp)));
22358+
GenTreePtr unpinStmt = gtNewStmt(unpinExpr, callILOffset);
22359+
22360+
if (stmtAfter == nullptr)
22361+
{
22362+
stmtAfter = fgInsertStmtAtBeg(block, unpinStmt);
22363+
}
22364+
else
22365+
{
22366+
stmtAfter = fgInsertStmtAfter(block, stmtAfter, unpinStmt);
22367+
}
22368+
22369+
#ifdef DEBUG
22370+
if (verbose)
22371+
{
22372+
gtDispTree(unpinStmt);
22373+
}
22374+
#endif // DEBUG
22375+
22376+
}
22377+
}
2227922378

2228022379
/*****************************************************************************/
2228122380
/*static*/

src/jit/importer.cpp

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14911,7 +14911,8 @@ bool Compiler::impReturnInstruction(BasicBlock* block, int prefixFlags, OPCODE&
1491114911

1491214912
if (lvaInlineeReturnSpillTemp != BAD_VAR_NUM)
1491314913
{
14914-
assert(info.compRetNativeType != TYP_VOID && fgMoreThanOneReturnBlock());
14914+
assert(info.compRetNativeType != TYP_VOID &&
14915+
(fgMoreThanOneReturnBlock() || impInlineInfo->hasPinnedLocals));
1491514916

1491614917
// This is a bit of a workaround...
1491714918
// If we are inlining a call that returns a struct, where the actual "native" return type is
@@ -15002,7 +15003,7 @@ bool Compiler::impReturnInstruction(BasicBlock* block, int prefixFlags, OPCODE&
1500215003
// in this case we have to insert multiple struct copies to the temp
1500315004
// and the retexpr is just the temp.
1500415005
assert(info.compRetNativeType != TYP_VOID);
15005-
assert(fgMoreThanOneReturnBlock());
15006+
assert(fgMoreThanOneReturnBlock() || impInlineInfo->hasPinnedLocals);
1500615007

1500715008
impAssignTempGen(lvaInlineeReturnSpillTemp, op2, se.seTypeInfo.GetClassHandle(),
1500815009
(unsigned)CHECK_SPILL_ALL);
@@ -17429,12 +17430,17 @@ void Compiler::impInlineInitVars(InlineInfo* pInlineInfo)
1742917430
var_types type = (var_types)eeGetArgType(localsSig, &methInfo->locals, &isPinned);
1743017431

1743117432
lclVarInfo[i + argCnt].lclHasLdlocaOp = false;
17433+
lclVarInfo[i + argCnt].lclIsPinned = isPinned;
1743217434
lclVarInfo[i + argCnt].lclTypeInfo = type;
1743317435

1743417436
if (isPinned)
1743517437
{
17436-
inlineResult->NoteFatal(InlineObservation::CALLEE_HAS_PINNED_LOCALS);
17437-
return;
17438+
// Pinned locals may cause inlines to fail.
17439+
inlineResult->Note(InlineObservation::CALLEE_HAS_PINNED_LOCALS);
17440+
if (inlineResult->IsFailure())
17441+
{
17442+
return;
17443+
}
1743817444
}
1743917445

1744017446
lclVarInfo[i + argCnt].lclVerTypeInfo = verParseArgSigToTypeInfo(&methInfo->locals, localsSig);
@@ -17511,6 +17517,28 @@ unsigned Compiler::impInlineFetchLocal(unsigned lclNum DEBUGARG(const char* reas
1751117517
lvaTable[tmpNum].lvHasLdAddrOp = 1;
1751217518
}
1751317519

17520+
if (impInlineInfo->lclVarInfo[lclNum + impInlineInfo->argCnt].lclIsPinned)
17521+
{
17522+
lvaTable[tmpNum].lvPinned = 1;
17523+
17524+
if (!impInlineInfo->hasPinnedLocals)
17525+
{
17526+
// If the inlinee returns a value, use a spill temp
17527+
// for the return value to ensure that even in case
17528+
// where the return expression refers to one of the
17529+
// pinned locals, we can unpin the local right after
17530+
// the inlined method body.
17531+
if ((info.compRetNativeType != TYP_VOID) && (lvaInlineeReturnSpillTemp == BAD_VAR_NUM))
17532+
{
17533+
lvaInlineeReturnSpillTemp =
17534+
lvaGrabTemp(false DEBUGARG("Inline candidate pinned local return spill temp"));
17535+
lvaTable[lvaInlineeReturnSpillTemp].lvType = info.compRetNativeType;
17536+
}
17537+
}
17538+
17539+
impInlineInfo->hasPinnedLocals = true;
17540+
}
17541+
1751417542
if (impInlineInfo->lclVarInfo[lclNum + impInlineInfo->argCnt].lclVerTypeInfo.IsStruct())
1751517543
{
1751617544
if (varTypeIsStruct(lclTyp))

src/jit/inline.def

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ INLINE_OBSERVATION(HAS_MANAGED_VARARGS, bool, "managed varargs",
4040
INLINE_OBSERVATION(HAS_NATIVE_VARARGS, bool, "native varargs", FATAL, CALLEE)
4141
INLINE_OBSERVATION(HAS_NO_BODY, bool, "has no body", FATAL, CALLEE)
4242
INLINE_OBSERVATION(HAS_NULL_FOR_LDELEM, bool, "has null pointer for ldelem", FATAL, CALLEE)
43-
INLINE_OBSERVATION(HAS_PINNED_LOCALS, bool, "has pinned locals", FATAL, CALLEE)
4443
INLINE_OBSERVATION(IS_ARRAY_METHOD, bool, "is array method", FATAL, CALLEE)
4544
INLINE_OBSERVATION(IS_GENERIC_VIRTUAL, bool, "generic virtual", FATAL, CALLEE)
4645
INLINE_OBSERVATION(IS_JIT_NOINLINE, bool, "noinline per JitNoinline", FATAL, CALLEE)
@@ -79,6 +78,7 @@ INLINE_OBSERVATION(CLASS_PROMOTABLE, bool, "promotable value class",
7978
INLINE_OBSERVATION(DOES_NOT_RETURN, bool, "does not return", INFORMATION, CALLEE)
8079
INLINE_OBSERVATION(END_OPCODE_SCAN, bool, "done looking at opcodes", INFORMATION, CALLEE)
8180
INLINE_OBSERVATION(HAS_GC_STRUCT, bool, "has gc field in struct local", INFORMATION, CALLEE)
81+
INLINE_OBSERVATION(HAS_PINNED_LOCALS, bool, "has pinned locals", INFORMATION, CALLEE)
8282
INLINE_OBSERVATION(HAS_SIMD, bool, "has SIMD arg, local, or ret", INFORMATION, CALLEE)
8383
INLINE_OBSERVATION(HAS_SWITCH, bool, "has switch", INFORMATION, CALLEE)
8484
INLINE_OBSERVATION(IL_CODE_SIZE, int, "number of bytes of IL", INFORMATION, CALLEE)
@@ -139,7 +139,7 @@ INLINE_OBSERVATION(IS_TOO_DEEP, bool, "too deep",
139139
INLINE_OBSERVATION(IS_VIRTUAL, bool, "virtual", FATAL, CALLSITE)
140140
INLINE_OBSERVATION(IS_VM_NOINLINE, bool, "noinline per VM", FATAL, CALLSITE)
141141
INLINE_OBSERVATION(IS_WITHIN_CATCH, bool, "within catch region", FATAL, CALLSITE)
142-
INLINE_OBSERVATION(IS_WITHIN_FILTER, bool, "within filterregion", FATAL, CALLSITE)
142+
INLINE_OBSERVATION(IS_WITHIN_FILTER, bool, "within filter region", FATAL, CALLSITE)
143143
INLINE_OBSERVATION(LDARGA_NOT_LOCAL_VAR, bool, "ldarga not on local var", FATAL, CALLSITE)
144144
INLINE_OBSERVATION(LDFLD_NEEDS_HELPER, bool, "ldfld needs helper", FATAL, CALLSITE)
145145
INLINE_OBSERVATION(LDVIRTFN_ON_NON_VIRTUAL, bool, "ldvirtfn on non-virtual", FATAL, CALLSITE)
@@ -148,6 +148,7 @@ INLINE_OBSERVATION(NOT_CANDIDATE, bool, "not inline candidate",
148148
INLINE_OBSERVATION(NOT_PROFITABLE_INLINE, bool, "unprofitable inline", FATAL, CALLSITE)
149149
INLINE_OBSERVATION(OVER_BUDGET, bool, "inline exceeds budget", FATAL, CALLSITE)
150150
INLINE_OBSERVATION(OVER_INLINE_LIMIT, bool, "limited by JitInlineLimit", FATAL, CALLSITE)
151+
INLINE_OBSERVATION(PIN_IN_TRY_REGION, bool, "within try region, pinned", FATAL, CALLSITE)
151152
INLINE_OBSERVATION(RANDOM_REJECT, bool, "random reject", FATAL, CALLSITE)
152153
INLINE_OBSERVATION(REQUIRES_SAME_THIS, bool, "requires same this", FATAL, CALLSITE)
153154
INLINE_OBSERVATION(RETURN_TYPE_MISMATCH, bool, "return type mismatch", FATAL, CALLSITE)
@@ -163,6 +164,7 @@ INLINE_OBSERVATION(RARE_GC_STRUCT, bool, "rarely called, has gc str
163164
INLINE_OBSERVATION(CONSTANT_ARG_FEEDS_TEST, bool, "constant argument feeds test", INFORMATION, CALLSITE)
164165
INLINE_OBSERVATION(DEPTH, int, "depth", INFORMATION, CALLSITE)
165166
INLINE_OBSERVATION(FREQUENCY, int, "rough call site frequency", INFORMATION, CALLSITE)
167+
INLINE_OBSERVATION(IN_TRY_REGION, bool, "call site in try region", INFORMATION, CALLSITE)
166168
INLINE_OBSERVATION(IS_PROFITABLE_INLINE, bool, "profitable inline", INFORMATION, CALLSITE)
167169
INLINE_OBSERVATION(IS_SAME_THIS, bool, "same this as root caller", INFORMATION, CALLSITE)
168170
INLINE_OBSERVATION(IS_SIZE_DECREASING_INLINE, bool, "size decreasing inline", INFORMATION, CALLSITE)

src/jit/inline.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,7 @@ struct InlLclVarInfo
537537
var_types lclTypeInfo;
538538
typeInfo lclVerTypeInfo;
539539
bool lclHasLdlocaOp; // Is there LDLOCA(s) operation on this argument?
540+
bool lclIsPinned;
540541
};
541542

542543
// InlineInfo provides detailed information about a particular inline candidate.
@@ -563,6 +564,7 @@ struct InlineInfo
563564
InlLclVarInfo lclVarInfo[MAX_INL_LCLS + MAX_INL_ARGS + 1]; // type information from local sig
564565

565566
bool thisDereferencedFirst;
567+
bool hasPinnedLocals;
566568
#ifdef FEATURE_SIMD
567569
bool hasSIMDTypeArgLocalOrReturn;
568570
#endif // FEATURE_SIMD

src/jit/inlinepolicy.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,12 @@ void LegacyPolicy::NoteBool(InlineObservation obs, bool value)
383383
break;
384384
}
385385

386+
case InlineObservation::CALLEE_HAS_PINNED_LOCALS:
387+
// The legacy policy is to never inline methods with
388+
// pinned locals.
389+
SetNever(obs);
390+
break;
391+
386392
default:
387393
// Ignore the remainder for now
388394
break;
@@ -883,6 +889,17 @@ void EnhancedLegacyPolicy::NoteBool(InlineObservation obs, bool value)
883889
}
884890
break;
885891

892+
case InlineObservation::CALLEE_HAS_PINNED_LOCALS:
893+
if (m_CallsiteIsInTryRegion)
894+
{
895+
// Inlining a method with pinned locals in a try
896+
// region requires wrapping the inline body in a
897+
// try/finally to ensure unpinning. Bail instead.
898+
SetFailure(InlineObservation::CALLSITE_PIN_IN_TRY_REGION);
899+
return;
900+
}
901+
break;
902+
886903
default:
887904
// Pass all other information to the legacy policy
888905
LegacyPolicy::NoteBool(obs, value);

src/jit/inlinepolicy.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ class LegacyPolicy : public LegalPolicy
9898
, m_HasSimd(false)
9999
, m_LooksLikeWrapperMethod(false)
100100
, m_MethodIsMostlyLoadStore(false)
101+
, m_CallsiteIsInTryRegion(false)
101102
{
102103
// empty
103104
}
@@ -165,6 +166,7 @@ class LegacyPolicy : public LegalPolicy
165166
bool m_HasSimd : 1;
166167
bool m_LooksLikeWrapperMethod : 1;
167168
bool m_MethodIsMostlyLoadStore : 1;
169+
bool m_CallsiteIsInTryRegion : 1;
168170
};
169171

170172
// EnhancedLegacyPolicy extends the legacy policy by rejecting

tests/src/baseservices/compilerservices/FixedAddressValueType/FixedAddressValueType.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
<ReferenceLocalMscorlib>true</ReferenceLocalMscorlib>
1717
<CLRTestKind>BuildAndRun</CLRTestKind>
1818
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
19+
<GCStressIncompatible>true</GCStressIncompatible>
1920
</PropertyGroup>
2021
<!-- Default configurations to help VS understand the configurations -->
2122
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">

0 commit comments

Comments
 (0)