Skip to content

Commit 1e22d8d

Browse files
committed
Let redeferral work around block scope limitation of deferred parsing. Detect cases where only block-scoping prevents upfront deferral. Also detect cases where it is safe to treat a lexically-scoped function as a var-scoped one. For functions that meet both criteria, use the var binding for it in place of the lexical binding. If at byte code gen time there are no lexical/with/catch scopes in the current chain, mark the function as a redeferral candidate.
1 parent 08df52c commit 1e22d8d

11 files changed

Lines changed: 246 additions & 73 deletions

File tree

lib/Common/ConfigFlagsList.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ PHASE(All)
2929
PHASE(ByteCodeSerialization)
3030
PHASE(VariableIntEncoding)
3131
PHASE(NativeCodeSerialization)
32+
PHASE(OptimizeBlockScope)
3233
PHASE(Delay)
3334
PHASE(Speculation)
3435
PHASE(GatherCodeGenData)

lib/Parser/Parse.cpp

Lines changed: 81 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ struct StmtNest
5555
};
5656
};
5757
StmtNest *pstmtOuter; // Enclosing statement.
58+
59+
OpCode GetNop() const
60+
{
61+
AnalysisAssert(isDeferred || pnodeStmt != nullptr);
62+
return isDeferred ? op : pnodeStmt->nop;
63+
}
5864
};
5965

6066
struct BlockInfoStack
@@ -110,6 +116,7 @@ Parser::Parser(Js::ScriptContext* scriptContext, BOOL strictMode, PageAllocator
110116
m_errorCallback = nullptr;
111117
m_uncertainStructure = FALSE;
112118
m_reparsingLambdaParams = false;
119+
m_inFIB = false;
113120
currBackgroundParseItem = nullptr;
114121
backgroundParseItems = nullptr;
115122
fastScannedRegExpNodes = nullptr;
@@ -722,59 +729,46 @@ ParseNodePtr Parser::CreateNodeT(charcount_t ichMin,charcount_t ichLim)
722729
return pnode;
723730
}
724731

725-
ParseNodePtr Parser::CreateDeclNode(OpCode nop, IdentPtr pid, SymbolType symbolType, bool errorOnRedecl)
732+
ParseNodePtr Parser::CreateDeclNode(OpCode nop, IdentPtr pid, SymbolType symbolType, bool errorOnRedecl, bool *isRedecl)
726733
{
727734
ParseNodePtr pnode = CreateNode(nop);
728735

729736
pnode->sxVar.InitDeclNode(pid, NULL);
730737

731738
if (symbolType != STUnknown)
732739
{
733-
pnode->sxVar.sym = AddDeclForPid(pnode, pid, symbolType, errorOnRedecl);
740+
pnode->sxVar.sym = AddDeclForPid(pnode, pid, symbolType, errorOnRedecl, isRedecl);
734741
}
735742

736743
return pnode;
737744
}
738745

739-
Symbol* Parser::AddDeclForPid(ParseNodePtr pnode, IdentPtr pid, SymbolType symbolType, bool errorOnRedecl)
746+
Symbol* Parser::AddDeclForPid(ParseNodePtr pnode, IdentPtr pid, SymbolType symbolType, bool errorOnRedecl, bool *isRedecl)
740747
{
741748
Assert(pnode->IsVarLetOrConst());
742749

743750
PidRefStack *refForUse = nullptr, *refForDecl = nullptr;
744751

752+
if (isRedecl)
753+
{
754+
*isRedecl = false;
755+
}
756+
745757
BlockInfoStack *blockInfo;
746758
bool fBlockScope = false;
747759
if (pnode->nop != knopVarDecl || symbolType == STFunction)
748760
{
749761
Assert(m_pstmtCur);
750-
if (m_pstmtCur->isDeferred)
762+
if (m_pstmtCur->GetNop() != knopBlock)
751763
{
752-
// Deferred parsing: there's no pnodeStmt node, only an opcode on the Stmt struct.
753-
if (m_pstmtCur->op != knopBlock)
754-
{
755-
// Let/const declared in a bare statement context.
756-
Error(ERRDeclOutOfStmt);
757-
}
758-
759-
if (m_pstmtCur->pstmtOuter && m_pstmtCur->pstmtOuter->op == knopSwitch)
760-
{
761-
// Let/const declared inside a switch block (requiring conservative use-before-decl check).
762-
pnode->sxVar.isSwitchStmtDecl = true;
763-
}
764+
// Let/const declared in a bare statement context.
765+
Error(ERRDeclOutOfStmt);
764766
}
765-
else
766-
{
767-
if (m_pstmtCur->pnodeStmt->nop != knopBlock)
768-
{
769-
// Let/const declared in a bare statement context.
770-
Error(ERRDeclOutOfStmt);
771-
}
772767

773-
if (m_pstmtCur->pstmtOuter && m_pstmtCur->pstmtOuter->pnodeStmt->nop == knopSwitch)
774-
{
775-
// Let/const declared inside a switch block (requiring conservative use-before-decl check).
776-
pnode->sxVar.isSwitchStmtDecl = true;
777-
}
768+
if (m_pstmtCur->pstmtOuter && m_pstmtCur->pstmtOuter->GetNop() == knopSwitch)
769+
{
770+
// Let/const declared inside a switch block (requiring conservative use-before-decl check).
771+
pnode->sxVar.isSwitchStmtDecl = true;
778772
}
779773

780774
fBlockScope = pnode->nop != knopVarDecl ||
@@ -833,6 +827,10 @@ Symbol* Parser::AddDeclForPid(ParseNodePtr pnode, IdentPtr pid, SymbolType symbo
833827
Symbol *sym = refForDecl->GetSym();
834828
if (sym != nullptr)
835829
{
830+
if (isRedecl)
831+
{
832+
*isRedecl = true;
833+
}
836834
// Multiple declarations in the same scope. 3 possibilities: error, existing one wins, new one wins.
837835
switch (pnode->nop)
838836
{
@@ -901,6 +899,10 @@ Symbol* Parser::AddDeclForPid(ParseNodePtr pnode, IdentPtr pid, SymbolType symbo
901899
{
902900
Assert(blockInfo->pnodeBlock->sxBlock.blockType == PnodeBlockType::Regular);
903901
scope = Anew(&m_nodeAllocator, Scope, &m_nodeAllocator, ScopeType_Block);
902+
if (this->IsCurBlockInLoop())
903+
{
904+
scope->SetIsBlockInLoop();
905+
}
904906
blockInfo->pnodeBlock->sxBlock.scope = scope;
905907
PushScope(scope);
906908
}
@@ -961,6 +963,23 @@ Symbol* Parser::AddDeclForPid(ParseNodePtr pnode, IdentPtr pid, SymbolType symbo
961963
return sym;
962964
}
963965

966+
bool Parser::IsCurBlockInLoop() const
967+
{
968+
for (StmtNest *stmt = this->m_pstmtCur; stmt != nullptr; stmt = stmt->pstmtOuter)
969+
{
970+
OpCode nop = stmt->GetNop();
971+
if (ParseNode::Grfnop(nop) & fnopContinue)
972+
{
973+
return true;
974+
}
975+
if (nop == knopFncDecl)
976+
{
977+
return false;
978+
}
979+
}
980+
return false;
981+
}
982+
964983
void Parser::RestorePidRefForSym(Symbol *sym)
965984
{
966985
IdentPtr pid = m_pscan->m_phtbl->PidHashNameLen(sym->GetName().GetBuffer(), sym->GetName().GetLength());
@@ -1369,9 +1388,9 @@ ParseNodePtr Parser::CreateModuleImportDeclNode(IdentPtr localName)
13691388
return declNode;
13701389
}
13711390

1372-
ParseNodePtr Parser::CreateVarDeclNode(IdentPtr pid, SymbolType symbolType, bool autoArgumentsObject, ParseNodePtr pnodeFnc, bool errorOnRedecl)
1391+
ParseNodePtr Parser::CreateVarDeclNode(IdentPtr pid, SymbolType symbolType, bool autoArgumentsObject, ParseNodePtr pnodeFnc, bool errorOnRedecl, bool *isRedecl)
13731392
{
1374-
ParseNodePtr pnode = CreateDeclNode(knopVarDecl, pid, symbolType, errorOnRedecl);
1393+
ParseNodePtr pnode = CreateDeclNode(knopVarDecl, pid, symbolType, errorOnRedecl, isRedecl);
13751394

13761395
// Append the variable to the end of the current variable list.
13771396
AssertMem(m_ppnodeVar);
@@ -4540,10 +4559,7 @@ ParseNodePtr Parser::ParseFncDecl(ushort flags, LPCOLESTR pNameHint, const bool
45404559

45414560
if (fDeclaration)
45424561
{
4543-
AnalysisAssert(m_pstmtCur->isDeferred || m_pstmtCur->pnodeStmt != nullptr);
4544-
noStmtContext =
4545-
(m_pstmtCur->isDeferred && m_pstmtCur->op != knopBlock) ||
4546-
(!m_pstmtCur->isDeferred && m_pstmtCur->pnodeStmt->nop != knopBlock);
4562+
noStmtContext = m_pstmtCur->GetNop() != knopBlock;
45474563

45484564
if (noStmtContext)
45494565
{
@@ -4712,8 +4728,13 @@ ParseNodePtr Parser::ParseFncDecl(ushort flags, LPCOLESTR pNameHint, const bool
47124728
// level and we accomplish this by having each block scoped function
47134729
// declaration assign to both the block scoped "let" binding, as well
47144730
// as the function scoped "var" binding.
4715-
ParseNodePtr vardecl = CreateVarDeclNode(pnodeFnc->sxFnc.pnodeName->sxVar.pid, STVariable, false, nullptr, false);
4731+
bool isRedecl = false;
4732+
ParseNodePtr vardecl = CreateVarDeclNode(pnodeFnc->sxFnc.pnodeName->sxVar.pid, STVariable, false, nullptr, false, &isRedecl);
47164733
vardecl->sxVar.isBlockScopeFncDeclVar = true;
4734+
if (isRedecl)
4735+
{
4736+
vardecl->sxVar.sym->SetHasBlockFncVarRedecl();
4737+
}
47174738
}
47184739
}
47194740

@@ -4906,7 +4927,6 @@ bool Parser::ParseFncDeclHelper(ParseNodePtr pnodeFnc, LPCOLESTR pNameHint, usho
49064927

49074928
uint uDeferSave = m_grfscr & fscrDeferFncParse;
49084929
if ((!fDeclaration && m_ppnodeExprScope) ||
4909-
fFunctionInBlock ||
49104930
isEnclosedInParamScope ||
49114931
(flags & (fFncNoName | fFncLambda)))
49124932
{
@@ -4920,6 +4940,8 @@ bool Parser::ParseFncDeclHelper(ParseNodePtr pnodeFnc, LPCOLESTR pNameHint, usho
49204940
m_grfscr &= ~fscrDeferFncParse;
49214941
}
49224942

4943+
bool saveInFIB = this->m_inFIB;
4944+
this->m_inFIB = fFunctionInBlock || this->m_inFIB;
49234945

49244946
bool isTopLevelDeferredFunc = false;
49254947

@@ -4938,14 +4960,12 @@ bool Parser::ParseFncDeclHelper(ParseNodePtr pnodeFnc, LPCOLESTR pNameHint, usho
49384960

49394961
BOOL isDeferredFnc = IsDeferredFnc();
49404962
AnalysisAssert(isDeferredFnc || pnodeFnc);
4963+
// These are the conditions that prohibit upfront deferral *and* redeferral.
49414964
isTopLevelDeferredFunc =
49424965
(!fLambda
49434966
&& pnodeFnc
49444967
&& DeferredParse(pnodeFnc->sxFnc.functionId)
49454968
&& (!pnodeFnc->sxFnc.IsNested() || CONFIG_FLAG(DeferNested))
4946-
// Don't defer if this is a function expression not contained in a statement or other expression.
4947-
// Assume it will be called as part of this expression.
4948-
&& (!isLikelyIIFE || !topLevelStmt || PHASE_FORCE_RAW(Js::DeferParsePhase, m_sourceContextInfo->sourceContextId, pnodeFnc->sxFnc.functionId))
49494969
&& !m_InAsmMode
49504970
// Don't defer a module function wrapper because we need to do export resolution at parse time
49514971
&& !fModule
@@ -4954,9 +4974,25 @@ bool Parser::ParseFncDeclHelper(ParseNodePtr pnodeFnc, LPCOLESTR pNameHint, usho
49544974
if (pnodeFnc)
49554975
{
49564976
pnodeFnc->sxFnc.SetCanBeDeferred(isTopLevelDeferredFunc && PnFnc::CanBeRedeferred(pnodeFnc->sxFnc.fncFlags));
4977+
pnodeFnc->sxFnc.SetFIBPreventsDeferral(false);
49574978
}
4958-
isTopLevelDeferredFunc = isTopLevelDeferredFunc && !isDeferredFnc;
49594979

4980+
if (this->m_inFIB)
4981+
{
4982+
if (isTopLevelDeferredFunc)
4983+
{
4984+
// Block-scoping is the only non-heuristic reason for not deferring this function up front.
4985+
// So on creating the full FunctionBody at byte code gen time, verify that there is no
4986+
// block-scoped content visible to this function so it can remain a redeferral candidate.
4987+
pnodeFnc->sxFnc.SetFIBPreventsDeferral(true);
4988+
}
4989+
isTopLevelDeferredFunc = false;
4990+
}
4991+
4992+
// These are heuristic conditions that prohibit upfront deferral but not redeferral.
4993+
isTopLevelDeferredFunc = isTopLevelDeferredFunc && !isDeferredFnc &&
4994+
(!isLikelyIIFE || !topLevelStmt || PHASE_FORCE_RAW(Js::DeferParsePhase, m_sourceContextInfo->sourceContextId, pnodeFnc->sxFnc.functionId));
4995+
;
49604996
if (!fLambda &&
49614997
!isDeferredFnc &&
49624998
!isLikelyIIFE &&
@@ -5392,7 +5428,7 @@ bool Parser::ParseFncDeclHelper(ParseNodePtr pnodeFnc, LPCOLESTR pNameHint, usho
53925428
{
53935429
m_grfscr |= uDeferSave;
53945430
}
5395-
5431+
m_inFIB = saveInFIB;
53965432

53975433
m_pscan->SetYieldIsKeyword(fPreviousYieldIsKeyword);
53985434
m_pscan->SetAwaitIsKeyword(fPreviousAwaitIsKeyword);
@@ -10012,21 +10048,14 @@ ParseNodePtr Parser::ParseStatement()
1001210048
}
1001310049
else
1001410050
{
10015-
if (pstmt->isDeferred)
10051+
if (ParseNode::Grfnop(pstmt->GetNop()) & fnop)
1001610052
{
10017-
if (ParseNode::Grfnop(pstmt->op) & fnop)
10018-
{
10019-
goto LNeedTerminator;
10020-
}
10021-
}
10022-
else
10023-
{
10024-
AnalysisAssert(pstmt->pnodeStmt);
10025-
if (pstmt->pnodeStmt->Grfnop() & fnop)
10053+
if (!pstmt->isDeferred)
1002610054
{
10055+
AnalysisAssert(pstmt->pnodeStmt);
1002710056
pstmt->pnodeStmt->sxStmt.grfnop |= fnop;
10028-
goto LNeedTerminator;
1002910057
}
10058+
goto LNeedTerminator;
1003010059
}
1003110060
}
1003210061
}

lib/Parser/Parse.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ class Parser
225225
bool CheckStrictModeStrPid(IdentPtr pid);
226226
bool CheckAsmjsModeStrPid(IdentPtr pid);
227227

228+
bool IsCurBlockInLoop() const;
228229

229230
void InitPids();
230231

@@ -263,8 +264,8 @@ class Parser
263264
ParseNodePtr CreateTempRef(ParseNode* tempNode);
264265

265266
ParseNodePtr CreateNode(OpCode nop) { return CreateNode(nop, m_pscan? m_pscan->IchMinTok() : 0); }
266-
ParseNodePtr CreateDeclNode(OpCode nop, IdentPtr pid, SymbolType symbolType, bool errorOnRedecl = true);
267-
Symbol* AddDeclForPid(ParseNodePtr pnode, IdentPtr pid, SymbolType symbolType, bool errorOnRedecl);
267+
ParseNodePtr CreateDeclNode(OpCode nop, IdentPtr pid, SymbolType symbolType, bool errorOnRedecl = true, bool *isRedecl = nullptr);
268+
Symbol* AddDeclForPid(ParseNodePtr pnode, IdentPtr pid, SymbolType symbolType, bool errorOnRedecl, bool *isRedecl = nullptr);
268269
ParseNodePtr CreateNameNode(IdentPtr pid)
269270
{
270271
ParseNodePtr pnode = CreateNode(knopName);
@@ -314,7 +315,7 @@ class Parser
314315
void CheckPidIsValid(IdentPtr pid, bool autoArgumentsObject = false);
315316
void AddVarDeclToBlock(ParseNode *pnode);
316317
// Add a var declaration. Only use while parsing. Assumes m_ppnodeVar is pointing to the right place already
317-
ParseNodePtr CreateVarDeclNode(IdentPtr pid, SymbolType symbolType, bool autoArgumentsObject = false, ParseNodePtr pnodeFnc = NULL, bool checkReDecl = true);
318+
ParseNodePtr CreateVarDeclNode(IdentPtr pid, SymbolType symbolType, bool autoArgumentsObject = false, ParseNodePtr pnodeFnc = NULL, bool checkReDecl = true, bool *isRedecl = nullptr);
318319
// Add a var declaration, during parse tree rewriting. Will setup m_ppnodeVar for the given pnodeFnc
319320
ParseNodePtr AddVarDeclNode(IdentPtr pid, ParseNodePtr pnodeFnc);
320321
// Add a 'const' or 'let' declaration.
@@ -368,6 +369,7 @@ class Parser
368369
bool m_inDeferredNestedFunc; // true if parsing a function in deferred mode, nested within the current node
369370
bool m_isInBackground;
370371
bool m_reparsingLambdaParams;
372+
bool m_inFIB;
371373

372374
// This bool is used for deferring the shorthand initializer error ( {x = 1}) - as it is allowed in the destructuring grammar.
373375
bool m_hasDeferredShorthandInitError;

lib/Parser/ptree.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ struct PnFnc
247247
RestorePoint *pRestorePoint;
248248
DeferredFunctionStub *deferredStub;
249249
bool canBeDeferred;
250+
bool fibPreventsDeferral;
250251

251252
static const int32 MaxStackClosureAST = 800000;
252253

@@ -284,6 +285,8 @@ struct PnFnc
284285
void ClearFlags()
285286
{
286287
fncFlags = kFunctionNone;
288+
canBeDeferred = false;
289+
fibPreventsDeferral = false;
287290
}
288291

289292
void SetAsmjsMode(bool set = true) { SetFlags(kFunctionAsmjsMode, set); }
@@ -320,6 +323,7 @@ struct PnFnc
320323
void SetIsDefaultModuleExport(bool set = true) { SetFlags(kFunctionIsDefaultModuleExport, set); }
321324
void SetNestedFuncEscapes(bool set = true) { nestedFuncEscapes = set; }
322325
void SetCanBeDeferred(bool set = true) { canBeDeferred = set; }
326+
void SetFIBPreventsDeferral(bool set = true) { fibPreventsDeferral = set; }
323327

324328
bool CallsEval() const { return HasFlags(kFunctionCallsEval); }
325329
bool ChildCallsEval() const { return HasFlags(kFunctionChildCallsEval); }
@@ -358,6 +362,7 @@ struct PnFnc
358362
bool IsDefaultModuleExport() const { return HasFlags(kFunctionIsDefaultModuleExport); }
359363
bool NestedFuncEscapes() const { return nestedFuncEscapes; }
360364
bool CanBeDeferred() const { return canBeDeferred; }
365+
bool FIBPreventsDeferral() const { return fibPreventsDeferral; }
361366

362367
size_t LengthInBytes()
363368
{

lib/Runtime/ByteCode/ByteCodeEmitter.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1169,7 +1169,7 @@ void EmitAssignmentToFuncName(ParseNode *pnodeFnc, ByteCodeGenerator *byteCodeGe
11691169
{
11701170
byteCodeGenerator->EmitPropStore(pnodeFnc->location, sym, nullptr, funcInfoParent);
11711171
}
1172-
else
1172+
else if (!sym->GetIsBlockVar() || sym->HasRealBlockVarRef() || sym->GetScope()->GetIsObject())
11731173
{
11741174
byteCodeGenerator->EmitLocalPropInit(pnodeFnc->location, sym, funcInfoParent);
11751175
}
@@ -3839,6 +3839,21 @@ void ByteCodeGenerator::StartEmitFunction(ParseNode *pnodeFnc)
38393839
{
38403840
// Only set the environment depth if it's truly known (i.e., not in eval or event handler).
38413841
funcInfo->GetParsedFunctionBody()->SetEnvDepth(this->envDepth);
3842+
3843+
if (pnodeFnc->sxFnc.FIBPreventsDeferral())
3844+
{
3845+
for (Scope *scope = this->currentScope; scope; scope = scope->GetEnclosingScope())
3846+
{
3847+
if (scope->GetScopeType() != ScopeType_FunctionBody &&
3848+
scope->GetScopeType() != ScopeType_Global &&
3849+
scope->GetScopeType() != ScopeType_GlobalEvalBlock &&
3850+
scope->GetMustInstantiate())
3851+
{
3852+
funcInfo->byteCodeFunction->SetAttributes((Js::FunctionInfo::Attributes)(funcInfo->byteCodeFunction->GetAttributes() & ~Js::FunctionInfo::Attributes::CanDefer));
3853+
break;
3854+
}
3855+
}
3856+
}
38423857
}
38433858

38443859
if (funcInfo->GetCallsEval())

0 commit comments

Comments
 (0)