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.
4
5#include "jitpch.h"
6#ifdef _MSC_VER
7#pragma hdrstop
8#endif
9
10#include "inlinepolicy.h"
11
12// Lookup table for inline description strings
13
14static const char* InlineDescriptions[] = {
15#define INLINE_OBSERVATION(name, type, description, impact, target) description,
16#include "inline.def"
17#undef INLINE_OBSERVATION
18};
19
20// Lookup table for inline targets
21
22static const InlineTarget InlineTargets[] = {
23#define INLINE_OBSERVATION(name, type, description, impact, target) InlineTarget::target,
24#include "inline.def"
25#undef INLINE_OBSERVATION
26};
27
28// Lookup table for inline impacts
29
30static const InlineImpact InlineImpacts[] = {
31#define INLINE_OBSERVATION(name, type, description, impact, target) InlineImpact::impact,
32#include "inline.def"
33#undef INLINE_OBSERVATION
34};
35
36#ifdef DEBUG
37
38//------------------------------------------------------------------------
39// InlIsValidObservation: run a validity check on an inline observation
40//
41// Arguments:
42// obs - the observation in question
43//
44// Return Value:
45// true if the observation is valid
46
47bool InlIsValidObservation(InlineObservation obs)
48{
49 return ((obs > InlineObservation::CALLEE_UNUSED_INITIAL) && (obs < InlineObservation::CALLEE_UNUSED_FINAL));
50}
51
52#endif // DEBUG
53
54//------------------------------------------------------------------------
55// InlGetObservationString: get a string describing this inline observation
56//
57// Arguments:
58// obs - the observation in question
59//
60// Return Value:
61// string describing the observation
62
63const char* InlGetObservationString(InlineObservation obs)
64{
65 assert(InlIsValidObservation(obs));
66 return InlineDescriptions[static_cast<int>(obs)];
67}
68
69//------------------------------------------------------------------------
70// InlGetTarget: get the target of an inline observation
71//
72// Arguments:
73// obs - the observation in question
74//
75// Return Value:
76// enum describing the target
77
78InlineTarget InlGetTarget(InlineObservation obs)
79{
80 assert(InlIsValidObservation(obs));
81 return InlineTargets[static_cast<int>(obs)];
82}
83
84//------------------------------------------------------------------------
85// InlGetTargetString: get a string describing the target of an inline observation
86//
87// Arguments:
88// obs - the observation in question
89//
90// Return Value:
91// string describing the target
92
93const char* InlGetTargetString(InlineObservation obs)
94{
95 InlineTarget t = InlGetTarget(obs);
96 switch (t)
97 {
98 case InlineTarget::CALLER:
99 return "caller";
100 case InlineTarget::CALLEE:
101 return "callee";
102 case InlineTarget::CALLSITE:
103 return "call site";
104 default:
105 return "unexpected target";
106 }
107}
108
109//------------------------------------------------------------------------
110// InlGetImpact: get the impact of an inline observation
111//
112// Arguments:
113// obs - the observation in question
114//
115// Return Value:
116// enum value describing the impact
117
118InlineImpact InlGetImpact(InlineObservation obs)
119{
120 assert(InlIsValidObservation(obs));
121 return InlineImpacts[static_cast<int>(obs)];
122}
123
124//------------------------------------------------------------------------
125// InlGetImpactString: get a string describing the impact of an inline observation
126//
127// Arguments:
128// obs - the observation in question
129//
130// Return Value:
131// string describing the impact
132
133const char* InlGetImpactString(InlineObservation obs)
134{
135 InlineImpact i = InlGetImpact(obs);
136 switch (i)
137 {
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";
148 default:
149 return "unexpected impact";
150 }
151}
152
153//------------------------------------------------------------------------
154// InlGetCorInfoInlineDecision: translate decision into a CorInfoInline
155//
156// Arguments:
157// d - the decision in question
158//
159// Return Value:
160// CorInfoInline value representing the decision
161
162CorInfoInline InlGetCorInfoInlineDecision(InlineDecision d)
163{
164 switch (d)
165 {
166 case InlineDecision::SUCCESS:
167 return INLINE_PASS;
168 case InlineDecision::FAILURE:
169 return INLINE_FAIL;
170 case InlineDecision::NEVER:
171 return INLINE_NEVER;
172 default:
173 assert(!"Unexpected InlineDecision");
174 unreached();
175 }
176}
177
178//------------------------------------------------------------------------
179// InlGetDecisionString: get a string representing this decision
180//
181// Arguments:
182// d - the decision in question
183//
184// Return Value:
185// string representing the decision
186
187const char* InlGetDecisionString(InlineDecision d)
188{
189 switch (d)
190 {
191 case InlineDecision::SUCCESS:
192 return "success";
193 case InlineDecision::FAILURE:
194 return "failed this call site";
195 case InlineDecision::NEVER:
196 return "failed this callee";
197 case InlineDecision::CANDIDATE:
198 return "candidate";
199 case InlineDecision::UNDECIDED:
200 return "undecided";
201 default:
202 assert(!"Unexpected InlineDecision");
203 unreached();
204 }
205}
206
207//------------------------------------------------------------------------
208// InlDecisionIsFailure: check if this decision describes a failing inline
209//
210// Arguments:
211// d - the decision in question
212//
213// Return Value:
214// true if the inline is definitely a failure
215
216bool InlDecisionIsFailure(InlineDecision d)
217{
218 switch (d)
219 {
220 case InlineDecision::SUCCESS:
221 case InlineDecision::UNDECIDED:
222 case InlineDecision::CANDIDATE:
223 return false;
224 case InlineDecision::FAILURE:
225 case InlineDecision::NEVER:
226 return true;
227 default:
228 assert(!"Unexpected InlineDecision");
229 unreached();
230 }
231}
232
233//------------------------------------------------------------------------
234// InlDecisionIsSuccess: check if this decision describes a sucessful inline
235//
236// Arguments:
237// d - the decision in question
238//
239// Return Value:
240// true if the inline is definitely a success
241
242bool InlDecisionIsSuccess(InlineDecision d)
243{
244 switch (d)
245 {
246 case InlineDecision::SUCCESS:
247 return true;
248 case InlineDecision::FAILURE:
249 case InlineDecision::NEVER:
250 case InlineDecision::UNDECIDED:
251 case InlineDecision::CANDIDATE:
252 return false;
253 default:
254 assert(!"Unexpected InlineDecision");
255 unreached();
256 }
257}
258
259//------------------------------------------------------------------------
260// InlDecisionIsNever: check if this decision describes a never inline
261//
262// Arguments:
263// d - the decision in question
264//
265// Return Value:
266// true if the inline is a never inline case
267
268bool InlDecisionIsNever(InlineDecision d)
269{
270 switch (d)
271 {
272 case InlineDecision::NEVER:
273 return true;
274 case InlineDecision::FAILURE:
275 case InlineDecision::SUCCESS:
276 case InlineDecision::UNDECIDED:
277 case InlineDecision::CANDIDATE:
278 return false;
279 default:
280 assert(!"Unexpected InlineDecision");
281 unreached();
282 }
283}
284
285//------------------------------------------------------------------------
286// InlDecisionIsCandidate: check if this decision describes a viable candidate
287//
288// Arguments:
289// d - the decision in question
290//
291// Return Value:
292// true if this inline still might happen
293
294bool InlDecisionIsCandidate(InlineDecision d)
295{
296 return !InlDecisionIsFailure(d);
297}
298
299//------------------------------------------------------------------------
300// InlDecisionIsDecided: check if this decision has been made
301//
302// Arguments:
303// d - the decision in question
304//
305// Return Value:
306// true if this inline has been decided one way or another
307
308bool InlDecisionIsDecided(InlineDecision d)
309{
310 switch (d)
311 {
312 case InlineDecision::NEVER:
313 case InlineDecision::FAILURE:
314 case InlineDecision::SUCCESS:
315 return true;
316 case InlineDecision::UNDECIDED:
317 case InlineDecision::CANDIDATE:
318 return false;
319 default:
320 assert(!"Unexpected InlineDecision");
321 unreached();
322 }
323}
324
325//------------------------------------------------------------------------
326// InlineContext: default constructor
327
328InlineContext::InlineContext(InlineStrategy* strategy)
329 : m_InlineStrategy(strategy)
330 , m_Parent(nullptr)
331 , m_Child(nullptr)
332 , m_Sibling(nullptr)
333 , m_Code(nullptr)
334 , m_ILSize(0)
335 , m_Offset(BAD_IL_OFFSET)
336 , m_Observation(InlineObservation::CALLEE_UNUSED_INITIAL)
337 , m_CodeSizeEstimate(0)
338 , m_Success(true)
339 , m_Devirtualized(false)
340 , m_Guarded(false)
341 , m_Unboxed(false)
342#if defined(DEBUG) || defined(INLINE_DATA)
343 , m_Policy(nullptr)
344 , m_Callee(nullptr)
345 , m_TreeID(0)
346 , m_Ordinal(0)
347#endif // defined(DEBUG) || defined(INLINE_DATA)
348{
349 // Empty
350}
351
352#if defined(DEBUG) || defined(INLINE_DATA)
353
354//------------------------------------------------------------------------
355// Dump: Dump an InlineContext entry and all descendants to jitstdout
356//
357// Arguments:
358// indent - indentation level for this node
359
360void InlineContext::Dump(unsigned indent)
361{
362 // Handle fact that siblings are in reverse order.
363 if (m_Sibling != nullptr)
364 {
365 m_Sibling->Dump(indent);
366 }
367
368 // We may not know callee name in some of the failing cases
369 Compiler* compiler = m_InlineStrategy->GetCompiler();
370 const char* calleeName = nullptr;
371
372 if (m_Callee == nullptr)
373 {
374 assert(!m_Success);
375 calleeName = "<unknown>";
376 }
377 else
378 {
379
380#if defined(DEBUG)
381 calleeName = compiler->eeGetMethodFullName(m_Callee);
382#else
383 calleeName = "callee";
384#endif // defined(DEBUG)
385 }
386
387 mdMethodDef calleeToken = compiler->info.compCompHnd->getMethodDefFromMethod(m_Callee);
388
389 // Dump this node
390 if (m_Parent == nullptr)
391 {
392 // Root method
393 printf("Inlines into %08X %s\n", calleeToken, calleeName);
394 }
395 else
396 {
397 // Inline attempt.
398 const char* inlineReason = InlGetObservationString(m_Observation);
399 const char* inlineResult = m_Success ? "" : "FAILED: ";
400 const char* devirtualized = m_Devirtualized ? " devirt" : "";
401 const char* guarded = m_Guarded ? " guarded" : "";
402 const char* unboxed = m_Unboxed ? " unboxed" : "";
403
404 if (m_Offset == BAD_IL_OFFSET)
405 {
406 printf("%*s[%u IL=???? TR=%06u %08X] [%s%s%s%s%s] %s\n", indent, "", m_Ordinal, m_TreeID, calleeToken,
407 inlineResult, inlineReason, guarded, devirtualized, unboxed, calleeName);
408 }
409 else
410 {
411 IL_OFFSET offset = jitGetILoffs(m_Offset);
412 printf("%*s[%u IL=%04d TR=%06u %08X] [%s%s%s%s%s] %s\n", indent, "", m_Ordinal, offset, m_TreeID,
413 calleeToken, inlineResult, inlineReason, guarded, devirtualized, unboxed, calleeName);
414 }
415 }
416
417 // Recurse to first child
418 if (m_Child != nullptr)
419 {
420 m_Child->Dump(indent + 2);
421 }
422}
423
424//------------------------------------------------------------------------
425// DumpData: Dump a successful InlineContext entry, detailed data, and
426// any successful descendant inlines
427//
428// Arguments:
429// indent - indentation level for this node
430
431void InlineContext::DumpData(unsigned indent)
432{
433 // Handle fact that siblings are in reverse order.
434 if (m_Sibling != nullptr)
435 {
436 m_Sibling->DumpData(indent);
437 }
438
439 Compiler* compiler = m_InlineStrategy->GetCompiler();
440
441#if defined(DEBUG)
442 const char* calleeName = compiler->eeGetMethodFullName(m_Callee);
443#else
444 const char* calleeName = "callee";
445#endif // defined(DEBUG)
446
447 if (m_Parent == nullptr)
448 {
449 // Root method... cons up a policy so we can display the name
450 InlinePolicy* policy = InlinePolicy::GetPolicy(compiler, true);
451 printf("\nInlines [%u] into \"%s\" [%s]\n", m_InlineStrategy->GetInlineCount(), calleeName, policy->GetName());
452 }
453 else if (m_Success)
454 {
455 const char* inlineReason = InlGetObservationString(m_Observation);
456 printf("%*s%u,\"%s\",\"%s\",", indent, "", m_Ordinal, inlineReason, calleeName);
457 m_Policy->DumpData(jitstdout);
458 printf("\n");
459 }
460
461 // Recurse to first child
462 if (m_Child != nullptr)
463 {
464 m_Child->DumpData(indent + 2);
465 }
466}
467
468//------------------------------------------------------------------------
469// DumpXml: Dump an InlineContext entry and all descendants in xml format
470//
471// Arguments:
472// file - file for output
473// indent - indentation level for this node
474
475void InlineContext::DumpXml(FILE* file, unsigned indent)
476{
477 // Handle fact that siblings are in reverse order.
478 if (m_Sibling != nullptr)
479 {
480 m_Sibling->DumpXml(file, indent);
481 }
482
483 // Optionally suppress failing inline records
484 if ((JitConfig.JitInlineDumpXml() == 3) && !m_Success)
485 {
486 return;
487 }
488
489 const bool isRoot = m_Parent == nullptr;
490 const bool hasChild = m_Child != nullptr;
491 const char* inlineType = m_Success ? "Inline" : "FailedInline";
492 unsigned newIndent = indent;
493
494 if (!isRoot)
495 {
496 Compiler* compiler = m_InlineStrategy->GetCompiler();
497
498 mdMethodDef calleeToken = compiler->info.compCompHnd->getMethodDefFromMethod(m_Callee);
499 unsigned calleeHash = compiler->info.compCompHnd->getMethodHash(m_Callee);
500
501 const char* inlineReason = InlGetObservationString(m_Observation);
502
503 int offset = -1;
504 if (m_Offset != BAD_IL_OFFSET)
505 {
506 offset = (int)jitGetILoffs(m_Offset);
507 }
508
509 fprintf(file, "%*s<%s>\n", indent, "", inlineType);
510 fprintf(file, "%*s<Token>%u</Token>\n", indent + 2, "", calleeToken);
511 fprintf(file, "%*s<Hash>%u</Hash>\n", indent + 2, "", calleeHash);
512 fprintf(file, "%*s<Offset>%u</Offset>\n", indent + 2, "", offset);
513 fprintf(file, "%*s<Reason>%s</Reason>\n", indent + 2, "", inlineReason);
514
515 // Optionally, dump data about the inline
516 const int dumpDataSetting = JitConfig.JitInlineDumpData();
517
518 // JitInlineDumpData=1 -- dump data plus deltas for last inline only
519 if ((dumpDataSetting == 1) && (this == m_InlineStrategy->GetLastContext()))
520 {
521 fprintf(file, "%*s<Data>", indent + 2, "");
522 m_InlineStrategy->DumpDataContents(file);
523 fprintf(file, "</Data>\n");
524 }
525
526 // JitInlineDumpData=2 -- dump data for all inlines, no deltas
527 if ((dumpDataSetting == 2) && (m_Policy != nullptr))
528 {
529 fprintf(file, "%*s<Data>", indent + 2, "");
530 m_Policy->DumpData(file);
531 fprintf(file, "</Data>\n");
532 }
533
534 newIndent = indent + 2;
535 }
536
537 // Handle children
538
539 if (hasChild)
540 {
541 fprintf(file, "%*s<Inlines>\n", newIndent, "");
542 m_Child->DumpXml(file, newIndent + 2);
543 fprintf(file, "%*s</Inlines>\n", newIndent, "");
544 }
545 else
546 {
547 fprintf(file, "%*s<Inlines />\n", newIndent, "");
548 }
549
550 // Close out
551
552 if (!isRoot)
553 {
554 fprintf(file, "%*s</%s>\n", indent, "", inlineType);
555 }
556}
557
558#endif // defined(DEBUG) || defined(INLINE_DATA)
559
560//------------------------------------------------------------------------
561// InlineResult: Construct an InlineResult to evaluate a particular call
562// for inlining.
563//
564// Arguments:
565// compiler - the compiler instance examining a call for inlining
566// call - the call in question
567// stmt - statement containing the call (if known)
568// description - string describing the context of the decision
569
570InlineResult::InlineResult(Compiler* compiler, GenTreeCall* call, GenTreeStmt* stmt, const char* description)
571 : m_RootCompiler(nullptr)
572 , m_Policy(nullptr)
573 , m_Call(call)
574 , m_InlineContext(nullptr)
575 , m_Caller(nullptr)
576 , m_Callee(nullptr)
577 , m_Description(description)
578 , m_Reported(false)
579{
580 // Set the compiler instance
581 m_RootCompiler = compiler->impInlineRoot();
582
583 // Set the policy
584 const bool isPrejitRoot = false;
585 m_Policy = InlinePolicy::GetPolicy(m_RootCompiler, isPrejitRoot);
586
587 // Pass along some optional information to the policy.
588 if (stmt != nullptr)
589 {
590 m_InlineContext = stmt->gtInlineContext;
591 m_Policy->NoteContext(m_InlineContext);
592
593#if defined(DEBUG) || defined(INLINE_DATA)
594 m_Policy->NoteOffset(call->gtRawILOffset);
595#else
596 m_Policy->NoteOffset(stmt->gtStmtILoffsx);
597#endif // defined(DEBUG) || defined(INLINE_DATA)
598 }
599
600 // Get method handle for caller. Note we use the
601 // handle for the "immediate" caller here.
602 m_Caller = compiler->info.compMethodHnd;
603
604 // Get method handle for callee, if known
605 if (m_Call->gtCall.gtCallType == CT_USER_FUNC)
606 {
607 m_Callee = m_Call->gtCall.gtCallMethHnd;
608 }
609}
610
611//------------------------------------------------------------------------
612// InlineResult: Construct an InlineResult to evaluate a particular
613// method as a possible inline candidate, while prejtting.
614//
615// Arguments:
616// compiler - the compiler instance doing the prejitting
617// method - the method in question
618// description - string describing the context of the decision
619//
620// Notes:
621// Used only during prejitting to try and pre-identify methods that
622// cannot be inlined, to help subsequent jit throughput.
623//
624// We use the inlCallee member to track the method since logically
625// it is the callee here.
626
627InlineResult::InlineResult(Compiler* compiler, CORINFO_METHOD_HANDLE method, const char* description)
628 : m_RootCompiler(nullptr)
629 , m_Policy(nullptr)
630 , m_Call(nullptr)
631 , m_InlineContext(nullptr)
632 , m_Caller(nullptr)
633 , m_Callee(method)
634 , m_Description(description)
635 , m_Reported(false)
636{
637 // Set the compiler instance
638 m_RootCompiler = compiler->impInlineRoot();
639
640 // Set the policy
641 const bool isPrejitRoot = true;
642 m_Policy = InlinePolicy::GetPolicy(m_RootCompiler, isPrejitRoot);
643}
644
645//------------------------------------------------------------------------
646// Report: Dump, log, and report information about an inline decision.
647//
648// Notes:
649// Called (automatically via the InlineResult dtor) when the
650// inliner is done evaluating a candidate.
651//
652// Dumps state of the inline candidate, and if a decision was
653// reached, sends it to the log and reports the decision back to the
654// EE. Optionally update the method attribute to NOINLINE if
655// observation and policy warrant.
656//
657// All this can be suppressed if desired by calling setReported()
658// before the InlineResult goes out of scope.
659
660void InlineResult::Report()
661{
662
663#ifdef DEBUG
664 // If this is a failure of a specific inline candidate and we haven't captured
665 // a failing observation yet, do so now.
666 if (IsFailure() && (m_Call != nullptr))
667 {
668 // compiler should have revoked candidacy on the call by now
669 assert((m_Call->gtFlags & GTF_CALL_INLINE_CANDIDATE) == 0);
670
671 if (m_Call->gtInlineObservation == InlineObservation::CALLEE_UNUSED_INITIAL)
672 {
673 m_Call->gtInlineObservation = m_Policy->GetObservation();
674 }
675 }
676#endif // DEBUG
677
678 // If we weren't actually inlining, user may have suppressed
679 // reporting via setReported(). If so, do nothing.
680 if (m_Reported)
681 {
682 return;
683 }
684
685 m_Reported = true;
686
687#ifdef DEBUG
688 const char* callee = nullptr;
689 const bool showInlines = (JitConfig.JitPrintInlinedMethods() == 1);
690
691 // Optionally dump the result
692 if (VERBOSE || showInlines)
693 {
694 const char* format = "INLINER: during '%s' result '%s' reason '%s' for '%s' calling '%s'\n";
695 const char* caller = (m_Caller == nullptr) ? "n/a" : m_RootCompiler->eeGetMethodFullName(m_Caller);
696
697 callee = (m_Callee == nullptr) ? "n/a" : m_RootCompiler->eeGetMethodFullName(m_Callee);
698
699 JITDUMP(format, m_Description, ResultString(), ReasonString(), caller, callee);
700 }
701#endif // DEBUG
702
703 // Was the result NEVER? If so we might want to propagate this to
704 // the runtime.
705
706 if (IsNever() && m_Policy->PropagateNeverToRuntime())
707 {
708 // If we know the callee, and if the observation that got us
709 // to this Never inline state is something *other* than
710 // IS_NOINLINE, then we've uncovered a reason why this method
711 // can't ever be inlined. Update the callee method attributes
712 // so that future inline attempts for this callee fail faster.
713
714 InlineObservation obs = m_Policy->GetObservation();
715
716 if ((m_Callee != nullptr) && (obs != InlineObservation::CALLEE_IS_NOINLINE))
717 {
718
719#ifdef DEBUG
720
721 const char* obsString = InlGetObservationString(obs);
722
723 if (VERBOSE)
724 {
725 JITDUMP("\nINLINER: Marking %s as NOINLINE because of %s\n", callee, obsString);
726 }
727
728 if (showInlines)
729 {
730 printf("Marking %s as NOINLINE because of %s\n", callee, obsString);
731 }
732
733#endif // DEBUG
734
735 COMP_HANDLE comp = m_RootCompiler->info.compCompHnd;
736 comp->setMethodAttribs(m_Callee, CORINFO_FLG_BAD_INLINEE);
737 }
738 }
739
740 if (IsDecided())
741 {
742 const char* format = "INLINER: during '%s' result '%s' reason '%s'\n";
743 JITLOG_THIS(m_RootCompiler, (LL_INFO100000, format, m_Description, ResultString(), ReasonString()));
744 COMP_HANDLE comp = m_RootCompiler->info.compCompHnd;
745 comp->reportInliningDecision(m_Caller, m_Callee, Result(), ReasonString());
746 }
747}
748
749//------------------------------------------------------------------------
750// InlineStrategy construtor
751//
752// Arguments
753// compiler - root compiler instance
754
755InlineStrategy::InlineStrategy(Compiler* compiler)
756 : m_Compiler(compiler)
757 , m_RootContext(nullptr)
758 , m_LastSuccessfulPolicy(nullptr)
759 , m_LastContext(nullptr)
760 , m_PrejitRootDecision(InlineDecision::UNDECIDED)
761 , m_CallCount(0)
762 , m_CandidateCount(0)
763 , m_AlwaysCandidateCount(0)
764 , m_ForceCandidateCount(0)
765 , m_DiscretionaryCandidateCount(0)
766 , m_UnprofitableCandidateCount(0)
767 , m_ImportCount(0)
768 , m_InlineCount(0)
769 , m_MaxInlineSize(DEFAULT_MAX_INLINE_SIZE)
770 , m_MaxInlineDepth(DEFAULT_MAX_INLINE_DEPTH)
771 , m_InitialTimeBudget(0)
772 , m_InitialTimeEstimate(0)
773 , m_CurrentTimeBudget(0)
774 , m_CurrentTimeEstimate(0)
775 , m_InitialSizeEstimate(0)
776 , m_CurrentSizeEstimate(0)
777 , m_HasForceViaDiscretionary(false)
778#if defined(DEBUG) || defined(INLINE_DATA)
779 , m_MethodXmlFilePosition(0)
780 , m_Random(nullptr)
781#endif // defined(DEBUG) || defined(INLINE_DATA)
782
783{
784 // Verify compiler is a root compiler instance
785 assert(m_Compiler->impInlineRoot() == m_Compiler);
786
787#ifdef DEBUG
788
789 // Possibly modify the max inline size.
790 //
791 // Default value of JitInlineSize is the same as our default.
792 // So normally this next line does not change the size.
793 m_MaxInlineSize = JitConfig.JitInlineSize();
794
795 // Up the max size under stress
796 if (m_Compiler->compInlineStress())
797 {
798 m_MaxInlineSize *= 10;
799 }
800
801 // But don't overdo it
802 if (m_MaxInlineSize > IMPLEMENTATION_MAX_INLINE_SIZE)
803 {
804 m_MaxInlineSize = IMPLEMENTATION_MAX_INLINE_SIZE;
805 }
806
807 // Verify: not too small, not too big.
808 assert(m_MaxInlineSize >= ALWAYS_INLINE_SIZE);
809 assert(m_MaxInlineSize <= IMPLEMENTATION_MAX_INLINE_SIZE);
810
811 // Possibly modify the max inline depth
812 //
813 // Default value of JitInlineDepth is the same as our default.
814 // So normally this next line does not change the size.
815 m_MaxInlineDepth = JitConfig.JitInlineDepth();
816
817 // But don't overdo it
818 if (m_MaxInlineDepth > IMPLEMENTATION_MAX_INLINE_DEPTH)
819 {
820 m_MaxInlineDepth = IMPLEMENTATION_MAX_INLINE_DEPTH;
821 }
822
823#endif // DEBUG
824}
825
826//------------------------------------------------------------------------
827// GetRootContext: get the InlineContext for the root method
828//
829// Return Value:
830// Root context; describes the method being jitted.
831//
832// Note:
833// Also initializes the jit time estimate and budget.
834
835InlineContext* InlineStrategy::GetRootContext()
836{
837 if (m_RootContext == nullptr)
838 {
839 // Allocate on first demand.
840 m_RootContext = NewRoot();
841
842 // Estimate how long the jit will take if there's no inlining
843 // done to this method.
844 m_InitialTimeEstimate = EstimateTime(m_RootContext);
845 m_CurrentTimeEstimate = m_InitialTimeEstimate;
846
847 // Set the initial budget for inlining. Note this is
848 // deliberately set very high and is intended to catch
849 // only pathological runaway inline cases.
850 m_InitialTimeBudget = BUDGET * m_InitialTimeEstimate;
851 m_CurrentTimeBudget = m_InitialTimeBudget;
852
853 // Estimate the code size if there's no inlining
854 m_InitialSizeEstimate = EstimateSize(m_RootContext);
855 m_CurrentSizeEstimate = m_InitialSizeEstimate;
856
857 // Sanity check
858 assert(m_CurrentTimeEstimate > 0);
859 assert(m_CurrentSizeEstimate > 0);
860
861 // Cache as the "last" context created
862 m_LastContext = m_RootContext;
863 }
864
865 return m_RootContext;
866}
867
868//------------------------------------------------------------------------
869// NoteAttempt: do bookkeeping for an inline attempt
870//
871// Arguments:
872// result -- InlineResult for successful inline candidate
873
874void InlineStrategy::NoteAttempt(InlineResult* result)
875{
876 assert(result->IsCandidate());
877 InlineObservation obs = result->GetObservation();
878
879 if (obs == InlineObservation::CALLEE_BELOW_ALWAYS_INLINE_SIZE)
880 {
881 m_AlwaysCandidateCount++;
882 }
883 else if (obs == InlineObservation::CALLEE_IS_FORCE_INLINE)
884 {
885 m_ForceCandidateCount++;
886 }
887 else
888 {
889 m_DiscretionaryCandidateCount++;
890 }
891}
892
893//------------------------------------------------------------------------
894// DumpCsvHeader: dump header for csv inline stats
895//
896// Argument:
897// fp -- file for dump output
898
899void InlineStrategy::DumpCsvHeader(FILE* fp)
900{
901 fprintf(fp, "\"InlineCalls\",");
902 fprintf(fp, "\"InlineCandidates\",");
903 fprintf(fp, "\"InlineAlways\",");
904 fprintf(fp, "\"InlineForce\",");
905 fprintf(fp, "\"InlineDiscretionary\",");
906 fprintf(fp, "\"InlineUnprofitable\",");
907 fprintf(fp, "\"InlineEarlyFail\",");
908 fprintf(fp, "\"InlineImport\",");
909 fprintf(fp, "\"InlineLateFail\",");
910 fprintf(fp, "\"InlineSuccess\",");
911}
912
913//------------------------------------------------------------------------
914// DumpCsvData: dump data for csv inline stats
915//
916// Argument:
917// fp -- file for dump output
918
919void InlineStrategy::DumpCsvData(FILE* fp)
920{
921 fprintf(fp, "%u,", m_CallCount);
922 fprintf(fp, "%u,", m_CandidateCount);
923 fprintf(fp, "%u,", m_AlwaysCandidateCount);
924 fprintf(fp, "%u,", m_ForceCandidateCount);
925 fprintf(fp, "%u,", m_DiscretionaryCandidateCount);
926 fprintf(fp, "%u,", m_UnprofitableCandidateCount);
927
928 // Early failures are cases where candates are rejected between
929 // the time the jit invokes the inlinee compiler and the time it
930 // starts to import the inlinee IL.
931 //
932 // So they are "cheaper" that late failures.
933
934 unsigned profitableCandidateCount = m_DiscretionaryCandidateCount - m_UnprofitableCandidateCount;
935
936 unsigned earlyFailCount =
937 m_CandidateCount - m_AlwaysCandidateCount - m_ForceCandidateCount - profitableCandidateCount;
938
939 fprintf(fp, "%u,", earlyFailCount);
940
941 unsigned lateFailCount = m_ImportCount - m_InlineCount;
942
943 fprintf(fp, "%u,", m_ImportCount);
944 fprintf(fp, "%u,", lateFailCount);
945 fprintf(fp, "%u,", m_InlineCount);
946}
947
948//------------------------------------------------------------------------
949// EstimateTime: estimate impact of this inline on the method jit time
950//
951// Arguments:
952// context - context describing this inline
953//
954// Return Value:
955// Nominal estimate of jit time.
956
957int InlineStrategy::EstimateTime(InlineContext* context)
958{
959 // Simple linear models based on observations
960 // show time is fairly well predicted by IL size.
961 unsigned ilSize = context->GetILSize();
962
963 // Prediction varies for root and inlines.
964 if (context == m_RootContext)
965 {
966 return EstimateRootTime(ilSize);
967 }
968 else
969 {
970 return EstimateInlineTime(ilSize);
971 }
972}
973
974//------------------------------------------------------------------------
975// EstimteRootTime: estimate jit time for method of this size with
976// no inlining.
977//
978// Arguments:
979// ilSize - size of the method's IL
980//
981// Return Value:
982// Nominal estimate of jit time.
983//
984// Notes:
985// Based on observational data. Time is nominally microseconds.
986
987int InlineStrategy::EstimateRootTime(unsigned ilSize)
988{
989 return 60 + 3 * ilSize;
990}
991
992//------------------------------------------------------------------------
993// EstimteInlineTime: estimate time impact on jitting for an inline
994// of this size.
995//
996// Arguments:
997// ilSize - size of the method's IL
998//
999// Return Value:
1000// Nominal increase in jit time.
1001//
1002// Notes:
1003// Based on observational data. Time is nominally microseconds.
1004// Small inlines will make the jit a bit faster.
1005
1006int InlineStrategy::EstimateInlineTime(unsigned ilSize)
1007{
1008 return -14 + 2 * ilSize;
1009}
1010
1011//------------------------------------------------------------------------
1012// EstimateSize: estimate impact of this inline on the method size
1013//
1014// Arguments:
1015// context - context describing this inline
1016//
1017// Return Value:
1018// Nominal estimate of method size (bytes * 10)
1019
1020int InlineStrategy::EstimateSize(InlineContext* context)
1021{
1022 // Prediction varies for root and inlines.
1023 if (context == m_RootContext)
1024 {
1025 // Simple linear models based on observations show root method
1026 // native code size is fairly well predicted by IL size.
1027 //
1028 // Model below is for x64 on windows.
1029 unsigned ilSize = context->GetILSize();
1030 int estimate = (1312 + 228 * ilSize) / 10;
1031
1032 return estimate;
1033 }
1034 else
1035 {
1036 // Use context's code size estimate.
1037 return context->GetCodeSizeEstimate();
1038 }
1039}
1040
1041//------------------------------------------------------------------------
1042// NoteOutcome: do bookkeeping for an inline
1043//
1044// Arguments:
1045// context - context for the inlie
1046
1047void InlineStrategy::NoteOutcome(InlineContext* context)
1048{
1049 // Note we can't generally count up failures here -- we only
1050 // create contexts for failures in debug modes, and even then
1051 // we may not get them all.
1052 if (context->IsSuccess())
1053 {
1054 m_InlineCount++;
1055
1056#if defined(DEBUG) || defined(INLINE_DATA)
1057
1058 // Keep track of the inline targeted for data collection or,
1059 // if we don't have one (yet), the last successful inline.
1060 bool updateLast = (m_LastSuccessfulPolicy == nullptr) || !m_LastSuccessfulPolicy->IsDataCollectionTarget();
1061
1062 if (updateLast)
1063 {
1064 m_LastContext = context;
1065 m_LastSuccessfulPolicy = context->m_Policy;
1066 }
1067 else
1068 {
1069 // We only expect one inline to be a data collection
1070 // target.
1071 assert(!context->m_Policy->IsDataCollectionTarget());
1072 }
1073
1074#endif // defined(DEBUG) || defined(INLINE_DATA)
1075
1076 // Budget update.
1077 //
1078 // If callee is a force inline, increase budget, provided all
1079 // parent contexts are likewise force inlines.
1080 //
1081 // If callee is discretionary or has a discretionary ancestor,
1082 // increase expense.
1083
1084 InlineContext* currentContext = context;
1085 bool isForceInline = false;
1086
1087 while (currentContext != m_RootContext)
1088 {
1089 InlineObservation observation = currentContext->GetObservation();
1090
1091 if (observation != InlineObservation::CALLEE_IS_FORCE_INLINE)
1092 {
1093 if (isForceInline)
1094 {
1095 // Interesting case where discretionary inlines pull
1096 // in a force inline...
1097 m_HasForceViaDiscretionary = true;
1098 }
1099
1100 isForceInline = false;
1101 break;
1102 }
1103
1104 isForceInline = true;
1105 currentContext = currentContext->GetParent();
1106 }
1107
1108 int timeDelta = EstimateTime(context);
1109
1110 if (isForceInline)
1111 {
1112 // Update budget since this inline was forced. Only allow
1113 // budget to increase.
1114 if (timeDelta > 0)
1115 {
1116 m_CurrentTimeBudget += timeDelta;
1117 }
1118 }
1119
1120 // Update time estimate.
1121 m_CurrentTimeEstimate += timeDelta;
1122
1123 // Update size estimate.
1124 //
1125 // Sometimes estimates don't make sense. Don't let the method
1126 // size go negative.
1127 int sizeDelta = EstimateSize(context);
1128
1129 if (m_CurrentSizeEstimate + sizeDelta <= 0)
1130 {
1131 sizeDelta = 0;
1132 }
1133
1134 // Update the code size estimate.
1135 m_CurrentSizeEstimate += sizeDelta;
1136 }
1137}
1138
1139//------------------------------------------------------------------------
1140// BudgetCheck: return true if as inline of this size would exceed the
1141// jit time budget for this method
1142//
1143// Arguments:
1144// ilSize - size of the method's IL
1145//
1146// Return Value:
1147// true if the inline would go over budget
1148
1149bool InlineStrategy::BudgetCheck(unsigned ilSize)
1150{
1151 int timeDelta = EstimateInlineTime(ilSize);
1152 return (timeDelta + m_CurrentTimeEstimate > m_CurrentTimeBudget);
1153}
1154
1155//------------------------------------------------------------------------
1156// NewRoot: construct an InlineContext for the root method
1157//
1158// Return Value:
1159// InlineContext for use as the root context
1160//
1161// Notes:
1162// We leave m_Code as nullptr here (rather than the IL buffer
1163// address of the root method) to preserve existing behavior, which
1164// is to allow one recursive inline.
1165
1166InlineContext* InlineStrategy::NewRoot()
1167{
1168 InlineContext* rootContext = new (m_Compiler, CMK_Inlining) InlineContext(this);
1169
1170 rootContext->m_ILSize = m_Compiler->info.compILCodeSize;
1171
1172#if defined(DEBUG) || defined(INLINE_DATA)
1173
1174 rootContext->m_Callee = m_Compiler->info.compMethodHnd;
1175
1176#endif // defined(DEBUG) || defined(INLINE_DATA)
1177
1178 return rootContext;
1179}
1180
1181//------------------------------------------------------------------------
1182// NewSuccess: construct an InlineContext for a successful inline
1183// and link it into the context tree
1184//
1185// Arguments:
1186// inlineInfo - information about this inline
1187//
1188// Return Value:
1189// A new InlineContext for statements brought into the method by
1190// this inline.
1191
1192InlineContext* InlineStrategy::NewSuccess(InlineInfo* inlineInfo)
1193{
1194 InlineContext* calleeContext = new (m_Compiler, CMK_Inlining) InlineContext(this);
1195 GenTreeStmt* stmt = inlineInfo->iciStmt;
1196 BYTE* calleeIL = inlineInfo->inlineCandidateInfo->methInfo.ILCode;
1197 unsigned calleeILSize = inlineInfo->inlineCandidateInfo->methInfo.ILCodeSize;
1198 InlineContext* parentContext = stmt->gtInlineContext;
1199 GenTreeCall* originalCall = inlineInfo->inlineResult->GetCall();
1200
1201 noway_assert(parentContext != nullptr);
1202
1203 calleeContext->m_Code = calleeIL;
1204 calleeContext->m_ILSize = calleeILSize;
1205 calleeContext->m_Parent = parentContext;
1206 // Push on front here will put siblings in reverse lexical
1207 // order which we undo in the dumper
1208 calleeContext->m_Sibling = parentContext->m_Child;
1209 parentContext->m_Child = calleeContext;
1210 calleeContext->m_Child = nullptr;
1211 calleeContext->m_Offset = stmt->gtStmtILoffsx;
1212 calleeContext->m_Observation = inlineInfo->inlineResult->GetObservation();
1213 calleeContext->m_Success = true;
1214 calleeContext->m_Devirtualized = originalCall->IsDevirtualized();
1215 calleeContext->m_Guarded = originalCall->IsGuarded();
1216 calleeContext->m_Unboxed = originalCall->IsUnboxed();
1217
1218#if defined(DEBUG) || defined(INLINE_DATA)
1219
1220 InlinePolicy* policy = inlineInfo->inlineResult->GetPolicy();
1221
1222 calleeContext->m_Policy = policy;
1223 calleeContext->m_CodeSizeEstimate = policy->CodeSizeEstimate();
1224 calleeContext->m_Callee = inlineInfo->fncHandle;
1225 // +1 here since we set this before calling NoteOutcome.
1226 calleeContext->m_Ordinal = m_InlineCount + 1;
1227 // Update offset with more accurate info
1228 calleeContext->m_Offset = originalCall->gtRawILOffset;
1229
1230#endif // defined(DEBUG) || defined(INLINE_DATA)
1231
1232#if defined(DEBUG)
1233
1234 calleeContext->m_TreeID = originalCall->gtTreeID;
1235
1236#endif // defined(DEBUG)
1237
1238 NoteOutcome(calleeContext);
1239
1240 return calleeContext;
1241}
1242
1243#if defined(DEBUG) || defined(INLINE_DATA)
1244
1245//------------------------------------------------------------------------
1246// NewFailure: construct an InlineContext for a failing inline
1247// and link it into the context tree
1248//
1249// Arguments:
1250// stmt - statement containing the attempted inline
1251// inlineResult - inlineResult for the attempt
1252//
1253// Return Value:
1254// A new InlineContext for diagnostic purposes
1255
1256InlineContext* InlineStrategy::NewFailure(GenTreeStmt* stmt, InlineResult* inlineResult)
1257{
1258 // Check for a parent context first. We should now have a parent
1259 // context for all statements.
1260 InlineContext* parentContext = stmt->gtInlineContext;
1261 assert(parentContext != nullptr);
1262 InlineContext* failedContext = new (m_Compiler, CMK_Inlining) InlineContext(this);
1263 GenTreeCall* originalCall = inlineResult->GetCall();
1264
1265 // Pushing the new context on the front of the parent child list
1266 // will put siblings in reverse lexical order which we undo in the
1267 // dumper.
1268 failedContext->m_Parent = parentContext;
1269 failedContext->m_Sibling = parentContext->m_Child;
1270 parentContext->m_Child = failedContext;
1271 failedContext->m_Child = nullptr;
1272 failedContext->m_Offset = stmt->gtStmtILoffsx;
1273 failedContext->m_Observation = inlineResult->GetObservation();
1274 failedContext->m_Callee = inlineResult->GetCallee();
1275 failedContext->m_Success = false;
1276 failedContext->m_Devirtualized = originalCall->IsDevirtualized();
1277 failedContext->m_Guarded = originalCall->IsGuarded();
1278 failedContext->m_Unboxed = originalCall->IsUnboxed();
1279
1280 assert(InlIsValidObservation(failedContext->m_Observation));
1281
1282#if defined(DEBUG) || defined(INLINE_DATA)
1283
1284 // Update offset with more accurate info
1285 failedContext->m_Offset = originalCall->gtRawILOffset;
1286
1287#endif // #if defined(DEBUG) || defined(INLINE_DATA)
1288
1289#if defined(DEBUG)
1290
1291 failedContext->m_TreeID = originalCall->gtTreeID;
1292
1293#endif // defined(DEBUG)
1294
1295 NoteOutcome(failedContext);
1296
1297 return failedContext;
1298}
1299
1300//------------------------------------------------------------------------
1301// Dump: dump description of inline behavior
1302//
1303// Arguments:
1304// showBudget - also dump final budget values
1305
1306void InlineStrategy::Dump(bool showBudget)
1307{
1308 m_RootContext->Dump();
1309
1310 if (!showBudget)
1311 {
1312 return;
1313 }
1314
1315 printf("Budget: initialTime=%d, finalTime=%d, initialBudget=%d, currentBudget=%d\n", m_InitialTimeEstimate,
1316 m_CurrentTimeEstimate, m_InitialTimeBudget, m_CurrentTimeBudget);
1317
1318 if (m_CurrentTimeBudget > m_InitialTimeBudget)
1319 {
1320 printf("Budget: increased by %d because of force inlines\n", m_CurrentTimeBudget - m_InitialTimeBudget);
1321 }
1322
1323 if (m_CurrentTimeEstimate > m_CurrentTimeBudget)
1324 {
1325 printf("Budget: went over budget by %d\n", m_CurrentTimeEstimate - m_CurrentTimeBudget);
1326 }
1327
1328 if (m_HasForceViaDiscretionary)
1329 {
1330 printf("Budget: discretionary inline caused a force inline\n");
1331 }
1332
1333 printf("Budget: initialSize=%d, finalSize=%d\n", m_InitialSizeEstimate, m_CurrentSizeEstimate);
1334}
1335
1336// Static to track emission of the inline data header
1337
1338bool InlineStrategy::s_HasDumpedDataHeader = false;
1339
1340//------------------------------------------------------------------------
1341// DumpData: dump data about the last successful inline into this method
1342// in a format suitable for automated analysis.
1343
1344void InlineStrategy::DumpData()
1345{
1346 // Is dumping enabled? If not, nothing to do.
1347 if (JitConfig.JitInlineDumpData() == 0)
1348 {
1349 return;
1350 }
1351
1352 // If we're also dumping inline XML, we'll let it dump the data.
1353 if (JitConfig.JitInlineDumpXml() != 0)
1354 {
1355 return;
1356 }
1357
1358 // Don't dump anything if limiting is on and we didn't reach
1359 // the limit while inlining.
1360 //
1361 // This serves to filter out duplicate data.
1362 const int limit = JitConfig.JitInlineLimit();
1363
1364 if ((limit >= 0) && (m_InlineCount < static_cast<unsigned>(limit)))
1365 {
1366 return;
1367 }
1368
1369 // Dump header, if not already dumped
1370 if (!s_HasDumpedDataHeader)
1371 {
1372 DumpDataHeader(stderr);
1373 s_HasDumpedDataHeader = true;
1374 }
1375
1376 // Dump contents
1377 DumpDataContents(stderr);
1378 fprintf(stderr, "\n");
1379}
1380
1381//------------------------------------------------------------------------
1382// DumpDataEnsurePolicyIsSet: ensure m_LastSuccessfulPolicy describes the
1383// inline policy in effect.
1384//
1385// Notes:
1386// Needed for methods that don't have any successful inlines.
1387
1388void InlineStrategy::DumpDataEnsurePolicyIsSet()
1389{
1390 // Cache references to compiler substructures.
1391 const Compiler::Info& info = m_Compiler->info;
1392 const Compiler::Options& opts = m_Compiler->opts;
1393
1394 // If there weren't any successful inlines, we won't have a
1395 // successful policy, so fake one up.
1396 if (m_LastSuccessfulPolicy == nullptr)
1397 {
1398 const bool isPrejitRoot = opts.jitFlags->IsSet(JitFlags::JIT_FLAG_PREJIT);
1399 m_LastSuccessfulPolicy = InlinePolicy::GetPolicy(m_Compiler, isPrejitRoot);
1400
1401 // Add in a bit of data....
1402 const bool isForceInline = (info.compFlags & CORINFO_FLG_FORCEINLINE) != 0;
1403 m_LastSuccessfulPolicy->NoteBool(InlineObservation::CALLEE_IS_FORCE_INLINE, isForceInline);
1404 m_LastSuccessfulPolicy->NoteInt(InlineObservation::CALLEE_IL_CODE_SIZE, info.compMethodInfo->ILCodeSize);
1405 }
1406}
1407
1408//------------------------------------------------------------------------
1409// DumpDataHeader: dump header for inline data.
1410//
1411// Arguments:
1412// file - file for data output
1413
1414void InlineStrategy::DumpDataHeader(FILE* file)
1415{
1416 DumpDataEnsurePolicyIsSet();
1417 const int limit = JitConfig.JitInlineLimit();
1418 fprintf(file, "*** Inline Data: Policy=%s JitInlineLimit=%d ***\n", m_LastSuccessfulPolicy->GetName(), limit);
1419 DumpDataSchema(file);
1420 fprintf(file, "\n");
1421}
1422
1423//------------------------------------------------------------------------
1424// DumpSchema: dump schema for inline data.
1425//
1426// Arguments:
1427// file - file for data output
1428
1429void InlineStrategy::DumpDataSchema(FILE* file)
1430{
1431 DumpDataEnsurePolicyIsSet();
1432 fprintf(file, "Method,Version,HotSize,ColdSize,JitTime,SizeEstimate,TimeEstimate,");
1433 m_LastSuccessfulPolicy->DumpSchema(file);
1434}
1435
1436//------------------------------------------------------------------------
1437// DumpDataContents: dump contents of inline data
1438//
1439// Arguments:
1440// file - file for data output
1441
1442void InlineStrategy::DumpDataContents(FILE* file)
1443{
1444 DumpDataEnsurePolicyIsSet();
1445
1446 // Cache references to compiler substructures.
1447 const Compiler::Info& info = m_Compiler->info;
1448 const Compiler::Options& opts = m_Compiler->opts;
1449
1450 // We'd really like the method identifier to be unique and
1451 // durable across crossgen invocations. Not clear how to
1452 // accomplish this, so we'll use the token for now.
1453 //
1454 // Post processing will have to filter out all data from
1455 // methods where the root entry appears multiple times.
1456 mdMethodDef currentMethodToken = info.compCompHnd->getMethodDefFromMethod(info.compMethodHnd);
1457
1458 // Convert time spent jitting into microseconds
1459 unsigned microsecondsSpentJitting = 0;
1460 unsigned __int64 compCycles = m_Compiler->getInlineCycleCount();
1461 if (compCycles > 0)
1462 {
1463 double countsPerSec = CycleTimer::CyclesPerSecond();
1464 double counts = (double)compCycles;
1465 microsecondsSpentJitting = (unsigned)((counts / countsPerSec) * 1000 * 1000);
1466 }
1467
1468 fprintf(file, "%08X,%u,%u,%u,%u,%d,%d,", currentMethodToken, m_InlineCount, info.compTotalHotCodeSize,
1469 info.compTotalColdCodeSize, microsecondsSpentJitting, m_CurrentSizeEstimate / 10, m_CurrentTimeEstimate);
1470 m_LastSuccessfulPolicy->DumpData(file);
1471}
1472
1473// Static to track emission of the xml data header
1474// and lock to prevent interleaved file writes
1475
1476bool InlineStrategy::s_HasDumpedXmlHeader = false;
1477CritSecObject InlineStrategy::s_XmlWriterLock;
1478
1479//------------------------------------------------------------------------
1480// DumpXml: dump xml-formatted version of the inline tree.
1481//
1482// Arguments
1483// file - file for data output
1484// indent - indent level of this element
1485
1486void InlineStrategy::DumpXml(FILE* file, unsigned indent)
1487{
1488 if (JitConfig.JitInlineDumpXml() == 0)
1489 {
1490 return;
1491 }
1492
1493 // Lock to prevent interleaving of trees.
1494 CritSecHolder writeLock(s_XmlWriterLock);
1495
1496 // Dump header
1497 if (!s_HasDumpedXmlHeader)
1498 {
1499 DumpDataEnsurePolicyIsSet();
1500
1501 fprintf(file, "<?xml version=\"1.0\"?>\n");
1502 fprintf(file, "<InlineForest>\n");
1503 fprintf(file, "<Policy>%s</Policy>\n", m_LastSuccessfulPolicy->GetName());
1504
1505 const int dumpDataSetting = JitConfig.JitInlineDumpData();
1506 if (dumpDataSetting != 0)
1507 {
1508 fprintf(file, "<DataSchema>");
1509
1510 if (dumpDataSetting == 1)
1511 {
1512 // JitInlineDumpData=1 -- dump schema for data plus deltas
1513 DumpDataSchema(file);
1514 }
1515 else if (dumpDataSetting == 2)
1516 {
1517 // JitInlineDumpData=2 -- dump schema for data only
1518 m_LastSuccessfulPolicy->DumpSchema(file);
1519 }
1520
1521 fprintf(file, "</DataSchema>\n");
1522 }
1523
1524 fprintf(file, "<Methods>\n");
1525 s_HasDumpedXmlHeader = true;
1526 }
1527
1528 // If we're dumping "minimal" Xml, and we didn't do
1529 // any inlines into this method, then there's nothing
1530 // to emit here.
1531 if ((m_InlineCount == 0) && (JitConfig.JitInlineDumpXml() >= 2))
1532 {
1533 return;
1534 }
1535
1536 // Cache references to compiler substructures.
1537 const Compiler::Info& info = m_Compiler->info;
1538 const Compiler::Options& opts = m_Compiler->opts;
1539
1540 const bool isPrejitRoot = opts.jitFlags->IsSet(JitFlags::JIT_FLAG_PREJIT);
1541 const bool isForceInline = (info.compFlags & CORINFO_FLG_FORCEINLINE) != 0;
1542
1543 // We'd really like the method identifier to be unique and
1544 // durable across crossgen invocations. Not clear how to
1545 // accomplish this, so we'll use the token for now.
1546 //
1547 // Post processing will have to filter out all data from
1548 // methods where the root entry appears multiple times.
1549 mdMethodDef currentMethodToken = info.compCompHnd->getMethodDefFromMethod(info.compMethodHnd);
1550
1551 unsigned hash = info.compMethodHash();
1552
1553 // Convert time spent jitting into microseconds
1554 unsigned microsecondsSpentJitting = 0;
1555 unsigned __int64 compCycles = m_Compiler->getInlineCycleCount();
1556 if (compCycles > 0)
1557 {
1558 double countsPerSec = CycleTimer::CyclesPerSecond();
1559 double counts = (double)compCycles;
1560 microsecondsSpentJitting = (unsigned)((counts / countsPerSec) * 1000 * 1000);
1561 }
1562
1563 // Get method name just for root method, to make it a bit easier
1564 // to search for things in the inline xml.
1565 const char* methodName = info.compCompHnd->getMethodName(info.compMethodHnd, nullptr);
1566
1567 // Cheap xml quoting for values. Only < and & are troublemakers,
1568 // but change > for symmetry.
1569 //
1570 // Ok to truncate name, just ensure it's null terminated.
1571 char buf[64];
1572 strncpy(buf, methodName, sizeof(buf));
1573 buf[sizeof(buf) - 1] = 0;
1574
1575 for (int i = 0; i < _countof(buf); i++)
1576 {
1577 switch (buf[i])
1578 {
1579 case '<':
1580 buf[i] = '[';
1581 break;
1582 case '>':
1583 buf[i] = ']';
1584 break;
1585 case '&':
1586 buf[i] = '#';
1587 break;
1588 default:
1589 break;
1590 }
1591 }
1592
1593 fprintf(file, "%*s<Method>\n", indent, "");
1594 fprintf(file, "%*s<Token>%u</Token>\n", indent + 2, "", currentMethodToken);
1595 fprintf(file, "%*s<Hash>%u</Hash>\n", indent + 2, "", hash);
1596 fprintf(file, "%*s<Name>%s</Name>\n", indent + 2, "", buf);
1597 fprintf(file, "%*s<InlineCount>%u</InlineCount>\n", indent + 2, "", m_InlineCount);
1598 fprintf(file, "%*s<HotSize>%u</HotSize>\n", indent + 2, "", info.compTotalHotCodeSize);
1599 fprintf(file, "%*s<ColdSize>%u</ColdSize>\n", indent + 2, "", info.compTotalColdCodeSize);
1600 fprintf(file, "%*s<JitTime>%u</JitTime>\n", indent + 2, "", microsecondsSpentJitting);
1601 fprintf(file, "%*s<SizeEstimate>%u</SizeEstimate>\n", indent + 2, "", m_CurrentSizeEstimate / 10);
1602 fprintf(file, "%*s<TimeEstimate>%u</TimeEstimate>\n", indent + 2, "", m_CurrentTimeEstimate);
1603
1604 // For prejit roots also propagate out the assessment of the root method
1605 if (isPrejitRoot)
1606 {
1607 fprintf(file, "%*s<PrejitDecision>%s</PrejitDecision>\n", indent + 2, "",
1608 InlGetDecisionString(m_PrejitRootDecision));
1609 fprintf(file, "%*s<PrejitObservation>%s</PrejitObservation>\n", indent + 2, "",
1610 InlGetObservationString(m_PrejitRootObservation));
1611 }
1612
1613 // Root context will be null if we're not optimizing the method.
1614 //
1615 // Note there are cases of this in mscorlib even in release builds,
1616 // eg Task.NotifyDebuggerOfWaitCompletion.
1617 //
1618 // For such methods there aren't any inlines.
1619 if (m_RootContext != nullptr)
1620 {
1621 m_RootContext->DumpXml(file, indent + 2);
1622 }
1623 else
1624 {
1625 fprintf(file, "%*s<Inlines/>\n", indent + 2, "");
1626 }
1627
1628 fprintf(file, "%*s</Method>\n", indent, "");
1629}
1630
1631//------------------------------------------------------------------------
1632// FinalizeXml: finalize the xml-formatted version of the inline tree.
1633//
1634// Arguments
1635// file - file for data output
1636
1637void InlineStrategy::FinalizeXml(FILE* file)
1638{
1639 // If we dumped the header, dump a footer
1640 if (s_HasDumpedXmlHeader)
1641 {
1642 fprintf(file, "</Methods>\n");
1643 fprintf(file, "</InlineForest>\n");
1644 fflush(file);
1645
1646 // Workaroud compShutdown getting called twice.
1647 s_HasDumpedXmlHeader = false;
1648 }
1649
1650 // Finalize reading inline xml
1651 ReplayPolicy::FinalizeXml();
1652}
1653
1654//------------------------------------------------------------------------
1655// GetRandom: setup or access random state
1656//
1657// Return Value:
1658// New or pre-existing random state.
1659//
1660// Notes:
1661// Random state is kept per jit compilation request. Seed is partially
1662// specified externally (via stress or policy setting) and partially
1663// specified internally via method hash.
1664
1665CLRRandom* InlineStrategy::GetRandom()
1666{
1667 if (m_Random == nullptr)
1668 {
1669 int externalSeed = 0;
1670
1671#ifdef DEBUG
1672
1673 if (m_Compiler->compRandomInlineStress())
1674 {
1675 externalSeed = getJitStressLevel();
1676 }
1677
1678#endif // DEBUG
1679
1680 int randomPolicyFlag = JitConfig.JitInlinePolicyRandom();
1681 if (randomPolicyFlag != 0)
1682 {
1683 externalSeed = randomPolicyFlag;
1684 }
1685
1686 int internalSeed = m_Compiler->info.compMethodHash();
1687
1688 assert(externalSeed != 0);
1689 assert(internalSeed != 0);
1690
1691 int seed = externalSeed ^ internalSeed;
1692
1693 m_Random = new (m_Compiler, CMK_Inlining) CLRRandom();
1694 m_Random->Init(seed);
1695 }
1696
1697 return m_Random;
1698}
1699
1700#endif // defined(DEBUG) || defined(INLINE_DATA)
1701
1702//------------------------------------------------------------------------
1703// IsNoInline: allow strategy to disable inlining in a method
1704//
1705// Arguments:
1706// info -- compiler interface from the EE
1707// method -- handle for the root method
1708//
1709// Notes:
1710// Only will return true in debug or special release builds.
1711// Expects JitNoInlineRange to be set to the hashes of methods
1712// where inlining is disabled.
1713
1714bool InlineStrategy::IsNoInline(ICorJitInfo* info, CORINFO_METHOD_HANDLE method)
1715{
1716
1717#if defined(DEBUG) || defined(INLINE_DATA)
1718
1719 static ConfigMethodRange range;
1720 const wchar_t* noInlineRange = JitConfig.JitNoInlineRange();
1721
1722 if (noInlineRange == nullptr)
1723 {
1724 return false;
1725 }
1726
1727 // If we have a config string we have at least one entry. Count
1728 // number of spaces in our config string to see if there are
1729 // more. Number of ranges we need is 2x that value.
1730 unsigned entryCount = 1;
1731 for (const wchar_t* p = noInlineRange; *p != 0; p++)
1732 {
1733 if (*p == L' ')
1734 {
1735 entryCount++;
1736 }
1737 }
1738
1739 range.EnsureInit(noInlineRange, 2 * entryCount);
1740 assert(!range.Error());
1741 return range.Contains(info, method);
1742
1743#else
1744
1745 return false;
1746
1747#endif // defined(DEBUG) || defined(INLINE_DATA)
1748}
1749