Skip to content

Commit

Permalink
[MERGE #4136 @boingoing] Support for defer parse of object literal me…
Browse files Browse the repository at this point in the history
…thods

Merge pull request #4136 from boingoing:DeferParseMethods

This is mostly a straightforward modification to the defer-parse methods in the parser to enable them to know how to handle the object literal method grammar.

Communicate the fact that the deferred function is a method via a parser flag - `fscrDeferredFncIsMethod`.
  • Loading branch information
boingoing committed Nov 4, 2017
2 parents b006606 + 7941e72 commit 83b1218
Show file tree
Hide file tree
Showing 7 changed files with 321 additions and 36 deletions.
131 changes: 102 additions & 29 deletions lib/Parser/Parse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3215,6 +3215,14 @@ ParseNodePtr Parser::ParseTerm(BOOL fAllowCall,
pid = ParseSuper<buildAST>(!!fAllowCall);
isSpecialName = true;

// Super reference and super call need to push a pid ref to 'this' even when not building an AST
ReferenceSpecialName(wellKnownPropertyPids._this, ichMin, ichLim);
// Super call needs to reference 'new.target'
if (pid == wellKnownPropertyPids._superConstructor)
{
ReferenceSpecialName(wellKnownPropertyPids._newTarget, ichMin, ichLim);
}

goto LIdentifier;

case tkTHIS:
Expand Down Expand Up @@ -4529,6 +4537,11 @@ ParseNodePtr Parser::ParseMemberList(LPCOLESTR pNameHint, uint32* pNameHintLengt
{
Error(ERRsyntax);
}

// Include star character in the function extents
ichMin = m_pscan->IchMinTok();
iecpMin = m_pscan->IecpMinTok();

m_pscan->ScanForcingPid();
fncDeclFlags |= fFncGenerator;
}
Expand Down Expand Up @@ -4702,7 +4715,7 @@ ParseNodePtr Parser::ParseMemberList(LPCOLESTR pNameHint, uint32* pNameHintLengt
ParseNodePtr pnodeFunc = ParseFncDecl<buildAST>(fncDeclFlags | (isAsyncMethod ? fFncAsync : fFncNoFlgs), pFullNameHint,
/*needsPIDOnRCurlyScan*/ false, /*resetParsingSuperRestrictionState*/ false);

if (isAsyncMethod)
if (isAsyncMethod || isGenerator)
{
pnodeFunc->sxFnc.cbMin = iecpMin;
pnodeFunc->ichMin = ichMin;
Expand Down Expand Up @@ -5277,7 +5290,6 @@ bool Parser::ParseFncDeclHelper(ParseNodePtr pnodeFnc, LPCOLESTR pNameHint, usho
// switch scanner to treat 'yield' as keyword in generator functions
// or as an identifier in non-generator functions
bool fPreviousYieldIsKeyword = m_pscan->SetYieldIsKeywordRegion(pnodeFnc && pnodeFnc->sxFnc.IsGenerator());

bool fPreviousAwaitIsKeyword = m_pscan->SetAwaitIsKeywordRegion(fAsync);

if (pnodeFnc && pnodeFnc->sxFnc.IsGenerator())
Expand Down Expand Up @@ -5318,10 +5330,10 @@ bool Parser::ParseFncDeclHelper(ParseNodePtr pnodeFnc, LPCOLESTR pNameHint, usho
}

uint uDeferSave = m_grfscr & fscrDeferFncParse;
if (flags & fFncNoName)
if (flags & fFncClassMember)
{
// Disable deferral on getter/setter or other construct with unusual text bounds
// (fFncNoName) as these are usually trivial, and re-parsing is problematic.
// Disable deferral on class members or other construct with unusual text bounds
// as these are usually trivial, and re-parsing is problematic.
// NOTE: It is probably worth supporting these cases for memory and load-time purposes,
// especially as they become more and more common.
m_grfscr &= ~fscrDeferFncParse;
Expand Down Expand Up @@ -7129,6 +7141,7 @@ void Parser::FinishFncNode(ParseNodePtr pnodeFnc)
m_pnestedCount = &pnodeFnc->sxFnc.nestedCount;

bool fLambda = pnodeFnc->sxFnc.IsLambda();
bool fMethod = pnodeFnc->sxFnc.IsMethod();

// Cue up the parser to the start of the function body.
if (pnodeFnc->sxFnc.pnodeName)
Expand All @@ -7139,7 +7152,30 @@ void Parser::FinishFncNode(ParseNodePtr pnodeFnc)
else
{
m_pscan->SetCurrentCharacter(pnodeFnc->ichMin, pnodeFnc->sxFnc.lineNumber);
if (pnodeFnc->sxFnc.IsAccessor())

if (fMethod)
{
// Method. Skip identifier name, computed property name, "async", "get", "set", and '*' or '(' characters.
for (;;)
{
m_pscan->Scan();
// '[' character indicates a computed property name for this method. We should consume it.
if (m_token.tk == tkLBrack)
{
// We don't care what the name expr is.
m_pscan->Scan();
ParseExpr<false>();
Assert(m_token.tk == tkRBrack);
continue;
}
// Quit scanning ahead when we reach a '(' character which opens the arg list.
if (m_token.tk == tkLParen)
{
break;
}
}
}
else if (pnodeFnc->sxFnc.IsAccessor())
{
// Getter/setter. The node text starts with the name, so eat that.
m_pscan->ScanNoKeywords();
Expand Down Expand Up @@ -7171,7 +7207,11 @@ void Parser::FinishFncNode(ParseNodePtr pnodeFnc)
bool fPreviousAwaitIsKeyword = m_pscan->SetAwaitIsKeywordRegion(pnodeFnc && pnodeFnc->sxFnc.IsAsync());

// Skip the arg list.
m_pscan->Scan();
if (!fMethod)
{
// If this is a method, we've already advanced to the '(' token.
m_pscan->Scan();
}
if (m_token.tk == tkStar)
{
Assert(pnodeFnc->sxFnc.IsGenerator());
Expand Down Expand Up @@ -11517,7 +11557,9 @@ ParseNodePtr Parser::Parse(LPCUTF8 pszSrc, size_t offset, size_t length, charcou
ushort flags = fFncNoFlgs;
size_t iecpMin = 0;
charcount_t ichMin = 0;
bool isAsyncMethod = false;
bool isAsync = false;
bool isGenerator = false;
bool isMethod = false;

// The top-level deferred function body was defined by a function expression whose parsing was deferred. We are now
// parsing it, so unset the flag so that any nested functions are parsed normally. This flag is only applicable the
Expand All @@ -11536,49 +11578,73 @@ ParseNodePtr Parser::Parse(LPCUTF8 pszSrc, size_t offset, size_t length, charcou
flags |= fFncDeclaration;
}

// There are three cases which can confirm async function:
// async function... -> async function
// async (... -> async lambda with parens around the formal parameter
// async identifier... -> async lambda with single identifier parameter
if (m_grfscr & fscrDeferredFncIsMethod)
{
m_grfscr &= ~fscrDeferredFncIsMethod;
isMethod = true;
flags |= fFncNoName | fFncMethod;
}

// These are the cases which can confirm async function:
// async function() {} -> async function
// async () => {} -> async lambda with parens around the formal parameter
// async arg => {} -> async lambda with single identifier parameter
// async name() {} -> async method
// async [computed_name]() {} -> async method with a computed name
if (m_token.tk == tkID && m_token.GetIdentifier(m_phtbl) == wellKnownPropertyPids.async && m_scriptContext->GetConfig()->IsES7AsyncAndAwaitEnabled())
{
ichMin = m_pscan->IchMinTok();
iecpMin = m_pscan->IecpMinTok();

// Keep state so we can rewind if it turns out that this isn't an async function.
// The only way this can happen is if we have a lambda with a single formal parameter named 'async' not enclosed by parens.
// Keep state so we can rewind if it turns out that this isn't an async function:
// async() {} -> method named async
// async => {} -> lambda with single parameter named async
RestorePoint termStart;
m_pscan->Capture(&termStart);

m_pscan->Scan();
if ((m_token.tk == tkID || m_token.tk == tkLParen || m_token.tk == tkFUNCTION) && !m_pscan->FHadNewLine())

if (m_token.tk == tkDArrow || (m_token.tk == tkLParen && isMethod) || m_pscan->FHadNewLine())
{
flags |= fFncAsync;
isAsyncMethod = true;
m_pscan->SeekTo(termStart);
}
else
{
m_pscan->SeekTo(termStart);
flags |= fFncAsync;
isAsync = true;
}
}

// If first token of the function is tkID or tkLParen, this is a lambda.
if (m_token.tk == tkID || m_token.tk == tkLParen)
if (m_token.tk == tkStar && m_scriptContext->GetConfig()->IsES6GeneratorsEnabled())
{
flags |= fFncLambda;
ichMin = m_pscan->IchMinTok();
iecpMin = m_pscan->IecpMinTok();

flags |= fFncGenerator;
isGenerator = true;

m_pscan->Scan();
}
else

// Eat the computed name expression
if (m_token.tk == tkLBrack && isMethod)
{
// Must be ordinary function keyword - do not eat the token
ChkCurTokNoScan(tkFUNCTION, ERRsyntax);
m_pscan->Scan();
ParseExpr<false>();
}

if (!isMethod && (m_token.tk == tkID || m_token.tk == tkLParen))
{
// If first token of the function is tkID or tkLParen, this is a lambda.
flags |= fFncLambda;
}

ParseNodePtr pnodeFnc = ParseFncDecl<true>(flags, nullptr, false, false);
pnodeProg->sxFnc.pnodeBody = nullptr;
AddToNodeList(&pnodeProg->sxFnc.pnodeBody, &lastNodeRef, pnodeFnc);

// Include the async keyword in the function extents
if (isAsyncMethod)
// Include the async keyword or star character in the function extents
if (isAsync || isGenerator)
{
pnodeFnc->sxFnc.cbMin = iecpMin;
pnodeFnc->ichMin = ichMin;
Expand Down Expand Up @@ -12519,6 +12585,16 @@ IdentPtr Parser::ParseSuper(bool fAllowCall)
break;
}

currentNodeFunc->sxFnc.SetHasSuperReference(TRUE);
CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(Super, m_scriptContext);

// If we are defer parsing, we can skip verifying that the super reference is valid.
// If it wasn't the parser would have thrown during upfront parsing and we wouldn't be defer parsing the function.
if (m_parseType == ParseType_Deferred)
{
return superPid;
}

if (!fAllowCall && (m_token.tk == tkLParen))
{
Error(ERRInvalidSuper); // new super() is not allowed
Expand Down Expand Up @@ -12556,9 +12632,6 @@ IdentPtr Parser::ParseSuper(bool fAllowCall)
// Anything else is an error
Error(ERRInvalidSuper);
}

currentNodeFunc->sxFnc.SetHasSuperReference(TRUE);
CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(Super, m_scriptContext);

return superPid;
}
Expand Down
5 changes: 3 additions & 2 deletions lib/Parser/ParseFlags.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ enum
// let/const in global scope instead of eval scope so that they can be preserved across console inputs
fscrNoAsmJs = 1 << 25, // Disable generation of asm.js code
fscrIsModuleCode = 1 << 26, // Current code should be parsed as a module body
fscrAll = (1 << 27) - 1
};

fscrDeferredFncIsMethod = 1 << 27,
fscrAll = (1 << 28) - 1
};
14 changes: 13 additions & 1 deletion lib/Runtime/Base/FunctionBody.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1605,7 +1605,8 @@ namespace Js
paramScopeSlotArraySize(0),
m_reparsed(false),
m_isAsmJsFunction(false),
m_tag21(true)
m_tag21(true),
m_isMethod(false)
#if DBG
,m_wasEverAsmjsMode(false)
,scopeObjectSize(0)
Expand Down Expand Up @@ -1655,6 +1656,7 @@ namespace Js
m_isStaticNameFunction(proxy->GetIsStaticNameFunction()),
m_reportedInParamCount(proxy->GetReportedInParamsCount()),
m_reparsed(proxy->IsReparsed()),
m_isMethod(proxy->IsMethod()),
m_tag21(true)
#if DBG
,m_wasEverAsmjsMode(proxy->m_wasEverAsmjsMode)
Expand Down Expand Up @@ -2336,6 +2338,16 @@ namespace Js
// (not a function declaration statement).
grfscr |= fscrDeferredFncExpression;
}

if (funcBody->IsMethod())
{
grfscr |= fscrDeferredFncIsMethod;
}
else
{
grfscr &= ~fscrDeferredFncIsMethod;
}

if (!CONFIG_FLAG(DeferNested) || isDebugOrAsmJsReparse)
{
grfscr &= ~fscrDeferFncParse; // Disable deferred parsing if not DeferNested, or doing a debug/asm.js re-parse
Expand Down
9 changes: 5 additions & 4 deletions lib/Runtime/Base/FunctionBody.h
Original file line number Diff line number Diff line change
Expand Up @@ -2025,6 +2025,8 @@ namespace Js
LPCUTF8 GetStartOfDocument(const char16* reason = nullptr) const;
bool IsReparsed() const { return m_reparsed; }
void SetReparsed(bool set) { m_reparsed = set; }
bool IsMethod() const { return m_isMethod; }
void SetIsMethod(bool set) { m_isMethod = set; }
bool GetExternalDisplaySourceName(BSTR* sourceName);

void CleanupToReparse();
Expand Down Expand Up @@ -2159,10 +2161,9 @@ namespace Js
FieldWithBarrier(bool) m_isEval : 1; // Source code is in 'eval'
FieldWithBarrier(bool) m_isDynamicFunction : 1; // Source code is in 'Function'
FieldWithBarrier(bool) m_hasImplicitArgIns : 1;
FieldWithBarrier(bool) m_dontInline : 1; // Used by the JIT's inliner

// Indicates if the function has been reparsed for debug attach/detach scenario.
FieldWithBarrier(bool) m_reparsed : 1;
FieldWithBarrier(bool) m_dontInline : 1; // Used by the JIT's inliner
FieldWithBarrier(bool) m_reparsed : 1; // Indicates if the function has been reparsed for debug attach/detach scenario.
FieldWithBarrier(bool) m_isMethod : 1; // Function is an object literal method

// This field is not required for deferred parsing but because our thunks can't handle offsets > 128 bytes
// yet, leaving this here for now. We can look at optimizing the function info and function proxy structures some
Expand Down
1 change: 1 addition & 0 deletions lib/Runtime/ByteCode/ByteCodeGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1403,6 +1403,7 @@ FuncInfo * ByteCodeGenerator::StartBindFunction(const char16 *name, uint nameLen
parseableFunctionInfo->deferredParseNextFunctionId = pnode->sxFnc.deferredParseNextFunctionId;
#endif
parseableFunctionInfo->SetIsDeclaration(pnode->sxFnc.IsDeclaration() != 0);
parseableFunctionInfo->SetIsMethod(pnode->sxFnc.IsMethod() != 0);
parseableFunctionInfo->SetIsAccessor(pnode->sxFnc.IsAccessor() != 0);
if (pnode->sxFnc.IsAccessor())
{
Expand Down
Loading

0 comments on commit 83b1218

Please sign in to comment.