Inline refactoring: capture failing observation for non-candidates
authorAndy Ayers <andya@microsoft.com>
Thu, 18 Feb 2016 20:48:37 +0000 (12:48 -0800)
committerAndy Ayers <andya@microsoft.com>
Thu, 18 Feb 2016 23:30:34 +0000 (15:30 -0800)
Refactor the InlineResult to take a `GenTreeCall` instead of artifacts
derived from the call. Use this to decorate the call (in DEBUG) if
an inline fails with the observation that lead to the failure. Move
this constructor out of the header since we now need it to invoke
methods on types that are header-opaque.

Try and pick this reason up later on when non-candidate call sites are
encountered during inlining.

Introduce a second constructor for the pre-jit use case, where we are
evaulating a method to see if we can mark it as never inline to save
work in any subsequent compilation. Put this into the cpp file too for
symmetry.

Type the backing field in GenTreeCall as unsigned to avoid creating
more deeply entangled include circularities. Happy to reconsider if
this seems ill-advised.

Reword a few more uses of inlinee to callee (similarly inliner to
caller). Make `inlIsValidObservation` globally visible and enable
a prior commented-out assert.

Commit migrated from https://github.com/dotnet/coreclr/commit/df9c1264c5252cdd6154f778dca9cb7e01c3cea9

src/coreclr/src/jit/compiler.cpp
src/coreclr/src/jit/gentree.h
src/coreclr/src/jit/importer.cpp
src/coreclr/src/jit/inline.cpp
src/coreclr/src/jit/inline.h
src/coreclr/src/jit/morph.cpp

index 022a49a..71312bc 100644 (file)
@@ -4970,7 +4970,7 @@ int           Compiler::compCompileHelper (CORINFO_MODULE_HANDLE            clas
             if (opts.eeFlags & CORJIT_FLG_PREJIT)
             {
                 // Cache inlining hint during NGen to avoid touching bodies of non-inlineable methods at runtime
-                InlineResult trialResult(this, nullptr, methodHnd, "prejit1");
+                InlineResult trialResult(this, methodHnd, "prejit1");
                 impCanInlineIL(methodHnd, methodInfo, forceInline, &trialResult);
                 if (trialResult.isFailure())
                 {
@@ -5016,7 +5016,7 @@ int           Compiler::compCompileHelper (CORINFO_MODULE_HANDLE            clas
             assert(compNativeSizeEstimate != NATIVE_SIZE_INVALID); 
 
             int callsiteNativeSizeEstimate = impEstimateCallsiteNativeSize(methodInfo);
-            InlineResult trialResult(this, nullptr, methodHnd, "prejit2");
+            InlineResult trialResult(this, methodHnd, "prejit2");
             
             impCanInlineNative(callsiteNativeSizeEstimate, 
                                compNativeSizeEstimate,
index da751f4..3c3a203 100644 (file)
@@ -2507,6 +2507,12 @@ struct GenTreeCall final : public GenTree
     CORINFO_CONST_LOOKUP gtEntryPoint;
 #endif
 
+#ifdef DEBUG
+    // For non-inline candidates, track the first observation
+    // that blocks candidacy.
+    unsigned gtInlineObservation;
+#endif
+
     GenTreeCall(var_types type) : 
         GenTree(GT_CALL, type) 
     {
index 3ea0d95..e4ea4c8 100644 (file)
@@ -15762,7 +15762,7 @@ void             Compiler::impCanInlineNative(int           callsiteNativeEstima
         }
         else 
         {
-            // Static hint case.... here the "callee" is the function being assessed. (PLS VERIFY)
+            // Static hint case.... here the "callee" is the function being assessed.
             inlineResult->noteFatal(InlineObservation::CALLEE_EXCEEDS_THRESHOLD);
         }
     }
@@ -16816,9 +16816,7 @@ void          Compiler::impMarkInlineCandidate(GenTreePtr callNode, CORINFO_CONT
     }
     
     GenTreeCall* call = callNode->AsCall();
-    CORINFO_METHOD_HANDLE callerHandle = info.compMethodHnd;
-    CORINFO_METHOD_HANDLE calleeHandle = call->gtCall.gtCallType == CT_USER_FUNC ? call->gtCall.gtCallMethHnd : nullptr;
-    InlineResult inlineResult(this, callerHandle, calleeHandle, "impMarkInlineCandidate");
+    InlineResult inlineResult(this, call, "impMarkInlineCandidate");
     
     /* Don't inline if not optimized code */
     if  (opts.compDbgCode)
index f082919..e642964 100644 (file)
@@ -45,7 +45,7 @@ static const InlineImpact InlineImpacts[] =
 // Return Value:
 //    true if the observation is valid
 
-static bool inlIsValidObservation(InlineObservation obs)
+bool inlIsValidObservation(InlineObservation obs)
 {
     return((obs > InlineObservation::CALLEE_UNUSED_INITIAL) &&
            (obs < InlineObservation::CALLEE_UNUSED_FINAL));
@@ -153,6 +153,68 @@ const char* inlGetImpactString(InlineObservation obs)
 }
 
 //------------------------------------------------------------------------
+// InlineResult: Construct an InlineResult to evaluate a particular call
+// for inlining.
+//
+// Arguments
+//   compiler - the compiler instance examining an call for ininling
+//   call     - the call in question
+//   context  - descrptive string to describe the context of the decision
+
+InlineResult::InlineResult(Compiler*    compiler,
+                           GenTreeCall* call,
+                           const char*  context)
+    : inlCompiler(compiler)
+    , inlDecision(InlineDecision::UNDECIDED)
+    , inlObservation(InlineObservation::CALLEE_UNUSED_INITIAL)
+    , inlCall(call)
+    , inlCaller(nullptr)
+    , inlCallee(nullptr)
+    , inlContext(context)
+    , inlReported(false)
+{
+    // Get method handle for caller
+    inlCaller = inlCompiler->info.compMethodHnd;
+
+    // Get method handle for callee, if known
+    if (inlCall->gtCall.gtCallType == CT_USER_FUNC)
+    {
+        inlCallee = call->gtCall.gtCallMethHnd;
+    }
+}
+
+//------------------------------------------------------------------------
+// InlineResult: Construct an InlineResult to evaluate a particular 
+// method as a possible inline candidate.
+//
+// Notes:
+//   Used only during prejitting to try and pre-identify methods
+//   that cannot be inlined, to help subsequent jit throughput.
+//
+//   We use the inlCallee member to track the method since logically
+//   it is the callee here.
+//
+// Arguments
+//   compiler - the compiler instance doing the prejitting
+//   method   - the method in question
+//   context  - descrptive string to describe the context of the decision
+
+InlineResult::InlineResult(Compiler*              compiler,
+                           CORINFO_METHOD_HANDLE  method,
+                           const char*            context)
+    : inlCompiler(compiler)
+    , inlDecision(InlineDecision::UNDECIDED)
+    , inlObservation(InlineObservation::CALLEE_UNUSED_INITIAL)
+    , inlCall(nullptr)
+    , inlCaller(nullptr)
+    , inlCallee(method)
+    , inlContext(context)
+    , inlReported(false)
+{
+    // Empty
+}
+
+//------------------------------------------------------------------------
 // report: Dump, log, and report information about an inline decision.
 //
 // Notes:
@@ -178,22 +240,33 @@ void InlineResult::report()
 
 #ifdef DEBUG
 
+    // Optionally dump the result
     if (VERBOSE)
     {
         const char* format = "INLINER: during '%s' result '%s' reason '%s' for '%s' calling '%s'\n";
-        const char* caller = (inlInliner == nullptr) ? "n/a" : inlCompiler->eeGetMethodFullName(inlInliner);
-        const char* callee = (inlInlinee == nullptr) ? "n/a" : inlCompiler->eeGetMethodFullName(inlInlinee);
+        const char* caller = (inlCaller == nullptr) ? "n/a" : inlCompiler->eeGetMethodFullName(inlCaller);
+        const char* callee = (inlCallee == nullptr) ? "n/a" : inlCompiler->eeGetMethodFullName(inlCallee);
 
         JITDUMP(format, inlContext, resultString(), reasonString(), caller, callee);
     }
 
+    // If the inline failed, leave information on the call so we can
+    // later recover what observation lead to the failure.
+    if (isFailure() && (inlCall != nullptr))
+    {
+        // compiler should have revoked candidacy on the call by now
+        assert((inlCall->gtFlags & GTF_CALL_INLINE_CANDIDATE) == 0);
+
+        inlCall->gtInlineObservation = static_cast<unsigned>(inlObservation);
+    }
+
 #endif // DEBUG
 
-    if (isDecided()) 
+    if (isDecided())
     {
         const char* format = "INLINER: during '%s' result '%s' reason '%s'\n";
         JITLOG_THIS(inlCompiler, (LL_INFO100000, format, inlContext, resultString(), reasonString()));
         COMP_HANDLE comp = inlCompiler->info.compCompHnd;
-        comp->reportInliningDecision(inlInliner, inlInlinee, result(), reasonString());
+        comp->reportInliningDecision(inlCaller, inlCallee, result(), reasonString());
     }
 }
index ef198ee..202b5ca 100644 (file)
@@ -88,6 +88,14 @@ enum class InlineObservation
 #undef INLINE_OBSERVATION
 };
 
+#ifdef DEBUG
+
+// Sanity check the observation value
+
+bool inlIsValidObservation(InlineObservation obs);
+
+#endif // DEBUG
+
 // Get a string describing this observation
 
 const char* inlGetDescriptionString(InlineObservation obs);
@@ -115,21 +123,17 @@ class InlineResult
 {
 public:
 
-    // Construct a new InlineResult.
+    // Construct a new InlineResult to help evaluate a
+    // particular call for inlining.
     InlineResult(Compiler*              compiler,
-                 CORINFO_METHOD_HANDLE  inliner,
-                 CORINFO_METHOD_HANDLE  inlinee,
-                 const char*            context)
-        : inlCompiler(compiler)
-        , inlDecision(InlineDecision::UNDECIDED)
-        , inlObservation(InlineObservation::CALLEE_UNUSED_INITIAL)
-        , inlInliner(inliner)
-        , inlInlinee(inlinee)
-        , inlContext(context)
-        , inlReported(false)
-    {
-        // empty
-    }
+                 GenTreeCall*           call,
+                 const char*            context);
+
+    // Construct a new InlineResult to evaluate a particular
+    // method to see if it is inlineable.
+    InlineResult(Compiler*              compiler,
+                 CORINFO_METHOD_HANDLE  method,
+                 const char*            context);
 
     // Translate into CorInfoInline for reporting back to the runtime.
     //
@@ -315,9 +319,9 @@ public:
     }
 
     // The reason for this particular result
-    const char * reasonString() const 
-    { 
-        return inlGetDescriptionString(inlObservation); 
+    const char * reasonString() const
+    {
+        return inlGetDescriptionString(inlObservation);
     }
 
     // setReported indicates that this particular result doesn't need
@@ -372,7 +376,7 @@ private:
     // Helper for setting decision and reason
     void setCommon(InlineDecision decision, InlineObservation obs)
     {
-        // assert(inlIsValidObservation(obs));
+        assert(inlIsValidObservation(obs));
         assert(decision != InlineDecision::UNDECIDED);
         inlDecision = decision;
         inlObservation = obs;
@@ -384,8 +388,9 @@ private:
     Compiler*               inlCompiler;
     InlineDecision          inlDecision;
     InlineObservation       inlObservation;
-    CORINFO_METHOD_HANDLE   inlInliner;
-    CORINFO_METHOD_HANDLE   inlInlinee;
+    GenTreeCall*            inlCall;
+    CORINFO_METHOD_HANDLE   inlCaller;
+    CORINFO_METHOD_HANDLE   inlCallee;
     const char*             inlContext;
     bool                    inlReported;
 };
index da188f9..86c579c 100644 (file)
@@ -5615,9 +5615,7 @@ bool        Compiler::fgMorphCallInline(GenTreePtr node)
     GenTreeCall* call = node->AsCall();
 
     // Prepare to record information about this inline
-    CORINFO_METHOD_HANDLE callerHandle = call->gtCall.gtInlineCandidateInfo->ilCallerHandle;
-    CORINFO_METHOD_HANDLE calleeHandle = call->gtCall.gtCallType == CT_USER_FUNC ? call->gtCall.gtCallMethHnd : nullptr;
-    InlineResult inlineResult(this, callerHandle, calleeHandle, "fgMorphCallInline");
+    InlineResult inlineResult(this, call, "fgMorphCallInline");
 
     // Attempt the inline
     fgMorphCallInlineHelper(call, &inlineResult);
@@ -5692,6 +5690,19 @@ void Compiler::fgMorphCallInlineHelper(GenTreeCall* call, InlineResult* result)
 
     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 = static_cast<InlineObservation>(call->gtInlineObservation);
+        if (inlIsValidObservation(priorObservation))
+        {
+            currentObservation = priorObservation;
+        }
+#endif
+
         result->noteFatal(InlineObservation::CALLSITE_NOT_CANDIDATE);
         return;
     }