Bug 1636800 - Locate field-initializer information before parse starts. r=mgaudet
authorTed Campbell <tcampbell@mozilla.com>
Fri, 15 May 2020 17:34:32 +0000
changeset 530342 0514a40bd58861ba22ae848918b72d2db1d77a4c
parent 530341 a16d2c3fe7e572eb6bf6c3fa663bd8d9713c2fb7
child 530343 220c7fc79ada4717a2ee26b0dd2604ea7fba7285
push id37420
push usernerli@mozilla.com
push dateFri, 15 May 2020 21:52:36 +0000
treeherdermozilla-central@f340bbb582d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmgaudet
bugs1636800
milestone78.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 1636800 - Locate field-initializer information before parse starts. r=mgaudet The CompilationInfo now shapshots the required information so that the BCE does not need to read scope chains from the VM. This is used by the unusual edge cases such as: ``` class C {} class D extends C { field1 = 1; constructor() { eval("super()"); } } ``` This also cleans up the traversal to stop at any non-arrow non-constructor function on the chain since they would not have been allowed to make a super() call and require field info. Differential Revision: https://phabricator.services.mozilla.com/D75458
js/src/frontend/AbstractScopePtr.cpp
js/src/frontend/AbstractScopePtr.h
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/CompilationInfo.h
js/src/frontend/SharedContext.cpp
js/src/frontend/Stencil.cpp
js/src/frontend/Stencil.h
--- a/js/src/frontend/AbstractScopePtr.cpp
+++ b/js/src/frontend/AbstractScopePtr.cpp
@@ -88,36 +88,16 @@ bool AbstractScopePtr::isArrow() const {
   // !isNullptr()
   MOZ_ASSERT(is<FunctionScope>());
   if (isScopeCreationData()) {
     return scopeCreationData().get().isArrow();
   }
   return scope()->as<FunctionScope>().canonicalFunction()->isArrow();
 }
 
-bool AbstractScopePtr::isClassConstructor() const {
-  MOZ_ASSERT(is<FunctionScope>());
-  if (isScopeCreationData()) {
-    return scopeCreationData().get().isClassConstructor();
-  }
-  return scope()->as<FunctionScope>().canonicalFunction()->isClassConstructor();
-}
-
-const FieldInitializers& AbstractScopePtr::fieldInitializers() const {
-  MOZ_ASSERT(is<FunctionScope>());
-  if (isScopeCreationData()) {
-    return scopeCreationData().get().fieldInitializers();
-  }
-  return scope()
-      ->as<FunctionScope>()
-      .canonicalFunction()
-      ->baseScript()
-      ->getFieldInitializers();
-}
-
 uint32_t AbstractScopePtr::nextFrameSlot() const {
   if (isScopeCreationData()) {
     return scopeCreationData().get().nextFrameSlot();
   }
 
   switch (kind()) {
     case ScopeKind::Function:
       return scope()->as<FunctionScope>().nextFrameSlot();
--- a/js/src/frontend/AbstractScopePtr.h
+++ b/js/src/frontend/AbstractScopePtr.h
@@ -112,18 +112,16 @@ class AbstractScopePtr {
   }
 
   ScopeKind kind() const;
   AbstractScopePtr enclosing() const;
   bool hasEnvironment() const;
   uint32_t nextFrameSlot() const;
   // Valid iff is<FunctionScope>
   bool isArrow() const;
-  bool isClassConstructor() const;
-  const FieldInitializers& fieldInitializers() const;
 
   bool hasOnChain(ScopeKind kind) const {
     for (AbstractScopePtr it = *this; it; it = it.enclosing()) {
       if (it.kind() == kind) {
         return true;
       }
     }
     return false;
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -9027,35 +9027,32 @@ bool BytecodeEmitter::emitCreateFieldIni
 
   return true;
 }
 
 const FieldInitializers& BytecodeEmitter::findFieldInitializersForCall() {
   for (BytecodeEmitter* current = this; current; current = current->parent) {
     if (current->sc->isFunctionBox()) {
       FunctionBox* funbox = current->sc->asFunctionBox();
-      if (funbox->isClassConstructor()) {
-        MOZ_ASSERT(funbox->fieldInitializers->valid);
-        return *funbox->fieldInitializers;
-      }
-    }
-  }
-
-  for (AbstractScopePtrIter si(innermostScope()); si; si++) {
-    if (si.abstractScopePtr().is<FunctionScope>()) {
-      if (si.abstractScopePtr().isClassConstructor()) {
-        const FieldInitializers& fieldInitializers =
-            si.abstractScopePtr().fieldInitializers();
-        MOZ_ASSERT(fieldInitializers.valid);
-        return fieldInitializers;
-      }
-    }
-  }
-
-  MOZ_CRASH("Constructor for field initializers not found.");
+
+      if (funbox->isArrow()) {
+        continue;
+      }
+
+      // If we found a non-arrow / non-constructor we were never allowed to
+      // expect fields in the first place.
+      MOZ_RELEASE_ASSERT(funbox->isClassConstructor());
+
+      MOZ_ASSERT(funbox->fieldInitializers->valid);
+      return *funbox->fieldInitializers;
+    }
+  }
+
+  MOZ_RELEASE_ASSERT(compilationInfo.scopeContext.fieldInitializers);
+  return *compilationInfo.scopeContext.fieldInitializers;
 }
 
 bool BytecodeEmitter::emitInitializeInstanceFields() {
   const FieldInitializers& fieldInitializers = findFieldInitializersForCall();
   size_t numFields = fieldInitializers.numFieldInitializers;
 
   if (numFields == 0) {
     return true;
--- a/js/src/frontend/CompilationInfo.h
+++ b/js/src/frontend/CompilationInfo.h
@@ -40,26 +40,32 @@ struct ScopeContext {
   // The type of binding required for `this` of the top level context, as
   // indicated by the enclosing scopes of this parse.
   ThisBinding thisBinding = ThisBinding::Global;
 
   // Somewhere on the scope chain this parse is embedded within is a 'With'
   // scope.
   bool inWith = false;
 
+  // Class field initializer info if we are nested within a class constructor.
+  // We may be an combination of arrow and eval context within the constructor.
+  mozilla::Maybe<FieldInitializers> fieldInitializers = {};
+
   explicit ScopeContext(Scope* scope, JSObject* enclosingEnv = nullptr) {
     computeAllowSyntax(scope);
     computeThisBinding(scope, enclosingEnv);
     computeInWith(scope);
+    computeExternalInitializers(scope);
   }
 
  private:
   void computeAllowSyntax(Scope* scope);
   void computeThisBinding(Scope* scope, JSObject* environment = nullptr);
   void computeInWith(Scope* scope);
+  void computeExternalInitializers(Scope* scope);
 };
 
 // CompilationInfo owns a number of pieces of information about script
 // compilation as well as controls the lifetime of parse nodes and other data by
 // controling the mark and reset of the LifoAlloc.
 struct MOZ_RAII CompilationInfo : public JS::CustomAutoRooter {
   JSContext* cx;
   const JS::ReadOnlyCompileOptions& options;
--- a/js/src/frontend/SharedContext.cpp
+++ b/js/src/frontend/SharedContext.cpp
@@ -159,16 +159,38 @@ void ScopeContext::computeInWith(Scope* 
   for (ScopeIter si(scope); si; si++) {
     if (si.kind() == ScopeKind::With) {
       inWith = true;
       break;
     }
   }
 }
 
+void ScopeContext::computeExternalInitializers(Scope* scope) {
+  for (ScopeIter si(scope); si; si++) {
+    if (si.scope()->is<FunctionScope>()) {
+      FunctionScope& funcScope = si.scope()->as<FunctionScope>();
+      JSFunction* fun = funcScope.canonicalFunction();
+
+      // Arrows can call `super()` on behalf on parent so keep searching.
+      if (fun->isArrow()) {
+        continue;
+      }
+
+      if (fun->isClassConstructor()) {
+        fieldInitializers =
+            mozilla::Some(fun->baseScript()->getFieldInitializers());
+        MOZ_ASSERT(fieldInitializers->valid);
+      }
+
+      break;
+    }
+  }
+}
+
 EvalSharedContext::EvalSharedContext(JSContext* cx,
                                      CompilationInfo& compilationInfo,
                                      Scope* enclosingScope,
                                      Directives directives, SourceExtent extent)
     : SharedContext(cx, Kind::Eval, compilationInfo, directives, extent),
       enclosingScope_(cx, enclosingScope),
       bindings(cx) {
   // Eval inherits syntax and binding rules from enclosing environment.
--- a/js/src/frontend/Stencil.cpp
+++ b/js/src/frontend/Stencil.cpp
@@ -193,22 +193,16 @@ uint32_t ScopeCreationData::nextFrameSlo
           "With, WasmInstance and WasmFunction Scopes don't get "
           "nextFrameSlot()");
       return 0;
   }
   MOZ_CRASH("Not an enclosing intra-frame scope");
 }
 
 bool ScopeCreationData::isArrow() const { return funbox_->isArrow(); }
-bool ScopeCreationData::isClassConstructor() const {
-  return funbox_->isClassConstructor();
-}
-const FieldInitializers& ScopeCreationData::fieldInitializers() const {
-  return *funbox_->fieldInitializers;
-}
 
 void ScriptStencilBase::trace(JSTracer* trc) {
   for (ScriptThingVariant& thing : gcThings) {
     if (thing.is<ClosedOverBinding>()) {
       JSAtom* atom = thing.as<ClosedOverBinding>();
       TraceRoot(trc, &atom, "closed-over-binding");
       MOZ_ASSERT(atom == thing.as<ClosedOverBinding>(),
                  "Atoms should be unmovable");
--- a/js/src/frontend/Stencil.h
+++ b/js/src/frontend/Stencil.h
@@ -277,18 +277,16 @@ class ScopeCreationData {
   bool hasEnvironment() const {
     // Check if scope kind alone means we have an env shape, and
     // otherwise check if we have one created.
     return Scope::hasEnvironment(kind(), !!environmentShape_);
   }
 
   // Valid for functions;
   bool isArrow() const;
-  bool isClassConstructor() const;
-  const FieldInitializers& fieldInitializers() const;
 
   bool hasScope() const { return scope_ != nullptr; }
 
   Scope* getScope() const {
     MOZ_ASSERT(hasScope());
     return scope_;
   }