| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421 |
- //-------------------------------------------------------------------------------------------------------
- // 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;
- }
|