InlineRefactoring: capture remaining failing cases in inline tree
authorAndy Ayers <andya@microsoft.com>
Mon, 22 Feb 2016 18:38:49 +0000 (10:38 -0800)
committerAndy Ayers <andya@microsoft.com>
Tue, 23 Feb 2016 04:18:39 +0000 (20:18 -0800)
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

src/coreclr/src/jit/compiler.h
src/coreclr/src/jit/compiler.hpp
src/coreclr/src/jit/flowgraph.cpp
src/coreclr/src/jit/inline.h
src/coreclr/src/jit/morph.cpp

index f099811..340ba6e 100644 (file)
@@ -4533,8 +4533,12 @@ private:
                                                               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);
index b5543e6..6637891 100644 (file)
@@ -1104,6 +1104,13 @@ GenTreeCall*        Compiler::gtNewHelperCallNode(unsigned        helper,
                                         type,
                                         args);
     result->gtFlags |= flags;
+
+#if DEBUG
+    // Helper calls are never candidates.
+
+    result->gtInlineObservation = InlineObservation::CALLSITE_IS_CALL_TO_HELPER;
+#endif
+
     return result;
 }
 
index 344d716..3af856f 100644 (file)
@@ -21415,9 +21415,12 @@ void                Compiler::fgInline()
             // 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())
                 {
@@ -21425,6 +21428,13 @@ void                Compiler::fgInline()
                     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,
@@ -21486,6 +21496,91 @@ void                Compiler::fgInline()
 #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)
 
 /*********************************************************************************
index eb2890a..224e954 100644 (file)
@@ -27,7 +27,7 @@
 //
 // 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.
 //
@@ -40,7 +40,7 @@
 // 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.
index 6183a9d..723d403 100644 (file)
@@ -5602,36 +5602,43 @@ GenTreePtr          Compiler::fgMorphField(GenTreePtr tree, MorphAddrContext* ma
 }
 
 
-/*****************************************************************************
- *  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
 
@@ -5651,8 +5658,6 @@ bool        Compiler::fgMorphCallInline(GenTreePtr node)
         //
         call->gtFlags &= ~GTF_CALL_INLINE_CANDIDATE;
     }
-
-    return inlineResult.isSuccess();
 }
 
 /*****************************************************************************
@@ -5696,42 +5701,6 @@ void Compiler::fgMorphCallInlineHelper(GenTreeCall* call, InlineResult* result)
         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.