Inliner: introduce ReplayPolicy
authorAndy Ayers <andya@microsoft.com>
Tue, 10 May 2016 21:19:41 +0000 (14:19 -0700)
committerAndy Ayers <andya@microsoft.com>
Thu, 12 May 2016 23:39:41 +0000 (16:39 -0700)
The ReplayPolicy reads an external script to determine which inlines
to perform. The script is the same Xml syntax that's produced by
the inliner when JitInlineDumpXml is enabled. This format can be edited
by hand or tool to force particular inlining patterns to occur. Methods
or calls sites not mentioned in the script are considered as noinline.

There's a bunch of work still left to make this fully robust, but in
testing it works well enough for my immediate use case that I'll hold
off on further polish until it's needed. But, for future reference,
here's a laundry list:

* Need better ways to identify methods. Token and hash are not enough.
* Need better ways to identify call sites. Callee token is not enough.
* Consider preparsing or mapping the script into memory.
* Consider caching node positions in the InlineContexts.
* Make it robust for multithreading.
* Handle the prejit root case somehow.
* Possibly allow overriding of inline attributes.

src/jit/compiler.cpp
src/jit/flowgraph.cpp
src/jit/importer.cpp
src/jit/inline.cpp
src/jit/inline.def
src/jit/inline.h
src/jit/inlinepolicy.cpp
src/jit/inlinepolicy.h
src/jit/jitconfigvalues.h

index 3b2c3d6..83853e6 100644 (file)
@@ -683,7 +683,7 @@ void                Compiler::compShutdown()
     emitter::emitDone();
 
 #if defined(DEBUG) || defined(INLINE_DATA)
-    // Finish off any in-progress inline xml
+    // Finish reading and/or writing inline xml
     InlineStrategy::FinalizeXml();
 #endif // defined(DEBUG) || defined(INLINE_DATA)
 
index 2cfed2e..27ed8a4 100644 (file)
@@ -21429,7 +21429,7 @@ void                Compiler::fgInline()
             if ((expr->gtOper == GT_CALL) && ((expr->gtFlags & GTF_CALL_INLINE_CANDIDATE) != 0))
             {
                 GenTreeCall* call = expr->AsCall();
-                InlineResult inlineResult(this, call, "fgInline");
+                InlineResult inlineResult(this, call, stmt->gtInlineContext, "fgInline");
 
                 fgMorphStmt = stmt;
 
@@ -21555,7 +21555,7 @@ Compiler::fgWalkResult      Compiler::fgFindNonInlineCandidate(GenTreePtr* pTree
 void Compiler::fgNoteNonInlineCandidate(GenTreePtr   tree,
                                         GenTreeCall* call)
 {
-    InlineResult inlineResult(this, call, "fgNotInlineCandidate");
+    InlineResult inlineResult(this, call, nullptr, "fgNotInlineCandidate");
     InlineObservation currentObservation = InlineObservation::CALLSITE_NOT_CANDIDATE;
 
     // Try and recover the reason left behind when the jit decided
index 3ba0cd0..a97e7c3 100644 (file)
@@ -16461,7 +16461,7 @@ void          Compiler::impMarkInlineCandidate(GenTreePtr callNode,
     }
     
     GenTreeCall* call = callNode->AsCall();
-    InlineResult inlineResult(this, call, "impMarkInlineCandidate");
+    InlineResult inlineResult(this, call, nullptr, "impMarkInlineCandidate");
     
     // Don't inline if not optimizing root method
     if (opts.compDbgCode)
index 1f728dd..1acea8e 100644 (file)
@@ -7,6 +7,8 @@
 #pragma hdrstop
 #endif
 
+#include "inlinepolicy.h"
+
 // Lookup table for inline description strings
 
 static const char* InlineDescriptions[] =
@@ -441,7 +443,7 @@ void InlineContext::DumpData(unsigned indent)
     if (m_Parent == nullptr)
     {
         // Root method... cons up a policy so we can display the name
-        InlinePolicy* policy = InlinePolicy::GetPolicy(compiler, true);
+        InlinePolicy* policy = InlinePolicy::GetPolicy(compiler, nullptr, true);
         printf("\nInlines [%u] into \"%s\" [%s]\n",
                m_InlineStrategy->GetInlineCount(),
                calleeName,
@@ -531,20 +533,23 @@ void InlineContext::DumpXml(FILE* file, unsigned indent)
 // InlineResult: Construct an InlineResult to evaluate a particular call
 // for inlining.
 //
-// Arguments
-//   compiler - the compiler instance examining a call for inlining
-//   call     - the call in question
-//   context  - descrptive string to describe the context of the decision
-
-InlineResult::InlineResult(Compiler*    compiler,
-                           GenTreeCall* call,
-                           const char*  context)
+// Arguments:
+//   compiler      - the compiler instance examining a call for inlining
+//   call          - the call in question
+//   inlineContext - the inline context for the inline, if known
+//   description   - string describing the context of the decision
+
+InlineResult::InlineResult(Compiler*      compiler,
+                           GenTreeCall*   call,
+                           InlineContext* inlineContext,
+                           const char*    description)
     : m_RootCompiler(nullptr)
     , m_Policy(nullptr)
     , m_Call(call)
+    , m_InlineContext(inlineContext)
     , m_Caller(nullptr)
     , m_Callee(nullptr)
-    , m_Context(context)
+    , m_Description(description)
     , m_Reported(false)
 {
     // Set the compiler instance
@@ -552,7 +557,7 @@ InlineResult::InlineResult(Compiler*    compiler,
 
     // Set the policy
     const bool isPrejitRoot = false;
-    m_Policy = InlinePolicy::GetPolicy(m_RootCompiler, isPrejitRoot);
+    m_Policy = InlinePolicy::GetPolicy(m_RootCompiler, m_InlineContext, isPrejitRoot);
 
     // Get method handle for caller. Note we use the
     // handle for the "immediate" caller here.
@@ -570,9 +575,9 @@ InlineResult::InlineResult(Compiler*    compiler,
 // method as a possible inline candidate, while prejtting.
 //
 // Arguments:
-//    compiler - the compiler instance doing the prejitting
-//    method   - the method in question
-//    context  - descrptive string to describe the context of the decision
+//    compiler    - the compiler instance doing the prejitting
+//    method      - the method in question
+//    description - string describing the context of the decision
 //
 // Notes:
 //    Used only during prejitting to try and pre-identify methods that
@@ -583,13 +588,14 @@ InlineResult::InlineResult(Compiler*    compiler,
 
 InlineResult::InlineResult(Compiler*              compiler,
                            CORINFO_METHOD_HANDLE  method,
-                           const char*            context)
+                           const char*            description)
     : m_RootCompiler(nullptr)
     , m_Policy(nullptr)
     , m_Call(nullptr)
+    , m_InlineContext(nullptr)
     , m_Caller(nullptr)
     , m_Callee(method)
-    , m_Context(context)
+    , m_Description(description)
     , m_Reported(false)
 {
     // Set the compiler instance
@@ -597,7 +603,7 @@ InlineResult::InlineResult(Compiler*              compiler,
 
     // Set the policy
     const bool isPrejitRoot = true;
-    m_Policy = InlinePolicy::GetPolicy(m_RootCompiler, isPrejitRoot);
+    m_Policy = InlinePolicy::GetPolicy(m_RootCompiler, nullptr, isPrejitRoot);
 }
 
 //------------------------------------------------------------------------
@@ -637,7 +643,7 @@ void InlineResult::Report()
 
         callee = (m_Callee == nullptr) ? "n/a" : m_RootCompiler->eeGetMethodFullName(m_Callee);
 
-        JITDUMP(format, m_Context, ResultString(), ReasonString(), caller, callee);
+        JITDUMP(format, m_Description, ResultString(), ReasonString(), caller, callee);
     }
 
     // If the inline failed, leave information on the call so we can
@@ -686,7 +692,7 @@ void InlineResult::Report()
     if (IsDecided())
     {
         const char* format = "INLINER: during '%s' result '%s' reason '%s'\n";
-        JITLOG_THIS(m_RootCompiler, (LL_INFO100000, format, m_Context, ResultString(), ReasonString()));
+        JITLOG_THIS(m_RootCompiler, (LL_INFO100000, format, m_Description, ResultString(), ReasonString()));
         COMP_HANDLE comp = m_RootCompiler->info.compCompHnd;
         comp->reportInliningDecision(m_Caller, m_Callee, Result(), ReasonString());
     }
@@ -1214,7 +1220,7 @@ void InlineStrategy::DumpData()
     {
         assert(limit <= 0);
         const bool isPrejitRoot = (opts.eeFlags & CORJIT_FLG_PREJIT) != 0;
-        m_LastSuccessfulPolicy = InlinePolicy::GetPolicy(m_Compiler, isPrejitRoot);
+        m_LastSuccessfulPolicy = InlinePolicy::GetPolicy(m_Compiler, nullptr, isPrejitRoot);
 
         // Add in a bit of data....
         const bool isForceInline = (info.compFlags & CORINFO_FLG_FORCEINLINE) != 0;
@@ -1371,6 +1377,9 @@ void InlineStrategy::FinalizeXml(FILE* file)
         // Workaroud compShutdown getting called twice.
         s_HasDumpedXmlHeader = false;
     }
+
+    // Finalize reading inline xml
+    ReplayPolicy::FinalizeXml();
 }
 
 #endif // defined(DEBUG) || defined(INLINE_DATA)
index cd4f98a..4ee03bb 100644 (file)
@@ -49,6 +49,7 @@ INLINE_OBSERVATION(IS_SYNCHRONIZED,           bool,   "is synchronized",
 INLINE_OBSERVATION(IS_VM_NOINLINE,            bool,   "noinline per VM",               FATAL,       CALLEE)
 INLINE_OBSERVATION(LACKS_RETURN,              bool,   "no return opcode",              FATAL,       CALLEE)
 INLINE_OBSERVATION(LDFLD_NEEDS_HELPER,        bool,   "ldfld needs helper",            FATAL,       CALLEE)
+INLINE_OBSERVATION(LOG_REPLAY_REJECT,         bool,   "rejected by log replay",        FATAL,       CALLEE)
 INLINE_OBSERVATION(MARKED_AS_SKIPPED,         bool,   "skipped by complus request",    FATAL,       CALLEE)
 INLINE_OBSERVATION(MAXSTACK_TOO_BIG,          bool,   "maxstack too big"  ,            FATAL,       CALLEE)
 INLINE_OBSERVATION(NEEDS_SECURITY_CHECK,      bool,   "needs security check",          FATAL,       CALLEE)
@@ -84,6 +85,7 @@ INLINE_OBSERVATION(IS_FORCE_INLINE,           bool,   "aggressive inline attribu
 INLINE_OBSERVATION(IS_INSTANCE_CTOR,          bool,   "instance constructor",          INFORMATION, CALLEE)
 INLINE_OBSERVATION(IS_PROFITABLE_INLINE,      bool,   "profitable inline",             INFORMATION, CALLEE)
 INLINE_OBSERVATION(IS_SIZE_DECREASING_INLINE, bool,   "size decreasing inline",        INFORMATION, CALLEE)
+INLINE_OBSERVATION(LOG_REPLAY_ACCEPT,         bool,   "accepted by log replay",        INFORMATION, CALLEE)
 INLINE_OBSERVATION(LOOKS_LIKE_WRAPPER,        bool,   "thin wrapper around a call",    INFORMATION, CALLEE)
 INLINE_OBSERVATION(MAXSTACK,                  int,    "maxstack",                      INFORMATION, CALLEE)
 INLINE_OBSERVATION(OPCODE,                    int,    "next opcode in IL stream",      INFORMATION, CALLEE)
@@ -136,6 +138,7 @@ INLINE_OBSERVATION(IS_WITHIN_FILTER,          bool,   "within filterregion",
 INLINE_OBSERVATION(LDARGA_NOT_LOCAL_VAR,      bool,   "ldarga not on local var",       FATAL,       CALLSITE)
 INLINE_OBSERVATION(LDFLD_NEEDS_HELPER,        bool,   "ldfld needs helper",            FATAL,       CALLSITE)
 INLINE_OBSERVATION(LDVIRTFN_ON_NON_VIRTUAL,   bool,   "ldvirtfn on non-virtual",       FATAL,       CALLSITE)
+INLINE_OBSERVATION(LOG_REPLAY_REJECT,         bool,   "rejected by log replay",        FATAL,       CALLSITE)
 INLINE_OBSERVATION(NOT_CANDIDATE,             bool,   "not inline candidate",          FATAL,       CALLSITE)
 INLINE_OBSERVATION(NOT_PROFITABLE_INLINE,     bool,   "unprofitable inline",           FATAL,       CALLSITE)
 INLINE_OBSERVATION(OVER_BUDGET,               bool,   "inline exceeds budget",         FATAL,       CALLSITE)
@@ -156,6 +159,7 @@ INLINE_OBSERVATION(DEPTH,                     int,    "depth",
 INLINE_OBSERVATION(FREQUENCY,                 int,    "execution frequency",           INFORMATION, CALLSITE)
 INLINE_OBSERVATION(IS_PROFITABLE_INLINE,      bool,   "profitable inline",             INFORMATION, CALLSITE)
 INLINE_OBSERVATION(IS_SIZE_DECREASING_INLINE, bool,   "size decreasing inline",        INFORMATION, CALLSITE)
+INLINE_OBSERVATION(LOG_REPLAY_ACCEPT,         bool,   "accepted by log replay",        INFORMATION, CALLSITE)
 INLINE_OBSERVATION(RANDOM_ACCEPT,             bool,   "random accept",                 INFORMATION, CALLSITE)
 
 // ------ Final Sentinel ------- 
index 19c3e2d..cac0fc8 100644 (file)
@@ -218,7 +218,7 @@ class InlinePolicy
 public:
 
     // Factory method for getting policies
-    static InlinePolicy* GetPolicy(Compiler* compiler, bool isPrejitRoot);
+    static InlinePolicy* GetPolicy(Compiler* compiler, InlineContext* context, bool isPrejitRoot);
 
     // Obligatory virtual dtor
     virtual ~InlinePolicy() {}
@@ -289,13 +289,14 @@ public:
     // particular call for inlining.
     InlineResult(Compiler*              compiler,
                  GenTreeCall*           call,
-                 const char*            context);
+                 InlineContext*         inlineContext,
+                 const char*            description);
 
     // 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);
+                 const char*            description);
 
     // Has the policy determined this inline should fail?
     bool IsFailure() const
@@ -442,6 +443,12 @@ public:
         m_Reported = true;
     }
 
+    // Get the InlineContext for this inline
+    InlineContext* GetInlineContext() const
+    {
+        return m_InlineContext;
+    }
+
 private:
 
     // No copying or assignment allowed.
@@ -454,9 +461,10 @@ private:
     Compiler*               m_RootCompiler;
     InlinePolicy*           m_Policy;
     GenTreeCall*            m_Call;
+    InlineContext*          m_InlineContext;
     CORINFO_METHOD_HANDLE   m_Caller;     // immediate caller's handle
     CORINFO_METHOD_HANDLE   m_Callee;
-    const char*             m_Context;
+    const char*             m_Description;
     bool                    m_Reported;
 };
 
@@ -570,6 +578,12 @@ public:
     // Dump full subtree in xml format
     void DumpXml(FILE* file = stderr, unsigned indent = 0);
 
+    // Get callee handle
+    CORINFO_METHOD_HANDLE GetCallee() const
+    {
+        return m_Callee;
+    }
+
 #endif // defined(DEBUG) || defined(INLINE_DATA)
 
     // Get the parent context for this context.
@@ -591,7 +605,7 @@ public:
     }
 
     // Get the observation that supported or disqualified this inline.
-    InlineObservation GetObservation()
+    InlineObservation GetObservation() const
     {
         return m_Observation;
     }
@@ -608,6 +622,12 @@ public:
         return m_CodeSizeEstimate;
     }
 
+    // True if this is the root context
+    bool IsRoot() const
+    {
+        return m_Parent == nullptr;
+    }
+
 private:
 
     InlineContext(InlineStrategy* strategy);
index 0e4b436..deaa849 100644 (file)
 // getPolicy: Factory method for getting an InlinePolicy
 //
 // Arguments:
-//    compiler     - the compiler instance that will evaluate inlines
-//    isPrejitRoot - true if this policy is evaluating a prejit root
+//    compiler      - the compiler instance that will evaluate inlines
+//    inlineContext - the context of the inline
+//    isPrejitRoot  - true if this policy is evaluating a prejit root
 //
 // Return Value:
-//    InlinePolicy to use in evaluating the inlines
+//    InlinePolicy to use in evaluating an inline.
 //
 // Notes:
 //    Determines which of the various policies should apply,
 //    and creates (or reuses) a policy instance to use.
 
-InlinePolicy* InlinePolicy::GetPolicy(Compiler* compiler, bool isPrejitRoot)
+InlinePolicy* InlinePolicy::GetPolicy(Compiler* compiler, InlineContext* inlineContext, bool isPrejitRoot)
 {
 
+    // inlineContext only conditionally used below.
+    (void) inlineContext;
+
 #ifdef DEBUG
 
     // Optionally install the RandomPolicy.
@@ -43,6 +47,14 @@ InlinePolicy* InlinePolicy::GetPolicy(Compiler* compiler, bool isPrejitRoot)
 
 #if defined(DEBUG) || defined(INLINE_DATA)
 
+    // Optionally install the ReplayPolicy.
+    bool useReplayPolicy = JitConfig.JitInlinePolicyReplay() != 0;
+
+    if (useReplayPolicy)
+    {
+        return new (compiler, CMK_Inlining) ReplayPolicy(compiler, inlineContext, isPrejitRoot);
+    }
+
     // Optionally install the SizePolicy.
     bool useSizePolicy = JitConfig.JitInlinePolicySize() != 0;
 
@@ -1943,14 +1955,14 @@ void FullPolicy::DetermineProfitability(CORINFO_METHOD_INFO* methodInfo)
 
     unsigned depthLimit = m_RootCompiler->m_inlineStrategy->GetMaxInlineDepth();
 
-    if (m_Depth > depthLimit) 
+    if (m_Depth > depthLimit)
     {
         SetFailure(InlineObservation::CALLSITE_IS_TOO_DEEP);
         return;
     }
 
     // Check size
-    
+
     unsigned sizeLimit = m_RootCompiler->m_inlineStrategy->GetMaxInlineILSize();
 
     if (m_CodeSize > sizeLimit)
@@ -2042,4 +2054,368 @@ void SizePolicy::DetermineProfitability(CORINFO_METHOD_INFO* methodInfo)
     return;
 }
 
+bool  ReplayPolicy::s_WroteReplayBanner = false;
+FILE* ReplayPolicy::s_ReplayFile = nullptr;
+
+//------------------------------------------------------------------------/
+// ReplayPolicy: construct a new ReplayPolicy
+//
+// Arguments:
+//    compiler -- compiler instance doing the inlining (root compiler)
+//    inlineContext -- inline context for the inline
+//    isPrejitRoot -- true if this compiler is prejitting the root method
+
+ReplayPolicy::ReplayPolicy(Compiler* compiler, InlineContext* inlineContext, bool isPrejitRoot)
+    : DiscretionaryPolicy(compiler, isPrejitRoot)
+    , m_InlineContext(inlineContext)
+{
+    // Is there a log file open already? If so, we can use it.
+    if (s_ReplayFile == nullptr)
+    {
+        // Did we already try and open and fail?
+        if (!s_WroteReplayBanner)
+        {
+            // Nope, open it up.
+            const wchar_t* replayFileName = JitConfig.JitInlineReplayFile();
+            s_ReplayFile = _wfopen(replayFileName, W("r"));
+            fprintf(stderr, "*** %s inlines from %ws",
+                    s_ReplayFile == nullptr ? "Unable to replay" : "Replaying",
+                    replayFileName);
+            s_WroteReplayBanner = true;
+        }
+    }
+}
+
+//------------------------------------------------------------------------
+// ReplayPolicy: Finalize reading of inline Xml
+//
+// Notes:
+//    Called during jitShutdown()
+
+void ReplayPolicy::FinalizeXml()
+{
+    if (s_ReplayFile != nullptr)
+    {
+        fclose(s_ReplayFile);
+        s_ReplayFile = nullptr;
+    }
+}
+
+//------------------------------------------------------------------------
+// FindMethod: find the root method in the inline Xml
+//
+// ReturnValue:
+//    true if found. File position left pointing just after the
+//    <Token> entry for the method.
+
+bool ReplayPolicy::FindMethod()
+{
+    const mdMethodDef methodToken =
+        m_RootCompiler->info.compCompHnd->getMethodDefFromMethod(
+            m_RootCompiler->info.compMethodHnd);
+    const unsigned methodHash =
+        m_RootCompiler->info.compMethodHash();
+
+    if (s_ReplayFile == nullptr)
+    {
+        return false;
+    }
+
+    bool foundMethod = false;
+    char buffer[256];
+    fseek(s_ReplayFile, 0, SEEK_SET);
+
+    while (!foundMethod)
+    {
+        // Get next line
+        if (fgets(buffer, sizeof(buffer), s_ReplayFile) == nullptr)
+        {
+            break;
+        }
+
+        // Look for next method entry
+        if (strstr(buffer, "<Method>") == nullptr)
+        {
+            continue;
+        }
+
+        // Get next line
+        if (fgets(buffer, sizeof(buffer), s_ReplayFile) == nullptr)
+        {
+            break;
+        }
+
+        // See if token matches
+        unsigned token = 0;
+        int count = sscanf(buffer, " <Token>%u</Token> ", &token);
+        if ((count != 1) || (token != methodToken))
+        {
+            continue;
+        }
+
+        // Get next line
+        if (fgets(buffer, sizeof(buffer), s_ReplayFile) == nullptr)
+        {
+            break;
+        }
+
+        // See if hash matches
+        unsigned hash = 0;
+        count = sscanf(buffer, " <Hash>%u</Hash> ", &hash);
+        if ((count != 1) || (hash != methodHash))
+        {
+            continue;
+        }
+
+        // Found a match...
+        foundMethod = true;
+        break;
+    }
+
+    return foundMethod;
+}
+
+//------------------------------------------------------------------------
+// FindContext: find an inline context in the inline Xml
+//
+// Notes:
+//    Assumes file position within the relevant method has just been
+//    set by a successful call to FindMethod().
+//
+// Arguments:
+//    context -- context of interest
+//
+// ReturnValue:
+//    true if found. File position left pointing just after the
+//    <Token> entry for the context.
+
+bool ReplayPolicy::FindContext(InlineContext* context)
+{
+    // Make sure we've found the parent context.
+    if (context->IsRoot())
+    {
+        // We've already found the method context so we're good.
+        return true;
+    }
+
+    bool foundParent = FindContext(context->GetParent());
+
+    if (!foundParent)
+    {
+        return false;
+    }
+
+    // File pointer should be pointing at the parent context level.
+    // See if we see an inline entry for this context.
+    //
+    // Token we're looking for.
+    mdMethodDef contextToken =
+        m_RootCompiler->info.compCompHnd->getMethodDefFromMethod(
+            context->GetCallee());
+
+    return FindInline(contextToken);
+}
+
+//------------------------------------------------------------------------
+// FindInline: find entry for the current inline in inline Xml.
+//
+// Arguments:
+//    token -- token describing the inline
+//
+// ReturnValue:
+//    true if the inline entry was found
+//
+// Notes:
+//    Assumes file position has just been set by a successful call to
+//    FindMethod or FindContext.
+//
+//    Token will not be sufficiently unique to identify a particular
+//    inline, if there are multiple calls to the same method.
+
+bool ReplayPolicy::FindInline(unsigned token)
+{
+    char buffer[256];
+    bool foundInline = false;
+    int  depth = 0;
+
+    while (!foundInline)
+    {
+        // Get next line
+        if (fgets(buffer, sizeof(buffer), s_ReplayFile) == nullptr)
+        {
+            break;
+        }
+
+        // If we hit </Method> we've gone too far,
+        // and the XML is messed up.
+        if (strstr(buffer, "</Method>") != nullptr)
+        {
+            break;
+        }
+
+        // Look for <Inlines />....
+        if (strstr(buffer, "<Inlines />") != nullptr)
+        {
+            if (depth == 0)
+            {
+                // Exited depth 1, failed to find the context
+                break;
+            }
+            else
+            {
+                // Exited nested, keep looking
+                continue;
+            }
+        }
+
+        // Look for <Inlines>....
+        if (strstr(buffer, "<Inlines>") != nullptr)
+        {
+            depth++;
+            continue;
+        }
+
+        // If we hit </Inlines> we've exited a nested entry
+        // or the current entry.
+        if (strstr(buffer, "</Inlines>") != nullptr)
+        {
+            depth--;
+
+            if (depth == 0)
+            {
+                // Exited depth 1, failed to find the context
+                break;
+            }
+            else
+            {
+                // Exited nested, keep looking
+                continue;
+            }
+        }
+
+        // Look for start of inline section at the right depth
+        if ((depth != 1) || (strstr(buffer, "<Inline>") == nullptr))
+        {
+            continue;
+        }
+
+        // Get next line
+        if (fgets(buffer, sizeof(buffer), s_ReplayFile) == nullptr)
+        {
+            break;
+        }
+
+        unsigned inlineToken = 0;
+        int count = sscanf(buffer, " <Token>%u</Token> ", &inlineToken);
+
+        // Need a secondary check here for callsite
+        // ID... offset or similar. Hash would be nice too.
+        if ((count != 1) || (inlineToken != token))
+        {
+            continue;
+        }
+
+        // We're good!
+        foundInline = true;
+        break;
+    }
+
+    return foundInline;
+}
+
+//------------------------------------------------------------------------
+// FindInline: find entry for a particular callee in inline Xml.
+//
+// Arguments:
+//    callee -- handle for the callee method
+//
+// ReturnValue:
+//    true if the inline should be performed.
+//
+// Notes:
+//    Assumes file position has just been set by a successful call to
+//    FindContext(...);
+//
+//    callee handle will not be sufficiently unique to identify a
+//    particular inline, if there are multiple calls to the same
+//    method.
+
+bool ReplayPolicy::FindInline(CORINFO_METHOD_HANDLE callee)
+{
+    // Token we're looking for
+    mdMethodDef calleeToken =
+        m_RootCompiler->info.compCompHnd->getMethodDefFromMethod(callee);
+
+    bool foundInline = FindInline(calleeToken);
+
+    return foundInline;
+}
+
+//------------------------------------------------------------------------
+// DetermineProfitability: determine if this inline is profitable
+//
+// Arguments:
+//    methodInfo -- method info for the callee
+
+void ReplayPolicy::DetermineProfitability(CORINFO_METHOD_INFO* methodInfo)
+{
+    // TODO: handle prejit root case....need to record this in the
+    // root method XML.
+    if (m_IsPrejitRoot)
+    {
+        // Fall back to discretionary policy for now.
+        return DiscretionaryPolicy::DetermineProfitability(methodInfo);
+    }
+
+    // Otherwise try and find this candiate in the Xml. If we fail
+    // the don't inline.
+    bool accept = false;
+
+    // First, locate the entries for the root method.
+    bool foundMethod = FindMethod();
+
+    if (foundMethod && (m_InlineContext != nullptr))
+    {
+        // Next, navigate the context tree to find the entries
+        // for the context that contains this candidate.
+        bool foundContext = FindContext(m_InlineContext);
+
+        if (foundContext)
+        {
+            // Finally, find this candidate within its context
+            CORINFO_METHOD_HANDLE calleeHandle = methodInfo->ftn;
+            accept = FindInline(calleeHandle);
+        }
+    }
+
+    if (accept)
+    {
+        JITLOG_THIS(m_RootCompiler, (LL_INFO100000, "Inline accepted via log replay"))
+
+        if (m_IsPrejitRoot)
+        {
+            SetCandidate(InlineObservation::CALLEE_LOG_REPLAY_ACCEPT);
+        }
+        else
+        {
+            SetCandidate(InlineObservation::CALLSITE_LOG_REPLAY_ACCEPT);
+        }
+    }
+    else
+    {
+        JITLOG_THIS(m_RootCompiler, (LL_INFO100000, "Inline rejected via log replay"))
+
+        if (m_IsPrejitRoot)
+        {
+            SetNever(InlineObservation::CALLEE_LOG_REPLAY_REJECT);
+        }
+        else
+        {
+            SetFailure(InlineObservation::CALLSITE_LOG_REPLAY_REJECT);
+        }
+    }
+
+    return;
+}
+
 #endif // defined(DEBUG) || defined(INLINE_DATA)
index 31a686e..29e6af4 100644 (file)
@@ -338,6 +338,35 @@ public:
     const char* GetName() const override { return "SizePolicy"; }
 };
 
+// The ReplayPolicy performs only inlines specified by an external
+// inline replay log.
+
+class ReplayPolicy : public DiscretionaryPolicy
+{
+public:
+
+    // Construct a ReplayPolicy
+    ReplayPolicy(Compiler* compiler, InlineContext* inlineContext, bool isPrejitRoot);
+
+    // Policy determinations
+    void DetermineProfitability(CORINFO_METHOD_INFO* methodInfo) override;
+
+    // Miscellaneous
+    const char* GetName() const override { return "ReplayPolicy"; }
+
+    static void FinalizeXml();
+
+private:
+
+    bool FindMethod();
+    bool FindContext(InlineContext* context);
+    bool FindInline(CORINFO_METHOD_HANDLE callee);
+    bool FindInline(unsigned token);
+
+    static bool    s_WroteReplayBanner;
+    static FILE*   s_ReplayFile;
+    InlineContext* m_InlineContext;
+};
 
 #endif // defined(DEBUG) || defined(INLINE_DATA)
 
index cb4724d..2fe24df 100644 (file)
@@ -198,7 +198,9 @@ CONFIG_INTEGER(JitInlinePolicyDiscretionary, W("JitInlinePolicyDiscretionary"),
 CONFIG_INTEGER(JitInlinePolicyModel, W("JitInlinePolicyModel"), 0)
 CONFIG_INTEGER(JitInlinePolicyFull, W("JitInlinePolicyFull"), 0)
 CONFIG_INTEGER(JitInlinePolicySize, W("JitInlinePolicySize"), 0)
+CONFIG_INTEGER(JitInlinePolicyReplay, W("JitInlinePolicyReplay"), 0)
 CONFIG_STRING(JitNoInlineRange, W("JitNoInlineRange"))
+CONFIG_STRING(JitInlineReplayFile, W("JitInlineReplayFile"))
 #endif // defined(DEBUG) || defined(INLINE_DATA)
 
 #undef CONFIG_INTEGER