From 4fb83415669c44af9517d5db9adedcfbc67d74b9 Mon Sep 17 00:00:00 2001 From: Paul Leathers Date: Thu, 2 Mar 2017 18:06:26 -0800 Subject: [PATCH] Defer functions enclosed in all contexts. Support deferral of functions enclosed in scopes other than function body and function expression scope -- in particular, ES6-style lexical scopes and parameter scopes. This requires changing ScopeInfo so that it is associated with a single scope rather than a function body. Instead of carrying per-function information for body/var scope, parameter scope, and function expression scope, ScopeInfo will describe one scope, with a value identifying the scope type, a pointer to the FunctionInfo that contains it, and a pointer to the enclosing ScopeInfo.. A FunctionProxy will point to the ScopeInfo that is the nearest enclosing scope. At parse time, we will reconstitute the closure environment by walking the list of enclosing ScopeInfo's. The code that allowed redeferral to work around the context limitation of deferring parsing is deleted, and the OptimizeBlockScope feature is off by default, as it doesn't play well with this new logic and isn't required to make (re-)deferral effective. (We will want to revisit it.) --- lib/Parser/Hash.h | 10 + lib/Parser/Parse.cpp | 274 ++++++++---------- lib/Parser/Parse.h | 12 +- lib/Parser/ptree.h | 4 - lib/Runtime/ByteCode/ByteCodeEmitter.cpp | 28 +- lib/Runtime/ByteCode/ByteCodeGenerator.cpp | 149 ++++++---- lib/Runtime/ByteCode/ByteCodeGenerator.h | 4 +- lib/Runtime/ByteCode/FuncInfo.cpp | 5 +- lib/Runtime/ByteCode/FuncInfo.h | 25 +- lib/Runtime/ByteCode/Scope.h | 12 + lib/Runtime/ByteCode/ScopeInfo.cpp | 204 ++++--------- lib/Runtime/ByteCode/ScopeInfo.h | 76 ++--- lib/Runtime/ByteCode/Symbol.cpp | 2 +- ...onDeclarationSlotArrayTest.js.dbg.baseline | 1 + .../let_blockescape.deferparse.baseline | 2 +- .../let_stackfunc.deferparse.baseline | 5 +- .../with_namedfunc.deferparse.baseline | 1 + 17 files changed, 378 insertions(+), 436 deletions(-) diff --git a/lib/Parser/Hash.h b/lib/Parser/Hash.h index a1f2487a0b1..0bfcc86af0f 100644 --- a/lib/Parser/Hash.h +++ b/lib/Parser/Hash.h @@ -144,6 +144,16 @@ struct Ident return m_pidRefStack; } + PidRefStack *GetTopRef(uint maxBlockId) const + { + PidRefStack *ref; + for (ref = m_pidRefStack; ref && (uint)ref->id > maxBlockId; ref = ref->prev) + { + ; // nothing + } + return ref; + } + void SetTopRef(PidRefStack *ref) { m_pidRefStack = ref; diff --git a/lib/Parser/Parse.cpp b/lib/Parser/Parse.cpp index 6c1134cbe71..6de17576522 100644 --- a/lib/Parser/Parse.cpp +++ b/lib/Parser/Parse.cpp @@ -116,7 +116,6 @@ Parser::Parser(Js::ScriptContext* scriptContext, BOOL strictMode, PageAllocator m_errorCallback = nullptr; m_uncertainStructure = FALSE; m_reparsingLambdaParams = false; - m_inFIB = false; currBackgroundParseItem = nullptr; backgroundParseItems = nullptr; fastScannedRegExpNodes = nullptr; @@ -859,7 +858,6 @@ Symbol* Parser::AddDeclForPid(ParseNodePtr pnode, IdentPtr pid, SymbolType symbo if (sym->GetDecl() == nullptr) { - Assert(symbolType == STFunction); sym->SetDecl(pnode); break; } @@ -1446,12 +1444,11 @@ template ParseNodePtr Parser::StartParseBlockWithCapacity(PnodeBlockType blockType, ScopeType scopeType, int capacity) { Scope *scope = nullptr; - // Block scopes are created lazily when we discover block-scoped content. - if (scopeType != ScopeType_Unknown && scopeType != ScopeType_Block) - { - scope = Anew(&m_nodeAllocator, Scope, &m_nodeAllocator, scopeType, capacity); - PushScope(scope); - } + + // Block scopes are not created lazily in the case where we're repopulating a persisted scope. + + scope = Anew(&m_nodeAllocator, Scope, &m_nodeAllocator, scopeType, capacity); + PushScope(scope); return StartParseBlockHelper(blockType, scope, nullptr, nullptr); } @@ -4501,7 +4498,11 @@ BOOL Parser::DeferredParse(Js::LocalFunctionId functionId) { return false; } - if (PHASE_FORCE_RAW(Js::DeferParsePhase, m_sourceContextInfo->sourceContextId, functionId)) + if (PHASE_FORCE_RAW(Js::DeferParsePhase, m_sourceContextInfo->sourceContextId, functionId) +#ifdef ENABLE_DEBUG_CONFIG_OPTIONS + || Js::Configuration::Global.flags.IsEnabled(Js::ForceUndoDeferFlag) +#endif + ) { return true; } @@ -4925,17 +4926,6 @@ bool Parser::ParseFncDeclHelper(ParseNodePtr pnodeFnc, LPCOLESTR pNameHint, usho pstmtSave = m_pstmtCur; SetCurrentStatement(nullptr); - // Function definition is inside the parent function's parameter scope - bool isEnclosedInParamScope = this->m_currentScope->GetScopeType() == ScopeType_Parameter; - - if (this->m_currentScope->GetScopeType() == ScopeType_FuncExpr || this->m_currentScope->GetScopeType() == ScopeType_Block) - { - // Or this is a function expression or class enclosed in a parameter scope - isEnclosedInParamScope = this->m_currentScope->GetEnclosingScope() && this->m_currentScope->GetEnclosingScope()->GetScopeType() == ScopeType_Parameter; - } - - Assert(!isEnclosedInParamScope || pnodeFncSave->sxFnc.HasNonSimpleParameterList()); - RestorePoint beginFormals; m_pscan->Capture(&beginFormals); BOOL fWasAlreadyStrictMode = IsStrictMode(); @@ -4947,23 +4937,15 @@ bool Parser::ParseFncDeclHelper(ParseNodePtr pnodeFnc, LPCOLESTR pNameHint, usho } uint uDeferSave = m_grfscr & fscrDeferFncParse; - if ((!fDeclaration && m_ppnodeExprScope) || - isEnclosedInParamScope || - (flags & (fFncNoName | fFncLambda))) - { - // NOTE: Don't defer if this is a function expression inside a construct that induces - // a scope nested within the current function (like a with, or a catch in ES5 mode, or - // any function declared inside a nested lexical block or param scope in ES6 mode). - // We won't be able to reconstruct the scope chain properly when we come back and - // try to compile just the function expression. - // Also shut off deferring on getter/setter or other construct with unusual text bounds + if (flags & (fFncNoName | fFncLambda)) + { + // Disable deferral on getter/setter or other construct with unusual text bounds // (fFncNoName|fFncLambda) as these are usually trivial, and re-parsing is problematic. + // NOTE: It is probably worth supporting these cases for memory and load-time purposes, + // especially as they become more and more common. m_grfscr &= ~fscrDeferFncParse; } - bool saveInFIB = this->m_inFIB; - this->m_inFIB = fFunctionInBlock || this->m_inFIB; - bool isTopLevelDeferredFunc = false; struct AutoFastScanFlag { @@ -4995,21 +4977,8 @@ bool Parser::ParseFncDeclHelper(ParseNodePtr pnodeFnc, LPCOLESTR pNameHint, usho if (pnodeFnc) { pnodeFnc->sxFnc.SetCanBeDeferred(isTopLevelDeferredFunc && PnFnc::CanBeRedeferred(pnodeFnc->sxFnc.fncFlags)); - pnodeFnc->sxFnc.SetFIBPreventsDeferral(false); } - if (this->m_inFIB) - { - if (isTopLevelDeferredFunc) - { - // Block-scoping is the only non-heuristic reason for not deferring this function up front. - // So on creating the full FunctionBody at byte code gen time, verify that there is no - // block-scoped content visible to this function so it can remain a redeferral candidate. - pnodeFnc->sxFnc.SetFIBPreventsDeferral(true); - } - isTopLevelDeferredFunc = false; - } - // These are heuristic conditions that prohibit upfront deferral but not redeferral. isTopLevelDeferredFunc = isTopLevelDeferredFunc && !isDeferredFnc && (!isLikelyIIFE || !topLevelStmt || PHASE_FORCE_RAW(Js::DeferParsePhase, m_sourceContextInfo->sourceContextId, pnodeFnc->sxFnc.functionId)); @@ -5287,12 +5256,7 @@ bool Parser::ParseFncDeclHelper(ParseNodePtr pnodeFnc, LPCOLESTR pNameHint, usho if (buildAST) { DeferredFunctionStub *saveCurrentStub = m_currDeferredStub; - if (isEnclosedInParamScope) - { - // if the enclosed scope is the param scope we would not have created the deferred stub. - m_currDeferredStub = nullptr; - } - else if (pnodeFncSave && m_currDeferredStub) + if (pnodeFncSave && m_currDeferredStub) { // the Deferred stub will not match for the function which are defined on lambda formals. // Since this is not determined upfront that the current function is a part of outer function or part of lambda formal until we have seen the Arrow token. @@ -5449,7 +5413,6 @@ bool Parser::ParseFncDeclHelper(ParseNodePtr pnodeFnc, LPCOLESTR pNameHint, usho { m_grfscr |= uDeferSave; } - m_inFIB = saveInFIB; m_pscan->SetYieldIsKeyword(fPreviousYieldIsKeyword); m_pscan->SetAwaitIsKeyword(fPreviousAwaitIsKeyword); @@ -8690,7 +8653,7 @@ PidRefStack* Parser::PushPidRef(IdentPtr pid) Assert(GetCurrentBlock() != nullptr); AssertMsg(pid != nullptr, "PID should be created"); - PidRefStack *ref = pid->GetTopRef(); + PidRefStack *ref = pid->GetTopRef(m_nextBlockId - 1); int blockId = GetCurrentBlock()->sxBlock.blockId; int funcId = GetCurrentFunctionNode()->sxFnc.functionId; if (!ref || (ref->GetScopeId() < blockId)) @@ -9161,7 +9124,7 @@ ParseNodePtr Parser::ParseCatch() } pidCatch = m_token.GetIdentifier(m_phtbl); - PidRefStack *ref = this->PushPidRef(pidCatch); + PidRefStack *ref = this->FindOrAddPidRef(pidCatch, GetCurrentBlock()->sxBlock.blockId, GetCurrentFunctionNode()->sxFnc.functionId); ParseNodePtr pnodeParam = CreateNameNode(pidCatch); pnodeParam->sxPid.symRef = ref->GetSymRef(); @@ -10558,15 +10521,31 @@ void Parser::ParseStmtList(ParseNodePtr *ppnodeList, ParseNodePtr **pppnodeLast, } template -void Parser::VisitFunctionsInScope(ParseNodePtr pnodeScopeList, Fn fn) +void Parser::FinishFunctionsInScope(ParseNodePtr pnodeScopeList, Fn fn) { + Scope * scope; + Scope * origCurrentScope = this->m_currentScope; ParseNodePtr pnodeScope; + ParseNodePtr pnodeBlock; for (pnodeScope = pnodeScopeList; pnodeScope;) { switch (pnodeScope->nop) { case knopBlock: - VisitFunctionsInScope(pnodeScope->sxBlock.pnodeScopes, fn); + m_nextBlockId = pnodeScope->sxBlock.blockId + 1; + PushBlockInfo(pnodeScope); + scope = pnodeScope->sxBlock.scope; + if (scope && scope != origCurrentScope) + { + PushScope(scope); + } + FinishFunctionsInScope(pnodeScope->sxBlock.pnodeScopes, fn); + if (scope && scope != origCurrentScope) + { + BindPidRefs(GetCurrentBlockInfo(), m_nextBlockId - 1); + PopScope(scope); + } + PopBlockInfo(); pnodeScope = pnodeScope->sxBlock.pnodeNext; break; @@ -10576,12 +10555,29 @@ void Parser::VisitFunctionsInScope(ParseNodePtr pnodeScopeList, Fn fn) break; case knopCatch: - VisitFunctionsInScope(pnodeScope->sxCatch.pnodeScopes, fn); + scope = pnodeScope->sxCatch.scope; + if (scope) + { + PushScope(scope); + } + pnodeBlock = CreateBlockNode(PnodeBlockType::Regular); + pnodeBlock->sxBlock.scope = scope; + PushBlockInfo(pnodeBlock); + FinishFunctionsInScope(pnodeScope->sxCatch.pnodeScopes, fn); + if (scope) + { + BindPidRefs(GetCurrentBlockInfo(), m_nextBlockId - 1); + PopScope(scope); + } + PopBlockInfo(); pnodeScope = pnodeScope->sxCatch.pnodeNext; break; case knopWith: - VisitFunctionsInScope(pnodeScope->sxWith.pnodeScopes, fn); + PushBlockInfo(CreateBlockNode()); + PushDynamicBlock(); + FinishFunctionsInScope(pnodeScope->sxWith.pnodeScopes, fn); + PopBlockInfo(); pnodeScope = pnodeScope->sxWith.pnodeNext; break; @@ -10620,7 +10616,10 @@ ULONG Parser::GetDeferralThreshold(bool isProfileLoaded) void Parser::FinishDeferredFunction(ParseNodePtr pnodeScopeList) { - VisitFunctionsInScope(pnodeScopeList, + uint saveNextBlockId = m_nextBlockId; + m_nextBlockId = pnodeScopeList->sxBlock.blockId + 1; + + FinishFunctionsInScope(pnodeScopeList, [this](ParseNodePtr pnodeFnc) { Assert(pnodeFnc->nop == knopFncDecl); @@ -10637,22 +10636,24 @@ void Parser::FinishDeferredFunction(ParseNodePtr pnodeScopeList) this->m_currentNodeFunc = pnodeFnc; ParseNodePtr pnodeFncExprBlock = nullptr; - if (pnodeFnc->sxFnc.pnodeName && - !pnodeFnc->sxFnc.IsDeclaration()) + ParseNodePtr pnodeName = pnodeFnc->sxFnc.pnodeName; + if (pnodeName) { - // Set up the named function expression symbol so references inside the function can be bound. - ParseNodePtr pnodeName = pnodeFnc->sxFnc.pnodeName; Assert(pnodeName->nop == knopVarDecl); Assert(pnodeName->sxVar.pnodeNext == nullptr); - pnodeFncExprBlock = this->StartParseBlock(PnodeBlockType::Function, ScopeType_FuncExpr); - PidRefStack *ref = this->PushPidRef(pnodeName->sxVar.pid); - pnodeName->sxVar.symRef = ref->GetSymRef(); - ref->SetSym(pnodeName->sxVar.sym); + if (!pnodeFnc->sxFnc.IsDeclaration()) + { + // Set up the named function expression symbol so references inside the function can be bound. + pnodeFncExprBlock = this->StartParseBlock(PnodeBlockType::Function, ScopeType_FuncExpr); + PidRefStack *ref = this->PushPidRef(pnodeName->sxVar.pid); + pnodeName->sxVar.symRef = ref->GetSymRef(); + ref->SetSym(pnodeName->sxVar.sym); - Scope *fncExprScope = pnodeFncExprBlock->sxBlock.scope; - fncExprScope->AddNewSymbol(pnodeName->sxVar.sym); - pnodeFnc->sxFnc.scope = fncExprScope; + Scope *fncExprScope = pnodeFncExprBlock->sxBlock.scope; + fncExprScope->AddNewSymbol(pnodeName->sxVar.sym); + pnodeFnc->sxFnc.scope = fncExprScope; + } } ParseNodePtr pnodeBlock = this->StartParseBlock(PnodeBlockType::Parameter, ScopeType_Parameter); @@ -10662,10 +10663,12 @@ void Parser::FinishDeferredFunction(ParseNodePtr pnodeScopeList) // Add the args to the scope, since we won't re-parse those. Scope *scope = pnodeBlock->sxBlock.scope; + uint blockId = GetCurrentBlock()->sxBlock.blockId; + uint funcId = GetCurrentFunctionNode()->sxFnc.functionId; auto addArgsToScope = [&](ParseNodePtr pnodeArg) { if (pnodeArg->IsVarLetOrConst()) { - PidRefStack *ref = this->PushPidRef(pnodeArg->sxVar.pid); + PidRefStack *ref = this->FindOrAddPidRef(pnodeArg->sxVar.pid, blockId, funcId);//this->PushPidRef(pnodeArg->sxVar.pid); pnodeArg->sxVar.symRef = ref->GetSymRef(); if (ref->GetSym() != nullptr) { @@ -10715,9 +10718,11 @@ void Parser::FinishDeferredFunction(ParseNodePtr pnodeScopeList) { if (scope->GetCanMergeWithBodyScope()) { - scope->ForEachSymbol([this](Symbol* paramSym) + blockId = GetCurrentBlock()->sxBlock.blockId; + funcId = GetCurrentFunctionNode()->sxFnc.functionId; + scope->ForEachSymbol([this, blockId, funcId](Symbol* paramSym) { - PidRefStack* ref = PushPidRef(paramSym->GetPid()); + PidRefStack* ref = this->FindOrAddPidRef(paramSym->GetPid(), blockId, funcId); ref->SetSym(paramSym); }); } @@ -10761,6 +10766,8 @@ void Parser::FinishDeferredFunction(ParseNodePtr pnodeScopeList) this->m_currentNodeFunc = pnodeFncSave; } }); + + m_nextBlockId = saveNextBlockId; } void Parser::InitPids() @@ -10784,14 +10791,8 @@ void Parser::InitPids() wellKnownPropertyPids._star = m_phtbl->PidHashNameLen(_u("*"), sizeof("*") - 1); } -void Parser::RestoreScopeInfo(Js::ParseableFunctionInfo* functionBody) +void Parser::RestoreScopeInfo(Js::ScopeInfo * scopeInfo) { - if (!functionBody) - { - return; - } - - Js::ScopeInfo* scopeInfo = functionBody->GetScopeInfo(); if (!scopeInfo) { return; @@ -10806,53 +10807,47 @@ void Parser::RestoreScopeInfo(Js::ParseableFunctionInfo* functionBody) PROBE_STACK(m_scriptContext, Js::Constants::MinStackByteCodeVisitor); } - RestoreScopeInfo(scopeInfo->GetParent()); // Recursively restore outer func scope info + RestoreScopeInfo(scopeInfo->GetParentScopeInfo()); // Recursively restore outer func scope info - Js::ScopeInfo* funcExprScopeInfo = scopeInfo->GetFuncExprScopeInfo(); - if (funcExprScopeInfo) - { - funcExprScopeInfo->SetScopeId(m_nextBlockId); - ParseNodePtr pnodeFncExprScope = StartParseBlockWithCapacity(PnodeBlockType::Function, ScopeType_FuncExpr, funcExprScopeInfo->GetSymbolCount()); - Scope *scope = pnodeFncExprScope->sxBlock.scope; - funcExprScopeInfo->GetScopeInfo(this, nullptr, nullptr, scope); - } + scopeInfo->SetScopeId(m_nextBlockId); + ParseNodePtr pnodeScope = nullptr; + ScopeType scopeType = scopeInfo->GetScopeType(); + PnodeBlockType blockType; + switch (scopeType) + { + case ScopeType_With: + PushDynamicBlock(); + // fall through + case ScopeType_Block: + case ScopeType_Catch: + case ScopeType_CatchParamPattern: + case ScopeType_GlobalEvalBlock: + blockType = PnodeBlockType::Regular; + break; - Js::ScopeInfo* paramScopeInfo = scopeInfo->GetParamScopeInfo(); - if (paramScopeInfo) - { - paramScopeInfo->SetScopeId(m_nextBlockId); - ParseNodePtr pnodeFncExprScope = StartParseBlockWithCapacity(PnodeBlockType::Parameter, ScopeType_Parameter, paramScopeInfo->GetSymbolCount()); - Scope *scope = pnodeFncExprScope->sxBlock.scope; - paramScopeInfo->GetScopeInfo(this, nullptr, nullptr, scope); - } + case ScopeType_FunctionBody: + case ScopeType_FuncExpr: + blockType = PnodeBlockType::Function; + break; - scopeInfo->SetScopeId(m_nextBlockId); - ParseNodePtr pnodeFncScope = nullptr; - if (scopeInfo->IsGlobalEval()) - { - pnodeFncScope = StartParseBlockWithCapacity(PnodeBlockType::Regular, ScopeType_GlobalEvalBlock, scopeInfo->GetSymbolCount()); - } - else - { - pnodeFncScope = StartParseBlockWithCapacity(PnodeBlockType::Function, ScopeType_FunctionBody, scopeInfo->GetSymbolCount()); - } - Scope *scope = pnodeFncScope->sxBlock.scope; - scopeInfo->GetScopeInfo(this, nullptr, nullptr, scope); -} + case ScopeType_Parameter: + blockType = PnodeBlockType::Parameter; + break; -void Parser::FinishScopeInfo(Js::ParseableFunctionInfo *functionBody) -{ - if (!functionBody) - { - return; - } - Js::ScopeInfo* scopeInfo = functionBody->GetScopeInfo(); - if (!scopeInfo) - { - return; + default: + Assert(0); + return; } + pnodeScope = StartParseBlockWithCapacity(blockType, scopeType, scopeInfo->GetSymbolCount()); + Scope *scope = pnodeScope->sxBlock.scope; + scope->SetScopeInfo(scopeInfo); + scopeInfo->ExtractScopeInfo(this, /*nullptr, nullptr,*/ scope); +} + +void Parser::FinishScopeInfo(Js::ScopeInfo * scopeInfo) +{ if (this->IsBackgroundParser()) { PROBE_STACK_NO_DISPOSE(m_scriptContext, Js::Constants::MinStackByteCodeVisitor); @@ -10862,43 +10857,18 @@ void Parser::FinishScopeInfo(Js::ParseableFunctionInfo *functionBody) PROBE_STACK(m_scriptContext, Js::Constants::MinStackByteCodeVisitor); } - int scopeId = scopeInfo->GetScopeId(); - - scopeInfo->GetScope()->ForEachSymbol([this, scopeId](Symbol *sym) - { - this->BindPidRefsInScope(sym->GetPid(), sym, scopeId); - }); - PopScope(scopeInfo->GetScope()); - PopStmt(&m_currentBlockInfo->pstmt); - PopBlockInfo(); - - Js::ScopeInfo *paramScopeInfo = scopeInfo->GetParamScopeInfo(); - if (paramScopeInfo) + for (;scopeInfo != nullptr; scopeInfo = scopeInfo->GetParentScopeInfo()) { - scopeId = paramScopeInfo->GetScopeId(); - paramScopeInfo->GetScope()->ForEachSymbol([this, scopeId](Symbol *sym) - { - this->BindPidRefsInScope(sym->GetPid(), sym, scopeId); - }); - PopScope(paramScopeInfo->GetScope()); - PopStmt(&m_currentBlockInfo->pstmt); - PopBlockInfo(); - } + int scopeId = scopeInfo->GetScopeId(); - Js::ScopeInfo *funcExprScopeInfo = scopeInfo->GetFuncExprScopeInfo(); - if (funcExprScopeInfo) - { - scopeId = funcExprScopeInfo->GetScopeId(); - funcExprScopeInfo->GetScope()->ForEachSymbol([this, scopeId](Symbol *sym) + scopeInfo->GetScope()->ForEachSymbol([this, scopeId](Symbol *sym) { this->BindPidRefsInScope(sym->GetPid(), sym, scopeId); }); - PopScope(funcExprScopeInfo->GetScope()); + PopScope(scopeInfo->GetScope()); PopStmt(&m_currentBlockInfo->pstmt); PopBlockInfo(); } - - FinishScopeInfo(scopeInfo->GetParent()); } /*************************************************************************** @@ -11054,7 +11024,7 @@ ParseNodePtr Parser::Parse(LPCUTF8 pszSrc, size_t offset, size_t length, charcou m_currentNodeFunc->sxFnc.nestedCount = m_functionBody->GetNestedCount(); m_currentNodeFunc->sxFnc.SetStrictMode(!!this->m_fUseStrictMode); - this->RestoreScopeInfo(scopeInfo->GetParent()); + this->RestoreScopeInfo(scopeInfo); } } @@ -11079,7 +11049,7 @@ ParseNodePtr Parser::Parse(LPCUTF8 pszSrc, size_t offset, size_t length, charcou { if (scopeInfo) { - this->FinishScopeInfo(scopeInfo->GetParent()); + this->FinishScopeInfo(scopeInfo); } } diff --git a/lib/Parser/Parse.h b/lib/Parser/Parse.h index 4eab6d6ddde..be61da6df08 100644 --- a/lib/Parser/Parse.h +++ b/lib/Parser/Parse.h @@ -6,6 +6,11 @@ #include "ParseFlags.h" +namespace Js +{ + class ScopeInfo; +}; + // Operator precedence levels enum { @@ -369,7 +374,6 @@ class Parser bool m_inDeferredNestedFunc; // true if parsing a function in deferred mode, nested within the current node bool m_isInBackground; bool m_reparsingLambdaParams; - bool m_inFIB; // This bool is used for deferring the shorthand initializer error ( {x = 1}) - as it is allowed in the destructuring grammar. bool m_hasDeferredShorthandInitError; @@ -976,8 +980,8 @@ class Parser void RemovePrevPidRef(IdentPtr pid, PidRefStack *lastRef); void SetPidRefsInScopeDynamic(IdentPtr pid, int blockId); - void RestoreScopeInfo(Js::ParseableFunctionInfo* functionBody); - void FinishScopeInfo(Js::ParseableFunctionInfo* functionBody); + void RestoreScopeInfo(Js::ScopeInfo * scopeInfo); + void FinishScopeInfo(Js::ScopeInfo * scopeInfo); BOOL PnodeLabelNoAST(IdentToken* pToken, LabelId* pLabelIdList); LabelId* CreateLabelId(IdentToken* pToken); @@ -1011,7 +1015,7 @@ class Parser } template - void VisitFunctionsInScope(ParseNodePtr pnodeScopeList, Fn fn); + void FinishFunctionsInScope(ParseNodePtr pnodeScopeList, Fn fn); void FinishDeferredFunction(ParseNodePtr pnodeScopeList); /*********************************************************************** diff --git a/lib/Parser/ptree.h b/lib/Parser/ptree.h index b11c73fc428..43aab753949 100644 --- a/lib/Parser/ptree.h +++ b/lib/Parser/ptree.h @@ -247,7 +247,6 @@ struct PnFnc RestorePoint *pRestorePoint; DeferredFunctionStub *deferredStub; bool canBeDeferred; - bool fibPreventsDeferral; static const int32 MaxStackClosureAST = 800000; @@ -286,7 +285,6 @@ struct PnFnc { fncFlags = kFunctionNone; canBeDeferred = false; - fibPreventsDeferral = false; } void SetAsmjsMode(bool set = true) { SetFlags(kFunctionAsmjsMode, set); } @@ -323,7 +321,6 @@ struct PnFnc void SetIsDefaultModuleExport(bool set = true) { SetFlags(kFunctionIsDefaultModuleExport, set); } void SetNestedFuncEscapes(bool set = true) { nestedFuncEscapes = set; } void SetCanBeDeferred(bool set = true) { canBeDeferred = set; } - void SetFIBPreventsDeferral(bool set = true) { fibPreventsDeferral = set; } bool CallsEval() const { return HasFlags(kFunctionCallsEval); } bool ChildCallsEval() const { return HasFlags(kFunctionChildCallsEval); } @@ -362,7 +359,6 @@ struct PnFnc bool IsDefaultModuleExport() const { return HasFlags(kFunctionIsDefaultModuleExport); } bool NestedFuncEscapes() const { return nestedFuncEscapes; } bool CanBeDeferred() const { return canBeDeferred; } - bool FIBPreventsDeferral() const { return fibPreventsDeferral; } size_t LengthInBytes() { diff --git a/lib/Runtime/ByteCode/ByteCodeEmitter.cpp b/lib/Runtime/ByteCode/ByteCodeEmitter.cpp index a1d46680add..d0d824f90cd 100644 --- a/lib/Runtime/ByteCode/ByteCodeEmitter.cpp +++ b/lib/Runtime/ByteCode/ByteCodeEmitter.cpp @@ -2993,6 +2993,11 @@ void ByteCodeGenerator::EmitOneFunction(ParseNode *pnode) deferParseFunction->SetReportedInParamsCount(funcInfo->inArgsCount); } + if (deferParseFunction->IsDeferred() || deferParseFunction->CanBeDeferred()) + { + Js::ScopeInfo::SaveEnclosingScopeInfo(this, funcInfo); + } + if (funcInfo->root->sxFnc.pnodeBody == nullptr) { if (!PHASE_OFF1(Js::SkipNestedDeferredPhase)) @@ -3658,13 +3663,7 @@ void ByteCodeGenerator::EmitScopeList(ParseNode *pnode, ParseNode *breakOnBodySc } this->StartEmitFunction(pnode); - // Persist outer func scope info if nested func is deferred - if (CONFIG_FLAG(DeferNested)) - { - FuncInfo* parentFunc = TopFuncInfo(); - Js::ScopeInfo::SaveScopeInfoForDeferParse(this, parentFunc, funcInfo); - PushFuncInfo(_u("StartEmitFunction"), funcInfo); - } + PushFuncInfo(_u("StartEmitFunction"), funcInfo); if (paramScope && !paramScope->GetCanMergeWithBodyScope()) { @@ -3842,21 +3841,6 @@ void ByteCodeGenerator::StartEmitFunction(ParseNode *pnodeFnc) // Only set the environment depth if it's truly known (i.e., not in eval or event handler). funcInfo->GetParsedFunctionBody()->SetEnvDepth(this->envDepth); } - - if (pnodeFnc->sxFnc.FIBPreventsDeferral()) - { - for (Scope *scope = this->currentScope; scope; scope = scope->GetEnclosingScope()) - { - if (scope->GetScopeType() != ScopeType_FunctionBody && - scope->GetScopeType() != ScopeType_Global && - scope->GetScopeType() != ScopeType_GlobalEvalBlock && - scope->GetMustInstantiate()) - { - funcInfo->byteCodeFunction->SetAttributes((Js::FunctionInfo::Attributes)(funcInfo->byteCodeFunction->GetAttributes() & ~Js::FunctionInfo::Attributes::CanDefer)); - break; - } - } - } } if (funcInfo->GetCallsEval()) diff --git a/lib/Runtime/ByteCode/ByteCodeGenerator.cpp b/lib/Runtime/ByteCode/ByteCodeGenerator.cpp index 25a785514f1..55b9af6173c 100644 --- a/lib/Runtime/ByteCode/ByteCodeGenerator.cpp +++ b/lib/Runtime/ByteCode/ByteCodeGenerator.cpp @@ -968,84 +968,101 @@ Js::RegSlot ByteCodeGenerator::EnregisterStringTemplateCallsiteConstant(ParseNod // // Restore all outer func scope info when reparsing a deferred func. // -void ByteCodeGenerator::RestoreScopeInfo(Js::ParseableFunctionInfo* functionBody) +void ByteCodeGenerator::RestoreScopeInfo(Js::ScopeInfo *scopeInfo, FuncInfo * func) { - if (functionBody && functionBody->GetScopeInfo()) + if (scopeInfo) { PROBE_STACK(scriptContext, Js::Constants::MinStackByteCodeVisitor); - Js::ScopeInfo* scopeInfo = functionBody->GetScopeInfo(); - RestoreScopeInfo(scopeInfo->GetParent()); // Recursively restore outer func scope info + Js::ParseableFunctionInfo * pfi = scopeInfo->GetFunctionInfo()->GetParseableFunctionInfo(); + bool newFunc = (func == nullptr || func->byteCodeFunction != pfi); - Js::ScopeInfo* paramScopeInfo = scopeInfo->GetParamScopeInfo(); - Scope* paramScope = nullptr; - if (paramScopeInfo != nullptr) + if (newFunc) { - paramScope = paramScopeInfo->GetScope(); - Assert(paramScope); - if (!paramScopeInfo->GetCanMergeWithBodyScope()) - { - paramScope->SetCannotMergeWithBodyScope(); - } - // We need the funcInfo before continuing the restoration of the param scope, so wait for the funcInfo to be created. - } - - Scope* bodyScope = scopeInfo->GetScope(); - - Assert(bodyScope); - bodyScope->SetHasOwnLocalInClosure(scopeInfo->GetHasOwnLocalInClosure()); - - FuncInfo* func = Anew(alloc, FuncInfo, functionBody->GetDisplayName(), alloc, paramScope, bodyScope, nullptr, functionBody); - - if (bodyScope->GetScopeType() == ScopeType_GlobalEvalBlock) - { - func->bodyScope = this->currentScope; + func = Anew(alloc, FuncInfo, pfi->GetDisplayName(), alloc, nullptr, nullptr, nullptr, pfi); + newFunc = true; } - PushFuncInfo(_u("RestoreScopeInfo"), func); - if (!functionBody->DoStackNestedFunc()) - { - func->hasEscapedUseNestedFunc = true; - } + // Recursively restore enclosing scope info so outermost scopes/funcs are pushed first. + this->RestoreScopeInfo(scopeInfo->GetParentScopeInfo(), func); + this->RestoreOneScope(scopeInfo, func); - Js::ScopeInfo* funcExprScopeInfo = scopeInfo->GetFuncExprScopeInfo(); - if (funcExprScopeInfo) + if (newFunc) { - Scope* funcExprScope = funcExprScopeInfo->GetScope(); - Assert(funcExprScope); - funcExprScope->SetFunc(func); - func->SetFuncExprScope(funcExprScope); - funcExprScopeInfo->GetScopeInfo(nullptr, this, func, funcExprScope); - } - - // Restore the param scope after the function expression scope - if (paramScope != nullptr) - { - paramScope->SetFunc(func); - paramScopeInfo->GetScopeInfo(nullptr, this, func, paramScope); + PushFuncInfo(_u("RestoreScopeInfo"), func); + if (!pfi->DoStackNestedFunc()) + { + func->hasEscapedUseNestedFunc = true; + } } - scopeInfo->GetScopeInfo(nullptr, this, func, bodyScope); } else { Assert(this->TopFuncInfo() == nullptr); // funcBody is glo + Assert(currentScope == nullptr); currentScope = Anew(alloc, Scope, alloc, ScopeType_Global); globalScope = currentScope; - FuncInfo *func = Anew(alloc, FuncInfo, Js::Constants::GlobalFunction, - alloc, nullptr, currentScope, nullptr, functionBody); - PushFuncInfo(_u("RestoreScopeInfo"), func); + if (func == nullptr || !func->byteCodeFunction->GetIsGlobalFunc()) + { + func = Anew(alloc, FuncInfo, Js::Constants::GlobalFunction, + alloc, nullptr, nullptr/*currentScope*/, nullptr, nullptr/*functionBody*/); + PushFuncInfo(_u("RestoreScopeInfo"), func); + } + func->SetBodyScope(currentScope); } } +void ByteCodeGenerator::RestoreOneScope(Js::ScopeInfo * scopeInfo, FuncInfo * func) +{ + TRACE_BYTECODE(_u("\nRestore ScopeInfo: %s #symbols: %d %s\n"), + func->name, scopeInfo->GetSymbolCount(), scopeInfo->IsObject() ? _u("isObject") : _u("")); + + Scope * scope = scopeInfo->GetScope(); + + scope->SetFunc(func); + + switch (scope->GetScopeType()) + { + case ScopeType_Parameter: + if (!scopeInfo->GetCanMergeWithBodyScope()) + { + scope->SetCannotMergeWithBodyScope(); + } + Assert(func->GetParamScope() == nullptr); + func->SetParamScope(scope); + break; + + case ScopeType_FuncExpr: + Assert(func->GetFuncExprScope() == nullptr); + func->SetFuncExprScope(scope); + break; + + case ScopeType_FunctionBody: + case ScopeType_GlobalEvalBlock: + Assert(func->GetBodyScope() == nullptr || (func->GetBodyScope()->GetScopeType() == ScopeType_Global && scope->GetScopeType() == ScopeType_GlobalEvalBlock)); + func->SetBodyScope(scope); + func->SetHasCachedScope(scopeInfo->IsCached()); + break; + } + + Assert(!scopeInfo->IsCached() || scope == func->GetBodyScope()); + + // scopeInfo->scope was created/saved during parsing. + // We no longer need it by now. + // Clear it to avoid GC false positive (arena memory later used by GC). + scopeInfo->SetScope(nullptr); + this->PushScope(scope); +} + FuncInfo * ByteCodeGenerator::StartBindGlobalStatements(ParseNode *pnode) { - if (parentScopeInfo && parentScopeInfo->GetParent() && (!parentScopeInfo->GetParent()->GetIsGlobalFunc() || parentScopeInfo->GetParent()->IsEval())) + if (parentScopeInfo) { Assert(CONFIG_FLAG(DeferNested)); trackEnvDepth = true; - RestoreScopeInfo(parentScopeInfo->GetParent()); + RestoreScopeInfo(parentScopeInfo, nullptr); trackEnvDepth = false; // "currentScope" is the parentFunc scope. This ensures the deferred func declaration // symbol will bind to the func declaration symbol already available in parentFunc scope. @@ -1211,7 +1228,7 @@ FuncInfo * ByteCodeGenerator::StartBindFunction(const char16 *name, uint nameLen if (parsedFunctionBody->GetScopeInfo()) { // Propagate flags from the (real) parent function. - Js::ParseableFunctionInfo *parent = parsedFunctionBody->GetScopeInfo()->GetParent(); + Js::ParseableFunctionInfo *parent = parsedFunctionBody->GetScopeInfo()->GetParseableFunctionInfo(); if (parent) { if (parent->GetHasOrParentHasArguments()) @@ -1632,7 +1649,8 @@ Symbol * ByteCodeGenerator::FindSymbol(Symbol **symRef, IdentPtr pid, bool forRe bool didTransferToFncVarSym = false; - if (!PHASE_OFF(Js::OptimizeBlockScopePhase, top->byteCodeFunction) && + #pragma prefast(suppress:6237, "The right hand side condition does not have any side effects.") + if (PHASE_ON(Js::OptimizeBlockScopePhase, top->byteCodeFunction) && sym->GetIsBlockVar() && !sym->GetScope()->IsBlockInLoop() && sym->GetSymbolType() == STFunction) @@ -1755,7 +1773,7 @@ Symbol * ByteCodeGenerator::AddSymbolToScope(Scope *scope, const char16 *key, in // on such compiles, so we essentially have to migrate the symbol to the new scope. // We check fscrEvalCode, not fscrEval, because the same thing can happen in indirect eval, // when fscrEval is not set. - Assert(scope->GetScopeType() == ScopeType_Global); + Assert(scope->GetScopeType() == ScopeType_Global || scope->GetScopeType() == ScopeType_GlobalEvalBlock); scope->AddNewSymbol(sym); } @@ -1995,7 +2013,7 @@ void ByteCodeGenerator::Generate(__in ParseNode *pnode, uint32 grfscr, __in Byte void ByteCodeGenerator::CheckDeferParseHasMaybeEscapedNestedFunc() { - if (!this->parentScopeInfo || (this->parentScopeInfo->GetParent() && this->parentScopeInfo->GetParent()->GetIsGlobalFunc())) + if (!this->parentScopeInfo) { return; } @@ -2023,7 +2041,7 @@ void ByteCodeGenerator::CheckDeferParseHasMaybeEscapedNestedFunc() else { // We have to wait until it is parsed before we populate the stack nested func parent. - FuncInfo * parentFunc = top->GetBodyScope()->GetEnclosingFunc(); + FuncInfo * parentFunc = top->GetParamScope() ? top->GetParamScope()->GetEnclosingFunc() : top->GetBodyScope()->GetEnclosingFunc(); if (!parentFunc->IsGlobalFunction()) { Assert(parentFunc->byteCodeFunction != rootFuncBody); @@ -2040,6 +2058,11 @@ void ByteCodeGenerator::CheckDeferParseHasMaybeEscapedNestedFunc() FuncInfo * funcInfo = i.Data(); Assert(funcInfo->IsRestored()); Js::ParseableFunctionInfo * parseableFunctionInfo = funcInfo->byteCodeFunction; + if (parseableFunctionInfo == nullptr) + { + Assert(funcInfo->GetBodyScope() && funcInfo->GetBodyScope()->GetScopeType() == ScopeType_Global); + return; + } bool didStackNestedFunc = parseableFunctionInfo->DoStackNestedFunc(); if (!didStackNestedFunc) { @@ -3247,6 +3270,7 @@ void AddFunctionsToScope(ParseNodePtr scope, ByteCodeGenerator * byteCodeGenerat sym->GetScope() != sym->GetScope()->GetFunc()->GetParamScope()) { sym->SetIsBlockVar(true); + sym->SetHasRealBlockVarRef(true); } } }); @@ -3629,15 +3653,21 @@ void PostVisitBlock(ParseNode *pnode, ByteCodeGenerator *byteCodeGenerator) return; } + Scope *scope = pnode->sxBlock.scope; + if (pnode->sxBlock.GetCallsEval() || pnode->sxBlock.GetChildCallsEval() || (byteCodeGenerator->GetFlags() & (fscrEval | fscrImplicitThis | fscrImplicitParents))) { - Scope *scope = pnode->sxBlock.scope; bool scopeIsEmpty = scope->IsEmpty(); scope->SetIsObject(); scope->SetCapturesAll(true); scope->SetMustInstantiate(!scopeIsEmpty); } + if (scope->GetHasOwnLocalInClosure()) + { + byteCodeGenerator->ProcessScopeWithCapturedSym(scope); + } + byteCodeGenerator->PopScope(); byteCodeGenerator->PopBlock(); @@ -3686,6 +3716,11 @@ void PreVisitCatch(ParseNode *pnode, ByteCodeGenerator *byteCodeGenerator) void PostVisitCatch(ParseNode *pnode, ByteCodeGenerator *byteCodeGenerator) { + Scope *scope = pnode->sxCatch.scope; + if (scope->GetHasOwnLocalInClosure()) + { + byteCodeGenerator->ProcessScopeWithCapturedSym(scope); + } byteCodeGenerator->EndBindCatch(); } diff --git a/lib/Runtime/ByteCode/ByteCodeGenerator.h b/lib/Runtime/ByteCode/ByteCodeGenerator.h index 913f0073222..4a0e6f9fcda 100644 --- a/lib/Runtime/ByteCode/ByteCodeGenerator.h +++ b/lib/Runtime/ByteCode/ByteCodeGenerator.h @@ -207,7 +207,9 @@ class ByteCodeGenerator return protoReg; } - void RestoreScopeInfo(Js::ParseableFunctionInfo* funcInfo); + void RestoreScopeInfo(Js::ScopeInfo *scopeInfo, FuncInfo * func); + void RestoreOneScope(Js::ScopeInfo * scopeInfo, FuncInfo * func); + FuncInfo *StartBindGlobalStatements(ParseNode *pnode); void AssignPropertyId(Symbol *sym, Js::ParseableFunctionInfo* functionInfo); void AssignPropertyId(IdentPtr pid); diff --git a/lib/Runtime/ByteCode/FuncInfo.cpp b/lib/Runtime/ByteCode/FuncInfo.cpp index 2bd8a38e33a..7446204c688 100644 --- a/lib/Runtime/ByteCode/FuncInfo.cpp +++ b/lib/Runtime/ByteCode/FuncInfo.cpp @@ -105,7 +105,10 @@ FuncInfo::FuncInfo( maxForInLoopLevel(0) { this->byteCodeFunction = byteCodeFunction; - bodyScope->SetFunc(this); + if (bodyScope != nullptr) + { + bodyScope->SetFunc(this); + } if (paramScope != nullptr) { paramScope->SetFunc(this); diff --git a/lib/Runtime/ByteCode/FuncInfo.h b/lib/Runtime/ByteCode/FuncInfo.h index c9725acda85..d47e14c8f99 100644 --- a/lib/Runtime/ByteCode/FuncInfo.h +++ b/lib/Runtime/ByteCode/FuncInfo.h @@ -263,24 +263,39 @@ class FuncInfo // 1) new Function code's global code // 2) global code generated from the reparsing deferred parse function - bool IsFakeGlobalFunction(uint32 flags) const { + bool IsFakeGlobalFunction(uint32 flags) const + { return IsGlobalFunction() && !(flags & fscrGlobalCode); } - Scope *GetBodyScope() const { + Scope *GetBodyScope() const + { return bodyScope; } - Scope *GetParamScope() const { + void SetBodyScope(Scope * scope) + { + bodyScope = scope; + } + + Scope *GetParamScope() const + { return paramScope; } - Scope *GetTopLevelScope() const { + void SetParamScope(Scope * scope) + { + paramScope = scope; + } + + Scope *GetTopLevelScope() const + { // Top level scope will be the same for knopProg and knopFncDecl. return paramScope; } - Scope* GetFuncExprScope() const { + Scope* GetFuncExprScope() const + { return funcExprScope; } diff --git a/lib/Runtime/ByteCode/Scope.h b/lib/Runtime/ByteCode/Scope.h index fd22ab74cd1..1897675e973 100644 --- a/lib/Runtime/ByteCode/Scope.h +++ b/lib/Runtime/ByteCode/Scope.h @@ -22,6 +22,7 @@ class Scope { private: Scope *enclosingScope; + Js::ScopeInfo *scopeInfo; Js::RegSlot location; FuncInfo *func; Symbol *m_symList; @@ -48,6 +49,7 @@ class Scope alloc(alloc), func(nullptr), enclosingScope(nullptr), + scopeInfo(nullptr), isDynamic(false), isObject(false), canMerge(true), @@ -177,6 +179,16 @@ class Scope return enclosingScope; } + void SetScopeInfo(Js::ScopeInfo * scopeInfo) + { + this->scopeInfo = scopeInfo; + } + + Js::ScopeInfo * GetScopeInfo() const + { + return this->scopeInfo; + } + ScopeType GetScopeType() const { return this->scopeType; diff --git a/lib/Runtime/ByteCode/ScopeInfo.cpp b/lib/Runtime/ByteCode/ScopeInfo.cpp index a54800c5c8e..20841053aea 100644 --- a/lib/Runtime/ByteCode/ScopeInfo.cpp +++ b/lib/Runtime/ByteCode/ScopeInfo.cpp @@ -42,15 +42,6 @@ namespace Js TRACE_BYTECODE(_u("%12s %d\n"), sym->GetName().GetBuffer(), sym->GetScopeSlot()); } - // - // Create scope info for a deferred child to refer to its parent ParseableFunctionInfo. - // - ScopeInfo* ScopeInfo::FromParent(FunctionBody* parent) - { - return RecyclerNew(parent->GetScriptContext()->GetRecycler(), // Alloc with ParseableFunctionInfo - ScopeInfo, parent->GetFunctionInfo(), 0); - } - inline void AddSlotCount(int& count, int addCount) { if (addCount != 0 && Int32Math::Add(count, addCount, &count)) @@ -60,10 +51,13 @@ namespace Js } // - // Create scope info for an outer scope. + // Create scope info for a single scope. // - ScopeInfo* ScopeInfo::FromScope(ByteCodeGenerator* byteCodeGenerator, ParseableFunctionInfo* parent, Scope* scope, ScriptContext *scriptContext) + ScopeInfo* ScopeInfo::SaveOneScopeInfo(/*ByteCodeGenerator* byteCodeGenerator, ParseableFunctionInfo* parent,*/ Scope* scope, ScriptContext *scriptContext) { + Assert(scope->GetScopeInfo() == nullptr); + Assert(scope->GetScopeType() != ScopeType_Global); + int count = scope->Count(); // Add argsPlaceHolder which includes same name args and destructuring patterns on parameters @@ -74,26 +68,29 @@ namespace Js ScopeInfo* scopeInfo = RecyclerNewPlusZ(scriptContext->GetRecycler(), count * sizeof(SymbolInfo), - ScopeInfo, parent ? parent->GetFunctionInfo() : nullptr, count); + ScopeInfo, scope->GetFunc()->byteCodeFunction->GetFunctionInfo(),/*parent ? parent->GetFunctionInfo() : nullptr,*/ count); + scopeInfo->SetScopeType(scope->GetScopeType()); scopeInfo->isDynamic = scope->GetIsDynamic(); scopeInfo->isObject = scope->GetIsObject(); scopeInfo->mustInstantiate = scope->GetMustInstantiate(); scopeInfo->isCached = (scope->GetFunc()->GetBodyScope() == scope) && scope->GetFunc()->GetHasCachedScope(); - scopeInfo->isGlobalEval = scope->GetScopeType() == ScopeType_GlobalEvalBlock; scopeInfo->canMergeWithBodyScope = scope->GetCanMergeWithBodyScope(); scopeInfo->hasLocalInClosure = scope->GetHasOwnLocalInClosure(); + - TRACE_BYTECODE(_u("\nSave ScopeInfo: %s parent: %s #symbols: %d %s\n"), - scope->GetFunc()->name, parent ? parent->GetDisplayName() : _u(""), count, + TRACE_BYTECODE(_u("\nSave ScopeInfo: %s #symbols: %d %s\n"), + scope->GetFunc()->name, /*parent ? parent->GetDisplayName() : _u(""),*/ count, scopeInfo->isObject ? _u("isObject") : _u("")); - MapSymbolData mapSymbolData = { byteCodeGenerator, scope->GetFunc(), 0 }; + MapSymbolData mapSymbolData = { /*byteCodeGenerator,*/ scope->GetFunc(), 0 }; scope->ForEachSymbol([&mapSymbolData, scopeInfo, scope](Symbol * sym) { Assert(scope == sym->GetScope()); scopeInfo->SaveSymbolInfo(sym, &mapSymbolData); }); + scope->SetScopeInfo(scopeInfo); + return scopeInfo; } @@ -107,75 +104,46 @@ namespace Js auto propertyName = scriptContext->GetPropertyName(symbols[i].propertyId); scriptContext->TrackPid(propertyName); } - if (funcExprScopeInfo) - { - funcExprScopeInfo->EnsurePidTracking(scriptContext); - } - if (paramScopeInfo) - { - paramScopeInfo->EnsurePidTracking(scriptContext); - } - } - - // - // Save needed scope info for a deferred child func. The scope info is empty and only links to parent. - // - void ScopeInfo::SaveParentScopeInfo(FuncInfo* parentFunc, FuncInfo* func) - { - Assert(func->IsDeferred() || func->byteCodeFunction->CanBeDeferred()); - - // Parent must be parsed - FunctionBody* parent = parentFunc->byteCodeFunction->GetFunctionBody(); - ParseableFunctionInfo* funcBody = func->byteCodeFunction; - - TRACE_BYTECODE(_u("\nSave ScopeInfo: %s parent: %s\n\n"), - funcBody->GetDisplayName(), parent->GetDisplayName()); - - ScopeInfo *info = FromParent(parent); - info->parentOnly = true; - funcBody->SetScopeInfo(info); } // - // Save scope info for an outer func which has deferred child. + // Save scope info for an individual scope and link it to its enclosing scope. // - void ScopeInfo::SaveScopeInfo(ByteCodeGenerator* byteCodeGenerator, FuncInfo* parentFunc, FuncInfo* func) + ScopeInfo * ScopeInfo::SaveScopeInfo(Scope * scope/*ByteCodeGenerator* byteCodeGenerator, FuncInfo* parentFunc, FuncInfo* func*/, ScriptContext * scriptContext) { - ParseableFunctionInfo* funcBody = func->byteCodeFunction; - - Assert((!func->IsGlobalFunction() || byteCodeGenerator->GetFlags() & fscrEvalCode) && - (func->HasDeferredChild() || func->HasRedeferrableChild() || funcBody->IsReparsed())); - - // If we are reparsing a deferred function, we already have correct "parent" info in - // funcBody->scopeInfo. parentFunc is the knopProg shell and should not be used in this - // case. We should use existing parent if available. - ParseableFunctionInfo * parent = funcBody->GetScopeInfo() ? - funcBody->GetScopeInfo()->GetParent() : - parentFunc ? parentFunc->byteCodeFunction : nullptr; + // Advance past scopes that will be excluded from the closure environment. (But note that we always want the body scope.) + while (scope && (!scope->GetMustInstantiate() && scope != scope->GetFunc()->GetBodyScope())) + { + scope = scope->GetEnclosingScope(); + } - ScopeInfo* funcExprScopeInfo = nullptr; - Scope* funcExprScope = func->GetFuncExprScope(); - if (funcExprScope && funcExprScope->GetMustInstantiate()) + // If we've exhausted the scope chain, we're done. + if (scope == nullptr || scope->GetScopeType() == ScopeType_Global) { - funcExprScopeInfo = FromScope(byteCodeGenerator, parent, funcExprScope, funcBody->GetScriptContext()); + return nullptr; } - Scope* bodyScope = func->IsGlobalFunction() ? func->GetGlobalEvalBlockScope() : func->GetBodyScope(); - ScopeInfo* paramScopeInfo = nullptr; - Scope* paramScope = func->GetParamScope(); - if (paramScope && !paramScope->GetCanMergeWithBodyScope()) + // If we've already collected info for this scope, we're done. + ScopeInfo * scopeInfo = scope->GetScopeInfo(); + if (scopeInfo != nullptr) { - paramScopeInfo = FromScope(byteCodeGenerator, parent, paramScope, funcBody->GetScriptContext()); + return scopeInfo; } - ScopeInfo* scopeInfo = FromScope(byteCodeGenerator, parent, bodyScope, funcBody->GetScriptContext()); - scopeInfo->SetFuncExprScopeInfo(funcExprScopeInfo); - scopeInfo->SetParamScopeInfo(paramScopeInfo); + // Do the work for this scope. + scopeInfo = ScopeInfo::SaveOneScopeInfo(scope, scriptContext); - funcBody->SetScopeInfo(scopeInfo); + // Link to the parent (if any). + scope = scope->GetEnclosingScope(); + if (scope) + { + scopeInfo->SetParentScopeInfo(ScopeInfo::SaveScopeInfo(scope, scriptContext)); + } + + return scopeInfo; } - void ScopeInfo::SaveScopeInfoForDeferParse(ByteCodeGenerator* byteCodeGenerator, FuncInfo* parentFunc, FuncInfo* funcInfo) + void ScopeInfo::SaveEnclosingScopeInfo(ByteCodeGenerator* byteCodeGenerator, /*FuncInfo* parentFunc,*/ FuncInfo* funcInfo) { // TODO: Not technically necessary, as we always do scope look up on eval if it is // not found in the scope chain, and block scopes are always objects in eval. @@ -185,116 +153,52 @@ namespace Js // enable defer parsing function that are in block scopes. if (funcInfo->byteCodeFunction && - funcInfo->byteCodeFunction->GetScopeInfo() != nullptr && - !funcInfo->byteCodeFunction->GetScopeInfo()->IsParentInfoOnly()) + funcInfo->byteCodeFunction->GetScopeInfo() != nullptr /*&& + !funcInfo->byteCodeFunction->GetScopeInfo()->IsParentInfoOnly()*/) { // No need to regenerate scope info if we re-compile an enclosing function return; } Scope* currentScope = byteCodeGenerator->GetCurrentScope(); - Assert(currentScope == funcInfo->GetBodyScope()); - if (funcInfo->HasDeferredChild() || - funcInfo->HasRedeferrableChild() || - (!funcInfo->IsGlobalFunction() && - funcInfo->byteCodeFunction && - funcInfo->byteCodeFunction->IsReparsed() && - funcInfo->byteCodeFunction->GetFunctionBody()->HasAllNonLocalReferenced())) - { - // When we reparse due to attach, we would need to capture all of them, since they were captured before going to debug mode. + Assert(currentScope->GetFunc() == funcInfo); - Js::ScopeInfo::SaveScopeInfo(byteCodeGenerator, parentFunc, funcInfo); + while (currentScope->GetFunc() == funcInfo) + { + currentScope = currentScope->GetEnclosingScope(); } - else if (funcInfo->IsDeferred() || funcInfo->IsRedeferrable()) + + ScopeInfo * scopeInfo = ScopeInfo::SaveScopeInfo(currentScope, byteCodeGenerator->GetScriptContext()); + if (scopeInfo != nullptr) { - // Don't need to remember the parent function if we have a global function - if (!parentFunc->IsGlobalFunction() || - ((byteCodeGenerator->GetFlags() & fscrEvalCode) && (parentFunc->HasDeferredChild() || parentFunc->HasRedeferrableChild()))) - { - // TODO: currently we only support defer nested function that is in function scope (no block scope, no with scope, etc.) -#if DBG - if (funcInfo->GetFuncExprScope() && funcInfo->GetFuncExprScope()->GetIsObject()) - { - if (funcInfo->paramScope && !funcInfo->paramScope->GetCanMergeWithBodyScope()) - { - Assert(currentScope->GetEnclosingScope()->GetEnclosingScope() == funcInfo->GetFuncExprScope()); - } - else - { - Assert(currentScope->GetEnclosingScope() == funcInfo->GetFuncExprScope() && - currentScope->GetEnclosingScope()->GetEnclosingScope() == - (parentFunc->IsGlobalFunction() && parentFunc->GetGlobalEvalBlockScope()->GetMustInstantiate() ? - parentFunc->GetGlobalEvalBlockScope() : parentFunc->GetBodyScope())); - } - } - else - { - if (currentScope->GetEnclosingScope() == parentFunc->GetParamScope()) - { - Assert(!parentFunc->GetParamScope()->GetCanMergeWithBodyScope()); - Assert(funcInfo->GetParamScope()->GetCanMergeWithBodyScope()); - } - else if (currentScope->GetEnclosingScope() == funcInfo->GetParamScope()) - { - Assert(!funcInfo->GetParamScope()->GetCanMergeWithBodyScope()); - } -#if 0 - else - { - Assert(currentScope->GetEnclosingScope() == - (parentFunc->IsGlobalFunction() && parentFunc->GetGlobalEvalBlockScope() && parentFunc->GetGlobalEvalBlockScope()->GetMustInstantiate() ? parentFunc->GetGlobalEvalBlockScope() : parentFunc->GetBodyScope())); - } -#endif - } -#endif - Js::ScopeInfo::SaveParentScopeInfo(parentFunc, funcInfo); - } + funcInfo->byteCodeFunction->SetScopeInfo(scopeInfo); } } // // Load persisted scope info. // - void ScopeInfo::GetScopeInfo(Parser *parser, ByteCodeGenerator* byteCodeGenerator, FuncInfo* funcInfo, Scope* scope) + void ScopeInfo::ExtractScopeInfo(Parser *parser, /*ByteCodeGenerator* byteCodeGenerator, FuncInfo* funcInfo,*/ Scope* scope) { ScriptContext* scriptContext; ArenaAllocator* alloc; // Load scope attributes and push onto scope stack. + scope->SetMustInstantiate(this->mustInstantiate); + scope->SetHasOwnLocalInClosure(this->hasLocalInClosure); scope->SetIsDynamic(this->isDynamic); if (this->isObject) { scope->SetIsObject(); } - scope->SetMustInstantiate(this->mustInstantiate); if (!this->GetCanMergeWithBodyScope()) { scope->SetCannotMergeWithBodyScope(); } - scope->SetHasOwnLocalInClosure(this->hasLocalInClosure); - if (parser) - { - scriptContext = parser->GetScriptContext(); - alloc = parser->GetAllocator(); - } - else - { - TRACE_BYTECODE(_u("\nRestore ScopeInfo: %s #symbols: %d %s\n"), - funcInfo->name, symbolCount, isObject ? _u("isObject") : _u("")); - - Assert(!this->isCached || scope == funcInfo->GetBodyScope()); - funcInfo->SetHasCachedScope(this->isCached); - byteCodeGenerator->PushScope(scope); - - // this->scope was created/saved during parsing and used by - // ByteCodeGenerator::RestoreScopeInfo. We no longer need it by now. - // Clear it to avoid GC false positive (arena memory later used by GC). - Assert(this->scope == scope); - this->scope = nullptr; + Assert(parser); - // The scope is already populated, so we're done. - return; - } + scriptContext = parser->GetScriptContext(); + alloc = parser->GetAllocator(); // Load scope symbols // On first access to the scopeinfo, replace the ID's with PropertyRecord*'s to save the dictionary lookup diff --git a/lib/Runtime/ByteCode/ScopeInfo.h b/lib/Runtime/ByteCode/ScopeInfo.h index ec502a99d5b..a3ae0b8f057 100644 --- a/lib/Runtime/ByteCode/ScopeInfo.h +++ b/lib/Runtime/ByteCode/ScopeInfo.h @@ -13,7 +13,6 @@ namespace Js { { struct MapSymbolData { - ByteCodeGenerator* byteCodeGenerator; FuncInfo* func; int nonScopeSymbolCount; }; @@ -34,43 +33,29 @@ namespace Js { }; private: - Field(FunctionInfo * const) parent; // link to parent function - Field(ScopeInfo*) funcExprScopeInfo; // optional func expr scope info - Field(ScopeInfo*) paramScopeInfo; // optional param scope info + Field(ScopeInfo *) parent; // link to parent scope info (if any) + Field(FunctionInfo * const) functionInfo;// link to function owning this scope Field(BYTE) isDynamic : 1; // isDynamic bit affects how deferredChild access global ref Field(BYTE) isObject : 1; // isObject bit affects how deferredChild access closure symbols Field(BYTE) mustInstantiate : 1; // the scope must be instantiated as an object/array Field(BYTE) isCached : 1; // indicates that local vars and functions are cached across invocations - Field(BYTE) isGlobalEval : 1; Field(BYTE) areNamesCached : 1; Field(BYTE) canMergeWithBodyScope : 1; Field(BYTE) hasLocalInClosure : 1; - Field(BYTE) parentOnly : 1; FieldNoBarrier(Scope *) scope; + Field(::ScopeType) scopeType; Field(int) scopeId; Field(int) symbolCount; // symbol count in this scope Field(SymbolInfo) symbols[]; // symbol PropertyIDs, index == sym.scopeSlot private: - ScopeInfo(FunctionInfo * parent, int symbolCount) - : parent(parent), funcExprScopeInfo(nullptr), paramScopeInfo(nullptr), symbolCount(symbolCount), scope(nullptr), areNamesCached(false), canMergeWithBodyScope(true), hasLocalInClosure(false), parentOnly(false) + ScopeInfo(FunctionInfo * function, int symbolCount) + : functionInfo(function), /*funcExprScopeInfo(nullptr), paramScopeInfo(nullptr),*/ symbolCount(symbolCount), parent(nullptr), scope(nullptr), areNamesCached(false), canMergeWithBodyScope(true), hasLocalInClosure(false)/*, parentOnly(false)*/ { } - bool IsParentInfoOnly() const { return parentOnly; } - - void SetFuncExprScopeInfo(ScopeInfo* funcExprScopeInfo) - { - this->funcExprScopeInfo = funcExprScopeInfo; - } - - void SetParamScopeInfo(ScopeInfo* paramScopeInfo) - { - this->paramScopeInfo = paramScopeInfo; - } - void SetSymbolId(int i, PropertyId propertyId) { Assert(!areNamesCached); @@ -179,30 +164,29 @@ namespace Js { void SaveSymbolInfo(Symbol* sym, MapSymbolData* mapSymbolData); - static ScopeInfo* FromParent(FunctionBody* parent); - static ScopeInfo* FromScope(ByteCodeGenerator* byteCodeGenerator, ParseableFunctionInfo* parent, Scope* scope, ScriptContext *scriptContext); - static void SaveParentScopeInfo(FuncInfo* parentFunc, FuncInfo* func); - static void SaveScopeInfo(ByteCodeGenerator* byteCodeGenerator, FuncInfo* parentFunc, FuncInfo* func); + static ScopeInfo* SaveScopeInfo(Scope * scope, ScriptContext * scriptContext); + static ScopeInfo* SaveOneScopeInfo(Scope * scope, ScriptContext * scriptContext); public: - ParseableFunctionInfo * GetParent() const + FunctionInfo * GetFunctionInfo() const { - return parent ? parent->GetParseableFunctionInfo() : nullptr; + return functionInfo; } - ScopeInfo* GetParentScopeInfo() const + ParseableFunctionInfo * GetParseableFunctionInfo() const { - return parent ? parent->GetParseableFunctionInfo()->GetScopeInfo() : nullptr; + return functionInfo ? functionInfo->GetParseableFunctionInfo() : nullptr; } - ScopeInfo* GetFuncExprScopeInfo() const + ScopeInfo* GetParentScopeInfo() const { - return funcExprScopeInfo; + return parent;//? parent->GetParseableFunctionInfo()->GetScopeInfo() : nullptr; } - ScopeInfo* GetParamScopeInfo() const + void SetParentScopeInfo(ScopeInfo * parent) { - return paramScopeInfo; + Assert(this->parent == nullptr); + this->parent = parent; } Scope * GetScope() const @@ -210,6 +194,21 @@ namespace Js { return scope; } + void SetScope(Scope * scope) + { + this->scope = scope; + } + + ::ScopeType GetScopeType() const + { + return scopeType; + } + + void SetScopeType(::ScopeType type) + { + this->scopeType = type; + } + void SetScopeId(int id) { this->scopeId = id; @@ -225,9 +224,14 @@ namespace Js { return symbolCount; } - bool IsGlobalEval() const + bool IsObject() const + { + return isObject; + } + + bool IsCached() const { - return isGlobalEval; + return isCached; } bool GetCanMergeWithBodyScope() const @@ -245,11 +249,11 @@ namespace Js { return hasLocalInClosure; } - static void SaveScopeInfoForDeferParse(ByteCodeGenerator* byteCodeGenerator, FuncInfo* parentFunc, FuncInfo* func); + static void SaveEnclosingScopeInfo(ByteCodeGenerator* byteCodeGenerator, /*FuncInfo* parentFunc,*/ FuncInfo* func); void EnsurePidTracking(ScriptContext* scriptContext); - void GetScopeInfo(Parser *parser, ByteCodeGenerator* byteCodeGenerator, FuncInfo* funcInfo, Scope* scope); + void ExtractScopeInfo(Parser *parser, /*ByteCodeGenerator* byteCodeGenerator, FuncInfo* funcInfo,*/ Scope* scope); // // Turn on capturesAll for a Scope temporarily. Restore old capturesAll when this object diff --git a/lib/Runtime/ByteCode/Symbol.cpp b/lib/Runtime/ByteCode/Symbol.cpp index db255ae08f4..e4e36aa147e 100644 --- a/lib/Runtime/ByteCode/Symbol.cpp +++ b/lib/Runtime/ByteCode/Symbol.cpp @@ -200,7 +200,7 @@ Symbol * Symbol::GetFuncScopeVarSym() const Scope* paramScope = parentFuncInfo->GetParamScope(); fncScopeSym = paramScope->FindLocalSymbol(this->GetName()); } - Assert(fncScopeSym); + // Parser should have added a fake var decl node for block scoped functions in non-strict mode // IsBlockVar() indicates a user let declared variable at function scope which // shadows the function's var binding, thus only emit the var binding init if diff --git a/test/DebuggerCommon/blockScopeFunctionDeclarationSlotArrayTest.js.dbg.baseline b/test/DebuggerCommon/blockScopeFunctionDeclarationSlotArrayTest.js.dbg.baseline index 6026b71ec25..bbba8616b71 100644 --- a/test/DebuggerCommon/blockScopeFunctionDeclarationSlotArrayTest.js.dbg.baseline +++ b/test/DebuggerCommon/blockScopeFunctionDeclarationSlotArrayTest.js.dbg.baseline @@ -21,6 +21,7 @@ "this": "Object {...}", "arguments": "Object {...}", "locals": { + "f": "function function f() { }", "g": "function ", "a": "number 1" } diff --git a/test/stackfunc/let_blockescape.deferparse.baseline b/test/stackfunc/let_blockescape.deferparse.baseline index 89fa736aacf..c2ee9665ac7 100644 --- a/test/stackfunc/let_blockescape.deferparse.baseline +++ b/test/stackfunc/let_blockescape.deferparse.baseline @@ -1,7 +1,7 @@ HasFuncDecl: test HasFuncDecl: test -HasMaybeEscapedUse: x HasMaybeEscapedNestedFunc (CrossScopeAssignment): test (function (#1.1), #2) HasFuncAssignment: f +HasMaybeEscapedUse: x test1str test2str diff --git a/test/stackfunc/let_stackfunc.deferparse.baseline b/test/stackfunc/let_stackfunc.deferparse.baseline index a6f27af9de5..c69367d32fa 100644 --- a/test/stackfunc/let_stackfunc.deferparse.baseline +++ b/test/stackfunc/let_stackfunc.deferparse.baseline @@ -2,17 +2,18 @@ HasFuncDecl: test HasFuncDecl: nested HasFuncDecl: test HasFuncDecl: nested -HasMaybeEscapedUse: x -HasMaybeEscapedUse: x HasFuncAssignment: f HasMaybeEscapedUse: i HasFuncAssignment: f2 HasFuncAssignment: f3 DoStackNestedFunc: test (function (#1.1), #2) HasFuncDecl: nested +HasMaybeEscapedUse: x test1glo yes test2glo yes +HasMaybeEscapedUse: x 0 +HasMaybeEscapedUse: x f30 undefined f3undefined diff --git a/test/stackfunc/with_namedfunc.deferparse.baseline b/test/stackfunc/with_namedfunc.deferparse.baseline index abb45dca7a8..1dab310b32e 100644 --- a/test/stackfunc/with_namedfunc.deferparse.baseline +++ b/test/stackfunc/with_namedfunc.deferparse.baseline @@ -3,5 +3,6 @@ HasFuncDecl: blah HasFuncDecl: test HasFuncDecl: blah HasMaybeEscapedNestedFunc (UnknownAssignment): test (function (#1.1), #2) +HasFuncDecl: blah test1 test2