InliningHeuristics.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. //-------------------------------------------------------------------------------------------------------
  2. // Copyright (C) Microsoft. All rights reserved.
  3. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  4. //-------------------------------------------------------------------------------------------------------
  5. #include "BackEnd.h"
  6. InliningThreshold::InliningThreshold(Js::FunctionBody *topFunc, bool aggressive) : topFunc(topFunc)
  7. {
  8. if (aggressive)
  9. {
  10. SetAggressiveHeuristics();
  11. }
  12. else
  13. {
  14. SetHeuristics();
  15. }
  16. }
  17. void InliningThreshold::SetAggressiveHeuristics()
  18. {
  19. int limit = CONFIG_FLAG(AggressiveInlineThreshold);
  20. inlineThreshold = limit;
  21. constructorInlineThreshold = limit;
  22. outsideLoopInlineThreshold = limit;
  23. leafInlineThreshold = limit;
  24. loopInlineThreshold = limit;
  25. polymorphicInlineThreshold = limit;
  26. maxNumberOfInlineesWithLoop = CONFIG_FLAG(MaxNumberOfInlineesWithLoop);
  27. inlineCountMax = CONFIG_FLAG(AggressiveInlineCountMax);
  28. }
  29. void InliningThreshold::Reset()
  30. {
  31. SetHeuristics();
  32. }
  33. void InliningThreshold::SetHeuristics()
  34. {
  35. inlineThreshold = CONFIG_FLAG(InlineThreshold);
  36. // Inline less aggressively in large functions since the register pressure is likely high.
  37. // Small functions shouldn't be a problem.
  38. if (topFunc->GetByteCodeWithoutLDACount() > 800)
  39. {
  40. inlineThreshold -= CONFIG_FLAG(InlineThresholdAdjustCountInLargeFunction);
  41. }
  42. else if (topFunc->GetByteCodeWithoutLDACount() > 200)
  43. {
  44. inlineThreshold -= CONFIG_FLAG(InlineThresholdAdjustCountInMediumSizedFunction);
  45. }
  46. else if (topFunc->GetByteCodeWithoutLDACount() < 50)
  47. {
  48. inlineThreshold += CONFIG_FLAG(InlineThresholdAdjustCountInSmallFunction);
  49. }
  50. constructorInlineThreshold = CONFIG_FLAG(ConstructorInlineThreshold);
  51. outsideLoopInlineThreshold = CONFIG_FLAG(OutsideLoopInlineThreshold);
  52. leafInlineThreshold = CONFIG_FLAG(LeafInlineThreshold);
  53. loopInlineThreshold = CONFIG_FLAG(LoopInlineThreshold);
  54. polymorphicInlineThreshold = CONFIG_FLAG(PolymorphicInlineThreshold);
  55. maxNumberOfInlineesWithLoop = CONFIG_FLAG(MaxNumberOfInlineesWithLoop);
  56. constantArgumentInlineThreshold = CONFIG_FLAG(ConstantArgumentInlineThreshold);
  57. inlineCountMax = CONFIG_FLAG(InlineCountMax);
  58. }
  59. bool InliningHeuristics::CanRecursivelyInline(Js::FunctionBody* inlinee, Js::FunctionBody *inliner, bool allowRecursiveInlining, uint recursiveInlineDepth)
  60. {
  61. #if defined(DBG_DUMP) || defined(ENABLE_DEBUG_CONFIG_OPTIONS)
  62. wchar_t debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
  63. wchar_t debugStringBuffer2[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
  64. #endif
  65. if (!PHASE_OFF(Js::InlineRecursivePhase, inliner)
  66. && allowRecursiveInlining
  67. && inlinee == inliner
  68. && inlinee->CanInlineRecursively(recursiveInlineDepth) )
  69. {
  70. INLINE_TESTTRACE(L"INLINING: Inlined recursively\tInlinee: %s (%s)\tCaller: %s (%s)\tDepth: %d\n",
  71. inlinee->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer), inliner->GetDisplayName(),
  72. inliner->GetDebugNumberSet(debugStringBuffer2), recursiveInlineDepth);
  73. return true;
  74. }
  75. if (!inlinee->CanInlineAgain())
  76. {
  77. INLINE_TESTTRACE(L"INLINING: Skip Inline: Do not inline recursive functions\tInlinee: %s (%s)\tCaller: %s (%s)\n",
  78. inlinee->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer), inliner->GetDisplayName(),
  79. inliner->GetDebugNumberSet(debugStringBuffer2));
  80. return false;
  81. }
  82. return true;
  83. }
  84. // This function is called from Inlining decider, this only enables collection of the inlinee data, we are much more aggressive here.
  85. // Actual decision of whether something is inlined or not is taken in CommitInlineIntoInliner
  86. bool InliningHeuristics::DeciderInlineIntoInliner(Js::FunctionBody* inlinee, Js::FunctionBody *inliner, bool isConstructorCall, bool isPolymorphicCall, InliningDecider* inliningDecider, uint16 constantArgInfo, uint recursiveInlineDepth, bool allowRecursiveInlining)
  87. {
  88. if (!CanRecursivelyInline(inlinee, inliner, allowRecursiveInlining, recursiveInlineDepth))
  89. {
  90. return false;
  91. }
  92. if (PHASE_FORCE(Js::InlinePhase, this->topFunc) ||
  93. PHASE_FORCE(Js::InlinePhase, inliner) ||
  94. PHASE_FORCE(Js::InlinePhase, inlinee))
  95. {
  96. return true;
  97. }
  98. if (PHASE_OFF(Js::InlinePhase, this->topFunc) ||
  99. PHASE_OFF(Js::InlinePhase, inliner) ||
  100. PHASE_OFF(Js::InlinePhase, inlinee))
  101. {
  102. return false;
  103. }
  104. if (PHASE_FORCE(Js::InlineTreePhase, this->topFunc) ||
  105. PHASE_FORCE(Js::InlineTreePhase, inliner))
  106. {
  107. return true;
  108. }
  109. if (PHASE_FORCE(Js::InlineAtEveryCallerPhase, inlinee))
  110. {
  111. return true;
  112. }
  113. if (inlinee->GetIsAsmjsMode() || inliner->GetIsAsmjsMode())
  114. {
  115. return false;
  116. }
  117. uint inlineeByteCodeCount = inlinee->GetByteCodeWithoutLDACount();
  118. // Heuristics are hit in the following order (Note *order* is important)
  119. // 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
  120. // 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)
  121. // 3. InlineThreshold: If an inlinee candidate exceeds InlineThreshold just don't inline no matter what.
  122. // Following are additional constraint for an inlinee which meets InlineThreshold (Rule no 3)
  123. // 4. Rule for inlinee with loops:
  124. // 4a. Only single loop in inlinee is permitted.
  125. // 4b. Should not have polymorphic field access.
  126. // 4c. Should not be a constructor.
  127. // 4d. Should meet LoopInlineThreshold (25)
  128. // 5. Rule for polymorphic inlinee:
  129. // 4a. Should meet PolymorphicInlineThreshold (32)
  130. // 6. Rule for constructors:
  131. // 5a. Always inline if inlinee has polymorphic field access (as we have cloned runtime data).
  132. // 5b. If inlinee is monomorphic, inline only small constructors. They are governed by ConstructorInlineThreshold (21)
  133. // 7. Rule for inlinee which is not interpreted enough (as we might not have all the profile data):
  134. // 7a. As of now it is still governed by the InlineThreshold. Plan to play with this in future.
  135. // 8. Rest should be inlined.
  136. if (!isPolymorphicCall && !isConstructorCall && IsInlineeLeaf(inlinee) && (inlinee->GetLoopCount() <= 2))
  137. {
  138. // Inlinee is a leaf function
  139. if (inliningDecider->getNumberOfInlineesWithLoop() <= (uint)threshold.maxNumberOfInlineesWithLoop) // Don't inlinee too many inlinees with loops.
  140. {
  141. // Negative LeafInlineThreshold disable the threshold
  142. if (threshold.leafInlineThreshold >= 0 && inlineeByteCodeCount < (uint)threshold.leafInlineThreshold)
  143. {
  144. if (inlinee->GetLoopCount())
  145. {
  146. inliningDecider->incrementNumberOfInlineesWithLoop();
  147. }
  148. return true;
  149. }
  150. }
  151. }
  152. uint16 mask = constantArgInfo & inlinee->m_argUsedForBranch;
  153. if (mask && inlineeByteCodeCount < (uint)CONFIG_FLAG(ConstantArgumentInlineThreshold))
  154. {
  155. return true;
  156. }
  157. #if ENABLE_DEBUG_CONFIG_OPTIONS
  158. wchar_t debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
  159. wchar_t debugStringBuffer2[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
  160. wchar_t debugStringBuffer3[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
  161. #endif
  162. if (inlineeByteCodeCount > (uint)this->threshold.inlineThreshold) // Don't inline function too big to inline
  163. {
  164. INLINE_TESTTRACE(L"INLINING: Skip Inline: Bytecode greater than threshold\tInlinee: %s (%s)\tByteCodeCount: %d\tByteCodeInlinedThreshold: %d\tCaller: %s (%s) \tRoot: %s (%s)\n",
  165. inlinee->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer), inlinee->GetByteCodeCount(), this->threshold.inlineThreshold,
  166. inliner->GetDisplayName(), inliner->GetDebugNumberSet(debugStringBuffer2),
  167. topFunc->GetDisplayName(), topFunc->GetDebugNumberSet(debugStringBuffer3));
  168. return false;
  169. }
  170. if (inlinee->GetHasLoops())
  171. {
  172. // Small function with a single loop, go ahead and inline that.
  173. if (threshold.loopInlineThreshold < 0 || // Negative LoopInlineThreshold disable inlining with loop
  174. inlineeByteCodeCount >(uint)threshold.loopInlineThreshold ||
  175. inliningDecider->getNumberOfInlineesWithLoop() > (uint)threshold.maxNumberOfInlineesWithLoop || // See if we are inlining too many inlinees with loops.
  176. (inlinee->GetLoopCount() > 2) || // Allow at most 2 loops.
  177. inlinee->GetHasNestedLoop() || // Nested loops are not a good inlinee candidate
  178. isConstructorCall || // If the function is constructor or has polymorphic fields don't inline.
  179. PHASE_OFF(Js::InlineFunctionsWithLoopsPhase,this->topFunc))
  180. {
  181. 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",
  182. inlinee->GetByteCodeCount(),
  183. inliningDecider->getNumberOfInlineesWithLoop(),
  184. inlinee->GetLoopCount(),
  185. inlinee->GetHasNestedLoop(),
  186. isConstructorCall,
  187. inlinee->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer),
  188. inliner->GetDisplayName(), inliner->GetDebugNumberSet(debugStringBuffer2),
  189. topFunc->GetDisplayName(), topFunc->GetDebugNumberSet(debugStringBuffer3));
  190. // Don't inline function with loops
  191. return false;
  192. }
  193. inliningDecider->incrementNumberOfInlineesWithLoop();
  194. return true;
  195. }
  196. if (isPolymorphicCall)
  197. {
  198. if (threshold.polymorphicInlineThreshold < 0 || // Negative PolymorphicInlineThreshold disable inlining
  199. inlineeByteCodeCount > (uint)threshold.polymorphicInlineThreshold || // This is second level check to ensure we don't inline huge polymorphic functions.
  200. isConstructorCall)
  201. {
  202. INLINE_TESTTRACE(L"INLINING: Skip Inline: Polymorphic call under PolymorphicInlineThreshold: %d \tBytecode size: %d\tInlinee: %s (%s)\tCaller: %s (%s) \tRoot: %s (%s)\n",
  203. threshold.polymorphicInlineThreshold,
  204. inlinee->GetByteCodeCount(),
  205. inlinee->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer),
  206. inliner->GetDisplayName(), inliner->GetDebugNumberSet(debugStringBuffer2),
  207. topFunc->GetDisplayName(), topFunc->GetDebugNumberSet(debugStringBuffer3));
  208. return false;
  209. }
  210. return true;
  211. }
  212. if(isConstructorCall)
  213. {
  214. #pragma prefast(suppress: 6285, "logical-or of constants is by design")
  215. if(PHASE_OFF(Js::InlineConstructorsPhase, this->topFunc) ||
  216. PHASE_OFF(Js::InlineConstructorsPhase, inliner) ||
  217. PHASE_OFF(Js::InlineConstructorsPhase, inlinee) ||
  218. !CONFIG_FLAG(CloneInlinedPolymorphicCaches))
  219. {
  220. return false;
  221. }
  222. if(PHASE_FORCE(Js::InlineConstructorsPhase, this->topFunc) ||
  223. PHASE_FORCE(Js::InlineConstructorsPhase, inliner) ||
  224. PHASE_FORCE(Js::InlineConstructorsPhase, inlinee))
  225. {
  226. return true;
  227. }
  228. if (inlinee->HasDynamicProfileInfo() && inlinee->GetAnyDynamicProfileInfo()->HasPolymorphicFldAccess())
  229. {
  230. // As of now this is dependent on bytecodeInlinedThreshold.
  231. return true;
  232. }
  233. // Negative ConstructorInlineThreshold always disable constructor inlining
  234. if (threshold.constructorInlineThreshold < 0 || inlineeByteCodeCount >(uint)threshold.constructorInlineThreshold)
  235. {
  236. 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",
  237. inlinee->GetByteCodeCount(),
  238. inlinee->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer),
  239. inliner->GetDisplayName(), inliner->GetDebugNumberSet(debugStringBuffer2),
  240. topFunc->GetDisplayName(), topFunc->GetDebugNumberSet(debugStringBuffer3));
  241. // Don't inline constructor that does not have a polymorphic field access, or if cloning polymorphic inline
  242. // caches is disabled
  243. return false;
  244. }
  245. return true;
  246. }
  247. return true;
  248. }
  249. // Called from background thread to commit inlining.
  250. bool InliningHeuristics::BackendInlineIntoInliner(Js::FunctionBody* inlinee,
  251. Js::FunctionBody *inliner,
  252. Func *topFunction,
  253. Js::ProfileId callSiteId,
  254. bool isConstructorCall,
  255. bool isFixedMethodCall, // Reserved
  256. bool isCallOutsideLoopInTopFunc, // There is a loop for sure and this call is outside loop
  257. bool isCallInsideLoop,
  258. uint recursiveInlineDepth,
  259. uint16 constantArguments
  260. )
  261. {
  262. // We have one piece of additional data in backend, whether we are outside loop or inside
  263. // This function decides to inline or not based on that additional data. Most of the filtering is already done by DeciderInlineIntoInliner which is called
  264. // during work item creation.
  265. // This is additional filtering during actual inlining phase.
  266. // Note *order* is important
  267. // Following are
  268. // 1. Constructor is always inlined (irrespective of inside or outside)
  269. // 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
  270. // 3. Inside loops:
  271. // 3a. Leaf function will always get inlined (irrespective of leaf has loop or not)
  272. // 3b. If the inlinee has loops, don't inline it (Basically avoiding inlining a loop within another loop unless its leaf).
  273. // 4. Outside loop (inliner has loops):
  274. // 4a. Only inline small inlinees. Governed by OutsideLoopInlineThreshold (16)
  275. // 5. Rest are inlined.
  276. #if ENABLE_DEBUG_CONFIG_OPTIONS
  277. wchar_t debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
  278. wchar_t debugStringBuffer2[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
  279. wchar_t debugStringBuffer3[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
  280. #endif
  281. bool doBackEndAggressiveInline = (constantArguments & inlinee->m_argUsedForBranch) != 0;
  282. if (!PHASE_OFF(Js::InlineRecursivePhase, inliner)
  283. && inlinee == inliner
  284. && (!inlinee->CanInlineRecursively(recursiveInlineDepth, doBackEndAggressiveInline)))
  285. {
  286. INLINE_TESTTRACE(L"INLINING: Skip Inline (backend): Recursive inlining\tInlinee: %s (#%s)\tCaller: %s (#%s) \tRoot: %s (#%s) Depth: %d\n",
  287. inlinee->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer),
  288. inliner->GetDisplayName(), inliner->GetDebugNumberSet(debugStringBuffer2),
  289. topFunc->GetDisplayName(), topFunc->GetDebugNumberSet(debugStringBuffer3),
  290. recursiveInlineDepth);
  291. return false;
  292. }
  293. if(PHASE_FORCE(Js::InlinePhase, this->topFunc) ||
  294. PHASE_FORCE(Js::InlinePhase, inliner) ||
  295. PHASE_FORCE(Js::InlinePhase, inlinee))
  296. {
  297. return true;
  298. }
  299. if (PHASE_FORCE(Js::InlineTreePhase, this->topFunc) ||
  300. PHASE_FORCE(Js::InlineTreePhase, inliner))
  301. {
  302. return true;
  303. }
  304. if (PHASE_FORCE(Js::InlineAtEveryCallerPhase, inlinee))
  305. {
  306. return true;
  307. }
  308. Js::DynamicProfileInfo *dynamicProfile = inliner->GetAnyDynamicProfileInfo();
  309. bool doConstantArgumentInlining = (dynamicProfile->GetConstantArgInfo(callSiteId) & inlinee->m_argUsedForBranch) != 0;
  310. if (doConstantArgumentInlining && inlinee->GetByteCodeWithoutLDACount() < (uint)threshold.constantArgumentInlineThreshold)
  311. {
  312. return true;
  313. }
  314. if (topFunction->m_workItem->RecyclableData()->JitTimeData()->GetIsAggressiveInliningEnabled())
  315. {
  316. return true;
  317. }
  318. if (isConstructorCall)
  319. {
  320. return true;
  321. }
  322. if (isCallInsideLoop && IsInlineeLeaf(inlinee))
  323. {
  324. return true;
  325. }
  326. if (isCallInsideLoop && inlinee->GetHasLoops() ) // Don't inline function with loops inside another loop unless it is a leaf
  327. {
  328. INLINE_TESTTRACE(L"INLINING: Skip Inline (backend): Recursive loop inlining\tInlinee: %s (#%s)\tCaller: %s (#%s) \tRoot: %s (#%s)\n",
  329. inlinee->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer),
  330. inliner->GetDisplayName(), inliner->GetDebugNumberSet(debugStringBuffer2),
  331. topFunc->GetDisplayName(), topFunc->GetDebugNumberSet(debugStringBuffer3));
  332. return false;
  333. }
  334. byte scale = 1;
  335. if (doBackEndAggressiveInline)
  336. {
  337. scale = 2;
  338. }
  339. if (isCallOutsideLoopInTopFunc &&
  340. (threshold.outsideLoopInlineThreshold < 0 ||
  341. inlinee->GetByteCodeWithoutLDACount() > (uint)threshold.outsideLoopInlineThreshold * scale))
  342. {
  343. Assert(!isCallInsideLoop);
  344. 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",
  345. threshold.outsideLoopInlineThreshold,
  346. inlinee->GetByteCodeCount(),
  347. inlinee->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer),
  348. inliner->GetDisplayName(), inliner->GetDebugNumberSet(debugStringBuffer2),
  349. topFunc->GetDisplayName(), topFunc->GetDebugNumberSet(debugStringBuffer3));
  350. return false;
  351. }
  352. return true;
  353. }
  354. bool InliningHeuristics::ContinueInliningUserDefinedFunctions(uint32 bytecodeInlinedCount) const
  355. {
  356. #if ENABLE_DEBUG_CONFIG_OPTIONS
  357. wchar_t debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
  358. #endif
  359. if (PHASE_FORCE(Js::InlinePhase, this->topFunc) || bytecodeInlinedCount <= (uint)threshold.inlineCountMax)
  360. {
  361. return true;
  362. }
  363. INLINE_TESTTRACE(L"INLINING: Skip Inline: InlineCountMax threshold %d, reached: %s (#%s)\n",
  364. (uint)threshold.inlineCountMax,
  365. this->topFunc->GetDisplayName(), this->topFunc->GetDebugNumberSet(debugStringBuffer));
  366. return false;
  367. }