1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
10 #include "inlinepolicy.h"
12 // Lookup table for inline description strings
14 static const char* InlineDescriptions[] = {
15 #define INLINE_OBSERVATION(name, type, description, impact, target) description,
17 #undef INLINE_OBSERVATION
20 // Lookup table for inline targets
22 static const InlineTarget InlineTargets[] = {
23 #define INLINE_OBSERVATION(name, type, description, impact, target) InlineTarget::target,
25 #undef INLINE_OBSERVATION
28 // Lookup table for inline impacts
30 static const InlineImpact InlineImpacts[] = {
31 #define INLINE_OBSERVATION(name, type, description, impact, target) InlineImpact::impact,
33 #undef INLINE_OBSERVATION
38 //------------------------------------------------------------------------
39 // InlIsValidObservation: run a validity check on an inline observation
42 // obs - the observation in question
45 // true if the observation is valid
47 bool InlIsValidObservation(InlineObservation obs)
49 return ((obs > InlineObservation::CALLEE_UNUSED_INITIAL) && (obs < InlineObservation::CALLEE_UNUSED_FINAL));
54 //------------------------------------------------------------------------
55 // InlGetObservationString: get a string describing this inline observation
58 // obs - the observation in question
61 // string describing the observation
63 const char* InlGetObservationString(InlineObservation obs)
65 assert(InlIsValidObservation(obs));
66 return InlineDescriptions[static_cast<int>(obs)];
69 //------------------------------------------------------------------------
70 // InlGetTarget: get the target of an inline observation
73 // obs - the observation in question
76 // enum describing the target
78 InlineTarget InlGetTarget(InlineObservation obs)
80 assert(InlIsValidObservation(obs));
81 return InlineTargets[static_cast<int>(obs)];
84 //------------------------------------------------------------------------
85 // InlGetTargetString: get a string describing the target of an inline observation
88 // obs - the observation in question
91 // string describing the target
93 const char* InlGetTargetString(InlineObservation obs)
95 InlineTarget t = InlGetTarget(obs);
98 case InlineTarget::CALLER:
100 case InlineTarget::CALLEE:
102 case InlineTarget::CALLSITE:
105 return "unexpected target";
109 //------------------------------------------------------------------------
110 // InlGetImpact: get the impact of an inline observation
113 // obs - the observation in question
116 // enum value describing the impact
118 InlineImpact InlGetImpact(InlineObservation obs)
120 assert(InlIsValidObservation(obs));
121 return InlineImpacts[static_cast<int>(obs)];
124 //------------------------------------------------------------------------
125 // InlGetImpactString: get a string describing the impact of an inline observation
128 // obs - the observation in question
131 // string describing the impact
133 const char* InlGetImpactString(InlineObservation obs)
135 InlineImpact i = InlGetImpact(obs);
138 case InlineImpact::FATAL:
139 return "correctness -- fatal";
140 case InlineImpact::FUNDAMENTAL:
141 return "correctness -- fundamental limitation";
142 case InlineImpact::LIMITATION:
143 return "correctness -- jit limitation";
144 case InlineImpact::PERFORMANCE:
145 return "performance";
146 case InlineImpact::INFORMATION:
147 return "information";
149 return "unexpected impact";
153 //------------------------------------------------------------------------
154 // InlGetCorInfoInlineDecision: translate decision into a CorInfoInline
157 // d - the decision in question
160 // CorInfoInline value representing the decision
162 CorInfoInline InlGetCorInfoInlineDecision(InlineDecision d)
166 case InlineDecision::SUCCESS:
168 case InlineDecision::FAILURE:
170 case InlineDecision::NEVER:
173 assert(!"Unexpected InlineDecision");
178 //------------------------------------------------------------------------
179 // InlGetDecisionString: get a string representing this decision
182 // d - the decision in question
185 // string representing the decision
187 const char* InlGetDecisionString(InlineDecision d)
191 case InlineDecision::SUCCESS:
193 case InlineDecision::FAILURE:
194 return "failed this call site";
195 case InlineDecision::NEVER:
196 return "failed this callee";
197 case InlineDecision::CANDIDATE:
199 case InlineDecision::UNDECIDED:
202 assert(!"Unexpected InlineDecision");
207 //------------------------------------------------------------------------
208 // InlDecisionIsFailure: check if this decision describes a failing inline
211 // d - the decision in question
214 // true if the inline is definitely a failure
216 bool InlDecisionIsFailure(InlineDecision d)
220 case InlineDecision::SUCCESS:
221 case InlineDecision::UNDECIDED:
222 case InlineDecision::CANDIDATE:
224 case InlineDecision::FAILURE:
225 case InlineDecision::NEVER:
228 assert(!"Unexpected InlineDecision");
233 //------------------------------------------------------------------------
234 // InlDecisionIsSuccess: check if this decision describes a sucessful inline
237 // d - the decision in question
240 // true if the inline is definitely a success
242 bool InlDecisionIsSuccess(InlineDecision d)
246 case InlineDecision::SUCCESS:
248 case InlineDecision::FAILURE:
249 case InlineDecision::NEVER:
250 case InlineDecision::UNDECIDED:
251 case InlineDecision::CANDIDATE:
254 assert(!"Unexpected InlineDecision");
259 //------------------------------------------------------------------------
260 // InlDecisionIsNever: check if this decision describes a never inline
263 // d - the decision in question
266 // true if the inline is a never inline case
268 bool InlDecisionIsNever(InlineDecision d)
272 case InlineDecision::NEVER:
274 case InlineDecision::FAILURE:
275 case InlineDecision::SUCCESS:
276 case InlineDecision::UNDECIDED:
277 case InlineDecision::CANDIDATE:
280 assert(!"Unexpected InlineDecision");
285 //------------------------------------------------------------------------
286 // InlDecisionIsCandidate: check if this decision describes a viable candidate
289 // d - the decision in question
292 // true if this inline still might happen
294 bool InlDecisionIsCandidate(InlineDecision d)
296 return !InlDecisionIsFailure(d);
299 //------------------------------------------------------------------------
300 // InlDecisionIsDecided: check if this decision has been made
303 // d - the decision in question
306 // true if this inline has been decided one way or another
308 bool InlDecisionIsDecided(InlineDecision d)
312 case InlineDecision::NEVER:
313 case InlineDecision::FAILURE:
314 case InlineDecision::SUCCESS:
316 case InlineDecision::UNDECIDED:
317 case InlineDecision::CANDIDATE:
320 assert(!"Unexpected InlineDecision");
325 //------------------------------------------------------------------------
326 // InlineContext: default constructor
328 InlineContext::InlineContext(InlineStrategy* strategy)
329 : m_InlineStrategy(strategy)
335 , m_Offset(BAD_IL_OFFSET)
336 , m_Observation(InlineObservation::CALLEE_UNUSED_INITIAL)
337 , m_CodeSizeEstimate(0)
339 #if defined(DEBUG) || defined(INLINE_DATA)
344 #endif // defined(DEBUG) || defined(INLINE_DATA)
349 #if defined(DEBUG) || defined(INLINE_DATA)
351 //------------------------------------------------------------------------
352 // Dump: Dump an InlineContext entry and all descendants to jitstdout
355 // indent - indentation level for this node
357 void InlineContext::Dump(unsigned indent)
359 // Handle fact that siblings are in reverse order.
360 if (m_Sibling != nullptr)
362 m_Sibling->Dump(indent);
365 // We may not know callee name in some of the failing cases
366 Compiler* compiler = m_InlineStrategy->GetCompiler();
367 const char* calleeName = nullptr;
369 if (m_Callee == nullptr)
372 calleeName = "<unknown>";
378 calleeName = compiler->eeGetMethodFullName(m_Callee);
380 calleeName = "callee";
381 #endif // defined(DEBUG)
384 mdMethodDef calleeToken = compiler->info.compCompHnd->getMethodDefFromMethod(m_Callee);
387 if (m_Parent == nullptr)
390 printf("Inlines into %08X %s\n", calleeToken, calleeName);
395 const char* inlineReason = InlGetObservationString(m_Observation);
396 const char* inlineResult = m_Success ? "" : "FAILED: ";
398 if (m_Offset == BAD_IL_OFFSET)
400 printf("%*s[%u IL=???? TR=%06u %08X] [%s%s] %s\n", indent, "", m_Ordinal, m_TreeID, calleeToken,
401 inlineResult, inlineReason, calleeName);
405 IL_OFFSET offset = jitGetILoffs(m_Offset);
406 printf("%*s[%u IL=%04d TR=%06u %08X] [%s%s] %s\n", indent, "", m_Ordinal, offset, m_TreeID, calleeToken,
407 inlineResult, inlineReason, calleeName);
411 // Recurse to first child
412 if (m_Child != nullptr)
414 m_Child->Dump(indent + 2);
418 //------------------------------------------------------------------------
419 // DumpData: Dump a successful InlineContext entry, detailed data, and
420 // any successful descendant inlines
423 // indent - indentation level for this node
425 void InlineContext::DumpData(unsigned indent)
427 // Handle fact that siblings are in reverse order.
428 if (m_Sibling != nullptr)
430 m_Sibling->DumpData(indent);
433 Compiler* compiler = m_InlineStrategy->GetCompiler();
436 const char* calleeName = compiler->eeGetMethodFullName(m_Callee);
438 const char* calleeName = "callee";
439 #endif // defined(DEBUG)
441 if (m_Parent == nullptr)
443 // Root method... cons up a policy so we can display the name
444 InlinePolicy* policy = InlinePolicy::GetPolicy(compiler, true);
445 printf("\nInlines [%u] into \"%s\" [%s]\n", m_InlineStrategy->GetInlineCount(), calleeName, policy->GetName());
449 const char* inlineReason = InlGetObservationString(m_Observation);
450 printf("%*s%u,\"%s\",\"%s\",", indent, "", m_Ordinal, inlineReason, calleeName);
451 m_Policy->DumpData(jitstdout);
455 // Recurse to first child
456 if (m_Child != nullptr)
458 m_Child->DumpData(indent + 2);
462 //------------------------------------------------------------------------
463 // DumpXml: Dump an InlineContext entry and all descendants in xml format
466 // file - file for output
467 // indent - indentation level for this node
469 void InlineContext::DumpXml(FILE* file, unsigned indent)
471 // Handle fact that siblings are in reverse order.
472 if (m_Sibling != nullptr)
474 m_Sibling->DumpXml(file, indent);
477 // Optionally suppress failing inline records
478 if ((JitConfig.JitInlineDumpXml() == 3) && !m_Success)
483 const bool isRoot = m_Parent == nullptr;
484 const bool hasChild = m_Child != nullptr;
485 const char* inlineType = m_Success ? "Inline" : "FailedInline";
486 unsigned newIndent = indent;
490 Compiler* compiler = m_InlineStrategy->GetCompiler();
492 mdMethodDef calleeToken = compiler->info.compCompHnd->getMethodDefFromMethod(m_Callee);
493 unsigned calleeHash = compiler->info.compCompHnd->getMethodHash(m_Callee);
495 const char* inlineReason = InlGetObservationString(m_Observation);
498 if (m_Offset != BAD_IL_OFFSET)
500 offset = (int)jitGetILoffs(m_Offset);
503 fprintf(file, "%*s<%s>\n", indent, "", inlineType);
504 fprintf(file, "%*s<Token>%u</Token>\n", indent + 2, "", calleeToken);
505 fprintf(file, "%*s<Hash>%u</Hash>\n", indent + 2, "", calleeHash);
506 fprintf(file, "%*s<Offset>%u</Offset>\n", indent + 2, "", offset);
507 fprintf(file, "%*s<Reason>%s</Reason>\n", indent + 2, "", inlineReason);
509 // Optionally, dump data about the inline
510 const int dumpDataSetting = JitConfig.JitInlineDumpData();
512 // JitInlineDumpData=1 -- dump data plus deltas for last inline only
513 if ((dumpDataSetting == 1) && (this == m_InlineStrategy->GetLastContext()))
515 fprintf(file, "%*s<Data>", indent + 2, "");
516 m_InlineStrategy->DumpDataContents(file);
517 fprintf(file, "</Data>\n");
520 // JitInlineDumpData=2 -- dump data for all inlines, no deltas
521 if ((dumpDataSetting == 2) && (m_Policy != nullptr))
523 fprintf(file, "%*s<Data>", indent + 2, "");
524 m_Policy->DumpData(file);
525 fprintf(file, "</Data>\n");
528 newIndent = indent + 2;
535 fprintf(file, "%*s<Inlines>\n", newIndent, "");
536 m_Child->DumpXml(file, newIndent + 2);
537 fprintf(file, "%*s</Inlines>\n", newIndent, "");
541 fprintf(file, "%*s<Inlines />\n", newIndent, "");
548 fprintf(file, "%*s</%s>\n", indent, "", inlineType);
552 #endif // defined(DEBUG) || defined(INLINE_DATA)
554 //------------------------------------------------------------------------
555 // InlineResult: Construct an InlineResult to evaluate a particular call
559 // compiler - the compiler instance examining a call for inlining
560 // call - the call in question
561 // stmt - statement containing the call (if known)
562 // description - string describing the context of the decision
564 InlineResult::InlineResult(Compiler* compiler, GenTreeCall* call, GenTreeStmt* stmt, const char* description)
565 : m_RootCompiler(nullptr)
568 , m_InlineContext(nullptr)
571 , m_Description(description)
574 // Set the compiler instance
575 m_RootCompiler = compiler->impInlineRoot();
578 const bool isPrejitRoot = false;
579 m_Policy = InlinePolicy::GetPolicy(m_RootCompiler, isPrejitRoot);
581 // Pass along some optional information to the policy.
584 m_InlineContext = stmt->gtInlineContext;
585 m_Policy->NoteContext(m_InlineContext);
587 #if defined(DEBUG) || defined(INLINE_DATA)
588 m_Policy->NoteOffset(call->gtRawILOffset);
590 m_Policy->NoteOffset(stmt->gtStmtILoffsx);
591 #endif // defined(DEBUG) || defined(INLINE_DATA)
594 // Get method handle for caller. Note we use the
595 // handle for the "immediate" caller here.
596 m_Caller = compiler->info.compMethodHnd;
598 // Get method handle for callee, if known
599 if (m_Call->gtCall.gtCallType == CT_USER_FUNC)
601 m_Callee = m_Call->gtCall.gtCallMethHnd;
605 //------------------------------------------------------------------------
606 // InlineResult: Construct an InlineResult to evaluate a particular
607 // method as a possible inline candidate, while prejtting.
610 // compiler - the compiler instance doing the prejitting
611 // method - the method in question
612 // description - string describing the context of the decision
615 // Used only during prejitting to try and pre-identify methods that
616 // cannot be inlined, to help subsequent jit throughput.
618 // We use the inlCallee member to track the method since logically
619 // it is the callee here.
621 InlineResult::InlineResult(Compiler* compiler, CORINFO_METHOD_HANDLE method, const char* description)
622 : m_RootCompiler(nullptr)
625 , m_InlineContext(nullptr)
628 , m_Description(description)
631 // Set the compiler instance
632 m_RootCompiler = compiler->impInlineRoot();
635 const bool isPrejitRoot = true;
636 m_Policy = InlinePolicy::GetPolicy(m_RootCompiler, isPrejitRoot);
639 //------------------------------------------------------------------------
640 // Report: Dump, log, and report information about an inline decision.
643 // Called (automatically via the InlineResult dtor) when the
644 // inliner is done evaluating a candidate.
646 // Dumps state of the inline candidate, and if a decision was
647 // reached, sends it to the log and reports the decision back to the
648 // EE. Optionally update the method attribute to NOINLINE if
649 // observation and policy warrant.
651 // All this can be suppressed if desired by calling setReported()
652 // before the InlineResult goes out of scope.
654 void InlineResult::Report()
656 // If we weren't actually inlining, user may have suppressed
657 // reporting via setReported(). If so, do nothing.
666 const char* callee = nullptr;
667 const bool showInlines = (JitConfig.JitPrintInlinedMethods() == 1);
669 // Optionally dump the result
670 if (VERBOSE || showInlines)
672 const char* format = "INLINER: during '%s' result '%s' reason '%s' for '%s' calling '%s'\n";
673 const char* caller = (m_Caller == nullptr) ? "n/a" : m_RootCompiler->eeGetMethodFullName(m_Caller);
675 callee = (m_Callee == nullptr) ? "n/a" : m_RootCompiler->eeGetMethodFullName(m_Callee);
677 JITDUMP(format, m_Description, ResultString(), ReasonString(), caller, callee);
680 // If the inline failed, leave information on the call so we can
681 // later recover what observation lead to the failure.
682 if (IsFailure() && (m_Call != nullptr))
684 // compiler should have revoked candidacy on the call by now
685 assert((m_Call->gtFlags & GTF_CALL_INLINE_CANDIDATE) == 0);
687 m_Call->gtInlineObservation = m_Policy->GetObservation();
692 // Was the result NEVER? If so we might want to propagate this to
695 if (IsNever() && m_Policy->PropagateNeverToRuntime())
697 // If we know the callee, and if the observation that got us
698 // to this Never inline state is something *other* than
699 // IS_NOINLINE, then we've uncovered a reason why this method
700 // can't ever be inlined. Update the callee method attributes
701 // so that future inline attempts for this callee fail faster.
703 InlineObservation obs = m_Policy->GetObservation();
705 if ((m_Callee != nullptr) && (obs != InlineObservation::CALLEE_IS_NOINLINE))
710 const char* obsString = InlGetObservationString(obs);
714 JITDUMP("\nINLINER: Marking %s as NOINLINE because of %s\n", callee, obsString);
719 printf("Marking %s as NOINLINE because of %s\n", callee, obsString);
724 COMP_HANDLE comp = m_RootCompiler->info.compCompHnd;
725 comp->setMethodAttribs(m_Callee, CORINFO_FLG_BAD_INLINEE);
731 const char* format = "INLINER: during '%s' result '%s' reason '%s'\n";
732 JITLOG_THIS(m_RootCompiler, (LL_INFO100000, format, m_Description, ResultString(), ReasonString()));
733 COMP_HANDLE comp = m_RootCompiler->info.compCompHnd;
734 comp->reportInliningDecision(m_Caller, m_Callee, Result(), ReasonString());
738 //------------------------------------------------------------------------
739 // InlineStrategy construtor
742 // compiler - root compiler instance
744 InlineStrategy::InlineStrategy(Compiler* compiler)
745 : m_Compiler(compiler)
746 , m_RootContext(nullptr)
747 , m_LastSuccessfulPolicy(nullptr)
748 , m_LastContext(nullptr)
749 , m_PrejitRootDecision(InlineDecision::UNDECIDED)
751 , m_CandidateCount(0)
752 , m_AlwaysCandidateCount(0)
753 , m_ForceCandidateCount(0)
754 , m_DiscretionaryCandidateCount(0)
755 , m_UnprofitableCandidateCount(0)
758 , m_MaxInlineSize(DEFAULT_MAX_INLINE_SIZE)
759 , m_MaxInlineDepth(DEFAULT_MAX_INLINE_DEPTH)
760 , m_InitialTimeBudget(0)
761 , m_InitialTimeEstimate(0)
762 , m_CurrentTimeBudget(0)
763 , m_CurrentTimeEstimate(0)
764 , m_InitialSizeEstimate(0)
765 , m_CurrentSizeEstimate(0)
766 , m_HasForceViaDiscretionary(false)
767 #if defined(DEBUG) || defined(INLINE_DATA)
768 , m_MethodXmlFilePosition(0)
770 #endif // defined(DEBUG) || defined(INLINE_DATA)
773 // Verify compiler is a root compiler instance
774 assert(m_Compiler->impInlineRoot() == m_Compiler);
778 // Possibly modify the max inline size.
780 // Default value of JitInlineSize is the same as our default.
781 // So normally this next line does not change the size.
782 m_MaxInlineSize = JitConfig.JitInlineSize();
784 // Up the max size under stress
785 if (m_Compiler->compInlineStress())
787 m_MaxInlineSize *= 10;
790 // But don't overdo it
791 if (m_MaxInlineSize > IMPLEMENTATION_MAX_INLINE_SIZE)
793 m_MaxInlineSize = IMPLEMENTATION_MAX_INLINE_SIZE;
796 // Verify: not too small, not too big.
797 assert(m_MaxInlineSize >= ALWAYS_INLINE_SIZE);
798 assert(m_MaxInlineSize <= IMPLEMENTATION_MAX_INLINE_SIZE);
800 // Possibly modify the max inline depth
802 // Default value of JitInlineDepth is the same as our default.
803 // So normally this next line does not change the size.
804 m_MaxInlineDepth = JitConfig.JitInlineDepth();
806 // But don't overdo it
807 if (m_MaxInlineDepth > IMPLEMENTATION_MAX_INLINE_DEPTH)
809 m_MaxInlineDepth = IMPLEMENTATION_MAX_INLINE_DEPTH;
815 //------------------------------------------------------------------------
816 // GetRootContext: get the InlineContext for the root method
819 // Root context; describes the method being jitted.
822 // Also initializes the jit time estimate and budget.
824 InlineContext* InlineStrategy::GetRootContext()
826 if (m_RootContext == nullptr)
828 // Allocate on first demand.
829 m_RootContext = NewRoot();
831 // Estimate how long the jit will take if there's no inlining
832 // done to this method.
833 m_InitialTimeEstimate = EstimateTime(m_RootContext);
834 m_CurrentTimeEstimate = m_InitialTimeEstimate;
836 // Set the initial budget for inlining. Note this is
837 // deliberately set very high and is intended to catch
838 // only pathological runaway inline cases.
839 m_InitialTimeBudget = BUDGET * m_InitialTimeEstimate;
840 m_CurrentTimeBudget = m_InitialTimeBudget;
842 // Estimate the code size if there's no inlining
843 m_InitialSizeEstimate = EstimateSize(m_RootContext);
844 m_CurrentSizeEstimate = m_InitialSizeEstimate;
847 assert(m_CurrentTimeEstimate > 0);
848 assert(m_CurrentSizeEstimate > 0);
850 // Cache as the "last" context created
851 m_LastContext = m_RootContext;
854 return m_RootContext;
857 //------------------------------------------------------------------------
858 // NoteAttempt: do bookkeeping for an inline attempt
861 // result -- InlineResult for successful inline candidate
863 void InlineStrategy::NoteAttempt(InlineResult* result)
865 assert(result->IsCandidate());
866 InlineObservation obs = result->GetObservation();
868 if (obs == InlineObservation::CALLEE_BELOW_ALWAYS_INLINE_SIZE)
870 m_AlwaysCandidateCount++;
872 else if (obs == InlineObservation::CALLEE_IS_FORCE_INLINE)
874 m_ForceCandidateCount++;
878 m_DiscretionaryCandidateCount++;
882 //------------------------------------------------------------------------
883 // DumpCsvHeader: dump header for csv inline stats
886 // fp -- file for dump output
888 void InlineStrategy::DumpCsvHeader(FILE* fp)
890 fprintf(fp, "\"InlineCalls\",");
891 fprintf(fp, "\"InlineCandidates\",");
892 fprintf(fp, "\"InlineAlways\",");
893 fprintf(fp, "\"InlineForce\",");
894 fprintf(fp, "\"InlineDiscretionary\",");
895 fprintf(fp, "\"InlineUnprofitable\",");
896 fprintf(fp, "\"InlineEarlyFail\",");
897 fprintf(fp, "\"InlineImport\",");
898 fprintf(fp, "\"InlineLateFail\",");
899 fprintf(fp, "\"InlineSuccess\",");
902 //------------------------------------------------------------------------
903 // DumpCsvData: dump data for csv inline stats
906 // fp -- file for dump output
908 void InlineStrategy::DumpCsvData(FILE* fp)
910 fprintf(fp, "%u,", m_CallCount);
911 fprintf(fp, "%u,", m_CandidateCount);
912 fprintf(fp, "%u,", m_AlwaysCandidateCount);
913 fprintf(fp, "%u,", m_ForceCandidateCount);
914 fprintf(fp, "%u,", m_DiscretionaryCandidateCount);
915 fprintf(fp, "%u,", m_UnprofitableCandidateCount);
917 // Early failures are cases where candates are rejected between
918 // the time the jit invokes the inlinee compiler and the time it
919 // starts to import the inlinee IL.
921 // So they are "cheaper" that late failures.
923 unsigned profitableCandidateCount = m_DiscretionaryCandidateCount - m_UnprofitableCandidateCount;
925 unsigned earlyFailCount =
926 m_CandidateCount - m_AlwaysCandidateCount - m_ForceCandidateCount - profitableCandidateCount;
928 fprintf(fp, "%u,", earlyFailCount);
930 unsigned lateFailCount = m_ImportCount - m_InlineCount;
932 fprintf(fp, "%u,", m_ImportCount);
933 fprintf(fp, "%u,", lateFailCount);
934 fprintf(fp, "%u,", m_InlineCount);
937 //------------------------------------------------------------------------
938 // EstimateTime: estimate impact of this inline on the method jit time
941 // context - context describing this inline
944 // Nominal estimate of jit time.
946 int InlineStrategy::EstimateTime(InlineContext* context)
948 // Simple linear models based on observations
949 // show time is fairly well predicted by IL size.
950 unsigned ilSize = context->GetILSize();
952 // Prediction varies for root and inlines.
953 if (context == m_RootContext)
955 return EstimateRootTime(ilSize);
959 return EstimateInlineTime(ilSize);
963 //------------------------------------------------------------------------
964 // EstimteRootTime: estimate jit time for method of this size with
968 // ilSize - size of the method's IL
971 // Nominal estimate of jit time.
974 // Based on observational data. Time is nominally microseconds.
976 int InlineStrategy::EstimateRootTime(unsigned ilSize)
978 return 60 + 3 * ilSize;
981 //------------------------------------------------------------------------
982 // EstimteInlineTime: estimate time impact on jitting for an inline
986 // ilSize - size of the method's IL
989 // Nominal increase in jit time.
992 // Based on observational data. Time is nominally microseconds.
993 // Small inlines will make the jit a bit faster.
995 int InlineStrategy::EstimateInlineTime(unsigned ilSize)
997 return -14 + 2 * ilSize;
1000 //------------------------------------------------------------------------
1001 // EstimateSize: estimate impact of this inline on the method size
1004 // context - context describing this inline
1007 // Nominal estimate of method size (bytes * 10)
1009 int InlineStrategy::EstimateSize(InlineContext* context)
1011 // Prediction varies for root and inlines.
1012 if (context == m_RootContext)
1014 // Simple linear models based on observations show root method
1015 // native code size is fairly well predicted by IL size.
1017 // Model below is for x64 on windows.
1018 unsigned ilSize = context->GetILSize();
1019 int estimate = (1312 + 228 * ilSize) / 10;
1025 // Use context's code size estimate.
1026 return context->GetCodeSizeEstimate();
1030 //------------------------------------------------------------------------
1031 // NoteOutcome: do bookkeeping for an inline
1034 // context - context for the inlie
1036 void InlineStrategy::NoteOutcome(InlineContext* context)
1038 // Note we can't generally count up failures here -- we only
1039 // create contexts for failures in debug modes, and even then
1040 // we may not get them all.
1041 if (context->IsSuccess())
1045 #if defined(DEBUG) || defined(INLINE_DATA)
1047 // Keep track of the inline targeted for data collection or,
1048 // if we don't have one (yet), the last successful inline.
1049 bool updateLast = (m_LastSuccessfulPolicy == nullptr) || !m_LastSuccessfulPolicy->IsDataCollectionTarget();
1053 m_LastContext = context;
1054 m_LastSuccessfulPolicy = context->m_Policy;
1058 // We only expect one inline to be a data collection
1060 assert(!context->m_Policy->IsDataCollectionTarget());
1063 #endif // defined(DEBUG) || defined(INLINE_DATA)
1067 // If callee is a force inline, increase budget, provided all
1068 // parent contexts are likewise force inlines.
1070 // If callee is discretionary or has a discretionary ancestor,
1071 // increase expense.
1073 InlineContext* currentContext = context;
1074 bool isForceInline = false;
1076 while (currentContext != m_RootContext)
1078 InlineObservation observation = currentContext->GetObservation();
1080 if (observation != InlineObservation::CALLEE_IS_FORCE_INLINE)
1084 // Interesting case where discretionary inlines pull
1085 // in a force inline...
1086 m_HasForceViaDiscretionary = true;
1089 isForceInline = false;
1093 isForceInline = true;
1094 currentContext = currentContext->GetParent();
1097 int timeDelta = EstimateTime(context);
1101 // Update budget since this inline was forced. Only allow
1102 // budget to increase.
1105 m_CurrentTimeBudget += timeDelta;
1109 // Update time estimate.
1110 m_CurrentTimeEstimate += timeDelta;
1112 // Update size estimate.
1114 // Sometimes estimates don't make sense. Don't let the method
1115 // size go negative.
1116 int sizeDelta = EstimateSize(context);
1118 if (m_CurrentSizeEstimate + sizeDelta <= 0)
1123 // Update the code size estimate.
1124 m_CurrentSizeEstimate += sizeDelta;
1128 //------------------------------------------------------------------------
1129 // BudgetCheck: return true if as inline of this size would exceed the
1130 // jit time budget for this method
1133 // ilSize - size of the method's IL
1136 // true if the inline would go over budget
1138 bool InlineStrategy::BudgetCheck(unsigned ilSize)
1140 int timeDelta = EstimateInlineTime(ilSize);
1141 return (timeDelta + m_CurrentTimeEstimate > m_CurrentTimeBudget);
1144 //------------------------------------------------------------------------
1145 // NewRoot: construct an InlineContext for the root method
1148 // InlineContext for use as the root context
1151 // We leave m_Code as nullptr here (rather than the IL buffer
1152 // address of the root method) to preserve existing behavior, which
1153 // is to allow one recursive inline.
1155 InlineContext* InlineStrategy::NewRoot()
1157 InlineContext* rootContext = new (m_Compiler, CMK_Inlining) InlineContext(this);
1159 rootContext->m_ILSize = m_Compiler->info.compILCodeSize;
1161 #if defined(DEBUG) || defined(INLINE_DATA)
1163 rootContext->m_Callee = m_Compiler->info.compMethodHnd;
1165 #endif // defined(DEBUG) || defined(INLINE_DATA)
1170 //------------------------------------------------------------------------
1171 // NewSuccess: construct an InlineContext for a successful inline
1172 // and link it into the context tree
1175 // stmt - statement containing call being inlined
1176 // inlineInfo - information about this inline
1179 // A new InlineContext for statements brought into the method by
1182 InlineContext* InlineStrategy::NewSuccess(InlineInfo* inlineInfo)
1184 InlineContext* calleeContext = new (m_Compiler, CMK_Inlining) InlineContext(this);
1185 GenTreeStmt* stmt = inlineInfo->iciStmt;
1186 BYTE* calleeIL = inlineInfo->inlineCandidateInfo->methInfo.ILCode;
1187 unsigned calleeILSize = inlineInfo->inlineCandidateInfo->methInfo.ILCodeSize;
1188 InlineContext* parentContext = stmt->gtInlineContext;
1190 noway_assert(parentContext != nullptr);
1192 calleeContext->m_Code = calleeIL;
1193 calleeContext->m_ILSize = calleeILSize;
1194 calleeContext->m_Parent = parentContext;
1195 // Push on front here will put siblings in reverse lexical
1196 // order which we undo in the dumper
1197 calleeContext->m_Sibling = parentContext->m_Child;
1198 parentContext->m_Child = calleeContext;
1199 calleeContext->m_Child = nullptr;
1200 calleeContext->m_Offset = stmt->AsStmt()->gtStmtILoffsx;
1201 calleeContext->m_Observation = inlineInfo->inlineResult->GetObservation();
1202 calleeContext->m_Success = true;
1204 #if defined(DEBUG) || defined(INLINE_DATA)
1206 InlinePolicy* policy = inlineInfo->inlineResult->GetPolicy();
1208 calleeContext->m_Policy = policy;
1209 calleeContext->m_CodeSizeEstimate = policy->CodeSizeEstimate();
1210 calleeContext->m_Callee = inlineInfo->fncHandle;
1211 // +1 here since we set this before calling NoteOutcome.
1212 calleeContext->m_Ordinal = m_InlineCount + 1;
1213 // Update offset with more accurate info
1214 calleeContext->m_Offset = inlineInfo->inlineResult->GetCall()->gtRawILOffset;
1216 #endif // defined(DEBUG) || defined(INLINE_DATA)
1220 calleeContext->m_TreeID = inlineInfo->inlineResult->GetCall()->gtTreeID;
1222 #endif // defined(DEBUG)
1224 NoteOutcome(calleeContext);
1226 return calleeContext;
1229 #if defined(DEBUG) || defined(INLINE_DATA)
1231 //------------------------------------------------------------------------
1232 // NewFailure: construct an InlineContext for a failing inline
1233 // and link it into the context tree
1236 // stmt - statement containing the attempted inline
1237 // inlineResult - inlineResult for the attempt
1240 // A new InlineContext for diagnostic purposes, or nullptr if
1241 // the desired context could not be created.
1243 InlineContext* InlineStrategy::NewFailure(GenTreeStmt* stmt, InlineResult* inlineResult)
1245 // Check for a parent context first. We should now have a parent
1246 // context for all statements.
1247 InlineContext* parentContext = stmt->gtInlineContext;
1248 assert(parentContext != nullptr);
1249 InlineContext* failedContext = new (m_Compiler, CMK_Inlining) InlineContext(this);
1251 // Pushing the new context on the front of the parent child list
1252 // will put siblings in reverse lexical order which we undo in the
1254 failedContext->m_Parent = parentContext;
1255 failedContext->m_Sibling = parentContext->m_Child;
1256 parentContext->m_Child = failedContext;
1257 failedContext->m_Child = nullptr;
1258 failedContext->m_Offset = stmt->gtStmtILoffsx;
1259 failedContext->m_Observation = inlineResult->GetObservation();
1260 failedContext->m_Callee = inlineResult->GetCallee();
1261 failedContext->m_Success = false;
1263 assert(InlIsValidObservation(failedContext->m_Observation));
1265 #if defined(DEBUG) || defined(INLINE_DATA)
1267 // Update offset with more accurate info
1268 failedContext->m_Offset = inlineResult->GetCall()->gtRawILOffset;
1270 #endif // #if defined(DEBUG) || defined(INLINE_DATA)
1274 failedContext->m_TreeID = inlineResult->GetCall()->gtTreeID;
1276 #endif // defined(DEBUG)
1278 NoteOutcome(failedContext);
1280 return failedContext;
1283 //------------------------------------------------------------------------
1284 // Dump: dump description of inline behavior
1286 void InlineStrategy::Dump()
1288 m_RootContext->Dump();
1290 printf("Budget: initialTime=%d, finalTime=%d, initialBudget=%d, currentBudget=%d\n", m_InitialTimeEstimate,
1291 m_CurrentTimeEstimate, m_InitialTimeBudget, m_CurrentTimeBudget);
1293 if (m_CurrentTimeBudget > m_InitialTimeBudget)
1295 printf("Budget: increased by %d because of force inlines\n", m_CurrentTimeBudget - m_InitialTimeBudget);
1298 if (m_CurrentTimeEstimate > m_CurrentTimeBudget)
1300 printf("Budget: went over budget by %d\n", m_CurrentTimeEstimate - m_CurrentTimeBudget);
1303 if (m_HasForceViaDiscretionary)
1305 printf("Budget: discretionary inline caused a force inline\n");
1308 printf("Budget: initialSize=%d, finalSize=%d\n", m_InitialSizeEstimate, m_CurrentSizeEstimate);
1311 // Static to track emission of the inline data header
1313 bool InlineStrategy::s_HasDumpedDataHeader = false;
1315 //------------------------------------------------------------------------
1316 // DumpData: dump data about the last successful inline into this method
1317 // in a format suitable for automated analysis.
1319 void InlineStrategy::DumpData()
1321 // Is dumping enabled? If not, nothing to do.
1322 if (JitConfig.JitInlineDumpData() == 0)
1327 // If we're also dumping inline XML, we'll let it dump the data.
1328 if (JitConfig.JitInlineDumpXml() != 0)
1333 // Don't dump anything if limiting is on and we didn't reach
1334 // the limit while inlining.
1336 // This serves to filter out duplicate data.
1337 const int limit = JitConfig.JitInlineLimit();
1339 if ((limit >= 0) && (m_InlineCount < static_cast<unsigned>(limit)))
1344 // Dump header, if not already dumped
1345 if (!s_HasDumpedDataHeader)
1347 DumpDataHeader(stderr);
1348 s_HasDumpedDataHeader = true;
1352 DumpDataContents(stderr);
1353 fprintf(stderr, "\n");
1356 //------------------------------------------------------------------------
1357 // DumpDataEnsurePolicyIsSet: ensure m_LastSuccessfulPolicy describes the
1358 // inline policy in effect.
1361 // Needed for methods that don't have any successful inlines.
1363 void InlineStrategy::DumpDataEnsurePolicyIsSet()
1365 // Cache references to compiler substructures.
1366 const Compiler::Info& info = m_Compiler->info;
1367 const Compiler::Options& opts = m_Compiler->opts;
1369 // If there weren't any successful inlines, we won't have a
1370 // successful policy, so fake one up.
1371 if (m_LastSuccessfulPolicy == nullptr)
1373 const bool isPrejitRoot = opts.jitFlags->IsSet(JitFlags::JIT_FLAG_PREJIT);
1374 m_LastSuccessfulPolicy = InlinePolicy::GetPolicy(m_Compiler, isPrejitRoot);
1376 // Add in a bit of data....
1377 const bool isForceInline = (info.compFlags & CORINFO_FLG_FORCEINLINE) != 0;
1378 m_LastSuccessfulPolicy->NoteBool(InlineObservation::CALLEE_IS_FORCE_INLINE, isForceInline);
1379 m_LastSuccessfulPolicy->NoteInt(InlineObservation::CALLEE_IL_CODE_SIZE, info.compMethodInfo->ILCodeSize);
1383 //------------------------------------------------------------------------
1384 // DumpDataHeader: dump header for inline data.
1387 // file - file for data output
1389 void InlineStrategy::DumpDataHeader(FILE* file)
1391 DumpDataEnsurePolicyIsSet();
1392 const int limit = JitConfig.JitInlineLimit();
1393 fprintf(file, "*** Inline Data: Policy=%s JitInlineLimit=%d ***\n", m_LastSuccessfulPolicy->GetName(), limit);
1394 DumpDataSchema(file);
1395 fprintf(file, "\n");
1398 //------------------------------------------------------------------------
1399 // DumpSchema: dump schema for inline data.
1402 // file - file for data output
1404 void InlineStrategy::DumpDataSchema(FILE* file)
1406 DumpDataEnsurePolicyIsSet();
1407 fprintf(file, "Method,Version,HotSize,ColdSize,JitTime,SizeEstimate,TimeEstimate,");
1408 m_LastSuccessfulPolicy->DumpSchema(file);
1411 //------------------------------------------------------------------------
1412 // DumpDataContents: dump contents of inline data
1415 // file - file for data output
1417 void InlineStrategy::DumpDataContents(FILE* file)
1419 DumpDataEnsurePolicyIsSet();
1421 // Cache references to compiler substructures.
1422 const Compiler::Info& info = m_Compiler->info;
1423 const Compiler::Options& opts = m_Compiler->opts;
1425 // We'd really like the method identifier to be unique and
1426 // durable across crossgen invocations. Not clear how to
1427 // accomplish this, so we'll use the token for now.
1429 // Post processing will have to filter out all data from
1430 // methods where the root entry appears multiple times.
1431 mdMethodDef currentMethodToken = info.compCompHnd->getMethodDefFromMethod(info.compMethodHnd);
1433 // Convert time spent jitting into microseconds
1434 unsigned microsecondsSpentJitting = 0;
1435 unsigned __int64 compCycles = m_Compiler->getInlineCycleCount();
1438 double countsPerSec = CycleTimer::CyclesPerSecond();
1439 double counts = (double)compCycles;
1440 microsecondsSpentJitting = (unsigned)((counts / countsPerSec) * 1000 * 1000);
1443 fprintf(file, "%08X,%u,%u,%u,%u,%d,%d,", currentMethodToken, m_InlineCount, info.compTotalHotCodeSize,
1444 info.compTotalColdCodeSize, microsecondsSpentJitting, m_CurrentSizeEstimate / 10, m_CurrentTimeEstimate);
1445 m_LastSuccessfulPolicy->DumpData(file);
1448 // Static to track emission of the xml data header
1449 // and lock to prevent interleaved file writes
1451 bool InlineStrategy::s_HasDumpedXmlHeader = false;
1452 CritSecObject InlineStrategy::s_XmlWriterLock;
1454 //------------------------------------------------------------------------
1455 // DumpXml: dump xml-formatted version of the inline tree.
1458 // file - file for data output
1459 // indent - indent level of this element
1461 void InlineStrategy::DumpXml(FILE* file, unsigned indent)
1463 if (JitConfig.JitInlineDumpXml() == 0)
1468 // Lock to prevent interleaving of trees.
1469 CritSecHolder writeLock(s_XmlWriterLock);
1472 if (!s_HasDumpedXmlHeader)
1474 DumpDataEnsurePolicyIsSet();
1476 fprintf(file, "<?xml version=\"1.0\"?>\n");
1477 fprintf(file, "<InlineForest>\n");
1478 fprintf(file, "<Policy>%s</Policy>\n", m_LastSuccessfulPolicy->GetName());
1480 const int dumpDataSetting = JitConfig.JitInlineDumpData();
1481 if (dumpDataSetting != 0)
1483 fprintf(file, "<DataSchema>");
1485 if (dumpDataSetting == 1)
1487 // JitInlineDumpData=1 -- dump schema for data plus deltas
1488 DumpDataSchema(file);
1490 else if (dumpDataSetting == 2)
1492 // JitInlineDumpData=2 -- dump schema for data only
1493 m_LastSuccessfulPolicy->DumpSchema(file);
1496 fprintf(file, "</DataSchema>\n");
1499 fprintf(file, "<Methods>\n");
1500 s_HasDumpedXmlHeader = true;
1503 // If we're dumping "minimal" Xml, and we didn't do
1504 // any inlines into this method, then there's nothing
1506 if ((m_InlineCount == 0) && (JitConfig.JitInlineDumpXml() >= 2))
1511 // Cache references to compiler substructures.
1512 const Compiler::Info& info = m_Compiler->info;
1513 const Compiler::Options& opts = m_Compiler->opts;
1515 const bool isPrejitRoot = opts.jitFlags->IsSet(JitFlags::JIT_FLAG_PREJIT);
1516 const bool isForceInline = (info.compFlags & CORINFO_FLG_FORCEINLINE) != 0;
1518 // We'd really like the method identifier to be unique and
1519 // durable across crossgen invocations. Not clear how to
1520 // accomplish this, so we'll use the token for now.
1522 // Post processing will have to filter out all data from
1523 // methods where the root entry appears multiple times.
1524 mdMethodDef currentMethodToken = info.compCompHnd->getMethodDefFromMethod(info.compMethodHnd);
1526 unsigned hash = info.compMethodHash();
1528 // Convert time spent jitting into microseconds
1529 unsigned microsecondsSpentJitting = 0;
1530 unsigned __int64 compCycles = m_Compiler->getInlineCycleCount();
1533 double countsPerSec = CycleTimer::CyclesPerSecond();
1534 double counts = (double)compCycles;
1535 microsecondsSpentJitting = (unsigned)((counts / countsPerSec) * 1000 * 1000);
1538 // Get method name just for root method, to make it a bit easier
1539 // to search for things in the inline xml.
1540 const char* methodName = info.compCompHnd->getMethodName(info.compMethodHnd, nullptr);
1542 // Cheap xml quoting for values. Only < and & are troublemakers,
1543 // but change > for symmetry.
1545 // Ok to truncate name, just ensure it's null terminated.
1547 strncpy(buf, methodName, sizeof(buf));
1548 buf[sizeof(buf) - 1] = 0;
1550 for (int i = 0; i < _countof(buf); i++)
1568 fprintf(file, "%*s<Method>\n", indent, "");
1569 fprintf(file, "%*s<Token>%u</Token>\n", indent + 2, "", currentMethodToken);
1570 fprintf(file, "%*s<Hash>%u</Hash>\n", indent + 2, "", hash);
1571 fprintf(file, "%*s<Name>%s</Name>\n", indent + 2, "", buf);
1572 fprintf(file, "%*s<InlineCount>%u</InlineCount>\n", indent + 2, "", m_InlineCount);
1573 fprintf(file, "%*s<HotSize>%u</HotSize>\n", indent + 2, "", info.compTotalHotCodeSize);
1574 fprintf(file, "%*s<ColdSize>%u</ColdSize>\n", indent + 2, "", info.compTotalColdCodeSize);
1575 fprintf(file, "%*s<JitTime>%u</JitTime>\n", indent + 2, "", microsecondsSpentJitting);
1576 fprintf(file, "%*s<SizeEstimate>%u</SizeEstimate>\n", indent + 2, "", m_CurrentSizeEstimate / 10);
1577 fprintf(file, "%*s<TimeEstimate>%u</TimeEstimate>\n", indent + 2, "", m_CurrentTimeEstimate);
1579 // For prejit roots also propagate out the assessment of the root method
1582 fprintf(file, "%*s<PrejitDecision>%s</PrejitDecision>\n", indent + 2, "",
1583 InlGetDecisionString(m_PrejitRootDecision));
1584 fprintf(file, "%*s<PrejitObservation>%s</PrejitObservation>\n", indent + 2, "",
1585 InlGetObservationString(m_PrejitRootObservation));
1588 // Root context will be null if we're not optimizing the method.
1590 // Note there are cases of this in mscorlib even in release builds,
1591 // eg Task.NotifyDebuggerOfWaitCompletion.
1593 // For such methods there aren't any inlines.
1594 if (m_RootContext != nullptr)
1596 m_RootContext->DumpXml(file, indent + 2);
1600 fprintf(file, "%*s<Inlines/>\n", indent + 2, "");
1603 fprintf(file, "%*s</Method>\n", indent, "");
1606 //------------------------------------------------------------------------
1607 // FinalizeXml: finalize the xml-formatted version of the inline tree.
1610 // file - file for data output
1612 void InlineStrategy::FinalizeXml(FILE* file)
1614 // If we dumped the header, dump a footer
1615 if (s_HasDumpedXmlHeader)
1617 fprintf(file, "</Methods>\n");
1618 fprintf(file, "</InlineForest>\n");
1621 // Workaroud compShutdown getting called twice.
1622 s_HasDumpedXmlHeader = false;
1625 // Finalize reading inline xml
1626 ReplayPolicy::FinalizeXml();
1629 //------------------------------------------------------------------------
1630 // GetRandom: setup or access random state
1633 // New or pre-existing random state.
1636 // Random state is kept per jit compilation request. Seed is partially
1637 // specified externally (via stress or policy setting) and partially
1638 // specified internally via method hash.
1640 CLRRandom* InlineStrategy::GetRandom()
1642 if (m_Random == nullptr)
1644 int externalSeed = 0;
1648 if (m_Compiler->compRandomInlineStress())
1650 externalSeed = getJitStressLevel();
1655 int randomPolicyFlag = JitConfig.JitInlinePolicyRandom();
1656 if (randomPolicyFlag != 0)
1658 externalSeed = randomPolicyFlag;
1661 int internalSeed = m_Compiler->info.compMethodHash();
1663 assert(externalSeed != 0);
1664 assert(internalSeed != 0);
1666 int seed = externalSeed ^ internalSeed;
1668 m_Random = new (m_Compiler, CMK_Inlining) CLRRandom();
1669 m_Random->Init(seed);
1675 #endif // defined(DEBUG) || defined(INLINE_DATA)
1677 //------------------------------------------------------------------------
1678 // IsNoInline: allow strategy to disable inlining in a method
1681 // info -- compiler interface from the EE
1682 // method -- handle for the root method
1685 // Only will return true in debug or special release builds.
1686 // Expects JitNoInlineRange to be set to the hashes of methods
1687 // where inlining is disabled.
1689 bool InlineStrategy::IsNoInline(ICorJitInfo* info, CORINFO_METHOD_HANDLE method)
1692 #if defined(DEBUG) || defined(INLINE_DATA)
1694 static ConfigMethodRange range;
1695 const wchar_t* noInlineRange = JitConfig.JitNoInlineRange();
1697 if (noInlineRange == nullptr)
1702 // If we have a config string we have at least one entry. Count
1703 // number of spaces in our config string to see if there are
1704 // more. Number of ranges we need is 2x that value.
1705 unsigned entryCount = 1;
1706 for (const wchar_t* p = noInlineRange; *p != 0; p++)
1714 range.EnsureInit(noInlineRange, 2 * entryCount);
1715 assert(!range.Error());
1716 return range.Contains(info, method);
1722 #endif // defined(DEBUG) || defined(INLINE_DATA)