Skip to content
Permalink
Browse files

[MERGE #6253 @boingoing] Enable defer parse for class members includi…

…ng constructors

Merge pull request #6253 from boingoing:defer_parse_class_members

Enable defer parse for class members including constructors

We have the ability to defer parse any function except for class members (which includes explicit class constructors). Class members are basically parsed in the same way as object literal methods so it isn't very hard to support defer parsing for them since we already support defer parsing for object literal methods.

The main complication here is supporting the unusual text extents for class constructors. The class constructor function itself is what we will eventually bind to the name of the class. However, calling toString on the class name should print the text of the entire class. We also need to know the exact text extents for the constructor method in order to defer parse it. There's already a mechanism we use to adjust the beginning of the text extents for async methods so I've extended this to support adjusting the length of the extents as well. We used to keep track of an extra uint tacked-on to FunctionBody but I moved this into a struct of two uints stored in the AuxPtr array instead. This struct is only allocated for objects with unusual text extents.

Besides the text extents work, the remainder is mostly just bookkeeping various flags to let us know we're parsing a class constructor, derived constructor, class member, etc.
  • Loading branch information...
boingoing committed Sep 5, 2019
2 parents 8fcb0f1 + fda2217 commit b2c0518fc9b2fa29ed39387364549bfff902a00a
@@ -1134,6 +1134,7 @@ ParseNodeProg * Parser::CreateProgNode(bool isModuleSource, ULONG lineNumber)

pnodeProg->cbMin = this->GetScanner()->IecpMinTok();
pnodeProg->cbStringMin = pnodeProg->cbMin;
pnodeProg->cbStringLim = pnodeProg->cbLim;
pnodeProg->lineNumber = lineNumber;
pnodeProg->homeObjLocation = Js::Constants::NoRegister;
pnodeProg->superRestrictionState = SuperRestrictionState::Disallowed;
@@ -3678,6 +3679,7 @@ ParseNodePtr Parser::ParseTerm(BOOL fAllowCall,
if (isAsyncExpr)
{
pnode->AsParseNodeFnc()->cbStringMin = iecpMin;
pnode->AsParseNodeFnc()->cbStringLim = pnode->AsParseNodeFnc()->cbLim;
}
fCanAssign = FALSE;
break;
@@ -4687,6 +4689,7 @@ ParseNodeBin * Parser::ParseMemberGetSet(OpCode nop, LPCOLESTR* ppNameHint, size
/*needsPIDOnRCurlyScan*/ false);

pnodeFnc->cbStringMin = iecpMin;
pnodeFnc->cbStringLim = pnodeFnc->cbLim;

if (isComputedName)
{
@@ -5031,13 +5034,14 @@ ParseNodePtr Parser::ParseMemberList(LPCOLESTR pNameHint, uint32* pNameHintLengt
if (isAsyncMethod || isGenerator)
{
pnodeFnc->cbStringMin = iecpMin;
pnodeFnc->cbStringLim = pnodeFnc->cbLim;
}

if (isComputedName)
{
pnodeFnc->SetHasComputedName();
pnodeFnc->cbStringMin = iecpMin;

pnodeFnc->cbStringLim = pnodeFnc->cbLim;
}
pnodeFnc->SetHasHomeObj();

@@ -5354,6 +5358,7 @@ ParseNodeFnc * Parser::ParseFncDeclInternal(ushort flags, LPCOLESTR pNameHint, c
pnodeFnc->nestedFuncEscapes = false;
pnodeFnc->cbMin = this->GetScanner()->IecpMinTok();
pnodeFnc->cbStringMin = pnodeFnc->cbMin;
pnodeFnc->cbStringLim = pnodeFnc->cbLim;
pnodeFnc->functionId = (*m_nextFunctionId)++;
pnodeFnc->superRestrictionState = superRestrictionState;

@@ -5390,6 +5395,7 @@ ParseNodeFnc * Parser::ParseFncDeclInternal(ushort flags, LPCOLESTR pNameHint, c
pnodeFnc->SetIsClassConstructor((flags & fFncClassConstructor) != 0);
pnodeFnc->SetIsBaseClassConstructor((flags & fFncBaseClassConstructor) != 0);
pnodeFnc->SetHomeObjLocation(Js::Constants::NoRegister);
pnodeFnc->SetHasNonThisStmt(pnodeFnc->IsClassConstructor());

if (this->m_currentScope && this->m_currentScope->GetScopeType() == ScopeType_Parameter)
{
@@ -5677,15 +5683,6 @@ void Parser::ParseFncDeclHelper(ParseNodeFnc * pnodeFnc, LPCOLESTR pNameHint, us

uint uCanDeferSave = m_grfscr & fscrCanDeferFncParse;
uint uDeferSave = m_grfscr & fscrWillDeferFncParse;
if (flags & fFncClassMember)
{
// 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 &= ~(fscrCanDeferFncParse | fscrWillDeferFncParse);
}

bool isTopLevelDeferredFunc = false;

#if ENABLE_BACKGROUND_PARSING
@@ -7176,6 +7173,7 @@ ParseNodeFnc * Parser::GenerateEmptyConstructor(bool extends)
pnodeFnc->cbLim = this->GetScanner()->IecpLimTok();
pnodeFnc->cbMin = this->GetScanner()->IecpMinTok();
pnodeFnc->cbStringMin = pnodeFnc->cbMin;
pnodeFnc->cbStringLim = pnodeFnc->cbLim;
pnodeFnc->lineNumber = this->GetScanner()->LineCur();

pnodeFnc->functionId = (*m_nextFunctionId);
@@ -8143,15 +8141,11 @@ ParseNodeClass * Parser::ParseClassDecl(BOOL isDeclaration, LPCOLESTR pNameHint,
fncDeclFlags |= fFncAsync;
}
pnodeFnc = ParseFncDeclNoCheckScope<buildAST>(fncDeclFlags, SuperRestrictionState::PropertyAllowed, pidHint ? pidHint->Psz() : nullptr, /* needsPIDOnRCurlyScan */ true);
if (isAsyncMethod)
{
pnodeFnc->cbMin = iecpMin;
pnodeFnc->ichMin = ichMin;
}

if (isAsyncMethod || isGenerator || isComputedName)
{
pnodeFnc->cbStringMin = iecpMin;
pnodeFnc->cbStringLim = pnodeFnc->cbLim;
}
}
pnodeFnc->SetIsStaticMember(isStatic);
@@ -8221,11 +8215,8 @@ ParseNodeClass * Parser::ParseClassDecl(BOOL isDeclaration, LPCOLESTR pNameHint,

if (buildAST)
{
pnodeConstructor->cbMin = cbMinConstructor;
pnodeConstructor->cbStringMin = cbMinConstructor;
pnodeConstructor->cbLim = cbLimConstructor;
pnodeConstructor->ichMin = pnodeClass->ichMin;
pnodeConstructor->ichLim = pnodeClass->ichLim;
pnodeConstructor->cbStringLim = cbLimConstructor;

PopFuncBlockScope(ppnodeScopeSave, ppnodeExprScopeSave);

@@ -9275,6 +9266,7 @@ ParseNodePtr Parser::ParseExpr(int oplMin,
if (isAsyncMethod)
{
pnode->AsParseNodeFnc()->cbStringMin = iecpMin;
pnode->AsParseNodeFnc()->cbStringLim = pnode->AsParseNodeFnc()->cbLim;
}

// ArrowFunction/AsyncArrowFunction is part of AssignmentExpression, which should terminate the expression unless followed by a comma
@@ -10204,6 +10196,7 @@ ParseNodePtr Parser::ParseStatement()
if (isAsyncMethod)
{
pnode->AsParseNodeFnc()->cbStringMin = iecpMin;
pnode->AsParseNodeFnc()->cbStringLim = pnode->AsParseNodeFnc()->cbLim;
}
break;
}
@@ -11957,6 +11950,7 @@ ParseNodeProg * Parser::Parse(LPCUTF8 pszSrc, size_t offset, size_t length, char

m_currentNodeFunc->SetIsGenerator(scopeInfo->IsGeneratorFunctionBody());
m_currentNodeFunc->SetIsAsync(scopeInfo->IsAsyncFunctionBody());
m_currentNodeFunc->SetIsClassConstructor(scopeInfo->IsClassConstructor());
}
}

@@ -12021,6 +12015,23 @@ ParseNodeProg * Parser::Parse(LPCUTF8 pszSrc, size_t offset, size_t length, char
flags |= fFncAsync;
}

if (m_grfscr & fscrDeferredFncIsClassConstructor)
{
m_grfscr &= ~fscrDeferredFncIsClassConstructor;
flags |= fFncClassConstructor | fFncClassMember;
}

if (m_grfscr & fscrDeferredFncIsBaseClassConstructor)
{
m_grfscr &= ~fscrDeferredFncIsBaseClassConstructor;
flags |= fFncBaseClassConstructor;
}

if (m_grfscr & fscrDeferredFncIsClassMember)
{
m_grfscr &= ~fscrDeferredFncIsClassMember;
flags |= fFncClassMember;
}

#if DBG
if (isMethod && m_token.tk == tkID)
@@ -14,7 +14,7 @@ enum
fscrWillDeferFncParse = 1 << 3, // Heuristically choosing to defer parsing of functions
fscrCanDeferFncParse = 1 << 4, // Functionally able to defer parsing of functions
fscrDynamicCode = 1 << 5, // The code is being generated dynamically (eval, new Function, etc.)
fscrDeferredFncIsGenerator = 1 << 6,
fscrUseStrictMode = 1 << 6,
fscrNoImplicitHandlers = 1 << 7, // same as Opt NoConnect at start of block
fscrCreateParserState = 1 << 8, // The parser should expose parser state information on the parse nodes.
// This parser state includes the set of names which are captured by each function
@@ -28,29 +28,31 @@ enum
fscrEval = 1 << 10, // this expression has eval semantics (i.e., run in caller's context
fscrEvalCode = 1 << 11, // this is an eval expression
fscrGlobalCode = 1 << 12, // this is a global script
fscrDeferredFncIsAsync = 1 << 13,
fscrDeferredFncExpression = 1 << 14, // the function decl node we deferred is an expression,
// i.e., not a declaration statement
fscrDeferredFnc = 1 << 15, // the function we are parsing is deferred
fscrNoPreJit = 1 << 16, // ignore prejit global flag
fscrAllowFunctionProxy = 1 << 17, // Allow creation of function proxies instead of function bodies
fscrIsLibraryCode = 1 << 18, // Current code is engine library code written in Javascript
fscrNoDeferParse = 1 << 19, // Do not defer parsing
fscrJsBuiltIn = 1 << 20, // Current code is a JS built in code written in JavaScript
fscrIsModuleCode = 1 << 13, // Current code should be parsed as a module body
fscrNoAsmJs = 1 << 14, // Disable generation of asm.js code
fscrNoPreJit = 1 << 15, // ignore prejit global flag
fscrAllowFunctionProxy = 1 << 16, // Allow creation of function proxies instead of function bodies
fscrIsLibraryCode = 1 << 17, // Current code is engine library code written in Javascript
fscrNoDeferParse = 1 << 18, // Do not defer parsing
fscrJsBuiltIn = 1 << 19, // Current code is a JS built in code written in JavaScript
#ifdef IR_VIEWER
fscrIrDumpEnable = 1 << 21, // Allow parseIR to generate an IR dump
fscrIrDumpEnable = 1 << 20, // Allow parseIR to generate an IR dump
#endif /* IRVIEWER */

// Throw a ReferenceError when the global 'this' is used (possibly in a lambda),
// for debugger when broken in a lambda that doesn't capture 'this'
fscrDebuggerErrorOnGlobalThis = 1 << 22,
fscrDeferredClassMemberFnc = 1 << 23,
fscrConsoleScopeEval = 1 << 24, // The eval string is console eval or debugEval, used to have top level
fscrDebuggerErrorOnGlobalThis = 1 << 21,
fscrConsoleScopeEval = 1 << 22, // The eval string is console eval or debugEval, used to have top level
// 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

fscrDeferredFncIsMethod = 1 << 27,
fscrUseStrictMode = 1 << 28,
fscrDeferredFnc = 1 << 23, // the function we are parsing is deferred
fscrDeferredFncExpression = 1 << 24, // the function decl node we deferred is an expression,
// i.e., not a declaration statement
fscrDeferredFncIsAsync = 1 << 25,
fscrDeferredFncIsMethod = 1 << 26,
fscrDeferredFncIsGenerator = 1 << 27,
fscrDeferredFncIsClassMember = 1 << 28,
fscrDeferredFncIsClassConstructor = 1 << 29,
fscrDeferredFncIsBaseClassConstructor = 1 << 30,
fscrAll = (1 << 29) - 1
};
@@ -521,6 +521,7 @@ class ParseNodeFnc : public ParseNode
int32 astSize;
size_t cbMin; // Min an Lim UTF8 offsets.
size_t cbStringMin;
size_t cbStringLim;
size_t cbLim;
ULONG lineNumber; // Line number relative to the current source buffer of the function declaration.
ULONG columnNumber; // Column number of the declaration.
@@ -218,7 +218,12 @@ namespace Js
uint
ParseableFunctionInfo::PrintableStartOffset() const
{
return this->m_cbStartPrintOffset;
PrintOffsets* printOffsets = this->GetPrintOffsets();
if (printOffsets != nullptr)
{
return printOffsets->cbStartPrintOffset;
}
return this->m_cbStartOffset;
}

void ParseableFunctionInfo::RegisterFuncToDiag(ScriptContext * scriptContext, char16 const * pszTitle)
@@ -1526,6 +1531,7 @@ namespace Js
CopyDeferParseField(m_grfscr);
other->SetScopeInfo(this->GetScopeInfo());
other->SetDeferredStubs(this->GetDeferredStubs());
other->SetPrintOffsets(this->GetPrintOffsets());
CopyDeferParseField(m_utf8SourceHasBeenSet);
#if DBG
CopyDeferParseField(deferredParseNextFunctionId);
@@ -1549,7 +1555,6 @@ namespace Js
CopyDeferParseField(m_lineNumber);
CopyDeferParseField(m_columnNumber);
CopyDeferParseField(m_cbStartOffset);
CopyDeferParseField(m_cbStartPrintOffset);
CopyDeferParseField(m_cbLength);

this->CopyNestedArray(other);
@@ -1653,7 +1658,6 @@ namespace Js
m_cbLength(0),
m_cchStartOffset(0),
m_cbStartOffset(0),
m_cbStartPrintOffset(0),
m_lineNumber(0),
m_columnNumber(0),
m_isEval(false),
@@ -2460,6 +2464,33 @@ namespace Js
grfscr &= ~fscrDeferredFncIsGenerator;
}

if (funcBody->IsClassConstructor())
{
grfscr |= fscrDeferredFncIsClassConstructor;
}
else
{
grfscr &= ~fscrDeferredFncIsClassConstructor;
}

if (funcBody->IsBaseClassConstructor())
{
grfscr |= fscrDeferredFncIsBaseClassConstructor;
}
else
{
grfscr &= ~fscrDeferredFncIsBaseClassConstructor;
}

if (funcBody->IsClassMethod())
{
grfscr |= fscrDeferredFncIsClassMember;
}
else
{
grfscr &= ~fscrDeferredFncIsClassMember;
}

if (isDebugOrAsmJsReparse)
{
// Disable deferred parsing if not DeferNested, or doing a debug/asm.js re-parse
@@ -2894,9 +2925,16 @@ namespace Js
}
Assert(node->cbStringMin <= node->cbMin);
this->m_cbStartOffset = (uint)cbMin;
this->m_cbStartPrintOffset = (uint)node->cbStringMin;
this->m_cbLength = (uint)lengthInBytes;

if (node->cbStringMin != node->cbMin)
{
PrintOffsets* printOffsets = RecyclerNewLeaf(this->m_scriptContext->GetRecycler(), PrintOffsets);
printOffsets->cbStartPrintOffset = (uint)node->cbStringMin;
printOffsets->cbEndPrintOffset = (uint)node->cbStringLim;
this->SetPrintOffsets(printOffsets);
}

Assert(this->m_utf8SourceInfo != nullptr);
this->m_utf8SourceHasBeenSet = true;

@@ -2952,7 +2990,6 @@ namespace Js
this->m_columnNumber = 0;

this->m_cbStartOffset = 0;
this->m_cbStartPrintOffset = 0;
this->m_cbLength = 0;

this->m_utf8SourceHasBeenSet = true;
@@ -4598,7 +4635,6 @@ namespace Js
Output::Print(_u("\n\n Line %3d: "), line + 1);
// Need to match up cchStartOffset to appropriate cbStartOffset given function's cbStartOffset and cchStartOffset
size_t utf8SrcStartIdx = utf8::CharacterIndexToByteIndex(source, sourceInfo->GetCbLength(), cchStartOffset, this->m_cbStartOffset, this->m_cchStartOffset);

size_t utf8SrcEndIdx = StartOffset() + LengthInBytes();
char16* utf16Buf = HeapNewArray(char16, utf8SrcEndIdx - utf8SrcStartIdx + 2);
size_t utf16BufSz = utf8::DecodeUnitsIntoAndNullTerminateNoAdvance(utf16Buf, source + utf8SrcStartIdx, source + utf8SrcEndIdx, utf8::DecodeOptions::doDefault);
@@ -854,6 +854,12 @@ namespace Js
typedef Field(FunctionInfo*)* FunctionInfoArray;
typedef Field(FunctionInfo*)* FunctionInfoPtrPtr;

struct PrintOffsets
{
uint cbStartPrintOffset;
uint cbEndPrintOffset;
};

//
// FunctionProxy represents a user defined function
// This could be either from a source file or the byte code cache
@@ -905,7 +911,7 @@ namespace Js
CodeGenCallApplyTargetRuntimeData = 26,
CallSiteToCallApplyCallSiteArray = 27,
#endif

PrintOffsets = 28,
Max,
Invalid = 0xff
};
@@ -954,6 +960,7 @@ namespace Js
AuxPointerTypeEntry(AuxPointerType::CodeGenCallApplyTargetRuntimeData, Field(FunctionCodeGenRuntimeData*)*);
AuxPointerTypeEntry(AuxPointerType::CallSiteToCallApplyCallSiteArray, ProfileId*);
#endif
AuxPointerTypeEntry(AuxPointerType::PrintOffsets, PrintOffsets*);
#undef AuxPointerTypeEntry

typedef AuxPtrs<FunctionProxy, AuxPointerType> AuxPtrsT;
@@ -1772,6 +1779,8 @@ namespace Js
void BuildDeferredStubs(ParseNodeFnc* pnodeFnc);
DeferredFunctionStub *GetDeferredStubs() const { return this->GetAuxPtr<AuxPointerType::DeferredStubs>(); }
void SetDeferredStubs(DeferredFunctionStub *stub) { this->SetAuxPtr<AuxPointerType::DeferredStubs>(stub); }
PrintOffsets* GetPrintOffsets() const { return this->GetAuxPtr<AuxPointerType::PrintOffsets>(); }
void SetPrintOffsets(PrintOffsets* offsets) { this->SetAuxPtr<AuxPointerType::PrintOffsets>(offsets); }
void RegisterFuncToDiag(ScriptContext * scriptContext, char16 const * pszTitle);
bool IsES6ModuleCode() const;
private:
@@ -1842,7 +1851,6 @@ namespace Js

FieldWithBarrier(uint) m_cbStartOffset; // pUtf8Source is this many bytes from the start of the scriptContext's source buffer.
// This is generally the same as m_cchStartOffset unless the buffer has a BOM or other non-ascii characters
FieldWithBarrier(uint) m_cbStartPrintOffset; // pUtf8Source is this many bytes from the start of the toString-relevant part of the scriptContext's source buffer.

FieldWithBarrier(ULONG) m_lineNumber;
FieldWithBarrier(ULONG) m_columnNumber;

0 comments on commit b2c0518

Please sign in to comment.
You can’t perform that action at this time.