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 | |
14 | static 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 | |
22 | static 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 | |
30 | static 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 | |
47 | bool 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 | |
63 | const 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 | |
78 | InlineTarget 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 | |
93 | const 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 | |
118 | InlineImpact 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 | |
133 | const 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 | |
162 | CorInfoInline 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 | |
187 | const 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 | |
216 | bool 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 | |
242 | bool 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 | |
268 | bool 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 | |
294 | bool 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 | |
308 | bool 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 | |
328 | InlineContext::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 | |
360 | void 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 | |
431 | void 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 | |
475 | void 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 | |
570 | InlineResult::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 | |
627 | InlineResult::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 | |
660 | void 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 | |
755 | InlineStrategy::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 | |
835 | InlineContext* 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 | |
874 | void 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 | |
899 | void InlineStrategy::(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 | |
919 | void 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 | |
957 | int 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 | |
987 | int 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 | |
1006 | int 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 | |
1020 | int 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 | |
1047 | void 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 | |
1149 | bool 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 | |
1166 | InlineContext* 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 | |
1192 | InlineContext* 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 | |
1256 | InlineContext* 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 | |
1306 | void 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 | |
1338 | bool InlineStrategy:: = false; |
1339 | |
1340 | //------------------------------------------------------------------------ |
1341 | // DumpData: dump data about the last successful inline into this method |
1342 | // in a format suitable for automated analysis. |
1343 | |
1344 | void 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 | |
1388 | void 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 | |
1414 | void InlineStrategy::(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 | |
1429 | void 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 | |
1442 | void 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 | |
1476 | bool InlineStrategy:: = false; |
1477 | CritSecObject 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 | |
1486 | void 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 | |
1637 | void 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 | |
1665 | CLRRandom* 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 | |
1714 | bool 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 | |