-
Notifications
You must be signed in to change notification settings - Fork 4.8k
/
inlinepolicy.cpp
3704 lines (3204 loc) · 115 KB
/
inlinepolicy.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#include "jitpch.h"
#ifdef _MSC_VER
#pragma hdrstop
#endif
#include "inlinepolicy.h"
#include "sm.h"
//------------------------------------------------------------------------
// getPolicy: Factory method for getting an InlinePolicy
//
// Arguments:
// compiler - the compiler instance that will evaluate inlines
// isPrejitRoot - true if this policy is evaluating a prejit root
//
// Return Value:
// InlinePolicy to use in evaluating an inline.
//
// Notes:
// Determines which of the various policies should apply,
// and creates (or reuses) a policy instance to use.
InlinePolicy* InlinePolicy::GetPolicy(Compiler* compiler, bool isPrejitRoot)
{
#if defined(DEBUG) || defined(INLINE_DATA)
#if defined(DEBUG)
const bool useRandomPolicyForStress = compiler->compRandomInlineStress();
#else
const bool useRandomPolicyForStress = false;
#endif // defined(DEBUG)
const bool useRandomPolicy = (JitConfig.JitInlinePolicyRandom() != 0);
// Optionally install the RandomPolicy.
if (useRandomPolicyForStress || useRandomPolicy)
{
return new (compiler, CMK_Inlining) RandomPolicy(compiler, isPrejitRoot);
}
// Optionally install the ReplayPolicy.
bool useReplayPolicy = JitConfig.JitInlinePolicyReplay() != 0;
if (useReplayPolicy)
{
return new (compiler, CMK_Inlining) ReplayPolicy(compiler, isPrejitRoot);
}
// Optionally install the SizePolicy.
bool useSizePolicy = JitConfig.JitInlinePolicySize() != 0;
if (useSizePolicy)
{
return new (compiler, CMK_Inlining) SizePolicy(compiler, isPrejitRoot);
}
// Optionally install the FullPolicy.
bool useFullPolicy = JitConfig.JitInlinePolicyFull() != 0;
if (useFullPolicy)
{
return new (compiler, CMK_Inlining) FullPolicy(compiler, isPrejitRoot);
}
// Optionally install the DiscretionaryPolicy.
bool useDiscretionaryPolicy = JitConfig.JitInlinePolicyDiscretionary() != 0;
if (useDiscretionaryPolicy)
{
return new (compiler, CMK_Inlining) DiscretionaryPolicy(compiler, isPrejitRoot);
}
#endif // defined(DEBUG) || defined(INLINE_DATA)
// Optionally install the ModelPolicy.
bool useModelPolicy = JitConfig.JitInlinePolicyModel() != 0;
if (useModelPolicy)
{
return new (compiler, CMK_Inlining) ModelPolicy(compiler, isPrejitRoot);
}
// Optionally install the ProfilePolicy, if the method has profile data.
//
bool enableProfilePolicy = JitConfig.JitInlinePolicyProfile() != 0;
bool hasProfileData = compiler->fgIsUsingProfileWeights();
if (enableProfilePolicy && hasProfileData)
{
return new (compiler, CMK_Inlining) ProfilePolicy(compiler, isPrejitRoot);
}
const bool isPrejit = compiler->opts.jitFlags->IsSet(JitFlags::JIT_FLAG_PREJIT);
const bool isSpeedOpt = compiler->opts.jitFlags->IsSet(JitFlags::JIT_FLAG_SPEED_OPT);
if ((JitConfig.JitExtDefaultPolicy() != 0))
{
if (isPrejitRoot || !isPrejit || (isPrejit && isSpeedOpt))
{
return new (compiler, CMK_Inlining) ExtendedDefaultPolicy(compiler, isPrejitRoot);
}
}
return new (compiler, CMK_Inlining) DefaultPolicy(compiler, isPrejitRoot);
}
//------------------------------------------------------------------------
// NoteFatal: handle an observation with fatal impact
//
// Arguments:
// obs - the current obsevation
void LegalPolicy::NoteFatal(InlineObservation obs)
{
// As a safeguard, all fatal impact must be
// reported via NoteFatal.
assert(InlGetImpact(obs) == InlineImpact::FATAL);
NoteInternal(obs);
assert(InlDecisionIsFailure(m_Decision));
}
#if defined(DEBUG) || defined(INLINE_DATA)
//------------------------------------------------------------------------
// NotePriorFailure: record reason for earlier inline failure
//
// Arguments:
// obs - the current obsevation
//
// Notes:
// Used to "resurrect" failure observations from the early inline
// screen when building the inline context tree. Only used during
// debug modes.
void LegalPolicy::NotePriorFailure(InlineObservation obs)
{
NoteInternal(obs);
assert(InlDecisionIsFailure(m_Decision));
}
#endif // defined(DEBUG) || defined(INLINE_DATA)
//------------------------------------------------------------------------
// NoteInternal: helper for handling an observation
//
// Arguments:
// obs - the current obsevation
void LegalPolicy::NoteInternal(InlineObservation obs)
{
// Note any INFORMATION that reaches here will now cause failure.
// Non-fatal INFORMATION observations must be handled higher up.
InlineTarget target = InlGetTarget(obs);
if (target == InlineTarget::CALLEE)
{
this->SetNever(obs);
}
else
{
this->SetFailure(obs);
}
}
//------------------------------------------------------------------------
// SetFailure: helper for setting a failing decision
//
// Arguments:
// obs - the current obsevation
void LegalPolicy::SetFailure(InlineObservation obs)
{
// Expect a valid observation
assert(InlIsValidObservation(obs));
switch (m_Decision)
{
case InlineDecision::FAILURE:
// Repeated failure only ok if evaluating a prejit root
// (since we can't fail fast because we're not inlining)
// or if inlining and the observation is CALLSITE_TOO_MANY_LOCALS
// (since we can't fail fast from lvaGrabTemp).
assert(m_IsPrejitRoot || (obs == InlineObservation::CALLSITE_TOO_MANY_LOCALS));
break;
case InlineDecision::UNDECIDED:
case InlineDecision::CANDIDATE:
m_Decision = InlineDecision::FAILURE;
m_Observation = obs;
break;
default:
// SUCCESS, NEVER, or ??
assert(!"Unexpected m_Decision");
unreached();
}
}
//------------------------------------------------------------------------
// SetNever: helper for setting a never decision
//
// Arguments:
// obs - the current obsevation
void LegalPolicy::SetNever(InlineObservation obs)
{
// Expect a valid observation
assert(InlIsValidObservation(obs));
switch (m_Decision)
{
case InlineDecision::NEVER:
// Repeated never only ok if evaluating a prejit root
assert(m_IsPrejitRoot);
break;
case InlineDecision::UNDECIDED:
case InlineDecision::CANDIDATE:
m_Decision = InlineDecision::NEVER;
m_Observation = obs;
break;
default:
// SUCCESS, FAILURE or ??
assert(!"Unexpected m_Decision");
unreached();
}
}
//------------------------------------------------------------------------
// SetCandidate: helper updating candidacy
//
// Arguments:
// obs - the current obsevation
//
// Note:
// Candidate observations are handled here. If the inline has already
// failed, they're ignored. If there's already a candidate reason,
// this new reason trumps it.
void LegalPolicy::SetCandidate(InlineObservation obs)
{
// Ignore if this inline is going to fail.
if (InlDecisionIsFailure(m_Decision))
{
return;
}
// We should not have declared success yet.
assert(!InlDecisionIsSuccess(m_Decision));
// Update, overriding any previous candidacy.
m_Decision = InlineDecision::CANDIDATE;
m_Observation = obs;
}
//------------------------------------------------------------------------
// NoteSuccess: handle finishing all the inlining checks successfully
void DefaultPolicy::NoteSuccess()
{
assert(InlDecisionIsCandidate(m_Decision));
m_Decision = InlineDecision::SUCCESS;
}
//------------------------------------------------------------------------
// NoteBool: handle a boolean observation with non-fatal impact
//
// Arguments:
// obs - the current obsevation
// value - the value of the observation
void DefaultPolicy::NoteBool(InlineObservation obs, bool value)
{
// Check the impact
InlineImpact impact = InlGetImpact(obs);
// As a safeguard, all fatal impact must be
// reported via NoteFatal.
assert(impact != InlineImpact::FATAL);
// Handle most information here
bool isInformation = (impact == InlineImpact::INFORMATION);
bool propagate = !isInformation;
if (isInformation)
{
switch (obs)
{
case InlineObservation::CALLEE_IS_FORCE_INLINE:
// We may make the force-inline observation more than
// once. All observations should agree.
assert(!m_IsForceInlineKnown || (m_IsForceInline == value));
m_IsForceInline = value;
m_IsForceInlineKnown = true;
break;
case InlineObservation::CALLEE_IS_INSTANCE_CTOR:
m_IsInstanceCtor = value;
break;
case InlineObservation::CALLEE_CLASS_PROMOTABLE:
m_IsFromPromotableValueClass = value;
break;
case InlineObservation::CALLSITE_IN_TRY_REGION:
m_CallsiteIsInTryRegion = value;
break;
case InlineObservation::CALLEE_HAS_SIMD:
m_HasSimd = value;
break;
case InlineObservation::CALLEE_LOOKS_LIKE_WRAPPER:
m_LooksLikeWrapperMethod = value;
break;
case InlineObservation::CALLEE_ARG_FEEDS_TEST:
m_ArgFeedsTest++;
break;
case InlineObservation::CALLEE_ARG_FEEDS_CONSTANT_TEST:
m_ArgFeedsConstantTest++;
break;
case InlineObservation::CALLEE_ARG_FEEDS_RANGE_CHECK:
m_ArgFeedsRangeCheck++;
break;
case InlineObservation::CALLEE_CONST_ARG_FEEDS_ISCONST:
m_ConstArgFeedsIsKnownConst = true;
break;
case InlineObservation::CALLEE_ARG_FEEDS_ISCONST:
m_ArgFeedsIsKnownConst = true;
break;
case InlineObservation::CALLEE_UNSUPPORTED_OPCODE:
propagate = true;
break;
case InlineObservation::CALLSITE_CONSTANT_ARG_FEEDS_TEST:
// We shouldn't see this for a prejit root since
// we don't know anything about callers.
assert(!m_IsPrejitRoot);
m_ConstantArgFeedsConstantTest++;
break;
case InlineObservation::CALLEE_BEGIN_OPCODE_SCAN:
{
// Set up the state machine, if this inline is
// discretionary and is still a candidate.
if (InlDecisionIsCandidate(m_Decision) &&
(m_Observation == InlineObservation::CALLEE_IS_DISCRETIONARY_INLINE))
{
// Better not have a state machine already.
assert(m_StateMachine == nullptr);
m_StateMachine = new (m_RootCompiler, CMK_Inlining) CodeSeqSM;
m_StateMachine->Start(m_RootCompiler);
}
break;
}
case InlineObservation::CALLEE_END_OPCODE_SCAN:
{
if (m_StateMachine != nullptr)
{
m_StateMachine->End();
}
// If this function is mostly loads and stores, we
// should try harder to inline it. You can't just use
// the percentage test because if the method has 8
// instructions and 6 are loads, it's only 75% loads.
// This allows for CALL, RET, and one more non-ld/st
// instruction.
if (((m_InstructionCount - m_LoadStoreCount) < 4) ||
(((double)m_LoadStoreCount / (double)m_InstructionCount) > .90))
{
m_MethodIsMostlyLoadStore = true;
}
// Budget check.
//
// Conceptually this should happen when we
// observe the candidate's IL size.
//
// However, we do this here to avoid potential
// inconsistency between the state of the budget
// during candidate scan and the state when the IL is
// being scanned.
//
// Consider the case where we're just below the budget
// during candidate scan, and we have three possible
// inlines, any two of which put us over budget. We
// allow them all to become candidates. We then move
// on to inlining and the first two get inlined and
// put us over budget. Now the third can't be inlined
// anymore, but we have a policy that when we replay
// the candidate IL size during the inlining pass it
// "reestablishes" candidacy rather than alters
// candidacy ... so instead we bail out here.
//
bool overBudget = this->BudgetCheck();
if (overBudget)
{
SetFailure(InlineObservation::CALLSITE_OVER_BUDGET);
return;
}
break;
}
case InlineObservation::CALLSITE_IN_LOOP:
m_CallsiteIsInLoop = true;
break;
case InlineObservation::CALLEE_DOES_NOT_RETURN:
m_IsNoReturn = value;
m_IsNoReturnKnown = true;
break;
case InlineObservation::CALLSITE_RARE_GC_STRUCT:
// If this is a discretionary or always inline candidate
// with a gc struct, we may change our mind about inlining
// if the call site is rare, to avoid costs associated with
// zeroing the GC struct up in the root prolog.
if (m_Observation == InlineObservation::CALLEE_BELOW_ALWAYS_INLINE_SIZE)
{
assert(m_CallsiteFrequency == InlineCallsiteFrequency::UNUSED);
SetFailure(obs);
return;
}
else if (m_Observation == InlineObservation::CALLEE_IS_DISCRETIONARY_INLINE)
{
assert(m_CallsiteFrequency == InlineCallsiteFrequency::RARE);
SetFailure(obs);
return;
}
break;
case InlineObservation::CALLEE_HAS_PINNED_LOCALS:
if (m_CallsiteIsInTryRegion)
{
// Inlining a method with pinned locals in a try
// region requires wrapping the inline body in a
// try/finally to ensure unpinning. Bail instead.
SetFailure(InlineObservation::CALLSITE_PIN_IN_TRY_REGION);
return;
}
break;
case InlineObservation::CALLEE_HAS_LOCALLOC:
// We see this during the IL prescan. Ignore for now, we will
// bail out, if necessary, during importation
break;
default:
// Ignore the remainder for now
break;
}
}
if (propagate)
{
NoteInternal(obs);
}
}
//------------------------------------------------------------------------
// BudgetCheck: see if this inline would exceed the current budget
//
// Returns:
// True if inline would exceed the budget.
//
bool DefaultPolicy::BudgetCheck() const
{
// Only relevant if we're actually inlining.
//
if (m_IsPrejitRoot)
{
return false;
}
// The strategy tracks the amout of inlining done so far,
// so it performs the actual check.
//
InlineStrategy* strategy = m_RootCompiler->m_inlineStrategy;
const bool overBudget = strategy->BudgetCheck(m_CodeSize);
if (overBudget)
{
// If the candidate is a forceinline and the callsite is
// not too deep, allow the inline even if it goes over budget.
//
// For now, "not too deep" means a top-level inline. Note
// depth 0 is used for the root method, so inline candidate depth
// will be 1 or more.
//
assert(m_IsForceInlineKnown);
assert(m_CallsiteDepth > 0);
const bool allowOverBudget = m_IsForceInline && (m_CallsiteDepth == 1);
if (allowOverBudget)
{
JITDUMP("Allowing over-budget top-level forceinline\n");
}
else
{
return true;
}
}
return false;
}
//------------------------------------------------------------------------
// NoteInt: handle an observed integer value
//
// Arguments:
// obs - the current obsevation
// value - the value being observed
void DefaultPolicy::NoteInt(InlineObservation obs, int value)
{
switch (obs)
{
case InlineObservation::CALLEE_MAXSTACK:
{
assert(m_IsForceInlineKnown);
unsigned calleeMaxStack = static_cast<unsigned>(value);
if (!m_IsForceInline && (calleeMaxStack > SMALL_STACK_SIZE))
{
SetNever(InlineObservation::CALLEE_MAXSTACK_TOO_BIG);
}
break;
}
case InlineObservation::CALLEE_NUMBER_OF_BASIC_BLOCKS:
{
assert(m_IsForceInlineKnown);
assert(value != 0);
assert(m_IsNoReturnKnown);
//
// Let's be conservative for now and reject inlining of "no return" methods only
// if the callee contains a single basic block. This covers most of the use cases
// (typical throw helpers simply do "throw new X();" and so they have a single block)
// without affecting more exotic cases (loops that do actual work for example) where
// failure to inline could negatively impact code quality.
//
unsigned basicBlockCount = static_cast<unsigned>(value);
// CALLEE_IS_FORCE_INLINE overrides CALLEE_DOES_NOT_RETURN
if (!m_IsForceInline && m_IsNoReturn && (basicBlockCount == 1))
{
SetNever(InlineObservation::CALLEE_DOES_NOT_RETURN);
}
else if (!m_IsForceInline && (basicBlockCount > MAX_BASIC_BLOCKS))
{
SetNever(InlineObservation::CALLEE_TOO_MANY_BASIC_BLOCKS);
}
break;
}
case InlineObservation::CALLEE_IL_CODE_SIZE:
{
assert(m_IsForceInlineKnown);
assert(value != 0);
m_CodeSize = static_cast<unsigned>(value);
// Now that we know size and forceinline state,
// update candidacy.
if (m_IsForceInline)
{
// Candidate based on force inline
SetCandidate(InlineObservation::CALLEE_IS_FORCE_INLINE);
}
else if (m_CodeSize <= InlineStrategy::ALWAYS_INLINE_SIZE)
{
// Candidate based on small size
SetCandidate(InlineObservation::CALLEE_BELOW_ALWAYS_INLINE_SIZE);
}
else if (m_CodeSize <= m_RootCompiler->m_inlineStrategy->GetMaxInlineILSize())
{
// Candidate, pending profitability evaluation
SetCandidate(InlineObservation::CALLEE_IS_DISCRETIONARY_INLINE);
}
else
{
// Callee too big, not a candidate
SetNever(InlineObservation::CALLEE_TOO_MUCH_IL);
}
break;
}
case InlineObservation::CALLSITE_DEPTH:
{
m_CallsiteDepth = static_cast<unsigned>(value);
if (m_CallsiteDepth > m_RootCompiler->m_inlineStrategy->GetMaxInlineDepth())
{
SetFailure(InlineObservation::CALLSITE_IS_TOO_DEEP);
}
break;
}
case InlineObservation::CALLEE_OPCODE_NORMED:
case InlineObservation::CALLEE_OPCODE:
{
m_InstructionCount++;
OPCODE opcode = static_cast<OPCODE>(value);
if (m_StateMachine != nullptr)
{
SM_OPCODE smOpcode = CodeSeqSM::MapToSMOpcode(opcode);
noway_assert(smOpcode < SM_COUNT);
noway_assert(smOpcode != SM_PREFIX_N);
if (obs == InlineObservation::CALLEE_OPCODE_NORMED)
{
if (smOpcode == SM_LDARGA_S)
{
smOpcode = SM_LDARGA_S_NORMED;
}
else if (smOpcode == SM_LDLOCA_S)
{
smOpcode = SM_LDLOCA_S_NORMED;
}
}
m_StateMachine->Run(smOpcode DEBUGARG(0));
}
// Look for opcodes that imply loads and stores.
// Logic here is as it is to match legacy behavior.
if ((opcode >= CEE_LDARG_0 && opcode <= CEE_STLOC_S) || (opcode >= CEE_LDARG && opcode <= CEE_STLOC) ||
(opcode >= CEE_LDNULL && opcode <= CEE_LDC_R8) || (opcode >= CEE_LDIND_I1 && opcode <= CEE_STIND_R8) ||
(opcode >= CEE_LDFLD && opcode <= CEE_STOBJ) || (opcode >= CEE_LDELEMA && opcode <= CEE_STELEM) ||
(opcode == CEE_POP))
{
m_LoadStoreCount++;
}
break;
}
case InlineObservation::CALLSITE_FREQUENCY:
assert(m_CallsiteFrequency == InlineCallsiteFrequency::UNUSED);
m_CallsiteFrequency = static_cast<InlineCallsiteFrequency>(value);
assert(m_CallsiteFrequency != InlineCallsiteFrequency::UNUSED);
break;
default:
// Ignore all other information
break;
}
}
//------------------------------------------------------------------------
// NoteDouble: handle an observed double value
//
// Arguments:
// obs - the current obsevation
// value - the value being observed
void DefaultPolicy::NoteDouble(InlineObservation obs, double value)
{
assert(obs == InlineObservation::CALLSITE_PROFILE_FREQUENCY);
}
//------------------------------------------------------------------------
// DetermineMultiplier: determine benefit multiplier for this inline
//
// Notes: uses the accumulated set of observations to compute a
// profitability boost for the inline candidate.
double DefaultPolicy::DetermineMultiplier()
{
double multiplier = 0;
// Bump up the multiplier for instance constructors
if (m_IsInstanceCtor)
{
multiplier += 1.5;
JITDUMP("\nmultiplier in instance constructors increased to %g.", multiplier);
}
// Bump up the multiplier for methods in promotable struct
if (m_IsFromPromotableValueClass)
{
multiplier += 3;
JITDUMP("\nmultiplier in methods of promotable struct increased to %g.", multiplier);
}
#ifdef FEATURE_SIMD
if (m_HasSimd)
{
multiplier += JitConfig.JitInlineSIMDMultiplier();
JITDUMP("\nInline candidate has SIMD type args, locals or return value. Multiplier increased to %g.",
multiplier);
}
#endif // FEATURE_SIMD
if (m_LooksLikeWrapperMethod)
{
multiplier += 1.0;
JITDUMP("\nInline candidate looks like a wrapper method. Multiplier increased to %g.", multiplier);
}
if (m_ArgFeedsConstantTest > 0)
{
multiplier += 1.0;
JITDUMP("\nInline candidate has an arg that feeds a constant test. Multiplier increased to %g.", multiplier);
}
if (m_MethodIsMostlyLoadStore)
{
multiplier += 3.0;
JITDUMP("\nInline candidate is mostly loads and stores. Multiplier increased to %g.", multiplier);
}
if (m_ArgFeedsRangeCheck > 0)
{
multiplier += 0.5;
JITDUMP("\nInline candidate has arg that feeds range check. Multiplier increased to %g.", multiplier);
}
if (m_ConstantArgFeedsConstantTest > 0)
{
multiplier += 3.0;
JITDUMP("\nInline candidate has const arg that feeds a conditional. Multiplier increased to %g.", multiplier);
}
// For prejit roots we do not see the call sites. To be suitably optimistic
// assume that call sites may pass constants.
else if (m_IsPrejitRoot && ((m_ArgFeedsConstantTest > 0) || (m_ArgFeedsTest > 0)))
{
multiplier += 3.0;
JITDUMP("\nPrejit root candidate has arg that feeds a conditional. Multiplier increased to %g.", multiplier);
}
switch (m_CallsiteFrequency)
{
case InlineCallsiteFrequency::RARE:
// Note this one is not additive, it uses '=' instead of '+='
multiplier = 1.3;
JITDUMP("\nInline candidate callsite is rare. Multiplier limited to %g.", multiplier);
break;
case InlineCallsiteFrequency::BORING:
multiplier += 1.3;
JITDUMP("\nInline candidate callsite is boring. Multiplier increased to %g.", multiplier);
break;
case InlineCallsiteFrequency::WARM:
multiplier += 2.0;
JITDUMP("\nInline candidate callsite is warm. Multiplier increased to %g.", multiplier);
break;
case InlineCallsiteFrequency::LOOP:
multiplier += 3.0;
JITDUMP("\nInline candidate callsite is in a loop. Multiplier increased to %g.", multiplier);
break;
case InlineCallsiteFrequency::HOT:
multiplier += 3.0;
JITDUMP("\nInline candidate callsite is hot. Multiplier increased to %g.", multiplier);
break;
default:
assert(!"Unexpected callsite frequency");
break;
}
#ifdef DEBUG
int additionalMultiplier = JitConfig.JitInlineAdditionalMultiplier();
if (additionalMultiplier != 0)
{
multiplier += additionalMultiplier;
JITDUMP("\nmultiplier increased via JitInlineAdditionalMultiplier=%d to %g.", additionalMultiplier, multiplier);
}
if (m_RootCompiler->compInlineStress())
{
multiplier += 10;
JITDUMP("\nmultiplier increased via inline stress to %g.", multiplier);
}
#endif // DEBUG
return multiplier;
}
//------------------------------------------------------------------------
// DetermineNativeSizeEstimate: return estimated native code size for
// this inline candidate.
//
// Notes:
// This is an estimate for the size of the inlined callee.
// It does not include size impact on the caller side.
//
// Uses the results of a state machine model for discretionary
// candidates. Should not be needed for forced or always
// candidates.
int DefaultPolicy::DetermineNativeSizeEstimate()
{
// Should be a discretionary candidate.
assert(m_StateMachine != nullptr);
return m_StateMachine->NativeSize;
}
//------------------------------------------------------------------------
// DetermineCallsiteNativeSizeEstimate: estimate native size for the
// callsite.
//
// Arguments:
// methInfo -- method info for the callee
//
// Notes:
// Estimates the native size (in bytes, scaled up by 10x) for the
// call site. While the quality of the estimate here is questionable
// (especially for x64) it is being left as is for legacy compatibility.
int DefaultPolicy::DetermineCallsiteNativeSizeEstimate(CORINFO_METHOD_INFO* methInfo)
{
int callsiteSize = 55; // Direct call take 5 native bytes; indirect call takes 6 native bytes.
bool hasThis = methInfo->args.hasThis();
if (hasThis)
{
callsiteSize += 30; // "mov" or "lea"
}
CORINFO_ARG_LIST_HANDLE argLst = methInfo->args.args;
COMP_HANDLE comp = m_RootCompiler->info.compCompHnd;
for (unsigned i = (hasThis ? 1 : 0); i < methInfo->args.totalILArgs(); i++, argLst = comp->getArgNext(argLst))
{
var_types sigType = (var_types)m_RootCompiler->eeGetArgType(argLst, &methInfo->args);
if (sigType == TYP_STRUCT)
{
typeInfo verType = m_RootCompiler->verParseArgSigToTypeInfo(&methInfo->args, argLst);
/*
IN0028: 00009B lea EAX, bword ptr [EBP-14H]
IN0029: 00009E push dword ptr [EAX+4]
IN002a: 0000A1 push gword ptr [EAX]
IN002b: 0000A3 call [MyStruct.staticGetX2(struct):int]
*/
callsiteSize += 10; // "lea EAX, bword ptr [EBP-14H]"
unsigned opsz = roundUp(comp->getClassSize(verType.GetClassHandle()), TARGET_POINTER_SIZE);
unsigned slots = opsz / TARGET_POINTER_SIZE;
callsiteSize += slots * 20; // "push gword ptr [EAX+offs] "
}
else
{
callsiteSize += 30; // push by average takes 3 bytes.
}
}
return callsiteSize;
}
//------------------------------------------------------------------------
// DetermineProfitability: determine if this inline is profitable
//
// Arguments:
// methodInfo -- method info for the callee
//
// Notes:
// A profitable inline is one that is projected to have a beneficial
// size/speed tradeoff.
//
// It is expected that this method is only invoked for discretionary
// candidates, since it does not make sense to do this assessment for
// failed, always, or forced inlines.
void DefaultPolicy::DetermineProfitability(CORINFO_METHOD_INFO* methodInfo)
{
#if defined(DEBUG)
// Punt if we're inlining and we've reached the acceptance limit.
int limit = JitConfig.JitInlineLimit();
unsigned current = m_RootCompiler->m_inlineStrategy->GetInlineCount();
if (!m_IsPrejitRoot && (limit >= 0) && (current >= static_cast<unsigned>(limit)))
{
SetFailure(InlineObservation::CALLSITE_OVER_INLINE_LIMIT);
return;
}
#endif // defined(DEBUG)
assert(InlDecisionIsCandidate(m_Decision));
assert(m_Observation == InlineObservation::CALLEE_IS_DISCRETIONARY_INLINE);
m_CalleeNativeSizeEstimate = DetermineNativeSizeEstimate();
m_CallsiteNativeSizeEstimate = DetermineCallsiteNativeSizeEstimate(methodInfo);
m_Multiplier = DetermineMultiplier();
const int threshold = (int)(m_CallsiteNativeSizeEstimate * m_Multiplier);
// Note the DefaultPolicy estimates are scaled up by SIZE_SCALE
JITDUMP("\ncalleeNativeSizeEstimate=%d\n", m_CalleeNativeSizeEstimate)
JITDUMP("callsiteNativeSizeEstimate=%d\n", m_CallsiteNativeSizeEstimate);
JITDUMP("benefit multiplier=%g\n", m_Multiplier);
JITDUMP("threshold=%d\n", threshold);
// Reject if callee size is over the threshold
if (m_CalleeNativeSizeEstimate > threshold)
{
// Inline appears to be unprofitable
JITLOG_THIS(m_RootCompiler,
(LL_INFO100000, "Native estimate for function size exceeds threshold"
" for inlining %g > %g (multiplier = %g)\n",
(double)m_CalleeNativeSizeEstimate / SIZE_SCALE, (double)threshold / SIZE_SCALE, m_Multiplier));
// Fail the inline
if (m_IsPrejitRoot)
{
SetNever(InlineObservation::CALLEE_NOT_PROFITABLE_INLINE);
}
else
{
SetFailure(InlineObservation::CALLSITE_NOT_PROFITABLE_INLINE);
}
}
else
{
// Inline appears to be profitable
JITLOG_THIS(m_RootCompiler,
(LL_INFO100000, "Native estimate for function size is within threshold"
" for inlining %g <= %g (multiplier = %g)\n",
(double)m_CalleeNativeSizeEstimate / SIZE_SCALE, (double)threshold / SIZE_SCALE, m_Multiplier));
// Update candidacy
if (m_IsPrejitRoot)
{
SetCandidate(InlineObservation::CALLEE_IS_PROFITABLE_INLINE);
}
else
{
SetCandidate(InlineObservation::CALLSITE_IS_PROFITABLE_INLINE);
}
}
}
//------------------------------------------------------------------------
// CodeSizeEstimate: estimated code size impact of the inline
//
// Return Value:
// Estimated code size impact, in bytes * 10
//
// Notes:
// Only meaningful for discretionary inlines (whether successful or
// not). For always or force inlines the legacy policy doesn't
// estimate size impact.
int DefaultPolicy::CodeSizeEstimate()
{
if (m_StateMachine != nullptr)
{
// This is not something the DefaultPolicy explicitly computed,
// since it uses a blended evaluation model (mixing size and time
// together for overall profitability). But it's effecitvely an
// estimate of the size impact.
return (m_CalleeNativeSizeEstimate - m_CallsiteNativeSizeEstimate);
}
else
{
return 0;
}
}
#if defined(DEBUG) || defined(INLINE_DATA)
//------------------------------------------------------------------------
// OnDumpXml: Dump DefaultPolicy data as XML
//
// Arguments:
// file - stream to output to
// indent - indent level
void DefaultPolicy::OnDumpXml(FILE* file, unsigned indent) const
{
XATTR_R8(m_Multiplier);