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

Commit c06646d

Browse files
authored
Merge pull request #7788 from AndyAyersMS/InlinePins
Inliner: support inlining of methods with pinned locals
2 parents 388b148 + c60e68e commit c06646d

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)