blob: 98533e8f0e624e393f874f06021dd09f28ebdb0e [file]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Copyright (c) ChakraCore Project Contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include "RuntimeByteCodePch.h"
#include "FormalsUtil.h"
#include "Language/AsmJs.h"
#include "ConfigFlagsList.h"
void EmitReference(ParseNode *pnode, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo);
void EmitAssignment(ParseNode *asgnNode, ParseNode *lhs, Js::RegSlot rhsLocation, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo);
void EmitLoad(ParseNode *rhs, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo);
void EmitCall(ParseNodeCall* pnodeCall, ByteCodeGenerator* byteCodeGenerator, FuncInfo* funcInfo, BOOL fReturnValue, BOOL fEvaluateComponents, Js::RegSlot overrideThisLocation = Js::Constants::NoRegister, Js::RegSlot newTargetLocation = Js::Constants::NoRegister);
void EmitStartupYield(ByteCodeGenerator* byteCodeGenerator, FuncInfo* funcInfo);
void EmitYield(Js::RegSlot resultReg, Js::RegSlot inputReg, ByteCodeGenerator* byteCodeGenerator, FuncInfo* funcInfo);
void EmitAwait(Js::RegSlot resultReg, Js::RegSlot inputReg, ByteCodeGenerator* byteCodeGenerator, FuncInfo* funcInfo);
void EmitUseBeforeDeclaration(Symbol *sym, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo);
void EmitUseBeforeDeclarationRuntimeError(ByteCodeGenerator *byteCodeGenerator, Js::RegSlot location);
void VisitClearTmpRegs(ParseNode * pnode, ByteCodeGenerator * byteCodeGenerator, FuncInfo * funcInfo);
bool CallTargetIsArray(ParseNode *pnode)
{
return pnode->nop == knopName && pnode->AsParseNodeName()->PropertyIdFromNameNode() == Js::PropertyIds::Array;
}
#define STARTSTATEMENET_IFTOPLEVEL(isTopLevel, pnode) \
if ((isTopLevel)) \
{ \
byteCodeGenerator->StartStatement(pnode); \
}
#define ENDSTATEMENET_IFTOPLEVEL(isTopLevel, pnode) \
if ((isTopLevel)) \
{ \
byteCodeGenerator->EndStatement(pnode); \
}
DynamicLoadRecord::DynamicLoadRecord() : kind(DynamicLoadKind::Invalid), label(Js::Constants::NoByteCodeOffset), instance(Js::Constants::NoSlot) {}
BOOL MayHaveSideEffectOnNode(ParseNode *pnode, ParseNode *pnodeSE, ByteCodeGenerator *byteCodeGenerator)
{
// Try to determine whether pnodeSE (SE = side effect) may kill the named var represented by pnode.
if (pnode->nop == knopComputedName)
{
pnode = pnode->AsParseNodeUni()->pnode1;
}
if (pnode->nop != knopName)
{
// Only investigating named vars here.
return false;
}
ArenaAllocator *alloc = byteCodeGenerator->GetAllocator();
SList<ParseNode*> pNodeSEStack(alloc);
pNodeSEStack.Push(pnodeSE);
// A pnodeSE can have children that can cause a side effect on pnode. A stack is used to check
// pnodeSE and all potential pnodeSE children that could cause a side effect on pnode. When a
// child pnodeSE can cause a side effect on pnode, immediately return true. Otherwise continue
// checking children of pnodeSE until none exist
while (!pNodeSEStack.Empty())
{
ParseNode *currPnodeSE = pNodeSEStack.Pop();
uint fnop = ParseNode::Grfnop(currPnodeSE->nop);
if (fnop & fnopLeaf)
{
// pnodeSE is a leaf and can't kill anything.
continue;
}
else if (fnop & fnopAsg)
{
// pnodeSE is an assignment (=, ++, +=, etc.)
// Trying to examine the LHS of pnodeSE caused small perf regressions,
// maybe because of code layout or some other subtle effect.
return true;
}
else if (fnop & fnopUni)
{
// pnodeSE is a unary op, so recurse to the source (if present - e.g., [] may have no opnd).
if (currPnodeSE->nop == knopTempRef)
{
continue;
}
else if (currPnodeSE->AsParseNodeUni()->pnode1)
{
pNodeSEStack.Push(currPnodeSE->AsParseNodeUni()->pnode1);
}
}
else if (fnop & fnopBin)
{
// currPnodeSE is a binary (or ternary) op, so check sources (if present).
pNodeSEStack.Push(currPnodeSE->AsParseNodeBin()->pnode1);
if (currPnodeSE->AsParseNodeBin()->pnode2)
{
pNodeSEStack.Push(currPnodeSE->AsParseNodeBin()->pnode2);
}
}
else if (currPnodeSE->nop == knopQmark)
{
ParseNodeTri * pnodeTriSE = currPnodeSE->AsParseNodeTri();
pNodeSEStack.Push(pnodeTriSE->pnode1);
pNodeSEStack.Push(pnodeTriSE->pnode2);
pNodeSEStack.Push(pnodeTriSE->pnode3);
}
else if (currPnodeSE->nop == knopCall || currPnodeSE->nop == knopNew)
{
pNodeSEStack.Push(currPnodeSE->AsParseNodeCall()->pnodeTarget);
if (currPnodeSE->AsParseNodeCall()->pnodeArgs)
{
pNodeSEStack.Push(currPnodeSE->AsParseNodeCall()->pnodeArgs);
}
}
else if (currPnodeSE->nop == knopList)
{
return true;
}
}
return false;
}
bool IsCallOfConstants(ParseNode *pnode);
bool BlockHasOwnScope(ParseNodeBlock * pnodeBlock, ByteCodeGenerator *byteCodeGenerator);
bool CreateNativeArrays(ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo);
bool IsArguments(ParseNode *pnode)
{
for (;;)
{
switch (pnode->nop)
{
case knopName:
return pnode->AsParseNodeName()->sym && pnode->AsParseNodeName()->sym->IsArguments();
case knopCall:
case knopNew:
if (IsArguments(pnode->AsParseNodeCall()->pnodeTarget))
{
return true;
}
if (pnode->AsParseNodeCall()->pnodeArgs)
{
ParseNode *pnodeArg = pnode->AsParseNodeCall()->pnodeArgs;
while (pnodeArg->nop == knopList)
{
if (IsArguments(pnodeArg->AsParseNodeBin()->pnode1))
return true;
pnodeArg = pnodeArg->AsParseNodeBin()->pnode2;
}
pnode = pnodeArg;
break;
}
return false;
case knopArray:
if (pnode->AsParseNodeArrLit()->arrayOfNumbers || pnode->AsParseNodeArrLit()->count == 0)
{
return false;
}
pnode = pnode->AsParseNodeUni()->pnode1;
break;
case knopQmark:
if (IsArguments(pnode->AsParseNodeTri()->pnode1) || IsArguments(pnode->AsParseNodeTri()->pnode2))
{
return true;
}
pnode = pnode->AsParseNodeTri()->pnode3;
break;
//
// Cases where we don't check for "arguments" yet.
// Assume that they might have it. Disable the optimization in such scenarios
//
case knopList:
case knopObject:
case knopVarDecl:
case knopConstDecl:
case knopLetDecl:
case knopFncDecl:
case knopClassDecl:
case knopFor:
case knopIf:
case knopDoWhile:
case knopWhile:
case knopForIn:
case knopForOf:
case knopForAwaitOf:
case knopReturn:
case knopBlock:
case knopBreak:
case knopContinue:
case knopTypeof:
case knopThrow:
case knopWith:
case knopFinally:
case knopTry:
case knopTryCatch:
case knopTryFinally:
case knopArrayPattern:
case knopObjectPattern:
case knopParamPattern:
return true;
default:
{
uint flags = ParseNode::Grfnop(pnode->nop);
if (flags&fnopUni)
{
ParseNodeUni * pnodeUni = pnode->AsParseNodeUni();
Assert(pnodeUni->pnode1);
pnode = pnodeUni->pnode1;
break;
}
else if (flags&fnopBin)
{
ParseNodeBin * pnodeBin = pnode->AsParseNodeBin();
Assert(pnodeBin->pnode1 && pnodeBin->pnode2);
if (IsArguments(pnodeBin->pnode1))
{
return true;
}
pnode = pnodeBin->pnode2;
break;
}
return false;
}
}
}
}
bool ApplyEnclosesArgs(ParseNode* fncDecl, ByteCodeGenerator* byteCodeGenerator);
void Emit(ParseNode* pnode, ByteCodeGenerator* byteCodeGenerator, FuncInfo* funcInfo, BOOL fReturnValue, bool isConstructorCall = false, bool isTopLevel = false);
void EmitBinaryOpnds(ParseNode* pnode1, ParseNode* pnode2, ByteCodeGenerator* byteCodeGenerator, FuncInfo* funcInfo, Js::RegSlot computedPropertyLocation = Js::Constants::NoRegister);
bool IsExpressionStatement(ParseNode* stmt, const Js::ScriptContext *const scriptContext);
void EmitInvoke(Js::RegSlot location, Js::RegSlot callObjLocation, Js::PropertyId propertyId, ByteCodeGenerator* byteCodeGenerator, FuncInfo* funcInfo);
void EmitInvoke(Js::RegSlot location, Js::RegSlot callObjLocation, Js::PropertyId propertyId, ByteCodeGenerator* byteCodeGenerator, FuncInfo* funcInfo, Js::RegSlot arg1Location);
static const Js::OpCode nopToOp[knopLim] =
{
#define OP(x) Br##x##_A
#define PTNODE(nop,sn,pc,nk,grfnop,json) Js::OpCode::pc,
#include "ptlist.h"
};
static const Js::OpCode nopToCMOp[knopLim] =
{
#define OP(x) Cm##x##_A
#define PTNODE(nop,sn,pc,nk,grfnop,json) Js::OpCode::pc,
#include "ptlist.h"
};
Js::OpCode ByteCodeGenerator::ToChkUndeclOp(Js::OpCode op) const
{
switch (op)
{
case Js::OpCode::StLocalSlot:
return Js::OpCode::StLocalSlotChkUndecl;
case Js::OpCode::StParamSlot:
return Js::OpCode::StParamSlotChkUndecl;
case Js::OpCode::StInnerSlot:
return Js::OpCode::StInnerSlotChkUndecl;
case Js::OpCode::StEnvSlot:
return Js::OpCode::StEnvSlotChkUndecl;
case Js::OpCode::StObjSlot:
return Js::OpCode::StObjSlotChkUndecl;
case Js::OpCode::StLocalObjSlot:
return Js::OpCode::StLocalObjSlotChkUndecl;
case Js::OpCode::StParamObjSlot:
return Js::OpCode::StParamObjSlotChkUndecl;
case Js::OpCode::StInnerObjSlot:
return Js::OpCode::StInnerObjSlotChkUndecl;
case Js::OpCode::StEnvObjSlot:
return Js::OpCode::StEnvObjSlotChkUndecl;
default:
AssertMsg(false, "Unknown opcode for chk undecl mapping");
return Js::OpCode::InvalidOpCode;
}
}
// Tracks a register slot let/const property for the passed in debugger block/catch scope.
// debuggerScope - The scope to add the variable to.
// symbol - The symbol that represents the register property.
// funcInfo - The function info used to store the property into the tracked debugger register slot list.
// flags - The flags to assign to the property.
// isFunctionDeclaration - Whether or not the register is a function declaration, which requires that its byte code offset be updated immediately.
void ByteCodeGenerator::TrackRegisterPropertyForDebugger(
Js::DebuggerScope *debuggerScope,
Symbol *symbol,
FuncInfo *funcInfo,
Js::DebuggerScopePropertyFlags flags /*= Js::DebuggerScopePropertyFlags_None*/,
bool isFunctionDeclaration /*= false*/)
{
Assert(debuggerScope);
Assert(symbol);
Assert(funcInfo);
Js::RegSlot location = symbol->GetLocation();
Js::DebuggerScope *correctDebuggerScope = debuggerScope;
if (debuggerScope->scopeType != Js::DiagExtraScopesType::DiagBlockScopeDirect && debuggerScope->scopeType != Js::DiagExtraScopesType::DiagCatchScopeDirect)
{
// We have to get the appropriate scope and add property over there.
// Make sure the scope is created whether we're in debug mode or not, because we
// need the empty scopes present during reparsing for debug mode.
correctDebuggerScope = debuggerScope->GetSiblingScope(location, Writer()->GetFunctionWrite());
}
if (this->ShouldTrackDebuggerMetadata() && !symbol->GetIsTrackedForDebugger())
{
// Only track the property if we're in debug mode since it's only needed by the debugger.
Js::PropertyId propertyId = symbol->EnsurePosition(this);
this->Writer()->AddPropertyToDebuggerScope(
correctDebuggerScope,
location,
propertyId,
/*shouldConsumeRegister*/ true,
flags,
isFunctionDeclaration);
Js::FunctionBody *byteCodeFunction = funcInfo->GetParsedFunctionBody();
byteCodeFunction->InsertSymbolToRegSlotList(location, propertyId, funcInfo->varRegsCount);
symbol->SetIsTrackedForDebugger(true);
}
}
void ByteCodeGenerator::TrackActivationObjectPropertyForDebugger(
Js::DebuggerScope *debuggerScope,
Symbol *symbol,
Js::DebuggerScopePropertyFlags flags /*= Js::DebuggerScopePropertyFlags_None*/,
bool isFunctionDeclaration /*= false*/)
{
Assert(debuggerScope);
Assert(symbol);
// Only need to track activation object properties in debug mode.
if (ShouldTrackDebuggerMetadata() && !symbol->GetIsTrackedForDebugger())
{
Js::RegSlot location = symbol->GetLocation();
Js::PropertyId propertyId = symbol->EnsurePosition(this);
this->Writer()->AddPropertyToDebuggerScope(
debuggerScope,
location,
propertyId,
/*shouldConsumeRegister*/ false,
flags,
isFunctionDeclaration);
symbol->SetIsTrackedForDebugger(true);
}
}
void ByteCodeGenerator::TrackSlotArrayPropertyForDebugger(
Js::DebuggerScope *debuggerScope,
Symbol* symbol,
Js::PropertyId propertyId,
Js::DebuggerScopePropertyFlags flags /*= Js::DebuggerScopePropertyFlags_None*/,
bool isFunctionDeclaration /*= false*/)
{
// Note: Slot array properties are tracked even in non-debug mode in order to support slot array serialization
// of let/const variables between non-debug and debug mode (for example, when a slot array var escapes and is retrieved
// after a debugger attach or for WWA apps). They are also needed for heap enumeration.
Assert(debuggerScope);
Assert(symbol);
if (!symbol->GetIsTrackedForDebugger())
{
Js::RegSlot location = symbol->GetScopeSlot();
Assert(location != Js::Constants::NoRegister);
Assert(propertyId != Js::Constants::NoProperty);
this->Writer()->AddPropertyToDebuggerScope(
debuggerScope,
location,
propertyId,
/*shouldConsumeRegister*/ false,
flags,
isFunctionDeclaration);
symbol->SetIsTrackedForDebugger(true);
}
}
// Tracks a function declaration inside a block scope for the debugger metadata's current scope (let binding).
void ByteCodeGenerator::TrackFunctionDeclarationPropertyForDebugger(Symbol *functionDeclarationSymbol, FuncInfo *funcInfoParent)
{
Assert(functionDeclarationSymbol);
Assert(funcInfoParent);
AssertMsg(functionDeclarationSymbol->GetIsBlockVar(), "We should only track inner function let bindings for the debugger.");
// Note: we don't have to check symbol->GetIsTrackedForDebugger, as we are not doing actual work here,
// which is done in other Track* functions that we call.
if (functionDeclarationSymbol->IsInSlot(this, funcInfoParent))
{
if (functionDeclarationSymbol->GetScope()->GetIsObject())
{
this->TrackActivationObjectPropertyForDebugger(
this->Writer()->GetCurrentDebuggerScope(),
functionDeclarationSymbol,
Js::DebuggerScopePropertyFlags_None,
true /*isFunctionDeclaration*/);
}
else
{
// Make sure the property has a slot. This will bump up the size of the slot array if necessary.
// Note that slot array inner function bindings are tracked even in non-debug mode in order
// to keep the lifetime of the closure binding that could escape around for heap enumeration.
functionDeclarationSymbol->EnsureScopeSlot(this, funcInfoParent);
functionDeclarationSymbol->EnsurePosition(this);
this->TrackSlotArrayPropertyForDebugger(
this->Writer()->GetCurrentDebuggerScope(),
functionDeclarationSymbol,
functionDeclarationSymbol->GetPosition(),
Js::DebuggerScopePropertyFlags_None,
true /*isFunctionDeclaration*/);
}
}
else
{
this->TrackRegisterPropertyForDebugger(
this->Writer()->GetCurrentDebuggerScope(),
functionDeclarationSymbol,
funcInfoParent,
Js::DebuggerScopePropertyFlags_None,
true /*isFunctionDeclaration*/);
}
}
// Updates the byte code offset of the property with the passed in location and ID.
// Used to track let/const variables that are in the dead zone debugger side.
// location - The activation object, scope slot index, or register location for the property.
// propertyId - The ID of the property to update.
// shouldConsumeRegister - Whether or not the a register should be consumed (used for reg slot locations).
void ByteCodeGenerator::UpdateDebuggerPropertyInitializationOffset(Js::RegSlot location, Js::PropertyId propertyId, bool shouldConsumeRegister)
{
Assert(this->Writer());
Js::DebuggerScope* currentDebuggerScope = this->Writer()->GetCurrentDebuggerScope();
Assert(currentDebuggerScope);
if (currentDebuggerScope != nullptr)
{
this->Writer()->UpdateDebuggerPropertyInitializationOffset(
currentDebuggerScope,
location,
propertyId,
shouldConsumeRegister);
}
}
void ByteCodeGenerator::LoadHeapArguments(FuncInfo *funcInfo)
{
if (funcInfo->GetHasCachedScope())
{
this->LoadCachedHeapArguments(funcInfo);
}
else
{
this->LoadUncachedHeapArguments(funcInfo);
}
}
void GetFormalArgsArray(ByteCodeGenerator *byteCodeGenerator, FuncInfo * funcInfo, Js::PropertyIdArray *propIds)
{
Assert(funcInfo);
Assert(propIds);
Assert(byteCodeGenerator);
bool hadDuplicates = false;
Js::ArgSlot i = 0;
auto processArg = [&](ParseNode *pnode)
{
if (pnode->IsVarLetOrConst())
{
Assert(i < propIds->count);
Symbol *sym = pnode->AsParseNodeVar()->sym;
Assert(sym);
Js::PropertyId symPos = sym->EnsurePosition(byteCodeGenerator);
//
// Check if the function has any same name parameters
// For the same name param, only the last one will be passed the correct propertyid
// For remaining dup param names, pass Constants::NoProperty
//
for (Js::ArgSlot j = 0; j < i; j++)
{
if (propIds->elements[j] == symPos)
{
// Found a dup parameter name
propIds->elements[j] = Js::Constants::NoProperty;
hadDuplicates = true;
break;
}
}
propIds->elements[i] = symPos;
}
else
{
propIds->elements[i] = Js::Constants::NoProperty;
}
++i;
};
MapFormals(funcInfo->root, processArg);
propIds->hadDuplicates = hadDuplicates;
}
void ByteCodeGenerator::LoadUncachedHeapArguments(FuncInfo *funcInfo)
{
Assert(funcInfo->GetHasHeapArguments());
Scope *scope = funcInfo->GetBodyScope();
Assert(scope);
Symbol *argSym = funcInfo->GetArgumentsSymbol();
Assert(argSym && argSym->IsArguments());
Js::RegSlot argumentsLoc = argSym->GetLocation();
Js::OpCode opcode = !funcInfo->root->HasNonSimpleParameterList() ? Js::OpCode::LdHeapArguments : Js::OpCode::LdLetHeapArguments;
bool hasRest = funcInfo->root->pnodeRest != nullptr;
uint count = funcInfo->inArgsCount + (hasRest ? 1 : 0) - 1;
if (count == 0)
{
// If no formals to function (only "this"), then no need to create the scope object.
// Leave both the arguments location and the propertyIds location as null.
Assert(funcInfo->root->pnodeParams == nullptr && !hasRest);
}
else if (!NeedScopeObjectForArguments(funcInfo, funcInfo->root))
{
// We may not need a scope object for arguments, e.g. strict mode with no eval.
}
else if (funcInfo->frameObjRegister != Js::Constants::NoRegister)
{
// Pass the frame object and ID array to the runtime, and put the resulting Arguments object
// at the expected location.
Js::PropertyIdArray *propIds = funcInfo->GetParsedFunctionBody()->AllocatePropertyIdArrayForFormals(UInt32Math::Mul(count, sizeof(Js::PropertyId)), count, 0);
GetFormalArgsArray(this, funcInfo, propIds);
}
this->m_writer.Reg1(opcode, argumentsLoc);
EmitLocalPropInit(argSym->GetLocation(), argSym, funcInfo);
}
void ByteCodeGenerator::LoadCachedHeapArguments(FuncInfo *funcInfo)
{
Assert(funcInfo->GetHasHeapArguments());
Scope *scope = funcInfo->GetBodyScope();
Assert(scope);
Symbol *argSym = funcInfo->GetArgumentsSymbol();
Assert(argSym && argSym->IsArguments());
Js::RegSlot argumentsLoc = argSym->GetLocation();
Js::OpCode op = !funcInfo->root->HasNonSimpleParameterList() ? Js::OpCode::LdHeapArgsCached : Js::OpCode::LdLetHeapArgsCached;
this->m_writer.Reg1(op, argumentsLoc);
EmitLocalPropInit(argumentsLoc, argSym, funcInfo);
}
Js::JavascriptArray* ByteCodeGenerator::BuildArrayFromStringList(ParseNode* stringNodeList, uint arrayLength, Js::ScriptContext* scriptContext)
{
Assert(stringNodeList);
uint index = 0;
Js::Var str;
IdentPtr pid;
Js::JavascriptArray* pArr = scriptContext->GetLibrary()->CreateArray(arrayLength);
while (stringNodeList->nop == knopList)
{
Assert(stringNodeList->AsParseNodeBin()->pnode1->nop == knopStr);
pid = stringNodeList->AsParseNodeBin()->pnode1->AsParseNodeStr()->pid;
str = Js::JavascriptString::NewCopyBuffer(pid->Psz(), pid->Cch(), scriptContext);
pArr->SetItemWithAttributes(index, str, PropertyEnumerable);
stringNodeList = stringNodeList->AsParseNodeBin()->pnode2;
index++;
}
Assert(stringNodeList->nop == knopStr);
pid = stringNodeList->AsParseNodeStr()->pid;
str = Js::JavascriptString::NewCopyBuffer(pid->Psz(), pid->Cch(), scriptContext);
pArr->SetItemWithAttributes(index, str, PropertyEnumerable);
return pArr;
}
// For now, this just assigns field ids for the current script.
// Later, we will combine this information with the global field id map.
// This temporary code will not work if a global member is accessed both with and without a LHS.
void ByteCodeGenerator::AssignPropertyIds(Js::ParseableFunctionInfo* functionInfo)
{
globalScope->ForEachSymbol([this, functionInfo](Symbol * sym)
{
this->AssignPropertyId(sym, functionInfo);
});
}
void ByteCodeGenerator::InitBlockScopedContent(ParseNodeBlock *pnodeBlock, Js::DebuggerScope* debuggerScope, FuncInfo *funcInfo)
{
Assert(pnodeBlock->nop == knopBlock);
auto genBlockInit = [this, debuggerScope, funcInfo](ParseNode *pnode)
{
// Only check if the scope is valid when let/const vars are in the scope. If there are no let/const vars,
// the debugger scope will not be created.
AssertMsg(debuggerScope, "Missing a case of scope tracking in BeginEmitBlock.");
FuncInfo *funcInfo = this->TopFuncInfo();
Symbol *sym = pnode->AsParseNodeVar()->sym;
Scope *scope = sym->GetScope();
if (sym->GetIsGlobal())
{
Js::PropertyId propertyId = sym->EnsurePosition(this);
if (this->flags & fscrEval)
{
AssertMsg(this->IsConsoleScopeEval(), "Let/Consts cannot be in global scope outside of console eval");
Js::OpCode op = (sym->GetDecl()->nop == knopConstDecl) ? Js::OpCode::InitUndeclConsoleConstFld : Js::OpCode::InitUndeclConsoleLetFld;
this->m_writer.ElementScopedU(op, funcInfo->FindOrAddReferencedPropertyId(propertyId));
}
else
{
Js::OpCode op = (sym->GetDecl()->nop == knopConstDecl) ?
Js::OpCode::InitUndeclRootConstFld : Js::OpCode::InitUndeclRootLetFld;
this->m_writer.ElementRootU(op, funcInfo->FindOrAddReferencedPropertyId(propertyId));
}
}
else if (sym->IsInSlot(this, funcInfo) || (scope->GetIsObject() && sym->NeedsSlotAlloc(this, funcInfo)))
{
if (scope->GetIsObject())
{
Js::RegSlot scopeLocation = scope->GetLocation();
Js::PropertyId propertyId = sym->EnsurePosition(this);
if (scopeLocation != Js::Constants::NoRegister && scopeLocation == funcInfo->frameObjRegister)
{
uint cacheId = funcInfo->FindOrAddInlineCacheId(scopeLocation, propertyId, false, true);
Js::OpCode op = (sym->GetDecl()->nop == knopConstDecl) ?
Js::OpCode::InitUndeclLocalConstFld : Js::OpCode::InitUndeclLocalLetFld;
this->m_writer.ElementP(op, ByteCodeGenerator::ReturnRegister, cacheId);
}
else
{
uint cacheId = funcInfo->FindOrAddInlineCacheId(funcInfo->InnerScopeToRegSlot(scope), propertyId, false, true);
Js::OpCode op = (sym->GetDecl()->nop == knopConstDecl) ?
Js::OpCode::InitUndeclConstFld : Js::OpCode::InitUndeclLetFld;
this->m_writer.ElementPIndexed(op, ByteCodeGenerator::ReturnRegister, scope->GetInnerScopeIndex(), cacheId);
}
TrackActivationObjectPropertyForDebugger(debuggerScope, sym, pnode->nop == knopConstDecl ? Js::DebuggerScopePropertyFlags_Const : Js::DebuggerScopePropertyFlags_None);
}
else
{
Js::RegSlot tmpReg = funcInfo->AcquireTmpRegister();
this->m_writer.Reg1(Js::OpCode::InitUndecl, tmpReg);
this->EmitLocalPropInit(tmpReg, sym, funcInfo);
funcInfo->ReleaseTmpRegister(tmpReg);
// Slot array properties are tracked in non-debug mode as well because they need to stay
// around for heap enumeration and escaping during attach/detach.
TrackSlotArrayPropertyForDebugger(debuggerScope, sym, sym->EnsurePosition(this), pnode->nop == knopConstDecl ? Js::DebuggerScopePropertyFlags_Const : Js::DebuggerScopePropertyFlags_None);
}
}
else if (!sym->GetIsModuleExportStorage())
{
if (sym->GetDecl()->AsParseNodeVar()->isSwitchStmtDecl)
{
// let/const declared in a switch is the only case of a variable that must be checked for
// use-before-declaration dynamically within its own function.
this->m_writer.Reg1(Js::OpCode::InitUndecl, sym->GetLocation());
}
// Syms that begin in register may be delay-captured. In debugger mode, such syms
// will live only in slots, so tell the debugger to find them there.
if (sym->NeedsSlotAlloc(this, funcInfo))
{
TrackSlotArrayPropertyForDebugger(debuggerScope, sym, sym->EnsurePosition(this), pnode->nop == knopConstDecl ? Js::DebuggerScopePropertyFlags_Const : Js::DebuggerScopePropertyFlags_None);
}
else
{
TrackRegisterPropertyForDebugger(debuggerScope, sym, funcInfo, pnode->nop == knopConstDecl ? Js::DebuggerScopePropertyFlags_Const : Js::DebuggerScopePropertyFlags_None);
}
}
};
IterateBlockScopedVariables(pnodeBlock, genBlockInit);
}
// Records the start of a debugger scope if the passed in node has any let/const variables (or is not a block node).
// If it has no let/const variables, nullptr will be returned as no scope will be created.
Js::DebuggerScope* ByteCodeGenerator::RecordStartScopeObject(ParseNode * pnode, Js::DiagExtraScopesType scopeType, Js::RegSlot scopeLocation /*= Js::Constants::NoRegister*/, int* index /*= nullptr*/)
{
Assert(pnode);
if (pnode->nop == knopBlock && !pnode->AsParseNodeBlock()->HasBlockScopedContent())
{
// In order to reduce allocations now that we track debugger scopes in non-debug mode,
// don't add a block to the chain if it has no let/const variables at all.
return nullptr;
}
return this->Writer()->RecordStartScopeObject(scopeType, scopeLocation, index);
}
// Records the end of the current scope, but only if the current block has block scoped content.
// Otherwise, a scope would not have been added (see ByteCodeGenerator::RecordStartScopeObject()).
void ByteCodeGenerator::RecordEndScopeObject(ParseNode *pnodeBlock)
{
Assert(pnodeBlock);
if (pnodeBlock->nop == knopBlock && !pnodeBlock->AsParseNodeBlock()->HasBlockScopedContent())
{
return;
}
this->Writer()->RecordEndScopeObject();
}
void BeginEmitBlock(ParseNodeBlock *pnodeBlock, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo)
{
Js::DebuggerScope* debuggerScope = nullptr;
if (BlockHasOwnScope(pnodeBlock, byteCodeGenerator))
{
Scope *scope = pnodeBlock->scope;
byteCodeGenerator->PushScope(scope);
Js::RegSlot scopeLocation = scope->GetLocation();
if (scope->GetMustInstantiate())
{
Assert(scopeLocation == Js::Constants::NoRegister);
scopeLocation = funcInfo->FirstInnerScopeReg() + scope->GetInnerScopeIndex();
if (scope->GetIsObject())
{
debuggerScope = byteCodeGenerator->RecordStartScopeObject(pnodeBlock, Js::DiagExtraScopesType::DiagBlockScopeInObject, scopeLocation);
byteCodeGenerator->Writer()->Unsigned1(Js::OpCode::NewBlockScope, scope->GetInnerScopeIndex());
}
else
{
int scopeIndex = Js::DebuggerScope::InvalidScopeIndex;
debuggerScope = byteCodeGenerator->RecordStartScopeObject(pnodeBlock, Js::DiagExtraScopesType::DiagBlockScopeInSlot, scopeLocation, &scopeIndex);
// TODO: Handle heap enumeration
int scopeSlotCount = scope->GetScopeSlotCount();
byteCodeGenerator->Writer()->Num3(Js::OpCode::NewInnerScopeSlots, scope->GetInnerScopeIndex(), scopeSlotCount + Js::ScopeSlots::FirstSlotIndex, scopeIndex);
}
}
else
{
// In the direct register access case, there is no block scope emitted but we can still track
// the start and end offset of the block. The location registers for let/const variables will still be
// captured along with this range in InitBlockScopedContent().
debuggerScope = byteCodeGenerator->RecordStartScopeObject(pnodeBlock, Js::DiagExtraScopesType::DiagBlockScopeDirect);
}
bool const isGlobalEvalBlockScope = scope->IsGlobalEvalBlockScope();
Js::RegSlot frameDisplayLoc = Js::Constants::NoRegister;
Js::RegSlot tmpInnerEnvReg = Js::Constants::NoRegister;
ParseNodePtr pnodeScope;
for (pnodeScope = pnodeBlock->pnodeScopes; pnodeScope;)
{
switch (pnodeScope->nop)
{
case knopFncDecl:
if (pnodeScope->AsParseNodeFnc()->IsDeclaration())
{
// The frameDisplayLoc register's lifetime has to be controlled by this function. We can't let
// it be released by DefineOneFunction, because further iterations of this loop can allocate
// temps, and we can't let frameDisplayLoc be re-purposed until this loop completes.
// So we'll supply a temp that we allocate and release here.
if (frameDisplayLoc == Js::Constants::NoRegister)
{
if (funcInfo->frameDisplayRegister != Js::Constants::NoRegister)
{
frameDisplayLoc = funcInfo->frameDisplayRegister;
}
else
{
frameDisplayLoc = funcInfo->GetEnvRegister();
}
tmpInnerEnvReg = funcInfo->AcquireTmpRegister();
frameDisplayLoc = byteCodeGenerator->PrependLocalScopes(frameDisplayLoc, tmpInnerEnvReg, funcInfo);
}
byteCodeGenerator->DefineOneFunction(pnodeScope->AsParseNodeFnc(), funcInfo, true, frameDisplayLoc);
}
// If this is the global eval block scope, the function is actually assigned to the global
// so we don't need to keep the registers.
if (isGlobalEvalBlockScope)
{
funcInfo->ReleaseLoc(pnodeScope);
pnodeScope->location = Js::Constants::NoRegister;
}
pnodeScope = pnodeScope->AsParseNodeFnc()->pnodeNext;
break;
case knopBlock:
pnodeScope = pnodeScope->AsParseNodeBlock()->pnodeNext;
break;
case knopCatch:
pnodeScope = pnodeScope->AsParseNodeCatch()->pnodeNext;
break;
case knopWith:
pnodeScope = pnodeScope->AsParseNodeWith()->pnodeNext;
break;
}
}
if (tmpInnerEnvReg != Js::Constants::NoRegister)
{
funcInfo->ReleaseTmpRegister(tmpInnerEnvReg);
}
}
else
{
Scope *scope = pnodeBlock->scope;
if (scope)
{
if (scope->GetMustInstantiate())
{
debuggerScope = byteCodeGenerator->RecordStartScopeObject(pnodeBlock, Js::DiagExtraScopesType::DiagBlockScopeInObject);
}
else
{
debuggerScope = byteCodeGenerator->RecordStartScopeObject(pnodeBlock, Js::DiagExtraScopesType::DiagBlockScopeDirect);
}
}
else
{
debuggerScope = byteCodeGenerator->RecordStartScopeObject(pnodeBlock, Js::DiagExtraScopesType::DiagBlockScopeInSlot);
}
}
byteCodeGenerator->InitBlockScopedContent(pnodeBlock, debuggerScope, funcInfo);
}
void EndEmitBlock(ParseNodeBlock *pnodeBlock, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo)
{
if (BlockHasOwnScope(pnodeBlock, byteCodeGenerator))
{
Scope *scope = pnodeBlock->scope;
Assert(scope);
Assert(scope == byteCodeGenerator->GetCurrentScope());
byteCodeGenerator->PopScope();
}
byteCodeGenerator->RecordEndScopeObject(pnodeBlock);
}
void CloneEmitBlock(ParseNodeBlock *pnodeBlock, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo)
{
if (BlockHasOwnScope(pnodeBlock, byteCodeGenerator))
{
// Only let variables have observable behavior when there are per iteration
// bindings. const variables do not since they are immutable. Therefore,
// (and the spec agrees), only create new scope clones if the loop variable
// is a let declaration.
bool isConst = false;
pnodeBlock->scope->ForEachSymbolUntil([&isConst](Symbol * const sym) {
// Exploit the fact that a for loop sxBlock can only have let and const
// declarations, and can only have one or the other, regardless of how
// many syms there might be. Thus only check the first sym.
isConst = sym->GetDecl()->nop == knopConstDecl;
return true;
});
if (!isConst)
{
Scope *scope = pnodeBlock->scope;
Assert(scope == byteCodeGenerator->GetCurrentScope());
if (scope->GetMustInstantiate())
{
Js::OpCode op = scope->GetIsObject() ? Js::OpCode::CloneBlockScope : Js::OpCode::CloneInnerScopeSlots;
byteCodeGenerator->Writer()->Unsigned1(op, scope->GetInnerScopeIndex());
}
}
}
}
void EmitBlock(ParseNodeBlock *pnodeBlock, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo, BOOL fReturnValue)
{
Assert(pnodeBlock->nop == knopBlock);
ParseNode *pnode = pnodeBlock->pnodeStmt;
if (pnode == nullptr)
{
return;
}
BeginEmitBlock(pnodeBlock, byteCodeGenerator, funcInfo);
ParseNode *pnodeLastValStmt = pnodeBlock->pnodeLastValStmt;
while (pnode->nop == knopList)
{
ParseNode* stmt = pnode->AsParseNodeBin()->pnode1;
if (stmt == pnodeLastValStmt)
{
// This is the last guaranteed return value, so any potential return values have to be
// copied to the return register from this point forward.
pnodeLastValStmt = nullptr;
}
byteCodeGenerator->EmitTopLevelStatement(stmt, funcInfo, fReturnValue && (pnodeLastValStmt == nullptr));
pnode = pnode->AsParseNodeBin()->pnode2;
}
if (pnode == pnodeLastValStmt)
{
pnodeLastValStmt = nullptr;
}
byteCodeGenerator->EmitTopLevelStatement(pnode, funcInfo, fReturnValue && (pnodeLastValStmt == nullptr));
EndEmitBlock(pnodeBlock, byteCodeGenerator, funcInfo);
}
void ClearTmpRegs(ParseNode* pnode, ByteCodeGenerator* byteCodeGenerator, FuncInfo* emitFunc)
{
if (emitFunc->IsTmpReg(pnode->location))
{
pnode->location = Js::Constants::NoRegister;
}
}
void ByteCodeGenerator::EmitTopLevelStatement(ParseNode *stmt, FuncInfo *funcInfo, BOOL fReturnValue)
{
if (stmt->nop == knopFncDecl && stmt->AsParseNodeFnc()->IsDeclaration())
{
// Function declarations (not function-declaration RHS's) are already fully processed.
// Skip them here so the temp registers don't get messed up.
return;
}
if (stmt->nop == knopName || stmt->nop == knopDot)
{
// Generating span for top level names are mostly useful in debugging mode, because user can debug it even though no side-effect expected.
// But the name can have runtime error, e.g., foo.bar; // where foo is not defined.
// At this time we need to throw proper line number and offset. so recording on all modes will be useful.
StartStatement(stmt);
Writer()->Empty(Js::OpCode::Nop);
EndStatement(stmt);
}
Emit(stmt, this, funcInfo, fReturnValue, false/*isConstructorCall*/, true/*isTopLevel*/);
if (funcInfo->IsTmpReg(stmt->location))
{
funcInfo->ReleaseLoc(stmt);
}
}
// ByteCodeGenerator::DefineFunctions
//
// Emit byte code for scope-wide function definitions before any calls in the scope, regardless of lexical
// order. Note that stores to the closure array are not emitted until we see the knopFncDecl in the tree
// to make sure that sources of the stores have been defined.
void ByteCodeGenerator::DefineFunctions(FuncInfo *funcInfoParent)
{
// DefineCachedFunctions doesn't depend on whether the user vars are declared or not, so
// we'll just overload this variable to mean that the functions getting called again and we don't need to do anything
if (funcInfoParent->GetHasCachedScope())
{
this->DefineCachedFunctions(funcInfoParent);
}
else
{
this->DefineUncachedFunctions(funcInfoParent);
}
}
// Iterate over all child functions in a function's parameter and body scopes.
template<typename Fn>
void MapContainerScopeFunctions(ParseNode* pnodeScope, Fn fn)
{
auto mapFncDeclsInScopeList = [&](ParseNode *pnodeHead)
{
for (ParseNode *pnode = pnodeHead; pnode != nullptr;)
{
switch (pnode->nop)
{
case knopFncDecl:
fn(pnode);
pnode = pnode->AsParseNodeFnc()->pnodeNext;
break;
case knopBlock:
pnode = pnode->AsParseNodeBlock()->pnodeNext;
break;
case knopCatch:
pnode = pnode->AsParseNodeCatch()->pnodeNext;
break;
case knopWith:
pnode = pnode->AsParseNodeWith()->pnodeNext;
break;
default:
AssertMsg(false, "Unexpected opcode in tree of scopes");
return;
}
}
};
pnodeScope->AsParseNodeFnc()->MapContainerScopes(mapFncDeclsInScopeList);
}
void ByteCodeGenerator::DefineCachedFunctions(FuncInfo *funcInfoParent)
{
ParseNode *pnodeParent = funcInfoParent->root;
uint slotCount = 0;
auto countFncSlots = [&](ParseNode *pnodeFnc)
{
if (pnodeFnc->AsParseNodeFnc()->GetFuncSymbol() != nullptr && pnodeFnc->AsParseNodeFnc()->IsDeclaration())
{
slotCount++;
}
};
MapContainerScopeFunctions(pnodeParent, countFncSlots);
if (slotCount == 0)
{
return;
}
size_t extraBytesActual = AllocSizeMath::Mul(slotCount, sizeof(Js::FuncInfoEntry));
// Reg2Aux takes int for byteCount so we need to convert to int. OOM if we can't because it would truncate data.
if (extraBytesActual > INT_MAX)
{
Js::Throw::OutOfMemory();
}
int extraBytes = (int)extraBytesActual;
Js::FuncInfoArray *info = AnewPlus(alloc, extraBytes, Js::FuncInfoArray, slotCount);
// slotCount is guaranteed to be non-zero here.
Js::AuxArray<uint32> * slotIdInCachedScopeToNestedIndexArray = funcInfoParent->GetParsedFunctionBody()->AllocateSlotIdInCachedScopeToNestedIndexArray(slotCount);
slotCount = 0;
auto fillEntries = [&](ParseNode *pnodeFnc)
{
Symbol *sym = pnodeFnc->AsParseNodeFnc()->GetFuncSymbol();
if (sym != nullptr && (pnodeFnc->AsParseNodeFnc()->IsDeclaration() || pnodeFnc->AsParseNodeFnc()->IsDefaultModuleExport()))
{
AssertMsg(!pnodeFnc->AsParseNodeFnc()->IsGenerator(), "Generator functions are not supported by InitCachedFuncs but since they always escape they should disable function caching");
Js::FuncInfoEntry *entry = &info->elements[slotCount];
entry->nestedIndex = pnodeFnc->AsParseNodeFnc()->nestedIndex;
entry->scopeSlot = sym->GetScopeSlot();
slotIdInCachedScopeToNestedIndexArray->elements[slotCount] = pnodeFnc->AsParseNodeFnc()->nestedIndex;
slotCount++;
}
};
MapContainerScopeFunctions(pnodeParent, fillEntries);
m_writer.AuxNoReg(Js::OpCode::InitCachedFuncs,
info,
sizeof(Js::FuncInfoArray) + extraBytes,
sizeof(Js::FuncInfoArray) + extraBytes);
slotCount = 0;
auto defineOrGetCachedFunc = [&](ParseNode *pnodeFnc)
{
Symbol *sym = pnodeFnc->AsParseNodeFnc()->GetFuncSymbol();
if (pnodeFnc->AsParseNodeFnc()->IsDeclaration())
{
// Do we need to define the function here (i.e., is it not one of our cached locals)?
// Only happens if the sym is null (e.g., function x.y(){}).
if (sym == nullptr)
{
this->DefineOneFunction(pnodeFnc->AsParseNodeFnc(), funcInfoParent);
}
else if (!sym->IsInSlot(this, funcInfoParent) && sym->GetLocation() != Js::Constants::NoRegister)
{
// If it was defined by InitCachedFuncs, do we need to put it in a register rather than a slot?
m_writer.Reg1Unsigned1(Js::OpCode::GetCachedFunc, sym->GetLocation(), slotCount);
}
// The "x = function() {...}" case is being generated on the fly, during emission,
// so the caller expects to be able to release this register.
funcInfoParent->ReleaseLoc(pnodeFnc);
pnodeFnc->location = Js::Constants::NoRegister;
slotCount++;
}
};
MapContainerScopeFunctions(pnodeParent, defineOrGetCachedFunc);
AdeletePlus(alloc, extraBytes, info);
}
void ByteCodeGenerator::DefineUncachedFunctions(FuncInfo *funcInfoParent)
{
ParseNode *pnodeParent = funcInfoParent->root;
auto defineCheck = [&](ParseNode *pnodeFnc)
{
Assert(pnodeFnc->nop == knopFncDecl);
//
// Don't define the function upfront in following cases
// 1. x = function() {...};
// Don't define the function for all modes.
// Such a function can only be accessed via the LHS, so we define it at the assignment point
// rather than the scope entry to save a register (and possibly save the whole definition).
//
// 2. x = function f() {...};
// f is not visible in the enclosing scope.
// Such function expressions should be emitted only at the assignment point, as can be used only
// after the assignment. Might save register.
//
if (pnodeFnc->AsParseNodeFnc()->IsDeclaration() || pnodeFnc->AsParseNodeFnc()->IsDefaultModuleExport())
{
this->DefineOneFunction(pnodeFnc->AsParseNodeFnc(), funcInfoParent);
// The "x = function() {...}" case is being generated on the fly, during emission,
// so the caller expects to be able to release this register.
funcInfoParent->ReleaseLoc(pnodeFnc);
pnodeFnc->location = Js::Constants::NoRegister;
}
};
MapContainerScopeFunctions(pnodeParent, defineCheck);
}
void EmitAssignmentToFuncName(ParseNodeFnc *pnodeFnc, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfoParent)
{
// Assign the location holding the func object reference to the given name.
Assert(pnodeFnc->pnodeName->nop == knopVarDecl);
Symbol *sym = pnodeFnc->pnodeName->sym;
if (sym != nullptr && !sym->GetIsFuncExpr())
{
if (sym->GetIsModuleExportStorage())
{
byteCodeGenerator->EmitPropStore(pnodeFnc->location, sym, nullptr, funcInfoParent);
}
else if (sym->GetIsGlobal())
{
Js::PropertyId propertyId = sym->GetPosition();
byteCodeGenerator->EmitGlobalFncDeclInit(pnodeFnc->location, propertyId, funcInfoParent);
if (byteCodeGenerator->GetFlags() & fscrEval && !funcInfoParent->GetIsStrictMode())
{
byteCodeGenerator->EmitPropStore(pnodeFnc->location, sym, nullptr, funcInfoParent);
}
}
else
{
if (sym->NeedsSlotAlloc(byteCodeGenerator, funcInfoParent))
{
if (!sym->GetHasNonCommittedReference() ||
(funcInfoParent->GetParsedFunctionBody()->DoStackNestedFunc()))
{
// No point in trying to optimize if there are no references before we have to commit to slot.
// And not safe to delay putting a stack function in the slot, since we may miss boxing.
sym->SetIsCommittedToSlot();
}
}
if (sym->GetScope()->GetFunc() != byteCodeGenerator->TopFuncInfo())
{
byteCodeGenerator->EmitPropStore(pnodeFnc->location, sym, nullptr, funcInfoParent);
}
else
{
byteCodeGenerator->EmitLocalPropInit(pnodeFnc->location, sym, funcInfoParent);
}
Symbol * fncScopeSym = sym->GetFuncScopeVarSym();
if (fncScopeSym)
{
if (fncScopeSym->GetIsGlobal() && byteCodeGenerator->GetFlags() & fscrEval)
{
Js::PropertyId propertyId = fncScopeSym->GetPosition();
byteCodeGenerator->EmitGlobalFncDeclInit(pnodeFnc->location, propertyId, funcInfoParent);
}
else
{
byteCodeGenerator->EmitPropStore(pnodeFnc->location, fncScopeSym, nullptr, funcInfoParent, false, false, /* isFncDeclVar */true);
}
}
}
}
}
Js::RegSlot ByteCodeGenerator::DefineOneFunction(ParseNodeFnc *pnodeFnc, FuncInfo *funcInfoParent, bool generateAssignment, Js::RegSlot regEnv, Js::RegSlot frameDisplayTemp)
{
Assert(pnodeFnc->nop == knopFncDecl);
funcInfoParent->AcquireLoc(pnodeFnc);
if (regEnv == Js::Constants::NoRegister)
{
// If the child needs a closure, find a heap-allocated frame to pass to it.
if (frameDisplayTemp != Js::Constants::NoRegister)
{
// We allocated a temp to hold a local frame display value. Use that.
// It's likely that the FD is on the stack, and we used the temp to load it back.
regEnv = frameDisplayTemp;
}
else if (funcInfoParent->frameDisplayRegister != Js::Constants::NoRegister)
{
// This function has built a frame display, so pass it down.
regEnv = funcInfoParent->frameDisplayRegister;
}
else
{
// This function has no captured locals but inherits a closure environment, so pass it down.
regEnv = funcInfoParent->GetEnvRegister();
}
regEnv = this->PrependLocalScopes(regEnv, Js::Constants::NoRegister, funcInfoParent);
}
// AssertMsg(funcInfo->nonLocalSymbols == 0 || regEnv != funcInfoParent->nullConstantRegister,
// "We need a closure for the nested function");
Assert(pnodeFnc->nestedIndex != (uint)-1);
// If we are in a parameter scope and it is not merged with body scope then we have to create the child function as an inner function
if (regEnv == funcInfoParent->frameDisplayRegister || regEnv == funcInfoParent->GetEnvRegister())
{
m_writer.NewFunction(pnodeFnc->location, pnodeFnc->nestedIndex, pnodeFnc->IsCoroutine(), pnodeFnc->GetHomeObjLocation());
}
else
{
m_writer.NewInnerFunction(pnodeFnc->location, pnodeFnc->nestedIndex, regEnv, pnodeFnc->IsCoroutine(), pnodeFnc->GetHomeObjLocation());
}
if (funcInfoParent->IsGlobalFunction() && (this->flags & fscrEval))
{
// A function declared at global scope in eval is untrackable,
// so make sure the caller's cached scope is invalidated.
this->funcEscapes = true;
}
else
{
if (pnodeFnc->IsDeclaration())
{
Symbol * funcSymbol = pnodeFnc->GetFuncSymbol();
if (funcSymbol)
{
// In the case where a let/const declaration is the same symbol name
// as the function declaration (shadowing case), the let/const var and
// the function declaration symbol are the same and share the same flags
// (particularly, sym->GetIsBlockVar() for this code path).
//
// For example:
// let a = 0; // <-- sym->GetIsBlockVar() = true
// function b(){} // <-- sym2->GetIsBlockVar() = false
//
// let x = 0; // <-- sym3->GetIsBlockVar() = true
// function x(){} // <-- sym3->GetIsBlockVar() = true
//
// In order to tell if the function is actually part
// of a block scope, we compare against the function scope here.
// Note that having a function with the same name as a let/const declaration
// is a redeclaration error, but we're pushing the fix for this out since it's
// a bit involved.
Assert(funcInfoParent->GetBodyScope() != nullptr && funcSymbol->GetScope() != nullptr);
bool isFunctionDeclarationInBlock = funcSymbol->GetIsBlockVar();
// Track all vars/lets/consts register slot function declarations.
if (ShouldTrackDebuggerMetadata()
// If this is a let binding function declaration at global level, we want to
// be sure to track the register location as well.
&& !(funcInfoParent->IsGlobalFunction() && !isFunctionDeclarationInBlock))
{
if (!funcSymbol->IsInSlot(this, funcInfoParent))
{
funcInfoParent->byteCodeFunction->GetFunctionBody()->InsertSymbolToRegSlotList(funcSymbol->GetName(), pnodeFnc->location, funcInfoParent->varRegsCount);
}
}
if (isFunctionDeclarationInBlock)
{
// We only track inner let bindings for the debugger side.
this->TrackFunctionDeclarationPropertyForDebugger(funcSymbol, funcInfoParent);
}
}
}
}
if (pnodeFnc->IsDefaultModuleExport())
{
this->EmitAssignmentToDefaultModuleExport(pnodeFnc, funcInfoParent);
}
if (pnodeFnc->pnodeName == nullptr || !generateAssignment)
{
return regEnv;
}
EmitAssignmentToFuncName(pnodeFnc, this, funcInfoParent);
return regEnv;
}
void ByteCodeGenerator::DefineUserVars(FuncInfo *funcInfo)
{
// Initialize scope-wide variables on entry to the scope. TODO: optimize by detecting uses that are always reached
// by an existing initialization.
BOOL fGlobal = funcInfo->IsGlobalFunction();
ParseNode *pnode;
Js::FunctionBody *byteCodeFunction = funcInfo->GetParsedFunctionBody();
// Global declarations need a temp register to hold the init value, but the node shouldn't get a register.
// Just assign one on the fly and re-use it for all initializations.
Js::RegSlot tmpReg = fGlobal ? funcInfo->AcquireTmpRegister() : Js::Constants::NoRegister;
for (pnode = funcInfo->root->pnodeVars; pnode; pnode = pnode->AsParseNodeVar()->pnodeNext)
{
Symbol* sym = pnode->AsParseNodeVar()->sym;
if (sym != nullptr && !(pnode->AsParseNodeVar()->isBlockScopeFncDeclVar && sym->GetIsBlockVar()))
{
if (sym->IsSpecialSymbol())
{
// Special symbols have already had their initial values stored into their registers.
// In default-argument case we've stored those values into their slot locations, as well.
// We must do that because a default parameter may access a special symbol through a scope slot.
// In the non-default-argument case, though, we didn't yet store the values into the
// slots so let's do that now.
if (!funcInfo->root->HasNonSimpleParameterList())
{
EmitPropStoreForSpecialSymbol(sym->GetLocation(), sym, sym->GetPid(), funcInfo, true);
if (ShouldTrackDebuggerMetadata() && !sym->IsInSlot(this, funcInfo))
{
byteCodeFunction->InsertSymbolToRegSlotList(sym->GetName(), sym->GetLocation(), funcInfo->varRegsCount);
}
}
continue;
}
if (sym->GetIsCatch() || (pnode->nop == knopVarDecl && sym->GetIsBlockVar()))
{
// The init node was bound to the catch object, because it's inside a catch and has the
// same name as the catch object. But we want to define a user var at function scope,
// so find the right symbol. (We'll still assign the RHS value to the catch object symbol.)
// This also applies to a var declaration in the same scope as a let declaration.
#if DBG
if (sym->IsArguments())
{
// There is a block scoped var named arguments
Assert(!funcInfo->GetHasArguments());
continue;
}
else if (!sym->GetIsCatch())
{
// Assert that catch cannot be at function scope and let and var at function scope is redeclaration error.
Assert(funcInfo->bodyScope != sym->GetScope());
}
#endif
sym = funcInfo->bodyScope->FindLocalSymbol(sym->GetName());
Assert(sym && !sym->GetIsCatch() && !sym->GetIsBlockVar());
}
if (sym->GetSymbolType() == STVariable && !sym->GetIsModuleExportStorage())
{
if (fGlobal)
{
Js::PropertyId propertyId = sym->EnsurePosition(this);
// We do need to initialize some globals to avoid JS errors on loading undefined variables.
// But we first need to make sure we're not trashing built-ins.
if (this->flags & fscrEval)
{
if (funcInfo->byteCodeFunction->GetIsStrictMode())
{
// Check/Init the property of the frame object
this->m_writer.ElementRootU(Js::OpCode::LdLocalElemUndef,
funcInfo->FindOrAddReferencedPropertyId(propertyId));
}
else
{
// The check and the init involve the first element in the scope chain.
this->m_writer.ElementScopedU(
Js::OpCode::LdElemUndefScoped, funcInfo->FindOrAddReferencedPropertyId(propertyId));
}
}
else
{
this->m_writer.ElementU(Js::OpCode::LdElemUndef, ByteCodeGenerator::RootObjectRegister,
funcInfo->FindOrAddReferencedPropertyId(propertyId));
}
}
else if (!sym->IsArguments())
{
if (sym->NeedsSlotAlloc(this, funcInfo))
{
if (!sym->GetHasNonCommittedReference() ||
(sym->GetHasFuncAssignment() && funcInfo->GetParsedFunctionBody()->DoStackNestedFunc()))
{
// No point in trying to optimize if there are no references before we have to commit to slot.
// And not safe to delay putting a stack function in the slot, since we may miss boxing.
sym->SetIsCommittedToSlot();
}
}
// Undef-initialize the home location if it is a register (not closure-captured, or else capture
// is delayed) or a property of an object.
if ((!sym->GetHasInit() && !sym->IsInSlot(this, funcInfo)) ||
(funcInfo->bodyScope->GetIsObject() && !funcInfo->GetHasCachedScope()))
{
Js::RegSlot reg = sym->GetLocation();
if (reg == Js::Constants::NoRegister)
{
Assert(sym->IsInSlot(this, funcInfo));
reg = funcInfo->AcquireTmpRegister();
}
this->m_writer.Reg1(Js::OpCode::LdUndef, reg);
this->EmitLocalPropInit(reg, sym, funcInfo);
if (ShouldTrackDebuggerMetadata() && !sym->GetHasInit() && !sym->IsInSlot(this, funcInfo))
{
byteCodeFunction->InsertSymbolToRegSlotList(sym->GetName(), reg, funcInfo->varRegsCount);
}
funcInfo->ReleaseTmpRegister(reg);
}
}
else if (ShouldTrackDebuggerMetadata())
{
if (!sym->GetHasInit() && !sym->IsInSlot(this, funcInfo))
{
Js::RegSlot reg = sym->GetLocation();
if (reg != Js::Constants::NoRegister)
{
byteCodeFunction->InsertSymbolToRegSlotList(sym->GetName(), reg, funcInfo->varRegsCount);
}
}
}
sym->SetHasInit(TRUE);
}
}
}
if (tmpReg != Js::Constants::NoRegister)
{
funcInfo->ReleaseTmpRegister(tmpReg);
}
for (int i = 0; i < funcInfo->nonUserNonTempRegistersToInitialize.Count(); ++i)
{
m_writer.Reg1(Js::OpCode::LdUndef, funcInfo->nonUserNonTempRegistersToInitialize.Item(i));
}
}
void ByteCodeGenerator::InitBlockScopedNonTemps(ParseNode *pnode, FuncInfo *funcInfo)
{
// Initialize all non-temp register variables on entry to the enclosing func - in particular,
// those with lifetimes that begin after the start of user code and may not be initialized normally.
// This protects us from, for instance, trying to restore garbage on bailout.
// It was originally done in debugger mode only, but we do it always to avoid issues with boxing
// garbage on exit from jitted loop bodies.
while (pnode)
{
switch (pnode->nop)
{
case knopFncDecl:
{
// If this is a block-scoped function, initialize it.
ParseNodeFnc * pnodeFnc = pnode->AsParseNodeFnc();
ParseNodeVar *pnodeName = pnodeFnc->pnodeName;
if (!pnodeFnc->IsMethod() && pnodeName != nullptr)
{
Symbol *sym = pnodeName->sym;
Assert(sym);
if (sym->GetLocation() != Js::Constants::NoRegister &&
sym->GetScope()->IsBlockScope(funcInfo) &&
sym->GetScope()->GetFunc() == funcInfo)
{
this->m_writer.Reg1(Js::OpCode::LdUndef, sym->GetLocation());
}
}
// No need to recurse to the nested scopes, as they belong to a nested function.
pnode = pnodeFnc->pnodeNext;
break;
}
case knopBlock:
{
ParseNodeBlock * pnodeBlock = pnode->AsParseNodeBlock();
Scope *scope = pnodeBlock->scope;
if (scope)
{
if (scope->IsBlockScope(funcInfo))
{
Js::RegSlot scopeLoc = scope->GetLocation();
if (scopeLoc != Js::Constants::NoRegister && !funcInfo->IsTmpReg(scopeLoc))
{
this->m_writer.Reg1(Js::OpCode::LdUndef, scopeLoc);
}
}
auto fnInit = [this, funcInfo](ParseNode *pnode)
{
Symbol *sym = pnode->AsParseNodeVar()->sym;
if (!sym->IsInSlot(this, funcInfo) && !sym->GetIsGlobal() && !sym->GetIsModuleImport())
{
this->m_writer.Reg1(Js::OpCode::InitUndecl, pnode->AsParseNodeVar()->sym->GetLocation());
}
};
IterateBlockScopedVariables(pnodeBlock, fnInit);
}
InitBlockScopedNonTemps(pnodeBlock->pnodeScopes, funcInfo);
pnode = pnodeBlock->pnodeNext;
break;
}
case knopCatch:
InitBlockScopedNonTemps(pnode->AsParseNodeCatch()->pnodeScopes, funcInfo);
pnode = pnode->AsParseNodeCatch()->pnodeNext;
break;
case knopWith:
{
Js::RegSlot withLoc = pnode->location;
AssertMsg(withLoc != Js::Constants::NoRegister && !funcInfo->IsTmpReg(withLoc),
"We should put with objects at known stack locations in debug mode");
this->m_writer.Reg1(Js::OpCode::LdUndef, withLoc);
InitBlockScopedNonTemps(pnode->AsParseNodeWith()->pnodeScopes, funcInfo);
pnode = pnode->AsParseNodeWith()->pnodeNext;
break;
}
default:
Assert(false);
return;
}
}
}
void ByteCodeGenerator::EmitScopeObjectInit(FuncInfo *funcInfo)
{
Assert(!funcInfo->byteCodeFunction->GetFunctionBody()->DoStackNestedFunc());
if (!funcInfo->GetHasCachedScope() /* || forcing scope/inner func caching */)
{
return;
}
Scope* currentScope = funcInfo->GetCurrentChildScope();
uint slotCount = currentScope->GetScopeSlotCount();
uint cachedFuncCount = 0;
Js::PropertyId firstFuncSlot = Js::Constants::NoProperty;
Js::PropertyId firstVarSlot = Js::Constants::NoProperty;
uint extraAlloc = UInt32Math::Add(slotCount, Js::ActivationObjectEx::ExtraSlotCount());
extraAlloc = UInt32Math::Mul(extraAlloc, sizeof(Js::PropertyId));
// Create and fill the array of local property ID's.
// They all have slots assigned to them already (if they need them): see StartEmitFunction.
Js::PropertyIdArray *propIds = funcInfo->GetParsedFunctionBody()->AllocatePropertyIdArrayForFormals(extraAlloc, slotCount, Js::ActivationObjectEx::ExtraSlotCount());
ParseNodeFnc *pnodeFnc = funcInfo->root;
if (funcInfo->GetFuncExprNameReference() && pnodeFnc->GetFuncSymbol()->GetScope() == funcInfo->GetBodyScope())
{
Symbol::SaveToPropIdArray(pnodeFnc->GetFuncSymbol(), propIds, this);
}
if (funcInfo->GetHasArguments())
{
// Because the arguments object can access all instances of same-named formals ("function(x,x){...}"),
// be sure we initialize any duplicate appearances of a formal parameter to "NoProperty".
Js::PropertyId slot = 0;
auto initArg = [&](ParseNode *pnode)
{
if (pnode->IsVarLetOrConst())
{
Symbol *sym = pnode->AsParseNodeVar()->sym;
Assert(sym);
if (sym->GetScopeSlot() == slot)
{
// This is the last appearance of the formal, so record the ID.
Symbol::SaveToPropIdArray(sym, propIds, this);
}
else
{
// This is an earlier duplicate appearance of the formal, so use NoProperty as a placeholder
// since this slot can't be accessed by name.
Assert(sym->GetScopeSlot() != Js::Constants::NoProperty && sym->GetScopeSlot() > slot);
propIds->elements[slot] = Js::Constants::NoProperty;
}
}
else
{
// This is for patterns
propIds->elements[slot] = Js::Constants::NoProperty;
}
slot++;
};
MapFormalsWithoutRest(pnodeFnc, initArg);
ParseNodePtr rest = pnodeFnc->pnodeRest;
if (rest != nullptr && rest->IsVarLetOrConst())
{
// If the rest is in the slot - we need to keep that slot.
Symbol *sym = rest->AsParseNodeVar()->sym;
if (sym->IsInSlot(this, funcInfo))
{
Symbol::SaveToPropIdArray(sym, propIds, this);
}
}
}
else
{
MapFormals(pnodeFnc, [&](ParseNode *pnode)
{
if (pnode->IsVarLetOrConst())
{
Symbol::SaveToPropIdArray(pnode->AsParseNodeVar()->sym, propIds, this);
}
});
}
auto saveFunctionVarsToPropIdArray = [&](ParseNode *pnodeFunction)
{
if (pnodeFunction->AsParseNodeFnc()->IsDeclaration())
{
ParseNode *pnodeName = pnodeFunction->AsParseNodeFnc()->pnodeName;
if (pnodeName != nullptr)
{
while (pnodeName->nop == knopList)
{
if (pnodeName->AsParseNodeBin()->pnode1->nop == knopVarDecl)
{
Symbol *sym = pnodeName->AsParseNodeBin()->pnode1->AsParseNodeVar()->sym;
if (sym)
{
Symbol::SaveToPropIdArray(sym, propIds, this, &firstFuncSlot);
}
}
pnodeName = pnodeName->AsParseNodeBin()->pnode2;
}
if (pnodeName->nop == knopVarDecl)
{
Symbol *sym = pnodeName->AsParseNodeVar()->sym;
if (sym)
{
Symbol::SaveToPropIdArray(sym, propIds, this, &firstFuncSlot);
cachedFuncCount++;
}
}
}
}
};
MapContainerScopeFunctions(pnodeFnc, saveFunctionVarsToPropIdArray);
if (currentScope->GetScopeType() != ScopeType_Parameter)
{
ParseNode *pnode;
Symbol *sym;
for (pnode = pnodeFnc->pnodeVars; pnode; pnode = pnode->AsParseNodeVar()->pnodeNext)
{
sym = pnode->AsParseNodeVar()->sym;
if (!(pnode->AsParseNodeVar()->isBlockScopeFncDeclVar && sym->GetIsBlockVar()))
{
if (sym->GetIsCatch() || (pnode->nop == knopVarDecl && sym->GetIsBlockVar()))
{
sym = currentScope->FindLocalSymbol(sym->GetName());
}
Symbol::SaveToPropIdArray(sym, propIds, this, &firstVarSlot);
}
}
ParseNodeBlock *pnodeBlock = pnodeFnc->pnodeScopes;
for (pnode = pnodeBlock->pnodeLexVars; pnode; pnode = pnode->AsParseNodeVar()->pnodeNext)
{
sym = pnode->AsParseNodeVar()->sym;
Symbol::SaveToPropIdArray(sym, propIds, this, &firstVarSlot);
}
pnodeBlock = pnodeFnc->pnodeBodyScope;
for (pnode = pnodeBlock->pnodeLexVars; pnode; pnode = pnode->AsParseNodeVar()->pnodeNext)
{
sym = pnode->AsParseNodeVar()->sym;
Symbol::SaveToPropIdArray(sym, propIds, this, &firstVarSlot);
}
}
else
{
Assert(!funcInfo->IsBodyAndParamScopeMerged());
}
// Write the first func slot and first var slot into the auxiliary data
Js::PropertyId *slots = propIds->elements + slotCount;
slots[0] = cachedFuncCount;
slots[1] = firstFuncSlot;
slots[2] = firstVarSlot;
slots[3] = funcInfo->GetParsedFunctionBody()->NewObjectLiteral();
propIds->hasNonSimpleParams = funcInfo->root->HasNonSimpleParameterList();
funcInfo->GetParsedFunctionBody()->SetHasCachedScopePropIds(true);
}
void ByteCodeGenerator::SetClosureRegisters(FuncInfo* funcInfo, Js::FunctionBody* byteCodeFunction)
{
if (funcInfo->frameDisplayRegister != Js::Constants::NoRegister)
{
byteCodeFunction->MapAndSetLocalFrameDisplayRegister(funcInfo->frameDisplayRegister);
}
if (funcInfo->frameObjRegister != Js::Constants::NoRegister)
{
byteCodeFunction->MapAndSetLocalClosureRegister(funcInfo->frameObjRegister);
byteCodeFunction->SetHasScopeObject(true);
}
else if (funcInfo->frameSlotsRegister != Js::Constants::NoRegister)
{
byteCodeFunction->MapAndSetLocalClosureRegister(funcInfo->frameSlotsRegister);
}
if (funcInfo->paramSlotsRegister != Js::Constants::NoRegister)
{
byteCodeFunction->MapAndSetParamClosureRegister(funcInfo->paramSlotsRegister);
}
}
void ByteCodeGenerator::FinalizeRegisters(FuncInfo* funcInfo, Js::FunctionBody* byteCodeFunction)
{
if (funcInfo->NeedEnvRegister())
{
bool constReg = !funcInfo->GetIsTopLevelEventHandler() && funcInfo->IsGlobalFunction() && !(this->flags & fscrEval);
funcInfo->AssignEnvRegister(constReg);
}
// Set the function body's constant count before emitting anything so that the byte code writer
// can distinguish constants from variables.
byteCodeFunction->CheckAndSetConstantCount(funcInfo->constRegsCount);
this->SetClosureRegisters(funcInfo, byteCodeFunction);
if (this->IsInDebugMode() || byteCodeFunction->IsCoroutine())
{
// Give permanent registers to the inner scopes in debug mode.
// TODO: We create seperate debuggerscopes for each block which has own scope. These are stored in the var registers
// allocated below. Ideally we should change this logic to not allocate separate registers for these and save the debug
// info in corresponding symbols and use it from there. This will also affect the temp register allocation logic in
// EmitOneFunction.
uint innerScopeCount = funcInfo->InnerScopeCount();
byteCodeFunction->SetInnerScopeCount(innerScopeCount);
if (innerScopeCount)
{
funcInfo->SetFirstInnerScopeReg(funcInfo->NextVarRegister());
for (uint i = 1; i < innerScopeCount; i++)
{
funcInfo->NextVarRegister();
}
}
}
// NOTE: The FunctionBody expects the yield reg to be the final non-temp.
if (byteCodeFunction->IsCoroutine())
{
if (funcInfo->root->IsAsync())
{
funcInfo->AssignAwaitRegister();
}
funcInfo->AssignYieldRegister();
}
Js::RegSlot firstTmpReg = funcInfo->varRegsCount;
funcInfo->SetFirstTmpReg(firstTmpReg);
byteCodeFunction->SetFirstTmpReg(funcInfo->RegCount());
}
void ByteCodeGenerator::InitScopeSlotArray(FuncInfo * funcInfo)
{
// Record slots info for ScopeSlots/ScopeObject.
uint scopeSlotCount = funcInfo->bodyScope->GetScopeSlotCount();
bool isSplitScope = !funcInfo->IsBodyAndParamScopeMerged();
Assert(funcInfo->paramScope == nullptr || funcInfo->paramScope->GetScopeSlotCount() == 0 || isSplitScope);
uint scopeSlotCountForParamScope = funcInfo->paramScope != nullptr ? funcInfo->paramScope->GetScopeSlotCount() : 0;
if (scopeSlotCount == 0 && scopeSlotCountForParamScope == 0)
{
return;
}
Js::FunctionBody *byteCodeFunction = funcInfo->GetParsedFunctionBody();
if (scopeSlotCount > 0 || scopeSlotCountForParamScope > 0)
{
byteCodeFunction->SetScopeSlotArraySizes(scopeSlotCount, scopeSlotCountForParamScope);
}
// TODO: Need to add property ids for the case when scopeSlotCountForParamSCope is non-zero
if (scopeSlotCount)
{
Js::PropertyId *propertyIdsForScopeSlotArray = RecyclerNewArrayLeafZ(scriptContext->GetRecycler(), Js::PropertyId, scopeSlotCount);
byteCodeFunction->SetPropertyIdsForScopeSlotArray(propertyIdsForScopeSlotArray, scopeSlotCount, scopeSlotCountForParamScope);
AssertMsg(!byteCodeFunction->IsReparsed() || byteCodeFunction->WasEverAsmJsMode() || byteCodeFunction->scopeSlotArraySize == scopeSlotCount,
"The slot array size is different between debug and non-debug mode");
#if DEBUG
for (UINT i = 0; i < scopeSlotCount; i++)
{
propertyIdsForScopeSlotArray[i] = Js::Constants::NoProperty;
}
#endif
auto setPropertyIdForScopeSlotArray =
[scopeSlotCount, propertyIdsForScopeSlotArray]
(Js::PropertyId slot, Js::PropertyId propId)
{
if (slot < 0 || (uint)slot >= scopeSlotCount)
{
Js::Throw::FatalInternalError();
}
propertyIdsForScopeSlotArray[slot] = propId;
};
auto setPropIdsForScopeSlotArray = [this, funcInfo, setPropertyIdForScopeSlotArray](Symbol *const sym)
{
if (sym->NeedsSlotAlloc(this, funcInfo))
{
// All properties should get correct propertyId here.
Assert(sym->HasScopeSlot()); // We can't allocate scope slot now. Any symbol needing scope slot must have allocated it before this point.
setPropertyIdForScopeSlotArray(sym->GetScopeSlot(), sym->EnsurePosition(funcInfo));
}
};
funcInfo->GetBodyScope()->ForEachSymbol(setPropIdsForScopeSlotArray);
#if DEBUG
for (UINT i = 0; i < scopeSlotCount; i++)
{
Assert(propertyIdsForScopeSlotArray[i] != Js::Constants::NoProperty
|| funcInfo->frameObjRegister != Js::Constants::NoRegister); // ScopeObject may have unassigned entries, e.g. for same-named parameters
}
#endif
}
}
// temporarily load all constants and special registers in a single block
void ByteCodeGenerator::LoadAllConstants(FuncInfo *funcInfo)
{
Symbol *sym;
Js::FunctionBody *byteCodeFunction = funcInfo->GetParsedFunctionBody();
byteCodeFunction->CreateConstantTable();
if (funcInfo->nullConstantRegister != Js::Constants::NoRegister)
{
byteCodeFunction->RecordNullObject(byteCodeFunction->MapRegSlot(funcInfo->nullConstantRegister));
}
if (funcInfo->undefinedConstantRegister != Js::Constants::NoRegister)
{
byteCodeFunction->RecordUndefinedObject(byteCodeFunction->MapRegSlot(funcInfo->undefinedConstantRegister));
}
if (funcInfo->trueConstantRegister != Js::Constants::NoRegister)
{
byteCodeFunction->RecordTrueObject(byteCodeFunction->MapRegSlot(funcInfo->trueConstantRegister));
}
if (funcInfo->falseConstantRegister != Js::Constants::NoRegister)
{
byteCodeFunction->RecordFalseObject(byteCodeFunction->MapRegSlot(funcInfo->falseConstantRegister));
}
if (funcInfo->frameObjRegister != Js::Constants::NoRegister)
{
m_writer.RecordObjectRegister(funcInfo->frameObjRegister);
if (!funcInfo->GetApplyEnclosesArgs())
{
this->EmitScopeObjectInit(funcInfo);
}
#if DBG
uint count = 0;
funcInfo->GetBodyScope()->ForEachSymbol([&](Symbol *const sym)
{
if (sym->NeedsSlotAlloc(this, funcInfo))
{
// All properties should get correct propertyId here.
count++;
}
});
if (funcInfo->GetParamScope() != nullptr)
{
funcInfo->GetParamScope()->ForEachSymbol([&](Symbol *const sym)
{
if (sym->NeedsSlotAlloc(this, funcInfo))
{
// All properties should get correct propertyId here.
count++;
}
});
}
// A reparse should result in the same size of the activation object.
// Exclude functions which were created from the ByteCodeCache.
AssertMsg(!byteCodeFunction->IsReparsed() || byteCodeFunction->HasGeneratedFromByteCodeCache() ||
byteCodeFunction->scopeObjectSize == count || byteCodeFunction->WasEverAsmJsMode(),
"The activation object size is different between debug and non-debug mode");
byteCodeFunction->scopeObjectSize = count;
#endif
}
else if (funcInfo->frameSlotsRegister != Js::Constants::NoRegister)
{
int scopeSlotCount = funcInfo->bodyScope->GetScopeSlotCount();
int paramSlotCount = funcInfo->paramScope->GetScopeSlotCount();
if (scopeSlotCount == 0 && paramSlotCount == 0)
{
AssertMsg(funcInfo->frameDisplayRegister != Js::Constants::NoRegister, "Why do we need scope slots?");
m_writer.Reg1(Js::OpCode::LdC_A_Null, funcInfo->frameSlotsRegister);
}
}
if (funcInfo->funcExprScope && funcInfo->funcExprScope->GetIsObject())
{
byteCodeFunction->MapAndSetFuncExprScopeRegister(funcInfo->funcExprScope->GetLocation());
byteCodeFunction->SetEnvDepth((uint16)-1);
}
bool thisLoadedFromParams = false;
if (funcInfo->NeedEnvRegister())
{
byteCodeFunction->MapAndSetEnvRegister(funcInfo->GetEnvRegister());
if (funcInfo->GetIsTopLevelEventHandler())
{
if (funcInfo->GetThisSymbol())
{
byteCodeFunction->MapAndSetThisRegisterForEventHandler(funcInfo->GetThisSymbol()->GetLocation());
}
// The environment is the namespace hierarchy starting with "this".
Assert(!funcInfo->RegIsConst(funcInfo->GetEnvRegister()));
thisLoadedFromParams = true;
this->InvalidateCachedOuterScopes(funcInfo);
}
else if (funcInfo->IsGlobalFunction() && !(this->flags & fscrEval))
{
Assert(funcInfo->RegIsConst(funcInfo->GetEnvRegister()));
if (funcInfo->GetIsStrictMode())
{
byteCodeFunction->RecordStrictNullDisplayConstant(byteCodeFunction->MapRegSlot(funcInfo->GetEnvRegister()));
}
else
{
byteCodeFunction->RecordNullDisplayConstant(byteCodeFunction->MapRegSlot(funcInfo->GetEnvRegister()));
}
}
else
{
// environment may be required to load "this"
Assert(!funcInfo->RegIsConst(funcInfo->GetEnvRegister()));
this->InvalidateCachedOuterScopes(funcInfo);
}
}
if (funcInfo->frameDisplayRegister != Js::Constants::NoRegister)
{
m_writer.RecordFrameDisplayRegister(funcInfo->frameDisplayRegister);
}
this->RecordAllIntConstants(funcInfo);
this->RecordAllStrConstants(funcInfo);
this->RecordAllBigIntConstants(funcInfo);
this->RecordAllStringTemplateCallsiteConstants(funcInfo);
funcInfo->doubleConstantToRegister.Map([byteCodeFunction](double d, Js::RegSlot location)
{
byteCodeFunction->RecordFloatConstant(byteCodeFunction->MapRegSlot(location), d);
});
// WARNING !!!
// DO NOT emit any bytecode before loading the heap arguments. This is because those opcodes may bail
// out (unlikely, since opcodes emitted in this function should not correspond to user code, but possible)
// and the Jit assumes that there cannot be any bailouts before LdHeapArguments (or its equivalent)
if (funcInfo->GetHasArguments())
{
sym = funcInfo->GetArgumentsSymbol();
Assert(sym);
Assert(funcInfo->GetHasHeapArguments());
if (funcInfo->GetCallsEval() || (!funcInfo->GetApplyEnclosesArgs()))
{
this->LoadHeapArguments(funcInfo);
}
}
else if (!funcInfo->IsGlobalFunction() && !IsInNonDebugMode())
{
uint count = funcInfo->inArgsCount + (funcInfo->root->pnodeRest != nullptr ? 1 : 0) - 1;
if (count != 0)
{
Js::PropertyIdArray *propIds = RecyclerNewPlus(scriptContext->GetRecycler(), UInt32Math::Mul(count, sizeof(Js::PropertyId)), Js::PropertyIdArray, count, 0);
GetFormalArgsArray(this, funcInfo, propIds);
byteCodeFunction->SetPropertyIdsOfFormals(propIds);
}
}
// Class constructors do not have a [[call]] slot but we don't implement a generic way to express this.
// What we do is emit a check for the new flag here. If we don't have CallFlags_New set, the opcode will throw.
// We need to do this before emitting 'this' since the base class constructor will try to construct a new object.
if (funcInfo->IsClassConstructor())
{
m_writer.Empty(Js::OpCode::ChkNewCallFlag);
}
// new.target may be used to construct the 'this' register so make sure to load it first
if (funcInfo->GetNewTargetSymbol())
{
this->LoadNewTargetObject(funcInfo);
}
if (funcInfo->GetThisSymbol())
{
this->LoadThisObject(funcInfo, thisLoadedFromParams);
}
else if (ShouldLoadConstThis(funcInfo))
{
this->EmitThis(funcInfo, funcInfo->thisConstantRegister, funcInfo->nullConstantRegister);
}
if (funcInfo->GetSuperSymbol())
{
this->LoadSuperObject(funcInfo);
}
if (funcInfo->GetSuperConstructorSymbol())
{
this->LoadSuperConstructorObject(funcInfo);
}
if (funcInfo->GetImportMetaSymbol())
{
this->LoadImportMetaObject(funcInfo);
}
//
// If the function is a function expression with a name,
// load the function object at runtime to its activation object.
//
sym = funcInfo->root->GetFuncSymbol();
bool funcExprWithName = !funcInfo->IsGlobalFunction() && sym && sym->GetIsFuncExpr();
if (funcExprWithName)
{
if (funcInfo->GetFuncExprNameReference() ||
(funcInfo->funcExprScope && funcInfo->funcExprScope->GetIsObject()))
{
//
// x = function f(...) { ... }
// A named function expression's name (Symbol:f) belongs to the enclosing scope.
// Thus there are no uses of 'f' within the scope of the function (as references to 'f'
// are looked up in the closure). So, we can't use f's register as it is from the enclosing
// scope's register namespace. So use a tmp register.
// In ES5 mode though 'f' is *not* a part of the enclosing scope. So we always assign 'f' a register
// from it's register namespace, which LdFuncExpr can use.
//
Js::RegSlot ldFuncExprDst = sym->GetLocation();
this->m_writer.Reg1(Js::OpCode::LdFuncExpr, ldFuncExprDst);
if (sym->IsInSlot(this, funcInfo))
{
Js::RegSlot scopeLocation;
AnalysisAssert(funcInfo->funcExprScope);
if (funcInfo->funcExprScope->GetIsObject())
{
scopeLocation = funcInfo->funcExprScope->GetLocation();
this->m_writer.Property(Js::OpCode::StFuncExpr, sym->GetLocation(), scopeLocation,
funcInfo->FindOrAddReferencedPropertyId(sym->GetPosition()));
}
else if (funcInfo->paramScope->GetIsObject() || (funcInfo->paramScope->GetCanMerge() && funcInfo->bodyScope->GetIsObject()))
{
this->m_writer.ElementU(Js::OpCode::StLocalFuncExpr, sym->GetLocation(),
funcInfo->FindOrAddReferencedPropertyId(sym->GetPosition()));
}
else
{
Assert(sym->HasScopeSlot());
this->m_writer.SlotI1(Js::OpCode::StLocalSlot, sym->GetLocation(),
sym->GetScopeSlot() + Js::ScopeSlots::FirstSlotIndex);
}
}
else if (ShouldTrackDebuggerMetadata())
{
funcInfo->byteCodeFunction->GetFunctionBody()->InsertSymbolToRegSlotList(sym->GetName(), sym->GetLocation(), funcInfo->varRegsCount);
}
}
}
}
void ByteCodeGenerator::InvalidateCachedOuterScopes(FuncInfo *funcInfo)
{
Assert(funcInfo->GetEnvRegister() != Js::Constants::NoRegister);
// Walk the scope stack, from funcInfo outward, looking for scopes that have been cached.
Scope *scope = funcInfo->GetBodyScope()->GetEnclosingScope();
uint32 envIndex = 0;
while (scope && scope->GetFunc() == funcInfo)
{
// Skip over FuncExpr Scope and parameter scope for current funcInfo to get to the first enclosing scope of the outer function.
scope = scope->GetEnclosingScope();
}
for (; scope; scope = scope->GetEnclosingScope())
{
FuncInfo *func = scope->GetFunc();
if (scope == func->GetBodyScope())
{
if (func->Escapes() && func->GetHasCachedScope())
{
AssertOrFailFast(scope->GetIsObject());
this->m_writer.Unsigned1(Js::OpCode::InvalCachedScope, envIndex);
}
}
if (scope->GetMustInstantiate())
{
envIndex++;
}
}
}
void ByteCodeGenerator::LoadThisObject(FuncInfo *funcInfo, bool thisLoadedFromParams)
{
Symbol* thisSym = funcInfo->GetThisSymbol();
Assert(thisSym);
Assert(!funcInfo->IsLambda());
if (funcInfo->IsClassConstructor())
{
// Derived class constructors initialize 'this' to be Undecl
// - we'll check this value during a super call and during 'this' access
//
// Base class constructors initialize 'this' to a new object using new.target
if (funcInfo->IsBaseClassConstructor())
{
Symbol* newTargetSym = funcInfo->GetNewTargetSymbol();
Assert(newTargetSym);
this->Writer()->Reg2(Js::OpCode::NewScObjectNoCtorFull, thisSym->GetLocation(), newTargetSym->GetLocation());
}
else
{
this->m_writer.Reg1(Js::OpCode::InitUndecl, thisSym->GetLocation());
}
}
else if (!funcInfo->IsGlobalFunction())
{
//
// thisLoadedFromParams would be true for the event Handler case,
// "this" would have been loaded from parameters to put in the environment
//
if (funcInfo->GetIsStrictMode())
{
m_writer.ArgIn0(thisSym->GetLocation());
}
else if (!thisLoadedFromParams)
{
Js::RegSlot tmpReg = funcInfo->AcquireTmpRegister();
m_writer.ArgIn0(tmpReg);
EmitThis(funcInfo, thisSym->GetLocation(), tmpReg);
funcInfo->ReleaseTmpRegister(tmpReg);
}
else
{
EmitThis(funcInfo, thisSym->GetLocation(), thisSym->GetLocation());
}
}
else
{
Assert(funcInfo->IsGlobalFunction());
Js::RegSlot root = funcInfo->nullConstantRegister;
EmitThis(funcInfo, thisSym->GetLocation(), root);
}
}
void ByteCodeGenerator::LoadNewTargetObject(FuncInfo *funcInfo)
{
Symbol* newTargetSym = funcInfo->GetNewTargetSymbol();
Assert(newTargetSym);
if (funcInfo->IsClassConstructor())
{
Assert(!funcInfo->IsLambda());
m_writer.ArgIn0(newTargetSym->GetLocation());
}
else if (funcInfo->IsGlobalFunction())
{
m_writer.Reg1(Js::OpCode::LdUndef, newTargetSym->GetLocation());
}
else
{
m_writer.Reg1(Js::OpCode::LdNewTarget, newTargetSym->GetLocation());
}
}
void ByteCodeGenerator::LoadImportMetaObject(FuncInfo* funcInfo)
{
Symbol* importMetaSym = funcInfo->GetImportMetaSymbol();
Assert(importMetaSym);
m_writer.Reg1Unsigned1(Js::OpCode::LdImportMeta, importMetaSym->GetLocation(), this->GetModuleID());
}
void ByteCodeGenerator::LoadSuperConstructorObject(FuncInfo *funcInfo)
{
Symbol* superConstructorSym = funcInfo->GetSuperConstructorSymbol();
Assert(superConstructorSym);
Assert(!funcInfo->IsLambda());
if (funcInfo->IsDerivedClassConstructor())
{
m_writer.Reg1(Js::OpCode::LdFuncObj, superConstructorSym->GetLocation());
}
else
{
m_writer.Reg1(Js::OpCode::LdUndef, superConstructorSym->GetLocation());
}
}
void ByteCodeGenerator::LoadSuperObject(FuncInfo *funcInfo)
{
Symbol* superSym = funcInfo->GetSuperSymbol();
Assert(superSym);
Assert(!funcInfo->IsLambda());
m_writer.Reg1(Js::OpCode::LdHomeObj, superSym->GetLocation());
}
void ByteCodeGenerator::EmitSuperCall(FuncInfo* funcInfo, ParseNodeSuperCall * pnodeSuperCall, BOOL fReturnValue, BOOL fEvaluateComponents)
{
FuncInfo* nonLambdaFunc = funcInfo;
bool isResultUsed = pnodeSuperCall->isUsed;
if (funcInfo->IsLambda())
{
nonLambdaFunc = this->FindEnclosingNonLambda();
}
if (nonLambdaFunc->IsBaseClassConstructor())
{
// super() is not allowed in base class constructors. If we detect this, emit a ReferenceError and skip making the call.
this->Writer()->W1(Js::OpCode::RuntimeReferenceError, SCODE_CODE(JSERR_ClassSuperInBaseClass));
return;
}
pnodeSuperCall->isUsed = true;
// pnode->location refers to two things: the result of the inner function call (`temp` in the pseudocode below),
// and the result of the super() expression itself
funcInfo->AcquireLoc(pnodeSuperCall);
// We need to emit 'this' directly so we can skip throwing a reference error if 'this' is currently undecl (we want to get undecl if 'this' is undecl)
funcInfo->AcquireLoc(pnodeSuperCall->pnodeThis);
EmitPropLoadThis(pnodeSuperCall->pnodeThis->location, pnodeSuperCall->pnodeThis, funcInfo, false);
EmitLoad(pnodeSuperCall->pnodeNewTarget, this, funcInfo);
Assert(pnodeSuperCall->isSuperCall);
EmitLoad(pnodeSuperCall->pnodeTarget, this, funcInfo);
//
// if (super is class constructor) {
// _this = new.target;
// } else {
// _this = NewScObjFull(new.target);
// }
//
// temp = super.call(_this, new.target); // CallFlag_New | CallFlag_NewTarget | CallFlag_ExtraArg
// if (temp is object) {
// _this = temp;
// }
//
// if (UndeclBlockVar === this) {
// this = _this;
// } else {
// throw ReferenceError;
// }
//
Js::RegSlot thisForSuperCall = funcInfo->AcquireTmpRegister();
Js::RegSlot valueForThis = funcInfo->AcquireTmpRegister();
Js::RegSlot tmpUndeclReg = funcInfo->AcquireTmpRegister();
Js::ByteCodeLabel useNewTargetForThisLabel = this->Writer()->DefineLabel();
Js::ByteCodeLabel makeCallLabel = this->Writer()->DefineLabel();
Js::ByteCodeLabel useSuperCallResultLabel = this->Writer()->DefineLabel();
Js::ByteCodeLabel doneLabel = this->Writer()->DefineLabel();
Js::RegSlot tmpReg = this->EmitLdObjProto(Js::OpCode::LdFuncObjProto, pnodeSuperCall->pnodeTarget->location, funcInfo);
this->Writer()->BrReg1(Js::OpCode::BrOnClassConstructor, useNewTargetForThisLabel, tmpReg);
this->Writer()->Reg2(Js::OpCode::NewScObjectNoCtorFull, thisForSuperCall, pnodeSuperCall->pnodeNewTarget->location);
this->Writer()->Br(Js::OpCode::Br, makeCallLabel);
this->Writer()->MarkLabel(useNewTargetForThisLabel);
this->Writer()->Reg2(Js::OpCode::Ld_A_ReuseLoc, thisForSuperCall, pnodeSuperCall->pnodeNewTarget->location);
this->Writer()->MarkLabel(makeCallLabel);
EmitCall(pnodeSuperCall, this, funcInfo, fReturnValue, fEvaluateComponents, thisForSuperCall, pnodeSuperCall->pnodeNewTarget->location);
// We have to use another temp for the this value before assigning to this register.
// This is because IRBuilder does not expect us to use the value of a temp after potentially assigning to that same temp.
// Ex:
// _this = new.target;
// temp = super.call(_this);
// if (temp is object) {
// _this = temp; // creates a new sym for _this as it was previously used
// }
// this = _this; // tries to loads a value from the old sym (which is dead)
this->Writer()->BrReg1(Js::OpCode::BrOnObject_A, useSuperCallResultLabel, pnodeSuperCall->location);
this->Writer()->Reg2(Js::OpCode::Ld_A, valueForThis, thisForSuperCall);
this->Writer()->Br(Js::OpCode::Br, doneLabel);
this->Writer()->MarkLabel(useSuperCallResultLabel);
this->Writer()->Reg2(Js::OpCode::Ld_A_ReuseLoc, valueForThis, pnodeSuperCall->location);
this->Writer()->MarkLabel(doneLabel);
// The call is done and we know what we will bind to 'this' so let's check to see if 'this' is already decl.
Js::ByteCodeLabel skipLabel = this->Writer()->DefineLabel();
this->Writer()->Reg1(Js::OpCode::InitUndecl, tmpUndeclReg);
this->Writer()->BrReg2(Js::OpCode::BrSrEq_A, skipLabel, pnodeSuperCall->pnodeThis->location, tmpUndeclReg);
this->Writer()->W1(Js::OpCode::RuntimeReferenceError, SCODE_CODE(JSERR_ClassThisAlreadyAssigned));
this->Writer()->MarkLabel(skipLabel);
// If calling code cares about the return value, then move the selected `this` value into the result register.
if (isResultUsed)
{
this->Writer()->Reg2(Js::OpCode::Ld_A, pnodeSuperCall->location, valueForThis);
}
Symbol* thisSym = pnodeSuperCall->pnodeThis->sym;
this->Writer()->Reg2(Js::OpCode::Ld_A, pnodeSuperCall->pnodeThis->location, valueForThis);
EmitPropStoreForSpecialSymbol(pnodeSuperCall->pnodeThis->location, thisSym, pnodeSuperCall->pnodeThis->pid, funcInfo, false);
funcInfo->ReleaseTmpRegister(tmpUndeclReg);
funcInfo->ReleaseTmpRegister(valueForThis);
funcInfo->ReleaseTmpRegister(thisForSuperCall);
funcInfo->ReleaseLoc(pnodeSuperCall->pnodeTarget);
funcInfo->ReleaseLoc(pnodeSuperCall->pnodeNewTarget);
funcInfo->ReleaseLoc(pnodeSuperCall->pnodeThis);
}
void ByteCodeGenerator::EmitClassConstructorEndCode(FuncInfo *funcInfo)
{
Symbol* thisSym = funcInfo->GetThisSymbol();
if (thisSym && thisSym->GetLocation() != Js::Constants::NoRegister)
{
EmitPropLoad(ByteCodeGenerator::ReturnRegister, thisSym, thisSym->GetPid(), funcInfo, true);
this->m_writer.Reg1(Js::OpCode::ChkUndecl, ByteCodeGenerator::ReturnRegister);
}
}
void ByteCodeGenerator::EmitThis(FuncInfo *funcInfo, Js::RegSlot lhsLocation, Js::RegSlot fromRegister)
{
if (funcInfo->byteCodeFunction->GetIsStrictMode() && !funcInfo->IsGlobalFunction() && !funcInfo->IsLambda())
{
if (lhsLocation != fromRegister)
{
m_writer.Reg2(Js::OpCode::Ld_A, lhsLocation, fromRegister);
}
}
else
{
m_writer.Reg2Int1(Js::OpCode::LdThis, lhsLocation, fromRegister, this->GetModuleID());
}
}
void ByteCodeGenerator::EmitLoadFormalIntoRegister(ParseNode *pnodeFormal, Js::RegSlot pos, FuncInfo *funcInfo)
{
if (pnodeFormal->IsVarLetOrConst())
{
// Get the param from its argument position into its assigned register.
// The position should match the location, otherwise, it has been shadowed by parameter with the same name
Symbol *formal = pnodeFormal->AsParseNodeVar()->sym;
if (formal->GetLocation() + 1 == pos)
{
// Transfer to the frame object, etc., if necessary.
this->EmitLocalPropInit(formal->GetLocation(), formal, funcInfo);
}
}
}
void ByteCodeGenerator::HomeArguments(FuncInfo *funcInfo)
{
if (ShouldTrackDebuggerMetadata())
{
// Add formals to the debugger propertyidcontainer for reg slots
auto addFormalsToPropertyIdContainer = [this, funcInfo](ParseNode *pnodeFormal)
{
if (pnodeFormal->IsVarLetOrConst())
{
Symbol* formal = pnodeFormal->AsParseNodeVar()->sym;
if (!formal->IsInSlot(this, funcInfo))
{
Assert(!formal->GetHasInit());
funcInfo->GetParsedFunctionBody()->InsertSymbolToRegSlotList(formal->GetName(), formal->GetLocation(), funcInfo->varRegsCount);
}
}
};
MapFormals(funcInfo->root, addFormalsToPropertyIdContainer);
}
// Transfer formal parameters to their home locations on the local frame.
if (funcInfo->GetHasArguments())
{
ParseNodePtr rest = funcInfo->root->pnodeRest;
if (rest != nullptr && rest->IsVarLetOrConst())
{
// Since we don't have to iterate over arguments here, we'll trust the location to be correct.
Symbol* sym = rest->AsParseNodeVar()->sym;
EmitLoadFormalIntoRegister(rest, sym->GetLocation() + 1, funcInfo);
}
// The arguments object creation helper does this work for us.
return;
}
Js::ArgSlot pos = 1;
auto loadFormal = [&](ParseNode *pnodeFormal)
{
EmitLoadFormalIntoRegister(pnodeFormal, pos, funcInfo);
pos++;
};
MapFormals(funcInfo->root, loadFormal);
}
void ByteCodeGenerator::DefineLabels(FuncInfo *funcInfo)
{
funcInfo->singleExit = m_writer.DefineLabel();
SList<ParseNodeStmt *>::Iterator iter(&funcInfo->targetStatements);
while (iter.Next())
{
ParseNodeStmt * node = iter.Data();
node->breakLabel = m_writer.DefineLabel();
node->continueLabel = m_writer.DefineLabel();
node->emitLabels = true;
}
}
void ByteCodeGenerator::EmitGlobalBody(FuncInfo *funcInfo)
{
// Emit global code (global scope or eval), fixing up the return register with the implicit
// return value.
ParseNode *pnode = funcInfo->root->pnodeBody;
ParseNode *pnodeLastVal = funcInfo->root->AsParseNodeProg()->pnodeLastValStmt;
if (pnodeLastVal == nullptr || pnodeLastVal->IsPatternDeclaration())
{
// We're not guaranteed to compute any values, so fix up the return register at the top
// in case.
this->m_writer.Reg1(Js::OpCode::LdUndef, ReturnRegister);
}
while (pnode->nop == knopList)
{
ParseNode *stmt = pnode->AsParseNodeBin()->pnode1;
if (stmt == pnodeLastVal)
{
pnodeLastVal = nullptr;
}
if (pnodeLastVal == nullptr && (this->flags & fscrReturnExpression))
{
EmitTopLevelStatement(stmt, funcInfo, true);
}
else
{
// Haven't hit the post-dominating return value yet,
// so don't bother with the return register.
EmitTopLevelStatement(stmt, funcInfo, false);
}
pnode = pnode->AsParseNodeBin()->pnode2;
}
EmitTopLevelStatement(pnode, funcInfo, false);
}
void ByteCodeGenerator::EmitFunctionBody(FuncInfo *funcInfo)
{
// Emit a function body. Only explicit returns and the implicit "undef" at the bottom
// get copied to the return register.
ParseNode *pnodeBody = funcInfo->root->pnodeBody;
ParseNode *pnode = pnodeBody;
while (pnode->nop == knopList)
{
ParseNode *stmt = pnode->AsParseNodeBin()->pnode1;
if (stmt->CapturesSyms())
{
CapturedSymMap *map = funcInfo->EnsureCapturedSymMap();
SList<Symbol*> *list = map->Item(stmt);
FOREACH_SLIST_ENTRY(Symbol*, sym, list)
{
if (!sym->GetIsCommittedToSlot())
{
Assert(sym->GetLocation() != Js::Constants::NoProperty);
sym->SetIsCommittedToSlot();
ParseNode *decl = sym->GetDecl();
Assert(decl);
if (PHASE_TRACE(Js::DelayCapturePhase, funcInfo->byteCodeFunction))
{
Output::Print(_u("--- DelayCapture: Committed symbol '%s' to slot.\n"),
sym->GetName().GetBuffer());
Output::Flush();
}
// REVIEW[ianhall]: HACK to work around this causing an error due to sym not yet being initialized
// what is this doing? Why are we assigning sym to itself?
bool old = sym->GetNeedDeclaration();
sym->SetNeedDeclaration(false);
this->EmitPropStore(sym->GetLocation(), sym, sym->GetPid(), funcInfo, decl->nop == knopLetDecl, decl->nop == knopConstDecl);
sym->SetNeedDeclaration(old);
}
}
NEXT_SLIST_ENTRY;
}
EmitTopLevelStatement(stmt, funcInfo, false);
pnode = pnode->AsParseNodeBin()->pnode2;
}
Assert(!pnode->CapturesSyms());
EmitTopLevelStatement(pnode, funcInfo, false);
}
void ByteCodeGenerator::EmitProgram(ParseNodeProg *pnodeProg)
{
// Indicate that the binding phase is over.
this->isBinding = false;
this->trackEnvDepth = true;
AssignPropertyIds(pnodeProg->funcInfo->byteCodeFunction);
int32 initSize = this->maxAstSize / AstBytecodeRatioEstimate;
// Use the temp allocator in bytecode write temp buffer.
m_writer.InitData(this->alloc, initSize);
#ifdef LOG_BYTECODE_AST_RATIO
// log the max Ast size
Output::Print(_u("Max Ast size: %d"), initSize);
#endif
Assert(pnodeProg && pnodeProg->nop == knopProg);
if (this->parentScopeInfo)
{
// Scope stack is already set up the way we want it, so don't visit the global scope.
// Start emitting with the nested scope (i.e., the deferred function).
this->EmitScopeList(pnodeProg->pnodeScopes);
}
else
{
this->EmitScopeList(pnodeProg);
}
}
void EmitDestructuredObject(ParseNode *lhs, Js::RegSlot rhsLocation, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo);
void EmitDestructuredValueOrInitializer(ParseNodePtr lhsElementNode, Js::RegSlot rhsLocation, ParseNodePtr initializer, bool isNonPatternAssignmentTarget, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo);
void ByteCodeGenerator::PopulateFormalsScope(uint beginOffset, FuncInfo *funcInfo, ParseNodeFnc *pnodeFnc)
{
Js::DebuggerScope *debuggerScope = nullptr;
auto processArg = [&](ParseNode *pnodeArg) {
if (pnodeArg->IsVarLetOrConst())
{
if (debuggerScope == nullptr)
{
debuggerScope = RecordStartScopeObject(pnodeFnc, funcInfo->paramScope && funcInfo->paramScope->GetIsObject() ? Js::DiagParamScopeInObject : Js::DiagParamScope);
debuggerScope->SetBegin(beginOffset);
}
InsertPropertyToDebuggerScope(funcInfo, debuggerScope, pnodeArg->AsParseNodeVar()->sym);
}
};
MapFormals(pnodeFnc, processArg);
MapFormalsFromPattern(pnodeFnc, processArg);
if (debuggerScope != nullptr)
{
if (!funcInfo->GetParsedFunctionBody()->IsParamAndBodyScopeMerged())
{
InsertPropertyToDebuggerScope(funcInfo, debuggerScope, funcInfo->GetArgumentsSymbol());
}
RecordEndScopeObject(pnodeFnc);
}
}
void ByteCodeGenerator::InsertPropertyToDebuggerScope(FuncInfo* funcInfo, Js::DebuggerScope* debuggerScope, Symbol* sym)
{
if (sym)
{
Js::FunctionBody* funcBody = funcInfo->GetParsedFunctionBody();
Js::DebuggerScopePropertyFlags flag = Js::DebuggerScopePropertyFlags_None;
Js::RegSlot location = sym->GetLocation();
if (ShouldTrackDebuggerMetadata() && !funcInfo->IsBodyAndParamScopeMerged() && funcInfo->bodyScope->FindLocalSymbol(sym->GetName()) != nullptr)
{
flag |= Js::DebuggerScopePropertyFlags_HasDuplicateInBody;
location = funcBody->MapRegSlot(location);
}
debuggerScope->AddProperty(location, sym->EnsurePosition(funcInfo), flag);
}
}
void ByteCodeGenerator::EmitDefaultArgs(FuncInfo *funcInfo, ParseNodeFnc *pnodeFnc)
{
uint beginOffset = m_writer.GetCurrentOffset();
MapFormals(pnodeFnc, [&](ParseNode *pnodeArg)
{
if (pnodeArg->nop == knopParamPattern)
{
this->StartStatement(pnodeArg);
Assert(pnodeArg->AsParseNodeParamPattern()->location != Js::Constants::NoRegister);
ParseNodePtr pnode1 = pnodeArg->AsParseNodeParamPattern()->pnode1;
if (pnode1->IsPattern())
{
EmitAssignment(nullptr, pnode1, pnodeArg->AsParseNodeParamPattern()->location, this, funcInfo);
}
else
{
Assert(pnode1->nop == knopAsg);
Assert(pnode1->AsParseNodeBin()->pnode1->IsPattern());
EmitDestructuredValueOrInitializer(pnode1->AsParseNodeBin()->pnode1,
pnodeArg->AsParseNodeParamPattern()->location,
pnode1->AsParseNodeBin()->pnode2,
false /*isNonPatternAssignmentTarget*/,
this,
funcInfo);
}
this->EndStatement(pnodeArg);
return;
}
else if (pnodeArg->IsVarLetOrConst())
{
Js::RegSlot location = pnodeArg->AsParseNodeVar()->sym->GetLocation();
if (pnodeArg->AsParseNodeVar()->pnodeInit == nullptr)
{
// Since the formal hasn't been initialized in LdLetHeapArguments, we'll initialize it here.
pnodeArg->AsParseNodeVar()->sym->SetNeedDeclaration(false);
EmitPropStore(location, pnodeArg->AsParseNodeVar()->sym, pnodeArg->AsParseNodeVar()->pid, funcInfo, true);
return;
}
// Load the default argument if we got undefined, skip RHS evaluation otherwise.
Js::ByteCodeLabel noDefaultLabel = this->m_writer.DefineLabel();
Js::ByteCodeLabel endLabel = this->m_writer.DefineLabel();
this->StartStatement(pnodeArg);
// Let us use strict not equal to differentiate between null and undefined
m_writer.BrReg2(Js::OpCode::BrSrNeq_A, noDefaultLabel, location, funcInfo->undefinedConstantRegister);
Emit(pnodeArg->AsParseNodeVar()->pnodeInit, this, funcInfo, false);
pnodeArg->AsParseNodeVar()->sym->SetNeedDeclaration(false); // After emit to prevent foo(a = a)
if (funcInfo->GetHasArguments() && pnodeArg->AsParseNodeVar()->sym->IsInSlot(this, funcInfo))
{
EmitPropStore(pnodeArg->AsParseNodeVar()->pnodeInit->location, pnodeArg->AsParseNodeVar()->sym, pnodeArg->AsParseNodeVar()->pid, funcInfo, true);
m_writer.Br(endLabel);
}
else
{
EmitAssignment(nullptr, pnodeArg, pnodeArg->AsParseNodeVar()->pnodeInit->location, this, funcInfo);
}
funcInfo->ReleaseLoc(pnodeArg->AsParseNodeVar()->pnodeInit);
m_writer.MarkLabel(noDefaultLabel);
if (funcInfo->GetHasArguments() && pnodeArg->AsParseNodeVar()->sym->IsInSlot(this, funcInfo))
{
EmitPropStore(location, pnodeArg->AsParseNodeVar()->sym, pnodeArg->AsParseNodeVar()->pid, funcInfo, true);
m_writer.MarkLabel(endLabel);
}
this->EndStatement(pnodeArg);
}
});
if (m_writer.GetCurrentOffset() > beginOffset)
{
PopulateFormalsScope(beginOffset, funcInfo, pnodeFnc);
}
}
void ByteCodeGenerator::EmitOneFunction(ParseNodeFnc *pnodeFnc)
{
Assert(pnodeFnc && (pnodeFnc->nop == knopProg || pnodeFnc->nop == knopFncDecl));
FuncInfo *funcInfo = pnodeFnc->funcInfo;
Assert(funcInfo != nullptr);
if (funcInfo->IsFakeGlobalFunction(this->flags))
{
return;
}
Js::ParseableFunctionInfo* deferParseFunction = funcInfo->byteCodeFunction;
deferParseFunction->SetGrfscr(deferParseFunction->GetGrfscr() | (this->flags & ~fscrDeferredFncExpression));
deferParseFunction->SetSourceInfo(this->GetCurrentSourceIndex(),
funcInfo->root,
!!(this->flags & fscrEvalCode),
((this->flags & fscrDynamicCode) && !(this->flags & fscrEvalCode)));
deferParseFunction->SetInParamsCount(funcInfo->inArgsCount);
if (pnodeFnc->HasDefaultArguments())
{
deferParseFunction->SetReportedInParamsCount(pnodeFnc->firstDefaultArg + 1);
}
else
{
deferParseFunction->SetReportedInParamsCount(funcInfo->inArgsCount);
}
// Note: Don't check the actual attributes on the functionInfo here, since CanDefer has been cleared while
// we're generating byte code.
if (deferParseFunction->IsDeferred() || funcInfo->canDefer)
{
Js::ScopeInfo::SaveEnclosingScopeInfo(this, funcInfo);
}
if (funcInfo->root->pnodeBody == nullptr)
{
if (!PHASE_OFF1(Js::SkipNestedDeferredPhase) && (this->GetFlags() & fscrCreateParserState) == fscrCreateParserState && deferParseFunction->GetCompileCount() == 0)
{
deferParseFunction->BuildDeferredStubs(funcInfo->root);
}
Assert(!deferParseFunction->IsFunctionBody() || deferParseFunction->GetFunctionBody()->GetByteCode() != nullptr);
return;
}
Js::FunctionBody* byteCodeFunction = funcInfo->GetParsedFunctionBody();
try
{
if (!funcInfo->IsGlobalFunction())
{
// Note: Do not set the stack nested func flag if the function has been redeferred and recompiled.
// In that case the flag already has the value we want.
if (CanStackNestedFunc(funcInfo, true) && byteCodeFunction->GetCompileCount() == 0)
{
#if DBG
byteCodeFunction->SetCanDoStackNestedFunc();
#endif
if (funcInfo->root->astSize <= ParseNodeFnc::MaxStackClosureAST)
{
byteCodeFunction->SetStackNestedFunc(true);
}
}
}
if (byteCodeFunction->DoStackNestedFunc())
{
uint nestedCount = byteCodeFunction->GetNestedCount();
for (uint i = 0; i < nestedCount; i++)
{
Js::FunctionProxy * nested = byteCodeFunction->GetNestedFunctionProxy(i);
if (nested->IsFunctionBody())
{
nested->GetFunctionBody()->SetStackNestedFuncParent(byteCodeFunction->GetFunctionInfo());
}
}
}
if (byteCodeFunction->GetByteCode() != nullptr)
{
// Previously compiled function nested within a re-deferred and re-compiled function.
return;
}
// Bug : 301517
// In the debug mode the hasOnlyThis optimization needs to be disabled, since user can break in this function
// and do operation on 'this' and its property, which may not be defined yet.
if (funcInfo->root->HasOnlyThisStmts() && !IsInDebugMode())
{
byteCodeFunction->SetHasOnlyThisStmts(true);
}
if (byteCodeFunction->IsInlineApplyDisabled() || this->scriptContext->GetConfig()->IsNoNative())
{
if ((pnodeFnc->nop == knopFncDecl) && (funcInfo->GetHasHeapArguments()) && (!funcInfo->GetCallsEval()) && ApplyEnclosesArgs(pnodeFnc, this))
{
bool applyEnclosesArgs = true;
for (ParseNode* pnodeVar = funcInfo->root->pnodeVars; pnodeVar; pnodeVar = pnodeVar->AsParseNodeVar()->pnodeNext)
{
Symbol* sym = pnodeVar->AsParseNodeVar()->sym;
if (sym->GetSymbolType() == STVariable && !sym->IsArguments())
{
applyEnclosesArgs = false;
break;
}
}
auto constAndLetCheck = [](ParseNodeBlock *pnodeBlock, bool *applyEnclosesArgs)
{
if (*applyEnclosesArgs)
{
for (auto lexvar = pnodeBlock->pnodeLexVars; lexvar; lexvar = lexvar->AsParseNodeVar()->pnodeNext)
{
Symbol* sym = lexvar->AsParseNodeVar()->sym;
if (sym->GetSymbolType() == STVariable && !sym->IsArguments())
{
*applyEnclosesArgs = false;
break;
}
}
}
};
constAndLetCheck(funcInfo->root->pnodeScopes, &applyEnclosesArgs);
constAndLetCheck(funcInfo->root->pnodeBodyScope, &applyEnclosesArgs);
funcInfo->SetApplyEnclosesArgs(applyEnclosesArgs);
}
}
InitScopeSlotArray(funcInfo);
FinalizeRegisters(funcInfo, byteCodeFunction);
DebugOnly(Js::RegSlot firstTmpReg = funcInfo->varRegsCount);
// Reserve temp registers for the inner scopes. We prefer temps because the JIT will then renumber them
// and see different lifetimes. (Note that debug mode requires permanent registers. See FinalizeRegisters.)
// Need to revisit the condition when enabling JitES6Generators.
uint innerScopeCount = funcInfo->InnerScopeCount();
if (!this->IsInDebugMode() && !byteCodeFunction->IsCoroutine())
{
byteCodeFunction->SetInnerScopeCount(innerScopeCount);
if (innerScopeCount)
{
funcInfo->SetFirstInnerScopeReg(funcInfo->AcquireTmpRegister());
for (uint i = 1; i < innerScopeCount; i++)
{
funcInfo->AcquireTmpRegister();
}
}
}
funcInfo->inlineCacheMap = Anew(alloc, FuncInfo::InlineCacheMap,
alloc,
funcInfo->RegCount() // Pass the actual register count. // TODO: Check if we can reduce this count
);
funcInfo->rootObjectLoadInlineCacheMap = Anew(alloc, FuncInfo::RootObjectInlineCacheIdMap,
alloc,
10);
funcInfo->rootObjectLoadMethodInlineCacheMap = Anew(alloc, FuncInfo::RootObjectInlineCacheIdMap,
alloc,
10);
funcInfo->rootObjectStoreInlineCacheMap = Anew(alloc, FuncInfo::RootObjectInlineCacheIdMap,
alloc,
10);
funcInfo->referencedPropertyIdToMapIndex = Anew(alloc, FuncInfo::RootObjectInlineCacheIdMap,
alloc,
10);
byteCodeFunction->AllocateLiteralRegexArray();
m_callSiteId = 0;
m_callApplyCallSiteCount = 0;
m_writer.Begin(byteCodeFunction, alloc, this->DoJitLoopBodies(funcInfo), funcInfo->hasLoop, this->IsInDebugMode());
this->PushFuncInfo(_u("EmitOneFunction"), funcInfo);
this->inPrologue = true;
Scope* paramScope = funcInfo->GetParamScope();
Scope* bodyScope = funcInfo->GetBodyScope();
// For now, emit all constant loads at top of function (should instead put in closest dominator of uses).
LoadAllConstants(funcInfo);
HomeArguments(funcInfo);
if (funcInfo->root->IsAsync())
{
Writer()->Reg1(Js::OpCode::NewAwaitObject, funcInfo->awaitRegister);
}
if (!funcInfo->IsBodyAndParamScopeMerged())
{
byteCodeFunction->SetParamAndBodyScopeNotMerged();
// Pop the body scope before emitting the default args
PopScope();
Assert(this->GetCurrentScope() == paramScope);
}
if (funcInfo->root->pnodeRest != nullptr)
{
byteCodeFunction->SetHasRestParameter();
}
if (funcInfo->IsGlobalFunction())
{
EnsureNoRedeclarations(pnodeFnc->pnodeScopes, funcInfo);
}
::BeginEmitBlock(pnodeFnc->pnodeScopes, this, funcInfo);
DefineLabels(funcInfo);
// We need to emit the storage for special symbols before we emit the default arguments in case the default
// argument expressions reference those special names.
if (pnodeFnc->HasNonSimpleParameterList())
{
// If the param and body scope are merged, the special symbol vars are located in the body scope so we
// need to walk over the var list.
if (funcInfo->IsBodyAndParamScopeMerged())
{
for (ParseNodePtr pnodeVar = pnodeFnc->pnodeVars; pnodeVar; pnodeVar = pnodeVar->AsParseNodeVar()->pnodeNext)
{
#if DBG
bool reachedEndOfSpecialSymbols = false;
#endif
Symbol* sym = pnodeVar->AsParseNodeVar()->sym;
if (sym != nullptr && sym->IsSpecialSymbol())
{
EmitPropStoreForSpecialSymbol(sym->GetLocation(), sym, sym->GetPid(), funcInfo, true);
if (ShouldTrackDebuggerMetadata() && !sym->IsInSlot(this, funcInfo))
{
byteCodeFunction->InsertSymbolToRegSlotList(sym->GetName(), sym->GetLocation(), funcInfo->varRegsCount);
}
}
else
{
#if DBG
reachedEndOfSpecialSymbols = true;
#else
// All of the special symbols exist at the beginning of the var list (parser guarantees this and debug build asserts this)
// so we can quit walking at the first non-special one we see.
break;
#endif
}
#if DBG
if (reachedEndOfSpecialSymbols)
{
Assert(sym == nullptr || !sym->IsSpecialSymbol());
}
#endif
}
}
else
{
paramScope->ForEachSymbol([&](Symbol* sym) {
if (sym && sym->IsSpecialSymbol())
{
EmitPropStoreForSpecialSymbol(sym->GetLocation(), sym, sym->GetPid(), funcInfo, true);
}
});
}
}
if (pnodeFnc->HasNonSimpleParameterList() || !funcInfo->IsBodyAndParamScopeMerged())
{
Assert(pnodeFnc->HasNonSimpleParameterList() || CONFIG_FLAG(ForceSplitScope));
this->InitBlockScopedNonTemps(funcInfo->root->pnodeScopes, funcInfo);
EmitDefaultArgs(funcInfo, pnodeFnc);
if (!funcInfo->IsBodyAndParamScopeMerged())
{
Assert(this->GetCurrentScope() == paramScope);
// Push the body scope
PushScope(bodyScope);
funcInfo->SetCurrentChildScope(bodyScope);
// Mark the beginning of the body scope so that new scope slots can be created.
this->Writer()->Empty(Js::OpCode::BeginBodyScope);
}
}
DefineUserVars(funcInfo);
// Emit all scope-wide function definitions before emitting function bodies
// so that calls may reference functions they precede lexically.
// Note, global eval scope is a fake local scope and is handled as if it were
// a lexical block instead of a true global scope, so do not define the functions
// here. They will be defined during BeginEmitBlock.
if (!(funcInfo->IsGlobalFunction() && this->IsEvalWithNoParentScopeInfo()))
{
// This only handles function declarations, which param scope cannot have any.
DefineFunctions(funcInfo);
}
// If the function has non simple parameter list, the params needs to be evaluated when the generator object is created
// (that is when the function is called). So insert an extra yield we can execute up to, to do this.
// In a Module we execute until this yield to hoist functions accross modules.
if (pnodeFnc->IsGenerator() && (pnodeFnc->HasNonSimpleParameterList() || pnodeFnc->IsModule()))
{
EmitStartupYield(this, funcInfo);
}
if (pnodeFnc->HasNonSimpleParameterList() || !funcInfo->IsBodyAndParamScopeMerged())
{
Assert(pnodeFnc->HasNonSimpleParameterList() || CONFIG_FLAG(ForceSplitScope));
this->InitBlockScopedNonTemps(funcInfo->root->pnodeBodyScope, funcInfo);
}
else
{
this->InitBlockScopedNonTemps(funcInfo->root->pnodeScopes, funcInfo);
}
if (!pnodeFnc->HasNonSimpleParameterList() && funcInfo->GetHasArguments() && !NeedScopeObjectForArguments(funcInfo, pnodeFnc))
{
// If we didn't create a scope object and didn't have default args, we still need to transfer the formals to their slots.
MapFormalsWithoutRest(pnodeFnc, [&](ParseNode *pnodeArg) { EmitPropStore(pnodeArg->AsParseNodeVar()->sym->GetLocation(), pnodeArg->AsParseNodeVar()->sym, pnodeArg->AsParseNodeVar()->pid, funcInfo); });
}
Js::RegSlot formalsUpperBound = Js::Constants::NoRegister; // Needed for tracking the last RegSlot in the param scope
if (!funcInfo->IsBodyAndParamScopeMerged())
{
// Emit bytecode to copy the initial values from param names to their corresponding body bindings.
// We have to do this after the rest param is marked as false for need declaration.
Symbol* funcSym = funcInfo->root->GetFuncSymbol();
paramScope->ForEachSymbol([&](Symbol* param) {
Symbol* varSym = funcInfo->GetBodyScope()->FindLocalSymbol(param->GetName());
if ((funcSym == nullptr || funcSym != param) // Do not copy the symbol over to body as the function expression symbol
// is expected to stay inside the function expression scope
&& (varSym && varSym->GetSymbolType() == STVariable && (varSym->IsInSlot(this, funcInfo) || varSym->GetLocation() != Js::Constants::NoRegister)))
{
if (!varSym->GetNeedDeclaration())
{
if (param->IsInSlot(this, funcInfo))
{
// Simulating EmitPropLoad here. We can't directly call the method as we have to use the param scope specifically.
// Walking the scope chain is not possible at this time.
Js::RegSlot tempReg = funcInfo->AcquireTmpRegister();
Js::PropertyId slot = param->EnsureScopeSlot(this, funcInfo);
Js::ProfileId profileId = funcInfo->FindOrAddSlotProfileId(paramScope, slot);
Js::OpCode op = paramScope->GetIsObject() ? Js::OpCode::LdParamObjSlot : Js::OpCode::LdParamSlot;
slot = slot + (paramScope->GetIsObject() ? 0 : Js::ScopeSlots::FirstSlotIndex);
this->m_writer.SlotI1(op, tempReg, slot, profileId);
this->EmitPropStore(tempReg, varSym, varSym->GetPid(), funcInfo);
funcInfo->ReleaseTmpRegister(tempReg);
}
else if (param->GetLocation() != Js::Constants::NoRegister)
{
this->EmitPropStore(param->GetLocation(), varSym, varSym->GetPid(), funcInfo);
}
else
{
Assert(param->IsArguments() && !funcInfo->GetHasArguments());
}
}
else
{
// There is a let redeclaration of arguments symbol. Any other var will cause a
// re-declaration error.
Assert(param->IsArguments());
}
}
if (ShouldTrackDebuggerMetadata() && param->GetLocation() != Js::Constants::NoRegister)
{
if (formalsUpperBound == Js::Constants::NoRegister || formalsUpperBound < param->GetLocation())
{
formalsUpperBound = param->GetLocation();
}
}
});
}
if (ShouldTrackDebuggerMetadata() && byteCodeFunction->GetPropertyIdOnRegSlotsContainer())
{
byteCodeFunction->GetPropertyIdOnRegSlotsContainer()->formalsUpperBound = formalsUpperBound;
}
if (pnodeFnc->pnodeBodyScope != nullptr)
{
::BeginEmitBlock(pnodeFnc->pnodeBodyScope, this, funcInfo);
}
this->inPrologue = false;
if (funcInfo->IsGlobalFunction())
{
EmitGlobalBody(funcInfo);
}
else
{
EmitFunctionBody(funcInfo);
}
if (pnodeFnc->pnodeBodyScope != nullptr)
{
::EndEmitBlock(pnodeFnc->pnodeBodyScope, this, funcInfo);
}
::EndEmitBlock(pnodeFnc->pnodeScopes, this, funcInfo);
if (!this->IsInDebugMode())
{
// Release the temp registers that we reserved for inner scopes above.
if (innerScopeCount)
{
Js::RegSlot tmpReg = funcInfo->FirstInnerScopeReg() + innerScopeCount - 1;
for (uint i = 0; i < innerScopeCount; i++)
{
funcInfo->ReleaseTmpRegister(tmpReg);
tmpReg--;
}
}
}
Assert(funcInfo->firstTmpReg == firstTmpReg);
Assert(funcInfo->curTmpReg == firstTmpReg);
Assert(byteCodeFunction->GetFirstTmpReg() == firstTmpReg + byteCodeFunction->GetConstantCount());
byteCodeFunction->CheckAndSetVarCount(funcInfo->varRegsCount);
byteCodeFunction->CheckAndSetOutParamMaxDepth(funcInfo->outArgsMaxDepth);
byteCodeFunction->SetForInLoopDepth(funcInfo->GetMaxForInLoopLevel());
// Do a uint32 add just to verify that we haven't overflowed the reg slot type.
UInt32Math::Add(funcInfo->varRegsCount, funcInfo->constRegsCount);
#if DBG_DUMP
if (PHASE_STATS1(Js::ByteCodePhase))
{
Output::Print(_u(" BCode: %-10d, Aux: %-10d, AuxC: %-10d Total: %-10d, %s\n"),
m_writer.ByteCodeDataSize(),
m_writer.AuxiliaryDataSize(),
m_writer.AuxiliaryContextDataSize(),
m_writer.ByteCodeDataSize() + m_writer.AuxiliaryDataSize() + m_writer.AuxiliaryContextDataSize(),
funcInfo->name);
this->scriptContext->byteCodeDataSize += m_writer.ByteCodeDataSize();
this->scriptContext->byteCodeAuxiliaryDataSize += m_writer.AuxiliaryDataSize();
this->scriptContext->byteCodeAuxiliaryContextDataSize += m_writer.AuxiliaryContextDataSize();
}
#endif
this->MapCacheIdsToPropertyIds(funcInfo);
this->MapReferencedPropertyIds(funcInfo);
Assert(this->TopFuncInfo() == funcInfo);
PopFuncInfo(_u("EmitOneFunction"));
m_writer.SetCallSiteCount(m_callSiteId);
m_writer.SetCallApplyCallsiteCount(m_callApplyCallSiteCount);
#if ENABLE_NATIVE_CODEGEN
if (funcInfo->callSiteToCallApplyCallSiteMap)
{
this->MapCallSiteToCallApplyCallSiteMap(funcInfo);
}
#endif
#ifdef LOG_BYTECODE_AST_RATIO
m_writer.End(funcInfo->root->astSize, this->maxAstSize);
#else
m_writer.End();
#endif
}
catch (...)
{
// Failed to generate byte-code for this function body (likely OOM or stack overflow). Notify the function body so that
// it can revert intermediate state changes that may have taken place during byte code generation before the failure.
byteCodeFunction->ResetByteCodeGenState();
m_writer.Reset();
throw;
}
#ifdef PERF_HINT
if (PHASE_TRACE1(Js::PerfHintPhase) && !byteCodeFunction->GetIsGlobalFunc())
{
if (byteCodeFunction->GetHasTry())
{
WritePerfHint(PerfHints::HasTryBlock_Verbose, byteCodeFunction);
}
if (funcInfo->GetCallsEval())
{
WritePerfHint(PerfHints::CallsEval_Verbose, byteCodeFunction);
}
else if (funcInfo->GetChildCallsEval())
{
WritePerfHint(PerfHints::ChildCallsEval, byteCodeFunction);
}
}
#endif
if (!byteCodeFunction->GetSourceContextInfo()->IsDynamic() && byteCodeFunction->GetIsTopLevel() && !(this->flags & fscrEvalCode))
{
// Add the top level of nested functions to the tracking dictionary. Wait until this point so that all nested functions have gone
// through the Emit API so source info, etc., is initialized, and these are not orphaned functions left behind by an unfinished pass.
byteCodeFunction->ForEachNestedFunc([&](Js::FunctionProxy * nestedFunc, uint32 i)
{
if (nestedFunc && nestedFunc->IsDeferredParseFunction() && nestedFunc->GetParseableFunctionInfo()->GetIsDeclaration())
{
byteCodeFunction->GetUtf8SourceInfo()->TrackDeferredFunction(nestedFunc->GetLocalFunctionId(), nestedFunc->GetParseableFunctionInfo());
}
return true;
});
}
byteCodeFunction->SetInitialDefaultEntryPoint();
byteCodeFunction->SetCompileCount(UInt32Math::Add(byteCodeFunction->GetCompileCount(), 1));
#ifdef ENABLE_DEBUG_CONFIG_OPTIONS
if (byteCodeFunction->IsInDebugMode() != scriptContext->IsScriptContextInDebugMode()) // debug mode mismatch
{
if (m_utf8SourceInfo->GetIsLibraryCode())
{
Assert(!byteCodeFunction->IsInDebugMode()); // Library script byteCode is never in debug mode
}
else
{
Js::Throw::FatalInternalError();
}
}
#endif
#if DBG_DUMP
if (PHASE_DUMP(Js::ByteCodePhase, funcInfo->byteCodeFunction) && Js::Configuration::Global.flags.Verbose)
{
pnodeFnc->Dump();
}
if (this->Trace() || PHASE_DUMP(Js::ByteCodePhase, funcInfo->byteCodeFunction))
{
Js::ByteCodeDumper::Dump(byteCodeFunction);
}
if (PHASE_DUMP(Js::DebuggerScopePhase, funcInfo->byteCodeFunction))
{
byteCodeFunction->DumpScopes();
}
#endif
#if ENABLE_NATIVE_CODEGEN
if ((!PHASE_OFF(Js::BackEndPhase, funcInfo->byteCodeFunction))
&& !this->forceNoNative
&& !this->scriptContext->GetConfig()->IsNoNative())
{
GenerateFunction(this->scriptContext->GetNativeCodeGenerator(), byteCodeFunction);
}
#endif
}
void ByteCodeGenerator::MapCacheIdsToPropertyIds(FuncInfo *funcInfo)
{
Js::FunctionBody *functionBody = funcInfo->GetParsedFunctionBody();
uint rootObjectLoadInlineCacheStart = funcInfo->GetInlineCacheCount();
uint rootObjectLoadMethodInlineCacheStart = rootObjectLoadInlineCacheStart + funcInfo->GetRootObjectLoadInlineCacheCount();
uint rootObjectStoreInlineCacheStart = rootObjectLoadMethodInlineCacheStart + funcInfo->GetRootObjectLoadMethodInlineCacheCount();
uint totalFieldAccessInlineCacheCount = rootObjectStoreInlineCacheStart + funcInfo->GetRootObjectStoreInlineCacheCount();
functionBody->CreateCacheIdToPropertyIdMap(rootObjectLoadInlineCacheStart, rootObjectLoadMethodInlineCacheStart,
rootObjectStoreInlineCacheStart, totalFieldAccessInlineCacheCount, funcInfo->GetIsInstInlineCacheCount());
if (totalFieldAccessInlineCacheCount == 0)
{
return;
}
funcInfo->inlineCacheMap->Map([functionBody](Js::RegSlot regSlot, FuncInfo::InlineCacheIdMap *inlineCacheIdMap)
{
inlineCacheIdMap->Map([functionBody](Js::PropertyId propertyId, FuncInfo::InlineCacheList* inlineCacheList)
{
if (inlineCacheList)
{
inlineCacheList->Iterate([functionBody, propertyId](InlineCacheUnit cacheUnit)
{
CompileAssert(offsetof(InlineCacheUnit, cacheId) == offsetof(InlineCacheUnit, loadCacheId));
if (cacheUnit.loadCacheId != -1)
{
functionBody->SetPropertyIdForCacheId(cacheUnit.loadCacheId, propertyId);
}
if (cacheUnit.loadMethodCacheId != -1)
{
functionBody->SetPropertyIdForCacheId(cacheUnit.loadMethodCacheId, propertyId);
}
if (cacheUnit.storeCacheId != -1)
{
functionBody->SetPropertyIdForCacheId(cacheUnit.storeCacheId, propertyId);
}
});
}
});
});
funcInfo->rootObjectLoadInlineCacheMap->Map([functionBody, rootObjectLoadInlineCacheStart](Js::PropertyId propertyId, uint cacheId)
{
functionBody->SetPropertyIdForCacheId(cacheId + rootObjectLoadInlineCacheStart, propertyId);
});
funcInfo->rootObjectLoadMethodInlineCacheMap->Map([functionBody, rootObjectLoadMethodInlineCacheStart](Js::PropertyId propertyId, uint cacheId)
{
functionBody->SetPropertyIdForCacheId(cacheId + rootObjectLoadMethodInlineCacheStart, propertyId);
});
funcInfo->rootObjectStoreInlineCacheMap->Map([functionBody, rootObjectStoreInlineCacheStart](Js::PropertyId propertyId, uint cacheId)
{
functionBody->SetPropertyIdForCacheId(cacheId + rootObjectStoreInlineCacheStart, propertyId);
});
SListBase<uint>::Iterator valueOfIter(&funcInfo->valueOfStoreCacheIds);
while (valueOfIter.Next())
{
functionBody->SetPropertyIdForCacheId(valueOfIter.Data(), Js::PropertyIds::valueOf);
}
SListBase<uint>::Iterator toStringIter(&funcInfo->toStringStoreCacheIds);
while (toStringIter.Next())
{
functionBody->SetPropertyIdForCacheId(toStringIter.Data(), Js::PropertyIds::toString);
}
#if DBG
functionBody->VerifyCacheIdToPropertyIdMap();
#endif
}
void ByteCodeGenerator::MapReferencedPropertyIds(FuncInfo * funcInfo)
{
Js::FunctionBody *functionBody = funcInfo->GetParsedFunctionBody();
uint referencedPropertyIdCount = funcInfo->GetReferencedPropertyIdCount();
functionBody->CreateReferencedPropertyIdMap(referencedPropertyIdCount);
funcInfo->referencedPropertyIdToMapIndex->Map([functionBody](Js::PropertyId propertyId, uint mapIndex)
{
functionBody->SetReferencedPropertyIdWithMapIndex(mapIndex, propertyId);
});
#if DBG
functionBody->VerifyReferencedPropertyIdMap();
#endif
}
#if ENABLE_NATIVE_CODEGEN
void ByteCodeGenerator::MapCallSiteToCallApplyCallSiteMap(FuncInfo * funcInfo)
{
Js::FunctionBody * functionBody = funcInfo->GetParsedFunctionBody();
if (functionBody->CreateCallSiteToCallApplyCallSiteArray())
{
funcInfo->callSiteToCallApplyCallSiteMap->Map([functionBody](Js::ProfileId callSiteId, Js::ProfileId callApplyCallSiteId)
{
functionBody->GetCallSiteToCallApplyCallSiteArray()[callSiteId] = callApplyCallSiteId;
});
}
}
#endif
void ByteCodeGenerator::EmitScopeList(ParseNode *pnode, ParseNode *breakOnBodyScopeNode)
{
while (pnode)
{
if (breakOnBodyScopeNode != nullptr && breakOnBodyScopeNode == pnode)
{
break;
}
switch (pnode->nop)
{
case knopFncDecl:
#ifdef ASMJS_PLAT
if (pnode->AsParseNodeFnc()->GetAsmjsMode())
{
Js::ExclusiveContext context(this, GetScriptContext());
if (Js::AsmJSCompiler::Compile(&context, pnode->AsParseNodeFnc(), pnode->AsParseNodeFnc()->pnodeParams))
{
pnode = pnode->AsParseNodeFnc()->pnodeNext;
break;
}
else if (CONFIG_FLAG(AsmJsStopOnError))
{
exit(JSERR_AsmJsCompileError);
}
else
{
// If deferral is not allowed, throw and reparse everything with asm.js disabled.
throw Js::AsmJsParseException();
}
}
#endif
// FALLTHROUGH
case knopProg:
if (pnode->AsParseNodeFnc()->funcInfo)
{
FuncInfo* funcInfo = pnode->AsParseNodeFnc()->funcInfo;
Scope* paramScope = funcInfo->GetParamScope();
if (!funcInfo->IsBodyAndParamScopeMerged())
{
funcInfo->SetCurrentChildScope(paramScope);
}
else
{
funcInfo->SetCurrentChildScope(funcInfo->GetBodyScope());
}
this->StartEmitFunction(pnode->AsParseNodeFnc());
if (!funcInfo->IsBodyAndParamScopeMerged())
{
this->EmitScopeList(pnode->AsParseNodeFnc()->pnodeBodyScope->pnodeScopes);
}
else
{
this->EmitScopeList(pnode->AsParseNodeFnc()->pnodeScopes);
}
this->EmitOneFunction(pnode->AsParseNodeFnc());
this->EndEmitFunction(pnode->AsParseNodeFnc());
Assert(pnode->AsParseNodeFnc()->pnodeBody == nullptr || funcInfo->isReused || funcInfo->GetCurrentChildScope() == funcInfo->GetBodyScope());
funcInfo->SetCurrentChildScope(nullptr);
}
pnode = pnode->AsParseNodeFnc()->pnodeNext;
break;
case knopBlock:
{
ParseNodeBlock * pnodeBlock = pnode->AsParseNodeBlock();
this->StartEmitBlock(pnodeBlock);
this->EmitScopeList(pnodeBlock->pnodeScopes);
this->EndEmitBlock(pnodeBlock);
pnode = pnodeBlock->pnodeNext;
break;
}
case knopCatch:
{
ParseNodeCatch * pnodeCatch = pnode->AsParseNodeCatch();
this->StartEmitCatch(pnodeCatch);
this->EmitScopeList(pnodeCatch->pnodeScopes);
this->EndEmitCatch(pnodeCatch);
pnode = pnodeCatch->pnodeNext;
break;
}
case knopWith:
this->StartEmitWith(pnode);
this->EmitScopeList(pnode->AsParseNodeWith()->pnodeScopes);
this->EndEmitWith(pnode);
pnode = pnode->AsParseNodeWith()->pnodeNext;
break;
default:
AssertMsg(false, "Unexpected opcode in tree of scopes");
break;
}
}
}
void ByteCodeGenerator::EnsureFncDeclScopeSlot(ParseNodeFnc *pnodeFnc, FuncInfo *funcInfo)
{
if (pnodeFnc->pnodeName)
{
Assert(pnodeFnc->pnodeName->nop == knopVarDecl);
Symbol *sym = pnodeFnc->pnodeName->sym;
// If this function is shadowing the arguments symbol in body then skip it.
// We will allocate scope slot for the arguments symbol during EmitLocalPropInit.
if (sym && !sym->IsArguments())
{
sym->EnsureScopeSlot(this, funcInfo);
}
}
}
// Similar to EnsureFncScopeSlot visitor function, but verifies that a slot is needed before assigning it.
void ByteCodeGenerator::CheckFncDeclScopeSlot(ParseNodeFnc *pnodeFnc, FuncInfo *funcInfo)
{
if (pnodeFnc->pnodeName)
{
Assert(pnodeFnc->pnodeName->nop == knopVarDecl);
Symbol *sym = pnodeFnc->pnodeName->sym;
if (sym && sym->NeedsSlotAlloc(this, funcInfo))
{
sym->EnsureScopeSlot(this, funcInfo);
}
}
}
void ByteCodeGenerator::StartEmitFunction(ParseNodeFnc *pnodeFnc)
{
Assert(pnodeFnc->nop == knopFncDecl || pnodeFnc->nop == knopProg);
FuncInfo *funcInfo = pnodeFnc->funcInfo;
Scope * const bodyScope = funcInfo->GetBodyScope();
Scope * const paramScope = funcInfo->GetParamScope();
if (funcInfo->byteCodeFunction->IsFunctionParsed() && funcInfo->root->pnodeBody != nullptr)
{
if (funcInfo->GetParsedFunctionBody()->GetByteCode() == nullptr && !(flags & (fscrEval | fscrImplicitThis)))
{
// Only set the environment depth if it's truly known (i.e., not in eval or event handler).
funcInfo->GetParsedFunctionBody()->SetEnvDepth(this->envDepth);
}
if (funcInfo->GetCallsEval())
{
funcInfo->byteCodeFunction->SetDontInline(true);
}
Scope * const funcExprScope = funcInfo->funcExprScope;
if (funcExprScope)
{
if (funcInfo->GetCallsEval())
{
Assert(funcExprScope->GetIsObject());
}
if (funcExprScope->GetIsObject())
{
funcExprScope->SetCapturesAll(true);
funcExprScope->SetMustInstantiate(true);
PushScope(funcExprScope);
}
else
{
Symbol *sym = funcInfo->root->GetFuncSymbol();
if (funcInfo->IsBodyAndParamScopeMerged())
{
funcInfo->bodyScope->AddSymbol(sym);
}
else
{
funcInfo->paramScope->AddSymbol(sym);
}
sym->EnsureScopeSlot(this, funcInfo);
if (sym->GetHasNonLocalReference())
{
sym->GetScope()->SetHasOwnLocalInClosure(true);
}
}
}
if (pnodeFnc->nop != knopProg)
{
if (!bodyScope->GetIsObject() && NeedObjectAsFunctionScope(funcInfo, pnodeFnc))
{
Assert(bodyScope->GetIsObject());
}
if (bodyScope->GetIsObject())
{
bodyScope->SetLocation(funcInfo->frameObjRegister);
}
else
{
bodyScope->SetLocation(funcInfo->frameSlotsRegister);
}
if (!funcInfo->IsBodyAndParamScopeMerged())
{
if (paramScope->GetIsObject())
{
paramScope->SetLocation(funcInfo->frameObjRegister);
}
else
{
paramScope->SetLocation(funcInfo->frameSlotsRegister);
}
}
if (bodyScope->GetIsObject())
{
// Win8 908700: Disable under F12 debugger because there are too many cached scopes holding onto locals.
funcInfo->SetHasCachedScope(
!PHASE_OFF(Js::CachedScopePhase, funcInfo->byteCodeFunction) &&
!funcInfo->Escapes() &&
funcInfo->frameObjRegister != Js::Constants::NoRegister &&
!ApplyEnclosesArgs(pnodeFnc, this) &&
funcInfo->IsBodyAndParamScopeMerged() && // There is eval in the param scope
!pnodeFnc->HasDefaultArguments() &&
!pnodeFnc->HasDestructuredParams() &&
(PHASE_FORCE(Js::CachedScopePhase, funcInfo->byteCodeFunction) || !IsInDebugMode())
#if ENABLE_TTD
&& !funcInfo->GetParsedFunctionBody()->GetScriptContext()->GetThreadContext()->IsRuntimeInTTDMode()
#endif
&& !funcInfo->byteCodeFunction->IsCoroutine()
);
if (funcInfo->GetHasCachedScope())
{
Assert(funcInfo->funcObjRegister == Js::Constants::NoRegister);
Symbol *funcSym = funcInfo->root->GetFuncSymbol();
if (funcSym && funcSym->GetIsFuncExpr())
{
if (funcSym->GetLocation() == Js::Constants::NoRegister)
{
funcInfo->funcObjRegister = funcInfo->NextVarRegister();
}
else
{
funcInfo->funcObjRegister = funcSym->GetLocation();
}
}
else
{
funcInfo->funcObjRegister = funcInfo->NextVarRegister();
}
Assert(funcInfo->funcObjRegister != Js::Constants::NoRegister);
}
ParseNode *pnode;
Symbol *sym;
if (funcInfo->GetHasArguments())
{
// Process function's formal parameters
MapFormals(pnodeFnc, [&](ParseNode *pnode)
{
if (pnode->IsVarLetOrConst())
{
pnode->AsParseNodeVar()->sym->EnsureScopeSlot(this, funcInfo);
}
});
MapFormalsFromPattern(pnodeFnc, [&](ParseNode *pnode) { pnode->AsParseNodeVar()->sym->EnsureScopeSlot(this, funcInfo); });
// Only allocate scope slot for "arguments" when really necessary. "hasDeferredChild"
// doesn't require scope slot for "arguments" because inner functions can't access
// outer function's arguments directly.
sym = funcInfo->GetArgumentsSymbol();
Assert(sym);
if (sym->NeedsSlotAlloc(this, funcInfo))
{
sym->EnsureScopeSlot(this, funcInfo);
}
}
sym = funcInfo->root->GetFuncSymbol();
if (sym && sym->NeedsSlotAlloc(this, funcInfo))
{
if (funcInfo->funcExprScope && funcInfo->funcExprScope->GetIsObject())
{
sym->SetScopeSlot(0);
}
else if (funcInfo->GetFuncExprNameReference())
{
sym->EnsureScopeSlot(this, funcInfo);
}
}
if (!funcInfo->GetHasArguments())
{
Symbol *formal;
Js::ArgSlot pos = 1;
auto moveArgToReg = [&](ParseNode *pnode)
{
if (pnode->IsVarLetOrConst())
{
formal = pnode->AsParseNodeVar()->sym;
// Get the param from its argument position into its assigned register.
// The position should match the location; otherwise, it has been shadowed by parameter with the same name.
if (formal->GetLocation() + 1 == pos)
{
pnode->AsParseNodeVar()->sym->EnsureScopeSlot(this, funcInfo);
}
}
pos++;
};
MapFormals(pnodeFnc, moveArgToReg);
MapFormalsFromPattern(pnodeFnc, [&](ParseNode *pnode) { pnode->AsParseNodeVar()->sym->EnsureScopeSlot(this, funcInfo); });
}
for (pnode = pnodeFnc->pnodeVars; pnode; pnode = pnode->AsParseNodeVar()->pnodeNext)
{
sym = pnode->AsParseNodeVar()->sym;
if (!(pnode->AsParseNodeVar()->isBlockScopeFncDeclVar && sym->GetIsBlockVar()))
{
if (sym->GetIsCatch() || (pnode->nop == knopVarDecl && sym->GetIsBlockVar()))
{
sym = funcInfo->bodyScope->FindLocalSymbol(sym->GetName());
}
if (sym->GetSymbolType() == STVariable && !sym->IsArguments())
{
sym->EnsureScopeSlot(this, funcInfo);
}
}
}
auto ensureFncDeclScopeSlots = [&](ParseNode *pnodeScope)
{
for (pnode = pnodeScope; pnode;)
{
switch (pnode->nop)
{
case knopFncDecl:
if (pnode->AsParseNodeFnc()->IsDeclaration())
{
EnsureFncDeclScopeSlot(pnode->AsParseNodeFnc(), funcInfo);
}
pnode = pnode->AsParseNodeFnc()->pnodeNext;
break;
case knopBlock:
pnode = pnode->AsParseNodeBlock()->pnodeNext;
break;
case knopCatch:
pnode = pnode->AsParseNodeCatch()->pnodeNext;
break;
case knopWith:
pnode = pnode->AsParseNodeWith()->pnodeNext;
break;
}
}
};
pnodeFnc->MapContainerScopes(ensureFncDeclScopeSlots);
if (pnodeFnc->pnodeBody)
{
Assert(pnodeFnc->pnodeScopes->nop == knopBlock);
this->EnsureLetConstScopeSlots(pnodeFnc->pnodeBodyScope, funcInfo);
}
}
else
{
ParseNode *pnode;
Symbol *sym;
pnodeFnc->MapContainerScopes([&](ParseNode *pnodeScope) { this->EnsureFncScopeSlots(pnodeScope, funcInfo); });
for (pnode = pnodeFnc->pnodeVars; pnode; pnode = pnode->AsParseNodeVar()->pnodeNext)
{
sym = pnode->AsParseNodeVar()->sym;
if (!(pnode->AsParseNodeVar()->isBlockScopeFncDeclVar && sym->GetIsBlockVar()))
{
if (sym->GetIsCatch() || (pnode->nop == knopVarDecl && sym->GetIsBlockVar()))
{
sym = funcInfo->bodyScope->FindLocalSymbol(sym->GetName());
}
if (sym->GetSymbolType() == STVariable && sym->NeedsSlotAlloc(this, funcInfo) && !sym->IsArguments())
{
sym->EnsureScopeSlot(this, funcInfo);
}
}
}
auto ensureScopeSlot = [&](ParseNode *pnode)
{
if (pnode->IsVarLetOrConst())
{
sym = pnode->AsParseNodeVar()->sym;
if (sym->GetSymbolType() == STFormal && sym->NeedsSlotAlloc(this, funcInfo))
{
sym->EnsureScopeSlot(this, funcInfo);
}
}
};
// Process function's formal parameters
MapFormals(pnodeFnc, ensureScopeSlot);
MapFormalsFromPattern(pnodeFnc, ensureScopeSlot);
if (funcInfo->GetHasArguments())
{
sym = funcInfo->GetArgumentsSymbol();
Assert(sym);
// There is no eval so the arguments may be captured in a lambda.
// But we cannot relay on slots getting allocated while the lambda is emitted as the function body may be reparsed.
sym->EnsureScopeSlot(this, funcInfo);
}
if (pnodeFnc->pnodeBody)
{
this->EnsureLetConstScopeSlots(pnodeFnc->pnodeScopes, funcInfo);
this->EnsureLetConstScopeSlots(pnodeFnc->pnodeBodyScope, funcInfo);
}
}
// When we have split scope and body scope does not have any scope slots allocated, we don't have to mark the body scope as mustinstantiate.
if (funcInfo->frameObjRegister != Js::Constants::NoRegister)
{
bodyScope->SetMustInstantiate(true);
}
else if (pnodeFnc->IsBodyAndParamScopeMerged() || bodyScope->GetScopeSlotCount() != 0)
{
bodyScope->SetMustInstantiate(funcInfo->frameSlotsRegister != Js::Constants::NoRegister);
if (pnodeFnc->IsBodyAndParamScopeMerged() && paramScope && paramScope->GetHasNestedParamFunc())
{
paramScope->SetMustInstantiate(funcInfo->frameSlotsRegister != Js::Constants::NoRegister);
}
}
if (!pnodeFnc->IsBodyAndParamScopeMerged())
{
if (funcInfo->frameObjRegister != Js::Constants::NoRegister)
{
paramScope->SetMustInstantiate(true);
}
else
{
// In the case of function expression being captured in the param scope the hasownlocalinclosure will be false for param scope,
// as function expression symbol stays in the function expression scope. We don't have to set mustinstantiate for param scope in that case.
paramScope->SetMustInstantiate(paramScope->GetHasOwnLocalInClosure());
}
}
}
else
{
bool newScopeForEval = (funcInfo->byteCodeFunction->GetIsStrictMode() && (this->GetFlags() & fscrEval));
if (newScopeForEval)
{
Assert(bodyScope->GetIsObject());
}
}
}
PushFuncInfo(_u("StartEmitFunction"), funcInfo);
if (!funcInfo->IsBodyAndParamScopeMerged())
{
ParseNodeBlock * paramBlock = pnodeFnc->pnodeScopes;
Assert(paramBlock->blockType == Parameter);
PushScope(paramScope);
// While emitting the functions we have to stop when we see the body scope block.
// Otherwise functions defined in the body scope will not be able to get the right references.
this->EmitScopeList(paramBlock->pnodeScopes, pnodeFnc->pnodeBodyScope);
Assert(this->GetCurrentScope() == paramScope);
}
PushScope(bodyScope);
}
void ByteCodeGenerator::EmitModuleExportAccess(Symbol* sym, Js::OpCode opcode, Js::RegSlot location, FuncInfo* funcInfo)
{
if (EnsureSymbolModuleSlots(sym, funcInfo))
{
this->Writer()->SlotI2(opcode, location, sym->GetModuleIndex(), sym->GetScopeSlot());
}
else
{
this->Writer()->W1(Js::OpCode::RuntimeReferenceError, SCODE_CODE(ERRInvalidExportName));
if (opcode == Js::OpCode::LdModuleSlot)
{
this->Writer()->Reg1(Js::OpCode::LdUndef, location);
}
}
}
bool ByteCodeGenerator::EnsureSymbolModuleSlots(Symbol* sym, FuncInfo* funcInfo)
{
Assert(sym->GetIsModuleExportStorage());
if (sym->GetModuleIndex() != Js::Constants::NoProperty && sym->GetScopeSlot() != Js::Constants::NoProperty)
{
return true;
}
Js::JavascriptLibrary* library = this->GetScriptContext()->GetLibrary();
library->EnsureModuleRecordList();
uint moduleIndex = this->GetModuleID();
uint moduleSlotIndex;
Js::SourceTextModuleRecord* moduleRecord = library->GetModuleRecord(moduleIndex);
if (sym->GetIsModuleImport())
{
Js::PropertyId localImportNameId = sym->EnsurePosition(funcInfo);
Js::ModuleNameRecord* moduleNameRecord = nullptr;
if (!moduleRecord->ResolveImport(localImportNameId, &moduleNameRecord))
{
return false;
}
AnalysisAssert(moduleNameRecord != nullptr);
Assert(moduleNameRecord->module->IsSourceTextModuleRecord());
Js::SourceTextModuleRecord* resolvedModuleRecord =
(Js::SourceTextModuleRecord*)PointerValue(moduleNameRecord->module);
moduleIndex = resolvedModuleRecord->GetModuleId();
moduleSlotIndex = resolvedModuleRecord->GetLocalExportSlotIndexByLocalName(moduleNameRecord->bindingName);
}
else
{
Js::PropertyId exportNameId = sym->EnsurePosition(funcInfo);
moduleSlotIndex = moduleRecord->GetLocalExportSlotIndexByLocalName(exportNameId);
}
sym->SetModuleIndex(moduleIndex);
sym->SetScopeSlot(moduleSlotIndex);
return true;
}
void ByteCodeGenerator::EmitAssignmentToDefaultModuleExport(ParseNode* pnode, FuncInfo* funcInfo)
{
// We are assigning pnode to the default export of the current module.
uint moduleIndex = this->GetModuleID();
Js::JavascriptLibrary* library = this->GetScriptContext()->GetLibrary();
library->EnsureModuleRecordList();
Js::SourceTextModuleRecord* moduleRecord = library->GetModuleRecord(moduleIndex);
uint moduleSlotIndex = moduleRecord->GetLocalExportSlotIndexByExportName(Js::PropertyIds::default_);
this->Writer()->SlotI2(Js::OpCode::StModuleSlot, pnode->location, moduleIndex, moduleSlotIndex);
}
void ByteCodeGenerator::EnsureLetConstScopeSlots(ParseNodeBlock *pnodeBlock, FuncInfo *funcInfo)
{
bool callsEval = pnodeBlock->GetCallsEval() || pnodeBlock->GetChildCallsEval();
auto ensureLetConstSlots = ([this, funcInfo, callsEval](ParseNode *pnode)
{
Symbol *sym = pnode->AsParseNodeVar()->sym;
if (callsEval || sym->NeedsSlotAlloc(this, funcInfo))
{
sym->EnsureScopeSlot(this, funcInfo);
this->ProcessCapturedSym(sym);
}
});
IterateBlockScopedVariables(pnodeBlock, ensureLetConstSlots);
}
void ByteCodeGenerator::EnsureFncScopeSlots(ParseNode *pnode, FuncInfo *funcInfo)
{
while (pnode)
{
switch (pnode->nop)
{
case knopFncDecl:
if (pnode->AsParseNodeFnc()->IsDeclaration())
{
this->CheckFncDeclScopeSlot(pnode->AsParseNodeFnc(), funcInfo);
}
pnode = pnode->AsParseNodeFnc()->pnodeNext;
break;
case knopBlock:
pnode = pnode->AsParseNodeBlock()->pnodeNext;
break;
case knopCatch:
pnode = pnode->AsParseNodeCatch()->pnodeNext;
break;
case knopWith:
pnode = pnode->AsParseNodeWith()->pnodeNext;
break;
}
}
}
void ByteCodeGenerator::EndEmitFunction(ParseNodeFnc *pnodeFnc)
{
Assert(pnodeFnc->nop == knopFncDecl || pnodeFnc->nop == knopProg);
Assert(pnodeFnc->nop == knopFncDecl && currentScope->GetEnclosingScope() != nullptr || pnodeFnc->nop == knopProg);
PopScope(); // function body
FuncInfo *funcInfo = pnodeFnc->funcInfo;
Scope* paramScope = funcInfo->paramScope;
if (!funcInfo->IsBodyAndParamScopeMerged())
{
Assert(this->GetCurrentScope() == paramScope);
PopScope(); // Pop the param scope
}
if (funcInfo->byteCodeFunction->IsFunctionParsed() && funcInfo->root->pnodeBody != nullptr)
{
// StartEmitFunction omits the matching PushScope for already-parsed functions.
// TODO: Refactor Start and EndEmitFunction for clarity.
Scope *scope = funcInfo->funcExprScope;
if (scope && scope->GetMustInstantiate())
{
Assert(currentScope == scope);
PopScope();
}
}
Assert(funcInfo == this->TopFuncInfo());
PopFuncInfo(_u("EndEmitFunction"));
}
void ByteCodeGenerator::StartEmitCatch(ParseNodeCatch *pnodeCatch)
{
Assert(pnodeCatch->nop == knopCatch);
Scope *scope = pnodeCatch->scope;
FuncInfo *funcInfo = scope->GetFunc();
// Catch scope is a dynamic object if it can be passed to a scoped lookup helper (i.e., eval is present or we're in an event handler).
if (funcInfo->GetCallsEval() || funcInfo->GetChildCallsEval() || (this->flags & (fscrEval | fscrImplicitThis)))
{
scope->SetIsObject();
}
if (pnodeCatch->HasPatternParam())
{
ParseNode *pnode1 = pnodeCatch->GetParam()->AsParseNodeParamPattern()->pnode1;
scope->SetCapturesAll(funcInfo->GetCallsEval() || funcInfo->GetChildCallsEval());
scope->SetMustInstantiate(scope->Count() > 0 && (scope->GetMustInstantiate() || scope->GetCapturesAll() || funcInfo->IsGlobalFunction()));
Parser::MapBindIdentifier(pnode1, [&](ParseNodePtr item)
{
Symbol *sym = item->AsParseNodeVar()->sym;
if (funcInfo->IsGlobalFunction())
{
sym->SetIsGlobalCatch(true);
}
if (sym->NeedsScopeObject())
{
scope->SetIsObject();
}
Assert(sym->GetScopeSlot() == Js::Constants::NoProperty);
if (sym->NeedsSlotAlloc(this, funcInfo))
{
sym->EnsureScopeSlot(this, funcInfo);
}
});
}
else if (pnodeCatch->HasParam())
{
Symbol *sym = pnodeCatch->GetParam()->AsParseNodeName()->sym;
// Catch object is stored in the catch scope if there may be an ambiguous lookup or a var declaration that hides it.
scope->SetCapturesAll(funcInfo->GetCallsEval() || funcInfo->GetChildCallsEval() || sym->GetHasNonLocalReference());
scope->SetMustInstantiate(scope->GetCapturesAll() || funcInfo->IsGlobalFunction());
if (funcInfo->IsGlobalFunction())
{
sym->SetIsGlobalCatch(true);
}
if (sym->NeedsScopeObject())
{
scope->SetIsObject();
}
if (scope->GetMustInstantiate())
{
if (sym->IsInSlot(this, funcInfo))
{
// Since there is only one symbol we are pushing to slot.
// Also in order to make IsInSlot to return true - forcing the sym-has-non-local-reference.
this->ProcessCapturedSym(sym);
sym->EnsureScopeSlot(this, funcInfo);
}
}
}
PushScope(scope);
}
void ByteCodeGenerator::EndEmitCatch(ParseNodeCatch *pnodeCatch)
{
Assert(pnodeCatch->nop == knopCatch);
Assert(currentScope == pnodeCatch->scope);
PopScope();
}
void ByteCodeGenerator::StartEmitBlock(ParseNodeBlock *pnodeBlock)
{
if (!BlockHasOwnScope(pnodeBlock, this))
{
return;
}
Assert(pnodeBlock->nop == knopBlock);
PushBlock(pnodeBlock);
Scope *scope = pnodeBlock->scope;
if (pnodeBlock->GetCallsEval() || pnodeBlock->GetChildCallsEval() || (this->flags & (fscrEval | fscrImplicitThis)))
{
Assert(scope->GetIsObject());
}
// TODO: Consider nested deferred parsing.
if (scope->GetMustInstantiate())
{
FuncInfo *funcInfo = scope->GetFunc();
this->EnsureFncScopeSlots(pnodeBlock->pnodeScopes, funcInfo);
this->EnsureLetConstScopeSlots(pnodeBlock, funcInfo);
PushScope(scope);
}
}
void ByteCodeGenerator::EndEmitBlock(ParseNodeBlock *pnodeBlock)
{
if (!BlockHasOwnScope(pnodeBlock, this))
{
return;
}
Assert(pnodeBlock->nop == knopBlock);
Scope *scope = pnodeBlock->scope;
if (scope && scope->GetMustInstantiate())
{
Assert(currentScope == pnodeBlock->scope);
PopScope();
}
PopBlock();
}
void ByteCodeGenerator::StartEmitWith(ParseNode *pnodeWith)
{
Assert(pnodeWith->nop == knopWith);
Scope *scope = pnodeWith->AsParseNodeWith()->scope;
AssertOrFailFast(scope->GetIsObject());
PushScope(scope);
}
void ByteCodeGenerator::EndEmitWith(ParseNode *pnodeWith)
{
Assert(pnodeWith->nop == knopWith);
Assert(currentScope == pnodeWith->AsParseNodeWith()->scope);
PopScope();
}
Js::RegSlot ByteCodeGenerator::PrependLocalScopes(Js::RegSlot evalEnv, Js::RegSlot tempLoc, FuncInfo *funcInfo)
{
Scope *currScope = this->currentScope;
Scope *funcScope = funcInfo->GetCurrentChildScope() ? funcInfo->GetCurrentChildScope() : funcInfo->GetBodyScope();
if (currScope == funcScope)
{
return evalEnv;
}
bool acquireTempLoc = tempLoc == Js::Constants::NoRegister;
if (acquireTempLoc)
{
tempLoc = funcInfo->AcquireTmpRegister();
}
// The with/catch objects must be prepended to the environment we pass to eval() or to a func declared inside with,
// but the list must first be reversed so that innermost scopes appear first in the list.
while (currScope != funcScope)
{
Scope *innerScope;
for (innerScope = currScope; innerScope->GetEnclosingScope() != funcScope; innerScope = innerScope->GetEnclosingScope())
;
if (innerScope->GetMustInstantiate())
{
if (!innerScope->HasInnerScopeIndex())
{
if (evalEnv == funcInfo->GetEnvRegister() || evalEnv == funcInfo->frameDisplayRegister)
{
this->m_writer.Reg2(Js::OpCode::LdInnerFrameDisplayNoParent, tempLoc, innerScope->GetLocation());
}
else
{
this->m_writer.Reg3(Js::OpCode::LdInnerFrameDisplay, tempLoc, innerScope->GetLocation(), evalEnv);
}
}
else
{
if (evalEnv == funcInfo->GetEnvRegister() || evalEnv == funcInfo->frameDisplayRegister)
{
this->m_writer.Reg1Unsigned1(Js::OpCode::LdIndexedFrameDisplayNoParent, tempLoc, innerScope->GetInnerScopeIndex());
}
else
{
this->m_writer.Reg2Int1(Js::OpCode::LdIndexedFrameDisplay, tempLoc, evalEnv, innerScope->GetInnerScopeIndex());
}
}
evalEnv = tempLoc;
}
funcScope = innerScope;
}
if (acquireTempLoc)
{
funcInfo->ReleaseTmpRegister(tempLoc);
}
return evalEnv;
}
void ByteCodeGenerator::EmitLoadInstance(Symbol *sym, IdentPtr pid, Js::RegSlot *pThisLocation, Js::RegSlot *pInstLocation, FuncInfo *funcInfo)
{
Js::RegSlot scopeLocation = Js::Constants::NoRegister;
Js::RegSlot thisLocation = *pThisLocation;
Js::RegSlot instLocation = *pInstLocation;
Js::PropertyId envIndex = -1;
Scope *scope = nullptr;
Scope *symScope = sym ? sym->GetScope() : this->globalScope;
Assert(symScope);
if (sym != nullptr && sym->GetIsModuleExportStorage())
{
*pInstLocation = Js::Constants::NoRegister;
return;
}
JsUtil::List<DynamicLoadRecord, ArenaAllocator> recList(this->alloc);
for (;;)
{
scope = this->FindScopeForSym(symScope, scope, &envIndex, funcInfo);
if (scope == this->globalScope)
{
break;
}
if (scope != symScope)
{
// We're not sure where the function is (eval/with/etc).
// So we're going to need registers to hold the instance where we (dynamically) find
// the function, and possibly to hold the "this" pointer we will pass to it.
// Assign them here so that they can't overlap with the scopeLocation assigned below.
// Otherwise we wind up with temp lifetime confusion in the IRBuilder. (Win8 281689)
if (instLocation == Js::Constants::NoRegister)
{
instLocation = funcInfo->AcquireTmpRegister();
}
}
if (envIndex == -1)
{
Assert(funcInfo == scope->GetFunc());
scopeLocation = scope->GetLocation();
}
if (scope == symScope)
{
break;
}
// Found a scope to which the property may have been added.
Assert(scope && scope->GetIsDynamic());
AssertOrFailFast(scope->GetIsObject());
// Record dynamic scopes, in order. Define a label for each one. Remember whether we've seen a 'with'.
// For each dynamic scope, emit BrOnHas[Env,Local]Property $Ln, where n is the scope's position in the list
// Then emit code for default access (i.e., static binding). If no 'with', do not create a temp for 'this', just use 'undefined'.
// End static portion with 'Br $Ldone'.
// Then, for each item in list, emit:
// $Ln:
// copy dynamic scope to 'instance' temp, using 'reuse_loc' form of the opcode
// do the same for 'this' temp', only if we've seen a 'with'
// if not the last item in the list, Br $Ldone
DynamicLoadRecord rec;
rec.label = this->m_writer.DefineLabel();
Js::PropertyId propertyId = sym ? sym->EnsurePosition(this) : pid->GetPropertyId();
Js::PropertyIdIndexType propertyIndex = funcInfo->FindOrAddReferencedPropertyId(propertyId);
if (scope->GetScopeType() != ScopeType_With)
{
if (envIndex == -1)
{
// The local body scope. Branch cannot bail on implicit calls.
this->m_writer.BrLocalProperty(Js::OpCode::BrOnHasLocalProperty, rec.label, propertyIndex);
rec.kind = DynamicLoadKind::Local;
}
else
{
// Function body scope in an enclosing function that calls eval.
// Emit a branch opcode that does not require bail on implicit calls.
uint32 frameDisplayIndex = envIndex + Js::FrameDisplay::GetOffsetOfScopes() / sizeof(Js::Var);
this->m_writer.BrEnvProperty(Js::OpCode::BrOnHasLocalEnvProperty, rec.label, propertyIndex, frameDisplayIndex);
rec.kind = DynamicLoadKind::Env;
rec.index = frameDisplayIndex;
}
}
else
{
if (thisLocation == Js::Constants::NoRegister)
{
thisLocation = funcInfo->AcquireTmpRegister();
}
if (envIndex == -1)
{
// With object declared in this function. HasProperty may have implicit calls.
this->m_writer.BrProperty(Js::OpCode::BrOnHasProperty, rec.label, scopeLocation, propertyIndex);
rec.kind = DynamicLoadKind::LocalWith;
rec.instance = scopeLocation;
}
else
{
// With object declared in an enclosing function. HasProperty may have implicit calls.
uint32 frameDisplayIndex = envIndex + Js::FrameDisplay::GetOffsetOfScopes() / sizeof(Js::Var);
this->m_writer.BrEnvProperty(Js::OpCode::BrOnHasEnvProperty, rec.label, propertyIndex, frameDisplayIndex);
rec.kind = DynamicLoadKind::EnvWith;
rec.index = frameDisplayIndex;
}
}
recList.Add(rec);
}
if (sym == nullptr || sym->GetIsGlobal())
{
if (this->flags & (fscrEval | fscrImplicitThis))
{
// Load of a symbol with unknown scope from within eval.
// Get it from the closure environment.
if (instLocation == Js::Constants::NoRegister)
{
instLocation = funcInfo->AcquireTmpRegister();
}
Js::PropertyId propertyId = sym ? sym->EnsurePosition(this) : pid->GetPropertyId();
if (thisLocation == Js::Constants::NoRegister)
{
thisLocation = funcInfo->AcquireTmpRegister();
}
this->m_writer.ScopedProperty2(Js::OpCode::ScopedLdInst, instLocation,
funcInfo->FindOrAddReferencedPropertyId(propertyId), thisLocation);
}
else
{
if (instLocation == Js::Constants::NoRegister)
{
instLocation = ByteCodeGenerator::RootObjectRegister;
}
else
{
this->m_writer.Reg2(Js::OpCode::Ld_A, instLocation, ByteCodeGenerator::RootObjectRegister);
}
if (thisLocation == Js::Constants::NoRegister)
{
thisLocation = funcInfo->undefinedConstantRegister;
}
else
{
this->m_writer.Reg2(Js::OpCode::Ld_A, thisLocation, funcInfo->undefinedConstantRegister);
}
}
}
else if (instLocation != Js::Constants::NoRegister)
{
if (envIndex != -1)
{
AssertOrFailFast(scope->GetIsObject());
this->m_writer.SlotI1(Js::OpCode::LdEnvObj, instLocation,
envIndex + Js::FrameDisplay::GetOffsetOfScopes() / sizeof(Js::Var));
}
else if (scope->HasInnerScopeIndex())
{
this->m_writer.Reg1Unsigned1(Js::OpCode::LdInnerScope, instLocation, scope->GetInnerScopeIndex());
}
else if (symScope == funcInfo->GetParamScope())
{
Assert(funcInfo->frameObjRegister != Js::Constants::NoRegister && !funcInfo->IsBodyAndParamScopeMerged());
this->m_writer.Reg1(Js::OpCode::LdParamObj, instLocation);
}
else if (symScope != funcInfo->GetBodyScope())
{
this->m_writer.Reg2(Js::OpCode::Ld_A, instLocation, scopeLocation);
}
else
{
Assert(funcInfo->frameObjRegister != Js::Constants::NoRegister);
this->m_writer.Reg1(Js::OpCode::LdLocalObj, instLocation);
}
if (thisLocation != Js::Constants::NoRegister)
{
this->m_writer.Reg2(Js::OpCode::Ld_A, thisLocation, funcInfo->undefinedConstantRegister);
}
else
{
thisLocation = funcInfo->undefinedConstantRegister;
}
}
if (!recList.Empty())
{
Assert(instLocation != Js::Constants::NoRegister);
Assert(thisLocation != Js::Constants::NoRegister);
Js::ByteCodeLabel doneLabel = this->m_writer.DefineLabel();
this->m_writer.Br(doneLabel);
for (int i = 0;; i++)
{
this->m_writer.MarkLabel(recList.Item(i).label);
switch(recList.Item(i).kind)
{
case DynamicLoadKind::Local:
this->m_writer.Reg1(Js::OpCode::LdLocalObj_ReuseLoc, instLocation);
if (thisLocation != funcInfo->undefinedConstantRegister)
{
Assert(thisLocation != instLocation);
this->m_writer.Reg2(Js::OpCode::Ld_A_ReuseLoc, thisLocation, funcInfo->undefinedConstantRegister);
}
break;
case DynamicLoadKind::Env:
this->m_writer.SlotI1(Js::OpCode::LdEnvObj_ReuseLoc, instLocation, recList.Item(i).index);
if (thisLocation != funcInfo->undefinedConstantRegister)
{
Assert(thisLocation != instLocation);
this->m_writer.Reg2(Js::OpCode::Ld_A_ReuseLoc, thisLocation, funcInfo->undefinedConstantRegister);
}
break;
case DynamicLoadKind::LocalWith:
this->m_writer.Reg2(Js::OpCode::UnwrapWithObj_ReuseLoc, instLocation, recList.Item(i).instance);
this->m_writer.Reg2(Js::OpCode::UnwrapWithObj_ReuseLoc, thisLocation, recList.Item(i).instance);
break;
case DynamicLoadKind::EnvWith:
{
Js::RegSlot tmpReg = funcInfo->AcquireTmpRegister();
this->m_writer.SlotI1(Js::OpCode::LdEnvObj, tmpReg, recList.Item(i).index);
this->m_writer.Reg2(Js::OpCode::UnwrapWithObj_ReuseLoc, instLocation, tmpReg);
this->m_writer.Reg2(Js::OpCode::UnwrapWithObj_ReuseLoc, thisLocation, tmpReg);
funcInfo->ReleaseTmpRegister(tmpReg);
break;
}
default:
AssertOrFailFast(UNREACHED);
}
if (i == recList.Count() - 1)
{
break;
}
this->m_writer.Br(doneLabel);
}
this->m_writer.MarkLabel(doneLabel);
}
*pThisLocation = thisLocation;
*pInstLocation = instLocation;
}
void ByteCodeGenerator::EmitGlobalFncDeclInit(Js::RegSlot rhsLocation, Js::PropertyId propertyId, FuncInfo * funcInfo)
{
// Note: declared variables and assignments in the global function go to the root object directly.
if (this->flags & fscrEval)
{
// Func decl's always get their init values before any use, so we don't pre-initialize the property to undef.
// That means that we have to use ScopedInitFld so that we initialize the property on the right instance
// even if the instance doesn't have the property yet (i.e., collapse the init-to-undef and the store
// into one operation). See WOOB 1121763 and 1120973.
this->m_writer.ScopedProperty(Js::OpCode::ScopedInitFunc, rhsLocation,
funcInfo->FindOrAddReferencedPropertyId(propertyId));
}
else
{
this->EmitPatchableRootProperty(Js::OpCode::InitRootFld, rhsLocation, propertyId, false, true, funcInfo);
}
}
void
ByteCodeGenerator::EmitPatchableRootProperty(Js::OpCode opcode,
Js::RegSlot regSlot, Js::PropertyId propertyId, bool isLoadMethod, bool isStore, FuncInfo * funcInfo)
{
uint cacheId = funcInfo->FindOrAddRootObjectInlineCacheId(propertyId, isLoadMethod, isStore);
this->m_writer.PatchableRootProperty(opcode, regSlot, cacheId, isLoadMethod, isStore);
}
void ByteCodeGenerator::EmitLocalPropInit(Js::RegSlot rhsLocation, Symbol *sym, FuncInfo *funcInfo)
{
Scope *scope = sym->GetScope();
// Check consistency of sym->IsInSlot.
Assert(sym->NeedsSlotAlloc(this, funcInfo) || sym->GetScopeSlot() == Js::Constants::NoProperty);
// Arrived at the scope in which the property was defined.
if (sym->NeedsSlotAlloc(this, funcInfo))
{
// The property is in memory rather than register. We'll have to load it from the slots.
if (scope->GetIsObject())
{
Assert(!this->TopFuncInfo()->GetParsedFunctionBody()->DoStackNestedFunc());
Js::PropertyId propertyId = sym->EnsurePosition(this);
Js::RegSlot objReg;
if (scope->HasInnerScopeIndex())
{
objReg = funcInfo->InnerScopeToRegSlot(scope);
}
else
{
objReg = scope->GetLocation();
}
uint cacheId = funcInfo->FindOrAddInlineCacheId(objReg, propertyId, false, true);
Js::OpCode op = this->GetInitFldOp(scope, objReg, funcInfo, sym->GetIsNonSimpleParameter());
if (objReg != Js::Constants::NoRegister && objReg == funcInfo->frameObjRegister)
{
this->m_writer.ElementP(op, rhsLocation, cacheId);
}
else if (scope->HasInnerScopeIndex())
{
this->m_writer.ElementPIndexed(op, rhsLocation, scope->GetInnerScopeIndex(), cacheId);
}
else
{
this->m_writer.PatchableProperty(op, rhsLocation, scope->GetLocation(), cacheId);
}
}
else
{
// Make sure the property has a slot. This will bump up the size of the slot array if necessary.
Js::PropertyId slot = sym->EnsureScopeSlot(this, funcInfo);
Js::RegSlot slotReg = scope->GetCanMerge() ? funcInfo->frameSlotsRegister : scope->GetLocation();
// Now store the property to its slot.
Js::OpCode op = this->GetStSlotOp(scope, -1, slotReg, false, funcInfo);
if (slotReg != Js::Constants::NoRegister && slotReg == funcInfo->frameSlotsRegister)
{
this->m_writer.SlotI1(op, rhsLocation, slot + Js::ScopeSlots::FirstSlotIndex);
}
else
{
this->m_writer.SlotI2(op, rhsLocation, scope->GetInnerScopeIndex(), slot + Js::ScopeSlots::FirstSlotIndex);
}
}
}
if (sym->GetLocation() != Js::Constants::NoRegister && rhsLocation != sym->GetLocation())
{
this->m_writer.Reg2(Js::OpCode::Ld_A, sym->GetLocation(), rhsLocation);
}
}
Js::OpCode
ByteCodeGenerator::GetStSlotOp(Scope *scope, int envIndex, Js::RegSlot scopeLocation, bool chkBlockVar, FuncInfo *funcInfo)
{
Js::OpCode op;
if (envIndex != -1)
{
if (scope->GetIsObject())
{
op = Js::OpCode::StEnvObjSlot;
}
else
{
op = Js::OpCode::StEnvSlot;
}
}
else if (scopeLocation != Js::Constants::NoRegister &&
scopeLocation == funcInfo->frameSlotsRegister)
{
if (scope->GetScopeType() == ScopeType_Parameter && scope != scope->GetFunc()->GetCurrentChildScope())
{
// Symbol is from the param scope of a split scope function and we are emitting the body.
// We should use the param scope's bytecode now.
Assert(!funcInfo->IsBodyAndParamScopeMerged());
op = Js::OpCode::StParamSlot;
}
else
{
op = Js::OpCode::StLocalSlot;
}
}
else if (scopeLocation != Js::Constants::NoRegister &&
scopeLocation == funcInfo->frameObjRegister)
{
if (scope->GetScopeType() == ScopeType_Parameter && scope != scope->GetFunc()->GetCurrentChildScope())
{
// Symbol is from the param scope of a split scope function and we are emitting the body.
// We should use the param scope's bytecode now.
Assert(!funcInfo->IsBodyAndParamScopeMerged());
op = Js::OpCode::StParamObjSlot;
}
else
{
op = Js::OpCode::StLocalObjSlot;
}
}
else
{
Assert(scope->HasInnerScopeIndex());
if (scope->GetIsObject())
{
op = Js::OpCode::StInnerObjSlot;
}
else
{
op = Js::OpCode::StInnerSlot;
}
}
if (chkBlockVar)
{
op = this->ToChkUndeclOp(op);
}
return op;
}
Js::OpCode
ByteCodeGenerator::GetInitFldOp(Scope *scope, Js::RegSlot scopeLocation, FuncInfo *funcInfo, bool letDecl)
{
Js::OpCode op;
if (scopeLocation != Js::Constants::NoRegister &&
scopeLocation == funcInfo->frameObjRegister)
{
op = letDecl ? Js::OpCode::InitLocalLetFld : Js::OpCode::InitLocalFld;
}
else if (scope->HasInnerScopeIndex())
{
op = letDecl ? Js::OpCode::InitInnerLetFld : Js::OpCode::InitInnerFld;
}
else
{
op = letDecl ? Js::OpCode::InitLetFld : Js::OpCode::InitFld;
}
return op;
}
void ByteCodeGenerator::EmitPropStore(Js::RegSlot rhsLocation, Symbol *sym, IdentPtr pid, FuncInfo *funcInfo, bool isLetDecl, bool isConstDecl, bool isFncDeclVar, bool skipUseBeforeDeclarationCheck)
{
Js::PropertyId envIndex = -1;
Scope *symScope = sym == nullptr || sym->GetIsGlobal() ? this->globalScope : sym->GetScope();
Assert(symScope);
// isFncDeclVar denotes that the symbol being stored to here is the var
// binding of a function declaration and we know we want to store directly
// to it, skipping over any dynamic scopes that may lie in between.
Scope *scope = nullptr;
Js::RegSlot scopeLocation = Js::Constants::NoRegister;
Js::OpCode op;
if (sym && sym->GetIsModuleExportStorage())
{
if (!isConstDecl && sym->GetDecl() && sym->GetDecl()->nop == knopConstDecl)
{
this->m_writer.W1(Js::OpCode::RuntimeTypeError, SCODE_CODE(ERRAssignmentToConst));
}
EmitModuleExportAccess(sym, Js::OpCode::StModuleSlot, rhsLocation, funcInfo);
return;
}
if (isFncDeclVar)
{
// async functions allow for the fncDeclVar to be in the body or parameter scope
// of the parent function, so we need to calculate envIndex in lieu of the while
// loop below.
do
{
scope = this->FindScopeForSym(symScope, scope, &envIndex, funcInfo);
} while (scope != symScope);
Assert(scope == symScope);
scopeLocation = scope->GetLocation();
}
JsUtil::List<DynamicLoadRecord, ArenaAllocator> recList(this->alloc);
while (!isFncDeclVar)
{
scope = this->FindScopeForSym(symScope, scope, &envIndex, funcInfo);
if (scope == this->globalScope)
{
break;
}
if (envIndex == -1)
{
Assert(funcInfo == scope->GetFunc());
scopeLocation = scope->GetLocation();
}
if (scope == symScope)
{
break;
}
// Found a scope to which the property may have been added.
Assert(scope && scope->GetIsDynamic());
AssertOrFailFast(scope->GetIsObject());
DynamicLoadRecord rec;
rec.label = this->m_writer.DefineLabel();
Js::PropertyId propertyId = sym ? sym->EnsurePosition(this) : pid->GetPropertyId();
Js::PropertyIdIndexType propertyIndex = funcInfo->FindOrAddReferencedPropertyId(propertyId);
if (scope->GetScopeType() != ScopeType_With)
{
if (envIndex == -1)
{
this->m_writer.BrLocalProperty(Js::OpCode::BrOnHasLocalProperty, rec.label, propertyIndex);
rec.kind = DynamicLoadKind::Local;
rec.instance = scopeLocation;
}
else
{
uint32 frameDisplayIndex = envIndex + Js::FrameDisplay::GetOffsetOfScopes() / sizeof(Js::Var);
this->m_writer.BrEnvProperty(Js::OpCode::BrOnHasLocalEnvProperty, rec.label, propertyIndex, frameDisplayIndex);
rec.kind = DynamicLoadKind::Env;
rec.index = frameDisplayIndex;
}
}
else
{
if (envIndex == -1)
{
this->m_writer.BrProperty(Js::OpCode::BrOnHasProperty, rec.label, scopeLocation, propertyIndex);
rec.kind = DynamicLoadKind::LocalWith;
rec.instance = scopeLocation;
}
else
{
uint32 frameDisplayIndex = envIndex + Js::FrameDisplay::GetOffsetOfScopes() / sizeof(Js::Var);
this->m_writer.BrEnvProperty(Js::OpCode::BrOnHasEnvProperty, rec.label, propertyIndex, frameDisplayIndex);
rec.kind = DynamicLoadKind::EnvWith;
rec.index = frameDisplayIndex;
}
}
recList.Add(rec);
}
// Arrived at the scope in which the property was defined.
if (!skipUseBeforeDeclarationCheck && sym && sym->GetNeedDeclaration() && scope->GetFunc() == funcInfo)
{
EmitUseBeforeDeclarationRuntimeError(this, Js::Constants::NoRegister);
// Intentionally continue on to do normal EmitPropStore behavior so
// that the bytecode ends up well-formed for the backend. This is
// in contrast to EmitPropLoad and EmitPropTypeof where they both
// tell EmitUseBeforeDeclarationRuntimeError to emit a LdUndef in place
// of their load and then they skip emitting their own bytecode.
// Potayto potahto.
}
if (sym == nullptr || sym->GetIsGlobal())
{
Js::PropertyId propertyId = sym ? sym->EnsurePosition(this) : pid->GetPropertyId();
bool isConsoleScopeLetConst = this->IsConsoleScopeEval() && (isLetDecl || isConstDecl);
if (this->flags & fscrEval)
{
if (funcInfo->byteCodeFunction->GetIsStrictMode() && funcInfo->IsGlobalFunction())
{
uint cacheId = funcInfo->FindOrAddInlineCacheId(funcInfo->frameDisplayRegister, propertyId, false, true);
this->m_writer.ElementP(GetScopedStFldOpCode(funcInfo, isConsoleScopeLetConst), rhsLocation, cacheId);
}
else
{
uint cacheId = funcInfo->FindOrAddInlineCacheId(funcInfo->GetEnvRegister(), propertyId, false, true);
// In "eval", store to a symbol with unknown scope goes through the closure environment.
this->m_writer.ElementP(GetScopedStFldOpCode(funcInfo, isConsoleScopeLetConst), rhsLocation, cacheId);
}
}
else if (this->flags & fscrImplicitThis)
{
uint cacheId = funcInfo->FindOrAddInlineCacheId(funcInfo->GetEnvRegister(), propertyId, false, true);
// In HTML event handler, store to a symbol with unknown scope goes through the closure environment.
this->m_writer.ElementP(GetScopedStFldOpCode(funcInfo, isConsoleScopeLetConst), rhsLocation, cacheId);
}
else
{
this->EmitPatchableRootProperty(GetStFldOpCode(funcInfo, true, isLetDecl, isConstDecl, false, forceStrictModeForClassComputedPropertyName), rhsLocation, propertyId, false, true, funcInfo);
}
}
else if (sym->GetIsFuncExpr())
{
// Store to function expr variable.
// strict mode: we need to throw type error
if (funcInfo->byteCodeFunction->GetIsStrictMode())
{
// Note that in this case the sym's location belongs to the parent function, so we can't use it.
// It doesn't matter which register we use, as long as it's valid for this function.
this->m_writer.W1(Js::OpCode::RuntimeTypeError, SCODE_CODE(JSERR_CantAssignToReadOnly));
}
}
else if (sym->IsInSlot(this, funcInfo) || envIndex != -1)
{
if (!isConstDecl && sym->GetIsConst())
{
// This is a case where const reassignment can't be proven statically (e.g., eval, with) so
// we have to catch it at runtime.
this->m_writer.W1(
Js::OpCode::RuntimeTypeError, SCODE_CODE(ERRAssignmentToConst));
}
// Make sure the property has a slot. This will bump up the size of the slot array if necessary.
Js::PropertyId slot = sym->EnsureScopeSlot(this, funcInfo);
bool chkBlockVar = !isLetDecl && !isConstDecl && NeedCheckBlockVar(sym, scope, funcInfo);
// The property is in memory rather than register. We'll have to load it from the slots.
op = this->GetStSlotOp(scope, envIndex, scopeLocation, chkBlockVar, funcInfo);
if (envIndex != -1)
{
this->m_writer.SlotI2(op, rhsLocation,
envIndex + Js::FrameDisplay::GetOffsetOfScopes() / sizeof(Js::Var),
slot + (sym->GetScope()->GetIsObject() ? 0 : Js::ScopeSlots::FirstSlotIndex));
}
else if (scopeLocation != Js::Constants::NoRegister &&
(scopeLocation == funcInfo->frameSlotsRegister || scopeLocation == funcInfo->frameObjRegister))
{
this->m_writer.SlotI1(op, rhsLocation,
slot + (sym->GetScope()->GetIsObject() ? 0 : Js::ScopeSlots::FirstSlotIndex));
}
else
{
Assert(scope->HasInnerScopeIndex());
this->m_writer.SlotI2(op, rhsLocation, scope->GetInnerScopeIndex(),
slot + (sym->GetScope()->GetIsObject() ? 0 : Js::ScopeSlots::FirstSlotIndex));
}
if (this->ShouldTrackDebuggerMetadata() && (isLetDecl || isConstDecl))
{
Js::PropertyId location = scope->GetIsObject() ? sym->GetLocation() : slot;
this->UpdateDebuggerPropertyInitializationOffset(location, sym->GetPosition(), false);
}
}
else
{
if (!isConstDecl && sym->GetDecl() && sym->GetDecl()->nop == knopConstDecl)
{
// This is a case where const reassignment can't be proven statically (e.g., eval, with) so
// we have to catch it at runtime.
this->m_writer.W1(Js::OpCode::RuntimeTypeError, SCODE_CODE(ERRAssignmentToConst));
}
if (rhsLocation != sym->GetLocation())
{
this->m_writer.Reg2(Js::OpCode::Ld_A, sym->GetLocation(), rhsLocation);
if (this->ShouldTrackDebuggerMetadata() && (isLetDecl || isConstDecl))
{
this->UpdateDebuggerPropertyInitializationOffset(sym->GetLocation(), sym->GetPosition());
}
}
}
if (!recList.Empty())
{
Js::ByteCodeLabel doneLabel = this->m_writer.DefineLabel();
this->m_writer.Br(doneLabel);
for (int i = 0;; i++)
{
uint cacheId;
Js::RegSlot instLocation;
Js::PropertyId propertyId = sym ? sym->EnsurePosition(this) : pid->GetPropertyId();
this->m_writer.MarkLabel(recList.Item(i).label);
switch(recList.Item(i).kind)
{
case DynamicLoadKind::Local:
cacheId = funcInfo->FindOrAddInlineCacheId(recList.Item(i).instance, propertyId, false, true);
this->m_writer.ElementP(Js::OpCode::StLocalFld, rhsLocation, cacheId);
break;
case DynamicLoadKind::Env:
instLocation = funcInfo->AcquireTmpRegister();
this->m_writer.SlotI1(Js::OpCode::LdEnvObj, instLocation, recList.Item(i).index);
cacheId = funcInfo->FindOrAddInlineCacheId(instLocation, propertyId, false, true);
this->m_writer.PatchableProperty(Js::OpCode::StFld, rhsLocation, instLocation, cacheId);
funcInfo->ReleaseTmpRegister(instLocation);
break;
case DynamicLoadKind::LocalWith:
instLocation = funcInfo->AcquireTmpRegister();
this->m_writer.Reg2(Js::OpCode::UnwrapWithObj, instLocation, recList.Item(i).instance);
cacheId = funcInfo->FindOrAddInlineCacheId(instLocation, propertyId, false, true);
this->m_writer.PatchableProperty(Js::OpCode::StFld, rhsLocation, instLocation, cacheId);
funcInfo->ReleaseTmpRegister(instLocation);
break;
case DynamicLoadKind::EnvWith:
instLocation = funcInfo->AcquireTmpRegister();
this->m_writer.SlotI1(Js::OpCode::LdEnvObj, instLocation, recList.Item(i).index);
this->m_writer.Reg2(Js::OpCode::UnwrapWithObj, instLocation, instLocation);
cacheId = funcInfo->FindOrAddInlineCacheId(instLocation, propertyId, false, true);
this->m_writer.PatchableProperty(Js::OpCode::StFld, rhsLocation, instLocation, cacheId);
funcInfo->ReleaseTmpRegister(instLocation);
break;
default:
AssertOrFailFast(UNREACHED);
}
if (i == recList.Count() - 1)
{
break;
}
this->m_writer.Br(doneLabel);
}
this->m_writer.MarkLabel(doneLabel);
}
}
Js::OpCode
ByteCodeGenerator::GetLdSlotOp(Scope *scope, int envIndex, Js::RegSlot scopeLocation, FuncInfo *funcInfo)
{
Js::OpCode op;
if (envIndex != -1)
{
if (scope->GetIsObject())
{
op = Js::OpCode::LdEnvObjSlot;
}
else
{
op = Js::OpCode::LdEnvSlot;
}
}
else if (scopeLocation != Js::Constants::NoRegister &&
scopeLocation == funcInfo->frameSlotsRegister)
{
if (scope->GetScopeType() == ScopeType_Parameter && scope != scope->GetFunc()->GetCurrentChildScope())
{
// Symbol is from the param scope of a split scope function and we are emitting the body.
// We should use the param scope's bytecode now.
Assert(!funcInfo->IsBodyAndParamScopeMerged());
op = Js::OpCode::LdParamSlot;
}
else
{
op = Js::OpCode::LdLocalSlot;
}
}
else if (scopeLocation != Js::Constants::NoRegister &&
scopeLocation == funcInfo->frameObjRegister)
{
if (scope->GetScopeType() == ScopeType_Parameter && scope != scope->GetFunc()->GetCurrentChildScope())
{
// Symbol is from the param scope of a split scope function and we are emitting the body.
// We should use the param scope's bytecode now.
Assert(!funcInfo->IsBodyAndParamScopeMerged());
op = Js::OpCode::LdParamObjSlot;
}
else
{
op = Js::OpCode::LdLocalObjSlot;
}
}
else if (scope->HasInnerScopeIndex())
{
if (scope->GetIsObject())
{
op = Js::OpCode::LdInnerObjSlot;
}
else
{
op = Js::OpCode::LdInnerSlot;
}
}
else
{
AssertOrFailFast(scope->GetIsObject());
op = Js::OpCode::LdObjSlot;
}
return op;
}
bool ByteCodeGenerator::ShouldLoadConstThis(FuncInfo* funcInfo)
{
#if DBG
// We should load a const 'this' binding if the following holds
// - The function has a 'this' name node
// - We are in a global or global lambda function
// - The function has no 'this' symbol (an indirect eval would have this symbol)
if (funcInfo->thisConstantRegister != Js::Constants::NoRegister)
{
Assert((funcInfo->IsLambda() || funcInfo->IsGlobalFunction())
&& !funcInfo->GetThisSymbol()
&& !(this->flags & fscrEval));
}
#endif
return funcInfo->thisConstantRegister != Js::Constants::NoRegister;
}
void ByteCodeGenerator::EmitPropLoadThis(Js::RegSlot lhsLocation, ParseNodeSpecialName *pnodeSpecialName, FuncInfo *funcInfo, bool chkUndecl)
{
Symbol* sym = pnodeSpecialName->sym;
if (!sym && this->ShouldLoadConstThis(funcInfo))
{
this->Writer()->Reg2(Js::OpCode::Ld_A, lhsLocation, funcInfo->thisConstantRegister);
}
else
{
this->EmitPropLoad(lhsLocation, pnodeSpecialName->sym, pnodeSpecialName->pid, funcInfo, true);
if ((!sym || sym->GetNeedDeclaration()) && chkUndecl)
{
this->Writer()->Reg1(Js::OpCode::ChkUndecl, lhsLocation);
}
}
}
void ByteCodeGenerator::EmitPropStoreForSpecialSymbol(Js::RegSlot rhsLocation, Symbol *sym, IdentPtr pid, FuncInfo *funcInfo, bool init)
{
if (!funcInfo->IsGlobalFunction() || (this->flags & fscrEval))
{
if (init)
{
EmitLocalPropInit(rhsLocation, sym, funcInfo);
}
else
{
EmitPropStore(rhsLocation, sym, pid, funcInfo, false, false, false, true);
}
}
}
void ByteCodeGenerator::EmitPropLoad(Js::RegSlot lhsLocation, Symbol *sym, IdentPtr pid, FuncInfo *funcInfo, bool skipUseBeforeDeclarationCheck)
{
// If sym belongs to a parent frame, get it from the closure environment.
// If it belongs to this func, but there's a non-local reference, get it from the heap-allocated frame.
// (TODO: optimize this by getting the sym from its normal location if there are no non-local defs.)
// Otherwise, just copy the value to the lhsLocation.
Js::RegSlot scopeLocation = Js::Constants::NoRegister;
Js::PropertyId envIndex = -1;
Scope *scope = nullptr;
Scope *symScope = sym ? sym->GetScope() : this->globalScope;
Assert(symScope);
if (sym && sym->GetIsModuleExportStorage())
{
EmitModuleExportAccess(sym, Js::OpCode::LdModuleSlot, lhsLocation, funcInfo);
return;
}
JsUtil::List<DynamicLoadRecord, ArenaAllocator> recList(this->alloc);
for (;;)
{
scope = this->FindScopeForSym(symScope, scope, &envIndex, funcInfo);
if (scope == this->globalScope)
{
break;
}
scopeLocation = scope->GetLocation();
if (scope == symScope)
{
break;
}
// Found a scope to which the property may have been added.
Assert(scope && scope->GetIsDynamic());
AssertOrFailFast(scope->GetIsObject());
DynamicLoadRecord rec;
rec.label = this->m_writer.DefineLabel();
Js::PropertyId propertyId = sym ? sym->EnsurePosition(this) : pid->GetPropertyId();
Js::PropertyIdIndexType propertyIndex = funcInfo->FindOrAddReferencedPropertyId(propertyId);
if (scope->GetScopeType() != ScopeType_With)
{
if (envIndex == -1)
{
this->m_writer.BrLocalProperty(Js::OpCode::BrOnHasLocalProperty, rec.label, propertyIndex);
rec.kind = DynamicLoadKind::Local;
rec.instance = scopeLocation;
}
else
{
uint32 frameDisplayIndex = envIndex + Js::FrameDisplay::GetOffsetOfScopes() / sizeof(Js::Var);
this->m_writer.BrEnvProperty(Js::OpCode::BrOnHasLocalEnvProperty, rec.label, propertyIndex, frameDisplayIndex);
rec.kind = DynamicLoadKind::Env;
rec.index = frameDisplayIndex;
}
}
else
{
if (envIndex == -1)
{
this->m_writer.BrProperty(Js::OpCode::BrOnHasProperty, rec.label, scopeLocation, propertyIndex);
rec.kind = DynamicLoadKind::LocalWith;
rec.instance = scopeLocation;
}
else
{
uint32 frameDisplayIndex = envIndex + Js::FrameDisplay::GetOffsetOfScopes() / sizeof(Js::Var);
this->m_writer.BrEnvProperty(Js::OpCode::BrOnHasEnvProperty, rec.label, propertyIndex, frameDisplayIndex);
rec.kind = DynamicLoadKind::EnvWith;
rec.index = frameDisplayIndex;
}
}
recList.Add(rec);
}
// Arrived at the scope in which the property was defined.
if (sym && sym->GetNeedDeclaration() && scope->GetFunc() == funcInfo && !skipUseBeforeDeclarationCheck)
{
// Ensure this symbol has a slot if it needs one.
if (sym->IsInSlot(this, funcInfo))
{
Js::PropertyId slot = sym->EnsureScopeSlot(this, funcInfo);
funcInfo->FindOrAddSlotProfileId(scope, slot);
}
if (skipUseBeforeDeclarationCheck)
{
if (lhsLocation != Js::Constants::NoRegister)
{
this->m_writer.Reg1(Js::OpCode::InitUndecl, lhsLocation);
}
}
else
{
EmitUseBeforeDeclarationRuntimeError(this, lhsLocation);
}
}
else if (sym == nullptr || sym->GetIsGlobal())
{
Js::PropertyId propertyId = sym ? sym->EnsurePosition(this) : pid->GetPropertyId();
if (this->flags & fscrEval)
{
if (funcInfo->byteCodeFunction->GetIsStrictMode() && funcInfo->IsGlobalFunction())
{
uint cacheId = funcInfo->FindOrAddInlineCacheId(funcInfo->frameDisplayRegister, propertyId, false, false);
this->m_writer.ElementP(Js::OpCode::ScopedLdFld, lhsLocation, cacheId);
}
else
{
uint cacheId = funcInfo->FindOrAddInlineCacheId(funcInfo->GetEnvRegister(), propertyId, false, false);
// Load of a symbol with unknown scope from within eval
// Get it from the closure environment.
this->m_writer.ElementP(Js::OpCode::ScopedLdFld, lhsLocation, cacheId);
}
}
else if (this->flags & fscrImplicitThis)
{
uint cacheId = funcInfo->FindOrAddInlineCacheId(funcInfo->GetEnvRegister(), propertyId, false, false);
// Load of a symbol with unknown scope from within event handler.
// Get it from the closure environment.
this->m_writer.ElementP(Js::OpCode::ScopedLdFld, lhsLocation, cacheId);
}
else
{
// Special case non-writable built-ins
// TODO: support non-writable global property in general by detecting what attribute the property have current?
// But can't be done if we are byte code serialized, because the attribute might be different for use fields
// next time we run. May want to catch that in the JIT.
Js::OpCode opcode = Js::OpCode::LdRootFld;
// These properties are non-writable
switch (propertyId)
{
case Js::PropertyIds::NaN:
opcode = Js::OpCode::LdNaN;
break;
case Js::PropertyIds::Infinity:
opcode = Js::OpCode::LdInfinity;
break;
case Js::PropertyIds::undefined:
opcode = Js::OpCode::LdUndef;
break;
case Js::PropertyIds::__chakraLibrary:
if (CONFIG_FLAG(LdChakraLib) || CONFIG_FLAG(TestChakraLib))
{
opcode = Js::OpCode::LdChakraLib;
}
break;
}
if (opcode == Js::OpCode::LdRootFld)
{
this->EmitPatchableRootProperty(Js::OpCode::LdRootFld, lhsLocation, propertyId, false, false, funcInfo);
}
else
{
this->Writer()->Reg1(opcode, lhsLocation);
}
}
}
else if (sym->IsInSlot(this, funcInfo) || envIndex != -1)
{
// Make sure the property has a slot. This will bump up the size of the slot array if necessary.
Js::PropertyId slot = sym->EnsureScopeSlot(this, funcInfo);
Js::ProfileId profileId = funcInfo->FindOrAddSlotProfileId(scope, slot);
bool chkBlockVar = NeedCheckBlockVar(sym, scope, funcInfo);
Js::OpCode op;
// Now get the property from its slot.
op = this->GetLdSlotOp(scope, envIndex, scopeLocation, funcInfo);
slot = slot + (sym->GetScope()->GetIsObject() ? 0 : Js::ScopeSlots::FirstSlotIndex);
if (envIndex != -1)
{
this->m_writer.SlotI2(op, lhsLocation, envIndex + Js::FrameDisplay::GetOffsetOfScopes() / sizeof(Js::Var), slot, profileId);
}
else if (scopeLocation != Js::Constants::NoRegister &&
(scopeLocation == funcInfo->frameSlotsRegister || scopeLocation == funcInfo->frameObjRegister))
{
this->m_writer.SlotI1(op, lhsLocation, slot, profileId);
}
else if (scope->HasInnerScopeIndex())
{
this->m_writer.SlotI2(op, lhsLocation, scope->GetInnerScopeIndex(), slot, profileId);
}
else
{
AssertOrFailFast(scope->GetIsObject());
this->m_writer.Slot(op, lhsLocation, scopeLocation, slot, profileId);
}
if (chkBlockVar)
{
this->m_writer.Reg1(Js::OpCode::ChkUndecl, lhsLocation);
}
}
else
{
if (lhsLocation != sym->GetLocation())
{
this->m_writer.Reg2(Js::OpCode::Ld_A, lhsLocation, sym->GetLocation());
}
if (sym->GetIsBlockVar() && ((sym->GetDecl()->nop == knopLetDecl || sym->GetDecl()->nop == knopConstDecl) && sym->GetDecl()->AsParseNodeVar()->isSwitchStmtDecl))
{
this->m_writer.Reg1(Js::OpCode::ChkUndecl, lhsLocation);
}
}
if (!recList.Empty())
{
Js::ByteCodeLabel doneLabel = this->m_writer.DefineLabel();
this->m_writer.Br(doneLabel);
for (int i = 0;; i++)
{
uint cacheId;
Js::RegSlot instLocation;
Js::PropertyId propertyId = sym ? sym->EnsurePosition(this) : pid->GetPropertyId();
this->m_writer.MarkLabel(recList.Item(i).label);
switch(recList.Item(i).kind)
{
case DynamicLoadKind::Local:
cacheId = funcInfo->FindOrAddInlineCacheId(recList.Item(i).instance, propertyId, false, false);
this->m_writer.ElementP(Js::OpCode::LdLocalFld_ReuseLoc, lhsLocation, cacheId);
break;
case DynamicLoadKind::Env:
instLocation = funcInfo->AcquireTmpRegister();
this->m_writer.SlotI1(Js::OpCode::LdEnvObj, instLocation, recList.Item(i).index);
cacheId = funcInfo->FindOrAddInlineCacheId(instLocation, propertyId, false, false);
this->m_writer.PatchableProperty(Js::OpCode::LdFld_ReuseLoc, lhsLocation, instLocation, cacheId);
funcInfo->ReleaseTmpRegister(instLocation);
break;
case DynamicLoadKind::LocalWith:
instLocation = funcInfo->AcquireTmpRegister();
this->m_writer.Reg2(Js::OpCode::UnwrapWithObj, instLocation, recList.Item(i).instance);
cacheId = funcInfo->FindOrAddInlineCacheId(instLocation, propertyId, false, false);
this->m_writer.PatchableProperty(Js::OpCode::LdFld_ReuseLoc, lhsLocation, instLocation, cacheId);
funcInfo->ReleaseTmpRegister(instLocation);
break;
case DynamicLoadKind::EnvWith:
instLocation = funcInfo->AcquireTmpRegister();
this->m_writer.SlotI1(Js::OpCode::LdEnvObj, instLocation, recList.Item(i).index);
this->m_writer.Reg2(Js::OpCode::UnwrapWithObj, instLocation, instLocation);
cacheId = funcInfo->FindOrAddInlineCacheId(instLocation, propertyId, false, false);
this->m_writer.PatchableProperty(Js::OpCode::LdFld_ReuseLoc, lhsLocation, instLocation, cacheId);
funcInfo->ReleaseTmpRegister(instLocation);
break;
default:
AssertOrFailFast(UNREACHED);
}
if (i == recList.Count() - 1)
{
break;
}
this->m_writer.Br(doneLabel);
}
this->m_writer.MarkLabel(doneLabel);
}
}
bool ByteCodeGenerator::NeedCheckBlockVar(Symbol* sym, Scope* scope, FuncInfo* funcInfo) const
{
bool tdz = sym->GetIsBlockVar()
&& (scope->GetFunc() != funcInfo || ((sym->GetDecl()->nop == knopLetDecl || sym->GetDecl()->nop == knopConstDecl) && sym->GetDecl()->AsParseNodeVar()->isSwitchStmtDecl));
return tdz || sym->GetIsNonSimpleParameter();
}
void ByteCodeGenerator::EmitPropDelete(Js::RegSlot lhsLocation, Symbol *sym, IdentPtr pid, FuncInfo *funcInfo)
{
// If sym belongs to a parent frame, delete it from the closure environment.
// If it belongs to this func, but there's a non-local reference, get it from the heap-allocated frame.
// (TODO: optimize this by getting the sym from its normal location if there are no non-local defs.)
// Otherwise, just return false.
Js::RegSlot scopeLocation = Js::Constants::NoRegister;
Js::PropertyId envIndex = -1;
Scope *scope = nullptr;
Scope *symScope = sym ? sym->GetScope() : this->globalScope;
Assert(symScope);
JsUtil::List<DynamicLoadRecord, ArenaAllocator> recList(this->alloc);
for (;;)
{
scope = this->FindScopeForSym(symScope, scope, &envIndex, funcInfo);
if (scope == this->globalScope)
{
scopeLocation = ByteCodeGenerator::RootObjectRegister;
}
else if (envIndex == -1)
{
Assert(funcInfo == scope->GetFunc());
scopeLocation = scope->GetLocation();
}
if (scope == symScope)
{
break;
}
// Found a scope to which the property may have been added.
Assert(scope && scope->GetIsDynamic());
AssertOrFailFast(scope->GetIsObject());
DynamicLoadRecord rec;
rec.label = this->m_writer.DefineLabel();
Js::PropertyId propertyId = sym ? sym->EnsurePosition(this) : pid->GetPropertyId();
Js::PropertyIdIndexType propertyIndex = funcInfo->FindOrAddReferencedPropertyId(propertyId);
if (scope->GetScopeType() != ScopeType_With)
{
if (envIndex == -1)
{
this->m_writer.BrLocalProperty(Js::OpCode::BrOnHasLocalProperty, rec.label, propertyIndex);
rec.kind = DynamicLoadKind::Local;
rec.instance = scopeLocation;
}
else
{
uint32 frameDisplayIndex = envIndex + Js::FrameDisplay::GetOffsetOfScopes() / sizeof(Js::Var);
this->m_writer.BrEnvProperty(Js::OpCode::BrOnHasLocalEnvProperty, rec.label, propertyIndex, frameDisplayIndex);
rec.kind = DynamicLoadKind::Env;
rec.index = frameDisplayIndex;
}
}
else
{
if (envIndex == -1)
{
this->m_writer.BrProperty(Js::OpCode::BrOnHasProperty, rec.label, scopeLocation, propertyIndex);
rec.kind = DynamicLoadKind::LocalWith;
rec.instance = scopeLocation;
}
else
{
uint32 frameDisplayIndex = envIndex + Js::FrameDisplay::GetOffsetOfScopes() / sizeof(Js::Var);
this->m_writer.BrEnvProperty(Js::OpCode::BrOnHasEnvProperty, rec.label, propertyIndex, frameDisplayIndex);
rec.kind = DynamicLoadKind::EnvWith;
rec.index = frameDisplayIndex;
}
}
recList.Add(rec);
}
// Arrived at the scope in which the property was defined.
if (sym == nullptr || sym->GetIsGlobal())
{
Js::PropertyId propertyId = sym ? sym->EnsurePosition(this) : pid->GetPropertyId();
if (this->flags & (fscrEval | fscrImplicitThis))
{
this->m_writer.ScopedProperty(Js::OpCode::ScopedDeleteFld, lhsLocation,
funcInfo->FindOrAddReferencedPropertyId(propertyId), forceStrictModeForClassComputedPropertyName);
}
else
{
this->m_writer.Property(Js::OpCode::DeleteRootFld, lhsLocation, ByteCodeGenerator::RootObjectRegister,
funcInfo->FindOrAddReferencedPropertyId(propertyId));
}
}
else
{
// The delete will look like a non-local reference, so make sure a slot is reserved.
sym->EnsureScopeSlot(this, funcInfo);
this->m_writer.Reg1(Js::OpCode::LdFalse, lhsLocation);
}
if (!recList.Empty())
{
Js::ByteCodeLabel doneLabel = this->m_writer.DefineLabel();
this->m_writer.Br(doneLabel);
for (int i = 0;; i++)
{
Js::RegSlot instLocation;
Js::PropertyId propertyId = sym ? sym->EnsurePosition(this) : pid->GetPropertyId();
Js::PropertyIdIndexType propertyIndex = funcInfo->FindOrAddReferencedPropertyId(propertyId);
this->m_writer.MarkLabel(recList.Item(i).label);
switch(recList.Item(i).kind)
{
case DynamicLoadKind::Local:
this->m_writer.ElementU(Js::OpCode::DeleteLocalFld_ReuseLoc, lhsLocation, propertyIndex);
break;
case DynamicLoadKind::Env:
instLocation = funcInfo->AcquireTmpRegister();
this->m_writer.SlotI1(Js::OpCode::LdEnvObj, instLocation, recList.Item(i).index);
this->m_writer.Property(Js::OpCode::DeleteFld_ReuseLoc, lhsLocation, instLocation, propertyIndex);
funcInfo->ReleaseTmpRegister(instLocation);
break;
case DynamicLoadKind::LocalWith:
instLocation = funcInfo->AcquireTmpRegister();
this->m_writer.Reg2(Js::OpCode::UnwrapWithObj, instLocation, recList.Item(i).instance);
this->m_writer.Property(Js::OpCode::DeleteFld_ReuseLoc, lhsLocation, instLocation, propertyIndex);
funcInfo->ReleaseTmpRegister(instLocation);
break;
case DynamicLoadKind::EnvWith:
instLocation = funcInfo->AcquireTmpRegister();
this->m_writer.SlotI1(Js::OpCode::LdEnvObj, instLocation, recList.Item(i).index);
this->m_writer.Reg2(Js::OpCode::UnwrapWithObj, instLocation, instLocation);
this->m_writer.Property(Js::OpCode::DeleteFld_ReuseLoc, lhsLocation, instLocation, propertyIndex);
funcInfo->ReleaseTmpRegister(instLocation);
break;
default:
AssertOrFailFast(UNREACHED);
}
if (i == recList.Count() - 1)
{
break;
}
this->m_writer.Br(doneLabel);
}
this->m_writer.MarkLabel(doneLabel);
}
}
void ByteCodeGenerator::EmitTypeOfFld(FuncInfo * funcInfo, Js::PropertyId propertyId, Js::RegSlot value, Js::RegSlot instance, Js::OpCode ldFldOp, bool reuseLoc)
{
uint cacheId;
Js::RegSlot tmpReg = funcInfo->AcquireTmpRegister();
switch (ldFldOp)
{
case Js::OpCode::LdRootFldForTypeOf:
cacheId = funcInfo->FindOrAddRootObjectInlineCacheId(propertyId, false, false);
this->Writer()->PatchableRootProperty(ldFldOp, tmpReg, cacheId, false, false);
break;
case Js::OpCode::LdLocalFld:
case Js::OpCode::ScopedLdFldForTypeOf:
cacheId = funcInfo->FindOrAddInlineCacheId(instance, propertyId, false, false);
this->Writer()->ElementP(ldFldOp, tmpReg, cacheId);
break;
default:
cacheId = funcInfo->FindOrAddInlineCacheId(instance, propertyId, false, false);
this->Writer()->PatchableProperty(ldFldOp, tmpReg, instance, cacheId);
break;
}
this->Writer()->Reg2(reuseLoc ? Js::OpCode::Typeof_ReuseLoc : Js::OpCode::Typeof, value, tmpReg);
funcInfo->ReleaseTmpRegister(tmpReg);
}
void ByteCodeGenerator::EmitPropTypeof(Js::RegSlot lhsLocation, Symbol *sym, IdentPtr pid, FuncInfo *funcInfo)
{
// If sym belongs to a parent frame, delete it from the closure environment.
// If it belongs to this func, but there's a non-local reference, get it from the heap-allocated frame.
// (TODO: optimize this by getting the sym from its normal location if there are no non-local defs.)
// Otherwise, just return false
Js::RegSlot scopeLocation = Js::Constants::NoRegister;
Js::PropertyId envIndex = -1;
Scope *scope = nullptr;
Scope *symScope = sym ? sym->GetScope() : this->globalScope;
Assert(symScope);
if (sym && sym->GetIsModuleExportStorage())
{
Js::RegSlot tmpLocation = funcInfo->AcquireTmpRegister();
EmitModuleExportAccess(sym, Js::OpCode::LdModuleSlot, tmpLocation, funcInfo);
this->m_writer.Reg2(Js::OpCode::Typeof, lhsLocation, tmpLocation);
funcInfo->ReleaseTmpRegister(tmpLocation);
return;
}
JsUtil::List<DynamicLoadRecord, ArenaAllocator> recList(this->alloc);
for (;;)
{
scope = this->FindScopeForSym(symScope, scope, &envIndex, funcInfo);
if (scope == this->globalScope)
{
scopeLocation = ByteCodeGenerator::RootObjectRegister;
}
else if (envIndex == -1)
{
Assert(funcInfo == scope->GetFunc());
scopeLocation = scope->GetLocation();
}
if (scope == symScope)
{
break;
}
// Found a scope to which the property may have been added.
Assert(scope && scope->GetIsDynamic());
AssertOrFailFast(scope->GetIsObject());
DynamicLoadRecord rec;
rec.label = this->m_writer.DefineLabel();
Js::PropertyId propertyId = sym ? sym->EnsurePosition(this) : pid->GetPropertyId();
Js::PropertyIdIndexType propertyIndex = funcInfo->FindOrAddReferencedPropertyId(propertyId);
if (scope->GetScopeType() != ScopeType_With)
{
if (envIndex == -1)
{
this->m_writer.BrLocalProperty(Js::OpCode::BrOnHasLocalProperty, rec.label, propertyIndex);
rec.kind = DynamicLoadKind::Local;
rec.instance = scopeLocation;
}
else
{
uint32 frameDisplayIndex = envIndex + Js::FrameDisplay::GetOffsetOfScopes() / sizeof(Js::Var);
this->m_writer.BrEnvProperty(Js::OpCode::BrOnHasLocalEnvProperty, rec.label, propertyIndex, frameDisplayIndex);
rec.kind = DynamicLoadKind::Env;
rec.index = frameDisplayIndex;
}
}
else
{
if (envIndex == -1)
{
this->m_writer.BrProperty(Js::OpCode::BrOnHasProperty, rec.label, scopeLocation, propertyIndex);
rec.kind = DynamicLoadKind::LocalWith;
rec.instance = scopeLocation;
}
else
{
uint32 frameDisplayIndex = envIndex + Js::FrameDisplay::GetOffsetOfScopes() / sizeof(Js::Var);
this->m_writer.BrEnvProperty(Js::OpCode::BrOnHasEnvProperty, rec.label, propertyIndex, frameDisplayIndex);
rec.kind = DynamicLoadKind::EnvWith;
rec.index = frameDisplayIndex;
}
}
recList.Add(rec);
}
// Arrived at the scope in which the property was defined.
if (sym && sym->GetNeedDeclaration() && scope->GetFunc() == funcInfo)
{
// Ensure this symbol has a slot if it needs one.
if (sym->IsInSlot(this, funcInfo))
{
Js::PropertyId slot = sym->EnsureScopeSlot(this, funcInfo);
funcInfo->FindOrAddSlotProfileId(scope, slot);
}
EmitUseBeforeDeclarationRuntimeError(this, lhsLocation);
}
else if (sym == nullptr || sym->GetIsGlobal())
{
Js::PropertyId propertyId = sym ? sym->EnsurePosition(this) : pid->GetPropertyId();
if (this->flags & fscrEval)
{
if (funcInfo->byteCodeFunction->GetIsStrictMode() && funcInfo->IsGlobalFunction())
{
this->EmitTypeOfFld(funcInfo, propertyId, lhsLocation, funcInfo->frameDisplayRegister, Js::OpCode::ScopedLdFldForTypeOf);
}
else
{
this->EmitTypeOfFld(funcInfo, propertyId, lhsLocation, funcInfo->GetEnvRegister(), Js::OpCode::ScopedLdFldForTypeOf);
}
}
else if (this->flags & fscrImplicitThis)
{
this->EmitTypeOfFld(funcInfo, propertyId, lhsLocation, funcInfo->GetEnvRegister(), Js::OpCode::ScopedLdFldForTypeOf);
}
else
{
this->EmitTypeOfFld(funcInfo, propertyId, lhsLocation, ByteCodeGenerator::RootObjectRegister, Js::OpCode::LdRootFldForTypeOf);
}
}
else if (sym->IsInSlot(this, funcInfo) || envIndex != -1)
{
// Make sure the property has a slot. This will bump up the size of the slot array if necessary.
Js::PropertyId slot = sym->EnsureScopeSlot(this, funcInfo);
Js::ProfileId profileId = funcInfo->FindOrAddSlotProfileId(scope, slot);
Js::RegSlot tmpLocation = funcInfo->AcquireTmpRegister();
bool chkBlockVar = NeedCheckBlockVar(sym, scope, funcInfo);
Js::OpCode op;
op = this->GetLdSlotOp(scope, envIndex, scopeLocation, funcInfo);
slot = slot + (sym->GetScope()->GetIsObject() ? 0 : Js::ScopeSlots::FirstSlotIndex);
if (envIndex != -1)
{
this->m_writer.SlotI2(op, tmpLocation, envIndex + Js::FrameDisplay::GetOffsetOfScopes() / sizeof(Js::Var), slot, profileId);
}
else if (scopeLocation != Js::Constants::NoRegister &&
(scopeLocation == funcInfo->frameSlotsRegister || scopeLocation == funcInfo->frameObjRegister))
{
this->m_writer.SlotI1(op, tmpLocation, slot, profileId);
}
else if (scope->HasInnerScopeIndex())
{
this->m_writer.SlotI2(op, tmpLocation, scope->GetInnerScopeIndex(), slot, profileId);
}
else
{
AssertOrFailFast(scope->GetIsObject());
this->m_writer.Slot(op, tmpLocation, scopeLocation, slot, profileId);
}
if (chkBlockVar)
{
this->m_writer.Reg1(Js::OpCode::ChkUndecl, tmpLocation);
}
this->m_writer.Reg2(Js::OpCode::Typeof, lhsLocation, tmpLocation);
funcInfo->ReleaseTmpRegister(tmpLocation);
}
else
{
this->m_writer.Reg2(Js::OpCode::Typeof, lhsLocation, sym->GetLocation());
}
if (!recList.Empty())
{
Js::ByteCodeLabel doneLabel = this->m_writer.DefineLabel();
this->m_writer.Br(doneLabel);
for (int i = 0;; i++)
{
Js::RegSlot instLocation;
Js::PropertyId propertyId = sym ? sym->EnsurePosition(this) : pid->GetPropertyId();
this->m_writer.MarkLabel(recList.Item(i).label);
switch(recList.Item(i).kind)
{
case DynamicLoadKind::Local:
this->EmitTypeOfFld(funcInfo, propertyId, lhsLocation, recList.Item(i).instance, Js::OpCode::LdLocalFld, true);
break;
case DynamicLoadKind::Env:
instLocation = funcInfo->AcquireTmpRegister();
this->m_writer.SlotI1(Js::OpCode::LdEnvObj, instLocation, recList.Item(i).index);
this->EmitTypeOfFld(funcInfo, propertyId, lhsLocation, instLocation, Js::OpCode::LdFldForTypeOf, true);
funcInfo->ReleaseTmpRegister(instLocation);
break;
case DynamicLoadKind::LocalWith:
instLocation = funcInfo->AcquireTmpRegister();
this->m_writer.Reg2(Js::OpCode::UnwrapWithObj, instLocation, recList.Item(i).instance);
this->EmitTypeOfFld(funcInfo, propertyId, lhsLocation, instLocation, Js::OpCode::LdFldForTypeOf, true);
funcInfo->ReleaseTmpRegister(instLocation);
break;
case DynamicLoadKind::EnvWith:
instLocation = funcInfo->AcquireTmpRegister();
this->m_writer.SlotI1(Js::OpCode::LdEnvObj, instLocation, recList.Item(i).index);
this->m_writer.Reg2(Js::OpCode::UnwrapWithObj, instLocation, instLocation);
this->EmitTypeOfFld(funcInfo, propertyId, lhsLocation, instLocation, Js::OpCode::LdFldForTypeOf, true);
funcInfo->ReleaseTmpRegister(instLocation);
break;
default:
AssertOrFailFast(UNREACHED);
}
if (i == recList.Count() - 1)
{
break;
}
this->m_writer.Br(doneLabel);
}
this->m_writer.MarkLabel(doneLabel);
}
}
void ByteCodeGenerator::EnsureNoRedeclarations(ParseNodeBlock *pnodeBlock, FuncInfo *funcInfo)
{
// Emit dynamic runtime checks for variable re-declarations. Only necessary for global functions (script or eval).
// In eval only var declarations can cause redeclaration, and only in non-strict mode, because let/const variables
// remain local to the eval code.
Assert(pnodeBlock->nop == knopBlock);
Assert(pnodeBlock->blockType == PnodeBlockType::Global || pnodeBlock->scope->GetScopeType() == ScopeType_GlobalEvalBlock);
if (!(this->flags & fscrEvalCode))
{
IterateBlockScopedVariables(pnodeBlock, [this](ParseNode *pnode)
{
FuncInfo *funcInfo = this->TopFuncInfo();
Symbol *sym = pnode->AsParseNodeVar()->sym;
Assert(sym->GetIsGlobal());
Js::PropertyId propertyId = sym->EnsurePosition(this);
this->m_writer.ElementRootU(Js::OpCode::EnsureNoRootFld, funcInfo->FindOrAddReferencedPropertyId(propertyId));
});
}
auto emitRedeclCheck = [this](Symbol * sym, FuncInfo * funcInfo, bool isFncDecl = false)
{
Js::PropertyId propertyId = sym->EnsurePosition(this);
// Global function declarations must pass #sec-candeclareglobalfunction
// which is enforced by EnsureCanDeclGloFunc
if (isFncDecl)
{
this->m_writer.ElementRootU(Js::OpCode::EnsureCanDeclGloFunc, funcInfo->FindOrAddReferencedPropertyId(propertyId));
}
if (this->flags & fscrEval)
{
if (!funcInfo->byteCodeFunction->GetIsStrictMode())
{
this->m_writer.ScopedProperty(Js::OpCode::ScopedEnsureNoRedeclFld, ByteCodeGenerator::RootObjectRegister,
funcInfo->FindOrAddReferencedPropertyId(propertyId));
}
}
else
{
this->m_writer.ElementRootU(Js::OpCode::EnsureNoRootRedeclFld, funcInfo->FindOrAddReferencedPropertyId(propertyId));
}
};
// scan for function declarations
// these behave like "var" declarations
for (ParseNodePtr pnode = pnodeBlock->pnodeScopes; pnode;)
{
switch (pnode->nop) {
case knopFncDecl:
if (pnode->AsParseNodeFnc()->IsDeclaration())
{
emitRedeclCheck(pnode->AsParseNodeFnc()->pnodeName->sym, funcInfo, true);
}
pnode = pnode->AsParseNodeFnc()->pnodeNext;
break;
case knopBlock:
pnode = pnode->AsParseNodeBlock()->pnodeNext;
break;
case knopCatch:
pnode = pnode->AsParseNodeCatch()->pnodeNext;
break;
case knopWith:
pnode = pnode->AsParseNodeWith()->pnodeNext;
break;
default:
Assert(UNREACHED);
}
}
// scan for var declarations
for (ParseNode *pnode = funcInfo->root->pnodeVars; pnode; pnode = pnode->AsParseNodeVar()->pnodeNext)
{
Symbol* sym = pnode->AsParseNodeVar()->sym;
if (sym == nullptr || pnode->AsParseNodeVar()->isBlockScopeFncDeclVar || sym->IsSpecialSymbol())
continue;
if (sym->GetIsCatch() || (pnode->nop == knopVarDecl && sym->GetIsBlockVar()))
{
// The init node was bound to the catch object, because it's inside a catch and has the
// same name as the catch object. But we want to define a user var at function scope,
// so find the right symbol. (We'll still assign the RHS value to the catch object symbol.)
// This also applies to a var declaration in the same scope as a let declaration.
// Assert that catch cannot be at function scope and let and var at function scope is redeclaration error.
Assert(sym->GetIsCatch() || funcInfo->bodyScope != sym->GetScope());
sym = funcInfo->bodyScope->FindLocalSymbol(sym->GetName());
Assert(sym && !sym->GetIsCatch() && !sym->GetIsBlockVar());
}
Assert(sym->GetIsGlobal());
if (sym->GetSymbolType() == STVariable)
{
emitRedeclCheck(sym, funcInfo);
}
}
}
void ByteCodeGenerator::RecordAllIntConstants(FuncInfo * funcInfo)
{
Js::FunctionBody *byteCodeFunction = this->TopFuncInfo()->GetParsedFunctionBody();
funcInfo->constantToRegister.Map([byteCodeFunction](unsigned int val, Js::RegSlot location)
{
byteCodeFunction->RecordIntConstant(byteCodeFunction->MapRegSlot(location), val);
});
}
void ByteCodeGenerator::RecordAllStrConstants(FuncInfo * funcInfo)
{
Js::FunctionBody *byteCodeFunction = this->TopFuncInfo()->GetParsedFunctionBody();
funcInfo->stringToRegister.Map([byteCodeFunction](IdentPtr pid, Js::RegSlot location)
{
byteCodeFunction->RecordStrConstant(byteCodeFunction->MapRegSlot(location), pid->Psz(), pid->Cch(), pid->IsUsedInLdElem());
});
}
void ByteCodeGenerator::RecordAllBigIntConstants(FuncInfo * funcInfo)
{
Js::FunctionBody *byteCodeFunction = this->TopFuncInfo()->GetParsedFunctionBody();
funcInfo->bigintToRegister.Map([byteCodeFunction](ParseNode* pnode, Js::RegSlot location)
{
IdentPtr pid = pnode->AsParseNodeBigInt()->pid;
byteCodeFunction->RecordBigIntConstant(byteCodeFunction->MapRegSlot(location), pid->Psz(), pid->Cch(), pnode->AsParseNodeBigInt()->isNegative);
});
}
void ByteCodeGenerator::RecordAllStringTemplateCallsiteConstants(FuncInfo* funcInfo)
{
Js::FunctionBody *byteCodeFunction = this->TopFuncInfo()->GetParsedFunctionBody();
funcInfo->stringTemplateCallsiteRegisterMap.Map([byteCodeFunction](ParseNodePtr pnode, Js::RegSlot location)
{
Js::ScriptContext* scriptContext = byteCodeFunction->GetScriptContext();
Js::RecyclableObject* rawArray = ByteCodeGenerator::BuildArrayFromStringList(pnode->AsParseNodeStrTemplate()->pnodeStringRawLiterals, pnode->AsParseNodeStrTemplate()->countStringLiterals, scriptContext);
rawArray->Freeze();
Js::RecyclableObject* callsiteObject = ByteCodeGenerator::BuildArrayFromStringList(pnode->AsParseNodeStrTemplate()->pnodeStringLiterals, pnode->AsParseNodeStrTemplate()->countStringLiterals, scriptContext);
callsiteObject->SetPropertyWithAttributes(Js::PropertyIds::raw, rawArray, PropertyNone, nullptr);
callsiteObject->Freeze();
byteCodeFunction->RecordConstant(byteCodeFunction->MapRegSlot(location), callsiteObject);
});
}
bool IsApplyArgs(ParseNodeCall* callNode)
{
ParseNode* target = callNode->pnodeTarget;
ParseNode* args = callNode->pnodeArgs;
if ((target != nullptr) && (target->nop == knopDot))
{
ParseNode* lhsNode = target->AsParseNodeBin()->pnode1;
if ((lhsNode != nullptr) && ((lhsNode->nop == knopDot) || (lhsNode->nop == knopName)) && !IsArguments(lhsNode))
{
ParseNode* nameNode = target->AsParseNodeBin()->pnode2;
if (nameNode != nullptr)
{
bool nameIsApply = nameNode->AsParseNodeName()->PropertyIdFromNameNode() == Js::PropertyIds::apply;
if (nameIsApply && args != nullptr && args->nop == knopList)
{
ParseNode* arg1 = args->AsParseNodeBin()->pnode1;
ParseNode* arg2 = args->AsParseNodeBin()->pnode2;
if ((arg1 != nullptr) && ByteCodeGenerator::IsThis(arg1) && (arg2 != nullptr) && (arg2->nop == knopName) && (arg2->AsParseNodeName()->sym != nullptr))
{
return arg2->AsParseNodeName()->sym->IsArguments();
}
}
}
}
}
return false;
}
void PostCheckApplyEnclosesArgs(ParseNode* pnode, ByteCodeGenerator* byteCodeGenerator, ApplyCheck* applyCheck)
{
if ((pnode == nullptr) || (!applyCheck->matches))
{
return;
}
if (pnode->nop == knopCall)
{
if ((!pnode->isUsed) && IsApplyArgs(pnode->AsParseNodeCall()))
{
if (!applyCheck->insideApplyCall)
{
applyCheck->matches = false;
}
applyCheck->insideApplyCall = false;
}
}
}
void CheckApplyEnclosesArgs(ParseNode* pnode, ByteCodeGenerator* byteCodeGenerator, ApplyCheck* applyCheck)
{
if ((pnode == nullptr) || (!applyCheck->matches))
{
return;
}
switch (pnode->nop)
{
case knopName:
{
Symbol* sym = pnode->AsParseNodeName()->sym;
if (sym != nullptr)
{
if (sym->IsArguments())
{
if (!applyCheck->insideApplyCall)
{
applyCheck->matches = false;
}
}
}
break;
}
case knopCall:
if ((!pnode->isUsed) && IsApplyArgs(pnode->AsParseNodeCall()))
{
// no nested apply calls
if (applyCheck->insideApplyCall)
{
applyCheck->matches = false;
}
else
{
applyCheck->insideApplyCall = true;
applyCheck->sawApply = true;
pnode->AsParseNodeCall()->isApplyCall = true;
}
}
break;
}
}
unsigned int CountArguments(ParseNode *pnode, BOOL *pSideEffect = nullptr)
{
// If the caller passed us a pSideEffect, it wants to know whether there are potential
// side-effects in the argument list. We need to know this so that the call target
// operands can be preserved if necessary.
// For now, treat any non-leaf op as a potential side-effect. This causes no detectable slowdowns,
// but we can be more precise if we need to be.
if (pSideEffect)
{
*pSideEffect = FALSE;
}
unsigned int argCount = 1;
if (pnode != nullptr)
{
while (pnode->nop == knopList)
{
argCount++;
if (pSideEffect && !(ParseNode::Grfnop(pnode->AsParseNodeBin()->pnode1->nop) & fnopLeaf))
{
*pSideEffect = TRUE;
}
pnode = pnode->AsParseNodeBin()->pnode2;
}
argCount++;
if (pSideEffect && !(ParseNode::Grfnop(pnode->nop) & fnopLeaf))
{
*pSideEffect = TRUE;
}
}
AssertOrFailFastMsg(argCount < Js::Constants::UShortMaxValue, "Number of allowed arguments are already capped at parser level");
return argCount;
}
void SaveOpndValue(ParseNode *pnode, FuncInfo *funcInfo)
{
// Save a local name to a register other than its home location.
// This guards against side-effects in cases like x.foo(x = bar()).
Symbol *sym = nullptr;
if (pnode->nop == knopName)
{
sym = pnode->AsParseNodeName()->sym;
}
else if (pnode->nop == knopComputedName)
{
ParseNode *pnode1 = pnode->AsParseNodeUni()->pnode1;
if (pnode1->nop == knopName)
{
sym = pnode1->AsParseNodeName()->sym;
}
}
if (sym == nullptr)
{
return;
}
// If the target is a local being kept in its home location,
// protect the target's value in the event the home location is overwritten.
if (pnode->location != Js::Constants::NoRegister &&
sym->GetScope()->GetFunc() == funcInfo &&
pnode->location == sym->GetLocation())
{
pnode->location = funcInfo->AcquireTmpRegister();
}
}
void ByteCodeGenerator::StartStatement(ParseNode* node)
{
Assert(TopFuncInfo() != nullptr);
m_writer.StartStatement(node, TopFuncInfo()->curTmpReg - TopFuncInfo()->firstTmpReg);
}
void ByteCodeGenerator::EndStatement(ParseNode* node)
{
m_writer.EndStatement(node);
}
void ByteCodeGenerator::StartSubexpression(ParseNode* node)
{
Assert(TopFuncInfo() != nullptr);
m_writer.StartSubexpression(node);
}
void ByteCodeGenerator::EndSubexpression(ParseNode* node)
{
m_writer.EndSubexpression(node);
}
void EmitReference(ParseNode *pnode, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo)
{
// Generate code for the LHS of an assignment.
switch (pnode->nop)
{
case knopDot:
Emit(pnode->AsParseNodeBin()->pnode1, byteCodeGenerator, funcInfo, false);
break;
case knopIndex:
Emit(pnode->AsParseNodeBin()->pnode1, byteCodeGenerator, funcInfo, false);
Emit(pnode->AsParseNodeBin()->pnode2, byteCodeGenerator, funcInfo, false);
break;
case knopName:
break;
case knopArrayPattern:
case knopObjectPattern:
break;
case knopCall:
case knopNew:
// Emit the operands of a call that will be used as a LHS.
// These have to be emitted before the RHS, but they have to persist until
// the end of the expression.
// Emit the call target operands first.
// The call target does not need to be emitted for a super call - EmitSuperCall will do this.
if (!pnode->AsParseNodeCall()->isSuperCall)
{
switch (pnode->AsParseNodeCall()->pnodeTarget->nop)
{
case knopDot:
case knopIndex:
funcInfo->AcquireLoc(pnode->AsParseNodeCall()->pnodeTarget);
EmitReference(pnode->AsParseNodeCall()->pnodeTarget, byteCodeGenerator, funcInfo);
break;
case knopName:
{
Symbol* sym = pnode->AsParseNodeCall()->pnodeTarget->AsParseNodeName()->sym;
if (!sym || sym->GetLocation() == Js::Constants::NoRegister)
{
funcInfo->AcquireLoc(pnode->AsParseNodeCall()->pnodeTarget);
}
if (sym && (sym->IsInSlot(byteCodeGenerator, funcInfo) || sym->GetScope()->GetFunc() != funcInfo))
{
// Can't get the value from the assigned register, so load it here.
EmitLoad(pnode->AsParseNodeCall()->pnodeTarget, byteCodeGenerator, funcInfo);
}
else
{
// EmitLoad will check for needsDeclaration and emit the Use Before Declaration error
// bytecode op as necessary, but EmitReference does not check this (by design). So we
// must manually check here.
EmitUseBeforeDeclaration(pnode->AsParseNodeCall()->pnodeTarget->AsParseNodeName()->sym, byteCodeGenerator, funcInfo);
EmitReference(pnode->AsParseNodeCall()->pnodeTarget, byteCodeGenerator, funcInfo);
}
break;
}
default:
EmitLoad(pnode->AsParseNodeCall()->pnodeTarget, byteCodeGenerator, funcInfo);
break;
}
}
// Now the arg list. We evaluate everything now and emit the ArgOut's later.
if (pnode->AsParseNodeCall()->pnodeArgs)
{
ParseNode *pnodeArg = pnode->AsParseNodeCall()->pnodeArgs;
while (pnodeArg->nop == knopList)
{
Emit(pnodeArg->AsParseNodeBin()->pnode1, byteCodeGenerator, funcInfo, false);
pnodeArg = pnodeArg->AsParseNodeBin()->pnode2;
}
Emit(pnodeArg, byteCodeGenerator, funcInfo, false);
}
break;
default:
Emit(pnode, byteCodeGenerator, funcInfo, false);
break;
}
}
void EmitGetIterator(Js::RegSlot iteratorReg, Js::RegSlot iterableReg, ByteCodeGenerator* byteCodeGenerator, FuncInfo* funcInfo);
void EmitIteratorClose(Js::RegSlot iteratorLocation, ByteCodeGenerator* byteCodeGenerator, FuncInfo* funcInfo, bool isAsync = false);
void EmitFunctionCall(
Js::RegSlot resultReg,
Js::RegSlot funcReg,
Js::RegSlot thisReg,
Js::RegSlot argReg,
ByteCodeGenerator* byteCodeGenerator,
FuncInfo* funcInfo)
{
Js::ArgSlot argCount = argReg == Js::Constants::NoRegister ? 1 : 2;
funcInfo->StartRecordingOutArgs(argCount);
Js::ProfileId callSite = byteCodeGenerator->GetNextCallSiteId(Js::OpCode::CallI);
byteCodeGenerator->Writer()->StartCall(Js::OpCode::StartCall, argCount);
byteCodeGenerator->Writer()->ArgOut<true>(0, thisReg, callSite, false);
if (argReg != Js::Constants::NoRegister)
byteCodeGenerator->Writer()->ArgOut<true>(1, argReg, callSite, false);
byteCodeGenerator->Writer()->CallI(Js::OpCode::CallI, resultReg, funcReg, argCount, callSite);
}
void EmitFunctionCall(
Js::RegSlot resultReg,
Js::RegSlot funcReg,
Js::RegSlot thisReg,
ByteCodeGenerator* byteCodeGenerator,
FuncInfo* funcInfo)
{
Js::RegSlot argReg = Js::Constants::NoRegister;
EmitFunctionCall(resultReg, funcReg, thisReg, argReg, byteCodeGenerator, funcInfo);
}
void EmitThrowOnNotObject(Js::RegSlot varReg, ByteCodeGenerator* byteCodeGenerator)
{
Js::ByteCodeLabel skipThrow = byteCodeGenerator->Writer()->DefineLabel();
byteCodeGenerator->Writer()->BrReg1(Js::OpCode::BrOnObject_A, skipThrow, varReg);
byteCodeGenerator->Writer()->W1(Js::OpCode::RuntimeTypeError, SCODE_CODE(JSERR_NeedObject));
byteCodeGenerator->Writer()->MarkLabel(skipThrow);
}
void EmitGetOptionalObjectMethod(
Js::RegSlot methodReg,
Js::RegSlot objectReg,
Js::PropertyId propertyId,
Js::ByteCodeLabel noMethodLabel,
ByteCodeGenerator* byteCodeGenerator,
FuncInfo* funcInfo)
{
uint cacheId = funcInfo->FindOrAddInlineCacheId(
objectReg,
propertyId,
/* isLoadMethod */ true,
/* isStore */ false);
byteCodeGenerator->Writer()->PatchableProperty(
Js::OpCode::LdFld,
methodReg,
objectReg,
cacheId,
/* isCtor */ false,
/* registerCacheIdForCall */ true);
byteCodeGenerator->Writer()->BrReg2(
Js::OpCode::BrEq_A,
noMethodLabel,
methodReg,
funcInfo->undefinedConstantRegister);
}
void EmitGetObjectMethod(
Js::RegSlot methodReg,
Js::RegSlot objectReg,
Js::PropertyId propertyId,
ByteCodeGenerator* byteCodeGenerator,
FuncInfo* funcInfo)
{
uint cacheId = funcInfo->FindOrAddInlineCacheId(
objectReg,
propertyId,
/* isLoadMethod */ true,
/* isStore */ false);
byteCodeGenerator->Writer()->PatchableProperty(
Js::OpCode::LdMethodFld,
methodReg,
objectReg,
cacheId,
/* isCtor */ false,
/* registerCacheIdForCall */ true);
}
void EmitGetObjectProperty(
Js::RegSlot resultReg,
Js::RegSlot objectReg,
Js::PropertyId propertyId,
ByteCodeGenerator* byteCodeGenerator,
FuncInfo* funcInfo)
{
uint cacheId = funcInfo->FindOrAddInlineCacheId(
objectReg,
propertyId,
/* isLoadMethod */ false,
/* isStore */ false);
byteCodeGenerator->Writer()->PatchableProperty(
Js::OpCode::LdFld,
resultReg,
objectReg,
cacheId,
/* isCtor */ false,
/* registerCacheIdForCall */ false);
}
void EmitDestructuredElement(ParseNode *elem, Js::RegSlot sourceLocation, ByteCodeGenerator* byteCodeGenerator, FuncInfo *funcInfo)
{
switch (elem->nop)
{
case knopVarDecl:
case knopLetDecl:
case knopConstDecl:
// We manually need to set NeedDeclaration since the node won't be visited.
elem->AsParseNodeVar()->sym->SetNeedDeclaration(false);
break;
default:
EmitReference(elem, byteCodeGenerator, funcInfo);
}
EmitAssignment(nullptr, elem, sourceLocation, byteCodeGenerator, funcInfo);
funcInfo->ReleaseReference(elem);
}
void EmitDestructuredRestArray(
ParseNode* elem,
Js::RegSlot iteratorLocation,
Js::RegSlot nextMethodReg,
Js::RegSlot shouldCallReturnFunctionLocation,
Js::RegSlot shouldCallReturnFunctionLocationFinally,
ByteCodeGenerator* byteCodeGenerator,
FuncInfo* funcInfo)
{
Js::RegSlot restArrayLocation = funcInfo->AcquireTmpRegister();
bool isAssignmentTarget = !(elem->AsParseNodeUni()->pnode1->IsPattern() || elem->AsParseNodeUni()->pnode1->IsVarLetOrConst());
if (isAssignmentTarget)
{
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdTrue_ReuseLoc, shouldCallReturnFunctionLocation);
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdTrue_ReuseLoc, shouldCallReturnFunctionLocationFinally);
EmitReference(elem->AsParseNodeUni()->pnode1, byteCodeGenerator, funcInfo);
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdFalse_ReuseLoc, shouldCallReturnFunctionLocation);
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdFalse_ReuseLoc, shouldCallReturnFunctionLocationFinally);
}
byteCodeGenerator->Writer()->Reg1Unsigned1(
Js::OpCode::NewScArray,
restArrayLocation,
ByteCodeGenerator::DefaultArraySize);
// BytecodeGen can't convey to IRBuilder that some of the temporaries used here are live. When we
// have a rest parameter, a counter is used in a loop for the array index, but there is no way to
// convey this is live on the back edge.
// As a workaround, we have a persistent var reg that is used for the loop counter
Js::RegSlot counterLocation = elem->location;
// TODO[ianhall]: Is calling EnregisterConstant() during Emit phase allowed?
Js::RegSlot zeroConstantReg = byteCodeGenerator->EnregisterConstant(0);
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Ld_A, counterLocation, zeroConstantReg);
// loopTop:
Js::ByteCodeLabel loopTop = byteCodeGenerator->Writer()->DefineLabel();
byteCodeGenerator->Writer()->MarkLabel(loopTop);
Js::RegSlot itemLocation = funcInfo->AcquireTmpRegister();
EmitFunctionCall(
itemLocation,
nextMethodReg,
iteratorLocation,
byteCodeGenerator,
funcInfo);
EmitThrowOnNotObject(itemLocation, byteCodeGenerator);
Js::RegSlot doneLocation = funcInfo->AcquireTmpRegister();
EmitGetObjectProperty(
doneLocation,
itemLocation,
Js::PropertyIds::done,
byteCodeGenerator,
funcInfo);
Js::ByteCodeLabel iteratorDone = byteCodeGenerator->Writer()->DefineLabel();
byteCodeGenerator->Writer()->BrReg1(Js::OpCode::BrTrue_A, iteratorDone, doneLocation);
Js::RegSlot valueLocation = funcInfo->AcquireTmpRegister();
EmitGetObjectProperty(
valueLocation,
itemLocation,
Js::PropertyIds::value,
byteCodeGenerator,
funcInfo);
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdTrue_ReuseLoc, shouldCallReturnFunctionLocation);
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdTrue_ReuseLoc, shouldCallReturnFunctionLocationFinally);
byteCodeGenerator->Writer()->Element(
ByteCodeGenerator::GetStElemIOpCode(funcInfo),
valueLocation, restArrayLocation, counterLocation);
funcInfo->ReleaseTmpRegister(valueLocation);
funcInfo->ReleaseTmpRegister(doneLocation);
funcInfo->ReleaseTmpRegister(itemLocation);
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Incr_A, counterLocation, counterLocation);
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdFalse_ReuseLoc, shouldCallReturnFunctionLocation);
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdFalse_ReuseLoc, shouldCallReturnFunctionLocationFinally);
byteCodeGenerator->Writer()->Br(loopTop);
// iteratorDone:
byteCodeGenerator->Writer()->MarkLabel(iteratorDone);
ParseNode *restElem = elem->AsParseNodeUni()->pnode1;
if (isAssignmentTarget)
{
EmitAssignment(nullptr, restElem, restArrayLocation, byteCodeGenerator, funcInfo);
funcInfo->ReleaseReference(restElem);
}
else
{
EmitDestructuredElement(restElem, restArrayLocation, byteCodeGenerator, funcInfo);
}
funcInfo->ReleaseTmpRegister(restArrayLocation);
}
void EmitDestructuredArray(
ParseNode *lhs,
Js::RegSlot rhsLocation,
ByteCodeGenerator *byteCodeGenerator,
FuncInfo *funcInfo);
void EmitIteratorCloseIfNotDone(Js::RegSlot iteratorLocation, Js::RegSlot doneLocation, ByteCodeGenerator* byteCodeGenerator, FuncInfo* funcInfo)
{
Js::ByteCodeLabel skipCloseLabel = byteCodeGenerator->Writer()->DefineLabel();
byteCodeGenerator->Writer()->BrReg1(Js::OpCode::BrTrue_A, skipCloseLabel, doneLocation);
EmitIteratorClose(iteratorLocation, byteCodeGenerator, funcInfo);
byteCodeGenerator->Writer()->MarkLabel(skipCloseLabel);
}
/*
EmitDestructuredArray(lhsArray, rhs):
iterator = rhs[@@iterator]
if lhsArray empty
return
for each element in lhsArray except rest
value = iterator.next()
if element is a nested destructured array
EmitDestructuredArray(element, value)
else
if value is undefined and there is an initializer
evaluate initializer
evaluate element reference
element = initializer
else
element = value
if lhsArray has a rest element
rest = []
while iterator is not done
value = iterator.next()
rest.append(value)
*/
void EmitDestructuredArrayCore(
ParseNode* list,
Js::RegSlot iteratorLocation,
Js::RegSlot nextMethodReg,
Js::RegSlot shouldCallReturnFunctionLocation,
Js::RegSlot shouldCallReturnFunctionLocationFinally,
ByteCodeGenerator* byteCodeGenerator,
FuncInfo* funcInfo)
{
Assert(list != nullptr);
ParseNode *elem = nullptr;
while (list != nullptr)
{
ParseNode *init = nullptr;
if (list->nop == knopList)
{
elem = list->AsParseNodeBin()->pnode1;
}
else
{
elem = list;
}
if (elem->nop == knopEllipsis)
{
break;
}
switch (elem->nop)
{
case knopAsg:
// An assignment node will always have an initializer
init = elem->AsParseNodeBin()->pnode2;
elem = elem->AsParseNodeBin()->pnode1;
break;
case knopVarDecl:
case knopLetDecl:
case knopConstDecl:
init = elem->AsParseNodeVar()->pnodeInit;
break;
default:
break;
}
byteCodeGenerator->StartStatement(elem);
bool isAssignmentTarget = !(elem->IsPattern() || elem->IsVarLetOrConst());
if (isAssignmentTarget)
{
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdTrue_ReuseLoc, shouldCallReturnFunctionLocation);
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdTrue_ReuseLoc, shouldCallReturnFunctionLocationFinally);
EmitReference(elem, byteCodeGenerator, funcInfo);
}
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdFalse_ReuseLoc, shouldCallReturnFunctionLocation);
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdFalse_ReuseLoc, shouldCallReturnFunctionLocationFinally);
Js::RegSlot itemLocation = funcInfo->AcquireTmpRegister();
EmitFunctionCall(
itemLocation,
nextMethodReg,
iteratorLocation,
byteCodeGenerator,
funcInfo);
EmitThrowOnNotObject(itemLocation, byteCodeGenerator);
Js::RegSlot doneLocation = funcInfo->AcquireTmpRegister();
EmitGetObjectProperty(
doneLocation,
itemLocation,
Js::PropertyIds::done,
byteCodeGenerator,
funcInfo);
if (elem->nop == knopEmpty)
{
if (list->nop == knopList)
{
list = list->AsParseNodeBin()->pnode2;
funcInfo->ReleaseTmpRegister(doneLocation);
funcInfo->ReleaseTmpRegister(itemLocation);
continue;
}
else
{
Assert(list->nop == knopEmpty);
EmitIteratorCloseIfNotDone(iteratorLocation, doneLocation, byteCodeGenerator, funcInfo);
funcInfo->ReleaseTmpRegister(doneLocation);
funcInfo->ReleaseTmpRegister(itemLocation);
break;
}
}
// If the iterator hasn't completed, skip assigning undefined.
Js::ByteCodeLabel iteratorAlreadyDone = byteCodeGenerator->Writer()->DefineLabel();
byteCodeGenerator->Writer()->BrReg1(Js::OpCode::BrTrue_A, iteratorAlreadyDone, doneLocation);
// We're not done with the iterator, so assign the .next() value.
Js::RegSlot valueLocation = funcInfo->AcquireTmpRegister();
EmitGetObjectProperty(
valueLocation,
itemLocation,
Js::PropertyIds::value,
byteCodeGenerator,
funcInfo);
Js::ByteCodeLabel beforeDefaultAssign = byteCodeGenerator->Writer()->DefineLabel();
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdTrue_ReuseLoc, shouldCallReturnFunctionLocation);
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdTrue_ReuseLoc, shouldCallReturnFunctionLocationFinally);
byteCodeGenerator->Writer()->Br(beforeDefaultAssign);
// iteratorAlreadyDone:
byteCodeGenerator->Writer()->MarkLabel(iteratorAlreadyDone);
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Ld_A_ReuseLoc, valueLocation, funcInfo->undefinedConstantRegister);
// beforeDefaultAssign:
byteCodeGenerator->Writer()->MarkLabel(beforeDefaultAssign);
if (elem->IsPattern())
{
// If we get an undefined value and have an initializer, use it in place of undefined.
if (init != nullptr)
{
/*
the IR builder uses two symbols for a temp register in the if else path
R9 <- R3
if (...)
R9 <- R2
R10 = R9.<property> // error -> IR creates a new lifetime for the if path, and the direct path dest is not referenced
hence we have to create a new temp
TEMP REG USED TO FIX THIS PRODUCES THIS
R9 <- R3
if (BrEq_A R9, R3)
R10 <- R2 :
else
R10 <- R9 : skipdefault
... = R10[@@iterator] : loadIter
*/
// Temp Register
Js::RegSlot valueLocationTmp = funcInfo->AcquireTmpRegister();
byteCodeGenerator->StartStatement(init);
Js::ByteCodeLabel skipDefault = byteCodeGenerator->Writer()->DefineLabel();
Js::ByteCodeLabel loadIter = byteCodeGenerator->Writer()->DefineLabel();
// check value is undefined
byteCodeGenerator->Writer()->BrReg2(Js::OpCode::BrSrNeq_A, skipDefault, valueLocation, funcInfo->undefinedConstantRegister);
// Evaluate the default expression and assign it.
Emit(init, byteCodeGenerator, funcInfo, false);
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Ld_A, valueLocationTmp, init->location);
funcInfo->ReleaseLoc(init);
// jmp to loadIter
byteCodeGenerator->Writer()->Br(loadIter);
// skipDefault:
byteCodeGenerator->Writer()->MarkLabel(skipDefault);
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Ld_A_ReuseLoc, valueLocationTmp, valueLocation);
// loadIter:
// @@iterator
byteCodeGenerator->Writer()->MarkLabel(loadIter);
byteCodeGenerator->EndStatement(init);
if (elem->nop == knopObjectPattern)
{
EmitDestructuredObject(elem, valueLocationTmp, byteCodeGenerator, funcInfo);
}
else
{
// Recursively emit a destructured array using the current .next() as the RHS.
EmitDestructuredArray(elem, valueLocationTmp, byteCodeGenerator, funcInfo);
}
funcInfo->ReleaseTmpRegister(valueLocationTmp);
}
else
{
if (elem->nop == knopObjectPattern)
{
EmitDestructuredObject(elem, valueLocation, byteCodeGenerator, funcInfo);
}
else
{
// Recursively emit a destructured array using the current .next() as the RHS.
EmitDestructuredArray(elem, valueLocation, byteCodeGenerator, funcInfo);
}
}
}
else
{
EmitDestructuredValueOrInitializer(elem, valueLocation, init, isAssignmentTarget, byteCodeGenerator, funcInfo);
}
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdFalse_ReuseLoc, shouldCallReturnFunctionLocation);
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdFalse_ReuseLoc, shouldCallReturnFunctionLocationFinally);
if (list->nop != knopList)
{
EmitIteratorCloseIfNotDone(iteratorLocation, doneLocation, byteCodeGenerator, funcInfo);
}
funcInfo->ReleaseTmpRegister(valueLocation);
funcInfo->ReleaseTmpRegister(doneLocation);
funcInfo->ReleaseTmpRegister(itemLocation);
if (isAssignmentTarget)
{
funcInfo->ReleaseReference(elem);
}
byteCodeGenerator->EndStatement(elem);
if (list->nop == knopList)
{
list = list->AsParseNodeBin()->pnode2;
}
else
{
break;
}
}
// If we saw a rest element, emit the rest array.
if (elem != nullptr && elem->nop == knopEllipsis)
{
EmitDestructuredRestArray(
elem,
iteratorLocation,
nextMethodReg,
shouldCallReturnFunctionLocation,
shouldCallReturnFunctionLocationFinally,
byteCodeGenerator,
funcInfo);
}
}
struct ByteCodeGenerator::TryScopeRecord : public JsUtil::DoublyLinkedListElement<TryScopeRecord>
{
Js::OpCode op;
Js::ByteCodeLabel label;
Js::RegSlot reg1;
Js::RegSlot reg2;
TryScopeRecord(Js::OpCode op, Js::ByteCodeLabel label) : op(op), label(label), reg1(Js::Constants::NoRegister), reg2(Js::Constants::NoRegister) { }
TryScopeRecord(Js::OpCode op, Js::ByteCodeLabel label, Js::RegSlot r1, Js::RegSlot r2) : op(op), label(label), reg1(r1), reg2(r2) { }
};
// Generating
// try {
// CallIteratorClose
// } catch (e) {
// do nothing
// }
void EmitTryCatchAroundIteratorClose(
Js::RegSlot iteratorLocation,
Js::ByteCodeLabel endLabel,
ByteCodeGenerator *byteCodeGenerator,
FuncInfo *funcInfo,
bool isAsync)
{
Js::ByteCodeLabel catchLabel = byteCodeGenerator->Writer()->DefineLabel();
byteCodeGenerator->Writer()->Br(Js::OpCode::TryCatch, catchLabel);
// Jump cleanup is only needed in a for-await loop since it will await the
// result of the call to the return method. Otherwise there can be no jump.
if (isAsync)
byteCodeGenerator->PushJumpCleanupForTry(Js::OpCode::TryCatch, catchLabel);
EmitIteratorClose(iteratorLocation, byteCodeGenerator, funcInfo, isAsync);
if (isAsync)
byteCodeGenerator->PopJumpCleanup();
byteCodeGenerator->Writer()->Empty(Js::OpCode::Leave);
byteCodeGenerator->Writer()->Br(endLabel);
byteCodeGenerator->Writer()->MarkLabel(catchLabel);
Js::RegSlot catchParamLocation = funcInfo->AcquireTmpRegister();
byteCodeGenerator->Writer()->Reg1(Js::OpCode::Catch, catchParamLocation);
funcInfo->ReleaseTmpRegister(catchParamLocation);
byteCodeGenerator->Writer()->Empty(Js::OpCode::Leave);
}
// Generating
// catch(e) {
// if (shouldCallReturn)
// CallReturnWhichWrappedByTryCatch
// throw e;
// }
void EmitIteratorTopLevelCatch(
Js::ByteCodeLabel catchLabel,
Js::RegSlot iteratorLocation,
Js::RegSlot shouldCallReturnLocation,
Js::RegSlot shouldCallReturnLocationFinally,
ByteCodeGenerator* byteCodeGenerator,
FuncInfo* funcInfo,
bool isAsync)
{
Js::ByteCodeLabel afterCatchBlockLabel = byteCodeGenerator->Writer()->DefineLabel();
byteCodeGenerator->Writer()->Empty(Js::OpCode::Leave);
byteCodeGenerator->Writer()->Br(afterCatchBlockLabel);
byteCodeGenerator->Writer()->MarkLabel(catchLabel);
Js::RegSlot catchParamLocation = funcInfo->AcquireTmpRegister();
byteCodeGenerator->Writer()->Reg1(Js::OpCode::Catch, catchParamLocation);
byteCodeGenerator->PushJumpCleanupForTry(Js::OpCode::ResumeCatch);
Js::ByteCodeLabel skipCallCloseLabel = byteCodeGenerator->Writer()->DefineLabel();
byteCodeGenerator->Writer()->BrReg1(Js::OpCode::BrFalse_A, skipCallCloseLabel, shouldCallReturnLocation);
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdFalse_ReuseLoc, shouldCallReturnLocationFinally);
EmitTryCatchAroundIteratorClose(iteratorLocation, skipCallCloseLabel, byteCodeGenerator, funcInfo, isAsync);
byteCodeGenerator->Writer()->MarkLabel(skipCallCloseLabel);
// Rethrow the exception
byteCodeGenerator->Writer()->Reg1(Js::OpCode::Throw, catchParamLocation);
funcInfo->ReleaseTmpRegister(catchParamLocation);
byteCodeGenerator->PopJumpCleanup();
byteCodeGenerator->Writer()->Empty(Js::OpCode::Leave);
byteCodeGenerator->Writer()->MarkLabel(afterCatchBlockLabel);
}
// Generating
// finally {
// if (shouldCallReturn)
// CallReturn
// }
void EmitIteratorTopLevelFinally(
Js::ByteCodeLabel finallyLabel,
Js::RegSlot iteratorLocation,
Js::RegSlot shouldCallReturnLocation,
Js::RegSlot yieldExceptionLocation,
Js::RegSlot yieldOffsetLocation,
ByteCodeGenerator* byteCodeGenerator,
FuncInfo* funcInfo,
bool isAsync)
{
bool isCoroutine = funcInfo->byteCodeFunction->IsCoroutine();
Js::ByteCodeLabel afterFinallyBlockLabel = byteCodeGenerator->Writer()->DefineLabel();
byteCodeGenerator->Writer()->Empty(Js::OpCode::Leave);
byteCodeGenerator->Writer()->RecordCrossFrameEntryExitRecord(false);
byteCodeGenerator->Writer()->RecordCrossFrameEntryExitRecord(true);
byteCodeGenerator->Writer()->Br(afterFinallyBlockLabel);
byteCodeGenerator->Writer()->MarkLabel(finallyLabel);
byteCodeGenerator->Writer()->Empty(Js::OpCode::Finally);
byteCodeGenerator->PushJumpCleanupForTry(
Js::OpCode::ResumeFinally,
finallyLabel,
yieldExceptionLocation,
yieldOffsetLocation);
Js::ByteCodeLabel skipCallCloseLabel = byteCodeGenerator->Writer()->DefineLabel();
byteCodeGenerator->Writer()->BrReg1(Js::OpCode::BrFalse_A, skipCallCloseLabel, shouldCallReturnLocation);
EmitIteratorClose(iteratorLocation, byteCodeGenerator, funcInfo, isAsync);
byteCodeGenerator->Writer()->MarkLabel(skipCallCloseLabel);
byteCodeGenerator->PopJumpCleanup();
if (isCoroutine)
{
funcInfo->ReleaseTmpRegister(yieldOffsetLocation);
funcInfo->ReleaseTmpRegister(yieldExceptionLocation);
}
byteCodeGenerator->Writer()->RecordCrossFrameEntryExitRecord(false);
byteCodeGenerator->Writer()->Empty(Js::OpCode::LeaveNull);
byteCodeGenerator->Writer()->MarkLabel(afterFinallyBlockLabel);
}
void EmitIteratorCatchAndFinally(
Js::ByteCodeLabel catchLabel,
Js::ByteCodeLabel finallyLabel,
Js::RegSlot iteratorLocation,
Js::RegSlot shouldCallReturnFunctionLocation,
Js::RegSlot shouldCallReturnFunctionLocationFinally,
Js::RegSlot yieldExceptionLocation,
Js::RegSlot yieldOffsetLocation,
ByteCodeGenerator* byteCodeGenerator,
FuncInfo* funcInfo,
bool isAsync = false)
{
byteCodeGenerator->PopJumpCleanup();
EmitIteratorTopLevelCatch(
catchLabel,
iteratorLocation,
shouldCallReturnFunctionLocation,
shouldCallReturnFunctionLocationFinally,
byteCodeGenerator,
funcInfo,
isAsync);
byteCodeGenerator->PopJumpCleanup();
EmitIteratorTopLevelFinally(
finallyLabel,
iteratorLocation,
shouldCallReturnFunctionLocationFinally,
yieldExceptionLocation,
yieldOffsetLocation,
byteCodeGenerator,
funcInfo,
isAsync);
funcInfo->ReleaseTmpRegister(shouldCallReturnFunctionLocationFinally);
funcInfo->ReleaseTmpRegister(shouldCallReturnFunctionLocation);
}
// Emit a wrapper try..finaly block around the destructuring elements
void EmitDestructuredArray(
ParseNode *lhs,
Js::RegSlot rhsLocation,
ByteCodeGenerator *byteCodeGenerator,
FuncInfo *funcInfo)
{
byteCodeGenerator->StartStatement(lhs);
Js::RegSlot iteratorLocation = funcInfo->AcquireTmpRegister();
EmitGetIterator(iteratorLocation, rhsLocation, byteCodeGenerator, funcInfo);
Assert(lhs->nop == knopArrayPattern);
ParseNode *list = lhs->AsParseNodeArrLit()->pnode1;
if (list == nullptr)
{ // Handline this case ([] = obj);
EmitIteratorClose(iteratorLocation, byteCodeGenerator, funcInfo);
// No elements to bind or assign.
funcInfo->ReleaseTmpRegister(iteratorLocation);
byteCodeGenerator->EndStatement(lhs);
return;
}
Js::RegSlot nextMethodReg = funcInfo->AcquireTmpRegister();
EmitGetObjectMethod(
nextMethodReg,
iteratorLocation,
Js::PropertyIds::next,
byteCodeGenerator,
funcInfo);
// This variable facilitates on when to call the return function (which is Iterator close). When we are emitting bytecode for destructuring element
// this variable will be set to true.
Js::RegSlot shouldCallReturnFunctionLocation = funcInfo->AcquireTmpRegister();
Js::RegSlot shouldCallReturnFunctionLocationFinally = funcInfo->AcquireTmpRegister();
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdFalse, shouldCallReturnFunctionLocation);
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdFalse, shouldCallReturnFunctionLocationFinally);
byteCodeGenerator->SetHasFinally(true);
byteCodeGenerator->SetHasTry(true);
byteCodeGenerator->TopFuncInfo()->byteCodeFunction->SetDontInline(true);
Js::RegSlot regException = Js::Constants::NoRegister;
Js::RegSlot regOffset = Js::Constants::NoRegister;
bool isCoroutine = funcInfo->byteCodeFunction->IsCoroutine();
if (isCoroutine)
{
regException = funcInfo->AcquireTmpRegister();
regOffset = funcInfo->AcquireTmpRegister();
}
// Insert try node here
Js::ByteCodeLabel finallyLabel = byteCodeGenerator->Writer()->DefineLabel();
Js::ByteCodeLabel catchLabel = byteCodeGenerator->Writer()->DefineLabel();
byteCodeGenerator->Writer()->RecordCrossFrameEntryExitRecord(true);
if (isCoroutine)
{
byteCodeGenerator->Writer()->BrReg2(Js::OpCode::TryFinallyWithYield, finallyLabel, regException, regOffset);
byteCodeGenerator->PushJumpCleanupForTry(
Js::OpCode::TryFinallyWithYield,
finallyLabel,
regException,
regOffset);
}
else
{
byteCodeGenerator->Writer()->Br(Js::OpCode::TryFinally, finallyLabel);
byteCodeGenerator->PushJumpCleanupForTry(Js::OpCode::TryFinally, finallyLabel);
}
byteCodeGenerator->Writer()->Br(Js::OpCode::TryCatch, catchLabel);
byteCodeGenerator->PushJumpCleanupForTry(Js::OpCode::TryCatch, catchLabel);
EmitDestructuredArrayCore(
list,
iteratorLocation,
nextMethodReg,
shouldCallReturnFunctionLocation,
shouldCallReturnFunctionLocationFinally,
byteCodeGenerator,
funcInfo);
EmitIteratorCatchAndFinally(
catchLabel,
finallyLabel,
iteratorLocation,
shouldCallReturnFunctionLocation,
shouldCallReturnFunctionLocationFinally,
regException,
regOffset,
byteCodeGenerator,
funcInfo);
funcInfo->ReleaseTmpRegister(nextMethodReg);
funcInfo->ReleaseTmpRegister(iteratorLocation);
byteCodeGenerator->EndStatement(lhs);
}
void EmitNameInvoke(Js::RegSlot lhsLocation,
Js::RegSlot objectLocation,
Js::RegSlot computedPropIdArrLocation,
uint32 *computedIndex,
bool hasRest,
ParseNodePtr nameNode,
ByteCodeGenerator* byteCodeGenerator,
FuncInfo* funcInfo)
{
Assert(nameNode != nullptr);
if (nameNode->nop == knopComputedName)
{
ParseNodePtr pnode1 = nameNode->AsParseNodeUni()->pnode1;
Emit(pnode1, byteCodeGenerator, funcInfo, false/*isConstructorCall*/);
byteCodeGenerator->Writer()->Element(Js::OpCode::LdElemI_A, lhsLocation, objectLocation, pnode1->location);
if (hasRest)
{
byteCodeGenerator->Writer()->Slot(Js::OpCode::StPropIdArrFromVar, pnode1->location, computedPropIdArrLocation, *computedIndex);
(*computedIndex)++;
}
funcInfo->ReleaseLoc(pnode1);
}
else
{
Assert(nameNode->nop == knopStr);
Js::PropertyId propertyId = nameNode->AsParseNodeStr()->pid->GetPropertyId();
uint cacheId = funcInfo->FindOrAddInlineCacheId(objectLocation, propertyId, false/*isLoadMethod*/, false/*isStore*/);
byteCodeGenerator->Writer()->PatchableProperty(Js::OpCode::LdFld, lhsLocation, objectLocation, cacheId);
}
}
void EmitDestructuredValueOrInitializer(ParseNodePtr lhsElementNode,
Js::RegSlot rhsLocation,
ParseNodePtr initializer,
bool isNonPatternAssignmentTarget,
ByteCodeGenerator *byteCodeGenerator,
FuncInfo *funcInfo)
{
// If we have initializer we need to see if the destructured value is undefined or not - if it is undefined we need to assign initializer
Js::ByteCodeLabel useDefault = -1;
Js::ByteCodeLabel end = -1;
Js::RegSlot rhsLocationTmp = rhsLocation;
if (initializer != nullptr)
{
rhsLocationTmp = funcInfo->AcquireTmpRegister();
useDefault = byteCodeGenerator->Writer()->DefineLabel();
end = byteCodeGenerator->Writer()->DefineLabel();
byteCodeGenerator->Writer()->BrReg2(Js::OpCode::BrSrEq_A, useDefault, rhsLocation, funcInfo->undefinedConstantRegister);
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Ld_A, rhsLocationTmp, rhsLocation);
byteCodeGenerator->Writer()->Br(end);
byteCodeGenerator->Writer()->MarkLabel(useDefault);
Emit(initializer, byteCodeGenerator, funcInfo, false/*isConstructorCall*/);
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Ld_A_ReuseLoc, rhsLocationTmp, initializer->location);
funcInfo->ReleaseLoc(initializer);
byteCodeGenerator->Writer()->MarkLabel(end);
}
if (lhsElementNode->nop == knopArrayPattern)
{
EmitDestructuredArray(lhsElementNode, rhsLocationTmp, byteCodeGenerator, funcInfo);
}
else if (lhsElementNode->nop == knopObjectPattern)
{
EmitDestructuredObject(lhsElementNode, rhsLocationTmp, byteCodeGenerator, funcInfo);
}
else if (isNonPatternAssignmentTarget)
{
EmitAssignment(nullptr, lhsElementNode, rhsLocationTmp, byteCodeGenerator, funcInfo);
}
else
{
EmitDestructuredElement(lhsElementNode, rhsLocationTmp, byteCodeGenerator, funcInfo);
}
if (initializer != nullptr)
{
funcInfo->ReleaseTmpRegister(rhsLocationTmp);
}
}
void EmitDestructuredObjectMember(ParseNodePtr memberNode,
Js::RegSlot rhsLocation,
Js::RegSlot propIdArrLocation,
Js::RegSlot computedPropIdArrLocation,
uint32 *computedIndex,
bool hasRest,
ByteCodeGenerator *byteCodeGenerator,
FuncInfo *funcInfo)
{
Assert(memberNode->nop == knopObjectPatternMember || memberNode->nop == knopEllipsis);
Js::RegSlot nameLocation = funcInfo->AcquireTmpRegister();
ParseNodePtr lhsElementNode = nullptr;
if (memberNode->nop == knopObjectPatternMember)
{
EmitNameInvoke(nameLocation, rhsLocation, computedPropIdArrLocation,
computedIndex, hasRest, memberNode->AsParseNodeBin()->pnode1, byteCodeGenerator, funcInfo);
// Imagine we are transforming
// {x:x1} = {} to x1 = {}.x (here x1 is the second node of the member but that is our lhsnode)
lhsElementNode = memberNode->AsParseNodeBin()->pnode2;
}
else
{
// memberNode->nop == knopEllipsis, aka we are performing Rest operation
byteCodeGenerator->Writer()->Reg1(Js::OpCode::NewScObjectSimple, nameLocation);
byteCodeGenerator->Writer()->Reg4(Js::OpCode::Restify, rhsLocation, nameLocation, propIdArrLocation, computedPropIdArrLocation);
lhsElementNode = memberNode->AsParseNodeUni()->pnode1;
}
ParseNodePtr init = nullptr;
if (lhsElementNode->IsVarLetOrConst())
{
init = lhsElementNode->AsParseNodeVar()->pnodeInit;
}
else if (lhsElementNode->nop == knopAsg)
{
init = lhsElementNode->AsParseNodeBin()->pnode2;
lhsElementNode = lhsElementNode->AsParseNodeBin()->pnode1;
}
EmitDestructuredValueOrInitializer(lhsElementNode, nameLocation, init, false /*isNonPatternAssignmentTarget*/, byteCodeGenerator, funcInfo);
funcInfo->ReleaseTmpRegister(nameLocation);
}
void EmitObjectPropertyIdsToArray(ByteCodeGenerator *byteCodeGenerator,
Js::PropertyId *ids,
ParseNodePtr memberNodes,
uint32 staticCount,
bool *hasComputedProps)
{
uint32 index = 0;
Parser::ForEachItemInList(memberNodes, [&](ParseNodePtr current) {
if (current->nop != knopEllipsis)
{
ParseNodePtr nameNode = current->AsParseNodeBin()->pnode1;
Assert(nameNode != nullptr);
Assert(nameNode->nop == knopComputedName || nameNode->nop == knopStr);
if (nameNode->nop == knopStr)
{
if (index >= staticCount)
{
Js::Throw::InternalError();
return;
}
ids[index] = nameNode->AsParseNodeStr()->pid->GetPropertyId();
index++;
}
else
{
*hasComputedProps = true;
}
}
});
}
void EmitDestructuredObject(ParseNode *lhs,
Js::RegSlot rhsLocationOrig,
ByteCodeGenerator *byteCodeGenerator,
FuncInfo *funcInfo)
{
Assert(lhs->nop == knopObjectPattern);
ParseNodeObjLit *pnodeObjLit = lhs->AsParseNodeObjLit();
ParseNodePtr pnode1 = pnodeObjLit->pnode1;
uint32 staticCount = pnodeObjLit->staticCount;
uint32 computedCount = pnodeObjLit->computedCount;
bool hasRest = pnodeObjLit->hasRest;
bool hasComputedProps = false;
byteCodeGenerator->StartStatement(lhs);
Js::ByteCodeLabel skipThrow = byteCodeGenerator->Writer()->DefineLabel();
Js::RegSlot rhsLocation = funcInfo->AcquireTmpRegister();
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Ld_A, rhsLocation, rhsLocationOrig);
byteCodeGenerator->Writer()->BrReg2(Js::OpCode::BrNeq_A, skipThrow, rhsLocation, funcInfo->undefinedConstantRegister);
byteCodeGenerator->Writer()->W1(Js::OpCode::RuntimeTypeError, SCODE_CODE(JSERR_ObjectCoercible));
byteCodeGenerator->Writer()->MarkLabel(skipThrow);
if (pnode1 != nullptr)
{
Js::RegSlot propIdArrLocation = Js::Constants::NoRegister;
Js::RegSlot computedPropIdArrLocation = Js::Constants::NoRegister;
if (hasRest)
{
uint extraAlloc = UInt32Math::Mul(staticCount, sizeof(Js::PropertyId));
uint auxSize = UInt32Math::Add(sizeof(Js::PropertyIdArray), extraAlloc);
Js::PropertyIdArray *propIds = AnewPlus(byteCodeGenerator->GetAllocator(), extraAlloc, Js::PropertyIdArray, staticCount, 0);
Assert(pnode1->nop == knopList || pnode1->nop == knopObjectPatternMember || pnode1->nop == knopEllipsis);
EmitObjectPropertyIdsToArray(byteCodeGenerator, propIds->elements, pnode1, staticCount, &hasComputedProps);
// Load static PropertyIdArray here
propIdArrLocation = funcInfo->AcquireTmpRegister();
byteCodeGenerator->Writer()->Auxiliary(Js::OpCode::LdPropIds, propIdArrLocation, propIds, auxSize, staticCount);
if (hasComputedProps)
{
computedPropIdArrLocation = funcInfo->AcquireTmpRegister();
byteCodeGenerator->Writer()->Reg1Unsigned1(Js::OpCode::NewPropIdArrForCompProps, computedPropIdArrLocation, computedCount);
}
else
{
computedPropIdArrLocation = propIdArrLocation;
}
}
uint32 index = 0;
Parser::ForEachItemInList(pnode1, [&](ParseNodePtr memberNode) {
EmitDestructuredObjectMember(memberNode, rhsLocation, propIdArrLocation, computedPropIdArrLocation,
&index, hasRest, byteCodeGenerator, funcInfo);
});
if (hasRest)
{
if (hasComputedProps)
{
funcInfo->ReleaseTmpRegister(computedPropIdArrLocation);
}
funcInfo->ReleaseTmpRegister(propIdArrLocation);
}
}
funcInfo->ReleaseTmpRegister(rhsLocation);
byteCodeGenerator->EndStatement(lhs);
}
void EmitAssignment(
ParseNode *asgnNode,
ParseNode *lhs,
Js::RegSlot rhsLocation,
ByteCodeGenerator *byteCodeGenerator,
FuncInfo *funcInfo)
{
switch (lhs->nop)
{
// assignment to a local or global variable
case knopVarDecl:
case knopLetDecl:
case knopConstDecl:
{
Symbol *sym = lhs->AsParseNodeVar()->sym;
Assert(sym != nullptr);
byteCodeGenerator->EmitPropStore(rhsLocation, sym, nullptr, funcInfo, lhs->nop == knopLetDecl, lhs->nop == knopConstDecl);
break;
}
case knopName:
{
// Special names like 'this' or 'new.target' cannot be assigned to
ParseNodeName * pnodeNameLhs = lhs->AsParseNodeName();
if (pnodeNameLhs->IsSpecialName())
{
byteCodeGenerator->Writer()->W1(Js::OpCode::RuntimeReferenceError, SCODE_CODE(JSERR_CantAssignTo));
}
else
{
byteCodeGenerator->EmitPropStore(rhsLocation, pnodeNameLhs->sym, pnodeNameLhs->pid, funcInfo);
}
break;
}
// x.y =
case knopDot:
{
// PutValue(x, "y", rhs)
Js::PropertyId propertyId = lhs->AsParseNodeBin()->pnode2->AsParseNodeName()->PropertyIdFromNameNode();
if (ByteCodeGenerator::IsSuper(lhs->AsParseNodeBin()->pnode1))
{
Emit(lhs->AsParseNodeSuperReference()->pnodeThis, byteCodeGenerator, funcInfo, false);
Js::RegSlot tmpReg = byteCodeGenerator->EmitLdObjProto(Js::OpCode::LdHomeObjProto, lhs->AsParseNodeBin()->pnode1->location, funcInfo);
funcInfo->ReleaseLoc(lhs->AsParseNodeSuperReference()->pnodeThis);
uint cacheId = funcInfo->FindOrAddInlineCacheId(tmpReg, propertyId, false, true);
Js::OpCode stFldOpCode = funcInfo->GetIsStrictMode() ? Js::OpCode::StSuperFldStrict : Js::OpCode::StSuperFld;
byteCodeGenerator->Writer()->PatchablePropertyWithThisPtr(stFldOpCode, rhsLocation, tmpReg, lhs->AsParseNodeSuperReference()->pnodeThis->location, cacheId);
}
else
{
uint cacheId = funcInfo->FindOrAddInlineCacheId(lhs->AsParseNodeBin()->pnode1->location, propertyId, false, true);
byteCodeGenerator->Writer()->PatchableProperty(
ByteCodeGenerator::GetStFldOpCode(funcInfo, false, false, false, false, byteCodeGenerator->forceStrictModeForClassComputedPropertyName), rhsLocation, lhs->AsParseNodeBin()->pnode1->location, cacheId);
}
break;
}
case knopIndex:
{
Js::RegSlot targetLocation = lhs->AsParseNodeBin()->pnode1->location;
if (ByteCodeGenerator::IsSuper(lhs->AsParseNodeBin()->pnode1))
{
// We need to emit the 'this' node for the super reference even if we aren't planning to use the 'this' value.
// This is because we might be in a derived class constructor where we haven't yet called super() to bind the 'this' value.
// See ecma262 abstract operation 'MakeSuperPropertyReference'
Emit(lhs->AsParseNodeSuperReference()->pnodeThis, byteCodeGenerator, funcInfo, false);
funcInfo->ReleaseLoc(lhs->AsParseNodeSuperReference()->pnodeThis);
targetLocation = byteCodeGenerator->EmitLdObjProto(Js::OpCode::LdHomeObjProto, targetLocation, funcInfo);
}
byteCodeGenerator->Writer()->Element(
ByteCodeGenerator::GetStElemIOpCode(funcInfo),
rhsLocation, targetLocation, lhs->AsParseNodeBin()->pnode2->location);
break;
}
case knopObjectPattern:
{
// Copy the rhs value to be the result of the assignment if needed.
if (asgnNode != nullptr)
{
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Ld_A, asgnNode->location, rhsLocation);
}
return EmitDestructuredObject(lhs, rhsLocation, byteCodeGenerator, funcInfo);
}
case knopArrayPattern:
{
// Copy the rhs value to be the result of the assignment if needed.
if (asgnNode != nullptr)
{
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Ld_A, asgnNode->location, rhsLocation);
}
return EmitDestructuredArray(lhs, rhsLocation, byteCodeGenerator, funcInfo);
}
case knopArray:
case knopObject:
// Assignment to array/object can get through to byte code gen when the parser fails to convert destructuring
// assignment to pattern (because of structural mismatch between LHS & RHS?). Revisit when we nail
// down early vs. runtime errors for destructuring.
byteCodeGenerator->Writer()->W1(Js::OpCode::RuntimeReferenceError, SCODE_CODE(JSERR_CantAssignTo));
break;
default:
byteCodeGenerator->Writer()->W1(Js::OpCode::RuntimeReferenceError, SCODE_CODE(JSERR_CantAssignTo));
break;
}
if (asgnNode != nullptr)
{
// We leave it up to the caller to pass this node only if the assignment expression is used.
if (asgnNode->location != rhsLocation)
{
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Ld_A, asgnNode->location, rhsLocation);
}
}
}
void EmitLoad(
ParseNode *lhs,
ByteCodeGenerator *byteCodeGenerator,
FuncInfo *funcInfo)
{
// Emit the instructions to load the value into the LHS location. Do not assign/free any temps
// in the process.
// We usually get here as part of an op-equiv expression: x.y += z;
// In such a case, x has to be emitted first, then the value of x.y loaded (by this function), then z emitted.
switch (lhs->nop)
{
// load of a local or global variable
case knopName:
{
funcInfo->AcquireLoc(lhs);
byteCodeGenerator->EmitPropLoad(lhs->location, lhs->AsParseNodeName()->sym, lhs->AsParseNodeName()->pid, funcInfo);
break;
}
// = x.y
case knopDot:
{
// get field id for "y"
Js::PropertyId propertyId = lhs->AsParseNodeBin()->pnode2->AsParseNodeName()->PropertyIdFromNameNode();
funcInfo->AcquireLoc(lhs);
EmitReference(lhs, byteCodeGenerator, funcInfo);
uint cacheId = funcInfo->FindOrAddInlineCacheId(lhs->AsParseNodeBin()->pnode1->location, propertyId, false, false);
byteCodeGenerator->Writer()->PatchableProperty(Js::OpCode::LdFld, lhs->location, lhs->AsParseNodeBin()->pnode1->location, cacheId);
break;
}
case knopIndex:
funcInfo->AcquireLoc(lhs);
EmitReference(lhs, byteCodeGenerator, funcInfo);
byteCodeGenerator->Writer()->Element(
Js::OpCode::LdElemI_A, lhs->location, lhs->AsParseNodeBin()->pnode1->location, lhs->AsParseNodeBin()->pnode2->location);
break;
// f(x) +=
case knopCall:
{
ParseNodeCall * pnodeCallLhs = lhs->AsParseNodeCall();
if (pnodeCallLhs->isSuperCall)
{
funcInfo->AcquireLoc(pnodeCallLhs);
EmitReference(pnodeCallLhs, byteCodeGenerator, funcInfo);
byteCodeGenerator->EmitSuperCall(funcInfo, pnodeCallLhs->AsParseNodeSuperCall(), /*fReturnValue=*/ false, /*fEvaluateComponents=*/ false);
}
else if (pnodeCallLhs->pnodeTarget->nop == knopImport)
{
ParseNodePtr args = pnodeCallLhs->pnodeArgs;
Assert(CountArguments(args) == 2); // import() takes one argument
Emit(args, byteCodeGenerator, funcInfo, false);
funcInfo->ReleaseLoc(args);
funcInfo->AcquireLoc(pnodeCallLhs);
byteCodeGenerator->Writer()->Reg2(Js::OpCode::ImportCall, pnodeCallLhs->location, args->location);
}
else
{
funcInfo->AcquireLoc(pnodeCallLhs);
EmitReference(pnodeCallLhs, byteCodeGenerator, funcInfo);
EmitCall(pnodeCallLhs, byteCodeGenerator, funcInfo, /*fReturnValue=*/ false, /*fEvaluateComponents=*/ false);
}
break;
}
default:
funcInfo->AcquireLoc(lhs);
Emit(lhs, byteCodeGenerator, funcInfo, false);
break;
}
}
void EmitList(ParseNode *pnode, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo)
{
if (pnode != nullptr)
{
while (pnode->nop == knopList)
{
byteCodeGenerator->EmitTopLevelStatement(pnode->AsParseNodeBin()->pnode1, funcInfo, false);
pnode = pnode->AsParseNodeBin()->pnode2;
}
byteCodeGenerator->EmitTopLevelStatement(pnode, funcInfo, false);
}
}
void EmitOneArg(
ParseNode *pnode,
BOOL fAssignRegs,
ByteCodeGenerator *byteCodeGenerator,
FuncInfo *funcInfo,
Js::ProfileId callSiteId,
Js::ArgSlot &argIndex,
Js::ArgSlot &spreadIndex,
Js::RegSlot argTempLocation,
bool emitProfiledArgout,
Js::AuxArray<uint32> *spreadIndices = nullptr
)
{
bool noArgOuts = argTempLocation != Js::Constants::NoRegister;
// If this is a put, the arguments have already been evaluated (see EmitReference).
// We just need to emit the ArgOut instructions.
if (fAssignRegs)
{
Emit(pnode, byteCodeGenerator, funcInfo, false);
}
if (pnode->nop == knopEllipsis)
{
Assert(spreadIndices != nullptr);
spreadIndices->elements[spreadIndex++] = argIndex + 1; // account for 'this'
Js::RegSlot regVal = funcInfo->AcquireTmpRegister();
byteCodeGenerator->Writer()->Reg2(Js::OpCode::LdCustomSpreadIteratorList, regVal, pnode->location);
if (noArgOuts)
{
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Ld_A, argTempLocation, regVal);
}
else
{
byteCodeGenerator->Writer()->ArgOut<true>(argIndex + 1, regVal, callSiteId, emitProfiledArgout);
}
funcInfo->ReleaseTmpRegister(regVal);
}
else
{
if (noArgOuts)
{
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Ld_A, argTempLocation, pnode->location);
}
else
{
byteCodeGenerator->Writer()->ArgOut<true>(argIndex + 1, pnode->location, callSiteId, emitProfiledArgout);
}
}
argIndex++;
if (fAssignRegs)
{
funcInfo->ReleaseLoc(pnode);
}
}
size_t EmitArgsWithArgOutsAtEnd(
ParseNode *pnode,
BOOL fAssignRegs,
ByteCodeGenerator *byteCodeGenerator,
FuncInfo *funcInfo,
Js::ProfileId callSiteId,
Js::RegSlot thisLocation,
Js::ArgSlot argsCountForStartCall,
bool emitProfiledArgouts,
Js::AuxArray<uint32> *spreadIndices = nullptr
)
{
AssertOrFailFast(pnode != nullptr);
Js::ArgSlot argIndex = 0;
Js::ArgSlot spreadIndex = 0;
Js::RegSlot argTempLocation = funcInfo->AcquireTmpRegister();
Js::RegSlot firstArgTempLocation = argTempLocation;
while (pnode->nop == knopList)
{
EmitOneArg(pnode->AsParseNodeBin()->pnode1, fAssignRegs, byteCodeGenerator, funcInfo, callSiteId, argIndex, spreadIndex, argTempLocation, false /*emitProfiledArgout*/, spreadIndices);
pnode = pnode->AsParseNodeBin()->pnode2;
argTempLocation = funcInfo->AcquireTmpRegister();
}
EmitOneArg(pnode, fAssignRegs, byteCodeGenerator, funcInfo, callSiteId, argIndex, spreadIndex, argTempLocation, false /*emitProfiledArgout*/, spreadIndices);
byteCodeGenerator->Writer()->StartCall(Js::OpCode::StartCall, argsCountForStartCall);
// Emit all argOuts now
if (thisLocation != Js::Constants::NoRegister)
{
// Emit the "this" object.
byteCodeGenerator->Writer()->ArgOut<true>(0, thisLocation, callSiteId, false /*emitProfiledArgouts*/);
}
for (Js::ArgSlot index = 0; index < argIndex; index++)
{
byteCodeGenerator->Writer()->ArgOut<true>(index + 1, firstArgTempLocation + index, callSiteId, emitProfiledArgouts);
}
// Now release all those temps register
for (Js::ArgSlot index = argIndex; index > 0; index--)
{
funcInfo->ReleaseTmpRegister(argTempLocation--);
}
return argIndex;
}
size_t EmitArgs(
ParseNode *pnode,
BOOL fAssignRegs,
ByteCodeGenerator *byteCodeGenerator,
FuncInfo *funcInfo,
Js::ProfileId callSiteId,
bool emitProfiledArgouts,
Js::AuxArray<uint32> *spreadIndices = nullptr
)
{
Js::ArgSlot argIndex = 0;
Js::ArgSlot spreadIndex = 0;
if (pnode != nullptr)
{
while (pnode->nop == knopList)
{
EmitOneArg(pnode->AsParseNodeBin()->pnode1, fAssignRegs, byteCodeGenerator, funcInfo, callSiteId, argIndex, spreadIndex, Js::Constants::NoRegister, emitProfiledArgouts, spreadIndices);
pnode = pnode->AsParseNodeBin()->pnode2;
}
EmitOneArg(pnode, fAssignRegs, byteCodeGenerator, funcInfo, callSiteId, argIndex, spreadIndex, Js::Constants::NoRegister, emitProfiledArgouts, spreadIndices);
}
return argIndex;
}
void EmitArgListStart(
Js::RegSlot thisLocation,
ByteCodeGenerator *byteCodeGenerator,
FuncInfo *funcInfo,
Js::ProfileId callSiteId)
{
if (thisLocation != Js::Constants::NoRegister)
{
// Emit the "this" object.
byteCodeGenerator->Writer()->ArgOut<true>(0, thisLocation, callSiteId, false /*emitProfiledArgout*/);
}
}
Js::ArgSlot EmitArgListEnd(
ParseNode *pnode,
Js::RegSlot thisLocation,
Js::RegSlot evalLocation,
Js::RegSlot newTargetLocation,
ByteCodeGenerator *byteCodeGenerator,
FuncInfo *funcInfo,
size_t argIndex,
Js::ProfileId callSiteId)
{
BOOL fEvalInModule = false;
BOOL fIsEval = (evalLocation != Js::Constants::NoRegister);
BOOL fHasNewTarget = (newTargetLocation != Js::Constants::NoRegister);
static const size_t maxExtraArgSlot = 4; // max(extraEvalArg, extraArg), where extraEvalArg==2 (moduleRoot,env), extraArg==4 (this, eval, evalInModule, newTarget)
AssertOrFailFastMsg(argIndex < Js::Constants::UShortMaxValue - maxExtraArgSlot, "Number of allowed arguments are already capped at parser level");
Js::ArgSlot argSlotIndex = (Js::ArgSlot) argIndex;
Js::ArgSlot evalIndex;
if (fIsEval && argSlotIndex > 0)
{
Assert(!fHasNewTarget);
// Pass the frame display as an extra argument to "eval".
// Do this only if eval is called with some args
Js::RegSlot evalEnv;
if (funcInfo->IsGlobalFunction() && !(funcInfo->GetIsStrictMode() && byteCodeGenerator->GetFlags() & fscrEval))
{
// Use current environment as the environment for the function being called when:
// - this is the root global function (not an eval's global function)
// - this is an eval's global function that is not in strict mode (see else block)
evalEnv = funcInfo->GetEnvRegister();
}
else
{
// Use the frame display as the environment for the function being called when:
// - this is not a global function and thus it will have its own scope
// - this is an eval's global function that is in strict mode, since in strict mode the eval's global function
// has its own scope
evalEnv = funcInfo->frameDisplayRegister;
}
evalEnv = byteCodeGenerator->PrependLocalScopes(evalEnv, evalLocation, funcInfo);
// Passing the FrameDisplay as an extra argument
evalIndex = argSlotIndex + 1;
if (evalEnv == funcInfo->GetEnvRegister() || evalEnv == funcInfo->frameDisplayRegister)
{
byteCodeGenerator->Writer()->ArgOutEnv(evalIndex);
}
else
{
byteCodeGenerator->Writer()->ArgOut<false>(evalIndex, evalEnv, callSiteId, false /*emitProfiledArgout*/);
}
}
if (fHasNewTarget)
{
Assert(!fIsEval);
byteCodeGenerator->Writer()->ArgOut<true>(argSlotIndex + 1, newTargetLocation, callSiteId, false /*emitProfiledArgout*/);
}
Js::ArgSlot argIntCount = argSlotIndex + 1 + (Js::ArgSlot)fIsEval + (Js::ArgSlot)fEvalInModule + (Js::ArgSlot)fHasNewTarget;
// eval and no args passed, return 1 as argument count
if (fIsEval && pnode == nullptr)
{
return 1;
}
return argIntCount;
}
Js::ArgSlot EmitArgList(
ParseNode *pnode,
Js::RegSlot thisLocation,
Js::RegSlot newTargetLocation,
BOOL fIsEval,
BOOL fAssignRegs,
ByteCodeGenerator *byteCodeGenerator,
FuncInfo *funcInfo,
Js::ProfileId callSiteId,
Js::ArgSlot argsCountForStartCall,
bool emitArgOutsAtEnd,
bool emitProfiledArgouts,
uint16 spreadArgCount = 0,
Js::AuxArray<uint32> **spreadIndices = nullptr)
{
// This function emits the arguments for a call.
// ArgOut's with uses immediately following defs.
if (!emitArgOutsAtEnd)
{
byteCodeGenerator->Writer()->StartCall(Js::OpCode::StartCall, argsCountForStartCall);
EmitArgListStart(thisLocation, byteCodeGenerator, funcInfo, callSiteId);
}
Js::RegSlot evalLocation = Js::Constants::NoRegister;
//
// If Emitting arguments for eval and assigning registers, get a tmpLocation for eval.
// This would be used while generating frameDisplay in EmitArgListEnd.
//
if (fIsEval)
{
evalLocation = funcInfo->AcquireTmpRegister();
}
if (spreadArgCount > 0)
{
const size_t extraAlloc = UInt32Math::Mul(spreadArgCount, sizeof(uint32));
Assert(spreadIndices != nullptr);
*spreadIndices = AnewPlus(byteCodeGenerator->GetAllocator(), extraAlloc, Js::AuxArray<uint32>, spreadArgCount);
}
size_t argIndex = 0;
if (emitArgOutsAtEnd)
{
argIndex = EmitArgsWithArgOutsAtEnd(pnode, fAssignRegs, byteCodeGenerator, funcInfo, callSiteId, thisLocation, argsCountForStartCall, emitProfiledArgouts, spreadIndices == nullptr ? nullptr : *spreadIndices);
}
else
{
argIndex = EmitArgs(pnode, fAssignRegs, byteCodeGenerator, funcInfo, callSiteId, emitProfiledArgouts, spreadIndices == nullptr ? nullptr : *spreadIndices);
}
Js::ArgSlot argumentsCount = EmitArgListEnd(pnode, thisLocation, evalLocation, newTargetLocation, byteCodeGenerator, funcInfo, argIndex, callSiteId);
if (fIsEval)
{
funcInfo->ReleaseTmpRegister(evalLocation);
}
return argumentsCount;
}
void EmitConstantArgsToVarArray(ByteCodeGenerator *byteCodeGenerator, __out_ecount(argCount) Js::Var *vars, ParseNode *args, uint argCount)
{
uint index = 0;
while (args->nop == knopList && index < argCount)
{
if (args->AsParseNodeBin()->pnode1->nop == knopInt)
{
int value = args->AsParseNodeBin()->pnode1->AsParseNodeInt()->lw;
vars[index++] = Js::TaggedInt::ToVarUnchecked(value);
}
else if (args->AsParseNodeBin()->pnode1->nop == knopFlt)
{
Js::Var number = Js::JavascriptNumber::New(args->AsParseNodeBin()->pnode1->AsParseNodeFloat()->dbl, byteCodeGenerator->GetScriptContext());
#if ! FLOATVAR
byteCodeGenerator->GetScriptContext()->BindReference(number);
#endif
vars[index++] = number;
}
else
{
AnalysisAssert(false);
}
args = args->AsParseNodeBin()->pnode2;
}
if (index == argCount)
{
Assert(false);
Js::Throw::InternalError();
return;
}
if (args->nop == knopInt)
{
int value = args->AsParseNodeInt()->lw;
vars[index++] = Js::TaggedInt::ToVarUnchecked(value);
}
else if (args->nop == knopFlt)
{
Js::Var number = Js::JavascriptNumber::New(args->AsParseNodeFloat()->dbl, byteCodeGenerator->GetScriptContext());
#if ! FLOATVAR
byteCodeGenerator->GetScriptContext()->BindReference(number);
#endif
vars[index++] = number;
}
else
{
AnalysisAssert(false);
}
}
void EmitConstantArgsToIntArray(ByteCodeGenerator *byteCodeGenerator, __out_ecount(argCount) int32 *vars, ParseNode *args, uint argCount)
{
uint index = 0;
while (args->nop == knopList && index < argCount)
{
Assert(args->AsParseNodeBin()->pnode1->nop == knopInt);
vars[index++] = args->AsParseNodeBin()->pnode1->AsParseNodeInt()->lw;
args = args->AsParseNodeBin()->pnode2;
}
if (index >= argCount)
{
Js::Throw::InternalError();
return;
}
Assert(args->nop == knopInt);
vars[index++] = args->AsParseNodeInt()->lw;
Assert(index == argCount);
}
void EmitConstantArgsToFltArray(ByteCodeGenerator *byteCodeGenerator, __out_ecount(argCount) double *vars, ParseNode *args, uint argCount)
{
uint index = 0;
while (args->nop == knopList && index < argCount)
{
OpCode nop = args->AsParseNodeBin()->pnode1->nop;
if (nop == knopInt)
{
vars[index++] = (double)args->AsParseNodeBin()->pnode1->AsParseNodeInt()->lw;
}
else
{
Assert(nop == knopFlt);
vars[index++] = args->AsParseNodeBin()->pnode1->AsParseNodeFloat()->dbl;
}
args = args->AsParseNodeBin()->pnode2;
}
if (index >= argCount)
{
Js::Throw::InternalError();
return;
}
if (args->nop == knopInt)
{
vars[index++] = (double)args->AsParseNodeInt()->lw;
}
else
{
Assert(args->nop == knopFlt);
vars[index++] = args->AsParseNodeFloat()->dbl;
}
Assert(index == argCount);
}
//
// Called when we have new Ctr(constant, constant...)
//
Js::ArgSlot EmitNewObjectOfConstants(
ParseNode *pnode,
ByteCodeGenerator *byteCodeGenerator,
FuncInfo *funcInfo,
unsigned int argCount)
{
EmitArgListStart(Js::Constants::NoRegister, byteCodeGenerator, funcInfo, Js::Constants::NoProfileId);
// Create the vars array
Js::VarArrayVarCount *vars = AnewPlus(byteCodeGenerator->GetAllocator(), UInt32Math::Mul((argCount - 1), sizeof(Js::Var)), Js::VarArrayVarCount, Js::TaggedInt::ToVarUnchecked(argCount - 1));
// Emit all constants to the vars array
EmitConstantArgsToVarArray(byteCodeGenerator, vars->elements, pnode->AsParseNodeCall()->pnodeArgs, argCount - 1);
// Finish the arg list
Js::ArgSlot actualArgCount = EmitArgListEnd(
pnode->AsParseNodeCall()->pnodeArgs,
Js::Constants::NoRegister,
Js::Constants::NoRegister,
Js::Constants::NoRegister,
byteCodeGenerator,
funcInfo,
argCount - 1,
Js::Constants::NoProfileId);
// Make sure the cacheId to regSlot map in the ByteCodeWriter is left in a consistent state after writing NewScObject_A
byteCodeGenerator->Writer()->RemoveEntryForRegSlotFromCacheIdMap(pnode->AsParseNodeCall()->pnodeTarget->location);
// Generate the opcode with vars
byteCodeGenerator->Writer()->AuxiliaryContext(
Js::OpCode::NewScObject_A,
funcInfo->AcquireLoc(pnode),
vars,
UInt32Math::MulAdd<sizeof(Js::Var), sizeof(Js::VarArray)>((argCount-1)),
pnode->AsParseNodeCall()->pnodeTarget->location);
AdeletePlus(byteCodeGenerator->GetAllocator(), UInt32Math::Mul((argCount-1), sizeof(Js::VarArrayVarCount)), vars);
return actualArgCount;
}
void EmitMethodFld(bool isRoot, bool isScoped, Js::RegSlot location, Js::RegSlot callObjLocation, Js::PropertyId propertyId, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo, bool registerCacheIdForCall = true)
{
Js::OpCode opcode;
if (!isRoot)
{
if (callObjLocation == funcInfo->frameObjRegister)
{
opcode = Js::OpCode::LdLocalMethodFld;
}
else
{
opcode = Js::OpCode::LdMethodFld;
}
}
else if (isScoped)
{
opcode = Js::OpCode::ScopedLdMethodFld;
}
else
{
opcode = Js::OpCode::LdRootMethodFld;
}
if (isScoped || !isRoot)
{
Assert(isScoped || !isRoot || callObjLocation == ByteCodeGenerator::RootObjectRegister);
uint cacheId = funcInfo->FindOrAddInlineCacheId(callObjLocation, propertyId, true, false);
if (callObjLocation == funcInfo->frameObjRegister)
{
byteCodeGenerator->Writer()->ElementP(opcode, location, cacheId, false /*isCtor*/, registerCacheIdForCall);
}
else
{
byteCodeGenerator->Writer()->PatchableProperty(opcode, location, callObjLocation, cacheId, false /*isCtor*/, registerCacheIdForCall);
}
}
else
{
uint cacheId = funcInfo->FindOrAddRootObjectInlineCacheId(propertyId, true, false);
byteCodeGenerator->Writer()->PatchableRootProperty(opcode, location, cacheId, true, false, registerCacheIdForCall);
}
}
void EmitMethodFld(ParseNode *pnode, Js::RegSlot callObjLocation, Js::PropertyId propertyId, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo, bool registerCacheIdForCall = true)
{
// Load a call target of the form x.y(). (Call target may be a plain knopName if we're getting it from
// the global object, etc.)
bool isRoot = pnode->nop == knopName && (pnode->AsParseNodeName()->sym == nullptr || pnode->AsParseNodeName()->sym->GetIsGlobal());
bool isScoped = (byteCodeGenerator->GetFlags() & fscrEval) != 0 ||
(isRoot && callObjLocation != ByteCodeGenerator::RootObjectRegister);
EmitMethodFld(isRoot, isScoped, pnode->location, callObjLocation, propertyId, byteCodeGenerator, funcInfo, registerCacheIdForCall);
}
// lhs.apply(this, arguments);
void EmitApplyCall(ParseNodeCall* pnodeCall, ByteCodeGenerator* byteCodeGenerator, FuncInfo* funcInfo, BOOL fReturnValue)
{
ParseNode* applyNode = pnodeCall->pnodeTarget;
ParseNode* thisNode = pnodeCall->pnodeArgs->AsParseNodeBin()->pnode1;
Assert(applyNode->nop == knopDot);
ParseNode* funcNode = applyNode->AsParseNodeBin()->pnode1;
Js::ByteCodeLabel slowPath = byteCodeGenerator->Writer()->DefineLabel();
Js::ByteCodeLabel afterSlowPath = byteCodeGenerator->Writer()->DefineLabel();
Js::ByteCodeLabel argsAlreadyCreated = byteCodeGenerator->Writer()->DefineLabel();
Assert(applyNode->nop == knopDot);
Emit(funcNode, byteCodeGenerator, funcInfo, false);
funcInfo->AcquireLoc(applyNode);
Js::PropertyId propertyId = applyNode->AsParseNodeBin()->pnode2->AsParseNodeName()->PropertyIdFromNameNode();
// As we won't be emitting a call instruction for apply, no need to register the cacheId for apply
// load to be associated with the call. This is also required, as in the absence of a corresponding
// call for apply, we won't remove the entry for "apply" cacheId from
// ByteCodeWriter::callRegToLdFldCacheIndexMap, which is contrary to our assumption that we would
// have removed an entry from a map upon seeing its corresponding call.
EmitMethodFld(applyNode, funcNode->location, propertyId, byteCodeGenerator, funcInfo, false /*registerCacheIdForCall*/);
Symbol *argSym = funcInfo->GetArgumentsSymbol();
Assert(argSym && argSym->IsArguments());
Js::RegSlot argumentsLoc = argSym->GetLocation();
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdArgumentsFromFrame, argumentsLoc);
byteCodeGenerator->Writer()->BrReg1(Js::OpCode::BrNotNull_A, argsAlreadyCreated, argumentsLoc);
// If apply is overridden, bail to slow path.
byteCodeGenerator->Writer()->BrReg1(Js::OpCode::BrFncNeqApply, slowPath, applyNode->location);
// Note: acquire and release a temp register for this stack arg pointer instead of trying to stash it
// in funcInfo->stackArgReg. Otherwise, we'll needlessly load and store it in jitted loop bodies and
// may crash if we try to unbox it on the store.
Js::RegSlot stackArgReg = funcInfo->AcquireTmpRegister();
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdStackArgPtr, stackArgReg);
Js::RegSlot argCountLocation = funcInfo->AcquireTmpRegister();
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdArgCnt, argCountLocation);
byteCodeGenerator->Writer()->Reg5(Js::OpCode::ApplyArgs, funcNode->location, funcNode->location, thisNode->location, stackArgReg, argCountLocation);
funcInfo->ReleaseTmpRegister(argCountLocation);
funcInfo->ReleaseTmpRegister(stackArgReg);
funcInfo->ReleaseLoc(applyNode);
funcInfo->ReleaseLoc(funcNode);
// Clear these nodes as they are going to be used to re-generate the slow path.
VisitClearTmpRegs(applyNode, byteCodeGenerator, funcInfo);
VisitClearTmpRegs(funcNode, byteCodeGenerator, funcInfo);
byteCodeGenerator->Writer()->Br(afterSlowPath);
// slow path
byteCodeGenerator->Writer()->MarkLabel(slowPath);
if (funcInfo->frameObjRegister != Js::Constants::NoRegister)
{
byteCodeGenerator->EmitScopeObjectInit(funcInfo);
}
byteCodeGenerator->LoadHeapArguments(funcInfo);
byteCodeGenerator->Writer()->MarkLabel(argsAlreadyCreated);
EmitCall(pnodeCall, byteCodeGenerator, funcInfo, fReturnValue, /*fEvaluateComponents*/true);
byteCodeGenerator->Writer()->MarkLabel(afterSlowPath);
}
void EmitMethodElem(ParseNode *pnode, Js::RegSlot callObjLocation, Js::RegSlot indexLocation, ByteCodeGenerator *byteCodeGenerator)
{
// Load a call target of the form x[y]().
byteCodeGenerator->Writer()->Element(Js::OpCode::LdMethodElem, pnode->location, callObjLocation, indexLocation);
}
void EmitCallTargetNoEvalComponents(
ParseNode *pnodeTarget,
BOOL fSideEffectArgs,
Js::RegSlot *thisLocation,
bool *releaseThisLocation,
Js::RegSlot *callObjLocation,
ByteCodeGenerator *byteCodeGenerator,
FuncInfo *funcInfo)
{
// We first get a reference to the call target, then evaluate the arguments, then
// evaluate the call target.
// - emit reference to target
// - copy instance to scratch reg if necessary.
// - assign this
// - assign instance for dynamic/global name
// - emit args
// - do call (CallFld/Elem/I)
*releaseThisLocation = true;
switch (pnodeTarget->nop)
{
case knopDot:
*thisLocation = pnodeTarget->AsParseNodeBin()->pnode1->location;
*callObjLocation = pnodeTarget->AsParseNodeBin()->pnode1->location;
break;
case knopIndex:
*thisLocation = pnodeTarget->AsParseNodeBin()->pnode1->location;
*callObjLocation = pnodeTarget->AsParseNodeBin()->pnode1->location;
break;
case knopName:
// If the call target is a name, do some extra work to get its instance and the "this" pointer.
byteCodeGenerator->EmitLoadInstance(pnodeTarget->AsParseNodeName()->sym, pnodeTarget->AsParseNodeName()->pid, thisLocation, callObjLocation, funcInfo);
if (*thisLocation == Js::Constants::NoRegister)
{
*thisLocation = funcInfo->undefinedConstantRegister;
}
break;
default:
*thisLocation = funcInfo->undefinedConstantRegister;
break;
}
}
void EmitCallTarget(
ParseNode *pnodeTarget,
BOOL fSideEffectArgs,
Js::RegSlot *thisLocation,
bool *releaseThisLocation,
Js::RegSlot *callObjLocation,
ByteCodeGenerator *byteCodeGenerator,
FuncInfo *funcInfo,
Js::ProfileId * callApplyCallSiteId)
{
// - emit target
// - assign this
// - emit args
// - do call
// The call target is fully evaluated before the argument list. Note that we're not handling
// put-call cases here currently, as such cases only apply to host objects
// and are very unlikely to behave differently depending on the order of evaluation.
*releaseThisLocation = true;
switch (pnodeTarget->nop)
{
case knopDot:
{
ParseNodeBin * pnodeBinTarget = pnodeTarget->AsParseNodeBin();
funcInfo->AcquireLoc(pnodeBinTarget);
// Assign the call target operand(s), putting them into expression temps if necessary to protect
// them from side-effects.
if (fSideEffectArgs)
{
// Though we're done with target evaluation after this point, still protect opnd1 from
// arg side-effects as it's the "this" pointer.
SaveOpndValue(pnodeBinTarget->pnode1, funcInfo);
}
Assert(pnodeBinTarget->pnode2->nop == knopName);
if ((pnodeBinTarget->pnode2->AsParseNodeName()->PropertyIdFromNameNode() == Js::PropertyIds::apply) || (pnodeTarget->AsParseNodeBin()->pnode2->AsParseNodeName()->PropertyIdFromNameNode() == Js::PropertyIds::call))
{
funcInfo->EnsureCallSiteToCallApplyCallSiteMap();
*callApplyCallSiteId = byteCodeGenerator->GetNextCallApplyCallSiteId(Js::OpCode::CallI);
pnodeBinTarget->pnode1->SetIsCallApplyTargetLoad();
}
Emit(pnodeBinTarget->pnode1, byteCodeGenerator, funcInfo, false);
Js::PropertyId propertyId = pnodeBinTarget->pnode2->AsParseNodeName()->PropertyIdFromNameNode();
Js::RegSlot protoLocation = pnodeBinTarget->pnode1->location;
if (ByteCodeGenerator::IsSuper(pnodeBinTarget->pnode1))
{
Emit(pnodeBinTarget->AsParseNodeSuperReference()->pnodeThis, byteCodeGenerator, funcInfo, false);
protoLocation = byteCodeGenerator->EmitLdObjProto(Js::OpCode::LdHomeObjProto, protoLocation, funcInfo);
funcInfo->ReleaseLoc(pnodeBinTarget->AsParseNodeSuperReference()->pnodeThis);
funcInfo->ReleaseLoc(pnodeBinTarget->pnode1);
// Function calls on the 'super' object should maintain current 'this' pointer.
*thisLocation = pnodeBinTarget->AsParseNodeSuperReference()->pnodeThis->location;
*releaseThisLocation = false;
uint cacheId = funcInfo->FindOrAddInlineCacheId(protoLocation, propertyId, false, false);
byteCodeGenerator->Writer()->PatchablePropertyWithThisPtr(Js::OpCode::LdSuperFld,
pnodeTarget->location, protoLocation, *thisLocation, cacheId, false);
}
else
{
*thisLocation = pnodeBinTarget->pnode1->location;
EmitMethodFld(pnodeBinTarget, protoLocation, propertyId, byteCodeGenerator, funcInfo);
}
break;
}
case knopIndex:
{
funcInfo->AcquireLoc(pnodeTarget);
// Assign the call target operand(s), putting them into expression temps if necessary to protect
// them from side-effects.
if (fSideEffectArgs || !(ParseNode::Grfnop(pnodeTarget->AsParseNodeBin()->pnode2->nop) & fnopLeaf))
{
// Though we're done with target evaluation after this point, still protect opnd1 from
// arg or opnd2 side-effects as it's the "this" pointer.
SaveOpndValue(pnodeTarget->AsParseNodeBin()->pnode1, funcInfo);
}
Emit(pnodeTarget->AsParseNodeBin()->pnode1, byteCodeGenerator, funcInfo, false);
Emit(pnodeTarget->AsParseNodeBin()->pnode2, byteCodeGenerator, funcInfo, false);
Js::RegSlot indexLocation = pnodeTarget->AsParseNodeBin()->pnode2->location;
Js::RegSlot protoLocation = pnodeTarget->AsParseNodeBin()->pnode1->location;
if (ByteCodeGenerator::IsSuper(pnodeTarget->AsParseNodeBin()->pnode1))
{
Emit(pnodeTarget->AsParseNodeSuperReference()->pnodeThis, byteCodeGenerator, funcInfo, false);
protoLocation = byteCodeGenerator->EmitLdObjProto(Js::OpCode::LdHomeObjProto, protoLocation, funcInfo);
funcInfo->ReleaseLoc(pnodeTarget->AsParseNodeSuperReference()->pnodeThis);
// Function calls on the 'super' object should maintain current 'this' pointer
*thisLocation = pnodeTarget->AsParseNodeSuperReference()->pnodeThis->location;
*releaseThisLocation = false;
}
else
{
*thisLocation = pnodeTarget->AsParseNodeBin()->pnode1->location;
}
EmitMethodElem(pnodeTarget, protoLocation, indexLocation, byteCodeGenerator);
funcInfo->ReleaseLoc(pnodeTarget->AsParseNodeBin()->pnode2); // don't release indexLocation until after we use it.
if (ByteCodeGenerator::IsSuper(pnodeTarget->AsParseNodeBin()->pnode1))
{
funcInfo->ReleaseLoc(pnodeTarget->AsParseNodeBin()->pnode1);
}
break;
}
case knopName:
{
ParseNodeName * pnodeNameTarget = pnodeTarget->AsParseNodeName();
if (!pnodeNameTarget->IsSpecialName())
{
funcInfo->AcquireLoc(pnodeNameTarget);
// Assign the call target operand(s), putting them into expression temps if necessary to protect
// them from side-effects.
if (fSideEffectArgs)
{
SaveOpndValue(pnodeNameTarget, funcInfo);
}
byteCodeGenerator->EmitLoadInstance(pnodeNameTarget->sym, pnodeNameTarget->pid, thisLocation, callObjLocation, funcInfo);
if (*callObjLocation != Js::Constants::NoRegister)
{
// Load the call target as a property of the instance.
Js::PropertyId propertyId = pnodeNameTarget->PropertyIdFromNameNode();
EmitMethodFld(pnodeNameTarget, *callObjLocation, propertyId, byteCodeGenerator, funcInfo);
break;
}
}
// FALL THROUGH to evaluate call target.
}
default:
// Assign the call target operand(s), putting them into expression temps if necessary to protect
// them from side-effects.
Emit(pnodeTarget, byteCodeGenerator, funcInfo, false);
*thisLocation = funcInfo->undefinedConstantRegister;
break;
}
// "This" pointer should have been assigned by the above.
Assert(*thisLocation != Js::Constants::NoRegister);
}
void EmitCallI(
ParseNodeCall *pnodeCall,
BOOL fEvaluateComponents,
BOOL fIsEval,
BOOL fHasNewTarget,
uint32 actualArgCount,
ByteCodeGenerator *byteCodeGenerator,
FuncInfo *funcInfo,
Js::ProfileId callSiteId,
Js::AuxArray<uint32> *spreadIndices = nullptr)
{
// Emit a call where the target is in a register, because it's either a local name or an expression we've
// already evaluated.
ParseNode *pnodeTarget = pnodeCall->pnodeTarget;
Js::OpCode op;
Js::CallFlags callFlags = Js::CallFlags::CallFlags_None;
uint spreadExtraAlloc = 0;
bool isSuperCall = pnodeCall->isSuperCall;
Js::ArgSlot actualArgSlotCount = (Js::ArgSlot) actualArgCount;
// check for integer overflow
if ((size_t)actualArgSlotCount != actualArgCount)
{
Js::Throw::OutOfMemory();
}
if (fEvaluateComponents && !isSuperCall)
{
// Release the call target operands we assigned above. If we didn't assign them here,
// we'll need them later, so we can't re-use them for the result of the call.
funcInfo->ReleaseLoc(pnodeTarget);
}
// Grab a register for the call result.
if (pnodeCall->isUsed)
{
funcInfo->AcquireLoc(pnodeCall);
}
if (fIsEval)
{
op = Js::OpCode::CallIExtendedFlags;
callFlags = Js::CallFlags::CallFlags_ExtraArg;
}
else
{
if (isSuperCall)
{
callFlags = Js::CallFlags_New;
}
if (fHasNewTarget)
{
callFlags = (Js::CallFlags) (callFlags | Js::CallFlags::CallFlags_ExtraArg | Js::CallFlags::CallFlags_NewTarget);
}
if (pnodeCall->spreadArgCount > 0)
{
op = (isSuperCall || fHasNewTarget) ? Js::OpCode::CallIExtendedFlags : Js::OpCode::CallIExtended;
}
else
{
op = (isSuperCall || fHasNewTarget) ? Js::OpCode::CallIFlags : Js::OpCode::CallI;
}
}
if (op == Js::OpCode::CallI || op == Js::OpCode::CallIFlags)
{
if (isSuperCall)
{
Js::RegSlot tmpReg = byteCodeGenerator->EmitLdObjProto(Js::OpCode::LdFuncObjProto, pnodeTarget->location, funcInfo);
byteCodeGenerator->Writer()->CallI(op, pnodeCall->location, tmpReg, actualArgSlotCount, callSiteId, callFlags);
}
else
{
byteCodeGenerator->Writer()->CallI(op, pnodeCall->location, pnodeTarget->location, actualArgSlotCount, callSiteId, callFlags);
}
}
else
{
uint spreadIndicesSize = 0;
Js::CallIExtendedOptions options = Js::CallIExtended_None;
if (pnodeCall->spreadArgCount > 0)
{
Assert(spreadIndices != nullptr);
spreadExtraAlloc = UInt32Math::Mul(spreadIndices->count, sizeof(uint32));
spreadIndicesSize = UInt32Math::Add(sizeof(*spreadIndices), spreadExtraAlloc);
options = Js::CallIExtended_SpreadArgs;
}
if (isSuperCall)
{
Js::RegSlot tmpReg = byteCodeGenerator->EmitLdObjProto(Js::OpCode::LdFuncObjProto, pnodeTarget->location, funcInfo);
byteCodeGenerator->Writer()->CallIExtended(op, pnodeCall->location, tmpReg, actualArgSlotCount, options, spreadIndices, spreadIndicesSize, callSiteId, callFlags);
}
else
{
byteCodeGenerator->Writer()->CallIExtended(op, pnodeCall->location, pnodeTarget->location, actualArgSlotCount, options, spreadIndices, spreadIndicesSize, callSiteId, callFlags);
}
}
if (pnodeCall->spreadArgCount > 0)
{
Assert(spreadExtraAlloc != 0);
AdeletePlus(byteCodeGenerator->GetAllocator(), spreadExtraAlloc, spreadIndices);
}
}
void EmitCallInstrNoEvalComponents(
ParseNodeCall *pnodeCall,
BOOL fIsEval,
BOOL fHasNewTarget,
Js::RegSlot thisLocation,
Js::RegSlot callObjLocation,
uint32 actualArgCount,
ByteCodeGenerator *byteCodeGenerator,
FuncInfo *funcInfo,
Js::ProfileId callSiteId,
Js::AuxArray<uint32> *spreadIndices = nullptr)
{
// Emit the call instruction. The call target is a reference at this point, and we evaluate
// it as part of doing the actual call.
// Note that we don't handle the (fEvaluateComponents == TRUE) case in this function.
// (This function is only called on the !fEvaluateComponents branch in EmitCall.)
ParseNode *pnodeTarget = pnodeCall->pnodeTarget;
switch (pnodeTarget->nop)
{
case knopDot:
{
Assert(pnodeTarget->AsParseNodeBin()->pnode2->nop == knopName);
Js::PropertyId propertyId = pnodeTarget->AsParseNodeBin()->pnode2->AsParseNodeName()->PropertyIdFromNameNode();
EmitMethodFld(pnodeTarget, callObjLocation, propertyId, byteCodeGenerator, funcInfo);
EmitCallI(pnodeCall, /*fEvaluateComponents*/ FALSE, fIsEval, fHasNewTarget, actualArgCount, byteCodeGenerator, funcInfo, callSiteId, spreadIndices);
}
break;
case knopIndex:
{
EmitMethodElem(pnodeTarget, pnodeTarget->AsParseNodeBin()->pnode1->location, pnodeTarget->AsParseNodeBin()->pnode2->location, byteCodeGenerator);
EmitCallI(pnodeCall, /*fEvaluateComponents*/ FALSE, fIsEval, fHasNewTarget, actualArgCount, byteCodeGenerator, funcInfo, callSiteId, spreadIndices);
}
break;
case knopName:
{
if (callObjLocation != Js::Constants::NoRegister)
{
// We still have to get the property from its instance, so emit CallFld.
if (thisLocation != callObjLocation)
{
funcInfo->ReleaseTmpRegister(thisLocation);
}
funcInfo->ReleaseTmpRegister(callObjLocation);
Js::PropertyId propertyId = pnodeTarget->AsParseNodeName()->PropertyIdFromNameNode();
EmitMethodFld(pnodeTarget, callObjLocation, propertyId, byteCodeGenerator, funcInfo);
EmitCallI(pnodeCall, /*fEvaluateComponents*/ FALSE, fIsEval, fHasNewTarget, actualArgCount, byteCodeGenerator, funcInfo, callSiteId, spreadIndices);
break;
}
}
// FALL THROUGH
default:
EmitCallI(pnodeCall, /*fEvaluateComponents*/ FALSE, fIsEval, fHasNewTarget, actualArgCount, byteCodeGenerator, funcInfo, callSiteId, spreadIndices);
break;
}
}
void EmitCallInstr(
ParseNodeCall *pnodeCall,
BOOL fIsEval,
BOOL fHasNewTarget,
Js::RegSlot thisLocation,
Js::RegSlot callObjLocation,
uint32 actualArgCount,
ByteCodeGenerator *byteCodeGenerator,
FuncInfo *funcInfo,
Js::ProfileId callSiteId,
Js::AuxArray<uint32> *spreadIndices = nullptr)
{
// Emit a call instruction. The call target has been fully evaluated already, so we always
// emit a CallI through the register that holds the target value.
// Note that we don't handle !fEvaluateComponents cases at this point.
// (This function is only called on the fEvaluateComponents branch in EmitCall.)
if (thisLocation != Js::Constants::NoRegister)
{
funcInfo->ReleaseTmpRegister(thisLocation);
}
if (callObjLocation != Js::Constants::NoRegister &&
callObjLocation != thisLocation)
{
funcInfo->ReleaseTmpRegister(callObjLocation);
}
EmitCallI(pnodeCall, /*fEvaluateComponents*/ TRUE, fIsEval, fHasNewTarget, actualArgCount, byteCodeGenerator, funcInfo, callSiteId, spreadIndices);
}
void EmitNew(ParseNode* pnode, ByteCodeGenerator* byteCodeGenerator, FuncInfo* funcInfo)
{
Js::ArgSlot argCount = pnode->AsParseNodeCall()->argCount;
argCount++; // include "this"
BOOL fSideEffectArgs = FALSE;
unsigned int tmpCount = CountArguments(pnode->AsParseNodeCall()->pnodeArgs, &fSideEffectArgs);
AssertOrFailFastMsg(argCount == tmpCount, "argCount cannot overflow as max args capped at parser level");
byteCodeGenerator->StartStatement(pnode);
// Start call, allocate out param space
funcInfo->StartRecordingOutArgs(argCount);
// Assign the call target operand(s), putting them into expression temps if necessary to protect
// them from side-effects.
if (fSideEffectArgs)
{
SaveOpndValue(pnode->AsParseNodeCall()->pnodeTarget, funcInfo);
}
Emit(pnode->AsParseNodeCall()->pnodeTarget, byteCodeGenerator, funcInfo, false, true);
if (pnode->AsParseNodeCall()->pnodeArgs == nullptr)
{
funcInfo->ReleaseLoc(pnode->AsParseNodeCall()->pnodeTarget);
Js::OpCode op = (CreateNativeArrays(byteCodeGenerator, funcInfo)
&& CallTargetIsArray(pnode->AsParseNodeCall()->pnodeTarget))
? Js::OpCode::NewScObjArray : Js::OpCode::NewScObject;
Assert(argCount == 1);
Js::ProfileId callSiteId = byteCodeGenerator->GetNextCallSiteId(op);
byteCodeGenerator->Writer()->StartCall(Js::OpCode::StartCall, argCount);
byteCodeGenerator->Writer()->CallI(op, funcInfo->AcquireLoc(pnode),
pnode->AsParseNodeCall()->pnodeTarget->location, argCount, callSiteId);
}
else
{
uint32 actualArgCount = 0;
if (IsCallOfConstants(pnode))
{
byteCodeGenerator->Writer()->StartCall(Js::OpCode::StartCall, argCount);
funcInfo->ReleaseLoc(pnode->AsParseNodeCall()->pnodeTarget);
actualArgCount = EmitNewObjectOfConstants(pnode, byteCodeGenerator, funcInfo, argCount);
}
else
{
Js::OpCode op;
if ((CreateNativeArrays(byteCodeGenerator, funcInfo) && CallTargetIsArray(pnode->AsParseNodeCall()->pnodeTarget)))
{
op = pnode->AsParseNodeCall()->spreadArgCount > 0 ? Js::OpCode::NewScObjArraySpread : Js::OpCode::NewScObjArray;
}
else
{
op = pnode->AsParseNodeCall()->spreadArgCount > 0 ? Js::OpCode::NewScObjectSpread : Js::OpCode::NewScObject;
}
Js::ProfileId callSiteId = byteCodeGenerator->GetNextCallSiteId(op);
// Only emit profiled argouts if we're going to profile this call.
bool emitProfiledArgouts = callSiteId != byteCodeGenerator->GetCurrentCallSiteId();
Js::AuxArray<uint32> *spreadIndices = nullptr;
// Emit argouts at end for generators so that we don't need to restore them when bailing in
bool emitArgOutsAtEnd = pnode->AsParseNodeCall()->hasDestructuring || (funcInfo->byteCodeFunction->IsCoroutine() && pnode->AsParseNodeCall()->pnodeArgs != nullptr);
actualArgCount = EmitArgList(pnode->AsParseNodeCall()->pnodeArgs, Js::Constants::NoRegister, Js::Constants::NoRegister,
false, true, byteCodeGenerator, funcInfo, callSiteId, argCount, emitArgOutsAtEnd, emitProfiledArgouts, pnode->AsParseNodeCall()->spreadArgCount, &spreadIndices);
funcInfo->ReleaseLoc(pnode->AsParseNodeCall()->pnodeTarget);
if (pnode->AsParseNodeCall()->spreadArgCount > 0)
{
Assert(spreadIndices != nullptr);
uint spreadExtraAlloc = UInt32Math::Mul(spreadIndices->count, sizeof(uint32));
uint spreadIndicesSize = UInt32Math::Add(sizeof(*spreadIndices), spreadExtraAlloc);
byteCodeGenerator->Writer()->CallIExtended(op, funcInfo->AcquireLoc(pnode), pnode->AsParseNodeCall()->pnodeTarget->location,
(uint16)actualArgCount, Js::CallIExtended_SpreadArgs,
spreadIndices, spreadIndicesSize, callSiteId);
}
else
{
byteCodeGenerator->Writer()->CallI(op, funcInfo->AcquireLoc(pnode), pnode->AsParseNodeCall()->pnodeTarget->location,
(uint16)actualArgCount, callSiteId);
}
}
Assert(argCount == actualArgCount);
}
// End call, pop param space
funcInfo->EndRecordingOutArgs(argCount);
return;
}
void EmitCall(
ParseNodeCall * pnodeCall,
ByteCodeGenerator* byteCodeGenerator,
FuncInfo* funcInfo,
BOOL fReturnValue,
BOOL fEvaluateComponents,
Js::RegSlot overrideThisLocation,
Js::RegSlot newTargetLocation)
{
// If the call returns a float, we'll note this in the byte code.
Js::RegSlot thisLocation = Js::Constants::NoRegister;
Js::RegSlot callObjLocation = Js::Constants::NoRegister;
BOOL fHasNewTarget = newTargetLocation != Js::Constants::NoRegister;
BOOL fSideEffectArgs = FALSE;
BOOL fIsSuperCall = pnodeCall->isSuperCall;
ParseNode *pnodeTarget = pnodeCall->pnodeTarget;
ParseNode *pnodeArgs = pnodeCall->pnodeArgs;
uint16 spreadArgCount = pnodeCall->spreadArgCount;
if (CreateNativeArrays(byteCodeGenerator, funcInfo) && CallTargetIsArray(pnodeTarget)) {
// some minifiers (potentially incorrectly) assume that "v = new Array()" and "v = Array()" are equivalent,
// and replace the former with the latter to save 4 characters. What that means for us is that it, at least
// initially, uses the "Call" path. We want to guess that it _is_ just "new Array()" and change over to the
// "new" path, since then our native array handling can kick in.
/*EmitNew(pnode, byteCodeGenerator, funcInfo);
return;*/
}
unsigned int argCount = CountArguments(pnodeArgs, &fSideEffectArgs);
BOOL fIsEval = pnodeCall->isEvalCall;
Js::ArgSlot argSlotCount = (Js::ArgSlot)argCount;
if (fIsEval)
{
Assert(!fHasNewTarget);
//
// "eval" takes the closure environment as an extra argument
// Pass the closure env only if some argument is passed
// For just eval(), don't pass the closure environment
//
if (argCount > 1)
{
argCount++;
}
}
else if (fHasNewTarget)
{
// When we need to pass new.target explicitly, it is passed as an extra argument.
// This is similar to how eval passes an extra argument for the frame display and is
// used to support cases where we need to pass both 'this' and new.target as part of
// a function call.
// OpCode::LdNewTarget knows how to look at the call flags and fetch this argument.
argCount++;
}
// argCount indicates the total arguments count including the extra arguments.
// argSlotCount indicates the actual arguments count. So argCount should always never be les sthan argSlotCount.
if (argCount < (unsigned int)argSlotCount)
{
Js::Throw::OutOfMemory();
}
if (fReturnValue)
{
pnodeCall->isUsed = true;
}
//
// Set up the call.
//
bool releaseThisLocation = true;
// We already emit the call target for super calls in EmitSuperCall
Js::ProfileId callApplyCallSiteId = Js::Constants::NoProfileId;
if (!fIsSuperCall)
{
if (!fEvaluateComponents)
{
EmitCallTargetNoEvalComponents(pnodeTarget, fSideEffectArgs, &thisLocation, &releaseThisLocation, &callObjLocation, byteCodeGenerator, funcInfo);
}
else
{
EmitCallTarget(pnodeTarget, fSideEffectArgs, &thisLocation, &releaseThisLocation, &callObjLocation, byteCodeGenerator, funcInfo, &callApplyCallSiteId);
}
}
// If we are strictly overriding the this location, ignore what the call target set this location to.
if (overrideThisLocation != Js::Constants::NoRegister)
{
thisLocation = overrideThisLocation;
releaseThisLocation = false;
}
// Evaluate the arguments (nothing mode-specific here).
// Start call, allocate out param space
// We have to use the arguments count including the extra args to Start Call as we use it to allocated space for all the args
funcInfo->StartRecordingOutArgs(argCount);
Js::ProfileId callSiteId = byteCodeGenerator->GetNextCallSiteId(Js::OpCode::CallI);
if (callApplyCallSiteId != Js::Constants::NoProfileId)
{
funcInfo->callSiteToCallApplyCallSiteMap->AddNew(callSiteId, callApplyCallSiteId);
}
// Only emit profiled argouts if we're going to allocate callSiteInfo (on the DynamicProfileInfo) for this call.
bool emitProfiledArgouts = callSiteId != byteCodeGenerator->GetCurrentCallSiteId();
// Emit argouts at end for generators so that we don't need to restore them when bailing in
bool emitArgOutsAtEnd = pnodeCall->hasDestructuring || (funcInfo->byteCodeFunction->IsCoroutine() && pnodeCall->pnodeArgs != nullptr);
Js::AuxArray<uint32> *spreadIndices;
EmitArgList(pnodeArgs, thisLocation, newTargetLocation, fIsEval, fEvaluateComponents, byteCodeGenerator, funcInfo, callSiteId, (Js::ArgSlot)argCount, emitArgOutsAtEnd, emitProfiledArgouts, spreadArgCount, &spreadIndices);
if (!fEvaluateComponents)
{
EmitCallInstrNoEvalComponents(pnodeCall, fIsEval, fHasNewTarget, thisLocation, callObjLocation, argSlotCount, byteCodeGenerator, funcInfo, callSiteId, spreadIndices);
}
else
{
EmitCallInstr(pnodeCall, fIsEval, fHasNewTarget, releaseThisLocation ? thisLocation : Js::Constants::NoRegister, callObjLocation, argSlotCount, byteCodeGenerator, funcInfo, callSiteId, spreadIndices);
}
// End call, pop param space
funcInfo->EndRecordingOutArgs((Js::ArgSlot)argCount);
}
void EmitInvoke(
Js::RegSlot location,
Js::RegSlot callObjLocation,
Js::PropertyId propertyId,
ByteCodeGenerator* byteCodeGenerator,
FuncInfo* funcInfo)
{
EmitMethodFld(false, false, location, callObjLocation, propertyId, byteCodeGenerator, funcInfo);
funcInfo->StartRecordingOutArgs(1);
Js::ProfileId callSiteId = byteCodeGenerator->GetNextCallSiteId(Js::OpCode::CallI);
byteCodeGenerator->Writer()->StartCall(Js::OpCode::StartCall, 1);
EmitArgListStart(callObjLocation, byteCodeGenerator, funcInfo, callSiteId);
byteCodeGenerator->Writer()->CallI(Js::OpCode::CallI, location, location, 1, callSiteId);
}
void EmitInvoke(
Js::RegSlot location,
Js::RegSlot callObjLocation,
Js::PropertyId propertyId,
ByteCodeGenerator* byteCodeGenerator,
FuncInfo* funcInfo,
Js::RegSlot arg1Location)
{
EmitMethodFld(false, false, location, callObjLocation, propertyId, byteCodeGenerator, funcInfo);
funcInfo->StartRecordingOutArgs(2);
Js::ProfileId callSiteId = byteCodeGenerator->GetNextCallSiteId(Js::OpCode::CallI);
byteCodeGenerator->Writer()->StartCall(Js::OpCode::StartCall, 2);
EmitArgListStart(callObjLocation, byteCodeGenerator, funcInfo, callSiteId);
byteCodeGenerator->Writer()->ArgOut<true>(1, arg1Location, callSiteId, false /*emitProfiledArgout*/);
byteCodeGenerator->Writer()->CallI(Js::OpCode::CallI, location, location, 2, callSiteId);
}
void EmitMemberNode(ParseNode *memberNode, Js::RegSlot objectLocation, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo, ParseNode* parentNode, bool useStore, bool* isObjectEmpty = nullptr)
{
ParseNode *nameNode = memberNode->AsParseNodeBin()->pnode1;
ParseNode *exprNode = memberNode->AsParseNodeBin()->pnode2;
bool isFncDecl = exprNode->nop == knopFncDecl;
bool isClassMember = isFncDecl && exprNode->AsParseNodeFnc()->IsClassMember();
if (isFncDecl)
{
Assert(exprNode->AsParseNodeFnc()->HasHomeObj());
exprNode->AsParseNodeFnc()->SetHomeObjLocation(objectLocation);
}
Js::RegSlot computedNamePropertyKey = Js::Constants::NoRegister;
// Moved SetComputedNameVar before LdFld of prototype because loading the prototype undefers the function TypeHandler
// which makes this bytecode too late to influence the function.name.
if (nameNode->nop == knopComputedName)
{
// Computed property name
// Transparently pass the name expr
// The Emit will replace this with a temp register if necessary to preserve the value.
nameNode->location = nameNode->AsParseNodeUni()->pnode1->location;
// Save the previous value of the flag to be restored later.
bool prevFlag = byteCodeGenerator->forceStrictModeForClassComputedPropertyName;
// Strict mode must be enforced on the evaluation of computed property names inside
// classes, thus enable the flag if the computed property name is a class member.
byteCodeGenerator->forceStrictModeForClassComputedPropertyName = isClassMember || prevFlag;
computedNamePropertyKey = funcInfo->AcquireTmpRegister();
EmitBinaryOpnds(nameNode, exprNode, byteCodeGenerator, funcInfo, computedNamePropertyKey);
// Restore the flag's previous value.
byteCodeGenerator->forceStrictModeForClassComputedPropertyName = prevFlag;
if (isFncDecl)
{
if (exprNode->AsParseNodeFnc()->pnodeName == nullptr)
{
byteCodeGenerator->Writer()->Reg2(Js::OpCode::SetComputedNameVar, exprNode->location, computedNamePropertyKey);
exprNode->AsParseNodeFnc()->SetHasComputedName();
}
}
else if (exprNode->nop == knopClassDecl)
{
if (exprNode->AsParseNodeClass()->pnodeName == nullptr)
{
byteCodeGenerator->Writer()->Reg2(Js::OpCode::SetComputedNameVar, exprNode->location, computedNamePropertyKey);
exprNode->AsParseNodeClass()->pnodeConstructor->SetHasComputedName();
}
}
}
if (nameNode->nop == knopComputedName)
{
AssertOrFailFast(memberNode->nop == knopGetMember || memberNode->nop == knopSetMember || memberNode->nop == knopMember);
Js::OpCode setOp = memberNode->nop == knopGetMember ?
(isClassMember ? Js::OpCode::InitClassMemberGetComputedName : Js::OpCode::InitGetElemI) :
memberNode->nop == knopSetMember ?
(isClassMember ? Js::OpCode::InitClassMemberSetComputedName : Js::OpCode::InitSetElemI) :
(isClassMember ? Js::OpCode::InitClassMemberComputedName : Js::OpCode::InitComputedProperty);
// Save the previous value of the flag to be restored later.
bool prevFlag = byteCodeGenerator->forceStrictModeForClassComputedPropertyName;
byteCodeGenerator->forceStrictModeForClassComputedPropertyName = isClassMember || prevFlag;
// Strict mode must be enforced on the evaluation of computed property names inside
// classes, thus enable the flag if the computed property name is a class member.
byteCodeGenerator->Writer()->Element(setOp, exprNode->location, objectLocation, computedNamePropertyKey, true,
byteCodeGenerator->forceStrictModeForClassComputedPropertyName);
// Restore the flag's previous value.
byteCodeGenerator->forceStrictModeForClassComputedPropertyName = prevFlag;
funcInfo->ReleaseLoc(exprNode);
funcInfo->ReleaseLoc(nameNode);
funcInfo->ReleaseTmpRegister(computedNamePropertyKey);
return;
}
Js::OpCode stFldOpCode = (Js::OpCode)0;
if (useStore)
{
stFldOpCode = ByteCodeGenerator::GetStFldOpCode(funcInfo, false, false, false, isClassMember);
}
Emit(exprNode, byteCodeGenerator, funcInfo, false);
Js::PropertyId propertyId = nameNode->AsParseNodeStr()->pid->GetPropertyId();
if (Js::PropertyIds::name == propertyId
&& exprNode->nop == knopFncDecl
&& exprNode->AsParseNodeFnc()->IsStaticMember()
&& parentNode != nullptr && parentNode->nop == knopClassDecl
&& parentNode->AsParseNodeClass()->pnodeConstructor != nullptr)
{
Js::ParseableFunctionInfo* nameFunc = parentNode->AsParseNodeClass()->pnodeConstructor->funcInfo->byteCodeFunction->GetParseableFunctionInfo();
nameFunc->SetIsStaticNameFunction(true);
}
if (memberNode->nop == knopMember || memberNode->nop == knopMemberShort)
{
// The internal prototype should be set only if the production is of the form PropertyDefinition : PropertyName : AssignmentExpression
if (propertyId == Js::PropertyIds::__proto__ && memberNode->nop != knopMemberShort && (exprNode->nop != knopFncDecl || !exprNode->AsParseNodeFnc()->IsMethod()))
{
byteCodeGenerator->Writer()->Property(Js::OpCode::InitProto, exprNode->location, objectLocation,
funcInfo->FindOrAddReferencedPropertyId(propertyId));
}
else
{
uint cacheId = funcInfo->FindOrAddInlineCacheId(objectLocation, propertyId, false, true);
Js::OpCode patchablePropertyOpCode;
if (useStore)
{
patchablePropertyOpCode = stFldOpCode;
}
else if (isClassMember)
{
patchablePropertyOpCode = Js::OpCode::InitClassMember;
}
else
{
patchablePropertyOpCode = Js::OpCode::InitFld;
}
byteCodeGenerator->Writer()->PatchableProperty(patchablePropertyOpCode, exprNode->location, objectLocation, cacheId);
}
}
else
{
AssertOrFailFast(memberNode->nop == knopGetMember || memberNode->nop == knopSetMember);
Js::OpCode setOp = memberNode->nop == knopGetMember ?
(isClassMember ? Js::OpCode::InitClassMemberGet : Js::OpCode::InitGetFld) :
(isClassMember ? Js::OpCode::InitClassMemberSet : Js::OpCode::InitSetFld);
byteCodeGenerator->Writer()->Property(setOp, exprNode->location, objectLocation, funcInfo->FindOrAddReferencedPropertyId(propertyId));
}
funcInfo->ReleaseLoc(exprNode);
if (propertyId == Js::PropertyIds::valueOf)
{
byteCodeGenerator->GetScriptContext()->optimizationOverrides.SetSideEffects(Js::SideEffects_ValueOf);
}
else if (propertyId == Js::PropertyIds::toString)
{
byteCodeGenerator->GetScriptContext()->optimizationOverrides.SetSideEffects(Js::SideEffects_ToString);
}
}
void EmitObjectSpreadNode(ParseNode *spreadNode, Js::RegSlot objectLocation, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo)
{
Js::RegSlot fromObjectLocation;
ParseNode *exprNode = spreadNode->AsParseNodeUni()->pnode1;
Emit(exprNode, byteCodeGenerator, funcInfo, false);
fromObjectLocation = exprNode->location;
byteCodeGenerator->Writer()->Reg2(Js::OpCode::SpreadObjectLiteral, fromObjectLocation, objectLocation);
funcInfo->ReleaseLoc(exprNode);
}
void EmitClassInitializers(ParseNode *memberList, Js::RegSlot objectLocation, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo, ParseNode* parentNode, bool isObjectEmpty)
{
if (memberList != nullptr)
{
while (memberList->nop == knopList)
{
ParseNode *memberNode = memberList->AsParseNodeBin()->pnode1;
EmitMemberNode(memberNode, objectLocation, byteCodeGenerator, funcInfo, parentNode, /*useStore*/ false, &isObjectEmpty);
memberList = memberList->AsParseNodeBin()->pnode2;
}
EmitMemberNode(memberList, objectLocation, byteCodeGenerator, funcInfo, parentNode, /*useStore*/ false, &isObjectEmpty);
}
}
void EmitObjectInitializers(ParseNode *memberList, Js::RegSlot objectLocation, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo)
{
ParseNode *pmemberList = memberList;
unsigned int argCount = 0;
uint32 value;
Js::PropertyId propertyId;
//
// 1. Add all non-int property ids to a dictionary propertyIds with value true
// 2. Get the count of propertyIds
// 3. Create a propertyId array of size count
// 4. Put the propIds in the auxiliary area
// 5. Get the objectLiteralCacheId
// 6. Generate propId inits with values
//
// Handle propertyId collision
typedef JsUtil::BaseHashSet<Js::PropertyId, ArenaAllocator, PowerOf2SizePolicy> PropertyIdSet;
PropertyIdSet* propertyIds = Anew(byteCodeGenerator->GetAllocator(), PropertyIdSet, byteCodeGenerator->GetAllocator(), 17);
bool hasComputedNameOrSpread = false;
if (memberList != nullptr)
{
while (memberList->nop == knopList)
{
if (memberList->AsParseNodeBin()->pnode1->nop == knopEllipsis || memberList->AsParseNodeBin()->pnode1->AsParseNodeBin()->pnode1->nop == knopComputedName)
{
hasComputedNameOrSpread = true;
break;
}
propertyId = memberList->AsParseNodeBin()->pnode1->AsParseNodeBin()->pnode1->AsParseNodeStr()->pid->GetPropertyId();
if (!byteCodeGenerator->GetScriptContext()->IsNumericPropertyId(propertyId, &value))
{
propertyIds->Item(propertyId);
}
memberList = memberList->AsParseNodeBin()->pnode2;
}
if (memberList->nop != knopEllipsis && memberList->AsParseNodeBin()->pnode1->nop != knopComputedName && !hasComputedNameOrSpread)
{
propertyId = memberList->AsParseNodeBin()->pnode1->AsParseNodeStr()->pid->GetPropertyId();
if (!byteCodeGenerator->GetScriptContext()->IsNumericPropertyId(propertyId, &value))
{
propertyIds->Item(propertyId);
}
}
}
argCount = propertyIds->Count();
memberList = pmemberList;
if ((memberList == nullptr) || (argCount == 0))
{
// Empty literal or numeric property only object literal
byteCodeGenerator->Writer()->Reg1(Js::OpCode::NewScObjectSimple, objectLocation);
}
else
{
uint32 allocSize = UInt32Math::Mul(argCount, sizeof(Js::PropertyId));
Js::PropertyIdArray *propIds = AnewPlus(byteCodeGenerator->GetAllocator(), allocSize, Js::PropertyIdArray, argCount, 0);
if (propertyIds->ContainsKey(Js::PropertyIds::__proto__))
{
// Always record whether the initializer contains __proto__ no matter if current environment has it enabled
// or not, in case the bytecode is later run with __proto__ enabled.
propIds->has__proto__ = true;
}
unsigned int argIndex = 0;
while (memberList->nop == knopList)
{
if (memberList->AsParseNodeBin()->pnode1->nop == knopEllipsis || memberList->AsParseNodeBin()->pnode1->AsParseNodeBin()->pnode1->nop == knopComputedName)
{
break;
}
propertyId = memberList->AsParseNodeBin()->pnode1->AsParseNodeBin()->pnode1->AsParseNodeStr()->pid->GetPropertyId();
if (!byteCodeGenerator->GetScriptContext()->IsNumericPropertyId(propertyId, &value) && propertyIds->Remove(propertyId))
{
propIds->elements[argIndex] = propertyId;
argIndex++;
}
memberList = memberList->AsParseNodeBin()->pnode2;
}
if (memberList->nop != knopEllipsis && memberList->AsParseNodeBin()->pnode1->nop != knopComputedName && !hasComputedNameOrSpread)
{
propertyId = memberList->AsParseNodeBin()->pnode1->AsParseNodeStr()->pid->GetPropertyId();
if (!byteCodeGenerator->GetScriptContext()->IsNumericPropertyId(propertyId, &value) && propertyIds->Remove(propertyId))
{
propIds->elements[argIndex] = propertyId;
argIndex++;
}
}
uint32 literalObjectId = funcInfo->GetParsedFunctionBody()->NewObjectLiteral();
// Generate the opcode with propIds and cacheId
byteCodeGenerator->Writer()->Auxiliary(Js::OpCode::NewScObjectLiteral, objectLocation, propIds, UInt32Math::Add(sizeof(Js::PropertyIdArray), allocSize), literalObjectId);
Adelete(byteCodeGenerator->GetAllocator(), propertyIds);
AdeletePlus(byteCodeGenerator->GetAllocator(), allocSize, propIds);
}
memberList = pmemberList;
bool useStore = false;
// Generate the actual assignment to those properties
if (memberList != nullptr)
{
while (memberList->nop == knopList)
{
ParseNode *memberNode = memberList->AsParseNodeBin()->pnode1;
if (memberNode->nop == knopEllipsis)
{
byteCodeGenerator->StartSubexpression(memberNode);
EmitObjectSpreadNode(memberNode, objectLocation, byteCodeGenerator, funcInfo);
byteCodeGenerator->EndSubexpression(memberNode);
}
else
{
if (memberNode->AsParseNodeBin()->pnode1->nop == knopComputedName)
{
useStore = true;
}
byteCodeGenerator->StartSubexpression(memberNode);
EmitMemberNode(memberNode, objectLocation, byteCodeGenerator, funcInfo, nullptr, useStore);
byteCodeGenerator->EndSubexpression(memberNode);
}
memberList = memberList->AsParseNodeBin()->pnode2;
}
byteCodeGenerator->StartSubexpression(memberList);
if (memberList->nop == knopEllipsis)
{
EmitObjectSpreadNode(memberList, objectLocation, byteCodeGenerator, funcInfo);
}
else
{
EmitMemberNode(memberList, objectLocation, byteCodeGenerator, funcInfo, nullptr, useStore);
}
byteCodeGenerator->EndSubexpression(memberList);
}
}
void EmitStringTemplate(ParseNodeStrTemplate *pnodeStrTemplate, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo)
{
Assert(pnodeStrTemplate->pnodeStringLiterals);
// For a tagged string template, we will create the callsite constant object as part of the FunctionBody constants table.
// We only need to emit code for non-tagged string templates here.
if (!pnodeStrTemplate->isTaggedTemplate)
{
// If we have no substitutions and this is not a tagged template, we can emit just the single cooked string.
if (pnodeStrTemplate->pnodeSubstitutionExpressions == nullptr)
{
Assert(pnodeStrTemplate->pnodeStringLiterals->nop != knopList);
funcInfo->AcquireLoc(pnodeStrTemplate);
Emit(pnodeStrTemplate->pnodeStringLiterals, byteCodeGenerator, funcInfo, false);
Assert(pnodeStrTemplate->location != pnodeStrTemplate->pnodeStringLiterals->location);
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Ld_A, pnodeStrTemplate->location, pnodeStrTemplate->pnodeStringLiterals->location);
funcInfo->ReleaseLoc(pnodeStrTemplate->pnodeStringLiterals);
}
else
{
// If we have substitutions but no tag function, we can skip the callSite object construction (and also ignore raw strings).
funcInfo->AcquireLoc(pnodeStrTemplate);
// First string must be a list node since we have substitutions.
AssertMsg(pnodeStrTemplate->pnodeStringLiterals->nop == knopList, "First string in the list must be a knopList node.");
ParseNode* stringNodeList = pnodeStrTemplate->pnodeStringLiterals;
// Emit the first string and load that into the pnode location.
// skip loading the string if it is empty
ParseNode* firstString = stringNodeList->AsParseNodeBin()->pnode1;
bool skippedFirst = false;
if (firstString->AsParseNodeStr()->pid->Cch() == 0)
{
skippedFirst = true;
}
else
{
Emit(stringNodeList->AsParseNodeBin()->pnode1, byteCodeGenerator, funcInfo, false);
Assert(pnodeStrTemplate->location != stringNodeList->AsParseNodeBin()->pnode1->location);
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Ld_A, pnodeStrTemplate->location, stringNodeList->AsParseNodeBin()->pnode1->location);
funcInfo->ReleaseLoc(stringNodeList->AsParseNodeBin()->pnode1);
}
ParseNode* expressionNodeList = pnodeStrTemplate->pnodeSubstitutionExpressions;
ParseNode* stringNode;
ParseNode* expressionNode;
// Now append the substitution expressions and remaining string constants via normal add operator
// We will always have one more string constant than substitution expression
// `strcon1 ${expr1} strcon2 ${expr2} strcon3` = strcon1 + expr1 + strcon2 + expr2 + strcon3
//
// strcon1 --- step 1 (above)
// expr1 \__ step 2
// strcon2 /
// expr2 \__ step 3
// strcon3 /
while (stringNodeList->nop == knopList)
{
// If the current head of the expression list is a list, fetch the node and walk the list.
if (expressionNodeList->nop == knopList)
{
expressionNode = expressionNodeList->AsParseNodeBin()->pnode1;
expressionNodeList = expressionNodeList->AsParseNodeBin()->pnode2;
}
else
{
// This is the last element of the expression list.
expressionNode = expressionNodeList;
}
// Emit the expression and append it to the string we're building.
Emit(expressionNode, byteCodeGenerator, funcInfo, false);
// if this is the first expression AND the initial string was empty write directly to the pnodeStrTemplate location
if (skippedFirst == true)
{
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Conv_Str, pnodeStrTemplate->location, expressionNode->location);
skippedFirst = false;
}
else
{
Js::RegSlot toStringLocation = funcInfo->AcquireTmpRegister();
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Conv_Str, toStringLocation, expressionNode->location);
byteCodeGenerator->Writer()->Reg3(Js::OpCode::Add_A, pnodeStrTemplate->location, pnodeStrTemplate->location, toStringLocation);
funcInfo->ReleaseTmpRegister(toStringLocation);
}
funcInfo->ReleaseLoc(expressionNode);
// Move to the next string in the list - we already got ahead of the expressions in the first string literal above.
stringNodeList = stringNodeList->AsParseNodeBin()->pnode2;
// If the current head of the string literal list is also a list node, need to fetch the actual string literal node.
if (stringNodeList->nop == knopList)
{
stringNode = stringNodeList->AsParseNodeBin()->pnode1;
}
else
{
// This is the last element of the string literal list.
stringNode = stringNodeList;
}
// Emit the string node following the previous expression and append it to the string.
// This is either just some string in the list or it is the last string.
if (stringNode->AsParseNodeStr()->pid->Cch() != 0)
{
Emit(stringNode, byteCodeGenerator, funcInfo, false);
byteCodeGenerator->Writer()->Reg3(Js::OpCode::Add_A, pnodeStrTemplate->location, pnodeStrTemplate->location, stringNode->location);
funcInfo->ReleaseLoc(stringNode);
}
}
}
}
}
void SetNewArrayElements(ParseNode *pnode, Js::RegSlot arrayLocation, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo)
{
ParseNode *args = pnode->AsParseNodeUni()->pnode1;
uint argCount = pnode->AsParseNodeArrLit()->count;
uint spreadCount = pnode->AsParseNodeArrLit()->spreadCount;
bool nativeArrays = CreateNativeArrays(byteCodeGenerator, funcInfo);
bool arrayIntOpt = nativeArrays && pnode->AsParseNodeArrLit()->arrayOfInts;
if (arrayIntOpt)
{
int extraAlloc = 0, auxSize = 0;
if (Int32Math::Mul(argCount, sizeof(int32), &extraAlloc)
|| Int32Math::Add(sizeof(Js::AuxArray<int>), extraAlloc, &auxSize))
{
::Math::DefaultOverflowPolicy();
}
Js::AuxArray<int> *ints = AnewPlus(byteCodeGenerator->GetAllocator(), extraAlloc, Js::AuxArray<int32>, argCount);
EmitConstantArgsToIntArray(byteCodeGenerator, ints->elements, args, argCount);
Assert(!pnode->AsParseNodeArrLit()->hasMissingValues);
byteCodeGenerator->Writer()->Auxiliary(
Js::OpCode::NewScIntArray,
pnode->location,
ints,
auxSize,
argCount);
AdeletePlus(byteCodeGenerator->GetAllocator(), extraAlloc, ints);
return;
}
bool arrayNumOpt = nativeArrays && pnode->AsParseNodeArrLit()->arrayOfNumbers;
if (arrayNumOpt)
{
int extraAlloc = 0, auxSize = 0;
if (Int32Math::Mul(argCount, sizeof(double), &extraAlloc)
|| Int32Math::Add(sizeof(Js::AuxArray<double>), extraAlloc, &auxSize))
{
::Math::DefaultOverflowPolicy();
}
Js::AuxArray<double> *doubles = AnewPlus(byteCodeGenerator->GetAllocator(), extraAlloc, Js::AuxArray<double>, argCount);
EmitConstantArgsToFltArray(byteCodeGenerator, doubles->elements, args, argCount);
Assert(!pnode->AsParseNodeArrLit()->hasMissingValues);
byteCodeGenerator->Writer()->Auxiliary(
Js::OpCode::NewScFltArray,
pnode->location,
doubles,
auxSize,
argCount);
AdeletePlus(byteCodeGenerator->GetAllocator(), extraAlloc, doubles);
return;
}
bool arrayLitOpt = pnode->AsParseNodeArrLit()->arrayOfTaggedInts && pnode->AsParseNodeArrLit()->count > 1;
Assert(!arrayLitOpt || !nativeArrays);
Js::RegSlot spreadArrLoc = arrayLocation;
Js::AuxArray<uint32> *spreadIndices = nullptr;
const uint extraAlloc = UInt32Math::Mul(spreadCount, sizeof(uint32));
if (pnode->AsParseNodeArrLit()->spreadCount > 0)
{
arrayLocation = funcInfo->AcquireTmpRegister();
spreadIndices = AnewPlus(byteCodeGenerator->GetAllocator(), extraAlloc, Js::AuxArray<uint32>, spreadCount);
}
byteCodeGenerator->Writer()->Reg1Unsigned1(
pnode->AsParseNodeArrLit()->hasMissingValues ? Js::OpCode::NewScArrayWithMissingValues : Js::OpCode::NewScArray,
arrayLocation,
argCount);
if (args != nullptr)
{
Js::OpCode opcode;
Js::RegSlot arrLoc;
if (argCount == 1 && !byteCodeGenerator->Writer()->DoProfileNewScArrayOp(Js::OpCode::NewScArray))
{
opcode = Js::OpCode::StArrItemC_CI4;
arrLoc = arrayLocation;
}
else if (arrayLitOpt)
{
opcode = Js::OpCode::StArrSegItem_A;
arrLoc = funcInfo->AcquireTmpRegister();
byteCodeGenerator->Writer()->Reg2(Js::OpCode::LdArrHead, arrLoc, arrayLocation);
}
else if (Js::JavascriptArray::HasInlineHeadSegment(argCount))
{
// The head segment will be allocated inline as an interior pointer. To keep the array alive, the set operation
// should be done relative to the array header to keep it alive (instead of the array segment).
opcode = Js::OpCode::StArrInlineItem_CI4;
arrLoc = arrayLocation;
}
else if (argCount <= Js::JavascriptArray::MaxInitialDenseLength)
{
opcode = Js::OpCode::StArrSegItem_CI4;
arrLoc = funcInfo->AcquireTmpRegister();
byteCodeGenerator->Writer()->Reg2(Js::OpCode::LdArrHead, arrLoc, arrayLocation);
}
else
{
opcode = Js::OpCode::StArrItemI_CI4;
arrLoc = arrayLocation;
}
if (arrayLitOpt)
{
uint32 allocSize = UInt32Math::Mul(argCount, sizeof(Js::Var));
Js::VarArray *vars = AnewPlus(byteCodeGenerator->GetAllocator(), allocSize, Js::VarArray, argCount);
EmitConstantArgsToVarArray(byteCodeGenerator, vars->elements, args, argCount);
// Generate the opcode with vars
byteCodeGenerator->Writer()->Auxiliary(Js::OpCode::StArrSegItem_A, arrLoc, vars, UInt32Math::Add(sizeof(Js::VarArray), allocSize), argCount);
AdeletePlus(byteCodeGenerator->GetAllocator(), allocSize, vars);
}
else
{
uint i = 0;
unsigned spreadIndex = 0;
Js::RegSlot rhsLocation;
while (args->nop == knopList)
{
if (args->AsParseNodeBin()->pnode1->nop != knopEmpty)
{
Emit(args->AsParseNodeBin()->pnode1, byteCodeGenerator, funcInfo, false);
rhsLocation = args->AsParseNodeBin()->pnode1->location;
Js::RegSlot regVal = rhsLocation;
if (args->AsParseNodeBin()->pnode1->nop == knopEllipsis)
{
AnalysisAssert(spreadIndices);
regVal = funcInfo->AcquireTmpRegister();
byteCodeGenerator->Writer()->Reg2(Js::OpCode::LdCustomSpreadIteratorList, regVal, rhsLocation);
spreadIndices->elements[spreadIndex++] = i;
}
byteCodeGenerator->Writer()->ElementUnsigned1(opcode, regVal, arrLoc, i);
if (args->AsParseNodeBin()->pnode1->nop == knopEllipsis)
{
funcInfo->ReleaseTmpRegister(regVal);
}
funcInfo->ReleaseLoc(args->AsParseNodeBin()->pnode1);
}
args = args->AsParseNodeBin()->pnode2;
i++;
}
if (args->nop != knopEmpty)
{
Emit(args, byteCodeGenerator, funcInfo, false);
rhsLocation = args->location;
Js::RegSlot regVal = rhsLocation;
if (args->nop == knopEllipsis)
{
regVal = funcInfo->AcquireTmpRegister();
byteCodeGenerator->Writer()->Reg2(Js::OpCode::LdCustomSpreadIteratorList, regVal, rhsLocation);
AnalysisAssert(spreadIndices);
spreadIndices->elements[spreadIndex] = i;
}
byteCodeGenerator->Writer()->ElementUnsigned1(opcode, regVal, arrLoc, i);
if (args->nop == knopEllipsis)
{
funcInfo->ReleaseTmpRegister(regVal);
}
funcInfo->ReleaseLoc(args);
i++;
}
Assert(i <= argCount);
}
if (arrLoc != arrayLocation)
{
funcInfo->ReleaseTmpRegister(arrLoc);
}
}
if (pnode->AsParseNodeArrLit()->spreadCount > 0)
{
byteCodeGenerator->Writer()->Reg2Aux(Js::OpCode::SpreadArrayLiteral, spreadArrLoc, arrayLocation, spreadIndices, UInt32Math::Add(sizeof(Js::AuxArray<uint32>), extraAlloc), extraAlloc);
AdeletePlus(byteCodeGenerator->GetAllocator(), extraAlloc, spreadIndices);
funcInfo->ReleaseTmpRegister(arrayLocation);
}
}
// FIX: TODO: mixed-mode expressions (arithmetic expressions mixed with boolean expressions); current solution
// will not short-circuit in some cases and is not complete (for example: var i=(x==y))
// This uses Aho and Ullman style double-branch generation (p. 494 ASU); we will need to peephole optimize or replace
// with special case for single-branch style.
void EmitBooleanExpression(
_In_ ParseNode* expr,
Js::ByteCodeLabel trueLabel,
Js::ByteCodeLabel falseLabel,
_In_ ByteCodeGenerator* byteCodeGenerator,
_In_ FuncInfo* funcInfo,
bool trueFallthrough,
bool falseFallthrough)
{
Assert(!trueFallthrough || !falseFallthrough);
byteCodeGenerator->StartStatement(expr);
switch (expr->nop)
{
case knopLogOr:
{
Js::ByteCodeLabel leftFalse = byteCodeGenerator->Writer()->DefineLabel();
EmitBooleanExpression(expr->AsParseNodeBin()->pnode1, trueLabel, leftFalse, byteCodeGenerator, funcInfo, false, true);
funcInfo->ReleaseLoc(expr->AsParseNodeBin()->pnode1);
byteCodeGenerator->Writer()->MarkLabel(leftFalse);
EmitBooleanExpression(expr->AsParseNodeBin()->pnode2, trueLabel, falseLabel, byteCodeGenerator, funcInfo, trueFallthrough, falseFallthrough);
funcInfo->ReleaseLoc(expr->AsParseNodeBin()->pnode2);
break;
}
case knopLogAnd:
{
Js::ByteCodeLabel leftTrue = byteCodeGenerator->Writer()->DefineLabel();
EmitBooleanExpression(expr->AsParseNodeBin()->pnode1, leftTrue, falseLabel, byteCodeGenerator, funcInfo, true, false);
funcInfo->ReleaseLoc(expr->AsParseNodeBin()->pnode1);
byteCodeGenerator->Writer()->MarkLabel(leftTrue);
EmitBooleanExpression(expr->AsParseNodeBin()->pnode2, trueLabel, falseLabel, byteCodeGenerator, funcInfo, trueFallthrough, falseFallthrough);
funcInfo->ReleaseLoc(expr->AsParseNodeBin()->pnode2);
break;
}
case knopLogNot:
EmitBooleanExpression(expr->AsParseNodeUni()->pnode1, falseLabel, trueLabel, byteCodeGenerator, funcInfo, falseFallthrough, trueFallthrough);
funcInfo->ReleaseLoc(expr->AsParseNodeUni()->pnode1);
break;
case knopEq:
case knopEqv:
case knopNEqv:
case knopNe:
case knopLt:
case knopLe:
case knopGe:
case knopGt:
EmitBinaryOpnds(expr->AsParseNodeBin()->pnode1, expr->AsParseNodeBin()->pnode2, byteCodeGenerator, funcInfo);
funcInfo->ReleaseLoc(expr->AsParseNodeBin()->pnode2);
funcInfo->ReleaseLoc(expr->AsParseNodeBin()->pnode1);
byteCodeGenerator->Writer()->BrReg2(nopToOp[expr->nop], trueLabel, expr->AsParseNodeBin()->pnode1->location,
expr->AsParseNodeBin()->pnode2->location);
if (!falseFallthrough)
{
byteCodeGenerator->Writer()->Br(falseLabel);
}
break;
case knopTrue:
if (!trueFallthrough)
{
byteCodeGenerator->Writer()->Br(trueLabel);
}
break;
case knopFalse:
if (!falseFallthrough)
{
byteCodeGenerator->Writer()->Br(falseLabel);
}
break;
default:
// Note: we usually release the temp assigned to a node after we Emit it.
// But in this case, EmitBooleanExpression is just a wrapper around a normal Emit call,
// and the caller of EmitBooleanExpression expects to be able to release this register.
Emit(expr, byteCodeGenerator, funcInfo, false);
if (trueFallthrough)
{
byteCodeGenerator->Writer()->BrReg1(Js::OpCode::BrFalse_A, falseLabel, expr->location);
}
else
{
byteCodeGenerator->Writer()->BrReg1(Js::OpCode::BrTrue_A, trueLabel, expr->location);
if (!falseFallthrough)
{
byteCodeGenerator->Writer()->Br(falseLabel);
}
}
break;
}
byteCodeGenerator->EndStatement(expr);
}
void EmitGeneratingBooleanExpression(ParseNode *expr, Js::ByteCodeLabel trueLabel, bool truefallthrough, Js::ByteCodeLabel falseLabel, bool falsefallthrough, Js::RegSlot writeto,
bool reuseLoc, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo)
{
switch (expr->nop)
{
case knopLogOr:
{
byteCodeGenerator->StartStatement(expr);
Js::ByteCodeLabel leftFalse = byteCodeGenerator->Writer()->DefineLabel();
EmitGeneratingBooleanExpression(expr->AsParseNodeBin()->pnode1, trueLabel, false, leftFalse, true, writeto, reuseLoc, byteCodeGenerator, funcInfo);
funcInfo->ReleaseLoc(expr->AsParseNodeBin()->pnode1);
byteCodeGenerator->Writer()->MarkLabel(leftFalse);
EmitGeneratingBooleanExpression(expr->AsParseNodeBin()->pnode2, trueLabel, truefallthrough, falseLabel, falsefallthrough, writeto, true, byteCodeGenerator, funcInfo);
funcInfo->ReleaseLoc(expr->AsParseNodeBin()->pnode2);
byteCodeGenerator->EndStatement(expr);
break;
}
case knopLogAnd:
{
byteCodeGenerator->StartStatement(expr);
Js::ByteCodeLabel leftTrue = byteCodeGenerator->Writer()->DefineLabel();
EmitGeneratingBooleanExpression(expr->AsParseNodeBin()->pnode1, leftTrue, true, falseLabel, false, writeto, reuseLoc, byteCodeGenerator, funcInfo);
funcInfo->ReleaseLoc(expr->AsParseNodeBin()->pnode1);
byteCodeGenerator->Writer()->MarkLabel(leftTrue);
EmitGeneratingBooleanExpression(expr->AsParseNodeBin()->pnode2, trueLabel, truefallthrough, falseLabel, falsefallthrough, writeto, true, byteCodeGenerator, funcInfo);
funcInfo->ReleaseLoc(expr->AsParseNodeBin()->pnode2);
byteCodeGenerator->EndStatement(expr);
break;
}
case knopLogNot:
{
byteCodeGenerator->StartStatement(expr);
// this time we want a boolean expression, since Logical Not is nice and only returns true or false
Js::ByteCodeLabel emitTrue = byteCodeGenerator->Writer()->DefineLabel();
Js::ByteCodeLabel emitFalse = byteCodeGenerator->Writer()->DefineLabel();
EmitBooleanExpression(expr->AsParseNodeUni()->pnode1, emitFalse, emitTrue, byteCodeGenerator, funcInfo, false, true);
byteCodeGenerator->Writer()->MarkLabel(emitTrue);
byteCodeGenerator->Writer()->Reg1(reuseLoc ? Js::OpCode::LdTrue_ReuseLoc : Js::OpCode::LdTrue, writeto);
byteCodeGenerator->Writer()->Br(trueLabel);
byteCodeGenerator->Writer()->MarkLabel(emitFalse);
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdFalse_ReuseLoc, writeto);
if (!falsefallthrough)
{
byteCodeGenerator->Writer()->Br(falseLabel);
}
funcInfo->ReleaseLoc(expr->AsParseNodeUni()->pnode1);
byteCodeGenerator->EndStatement(expr);
break;
}
case knopEq:
case knopEqv:
case knopNEqv:
case knopNe:
case knopLt:
case knopLe:
case knopGe:
case knopGt:
byteCodeGenerator->StartStatement(expr);
EmitBinaryOpnds(expr->AsParseNodeBin()->pnode1, expr->AsParseNodeBin()->pnode2, byteCodeGenerator, funcInfo);
funcInfo->ReleaseLoc(expr->AsParseNodeBin()->pnode2);
funcInfo->ReleaseLoc(expr->AsParseNodeBin()->pnode1);
funcInfo->AcquireLoc(expr);
byteCodeGenerator->Writer()->Reg3(nopToCMOp[expr->nop], expr->location, expr->AsParseNodeBin()->pnode1->location,
expr->AsParseNodeBin()->pnode2->location);
byteCodeGenerator->Writer()->Reg2(reuseLoc ? Js::OpCode::Ld_A_ReuseLoc : Js::OpCode::Ld_A, writeto, expr->location);
// The inliner likes small bytecode
if (!(truefallthrough || falsefallthrough))
{
byteCodeGenerator->Writer()->BrReg1(Js::OpCode::BrTrue_A, trueLabel, expr->location);
byteCodeGenerator->Writer()->Br(falseLabel);
}
else if (truefallthrough && !falsefallthrough) {
byteCodeGenerator->Writer()->BrReg1(Js::OpCode::BrFalse_A, falseLabel, expr->location);
}
else if (falsefallthrough && !truefallthrough) {
byteCodeGenerator->Writer()->BrReg1(Js::OpCode::BrTrue_A, trueLabel, expr->location);
}
byteCodeGenerator->EndStatement(expr);
break;
case knopTrue:
byteCodeGenerator->StartStatement(expr);
byteCodeGenerator->Writer()->Reg1(reuseLoc ? Js::OpCode::LdTrue_ReuseLoc : Js::OpCode::LdTrue, writeto);
if (!truefallthrough)
{
byteCodeGenerator->Writer()->Br(trueLabel);
}
byteCodeGenerator->EndStatement(expr);
break;
case knopFalse:
byteCodeGenerator->StartStatement(expr);
byteCodeGenerator->Writer()->Reg1(reuseLoc ? Js::OpCode::LdFalse_ReuseLoc : Js::OpCode::LdFalse, writeto);
if (!falsefallthrough)
{
byteCodeGenerator->Writer()->Br(falseLabel);
}
byteCodeGenerator->EndStatement(expr);
break;
default:
// Note: we usually release the temp assigned to a node after we Emit it.
// But in this case, EmitBooleanExpression is just a wrapper around a normal Emit call,
// and the caller of EmitBooleanExpression expects to be able to release this register.
// For diagnostics purposes, register the name and dot to the statement list.
if (expr->nop == knopName || expr->nop == knopDot)
{
byteCodeGenerator->StartStatement(expr);
Emit(expr, byteCodeGenerator, funcInfo, false);
byteCodeGenerator->Writer()->Reg2(reuseLoc ? Js::OpCode::Ld_A_ReuseLoc : Js::OpCode::Ld_A, writeto, expr->location);
// The inliner likes small bytecode
if (!(truefallthrough || falsefallthrough))
{
byteCodeGenerator->Writer()->BrReg1(Js::OpCode::BrTrue_A, trueLabel, expr->location);
byteCodeGenerator->Writer()->Br(falseLabel);
}
else if (truefallthrough && !falsefallthrough) {
byteCodeGenerator->Writer()->BrReg1(Js::OpCode::BrFalse_A, falseLabel, expr->location);
}
else if (falsefallthrough && !truefallthrough) {
byteCodeGenerator->Writer()->BrReg1(Js::OpCode::BrTrue_A, trueLabel, expr->location);
}
byteCodeGenerator->EndStatement(expr);
}
else
{
Emit(expr, byteCodeGenerator, funcInfo, false);
byteCodeGenerator->Writer()->Reg2(reuseLoc ? Js::OpCode::Ld_A_ReuseLoc : Js::OpCode::Ld_A, writeto, expr->location);
// The inliner likes small bytecode
if (!(truefallthrough || falsefallthrough))
{
byteCodeGenerator->Writer()->BrReg1(Js::OpCode::BrTrue_A, trueLabel, expr->location);
byteCodeGenerator->Writer()->Br(falseLabel);
}
else if (truefallthrough && !falsefallthrough) {
byteCodeGenerator->Writer()->BrReg1(Js::OpCode::BrFalse_A, falseLabel, expr->location);
}
else if (falsefallthrough && !truefallthrough) {
byteCodeGenerator->Writer()->BrReg1(Js::OpCode::BrTrue_A, trueLabel, expr->location);
}
}
break;
}
}
// used by while and for loops
void EmitLoop(
ParseNodeStmt *loopNode,
ParseNode *cond,
ParseNode *body,
ParseNode *incr,
ByteCodeGenerator *byteCodeGenerator,
FuncInfo *funcInfo,
BOOL fReturnValue,
BOOL doWhile = FALSE,
ParseNodeBlock *forLoopBlock = nullptr)
{
// Need to increment loop count whether we are going to profile or not for HasLoop()
Js::ByteCodeLabel loopEntrance = byteCodeGenerator->Writer()->DefineLabel();
Js::ByteCodeLabel continuePastLoop = byteCodeGenerator->Writer()->DefineLabel();
uint loopId = byteCodeGenerator->Writer()->EnterLoop(loopEntrance);
byteCodeGenerator->PushJumpCleanupForLoop(loopNode, loopId);
if (doWhile)
{
Emit(body, byteCodeGenerator, funcInfo, fReturnValue);
funcInfo->ReleaseLoc(body);
if (loopNode->emitLabels)
{
byteCodeGenerator->Writer()->MarkLabel(loopNode->continueLabel);
}
if (!ByteCodeGenerator::IsFalse(cond) ||
byteCodeGenerator->IsInDebugMode())
{
EmitBooleanExpression(cond, loopEntrance, continuePastLoop, byteCodeGenerator, funcInfo, false, false);
}
funcInfo->ReleaseLoc(cond);
}
else
{
if (cond)
{
if (!(cond->nop == knopInt &&
cond->AsParseNodeInt()->lw != 0))
{
Js::ByteCodeLabel trueLabel = byteCodeGenerator->Writer()->DefineLabel();
EmitBooleanExpression(cond, trueLabel, continuePastLoop, byteCodeGenerator, funcInfo, true, false);
byteCodeGenerator->Writer()->MarkLabel(trueLabel);
}
funcInfo->ReleaseLoc(cond);
}
Emit(body, byteCodeGenerator, funcInfo, fReturnValue);
funcInfo->ReleaseLoc(body);
if (forLoopBlock != nullptr)
{
CloneEmitBlock(forLoopBlock, byteCodeGenerator, funcInfo);
}
if (loopNode->emitLabels)
{
byteCodeGenerator->Writer()->MarkLabel(loopNode->continueLabel);
}
if (incr != nullptr)
{
Emit(incr, byteCodeGenerator, funcInfo, false);
funcInfo->ReleaseLoc(incr);
}
byteCodeGenerator->Writer()->Br(loopEntrance);
}
byteCodeGenerator->Writer()->MarkLabel(continuePastLoop);
if (loopNode->emitLabels)
{
byteCodeGenerator->Writer()->MarkLabel(loopNode->breakLabel);
}
byteCodeGenerator->PopJumpCleanup();
byteCodeGenerator->Writer()->ExitLoop(loopId);
}
void ByteCodeGenerator::EmitInvertedLoop(ParseNodeStmt* outerLoop, ParseNodeFor* invertedLoop, FuncInfo* funcInfo)
{
Js::ByteCodeLabel invertedLoopLabel = this->m_writer.DefineLabel();
Js::ByteCodeLabel afterInvertedLoop = this->m_writer.DefineLabel();
// emit branch around original
Emit(outerLoop->AsParseNodeFor()->pnodeInit, this, funcInfo, false);
funcInfo->ReleaseLoc(outerLoop->AsParseNodeFor()->pnodeInit);
this->m_writer.BrS(Js::OpCode::BrNotHasSideEffects, invertedLoopLabel, Js::SideEffects_Any);
// emit original
EmitLoop(outerLoop, outerLoop->AsParseNodeFor()->pnodeCond, outerLoop->AsParseNodeFor()->pnodeBody,
outerLoop->AsParseNodeFor()->pnodeIncr, this, funcInfo, false);
// clear temporary registers since inverted loop may share nodes with
// emitted original loop
VisitClearTmpRegs(outerLoop, this, funcInfo);
// emit branch around inverted
this->m_writer.Br(afterInvertedLoop);
this->m_writer.MarkLabel(invertedLoopLabel);
// Emit a zero trip test for the original outer-loop if the outer-loop
// has a condition
if (outerLoop->AsParseNodeFor()->pnodeCond)
{
Js::ByteCodeLabel zeroTrip = this->m_writer.DefineLabel();
ParseNode* testNode = this->GetParser()->CopyPnode(outerLoop->AsParseNodeFor()->pnodeCond);
EmitBooleanExpression(testNode, zeroTrip, afterInvertedLoop, this, funcInfo, true, false);
this->m_writer.MarkLabel(zeroTrip);
funcInfo->ReleaseLoc(testNode);
}
// emit inverted
Emit(invertedLoop->pnodeInit, this, funcInfo, false);
funcInfo->ReleaseLoc(invertedLoop->pnodeInit);
EmitLoop(invertedLoop, invertedLoop->pnodeCond, invertedLoop->pnodeBody,
invertedLoop->pnodeIncr, this, funcInfo, false);
this->m_writer.MarkLabel(afterInvertedLoop);
}
void EmitGetIterator(
Js::RegSlot iteratorReg,
Js::RegSlot iterableReg,
ByteCodeGenerator* byteCodeGenerator,
FuncInfo* funcInfo)
{
EmitGetObjectMethod(
iteratorReg,
iterableReg,
Js::PropertyIds::_symbolIterator,
byteCodeGenerator,
funcInfo);
EmitFunctionCall(iteratorReg, iteratorReg, iterableReg, byteCodeGenerator, funcInfo);
EmitThrowOnNotObject(iteratorReg, byteCodeGenerator);
}
void EmitGetAsyncIterator(
Js::RegSlot resultReg,
Js::RegSlot iterableReg,
ByteCodeGenerator* byteCodeGenerator,
FuncInfo* funcInfo)
{
auto* writer = byteCodeGenerator->Writer();
Js::ByteCodeLabel noAsyncIterator = writer->DefineLabel();
EmitGetOptionalObjectMethod(
resultReg,
iterableReg,
Js::PropertyIds::_symbolAsyncIterator,
noAsyncIterator,
byteCodeGenerator,
funcInfo);
EmitFunctionCall(resultReg, resultReg, iterableReg, byteCodeGenerator, funcInfo);
EmitThrowOnNotObject(resultReg, byteCodeGenerator);
Js::ByteCodeLabel finished = writer->DefineLabel();
writer->Br(finished);
// Iterable does not have a Symbol.asyncIterator method: attempt to get a sync
// iterable and wrap it with an AsyncFromSyncIterator
writer->MarkLabel(noAsyncIterator);
Js::RegSlot iteratorReg = funcInfo->AcquireTmpRegister();
EmitGetIterator(iteratorReg, iterableReg, byteCodeGenerator, funcInfo);
writer->Reg2(Js::OpCode::NewAsyncFromSyncIterator, iteratorReg, iteratorReg);
writer->Reg2(Js::OpCode::Ld_A_ReuseLoc, resultReg, iteratorReg);
funcInfo->ReleaseTmpRegister(iteratorReg);
byteCodeGenerator->Writer()->MarkLabel(finished);
}
// Generating
// if (hasReturnFunction) {
// value = Call Retrun;
// if (value != Object)
// throw TypeError;
// }
void EmitIteratorClose(
Js::RegSlot iteratorLocation,
ByteCodeGenerator* byteCodeGenerator,
FuncInfo* funcInfo,
bool isAsync)
{
Js::RegSlot returnReg = funcInfo->AcquireTmpRegister();
Js::ByteCodeLabel noReturn = byteCodeGenerator->Writer()->DefineLabel();
EmitGetOptionalObjectMethod(
returnReg,
iteratorLocation,
Js::PropertyIds::return_,
noReturn,
byteCodeGenerator,
funcInfo);
EmitFunctionCall(returnReg, returnReg, iteratorLocation, byteCodeGenerator, funcInfo);
// In for-await or async yield* use await on the returned value
if (isAsync)
EmitAwait(returnReg, returnReg, byteCodeGenerator, funcInfo);
EmitThrowOnNotObject(returnReg, byteCodeGenerator);
funcInfo->ReleaseTmpRegister(returnReg);
byteCodeGenerator->Writer()->MarkLabel(noReturn);
}
void EmitForInOfLoopBody(ParseNodeForInOrForOf *loopNode,
Js::ByteCodeLabel loopEntrance,
Js::ByteCodeLabel continuePastLoop,
ByteCodeGenerator *byteCodeGenerator,
FuncInfo *funcInfo,
BOOL fReturnValue)
{
if (loopNode->pnodeLval->nop != knopVarDecl &&
loopNode->pnodeLval->nop != knopLetDecl &&
loopNode->pnodeLval->nop != knopConstDecl)
{
EmitReference(loopNode->pnodeLval, byteCodeGenerator, funcInfo);
}
else
{
Symbol * sym = loopNode->pnodeLval->AsParseNodeVar()->sym;
sym->SetNeedDeclaration(false);
}
BeginEmitBlock(loopNode->pnodeBlock, byteCodeGenerator, funcInfo);
EmitAssignment(nullptr, loopNode->pnodeLval, loopNode->itemLocation, byteCodeGenerator, funcInfo);
// The StartStatement is already done in the caller of this function.
byteCodeGenerator->EndStatement(loopNode->pnodeLval);
funcInfo->ReleaseReference(loopNode->pnodeLval);
Emit(loopNode->pnodeBody, byteCodeGenerator, funcInfo, fReturnValue);
funcInfo->ReleaseLoc(loopNode->pnodeBody);
EndEmitBlock(loopNode->pnodeBlock, byteCodeGenerator, funcInfo);
funcInfo->ReleaseTmpRegister(loopNode->itemLocation);
if (loopNode->emitLabels)
{
byteCodeGenerator->Writer()->MarkLabel(loopNode->continueLabel);
}
byteCodeGenerator->Writer()->Br(loopEntrance);
byteCodeGenerator->Writer()->MarkLabel(continuePastLoop);
if (loopNode->emitLabels)
{
byteCodeGenerator->Writer()->MarkLabel(loopNode->breakLabel);
}
}
void EmitForIn(ParseNodeForInOrForOf *loopNode,
Js::ByteCodeLabel loopEntrance,
Js::ByteCodeLabel continuePastLoop,
ByteCodeGenerator *byteCodeGenerator,
FuncInfo *funcInfo,
BOOL fReturnValue)
{
Assert(loopNode->nop == knopForIn);
Assert(loopNode->location == Js::Constants::NoRegister);
// Grab registers for the enumerator and for the current enumerated item.
// The enumerator register will be released after this call returns.
loopNode->itemLocation = funcInfo->AcquireTmpRegister();
uint forInLoopLevel = funcInfo->AcquireForInLoopLevel();
// get enumerator from the collection
byteCodeGenerator->Writer()->Reg1Unsigned1(Js::OpCode::InitForInEnumerator, loopNode->pnodeObj->location, forInLoopLevel);
// The StartStatement is already done in the caller of the current function, which is EmitForInOrForOf
byteCodeGenerator->EndStatement(loopNode);
// Need to increment loop count whether we are going into profile or not for HasLoop()
uint loopId = byteCodeGenerator->Writer()->EnterLoop(loopEntrance);
byteCodeGenerator->PushJumpCleanupForLoop(loopNode, loopId);
// The EndStatement will happen in the EmitForInOfLoopBody function
byteCodeGenerator->StartStatement(loopNode->pnodeLval);
// branch past loop when MoveAndGetNext returns nullptr
byteCodeGenerator->Writer()->BrReg1Unsigned1(Js::OpCode::BrOnEmpty, continuePastLoop, loopNode->itemLocation, forInLoopLevel);
EmitForInOfLoopBody(loopNode, loopEntrance, continuePastLoop, byteCodeGenerator, funcInfo, fReturnValue);
byteCodeGenerator->PopJumpCleanup();
byteCodeGenerator->Writer()->ExitLoop(loopId);
funcInfo->ReleaseForInLoopLevel(forInLoopLevel);
}
void EmitForInOrForOf(ParseNodeForInOrForOf *loopNode, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo, BOOL fReturnValue)
{
bool isForIn = (loopNode->nop == knopForIn);
bool isForAwaitOf = (loopNode->nop == knopForAwaitOf);
Assert(isForAwaitOf || isForIn || loopNode->nop == knopForOf);
BeginEmitBlock(loopNode->pnodeBlock, byteCodeGenerator, funcInfo);
byteCodeGenerator->StartStatement(loopNode);
if (!isForIn)
{
funcInfo->AcquireLoc(loopNode);
}
// Record the branch bytecode offset.
// This is used for "ignore exception" and "set next stmt" scenarios. See ProbeContainer::GetNextUserStatementOffsetForAdvance:
// If there is a branch recorded between current offset and next stmt offset, we'll use offset of the branch recorded,
// otherwise use offset of next stmt.
// The idea here is that when we bail out after ignore exception, we need to bail out to the beginning of the ForIn,
// but currently ForIn stmt starts at the condition part, which is needed for correct handling of break point on ForIn
// (break every time on the loop back edge) and correct display of current statement under debugger.
// See WinBlue 231880 for details.
byteCodeGenerator->Writer()->RecordStatementAdjustment(Js::FunctionBody::SAT_All);
if (loopNode->pnodeBlock->HasBlockScopedContent())
{
byteCodeGenerator->Writer()->RecordForInOrOfCollectionScope();
}
Js::ByteCodeLabel loopEntrance = byteCodeGenerator->Writer()->DefineLabel();
Js::ByteCodeLabel continuePastLoop = byteCodeGenerator->Writer()->DefineLabel();
if (loopNode->pnodeLval->nop == knopVarDecl)
{
EmitReference(loopNode->pnodeLval, byteCodeGenerator, funcInfo);
}
Emit(loopNode->pnodeObj, byteCodeGenerator, funcInfo, false); // evaluate collection expression
funcInfo->ReleaseLoc(loopNode->pnodeObj);
EndEmitBlock(loopNode->pnodeBlock, byteCodeGenerator, funcInfo);
if (loopNode->pnodeBlock->scope != nullptr)
{
loopNode->pnodeBlock->scope->ForEachSymbol([](Symbol *sym) {
sym->SetIsTrackedForDebugger(false);
});
}
if (isForIn)
{
EmitForIn(loopNode, loopEntrance, continuePastLoop, byteCodeGenerator, funcInfo, fReturnValue);
return;
}
Js::ByteCodeLabel skipThrow = byteCodeGenerator->Writer()->DefineLabel();
byteCodeGenerator->Writer()->BrReg2(Js::OpCode::BrNeq_A, skipThrow, loopNode->pnodeObj->location, funcInfo->undefinedConstantRegister);
byteCodeGenerator->Writer()->W1(Js::OpCode::RuntimeTypeError, SCODE_CODE(JSERR_ObjectCoercible));
byteCodeGenerator->Writer()->MarkLabel(skipThrow);
Js::RegSlot regException = Js::Constants::NoRegister;
Js::RegSlot regOffset = Js::Constants::NoRegister;
// These two temp variables track whether the return method of the source iterator should
// be called. One variable is used for catch block and one is used for finally block.
// Non-temps are used so that when jitting loop bodies, writes to these variables are not
// mistaken for dead stores.
Js::RegSlot shouldCallReturnFunctionLocation = loopNode->shouldCallReturnFunctionLocation;
Js::RegSlot shouldCallReturnFunctionLocationFinally = loopNode->shouldCallReturnFunctionLocationFinally;
bool isCoroutine = funcInfo->byteCodeFunction->IsCoroutine();
if (isCoroutine)
{
regException = funcInfo->AcquireTmpRegister();
regOffset = funcInfo->AcquireTmpRegister();
}
// Grab registers for the enumerator and for the current enumerated item.
// The enumerator register will be released after this call returns.
Js::RegSlot nextMethodReg = funcInfo->AcquireTmpRegister();
loopNode->itemLocation = funcInfo->AcquireTmpRegister();
// We want call profile information on the @@iterator call, so instead of adding a GetForOfIterator bytecode op
// to do all the following work in a helper do it explicitly in bytecode so that the @@iterator call is exposed
// to the profiler and JIT.
byteCodeGenerator->SetHasFinally(true);
byteCodeGenerator->SetHasTry(true);
byteCodeGenerator->TopFuncInfo()->byteCodeFunction->SetDontInline(true);
// do a ToObject on the collection
Js::RegSlot tmpObj = funcInfo->AcquireTmpRegister();
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Conv_Obj, tmpObj, loopNode->pnodeObj->location);
if (isForAwaitOf)
EmitGetAsyncIterator(loopNode->location, tmpObj, byteCodeGenerator, funcInfo);
else
EmitGetIterator(loopNode->location, tmpObj, byteCodeGenerator, funcInfo);
funcInfo->ReleaseTmpRegister(tmpObj);
EmitGetObjectMethod(
nextMethodReg,
loopNode->location,
Js::PropertyIds::next,
byteCodeGenerator,
funcInfo);
// The whole loop is surrounded with try..catch..finally - in order to capture the abrupt completion.
Js::ByteCodeLabel finallyLabel = byteCodeGenerator->Writer()->DefineLabel();
Js::ByteCodeLabel catchLabel = byteCodeGenerator->Writer()->DefineLabel();
byteCodeGenerator->Writer()->RecordCrossFrameEntryExitRecord(true);
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdFalse, shouldCallReturnFunctionLocation);
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdFalse, shouldCallReturnFunctionLocationFinally);
if (isCoroutine)
{
byteCodeGenerator->Writer()->BrReg2(Js::OpCode::TryFinallyWithYield, finallyLabel, regException, regOffset);
byteCodeGenerator->PushJumpCleanupForTry(
Js::OpCode::TryFinallyWithYield,
finallyLabel,
regException,
regOffset);
}
else
{
byteCodeGenerator->Writer()->Br(Js::OpCode::TryFinally, finallyLabel);
byteCodeGenerator->PushJumpCleanupForTry(Js::OpCode::TryFinally, finallyLabel);
}
byteCodeGenerator->Writer()->Br(Js::OpCode::TryCatch, catchLabel);
byteCodeGenerator->PushJumpCleanupForTry(Js::OpCode::TryCatch, catchLabel);
byteCodeGenerator->EndStatement(loopNode);
// Need to increment loop count whether we are going into profile or not for HasLoop()
uint loopId = byteCodeGenerator->Writer()->EnterLoop(loopEntrance);
byteCodeGenerator->PushJumpCleanupForLoop(loopNode, loopId);
byteCodeGenerator->StartStatement(loopNode->pnodeLval);
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdFalse, shouldCallReturnFunctionLocation);
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdFalse, shouldCallReturnFunctionLocationFinally);
// Call next on the iterator
EmitFunctionCall(
loopNode->itemLocation,
nextMethodReg,
loopNode->location,
byteCodeGenerator,
funcInfo);
// If this is a for-await-of then await the iterator next result
if (isForAwaitOf)
EmitAwait(loopNode->itemLocation, loopNode->itemLocation, byteCodeGenerator, funcInfo);
EmitThrowOnNotObject(loopNode->itemLocation, byteCodeGenerator);
Js::RegSlot doneLocation = funcInfo->AcquireTmpRegister();
EmitGetObjectProperty(
doneLocation,
loopNode->itemLocation,
Js::PropertyIds::done,
byteCodeGenerator,
funcInfo);
// Branch past loop if the result's done property is truthy
byteCodeGenerator->Writer()->BrReg1(Js::OpCode::BrTrue_A, continuePastLoop, doneLocation);
funcInfo->ReleaseTmpRegister(doneLocation);
// Otherwise put result's value property in itemLocation
EmitGetObjectProperty(
loopNode->itemLocation,
loopNode->itemLocation,
Js::PropertyIds::value,
byteCodeGenerator,
funcInfo);
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdTrue, shouldCallReturnFunctionLocation);
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdTrue, shouldCallReturnFunctionLocationFinally);
EmitForInOfLoopBody(loopNode, loopEntrance, continuePastLoop, byteCodeGenerator, funcInfo, fReturnValue);
byteCodeGenerator->PopJumpCleanup();
byteCodeGenerator->Writer()->ExitLoop(loopId);
funcInfo->ReleaseTmpRegister(nextMethodReg);
EmitIteratorCatchAndFinally(catchLabel,
finallyLabel,
loopNode->location,
shouldCallReturnFunctionLocation,
shouldCallReturnFunctionLocationFinally,
regException,
regOffset,
byteCodeGenerator,
funcInfo,
isForAwaitOf);
}
void EmitArrayLiteral(ParseNode *pnode, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo)
{
funcInfo->AcquireLoc(pnode);
ParseNode *args = pnode->AsParseNodeUni()->pnode1;
if (args == nullptr)
{
byteCodeGenerator->Writer()->Reg1Unsigned1(
pnode->AsParseNodeArrLit()->hasMissingValues ? Js::OpCode::NewScArrayWithMissingValues : Js::OpCode::NewScArray,
pnode->location,
ByteCodeGenerator::DefaultArraySize);
}
else
{
SetNewArrayElements(pnode, pnode->location, byteCodeGenerator, funcInfo);
}
}
void ByteCodeGenerator::EmitJumpCleanup(ParseNode* target, FuncInfo* funcInfo)
{
for (JumpCleanupList::Iterator iter(this->jumpCleanupList); iter.Next();)
{
const JumpCleanupInfo& info = iter.Data();
if (info.loopNode)
{
if (info.loopNode == target)
break;
#if ENABLE_PROFILE_INFO
if (Js::DynamicProfileInfo::EnableImplicitCallFlags(funcInfo->GetParsedFunctionBody()))
this->Writer()->Unsigned1(Js::OpCode::ProfiledLoopEnd, info.loopId);
#endif
}
else
{
Assert(
info.tryOp == Js::OpCode::TryCatch ||
info.tryOp == Js::OpCode::TryFinally ||
info.tryOp == Js::OpCode::TryFinallyWithYield ||
info.tryOp == Js::OpCode::ResumeCatch ||
info.tryOp == Js::OpCode::ResumeFinally);
// We insert OpCode::Leave when there is a 'return' inside try/catch/finally.
// This is for flow control and does not participate in identifying boundaries
// of try/catch blocks, thus we shouldn't call RecordCrossFrameEntryExitRecord
// here.
this->Writer()->Empty(Js::OpCode::Leave);
}
}
}
void EmitBinaryOpnds(ParseNode* pnode1, ParseNode* pnode2, ByteCodeGenerator* byteCodeGenerator, FuncInfo* funcInfo, Js::RegSlot computedPropertyLocation)
{
// If opnd2 can overwrite opnd1, make sure the value of opnd1 is stashed away.
if (MayHaveSideEffectOnNode(pnode1, pnode2, byteCodeGenerator))
{
SaveOpndValue(pnode1, funcInfo);
}
Emit(pnode1, byteCodeGenerator, funcInfo, false);
if (pnode1->nop == knopComputedName && computedPropertyLocation != Js::Constants::NoRegister)
{
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Conv_Prop, computedPropertyLocation, pnode1->location);
}
Emit(pnode2, byteCodeGenerator, funcInfo, false, false, computedPropertyLocation);
}
void EmitBinaryReference(ParseNode *pnode1, ParseNode *pnode2, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo, BOOL fLoadLhs)
{
// Make sure that the RHS of an assignment doesn't kill the opnd's of the expression on the LHS.
switch (pnode1->nop)
{
case knopName:
if (fLoadLhs && MayHaveSideEffectOnNode(pnode1, pnode2, byteCodeGenerator))
{
// Given x op y, y may kill x, so stash x.
// Note that this only matters if we're loading x prior to the op.
SaveOpndValue(pnode1, funcInfo);
}
break;
case knopDot:
if (fLoadLhs)
{
// We're loading the value of the LHS before the RHS, so make sure the LHS gets a register first.
funcInfo->AcquireLoc(pnode1);
}
if (MayHaveSideEffectOnNode(pnode1->AsParseNodeBin()->pnode1, pnode2, byteCodeGenerator))
{
// Given x.y op z, z may kill x, so stash x away.
SaveOpndValue(pnode1->AsParseNodeBin()->pnode1, funcInfo);
}
break;
case knopIndex:
if (fLoadLhs)
{
// We're loading the value of the LHS before the RHS, so make sure the LHS gets a register first.
funcInfo->AcquireLoc(pnode1);
}
if (MayHaveSideEffectOnNode(pnode1->AsParseNodeBin()->pnode1, pnode2, byteCodeGenerator) ||
MayHaveSideEffectOnNode(pnode1->AsParseNodeBin()->pnode1, pnode1->AsParseNodeBin()->pnode2, byteCodeGenerator))
{
// Given x[y] op z, y or z may kill x, so stash x away.
SaveOpndValue(pnode1->AsParseNodeBin()->pnode1, funcInfo);
}
if (MayHaveSideEffectOnNode(pnode1->AsParseNodeBin()->pnode2, pnode2, byteCodeGenerator))
{
// Given x[y] op z, z may kill y, so stash y away.
// But make sure that x gets a register before y.
funcInfo->AcquireLoc(pnode1->AsParseNodeBin()->pnode1);
SaveOpndValue(pnode1->AsParseNodeBin()->pnode2, funcInfo);
}
break;
}
if (fLoadLhs)
{
// Emit code to load the value of the LHS.
EmitLoad(pnode1, byteCodeGenerator, funcInfo);
}
else
{
// Emit code to evaluate the LHS opnds, but don't load the LHS's value.
EmitReference(pnode1, byteCodeGenerator, funcInfo);
}
// Evaluate the RHS.
Emit(pnode2, byteCodeGenerator, funcInfo, false);
}
void EmitUseBeforeDeclarationRuntimeError(ByteCodeGenerator * byteCodeGenerator, Js::RegSlot location)
{
byteCodeGenerator->Writer()->W1(Js::OpCode::RuntimeReferenceError, SCODE_CODE(JSERR_UseBeforeDeclaration));
if (location != Js::Constants::NoRegister)
{
// Optionally load something into register in order to do not confuse IRBuilder. This value will never be used.
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdUndef, location);
}
}
void EmitUseBeforeDeclaration(Symbol *sym, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo)
{
// Don't emit static use-before-declaration error in a closure or dynamic scope case. We detect such cases with dynamic checks,
// if necessary.
if (sym != nullptr &&
!sym->GetIsModuleExportStorage() &&
sym->GetNeedDeclaration() &&
byteCodeGenerator->GetCurrentScope()->HasStaticPathToAncestor(sym->GetScope()) &&
sym->GetScope()->GetFunc() == funcInfo)
{
EmitUseBeforeDeclarationRuntimeError(byteCodeGenerator, Js::Constants::NoRegister);
}
}
void EmitBinary(Js::OpCode opcode, ParseNode *pnode, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo)
{
byteCodeGenerator->StartStatement(pnode);
EmitBinaryOpnds(pnode->AsParseNodeBin()->pnode1, pnode->AsParseNodeBin()->pnode2, byteCodeGenerator, funcInfo);
funcInfo->ReleaseLoc(pnode->AsParseNodeBin()->pnode2);
funcInfo->ReleaseLoc(pnode->AsParseNodeBin()->pnode1);
funcInfo->AcquireLoc(pnode);
byteCodeGenerator->Writer()->Reg3(opcode,
pnode->location,
pnode->AsParseNodeBin()->pnode1->location,
pnode->AsParseNodeBin()->pnode2->location);
byteCodeGenerator->EndStatement(pnode);
}
bool CollectConcat(ParseNode *pnodeAdd, DListCounted<ParseNode *, ArenaAllocator>& concatOpnds, ArenaAllocator *arenaAllocator
#ifdef ENABLE_TEST_HOOKS
, bool Force32BitByteCode = false
#endif
)
{
Assert(pnodeAdd->nop == knopAdd);
Assert(pnodeAdd->CanFlattenConcatExpr());
bool doConcatString = false;
DList<ParseNode*, ArenaAllocator> pnodeStack(arenaAllocator);
pnodeStack.Prepend(pnodeAdd->AsParseNodeBin()->pnode2);
ParseNode * pnode = pnodeAdd->AsParseNodeBin()->pnode1;
while (true)
{
if (!pnode->CanFlattenConcatExpr())
{
concatOpnds.Append(pnode);
}
else if (pnode->nop == knopStr)
{
concatOpnds.Append(pnode);
// Detect if there are any string larger then the append size limit.
// If there are, we can do concat; otherwise, still use add so we will not lose the AddLeftDead opportunities.
#ifdef ENABLE_TEST_HOOKS
if (Force32BitByteCode)
{
doConcatString = doConcatString || (pnode->AsParseNodeStr()->pid->Cch() > 4);
}
else
{
doConcatString = doConcatString || !Js::CompoundString::ShouldAppendChars(pnode->AsParseNodeStr()->pid->Cch());
}
#else
doConcatString = doConcatString || !Js::CompoundString::ShouldAppendChars(pnode->AsParseNodeStr()->pid->Cch());
#endif
}
else
{
Assert(pnode->nop == knopAdd);
pnodeStack.Prepend(pnode->AsParseNodeBin()->pnode2);
pnode = pnode->AsParseNodeBin()->pnode1;
continue;
}
if (pnodeStack.Empty())
{
break;
}
pnode = pnodeStack.Head();
pnodeStack.RemoveHead();
}
return doConcatString;
}
void EmitConcat3(ParseNode *pnode, ParseNode *pnode1, ParseNode *pnode2, ParseNode *pnode3, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo)
{
byteCodeGenerator->StartStatement(pnode);
if (MayHaveSideEffectOnNode(pnode1, pnode2, byteCodeGenerator) || MayHaveSideEffectOnNode(pnode1, pnode3, byteCodeGenerator))
{
SaveOpndValue(pnode1, funcInfo);
}
if (MayHaveSideEffectOnNode(pnode2, pnode3, byteCodeGenerator))
{
SaveOpndValue(pnode2, funcInfo);
}
Emit(pnode1, byteCodeGenerator, funcInfo, false);
Emit(pnode2, byteCodeGenerator, funcInfo, false);
Emit(pnode3, byteCodeGenerator, funcInfo, false);
funcInfo->ReleaseLoc(pnode3);
funcInfo->ReleaseLoc(pnode2);
funcInfo->ReleaseLoc(pnode1);
funcInfo->AcquireLoc(pnode);
byteCodeGenerator->Writer()->Reg4(Js::OpCode::Concat3,
pnode->location,
pnode1->location,
pnode2->location,
pnode3->location);
byteCodeGenerator->EndStatement(pnode);
}
void EmitNewConcatStrMulti(ParseNode *pnode, uint8 count, ParseNode *pnode1, ParseNode *pnode2, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo)
{
EmitBinaryOpnds(pnode1, pnode2, byteCodeGenerator, funcInfo);
funcInfo->ReleaseLoc(pnode2);
funcInfo->ReleaseLoc(pnode1);
funcInfo->AcquireLoc(pnode);
byteCodeGenerator->Writer()->Reg3B1(Js::OpCode::NewConcatStrMulti,
pnode->location,
pnode1->location,
pnode2->location,
count);
}
void EmitAdd(ParseNode *pnode, ByteCodeGenerator *byteCodeGenerator, FuncInfo *funcInfo)
{
Assert(pnode->nop == knopAdd);
if (pnode->CanFlattenConcatExpr())
{
// We should only have a string concat if the feature is on.
Assert(!PHASE_OFF1(Js::ByteCodeConcatExprOptPhase));
DListCounted<ParseNode*, ArenaAllocator> concatOpnds(byteCodeGenerator->GetAllocator());
#ifdef ENABLE_TEST_HOOKS
bool doConcatString = CollectConcat(pnode, concatOpnds, byteCodeGenerator->GetAllocator(),
byteCodeGenerator->GetScriptContext()->GetConfig()->Force32BitByteCode());
#else
bool doConcatString = CollectConcat(pnode, concatOpnds, byteCodeGenerator->GetAllocator());
#endif
if (doConcatString)
{
uint concatCount = concatOpnds.Count();
Assert(concatCount >= 2);
// Don't do concatN if the number is too high
// CONSIDER: although we could have done multiple ConcatNs
if (concatCount > 2 && concatCount <= UINT8_MAX)
{
#if DBG
char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
#endif
ParseNode * pnode1 = concatOpnds.Head();
concatOpnds.RemoveHead();
ParseNode * pnode2 = concatOpnds.Head();
concatOpnds.RemoveHead();
if (concatCount == 3)
{
OUTPUT_TRACE_DEBUGONLY(Js::ByteCodeConcatExprOptPhase, _u("%s(%s) offset:#%d : Concat3\n"),
funcInfo->GetParsedFunctionBody()->GetDisplayName(),
funcInfo->GetParsedFunctionBody()->GetDebugNumberSet(debugStringBuffer),
byteCodeGenerator->Writer()->ByteCodeDataSize());
EmitConcat3(pnode, pnode1, pnode2, concatOpnds.Head(), byteCodeGenerator, funcInfo);
return;
}
OUTPUT_TRACE_DEBUGONLY(Js::ByteCodeConcatExprOptPhase, _u("%s(%s) offset:#%d: ConcatMulti %d\n"),
funcInfo->GetParsedFunctionBody()->GetDisplayName(),
funcInfo->GetParsedFunctionBody()->GetDebugNumberSet(debugStringBuffer),
byteCodeGenerator->Writer()->ByteCodeDataSize(), concatCount);
byteCodeGenerator->StartStatement(pnode);
funcInfo->AcquireLoc(pnode);
// CONSIDER: this may cause the backend not able CSE repeating pattern within the concat.
EmitNewConcatStrMulti(pnode, (uint8)concatCount, pnode1, pnode2, byteCodeGenerator, funcInfo);
uint i = 2;
do
{
ParseNode * currNode = concatOpnds.Head();
concatOpnds.RemoveHead();
ParseNode * currNode2 = concatOpnds.Head();
concatOpnds.RemoveHead();
EmitBinaryOpnds(currNode, currNode2, byteCodeGenerator, funcInfo);
funcInfo->ReleaseLoc(currNode2);
funcInfo->ReleaseLoc(currNode);
byteCodeGenerator->Writer()->Reg3B1(
Js::OpCode::SetConcatStrMultiItem2, pnode->location, currNode->location, currNode2->location, (uint8)i);
i += 2;
} while (concatOpnds.Count() > 1);
if (!concatOpnds.Empty())
{
ParseNode * currNode = concatOpnds.Head();
Emit(currNode, byteCodeGenerator, funcInfo, false);
funcInfo->ReleaseLoc(currNode);
byteCodeGenerator->Writer()->Reg2B1(
Js::OpCode::SetConcatStrMultiItem, pnode->location, currNode->location, (uint8)i);
i++;
}
Assert(concatCount == i);
byteCodeGenerator->EndStatement(pnode);
return;
}
}
// Since we collected all the node already, let's just emit them instead of doing it recursively.
byteCodeGenerator->StartStatement(pnode);
ParseNode * currNode = concatOpnds.Head();
concatOpnds.RemoveHead();
ParseNode * currNode2 = concatOpnds.Head();
concatOpnds.RemoveHead();
EmitBinaryOpnds(currNode, currNode2, byteCodeGenerator, funcInfo);
funcInfo->ReleaseLoc(currNode2);
funcInfo->ReleaseLoc(currNode);
Js::RegSlot dstReg = funcInfo->AcquireLoc(pnode);
byteCodeGenerator->Writer()->Reg3(
Js::OpCode::Add_A, dstReg, currNode->location, currNode2->location);
while (!concatOpnds.Empty())
{
currNode = concatOpnds.Head();
concatOpnds.RemoveHead();
Emit(currNode, byteCodeGenerator, funcInfo, false);
funcInfo->ReleaseLoc(currNode);
byteCodeGenerator->Writer()->Reg3(
Js::OpCode::Add_A, dstReg, dstReg, currNode->location);
}
byteCodeGenerator->EndStatement(pnode);
}
else
{
EmitBinary(Js::OpCode::Add_A, pnode, byteCodeGenerator, funcInfo);
}
}
void ByteCodeGenerator::EmitLeaveOpCodesBeforeYield()
{
for (JumpCleanupList::Iterator iter(this->jumpCleanupList); iter.Next();)
{
const JumpCleanupInfo& info = iter.Data();
switch (info.tryOp)
{
case Js::OpCode::TryFinallyWithYield:
this->Writer()->Empty(Js::OpCode::LeaveNull);
break;
case Js::OpCode::TryCatch:
case Js::OpCode::ResumeFinally:
case Js::OpCode::ResumeCatch:
this->Writer()->Empty(Js::OpCode::Leave);
break;
case Js::OpCode::Nop:
break;
default:
AssertMsg(false, "Unexpected OpCode in jumpCleanupList");
break;
}
}
}
void ByteCodeGenerator::EmitTryBlockHeadersAfterYield()
{
for (JumpCleanupList::Iterator iter(this->jumpCleanupList); iter.Prev();)
{
const JumpCleanupInfo& info = iter.Data();
switch (info.tryOp)
{
case Js::OpCode::TryCatch:
this->Writer()->Br(info.tryOp, info.label);
break;
case Js::OpCode::TryFinallyWithYield:
case Js::OpCode::ResumeFinally:
this->Writer()->BrReg2(info.tryOp, info.label, info.regSlot1, info.regSlot2);
break;
case Js::OpCode::ResumeCatch:
this->Writer()->Empty(info.tryOp);
break;
case Js::OpCode::Nop:
break;
default:
AssertMsg(false, "Unexpected OpCode in jumpCleanupList");
break;
}
}
}
void EmitYieldAndResume(
Js::RegSlot resumeValueReg,
Js::RegSlot inputReg,
Js::ByteCodeLabel resumeNormalLabel,
Js::ByteCodeLabel resumeThrowLabel,
ByteCodeGenerator* byteCodeGenerator,
FuncInfo* funcInfo)
{
// If `resultThrowLabel` is a valid label, then the instruction
// sequence following this call must handle the return yield resume
// case. Otherwise, the instruction sequence following this call
// must handle both the throw and return cases.
auto* writer = byteCodeGenerator->Writer();
// If in a loop mark it as containing Yield and hence not eligible for Jit loop body
writer->SetCurrentLoopHasYield();
if (inputReg != funcInfo->yieldRegister)
writer->Reg2(Js::OpCode::Ld_A, funcInfo->yieldRegister, inputReg);
// Yield the result. On resume, the resume yield object will be in yieldRegister
byteCodeGenerator->EmitLeaveOpCodesBeforeYield();
writer->Reg2(Js::OpCode::Yield, funcInfo->yieldRegister, funcInfo->yieldRegister);
byteCodeGenerator->EmitTryBlockHeadersAfterYield();
Js::RegSlot resumeKindReg = funcInfo->AcquireTmpRegister();
// Get the "kind" property of the resume object
EmitGetObjectProperty(
resumeKindReg,
funcInfo->yieldRegister,
Js::PropertyIds::kind,
byteCodeGenerator,
funcInfo);
// Get the "value" property of the resume object
EmitGetObjectProperty(
resumeValueReg,
funcInfo->yieldRegister,
Js::PropertyIds::value,
byteCodeGenerator,
funcInfo);
Js::RegSlot normalConst = funcInfo->constantToRegister.Lookup(
(uint)Js::ResumeYieldKind::Normal,
Js::Constants::NoRegister);
Assert(normalConst != Js::Constants::NoRegister);
// Branch to normal resume if kind is Normal
writer->BrReg2(Js::OpCode::BrSrEq_A, resumeNormalLabel, resumeKindReg, normalConst);
if (resumeThrowLabel != Js::Constants::NoByteCodeLabel)
{
Js::RegSlot throwConst = funcInfo->constantToRegister.Lookup(
(uint)Js::ResumeYieldKind::Throw,
Js::Constants::NoRegister);
Assert(throwConst != Js::Constants::NoRegister);
// Branch to throw if kind is Throw
writer->BrReg2(Js::OpCode::BrSrEq_A, resumeThrowLabel, resumeKindReg, throwConst);
}
funcInfo->ReleaseTmpRegister(resumeKindReg);
}
void EmitReturnFromYield(
Js::RegSlot resultReg,
ByteCodeGenerator* byteCodeGenerator,
FuncInfo* funcInfo)
{
auto* writer = byteCodeGenerator->Writer();
writer->Reg2(Js::OpCode::Ld_A, ByteCodeGenerator::ReturnRegister, resultReg);
byteCodeGenerator->EmitJumpCleanup(nullptr, funcInfo);
writer->Br(funcInfo->singleExit);
}
void EmitStartupYield(ByteCodeGenerator* byteCodeGenerator, FuncInfo* funcInfo)
{
// Generators are "paused" after evaluating the parameter list. A synthetic
// yield is inserted at the beginning of the function for this purpose. It
// has the following special properties: it can only be resumed with a "normal"
// completion (the other types are handled in the generator logic), the
// resume value is not observable to user code, and it cannot be contained
// within a try scope.
Assert(!byteCodeGenerator->HasJumpCleanup());
auto* writer = byteCodeGenerator->Writer();
writer->Reg1(Js::OpCode::LdUndef, funcInfo->yieldRegister);
writer->Reg2(Js::OpCode::Yield, funcInfo->yieldRegister, funcInfo->yieldRegister);
}
void EmitAwait(
Js::RegSlot resultReg,
Js::RegSlot inputReg,
ByteCodeGenerator* byteCodeGenerator,
FuncInfo* funcInfo)
{
auto writer = byteCodeGenerator->Writer();
writer->PatchableProperty(
Js::OpCode::StFld,
inputReg,
funcInfo->awaitRegister,
funcInfo->FindOrAddInlineCacheId(funcInfo->awaitRegister, Js::PropertyIds::value, false, true));
Js::ByteCodeLabel resumeNormal = writer->DefineLabel();
EmitYieldAndResume(
resultReg,
funcInfo->awaitRegister,
resumeNormal,
Js::Constants::NoByteCodeLabel,
byteCodeGenerator,
funcInfo);
// Throw case: throw the resume value
writer->Reg1(Js::OpCode::Throw, resultReg);
// Normal case: continue (value is already in result register)
writer->MarkLabel(resumeNormal);
}
void EmitCreateYieldResult(
Js::RegSlot resultReg,
Js::RegSlot inputReg,
ByteCodeGenerator* byteCodeGenerator,
FuncInfo* funcInfo)
{
Assert(resultReg != inputReg);
byteCodeGenerator->Writer()->Reg1(Js::OpCode::NewScObjectSimple, resultReg);
byteCodeGenerator->Writer()->PatchableProperty(
Js::OpCode::StFld,
inputReg,
resultReg,
funcInfo->FindOrAddInlineCacheId(resultReg, Js::PropertyIds::value, false, true));
byteCodeGenerator->Writer()->PatchableProperty(
Js::OpCode::StFld,
funcInfo->falseConstantRegister,
resultReg,
funcInfo->FindOrAddInlineCacheId(resultReg, Js::PropertyIds::done, false, true));
}
void EmitYield(
Js::RegSlot resultReg,
Js::RegSlot inputReg,
ByteCodeGenerator* byteCodeGenerator,
FuncInfo* funcInfo)
{
auto* writer = byteCodeGenerator->Writer();
Js::RegSlot yieldReg = funcInfo->yieldRegister;
EmitCreateYieldResult(yieldReg, inputReg, byteCodeGenerator, funcInfo);
Js::ByteCodeLabel resumeNormal = writer->DefineLabel();
Js::ByteCodeLabel resumeThrow = writer->DefineLabel();
EmitYieldAndResume(
resultReg,
yieldReg,
resumeNormal,
resumeThrow,
byteCodeGenerator,
funcInfo);
// Return case: set the return register value and branch to function exit
EmitReturnFromYield(resultReg, byteCodeGenerator, funcInfo);
// Throw case: throw the resume value
writer->MarkLabel(resumeThrow);
writer->Reg1(Js::OpCode::Throw, resultReg);
// Normal case: continue (value is already in result register)
writer->MarkLabel(resumeNormal);
}
void EmitYieldStar(
ParseNodeUni* yieldStarNode,
ByteCodeGenerator* byteCodeGenerator,
FuncInfo* funcInfo)
{
bool isAsync = funcInfo->IsAsyncGenerator();
auto* writer = byteCodeGenerator->Writer();
Js::ByteCodeLabel loopEntrance = writer->DefineLabel();
Js::ByteCodeLabel continueLoop = writer->DefineLabel();
Js::ByteCodeLabel continuePastLoop = writer->DefineLabel();
Js::ByteCodeLabel finishReturn = writer->DefineLabel();
Js::ByteCodeLabel finishNormal = writer->DefineLabel();
Js::RegSlot yieldStarReg = yieldStarNode->location;
// Initialize shouldReturn to false; this flag is used to indicate
// whether we should return or continue after the loop is complete
Js::RegSlot shouldReturnReg = funcInfo->AcquireTmpRegister();
writer->Reg1(Js::OpCode::LdFalse, shouldReturnReg);
// Evaluate operand and get the inner iterator
Js::RegSlot iteratorReg = funcInfo->AcquireTmpRegister();
ParseNode* operand = yieldStarNode->pnode1;
Emit(operand, byteCodeGenerator, funcInfo, false);
if (isAsync)
EmitGetAsyncIterator(iteratorReg, operand->location, byteCodeGenerator, funcInfo);
else
EmitGetIterator(iteratorReg, operand->location, byteCodeGenerator, funcInfo);
funcInfo->ReleaseLoc(operand);
// Load next method
Js::RegSlot nextMethodReg = funcInfo->AcquireTmpRegister();
EmitGetObjectMethod(
nextMethodReg,
iteratorReg,
Js::PropertyIds::next,
byteCodeGenerator,
funcInfo);
// Call the next method of iterator to obtain the first result
EmitFunctionCall(
yieldStarReg,
nextMethodReg,
iteratorReg,
funcInfo->undefinedConstantRegister,
byteCodeGenerator,
funcInfo);
// Begin loop
uint loopId = writer->EnterLoop(loopEntrance);
// If this is an async generator await the yielded value
if (isAsync)
EmitAwait(yieldStarReg, yieldStarReg, byteCodeGenerator, funcInfo);
EmitThrowOnNotObject(yieldStarReg, byteCodeGenerator);
// Get the iterator result's done property
Js::RegSlot doneReg = funcInfo->AcquireTmpRegister();
EmitGetObjectProperty(
doneReg,
yieldStarReg,
Js::PropertyIds::done,
byteCodeGenerator,
funcInfo);
// Break out of loop if the done property is truthy
writer->BrReg1(Js::OpCode::BrTrue_A, continuePastLoop, doneReg);
funcInfo->ReleaseTmpRegister(doneReg);
// Reset shouldReturn to false
writer->Reg1(Js::OpCode::LdFalse_ReuseLoc, shouldReturnReg);
Js::ByteCodeLabel resumeNormal = writer->DefineLabel();
Js::ByteCodeLabel resumeThrow = writer->DefineLabel();
Js::RegSlot resumeValueReg = funcInfo->AcquireTmpRegister();
if (isAsync)
{
// For async generators, extract the value property and wrap it in a
// new result object, so that exceptions thrown when getting the value
// property are handled appropriately
Js::RegSlot valueReg = funcInfo->AcquireTmpRegister();
EmitGetObjectProperty(
valueReg,
yieldStarReg,
Js::PropertyIds::value,
byteCodeGenerator,
funcInfo);
EmitCreateYieldResult(yieldStarReg, valueReg, byteCodeGenerator, funcInfo);
funcInfo->ReleaseTmpRegister(valueReg);
}
// Yield the next result to the caller
EmitYieldAndResume(
resumeValueReg,
yieldStarReg,
resumeNormal,
resumeThrow,
byteCodeGenerator,
funcInfo);
// Return case: set shouldReturn to true and attempt to call return
writer->Reg1(Js::OpCode::LdTrue_ReuseLoc, shouldReturnReg);
Js::RegSlot returnMethodReg = funcInfo->AcquireTmpRegister();
// Load return method
Js::ByteCodeLabel noReturnMethod = writer->DefineLabel();
EmitGetOptionalObjectMethod(
returnMethodReg,
iteratorReg,
Js::PropertyIds::return_,
noReturnMethod,
byteCodeGenerator,
funcInfo);
// The iterator has a return method: call method and loop
EmitFunctionCall(
yieldStarReg,
returnMethodReg,
iteratorReg,
resumeValueReg,
byteCodeGenerator,
funcInfo);
funcInfo->ReleaseTmpRegister(returnMethodReg);
writer->Br(continueLoop);
// The iterator does not have a return method: exit loop
writer->MarkLabel(noReturnMethod);
if (isAsync)
{
Js::RegSlot awaitValue = funcInfo->AcquireTmpRegister();
writer->Reg2(Js::OpCode::Ld_A, awaitValue, resumeValueReg);
EmitAwait(awaitValue, awaitValue, byteCodeGenerator, funcInfo);
writer->Reg2(Js::OpCode::Ld_A_ReuseLoc, yieldStarReg, awaitValue);
funcInfo->ReleaseTmpRegister(awaitValue);
}
else
writer->Reg2(Js::OpCode::Ld_A_ReuseLoc, yieldStarReg, resumeValueReg);
writer->Br(finishReturn);
// Throw case: attempt to call throw
writer->MarkLabel(resumeThrow);
Js::RegSlot throwMethodReg = funcInfo->AcquireTmpRegister();
Js::ByteCodeLabel noThrowMethod = writer->DefineLabel();
// Load throw method
EmitGetOptionalObjectMethod(
throwMethodReg,
iteratorReg,
Js::PropertyIds::throw_,
noThrowMethod,
byteCodeGenerator,
funcInfo);
// Iterator has throw method: call method and loop
EmitFunctionCall(
yieldStarReg,
throwMethodReg,
iteratorReg,
resumeValueReg,
byteCodeGenerator,
funcInfo);
funcInfo->ReleaseTmpRegister(throwMethodReg);
writer->Br(continueLoop);
// Iterator does not have a throw method; close the iterator and throw a TypeError
writer->MarkLabel(noThrowMethod);
EmitIteratorClose(iteratorReg, byteCodeGenerator, funcInfo, isAsync);
byteCodeGenerator->Writer()->W1(
Js::OpCode::RuntimeTypeError,
SCODE_CODE(JSERR_YieldStarThrowMissing));
// Normal case: call next method of iterator
writer->MarkLabel(resumeNormal);
EmitFunctionCall(
yieldStarReg,
nextMethodReg,
iteratorReg,
resumeValueReg,
byteCodeGenerator,
funcInfo);
funcInfo->ReleaseTmpRegister(resumeValueReg);
funcInfo->ReleaseTmpRegister(nextMethodReg);
funcInfo->ReleaseTmpRegister(iteratorReg);
// Loop end
writer->MarkLabel(continueLoop);
writer->Br(loopEntrance);
writer->MarkLabel(continuePastLoop);
writer->ExitLoop(loopId);
// Load the iterator result value into place
EmitGetObjectProperty(
yieldStarReg,
yieldStarReg,
Js::PropertyIds::value,
byteCodeGenerator,
funcInfo);
writer->BrReg1(Js::OpCode::BrFalse_A, finishNormal, shouldReturnReg);
funcInfo->ReleaseTmpRegister(shouldReturnReg);
// Return case: return the value from the generator
writer->MarkLabel(finishReturn);
EmitReturnFromYield(yieldStarReg, byteCodeGenerator, funcInfo);
// Normal case
writer->MarkLabel(finishNormal);
}
void EmitClass(ParseNodeClass * pnodeClass, ByteCodeGenerator * byteCodeGenerator, FuncInfo * funcInfo)
{
funcInfo->AcquireLoc(pnodeClass);
Assert(pnodeClass->pnodeConstructor);
pnodeClass->pnodeConstructor->location = pnodeClass->location;
Js::RegSlot protoLoc = funcInfo->AcquireTmpRegister();
BeginEmitBlock(pnodeClass->pnodeBlock, byteCodeGenerator, funcInfo);
Js::RegSlot frameDisplayLoc = funcInfo->frameDisplayRegister != Js::Constants::NoRegister ? funcInfo->frameDisplayRegister : funcInfo->GetEnvRegister();
Js::RegSlot tmpFrameDisplayLoc = funcInfo->AcquireTmpRegister();
frameDisplayLoc = byteCodeGenerator->PrependLocalScopes(frameDisplayLoc, tmpFrameDisplayLoc, funcInfo);
if (frameDisplayLoc != tmpFrameDisplayLoc)
{
funcInfo->ReleaseTmpRegister(tmpFrameDisplayLoc);
}
// Extends
if (pnodeClass->pnodeExtends)
{
Emit(pnodeClass->pnodeExtends, byteCodeGenerator, funcInfo, false);
byteCodeGenerator->StartStatement(pnodeClass->pnodeExtends);
Js::RegSlot ctorParentLoc = funcInfo->IsTmpReg(pnodeClass->pnodeExtends->location) ? pnodeClass->pnodeExtends->location : funcInfo->AcquireTmpRegister();
Js::RegSlot protoParentLoc = funcInfo->AcquireTmpRegister();
if (pnodeClass->pnodeExtends->location != ctorParentLoc)
{
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Ld_A_ReuseLoc, ctorParentLoc, pnodeClass->pnodeExtends->location);
}
Js::ByteCodeLabel needProto = byteCodeGenerator->Writer()->DefineLabel();
Js::ByteCodeLabel haveConstructor = byteCodeGenerator->Writer()->DefineLabel();
Js::ByteCodeLabel labelParentsFound = byteCodeGenerator->Writer()->DefineLabel();
byteCodeGenerator->Writer()->BrReg1(Js::OpCode::BrOnNotNullObj_A, needProto, ctorParentLoc);
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdC_A_Null, protoParentLoc);
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdBaseFncProto, ctorParentLoc);
byteCodeGenerator->Writer()->Br(labelParentsFound);
byteCodeGenerator->Writer()->MarkLabel(needProto);
byteCodeGenerator->Writer()->BrReg1(Js::OpCode::BrOnConstructor_A, haveConstructor, ctorParentLoc);
byteCodeGenerator->Writer()->W1(Js::OpCode::RuntimeTypeError, SCODE_CODE(JSERR_ErrorOnNew));
byteCodeGenerator->Writer()->MarkLabel(haveConstructor);
uint cacheId = funcInfo->FindOrAddInlineCacheId(ctorParentLoc, Js::PropertyIds::prototype, false, false);
byteCodeGenerator->Writer()->PatchableProperty(Js::OpCode::LdFld_ReuseLoc, protoParentLoc, ctorParentLoc, cacheId);
byteCodeGenerator->Writer()->BrReg1(Js::OpCode::BrOnObjectOrNull_A, labelParentsFound, protoParentLoc);
byteCodeGenerator->Writer()->W1(Js::OpCode::RuntimeTypeError, SCODE_CODE(JSERR_InvalidPrototype));
byteCodeGenerator->Writer()->MarkLabel(labelParentsFound);
if (frameDisplayLoc == funcInfo->frameDisplayRegister || frameDisplayLoc == funcInfo->GetEnvRegister())
{
byteCodeGenerator->Writer()->Reg4U(Js::OpCode::InitClass, pnodeClass->location, protoLoc, ctorParentLoc, protoParentLoc, pnodeClass->pnodeConstructor->nestedIndex);
}
else
{
byteCodeGenerator->Writer()->Reg5U(Js::OpCode::InitInnerClass, pnodeClass->location, protoLoc, ctorParentLoc, protoParentLoc, frameDisplayLoc, pnodeClass->pnodeConstructor->nestedIndex);
}
funcInfo->ReleaseTmpRegister(protoParentLoc);
funcInfo->ReleaseTmpRegister(ctorParentLoc);
byteCodeGenerator->EndStatement(pnodeClass->pnodeExtends);
}
else
{
if (frameDisplayLoc == funcInfo->frameDisplayRegister || frameDisplayLoc == funcInfo->GetEnvRegister())
{
byteCodeGenerator->Writer()->Reg2U(Js::OpCode::InitBaseClass, pnodeClass->location, protoLoc, pnodeClass->pnodeConstructor->nestedIndex);
}
else
{
byteCodeGenerator->Writer()->Reg3U(Js::OpCode::InitInnerBaseClass, pnodeClass->location, protoLoc, frameDisplayLoc, pnodeClass->pnodeConstructor->nestedIndex);
}
}
funcInfo->ReleaseTmpRegister(frameDisplayLoc);
// Methods
bool isObjectEmpty = true;
ParseNode * pnodeMembers = pnodeClass->pnodeMembers;
ParseNode * pnodeMember;
ParseNodeFnc * pnodeMemberFnc;
if (pnodeMembers != nullptr)
{
while (pnodeMembers->nop == knopList)
{
pnodeMember = pnodeMembers->AsParseNodeBin()->pnode1;
pnodeMemberFnc = pnodeMember->AsParseNodeBin()->pnode2->AsParseNodeFnc();
EmitMemberNode(pnodeMember, pnodeMemberFnc->IsStaticMember() ? pnodeClass->location : protoLoc, byteCodeGenerator, funcInfo, pnodeClass, /*useStore*/ false, &isObjectEmpty);
pnodeMembers = pnodeMembers->AsParseNodeBin()->pnode2;
}
pnodeMemberFnc = pnodeMembers->AsParseNodeBin()->pnode2->AsParseNodeFnc();
EmitMemberNode(pnodeMembers, pnodeMemberFnc->IsStaticMember() ? pnodeClass->location : protoLoc, byteCodeGenerator, funcInfo, pnodeClass, /*useStore*/ false, &isObjectEmpty);
}
funcInfo->ReleaseTmpRegister(protoLoc);
// Emit name binding.
if (pnodeClass->pnodeName)
{
Symbol * sym = pnodeClass->pnodeName->sym;
sym->SetNeedDeclaration(false);
byteCodeGenerator->EmitPropStore(pnodeClass->location, sym, nullptr, funcInfo, false, true);
}
EndEmitBlock(pnodeClass->pnodeBlock, byteCodeGenerator, funcInfo);
if (pnodeClass->pnodeDeclName)
{
Symbol * sym = pnodeClass->pnodeDeclName->sym;
sym->SetNeedDeclaration(false);
byteCodeGenerator->EmitPropStore(pnodeClass->location, sym, nullptr, funcInfo, true, false);
}
if (pnodeClass->IsDefaultModuleExport())
{
byteCodeGenerator->EmitAssignmentToDefaultModuleExport(pnodeClass, funcInfo);
}
}
void TrackIntConstantsOnGlobalUserObject(ByteCodeGenerator *byteCodeGenerator, bool isSymGlobalAndSingleAssignment, Js::PropertyId propertyId)
{
if (isSymGlobalAndSingleAssignment)
{
byteCodeGenerator->GetScriptContext()->TrackIntConstPropertyOnGlobalUserObject(propertyId);
}
}
void TrackIntConstantsOnGlobalObject(ByteCodeGenerator *byteCodeGenerator, bool isSymGlobalAndSingleAssignment, Js::PropertyId propertyId)
{
if (isSymGlobalAndSingleAssignment)
{
byteCodeGenerator->GetScriptContext()->TrackIntConstPropertyOnGlobalObject(propertyId);
}
}
void TrackIntConstantsOnGlobalObject(ByteCodeGenerator *byteCodeGenerator, Symbol *sym)
{
if (sym && sym->GetIsGlobal() && sym->IsAssignedOnce())
{
Js::PropertyId propertyId = sym->EnsurePosition(byteCodeGenerator);
byteCodeGenerator->GetScriptContext()->TrackIntConstPropertyOnGlobalObject(propertyId);
}
}
void TrackMemberNodesInObjectForIntConstants(ByteCodeGenerator *byteCodeGenerator, ParseNodePtr objNode)
{
Assert(objNode->nop == knopObject);
ParseNodePtr memberList = objNode->AsParseNodeUni()->pnode1;
while (memberList != nullptr)
{
ParseNodePtr memberNode = memberList->nop == knopList ? memberList->AsParseNodeBin()->pnode1 : memberList;
if (memberNode->nop != knopEllipsis)
{
ParseNodePtr memberNameNode = memberNode->AsParseNodeBin()->pnode1;
ParseNodePtr memberValNode = memberNode->AsParseNodeBin()->pnode2;
if (memberNameNode->nop != knopComputedName && memberValNode->nop == knopInt)
{
Js::PropertyId propertyId = memberNameNode->AsParseNodeStr()->pid->GetPropertyId();
TrackIntConstantsOnGlobalUserObject(byteCodeGenerator, true, propertyId);
}
}
memberList = memberList->nop == knopList ? memberList->AsParseNodeBin()->pnode2 : nullptr;
}
}
void TrackGlobalIntAssignmentsForknopDotProps(ParseNodePtr knopDotNode, ByteCodeGenerator * byteCodeGenerator)
{
Assert(knopDotNode->nop == knopDot);
ParseNodePtr objectNode = knopDotNode->AsParseNodeBin()->pnode1;
ParseNodeName * propertyNode = knopDotNode->AsParseNodeBin()->pnode2->AsParseNodeName();
bool isSymGlobalAndSingleAssignment = false;
if (objectNode->nop == knopName)
{
if (ByteCodeGenerator::IsThis(objectNode))
{
// Assume 'this' always refer to GlobalObject
// Cases like "this.a = "
isSymGlobalAndSingleAssignment = propertyNode->pid->IsSingleAssignment();
Js::PropertyId propertyId = propertyNode->PropertyIdFromNameNode();
TrackIntConstantsOnGlobalObject(byteCodeGenerator, isSymGlobalAndSingleAssignment, propertyId);
}
else
{
Symbol * sym = objectNode->AsParseNodeName()->sym;
isSymGlobalAndSingleAssignment = sym && sym->GetIsGlobal() && sym->IsAssignedOnce() && propertyNode->pid->IsSingleAssignment();
Js::PropertyId propertyId = propertyNode->PropertyIdFromNameNode();
TrackIntConstantsOnGlobalUserObject(byteCodeGenerator, isSymGlobalAndSingleAssignment, propertyId);
}
}
}
void TrackGlobalIntAssignments(ParseNodePtr pnode, ByteCodeGenerator * byteCodeGenerator)
{
// Track the Global Int Constant properties' assignments here.
uint nodeType = ParseNode::Grfnop(pnode->nop);
if (nodeType & fnopAsg)
{
if (nodeType & fnopBin)
{
ParseNodePtr lhs = pnode->AsParseNodeBin()->pnode1;
ParseNodePtr rhs = pnode->AsParseNodeBin()->pnode2;
Assert(lhs && rhs);
// Don't track other than integers and objects with member nodes.
if (rhs->nop == knopObject)
{
TrackMemberNodesInObjectForIntConstants(byteCodeGenerator, rhs);
}
else if (rhs->nop != knopInt &&
((rhs->nop != knopLsh && rhs->nop != knopRsh) || (rhs->AsParseNodeBin()->pnode1->nop != knopInt || rhs->AsParseNodeBin()->pnode2->nop != knopInt)))
{
return;
}
if (lhs->nop == knopName)
{
// Handle "a = <Integer>" cases here
Symbol * sym = lhs->AsParseNodeName()->sym;
TrackIntConstantsOnGlobalObject(byteCodeGenerator, sym);
}
else if (lhs->nop == knopDot && lhs->AsParseNodeBin()->pnode2->nop == knopName)
{
// Cases like "obj.a = <Integer>"
TrackGlobalIntAssignmentsForknopDotProps(lhs, byteCodeGenerator);
}
}
else if (nodeType & fnopUni)
{
ParseNodePtr lhs = pnode->AsParseNodeUni()->pnode1;
if (lhs->nop == knopName)
{
// Cases like "a++"
Symbol * sym = lhs->AsParseNodeName()->sym;
TrackIntConstantsOnGlobalObject(byteCodeGenerator, sym);
}
else if (lhs->nop == knopDot && lhs->AsParseNodeBin()->pnode2->nop == knopName)
{
// Cases like "obj.a++"
TrackGlobalIntAssignmentsForknopDotProps(lhs, byteCodeGenerator);
}
}
}
}
void Emit(ParseNode* pnode, ByteCodeGenerator* byteCodeGenerator, FuncInfo* funcInfo, BOOL fReturnValue, bool isConstructorCall, bool isTopLevel)
{
if (pnode == nullptr)
{
return;
}
ThreadContext::ProbeCurrentStackNoDispose(Js::Constants::MinStackByteCodeVisitor, byteCodeGenerator->GetScriptContext());
TrackGlobalIntAssignments(pnode, byteCodeGenerator);
// printNop(pnode->nop);
switch (pnode->nop)
{
case knopList:
EmitList(pnode, byteCodeGenerator, funcInfo);
break;
case knopInt:
// currently, these are loaded at the top
break;
// PTNODE(knopFlt , "flt const" ,None ,Flt ,fnopLeaf|fnopConst)
case knopFlt:
// currently, these are loaded at the top
break;
// PTNODE(knopStr , "str const" ,None ,Pid ,fnopLeaf|fnopConst)
case knopStr:
// TODO: protocol for combining string constants
break;
// PTNODE(knopRegExp , "reg expr" ,None ,Pid ,fnopLeaf|fnopConst)
case knopBigInt:
// PTNODE(knopBigInt , "bigint const" ,None ,Pid ,fnopLeaf|fnopConst)
break;
case knopRegExp:
funcInfo->GetParsedFunctionBody()->SetLiteralRegex(pnode->AsParseNodeRegExp()->regexPatternIndex, pnode->AsParseNodeRegExp()->regexPattern);
byteCodeGenerator->Writer()->Reg1Unsigned1(Js::OpCode::NewRegEx, funcInfo->AcquireLoc(pnode), pnode->AsParseNodeRegExp()->regexPatternIndex);
break;
// PTNODE(knopNull , "null" ,Null ,None ,fnopLeaf)
case knopNull:
// enregistered
break;
// PTNODE(knopFalse , "false" ,False ,None ,fnopLeaf)
case knopFalse:
// enregistered
break;
// PTNODE(knopTrue , "true" ,True ,None ,fnopLeaf)
case knopTrue:
// enregistered
break;
// PTNODE(knopEmpty , "empty" ,Empty ,None ,fnopLeaf)
case knopEmpty:
break;
// Unary operators.
// PTNODE(knopNot , "~" ,BitNot ,Uni ,fnopUni)
case knopNot:
STARTSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
Emit(pnode->AsParseNodeUni()->pnode1, byteCodeGenerator, funcInfo, false);
funcInfo->ReleaseLoc(pnode->AsParseNodeUni()->pnode1);
byteCodeGenerator->Writer()->Reg2(
Js::OpCode::Not_A, funcInfo->AcquireLoc(pnode), pnode->AsParseNodeUni()->pnode1->location);
ENDSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
break;
// PTNODE(knopNeg , "unary -" ,Neg ,Uni ,fnopUni)
case knopNeg:
STARTSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
Emit(pnode->AsParseNodeUni()->pnode1, byteCodeGenerator, funcInfo, false);
funcInfo->ReleaseLoc(pnode->AsParseNodeUni()->pnode1);
funcInfo->AcquireLoc(pnode);
byteCodeGenerator->Writer()->Reg2(
Js::OpCode::Neg_A, pnode->location, pnode->AsParseNodeUni()->pnode1->location);
ENDSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
break;
// PTNODE(knopPos , "unary +" ,Pos ,Uni ,fnopUni)
case knopPos:
STARTSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
Emit(pnode->AsParseNodeUni()->pnode1, byteCodeGenerator, funcInfo, false);
funcInfo->ReleaseLoc(pnode->AsParseNodeUni()->pnode1);
byteCodeGenerator->Writer()->Reg2(
Js::OpCode::Conv_Num, funcInfo->AcquireLoc(pnode), pnode->AsParseNodeUni()->pnode1->location);
ENDSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
break;
// PTNODE(knopLogNot , "!" ,LogNot ,Uni ,fnopUni)
case knopLogNot:
{
STARTSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
Js::ByteCodeLabel doneLabel = byteCodeGenerator->Writer()->DefineLabel();
// For boolean expressions that compute a result, we have to burn a register for the result
// so that the back end can identify it cheaply as a single temp lifetime. Revisit this if we do
// full-on renaming in the back end.
funcInfo->AcquireLoc(pnode);
if (pnode->AsParseNodeUni()->pnode1->nop == knopInt)
{
int32 value = pnode->AsParseNodeUni()->pnode1->AsParseNodeInt()->lw;
Js::OpCode op = value ? Js::OpCode::LdFalse : Js::OpCode::LdTrue;
byteCodeGenerator->Writer()->Reg1(op, pnode->location);
}
else
{
Emit(pnode->AsParseNodeUni()->pnode1, byteCodeGenerator, funcInfo, false);
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdFalse, pnode->location);
byteCodeGenerator->Writer()->BrReg1(Js::OpCode::BrTrue_A, doneLabel, pnode->AsParseNodeUni()->pnode1->location);
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdTrue_ReuseLoc, pnode->location);
byteCodeGenerator->Writer()->MarkLabel(doneLabel);
}
funcInfo->ReleaseLoc(pnode->AsParseNodeUni()->pnode1);
ENDSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
break;
}
// PTNODE(knopEllipsis , "..." ,Spread ,Uni , fnopUni)
case knopEllipsis:
{
Emit(pnode->AsParseNodeUni()->pnode1, byteCodeGenerator, funcInfo, false);
// Transparently pass the location of the object or array.
pnode->location = pnode->AsParseNodeUni()->pnode1->location;
break;
}
// PTNODE(knopIncPost , "post++" ,Inc ,Uni ,fnopUni|fnopAsg)
case knopIncPost:
case knopDecPost:
// FALL THROUGH to the faster pre-inc/dec case if the result of the expression is not needed.
if (pnode->isUsed || fReturnValue)
{
byteCodeGenerator->StartStatement(pnode);
bool isESBigIntEnabled = byteCodeGenerator->GetScriptContext()->GetConfig()->IsESBigIntEnabled();
Js::OpCode op1;
if (isESBigIntEnabled)
{
op1 = (pnode->nop == knopDecPost) ? Js::OpCode::Decr_Num_A : Js::OpCode::Incr_Num_A;
}
else
{
op1 = (pnode->nop == knopDecPost) ? Js::OpCode::Sub_A : Js::OpCode::Add_A;
}
ParseNode* pnode1 = pnode->AsParseNodeUni()->pnode1;
// Grab a register for the expression result.
funcInfo->AcquireLoc(pnode);
// Load the initial value, convert it (this is the expression result), and increment it.
EmitLoad(pnode1, byteCodeGenerator, funcInfo);
const Js::OpCode op2 = isESBigIntEnabled ? Js::OpCode::Conv_Numeric : Js::OpCode::Conv_Num;
byteCodeGenerator->Writer()->Reg2(op2, pnode->location, pnode1->location);
// Use temporary register if lhs cannot be assigned
Js::RegSlot incDecResult = pnode1->location;
if (funcInfo->RegIsConst(incDecResult) ||
(pnode1->nop == knopName && pnode1->AsParseNodeName()->sym && pnode1->AsParseNodeName()->sym->GetIsFuncExpr()))
{
incDecResult = funcInfo->AcquireTmpRegister();
}
if (isESBigIntEnabled)
{
byteCodeGenerator->Writer()->Reg2(op1, incDecResult, pnode->location);
}
else
{
Js::RegSlot oneReg = funcInfo->constantToRegister.LookupWithKey(1, Js::Constants::NoRegister);
Assert(oneReg != Js::Constants::NoRegister);
byteCodeGenerator->Writer()->Reg3(op1, incDecResult, pnode->location, oneReg);
}
// Store the incremented value.
EmitAssignment(nullptr, pnode1, incDecResult, byteCodeGenerator, funcInfo);
// Release the incremented value and the l-value.
if (incDecResult != pnode1->location)
{
funcInfo->ReleaseTmpRegister(incDecResult);
}
funcInfo->ReleaseLoad(pnode1);
byteCodeGenerator->EndStatement(pnode);
break;
}
else
{
pnode->nop = (pnode->nop == knopIncPost) ? knopIncPre : knopDecPre;
}
// FALL THROUGH to the fast pre-inc/dec case if the result of the expression is not needed.
// PTNODE(knopIncPre , "++ pre" ,Inc ,Uni ,fnopUni|fnopAsg)
case knopIncPre:
case knopDecPre:
{
byteCodeGenerator->StartStatement(pnode);
const Js::OpCode op = (pnode->nop == knopDecPre) ? Js::OpCode::Decr_A : Js::OpCode::Incr_A;
ParseNode* pnode1 = pnode->AsParseNodeUni()->pnode1;
// Assign a register for the result only if the result is used or the LHS can't be assigned to
// (i.e., is a constant).
const bool need_result_location =
pnode->isUsed
|| fReturnValue
|| funcInfo->RegIsConst(pnode1->location)
|| (pnode1->nop == knopName && pnode1->AsParseNodeName()->sym && pnode1->AsParseNodeName()->sym->GetIsFuncExpr());
if (need_result_location)
{
const Js::RegSlot result_location = funcInfo->AcquireLoc(pnode);
EmitLoad(pnode1, byteCodeGenerator, funcInfo);
byteCodeGenerator->Writer()->Reg2(op, result_location, pnode1->location);
// Store the incremented value and release the l-value.
EmitAssignment(nullptr, pnode1, result_location, byteCodeGenerator, funcInfo);
}
else
{
EmitLoad(pnode1, byteCodeGenerator, funcInfo);
byteCodeGenerator->Writer()->Reg2(op, pnode1->location, pnode1->location);
// Store the incremented value and release the l-value.
EmitAssignment(nullptr, pnode1, pnode1->location, byteCodeGenerator, funcInfo);
}
funcInfo->ReleaseLoad(pnode1);
byteCodeGenerator->EndStatement(pnode);
break;
}
// PTNODE(knopTypeof , "typeof" ,None ,Uni ,fnopUni)
case knopTypeof:
{
STARTSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
ParseNode* pnodeOpnd = pnode->AsParseNodeUni()->pnode1;
switch (pnodeOpnd->nop)
{
case knopDot:
{
Emit(pnodeOpnd->AsParseNodeBin()->pnode1, byteCodeGenerator, funcInfo, false);
Js::PropertyId propertyId = pnodeOpnd->AsParseNodeBin()->pnode2->AsParseNodeName()->PropertyIdFromNameNode();
Assert(pnodeOpnd->AsParseNodeBin()->pnode2->nop == knopName);
funcInfo->ReleaseLoc(pnodeOpnd->AsParseNodeBin()->pnode1);
funcInfo->AcquireLoc(pnode);
byteCodeGenerator->EmitTypeOfFld(funcInfo, propertyId, pnode->location, pnodeOpnd->AsParseNodeBin()->pnode1->location, Js::OpCode::LdFldForTypeOf);
break;
}
case knopIndex:
{
EmitBinaryOpnds(pnodeOpnd->AsParseNodeBin()->pnode1, pnodeOpnd->AsParseNodeBin()->pnode2, byteCodeGenerator, funcInfo);
funcInfo->ReleaseLoc(pnodeOpnd->AsParseNodeBin()->pnode2);
funcInfo->ReleaseLoc(pnodeOpnd->AsParseNodeBin()->pnode1);
funcInfo->AcquireLoc(pnode);
byteCodeGenerator->Writer()->Element(Js::OpCode::TypeofElem, pnode->location, pnodeOpnd->AsParseNodeBin()->pnode1->location, pnodeOpnd->AsParseNodeBin()->pnode2->location);
break;
}
case knopName:
{
ParseNodeName * pnodeNameOpnd = pnodeOpnd->AsParseNodeName();
if (pnodeNameOpnd->IsUserIdentifier())
{
funcInfo->AcquireLoc(pnode);
byteCodeGenerator->EmitPropTypeof(pnode->location, pnodeNameOpnd->sym, pnodeNameOpnd->pid, funcInfo);
break;
}
// Special names should fallthrough to default case
}
default:
Emit(pnodeOpnd, byteCodeGenerator, funcInfo, false);
funcInfo->ReleaseLoc(pnodeOpnd);
byteCodeGenerator->Writer()->Reg2(
Js::OpCode::Typeof, funcInfo->AcquireLoc(pnode), pnodeOpnd->location);
break;
}
ENDSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
break;
}
// PTNODE(knopVoid , "void" ,Void ,Uni ,fnopUni)
case knopVoid:
Emit(pnode->AsParseNodeUni()->pnode1, byteCodeGenerator, funcInfo, false);
funcInfo->ReleaseLoc(pnode->AsParseNodeUni()->pnode1);
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdUndef, funcInfo->AcquireLoc(pnode));
break;
// PTNODE(knopArray , "arr cnst" ,None ,Uni ,fnopUni)
case knopArray:
STARTSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
EmitArrayLiteral(pnode, byteCodeGenerator, funcInfo);
ENDSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
break;
// PTNODE(knopObject , "obj cnst" ,None ,Uni ,fnopUni)
case knopObject:
STARTSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
funcInfo->AcquireLoc(pnode);
EmitObjectInitializers(pnode->AsParseNodeUni()->pnode1, pnode->location, byteCodeGenerator, funcInfo);
ENDSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
break;
// PTNODE(knopComputedName, "[name]" ,None ,Uni ,fnopUni)
case knopComputedName:
Emit(pnode->AsParseNodeUni()->pnode1, byteCodeGenerator, funcInfo, false);
if (pnode->location == Js::Constants::NoRegister)
{
// The name is some expression with no home location. We can just re-use the register.
pnode->location = pnode->AsParseNodeUni()->pnode1->location;
}
else if (pnode->location != pnode->AsParseNodeUni()->pnode1->location)
{
// The name had to be protected from side-effects of the RHS.
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Ld_A, pnode->location, pnode->AsParseNodeUni()->pnode1->location);
}
break;
// Binary and Ternary Operators
case knopAdd:
EmitAdd(pnode, byteCodeGenerator, funcInfo);
break;
case knopSub:
case knopMul:
case knopExpo:
case knopDiv:
case knopMod:
case knopOr:
case knopXor:
case knopAnd:
case knopLsh:
case knopRsh:
case knopRs2:
case knopIn:
EmitBinary(nopToOp[pnode->nop], pnode, byteCodeGenerator, funcInfo);
break;
case knopInstOf:
{
STARTSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
EmitBinaryOpnds(pnode->AsParseNodeBin()->pnode1, pnode->AsParseNodeBin()->pnode2, byteCodeGenerator, funcInfo);
funcInfo->ReleaseLoc(pnode->AsParseNodeBin()->pnode2);
funcInfo->ReleaseLoc(pnode->AsParseNodeBin()->pnode1);
funcInfo->AcquireLoc(pnode);
uint cacheId = funcInfo->NewIsInstInlineCache();
byteCodeGenerator->Writer()->Reg3C(nopToOp[pnode->nop], pnode->location, pnode->AsParseNodeBin()->pnode1->location,
pnode->AsParseNodeBin()->pnode2->location, cacheId);
ENDSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
}
break;
case knopEq:
case knopEqv:
case knopNEqv:
case knopNe:
case knopLt:
case knopLe:
case knopGe:
case knopGt:
STARTSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
EmitBinaryOpnds(pnode->AsParseNodeBin()->pnode1, pnode->AsParseNodeBin()->pnode2, byteCodeGenerator, funcInfo);
funcInfo->ReleaseLoc(pnode->AsParseNodeBin()->pnode2);
funcInfo->ReleaseLoc(pnode->AsParseNodeBin()->pnode1);
funcInfo->AcquireLoc(pnode);
byteCodeGenerator->Writer()->Reg3(nopToCMOp[pnode->nop], pnode->location, pnode->AsParseNodeBin()->pnode1->location,
pnode->AsParseNodeBin()->pnode2->location);
ENDSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
break;
case knopNew:
{
EmitNew(pnode, byteCodeGenerator, funcInfo);
byteCodeGenerator->EndStatement(pnode);
break;
}
case knopDelete:
{
ParseNode *pexpr = pnode->AsParseNodeUni()->pnode1;
byteCodeGenerator->StartStatement(pnode);
switch (pexpr->nop)
{
case knopName:
{
ParseNodeName * pnodeName = pexpr->AsParseNodeName();
if (pnodeName->IsSpecialName())
{
funcInfo->AcquireLoc(pnode);
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdTrue, pnode->location);
}
else
{
funcInfo->AcquireLoc(pnode);
byteCodeGenerator->EmitPropDelete(pnode->location, pnodeName->sym, pnodeName->pid, funcInfo);
}
break;
}
case knopDot:
{
if (ByteCodeGenerator::IsSuper(pexpr->AsParseNodeBin()->pnode1))
{
byteCodeGenerator->Writer()->W1(Js::OpCode::RuntimeReferenceError, SCODE_CODE(JSERR_DeletePropertyWithSuper));
funcInfo->AcquireLoc(pnode);
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdUndef, pnode->location);
}
else
{
Emit(pexpr->AsParseNodeBin()->pnode1, byteCodeGenerator, funcInfo, false);
funcInfo->ReleaseLoc(pexpr->AsParseNodeBin()->pnode1);
Js::PropertyId propertyId = pexpr->AsParseNodeBin()->pnode2->AsParseNodeName()->PropertyIdFromNameNode();
funcInfo->AcquireLoc(pnode);
byteCodeGenerator->Writer()->Property(Js::OpCode::DeleteFld, pnode->location, pexpr->AsParseNodeBin()->pnode1->location,
funcInfo->FindOrAddReferencedPropertyId(propertyId), byteCodeGenerator->forceStrictModeForClassComputedPropertyName);
}
break;
}
case knopIndex:
{
EmitBinaryOpnds(pexpr->AsParseNodeBin()->pnode1, pexpr->AsParseNodeBin()->pnode2, byteCodeGenerator, funcInfo);
funcInfo->ReleaseLoc(pexpr->AsParseNodeBin()->pnode2);
funcInfo->ReleaseLoc(pexpr->AsParseNodeBin()->pnode1);
funcInfo->AcquireLoc(pnode);
byteCodeGenerator->Writer()->Element(Js::OpCode::DeleteElemI_A, pnode->location, pexpr->AsParseNodeBin()->pnode1->location, pexpr->AsParseNodeBin()->pnode2->location);
break;
}
default:
{
Emit(pexpr, byteCodeGenerator, funcInfo, false);
funcInfo->ReleaseLoc(pexpr);
byteCodeGenerator->Writer()->Reg2(
Js::OpCode::Delete_A, funcInfo->AcquireLoc(pnode), pexpr->location);
break;
}
}
byteCodeGenerator->EndStatement(pnode);
break;
}
case knopCall:
{
ParseNodeCall * pnodeCall = pnode->AsParseNodeCall();
byteCodeGenerator->StartStatement(pnodeCall);
if (pnodeCall->isSuperCall)
{
byteCodeGenerator->EmitSuperCall(funcInfo, pnodeCall->AsParseNodeSuperCall(), fReturnValue, /*fEvaluateComponents=*/ true);
}
else if (pnodeCall->pnodeTarget->nop == knopImport)
{
ParseNodePtr args = pnodeCall->pnodeArgs;
Assert(CountArguments(args) == 2); // import() takes one argument
Emit(args, byteCodeGenerator, funcInfo, false);
funcInfo->ReleaseLoc(args);
funcInfo->AcquireLoc(pnodeCall);
byteCodeGenerator->Writer()->Reg2(Js::OpCode::ImportCall, pnodeCall->location, args->location);
}
else
{
if (pnodeCall->isApplyCall && funcInfo->GetApplyEnclosesArgs())
{
// TODO[ianhall]: Can we remove the ApplyCall bytecode gen time optimization?
EmitApplyCall(pnodeCall, byteCodeGenerator, funcInfo, fReturnValue);
}
else
{
EmitCall(pnodeCall, byteCodeGenerator, funcInfo, fReturnValue, /*fEvaluateComponents*/ true);
}
}
byteCodeGenerator->EndStatement(pnode);
break;
}
case knopIndex:
{
STARTSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
EmitBinaryOpnds(pnode->AsParseNodeBin()->pnode1, pnode->AsParseNodeBin()->pnode2, byteCodeGenerator, funcInfo);
Js::RegSlot callObjLocation = pnode->AsParseNodeBin()->pnode1->location;
Js::RegSlot protoLocation = callObjLocation;
if (ByteCodeGenerator::IsSuper(pnode->AsParseNodeBin()->pnode1))
{
Emit(pnode->AsParseNodeSuperReference()->pnodeThis, byteCodeGenerator, funcInfo, false);
protoLocation = byteCodeGenerator->EmitLdObjProto(Js::OpCode::LdHomeObjProto, callObjLocation, funcInfo);
funcInfo->ReleaseLoc(pnode->AsParseNodeSuperReference()->pnodeThis);
}
funcInfo->ReleaseLoc(pnode->AsParseNodeBin()->pnode2);
funcInfo->ReleaseLoc(pnode->AsParseNodeBin()->pnode1);
funcInfo->AcquireLoc(pnode);
byteCodeGenerator->Writer()->Element(
Js::OpCode::LdElemI_A, pnode->location, protoLocation, pnode->AsParseNodeBin()->pnode2->location);
ENDSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
break;
}
// this is MemberExpression as rvalue
case knopDot:
{
Emit(pnode->AsParseNodeBin()->pnode1, byteCodeGenerator, funcInfo, false);
Js::RegSlot callObjLocation = pnode->AsParseNodeBin()->pnode1->location;
Js::RegSlot protoLocation = callObjLocation;
if (ByteCodeGenerator::IsSuper(pnode->AsParseNodeBin()->pnode1))
{
Emit(pnode->AsParseNodeSuperReference()->pnodeThis, byteCodeGenerator, funcInfo, false);
protoLocation = byteCodeGenerator->EmitLdObjProto(Js::OpCode::LdHomeObjProto, callObjLocation, funcInfo);
funcInfo->ReleaseLoc(pnode->AsParseNodeSuperReference()->pnodeThis);
}
funcInfo->ReleaseLoc(pnode->AsParseNodeBin()->pnode1);
funcInfo->AcquireLoc(pnode);
Js::PropertyId propertyId = pnode->AsParseNodeBin()->pnode2->AsParseNodeName()->PropertyIdFromNameNode();
uint cacheId = funcInfo->FindOrAddInlineCacheId(protoLocation, propertyId, false, false);
if (propertyId == Js::PropertyIds::length)
{
byteCodeGenerator->Writer()->PatchableProperty(Js::OpCode::LdLen_A, pnode->location, protoLocation, cacheId);
}
else if (pnode->IsCallApplyTargetLoad())
{
byteCodeGenerator->Writer()->PatchableProperty(Js::OpCode::LdFldForCallApplyTarget, pnode->location, protoLocation, cacheId);
}
else if (ByteCodeGenerator::IsSuper(pnode->AsParseNodeBin()->pnode1))
{
byteCodeGenerator->Writer()->PatchablePropertyWithThisPtr(Js::OpCode::LdSuperFld, pnode->location, protoLocation, pnode->AsParseNodeSuperReference()->pnodeThis->location, cacheId, isConstructorCall);
}
else
{
byteCodeGenerator->Writer()->PatchableProperty(Js::OpCode::LdFld, pnode->location, callObjLocation, cacheId, isConstructorCall);
}
break;
}
// PTNODE(knopAsg , "=" ,None ,Bin ,fnopBin|fnopAsg)
case knopAsg:
{
ParseNode *lhs = pnode->AsParseNodeBin()->pnode1;
ParseNode *rhs = pnode->AsParseNodeBin()->pnode2;
byteCodeGenerator->StartStatement(pnode);
if (pnode->isUsed || fReturnValue)
{
// If the assignment result is used, grab a register to hold it and pass it to EmitAssignment,
// which will copy the assigned value there.
funcInfo->AcquireLoc(pnode);
EmitBinaryReference(lhs, rhs, byteCodeGenerator, funcInfo, false);
EmitAssignment(pnode, lhs, rhs->location, byteCodeGenerator, funcInfo);
}
else
{
EmitBinaryReference(lhs, rhs, byteCodeGenerator, funcInfo, false);
EmitAssignment(nullptr, lhs, rhs->location, byteCodeGenerator, funcInfo);
}
funcInfo->ReleaseLoc(rhs);
if (!lhs->IsPattern())
{
funcInfo->ReleaseReference(lhs);
}
byteCodeGenerator->EndStatement(pnode);
break;
}
case knopName:
funcInfo->AcquireLoc(pnode);
if (ByteCodeGenerator::IsThis(pnode))
{
byteCodeGenerator->EmitPropLoadThis(pnode->location, pnode->AsParseNodeSpecialName(), funcInfo, true);
}
else
{
byteCodeGenerator->EmitPropLoad(pnode->location, pnode->AsParseNodeName()->sym, pnode->AsParseNodeName()->pid, funcInfo);
}
break;
case knopComma:
{
STARTSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
// The parser marks binary opnd pnodes as used, but value of the first opnd of a comma is not used.
// Easier to correct this here than to check every binary op in the parser.
ParseNode *pnode1 = pnode->AsParseNodeBin()->pnode1;
pnode1->isUsed = false;
if (pnode1->nop == knopComma)
{
// Spot fix for giant comma expressions that send us into OOS if we use a simple recursive
// algorithm. Instead of recursing on comma LHS's, iterate over them, pushing the RHS's onto
// a stack. (This suggests a model for removing recursion from Emit altogether...)
ArenaAllocator *alloc = byteCodeGenerator->GetAllocator();
SList<ParseNode *> rhsStack(alloc);
do
{
rhsStack.Push(pnode1->AsParseNodeBin()->pnode2);
pnode1 = pnode1->AsParseNodeBin()->pnode1;
pnode1->isUsed = false;
} while (pnode1->nop == knopComma);
Emit(pnode1, byteCodeGenerator, funcInfo, false);
while (!rhsStack.Empty())
{
ParseNode *pnodeRhs = rhsStack.Pop();
pnodeRhs->isUsed = false;
Emit(pnodeRhs, byteCodeGenerator, funcInfo, false);
funcInfo->ReleaseLoc(pnodeRhs);
}
}
else
{
Emit(pnode1, byteCodeGenerator, funcInfo, false);
}
funcInfo->ReleaseLoc(pnode1);
pnode->AsParseNodeBin()->pnode2->isUsed = pnode->isUsed || fReturnValue;
Emit(pnode->AsParseNodeBin()->pnode2, byteCodeGenerator, funcInfo, false);
funcInfo->ReleaseLoc(pnode->AsParseNodeBin()->pnode2);
funcInfo->AcquireLoc(pnode);
if (pnode->AsParseNodeBin()->pnode2->isUsed && pnode->location != pnode->AsParseNodeBin()->pnode2->location)
{
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Ld_A, pnode->location, pnode->AsParseNodeBin()->pnode2->location);
}
ENDSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
}
break;
// The binary logical ops && and || resolve to the value of the left-hand expression if its
// boolean value short-circuits the operation, and to the value of the right-hand expression
// otherwise. (In other words, the "truth" of the right-hand expression is never tested.)
// PTNODE(knopLogOr , "||" ,None ,Bin ,fnopBin)
case knopLogOr:
{
STARTSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
Js::ByteCodeLabel doneLabel = byteCodeGenerator->Writer()->DefineLabel();
// We use a single dest here for the whole generating boolean expr, because we were poorly
// optimizing the previous version where we had a dest for each level
funcInfo->AcquireLoc(pnode);
EmitGeneratingBooleanExpression(pnode, doneLabel, true, doneLabel, true, pnode->location, false, byteCodeGenerator, funcInfo);
byteCodeGenerator->Writer()->MarkLabel(doneLabel);
ENDSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
break;
}
// PTNODE(knopLogAnd , "&&" ,None ,Bin ,fnopBin)
case knopLogAnd:
{
STARTSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
Js::ByteCodeLabel doneLabel = byteCodeGenerator->Writer()->DefineLabel();
// We use a single dest here for the whole generating boolean expr, because we were poorly
// optimizing the previous version where we had a dest for each level
funcInfo->AcquireLoc(pnode);
EmitGeneratingBooleanExpression(pnode, doneLabel, true, doneLabel, true, pnode->location, false, byteCodeGenerator, funcInfo);
byteCodeGenerator->Writer()->MarkLabel(doneLabel);
ENDSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
break;
}
// The Coalescing operator resolves to the left hand side if it is not null or undefined
// In that case the right hand side is not evaluated
// If the left hand side is null or undefined it resolves to the right hand side
// PTNODE(knopCoalesce , "??" ,None ,Bin ,fnopBin)
case knopCoalesce:
{
STARTSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
Js::ByteCodeLabel doneLabel = byteCodeGenerator->Writer()->DefineLabel();
funcInfo->AcquireLoc(pnode);
// LHS
Emit(pnode->AsParseNodeBin()->pnode1, byteCodeGenerator, funcInfo, false);
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Ld_A, pnode->location, pnode->AsParseNodeBin()->pnode1->location);
funcInfo->ReleaseLoc(pnode->AsParseNodeBin()->pnode1);
// check for null/undefined with != null
byteCodeGenerator->Writer()->BrReg2(Js::OpCode::BrNeq_A, doneLabel, pnode->location, funcInfo->nullConstantRegister);
// RHS
Emit(pnode->AsParseNodeBin()->pnode2, byteCodeGenerator, funcInfo, false);
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Ld_A_ReuseLoc, pnode->location, pnode->AsParseNodeBin()->pnode2->location);
funcInfo->ReleaseLoc(pnode->AsParseNodeBin()->pnode2);
byteCodeGenerator->Writer()->MarkLabel(doneLabel);
ENDSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
break;
}
// PTNODE(knopQmark , "?" ,None ,Tri ,fnopBin)
case knopQmark:
{
Js::ByteCodeLabel trueLabel = byteCodeGenerator->Writer()->DefineLabel();
Js::ByteCodeLabel falseLabel = byteCodeGenerator->Writer()->DefineLabel();
Js::ByteCodeLabel skipLabel = byteCodeGenerator->Writer()->DefineLabel();
EmitBooleanExpression(pnode->AsParseNodeTri()->pnode1, trueLabel, falseLabel, byteCodeGenerator, funcInfo, true, false);
byteCodeGenerator->Writer()->MarkLabel(trueLabel);
funcInfo->ReleaseLoc(pnode->AsParseNodeTri()->pnode1);
// For boolean expressions that compute a result, we have to burn a register for the result
// so that the back end can identify it cheaply as a single temp lifetime. Revisit this if we do
// full-on renaming in the back end.
funcInfo->AcquireLoc(pnode);
Emit(pnode->AsParseNodeTri()->pnode2, byteCodeGenerator, funcInfo, false);
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Ld_A, pnode->location, pnode->AsParseNodeTri()->pnode2->location);
funcInfo->ReleaseLoc(pnode->AsParseNodeTri()->pnode2);
// Record the branch bytecode offset
byteCodeGenerator->Writer()->RecordStatementAdjustment(Js::FunctionBody::SAT_FromCurrentToNext);
byteCodeGenerator->Writer()->Br(skipLabel);
byteCodeGenerator->Writer()->MarkLabel(falseLabel);
Emit(pnode->AsParseNodeTri()->pnode3, byteCodeGenerator, funcInfo, false);
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Ld_A_ReuseLoc, pnode->location, pnode->AsParseNodeTri()->pnode3->location);
funcInfo->ReleaseLoc(pnode->AsParseNodeTri()->pnode3);
byteCodeGenerator->Writer()->MarkLabel(skipLabel);
break;
}
case knopAsgAdd:
case knopAsgSub:
case knopAsgMul:
case knopAsgDiv:
case knopAsgExpo:
case knopAsgMod:
case knopAsgAnd:
case knopAsgXor:
case knopAsgOr:
case knopAsgLsh:
case knopAsgRsh:
case knopAsgRs2:
{
byteCodeGenerator->StartStatement(pnode);
ParseNode *lhs = pnode->AsParseNodeBin()->pnode1;
ParseNode *rhs = pnode->AsParseNodeBin()->pnode2;
// Assign a register for the result only if the result is used or the LHS can't be assigned to
// (i.e., is a constant).
const bool need_result_location =
pnode->isUsed
|| fReturnValue
|| funcInfo->RegIsConst(lhs->location)
|| (lhs->nop == knopName && lhs->AsParseNodeName()->sym && lhs->AsParseNodeName()->sym->GetIsFuncExpr());
if (need_result_location)
{
const Js::RegSlot result_location = funcInfo->AcquireLoc(pnode);
// Grab a register for the initial value and load it.
EmitBinaryReference(lhs, rhs, byteCodeGenerator, funcInfo, true);
funcInfo->ReleaseLoc(rhs);
// Do the arithmetic, store the result, and release the l-value.
byteCodeGenerator->Writer()->Reg3(nopToOp[pnode->nop], result_location, lhs->location, rhs->location);
EmitAssignment(pnode, lhs, result_location, byteCodeGenerator, funcInfo);
}
else
{
// Grab a register for the initial value and load it. Might modify lhs->location.
EmitBinaryReference(lhs, rhs, byteCodeGenerator, funcInfo, true);
funcInfo->ReleaseLoc(rhs);
// Do the arithmetic, store the result, and release the l-value.
byteCodeGenerator->Writer()->Reg3(nopToOp[pnode->nop], lhs->location, lhs->location, rhs->location);
EmitAssignment(nullptr, lhs, lhs->location, byteCodeGenerator, funcInfo);
}
funcInfo->ReleaseLoad(lhs);
byteCodeGenerator->EndStatement(pnode);
break;
}
// General nodes.
// PTNODE(knopTempRef , "temp ref" ,None ,Uni ,fnopUni)
case knopTempRef:
// TODO: check whether mov is necessary
funcInfo->AcquireLoc(pnode);
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Ld_A, pnode->location, pnode->AsParseNodeUni()->pnode1->location);
break;
// PTNODE(knopTemp , "temp" ,None ,None ,fnopLeaf)
case knopTemp:
// Emit initialization code
if (pnode->AsParseNodeVar()->pnodeInit != nullptr)
{
byteCodeGenerator->StartStatement(pnode);
Emit(pnode->AsParseNodeVar()->pnodeInit, byteCodeGenerator, funcInfo, false);
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Ld_A, pnode->location, pnode->AsParseNodeVar()->pnodeInit->location);
funcInfo->ReleaseLoc(pnode->AsParseNodeVar()->pnodeInit);
byteCodeGenerator->EndStatement(pnode);
}
break;
// PTNODE(knopVarDecl , "varDcl" ,None ,Var ,fnopNone)
case knopVarDecl:
case knopConstDecl:
case knopLetDecl:
{
// Emit initialization code
ParseNodePtr initNode = pnode->AsParseNodeVar()->pnodeInit;
AssertMsg(pnode->nop != knopConstDecl || initNode != nullptr, "knopConstDecl expected to have an initializer");
if (initNode != nullptr || pnode->nop == knopLetDecl)
{
Symbol *sym = pnode->AsParseNodeVar()->sym;
Js::RegSlot rhsLocation;
byteCodeGenerator->StartStatement(pnode);
if (initNode != nullptr)
{
Emit(initNode, byteCodeGenerator, funcInfo, false);
rhsLocation = initNode->location;
if (initNode->nop == knopObject)
{
TrackMemberNodesInObjectForIntConstants(byteCodeGenerator, initNode);
}
else if (initNode->nop == knopInt)
{
TrackIntConstantsOnGlobalObject(byteCodeGenerator, sym);
}
}
else
{
Assert(pnode->nop == knopLetDecl);
rhsLocation = funcInfo->AcquireTmpRegister();
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdUndef, rhsLocation);
}
if (pnode->nop != knopVarDecl)
{
Assert(sym->GetDecl() == pnode || (sym->IsArguments() && !funcInfo->GetHasArguments()));
sym->SetNeedDeclaration(false);
}
EmitAssignment(nullptr, pnode, rhsLocation, byteCodeGenerator, funcInfo);
funcInfo->ReleaseTmpRegister(rhsLocation);
byteCodeGenerator->EndStatement(pnode);
}
break;
}
// PTNODE(knopFncDecl , "fncDcl" ,None ,Fnc ,fnopLeaf)
case knopFncDecl:
// The "function declarations" were emitted in DefineFunctions()
if (!pnode->AsParseNodeFnc()->IsDeclaration())
{
byteCodeGenerator->DefineOneFunction(pnode->AsParseNodeFnc(), funcInfo, false);
}
break;
// PTNODE(knopClassDecl, "class" ,None ,None ,fnopLeaf)
case knopClassDecl:
EmitClass(pnode->AsParseNodeClass(), byteCodeGenerator, funcInfo);
break;
case knopStrTemplate:
STARTSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
EmitStringTemplate(pnode->AsParseNodeStrTemplate(), byteCodeGenerator, funcInfo);
ENDSTATEMENET_IFTOPLEVEL(isTopLevel, pnode);
break;
case knopEndCode:
byteCodeGenerator->Writer()->RecordStatementAdjustment(Js::FunctionBody::SAT_All);
// load undefined for the fallthrough case:
if (!funcInfo->IsGlobalFunction())
{
if (funcInfo->IsClassConstructor())
{
// For class constructors, we need to explicitly load 'this' into the return register.
byteCodeGenerator->EmitClassConstructorEndCode(funcInfo);
}
else
{
// In the global function, implicit return values are copied to the return register, and if
// necessary the return register is initialized at the top. Don't clobber the value here.
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdUndef, ByteCodeGenerator::ReturnRegister);
}
}
// Label for non-fall-through return
byteCodeGenerator->Writer()->MarkLabel(funcInfo->singleExit);
if (funcInfo->GetHasCachedScope())
{
byteCodeGenerator->Writer()->Empty(Js::OpCode::CommitScope);
}
byteCodeGenerator->StartStatement(pnode);
byteCodeGenerator->Writer()->Empty(Js::OpCode::Ret);
byteCodeGenerator->EndStatement(pnode);
break;
// PTNODE(knopDebugger , "debugger" ,None ,None ,fnopNone)
case knopDebugger:
byteCodeGenerator->StartStatement(pnode);
byteCodeGenerator->Writer()->Empty(Js::OpCode::Break);
byteCodeGenerator->EndStatement(pnode);
break;
// PTNODE(knopFor , "for" ,None ,For ,fnopBreak|fnopContinue)
case knopFor:
{
ParseNodeFor * pnodeFor = pnode->AsParseNodeFor();
if (pnodeFor->pnodeInverted != nullptr)
{
byteCodeGenerator->EmitInvertedLoop(pnodeFor, pnodeFor->pnodeInverted, funcInfo);
}
else
{
BeginEmitBlock(pnodeFor->pnodeBlock, byteCodeGenerator, funcInfo);
Emit(pnodeFor->pnodeInit, byteCodeGenerator, funcInfo, false);
funcInfo->ReleaseLoc(pnodeFor->pnodeInit);
CloneEmitBlock(pnodeFor->pnodeBlock, byteCodeGenerator, funcInfo);
EmitLoop(pnodeFor,
pnodeFor->pnodeCond,
pnodeFor->pnodeBody,
pnodeFor->pnodeIncr,
byteCodeGenerator,
funcInfo,
fReturnValue,
FALSE,
pnodeFor->pnodeBlock);
EndEmitBlock(pnodeFor->pnodeBlock, byteCodeGenerator, funcInfo);
}
break;
}
// PTNODE(knopIf , "if" ,None ,If ,fnopNone)
case knopIf:
{
ParseNodeIf * pnodeIf = pnode->AsParseNodeIf();
byteCodeGenerator->StartStatement(pnodeIf);
Js::ByteCodeLabel trueLabel = byteCodeGenerator->Writer()->DefineLabel();
Js::ByteCodeLabel falseLabel = byteCodeGenerator->Writer()->DefineLabel();
EmitBooleanExpression(pnodeIf->pnodeCond, trueLabel, falseLabel, byteCodeGenerator, funcInfo, true, false);
funcInfo->ReleaseLoc(pnodeIf->pnodeCond);
byteCodeGenerator->EndStatement(pnodeIf);
byteCodeGenerator->Writer()->MarkLabel(trueLabel);
Emit(pnodeIf->pnodeTrue, byteCodeGenerator, funcInfo, fReturnValue);
funcInfo->ReleaseLoc(pnodeIf->pnodeTrue);
if (pnodeIf->pnodeFalse != nullptr)
{
// has else clause
Js::ByteCodeLabel skipLabel = byteCodeGenerator->Writer()->DefineLabel();
// Record the branch bytecode offset
byteCodeGenerator->Writer()->RecordStatementAdjustment(Js::FunctionBody::SAT_FromCurrentToNext);
// then clause skips else clause
byteCodeGenerator->Writer()->Br(skipLabel);
// generate code for else clause
byteCodeGenerator->Writer()->MarkLabel(falseLabel);
Emit(pnodeIf->pnodeFalse, byteCodeGenerator, funcInfo, fReturnValue);
funcInfo->ReleaseLoc(pnodeIf->pnodeFalse);
byteCodeGenerator->Writer()->MarkLabel(skipLabel);
}
else
{
byteCodeGenerator->Writer()->MarkLabel(falseLabel);
}
if (pnodeIf->emitLabels)
{
byteCodeGenerator->Writer()->MarkLabel(pnodeIf->breakLabel);
}
break;
}
case knopWhile:
{
ParseNodeWhile * pnodeWhile = pnode->AsParseNodeWhile();
EmitLoop(pnodeWhile,
pnodeWhile->pnodeCond,
pnodeWhile->pnodeBody,
nullptr,
byteCodeGenerator,
funcInfo,
fReturnValue);
break;
}
// PTNODE(knopDoWhile , "do-while" ,None ,While,fnopBreak|fnopContinue)
case knopDoWhile:
{
ParseNodeWhile * pnodeWhile = pnode->AsParseNodeWhile();
EmitLoop(pnodeWhile,
pnodeWhile->pnodeCond,
pnodeWhile->pnodeBody,
nullptr,
byteCodeGenerator,
funcInfo,
fReturnValue,
true);
break;
}
// PTNODE(knopForIn , "for in" ,None ,ForIn,fnopBreak|fnopContinue|fnopCleanup)
case knopForIn:
EmitForInOrForOf(pnode->AsParseNodeForInOrForOf(), byteCodeGenerator, funcInfo, fReturnValue);
break;
case knopForAwaitOf:
case knopForOf:
EmitForInOrForOf(pnode->AsParseNodeForInOrForOf(), byteCodeGenerator, funcInfo, fReturnValue);
break;
// PTNODE(knopReturn , "return" ,None ,Uni ,fnopNone)
case knopReturn:
{
ParseNodeReturn * pnodeReturn = pnode->AsParseNodeReturn();
byteCodeGenerator->StartStatement(pnodeReturn);
if (pnodeReturn->pnodeExpr != nullptr)
{
if (pnodeReturn->pnodeExpr->location == Js::Constants::NoRegister)
{
// No need to burn a register for the return value. If we need a temp, use R0 directly.
pnodeReturn->pnodeExpr->location = ByteCodeGenerator::ReturnRegister;
}
Emit(pnodeReturn->pnodeExpr, byteCodeGenerator, funcInfo, fReturnValue);
if (funcInfo->IsAsyncGenerator())
{
EmitAwait(
ByteCodeGenerator::ReturnRegister,
pnodeReturn->pnodeExpr->location,
byteCodeGenerator,
funcInfo);
}
else if (pnodeReturn->pnodeExpr->location != ByteCodeGenerator::ReturnRegister)
{
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Ld_A, ByteCodeGenerator::ReturnRegister, pnodeReturn->pnodeExpr->location);
}
funcInfo->GetParsedFunctionBody()->SetHasNoExplicitReturnValue(false);
}
else
{
byteCodeGenerator->Writer()->Reg1(Js::OpCode::LdUndef, ByteCodeGenerator::ReturnRegister);
}
if (funcInfo->IsClassConstructor())
{
// return expr; // becomes like below:
//
// if (IsObject(expr)) {
// return expr;
// } else if (IsBaseClassConstructor) {
// return this;
// } else if (!IsUndefined(expr)) {
// throw TypeError;
// }
Js::ByteCodeLabel returnExprLabel = byteCodeGenerator->Writer()->DefineLabel();
byteCodeGenerator->Writer()->BrReg1(Js::OpCode::BrOnObject_A, returnExprLabel, ByteCodeGenerator::ReturnRegister);
if (funcInfo->IsBaseClassConstructor())
{
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Ld_A, ByteCodeGenerator::ReturnRegister, funcInfo->GetThisSymbol()->GetLocation());
}
else
{
Js::ByteCodeLabel returnThisLabel = byteCodeGenerator->Writer()->DefineLabel();
byteCodeGenerator->Writer()->BrReg2(Js::OpCode::BrSrEq_A, returnThisLabel, ByteCodeGenerator::ReturnRegister, funcInfo->undefinedConstantRegister);
byteCodeGenerator->Writer()->W1(Js::OpCode::RuntimeTypeError, SCODE_CODE(JSERR_ClassDerivedConstructorInvalidReturnType));
byteCodeGenerator->Writer()->MarkLabel(returnThisLabel);
byteCodeGenerator->EmitClassConstructorEndCode(funcInfo);
}
byteCodeGenerator->Writer()->MarkLabel(returnExprLabel);
}
if (pnodeReturn->grfnop & fnopCleanup)
{
byteCodeGenerator->EmitJumpCleanup(nullptr, funcInfo);
}
byteCodeGenerator->Writer()->Br(funcInfo->singleExit);
byteCodeGenerator->EndStatement(pnodeReturn);
break;
}
// PTNODE(knopBlock , "{}" ,None ,Block,fnopNone)
case knopBlock:
{
ParseNodeBlock * pnodeBlock = pnode->AsParseNodeBlock();
if (pnodeBlock->pnodeStmt != nullptr)
{
EmitBlock(pnodeBlock, byteCodeGenerator, funcInfo, fReturnValue);
if (pnodeBlock->emitLabels)
{
byteCodeGenerator->Writer()->MarkLabel(pnodeBlock->breakLabel);
}
}
break;
}
// PTNODE(knopWith , "with" ,None ,With ,fnopCleanup)
case knopWith:
{
ParseNodeWith * pnodeWith = pnode->AsParseNodeWith();
Assert(pnodeWith->pnodeObj != nullptr);
byteCodeGenerator->StartStatement(pnodeWith);
// Copy the with object to a temp register (the location assigned to pnode) so that if the with object
// is overwritten in the body, the lookups are not affected.
funcInfo->AcquireLoc(pnodeWith);
Emit(pnodeWith->pnodeObj, byteCodeGenerator, funcInfo, false);
Js::RegSlot regVal = funcInfo->AcquireTmpRegister();
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Conv_Obj, regVal, pnodeWith->pnodeObj->location);
byteCodeGenerator->Writer()->Reg2(Js::OpCode::NewUnscopablesWrapperObject, pnodeWith->location, regVal);
byteCodeGenerator->EndStatement(pnodeWith);
#ifdef PERF_HINT
if (PHASE_TRACE1(Js::PerfHintPhase))
{
WritePerfHint(PerfHints::HasWithBlock, funcInfo->byteCodeFunction->GetFunctionBody(), byteCodeGenerator->Writer()->GetCurrentOffset() - 1);
}
#endif
if (pnodeWith->pnodeBody != nullptr)
{
Scope *scope = pnodeWith->scope;
scope->SetLocation(pnodeWith->location);
byteCodeGenerator->PushScope(scope);
Js::DebuggerScope *debuggerScope = byteCodeGenerator->RecordStartScopeObject(pnodeWith, Js::DiagExtraScopesType::DiagWithScope, regVal);
if (byteCodeGenerator->ShouldTrackDebuggerMetadata())
{
byteCodeGenerator->Writer()->AddPropertyToDebuggerScope(debuggerScope, regVal, Js::Constants::NoProperty, /*shouldConsumeRegister*/ true, Js::DebuggerScopePropertyFlags_WithObject);
}
Emit(pnodeWith->pnodeBody, byteCodeGenerator, funcInfo, fReturnValue);
funcInfo->ReleaseLoc(pnodeWith->pnodeBody);
byteCodeGenerator->PopScope();
byteCodeGenerator->RecordEndScopeObject(pnodeWith);
}
if (pnodeWith->emitLabels)
{
byteCodeGenerator->Writer()->MarkLabel(pnodeWith->breakLabel);
}
funcInfo->ReleaseTmpRegister(regVal);
funcInfo->ReleaseLoc(pnodeWith->pnodeObj);
break;
}
// PTNODE(knopBreak , "break" ,None ,Jump ,fnopNone)
case knopBreak:
{
ParseNodeJump * pnodeJump = pnode->AsParseNodeJump();
Assert(pnodeJump->pnodeTarget->emitLabels);
byteCodeGenerator->StartStatement(pnodeJump);
if (pnodeJump->grfnop & fnopCleanup)
{
byteCodeGenerator->EmitJumpCleanup(pnodeJump->pnodeTarget, funcInfo);
}
byteCodeGenerator->Writer()->Br(pnodeJump->pnodeTarget->breakLabel);
if (pnodeJump->emitLabels)
{
byteCodeGenerator->Writer()->MarkLabel(pnodeJump->breakLabel);
}
byteCodeGenerator->EndStatement(pnodeJump);
break;
}
case knopContinue:
{
ParseNodeJump * pnodeJump = pnode->AsParseNodeJump();
Assert(pnodeJump->pnodeTarget->emitLabels);
byteCodeGenerator->StartStatement(pnodeJump);
if (pnodeJump->grfnop & fnopCleanup)
{
byteCodeGenerator->EmitJumpCleanup(pnodeJump->pnodeTarget, funcInfo);
}
byteCodeGenerator->Writer()->Br(pnodeJump->pnodeTarget->continueLabel);
byteCodeGenerator->EndStatement(pnodeJump);
break;
}
// PTNODE(knopContinue , "continue" ,None ,Jump ,fnopNone)
case knopSwitch:
{
ParseNodeSwitch * pnodeSwitch = pnode->AsParseNodeSwitch();
BOOL fHasDefault = false;
Assert(pnodeSwitch->pnodeVal != nullptr);
byteCodeGenerator->StartStatement(pnodeSwitch);
Emit(pnodeSwitch->pnodeVal, byteCodeGenerator, funcInfo, false);
Js::RegSlot regVal = funcInfo->AcquireTmpRegister();
byteCodeGenerator->Writer()->Reg2(Js::OpCode::BeginSwitch, regVal, pnodeSwitch->pnodeVal->location);
BeginEmitBlock(pnodeSwitch->pnodeBlock, byteCodeGenerator, funcInfo);
byteCodeGenerator->EndStatement(pnodeSwitch);
// TODO: if all cases are compile-time constants, emit a switch statement in the byte
// code so the BE can optimize it.
ParseNodeCase *pnodeCase;
for (pnodeCase = pnodeSwitch->pnodeCases; pnodeCase; pnodeCase = pnodeCase->pnodeNext)
{
// Jump to the first case body if this one doesn't match. Make sure any side-effects of the case
// expression take place regardless.
pnodeCase->labelCase = byteCodeGenerator->Writer()->DefineLabel();
if (pnodeCase == pnodeSwitch->pnodeDefault)
{
fHasDefault = true;
continue;
}
Emit(pnodeCase->pnodeExpr, byteCodeGenerator, funcInfo, false);
byteCodeGenerator->Writer()->BrReg2(
Js::OpCode::Case, pnodeCase->labelCase, regVal, pnodeCase->pnodeExpr->location);
funcInfo->ReleaseLoc(pnodeCase->pnodeExpr);
}
// No explicit case value matches. Jump to the default arm (if any) or break out altogether.
if (fHasDefault)
{
byteCodeGenerator->Writer()->Br(Js::OpCode::EndSwitch, pnodeSwitch->pnodeDefault->labelCase);
}
else
{
if (!pnodeSwitch->emitLabels)
{
pnodeSwitch->breakLabel = byteCodeGenerator->Writer()->DefineLabel();
}
byteCodeGenerator->Writer()->Br(Js::OpCode::EndSwitch, pnodeSwitch->breakLabel);
}
// Now emit the case arms to which we jump on matching a case value.
for (pnodeCase = pnodeSwitch->pnodeCases; pnodeCase; pnodeCase = pnodeCase->pnodeNext)
{
byteCodeGenerator->Writer()->MarkLabel(pnodeCase->labelCase);
Emit(pnodeCase->pnodeBody, byteCodeGenerator, funcInfo, fReturnValue);
funcInfo->ReleaseLoc(pnodeCase->pnodeBody);
}
EndEmitBlock(pnodeSwitch->pnodeBlock, byteCodeGenerator, funcInfo);
funcInfo->ReleaseTmpRegister(regVal);
funcInfo->ReleaseLoc(pnodeSwitch->pnodeVal);
if (!fHasDefault || pnodeSwitch->emitLabels)
{
byteCodeGenerator->Writer()->MarkLabel(pnodeSwitch->breakLabel);
}
break;
}
case knopTryCatch:
{
Js::ByteCodeLabel catchLabel = (Js::ByteCodeLabel) - 1;
ParseNodeTryCatch * pnodeTryCatch = pnode->AsParseNodeTryCatch();
ParseNodeTry *pnodeTry = pnodeTryCatch->pnodeTry;
Assert(pnodeTry);
ParseNodeCatch *pnodeCatch = pnodeTryCatch->pnodeCatch;
Assert(pnodeCatch);
catchLabel = byteCodeGenerator->Writer()->DefineLabel();
// Note: try uses OpCode::Leave which causes a return to parent interpreter thunk,
// same for catch block. Thus record cross interpreter frame entry/exit records for them.
byteCodeGenerator->Writer()->RecordCrossFrameEntryExitRecord(/* isEnterBlock = */ true);
byteCodeGenerator->Writer()->Br(Js::OpCode::TryCatch, catchLabel);
byteCodeGenerator->PushJumpCleanupForTry(Js::OpCode::TryCatch, catchLabel);
Emit(pnodeTry->pnodeBody, byteCodeGenerator, funcInfo, fReturnValue);
byteCodeGenerator->PopJumpCleanup();
funcInfo->ReleaseLoc(pnodeTry->pnodeBody);
byteCodeGenerator->Writer()->RecordCrossFrameEntryExitRecord(/* isEnterBlock = */ false);
byteCodeGenerator->Writer()->Empty(Js::OpCode::Leave);
byteCodeGenerator->Writer()->Br(pnodeTryCatch->breakLabel);
byteCodeGenerator->Writer()->MarkLabel(catchLabel);
Js::RegSlot location = Js::Constants::NoRegister;
Js::DebuggerScope *debuggerScope = nullptr;
Js::DebuggerScopePropertyFlags debuggerPropertyFlags = Js::DebuggerScopePropertyFlags_CatchObject;
ParseNode *tempLocationNode = nullptr;
if (pnodeCatch->HasPatternParam())
{
location = pnodeCatch->GetParam()->AsParseNodeParamPattern()->location;
}
else if (pnodeCatch->HasParam())
{
location = pnodeCatch->GetParam()->AsParseNodeName()->sym->GetLocation();
}
if (location == Js::Constants::NoRegister)
{
tempLocationNode = pnodeCatch->HasParam() ? pnodeCatch->GetParam() : pnodeCatch;
location = funcInfo->AcquireLoc(tempLocationNode);
}
byteCodeGenerator->Writer()->Reg1(Js::OpCode::Catch, location);
Scope *scope = pnodeCatch->scope;
byteCodeGenerator->PushScope(scope);
if (scope->GetMustInstantiate())
{
Assert(scope->GetLocation() == Js::Constants::NoRegister);
if (scope->GetIsObject())
{
debuggerScope = byteCodeGenerator->RecordStartScopeObject(pnodeTryCatch, Js::DiagCatchScopeInObject, funcInfo->InnerScopeToRegSlot(scope));
byteCodeGenerator->Writer()->Unsigned1(Js::OpCode::NewPseudoScope, scope->GetInnerScopeIndex());
}
else
{
int index = Js::DebuggerScope::InvalidScopeIndex;
debuggerScope = byteCodeGenerator->RecordStartScopeObject(pnodeTryCatch, Js::DiagCatchScopeInSlot, funcInfo->InnerScopeToRegSlot(scope), &index);
byteCodeGenerator->Writer()->Num3(Js::OpCode::NewInnerScopeSlots, scope->GetInnerScopeIndex(), scope->GetScopeSlotCount() + Js::ScopeSlots::FirstSlotIndex, index);
}
}
else
{
debuggerScope = byteCodeGenerator->RecordStartScopeObject(pnodeTryCatch, Js::DiagCatchScopeDirect, location);
}
auto ParamTrackAndInitialization = [&](Symbol *sym, bool initializeParam, Js::RegSlot location)
{
if (sym->IsInSlot(byteCodeGenerator, funcInfo))
{
Assert(scope->GetMustInstantiate());
if (scope->GetIsObject())
{
Js::OpCode op = (sym->GetDecl()->nop == knopLetDecl) ? Js::OpCode::InitUndeclLetFld :
byteCodeGenerator->GetInitFldOp(scope, scope->GetLocation(), funcInfo, false);
Js::PropertyId propertyId = sym->EnsurePosition(byteCodeGenerator);
uint cacheId = funcInfo->FindOrAddInlineCacheId(funcInfo->InnerScopeToRegSlot(scope), propertyId, false, true);
byteCodeGenerator->Writer()->ElementPIndexed(op, location, scope->GetInnerScopeIndex(), cacheId);
byteCodeGenerator->TrackActivationObjectPropertyForDebugger(debuggerScope, sym, debuggerPropertyFlags);
}
else
{
byteCodeGenerator->TrackSlotArrayPropertyForDebugger(debuggerScope, sym, sym->EnsurePosition(byteCodeGenerator), debuggerPropertyFlags);
if (initializeParam)
{
byteCodeGenerator->EmitLocalPropInit(location, sym, funcInfo);
}
else
{
Js::RegSlot tmpReg = funcInfo->AcquireTmpRegister();
byteCodeGenerator->Writer()->Reg1(Js::OpCode::InitUndecl, tmpReg);
byteCodeGenerator->EmitLocalPropInit(tmpReg, sym, funcInfo);
funcInfo->ReleaseTmpRegister(tmpReg);
}
}
}
else
{
byteCodeGenerator->TrackRegisterPropertyForDebugger(debuggerScope, sym, funcInfo, debuggerPropertyFlags);
if (initializeParam)
{
byteCodeGenerator->EmitLocalPropInit(location, sym, funcInfo);
}
else
{
byteCodeGenerator->Writer()->Reg1(Js::OpCode::InitUndecl, location);
}
}
};
ByteCodeGenerator::TryScopeRecord tryRecForCatch(Js::OpCode::ResumeCatch, catchLabel);
if (pnodeCatch->HasPatternParam())
{
ParseNode *pnode1 = pnodeCatch->GetParam()->AsParseNodeParamPattern()->pnode1;
Parser::MapBindIdentifier(pnode1, [&](ParseNodePtr item)
{
Js::RegSlot itemLocation = item->AsParseNodeVar()->sym->GetLocation();
if (itemLocation == Js::Constants::NoRegister)
{
// The var has no assigned register, meaning it's captured, so we have no reg to write to.
// Emit the designated return reg in the byte code to avoid asserting on bad register.
itemLocation = ByteCodeGenerator::ReturnRegister;
}
ParamTrackAndInitialization(item->AsParseNodeVar()->sym, false /*initializeParam*/, itemLocation);
});
byteCodeGenerator->Writer()->RecordCrossFrameEntryExitRecord(true);
// Now emitting bytecode for destructuring pattern
byteCodeGenerator->StartStatement(pnodeCatch);
Assert(pnode1->IsPattern());
byteCodeGenerator->PushJumpCleanupForTry(Js::OpCode::ResumeCatch);
EmitAssignment(nullptr, pnode1, location, byteCodeGenerator, funcInfo);
byteCodeGenerator->EndStatement(pnodeCatch);
}
else
{
if (pnodeCatch->HasParam())
{
Symbol *sym = pnodeCatch->GetParam()->AsParseNodeName()->sym;
ParamTrackAndInitialization(sym, true /*initializeParam*/, location);
if (scope->GetMustInstantiate())
{
sym->SetIsGlobalCatch(true);
}
byteCodeGenerator->Writer()->RecordCrossFrameEntryExitRecord(true);
}
// Allow a debugger to stop on the 'catch'
byteCodeGenerator->StartStatement(pnodeCatch);
byteCodeGenerator->Writer()->Empty(Js::OpCode::Nop);
byteCodeGenerator->EndStatement(pnodeCatch);
byteCodeGenerator->PushJumpCleanupForTry(Js::OpCode::ResumeCatch);
}
Emit(pnodeCatch->pnodeBody, byteCodeGenerator, funcInfo, fReturnValue);
byteCodeGenerator->PopJumpCleanup();
byteCodeGenerator->PopScope();
byteCodeGenerator->RecordEndScopeObject(pnodeTryCatch);
funcInfo->ReleaseLoc(pnodeCatch->pnodeBody);
if (tempLocationNode != nullptr)
{
funcInfo->ReleaseLoc(tempLocationNode);
}
byteCodeGenerator->Writer()->RecordCrossFrameEntryExitRecord(false);
byteCodeGenerator->Writer()->Empty(Js::OpCode::Leave);
byteCodeGenerator->Writer()->MarkLabel(pnodeTryCatch->breakLabel);
break;
}
case knopTryFinally:
{
Js::ByteCodeLabel finallyLabel = (Js::ByteCodeLabel) - 1;
ParseNodeTryFinally * pnodeTryFinally = pnode->AsParseNodeTryFinally();
ParseNodeTry *pnodeTry = pnodeTryFinally->pnodeTry;
Assert(pnodeTry);
ParseNodeFinally *pnodeFinally = pnodeTryFinally->pnodeFinally;
Assert(pnodeFinally);
// If we yield from the finally block after an exception, we have to store the exception object for the future next call.
// When we yield from the Try-Finally the offset to the end of the Try block is needed for the branch instruction.
Js::RegSlot regException = Js::Constants::NoRegister;
Js::RegSlot regOffset = Js::Constants::NoRegister;
finallyLabel = byteCodeGenerator->Writer()->DefineLabel();
byteCodeGenerator->Writer()->RecordCrossFrameEntryExitRecord(true);
// [CONSIDER][aneeshd] Ideally the TryFinallyWithYield opcode needs to be used only if there is a yield expression.
// For now, if the function is generator we are using the TryFinallyWithYield.
if (funcInfo->byteCodeFunction->IsCoroutine())
{
regException = funcInfo->AcquireTmpRegister();
regOffset = funcInfo->AcquireTmpRegister();
byteCodeGenerator->Writer()->BrReg2(Js::OpCode::TryFinallyWithYield, finallyLabel, regException, regOffset);
byteCodeGenerator->PushJumpCleanupForTry(
Js::OpCode::TryFinallyWithYield,
finallyLabel,
regException,
regOffset);
}
else
{
byteCodeGenerator->Writer()->Br(Js::OpCode::TryFinally, finallyLabel);
byteCodeGenerator->PushJumpCleanupForTry(Js::OpCode::TryFinally, finallyLabel);
}
// Increasing the stack as we will be storing the additional values when we enter try..finally.
funcInfo->StartRecordingOutArgs(1);
Emit(pnodeTry->pnodeBody, byteCodeGenerator, funcInfo, fReturnValue);
funcInfo->ReleaseLoc(pnodeTry->pnodeBody);
byteCodeGenerator->PopJumpCleanup();
byteCodeGenerator->Writer()->Empty(Js::OpCode::Leave);
byteCodeGenerator->Writer()->RecordCrossFrameEntryExitRecord(false);
// Note: although we don't use OpCode::Leave for finally block,
// OpCode::LeaveNull causes a return to parent interpreter thunk.
// This has to be on offset prior to offset of 1st statement of finally.
byteCodeGenerator->Writer()->RecordCrossFrameEntryExitRecord(true);
byteCodeGenerator->Writer()->Br(pnodeTryFinally->breakLabel);
byteCodeGenerator->Writer()->MarkLabel(finallyLabel);
byteCodeGenerator->Writer()->Empty(Js::OpCode::Finally);
byteCodeGenerator->PushJumpCleanupForTry(
Js::OpCode::ResumeFinally,
finallyLabel,
regException,
regOffset);
Emit(pnodeFinally->pnodeBody, byteCodeGenerator, funcInfo, fReturnValue);
funcInfo->ReleaseLoc(pnodeFinally->pnodeBody);
if (funcInfo->byteCodeFunction->IsCoroutine())
{
funcInfo->ReleaseTmpRegister(regOffset);
funcInfo->ReleaseTmpRegister(regException);
}
byteCodeGenerator->PopJumpCleanup();
funcInfo->EndRecordingOutArgs(1);
byteCodeGenerator->Writer()->RecordCrossFrameEntryExitRecord(false);
byteCodeGenerator->Writer()->Empty(Js::OpCode::LeaveNull);
byteCodeGenerator->Writer()->MarkLabel(pnodeTryFinally->breakLabel);
break;
}
case knopThrow:
byteCodeGenerator->StartStatement(pnode);
Emit(pnode->AsParseNodeUni()->pnode1, byteCodeGenerator, funcInfo, false);
byteCodeGenerator->Writer()->Reg1(Js::OpCode::Throw, pnode->AsParseNodeUni()->pnode1->location);
funcInfo->ReleaseLoc(pnode->AsParseNodeUni()->pnode1);
byteCodeGenerator->EndStatement(pnode);
break;
case knopYieldLeaf:
byteCodeGenerator->StartStatement(pnode);
funcInfo->AcquireLoc(pnode);
EmitYield(pnode->location, funcInfo->undefinedConstantRegister, byteCodeGenerator, funcInfo);
byteCodeGenerator->EndStatement(pnode);
break;
case knopAwait:
byteCodeGenerator->StartStatement(pnode);
funcInfo->AcquireLoc(pnode);
Emit(pnode->AsParseNodeUni()->pnode1, byteCodeGenerator, funcInfo, false);
EmitAwait(pnode->location, pnode->AsParseNodeUni()->pnode1->location, byteCodeGenerator, funcInfo);
funcInfo->ReleaseLoc(pnode->AsParseNodeUni()->pnode1);
byteCodeGenerator->EndStatement(pnode);
break;
case knopYield:
byteCodeGenerator->StartStatement(pnode);
funcInfo->AcquireLoc(pnode);
Emit(pnode->AsParseNodeUni()->pnode1, byteCodeGenerator, funcInfo, false);
EmitYield(pnode->location, pnode->AsParseNodeUni()->pnode1->location, byteCodeGenerator, funcInfo);
funcInfo->ReleaseLoc(pnode->AsParseNodeUni()->pnode1);
byteCodeGenerator->EndStatement(pnode);
break;
case knopYieldStar:
byteCodeGenerator->StartStatement(pnode);
EmitYieldStar(pnode->AsParseNodeUni(), byteCodeGenerator, funcInfo);
byteCodeGenerator->EndStatement(pnode);
break;
case knopExportDefault:
Emit(pnode->AsParseNodeExportDefault()->pnodeExpr, byteCodeGenerator, funcInfo, false);
byteCodeGenerator->EmitAssignmentToDefaultModuleExport(pnode->AsParseNodeExportDefault()->pnodeExpr, funcInfo);
funcInfo->ReleaseLoc(pnode->AsParseNodeExportDefault()->pnodeExpr);
pnode = pnode->AsParseNodeExportDefault()->pnodeExpr;
break;
default:
AssertMsg(0, "emit unhandled pnode op");
break;
}
if (fReturnValue && IsExpressionStatement(pnode, byteCodeGenerator->GetScriptContext()) && !pnode->IsPatternDeclaration())
{
// If this statement may produce the global function's return value, copy its result to the return register.
// fReturnValue implies global function, which implies that "return" is a parse error.
Assert(funcInfo->IsGlobalFunction());
Assert(pnode->nop != knopReturn);
byteCodeGenerator->Writer()->Reg2(Js::OpCode::Ld_A, ByteCodeGenerator::ReturnRegister, pnode->location);
}
}