Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign up
Fetching contributors…
| //------------------------------------------------------------------------------------------------------- | |
| // 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; | |
| } |