Bug 1284719 part 1 - Emit hoisted top-level functions directly in the prologue, remove switchToPrologue. r=arai
authorJan de Mooij <jdemooij@mozilla.com>
Fri, 18 Jan 2019 13:14:58 +0000
changeset 514422 1d260750a87e00de63750365d7c0c6fb34241c58
parent 514421 db7342a96476113aa1428e345e704d64c02ab893
child 514423 83d6478d261167ec129ffc5286a60bfbb39d0c31
push id1953
push userffxbld-merge
push dateMon, 11 Mar 2019 12:10:20 +0000
treeherdermozilla-release@9c35dcbaa899 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersarai
bugs1284719
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1284719 part 1 - Emit hoisted top-level functions directly in the prologue, remove switchToPrologue. r=arai Differential Revision: https://phabricator.services.mozilla.com/D16947
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/ParseNode.h
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -93,17 +93,17 @@ BytecodeEmitter::BytecodeEmitter(Bytecod
                                  uint32_t lineNum, EmitterMode emitterMode)
     : sc(sc),
       cx(sc->context),
       parent(parent),
       script(cx, script),
       lazyScript(cx, lazyScript),
       prologue(cx, lineNum),
       main(cx, lineNum),
-      current(&main),
+      current(&prologue),
       parser(nullptr),
       atomIndices(cx->frontendCollectionPool()),
       firstLine(lineNum),
       maxFixedSlots(0),
       maxStackDepth(0),
       stackDepth(0),
       emitLevel(0),
       bodyScopeIndex(UINT32_MAX),
@@ -437,16 +437,21 @@ bool BytecodeEmitter::emitCheckIsCallabl
 }
 
 static inline unsigned LengthOfSetLine(unsigned line) {
   return 1 /* SRC_SETLINE */ + (line > SN_4BYTE_OFFSET_MASK ? 4 : 1);
 }
 
 /* Updates line number notes, not column notes. */
 bool BytecodeEmitter::updateLineNumberNotes(uint32_t offset) {
+  // Don't emit line/column number notes in the prologue.
+  if (inPrologue()) {
+    return true;
+  }
+
   ErrorReporter* er = &parser->errorReporter();
   bool onThisLine;
   if (!er->isOnThisLine(offset, currentLine(), &onThisLine)) {
     er->errorNoOffset(JSMSG_OUT_OF_MEMORY);
     return false;
   }
 
   if (!onThisLine) {
@@ -482,16 +487,21 @@ bool BytecodeEmitter::updateLineNumberNo
 }
 
 /* Updates the line number and column number information in the source notes. */
 bool BytecodeEmitter::updateSourceCoordNotes(uint32_t offset) {
   if (!updateLineNumberNotes(offset)) {
     return false;
   }
 
+  // Don't emit line/column number notes in the prologue.
+  if (inPrologue()) {
+    return true;
+  }
+
   uint32_t columnIndex = parser->errorReporter().columnAt(offset);
   ptrdiff_t colspan = ptrdiff_t(columnIndex) - ptrdiff_t(current->lastColumn);
   if (colspan != 0) {
     // If the column span is so large that we can't store it, then just
     // discard this information. This can happen with minimized or otherwise
     // machine-generated code. Even gigantic column numbers are still
     // valuable if you have a source map to relate them to something real;
     // but it's better to fail soft here.
@@ -2260,69 +2270,97 @@ bool BytecodeEmitter::emitSetThis(Binary
   if (!noe.emitAssignment()) {
     //              [stack] NEWTHIS
     return false;
   }
 
   return true;
 }
 
+bool BytecodeEmitter::defineHoistedTopLevelFunctions(ParseNode* body) {
+  MOZ_ASSERT(inPrologue());
+  MOZ_ASSERT(sc->isGlobalContext() || (sc->isEvalContext() && !sc->strict()));
+  MOZ_ASSERT(body->is<LexicalScopeNode>() || body->is<ListNode>());
+
+  if (body->is<LexicalScopeNode>()) {
+    body = body->as<LexicalScopeNode>().scopeBody();
+    MOZ_ASSERT(body->is<ListNode>());
+  }
+
+  if (!body->as<ListNode>().hasTopLevelFunctionDeclarations()) {
+    return true;
+  }
+
+  return emitHoistedFunctionsInList(&body->as<ListNode>());
+}
+
 bool BytecodeEmitter::emitScript(ParseNode* body) {
   AutoFrontendTraceLog traceLog(cx, TraceLogger_BytecodeEmission,
                                 parser->errorReporter(), body);
 
   setScriptStartOffsetIfUnset(body->pn_pos);
 
+  MOZ_ASSERT(inPrologue());
+
   TDZCheckCache tdzCache(this);
   EmitterScope emitterScope(this);
   if (sc->isGlobalContext()) {
-    switchToPrologue();
     if (!emitterScope.enterGlobal(this, sc->asGlobalContext())) {
       return false;
     }
-    switchToMain();
   } else if (sc->isEvalContext()) {
-    switchToPrologue();
     if (!emitterScope.enterEval(this, sc->asEvalContext())) {
       return false;
     }
-    switchToMain();
   } else {
     MOZ_ASSERT(sc->isModuleContext());
     if (!emitterScope.enterModule(this, sc->asModuleContext())) {
       return false;
     }
   }
 
   setFunctionBodyEndPos(body->pn_pos);
 
-  if (sc->isEvalContext() && !sc->strict() && body->is<LexicalScopeNode>() &&
+  bool isSloppyEval = sc->isEvalContext() && !sc->strict();
+  if (isSloppyEval && body->is<LexicalScopeNode>() &&
       !body->as<LexicalScopeNode>().isEmptyScope()) {
     // Sloppy eval scripts may need to emit DEFFUNs in the prologue. If there is
     // an immediately enclosed lexical scope, we need to enter the lexical
     // scope in the prologue for the DEFFUNs to pick up the right
     // environment chain.
     EmitterScope lexicalEmitterScope(this);
     LexicalScopeNode* scope = &body->as<LexicalScopeNode>();
 
-    switchToPrologue();
     if (!lexicalEmitterScope.enterLexical(this, ScopeKind::Lexical,
                                           scope->scopeBindings())) {
       return false;
     }
+
+    if (!defineHoistedTopLevelFunctions(scope->scopeBody())) {
+      return false;
+    }
+
     switchToMain();
 
     if (!emitLexicalScopeBody(scope->scopeBody())) {
       return false;
     }
 
     if (!lexicalEmitterScope.leave(this)) {
       return false;
     }
   } else {
+    if (sc->isGlobalContext() || isSloppyEval) {
+      if (!defineHoistedTopLevelFunctions(body)) {
+        return false;
+      }
+    }
+
+    switchToMain();
+
     if (!emitTree(body)) {
       return false;
     }
   }
 
   if (!updateSourceCoordNotes(body->pn_pos.end)) {
     return false;
   }
@@ -2345,16 +2383,17 @@ bool BytecodeEmitter::emitScript(ParseNo
 
   tellDebuggerAboutCompiledScript(cx);
 
   return true;
 }
 
 bool BytecodeEmitter::emitFunctionScript(CodeNode* funNode,
                                          TopLevelFunction isTopLevel) {
+  MOZ_ASSERT(inPrologue());
   MOZ_ASSERT(funNode->isKind(ParseNodeKind::Function));
   ParseNode* body = funNode->body();
   MOZ_ASSERT(body->isKind(ParseNodeKind::ParamsBody));
   FunctionBox* funbox = sc->asFunctionBox();
   AutoFrontendTraceLog traceLog(cx, TraceLogger_BytecodeEmission,
                                 parser->errorReporter(), funbox);
 
   setScriptStartOffsetIfUnset(body->pn_pos);
@@ -2378,21 +2417,19 @@ bool BytecodeEmitter::emitFunctionScript
    *
    * Also mark the script so that initializers created within it may be
    * given more precise types.
    */
   if (isRunOnceLambda()) {
     script->setTreatAsRunOnce();
     MOZ_ASSERT(!script->hasRunOnce());
 
-    switchToPrologue();
     if (!emit1(JSOP_RUNONCE)) {
       return false;
     }
-    switchToMain();
   }
 
   setFunctionBodyEndPos(body->pn_pos);
   if (!emitTree(body)) {
     return false;
   }
 
   if (!updateSourceCoordNotes(body->pn_pos.end)) {
@@ -4641,16 +4678,23 @@ if_again:
   }
 
   return true;
 }
 
 bool BytecodeEmitter::emitHoistedFunctionsInList(ListNode* stmtList) {
   MOZ_ASSERT(stmtList->hasTopLevelFunctionDeclarations());
 
+  // We can call this multiple times for sloppy eval scopes.
+  if (stmtList->emittedTopLevelFunctionDeclarations()) {
+    return true;
+  }
+
+  stmtList->setEmittedTopLevelFunctionDeclarations();
+
   for (ParseNode* stmt : stmtList->contents()) {
     ParseNode* maybeFun = stmt;
 
     if (!sc->strict()) {
       while (maybeFun->isKind(ParseNodeKind::LabelStmt)) {
         maybeFun = maybeFun->as<LabeledStatement>().statement();
       }
     }
@@ -5638,31 +5682,30 @@ MOZ_NEVER_INLINE bool BytecodeEmitter::e
 
       RootedModuleObject module(cx, sc->asModuleContext()->module());
       if (!module->noteFunctionDeclaration(cx, name, fun)) {
         return false;
       }
     } else {
       MOZ_ASSERT(sc->isGlobalContext() || sc->isEvalContext());
       MOZ_ASSERT(funNode->getOp() == JSOP_NOP);
-      switchToPrologue();
+      MOZ_ASSERT(inPrologue());
       if (funbox->isAsync()) {
         if (!emitAsyncWrapper(index, fun->isMethod(), fun->isArrow(),
                               fun->isGenerator())) {
           return false;
         }
       } else {
         if (!emitIndex32(JSOP_LAMBDA, index)) {
           return false;
         }
       }
       if (!emit1(JSOP_DEFFUN)) {
         return false;
       }
-      switchToMain();
     }
   } else {
     // For functions nested within functions and blocks, make a lambda and
     // initialize the binding name of the function in the current scope.
 
     NameOpEmitter noe(this, name, NameOpEmitter::Kind::Initialize);
     if (!noe.prepareForRhs()) {
       return false;
@@ -8048,23 +8091,26 @@ bool BytecodeEmitter::emitTypeof(UnaryNo
   }
 
   return emit1(op);
 }
 
 bool BytecodeEmitter::emitFunctionFormalParametersAndBody(
     ListNode* paramsBody) {
   MOZ_ASSERT(paramsBody->isKind(ParseNodeKind::ParamsBody));
+  MOZ_ASSERT(inPrologue());
 
   ParseNode* funBody = paramsBody->last();
   FunctionBox* funbox = sc->asFunctionBox();
 
   TDZCheckCache tdzCache(this);
 
   if (funbox->hasParameterExprs) {
+    switchToMain();
+
     EmitterScope funEmitterScope(this);
     if (!funEmitterScope.enterFunction(this, funbox)) {
       return false;
     }
 
     if (!emitInitializeFunctionSpecialNames()) {
       return false;
     }
@@ -8143,17 +8189,16 @@ bool BytecodeEmitter::emitFunctionFormal
   //
   // One caveat is that Debugger considers ops in the prologue to be
   // unreachable (i.e. cannot set a breakpoint on it). If there are no
   // parameter exprs, any unobservable environment ops (like pushing the
   // call object, setting '.this', etc) need to go in the prologue, else it
   // messes up breakpoint tests.
   EmitterScope emitterScope(this);
 
-  switchToPrologue();
   if (!emitterScope.enterFunction(this, funbox)) {
     return false;
   }
 
   if (!emitInitializeFunctionSpecialNames()) {
     return false;
   }
   switchToMain();
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -395,18 +395,20 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
     return current->code.begin() + offset;
   }
   ptrdiff_t offset() const {
     return current->code.end() - current->code.begin();
   }
   ptrdiff_t prologueOffset() const {
     return prologue.code.end() - prologue.code.begin();
   }
-  void switchToMain() { current = &main; }
-  void switchToPrologue() { current = &prologue; }
+  void switchToMain() {
+    MOZ_ASSERT(inPrologue());
+    current = &main;
+  }
   bool inPrologue() const { return current == &prologue; }
 
   SrcNotesVector& notes() const {
     // Prologue shouldn't have source notes.
     MOZ_ASSERT(!inPrologue());
     return current->notes;
   }
   ptrdiff_t lastNoteOffset() const { return current->lastNoteOffset; }
@@ -769,16 +771,18 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
     return emitIteratorCloseInScope(*innermostEmitterScope(), iterKind,
                                     completionKind, allowSelfHosted);
   }
 
   template <typename InnerEmitter>
   MOZ_MUST_USE bool wrapWithDestructuringTryNote(int32_t iterDepth,
                                                  InnerEmitter emitter);
 
+  MOZ_MUST_USE bool defineHoistedTopLevelFunctions(ParseNode* body);
+
   // Check if the value on top of the stack is "undefined". If so, replace
   // that value on the stack with the value defined by |defaultExpr|.
   // |pattern| is a lhs node of the default expression.  If it's an
   // identifier and |defaultExpr| is an anonymous function, |SetFunctionName|
   // is called at compile time.
   MOZ_MUST_USE bool emitDefault(ParseNode* defaultExpr, ParseNode* pattern);
 
   MOZ_MUST_USE bool setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name);
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -1149,16 +1149,19 @@ class ListNode : public ParseNode {
   //   * object/class has __proto__
   //   * object/class has property which is known not to be constant
   //   * object/class shorthand property
   //   * object/class spread property
   //   * object/class has method
   //   * object/class has computed property
   static constexpr uint32_t hasNonConstInitializerBit = 0x04;
 
+  // Flag set by the emitter after emitting top-level function statements.
+  static constexpr uint32_t emittedTopLevelFunctionDeclarationsBit = 0x08;
+
   void checkConsistency() const
 #ifndef DEBUG
   {
   }
 #endif
   ;
 
  public:
@@ -1222,32 +1225,44 @@ class ListNode : public ParseNode {
 
   bool empty() const { return count() == 0; }
 
   MOZ_MUST_USE bool hasTopLevelFunctionDeclarations() const {
     MOZ_ASSERT(isKind(ParseNodeKind::StatementList));
     return pn_u.list.xflags & hasTopLevelFunctionDeclarationsBit;
   }
 
+  MOZ_MUST_USE bool emittedTopLevelFunctionDeclarations() const {
+    MOZ_ASSERT(isKind(ParseNodeKind::StatementList));
+    MOZ_ASSERT(hasTopLevelFunctionDeclarations());
+    return pn_u.list.xflags & emittedTopLevelFunctionDeclarationsBit;
+  }
+
   MOZ_MUST_USE bool hasArrayHoleOrSpread() const {
     MOZ_ASSERT(isKind(ParseNodeKind::ArrayExpr));
     return pn_u.list.xflags & hasArrayHoleOrSpreadBit;
   }
 
   MOZ_MUST_USE bool hasNonConstInitializer() const {
     MOZ_ASSERT(isKind(ParseNodeKind::ArrayExpr) ||
                isKind(ParseNodeKind::ObjectExpr));
     return pn_u.list.xflags & hasNonConstInitializerBit;
   }
 
   void setHasTopLevelFunctionDeclarations() {
     MOZ_ASSERT(isKind(ParseNodeKind::StatementList));
     pn_u.list.xflags |= hasTopLevelFunctionDeclarationsBit;
   }
 
+  void setEmittedTopLevelFunctionDeclarations() {
+    MOZ_ASSERT(isKind(ParseNodeKind::StatementList));
+    MOZ_ASSERT(hasTopLevelFunctionDeclarations());
+    pn_u.list.xflags |= emittedTopLevelFunctionDeclarationsBit;
+  }
+
   void setHasArrayHoleOrSpread() {
     MOZ_ASSERT(isKind(ParseNodeKind::ArrayExpr));
     pn_u.list.xflags |= hasArrayHoleOrSpreadBit;
   }
 
   void setHasNonConstInitializer() {
     MOZ_ASSERT(isKind(ParseNodeKind::ArrayExpr) ||
                isKind(ParseNodeKind::ObjectExpr));