Skip to content

Commit 450c1d1

Browse files
committed
Add simple set of captured names to nested ParseNodeFnc
Each `ParseNodeFnc` has an IdentPtrSet which holds the set of names captured by the function. Captured names are ones which are referenced inside the function but not declared within the function. Implement this by intercepting name references at PushPidRef and keep the name reference in the set of captured names for the current nested function. Remove any names from the captured name list which do not have dangling references after we've bound the pid ref stack for a nested function. When finished parsing a nested function, add the set of captured names onto the parent functions' captured name set. Gate the feature via a ParseType flag and allow a forcing config option (`CreateParserState`). Example captured name set: ```js let x, y, z; function foo() { function bar() { return x; } function baz(x) { return y; } function bat() { { let z; return z; } } } // captured name sets // foo: [x, y] // bar: [x] // baz: [y] // bat: [] ```
1 parent bcc695a commit 450c1d1

File tree

9 files changed

+459
-6
lines changed

9 files changed

+459
-6
lines changed

lib/Common/ConfigFlagsList.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ PHASE(All)
1515
PHASE(DeferEventHandlers)
1616
PHASE(FunctionSourceInfoParse)
1717
PHASE(StringTemplateParse)
18+
PHASE(CreateParserState)
1819
PHASE(SkipNestedDeferred)
1920
PHASE(CacheScopeInfoNames)
2021
PHASE(ScanAhead)
@@ -427,6 +428,7 @@ PHASE(All)
427428
#define DEFAULT_CONFIG_PrintLineColumnInfo (false)
428429
#define DEFAULT_CONFIG_ForceDecommitOnCollect (false)
429430
#define DEFAULT_CONFIG_ForceDeferParse (false)
431+
#define DEFAULT_CONFIG_ForceCreateParserState (false)
430432
#define DEFAULT_CONFIG_NoDeferParse (false)
431433
#define DEFAULT_CONFIG_ForceDynamicProfile (false)
432434
#define DEFAULT_CONFIG_ForceExpireOnNonCacheCollect (false)
@@ -1144,6 +1146,7 @@ FLAGNR(Boolean, ForceCleanCacheOnCollect, "Force cleaning of dynamic caches on c
11441146
FLAGNR(Boolean, ForceGCAfterJSONParse, "Force GC to happen after JSON parsing", DEFAULT_CONFIG_ForceGCAfterJSONParse)
11451147
FLAGNR(Boolean, ForceDecommitOnCollect, "Force decommit collect", DEFAULT_CONFIG_ForceDecommitOnCollect)
11461148
FLAGNR(Boolean, ForceDeferParse , "Defer parsing of all function bodies", DEFAULT_CONFIG_ForceDeferParse)
1149+
FLAGNR(Boolean, ForceCreateParserState , "Force creation of parser state", DEFAULT_CONFIG_ForceCreateParserState)
11471150
FLAGNR(Boolean, ForceDiagnosticsMode , "Enable diagnostics mode and debug interpreter loop", false)
11481151
FLAGNR(Boolean, ForceGetWriteWatchOOM , "Force GetWriteWatch to go into OOM codepath in HeapBlockMap rescan", false)
11491152
FLAGNR(Boolean, ForcePostLowerGlobOptInstrString, "Force tracking of globopt instr string post lower", DEFAULT_CONFIG_ForcePostLowerGlobOptInstrString)

lib/Parser/Parse.cpp

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4881,7 +4881,6 @@ ParseNode * Parser::ParseFncDecl(ushort flags, LPCOLESTR pNameHint, const bool n
48814881
pnodeFnc->cbMin = this->GetScanner()->IecpMinTok();
48824882
pnodeFnc->functionId = (*m_nextFunctionId)++;
48834883

4884-
48854884
// Push new parser state with this new function node
48864885

48874886
AppendFunctionToScopeList(fDeclaration, pnodeFnc);
@@ -4918,6 +4917,9 @@ ParseNode * Parser::ParseFncDecl(ushort flags, LPCOLESTR pNameHint, const bool n
49184917
IdentPtr pFncNamePid = nullptr;
49194918
bool needScanRCurly = true;
49204919
bool result = ParseFncDeclHelper<buildAST>(pnodeFnc, pNameHint, flags, &funcHasName, fUnaryOrParen, noStmtContext, &needScanRCurly, fModule, &pFncNamePid);
4920+
4921+
AddNestedCapturedNames(pnodeFnc);
4922+
49214923
if (!result)
49224924
{
49234925
Assert(!pnodeFncBlockScope);
@@ -5659,6 +5661,8 @@ bool Parser::ParseFncDeclHelper(ParseNodeFnc * pnodeFnc, LPCOLESTR pNameHint, us
56595661
CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, StrictModeFunction, m_scriptContext);
56605662
}
56615663

5664+
ProcessCapturedNames(pnodeFnc);
5665+
56625666
if (fDeferred)
56635667
{
56645668
AnalysisAssert(pnodeFnc);
@@ -9029,19 +9033,27 @@ void Parser::TrackAssignment(ParseNodePtr pnodeT, IdentToken* pToken)
90299033

90309034
PidRefStack* Parser::PushPidRef(IdentPtr pid)
90319035
{
9036+
ParseNodeFnc* currentFnc = GetCurrentFunctionNode();
9037+
9038+
if (this->IsCreatingStateCache())
9039+
{
9040+
IdentPtrSet* capturedNames = currentFnc->EnsureCapturedNames(&m_nodeAllocator);
9041+
capturedNames->AddNew(pid);
9042+
}
9043+
90329044
if (PHASE_ON1(Js::ParallelParsePhase))
90339045
{
90349046
// NOTE: the phase check is here to protect perf. See OSG 1020424.
90359047
// In some LS AST-rewrite cases we lose a lot of perf searching the PID ref stack rather
90369048
// than just pushing on the top. This hasn't shown up as a perf issue in non-LS benchmarks.
9037-
return pid->FindOrAddPidRef(&m_nodeAllocator, GetCurrentBlock()->blockId, GetCurrentFunctionNode()->functionId);
9049+
return pid->FindOrAddPidRef(&m_nodeAllocator, GetCurrentBlock()->blockId, currentFnc->functionId);
90389050
}
90399051

90409052
Assert(GetCurrentBlock() != nullptr);
90419053
AssertMsg(pid != nullptr, "PID should be created");
90429054
PidRefStack *ref = pid->GetTopRef(m_nextBlockId - 1);
90439055
int blockId = GetCurrentBlock()->blockId;
9044-
int funcId = GetCurrentFunctionNode()->functionId;
9056+
int funcId = currentFnc->functionId;
90459057
if (!ref || (ref->GetScopeId() < blockId))
90469058
{
90479059
ref = Anew(&m_nodeAllocator, PidRefStack);
@@ -13826,6 +13838,59 @@ void ParseNode::Dump()
1382613838
}
1382713839
#endif
1382813840

13841+
void Parser::AddNestedCapturedNames(ParseNodeFnc* pnodeChildFnc)
13842+
{
13843+
if (m_currentNodeFunc && this->IsCreatingStateCache() && pnodeChildFnc->HasAnyCapturedNames())
13844+
{
13845+
IdentPtrSet* parentCapturedNames = GetCurrentFunctionNode()->EnsureCapturedNames(&m_nodeAllocator);
13846+
IdentPtrSet* childCaptureNames = pnodeChildFnc->GetCapturedNames();
13847+
13848+
auto iter = childCaptureNames->GetIterator();
13849+
13850+
while (iter.IsValid())
13851+
{
13852+
parentCapturedNames->AddNew(iter.CurrentValue());
13853+
iter.MoveNext();
13854+
}
13855+
}
13856+
}
13857+
13858+
void Parser::ProcessCapturedNames(ParseNodeFnc* pnodeFnc)
13859+
{
13860+
if (this->IsCreatingStateCache() && pnodeFnc->HasAnyCapturedNames())
13861+
{
13862+
IdentPtrSet* capturedNames = pnodeFnc->GetCapturedNames();
13863+
auto iter = capturedNames->GetIteratorWithRemovalSupport();
13864+
13865+
while (iter.IsValid())
13866+
{
13867+
const IdentPtr& pid = iter.CurrentValueReference();
13868+
PidRefStack* ref = pid->GetTopRef();
13869+
13870+
// If the pid has no refs left in our function's scope after binding, we didn't capture it.
13871+
if (!ref || ref->GetScopeId() < pnodeFnc->pnodeBodyScope->blockId)
13872+
{
13873+
iter.RemoveCurrent();
13874+
}
13875+
else
13876+
{
13877+
OUTPUT_TRACE_DEBUGONLY(Js::CreateParserStatePhase, _u("Function %u captured name \"%s\"\n"), pnodeFnc->functionId, pid->Psz());
13878+
}
13879+
13880+
iter.MoveNext();
13881+
}
13882+
}
13883+
}
13884+
13885+
bool Parser::IsCreatingStateCache()
13886+
{
13887+
return this->m_parseType == ParseType_StateCache
13888+
#ifdef ENABLE_DEBUG_CONFIG_OPTIONS
13889+
|| CONFIG_FLAG(ForceCreateParserState)
13890+
#endif
13891+
;
13892+
}
13893+
1382913894
DeferredFunctionStub * BuildDeferredStubTree(ParseNodeFnc *pnodeFnc, Recycler *recycler)
1383013895
{
1383113896
Assert(pnodeFnc->nop == knopFncDecl);
@@ -13887,6 +13952,7 @@ DeferredFunctionStub * BuildDeferredStubTree(ParseNodeFnc *pnodeFnc, Recycler *r
1388713952
deferredStubs[i].restorePoint = *pnodeFncChild->pRestorePoint;
1388813953
deferredStubs[i].deferredStubs = BuildDeferredStubTree(pnodeFncChild, recycler);
1388913954
deferredStubs[i].ichMin = pnodeChild->ichMin;
13955+
1389013956
++i;
1389113957
pnodeChild = pnodeFncChild->pnodeNext;
1389213958
}

lib/Parser/Parse.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ enum
3636
enum ParseType
3737
{
3838
ParseType_Upfront,
39-
ParseType_Deferred
39+
ParseType_Deferred,
40+
ParseType_StateCache
4041
};
4142

4243
enum DestructuringInitializerContext
@@ -117,6 +118,8 @@ class Parser
117118

118119
Js::ScriptContext* GetScriptContext() const { return m_scriptContext; }
119120

121+
bool IsCreatingStateCache();
122+
120123
#if ENABLE_BACKGROUND_PARSING
121124
bool IsBackgroundParser() const { return m_isInBackground; }
122125
bool IsDoingFastScan() const { return m_doingFastScan; }
@@ -317,6 +320,8 @@ class Parser
317320
return _u("Upfront");
318321
case ParseType_Deferred:
319322
return _u("Deferred");
323+
case ParseType_StateCache:
324+
return _u("StateCache");
320325
}
321326
Assert(false);
322327
return NULL;
@@ -684,6 +689,9 @@ class Parser
684689
ParseNodeSpecialName * ReferenceSpecialName(IdentPtr pid, charcount_t ichMin = 0, charcount_t ichLim = 0, bool createNode = false);
685690
ParseNodeVar * CreateSpecialVarDeclIfNeeded(ParseNodeFnc * pnodeFnc, IdentPtr pid, bool forceCreate = false);
686691

692+
void ProcessCapturedNames(ParseNodeFnc* pnodeFnc);
693+
void AddNestedCapturedNames(ParseNodeFnc* pnodeChildFnc);
694+
687695
template<const bool backgroundPidRefs>
688696
void BindPidRefs(BlockInfoStack *blockInfo, uint maxBlockId = (uint)-1);
689697
void BindPidRefsInScope(IdentPtr pid, Symbol *sym, int blockId, uint maxBlockId = (uint)-1);

lib/Parser/ParserCommon.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ struct ModuleImportOrExportEntry
5151

5252
typedef SList<ModuleImportOrExportEntry, ArenaAllocator> ModuleImportOrExportEntryList;
5353
typedef SList<IdentPtr, ArenaAllocator> IdentPtrList;
54+
typedef JsUtil::BaseHashSet<IdentPtr, ArenaAllocator, PowerOf2SizePolicy> IdentPtrSet;
5455

5556
//
5657
// Below was moved from scrutil.h to share with chakradiag.

lib/Parser/ptree.cpp

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ ParseNodeFnc::ParseNodeFnc(OpCode nop, charcount_t ichMin, charcount_t ichLim)
433433
this->pRestorePoint = nullptr;
434434
this->deferredStub = nullptr;
435435

436-
436+
this->capturedNames = nullptr;
437437
}
438438

439439
ParseNodeClass::ParseNodeClass(OpCode nop, charcount_t ichMin, charcount_t ichLim)
@@ -605,4 +605,23 @@ ParseNodeSuperCall::ParseNodeSuperCall(OpCode nop, charcount_t ichMin, charcount
605605
this->isSuperCall = true;
606606
this->pnodeThis = nullptr;
607607
this->pnodeNewTarget = nullptr;
608-
}
608+
}
609+
610+
IdentPtrSet* ParseNodeFnc::EnsureCapturedNames(ArenaAllocator* alloc)
611+
{
612+
if (this->capturedNames == nullptr)
613+
{
614+
this->capturedNames = Anew(alloc, IdentPtrSet, alloc);
615+
}
616+
return this->capturedNames;
617+
}
618+
619+
IdentPtrSet* ParseNodeFnc::GetCapturedNames()
620+
{
621+
return this->capturedNames;
622+
}
623+
624+
bool ParseNodeFnc::HasAnyCapturedNames()
625+
{
626+
return this->capturedNames != nullptr && this->capturedNames->Count() != 0;
627+
}

lib/Parser/ptree.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,7 @@ class ParseNodeFnc : public ParseNode
490490
#endif
491491
RestorePoint *pRestorePoint;
492492
DeferredFunctionStub *deferredStub;
493+
IdentPtrSet *capturedNames;
493494
bool canBeDeferred;
494495
bool isBodyAndParamScopeMerged; // Indicates whether the param scope and the body scope of the function can be merged together or not.
495496
// We cannot merge both scopes together if there is any closure capture or eval is present in the param scope.
@@ -641,6 +642,10 @@ class ParseNodeFnc : public ParseNode
641642
}
642643
}
643644

645+
IdentPtrSet* EnsureCapturedNames(ArenaAllocator* alloc);
646+
IdentPtrSet* GetCapturedNames();
647+
bool HasAnyCapturedNames();
648+
644649
DISABLE_SELF_CAST(ParseNodeFnc);
645650
};
646651

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
nested function capturing var decl
2+
CreateParserState:Function 2 captured name "a1"
3+
4+
nested function capturing a let decl
5+
CreateParserState:Function 2 captured name "a2"
6+
7+
nested function capturing a const decl
8+
CreateParserState:Function 2 captured name "a3"
9+
10+
nested function capturing non-decl names
11+
CreateParserState:Function 2 captured name "a4"
12+
CreateParserState:Function 2 captured name "b4"
13+
CreateParserState:Function 1 captured name "a4"
14+
CreateParserState:Function 1 captured name "b4"
15+
16+
nested function referencing a local shadowing formal
17+
18+
nested function capturing a named formal
19+
CreateParserState:Function 2 captured name "a6"
20+
21+
nested functions capturing global declarations
22+
CreateParserState:Function 3 captured name "d7"
23+
CreateParserState:Function 3 captured name "v7"
24+
CreateParserState:Function 3 captured name "a7"
25+
CreateParserState:Function 3 captured name "c7"
26+
CreateParserState:Function 3 captured name "b7"
27+
CreateParserState:Function 3 captured name "l7"
28+
CreateParserState:Function 2 captured name "d7"
29+
CreateParserState:Function 2 captured name "v7"
30+
CreateParserState:Function 2 captured name "a7"
31+
CreateParserState:Function 2 captured name "c7"
32+
CreateParserState:Function 2 captured name "l7"
33+
CreateParserState:Function 1 captured name "d7"
34+
CreateParserState:Function 1 captured name "v7"
35+
CreateParserState:Function 1 captured name "b7"
36+
CreateParserState:Function 1 captured name "c7"
37+
CreateParserState:Function 1 captured name "l7"
38+
39+
nested function capturing a class decl
40+
CreateParserState:Function 3 captured name "class_1"
41+
CreateParserState:Function 2 captured name "class_1"
42+
43+
object literal methods capturing names
44+
CreateParserState:Function 1 captured name "v9"
45+
CreateParserState:Function 1 captured name "o_9"
46+
CreateParserState:Function 1 captured name "c9"
47+
CreateParserState:Function 1 captured name "l9"
48+
CreateParserState:Function 1 captured name "b9"
49+
CreateParserState:Function 2 captured name "v9"
50+
CreateParserState:Function 2 captured name "o_9"
51+
CreateParserState:Function 2 captured name "c9"
52+
CreateParserState:Function 2 captured name "l9"
53+
CreateParserState:Function 2 captured name "b9"
54+
CreateParserState:Function 3 captured name "v9"
55+
CreateParserState:Function 3 captured name "o_9"
56+
CreateParserState:Function 3 captured name "c9"
57+
CreateParserState:Function 3 captured name "l9"
58+
CreateParserState:Function 3 captured name "b9"
59+
CreateParserState:Function 4 captured name "v9"
60+
CreateParserState:Function 4 captured name "o_9"
61+
CreateParserState:Function 4 captured name "c9"
62+
CreateParserState:Function 4 captured name "l9"
63+
CreateParserState:Function 4 captured name "b9"
64+
CreateParserState:Function 5 captured name "v9"
65+
CreateParserState:Function 5 captured name "o_9"
66+
CreateParserState:Function 5 captured name "c9"
67+
CreateParserState:Function 5 captured name "l9"
68+
CreateParserState:Function 5 captured name "b9"
69+
CreateParserState:Function 6 captured name "v9"
70+
CreateParserState:Function 6 captured name "a9"
71+
CreateParserState:Function 6 captured name "o_9"
72+
CreateParserState:Function 6 captured name "c9"
73+
CreateParserState:Function 6 captured name "l9"
74+
CreateParserState:Function 6 captured name "b9"
75+
CreateParserState:Function 7 captured name "v9"
76+
CreateParserState:Function 7 captured name "o_9"
77+
CreateParserState:Function 7 captured name "c9"
78+
CreateParserState:Function 7 captured name "l9"
79+
CreateParserState:Function 7 captured name "b9"
80+
81+
class members capturing names
82+
CreateParserState:Function 1 captured name "class_10"
83+
CreateParserState:Function 1 captured name "c10"
84+
CreateParserState:Function 1 captured name "l10"
85+
CreateParserState:Function 1 captured name "b10"
86+
CreateParserState:Function 1 captured name "v10"
87+
CreateParserState:Function 2 captured name "class_10"
88+
CreateParserState:Function 2 captured name "c10"
89+
CreateParserState:Function 2 captured name "l10"
90+
CreateParserState:Function 2 captured name "b10"
91+
CreateParserState:Function 2 captured name "v10"
92+
CreateParserState:Function 3 captured name "class_10"
93+
CreateParserState:Function 3 captured name "c10"
94+
CreateParserState:Function 3 captured name "l10"
95+
CreateParserState:Function 3 captured name "b10"
96+
CreateParserState:Function 3 captured name "v10"
97+
CreateParserState:Function 4 captured name "class_10"
98+
CreateParserState:Function 4 captured name "c10"
99+
CreateParserState:Function 4 captured name "l10"
100+
CreateParserState:Function 4 captured name "b10"
101+
CreateParserState:Function 4 captured name "v10"
102+
CreateParserState:Function 5 captured name "class_10"
103+
CreateParserState:Function 5 captured name "c10"
104+
CreateParserState:Function 5 captured name "l10"
105+
CreateParserState:Function 5 captured name "b10"
106+
CreateParserState:Function 5 captured name "v10"
107+
CreateParserState:Function 6 captured name "class_10"
108+
CreateParserState:Function 6 captured name "c10"
109+
CreateParserState:Function 6 captured name "l10"
110+
CreateParserState:Function 6 captured name "b10"
111+
CreateParserState:Function 6 captured name "v10"
112+
CreateParserState:Function 6 captured name "a10"
113+
CreateParserState:Function 7 captured name "class_10"
114+
CreateParserState:Function 7 captured name "c10"
115+
CreateParserState:Function 7 captured name "l10"
116+
CreateParserState:Function 7 captured name "b10"
117+
CreateParserState:Function 7 captured name "v10"
118+
119+
lambda capturing special names
120+
CreateParserState:Function 2 captured name "arguments"
121+
CreateParserState:Function 3 captured name "*this*"
122+
CreateParserState:Function 4 captured name "*new.target*"
123+
CreateParserState:Function 6 captured name "*superconstructor*"
124+
CreateParserState:Function 6 captured name "*new.target*"
125+
CreateParserState:Function 6 captured name "*this*"
126+
CreateParserState:Function 5 captured name "*superconstructor*"
127+
CreateParserState:Function 8 captured name "*super*"
128+
CreateParserState:Function 8 captured name "*this*"
129+
CreateParserState:Function 1 captured name "*superconstructor*"
130+
131+
nested function with shadowing block-scoped name
132+
CreateParserState:Function 1 captured name "v12"
133+
134+
nested functions with a few nested captures
135+
CreateParserState:Function 3 captured name "b13"
136+
CreateParserState:Function 3 captured name "d13"
137+
CreateParserState:Function 3 captured name "a13"
138+
CreateParserState:Function 3 captured name "c13"
139+
CreateParserState:Function 2 captured name "b13"
140+
CreateParserState:Function 2 captured name "a13"
141+
CreateParserState:Function 5 captured name "b13"
142+
CreateParserState:Function 5 captured name "f13"
143+
CreateParserState:Function 4 captured name "e13"
144+
CreateParserState:Function 4 captured name "b13"
145+
CreateParserState:Function 4 captured name "f13"
146+
CreateParserState:Function 1 captured name "e13"
147+
CreateParserState:Function 1 captured name "f13"
148+
CreateParserState:Function 1 captured name "a13"
149+

0 commit comments

Comments
 (0)