forked from chakra-core/ChakraCore
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathInliningHeuristics.cpp
More file actions
421 lines (367 loc) · 18.7 KB
/
InliningHeuristics.cpp
File metadata and controls
421 lines (367 loc) · 18.7 KB
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
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include "BackEnd.h"
InliningThreshold::InliningThreshold(Js::FunctionBody *topFunc, bool aggressive) : topFunc(topFunc)
{
if (aggressive)
{
SetAggressiveHeuristics();
}
else
{
SetHeuristics();
}
}
void InliningThreshold::SetAggressiveHeuristics()
{
int limit = CONFIG_FLAG(AggressiveInlineThreshold);
inlineThreshold = limit;
constructorInlineThreshold = limit;
outsideLoopInlineThreshold = limit;
leafInlineThreshold = limit;
loopInlineThreshold = limit;
polymorphicInlineThreshold = limit;
maxNumberOfInlineesWithLoop = CONFIG_FLAG(MaxNumberOfInlineesWithLoop);
inlineCountMax = CONFIG_FLAG(AggressiveInlineCountMax);
}
void InliningThreshold::Reset()
{
SetHeuristics();
}
void InliningThreshold::SetHeuristics()
{
inlineThreshold = CONFIG_FLAG(InlineThreshold);
// Inline less aggressively in large functions since the register pressure is likely high.
// Small functions shouldn't be a problem.
if (topFunc->GetByteCodeWithoutLDACount() > 800)
{
inlineThreshold -= CONFIG_FLAG(InlineThresholdAdjustCountInLargeFunction);
}
else if (topFunc->GetByteCodeWithoutLDACount() > 200)
{
inlineThreshold -= CONFIG_FLAG(InlineThresholdAdjustCountInMediumSizedFunction);
}
else if (topFunc->GetByteCodeWithoutLDACount() < 50)
{
inlineThreshold += CONFIG_FLAG(InlineThresholdAdjustCountInSmallFunction);
}
constructorInlineThreshold = CONFIG_FLAG(ConstructorInlineThreshold);
outsideLoopInlineThreshold = CONFIG_FLAG(OutsideLoopInlineThreshold);
leafInlineThreshold = CONFIG_FLAG(LeafInlineThreshold);
loopInlineThreshold = CONFIG_FLAG(LoopInlineThreshold);
polymorphicInlineThreshold = CONFIG_FLAG(PolymorphicInlineThreshold);
maxNumberOfInlineesWithLoop = CONFIG_FLAG(MaxNumberOfInlineesWithLoop);
constantArgumentInlineThreshold = CONFIG_FLAG(ConstantArgumentInlineThreshold);
inlineCountMax = CONFIG_FLAG(InlineCountMax);
}
bool InliningHeuristics::CanRecursivelyInline(Js::FunctionBody* inlinee, Js::FunctionBody *inliner, bool allowRecursiveInlining, uint recursiveInlineDepth)
{
#if defined(DBG_DUMP) || defined(ENABLE_DEBUG_CONFIG_OPTIONS)
wchar_t debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
wchar_t debugStringBuffer2[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
#endif
if (!PHASE_OFF(Js::InlineRecursivePhase, inliner)
&& allowRecursiveInlining
&& inlinee == inliner
&& inlinee->CanInlineRecursively(recursiveInlineDepth) )
{
INLINE_TESTTRACE(L"INLINING: Inlined recursively\tInlinee: %s (%s)\tCaller: %s (%s)\tDepth: %d\n",
inlinee->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer), inliner->GetDisplayName(),
inliner->GetDebugNumberSet(debugStringBuffer2), recursiveInlineDepth);
return true;
}
if (!inlinee->CanInlineAgain())
{
INLINE_TESTTRACE(L"INLINING: Skip Inline: Do not inline recursive functions\tInlinee: %s (%s)\tCaller: %s (%s)\n",
inlinee->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer), inliner->GetDisplayName(),
inliner->GetDebugNumberSet(debugStringBuffer2));
return false;
}
return true;
}
// This function is called from Inlining decider, this only enables collection of the inlinee data, we are much more aggressive here.
// Actual decision of whether something is inlined or not is taken in CommitInlineIntoInliner
bool InliningHeuristics::DeciderInlineIntoInliner(Js::FunctionBody* inlinee, Js::FunctionBody *inliner, bool isConstructorCall, bool isPolymorphicCall, InliningDecider* inliningDecider, uint16 constantArgInfo, uint recursiveInlineDepth, bool allowRecursiveInlining)
{
if (!CanRecursivelyInline(inlinee, inliner, allowRecursiveInlining, recursiveInlineDepth))
{
return false;
}
if (PHASE_FORCE(Js::InlinePhase, this->topFunc) ||
PHASE_FORCE(Js::InlinePhase, inliner) ||
PHASE_FORCE(Js::InlinePhase, inlinee))
{
return true;
}
if (PHASE_OFF(Js::InlinePhase, this->topFunc) ||
PHASE_OFF(Js::InlinePhase, inliner) ||
PHASE_OFF(Js::InlinePhase, inlinee))
{
return false;
}
if (PHASE_FORCE(Js::InlineTreePhase, this->topFunc) ||
PHASE_FORCE(Js::InlineTreePhase, inliner))
{
return true;
}
if (PHASE_FORCE(Js::InlineAtEveryCallerPhase, inlinee))
{
return true;
}
if (inlinee->GetIsAsmjsMode() || inliner->GetIsAsmjsMode())
{
return false;
}
uint inlineeByteCodeCount = inlinee->GetByteCodeWithoutLDACount();
// Heuristics are hit in the following order (Note *order* is important)
// 1. Leaf function: If the inlinee is a leaf (but not a constructor or a polymorphic call) inline threshold is LeafInlineThreshold (60). Also it can have max 1 loop
// 2. Constant Function Argument: If the inlinee candidate has a constant argument and that argument is used for branching, then the inline threshold is ConstantArgumentInlineThreshold (157)
// 3. InlineThreshold: If an inlinee candidate exceeds InlineThreshold just don't inline no matter what.
// Following are additional constraint for an inlinee which meets InlineThreshold (Rule no 3)
// 4. Rule for inlinee with loops:
// 4a. Only single loop in inlinee is permitted.
// 4b. Should not have polymorphic field access.
// 4c. Should not be a constructor.
// 4d. Should meet LoopInlineThreshold (25)
// 5. Rule for polymorphic inlinee:
// 4a. Should meet PolymorphicInlineThreshold (32)
// 6. Rule for constructors:
// 5a. Always inline if inlinee has polymorphic field access (as we have cloned runtime data).
// 5b. If inlinee is monomorphic, inline only small constructors. They are governed by ConstructorInlineThreshold (21)
// 7. Rule for inlinee which is not interpreted enough (as we might not have all the profile data):
// 7a. As of now it is still governed by the InlineThreshold. Plan to play with this in future.
// 8. Rest should be inlined.
if (!isPolymorphicCall && !isConstructorCall && IsInlineeLeaf(inlinee) && (inlinee->GetLoopCount() <= 2))
{
// Inlinee is a leaf function
if (inliningDecider->getNumberOfInlineesWithLoop() <= (uint)threshold.maxNumberOfInlineesWithLoop) // Don't inlinee too many inlinees with loops.
{
// Negative LeafInlineThreshold disable the threshold
if (threshold.leafInlineThreshold >= 0 && inlineeByteCodeCount < (uint)threshold.leafInlineThreshold)
{
if (inlinee->GetLoopCount())
{
inliningDecider->incrementNumberOfInlineesWithLoop();
}
return true;
}
}
}
uint16 mask = constantArgInfo & inlinee->m_argUsedForBranch;
if (mask && inlineeByteCodeCount < (uint)CONFIG_FLAG(ConstantArgumentInlineThreshold))
{
return true;
}
#if ENABLE_DEBUG_CONFIG_OPTIONS
wchar_t debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
wchar_t debugStringBuffer2[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
wchar_t debugStringBuffer3[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
#endif
if (inlineeByteCodeCount > (uint)this->threshold.inlineThreshold) // Don't inline function too big to inline
{
INLINE_TESTTRACE(L"INLINING: Skip Inline: Bytecode greater than threshold\tInlinee: %s (%s)\tByteCodeCount: %d\tByteCodeInlinedThreshold: %d\tCaller: %s (%s) \tRoot: %s (%s)\n",
inlinee->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer), inlinee->GetByteCodeCount(), this->threshold.inlineThreshold,
inliner->GetDisplayName(), inliner->GetDebugNumberSet(debugStringBuffer2),
topFunc->GetDisplayName(), topFunc->GetDebugNumberSet(debugStringBuffer3));
return false;
}
if (inlinee->GetHasLoops())
{
// Small function with a single loop, go ahead and inline that.
if (threshold.loopInlineThreshold < 0 || // Negative LoopInlineThreshold disable inlining with loop
inlineeByteCodeCount >(uint)threshold.loopInlineThreshold ||
inliningDecider->getNumberOfInlineesWithLoop() > (uint)threshold.maxNumberOfInlineesWithLoop || // See if we are inlining too many inlinees with loops.
(inlinee->GetLoopCount() > 2) || // Allow at most 2 loops.
inlinee->GetHasNestedLoop() || // Nested loops are not a good inlinee candidate
isConstructorCall || // If the function is constructor or has polymorphic fields don't inline.
PHASE_OFF(Js::InlineFunctionsWithLoopsPhase,this->topFunc))
{
INLINE_TESTTRACE(L"INLINING: Skip Inline: Has loops \tBytecode size: %d \tgetNumberOfInlineesWithLoop: %d\tloopCount: %d\thasNestedLoop: %B\tisConstructorCall:%B\tInlinee: %s (%s)\tCaller: %s (%s) \tRoot: %s (%s)\n",
inlinee->GetByteCodeCount(),
inliningDecider->getNumberOfInlineesWithLoop(),
inlinee->GetLoopCount(),
inlinee->GetHasNestedLoop(),
isConstructorCall,
inlinee->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer),
inliner->GetDisplayName(), inliner->GetDebugNumberSet(debugStringBuffer2),
topFunc->GetDisplayName(), topFunc->GetDebugNumberSet(debugStringBuffer3));
// Don't inline function with loops
return false;
}
inliningDecider->incrementNumberOfInlineesWithLoop();
return true;
}
if (isPolymorphicCall)
{
if (threshold.polymorphicInlineThreshold < 0 || // Negative PolymorphicInlineThreshold disable inlining
inlineeByteCodeCount > (uint)threshold.polymorphicInlineThreshold || // This is second level check to ensure we don't inline huge polymorphic functions.
isConstructorCall)
{
INLINE_TESTTRACE(L"INLINING: Skip Inline: Polymorphic call under PolymorphicInlineThreshold: %d \tBytecode size: %d\tInlinee: %s (%s)\tCaller: %s (%s) \tRoot: %s (%s)\n",
threshold.polymorphicInlineThreshold,
inlinee->GetByteCodeCount(),
inlinee->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer),
inliner->GetDisplayName(), inliner->GetDebugNumberSet(debugStringBuffer2),
topFunc->GetDisplayName(), topFunc->GetDebugNumberSet(debugStringBuffer3));
return false;
}
return true;
}
if(isConstructorCall)
{
#pragma prefast(suppress: 6285, "logical-or of constants is by design")
if(PHASE_OFF(Js::InlineConstructorsPhase, this->topFunc) ||
PHASE_OFF(Js::InlineConstructorsPhase, inliner) ||
PHASE_OFF(Js::InlineConstructorsPhase, inlinee) ||
!CONFIG_FLAG(CloneInlinedPolymorphicCaches))
{
return false;
}
if(PHASE_FORCE(Js::InlineConstructorsPhase, this->topFunc) ||
PHASE_FORCE(Js::InlineConstructorsPhase, inliner) ||
PHASE_FORCE(Js::InlineConstructorsPhase, inlinee))
{
return true;
}
if (inlinee->HasDynamicProfileInfo() && inlinee->GetAnyDynamicProfileInfo()->HasPolymorphicFldAccess())
{
// As of now this is dependent on bytecodeInlinedThreshold.
return true;
}
// Negative ConstructorInlineThreshold always disable constructor inlining
if (threshold.constructorInlineThreshold < 0 || inlineeByteCodeCount >(uint)threshold.constructorInlineThreshold)
{
INLINE_TESTTRACE(L"INLINING: Skip Inline: Constructor with no polymorphic field access \tBytecode size: %d\tInlinee: %s (%s)\tCaller: %s (%s) \tRoot: %s (%s)\n",
inlinee->GetByteCodeCount(),
inlinee->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer),
inliner->GetDisplayName(), inliner->GetDebugNumberSet(debugStringBuffer2),
topFunc->GetDisplayName(), topFunc->GetDebugNumberSet(debugStringBuffer3));
// Don't inline constructor that does not have a polymorphic field access, or if cloning polymorphic inline
// caches is disabled
return false;
}
return true;
}
return true;
}
// Called from background thread to commit inlining.
bool InliningHeuristics::BackendInlineIntoInliner(Js::FunctionBody* inlinee,
Js::FunctionBody *inliner,
Func *topFunction,
Js::ProfileId callSiteId,
bool isConstructorCall,
bool isFixedMethodCall, // Reserved
bool isCallOutsideLoopInTopFunc, // There is a loop for sure and this call is outside loop
bool isCallInsideLoop,
uint recursiveInlineDepth,
uint16 constantArguments
)
{
// We have one piece of additional data in backend, whether we are outside loop or inside
// This function decides to inline or not based on that additional data. Most of the filtering is already done by DeciderInlineIntoInliner which is called
// during work item creation.
// This is additional filtering during actual inlining phase.
// Note *order* is important
// Following are
// 1. Constructor is always inlined (irrespective of inside or outside)
// 2. If the inlinee candidate has constant argument and that argument is used for a branch and the inlinee size is within ConstantArgumentInlineThreshold(157) we inline
// 3. Inside loops:
// 3a. Leaf function will always get inlined (irrespective of leaf has loop or not)
// 3b. If the inlinee has loops, don't inline it (Basically avoiding inlining a loop within another loop unless its leaf).
// 4. Outside loop (inliner has loops):
// 4a. Only inline small inlinees. Governed by OutsideLoopInlineThreshold (16)
// 5. Rest are inlined.
#if ENABLE_DEBUG_CONFIG_OPTIONS
wchar_t debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
wchar_t debugStringBuffer2[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
wchar_t debugStringBuffer3[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
#endif
bool doBackEndAggressiveInline = (constantArguments & inlinee->m_argUsedForBranch) != 0;
if (!PHASE_OFF(Js::InlineRecursivePhase, inliner)
&& inlinee == inliner
&& (!inlinee->CanInlineRecursively(recursiveInlineDepth, doBackEndAggressiveInline)))
{
INLINE_TESTTRACE(L"INLINING: Skip Inline (backend): Recursive inlining\tInlinee: %s (#%s)\tCaller: %s (#%s) \tRoot: %s (#%s) Depth: %d\n",
inlinee->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer),
inliner->GetDisplayName(), inliner->GetDebugNumberSet(debugStringBuffer2),
topFunc->GetDisplayName(), topFunc->GetDebugNumberSet(debugStringBuffer3),
recursiveInlineDepth);
return false;
}
if(PHASE_FORCE(Js::InlinePhase, this->topFunc) ||
PHASE_FORCE(Js::InlinePhase, inliner) ||
PHASE_FORCE(Js::InlinePhase, inlinee))
{
return true;
}
if (PHASE_FORCE(Js::InlineTreePhase, this->topFunc) ||
PHASE_FORCE(Js::InlineTreePhase, inliner))
{
return true;
}
if (PHASE_FORCE(Js::InlineAtEveryCallerPhase, inlinee))
{
return true;
}
Js::DynamicProfileInfo *dynamicProfile = inliner->GetAnyDynamicProfileInfo();
bool doConstantArgumentInlining = (dynamicProfile->GetConstantArgInfo(callSiteId) & inlinee->m_argUsedForBranch) != 0;
if (doConstantArgumentInlining && inlinee->GetByteCodeWithoutLDACount() < (uint)threshold.constantArgumentInlineThreshold)
{
return true;
}
if (topFunction->m_workItem->RecyclableData()->JitTimeData()->GetIsAggressiveInliningEnabled())
{
return true;
}
if (isConstructorCall)
{
return true;
}
if (isCallInsideLoop && IsInlineeLeaf(inlinee))
{
return true;
}
if (isCallInsideLoop && inlinee->GetHasLoops() ) // Don't inline function with loops inside another loop unless it is a leaf
{
INLINE_TESTTRACE(L"INLINING: Skip Inline (backend): Recursive loop inlining\tInlinee: %s (#%s)\tCaller: %s (#%s) \tRoot: %s (#%s)\n",
inlinee->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer),
inliner->GetDisplayName(), inliner->GetDebugNumberSet(debugStringBuffer2),
topFunc->GetDisplayName(), topFunc->GetDebugNumberSet(debugStringBuffer3));
return false;
}
byte scale = 1;
if (doBackEndAggressiveInline)
{
scale = 2;
}
if (isCallOutsideLoopInTopFunc &&
(threshold.outsideLoopInlineThreshold < 0 ||
inlinee->GetByteCodeWithoutLDACount() > (uint)threshold.outsideLoopInlineThreshold * scale))
{
Assert(!isCallInsideLoop);
INLINE_TESTTRACE(L"INLINING: Skip Inline (backend): Inlining outside loop doesn't meet OutsideLoopInlineThreshold: %d \tBytecode size: %d\tInlinee: %s (#%s)\tCaller: %s (#%s) \tRoot: %s (#%s)\n",
threshold.outsideLoopInlineThreshold,
inlinee->GetByteCodeCount(),
inlinee->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer),
inliner->GetDisplayName(), inliner->GetDebugNumberSet(debugStringBuffer2),
topFunc->GetDisplayName(), topFunc->GetDebugNumberSet(debugStringBuffer3));
return false;
}
return true;
}
bool InliningHeuristics::ContinueInliningUserDefinedFunctions(uint32 bytecodeInlinedCount) const
{
#if ENABLE_DEBUG_CONFIG_OPTIONS
wchar_t debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
#endif
if (PHASE_FORCE(Js::InlinePhase, this->topFunc) || bytecodeInlinedCount <= (uint)threshold.inlineCountMax)
{
return true;
}
INLINE_TESTTRACE(L"INLINING: Skip Inline: InlineCountMax threshold %d, reached: %s (#%s)\n",
(uint)threshold.inlineCountMax,
this->topFunc->GetDisplayName(), this->topFunc->GetDebugNumberSet(debugStringBuffer));
return false;
}