summaryrefslogtreecommitdiff
path: root/src/jit/inline.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/jit/inline.cpp')
-rw-r--r--src/jit/inline.cpp1640
1 files changed, 1640 insertions, 0 deletions
diff --git a/src/jit/inline.cpp b/src/jit/inline.cpp
new file mode 100644
index 0000000000..deccc0e84b
--- /dev/null
+++ b/src/jit/inline.cpp
@@ -0,0 +1,1640 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#include "jitpch.h"
+#ifdef _MSC_VER
+#pragma hdrstop
+#endif
+
+#include "inlinepolicy.h"
+
+// Lookup table for inline description strings
+
+static const char* InlineDescriptions[] = {
+#define INLINE_OBSERVATION(name, type, description, impact, target) description,
+#include "inline.def"
+#undef INLINE_OBSERVATION
+};
+
+// Lookup table for inline targets
+
+static const InlineTarget InlineTargets[] = {
+#define INLINE_OBSERVATION(name, type, description, impact, target) InlineTarget::target,
+#include "inline.def"
+#undef INLINE_OBSERVATION
+};
+
+// Lookup table for inline impacts
+
+static const InlineImpact InlineImpacts[] = {
+#define INLINE_OBSERVATION(name, type, description, impact, target) InlineImpact::impact,
+#include "inline.def"
+#undef INLINE_OBSERVATION
+};
+
+#ifdef DEBUG
+
+//------------------------------------------------------------------------
+// InlIsValidObservation: run a validity check on an inline observation
+//
+// Arguments:
+// obs - the observation in question
+//
+// Return Value:
+// true if the observation is valid
+
+bool InlIsValidObservation(InlineObservation obs)
+{
+ return ((obs > InlineObservation::CALLEE_UNUSED_INITIAL) && (obs < InlineObservation::CALLEE_UNUSED_FINAL));
+}
+
+#endif // DEBUG
+
+//------------------------------------------------------------------------
+// InlGetObservationString: get a string describing this inline observation
+//
+// Arguments:
+// obs - the observation in question
+//
+// Return Value:
+// string describing the observation
+
+const char* InlGetObservationString(InlineObservation obs)
+{
+ assert(InlIsValidObservation(obs));
+ return InlineDescriptions[static_cast<int>(obs)];
+}
+
+//------------------------------------------------------------------------
+// InlGetTarget: get the target of an inline observation
+//
+// Arguments:
+// obs - the observation in question
+//
+// Return Value:
+// enum describing the target
+
+InlineTarget InlGetTarget(InlineObservation obs)
+{
+ assert(InlIsValidObservation(obs));
+ return InlineTargets[static_cast<int>(obs)];
+}
+
+//------------------------------------------------------------------------
+// InlGetTargetString: get a string describing the target of an inline observation
+//
+// Arguments:
+// obs - the observation in question
+//
+// Return Value:
+// string describing the target
+
+const char* InlGetTargetString(InlineObservation obs)
+{
+ InlineTarget t = InlGetTarget(obs);
+ switch (t)
+ {
+ case InlineTarget::CALLER:
+ return "caller";
+ case InlineTarget::CALLEE:
+ return "callee";
+ case InlineTarget::CALLSITE:
+ return "call site";
+ default:
+ return "unexpected target";
+ }
+}
+
+//------------------------------------------------------------------------
+// InlGetImpact: get the impact of an inline observation
+//
+// Arguments:
+// obs - the observation in question
+//
+// Return Value:
+// enum value describing the impact
+
+InlineImpact InlGetImpact(InlineObservation obs)
+{
+ assert(InlIsValidObservation(obs));
+ return InlineImpacts[static_cast<int>(obs)];
+}
+
+//------------------------------------------------------------------------
+// InlGetImpactString: get a string describing the impact of an inline observation
+//
+// Arguments:
+// obs - the observation in question
+//
+// Return Value:
+// string describing the impact
+
+const char* InlGetImpactString(InlineObservation obs)
+{
+ InlineImpact i = InlGetImpact(obs);
+ switch (i)
+ {
+ case InlineImpact::FATAL:
+ return "correctness -- fatal";
+ case InlineImpact::FUNDAMENTAL:
+ return "correctness -- fundamental limitation";
+ case InlineImpact::LIMITATION:
+ return "correctness -- jit limitation";
+ case InlineImpact::PERFORMANCE:
+ return "performance";
+ case InlineImpact::INFORMATION:
+ return "information";
+ default:
+ return "unexpected impact";
+ }
+}
+
+//------------------------------------------------------------------------
+// InlGetCorInfoInlineDecision: translate decision into a CorInfoInline
+//
+// Arguments:
+// d - the decision in question
+//
+// Return Value:
+// CorInfoInline value representing the decision
+
+CorInfoInline InlGetCorInfoInlineDecision(InlineDecision d)
+{
+ switch (d)
+ {
+ case InlineDecision::SUCCESS:
+ return INLINE_PASS;
+ case InlineDecision::FAILURE:
+ return INLINE_FAIL;
+ case InlineDecision::NEVER:
+ return INLINE_NEVER;
+ default:
+ assert(!"Unexpected InlineDecision");
+ unreached();
+ }
+}
+
+//------------------------------------------------------------------------
+// InlGetDecisionString: get a string representing this decision
+//
+// Arguments:
+// d - the decision in question
+//
+// Return Value:
+// string representing the decision
+
+const char* InlGetDecisionString(InlineDecision d)
+{
+ switch (d)
+ {
+ case InlineDecision::SUCCESS:
+ return "success";
+ case InlineDecision::FAILURE:
+ return "failed this call site";
+ case InlineDecision::NEVER:
+ return "failed this callee";
+ case InlineDecision::CANDIDATE:
+ return "candidate";
+ case InlineDecision::UNDECIDED:
+ return "undecided";
+ default:
+ assert(!"Unexpected InlineDecision");
+ unreached();
+ }
+}
+
+//------------------------------------------------------------------------
+// InlDecisionIsFailure: check if this decision describes a failing inline
+//
+// Arguments:
+// d - the decision in question
+//
+// Return Value:
+// true if the inline is definitely a failure
+
+bool InlDecisionIsFailure(InlineDecision d)
+{
+ switch (d)
+ {
+ case InlineDecision::SUCCESS:
+ case InlineDecision::UNDECIDED:
+ case InlineDecision::CANDIDATE:
+ return false;
+ case InlineDecision::FAILURE:
+ case InlineDecision::NEVER:
+ return true;
+ default:
+ assert(!"Unexpected InlineDecision");
+ unreached();
+ }
+}
+
+//------------------------------------------------------------------------
+// InlDecisionIsSuccess: check if this decision describes a sucessful inline
+//
+// Arguments:
+// d - the decision in question
+//
+// Return Value:
+// true if the inline is definitely a success
+
+bool InlDecisionIsSuccess(InlineDecision d)
+{
+ switch (d)
+ {
+ case InlineDecision::SUCCESS:
+ return true;
+ case InlineDecision::FAILURE:
+ case InlineDecision::NEVER:
+ case InlineDecision::UNDECIDED:
+ case InlineDecision::CANDIDATE:
+ return false;
+ default:
+ assert(!"Unexpected InlineDecision");
+ unreached();
+ }
+}
+
+//------------------------------------------------------------------------
+// InlDecisionIsNever: check if this decision describes a never inline
+//
+// Arguments:
+// d - the decision in question
+//
+// Return Value:
+// true if the inline is a never inline case
+
+bool InlDecisionIsNever(InlineDecision d)
+{
+ switch (d)
+ {
+ case InlineDecision::NEVER:
+ return true;
+ case InlineDecision::FAILURE:
+ case InlineDecision::SUCCESS:
+ case InlineDecision::UNDECIDED:
+ case InlineDecision::CANDIDATE:
+ return false;
+ default:
+ assert(!"Unexpected InlineDecision");
+ unreached();
+ }
+}
+
+//------------------------------------------------------------------------
+// InlDecisionIsCandidate: check if this decision describes a viable candidate
+//
+// Arguments:
+// d - the decision in question
+//
+// Return Value:
+// true if this inline still might happen
+
+bool InlDecisionIsCandidate(InlineDecision d)
+{
+ return !InlDecisionIsFailure(d);
+}
+
+//------------------------------------------------------------------------
+// InlDecisionIsDecided: check if this decision has been made
+//
+// Arguments:
+// d - the decision in question
+//
+// Return Value:
+// true if this inline has been decided one way or another
+
+bool InlDecisionIsDecided(InlineDecision d)
+{
+ switch (d)
+ {
+ case InlineDecision::NEVER:
+ case InlineDecision::FAILURE:
+ case InlineDecision::SUCCESS:
+ return true;
+ case InlineDecision::UNDECIDED:
+ case InlineDecision::CANDIDATE:
+ return false;
+ default:
+ assert(!"Unexpected InlineDecision");
+ unreached();
+ }
+}
+
+//------------------------------------------------------------------------
+// InlineContext: default constructor
+
+InlineContext::InlineContext(InlineStrategy* strategy)
+ : m_InlineStrategy(strategy)
+ , m_Parent(nullptr)
+ , m_Child(nullptr)
+ , m_Sibling(nullptr)
+ , m_Code(nullptr)
+ , m_ILSize(0)
+ , m_Offset(BAD_IL_OFFSET)
+ , m_Observation(InlineObservation::CALLEE_UNUSED_INITIAL)
+ , m_CodeSizeEstimate(0)
+ , m_Success(true)
+#if defined(DEBUG) || defined(INLINE_DATA)
+ , m_Policy(nullptr)
+ , m_Callee(nullptr)
+ , m_TreeID(0)
+ , m_Ordinal(0)
+#endif // defined(DEBUG) || defined(INLINE_DATA)
+{
+ // Empty
+}
+
+#if defined(DEBUG) || defined(INLINE_DATA)
+
+//------------------------------------------------------------------------
+// Dump: Dump an InlineContext entry and all descendants to jitstdout
+//
+// Arguments:
+// indent - indentation level for this node
+
+void InlineContext::Dump(unsigned indent)
+{
+ // Handle fact that siblings are in reverse order.
+ if (m_Sibling != nullptr)
+ {
+ m_Sibling->Dump(indent);
+ }
+
+ // We may not know callee name in some of the failing cases
+ Compiler* compiler = m_InlineStrategy->GetCompiler();
+ const char* calleeName = nullptr;
+
+ if (m_Callee == nullptr)
+ {
+ assert(!m_Success);
+ calleeName = "<unknown>";
+ }
+ else
+ {
+
+#if defined(DEBUG)
+ calleeName = compiler->eeGetMethodFullName(m_Callee);
+#else
+ calleeName = "callee";
+#endif // defined(DEBUG)
+ }
+
+ mdMethodDef calleeToken = compiler->info.compCompHnd->getMethodDefFromMethod(m_Callee);
+
+ // Dump this node
+ if (m_Parent == nullptr)
+ {
+ // Root method
+ printf("Inlines into %08X %s\n", calleeToken, calleeName);
+ }
+ else
+ {
+ // Inline attempt.
+ const char* inlineReason = InlGetObservationString(m_Observation);
+ const char* inlineResult = m_Success ? "" : "FAILED: ";
+
+ if (m_Offset == BAD_IL_OFFSET)
+ {
+ printf("%*s[%u IL=???? TR=%06u %08X] [%s%s] %s\n", indent, "", m_Ordinal, m_TreeID, calleeToken,
+ inlineResult, inlineReason, calleeName);
+ }
+ else
+ {
+ IL_OFFSET offset = jitGetILoffs(m_Offset);
+ printf("%*s[%u IL=%04d TR=%06u %08X] [%s%s] %s\n", indent, "", m_Ordinal, offset, m_TreeID, calleeToken,
+ inlineResult, inlineReason, calleeName);
+ }
+ }
+
+ // Recurse to first child
+ if (m_Child != nullptr)
+ {
+ m_Child->Dump(indent + 2);
+ }
+}
+
+//------------------------------------------------------------------------
+// DumpData: Dump a successful InlineContext entry, detailed data, and
+// any successful descendant inlines
+//
+// Arguments:
+// indent - indentation level for this node
+
+void InlineContext::DumpData(unsigned indent)
+{
+ // Handle fact that siblings are in reverse order.
+ if (m_Sibling != nullptr)
+ {
+ m_Sibling->DumpData(indent);
+ }
+
+ Compiler* compiler = m_InlineStrategy->GetCompiler();
+
+#if defined(DEBUG)
+ const char* calleeName = compiler->eeGetMethodFullName(m_Callee);
+#else
+ const char* calleeName = "callee";
+#endif // defined(DEBUG)
+
+ if (m_Parent == nullptr)
+ {
+ // Root method... cons up a policy so we can display the name
+ InlinePolicy* policy = InlinePolicy::GetPolicy(compiler, true);
+ printf("\nInlines [%u] into \"%s\" [%s]\n", m_InlineStrategy->GetInlineCount(), calleeName, policy->GetName());
+ }
+ else if (m_Success)
+ {
+ const char* inlineReason = InlGetObservationString(m_Observation);
+ printf("%*s%u,\"%s\",\"%s\"", indent, "", m_Ordinal, inlineReason, calleeName);
+ m_Policy->DumpData(jitstdout);
+ printf("\n");
+ }
+
+ // Recurse to first child
+ if (m_Child != nullptr)
+ {
+ m_Child->DumpData(indent + 2);
+ }
+}
+
+//------------------------------------------------------------------------
+// DumpXml: Dump an InlineContext entry and all descendants in xml format
+//
+// Arguments:
+// file - file for output
+// indent - indentation level for this node
+
+void InlineContext::DumpXml(FILE* file, unsigned indent)
+{
+ // Handle fact that siblings are in reverse order.
+ if (m_Sibling != nullptr)
+ {
+ m_Sibling->DumpXml(file, indent);
+ }
+
+ const bool isRoot = m_Parent == nullptr;
+ const bool hasChild = m_Child != nullptr;
+ const char* inlineType = m_Success ? "Inline" : "FailedInline";
+ unsigned newIndent = indent;
+
+ if (!isRoot)
+ {
+ Compiler* compiler = m_InlineStrategy->GetCompiler();
+
+ mdMethodDef calleeToken = compiler->info.compCompHnd->getMethodDefFromMethod(m_Callee);
+ unsigned calleeHash = compiler->info.compCompHnd->getMethodHash(m_Callee);
+
+ const char* inlineReason = InlGetObservationString(m_Observation);
+
+ int offset = -1;
+ if (m_Offset != BAD_IL_OFFSET)
+ {
+ offset = (int)jitGetILoffs(m_Offset);
+ }
+
+ fprintf(file, "%*s<%s>\n", indent, "", inlineType);
+ fprintf(file, "%*s<Token>%u</Token>\n", indent + 2, "", calleeToken);
+ fprintf(file, "%*s<Hash>%u</Hash>\n", indent + 2, "", calleeHash);
+ fprintf(file, "%*s<Offset>%u</Offset>\n", indent + 2, "", offset);
+ fprintf(file, "%*s<Reason>%s</Reason>\n", indent + 2, "", inlineReason);
+
+ // Optionally, dump data about the last inline
+ if ((JitConfig.JitInlineDumpData() != 0) && (this == m_InlineStrategy->GetLastContext()))
+ {
+ fprintf(file, "%*s<Data>", indent + 2, "");
+ m_InlineStrategy->DumpDataContents(file);
+ fprintf(file, "</Data>\n");
+ }
+
+ newIndent = indent + 2;
+ }
+
+ // Handle children
+
+ if (hasChild)
+ {
+ fprintf(file, "%*s<Inlines>\n", newIndent, "");
+ m_Child->DumpXml(file, newIndent + 2);
+ fprintf(file, "%*s</Inlines>\n", newIndent, "");
+ }
+ else
+ {
+ fprintf(file, "%*s<Inlines />\n", newIndent, "");
+ }
+
+ // Close out
+
+ if (!isRoot)
+ {
+ fprintf(file, "%*s</%s>\n", indent, "", inlineType);
+ }
+}
+
+#endif // defined(DEBUG) || defined(INLINE_DATA)
+
+//------------------------------------------------------------------------
+// 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
+// stmt - statement containing the call (if known)
+// description - string describing the context of the decision
+
+InlineResult::InlineResult(Compiler* compiler, GenTreeCall* call, GenTreeStmt* stmt, const char* description)
+ : m_RootCompiler(nullptr)
+ , m_Policy(nullptr)
+ , m_Call(call)
+ , m_InlineContext(nullptr)
+ , m_Caller(nullptr)
+ , m_Callee(nullptr)
+ , m_Description(description)
+ , m_Reported(false)
+{
+ // Set the compiler instance
+ m_RootCompiler = compiler->impInlineRoot();
+
+ // Set the policy
+ const bool isPrejitRoot = false;
+ m_Policy = InlinePolicy::GetPolicy(m_RootCompiler, isPrejitRoot);
+
+ // Pass along some optional information to the policy.
+ if (stmt != nullptr)
+ {
+ m_InlineContext = stmt->gtInlineContext;
+ m_Policy->NoteContext(m_InlineContext);
+
+#if defined(DEBUG) || defined(INLINE_DATA)
+ m_Policy->NoteOffset(call->gtRawILOffset);
+#else
+ m_Policy->NoteOffset(stmt->gtStmtILoffsx);
+#endif // defined(DEBUG) || defined(INLINE_DATA)
+ }
+
+ // Get method handle for caller. Note we use the
+ // handle for the "immediate" caller here.
+ m_Caller = compiler->info.compMethodHnd;
+
+ // Get method handle for callee, if known
+ if (m_Call->gtCall.gtCallType == CT_USER_FUNC)
+ {
+ m_Callee = m_Call->gtCall.gtCallMethHnd;
+ }
+}
+
+//------------------------------------------------------------------------
+// InlineResult: Construct an InlineResult to evaluate a particular
+// method as a possible inline candidate, while prejtting.
+//
+// Arguments:
+// 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
+// cannot be inlined, to help subsequent jit throughput.
+//
+// We use the inlCallee member to track the method since logically
+// it is the callee here.
+
+InlineResult::InlineResult(Compiler* compiler, CORINFO_METHOD_HANDLE method, const char* description)
+ : m_RootCompiler(nullptr)
+ , m_Policy(nullptr)
+ , m_Call(nullptr)
+ , m_InlineContext(nullptr)
+ , m_Caller(nullptr)
+ , m_Callee(method)
+ , m_Description(description)
+ , m_Reported(false)
+{
+ // Set the compiler instance
+ m_RootCompiler = compiler->impInlineRoot();
+
+ // Set the policy
+ const bool isPrejitRoot = true;
+ m_Policy = InlinePolicy::GetPolicy(m_RootCompiler, isPrejitRoot);
+}
+
+//------------------------------------------------------------------------
+// Report: Dump, log, and report information about an inline decision.
+//
+// Notes:
+// Called (automatically via the InlineResult dtor) when the
+// inliner is done evaluating a candidate.
+//
+// Dumps state of the inline candidate, and if a decision was
+// reached, sends it to the log and reports the decision back to the
+// EE. Optionally update the method attribute to NOINLINE if
+// observation and policy warrant.
+//
+// All this can be suppressed if desired by calling setReported()
+// before the InlineResult goes out of scope.
+
+void InlineResult::Report()
+{
+ // If we weren't actually inlining, user may have suppressed
+ // reporting via setReported(). If so, do nothing.
+ if (m_Reported)
+ {
+ return;
+ }
+
+ m_Reported = true;
+
+#ifdef DEBUG
+ const char* callee = nullptr;
+
+ // Optionally dump the result
+ if (VERBOSE)
+ {
+ const char* format = "INLINER: during '%s' result '%s' reason '%s' for '%s' calling '%s'\n";
+ const char* caller = (m_Caller == nullptr) ? "n/a" : m_RootCompiler->eeGetMethodFullName(m_Caller);
+
+ callee = (m_Callee == nullptr) ? "n/a" : m_RootCompiler->eeGetMethodFullName(m_Callee);
+
+ JITDUMP(format, m_Description, 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() && (m_Call != nullptr))
+ {
+ // compiler should have revoked candidacy on the call by now
+ assert((m_Call->gtFlags & GTF_CALL_INLINE_CANDIDATE) == 0);
+
+ m_Call->gtInlineObservation = m_Policy->GetObservation();
+ }
+
+#endif // DEBUG
+
+ // Was the result NEVER? If so we might want to propagate this to
+ // the runtime.
+
+ if (IsNever() && m_Policy->PropagateNeverToRuntime())
+ {
+ // If we know the callee, and if the observation that got us
+ // to this Never inline state is something *other* than
+ // IS_NOINLINE, then we've uncovered a reason why this method
+ // can't ever be inlined. Update the callee method attributes
+ // so that future inline attempts for this callee fail faster.
+
+ InlineObservation obs = m_Policy->GetObservation();
+
+ if ((m_Callee != nullptr) && (obs != InlineObservation::CALLEE_IS_NOINLINE))
+ {
+
+#ifdef DEBUG
+
+ if (VERBOSE)
+ {
+ const char* obsString = InlGetObservationString(obs);
+ JITDUMP("\nINLINER: Marking %s as NOINLINE because of %s\n", callee, obsString);
+ }
+
+#endif // DEBUG
+
+ COMP_HANDLE comp = m_RootCompiler->info.compCompHnd;
+ comp->setMethodAttribs(m_Callee, CORINFO_FLG_BAD_INLINEE);
+ }
+ }
+
+ if (IsDecided())
+ {
+ const char* format = "INLINER: during '%s' result '%s' reason '%s'\n";
+ 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());
+ }
+}
+
+//------------------------------------------------------------------------
+// InlineStrategy construtor
+//
+// Arguments
+// compiler - root compiler instance
+
+InlineStrategy::InlineStrategy(Compiler* compiler)
+ : m_Compiler(compiler)
+ , m_RootContext(nullptr)
+ , m_LastSuccessfulPolicy(nullptr)
+ , m_CallCount(0)
+ , m_CandidateCount(0)
+ , m_AlwaysCandidateCount(0)
+ , m_ForceCandidateCount(0)
+ , m_DiscretionaryCandidateCount(0)
+ , m_UnprofitableCandidateCount(0)
+ , m_ImportCount(0)
+ , m_InlineCount(0)
+ , m_MaxInlineSize(DEFAULT_MAX_INLINE_SIZE)
+ , m_MaxInlineDepth(DEFAULT_MAX_INLINE_DEPTH)
+ , m_InitialTimeBudget(0)
+ , m_InitialTimeEstimate(0)
+ , m_CurrentTimeBudget(0)
+ , m_CurrentTimeEstimate(0)
+ , m_InitialSizeEstimate(0)
+ , m_CurrentSizeEstimate(0)
+ , m_HasForceViaDiscretionary(false)
+#if defined(DEBUG) || defined(INLINE_DATA)
+ , m_MethodXmlFilePosition(0)
+#endif // defined(DEBUG) || defined(INLINE_DATA)
+
+{
+ // Verify compiler is a root compiler instance
+ assert(m_Compiler->impInlineRoot() == m_Compiler);
+
+#ifdef DEBUG
+
+ // Possibly modify the max inline size.
+ //
+ // Default value of JitInlineSize is the same as our default.
+ // So normally this next line does not change the size.
+ m_MaxInlineSize = JitConfig.JitInlineSize();
+
+ // Up the max size under stress
+ if (m_Compiler->compInlineStress())
+ {
+ m_MaxInlineSize *= 10;
+ }
+
+ // But don't overdo it
+ if (m_MaxInlineSize > IMPLEMENTATION_MAX_INLINE_SIZE)
+ {
+ m_MaxInlineSize = IMPLEMENTATION_MAX_INLINE_SIZE;
+ }
+
+ // Verify: not too small, not too big.
+ assert(m_MaxInlineSize >= ALWAYS_INLINE_SIZE);
+ assert(m_MaxInlineSize <= IMPLEMENTATION_MAX_INLINE_SIZE);
+
+ // Possibly modify the max inline depth
+ //
+ // Default value of JitInlineDepth is the same as our default.
+ // So normally this next line does not change the size.
+ m_MaxInlineDepth = JitConfig.JitInlineDepth();
+
+ // But don't overdo it
+ if (m_MaxInlineDepth > IMPLEMENTATION_MAX_INLINE_DEPTH)
+ {
+ m_MaxInlineDepth = IMPLEMENTATION_MAX_INLINE_DEPTH;
+ }
+
+#endif // DEBUG
+}
+
+//------------------------------------------------------------------------
+// GetRootContext: get the InlineContext for the root method
+//
+// Return Value:
+// Root context; describes the method being jitted.
+//
+// Note:
+// Also initializes the jit time estimate and budget.
+
+InlineContext* InlineStrategy::GetRootContext()
+{
+ if (m_RootContext == nullptr)
+ {
+ // Allocate on first demand.
+ m_RootContext = NewRoot();
+
+ // Estimate how long the jit will take if there's no inlining
+ // done to this method.
+ m_InitialTimeEstimate = EstimateTime(m_RootContext);
+ m_CurrentTimeEstimate = m_InitialTimeEstimate;
+
+ // Set the initial budget for inlining. Note this is
+ // deliberately set very high and is intended to catch
+ // only pathological runaway inline cases.
+ m_InitialTimeBudget = BUDGET * m_InitialTimeEstimate;
+ m_CurrentTimeBudget = m_InitialTimeBudget;
+
+ // Estimate the code size if there's no inlining
+ m_InitialSizeEstimate = EstimateSize(m_RootContext);
+ m_CurrentSizeEstimate = m_InitialSizeEstimate;
+
+ // Sanity check
+ assert(m_CurrentTimeEstimate > 0);
+ assert(m_CurrentSizeEstimate > 0);
+
+ // Cache as the "last" context created
+ m_LastContext = m_RootContext;
+ }
+
+ return m_RootContext;
+}
+
+//------------------------------------------------------------------------
+// NoteAttempt: do bookkeeping for an inline attempt
+//
+// Arguments:
+// result -- InlineResult for successful inline candidate
+
+void InlineStrategy::NoteAttempt(InlineResult* result)
+{
+ assert(result->IsCandidate());
+ InlineObservation obs = result->GetObservation();
+
+ if (obs == InlineObservation::CALLEE_BELOW_ALWAYS_INLINE_SIZE)
+ {
+ m_AlwaysCandidateCount++;
+ }
+ else if (obs == InlineObservation::CALLEE_IS_FORCE_INLINE)
+ {
+ m_ForceCandidateCount++;
+ }
+ else
+ {
+ m_DiscretionaryCandidateCount++;
+ }
+}
+
+//------------------------------------------------------------------------
+// DumpCsvHeader: dump header for csv inline stats
+//
+// Argument:
+// fp -- file for dump output
+
+void InlineStrategy::DumpCsvHeader(FILE* fp)
+{
+ fprintf(fp, "\"InlineCalls\",");
+ fprintf(fp, "\"InlineCandidates\",");
+ fprintf(fp, "\"InlineAlways\",");
+ fprintf(fp, "\"InlineForce\",");
+ fprintf(fp, "\"InlineDiscretionary\",");
+ fprintf(fp, "\"InlineUnprofitable\",");
+ fprintf(fp, "\"InlineEarlyFail\",");
+ fprintf(fp, "\"InlineImport\",");
+ fprintf(fp, "\"InlineLateFail\",");
+ fprintf(fp, "\"InlineSuccess\",");
+}
+
+//------------------------------------------------------------------------
+// DumpCsvData: dump data for csv inline stats
+//
+// Argument:
+// fp -- file for dump output
+
+void InlineStrategy::DumpCsvData(FILE* fp)
+{
+ fprintf(fp, "%u,", m_CallCount);
+ fprintf(fp, "%u,", m_CandidateCount);
+ fprintf(fp, "%u,", m_AlwaysCandidateCount);
+ fprintf(fp, "%u,", m_ForceCandidateCount);
+ fprintf(fp, "%u,", m_DiscretionaryCandidateCount);
+ fprintf(fp, "%u,", m_UnprofitableCandidateCount);
+
+ // Early failures are cases where candates are rejected between
+ // the time the jit invokes the inlinee compiler and the time it
+ // starts to import the inlinee IL.
+ //
+ // So they are "cheaper" that late failures.
+
+ unsigned profitableCandidateCount = m_DiscretionaryCandidateCount - m_UnprofitableCandidateCount;
+
+ unsigned earlyFailCount =
+ m_CandidateCount - m_AlwaysCandidateCount - m_ForceCandidateCount - profitableCandidateCount;
+
+ fprintf(fp, "%u,", earlyFailCount);
+
+ unsigned lateFailCount = m_ImportCount - m_InlineCount;
+
+ fprintf(fp, "%u,", m_ImportCount);
+ fprintf(fp, "%u,", lateFailCount);
+ fprintf(fp, "%u,", m_InlineCount);
+}
+
+//------------------------------------------------------------------------
+// EstimateTime: estimate impact of this inline on the method jit time
+//
+// Arguments:
+// context - context describing this inline
+//
+// Return Value:
+// Nominal estimate of jit time.
+
+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);
+ }
+ else
+ {
+ return EstimateInlineTime(ilSize);
+ }
+}
+
+//------------------------------------------------------------------------
+// EstimteRootTime: estimate jit time for method of this size with
+// no inlining.
+//
+// Arguments:
+// ilSize - size of the method's IL
+//
+// Return Value:
+// Nominal estimate of jit time.
+//
+// Notes:
+// Based on observational data. Time is nominally microseconds.
+
+int InlineStrategy::EstimateRootTime(unsigned ilSize)
+{
+ return 60 + 3 * ilSize;
+}
+
+//------------------------------------------------------------------------
+// EstimteInlineTime: estimate time impact on jitting for an inline
+// of this size.
+//
+// Arguments:
+// ilSize - size of the method's IL
+//
+// Return Value:
+// Nominal increase in jit time.
+//
+// Notes:
+// Based on observational data. Time is nominally microseconds.
+// Small inlines will make the jit a bit faster.
+
+int InlineStrategy::EstimateInlineTime(unsigned ilSize)
+{
+ return -14 + 2 * ilSize;
+}
+
+//------------------------------------------------------------------------
+// EstimateSize: estimate impact of this inline on the method size
+//
+// Arguments:
+// context - context describing this inline
+//
+// Return Value:
+// Nominal estimate of method size (bytes * 10)
+
+int InlineStrategy::EstimateSize(InlineContext* context)
+{
+ // Prediction varies for root and inlines.
+ if (context == m_RootContext)
+ {
+ // Simple linear models based on observations show root method
+ // native code size is fairly well predicted by IL size.
+ //
+ // Model below is for x64 on windows.
+ unsigned ilSize = context->GetILSize();
+ int estimate = (1312 + 228 * ilSize) / 10;
+
+ return estimate;
+ }
+ else
+ {
+ // Use context's code size estimate.
+ return context->GetCodeSizeEstimate();
+ }
+}
+
+//------------------------------------------------------------------------
+// NoteOutcome: do bookkeeping for an inline
+//
+// Arguments:
+// context - context for the inlie
+
+void InlineStrategy::NoteOutcome(InlineContext* context)
+{
+ // Note we can't generally count up failures here -- we only
+ // create contexts for failures in debug modes, and even then
+ // we may not get them all.
+ if (context->IsSuccess())
+ {
+ m_InlineCount++;
+
+#if defined(DEBUG) || defined(INLINE_DATA)
+
+ // Keep track of the inline targeted for data collection or,
+ // if we don't have one (yet), the last successful inline.
+ bool updateLast = (m_LastSuccessfulPolicy == nullptr) || !m_LastSuccessfulPolicy->IsDataCollectionTarget();
+
+ if (updateLast)
+ {
+ m_LastContext = context;
+ m_LastSuccessfulPolicy = context->m_Policy;
+ }
+ else
+ {
+ // We only expect one inline to be a data collection
+ // target.
+ assert(!context->m_Policy->IsDataCollectionTarget());
+ }
+
+#endif // defined(DEBUG) || defined(INLINE_DATA)
+
+ // Budget update.
+ //
+ // If callee is a force inline, increase budget, provided all
+ // parent contexts are likewise force inlines.
+ //
+ // If callee is discretionary or has a discretionary ancestor,
+ // increase expense.
+
+ InlineContext* currentContext = context;
+ bool isForceInline = false;
+
+ while (currentContext != m_RootContext)
+ {
+ InlineObservation observation = currentContext->GetObservation();
+
+ if (observation != InlineObservation::CALLEE_IS_FORCE_INLINE)
+ {
+ if (isForceInline)
+ {
+ // Interesting case where discretionary inlines pull
+ // in a force inline...
+ m_HasForceViaDiscretionary = true;
+ }
+
+ isForceInline = false;
+ break;
+ }
+
+ isForceInline = true;
+ currentContext = currentContext->GetParent();
+ }
+
+ int timeDelta = EstimateTime(context);
+
+ if (isForceInline)
+ {
+ // Update budget since this inline was forced. Only allow
+ // budget to increase.
+ if (timeDelta > 0)
+ {
+ m_CurrentTimeBudget += timeDelta;
+ }
+ }
+
+ // Update time estimate.
+ m_CurrentTimeEstimate += timeDelta;
+
+ // Update size estimate.
+ //
+ // Sometimes estimates don't make sense. Don't let the method
+ // size go negative.
+ int sizeDelta = EstimateSize(context);
+
+ if (m_CurrentSizeEstimate + sizeDelta <= 0)
+ {
+ sizeDelta = 0;
+ }
+
+ // Update the code size estimate.
+ m_CurrentSizeEstimate += sizeDelta;
+ }
+}
+
+//------------------------------------------------------------------------
+// BudgetCheck: return true if as inline of this size would 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
+
+bool InlineStrategy::BudgetCheck(unsigned ilSize)
+{
+ int timeDelta = EstimateInlineTime(ilSize);
+ return (timeDelta + m_CurrentTimeEstimate > m_CurrentTimeBudget);
+}
+
+//------------------------------------------------------------------------
+// NewRoot: construct an InlineContext for the root method
+//
+// Return Value:
+// InlineContext for use as the root context
+//
+// Notes:
+// We leave m_Code as nullptr here (rather than the IL buffer
+// address of the root method) to preserve existing behavior, which
+// is to allow one recursive inline.
+
+InlineContext* InlineStrategy::NewRoot()
+{
+ InlineContext* rootContext = new (m_Compiler, CMK_Inlining) InlineContext(this);
+
+ rootContext->m_ILSize = m_Compiler->info.compILCodeSize;
+
+#if defined(DEBUG) || defined(INLINE_DATA)
+
+ rootContext->m_Callee = m_Compiler->info.compMethodHnd;
+
+#endif // defined(DEBUG) || defined(INLINE_DATA)
+
+ return rootContext;
+}
+
+//------------------------------------------------------------------------
+// NewSuccess: construct an InlineContext for a successful inline
+// and link it into the context tree
+//
+// Arguments:
+// stmt - statement containing call being inlined
+// inlineInfo - information about this inline
+//
+// Return Value:
+// A new InlineContext for statements brought into the method by
+// this inline.
+
+InlineContext* InlineStrategy::NewSuccess(InlineInfo* inlineInfo)
+{
+ InlineContext* calleeContext = new (m_Compiler, CMK_Inlining) InlineContext(this);
+ GenTree* stmt = inlineInfo->iciStmt;
+ BYTE* calleeIL = inlineInfo->inlineCandidateInfo->methInfo.ILCode;
+ unsigned calleeILSize = inlineInfo->inlineCandidateInfo->methInfo.ILCodeSize;
+ InlineContext* parentContext = stmt->gtStmt.gtInlineContext;
+
+ noway_assert(parentContext != nullptr);
+
+ calleeContext->m_Code = calleeIL;
+ calleeContext->m_ILSize = calleeILSize;
+ 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->AsStmt()->gtStmtILoffsx;
+ calleeContext->m_Observation = inlineInfo->inlineResult->GetObservation();
+ calleeContext->m_Success = true;
+
+#if defined(DEBUG) || defined(INLINE_DATA)
+
+ InlinePolicy* policy = inlineInfo->inlineResult->GetPolicy();
+
+ calleeContext->m_Policy = policy;
+ calleeContext->m_CodeSizeEstimate = policy->CodeSizeEstimate();
+ calleeContext->m_Callee = inlineInfo->fncHandle;
+ // +1 here since we set this before calling NoteOutcome.
+ calleeContext->m_Ordinal = m_InlineCount + 1;
+ // Update offset with more accurate info
+ calleeContext->m_Offset = inlineInfo->inlineResult->GetCall()->gtRawILOffset;
+
+#endif // defined(DEBUG) || defined(INLINE_DATA)
+
+#if defined(DEBUG)
+
+ calleeContext->m_TreeID = inlineInfo->inlineResult->GetCall()->gtTreeID;
+
+#endif // defined(DEBUG)
+
+ NoteOutcome(calleeContext);
+
+ return calleeContext;
+}
+
+#if defined(DEBUG) || defined(INLINE_DATA)
+
+//------------------------------------------------------------------------
+// NewFailure: construct an InlineContext for a failing inline
+// and link it into the context tree
+//
+// Arguments:
+// stmt - statement containing the attempted inline
+// inlineResult - inlineResult for the attempt
+//
+// Return Value:
+// A new InlineContext for diagnostic purposes, or nullptr if
+// the desired context could not be created.
+
+InlineContext* InlineStrategy::NewFailure(GenTree* stmt, InlineResult* inlineResult)
+{
+ // Check for a parent context first. We may insert new statements
+ // between the caller and callee that do not pick up either's
+ // context, and these statements may have calls that we later
+ // examine and fail to inline.
+ //
+ // See fgInlinePrependStatements for examples.
+
+ InlineContext* parentContext = stmt->gtStmt.gtInlineContext;
+
+ if (parentContext == nullptr)
+ {
+ // Assume for now this is a failure to inline a call in a
+ // statement inserted between caller and callee. Just ignore
+ // it for the time being.
+
+ return nullptr;
+ }
+
+ InlineContext* failedContext = new (m_Compiler, CMK_Inlining) InlineContext(this);
+
+ failedContext->m_Parent = parentContext;
+ // Push on front here will put siblings in reverse lexical
+ // order which we undo in the dumper
+ failedContext->m_Sibling = parentContext->m_Child;
+ parentContext->m_Child = failedContext;
+ failedContext->m_Child = nullptr;
+ failedContext->m_Offset = stmt->AsStmt()->gtStmtILoffsx;
+ failedContext->m_Observation = inlineResult->GetObservation();
+ failedContext->m_Callee = inlineResult->GetCallee();
+ failedContext->m_Success = false;
+
+#if defined(DEBUG) || defined(INLINE_DATA)
+
+ // Update offset with more accurate info
+ failedContext->m_Offset = inlineResult->GetCall()->gtRawILOffset;
+
+#endif // #if defined(DEBUG) || defined(INLINE_DATA)
+
+#if defined(DEBUG)
+
+ failedContext->m_TreeID = inlineResult->GetCall()->gtTreeID;
+
+#endif // defined(DEBUG)
+
+ NoteOutcome(failedContext);
+
+ return failedContext;
+}
+
+//------------------------------------------------------------------------
+// Dump: dump description of inline behavior
+
+void InlineStrategy::Dump()
+{
+ m_RootContext->Dump();
+
+ printf("Budget: initialTime=%d, finalTime=%d, initialBudget=%d, currentBudget=%d\n", m_InitialTimeEstimate,
+ m_CurrentTimeEstimate, m_InitialTimeBudget, m_CurrentTimeBudget);
+
+ if (m_CurrentTimeBudget > m_InitialTimeBudget)
+ {
+ printf("Budget: increased by %d because of force inlines\n", m_CurrentTimeBudget - m_InitialTimeBudget);
+ }
+
+ if (m_CurrentTimeEstimate > m_CurrentTimeBudget)
+ {
+ printf("Budget: went over budget by %d\n", m_CurrentTimeEstimate - m_CurrentTimeBudget);
+ }
+
+ if (m_HasForceViaDiscretionary)
+ {
+ printf("Budget: discretionary inline caused a force inline\n");
+ }
+
+ printf("Budget: initialSize=%d, finalSize=%d\n", m_InitialSizeEstimate, m_CurrentSizeEstimate);
+}
+
+// Static to track emission of the inline data header
+
+bool InlineStrategy::s_HasDumpedDataHeader = false;
+
+//------------------------------------------------------------------------
+// DumpData: dump data about the last successful inline into this method
+// in a format suitable for automated analysis.
+
+void InlineStrategy::DumpData()
+{
+ // Is dumping enabled? If not, nothing to do.
+ if (JitConfig.JitInlineDumpData() == 0)
+ {
+ return;
+ }
+
+ // If we're also dumping inline XML, we'll let it dump the data.
+ if (JitConfig.JitInlineDumpXml() != 0)
+ {
+ return;
+ }
+
+ // Don't dump anything if limiting is on and we didn't reach
+ // the limit while inlining.
+ //
+ // This serves to filter out duplicate data.
+ const int limit = JitConfig.JitInlineLimit();
+
+ if ((limit >= 0) && (m_InlineCount < static_cast<unsigned>(limit)))
+ {
+ return;
+ }
+
+ // Dump header, if not already dumped
+ if (!s_HasDumpedDataHeader)
+ {
+ DumpDataHeader(stderr);
+ s_HasDumpedDataHeader = true;
+ }
+
+ // Dump contents
+ DumpDataContents(stderr);
+ fprintf(stderr, "\n");
+}
+
+//------------------------------------------------------------------------
+// DumpDataEnsurePolicyIsSet: ensure m_LastSuccessfulPolicy describes the
+// inline policy in effect.
+//
+// Notes:
+// Needed for methods that don't have any successful inlines.
+
+void InlineStrategy::DumpDataEnsurePolicyIsSet()
+{
+ // Cache references to compiler substructures.
+ const Compiler::Info& info = m_Compiler->info;
+ const Compiler::Options& opts = m_Compiler->opts;
+
+ // If there weren't any successful inlines, we won't have a
+ // successful policy, so fake one up.
+ if (m_LastSuccessfulPolicy == nullptr)
+ {
+ const bool isPrejitRoot = (opts.eeFlags & CORJIT_FLG_PREJIT) != 0;
+ m_LastSuccessfulPolicy = InlinePolicy::GetPolicy(m_Compiler, isPrejitRoot);
+
+ // Add in a bit of data....
+ const bool isForceInline = (info.compFlags & CORINFO_FLG_FORCEINLINE) != 0;
+ m_LastSuccessfulPolicy->NoteBool(InlineObservation::CALLEE_IS_FORCE_INLINE, isForceInline);
+ m_LastSuccessfulPolicy->NoteInt(InlineObservation::CALLEE_IL_CODE_SIZE, info.compMethodInfo->ILCodeSize);
+ }
+}
+
+//------------------------------------------------------------------------
+// DumpDataHeader: dump header for inline data.
+//
+// Arguments:
+// file - file for data output
+
+void InlineStrategy::DumpDataHeader(FILE* file)
+{
+ DumpDataEnsurePolicyIsSet();
+ const int limit = JitConfig.JitInlineLimit();
+ fprintf(file, "*** Inline Data: Policy=%s JitInlineLimit=%d ***\n", m_LastSuccessfulPolicy->GetName(), limit);
+ DumpDataSchema(file);
+ fprintf(file, "\n");
+}
+
+//------------------------------------------------------------------------
+// DumpSchema: dump schema for inline data.
+//
+// Arguments:
+// file - file for data output
+
+void InlineStrategy::DumpDataSchema(FILE* file)
+{
+ DumpDataEnsurePolicyIsSet();
+ fprintf(file, "Method,Version,HotSize,ColdSize,JitTime,SizeEstimate,TimeEstimate");
+ m_LastSuccessfulPolicy->DumpSchema(file);
+}
+
+//------------------------------------------------------------------------
+// DumpDataContents: dump contents of inline data
+//
+// Arguments:
+// file - file for data output
+
+void InlineStrategy::DumpDataContents(FILE* file)
+{
+ DumpDataEnsurePolicyIsSet();
+
+ // Cache references to compiler substructures.
+ const Compiler::Info& info = m_Compiler->info;
+ const Compiler::Options& opts = m_Compiler->opts;
+
+ // We'd really like the method identifier to be unique and
+ // durable across crossgen invocations. Not clear how to
+ // accomplish this, so we'll use the token for now.
+ //
+ // Post processing will have to filter out all data from
+ // methods where the root entry appears multiple times.
+ mdMethodDef currentMethodToken = info.compCompHnd->getMethodDefFromMethod(info.compMethodHnd);
+
+ // Convert time spent jitting into microseconds
+ unsigned microsecondsSpentJitting = 0;
+ unsigned __int64 compCycles = m_Compiler->getInlineCycleCount();
+ if (compCycles > 0)
+ {
+ double countsPerSec = CycleTimer::CyclesPerSecond();
+ double counts = (double)compCycles;
+ microsecondsSpentJitting = (unsigned)((counts / countsPerSec) * 1000 * 1000);
+ }
+
+ fprintf(file, "%08X,%u,%u,%u,%u,%d,%d", currentMethodToken, m_InlineCount, info.compTotalHotCodeSize,
+ info.compTotalColdCodeSize, microsecondsSpentJitting, m_CurrentSizeEstimate / 10, m_CurrentTimeEstimate);
+ m_LastSuccessfulPolicy->DumpData(file);
+}
+
+// Static to track emission of the xml data header
+// and lock to prevent interleaved file writes
+
+bool InlineStrategy::s_HasDumpedXmlHeader = false;
+CritSecObject InlineStrategy::s_XmlWriterLock;
+
+//------------------------------------------------------------------------
+// DumpXml: dump xml-formatted version of the inline tree.
+//
+// Arguments
+// file - file for data output
+// indent - indent level of this element
+
+void InlineStrategy::DumpXml(FILE* file, unsigned indent)
+{
+ if (JitConfig.JitInlineDumpXml() == 0)
+ {
+ return;
+ }
+
+ // Lock to prevent interleaving of trees.
+ CritSecHolder writeLock(s_XmlWriterLock);
+
+ // Dump header
+ if (!s_HasDumpedXmlHeader)
+ {
+ DumpDataEnsurePolicyIsSet();
+
+ fprintf(file, "<?xml version=\"1.0\"?>\n");
+ fprintf(file, "<InlineForest>\n");
+ fprintf(file, "<Policy>%s</Policy>\n", m_LastSuccessfulPolicy->GetName());
+
+ if (JitConfig.JitInlineDumpData() != 0)
+ {
+ fprintf(file, "<DataSchema>");
+ DumpDataSchema(file);
+ fprintf(file, "</DataSchema>\n");
+ }
+
+ fprintf(file, "<Methods>\n");
+ s_HasDumpedXmlHeader = true;
+ }
+
+ // If we're dumping "minimal" Xml, and we didn't do
+ // any inlines into this method, then there's nothing
+ // to emit here.
+ if ((m_InlineCount == 0) && (JitConfig.JitInlineDumpXml() == 2))
+ {
+ return;
+ }
+
+ // Cache references to compiler substructures.
+ const Compiler::Info& info = m_Compiler->info;
+ const Compiler::Options& opts = m_Compiler->opts;
+
+ const bool isPrejitRoot = (opts.eeFlags & CORJIT_FLG_PREJIT) != 0;
+ const bool isForceInline = (info.compFlags & CORINFO_FLG_FORCEINLINE) != 0;
+
+ // We'd really like the method identifier to be unique and
+ // durable across crossgen invocations. Not clear how to
+ // accomplish this, so we'll use the token for now.
+ //
+ // Post processing will have to filter out all data from
+ // methods where the root entry appears multiple times.
+ mdMethodDef currentMethodToken = info.compCompHnd->getMethodDefFromMethod(info.compMethodHnd);
+
+ unsigned hash = info.compMethodHash();
+
+ // Convert time spent jitting into microseconds
+ unsigned microsecondsSpentJitting = 0;
+ unsigned __int64 compCycles = m_Compiler->getInlineCycleCount();
+ if (compCycles > 0)
+ {
+ double countsPerSec = CycleTimer::CyclesPerSecond();
+ double counts = (double)compCycles;
+ microsecondsSpentJitting = (unsigned)((counts / countsPerSec) * 1000 * 1000);
+ }
+
+ // Get method name just for root method, to make it a bit easier
+ // to search for things in the inline xml.
+ const char* methodName = info.compCompHnd->getMethodName(info.compMethodHnd, nullptr);
+
+ // Cheap xml quoting for values. Only < and & are troublemakers,
+ // but change > for symmetry.
+ //
+ // Ok to truncate name, just ensure it's null terminated.
+ char buf[64];
+ strncpy(buf, methodName, sizeof(buf));
+ buf[sizeof(buf) - 1] = 0;
+
+ for (int i = 0; i < sizeof(buf); i++)
+ {
+ switch (buf[i])
+ {
+ case '<':
+ buf[i] = '[';
+ break;
+ case '>':
+ buf[i] = ']';
+ break;
+ case '&':
+ buf[i] = '#';
+ break;
+ default:
+ break;
+ }
+ }
+
+ fprintf(file, "%*s<Method>\n", indent, "");
+ fprintf(file, "%*s<Token>%u</Token>\n", indent + 2, "", currentMethodToken);
+ fprintf(file, "%*s<Hash>%u</Hash>\n", indent + 2, "", hash);
+ fprintf(file, "%*s<Name>%s</Name>\n", indent + 2, "", buf);
+ fprintf(file, "%*s<InlineCount>%u</InlineCount>\n", indent + 2, "", m_InlineCount);
+ fprintf(file, "%*s<HotSize>%u</HotSize>\n", indent + 2, "", info.compTotalHotCodeSize);
+ fprintf(file, "%*s<ColdSize>%u</ColdSize>\n", indent + 2, "", info.compTotalColdCodeSize);
+ fprintf(file, "%*s<JitTime>%u</JitTime>\n", indent + 2, "", microsecondsSpentJitting);
+ fprintf(file, "%*s<SizeEstimate>%u</SizeEstimate>\n", indent + 2, "", m_CurrentSizeEstimate / 10);
+ fprintf(file, "%*s<TimeEstimate>%u</TimeEstimate>\n", indent + 2, "", m_CurrentTimeEstimate);
+
+ // Root context will be null if we're not optimizing the method.
+ //
+ // Note there are cases of this in mscorlib even in release builds,
+ // eg Task.NotifyDebuggerOfWaitCompletion.
+ //
+ // For such methods there aren't any inlines.
+ if (m_RootContext != nullptr)
+ {
+ m_RootContext->DumpXml(file, indent + 2);
+ }
+ else
+ {
+ fprintf(file, "%*s<Inlines/>\n", indent + 2, "");
+ }
+
+ fprintf(file, "%*s</Method>\n", indent, "");
+}
+
+//------------------------------------------------------------------------
+// FinalizeXml: finalize the xml-formatted version of the inline tree.
+//
+// Arguments
+// file - file for data output
+
+void InlineStrategy::FinalizeXml(FILE* file)
+{
+ // If we dumped the header, dump a footer
+ if (s_HasDumpedXmlHeader)
+ {
+ fprintf(file, "</Methods>\n");
+ fprintf(file, "</InlineForest>\n");
+ fflush(file);
+
+ // Workaroud compShutdown getting called twice.
+ s_HasDumpedXmlHeader = false;
+ }
+
+ // Finalize reading inline xml
+ ReplayPolicy::FinalizeXml();
+}
+
+#endif // defined(DEBUG) || defined(INLINE_DATA)
+
+//------------------------------------------------------------------------
+// IsNoInline: allow strategy to disable inlining in a method
+//
+// Arguments:
+// info -- compiler interface from the EE
+// method -- handle for the root method
+//
+// Notes:
+// Only will return true in debug or special release builds.
+// Expects JitNoInlineRange to be set to the hashes of methods
+// where inlining is disabled.
+
+bool InlineStrategy::IsNoInline(ICorJitInfo* info, CORINFO_METHOD_HANDLE method)
+{
+
+#if defined(DEBUG) || defined(INLINE_DATA)
+
+ static ConfigMethodRange range;
+ const wchar_t* noInlineRange = JitConfig.JitNoInlineRange();
+
+ if (noInlineRange == nullptr)
+ {
+ return false;
+ }
+
+ // If we have a config string we have at least one entry. Count
+ // number of spaces in our config string to see if there are
+ // more. Number of ranges we need is 2x that value.
+ unsigned entryCount = 1;
+ for (const wchar_t* p = noInlineRange; *p != 0; p++)
+ {
+ if (*p == L' ')
+ {
+ entryCount++;
+ }
+ }
+
+ range.EnsureInit(noInlineRange, 2 * entryCount);
+ assert(!range.Error());
+ return range.Contains(info, method);
+
+#else
+
+ return false;
+
+#endif // defined(DEBUG) || defined(INLINE_DATA)
+}