Skip to content

Commit 14b97eb

Browse files
committed
Fix inconsistency in F12 rundown/reparse after redeferral. F12 rundown/reparse counts on having function bodies stay around so that top-level functions can be discovered and reparsed. With the changes in redeferral, and making the function body dictionary into a LeafValueDictionary, function bodies can be discarded, so the functions that appear to be top-level at reparse time are missing information about enclosing scopes. Fix this by detecting top-level functions at byte code gen time and reparsing them even if they've been kicked out of the dictionary.
1 parent c1e922b commit 14b97eb

8 files changed

Lines changed: 79 additions & 96 deletions

File tree

lib/Runtime/Base/FunctionBody.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ namespace Js
203203
// Given an offset into the source buffer, determine if the end of this SourceInfo
204204
// lies after the given offset.
205205
bool
206-
FunctionBody::EndsAfter(size_t offset) const
206+
ParseableFunctionInfo::EndsAfter(size_t offset) const
207207
{
208208
return offset < this->StartOffset() + this->LengthInBytes();
209209
}

lib/Runtime/Base/FunctionBody.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1980,6 +1980,8 @@ namespace Js
19801980
void SetReparsed(bool set) { m_reparsed = set; }
19811981
bool GetExternalDisplaySourceName(BSTR* sourceName);
19821982

1983+
bool EndsAfter(size_t offset) const;
1984+
19831985
void SetDoBackendArgumentsOptimization(bool set)
19841986
{
19851987
m_doBackendArgumentsOptimization = set;
@@ -2875,8 +2877,6 @@ namespace Js
28752877

28762878
bool HasGeneratedFromByteCodeCache() const { return this->byteCodeCache != nullptr; }
28772879

2878-
bool EndsAfter(size_t offset) const;
2879-
28802880
void TrackLoad(int ichMin);
28812881

28822882
SmallSpanSequence* GetStatementMapSpanSequence() const { return m_sourceInfo.pSpanSequence; }

lib/Runtime/Base/Utf8SourceInfo.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ namespace Js
3232
m_lineOffsetCache(nullptr),
3333
m_deferredFunctionsDictionary(nullptr),
3434
m_deferredFunctionsInitialized(false),
35+
topLevelFunctionInfoList(nullptr),
3536
debugModeSource(nullptr),
3637
debugModeSourceIsEmpty(false),
3738
debugModeSourceLength(0),
@@ -140,6 +141,31 @@ namespace Js
140141
functionBody->SetIsFuncRegistered(true);
141142
}
142143

144+
void Utf8SourceInfo::AddTopLevelFunctionInfo(FunctionInfo * functionInfo, Recycler * recycler)
145+
{
146+
JsUtil::List<FunctionInfo *, Recycler> * list = EnsureTopLevelFunctionInfoList(recycler);
147+
Assert(!list->Contains(functionInfo));
148+
list->Add(functionInfo);
149+
}
150+
151+
void Utf8SourceInfo::ClearTopLevelFunctionInfoList()
152+
{
153+
if (this->topLevelFunctionInfoList)
154+
{
155+
this->topLevelFunctionInfoList->Clear();
156+
}
157+
}
158+
159+
JsUtil::List<FunctionInfo *, Recycler> *
160+
Utf8SourceInfo::EnsureTopLevelFunctionInfoList(Recycler * recycler)
161+
{
162+
if (this->topLevelFunctionInfoList == nullptr)
163+
{
164+
this->topLevelFunctionInfoList = JsUtil::List<FunctionInfo *, Recycler>::New(recycler);
165+
}
166+
return this->topLevelFunctionInfoList;
167+
}
168+
143169
void Utf8SourceInfo::EnsureInitialized(int initialFunctionCount)
144170
{
145171
ThreadContext* threadContext = ThreadContext::GetContextForCurrentThread();

lib/Runtime/Base/Utf8SourceInfo.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,14 @@ namespace Js
130130
void SetFunctionBody(FunctionBody * functionBody);
131131
void RemoveFunctionBody(FunctionBody* functionBodyBeingRemoved);
132132

133+
void AddTopLevelFunctionInfo(Js::FunctionInfo * functionInfo, Recycler * recycler);
134+
void ClearTopLevelFunctionInfoList();
135+
JsUtil::List<Js::FunctionInfo *, Recycler> * EnsureTopLevelFunctionInfoList(Recycler * recycler);
136+
JsUtil::List<Js::FunctionInfo *, Recycler> * GetTopLevelFunctionInfoList() const
137+
{
138+
return this->topLevelFunctionInfoList;
139+
}
140+
133141
// The following functions could get called even if EnsureInitialized hadn't gotten called
134142
// (Namely in the OOM scenario), so we simply guard against that condition rather than
135143
// asserting
@@ -376,6 +384,7 @@ namespace Js
376384

377385
FunctionBodyDictionary* functionBodyDictionary;
378386
DeferredFunctionsDictionary* m_deferredFunctionsDictionary;
387+
JsUtil::List<Js::FunctionInfo *, Recycler> *topLevelFunctionInfoList;
379388

380389
DebugDocument* m_debugDocument;
381390

lib/Runtime/ByteCode/ByteCodeGenerator.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,7 @@ void ByteCodeGenerator::SetRootFuncInfo(FuncInfo* func)
775775
}
776776

777777
this->pRootFunc = func->byteCodeFunction->GetParseableFunctionInfo();
778+
this->m_utf8SourceInfo->AddTopLevelFunctionInfo(func->byteCodeFunction->GetFunctionInfo(), scriptContext->GetRecycler());
778779
}
779780

780781
Js::RegSlot ByteCodeGenerator::NextVarRegister()

lib/Runtime/ByteCode/ByteCodeSerializer.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3858,6 +3858,9 @@ class ByteCodeBufferReader
38583858

38593859
(*function) = functionBody;
38603860

3861+
sourceInfo->ClearTopLevelFunctionInfoList();
3862+
sourceInfo->AddTopLevelFunctionInfo(functionBody->GetFunctionInfo(), this->scriptContext->GetRecycler());
3863+
38613864
#if ENABLE_NATIVE_CODEGEN && defined(ENABLE_PREJIT)
38623865
if (prejit)
38633866
{

lib/Runtime/Debug/DebugContext.cpp

Lines changed: 36 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -72,16 +72,38 @@ namespace Js
7272
return;
7373
}
7474

75-
FunctionBody * functionBody;
75+
this->RegisterFunction(func, func->GetHostSourceContext(), title);
76+
}
77+
78+
void DebugContext::RegisterFunction(Js::ParseableFunctionInfo * func, DWORD_PTR dwDebugSourceContext, LPCWSTR title)
79+
{
80+
if (!this->CanRegisterFunction())
81+
{
82+
return;
83+
}
84+
85+
FunctionBody * functionBody = nullptr;
7686
if (func->IsDeferredParseFunction())
7787
{
78-
functionBody = func->Parse();
88+
HRESULT hr = S_OK;
89+
Assert(!this->scriptContext->GetThreadContext()->IsScriptActive());
90+
91+
BEGIN_JS_RUNTIME_CALL_EX_AND_TRANSLATE_EXCEPTION_AND_ERROROBJECT_TO_HRESULT_NESTED(this->scriptContext, false)
92+
{
93+
functionBody = func->Parse();
94+
}
95+
END_JS_RUNTIME_CALL_AND_TRANSLATE_EXCEPTION_AND_ERROROBJECT_TO_HRESULT(hr);
96+
97+
if (FAILED(hr))
98+
{
99+
return;
100+
}
79101
}
80102
else
81103
{
82104
functionBody = func->GetFunctionBody();
83105
}
84-
this->RegisterFunction(functionBody, functionBody->GetHostSourceContext(), title);
106+
this->RegisterFunction(functionBody, dwDebugSourceContext, title);
85107
}
86108

87109
void DebugContext::RegisterFunction(Js::FunctionBody * functionBody, DWORD_PTR dwDebugSourceContext, LPCWSTR title)
@@ -123,7 +145,7 @@ namespace Js
123145
this->scriptContext, shouldPerformSourceRundown, shouldReparseFunctions);
124146

125147
Js::TempArenaAllocatorObject *tempAllocator = nullptr;
126-
JsUtil::List<Js::FunctionBody *, ArenaAllocator>* pFunctionsToRegister = nullptr;
148+
JsUtil::List<Js::FunctionInfo *, Recycler>* pFunctionsToRegister = nullptr;
127149
JsUtil::List<Js::Utf8SourceInfo *, Recycler, false, Js::CopyRemovePolicy, RecyclerPointerComparer>* utf8SourceInfoList = nullptr;
128150

129151
HRESULT hr = S_OK;
@@ -132,7 +154,6 @@ namespace Js
132154
BEGIN_TRANSLATE_OOM_TO_HRESULT_NESTED
133155
tempAllocator = threadContext->GetTemporaryAllocator(_u("debuggerAlloc"));
134156

135-
pFunctionsToRegister = JsUtil::List<Js::FunctionBody*, ArenaAllocator>::New(tempAllocator->GetAllocator());
136157
utf8SourceInfoList = JsUtil::List<Js::Utf8SourceInfo *, Recycler, false, Js::CopyRemovePolicy, RecyclerPointerComparer>::New(this->scriptContext->GetRecycler());
137158

138159
this->MapUTF8SourceInfoUntil([&](Js::Utf8SourceInfo * sourceInfo) -> bool
@@ -197,9 +218,9 @@ namespace Js
197218
dwDebugHostSourceContext = this->hostDebugContext->GetHostSourceContext(sourceInfo);
198219
}
199220

200-
this->FetchTopLevelFunction(pFunctionsToRegister, sourceInfo);
221+
pFunctionsToRegister = sourceInfo->GetTopLevelFunctionInfoList();
201222

202-
if (pFunctionsToRegister->Count() == 0)
223+
if (pFunctionsToRegister == nullptr || pFunctionsToRegister->Count() == 0)
203224
{
204225
// This could happen if there are no functions to re-compile.
205226
return false;
@@ -221,21 +242,20 @@ namespace Js
221242
return true;
222243
}
223244

224-
Js::FunctionBody* pFuncBody = pFunctionsToRegister->Item(i);
225-
if (pFuncBody == nullptr)
245+
Js::FunctionInfo *functionInfo = pFunctionsToRegister->Item(i);
246+
if (functionInfo == nullptr)
226247
{
227248
continue;
228249
}
229250

230251
if (shouldReparseFunctions)
231252
{
232-
233253
BEGIN_JS_RUNTIME_CALL_EX_AND_TRANSLATE_EXCEPTION_AND_ERROROBJECT_TO_HRESULT_NESTED(cachedScriptContext, false)
234254
{
235-
pFuncBody->Parse();
255+
functionInfo->GetParseableFunctionInfo()->Parse();
236256
// This is the first call to the function, ensure dynamic profile info
237257
#if ENABLE_PROFILE_INFO
238-
pFuncBody->EnsureDynamicProfileInfo();
258+
functionInfo->GetFunctionBody()->EnsureDynamicProfileInfo();
239259
#endif
240260
}
241261
END_JS_RUNTIME_CALL_AND_TRANSLATE_EXCEPTION_AND_ERROROBJECT_TO_HRESULT(hr);
@@ -244,11 +264,14 @@ namespace Js
244264
DEBUGGER_ATTACHDETACH_FATAL_ERROR_IF_FAILED(hr);
245265
}
246266

267+
// Parsing the function may change its FunctionProxy.
268+
Js::ParseableFunctionInfo *parseableFunctionInfo = functionInfo->GetParseableFunctionInfo();
269+
247270
if (!fHasDoneSourceRundown && shouldPerformSourceRundown && !cachedScriptContext->IsClosed())
248271
{
249272
BEGIN_TRANSLATE_OOM_TO_HRESULT_NESTED
250273
{
251-
this->RegisterFunction(pFuncBody, dwDebugHostSourceContext, pFuncBody->GetSourceName());
274+
this->RegisterFunction(parseableFunctionInfo, dwDebugHostSourceContext, parseableFunctionInfo->GetSourceName());
252275
}
253276
END_TRANSLATE_OOM_TO_HRESULT(hr);
254277

@@ -294,85 +317,6 @@ namespace Js
294317
return hr;
295318
}
296319

297-
void DebugContext::FetchTopLevelFunction(JsUtil::List<Js::FunctionBody *, ArenaAllocator>* pFunctions, Js::Utf8SourceInfo * sourceInfo)
298-
{
299-
Assert(pFunctions != nullptr);
300-
Assert(sourceInfo != nullptr);
301-
302-
HRESULT hr = S_OK;
303-
304-
// Get FunctionBodys which are distinctly parseable, i.e. they are not enclosed in any other function (finding
305-
// out root node of the sub-tree, in which root node is not enclosed in any other available function) this is
306-
// by walking over all function and comparing their range.
307-
308-
BEGIN_TRANSLATE_OOM_TO_HRESULT_NESTED
309-
{
310-
pFunctions->Clear();
311-
312-
sourceInfo->MapFunctionUntil([&](Js::FunctionBody* pFuncBody) -> bool
313-
{
314-
if (pFuncBody->GetIsGlobalFunc())
315-
{
316-
if (pFuncBody->IsFakeGlobalFunc(pFuncBody->GetGrfscr()))
317-
{
318-
// This is created due to 'Function' code or deferred parsed functions, there is nothing to
319-
// re-compile in this function as this is just a place-holder/fake function.
320-
321-
Assert(pFuncBody->GetByteCode() == NULL);
322-
323-
return false;
324-
}
325-
326-
if (!pFuncBody->GetIsTopLevel())
327-
{
328-
return false;
329-
}
330-
331-
// If global function, there is no need to find out any other functions.
332-
333-
pFunctions->Clear();
334-
pFunctions->Add(pFuncBody);
335-
return true;
336-
}
337-
338-
if (pFuncBody->IsFunctionParsed())
339-
{
340-
bool isNeedToAdd = true;
341-
for (int i = 0; i < pFunctions->Count(); i++)
342-
{
343-
Js::FunctionBody *currentFunction = pFunctions->Item(i);
344-
if (currentFunction != nullptr)
345-
{
346-
if (currentFunction->StartInDocument() > pFuncBody->StartInDocument() || !currentFunction->EndsAfter(pFuncBody->StartInDocument()))
347-
{
348-
if (pFuncBody->StartInDocument() <= currentFunction->StartInDocument() && pFuncBody->EndsAfter(currentFunction->StartInDocument()))
349-
{
350-
// The stored item has got the parent, remove current Item
351-
pFunctions->Item(i, nullptr);
352-
}
353-
}
354-
else
355-
{
356-
// Parent (the enclosing function) is already in the list
357-
isNeedToAdd = false;
358-
break;
359-
}
360-
}
361-
}
362-
363-
if (isNeedToAdd)
364-
{
365-
pFunctions->Add(pFuncBody);
366-
}
367-
}
368-
return false;
369-
});
370-
}
371-
END_TRANSLATE_OOM_TO_HRESULT(hr);
372-
373-
Assert(hr == S_OK);
374-
}
375-
376320
// Create an ordered flat list of sources to reparse. Caller of a source should be added to the list before we add the source itself.
377321
void DebugContext::WalkAndAddUtf8SourceInfo(Js::Utf8SourceInfo* sourceInfo, JsUtil::List<Js::Utf8SourceInfo *, Recycler, false, Js::CopyRemovePolicy, RecyclerPointerComparer> *utf8SourceInfoList)
378322
{

lib/Runtime/Debug/DebugContext.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@ namespace Js
6767
ProbeContainer* diagProbesContainer;
6868

6969
// Private Functions
70-
void FetchTopLevelFunction(JsUtil::List<Js::FunctionBody *, ArenaAllocator>* pFunctions, Js::Utf8SourceInfo * sourceInfo);
7170
void WalkAndAddUtf8SourceInfo(Js::Utf8SourceInfo* sourceInfo, JsUtil::List<Js::Utf8SourceInfo *, Recycler, false, Js::CopyRemovePolicy, RecyclerPointerComparer> *utf8SourceInfoList);
7271
bool CanRegisterFunction() const;
72+
void RegisterFunction(Js::ParseableFunctionInfo * func, DWORD_PTR dwDebugSourceContext, LPCWSTR title);
7373
void RegisterFunction(Js::FunctionBody * functionBody, DWORD_PTR dwDebugSourceContext, LPCWSTR title);
7474

7575
template<class TMapFunction>

0 commit comments

Comments
 (0)