blob: 7357826c8043b4e07ad7f42320d3df25d9c3243b [file]
//-------------------------------------------------------------------------------------------------------
// 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"
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), threshold(topFunc->GetByteCodeWithoutLDACount(), isLoopBody)
{
Assert(topFunc);
}
InliningDecider::~InliningDecider()
{
INLINE_FLUSH();
}
bool InliningDecider::InlineIntoTopFunc() const
{
if (this->jitMode == ExecutionMode::SimpleJit ||
PHASE_OFF(Js::InlinePhase, this->topFunc) ||
PHASE_OFF(Js::GlobOptPhase, this->topFunc))
{
return false;
}
if (this->topFunc->GetHasTry())
{
#if defined(DBG_DUMP) || defined(ENABLE_DEBUG_CONFIG_OPTIONS)
char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
#endif
INLINE_TESTTRACE(_u("INLINING: Skip Inline: Has try\tCaller: %s (%s)\n"), this->topFunc->GetDisplayName(),
this->topFunc->GetDebugNumberSet(debugStringBuffer));
// Glob opt doesn't run on function with try, so we can't generate bailout for it
return false;
}
return InlineIntoInliner(topFunc);
}
bool InliningDecider::InlineIntoInliner(Js::FunctionBody *const inliner) const
{
Assert(inliner);
Assert(this->jitMode == ExecutionMode::FullJit);
#if defined(DBG_DUMP) || defined(ENABLE_DEBUG_CONFIG_OPTIONS)
char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
#endif
if (PHASE_OFF(Js::InlinePhase, inliner) ||
PHASE_OFF(Js::GlobOptPhase, inliner))
{
return false;
}
if (!inliner->HasDynamicProfileInfo())
{
INLINE_TESTTRACE(_u("INLINING: Skip Inline: No dynamic profile info\tCaller: %s (%s)\n"), inliner->GetDisplayName(),
inliner->GetDebugNumberSet(debugStringBuffer));
return false;
}
if (inliner->GetProfiledCallSiteCount() == 0 && !inliner->GetAnyDynamicProfileInfo()->HasLdFldCallSiteInfo())
{
INLINE_TESTTRACE_VERBOSE(_u("INLINING: Skip Inline: Leaf function\tCaller: %s (%s)\n"), inliner->GetDisplayName(),
inliner->GetDebugNumberSet(debugStringBuffer));
// Nothing to do
return false;
}
if (!inliner->GetAnyDynamicProfileInfo()->HasCallSiteInfo(inliner))
{
INLINE_TESTTRACE(_u("INLINING: Skip Inline: No call site info\tCaller: %s (#%d)\n"), inliner->GetDisplayName(),
inliner->GetDebugNumberSet(debugStringBuffer));
return false;
}
return true;
}
Js::FunctionInfo *InliningDecider::GetCallSiteFuncInfo(Js::FunctionBody *const inliner, const Js::ProfileId profiledCallSiteId, bool* isConstructorCall, bool* isPolymorphicCall)
{
Assert(inliner);
Assert(profiledCallSiteId < inliner->GetProfiledCallSiteCount());
const auto profileData = inliner->GetAnyDynamicProfileInfo();
Assert(profileData);
return profileData->GetCallSiteInfo(inliner, profiledCallSiteId, isConstructorCall, isPolymorphicCall);
}
uint16 InliningDecider::GetConstantArgInfo(Js::FunctionBody *const inliner, const Js::ProfileId profiledCallSiteId)
{
Assert(inliner);
Assert(profiledCallSiteId < inliner->GetProfiledCallSiteCount());
const auto profileData = inliner->GetAnyDynamicProfileInfo();
Assert(profileData);
return profileData->GetConstantArgInfo(profiledCallSiteId);
}
bool InliningDecider::HasCallSiteInfo(Js::FunctionBody *const inliner, const Js::ProfileId profiledCallSiteId)
{
Assert(inliner);
Assert(profiledCallSiteId < inliner->GetProfiledCallSiteCount());
const auto profileData = inliner->GetAnyDynamicProfileInfo();
Assert(profileData);
return profileData->HasCallSiteInfo(inliner, profiledCallSiteId);
}
Js::FunctionInfo *InliningDecider::InlineCallSite(Js::FunctionBody *const inliner, const Js::ProfileId profiledCallSiteId, uint recursiveInlineDepth)
{
bool isConstructorCall;
bool isPolymorphicCall;
Js::FunctionInfo *functionInfo = GetCallSiteFuncInfo(inliner, profiledCallSiteId, &isConstructorCall, &isPolymorphicCall);
if (functionInfo)
{
return Inline(inliner, functionInfo, isConstructorCall, false, GetConstantArgInfo(inliner, profiledCallSiteId), profiledCallSiteId, recursiveInlineDepth, true);
}
return nullptr;
}
uint InliningDecider::InlinePolymorphicCallSite(Js::FunctionBody *const inliner, const Js::ProfileId profiledCallSiteId,
Js::FunctionBody** functionBodyArray, uint functionBodyArrayLength, bool* canInlineArray, uint recursiveInlineDepth)
{
Assert(inliner);
Assert(profiledCallSiteId < inliner->GetProfiledCallSiteCount());
Assert(functionBodyArray);
const auto profileData = inliner->GetAnyDynamicProfileInfo();
Assert(profileData);
bool isConstructorCall;
if (!profileData->GetPolymorphicCallSiteInfo(inliner, profiledCallSiteId, &isConstructorCall, functionBodyArray, functionBodyArrayLength))
{
return 0;
}
uint inlineeCount = 0;
uint actualInlineeCount = 0;
for (inlineeCount = 0; inlineeCount < functionBodyArrayLength; inlineeCount++)
{
if (!functionBodyArray[inlineeCount])
{
AssertMsg(inlineeCount >= 2, "There are at least two polymorphic call site");
break;
}
if (Inline(inliner, functionBodyArray[inlineeCount]->GetFunctionInfo(), isConstructorCall, true /*isPolymorphicCall*/, 0, profiledCallSiteId, recursiveInlineDepth, false))
{
canInlineArray[inlineeCount] = true;
actualInlineeCount++;
}
}
if (inlineeCount != actualInlineeCount)
{
// We generate polymorphic dispatch and call even if there are no inlinees as it's seen to provide a perf boost
// Skip loop bodies for now as we do not handle re-jit scenarios for the bailouts from them
if (!PHASE_OFF(Js::PartialPolymorphicInlinePhase, inliner) && !this->isLoopBody)
{
#if defined(DBG_DUMP) || defined(ENABLE_DEBUG_CONFIG_OPTIONS)
char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
char16 debugStringBuffer2[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
#endif
INLINE_TESTTRACE(_u("Partial inlining of polymorphic call: %s (%s)\tCaller: %s (%s)\n"),
functionBodyArray[inlineeCount - 1]->GetDisplayName(), functionBodyArray[inlineeCount - 1]->GetDebugNumberSet(debugStringBuffer),
inliner->GetDisplayName(),
inliner->GetDebugNumberSet(debugStringBuffer2));
}
else
{
return 0;
}
}
return inlineeCount;
}
Js::FunctionInfo *InliningDecider::Inline(Js::FunctionBody *const inliner, Js::FunctionInfo* functionInfo,
bool isConstructorCall, bool isPolymorphicCall, uint16 constantArgInfo, Js::ProfileId callSiteId, uint recursiveInlineDepth, bool allowRecursiveInlining)
{
#if defined(DBG_DUMP) || defined(ENABLE_DEBUG_CONFIG_OPTIONS)
char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
char16 debugStringBuffer2[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
#endif
Js::FunctionProxy * proxy = functionInfo->GetFunctionProxy();
if (proxy && proxy->IsFunctionBody())
{
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));
return nullptr;
}
// Note: disable inline for debugger, as we can't bailout at return from function.
// Alternative can be generate this bailout as part of inline, which can be done later as perf improvement.
const auto inlinee = proxy->GetFunctionBody();
Assert(this->jitMode == ExecutionMode::FullJit);
if (PHASE_OFF(Js::InlinePhase, inlinee) ||
PHASE_OFF(Js::GlobOptPhase, inlinee) ||
!ContinueInliningUserDefinedFunctions(this->bytecodeInlinedCount) ||
this->isInDebugMode)
{
return nullptr;
}
if (functionInfo->IsDeferred() || inlinee->GetByteCode() == nullptr)
{
// DeferredParse...
INLINE_TESTTRACE(_u("INLINING: Skip Inline: No bytecode\tInlinee: %s (%s)\tCaller: %s (%s)\n"),
inlinee->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer), inliner->GetDisplayName(),
inliner->GetDebugNumberSet(debugStringBuffer2));
return nullptr;
}
if (inlinee->GetHasTry())
{
INLINE_TESTTRACE(_u("INLINING: Skip Inline: Has try\tInlinee: %s (%s)\tCaller: %s (%s)\n"),
inlinee->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer), inliner->GetDisplayName(),
inliner->GetDebugNumberSet(debugStringBuffer2));
return nullptr;
}
// This is a hard limit as the argOuts array is statically sized.
if (inlinee->GetInParamsCount() > Js::InlineeCallInfo::MaxInlineeArgoutCount)
{
INLINE_TESTTRACE(_u("INLINING: Skip Inline: Params count greater then MaxInlineeArgoutCount\tInlinee: %s (%s)\tParamcount: %d\tMaxInlineeArgoutCount: %d\tCaller: %s (%s)\n"),
inlinee->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer), inlinee->GetInParamsCount(), Js::InlineeCallInfo::MaxInlineeArgoutCount,
inliner->GetDisplayName(), inliner->GetDebugNumberSet(debugStringBuffer2));
return nullptr;
}
if (inlinee->GetInParamsCount() == 0)
{
// Inline candidate has no params, not even a this pointer. This can only be the global function,
// which we shouldn't inline.
INLINE_TESTTRACE(_u("INLINING: Skip Inline: Params count is zero!\tInlinee: %s (%s)\tParamcount: %d\tCaller: %s (%s)\n"),
inlinee->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer), inlinee->GetInParamsCount(),
inliner->GetDisplayName(), inliner->GetDebugNumberSet(debugStringBuffer2));
return nullptr;
}
if (inlinee->GetDontInline())
{
INLINE_TESTTRACE(_u("INLINING: Skip Inline: Do not inline\tInlinee: %s (%s)\tCaller: %s (%s)\n"),
inlinee->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer), inliner->GetDisplayName(),
inliner->GetDebugNumberSet(debugStringBuffer2));
return nullptr;
}
// Do not inline a call to a class constructor if it isn't part of a new expression since the call will throw a TypeError anyway.
if (inlinee->IsClassConstructor() && !isConstructorCall)
{
INLINE_TESTTRACE(_u("INLINING: Skip Inline: Class constructor without new keyword\tInlinee: %s (%s)\tCaller: %s (%s)\n"),
inlinee->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer), inliner->GetDisplayName(),
inliner->GetDebugNumberSet(debugStringBuffer2));
return nullptr;
}
if (!DeciderInlineIntoInliner(inlinee, inliner, isConstructorCall, isPolymorphicCall, constantArgInfo, recursiveInlineDepth, allowRecursiveInlining))
{
return nullptr;
}
#if defined(ENABLE_DEBUG_CONFIG_OPTIONS)
TraceInlining(inliner, inlinee->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer), inlinee->GetByteCodeCount(), this->topFunc, this->bytecodeInlinedCount, inlinee, callSiteId, this->isLoopBody);
#endif
this->bytecodeInlinedCount += inlinee->GetByteCodeCount();
return inlinee->GetFunctionInfo();
}
Js::OpCode builtInInlineCandidateOpCode;
ValueType builtInReturnType;
GetBuiltInInfo(functionInfo, &builtInInlineCandidateOpCode, &builtInReturnType);
if(builtInInlineCandidateOpCode == 0 && builtInReturnType.IsUninitialized())
{
return nullptr;
}
Assert(this->jitMode == ExecutionMode::FullJit);
if (builtInInlineCandidateOpCode != 0 &&
(
PHASE_OFF(Js::InlinePhase, inliner) ||
PHASE_OFF(Js::GlobOptPhase, inliner) ||
isConstructorCall
))
{
return nullptr;
}
// Note: for built-ins at this time we don't have enough data (the instr) to decide whether it's going to be inlined.
return functionInfo;
}
// TODO OOP JIT: add FunctionInfo interface so we can combine these?
/* static */
bool InliningDecider::GetBuiltInInfo(
const FunctionJITTimeInfo *const funcInfo,
Js::OpCode *const inlineCandidateOpCode,
ValueType *const returnType
)
{
Assert(funcInfo);
Assert(inlineCandidateOpCode);
Assert(returnType);
*inlineCandidateOpCode = (Js::OpCode)0;
*returnType = ValueType::Uninitialized;
if (funcInfo->HasBody())
{
return false;
}
return InliningDecider::GetBuiltInInfoCommon(
funcInfo->GetLocalFunctionId(),
inlineCandidateOpCode,
returnType);
}
/* static */
bool InliningDecider::GetBuiltInInfo(
Js::FunctionInfo *const funcInfo,
Js::OpCode *const inlineCandidateOpCode,
ValueType *const returnType
)
{
Assert(funcInfo);
Assert(inlineCandidateOpCode);
Assert(returnType);
*inlineCandidateOpCode = (Js::OpCode)0;
*returnType = ValueType::Uninitialized;
if (funcInfo->HasBody())
{
return false;
}
return InliningDecider::GetBuiltInInfoCommon(
funcInfo->GetLocalFunctionId(),
inlineCandidateOpCode,
returnType);
}
bool InliningDecider::GetBuiltInInfoCommon(
uint localFuncId,
Js::OpCode *const inlineCandidateOpCode,
ValueType *const returnType
)
{
// TODO: consider adding another column to JavascriptBuiltInFunctionList.h/LibraryFunction.h
// and getting helper method from there instead of multiple switch labels. And for return value types too.
switch (localFuncId)
{
case Js::JavascriptBuiltInFunction::Math_Abs:
*inlineCandidateOpCode = Js::OpCode::InlineMathAbs;
break;
case Js::JavascriptBuiltInFunction::Math_Acos:
*inlineCandidateOpCode = Js::OpCode::InlineMathAcos;
break;
case Js::JavascriptBuiltInFunction::Math_Asin:
*inlineCandidateOpCode = Js::OpCode::InlineMathAsin;
break;
case Js::JavascriptBuiltInFunction::Math_Atan:
*inlineCandidateOpCode = Js::OpCode::InlineMathAtan;
break;
case Js::JavascriptBuiltInFunction::Math_Atan2:
*inlineCandidateOpCode = Js::OpCode::InlineMathAtan2;
break;
case Js::JavascriptBuiltInFunction::Math_Cos:
*inlineCandidateOpCode = Js::OpCode::InlineMathCos;
break;
case Js::JavascriptBuiltInFunction::Math_Exp:
*inlineCandidateOpCode = Js::OpCode::InlineMathExp;
break;
case Js::JavascriptBuiltInFunction::Math_Log:
*inlineCandidateOpCode = Js::OpCode::InlineMathLog;
break;
case Js::JavascriptBuiltInFunction::Math_Pow:
*inlineCandidateOpCode = Js::OpCode::InlineMathPow;
break;
case Js::JavascriptBuiltInFunction::Math_Sin:
*inlineCandidateOpCode = Js::OpCode::InlineMathSin;
break;
case Js::JavascriptBuiltInFunction::Math_Sqrt:
*inlineCandidateOpCode = Js::OpCode::InlineMathSqrt;
break;
case Js::JavascriptBuiltInFunction::Math_Tan:
*inlineCandidateOpCode = Js::OpCode::InlineMathTan;
break;
case Js::JavascriptBuiltInFunction::Math_Floor:
*inlineCandidateOpCode = Js::OpCode::InlineMathFloor;
break;
case Js::JavascriptBuiltInFunction::Math_Ceil:
*inlineCandidateOpCode = Js::OpCode::InlineMathCeil;
break;
case Js::JavascriptBuiltInFunction::Math_Round:
*inlineCandidateOpCode = Js::OpCode::InlineMathRound;
break;
case Js::JavascriptBuiltInFunction::Math_Min:
*inlineCandidateOpCode = Js::OpCode::InlineMathMin;
break;
case Js::JavascriptBuiltInFunction::Math_Max:
*inlineCandidateOpCode = Js::OpCode::InlineMathMax;
break;
case Js::JavascriptBuiltInFunction::Math_Imul:
*inlineCandidateOpCode = Js::OpCode::InlineMathImul;
break;
case Js::JavascriptBuiltInFunction::Math_Clz32:
*inlineCandidateOpCode = Js::OpCode::InlineMathClz;
break;
case Js::JavascriptBuiltInFunction::Math_Random:
*inlineCandidateOpCode = Js::OpCode::InlineMathRandom;
break;
case Js::JavascriptBuiltInFunction::Math_Fround:
*inlineCandidateOpCode = Js::OpCode::InlineMathFround;
*returnType = ValueType::Float;
break;
case Js::JavascriptBuiltInFunction::JavascriptArray_Push:
*inlineCandidateOpCode = Js::OpCode::InlineArrayPush;
break;
case Js::JavascriptBuiltInFunction::JavascriptArray_Pop:
*inlineCandidateOpCode = Js::OpCode::InlineArrayPop;
break;
case Js::JavascriptBuiltInFunction::JavascriptArray_Concat:
case Js::JavascriptBuiltInFunction::JavascriptArray_Reverse:
case Js::JavascriptBuiltInFunction::JavascriptArray_Shift:
case Js::JavascriptBuiltInFunction::JavascriptArray_Slice:
case Js::JavascriptBuiltInFunction::JavascriptArray_Splice:
case Js::JavascriptBuiltInFunction::JavascriptString_Link:
case Js::JavascriptBuiltInFunction::JavascriptString_LocaleCompare:
goto CallDirectCommon;
case Js::JavascriptBuiltInFunction::JavascriptArray_Join:
case Js::JavascriptBuiltInFunction::JavascriptString_CharAt:
case Js::JavascriptBuiltInFunction::JavascriptString_Concat:
case Js::JavascriptBuiltInFunction::JavascriptString_FromCharCode:
case Js::JavascriptBuiltInFunction::JavascriptString_FromCodePoint:
case Js::JavascriptBuiltInFunction::JavascriptString_Replace:
case Js::JavascriptBuiltInFunction::JavascriptString_Slice:
case Js::JavascriptBuiltInFunction::JavascriptString_Substr:
case Js::JavascriptBuiltInFunction::JavascriptString_Substring:
case Js::JavascriptBuiltInFunction::JavascriptString_ToLocaleLowerCase:
case Js::JavascriptBuiltInFunction::JavascriptString_ToLocaleUpperCase:
case Js::JavascriptBuiltInFunction::JavascriptString_ToLowerCase:
case Js::JavascriptBuiltInFunction::JavascriptString_ToUpperCase:
case Js::JavascriptBuiltInFunction::JavascriptString_Trim:
case Js::JavascriptBuiltInFunction::JavascriptString_TrimLeft:
case Js::JavascriptBuiltInFunction::JavascriptString_TrimRight:
case Js::JavascriptBuiltInFunction::JavascriptString_PadStart:
case Js::JavascriptBuiltInFunction::JavascriptString_PadEnd:
*returnType = ValueType::String;
goto CallDirectCommon;
case Js::JavascriptBuiltInFunction::JavascriptArray_Includes:
case Js::JavascriptBuiltInFunction::JavascriptObject_HasOwnProperty:
case Js::JavascriptBuiltInFunction::JavascriptArray_IsArray:
*returnType = ValueType::Boolean;
goto CallDirectCommon;
case Js::JavascriptBuiltInFunction::JavascriptArray_IndexOf:
case Js::JavascriptBuiltInFunction::JavascriptArray_LastIndexOf:
case Js::JavascriptBuiltInFunction::JavascriptArray_Unshift:
case Js::JavascriptBuiltInFunction::JavascriptString_CharCodeAt:
case Js::JavascriptBuiltInFunction::JavascriptString_IndexOf:
case Js::JavascriptBuiltInFunction::JavascriptString_LastIndexOf:
case Js::JavascriptBuiltInFunction::JavascriptString_Search:
case Js::JavascriptBuiltInFunction::JavascriptRegExp_SymbolSearch:
case Js::JavascriptBuiltInFunction::GlobalObject_ParseInt:
*returnType = ValueType::GetNumberAndLikelyInt(true);
goto CallDirectCommon;
case Js::JavascriptBuiltInFunction::JavascriptString_Split:
*returnType = ValueType::GetObject(ObjectType::Array).SetHasNoMissingValues(true).SetArrayTypeId(Js::TypeIds_Array);
goto CallDirectCommon;
case Js::JavascriptBuiltInFunction::JavascriptString_Match:
case Js::JavascriptBuiltInFunction::JavascriptRegExp_Exec:
*returnType =
ValueType::GetObject(ObjectType::Array).SetHasNoMissingValues(true).SetArrayTypeId(Js::TypeIds_Array)
.Merge(ValueType::Null);
goto CallDirectCommon;
CallDirectCommon:
*inlineCandidateOpCode = Js::OpCode::CallDirect;
break;
case Js::JavascriptBuiltInFunction::JavascriptFunction_Apply:
*inlineCandidateOpCode = Js::OpCode::InlineFunctionApply;
break;
case Js::JavascriptBuiltInFunction::JavascriptFunction_Call:
*inlineCandidateOpCode = Js::OpCode::InlineFunctionCall;
break;
// The following are not currently inlined, but are tracked for their return type
// TODO: Add more built-ins that return objects. May consider tracking all built-ins.
case Js::JavascriptBuiltInFunction::JavascriptArray_NewInstance:
*returnType = ValueType::GetObject(ObjectType::Array).SetHasNoMissingValues(true).SetArrayTypeId(Js::TypeIds_Array);
break;
case Js::JavascriptBuiltInFunction::Int8Array_NewInstance:
#ifdef _M_X64
*returnType = (!PHASE_OFF1(Js::TypedArrayVirtualPhase)) ? ValueType::GetObject(ObjectType::Int8MixedArray) : ValueType::GetObject(ObjectType::Int8Array);
#else
*returnType = ValueType::GetObject(ObjectType::Int8Array);
#endif
break;
case Js::JavascriptBuiltInFunction::Uint8Array_NewInstance:
#ifdef _M_X64
*returnType = (!PHASE_OFF1(Js::TypedArrayVirtualPhase)) ? ValueType::GetObject(ObjectType::Uint8MixedArray) : ValueType::GetObject(ObjectType::Uint8Array);
#else
*returnType = ValueType::GetObject(ObjectType::Uint8Array);
#endif
break;
case Js::JavascriptBuiltInFunction::Uint8ClampedArray_NewInstance:
#ifdef _M_X64
*returnType = (!PHASE_OFF1(Js::TypedArrayVirtualPhase)) ? ValueType::GetObject(ObjectType::Uint8ClampedMixedArray) : ValueType::GetObject(ObjectType::Uint8ClampedArray);
#else
*returnType = ValueType::GetObject(ObjectType::Uint8ClampedArray);
#endif
break;
case Js::JavascriptBuiltInFunction::Int16Array_NewInstance:
#ifdef _M_X64
*returnType = (!PHASE_OFF1(Js::TypedArrayVirtualPhase)) ? ValueType::GetObject(ObjectType::Int16MixedArray) : ValueType::GetObject(ObjectType::Int16Array);
#else
*returnType = ValueType::GetObject(ObjectType::Int16Array);
#endif
break;
case Js::JavascriptBuiltInFunction::Uint16Array_NewInstance:
#ifdef _M_X64
*returnType = (!PHASE_OFF1(Js::TypedArrayVirtualPhase)) ? ValueType::GetObject(ObjectType::Uint16MixedArray) : ValueType::GetObject(ObjectType::Uint16Array);
#else
*returnType = ValueType::GetObject(ObjectType::Uint16Array);
#endif
break;
case Js::JavascriptBuiltInFunction::Int32Array_NewInstance:
#ifdef _M_X64
*returnType = (!PHASE_OFF1(Js::TypedArrayVirtualPhase)) ? ValueType::GetObject(ObjectType::Int32MixedArray) : ValueType::GetObject(ObjectType::Int32Array);
#else
*returnType = ValueType::GetObject(ObjectType::Int32Array);
#endif
break;
case Js::JavascriptBuiltInFunction::Uint32Array_NewInstance:
#ifdef _M_X64
*returnType = (!PHASE_OFF1(Js::TypedArrayVirtualPhase)) ? ValueType::GetObject(ObjectType::Uint32MixedArray) : ValueType::GetObject(ObjectType::Uint32Array);
#else
*returnType = ValueType::GetObject(ObjectType::Uint32Array);
#endif
break;
case Js::JavascriptBuiltInFunction::Float32Array_NewInstance:
#ifdef _M_X64
*returnType = (!PHASE_OFF1(Js::TypedArrayVirtualPhase)) ? ValueType::GetObject(ObjectType::Float32MixedArray) : ValueType::GetObject(ObjectType::Float32Array);
#else
*returnType = ValueType::GetObject(ObjectType::Float32Array);
#endif
break;
case Js::JavascriptBuiltInFunction::Float64Array_NewInstance:
#ifdef _M_X64
*returnType = (!PHASE_OFF1(Js::TypedArrayVirtualPhase)) ? ValueType::GetObject(ObjectType::Float64MixedArray) : ValueType::GetObject(ObjectType::Float64Array);
#else
*returnType = ValueType::GetObject(ObjectType::Float64Array);
#endif
break;
case Js::JavascriptBuiltInFunction::Int64Array_NewInstance:
*returnType = ValueType::GetObject(ObjectType::Int64Array);
break;
case Js::JavascriptBuiltInFunction::Uint64Array_NewInstance:
*returnType = ValueType::GetObject(ObjectType::Uint64Array);
break;
case Js::JavascriptBuiltInFunction::BoolArray_NewInstance:
*returnType = ValueType::GetObject(ObjectType::BoolArray);
break;
case Js::JavascriptBuiltInFunction::CharArray_NewInstance:
*returnType = ValueType::GetObject(ObjectType::CharArray);
break;
#ifdef ENABLE_DOM_FAST_PATH
case Js::JavascriptBuiltInFunction::DOMFastPathGetter:
*inlineCandidateOpCode = Js::OpCode::DOMFastPathGetter;
break;
#endif
#ifdef ENABLE_SIMDJS
// SIMD_JS
// we only inline, and hence type-spec on IA
#if defined(_M_X64) || defined(_M_IX86)
default:
{
#if 0 // TODO OOP JIT, inline SIMD
// inline only if simdjs and simd128 type-spec is enabled.
if (scriptContext->GetConfig()->IsSimdjsEnabled() && SIMD128_TYPE_SPEC_FLAG)
{
*inlineCandidateOpCode = scriptContext->GetThreadContext()->GetSimdOpcodeFromFuncInfo(funcInfo);
}
else
#endif
{
return false;
}
}
#endif
#endif // ENABLE_SIMDJS
}
return true;
}
bool InliningDecider::CanRecursivelyInline(Js::FunctionBody * inlinee, Js::FunctionBody *inliner, bool allowRecursiveInlining, uint recursiveInlineDepth)
{
#if defined(DBG_DUMP) || defined(ENABLE_DEBUG_CONFIG_OPTIONS)
char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
char16 debugStringBuffer2[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
#endif
if (!PHASE_OFF(Js::InlineRecursivePhase, inliner)
&& allowRecursiveInlining
&& inlinee == inliner
&& inlinee->CanInlineRecursively(recursiveInlineDepth))
{
INLINE_TESTTRACE(_u("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(_u("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 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 InliningDecider::DeciderInlineIntoInliner(Js::FunctionBody * inlinee, Js::FunctionBody * inliner, bool isConstructorCall, bool isPolymorphicCall, uint16 constantArgInfo, uint recursiveInlineDepth, bool allowRecursiveInlining)
{
if (!CanRecursivelyInline(inlinee, inliner, allowRecursiveInlining, recursiveInlineDepth))
{
return false;
}
if (inlinee->GetIsAsmjsMode() || inliner->GetIsAsmjsMode())
{
return false;
}
if (inlinee->IsJsBuiltInForceInline() ||
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;
}
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.
uint16 mask = constantArgInfo & inlinee->m_argUsedForBranch;
if (mask && inlineeByteCodeCount < (uint)CONFIG_FLAG(ConstantArgumentInlineThreshold))
{
return true;
}
int inlineThreshold = threshold.inlineThreshold;
if (!isPolymorphicCall && !isConstructorCall && IsInlineeLeaf(inlinee) && (inlinee->GetLoopCount() <= 2))
{
// Inlinee is a leaf function
if (inlinee->GetLoopCount() == 0 || GetNumberOfInlineesWithLoop() <= (uint)threshold.maxNumberOfInlineesWithLoop) // Don't inlinee too many inlinees with loops.
{
// Negative LeafInlineThreshold disable the threshold
if (threshold.leafInlineThreshold >= 0)
{
inlineThreshold += threshold.leafInlineThreshold - threshold.inlineThreshold;
}
}
}
#if ENABLE_DEBUG_CONFIG_OPTIONS
char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
char16 debugStringBuffer2[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
char16 debugStringBuffer3[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
#endif
if (inlinee->GetHasLoops())
{
if (threshold.loopInlineThreshold < 0 || // Negative LoopInlineThreshold disable inlining with loop
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 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(),
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;
}
else
{
inlineThreshold -= (threshold.inlineThreshold > threshold.loopInlineThreshold) ? threshold.inlineThreshold - threshold.loopInlineThreshold : 0;
}
}
if (isPolymorphicCall)
{
if (threshold.polymorphicInlineThreshold < 0 || // Negative PolymorphicInlineThreshold disable inlining
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"),
threshold.polymorphicInlineThreshold,
inlinee->GetByteCodeCount(),
inlinee->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer),
inliner->GetDisplayName(), inliner->GetDebugNumberSet(debugStringBuffer2),
topFunc->GetDisplayName(), topFunc->GetDebugNumberSet(debugStringBuffer3));
return false;
}
else
{
inlineThreshold -= (threshold.inlineThreshold > threshold.polymorphicInlineThreshold) ? threshold.inlineThreshold - threshold.polymorphicInlineThreshold : 0;
}
}
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 not dependent on bytecodeInlinedThreshold.
return true;
}
// Negative ConstructorInlineThreshold always disable constructor inlining
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(),
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;
}
else
{
inlineThreshold -= (threshold.inlineThreshold > threshold.constructorInlineThreshold) ? threshold.inlineThreshold - threshold.constructorInlineThreshold : 0;
}
}
if (threshold.forLoopBody)
{
inlineThreshold /= CONFIG_FLAG(InlineInLoopBodyScaleDownFactor);
}
if (inlineThreshold > 0 && inlineeByteCodeCount <= (uint)inlineThreshold)
{
if (inlinee->GetLoopCount())
{
IncrementNumberOfInlineesWithLoop();
}
return true;
}
else
{
return false;
}
}
bool InliningDecider::ContinueInliningUserDefinedFunctions(uint32 bytecodeInlinedCount) const
{
#if ENABLE_DEBUG_CONFIG_OPTIONS
char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
#endif
if (PHASE_FORCE(Js::InlinePhase, this->topFunc) || bytecodeInlinedCount <= (uint)this->threshold.inlineCountMax)
{
return true;
}
INLINE_TESTTRACE(_u("INLINING: Skip Inline: InlineCountMax threshold %d, reached: %s (#%s)\n"),
(uint)this->threshold.inlineCountMax,
this->topFunc->GetDisplayName(), this->topFunc->GetDebugNumberSet(debugStringBuffer));
return false;
}
#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, 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 %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(),
callSiteId
);
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->GetDisplayName(), topFunc->GetDebugNumberSet(debugStringBuffer2), topFunc->GetByteCodeCount(),
callSiteId
);
// Now Trace inlining across files cases
if (builtIn != -1) // built-in functions
{
return;
}
Assert(inliner && inlinee);
if (inliner->GetSourceContextId() != inlinee->GetSourceContextId())
{
INLINE_TESTTRACE(_u("INLINING_ACROSS_FILES: Inlinee: %s (%s)\tSize: %d\tCaller: %s (%s)\tSize: %d\tInlineCount: %d\tRoot: %s (%s)\tSize: %d\n"),
inlinee->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer), inlinee->GetByteCodeCount(),
inliner->GetDisplayName(), inliner->GetDebugNumberSet(debugStringBuffer2), inliner->GetByteCodeCount(), inlinedByteCodeCount,
topFunc->GetDisplayName(), topFunc->GetDebugNumberSet(debugStringBuffer3), topFunc->GetByteCodeCount()
);
INLINE_TRACE(_u("INLINING_ACROSS_FILES: Inlinee: %s (%s)\tSize: %d\tCaller: %s (%s)\tSize: %d\tInlineCount: %d\tRoot: %s (%s)\tSize: %d\n"),
inlinee->GetDisplayName(), inlinee->GetDebugNumberSet(debugStringBuffer), inlinee->GetByteCodeCount(),
inliner->GetDisplayName(), inliner->GetDebugNumberSet(debugStringBuffer2), inliner->GetByteCodeCount(), inlinedByteCodeCount,
topFunc->GetDisplayName(), topFunc->GetDebugNumberSet(debugStringBuffer3), topFunc->GetByteCodeCount()
);
}
}
#endif