The main ininling loop in `fgInline` currently only looks at calls
that are inline candidates and at top-level in their statements.
This is sensible since any call that is an inline candidate has been
hoisted to top-level during importing.
However, to find all the failed inline cases, the jit also needs to
look through the full tree to find calls that were not identified as
candidates.
For instance, in the Secant test, the jit decides to inline `TestBase`
into `Main`. While importing the code for `TestBase` for inlining, the
jit sees that the call to `Bench` is marked with
`[MethodImpl(MethodImplOptions.NoInlining)]` and so the call is not
considered to be an inline candidate. And because `Bench` returns a
value, the call expression is in a subtree under an assign expression.
Thus the failure to inline `Bench` is overlooked by the current code.
```
;; current code
Inlines into Secant:Main():int
[IL=0000 TR=000001] [below ALWAYS_INLINE size] Secant:TestBase():bool
```
With this change, under DEBUG, the main inline control loop in `fgInline`
now also scans the tree for non-candidates, and adds their failures
to the InlineContext tree in appropriate locations.
`fgMorphInline` and `fgMorphInlineHelper` are now simpified since they
can assume any call they see must be a candidate.
The jit now also only notes failures for `CT_USER_FUNC` calls, since
otherwise the trees would be full of failed calls to helpers and the
like.
With this change, the jit can now report that `Bench` is a failed inline
into `Main`.
```
;; new code
Inlines into Secant:Main():int
[IL=0000 TR=000001] [below ALWAYS_INLINE size] Secant:TestBase():bool
[IL=0000 TR=000021] [FAILED: noinline per IL/cached result] Secant:Bench():bool
```
Commit migrated from https://github.com/dotnet/coreclr/commit/
fff03183878a074539544f9b3c3d1cda8176fc85
GenTreePtr tmpAssignmentInsertionPoint, GenTreePtr paramAssignmentInsertionPoint);
static int fgEstimateCallStackSize(GenTreeCall* call);
GenTreePtr fgMorphCall (GenTreeCall* call);
- bool fgMorphCallInline (GenTreePtr call);
+ void fgMorphCallInline (GenTreeCall* call, InlineResult* result);
void fgMorphCallInlineHelper(GenTreeCall* call, InlineResult* result);
+#if DEBUG
+ void fgNoteNonInlineCandidate(GenTreePtr tree, GenTreeCall* call);
+ static fgWalkPreFn fgFindNonInlineCandidate;
+#endif
GenTreePtr fgOptimizeDelegateConstructor(GenTreePtr call, CORINFO_CONTEXT_HANDLE * ExactContextHnd);
GenTreePtr fgMorphLeaf (GenTreePtr tree);
void fgAssignSetVarDef (GenTreePtr tree);
type,
args);
result->gtFlags |= flags;
+
+#if DEBUG
+ // Helper calls are never candidates.
+
+ result->gtInlineObservation = InlineObservation::CALLSITE_IS_CALL_TO_HELPER;
+#endif
+
return result;
}
// See if we can expand the inline candidate
if ((expr->gtOper == GT_CALL) && ((expr->gtFlags & GTF_CALL_INLINE_CANDIDATE) != 0))
{
+ GenTreeCall* call = expr->AsCall();
+ InlineResult inlineResult(this, call, "fgInline");
+
fgMorphStmt = stmt;
- fgMorphCallInline(expr);
+ fgMorphCallInline(call, &inlineResult);
if (stmt->gtStmtExpr->IsNothingNode())
{
continue;
}
}
+ else
+ {
+#ifdef DEBUG
+ // Look for non-candidates.
+ fgWalkTreePre(&stmt->gtStmtExpr, fgFindNonInlineCandidate, stmt);
+#endif
+ }
// See if we need to replace the return value place holder.
fgWalkTreePre(&stmt->gtStmtExpr,
#endif // DEBUG
}
+#ifdef DEBUG
+
+//------------------------------------------------------------------------
+// fgFindNonInlineCandidate: tree walk helper to ensure that a tree node
+// that is not an inline candidate is noted as a failed inline.
+//
+// Arguments:
+// pTree - pointer to pointer tree node being walked
+// data - contextual data for the walk
+//
+// Return Value:
+// walk result
+//
+// Note:
+// Invokes fgNoteNonInlineCandidate on the nodes it finds.
+
+Compiler::fgWalkResult Compiler::fgFindNonInlineCandidate(GenTreePtr* pTree,
+ fgWalkData* data)
+{
+ GenTreePtr tree = *pTree;
+ if (tree->gtOper == GT_CALL)
+ {
+ Compiler* compiler = data->compiler;
+ GenTreePtr stmt = (GenTreePtr) data->pCallbackData;
+ GenTreeCall* call = tree->AsCall();
+
+ compiler->fgNoteNonInlineCandidate(stmt, call);
+ }
+ return WALK_CONTINUE;
+}
+
+//------------------------------------------------------------------------
+// fgNoteNonInlineCandidate: account for inlining failures in calls
+// not marked as inline candidates.
+//
+// Arguments:
+// tree - statement containing the call
+// call - the call itself
+//
+// Notes:
+// Used in debug only to try and place descriptions of inline failures
+// into the proper context in the inline tree.
+
+void Compiler::fgNoteNonInlineCandidate(GenTreePtr tree,
+ GenTreeCall* call)
+{
+ InlineResult inlineResult(this, call, "fgNotInlineCandidate");
+ InlineObservation currentObservation = InlineObservation::CALLSITE_NOT_CANDIDATE;
+
+ // Try and recover the reason left behind when the jit decided
+ // this call was not a candidate.
+ InlineObservation priorObservation = call->gtInlineObservation;
+
+ if (inlIsValidObservation(priorObservation))
+ {
+ currentObservation = priorObservation;
+ }
+
+ // Would like to just call noteFatal here, since this
+ // observation blocked candidacy, but policy comes into play
+ // here too. Also note there's no need to re-report these
+ // failures, since we reported them during the initial
+ // candidate scan.
+ InlineImpact impact = inlGetImpact(currentObservation);
+
+ if (impact == InlineImpact::FATAL)
+ {
+ inlineResult.noteFatal(currentObservation);
+ }
+ else
+ {
+ inlineResult.note(currentObservation);
+ }
+
+ inlineResult.setReported();
+
+ if (call->gtCallType == CT_USER_FUNC)
+ {
+ // Create InlineContext for the failure
+ InlineContext::newFailure(this, tree, &inlineResult);
+ }
+}
+
+#endif
+
#if defined(_TARGET_ARM_) || defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)
/*********************************************************************************
//
// Enums are used throughout to provide various descriptions.
//
-// Classes are used as follows. There are 4 sitations where inline
+// Classes are used as follows. There are 5 sitations where inline
// candidacy is evaluated. In each case an InlineResult is allocated
// on the stack to collect information about the inline candidate.
//
// imported as well as when prospective inlines are being imported.
// Candidates are marked in the IL and given an InlineCandidateInfo.
//
-// 2. Inlining Optimization Pass (fgInline/fgMorphCallInline)
+// 2. Inlining Optimization Pass -- candidates (fgInline)
//
// Creates / Uses: InlineContext
// Creates: InlineInfo, InlArgInfo, InlLocalVarInfo
// created to remember this inline. In DEBUG builds, failing inlines
// also create InlineContexts.
//
-// 3 & 4. Prejit suitability screens (compCompileHelper)
+// 3. Inlining Optimization Pass -- non-candidates (fgNoteNotInlineCandidate)
+//
+// Creates / Uses: InlineContext
+//
+// In DEBUG, the jit also searches for non-candidate calls to try
+// and get a complete picture of the set of failed inlines.
+//
+// 4 & 5. Prejit suitability screens (compCompileHelper)
//
// When prejitting, each method is scanned to see if it is a viable
// inline candidate. The scanning happens in two stages.
}
-/*****************************************************************************
- * Attempt to inline a call
- * Returns true if the inline was successful
- *
- * Reports inline result to the jit (unless suppressed).
- * Unmarks call as candidate if inline failed.
- */
+//------------------------------------------------------------------------------
+// fgMorphCallInline: attempt to inline a call
+//
+// Arguments:
+// call - call expression to inline, inline candidate
+// inlineResult - result tracking and reporting
+//
+// Notes:
+// Attempts to inline the call.
+//
+// If successful, callee's IR is inserted in place of the call, and
+// is marked with an InlineContext.
+//
+// If unsuccessful, the transformations done in anticpation of a
+// possible inline are undone, and the candidate flag on the call
+// is cleared.
-bool Compiler::fgMorphCallInline(GenTreePtr node)
+void Compiler::fgMorphCallInline(GenTreeCall* call, InlineResult* inlineResult)
{
- GenTreeCall* call = node->AsCall();
-
- // Prepare to record information about this inline
- InlineResult inlineResult(this, call, "fgMorphCallInline");
+ // The call must be a candiate for inlining.
+ assert((call->gtFlags & GTF_CALL_INLINE_CANDIDATE) != 0);
// Attempt the inline
- fgMorphCallInlineHelper(call, &inlineResult);
+ fgMorphCallInlineHelper(call, inlineResult);
// We should have made up our minds one way or another....
- assert(inlineResult.isDecided());
+ assert(inlineResult->isDecided());
// If we failed to inline, we have a bit of work to do to cleanup
- if (inlineResult.isFailure())
+ if (inlineResult->isFailure())
{
#ifdef DEBUG
- // Before we do any cleanup. create a failing InlineContext to
+ // Before we do any cleanup, create a failing InlineContext to
// capture details of the inlining attempt.
- InlineContext::newFailure(this, fgMorphStmt, &inlineResult);
+ InlineContext::newFailure(this, fgMorphStmt, inlineResult);
#endif
//
call->gtFlags &= ~GTF_CALL_INLINE_CANDIDATE;
}
-
- return inlineResult.isSuccess();
}
/*****************************************************************************
result->noteFatal(InlineObservation::CALLER_NEEDS_SECURITY_CHECK);
return;
}
-
- if ((call->gtFlags & GTF_CALL_INLINE_CANDIDATE) == 0)
- {
- InlineObservation currentObservation = InlineObservation::CALLSITE_NOT_CANDIDATE;
-
-#ifdef DEBUG
- // Try and recover the reason left behind when the jit decided
- // this call was not a candidate.
- InlineObservation priorObservation = call->gtInlineObservation;
-
- if (inlIsValidObservation(priorObservation))
- {
- currentObservation = priorObservation;
- }
-#endif
-
- // Would like to just call noteFatal here, since this
- // observation blocked candidacy, but policy comes into play
- // here too. Also note there's no need to re-report these
- // failures, since we reported them during the initial
- // candidate scan.
- InlineImpact impact = inlGetImpact(currentObservation);
-
- if (impact == InlineImpact::FATAL)
- {
- result->noteFatal(currentObservation);
- }
- else
- {
- result->note(currentObservation);
- }
-
- result->setReported();
-
- return;
- }
//
// Calling inlinee's compiler to inline the method.