JIT: modify inline budget update to use estimated imported IL size
authorAndy Ayers <andya@microsoft.com>
Tue, 8 Jan 2019 19:27:32 +0000 (11:27 -0800)
committerAndy Ayers <andya@microsoft.com>
Tue, 8 Jan 2019 22:16:43 +0000 (14:16 -0800)
The inliner keeps a time budget to try and avoid pathological runaway inline
behavior (see #4375). The jit estimates the time impact of an inline using a
simple projection based on IL size. If an prospective inline would put the jit
over the time budget, the inline is blocked -- and note even aggressive inlines
can be blocked this way.

We now have a fair number of aggressive inline methods like
`Vector256<T>.IsSupported` where the IL is optimized early on by the jit and the
actual impact on the calling method is much less than the initial IL size would
suggest. For instance `IsSupported` is 286 bytes of IL, but the net contribution
of this method at jit time is either a constant 0 or 1, and so the effective size
is more like 2 bytes of IL.

This set of changes updates the jit to estimate the imported IL size of a method
when updating the budget.

Closes #21794.

src/jit/compiler.cpp
src/jit/compiler.h
src/jit/flowgraph.cpp
src/jit/inline.cpp
src/jit/inline.h

index 40c31c6..d0cb9ac 100644 (file)
@@ -5800,8 +5800,9 @@ int Compiler::compCompileHelper(CORINFO_MODULE_HANDLE            classPtr,
 {
     CORINFO_METHOD_HANDLE methodHnd = info.compMethodHnd;
 
-    info.compCode       = methodInfo->ILCode;
-    info.compILCodeSize = methodInfo->ILCodeSize;
+    info.compCode         = methodInfo->ILCode;
+    info.compILCodeSize   = methodInfo->ILCodeSize;
+    info.compILImportSize = 0;
 
     if (info.compILCodeSize == 0)
     {
index 601c27b..8e50c16 100644 (file)
@@ -8695,6 +8695,7 @@ public:
 
         const BYTE*    compCode;
         IL_OFFSET      compILCodeSize;     // The IL code size
+        IL_OFFSET      compILImportSize;   // Estimated amount of IL actually imported
         UNATIVE_OFFSET compNativeCodeSize; // The native code size, after instructions are issued. This
                                            // is less than (compTotalHotCodeSize + compTotalColdCodeSize) only if:
         // (1) the code is not hot/cold split, and we issued less code than we expected, or
index 4e2fc40..2df3cce 100644 (file)
@@ -6822,6 +6822,42 @@ void Compiler::fgImport()
         verFlag = tiIsVerifiableCode ? CORINFO_FLG_VERIFIABLE : CORINFO_FLG_UNVERIFIABLE;
         info.compCompHnd->setMethodAttribs(info.compMethodHnd, verFlag);
     }
+
+    // Estimate how much of method IL was actually imported.
+    //
+    // Note this includes (to some extent) the impact of importer folded
+    // branches, provided the folded tree covered the entire block's IL.
+    unsigned importedILSize = 0;
+    for (BasicBlock* block = fgFirstBB; block != nullptr; block = block->bbNext)
+    {
+        if ((block->bbFlags & BBF_IMPORTED) != 0)
+        {
+            // Assume if we generate any IR for the block we generate IR for the entire block.
+            if (!block->isEmpty())
+            {
+                unsigned blockILSize = blockILSize = block->bbCodeOffsEnd - block->bbCodeOffs;
+                importedILSize += blockILSize;
+            }
+        }
+    }
+
+    // Could be tripped up if we ever duplicate blocks
+    assert(importedILSize <= info.compILCodeSize);
+
+    // Leave a note if we only did a partial import.
+    if (importedILSize != info.compILCodeSize)
+    {
+        JITDUMP("\n** Note: %s IL was partially imported -- imported %u of %u bytes of method IL\n",
+                compIsForInlining() ? "inlinee" : "root method", importedILSize, info.compILCodeSize);
+    }
+
+    // Record this for diagnostics and for the inliner's budget computations
+    info.compILImportSize = importedILSize;
+
+    if (compIsForInlining())
+    {
+        compInlineResult->SetImportedILSize(info.compILImportSize);
+    }
 }
 
 /*****************************************************************************
index 6bc8482..8435f9a 100644 (file)
@@ -332,6 +332,7 @@ InlineContext::InlineContext(InlineStrategy* strategy)
     , m_Sibling(nullptr)
     , m_Code(nullptr)
     , m_ILSize(0)
+    , m_ImportedILSize(0)
     , m_Offset(BAD_IL_OFFSET)
     , m_Observation(InlineObservation::CALLEE_UNUSED_INITIAL)
     , m_CodeSizeEstimate(0)
@@ -574,6 +575,7 @@ InlineResult::InlineResult(Compiler* compiler, GenTreeCall* call, GenTreeStmt* s
     , m_InlineContext(nullptr)
     , m_Caller(nullptr)
     , m_Callee(nullptr)
+    , m_ImportedILSize(0)
     , m_Description(description)
     , m_Reported(false)
 {
@@ -958,16 +960,16 @@ int InlineStrategy::EstimateTime(InlineContext* context)
 {
     // Simple linear models based on observations
     // show time is fairly well predicted by IL size.
-    unsigned ilSize = context->GetILSize();
-
+    //
     // Prediction varies for root and inlines.
     if (context == m_RootContext)
     {
-        return EstimateRootTime(ilSize);
+        return EstimateRootTime(context->GetILSize());
     }
     else
     {
-        return EstimateInlineTime(ilSize);
+        // Use amount of IL actually imported
+        return EstimateInlineTime(context->GetImportedILSize());
     }
 }
 
@@ -1137,14 +1139,17 @@ void InlineStrategy::NoteOutcome(InlineContext* context)
 }
 
 //------------------------------------------------------------------------
-// BudgetCheck: return true if as inline of this size would exceed the
-// jit time budget for this method
+// BudgetCheck: return true if an inline of this size would likely
+//     exceed the jit time budget for this method
 //
 // Arguments:
 //     ilSize - size of the method's IL
 //
 // Return Value:
 //     true if the inline would go over budget
+//
+// Notes:
+//     Presumes all IL in the method will be imported.
 
 bool InlineStrategy::BudgetCheck(unsigned ilSize)
 {
@@ -1205,15 +1210,16 @@ InlineContext* InlineStrategy::NewSuccess(InlineInfo* inlineInfo)
     calleeContext->m_Parent = parentContext;
     // Push on front here will put siblings in reverse lexical
     // order which we undo in the dumper
-    calleeContext->m_Sibling       = parentContext->m_Child;
-    parentContext->m_Child         = calleeContext;
-    calleeContext->m_Child         = nullptr;
-    calleeContext->m_Offset        = stmt->gtStmtILoffsx;
-    calleeContext->m_Observation   = inlineInfo->inlineResult->GetObservation();
-    calleeContext->m_Success       = true;
-    calleeContext->m_Devirtualized = originalCall->IsDevirtualized();
-    calleeContext->m_Guarded       = originalCall->IsGuarded();
-    calleeContext->m_Unboxed       = originalCall->IsUnboxed();
+    calleeContext->m_Sibling        = parentContext->m_Child;
+    parentContext->m_Child          = calleeContext;
+    calleeContext->m_Child          = nullptr;
+    calleeContext->m_Offset         = stmt->gtStmtILoffsx;
+    calleeContext->m_Observation    = inlineInfo->inlineResult->GetObservation();
+    calleeContext->m_Success        = true;
+    calleeContext->m_Devirtualized  = originalCall->IsDevirtualized();
+    calleeContext->m_Guarded        = originalCall->IsGuarded();
+    calleeContext->m_Unboxed        = originalCall->IsUnboxed();
+    calleeContext->m_ImportedILSize = inlineInfo->inlineResult->GetImportedILSize();
 
 #if defined(DEBUG) || defined(INLINE_DATA)
 
index e94765a..17e3b99 100644 (file)
@@ -470,12 +470,22 @@ public:
         m_Reported = true;
     }
 
-    // Get the InlineContext for this inline
+    // Get the InlineContext for this inline.
     InlineContext* GetInlineContext() const
     {
         return m_InlineContext;
     }
 
+    unsigned GetImportedILSize() const
+    {
+        return m_ImportedILSize;
+    }
+
+    void SetImportedILSize(unsigned x)
+    {
+        m_ImportedILSize = x;
+    }
+
 private:
     // No copying or assignment allowed.
     InlineResult(const InlineResult&) = delete;
@@ -490,6 +500,7 @@ private:
     InlineContext*        m_InlineContext;
     CORINFO_METHOD_HANDLE m_Caller; // immediate caller's handle
     CORINFO_METHOD_HANDLE m_Callee;
+    unsigned              m_ImportedILSize; // estimated size of imported IL
     const char*           m_Description;
     bool                  m_Reported;
 };
@@ -707,6 +718,11 @@ public:
         return m_Unboxed;
     }
 
+    unsigned GetImportedILSize() const
+    {
+        return m_ImportedILSize;
+    }
+
 private:
     InlineContext(InlineStrategy* strategy);
 
@@ -717,6 +733,7 @@ private:
     InlineContext*    m_Sibling;           // next child of the parent
     BYTE*             m_Code;              // address of IL buffer for the method
     unsigned          m_ILSize;            // size of IL buffer for the method
+    unsigned          m_ImportedILSize;    // estimated size of imported IL
     IL_OFFSETX        m_Offset;            // call site location within parent
     InlineObservation m_Observation;       // what lead to this inline
     int               m_CodeSizeEstimate;  // in bytes * 10