blob: a1f3d54b33af219a89d691bb01d316a0458eaec0 [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 "RuntimeByteCodePch.h"
FuncInfo::FuncInfo(
const char16 *name,
ArenaAllocator *alloc,
Scope *paramScope,
Scope *bodyScope,
ParseNode *pnode,
Js::ParseableFunctionInfo* byteCodeFunction)
: alloc(alloc),
varRegsCount(0),
constRegsCount(InitialConstRegsCount),
inArgsCount(0),
innerScopeCount(0),
currentInnerScopeIndex((uint)-1),
firstTmpReg(Js::Constants::NoRegister),
curTmpReg(Js::Constants::NoRegister),
outArgsMaxDepth(0),
outArgsCurrentExpr(0),
#if DBG
outArgsDepth(0),
#endif
name(name),
nullConstantRegister(Js::Constants::NoRegister),
undefinedConstantRegister(Js::Constants::NoRegister),
trueConstantRegister(Js::Constants::NoRegister),
falseConstantRegister(Js::Constants::NoRegister),
thisPointerRegister(Js::Constants::NoRegister),
superRegister(Js::Constants::NoRegister),
superCtorRegister(Js::Constants::NoRegister),
newTargetRegister(Js::Constants::NoRegister),
envRegister(Js::Constants::NoRegister),
frameObjRegister(Js::Constants::NoRegister),
frameSlotsRegister(Js::Constants::NoRegister),
paramSlotsRegister(Js::Constants::NoRegister),
frameDisplayRegister(Js::Constants::NoRegister),
funcObjRegister(Js::Constants::NoRegister),
localClosureReg(Js::Constants::NoRegister),
yieldRegister(Js::Constants::NoRegister),
paramScope(paramScope),
bodyScope(bodyScope),
funcExprScope(nullptr),
root(pnode),
capturedSyms(nullptr),
capturedSymMap(nullptr),
currentChildFunction(nullptr),
currentChildScope(nullptr),
callsEval(false),
childCallsEval(false),
hasArguments(false),
hasHeapArguments(false),
isTopLevelEventHandler(false),
hasLocalInClosure(false),
hasClosureReference(false),
hasCachedScope(false),
funcExprNameReference(false),
applyEnclosesArgs(false),
escapes(false),
hasDeferredChild(false),
hasRedeferrableChild(false),
hasLoop(false),
hasEscapedUseNestedFunc(false),
needEnvRegister(false),
isBodyAndParamScopeMerged(true),
#if DBG
isReused(false),
#endif
staticFuncId(-1),
inlineCacheMap(nullptr),
slotProfileIdMap(alloc),
argsPlaceHolderSlotCount(0),
thisScopeSlot(Js::Constants::NoProperty),
superScopeSlot(Js::Constants::NoProperty),
superCtorScopeSlot(Js::Constants::NoProperty),
newTargetScopeSlot(Js::Constants::NoProperty),
isThisLexicallyCaptured(false),
isSuperLexicallyCaptured(false),
isSuperCtorLexicallyCaptured(false),
isNewTargetLexicallyCaptured(false),
inlineCacheCount(0),
rootObjectLoadInlineCacheCount(0),
rootObjectLoadMethodInlineCacheCount(0),
rootObjectStoreInlineCacheCount(0),
isInstInlineCacheCount(0),
referencedPropertyIdCount(0),
argumentsSymbol(nullptr),
nonUserNonTempRegistersToInitialize(alloc),
constantToRegister(alloc, 17),
stringToRegister(alloc, 17),
doubleConstantToRegister(alloc, 17),
stringTemplateCallsiteRegisterMap(alloc, 17),
targetStatements(alloc),
nextForInLoopLevel(0),
maxForInLoopLevel(0)
{
this->byteCodeFunction = byteCodeFunction;
if (bodyScope != nullptr)
{
bodyScope->SetFunc(this);
}
if (paramScope != nullptr)
{
paramScope->SetFunc(this);
}
if (pnode && pnode->sxFnc.NestedFuncEscapes())
{
this->SetHasMaybeEscapedNestedFunc(DebugOnly(_u("Child")));
}
}
bool FuncInfo::IsGlobalFunction() const
{
return root && root->nop == knopProg;
}
bool FuncInfo::IsDeferred() const
{
return root && root->sxFnc.pnodeBody == nullptr;
}
bool FuncInfo::IsRedeferrable() const
{
return byteCodeFunction && byteCodeFunction->CanBeDeferred();
}
BOOL FuncInfo::HasSuperReference() const
{
return root->sxFnc.HasSuperReference();
}
BOOL FuncInfo::HasDirectSuper() const
{
return root->sxFnc.HasDirectSuper();
}
BOOL FuncInfo::IsClassMember() const
{
return root->sxFnc.IsClassMember();
}
BOOL FuncInfo::IsLambda() const
{
return root->sxFnc.IsLambda();
}
BOOL FuncInfo::IsClassConstructor() const
{
return root->sxFnc.IsClassConstructor();
}
BOOL FuncInfo::IsBaseClassConstructor() const
{
return root->sxFnc.IsBaseClassConstructor();
}
void FuncInfo::EnsureThisScopeSlot()
{
if (this->thisScopeSlot == Js::Constants::NoProperty)
{
// In case of split scope param and body has separate closures. So we have to use different scope slots for them.
Scope* scope = this->IsBodyAndParamScopeMerged() ? this->bodyScope : this->paramScope;
Scope* currentScope = scope->IsGlobalEvalBlockScope() ? this->GetGlobalEvalBlockScope() : scope;
this->thisScopeSlot = currentScope->AddScopeSlot();
}
}
void FuncInfo::EnsureSuperScopeSlot()
{
if (this->superScopeSlot == Js::Constants::NoProperty)
{
// In case of split scope param and body has separate closures. So we have to use different scope slots for them.
Scope* scope = this->IsBodyAndParamScopeMerged() ? this->bodyScope : this->paramScope;
this->superScopeSlot = scope->AddScopeSlot();
}
}
void FuncInfo::EnsureSuperCtorScopeSlot()
{
if (this->superCtorScopeSlot == Js::Constants::NoProperty)
{
// In case of split scope param and body has separate closures. So we have to use different scope slots for them.
Scope* scope = this->IsBodyAndParamScopeMerged() ? this->bodyScope : this->paramScope;
this->superCtorScopeSlot = scope->AddScopeSlot();
}
}
void FuncInfo::EnsureNewTargetScopeSlot()
{
if (this->newTargetScopeSlot == Js::Constants::NoProperty)
{
// In case of split scope param and body has separate closures. So we have to use different scope slots for them.
Scope* scope = this->IsBodyAndParamScopeMerged() ? this->bodyScope : this->paramScope;
this->newTargetScopeSlot = scope->AddScopeSlot();
}
}
Scope *
FuncInfo::GetGlobalBlockScope() const
{
Assert(this->IsGlobalFunction());
Scope * scope = this->root->sxFnc.pnodeScopes->sxBlock.scope;
Assert(scope == nullptr || scope == this->GetBodyScope() || scope->GetEnclosingScope() == this->GetBodyScope());
return scope;
}
Scope * FuncInfo::GetGlobalEvalBlockScope() const
{
Scope * globalEvalBlockScope = this->GetGlobalBlockScope();
Assert(globalEvalBlockScope->GetEnclosingScope() == this->GetBodyScope());
Assert(globalEvalBlockScope->GetScopeType() == ScopeType_GlobalEvalBlock);
return globalEvalBlockScope;
}
uint FuncInfo::FindOrAddReferencedPropertyId(Js::PropertyId propertyId)
{
Assert(propertyId != Js::Constants::NoProperty);
Assert(referencedPropertyIdToMapIndex != nullptr);
if (propertyId < TotalNumberOfBuiltInProperties)
{
return propertyId;
}
uint index;
if (!referencedPropertyIdToMapIndex->TryGetValue(propertyId, &index))
{
index = this->NewReferencedPropertyId();
referencedPropertyIdToMapIndex->Add(propertyId, index);
}
return index + TotalNumberOfBuiltInProperties;
}
uint FuncInfo::FindOrAddRootObjectInlineCacheId(Js::PropertyId propertyId, bool isLoadMethod, bool isStore)
{
Assert(propertyId != Js::Constants::NoProperty);
Assert(!isLoadMethod || !isStore);
uint cacheId;
RootObjectInlineCacheIdMap * idMap = isStore ? rootObjectStoreInlineCacheMap : isLoadMethod ? rootObjectLoadMethodInlineCacheMap : rootObjectLoadInlineCacheMap;
if (!idMap->TryGetValue(propertyId, &cacheId))
{
cacheId = isStore ? this->NewRootObjectStoreInlineCache() : isLoadMethod ? this->NewRootObjectLoadMethodInlineCache() : this->NewRootObjectLoadInlineCache();
idMap->Add(propertyId, cacheId);
}
return cacheId;
}
#if DBG_DUMP
void FuncInfo::Dump()
{
Output::Print(_u("FuncInfo: CallsEval:%s ChildCallsEval:%s HasArguments:%s HasHeapArguments:%s\n"),
IsTrueOrFalse(this->GetCallsEval()),
IsTrueOrFalse(this->GetChildCallsEval()),
IsTrueOrFalse(this->GetHasArguments()),
IsTrueOrFalse(this->GetHasHeapArguments()));
}
#endif
Js::RegSlot FuncInfo::AcquireLoc(ParseNode *pnode)
{
// Assign a new temp pseudo-register to this expression.
if (pnode->location == Js::Constants::NoRegister)
{
pnode->location = this->AcquireTmpRegister();
}
return pnode->location;
}
Js::RegSlot FuncInfo::AcquireTmpRegister()
{
Assert(this->firstTmpReg != Js::Constants::NoRegister);
// Allocate a new temp pseudo-register, increasing the locals count if necessary.
Assert(this->curTmpReg <= this->varRegsCount && this->curTmpReg >= this->firstTmpReg);
Js::RegSlot tmpReg = this->curTmpReg;
UInt32Math::Inc(this->curTmpReg);
if (this->curTmpReg > this->varRegsCount)
{
this->varRegsCount = this->curTmpReg;
}
return tmpReg;
}
void FuncInfo::ReleaseLoc(ParseNode *pnode)
{
// Release the temp assigned to this expression so it can be re-used.
if (pnode && pnode->location != Js::Constants::NoRegister)
{
this->ReleaseTmpRegister(pnode->location);
}
}
void FuncInfo::ReleaseLoad(ParseNode *pnode)
{
// Release any temp register(s) acquired by an EmitLoad.
switch (pnode->nop)
{
case knopDot:
case knopIndex:
case knopCall:
this->ReleaseReference(pnode);
break;
}
this->ReleaseLoc(pnode);
}
void FuncInfo::ReleaseReference(ParseNode *pnode)
{
// Release any temp(s) assigned to this reference expression so they can be re-used.
switch (pnode->nop)
{
case knopDot:
this->ReleaseLoc(pnode->sxBin.pnode1);
break;
case knopIndex:
this->ReleaseLoc(pnode->sxBin.pnode2);
this->ReleaseLoc(pnode->sxBin.pnode1);
break;
case knopName:
// Do nothing (see EmitReference)
break;
case knopCall:
case knopNew:
// For call/new, we have to release the ArgOut register(s) in reverse order,
// but we have the args in a singly linked list.
// Fortunately, we know that the set we have to release is sequential.
// So find the endpoints of the list and release them in descending order.
if (pnode->sxCall.pnodeArgs)
{
ParseNode *pnodeArg = pnode->sxCall.pnodeArgs;
Js::RegSlot firstArg = Js::Constants::NoRegister;
Js::RegSlot lastArg = Js::Constants::NoRegister;
if (pnodeArg->nop == knopList)
{
do
{
if (this->IsTmpReg(pnodeArg->sxBin.pnode1->location))
{
lastArg = pnodeArg->sxBin.pnode1->location;
if (firstArg == Js::Constants::NoRegister)
{
firstArg = lastArg;
}
}
pnodeArg = pnodeArg->sxBin.pnode2;
}
while (pnodeArg->nop == knopList);
}
if (this->IsTmpReg(pnodeArg->location))
{
lastArg = pnodeArg->location;
if (firstArg == Js::Constants::NoRegister)
{
// Just one: first and last point to the same node.
firstArg = lastArg;
}
}
if (lastArg != Js::Constants::NoRegister)
{
Assert(firstArg != Js::Constants::NoRegister);
Assert(lastArg >= firstArg);
do
{
// Walk down from last to first.
this->ReleaseTmpRegister(lastArg);
} while (lastArg-- > firstArg); // these are unsigned, so (--lastArg >= firstArg) will cause an infinite loop if firstArg is 0 (although that shouldn't happen)
}
}
// Now release the call target.
switch (pnode->sxCall.pnodeTarget->nop)
{
case knopDot:
case knopIndex:
this->ReleaseReference(pnode->sxCall.pnodeTarget);
this->ReleaseLoc(pnode->sxCall.pnodeTarget);
break;
default:
this->ReleaseLoad(pnode->sxCall.pnodeTarget);
break;
}
break;
default:
this->ReleaseLoc(pnode);
break;
}
}
void FuncInfo::ReleaseTmpRegister(Js::RegSlot tmpReg)
{
// Put this reg back on top of the temp stack (if it's a temp).
Assert(tmpReg != Js::Constants::NoRegister);
if (this->IsTmpReg(tmpReg))
{
Assert(tmpReg == this->curTmpReg - 1);
this->curTmpReg--;
}
}
Js::RegSlot FuncInfo::InnerScopeToRegSlot(Scope *scope) const
{
Js::RegSlot reg = FirstInnerScopeReg();
Assert(reg != Js::Constants::NoRegister);
uint32 index = scope->GetInnerScopeIndex();
return reg + index;
}
Js::RegSlot FuncInfo::FirstInnerScopeReg() const
{
// FunctionBody stores this as a mapped reg. Callers of this function want the pre-mapped value.
Js::RegSlot reg = this->GetParsedFunctionBody()->GetFirstInnerScopeRegister();
Assert(reg != Js::Constants::NoRegister);
return reg - this->constRegsCount;
}
void FuncInfo::SetFirstInnerScopeReg(Js::RegSlot reg)
{
// Just forward to the FunctionBody.
this->GetParsedFunctionBody()->MapAndSetFirstInnerScopeRegister(reg);
}
void FuncInfo::AddCapturedSym(Symbol *sym)
{
if (this->capturedSyms == nullptr)
{
this->capturedSyms = Anew(alloc, SymbolTable, alloc);
}
this->capturedSyms->AddNew(sym);
}
void FuncInfo::OnStartVisitFunction(ParseNode *pnodeFnc)
{
Assert(pnodeFnc->nop == knopFncDecl);
Assert(this->GetCurrentChildFunction() == nullptr);
this->SetCurrentChildFunction(pnodeFnc->sxFnc.funcInfo);
}
void FuncInfo::OnEndVisitFunction(ParseNode *pnodeFnc)
{
Assert(pnodeFnc->nop == knopFncDecl);
Assert(this->GetCurrentChildFunction() == pnodeFnc->sxFnc.funcInfo);
pnodeFnc->sxFnc.funcInfo->SetCurrentChildScope(nullptr);
this->SetCurrentChildFunction(nullptr);
}
void FuncInfo::OnStartVisitScope(Scope *scope, bool *pisMergedScope)
{
*pisMergedScope = false;
if (scope == nullptr)
{
return;
}
Scope* childScope = this->GetCurrentChildScope();
if (childScope)
{
if (scope->GetScopeType() == ScopeType_Parameter)
{
Assert(childScope->GetEnclosingScope() == scope);
}
else if (childScope->GetScopeType() == ScopeType_Parameter
&& childScope->GetFunc()->IsBodyAndParamScopeMerged()
&& scope->GetScopeType() == ScopeType_Block)
{
// If param and body are merged then the class declaration in param scope will have body as the parent
*pisMergedScope = true;
Assert(childScope == scope->GetEnclosingScope()->GetEnclosingScope());
}
else
{
Assert(childScope == scope->GetEnclosingScope());
}
}
this->SetCurrentChildScope(scope);
return;
}
void FuncInfo::OnEndVisitScope(Scope *scope, bool isMergedScope)
{
if (scope == nullptr)
{
return;
}
Assert(this->GetCurrentChildScope() == scope || (scope->GetScopeType() == ScopeType_Parameter && this->GetParamScope() == scope));
this->SetCurrentChildScope(isMergedScope ? scope->GetEnclosingScope()->GetEnclosingScope() : scope->GetEnclosingScope());
}
CapturedSymMap *FuncInfo::EnsureCapturedSymMap()
{
if (this->capturedSymMap == nullptr)
{
this->capturedSymMap = Anew(alloc, CapturedSymMap, alloc);
}
return this->capturedSymMap;
}
void FuncInfo::SetHasMaybeEscapedNestedFunc(DebugOnly(char16 const * reason))
{
if (PHASE_TESTTRACE(Js::StackFuncPhase, this->byteCodeFunction) && !hasEscapedUseNestedFunc)
{
char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
char16 const * r = _u("");
DebugOnly(r = reason);
Output::Print(_u("HasMaybeEscapedNestedFunc (%s): %s (function %s)\n"),
r,
this->byteCodeFunction->GetDisplayName(),
this->byteCodeFunction->GetDebugNumberSet(debugStringBuffer));
Output::Flush();
}
hasEscapedUseNestedFunc = true;
}
uint FuncInfo::AcquireInnerScopeIndex()
{
uint index = this->currentInnerScopeIndex;
if (index == (uint)-1)
{
index = 0;
}
else
{
index++;
if (index == (uint)-1)
{
Js::Throw::OutOfMemory();
}
}
if (index == this->innerScopeCount)
{
this->innerScopeCount = index + 1;
}
this->currentInnerScopeIndex = index;
return index;
}
void FuncInfo::ReleaseInnerScopeIndex()
{
uint index = this->currentInnerScopeIndex;
Assert(index != (uint)-1);
if (index == 0)
{
index = (uint)-1;
}
else
{
index--;
}
this->currentInnerScopeIndex = index;
}