Skip to content

Commit b1f36e3

Browse files
committed
[MERGE #2666 @pleath] Defer functions enclosed in all contexts.
Merge pull request #2666 from pleath:deferfib 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, though, so I've left it there.)
2 parents 4df783c + 61cafb8 commit b1f36e3

17 files changed

+377
-436
lines changed

lib/Parser/Hash.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,16 @@ struct Ident
144144
return m_pidRefStack;
145145
}
146146

147+
PidRefStack *GetTopRef(uint maxBlockId) const
148+
{
149+
PidRefStack *ref;
150+
for (ref = m_pidRefStack; ref && (uint)ref->id > maxBlockId; ref = ref->prev)
151+
{
152+
; // nothing
153+
}
154+
return ref;
155+
}
156+
147157
void SetTopRef(PidRefStack *ref)
148158
{
149159
m_pidRefStack = ref;

lib/Parser/Parse.cpp

Lines changed: 122 additions & 152 deletions
Large diffs are not rendered by default.

lib/Parser/Parse.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66

77
#include "ParseFlags.h"
88

9+
namespace Js
10+
{
11+
class ScopeInfo;
12+
};
13+
914
// Operator precedence levels
1015
enum
1116
{
@@ -369,7 +374,6 @@ class Parser
369374
bool m_inDeferredNestedFunc; // true if parsing a function in deferred mode, nested within the current node
370375
bool m_isInBackground;
371376
bool m_reparsingLambdaParams;
372-
bool m_inFIB;
373377

374378
// This bool is used for deferring the shorthand initializer error ( {x = 1}) - as it is allowed in the destructuring grammar.
375379
bool m_hasDeferredShorthandInitError;
@@ -976,8 +980,8 @@ class Parser
976980
void RemovePrevPidRef(IdentPtr pid, PidRefStack *lastRef);
977981
void SetPidRefsInScopeDynamic(IdentPtr pid, int blockId);
978982

979-
void RestoreScopeInfo(Js::ParseableFunctionInfo* functionBody);
980-
void FinishScopeInfo(Js::ParseableFunctionInfo* functionBody);
983+
void RestoreScopeInfo(Js::ScopeInfo * scopeInfo);
984+
void FinishScopeInfo(Js::ScopeInfo * scopeInfo);
981985

982986
BOOL PnodeLabelNoAST(IdentToken* pToken, LabelId* pLabelIdList);
983987
LabelId* CreateLabelId(IdentToken* pToken);
@@ -1011,7 +1015,7 @@ class Parser
10111015
}
10121016

10131017
template <class Fn>
1014-
void VisitFunctionsInScope(ParseNodePtr pnodeScopeList, Fn fn);
1018+
void FinishFunctionsInScope(ParseNodePtr pnodeScopeList, Fn fn);
10151019
void FinishDeferredFunction(ParseNodePtr pnodeScopeList);
10161020

10171021
/***********************************************************************

lib/Parser/ptree.h

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

252251
static const int32 MaxStackClosureAST = 800000;
253252

@@ -286,7 +285,6 @@ struct PnFnc
286285
{
287286
fncFlags = kFunctionNone;
288287
canBeDeferred = false;
289-
fibPreventsDeferral = false;
290288
}
291289

292290
void SetAsmjsMode(bool set = true) { SetFlags(kFunctionAsmjsMode, set); }
@@ -323,7 +321,6 @@ struct PnFnc
323321
void SetIsDefaultModuleExport(bool set = true) { SetFlags(kFunctionIsDefaultModuleExport, set); }
324322
void SetNestedFuncEscapes(bool set = true) { nestedFuncEscapes = set; }
325323
void SetCanBeDeferred(bool set = true) { canBeDeferred = set; }
326-
void SetFIBPreventsDeferral(bool set = true) { fibPreventsDeferral = set; }
327324

328325
bool CallsEval() const { return HasFlags(kFunctionCallsEval); }
329326
bool ChildCallsEval() const { return HasFlags(kFunctionChildCallsEval); }
@@ -362,7 +359,6 @@ struct PnFnc
362359
bool IsDefaultModuleExport() const { return HasFlags(kFunctionIsDefaultModuleExport); }
363360
bool NestedFuncEscapes() const { return nestedFuncEscapes; }
364361
bool CanBeDeferred() const { return canBeDeferred; }
365-
bool FIBPreventsDeferral() const { return fibPreventsDeferral; }
366362

367363
size_t LengthInBytes()
368364
{

lib/Runtime/ByteCode/ByteCodeEmitter.cpp

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2993,6 +2993,11 @@ void ByteCodeGenerator::EmitOneFunction(ParseNode *pnode)
29932993
deferParseFunction->SetReportedInParamsCount(funcInfo->inArgsCount);
29942994
}
29952995

2996+
if (deferParseFunction->IsDeferred() || deferParseFunction->CanBeDeferred())
2997+
{
2998+
Js::ScopeInfo::SaveEnclosingScopeInfo(this, funcInfo);
2999+
}
3000+
29963001
if (funcInfo->root->sxFnc.pnodeBody == nullptr)
29973002
{
29983003
if (!PHASE_OFF1(Js::SkipNestedDeferredPhase))
@@ -3658,13 +3663,7 @@ void ByteCodeGenerator::EmitScopeList(ParseNode *pnode, ParseNode *breakOnBodySc
36583663
}
36593664
this->StartEmitFunction(pnode);
36603665

3661-
// Persist outer func scope info if nested func is deferred
3662-
if (CONFIG_FLAG(DeferNested))
3663-
{
3664-
FuncInfo* parentFunc = TopFuncInfo();
3665-
Js::ScopeInfo::SaveScopeInfoForDeferParse(this, parentFunc, funcInfo);
3666-
PushFuncInfo(_u("StartEmitFunction"), funcInfo);
3667-
}
3666+
PushFuncInfo(_u("StartEmitFunction"), funcInfo);
36683667

36693668
if (paramScope && !paramScope->GetCanMergeWithBodyScope())
36703669
{
@@ -3842,21 +3841,6 @@ void ByteCodeGenerator::StartEmitFunction(ParseNode *pnodeFnc)
38423841
// Only set the environment depth if it's truly known (i.e., not in eval or event handler).
38433842
funcInfo->GetParsedFunctionBody()->SetEnvDepth(this->envDepth);
38443843
}
3845-
3846-
if (pnodeFnc->sxFnc.FIBPreventsDeferral())
3847-
{
3848-
for (Scope *scope = this->currentScope; scope; scope = scope->GetEnclosingScope())
3849-
{
3850-
if (scope->GetScopeType() != ScopeType_FunctionBody &&
3851-
scope->GetScopeType() != ScopeType_Global &&
3852-
scope->GetScopeType() != ScopeType_GlobalEvalBlock &&
3853-
scope->GetMustInstantiate())
3854-
{
3855-
funcInfo->byteCodeFunction->SetAttributes((Js::FunctionInfo::Attributes)(funcInfo->byteCodeFunction->GetAttributes() & ~Js::FunctionInfo::Attributes::CanDefer));
3856-
break;
3857-
}
3858-
}
3859-
}
38603844
}
38613845

38623846
if (funcInfo->GetCallsEval())

lib/Runtime/ByteCode/ByteCodeGenerator.cpp

Lines changed: 92 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -968,84 +968,101 @@ Js::RegSlot ByteCodeGenerator::EnregisterStringTemplateCallsiteConstant(ParseNod
968968
//
969969
// Restore all outer func scope info when reparsing a deferred func.
970970
//
971-
void ByteCodeGenerator::RestoreScopeInfo(Js::ParseableFunctionInfo* functionBody)
971+
void ByteCodeGenerator::RestoreScopeInfo(Js::ScopeInfo *scopeInfo, FuncInfo * func)
972972
{
973-
if (functionBody && functionBody->GetScopeInfo())
973+
if (scopeInfo)
974974
{
975975
PROBE_STACK(scriptContext, Js::Constants::MinStackByteCodeVisitor);
976976

977-
Js::ScopeInfo* scopeInfo = functionBody->GetScopeInfo();
978-
RestoreScopeInfo(scopeInfo->GetParent()); // Recursively restore outer func scope info
977+
Js::ParseableFunctionInfo * pfi = scopeInfo->GetFunctionInfo()->GetParseableFunctionInfo();
978+
bool newFunc = (func == nullptr || func->byteCodeFunction != pfi);
979979

980-
Js::ScopeInfo* paramScopeInfo = scopeInfo->GetParamScopeInfo();
981-
Scope* paramScope = nullptr;
982-
if (paramScopeInfo != nullptr)
980+
if (newFunc)
983981
{
984-
paramScope = paramScopeInfo->GetScope();
985-
Assert(paramScope);
986-
if (!paramScopeInfo->GetCanMergeWithBodyScope())
987-
{
988-
paramScope->SetCannotMergeWithBodyScope();
989-
}
990-
// We need the funcInfo before continuing the restoration of the param scope, so wait for the funcInfo to be created.
991-
}
992-
993-
Scope* bodyScope = scopeInfo->GetScope();
994-
995-
Assert(bodyScope);
996-
bodyScope->SetHasOwnLocalInClosure(scopeInfo->GetHasOwnLocalInClosure());
997-
998-
FuncInfo* func = Anew(alloc, FuncInfo, functionBody->GetDisplayName(), alloc, paramScope, bodyScope, nullptr, functionBody);
999-
1000-
if (bodyScope->GetScopeType() == ScopeType_GlobalEvalBlock)
1001-
{
1002-
func->bodyScope = this->currentScope;
982+
func = Anew(alloc, FuncInfo, pfi->GetDisplayName(), alloc, nullptr, nullptr, nullptr, pfi);
983+
newFunc = true;
1003984
}
1004-
PushFuncInfo(_u("RestoreScopeInfo"), func);
1005985

1006-
if (!functionBody->DoStackNestedFunc())
1007-
{
1008-
func->hasEscapedUseNestedFunc = true;
1009-
}
986+
// Recursively restore enclosing scope info so outermost scopes/funcs are pushed first.
987+
this->RestoreScopeInfo(scopeInfo->GetParentScopeInfo(), func);
988+
this->RestoreOneScope(scopeInfo, func);
1010989

1011-
Js::ScopeInfo* funcExprScopeInfo = scopeInfo->GetFuncExprScopeInfo();
1012-
if (funcExprScopeInfo)
990+
if (newFunc)
1013991
{
1014-
Scope* funcExprScope = funcExprScopeInfo->GetScope();
1015-
Assert(funcExprScope);
1016-
funcExprScope->SetFunc(func);
1017-
func->SetFuncExprScope(funcExprScope);
1018-
funcExprScopeInfo->GetScopeInfo(nullptr, this, func, funcExprScope);
1019-
}
1020-
1021-
// Restore the param scope after the function expression scope
1022-
if (paramScope != nullptr)
1023-
{
1024-
paramScope->SetFunc(func);
1025-
paramScopeInfo->GetScopeInfo(nullptr, this, func, paramScope);
992+
PushFuncInfo(_u("RestoreScopeInfo"), func);
993+
if (!pfi->DoStackNestedFunc())
994+
{
995+
func->hasEscapedUseNestedFunc = true;
996+
}
1026997
}
1027-
scopeInfo->GetScopeInfo(nullptr, this, func, bodyScope);
1028998
}
1029999
else
10301000
{
10311001
Assert(this->TopFuncInfo() == nullptr);
10321002
// funcBody is glo
1003+
Assert(currentScope == nullptr);
10331004
currentScope = Anew(alloc, Scope, alloc, ScopeType_Global);
10341005
globalScope = currentScope;
10351006

1036-
FuncInfo *func = Anew(alloc, FuncInfo, Js::Constants::GlobalFunction,
1037-
alloc, nullptr, currentScope, nullptr, functionBody);
1038-
PushFuncInfo(_u("RestoreScopeInfo"), func);
1007+
if (func == nullptr || !func->byteCodeFunction->GetIsGlobalFunc())
1008+
{
1009+
func = Anew(alloc, FuncInfo, Js::Constants::GlobalFunction,
1010+
alloc, nullptr, nullptr/*currentScope*/, nullptr, nullptr/*functionBody*/);
1011+
PushFuncInfo(_u("RestoreScopeInfo"), func);
1012+
}
1013+
func->SetBodyScope(currentScope);
10391014
}
10401015
}
10411016

1017+
void ByteCodeGenerator::RestoreOneScope(Js::ScopeInfo * scopeInfo, FuncInfo * func)
1018+
{
1019+
TRACE_BYTECODE(_u("\nRestore ScopeInfo: %s #symbols: %d %s\n"),
1020+
func->name, scopeInfo->GetSymbolCount(), scopeInfo->IsObject() ? _u("isObject") : _u(""));
1021+
1022+
Scope * scope = scopeInfo->GetScope();
1023+
1024+
scope->SetFunc(func);
1025+
1026+
switch (scope->GetScopeType())
1027+
{
1028+
case ScopeType_Parameter:
1029+
if (!scopeInfo->GetCanMergeWithBodyScope())
1030+
{
1031+
scope->SetCannotMergeWithBodyScope();
1032+
}
1033+
Assert(func->GetParamScope() == nullptr);
1034+
func->SetParamScope(scope);
1035+
break;
1036+
1037+
case ScopeType_FuncExpr:
1038+
Assert(func->GetFuncExprScope() == nullptr);
1039+
func->SetFuncExprScope(scope);
1040+
break;
1041+
1042+
case ScopeType_FunctionBody:
1043+
case ScopeType_GlobalEvalBlock:
1044+
Assert(func->GetBodyScope() == nullptr || (func->GetBodyScope()->GetScopeType() == ScopeType_Global && scope->GetScopeType() == ScopeType_GlobalEvalBlock));
1045+
func->SetBodyScope(scope);
1046+
func->SetHasCachedScope(scopeInfo->IsCached());
1047+
break;
1048+
}
1049+
1050+
Assert(!scopeInfo->IsCached() || scope == func->GetBodyScope());
1051+
1052+
// scopeInfo->scope was created/saved during parsing.
1053+
// We no longer need it by now.
1054+
// Clear it to avoid GC false positive (arena memory later used by GC).
1055+
scopeInfo->SetScope(nullptr);
1056+
this->PushScope(scope);
1057+
}
1058+
10421059
FuncInfo * ByteCodeGenerator::StartBindGlobalStatements(ParseNode *pnode)
10431060
{
1044-
if (parentScopeInfo && parentScopeInfo->GetParent() && (!parentScopeInfo->GetParent()->GetIsGlobalFunc() || parentScopeInfo->GetParent()->IsEval()))
1061+
if (parentScopeInfo)
10451062
{
10461063
Assert(CONFIG_FLAG(DeferNested));
10471064
trackEnvDepth = true;
1048-
RestoreScopeInfo(parentScopeInfo->GetParent());
1065+
RestoreScopeInfo(parentScopeInfo, nullptr);
10491066
trackEnvDepth = false;
10501067
// "currentScope" is the parentFunc scope. This ensures the deferred func declaration
10511068
// 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
12111228
if (parsedFunctionBody->GetScopeInfo())
12121229
{
12131230
// Propagate flags from the (real) parent function.
1214-
Js::ParseableFunctionInfo *parent = parsedFunctionBody->GetScopeInfo()->GetParent();
1231+
Js::ParseableFunctionInfo *parent = parsedFunctionBody->GetScopeInfo()->GetParseableFunctionInfo();
12151232
if (parent)
12161233
{
12171234
if (parent->GetHasOrParentHasArguments())
@@ -1632,7 +1649,8 @@ Symbol * ByteCodeGenerator::FindSymbol(Symbol **symRef, IdentPtr pid, bool forRe
16321649

16331650
bool didTransferToFncVarSym = false;
16341651

1635-
if (!PHASE_OFF(Js::OptimizeBlockScopePhase, top->byteCodeFunction) &&
1652+
#pragma prefast(suppress:6237, "The right hand side condition does not have any side effects.")
1653+
if (PHASE_ON(Js::OptimizeBlockScopePhase, top->byteCodeFunction) &&
16361654
sym->GetIsBlockVar() &&
16371655
!sym->GetScope()->IsBlockInLoop() &&
16381656
sym->GetSymbolType() == STFunction)
@@ -1755,7 +1773,7 @@ Symbol * ByteCodeGenerator::AddSymbolToScope(Scope *scope, const char16 *key, in
17551773
// on such compiles, so we essentially have to migrate the symbol to the new scope.
17561774
// We check fscrEvalCode, not fscrEval, because the same thing can happen in indirect eval,
17571775
// when fscrEval is not set.
1758-
Assert(scope->GetScopeType() == ScopeType_Global);
1776+
Assert(scope->GetScopeType() == ScopeType_Global || scope->GetScopeType() == ScopeType_GlobalEvalBlock);
17591777
scope->AddNewSymbol(sym);
17601778
}
17611779

@@ -1995,7 +2013,7 @@ void ByteCodeGenerator::Generate(__in ParseNode *pnode, uint32 grfscr, __in Byte
19952013

19962014
void ByteCodeGenerator::CheckDeferParseHasMaybeEscapedNestedFunc()
19972015
{
1998-
if (!this->parentScopeInfo || (this->parentScopeInfo->GetParent() && this->parentScopeInfo->GetParent()->GetIsGlobalFunc()))
2016+
if (!this->parentScopeInfo)
19992017
{
20002018
return;
20012019
}
@@ -2023,7 +2041,7 @@ void ByteCodeGenerator::CheckDeferParseHasMaybeEscapedNestedFunc()
20232041
else
20242042
{
20252043
// We have to wait until it is parsed before we populate the stack nested func parent.
2026-
FuncInfo * parentFunc = top->GetBodyScope()->GetEnclosingFunc();
2044+
FuncInfo * parentFunc = top->GetParamScope() ? top->GetParamScope()->GetEnclosingFunc() : top->GetBodyScope()->GetEnclosingFunc();
20272045
if (!parentFunc->IsGlobalFunction())
20282046
{
20292047
Assert(parentFunc->byteCodeFunction != rootFuncBody);
@@ -2040,6 +2058,11 @@ void ByteCodeGenerator::CheckDeferParseHasMaybeEscapedNestedFunc()
20402058
FuncInfo * funcInfo = i.Data();
20412059
Assert(funcInfo->IsRestored());
20422060
Js::ParseableFunctionInfo * parseableFunctionInfo = funcInfo->byteCodeFunction;
2061+
if (parseableFunctionInfo == nullptr)
2062+
{
2063+
Assert(funcInfo->GetBodyScope() && funcInfo->GetBodyScope()->GetScopeType() == ScopeType_Global);
2064+
return;
2065+
}
20432066
bool didStackNestedFunc = parseableFunctionInfo->DoStackNestedFunc();
20442067
if (!didStackNestedFunc)
20452068
{
@@ -3247,6 +3270,7 @@ void AddFunctionsToScope(ParseNodePtr scope, ByteCodeGenerator * byteCodeGenerat
32473270
sym->GetScope() != sym->GetScope()->GetFunc()->GetParamScope())
32483271
{
32493272
sym->SetIsBlockVar(true);
3273+
sym->SetHasRealBlockVarRef(true);
32503274
}
32513275
}
32523276
});
@@ -3629,15 +3653,21 @@ void PostVisitBlock(ParseNode *pnode, ByteCodeGenerator *byteCodeGenerator)
36293653
return;
36303654
}
36313655

3656+
Scope *scope = pnode->sxBlock.scope;
3657+
36323658
if (pnode->sxBlock.GetCallsEval() || pnode->sxBlock.GetChildCallsEval() || (byteCodeGenerator->GetFlags() & (fscrEval | fscrImplicitThis | fscrImplicitParents)))
36333659
{
3634-
Scope *scope = pnode->sxBlock.scope;
36353660
bool scopeIsEmpty = scope->IsEmpty();
36363661
scope->SetIsObject();
36373662
scope->SetCapturesAll(true);
36383663
scope->SetMustInstantiate(!scopeIsEmpty);
36393664
}
36403665

3666+
if (scope->GetHasOwnLocalInClosure())
3667+
{
3668+
byteCodeGenerator->ProcessScopeWithCapturedSym(scope);
3669+
}
3670+
36413671
byteCodeGenerator->PopScope();
36423672
byteCodeGenerator->PopBlock();
36433673

@@ -3686,6 +3716,11 @@ void PreVisitCatch(ParseNode *pnode, ByteCodeGenerator *byteCodeGenerator)
36863716

36873717
void PostVisitCatch(ParseNode *pnode, ByteCodeGenerator *byteCodeGenerator)
36883718
{
3719+
Scope *scope = pnode->sxCatch.scope;
3720+
if (scope->GetHasOwnLocalInClosure())
3721+
{
3722+
byteCodeGenerator->ProcessScopeWithCapturedSym(scope);
3723+
}
36893724
byteCodeGenerator->EndBindCatch();
36903725
}
36913726

0 commit comments

Comments
 (0)