From bee8b159764733cb0b0fd03be029f6bf2b688adb Mon Sep 17 00:00:00 2001 From: Aakash Patel Date: Thu, 30 May 2024 15:38:02 -0700 Subject: [PATCH] Enable lazy parsing for arrow functions Summary: Store the required flags in the parser and lazy parse arrow functions. The flags are stored in the JSParserImpl and restored based on whether we've finished parsing an arrow or non-arrow function. Reviewed By: tmikov Differential Revision: D55992926 fbshipit-source-id: 174c60ee75ca905ad6b29bfe50a4c76e262e7770 --- lib/Parser/JSParserImpl-flow.cpp | 6 +-- lib/Parser/JSParserImpl.cpp | 85 ++++++++++++++++++++++++++------ lib/Parser/JSParserImpl.h | 76 +++++++++++++++++++++++++--- lib/Sema/SemanticResolver.cpp | 4 ++ test/hermes/lazy-arrow.js | 42 ++++++++++++++++ 5 files changed, 189 insertions(+), 24 deletions(-) create mode 100644 test/hermes/lazy-arrow.js diff --git a/lib/Parser/JSParserImpl-flow.cpp b/lib/Parser/JSParserImpl-flow.cpp index 43993bcc25c..19ac83ea4b1 100644 --- a/lib/Parser/JSParserImpl-flow.cpp +++ b/lib/Parser/JSParserImpl-flow.cpp @@ -244,7 +244,7 @@ Optional JSParserImpl::parseComponentDeclarationFlow( return None; } - SaveStrictModeAndSeenDirectives saveStrictModeAndSeenDirectives{this}; + SaveFunctionState saveFunctionState{this}; auto parsedBody = parseFunctionBody( Param{}, false, false, false, JSLexer::AllowRegExp, true); @@ -765,7 +765,7 @@ Optional JSParserImpl::parseHookDeclarationFlow(SMLoc start) { return None; } - SaveStrictModeAndSeenDirectives saveStrictModeAndSeenDirectives{this}; + SaveFunctionState saveFunctionState{this}; auto parsedBody = parseFunctionBody( Param{}, false, false, false, JSLexer::AllowRegExp, true); @@ -1186,7 +1186,7 @@ Optional JSParserImpl::parseDeclareClassFlow(SMLoc start) { advance(JSLexer::GrammarContext::Type); // NOTE: Class definition is always strict mode code. - SaveStrictModeAndSeenDirectives saveStrictMode{this}; + SaveFunctionState saveStrictMode{this}; setStrictMode(true); if (!need( diff --git a/lib/Parser/JSParserImpl.cpp b/lib/Parser/JSParserImpl.cpp index 60e9f917495..b0b5c590bed 100644 --- a/lib/Parser/JSParserImpl.cpp +++ b/lib/Parser/JSParserImpl.cpp @@ -79,6 +79,7 @@ void JSParserImpl::initializeIdentifiers() { valueIdent_ = lexer_.getIdentifier("value"); typeIdent_ = lexer_.getIdentifier("type"); asyncIdent_ = lexer_.getIdentifier("async"); + argumentsIdent_ = lexer_.getIdentifier("arguments"); awaitIdent_ = lexer_.getIdentifier("await"); assertIdent_ = lexer_.getIdentifier("assert"); @@ -342,7 +343,7 @@ bool JSParserImpl::recursionDepthExceeded() { Optional JSParserImpl::parseProgram() { SMLoc startLoc = tok_->getStartLoc(); - SaveStrictModeAndSeenDirectives saveStrictModeAndSeenDirectives{this}; + SaveFunctionState saveFunctionState{this}; ESTree::NodeList stmtList; if (!parseStatementList( @@ -495,7 +496,7 @@ Optional JSParserImpl::parseFunctionHelper( return None; } - SaveStrictModeAndSeenDirectives saveStrictModeAndSeenDirectives{this}; + SaveFunctionState saveFunctionState{this}; // Grammar context to be used when lexing the closing brace. auto grammarContext = @@ -757,6 +758,9 @@ Optional JSParserImpl::parseFunctionBody( body->paramYield = paramYield; body->paramAwait = paramAwait; body->bufferId = lexer_.getBufferId(); + body->containsArrowFunctions = functionInfo.containsArrowFunctions; + body->mayContainArrowFunctionsUsingArguments = + functionInfo.mayContainArrowFunctionsUsingArguments; return setLocation(startLoc, endLoc, body); } } @@ -767,7 +771,11 @@ Optional JSParserImpl::parseFunctionBody( if (pass_ == PreParse) { preParsed_->functionInfo[(*body)->getStartLoc()] = PreParsedFunctionInfo{ - (*body)->getEndLoc(), isStrictMode(), copySeenDirectives()}; + (*body)->getEndLoc(), + isStrictMode(), + copySeenDirectives(), + containsArrowFunctions_, + mayContainArrowFunctionsUsingArguments_}; } return body; @@ -1162,7 +1170,10 @@ JSParserImpl::parseVariableDeclaration(Param param, SMLoc declLoc) { auto debugLoc = advance().Start; auto expr = parseAssignmentExpression( - param, AllowTypedArrowFunction::Yes, CoverTypedParameters::No); + param, + /* eagerly */ false, + AllowTypedArrowFunction::Yes, + CoverTypedParameters::No); if (!expr) return None; @@ -2335,6 +2346,11 @@ Optional JSParserImpl::parsePrimaryExpression() { return None; return func.getValue(); } + if (isArrowFunction_ && LLVM_UNLIKELY(check(argumentsIdent_))) { + // Found an identifier 'arguments', so set this flag to conservatively + // assume that the function uses 'arguments'. + mayContainArrowFunctionsUsingArguments_ = true; + } auto *res = setLocation( tok_, tok_, @@ -2643,7 +2659,7 @@ Optional JSParserImpl::parsePropertyAssignment(bool eagerly) { SMLoc startLoc = tok_->getStartLoc(); ESTree::NodePtr key = nullptr; - SaveStrictModeAndSeenDirectives saveStrictModeAndSeenDirectives{this}; + SaveFunctionState saveFunctionState{this}; bool computed = false; bool generator = false; @@ -4298,7 +4314,10 @@ Optional JSParserImpl::parseConditionalExpression( &sm_, Subsystem::Parser}; CHECK_RECURSION; auto optConsequent = parseAssignmentExpression( - ParamIn, AllowTypedArrowFunction::Yes, CoverTypedParameters::No); + ParamIn, + /* eagerly */ false, + AllowTypedArrowFunction::Yes, + CoverTypedParameters::No); if (optConsequent && check(TokenKind::colon)) { consequent = *optConsequent; } else { @@ -4319,7 +4338,10 @@ Optional JSParserImpl::parseConditionalExpression( // Consume the '?' (either for the first time or after savePoint.restore()). advance(); auto optConsequent = parseAssignmentExpression( - ParamIn, AllowTypedArrowFunction::No, CoverTypedParameters::No); + ParamIn, + /* eagerly */ false, + AllowTypedArrowFunction::No, + CoverTypedParameters::No); if (!optConsequent) return None; consequent = *optConsequent; @@ -4334,7 +4356,10 @@ Optional JSParserImpl::parseConditionalExpression( return None; auto optAlternate = parseAssignmentExpression( - param, AllowTypedArrowFunction::Yes, CoverTypedParameters::No); + param, + /* eagerly */ false, + AllowTypedArrowFunction::Yes, + CoverTypedParameters::No); if (!optAlternate) return None; ESTree::Node *alternate = *optAlternate; @@ -4405,6 +4430,7 @@ Optional JSParserImpl::parseYieldExpression( auto optArg = parseAssignmentExpression( param.get(ParamIn), + /* eagerly */ false, AllowTypedArrowFunction::Yes, CoverTypedParameters::No); if (!optArg) @@ -4420,7 +4446,7 @@ Optional JSParserImpl::parseClassDeclaration( Param param) { assert(check(TokenKind::rw_class) && "class must start with 'class'"); // NOTE: Class definition is always strict mode code. - SaveStrictModeAndSeenDirectives saveStrictModeAndSeenDirectives{this}; + SaveFunctionState saveFunctionState{this}; setStrictMode(true); SMLoc startLoc = advance().Start; @@ -4476,7 +4502,7 @@ Optional JSParserImpl::parseClassDeclaration( Optional JSParserImpl::parseClassExpression() { assert(check(TokenKind::rw_class) && "class must start with 'class'"); // NOTE: A class definition is always strict mode code. - SaveStrictModeAndSeenDirectives saveStrictModeAndSeenDirectives{this}; + SaveFunctionState saveFunctionState{this}; setStrictMode(true); SMLoc start = advance().Start; @@ -5296,6 +5322,7 @@ bool JSParserImpl::reparseArrowParameters( Optional JSParserImpl::parseArrowFunctionExpression( Param param, + bool forceEagerly, ESTree::Node *leftExpr, bool hasNewLine, ESTree::Node *typeParams, @@ -5324,7 +5351,8 @@ Optional JSParserImpl::parseArrowFunctionExpression( if (!reparseArrowParameters(leftExpr, hasNewLine, paramList, isAsync)) return None; - SaveStrictModeAndSeenDirectives saveStrictModeAndSeenDirectives{this}; + SaveFunctionState saveFunctionState{this, /* arrow */ true}; + ESTree::Node *body; bool expression; @@ -5333,7 +5361,7 @@ Optional JSParserImpl::parseArrowFunctionExpression( if (check(TokenKind::l_brace)) { auto optBody = parseFunctionBody( Param{}, - true, + forceEagerly, oldParamYield.get(), argsParamAwait.get(), JSLexer::AllowDiv, @@ -5348,6 +5376,7 @@ Optional JSParserImpl::parseArrowFunctionExpression( CHECK_RECURSION; auto optConcise = parseAssignmentExpression( param.get(ParamIn), + true, allowTypedArrowFunction, CoverTypedParameters::No, nullptr); @@ -5367,7 +5396,21 @@ Optional JSParserImpl::parseArrowFunctionExpression( expression, isAsync); - return setLocation(startLoc, getPrevTokenEndLoc(), arrow); + if (pass_ == PreParse) { + auto [it, inserted] = preParsed_->functionInfo.try_emplace( + startLoc, + PreParsedFunctionInfo{ + body->getEndLoc(), + isStrictMode(), + copySeenDirectives(), + containsArrowFunctions_, + mayContainArrowFunctionsUsingArguments_, + }); + (void)it; + assert(inserted); + } + + return setLocation(startLoc, body->getEndLoc(), arrow); } Optional JSParserImpl::reparseAssignmentPattern( @@ -5678,6 +5721,7 @@ Optional JSParserImpl::tryParseTypedAsyncArrowFunction( return parseArrowFunctionExpression( param, + /* eagerly */ false, leftExpr, hasNewLine, typeParams, @@ -5691,6 +5735,7 @@ Optional JSParserImpl::tryParseTypedAsyncArrowFunction( Optional JSParserImpl::parseAssignmentExpression( Param param, + bool forceEagerly, AllowTypedArrowFunction allowTypedArrowFunction, CoverTypedParameters coverTypedParameters, ESTree::Node *typeParams) { @@ -5704,7 +5749,7 @@ Optional JSParserImpl::parseAssignmentExpression( explicit State() {} }; - auto parseHelper = [this]( + auto parseHelper = [this, forceEagerly]( State &state, Param param, AllowTypedArrowFunction allowTypedArrowFunction, @@ -5757,6 +5802,7 @@ Optional JSParserImpl::parseAssignmentExpression( // typed arrow functions and attach the type parameters after the fact. auto optAssign = parseAssignmentExpression( param, + /* eagerly */ false, AllowTypedArrowFunction::No, CoverTypedParameters::No, nullptr); @@ -5773,6 +5819,7 @@ Optional JSParserImpl::parseAssignmentExpression( typeParams = *optTypeParams; optAssign = parseAssignmentExpression( param, + /* eagerly */ false, AllowTypedArrowFunction::Yes, CoverTypedParameters::No, typeParams); @@ -5910,6 +5957,7 @@ Optional JSParserImpl::parseAssignmentExpression( !lexer_.isNewLineBeforeCurrentToken()) { return parseArrowFunctionExpression( param, + forceEagerly, *state.optLeftExpr, state.hasNewLine, typeParams, @@ -6009,7 +6057,11 @@ Optional JSParserImpl::parseExpression( CoverTypedParameters coverTypedParameters) { SMLoc startLoc = tok_->getStartLoc(); auto optExpr = parseAssignmentExpression( - param, AllowTypedArrowFunction::Yes, coverTypedParameters, nullptr); + param, + /* eagerly */ false, + AllowTypedArrowFunction::Yes, + coverTypedParameters, + nullptr); if (!optExpr) return None; @@ -6964,6 +7016,9 @@ Optional JSParserImpl::parseLazyFunction( case ESTree::NodeKind::FunctionDeclaration: return castNode(parseFunctionDeclaration(ParamReturn, true)); + case ESTree::NodeKind::ArrowFunctionExpression: + return castNode(parseAssignmentExpression(ParamIn, true)); + case ESTree::NodeKind::Property: { auto node = parsePropertyAssignment(true); assert(node && "Reparsing of property assignment failed"); diff --git a/lib/Parser/JSParserImpl.h b/lib/Parser/JSParserImpl.h index 415fb1056a5..c418be810ad 100644 --- a/lib/Parser/JSParserImpl.h +++ b/lib/Parser/JSParserImpl.h @@ -216,6 +216,32 @@ class JSParserImpl { /// so we can recover directive nodes back in the lazyParse pass. llvh::SmallVector seenDirectives_{}; + /// Whether the current function is an arrow function. + /// Only set/restored by SaveFunctionState when entering/exiting a new + /// function. + bool isArrowFunction_ = false; + + /// Whether the nearest enclosing non-arrow function contains an arrow + /// function. + /// Used only in PreParse phase to send information to SemanticResolver for + /// from the LazyParse phase. + /// Only updated by SaveFunctionState. + /// * Set to true when entering an arrow function. + /// * Set to false when entering a non-arrow function. + /// * Unchanged when leaving an arrow function. + /// * Restored to previous value when leaving a non-arrow function. + bool containsArrowFunctions_ = false; + + /// Whether the nearest enclosing non-arrow function may contain an arrow + /// function using arguments. + /// Used only in PreParse phase to send information to SemanticResolver + /// for from the LazyParse phase. + /// Only set by SaveFunctionState or when parsing a primary expression as + /// 'arguments' IdentifierNode. + /// * Unchanged when leaving an arrow function. + /// * Restored to previous value when leaving a non-arrow function. + bool mayContainArrowFunctionsUsingArguments_ = false; + #if HERMES_PARSE_JSX /// Incremented when inside a JSX tag and decremented when leaving it. /// Used to know whether to lex JS values or JSX text. @@ -261,6 +287,7 @@ class JSParserImpl { UniqueString *valueIdent_; UniqueString *typeIdent_; UniqueString *asyncIdent_; + UniqueString *argumentsIdent_; UniqueString *awaitIdent_; UniqueString *assertIdent_; @@ -959,8 +986,10 @@ class JSParserImpl { /// \param forceAsync set to true when it is already known that the arrow /// function expression is 'async'. This occurs when there are no parens /// around the argument list. + /// \param forceEagerly force any arrow functions to be eagerly parsed. Optional parseArrowFunctionExpression( Param param, + bool forceEagerly, ESTree::Node *leftExpr, bool hasNewLine, ESTree::Node *typeParams, @@ -1005,8 +1034,10 @@ class JSParserImpl { ESTree::Node *node, bool inDecl); + /// \param forceEagerly force any arrow functions to be eagerly parsed. Optional parseAssignmentExpression( Param param = ParamIn, + bool forceEagerly = false, AllowTypedArrowFunction allowTypedArrowFunction = AllowTypedArrowFunction::Yes, CoverTypedParameters coverTypedParameters = CoverTypedParameters::Yes, @@ -1436,21 +1467,54 @@ class JSParserImpl { } #endif - /// RAII to save and restore the current setting of "strict mode" and - /// "seen directives". - class SaveStrictModeAndSeenDirectives { + /// RAII to save and restore the current setting of fields that are used on a + /// per-function basis. + /// Handles arrow functions differently than non-arrow functions because they + /// shouldn't restore the information about nested arrow functions (that info + /// needs to be sent to the nearest non-arrow function). + /// Sets containsArrowFunctions_ and mayContainArrowFunctionsUsingArguments_ + /// to false if we're entering a non-arrow function. + /// Sets isArrowFunction_ based on the constructor. + class SaveFunctionState { JSParserImpl *const parser_; const bool oldStrictMode_; const unsigned oldSeenDirectiveSize_; + const bool oldIsArrowFunction_; + const bool oldContainsArrowFunctions_; + const bool oldMayContainArrowFunctionsUsingArguments_; public: - explicit SaveStrictModeAndSeenDirectives(JSParserImpl *parser) + /// \param isArrowFunction whether we're about to enter an arrow function. + explicit SaveFunctionState( + JSParserImpl *parser, + bool isArrowFunction = false) : parser_(parser), oldStrictMode_(parser->isStrictMode()), - oldSeenDirectiveSize_(parser->getSeenDirectives().size()) {} - ~SaveStrictModeAndSeenDirectives() { + oldSeenDirectiveSize_(parser->getSeenDirectives().size()), + oldIsArrowFunction_(parser->isArrowFunction_), + oldContainsArrowFunctions_(parser->containsArrowFunctions_), + oldMayContainArrowFunctionsUsingArguments_( + parser->mayContainArrowFunctionsUsingArguments_) { + parser->isArrowFunction_ = isArrowFunction; + if (isArrowFunction) { + parser->containsArrowFunctions_ = true; + } else { + // Set flags to false when entering non-arrow functions. + parser->containsArrowFunctions_ = false; + parser->mayContainArrowFunctionsUsingArguments_ = false; + } + } + ~SaveFunctionState() { parser_->setStrictMode(oldStrictMode_); parser_->getSeenDirectives().resize(oldSeenDirectiveSize_); + if (!parser_->isArrowFunction_) { + // Restore the state when leaving non-arrow functions, + // but this information must be propagated for arrow functions. + parser_->containsArrowFunctions_ = oldContainsArrowFunctions_; + parser_->mayContainArrowFunctionsUsingArguments_ = + oldMayContainArrowFunctionsUsingArguments_; + } + parser_->isArrowFunction_ = oldIsArrowFunction_; } }; diff --git a/lib/Sema/SemanticResolver.cpp b/lib/Sema/SemanticResolver.cpp index 70196933391..0a49f9f6990 100644 --- a/lib/Sema/SemanticResolver.cpp +++ b/lib/Sema/SemanticResolver.cpp @@ -1128,6 +1128,10 @@ void SemanticResolver::visitFunctionLikeInFunctionContext( // But do record the surrounding scope in the FunctionInfo. assert(node->getSemInfo() && "semInfo must be set in first pass"); node->getSemInfo()->bindingTableScope = bindingTable_.getCurrentScope(); + node->getSemInfo()->containsArrowFunctions = + blockBody->containsArrowFunctions; + node->getSemInfo()->containsArrowFunctionsUsingArguments = + blockBody->mayContainArrowFunctionsUsingArguments; return; } diff --git a/test/hermes/lazy-arrow.js b/test/hermes/lazy-arrow.js new file mode 100644 index 00000000000..e3a73be644d --- /dev/null +++ b/test/hermes/lazy-arrow.js @@ -0,0 +1,42 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// RUN: %hermes %s | %FileCheck --match-full-lines %s +// RUN: %hermes -lazy %s | %FileCheck --match-full-lines %s + +print('main'); +// CHECK-LABEL: main + +function simple(x, y) { + ((z) => { + print(this, arguments[0], arguments[1], z); + })(3); +} +simple(1, 2); +// CHECK-NEXT: [object global] 1 2 3 +simple.call(100, 1, 2); +// CHECK-NEXT: 100 1 2 3 + +function noArgs(x, y) { + ((z) => { + print(z); + })(3); +} +noArgs(1, 2); +// CHECK-NEXT: 3 + +function nested(x, y) { + ((z) => { + (() => { + print(this, arguments[0], arguments[1], z); + })(); + })(3); +} +nested(1, 2); +// CHECK-NEXT: [object global] 1 2 3 +nested.call(100, 1, 2); +// CHECK-NEXT: 100 1 2 3