Skip to content

Commit 0730b1f

Browse files
committed
[MERGE #5133 @boingoing] Use the generated parser state cache to skip scanning nested deferred functions again when defer-parsing
Merge pull request #5133 from boingoing:UseDeferredStubs2 Use the generated parser state cache to skip scanning nested deferred functions again when defer-parsing When we do the initial parse of a function, we need to scan the entire function and all the functions nested inside it. We don't actually construct the AST for the function or any of it's nested functions but we must scan them all to check for early syntax errors. Later (when we execute this function) we will go back and construct the AST for the function which was previously deferred by scanning the source code once more - this is referred to as defer-parsing the function. While defer-parsing a function we currently scan all of the functions nested inside again. We don't construct an AST for these nested functions but we do scan and verify their syntax (again). This additional scan is unnecessary and can be costly, especially if there are a lot of sibling functions or deep nesting of functions. Now that we have the parser state cache, we can use it to avoid rescanning these nested functions during defer-parse. The parser state cache is a loose term but it consists of a couple of concepts. First, the literal cache which contains bytecode of non-deferred functions, header information for deferred functions, and deferred stubs for nested deferred functions. When we deserialize this cache, we use it to build script functions for non-deferred functions - these can be run immediately. We also hydrate the deferred functions into ParseableFunctionInfo objects which will be on-demand defer-parsed into regular script functions. The deferred functions keep the set of their nested deferred stubs and use them to skip reparsing the nested functions during defer-parse. The second aspect of the parser state cache is these deferred stubs themselves and what they contain. The stubs are arranged into a tree structure where each stub contains information about one nested deferred function which can be used to construct a ParseNodeFnc without scanning the function script in the source code. Each stub knows the extents of the function in the source, parser flags, the set of names captured by the function, and the set of nested functions nested inside the function. When we are defer-parsing a function and we start to parse a nested function we know that there was not a syntax error in that nested deferred function because if there was then we wouldn't have come back to try and defer-parse the function. Thus we can avoid actually scanning the nested function entirely with the deferred stub because we know the function extents. We move the parser ahead to the character position at the end of the function and we push references to any names captured within the function. Now the parser is in the same state as if we had parsed the nested function but we were able to skip it entirely.
2 parents 11fe82c + 318d4a0 commit 0730b1f

25 files changed

+10914
-10141
lines changed

bin/ch/WScriptJsrt.cpp

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -546,8 +546,17 @@ JsValueRef WScriptJsrt::LoadScript(JsValueRef callee, LPCSTR fileName,
546546
strlen(fullPath), &fname));
547547
JsSourceContext sourceContext = GetNextSourceContext();
548548
RegisterScriptDir(sourceContext, fullPath);
549-
errorCode = ChakraRTInterface::JsRun(scriptSource, sourceContext,
550-
fname, JsParseScriptAttributeNone, &returnValue);
549+
550+
if (HostConfigFlags::flags.UseParserStateCacheIsEnabled)
551+
{
552+
JsValueRef parserState;
553+
IfJsrtErrorSetGo(ChakraRTInterface::JsSerializeParserState(scriptSource, &parserState, JsParseScriptAttributeNone));
554+
errorCode = ChakraRTInterface::JsRunScriptWithParserState(scriptSource, sourceContext, fname, JsParseScriptAttributeNone, parserState, &returnValue);
555+
}
556+
else
557+
{
558+
errorCode = ChakraRTInterface::JsRun(scriptSource, sourceContext, fname, JsParseScriptAttributeNone, &returnValue);
559+
}
551560

552561
if(errorCode == JsNoError)
553562
{
@@ -583,8 +592,17 @@ JsValueRef WScriptJsrt::LoadScript(JsValueRef callee, LPCSTR fileName,
583592
strlen(fullPath), &fname));
584593
JsSourceContext sourceContext = GetNextSourceContext();
585594
RegisterScriptDir(sourceContext, fullPath);
586-
errorCode = ChakraRTInterface::JsRun(scriptSource, sourceContext,
587-
fname, JsParseScriptAttributeNone, &returnValue);
595+
596+
if (HostConfigFlags::flags.UseParserStateCacheIsEnabled)
597+
{
598+
JsValueRef parserState;
599+
IfJsrtErrorSetGo(ChakraRTInterface::JsSerializeParserState(scriptSource, &parserState, JsParseScriptAttributeNone));
600+
errorCode = ChakraRTInterface::JsRunScriptWithParserState(scriptSource, sourceContext, fname, JsParseScriptAttributeNone, parserState, &returnValue);
601+
}
602+
else
603+
{
604+
errorCode = ChakraRTInterface::JsRun(scriptSource, sourceContext, fname, JsParseScriptAttributeNone, &returnValue);
605+
}
588606

589607
if (errorCode == JsNoError)
590608
{

lib/Common/ConfigFlagsList.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,7 @@ PHASE(All)
394394
PHASE(PerfHint)
395395
PHASE(TypeShareForChangePrototype)
396396
PHASE(DeferSourceLoad)
397+
PHASE(DataCache)
397398
PHASE(ObjectMutationBreakpoint)
398399
PHASE(NativeCodeData)
399400
PHASE(XData)
@@ -448,7 +449,6 @@ PHASE(All)
448449
#define DEFAULT_CONFIG_PrintLineColumnInfo (false)
449450
#define DEFAULT_CONFIG_ForceDecommitOnCollect (false)
450451
#define DEFAULT_CONFIG_ForceDeferParse (false)
451-
#define DEFAULT_CONFIG_ForceCreateParserState (false)
452452
#define DEFAULT_CONFIG_NoDeferParse (false)
453453
#define DEFAULT_CONFIG_ForceDynamicProfile (false)
454454
#define DEFAULT_CONFIG_ForceExpireOnNonCacheCollect (false)
@@ -1176,7 +1176,6 @@ FLAGNR(Boolean, ForceCleanCacheOnCollect, "Force cleaning of dynamic caches on c
11761176
FLAGNR(Boolean, ForceGCAfterJSONParse, "Force GC to happen after JSON parsing", DEFAULT_CONFIG_ForceGCAfterJSONParse)
11771177
FLAGNR(Boolean, ForceDecommitOnCollect, "Force decommit collect", DEFAULT_CONFIG_ForceDecommitOnCollect)
11781178
FLAGNR(Boolean, ForceDeferParse , "Defer parsing of all function bodies", DEFAULT_CONFIG_ForceDeferParse)
1179-
FLAGNR(Boolean, ForceCreateParserState , "Force creation of parser state", DEFAULT_CONFIG_ForceCreateParserState)
11801179
FLAGNR(Boolean, ForceDiagnosticsMode , "Enable diagnostics mode and debug interpreter loop", false)
11811180
FLAGNR(Boolean, ForceGetWriteWatchOOM , "Force GetWriteWatch to go into OOM codepath in HeapBlockMap rescan", false)
11821181
FLAGNR(Boolean, ForcePostLowerGlobOptInstrString, "Force tracking of globopt instr string post lower", DEFAULT_CONFIG_ForcePostLowerGlobOptInstrString)

lib/Jsrt/Jsrt.cpp

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3910,7 +3910,7 @@ JsErrorCode RunSerializedScriptCore(
39103910
{
39113911
flags = fscrAllowFunctionProxy;
39123912
}
3913-
if (useParserStateCache)
3913+
if (useParserStateCache && !CONFIG_FLAG(ForceSerialized))
39143914
{
39153915
flags |= fscrCreateParserState;
39163916
}
@@ -5627,23 +5627,19 @@ CHAKRA_API JsRunScriptWithParserState(
56275627
_In_ JsValueRef parserState,
56285628
_Out_ JsValueRef *result)
56295629
{
5630-
return ContextAPINoScriptWrapper_NoRecord([&](Js::ScriptContext *scriptContext) -> JsErrorCode {
5631-
PARAM_NOT_NULL(script);
5632-
PARAM_NOT_NULL(parserState);
5630+
PARAM_NOT_NULL(script);
5631+
PARAM_NOT_NULL(parserState);
56335632

5633+
const WCHAR *url = nullptr;
5634+
uint sourceIndex = 0;
5635+
5636+
JsErrorCode errorCode = ContextAPINoScriptWrapper_NoRecord([&](Js::ScriptContext *scriptContext) -> JsErrorCode {
56345637
const byte* bytes;
56355638
size_t cb;
56365639
LoadScriptFlag loadScriptFlag;
56375640

56385641
JsErrorCode errorCode = GetScriptBufferDetails(script, parseAttributes, &loadScriptFlag, &cb, &bytes);
56395642

5640-
if (errorCode != JsNoError)
5641-
{
5642-
return errorCode;
5643-
}
5644-
5645-
const WCHAR *url;
5646-
56475643
if (sourceUrl && Js::JavascriptString::Is(sourceUrl))
56485644
{
56495645
url = ((Js::JavascriptString*)(sourceUrl))->GetSz();
@@ -5653,6 +5649,11 @@ CHAKRA_API JsRunScriptWithParserState(
56535649
return JsErrorInvalidArgument;
56545650
}
56555651

5652+
if (errorCode != JsNoError)
5653+
{
5654+
return errorCode;
5655+
}
5656+
56565657
SourceContextInfo* sourceContextInfo = scriptContext->GetSourceContextInfo(sourceContext, nullptr);
56575658

56585659
if (sourceContextInfo == nullptr)
@@ -5675,7 +5676,6 @@ CHAKRA_API JsRunScriptWithParserState(
56755676
/* grfsi */ 0
56765677
};
56775678

5678-
56795679
Js::Utf8SourceInfo* utf8SourceInfo = nullptr;
56805680
scriptContext->MakeUtf8SourceInfo(bytes, cb, &si, &utf8SourceInfo, loadScriptFlag, script);
56815681

@@ -5687,8 +5687,6 @@ CHAKRA_API JsRunScriptWithParserState(
56875687
ULONG grfscr = scriptContext->GetParseFlags(loadScriptFlag, utf8SourceInfo, sourceContextInfo);
56885688
utf8SourceInfo->SetParseFlags(grfscr);
56895689

5690-
uint sourceIndex = 0;
5691-
56925690
if ((loadScriptFlag & LoadScriptFlag_Utf8Source) != LoadScriptFlag_Utf8Source)
56935691
{
56945692
sourceIndex = scriptContext->SaveSourceNoCopy(utf8SourceInfo, static_cast<charcount_t>(utf8SourceInfo->GetCchLength()), /*isCesu8*/ true);
@@ -5699,19 +5697,26 @@ CHAKRA_API JsRunScriptWithParserState(
56995697
sourceIndex = scriptContext->SaveSourceNoCopy(utf8SourceInfo, static_cast<charcount_t>(utf8SourceInfo->GetCchLength()), /* isCesu8*/ false);
57005698
}
57015699

5702-
if (!Js::ExternalArrayBuffer::Is(parserState))
5703-
{
5704-
return JsErrorInvalidArgument;
5705-
}
5700+
return JsNoError;
5701+
});
57065702

5707-
byte* buffer = Js::ArrayBuffer::FromVar(parserState)->GetBuffer();
5708-
JsSerializedLoadScriptCallback dummy = DummyScriptLoadSourceCallbackForRunScriptWithParserState;
5703+
if (errorCode != JsNoError)
5704+
{
5705+
return errorCode;
5706+
}
57095707

5710-
return RunSerializedScriptCore(
5711-
dummy, DummyScriptUnloadCallback,
5712-
sourceContext, // use the same user provided sourceContext as scriptLoadSourceContext
5713-
buffer, parserState, sourceContext, url, false, true, result, sourceIndex);
5714-
});
5708+
if (!Js::ExternalArrayBuffer::Is(parserState))
5709+
{
5710+
return JsErrorInvalidArgument;
5711+
}
5712+
5713+
byte* buffer = Js::ArrayBuffer::FromVar(parserState)->GetBuffer();
5714+
JsSerializedLoadScriptCallback dummy = DummyScriptLoadSourceCallbackForRunScriptWithParserState;
5715+
5716+
return RunSerializedScriptCore(
5717+
dummy, DummyScriptUnloadCallback,
5718+
sourceContext, // use the same user provided sourceContext as scriptLoadSourceContext
5719+
buffer, parserState, sourceContext, url, false, true, result, sourceIndex);
57155720
}
57165721

57175722
#endif // _CHAKRACOREBUILD

0 commit comments

Comments
 (0)