diff --git a/lib/Backend/BackwardPass.cpp b/lib/Backend/BackwardPass.cpp index 3238f119e04..9431c43fd33 100644 --- a/lib/Backend/BackwardPass.cpp +++ b/lib/Backend/BackwardPass.cpp @@ -6497,7 +6497,7 @@ BackwardPass::ProcessInlineeStart(IR::Instr* inlineeStart) inlineeStart->IterateMetaArgs([&](IR::Instr* metaArg) { if (i == Js::Constants::InlineeMetaArgIndex_ArgumentsObject && - inlineeStart->m_func->GetHasArgumentObject()) + inlineeStart->m_func->GetJnFunction()->GetUsesArgumentsObject()) { Assert(!inlineeStart->m_func->GetHasUnoptimizedArgumentsAcccess()); // Do not remove arguments object meta arg if there is a reference to arguments object diff --git a/lib/Backend/Encoder.cpp b/lib/Backend/Encoder.cpp index aca6cefb5ed..030d5148175 100644 --- a/lib/Backend/Encoder.cpp +++ b/lib/Backend/Encoder.cpp @@ -595,7 +595,7 @@ void Encoder::RecordInlineeFrame(Func* inlinee, uint32 currentOffset) { // The only restriction for not supporting loop bodies is that inlinee frame map is created on FunctionEntryPointInfo & not // the base class EntryPointInfo. - if (!this->m_func->IsLoopBody() && !this->m_func->IsSimpleJit()) + if ((!this->m_func->IsLoopBody() || !PHASE_OFF(Js::InlineInJitLoopBodyPhase, this->m_func)) && !this->m_func->IsSimpleJit()) { InlineeFrameRecord* record = nullptr; if (inlinee->frameInfo && inlinee->m_hasInlineArgsOpt) diff --git a/lib/Backend/Func.cpp b/lib/Backend/Func.cpp index d0a9a3b293d..345e9bf5572 100644 --- a/lib/Backend/Func.cpp +++ b/lib/Backend/Func.cpp @@ -76,7 +76,6 @@ Func::Func(JitArenaAllocator *alloc, CodeGenWorkItem* workItem, const Js::Functi hasInlinee(false), thisOrParentInlinerHasArguments(false), hasStackArgs(false), - hasArgumentObject(false), hasUnoptimizedArgumentsAcccess(false), hasApplyTargetInlining(false), hasImplicitCalls(false), @@ -148,7 +147,8 @@ Func::Func(JitArenaAllocator *alloc, CodeGenWorkItem* workItem, const Js::Functi // as determined by the bytecode generator. SetHasStackArgs(true); } - if (doStackNestedFunc && m_jnFunction->GetNestedCount() != 0) + if (doStackNestedFunc && m_jnFunction->GetNestedCount() != 0 && + this->GetTopFunc()->m_workItem->Type() != JsLoopBodyWorkItemType) { Assert(!(this->IsJitInDebugMode() && !m_jnFunction->GetUtf8SourceInfo()->GetIsLibraryCode())); stackNestedFunc = true; @@ -376,7 +376,7 @@ Func::Codegen() BEGIN_CODEGEN_PHASE(this, Js::InlinePhase); - InliningHeuristics heuristics(this->GetJnFunction()); + InliningHeuristics heuristics(this->GetJnFunction(), this->IsLoopBody()); Inline inliner(this, heuristics); inliner.Optimize(); diff --git a/lib/Backend/Func.h b/lib/Backend/Func.h index 7808887448b..2f17be4aacd 100644 --- a/lib/Backend/Func.h +++ b/lib/Backend/Func.h @@ -516,7 +516,6 @@ static const unsigned __int64 c_debugFillPattern8 = 0xcececececececece; bool hasBailout: 1; bool hasBailoutInEHRegion : 1; bool hasStackArgs: 1; - bool hasArgumentObject : 1; bool hasUnoptimizedArgumentsAcccess : 1; // True if there are any arguments access beyond the simple case of this.apply pattern bool m_canDoInlineArgsOpt : 1; bool hasApplyTargetInlining:1; @@ -580,9 +579,6 @@ static const unsigned __int64 c_debugFillPattern8 = 0xcececececececece; bool GetHasStackArgs() const { return this->hasStackArgs;} void SetHasStackArgs(bool has) { this->hasStackArgs = has;} - bool GetHasArgumentObject() const { return this->hasArgumentObject;} - void SetHasArgumentObject() { this->hasArgumentObject = true;} - bool GetHasUnoptimizedArgumentsAcccess() const { return this->hasUnoptimizedArgumentsAcccess; } void SetHasUnoptimizedArgumentsAccess(bool args) { diff --git a/lib/Backend/IRBuilder.cpp b/lib/Backend/IRBuilder.cpp index cc4cb5f0d3d..c90931ad70c 100644 --- a/lib/Backend/IRBuilder.cpp +++ b/lib/Backend/IRBuilder.cpp @@ -1483,7 +1483,6 @@ IRBuilder::BuildReg1(Js::OpCode newOpcode, uint32 offset, Js::RegSlot R0) { case Js::OpCode::LdHeapArgsCached: case Js::OpCode::LdLetHeapArgsCached: - this->m_func->SetHasArgumentObject(); if (!m_func->GetJnFunction()->HasScopeObject()) { Js::Throw::FatalInternalError(); @@ -1724,7 +1723,6 @@ IRBuilder::BuildReg2(Js::OpCode newOpcode, uint32 offset, Js::RegSlot R0, Js::Re dstSym->m_isSafeThis = true; dstSym->m_isNotInt = true; } - this->m_func->SetHasArgumentObject(); return; } case Js::OpCode::SetHomeObj: @@ -6094,7 +6092,8 @@ IRBuilder::BuildProfiledCallI(Js::OpCode opcode, uint32 offset, Js::RegSlot retu if(this->m_func->m_jitTimeData) { const Js::FunctionCodeGenJitTimeData *inlinerData = this->m_func->m_jitTimeData; - if(!this->IsLoopBody() && inlinerData->inlineesBv && (!inlinerData->inlineesBv->Test(profileId) + if((!this->IsLoopBody() || !PHASE_OFF(Js::InlineInJitLoopBodyPhase, this->m_func)) && + inlinerData && inlinerData->inlineesBv && (!inlinerData->inlineesBv->Test(profileId) #if DBG || (PHASE_STRESS(Js::BailOnNoProfilePhase, this->m_func->GetTopFunc()) && (CONFIG_FLAG(SkipFuncCountForBailOnNoProfile) < 0 || diff --git a/lib/Backend/Inline.cpp b/lib/Backend/Inline.cpp index 4ce4993af4c..95b97a5dca9 100644 --- a/lib/Backend/Inline.cpp +++ b/lib/Backend/Inline.cpp @@ -1907,7 +1907,7 @@ Inline::InlineBuiltInFunction(IR::Instr *callInstr, Js::FunctionInfo *funcInfo, #if defined(ENABLE_DEBUG_CONFIG_OPTIONS) InliningDecider::TraceInlining(inlinerData->GetFunctionBody(), Js::JavascriptLibrary::GetNameForBuiltIn(builtInId), - nullptr, 0, this->topFunc->m_workItem->GetFunctionBody(), 0, nullptr, profileId, builtInId); + nullptr, 0, this->topFunc->m_workItem->GetFunctionBody(), 0, nullptr, profileId, callInstr->m_func->GetTopFunc()->IsLoopBody(), builtInId); #endif // From now on we are committed to inlining. @@ -2255,7 +2255,7 @@ IR::Instr* Inline::InlineApply(IR::Instr *callInstr, Js::FunctionInfo *funcInfo, #if defined(ENABLE_DEBUG_CONFIG_OPTIONS) InliningDecider::TraceInlining(inlinerData->GetFunctionBody(), Js::JavascriptLibrary::GetNameForBuiltIn(builtInId), - nullptr, 0, this->topFunc->m_workItem->GetFunctionBody(), 0, nullptr, callSiteId, builtInId); + nullptr, 0, this->topFunc->m_workItem->GetFunctionBody(), 0, nullptr, callSiteId, callInstr->m_func->GetTopFunc()->IsLoopBody(), builtInId); char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE]; #endif @@ -2718,7 +2718,7 @@ Inline::InlineCall(IR::Instr *callInstr, Js::FunctionInfo *funcInfo, const Js::F #if defined(ENABLE_DEBUG_CONFIG_OPTIONS) InliningDecider::TraceInlining(inlinerData->GetFunctionBody(), Js::JavascriptLibrary::GetNameForBuiltIn(builtInId), - nullptr, 0, this->topFunc->m_workItem->GetFunctionBody(), 0, nullptr, callSiteId, builtInId); + nullptr, 0, this->topFunc->m_workItem->GetFunctionBody(), 0, nullptr, callSiteId, callInstr->m_func->GetTopFunc()->IsLoopBody(), builtInId); #endif uint actualCount = 0; @@ -3618,6 +3618,11 @@ Inline::InlineScriptFunction(IR::Instr *callInstr, const Js::FunctionCodeGenJitT Js::FunctionBody *funcCaller = callInstr->m_func->GetJnFunction(); Js::FunctionBody *funcBody = inlineeData->GetFunctionBody(); + if (callInstr->m_func->IsLoopBody() && funcBody->GetUsesArgumentsObject()) + { + return instrNext; + } + if (callInstr->GetSrc2() && callInstr->GetSrc2()->IsSymOpnd() && callInstr->GetSrc2()->AsSymOpnd()->m_sym->AsStackSym()->GetArgSlotNum() > Js::InlineeCallInfo::MaxInlineeArgoutCount) @@ -5054,7 +5059,7 @@ Inline::HasArgumentsAccess(IR::Instr * instr, SymID argumentsSymId) bool Inline::GetInlineeHasArgumentObject(Func * inlinee) { - if (!inlinee->GetHasArgumentObject()) + if (!inlinee->GetJnFunction()->GetUsesArgumentsObject()) { // If inlinee has no arguments access return false return false; diff --git a/lib/Backend/InliningDecider.cpp b/lib/Backend/InliningDecider.cpp index 78ebf8825c9..00da8512e93 100644 --- a/lib/Backend/InliningDecider.cpp +++ b/lib/Backend/InliningDecider.cpp @@ -5,7 +5,7 @@ #include "Backend.h" InliningDecider::InliningDecider(Js::FunctionBody *const topFunc, bool isLoopBody, bool isInDebugMode, const ExecutionMode jitMode) - : topFunc(topFunc), isLoopBody(isLoopBody), isInDebugMode(isInDebugMode), jitMode(jitMode), bytecodeInlinedCount(0), numberOfInlineesWithLoop (0), inliningHeuristics(topFunc) + : topFunc(topFunc), isLoopBody(isLoopBody), isInDebugMode(isInDebugMode), jitMode(jitMode), bytecodeInlinedCount(0), numberOfInlineesWithLoop (0), inliningHeuristics(topFunc, isLoopBody) { Assert(topFunc); } @@ -190,7 +190,7 @@ Js::FunctionInfo *InliningDecider::Inline(Js::FunctionBody *const inliner, Js::F Js::FunctionProxy * proxy = functionInfo->GetFunctionProxy(); if (proxy && proxy->IsFunctionBody()) { - if (isLoopBody && !PHASE_ON(Js::InlineInJitLoopBodyPhase, this->topFunc)) + if (isLoopBody && PHASE_OFF(Js::InlineInJitLoopBodyPhase, this->topFunc)) { INLINE_TESTTRACE_VERBOSE(_u("INLINING: Skip Inline: Jit loop body: %s (%s)\n"), this->topFunc->GetDisplayName(), this->topFunc->GetDebugNumberSet(debugStringBuffer)); @@ -268,7 +268,7 @@ Js::FunctionInfo *InliningDecider::Inline(Js::FunctionBody *const inliner, Js::F } #if defined(ENABLE_DEBUG_CONFIG_OPTIONS) - TraceInlining(inliner, inlinee->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer), inlinee->GetByteCodeCount(), this->topFunc, this->bytecodeInlinedCount, inlinee, callSiteId); + TraceInlining(inliner, inlinee->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer), inlinee->GetByteCodeCount(), this->topFunc, this->bytecodeInlinedCount, inlinee, callSiteId, this->isLoopBody); #endif this->bytecodeInlinedCount += inlinee->GetByteCodeCount(); @@ -608,32 +608,32 @@ bool InliningDecider::GetBuiltInInfo( #if defined(ENABLE_DEBUG_CONFIG_OPTIONS) // static void InliningDecider::TraceInlining(Js::FunctionBody *const inliner, const char16* inlineeName, const char16* inlineeFunctionIdandNumberString, uint inlineeByteCodeCount, - Js::FunctionBody* topFunc, uint inlinedByteCodeCount, Js::FunctionBody *const inlinee, uint callSiteId, uint builtIn) + Js::FunctionBody* topFunc, uint inlinedByteCodeCount, Js::FunctionBody *const inlinee, uint callSiteId, bool inLoopBody, uint builtIn) { char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE]; char16 debugStringBuffer2[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE]; char16 debugStringBuffer3[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE]; if (inlineeName == nullptr) { - int len = swprintf_s(debugStringBuffer3, MAX_FUNCTION_BODY_DEBUG_STRING_SIZE, _u("built In Id: %u"), builtIn); Assert(len > 14); inlineeName = debugStringBuffer3; } - INLINE_TESTTRACE(_u("INLINING: Inlinee: %s (%s)\tSize: %d\tCaller: %s (%s)\tSize: %d\tInlineCount: %d\tRoot: %s (%s)\tSize: %d\tCallSiteId: %d\n"), + INLINE_TESTTRACE(_u("INLINING %s: Inlinee: %s (%s)\tSize: %d\tCaller: %s (%s)\tSize: %d\tInlineCount: %d\tRoot: %s (%s)\tSize: %d\tCallSiteId: %d\n"), + inLoopBody ? _u("IN LOOP BODY") : _u(""), inlineeName, inlineeFunctionIdandNumberString, inlineeByteCodeCount, - inliner->GetDisplayName(), inliner->GetDebugNumberSet(debugStringBuffer), inliner->GetByteCodeCount(), inlinedByteCodeCount, - topFunc->GetDisplayName(), - topFunc->GetDebugNumberSet(debugStringBuffer2), topFunc->GetByteCodeCount(), + inliner->GetDisplayName(), inliner->GetDebugNumberSet(debugStringBuffer), inliner->GetByteCodeCount(), + inlinedByteCodeCount, + topFunc->GetDisplayName(), topFunc->GetDebugNumberSet(debugStringBuffer2), topFunc->GetByteCodeCount(), callSiteId ); - INLINE_TRACE(_u("INLINING:\n\tInlinee: size: %4d %s\n\tCaller: size: %4d %-25s (%s) InlineCount: %d\tRoot: size: %4d %s (%s) CallSiteId %d\n"), - inlineeByteCodeCount, inlineeName, - inliner->GetByteCodeCount(), inliner->GetDisplayName(), - inliner->GetDebugNumberSet(debugStringBuffer), inlinedByteCodeCount, - topFunc->GetByteCodeCount(), - topFunc->GetDisplayName(), topFunc->GetDebugNumberSet(debugStringBuffer2), + INLINE_TRACE(_u("INLINING %s: Inlinee: %s(%s)\tSize : %d\tCaller : %s(%s)\tSize : %d\tInlineCount : %d\tRoot : %s(%s)\tSize : %d\tCallSiteId : %d\n"), + inLoopBody ? _u("IN LOOP BODY") : _u(""), + inlineeName, inlineeFunctionIdandNumberString, inlineeByteCodeCount, + inliner->GetDisplayName(), inliner->GetDebugNumberSet(debugStringBuffer), inliner->GetByteCodeCount(), + inlinedByteCodeCount, + topFunc->GetByteCodeCount(), topFunc->GetDisplayName(), topFunc->GetDebugNumberSet(debugStringBuffer2), callSiteId ); diff --git a/lib/Backend/InliningDecider.h b/lib/Backend/InliningDecider.h index 1d1074a56f8..e424e6ab56d 100644 --- a/lib/Backend/InliningDecider.h +++ b/lib/Backend/InliningDecider.h @@ -51,8 +51,8 @@ class InliningDecider bytecodeInlinedCount = 0; numberOfInlineesWithLoop = 0; } - uint32 getNumberOfInlineesWithLoop() { return numberOfInlineesWithLoop; } - void incrementNumberOfInlineesWithLoop() { numberOfInlineesWithLoop++; } + uint32 GetNumberOfInlineesWithLoop() { return numberOfInlineesWithLoop; } + void IncrementNumberOfInlineesWithLoop() { numberOfInlineesWithLoop++; } static bool GetBuiltInInfo( @@ -63,7 +63,7 @@ class InliningDecider #if defined(ENABLE_DEBUG_CONFIG_OPTIONS) static void TraceInlining(Js::FunctionBody *const inliner, const char16* inlineeName, const char16* inlineeFunctionIdandNumberString, uint inlineeByteCodeCount, - Js::FunctionBody* topFunc, uint inlinedByteCodeCount, Js::FunctionBody *const inlinee, uint callSiteId, uint builtIn = -1); + Js::FunctionBody* topFunc, uint inlinedByteCodeCount, Js::FunctionBody *const inlinee, uint callSiteId, bool isLoopBody, uint builtIn = -1); #endif PREVENT_COPY(InliningDecider) diff --git a/lib/Backend/InliningHeuristics.cpp b/lib/Backend/InliningHeuristics.cpp index deb5647340c..8ae152d545d 100644 --- a/lib/Backend/InliningHeuristics.cpp +++ b/lib/Backend/InliningHeuristics.cpp @@ -4,8 +4,9 @@ //------------------------------------------------------------------------------------------------------- #include "Backend.h" -InliningThreshold::InliningThreshold(Js::FunctionBody *topFunc, bool aggressive) : topFunc(topFunc) +InliningThreshold::InliningThreshold(Js::FunctionBody *topFunc, bool forLoopBody, bool aggressive) : topFunc(topFunc) { + this->forLoopBody = forLoopBody; if (aggressive) { SetAggressiveHeuristics(); @@ -17,6 +18,7 @@ InliningThreshold::InliningThreshold(Js::FunctionBody *topFunc, bool aggressive) } void InliningThreshold::SetAggressiveHeuristics() { + Assert(!this->forLoopBody); int limit = CONFIG_FLAG(AggressiveInlineThreshold); inlineThreshold = limit; @@ -60,7 +62,7 @@ void InliningThreshold::SetHeuristics() polymorphicInlineThreshold = CONFIG_FLAG(PolymorphicInlineThreshold); maxNumberOfInlineesWithLoop = CONFIG_FLAG(MaxNumberOfInlineesWithLoop); constantArgumentInlineThreshold = CONFIG_FLAG(ConstantArgumentInlineThreshold); - inlineCountMax = CONFIG_FLAG(InlineCountMax); + inlineCountMax = !forLoopBody ? CONFIG_FLAG(InlineCountMax) : CONFIG_FLAG(InlineCountMaxInLoopBodies); } bool InliningHeuristics::CanRecursivelyInline(Js::FunctionBody* inlinee, Js::FunctionBody *inliner, bool allowRecursiveInlining, uint recursiveInlineDepth) @@ -155,29 +157,24 @@ bool InliningHeuristics::DeciderInlineIntoInliner(Js::FunctionBody* inlinee, Js: // 7a. As of now it is still governed by the InlineThreshold. Plan to play with this in future. // 8. Rest should be inlined. + uint16 mask = constantArgInfo & inlinee->m_argUsedForBranch; + if (mask && inlineeByteCodeCount < (uint)CONFIG_FLAG(ConstantArgumentInlineThreshold)) + { + return true; + } + + double inlineThreshold = threshold.inlineThreshold; 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. + if (inlinee->GetLoopCount() == 0 || 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 (threshold.leafInlineThreshold >= 0) { - if (inlinee->GetLoopCount()) - { - inliningDecider->incrementNumberOfInlineesWithLoop(); - } - return true; + inlineThreshold += threshold.leafInlineThreshold - threshold.inlineThreshold; } } - - } - - - uint16 mask = constantArgInfo & inlinee->m_argUsedForBranch; - if (mask && inlineeByteCodeCount < (uint)CONFIG_FLAG(ConstantArgumentInlineThreshold)) - { - return true; } #if ENABLE_DEBUG_CONFIG_OPTIONS @@ -186,29 +183,18 @@ bool InliningHeuristics::DeciderInlineIntoInliner(Js::FunctionBody* inlinee, Js: char16 debugStringBuffer3[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE]; #endif - if (inlineeByteCodeCount > (uint)this->threshold.inlineThreshold) // Don't inline function too big to inline - { - INLINE_TESTTRACE(_u("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. + 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. + isConstructorCall || // If the function is constructor with loops, don't inline. PHASE_OFF(Js::InlineFunctionsWithLoopsPhase,this->topFunc)) { INLINE_TESTTRACE(_u("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(), + inliningDecider->GetNumberOfInlineesWithLoop(), inlinee->GetLoopCount(), inlinee->GetHasNestedLoop(), isConstructorCall, @@ -218,14 +204,15 @@ bool InliningHeuristics::DeciderInlineIntoInliner(Js::FunctionBody* inlinee, Js: // Don't inline function with loops return false; } - inliningDecider->incrementNumberOfInlineesWithLoop(); - return true; + else + { + inlineThreshold -= (threshold.inlineThreshold > threshold.loopInlineThreshold) ? threshold.inlineThreshold - threshold.loopInlineThreshold : 0; + } } 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(_u("INLINING: Skip Inline: Polymorphic call under PolymorphicInlineThreshold: %d \tBytecode size: %d\tInlinee: %s (%s)\tCaller: %s (%s) \tRoot: %s (%s)\n"), @@ -236,7 +223,10 @@ bool InliningHeuristics::DeciderInlineIntoInliner(Js::FunctionBody* inlinee, Js: topFunc->GetDisplayName(), topFunc->GetDebugNumberSet(debugStringBuffer3)); return false; } - return true; + else + { + inlineThreshold -= (threshold.inlineThreshold > threshold.polymorphicInlineThreshold) ? threshold.inlineThreshold - threshold.polymorphicInlineThreshold : 0; + } } if(isConstructorCall) @@ -259,12 +249,12 @@ bool InliningHeuristics::DeciderInlineIntoInliner(Js::FunctionBody* inlinee, Js: if (inlinee->HasDynamicProfileInfo() && inlinee->GetAnyDynamicProfileInfo()->HasPolymorphicFldAccess()) { - // As of now this is dependent on bytecodeInlinedThreshold. + // As of now this is not dependent on bytecodeInlinedThreshold. return true; } // Negative ConstructorInlineThreshold always disable constructor inlining - if (threshold.constructorInlineThreshold < 0 || inlineeByteCodeCount >(uint)threshold.constructorInlineThreshold) + if (threshold.constructorInlineThreshold < 0) { INLINE_TESTTRACE(_u("INLINING: Skip Inline: Constructor with no polymorphic field access \tBytecode size: %d\tInlinee: %s (%s)\tCaller: %s (%s) \tRoot: %s (%s)\n"), inlinee->GetByteCodeCount(), @@ -275,10 +265,29 @@ bool InliningHeuristics::DeciderInlineIntoInliner(Js::FunctionBody* inlinee, Js: // caches is disabled return false; } - return true; + else + { + inlineThreshold -= (threshold.inlineThreshold > threshold.constructorInlineThreshold) ? threshold.inlineThreshold - threshold.constructorInlineThreshold : 0; + } } - return true; + if (threshold.forLoopBody) + { + inlineThreshold /= CONFIG_FLAG(InlineInLoopBodyScaleDownFactor); + } + + if (inlineThreshold > 0 && inlineeByteCodeCount <= inlineThreshold) + { + if (inlinee->GetLoopCount()) + { + inliningDecider->IncrementNumberOfInlineesWithLoop(); + } + return true; + } + else + { + return false; + } } // Called from background thread to commit inlining. @@ -408,7 +417,7 @@ bool InliningHeuristics::ContinueInliningUserDefinedFunctions(uint32 bytecodeInl #if ENABLE_DEBUG_CONFIG_OPTIONS char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE]; #endif - if (PHASE_FORCE(Js::InlinePhase, this->topFunc) || bytecodeInlinedCount <= (uint)threshold.inlineCountMax) + if (PHASE_FORCE(Js::InlinePhase, this->topFunc) || bytecodeInlinedCount < (uint)threshold.inlineCountMax) { return true; } diff --git a/lib/Backend/InliningHeuristics.h b/lib/Backend/InliningHeuristics.h index 9088e88eeb9..d01453f4fe1 100644 --- a/lib/Backend/InliningHeuristics.h +++ b/lib/Backend/InliningHeuristics.h @@ -7,6 +7,7 @@ struct InliningThreshold { Js::FunctionBody * topFunc; + bool forLoopBody; int inlineThreshold; int constructorInlineThreshold; int outsideLoopInlineThreshold; @@ -17,7 +18,7 @@ struct InliningThreshold int maxNumberOfInlineesWithLoop; int constantArgumentInlineThreshold; - InliningThreshold(Js::FunctionBody * topFunc, bool aggressive = false); + InliningThreshold(Js::FunctionBody * topFunc, bool forLoopBody, bool aggressive = false); void SetHeuristics(); void SetAggressiveHeuristics(); void Reset(); @@ -33,7 +34,7 @@ class InliningHeuristics public: public: - InliningHeuristics(Js::FunctionBody * topFunc) :topFunc(topFunc), threshold(topFunc) {}; + InliningHeuristics(Js::FunctionBody * topFunc, bool forLoopBody) :topFunc(topFunc), threshold(topFunc, forLoopBody) {}; bool DeciderInlineIntoInliner(Js::FunctionBody* inlinee, Js::FunctionBody *inliner, bool isConstructorCall, bool isPolymorphicCall, InliningDecider* inliningDecider, uint16 constantArgInfo, uint recursiveInlineDepth, bool allowRecursiveInlining); bool CanRecursivelyInline(Js::FunctionBody* inlinee, Js::FunctionBody *inliner, bool allowRecursiveInlining, uint recursiveInlineDepth); bool BackendInlineIntoInliner(Js::FunctionBody* inlinee, diff --git a/lib/Backend/NativeCodeGenerator.cpp b/lib/Backend/NativeCodeGenerator.cpp index f0aac46f5d7..002613ae20e 100644 --- a/lib/Backend/NativeCodeGenerator.cpp +++ b/lib/Backend/NativeCodeGenerator.cpp @@ -1720,7 +1720,7 @@ NativeCodeGenerator::GatherCodeGenData( Assert(functionBody); Assert(jitTimeData); Assert(IsInlinee == !!runtimeData); - Assert(!IsInlinee || !inliningDecider.GetIsLoopBody()); + Assert(!IsInlinee || (!inliningDecider.GetIsLoopBody() || !PHASE_OFF(Js::InlineInJitLoopBodyPhase, topFunctionBody))); Assert(topFunctionBody != nullptr && (!entryPoint->GetWorkItem() || entryPoint->GetWorkItem()->GetFunctionBody() == topFunctionBody)); Assert(objTypeSpecFldInfoList != nullptr); @@ -1772,7 +1772,7 @@ NativeCodeGenerator::GatherCodeGenData( inliningDecider.SetAggressiveHeuristics(); if (!TryAggressiveInlining(topFunctionBody, functionBody, inliningDecider, inlineeCount, 0)) { - uint countOfInlineesWithLoops = inliningDecider.getNumberOfInlineesWithLoop(); + uint countOfInlineesWithLoops = inliningDecider.GetNumberOfInlineesWithLoop(); //TryAggressiveInlining failed, set back to default heuristics. inliningDecider.ResetInlineHeuristics(); inliningDecider.SetLimitOnInlineesWithLoop(countOfInlineesWithLoops); diff --git a/lib/Common/ConfigFlagsList.h b/lib/Common/ConfigFlagsList.h index 17ceefd4db4..252fc87b082 100644 --- a/lib/Common/ConfigFlagsList.h +++ b/lib/Common/ConfigFlagsList.h @@ -400,13 +400,15 @@ PHASE(All) #define DEFAULT_CONFIG_LoopInlineThreshold (25) //Inlinee threshold for function with loops #define DEFAULT_CONFIG_PolymorphicInlineThreshold (35) //Polymorphic inline threshold #define DEFAULT_CONFIG_InlineCountMax (1200) //Max sum of bytecodes of inlinees inlined into a function (excluding built-ins) +#define DEFAULT_CONFIG_InlineCountMaxInLoopBodies (500) // Max sum of bytecodes of inlinees that can be inlined into a jitted loop body (excluding built-ins) #define DEFAULT_CONFIG_AggressiveInlineCountMax (8000) //Max sum of bytecodes of inlinees inlined into a function (excluding built-ins) when inlined aggressively #define DEFAULT_CONFIG_MaxFuncInlineDepth (2) //Maximum number of times a function can be inlined within a top function #define DEFAULT_CONFIG_MaxNumberOfInlineesWithLoop (40) //Inlinee with a loop is controlled by LoopInlineThreshold, though we don't want to inline lot of inlinees with loop, this ensures a limit. #define DEFAULT_CONFIG_ConstantArgumentInlineThreshold (157) // Bytecode threshold for functions with constant arguments which are used for branching #define DEFAULT_CONFIG_RecursiveInlineThreshold (2000) // Bytecode threshold recursive call at a call site #define DEFAULT_CONFIG_RecursiveInlineDepthMax (8) // Maximum inline depth for recursive calls -#define DEFAULT_CONFIG_RecursiveInlineDepthMin (2) // Minimum inline depth for recursive calls +#define DEFAULT_CONFIG_RecursiveInlineDepthMin (2) // Minimum inline depth for recursive call +#define DEFAULT_CONFIG_InlineInLoopBodyScaleDownFactor (4) #define DEFAULT_CONFIG_CloneInlinedPolymorphicCaches (true) #define DEFAULT_CONFIG_HighPrecisionDate (false) @@ -985,6 +987,8 @@ FLAGNR(Number, GoptCleanupThreshold, "Number of instructions seen before we clea FLAGNR(Number, AsmGoptCleanupThreshold, "Number of instructions seen before we cleanup the value table", DEFAULT_CONFIG_AsmGoptCleanupThreshold) FLAGNR(Boolean, HighPrecisionDate, "Enable sub-millisecond resolution in Javascript Date for benchmark timing", DEFAULT_CONFIG_HighPrecisionDate) FLAGNR(Number, InlineCountMax , "Maximum count in bytecodes to inline in a given function", DEFAULT_CONFIG_InlineCountMax) +FLAGNRA(Number, InlineCountMaxInLoopBodies, icminlb, "Maximum count in bytecodes to inline in a given function", DEFAULT_CONFIG_InlineCountMaxInLoopBodies) +FLAGNRA(Number, InlineInLoopBodyScaleDownFactor, iilbsdf, "Maximum depth of a recursive inline call", DEFAULT_CONFIG_InlineInLoopBodyScaleDownFactor) FLAGNR(Number, InlineThreshold , "Maximum size in bytecodes of an inline candidate", DEFAULT_CONFIG_InlineThreshold) FLAGNR(Number, AggressiveInlineCountMax, "Maximum count in bytecodes to inline in a given function", DEFAULT_CONFIG_AggressiveInlineCountMax) FLAGNR(Number, AggressiveInlineThreshold, "Maximum size in bytecodes of an inline candidate for aggressive inlining", DEFAULT_CONFIG_AggressiveInlineThreshold) diff --git a/lib/Runtime/Base/FunctionBody.cpp b/lib/Runtime/Base/FunctionBody.cpp index fd6f091a97e..acff0424161 100644 --- a/lib/Runtime/Base/FunctionBody.cpp +++ b/lib/Runtime/Base/FunctionBody.cpp @@ -1018,6 +1018,7 @@ namespace Js CopyDeferParseField(m_isStrictMode); CopyDeferParseField(m_isGlobalFunc); CopyDeferParseField(m_doBackendArgumentsOptimization); + CopyDeferParseField(m_usesArgumentsObject); CopyDeferParseField(m_isEval); CopyDeferParseField(m_isDynamicFunction); CopyDeferParseField(m_hasImplicitArgIns); @@ -1112,6 +1113,7 @@ namespace Js m_isNameIdentifierRef (true), m_isStaticNameFunction(false), m_doBackendArgumentsOptimization(true), + m_usesArgumentsObject(false), m_isStrictMode(false), m_isAsmjsMode(false), m_dontInline(false), diff --git a/lib/Runtime/Base/FunctionBody.h b/lib/Runtime/Base/FunctionBody.h index 7b9e6cec2f6..0778011d9a8 100644 --- a/lib/Runtime/Base/FunctionBody.h +++ b/lib/Runtime/Base/FunctionBody.h @@ -1539,6 +1539,19 @@ namespace Js return m_doBackendArgumentsOptimization; } + void SetUsesArgumentsObject(bool set) + { + if (!m_usesArgumentsObject) + { + m_usesArgumentsObject = set; + } + } + + bool GetUsesArgumentsObject() + { + return m_usesArgumentsObject; + } + bool IsFunctionParsed() { return !IsDeferredParseFunction() || m_hasBeenParsed; @@ -1609,6 +1622,7 @@ namespace Js bool m_isAsmJsFunction : 1; bool m_isGlobalFunc : 1; bool m_doBackendArgumentsOptimization : 1; + bool m_usesArgumentsObject : 1; bool m_isEval : 1; // Source code is in 'eval' bool m_isDynamicFunction : 1; // Source code is in 'Function' bool m_hasImplicitArgIns : 1; diff --git a/lib/Runtime/ByteCode/ByteCodeGenerator.cpp b/lib/Runtime/ByteCode/ByteCodeGenerator.cpp index 02cc801210c..fe3ae7ca875 100644 --- a/lib/Runtime/ByteCode/ByteCodeGenerator.cpp +++ b/lib/Runtime/ByteCode/ByteCodeGenerator.cpp @@ -2333,6 +2333,7 @@ FuncInfo* PreVisitFunction(ParseNode* pnode, ByteCodeGenerator* byteCodeGenerato { // 3. the function directly references an 'arguments' identifier funcInfo->SetHasArguments(true); + funcInfo->GetParsedFunctionBody()->SetUsesArgumentsObject(true); if (pnode->sxFnc.HasHeapArguments()) { funcInfo->SetHasHeapArguments(true, !pnode->sxFnc.IsGenerator() /*= Optimize arguments in backend*/); diff --git a/lib/Runtime/ByteCode/ByteCodeSerializer.cpp b/lib/Runtime/ByteCode/ByteCodeSerializer.cpp index 3c86c577180..d2504a6bc8d 100644 --- a/lib/Runtime/ByteCode/ByteCodeSerializer.cpp +++ b/lib/Runtime/ByteCode/ByteCodeSerializer.cpp @@ -208,7 +208,8 @@ enum FunctionFlags ffIsNamedFunctionExpression = 0x20000, ffIsAsmJsMode = 0x40000, ffIsAsmJsFunction = 0x80000, - ffIsAnonymous = 0x100000 + ffIsAnonymous = 0x100000, + ffUsesArgumentsObject = 0x200000 }; // Kinds of constant @@ -2138,6 +2139,7 @@ class ByteCodeBufferBuilder | (function->m_isFuncRegistered ? ffIsFuncRegistered : 0) | (function->m_isStrictMode ? ffIsStrictMode : 0) | (function->m_doBackendArgumentsOptimization ? ffDoBackendArgumentsOptimization : 0) + | (function->m_usesArgumentsObject ? ffUsesArgumentsObject : 0) | (function->m_isEval ? ffIsEval : 0) | (function->m_isDynamicFunction ? ffIsDynamicFunction : 0) | (function->m_hasAllNonLocalReferenced ? ffhasAllNonLocalReferenced : 0) @@ -3662,6 +3664,7 @@ class ByteCodeBufferReader (*function)->m_dontInline = (bitflags & ffDontInline) ? true : false; (*function)->m_isStrictMode = (bitflags & ffIsStrictMode) ? true : false; (*function)->m_doBackendArgumentsOptimization = (bitflags & ffDoBackendArgumentsOptimization) ? true : false; + (*function)->m_usesArgumentsObject = (bitflags & ffUsesArgumentsObject) ? true : false; (*function)->m_isEval = (bitflags & ffIsEval) ? true : false; (*function)->m_isDynamicFunction = (bitflags & ffIsDynamicFunction) ? true : false; diff --git a/lib/Runtime/Language/JavascriptStackWalker.cpp b/lib/Runtime/Language/JavascriptStackWalker.cpp index fc267c5042a..96e87a4466b 100644 --- a/lib/Runtime/Language/JavascriptStackWalker.cpp +++ b/lib/Runtime/Language/JavascriptStackWalker.cpp @@ -374,31 +374,21 @@ namespace Js pCodeAddr--; } - uint loopNum = GetLoopNumber(); + bool usedInternalFrameInfo = false; + uint loopNum = GetLoopNumber(usedInternalFrameInfo); JavascriptFunction *function = nullptr; FunctionBody *inlinee = nullptr; - if (this->interpreterFrame == nullptr) //Inlining is disabled in Jit Loopbody. Don't attempt to get the statement map from the inlined frame. - { - function = this->GetCurrentFunctionFromPhysicalFrame(); + function = usedInternalFrameInfo ? this->GetCachedInternalFrameInfo().function : this->GetCurrentFunctionFromPhysicalFrame(); - // If there are inlined frames on the stack, we have to be able to return the byte code offsets of those inlined calls - // from their respective callers. But, we can use the current native address as IP for only the topmost inlined frame. - // TryGetByteCodeOffsetOfInlinee takes care of these conditions and sets up the offset of an inlinee in 'offset', if the - // current inlinee frame is not the topmost of the inlinee frames. - if (HasInlinedFramesOnStack() && TryGetByteCodeOffsetOfInlinee(function, loopNum, pCodeAddr, &inlinee, offset)) - { - return true; - } - } - else + // If there are inlined frames on the stack, we have to be able to return the byte code offsets of those inlined calls + // from their respective callers. But, we can use the current native address as IP for only the topmost inlined frame. + // TryGetByteCodeOffsetOfInlinee takes care of these conditions and sets up the offset of an inlinee in 'offset', if the + // current inlinee frame is not the topmost of the inlinee frames. + if (HasInlinedFramesOnStack() && TryGetByteCodeOffsetOfInlinee(function, loopNum, pCodeAddr, &inlinee, offset, usedInternalFrameInfo)) { - //Get the function from the interpreterFrame in jit loop body case - //This is exactly same as this->GetCurrentFunctionFromPhysicalFrame() if the interpreterFrame is not - //called from bailout path. - Assert(this->lastInternalFrameInfo.codeAddress); - function = this->interpreterFrame->GetJavascriptFunction(); + return true; } StatementData data; @@ -411,7 +401,7 @@ namespace Js return false; } - uint JavascriptStackWalker::GetLoopNumber() const + uint JavascriptStackWalker::GetLoopNumber(bool& usedInternalFrameInfo) const { uint loopNum = LoopHeader::NoLoop; if (this->lastInternalFrameInfo.codeAddress != nullptr) @@ -421,6 +411,7 @@ namespace Js AnalysisAssert(this->interpreterFrame); loopNum = this->interpreterFrame->GetCurrentLoopNum(); Assert(loopNum != LoopHeader::NoLoop); + usedInternalFrameInfo = true; } } else @@ -431,13 +422,14 @@ namespace Js Assert(this->tempInterpreterFrame); loopNum = this->tempInterpreterFrame->GetCurrentLoopNum(); Assert(loopNum != LoopHeader::NoLoop); + usedInternalFrameInfo = false; } } return loopNum; } - bool JavascriptStackWalker::TryGetByteCodeOffsetOfInlinee(Js::JavascriptFunction* function, uint loopNum, DWORD_PTR pCodeAddr, Js::FunctionBody** inlinee, uint32& offset) const + bool JavascriptStackWalker::TryGetByteCodeOffsetOfInlinee(Js::JavascriptFunction* parentFunction, uint loopNum, DWORD_PTR pCodeAddr, Js::FunctionBody** inlinee, uint32& offset, bool useInternalFrameInfo) const { // For inlined frames, translation from native offset -> source code happens in two steps. // The native offset is first translated into a statement index using the physical frame's @@ -463,19 +455,19 @@ namespace Js inlineeOffset = inlinedFrameWalker.GetCurrentInlineeOffset(); } } - else if (ScriptFunction::Is(function) && HasInlinedFramesOnStack()) + else if (ScriptFunction::Is(parentFunction) && HasInlinedFramesOnStack()) { // Inlined frames are not being walked right now. However, if there // are inlined frames on the stack the InlineeCallInfo of the first inlined frame // has the native offset of the current physical frame. Assert(!*inlinee); - InlinedFrameWalker::FromPhysicalFrame(tmpFrameWalker, currentFrame, ScriptFunction::FromVar(function), previousInterpreterFrameIsFromBailout, loopNum, this); + InlinedFrameWalker::FromPhysicalFrame(tmpFrameWalker, currentFrame, ScriptFunction::FromVar(parentFunction), PreviousInterpreterFrameIsFromBailout(), loopNum, this, useInternalFrameInfo); inlineeOffset = tmpFrameWalker.GetBottomMostInlineeOffset(); tmpFrameWalker.Close(); } if (inlineeOffset != 0 && - this->GetCurrentFunctionFromPhysicalFrame()->GetFunctionBody()->GetMatchingStatementMapFromNativeOffset(pCodeAddr, inlineeOffset, data, loopNum, *inlinee)) + parentFunction->GetFunctionBody()->GetMatchingStatementMapFromNativeOffset(pCodeAddr, inlineeOffset, data, loopNum, *inlinee)) { offset = data.bytecodeBegin; return true; @@ -484,6 +476,16 @@ namespace Js return false; } + bool JavascriptStackWalker::PreviousInterpreterFrameIsFromBailout() const + { + if (lastInternalFrameInfo.codeAddress) + { + return lastInternalFrameInfo.previousInterpreterFrameIsFromBailout; + } + + return this->previousInterpreterFrameIsFromBailout; + } + bool JavascriptStackWalker::InlinedFramesBeingWalked() const { if (lastInternalFrameInfo.codeAddress) @@ -491,7 +493,7 @@ namespace Js return false; } - return inlinedFramesBeingWalked; + return this->inlinedFramesBeingWalked; } bool JavascriptStackWalker::HasInlinedFramesOnStack() const @@ -568,7 +570,7 @@ namespace Js } bool hasInlinedFramesOnStack = InlinedFrameWalker::FromPhysicalFrame(inlinedFrameWalker, currentFrame, - ScriptFunction::FromVar(function), true /*fromBailout*/, loopNum, this); + ScriptFunction::FromVar(function), true /*fromBailout*/, loopNum, this, false /*useInternalFrameInfo*/); if (hasInlinedFramesOnStack) { // We're now back in the state where currentFrame == physical frame of the inliner, but @@ -592,7 +594,7 @@ namespace Js // inlined frames in the stack walk, we need to set the codeAddress on lastInternalFrameInfo, // which would have otherwise been set upon closing the inlinedFrameWalker, now. // Note that we already have an assert in CheckJavascriptFrame to ensure this. - SetCachedInternalFrameInfo(InternalFrameType_LoopBody, false /*hasInlinedFramesOnStack*/); + SetCachedInternalFrameInfo(InternalFrameType_LoopBody, function, false /*hasInlinedFramesOnStack*/, true /*previousInterpreterFrameIsFromBailout*/); } #else // How did we bail out when JIT was disabled? @@ -705,7 +707,7 @@ namespace Js { // Done walking inlined frames in a loop body, cache the native code address now // in order to skip the loop body frame. - this->SetCachedInternalFrameInfo(InternalFrameType_LoopBody, true /*hasInlinedFramesOnStack*/); + this->SetCachedInternalFrameInfo(InternalFrameType_LoopBody, this->GetCurrentFunctionFromPhysicalFrame(), true /*hasInlinedFramesOnStack*/, previousInterpreterFrameIsFromBailout); isJavascriptFrame = false; } } @@ -909,7 +911,7 @@ namespace Js Assert((this->interpreterFrame->GetFlags() & Js::InterpreterStackFrameFlags_FromBailOut) != 0); InlinedFrameWalker tmpFrameWalker; Assert(InlinedFrameWalker::FromPhysicalFrame(tmpFrameWalker, currentFrame, Js::ScriptFunction::FromVar(argv[JavascriptFunctionArgIndex_Function]), - true /*fromBailout*/, this->tempInterpreterFrame->GetCurrentLoopNum(), this, true /*noAlloc*/)); + true /*fromBailout*/, this->tempInterpreterFrame->GetCurrentLoopNum(), this, false /*useInternalFrameInfo*/, true /*noAlloc*/)); tmpFrameWalker.Close(); } #endif //DBG @@ -955,7 +957,7 @@ namespace Js { if (includeInlineFrames && InlinedFrameWalker::FromPhysicalFrame(inlinedFrameWalker, currentFrame, Js::ScriptFunction::FromVar(argv[JavascriptFunctionArgIndex_Function]), - false /*fromBailout*/, this->tempInterpreterFrame->GetCurrentLoopNum(), this)) + false /*fromBailout*/, this->tempInterpreterFrame->GetCurrentLoopNum(), this, false /*useInternalFrameInfo*/)) { // Found inlined frames in a jitted loop body. We dont want to skip the inlined frames; walk all of them before setting codeAddress on lastInternalFrameInfo. this->inlinedFramesBeingWalked = inlinedFrameWalker.Next(inlinedFrameCallInfo); @@ -964,7 +966,7 @@ namespace Js return true; } - SetCachedInternalFrameInfo(InternalFrameType_LoopBody, false /*hasInlinedFramesOnStack*/); + SetCachedInternalFrameInfo(InternalFrameType_LoopBody, funcObj, false /*hasInlinedFramesOnStack*/, previousInterpreterFrameIsFromBailout); return false; } @@ -1071,11 +1073,18 @@ namespace Js this->lastInternalFrameInfo.Clear(); } - void JavascriptStackWalker::SetCachedInternalFrameInfo(InternalFrameType frameType, bool hasInlinedFramesOnStack) + void JavascriptStackWalker::SetCachedInternalFrameInfo(InternalFrameType frameType, JavascriptFunction* function, bool hasInlinedFramesOnStack, bool previousInterpreterFrameIsFromBailout) { if (!this->lastInternalFrameInfo.codeAddress) { - this->lastInternalFrameInfo.Set(this->GetCurrentCodeAddr(), this->currentFrame.GetFrame(), this->currentFrame.GetStackCheckCodeHeight(), frameType, hasInlinedFramesOnStack); + this->lastInternalFrameInfo.Set( + this->GetCurrentCodeAddr(), + this->currentFrame.GetFrame(), + this->currentFrame.GetStackCheckCodeHeight(), + frameType, + function, + hasInlinedFramesOnStack, + previousInterpreterFrameIsFromBailout); } } #endif @@ -1157,7 +1166,8 @@ namespace Js } #if ENABLE_NATIVE_CODEGEN - bool InlinedFrameWalker::FromPhysicalFrame(InlinedFrameWalker& self, StackFrame& physicalFrame, Js::ScriptFunction *parent, bool fromBailout, int loopNum, const JavascriptStackWalker * const stackWalker, bool noAlloc) + bool InlinedFrameWalker::FromPhysicalFrame(InlinedFrameWalker& self, StackFrame& physicalFrame, Js::ScriptFunction *parent, bool fromBailout, + int loopNum, const JavascriptStackWalker * const stackWalker, bool useInternalFrameInfo, bool noAlloc) { bool inlinedFramesFound = false; FunctionBody* parentFunctionBody = parent->GetFunctionBody(); @@ -1167,8 +1177,22 @@ namespace Js { Assert(stackWalker); } - void *nativeCodeAddress = (loopNum == -1 || !stackWalker->GetCachedInternalFrameInfo().codeAddress) ? physicalFrame.GetInstructionPointer() : stackWalker->GetCachedInternalFrameInfo().codeAddress; - void *framePointer = (loopNum == -1 || !stackWalker->GetCachedInternalFrameInfo().codeAddress) ? physicalFrame.GetFrame() : stackWalker->GetCachedInternalFrameInfo().framePointer; + + void *nativeCodeAddress = nullptr; + void *framePointer = nullptr; + if (loopNum != -1 && useInternalFrameInfo) + { + Assert(stackWalker->GetCachedInternalFrameInfo().codeAddress != nullptr); + InternalFrameInfo lastInternalFrameInfo = stackWalker->GetCachedInternalFrameInfo(); + + nativeCodeAddress = lastInternalFrameInfo.codeAddress; + framePointer = lastInternalFrameInfo.framePointer; + } + else + { + nativeCodeAddress = physicalFrame.GetInstructionPointer(); + framePointer = physicalFrame.GetFrame(); + } if (loopNum != -1) { @@ -1176,14 +1200,14 @@ namespace Js } else { - entryPointInfo = (Js::EntryPointInfo*)parentFunctionBody->GetEntryPointFromNativeAddress((DWORD_PTR)physicalFrame.GetInstructionPointer()); + entryPointInfo = (Js::EntryPointInfo*)parentFunctionBody->GetEntryPointFromNativeAddress((DWORD_PTR)nativeCodeAddress); } AssertMsg(entryPointInfo != nullptr, "Inlined frame should resolve to the right parent address"); if (entryPointInfo->HasInlinees()) { void *entry = reinterpret_cast(entryPointInfo->GetNativeAddress()); - InlinedFrameWalker::InlinedFrame *outerMostFrame = InlinedFrame::FromPhysicalFrame(physicalFrame, stackWalker, entry, entryPointInfo); + InlinedFrameWalker::InlinedFrame *outerMostFrame = InlinedFrame::FromPhysicalFrame(physicalFrame, stackWalker, entry, entryPointInfo, useInternalFrameInfo); if (!outerMostFrame) { @@ -1395,7 +1419,7 @@ namespace Js this->currentIndex = -1; } - InlinedFrameWalker::InlinedFrame* InlinedFrameWalker::InlinedFrame::FromPhysicalFrame(StackFrame& currentFrame, const JavascriptStackWalker * const stackWalker, void *entry, EntryPointInfo* entryPointInfo) + InlinedFrameWalker::InlinedFrame* InlinedFrameWalker::InlinedFrame::FromPhysicalFrame(StackFrame& currentFrame, const JavascriptStackWalker * const stackWalker, void *entry, EntryPointInfo* entryPointInfo, bool useInternalFrameInfo) { // If the current javascript frame is a native frame, get the inlined frame from it, otherwise // it may be possible that current frame is the interpreter frame for a jitted loop body @@ -1405,7 +1429,7 @@ namespace Js void *codeAddr, *framePointer; size_t stackCheckCodeHeight; - if (entryPointInfo->IsLoopBody() && stackWalker && stackWalker->GetCachedInternalFrameInfo().codeAddress) + if (useInternalFrameInfo) { codeAddr = stackWalker->GetCachedInternalFrameInfo().codeAddress; framePointer = stackWalker->GetCachedInternalFrameInfo().framePointer; @@ -1426,7 +1450,14 @@ namespace Js return inlinedFrame; } - void InternalFrameInfo::Set(void *codeAddress, void *framePointer, size_t stackCheckCodeHeight, InternalFrameType frameType, bool hasInlinedFramesOnStack) + void InternalFrameInfo::Set( + void *codeAddress, + void *framePointer, + size_t stackCheckCodeHeight, + InternalFrameType frameType, + JavascriptFunction* function, + bool hasInlinedFramesOnStack, + bool previousInterpreterFrameIsFromBailout) { // We skip a jitted loop body's native frame when walking the stack and refer to the loop body's interpreter frame to get the function. // However, if the loop body has inlinees, to retrieve inlinee frames we need to cache some info about the loop body's native frame. @@ -1434,7 +1465,9 @@ namespace Js this->framePointer = framePointer; this->stackCheckCodeHeight = stackCheckCodeHeight; this->frameType = frameType; + this->function = function; this->hasInlinedFramesOnStack = hasInlinedFramesOnStack; + this->previousInterpreterFrameIsFromBailout = previousInterpreterFrameIsFromBailout; } void InternalFrameInfo::Clear() @@ -1443,7 +1476,9 @@ namespace Js this->framePointer = nullptr; this->stackCheckCodeHeight = (uint)-1; this->frameType = InternalFrameType_None; + this->function = nullptr; this->hasInlinedFramesOnStack = false; + this->previousInterpreterFrameIsFromBailout = false; } #endif diff --git a/lib/Runtime/Language/JavascriptStackWalker.h b/lib/Runtime/Language/JavascriptStackWalker.h index 078f6d635f9..85f1bf351a5 100644 --- a/lib/Runtime/Language/JavascriptStackWalker.h +++ b/lib/Runtime/Language/JavascriptStackWalker.h @@ -95,7 +95,8 @@ namespace Js Assert(currentIndex == -1); } - static bool FromPhysicalFrame(InlinedFrameWalker& self, StackFrame& physicalFrame, Js::ScriptFunction *parent, bool fromBailout = false, int loopNum = -1, const JavascriptStackWalker * const walker = nullptr, bool noAlloc = false); + static bool FromPhysicalFrame(InlinedFrameWalker& self, StackFrame& physicalFrame, Js::ScriptFunction *parent, bool fromBailout = false, + int loopNum = -1, const JavascriptStackWalker * const walker = nullptr, bool useInternalFrameInfo = false, bool noAlloc = false); void Close(); bool Next(CallInfo& callInfo); size_t GetArgc() const; @@ -129,7 +130,7 @@ namespace Js return (InlinedFrame*)next; } - static InlinedFrame *FromPhysicalFrame(StackFrame& currentFrame, const JavascriptStackWalker * const stackWalker, void *entry, EntryPointInfo* entryPointInfo); + static InlinedFrame *FromPhysicalFrame(StackFrame& currentFrame, const JavascriptStackWalker * const stackWalker, void *entry, EntryPointInfo* entryPointInfo, bool useInternalFrameInfo); }; @@ -152,19 +153,23 @@ namespace Js void *framePointer; size_t stackCheckCodeHeight; InternalFrameType frameType; + JavascriptFunction* function; bool hasInlinedFramesOnStack; + bool previousInterpreterFrameIsFromBailout; InternalFrameInfo() : codeAddress(nullptr), framePointer(nullptr), stackCheckCodeHeight((uint)-1), frameType(InternalFrameType_None), - hasInlinedFramesOnStack(false) + function(nullptr), + hasInlinedFramesOnStack(false), + previousInterpreterFrameIsFromBailout(false) { } void Clear(); - void Set(void *codeAddress, void *framePointer, size_t stackCheckCodeHeight, InternalFrameType frameType, bool hasInlinedFramesOnStack); + void Set(void *codeAddress, void *framePointer, size_t stackCheckCodeHeight, InternalFrameType frameType, JavascriptFunction* function, bool hasInlinedFramesOnStack, bool previousInterpreterFrameIsFromBailout); }; #endif @@ -226,7 +231,7 @@ namespace Js #if ENABLE_NATIVE_CODEGEN void ClearCachedInternalFrameInfo(); - void SetCachedInternalFrameInfo(InternalFrameType frameType, bool hasInlinedFramesOnStack); + void SetCachedInternalFrameInfo(InternalFrameType frameType, JavascriptFunction* function, bool hasInlinedFramesOnStack, bool prevIntFrameIsFromBailout); InternalFrameInfo GetCachedInternalFrameInfo() const { return this->lastInternalFrameInfo; } #endif bool IsCurrentPhysicalFrameForLoopBody() const; @@ -241,7 +246,7 @@ namespace Js bool GetDisplayCaller(JavascriptFunction ** ppFunc); PCWSTR GetCurrentNativeLibraryEntryName() const; static bool IsLibraryStackFrameEnabled(Js::ScriptContext * scriptContext); - + // Walk frames (until walkFrame returns true) template ushort WalkUntil(ushort stackTraceLimit, WalkFrame walkFrame, bool onlyOnDebugMode = false, bool filterDiagnosticsOM = false) @@ -298,7 +303,6 @@ namespace Js { return previousInterpreterFrameIsFromBailout; } - #if DBG static bool ValidateTopJitFrame(Js::ScriptContext* scriptContext); #endif @@ -332,10 +336,11 @@ namespace Js bool TryGetByteCodeOffsetFromInterpreterFrame(uint32& offset) const; #if ENABLE_NATIVE_CODEGEN bool TryGetByteCodeOffsetFromNativeFrame(uint32& offset) const; - bool TryGetByteCodeOffsetOfInlinee(Js::JavascriptFunction* function, uint loopNum, DWORD_PTR pCodeAddr, Js::FunctionBody** inlinee, uint32& offset) const; - uint GetLoopNumber() const; + bool TryGetByteCodeOffsetOfInlinee(Js::JavascriptFunction* function, uint loopNum, DWORD_PTR pCodeAddr, Js::FunctionBody** inlinee, uint32& offset, bool useInternalFrameInfo) const; + uint GetLoopNumber(bool& usedInternalFrameInfo) const; bool InlinedFramesBeingWalked() const; - bool HasInlinedFramesOnStack() const; + bool HasInlinedFramesOnStack() const; + bool PreviousInterpreterFrameIsFromBailout() const; InternalFrameInfo lastInternalFrameInfo; #endif mutable StackFrame currentFrame; diff --git a/test/Optimizer/test143.baseline b/test/Optimizer/test143.baseline index 972d53d9785..e190d3eacd7 100644 --- a/test/Optimizer/test143.baseline +++ b/test/Optimizer/test143.baseline @@ -5,7 +5,5 @@ Testtrace: ArrayCheckHoist function test0 ( (#1.1), #2): Separating array checks Testtrace: ArrayCheckHoist function test0 ( (#1.1), #2): Hoisting array checks with bailout out of loop Testtrace: ArrayCheckHoist function test0 ( (#1.1), #2): Eliminating array checks Testtrace: ArrayCheckHoist function test0 ( (#1.1), #2): Separating array checks with bailout -Testtrace: ArrayCheckHoist function test0 ( (#1.1), #2): Eliminating array checks -Testtrace: ArrayCheckHoist function test0 ( (#1.1), #2): Separating array checks with bailout Testtrace: ArrayCheckHoist function test0 ( (#1.1), #2): Hoisting array checks with bailout out of loop Testtrace: ArrayCheckHoist function test0 ( (#1.1), #2): Eliminating array checks