Bug 1660275 - Part 4: Cache enclosing lexical bindings when compiling eval. r=mgaudet
authorTooru Fujisawa <arai_a@mac.com>
Tue, 02 Feb 2021 14:49:27 +0000
changeset 565853 8b70fbd7fa257b60cfec758bdca566e6e5e09cde
parent 565852 d81a49d52423e3a9d1b3e0857f9d7ab1e0e16bad
child 565854 88423b2b1272dbe89a8d9eb6243969fbe54ff45c
push id38168
push userdluca@mozilla.com
push dateWed, 03 Feb 2021 21:45:46 +0000
treeherdermozilla-central@ca7a3f92939d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmgaudet
bugs1660275
milestone87.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 1660275 - Part 4: Cache enclosing lexical bindings when compiling eval. r=mgaudet Differential Revision: https://phabricator.services.mozilla.com/D103578
js/src/frontend/CompilationInfo.h
js/src/frontend/ParseContext.cpp
js/src/frontend/Stencil.cpp
--- a/js/src/frontend/CompilationInfo.h
+++ b/js/src/frontend/CompilationInfo.h
@@ -5,25 +5,28 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef frontend_CompilationInfo_h
 #define frontend_CompilationInfo_h
 
 #include "mozilla/AlreadyAddRefed.h"  // already_AddRefed
 #include "mozilla/Assertions.h"       // MOZ_ASSERT
 #include "mozilla/Attributes.h"
-#include "mozilla/RefPtr.h"  // RefPtr
+#include "mozilla/HashTable.h"  // mozilla::HashMap
+#include "mozilla/Maybe.h"      // mozilla::Maybe
+#include "mozilla/RefPtr.h"     // RefPtr
 #include "mozilla/Span.h"
 
 #include "builtin/ModuleObject.h"
 #include "ds/LifoAlloc.h"
-#include "frontend/ParserAtom.h"
+#include "frontend/ParserAtom.h"  // ParserAtom, ParserAtomsTable, TaggedParserAtomIndex
 #include "frontend/ScriptIndex.h"  // ScriptIndex
 #include "frontend/SharedContext.h"
 #include "frontend/Stencil.h"
+#include "frontend/TaggedParserAtomIndexHasher.h"  // TaggedParserAtomIndexHasher
 #include "frontend/UsedNameTracker.h"
 #include "js/GCVector.h"
 #include "js/HashTable.h"
 #include "js/RealmOptions.h"
 #include "js/SourceText.h"
 #include "js/Transcoding.h"
 #include "js/Vector.h"
 #include "js/WasmModule.h"
@@ -56,16 +59,30 @@ struct ScopeContext {
   //       private fields, while other contextual information only uses the
   //       actual scope passed to the compile.
   JS::Rooted<Scope*> effectiveScope;
 
   // 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<MemberInitializers> memberInitializers = {};
 
+  enum class EnclosingLexicalBindingKind {
+    Let,
+    Const,
+    CatchParameter,
+  };
+
+  using EnclosingLexicalBindingCache =
+      mozilla::HashMap<TaggedParserAtomIndex, EnclosingLexicalBindingKind,
+                       TaggedParserAtomIndexHasher>;
+
+  // Cache of enclosing lexical bindings.
+  // Used only for eval.
+  mozilla::Maybe<EnclosingLexicalBindingCache> enclosingLexicalBindingCache_;
+
   // Eval and arrow scripts also inherit the "this" environment -- used by
   // `super` expressions -- from their enclosing script. We count the number of
   // environment hops needed to get from enclosing scope to the nearest
   // appropriate environment. This value is undefined if the script we are
   // compiling is not an eval or arrow-function.
   uint32_t enclosingThisEnvironmentHops = 0;
 
   // The kind of enclosing scope.
@@ -96,26 +113,38 @@ struct ScopeContext {
 
 #ifdef DEBUG
   // True if the enclosing scope has non-syntactic scope on chain.
   bool hasNonSyntacticScopeOnChain = false;
 #endif
 
   explicit ScopeContext(JSContext* cx) : effectiveScope(cx) {}
 
-  bool init(JSContext* cx, CompilationInput& input, InheritThis inheritThis,
+  bool init(JSContext* cx, CompilationInput& input,
+            ParserAtomsTable& parserAtoms, InheritThis inheritThis,
             JSObject* enclosingEnv);
 
+  mozilla::Maybe<EnclosingLexicalBindingKind>
+  lookupLexicalBindingInEnclosingScope(TaggedParserAtomIndex name);
+
  private:
   void computeThisBinding(Scope* scope);
   void computeThisEnvironment(Scope* enclosingScope);
   void computeInScope(Scope* enclosingScope);
   void cacheEnclosingScope(Scope* enclosingScope);
 
   static Scope* determineEffectiveScope(Scope* scope, JSObject* environment);
+
+  bool cacheEnclosingScopeBindingForEval(JSContext* cx, CompilationInput& input,
+                                         ParserAtomsTable& parserAtoms);
+
+  bool addToEnclosingLexicalBindingCache(JSContext* cx, CompilationInput& input,
+                                         ParserAtomsTable& parserAtoms,
+                                         JSAtom* name,
+                                         EnclosingLexicalBindingKind kind);
 };
 
 struct CompilationAtomCache {
  public:
   using AtomCacheVector = JS::GCVector<JSAtom*, 0, js::SystemAllocPolicy>;
 
  private:
   // Atoms lowered into or converted from BaseCompilationStencil.parserAtomData.
@@ -314,17 +343,17 @@ struct MOZ_RAII CompilationState {
   size_t nonLazyFunctionCount = 0;
 
   CompilationState(JSContext* cx, LifoAllocScope& frontendAllocScope,
                    const JS::ReadOnlyCompileOptions& options,
                    CompilationStencil& stencil);
 
   bool init(JSContext* cx, InheritThis inheritThis = InheritThis::No,
             JSObject* enclosingEnv = nullptr) {
-    return scopeContext.init(cx, input, inheritThis, enclosingEnv);
+    return scopeContext.init(cx, input, parserAtoms, inheritThis, enclosingEnv);
   }
 
   bool finish(JSContext* cx, CompilationStencil& stencil);
 
   // Allocate space for `length` gcthings, and return the address of the
   // first element to `cursor` to initialize on the caller.
   bool allocateGCThingsUninitialized(JSContext* cx, ScriptIndex scriptIndex,
                                      size_t length,
--- a/js/src/frontend/ParseContext.cpp
+++ b/js/src/frontend/ParseContext.cpp
@@ -1,18 +1,19 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * vim: set ts=8 sts=2 et sw=2 tw=80:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "frontend/ParseContext-inl.h"
 
-#include "frontend/Parser.h"          // ParserBase
-#include "js/friend/ErrorMessages.h"  // JSMSG_*
+#include "frontend/CompilationInfo.h"  // ScopeContext
+#include "frontend/Parser.h"           // ParserBase
+#include "js/friend/ErrorMessages.h"   // JSMSG_*
 #include "vm/EnvironmentObject-inl.h"
 
 using mozilla::Maybe;
 using mozilla::Nothing;
 using mozilla::Some;
 
 namespace js {
 namespace frontend {
@@ -419,72 +420,34 @@ bool ParseContext::isVarRedeclaredInInne
   return tryDeclareVarHelper<DryRunInnermostScopeOnly>(
       name, parser, kind, DeclaredNameInfo::npos, out, &unused);
 }
 
 bool ParseContext::isVarRedeclaredInEval(TaggedParserAtomIndex name,
                                          ParserBase* parser,
                                          DeclarationKind kind,
                                          Maybe<DeclarationKind>* out) {
-  MOZ_ASSERT(out);
-  MOZ_ASSERT(DeclarationKindIsVar(kind));
-  MOZ_ASSERT(sc()->isEvalContext());
-
-  // TODO-Stencil: After scope snapshotting, this can be done away with.
-  const ParserAtom* atom =
-      parser->getCompilationState().parserAtoms.getParserAtom(name);
-  JSAtom* nameAtom =
-      atom->toJSAtom(sc()->cx_, name, sc()->stencil().input.atomCache);
-  if (!nameAtom) {
-    return false;
+  auto maybeKind = parser->getCompilationState()
+                       .scopeContext.lookupLexicalBindingInEnclosingScope(name);
+  if (!maybeKind) {
+    *out = Nothing();
+    return true;
   }
 
-  // In the case of eval, we also need to check enclosing VM scopes to see
-  // if the var declaration is allowed in the context.
-  js::Scope* enclosingScope = sc()->stencil().input.enclosingScope;
-  js::Scope* varScope = EvalScope::nearestVarScopeForDirectEval(enclosingScope);
-  MOZ_ASSERT(varScope);
-  for (ScopeIter si(enclosingScope); si; si++) {
-    for (js::BindingIter bi(si.scope()); bi; bi++) {
-      if (bi.name() != nameAtom) {
-        continue;
-      }
-
-      switch (bi.kind()) {
-        case BindingKind::Let: {
-          // Annex B.3.5 allows redeclaring simple (non-destructured)
-          // catch parameters with var declarations.
-          bool annexB35Allowance = si.kind() == ScopeKind::SimpleCatch;
-          if (!annexB35Allowance) {
-            *out = Some(ScopeKindIsCatch(si.kind())
-                            ? DeclarationKind::CatchParameter
-                            : DeclarationKind::Let);
-            return true;
-          }
-          break;
-        }
-
-        case BindingKind::Const:
-          *out = Some(DeclarationKind::Const);
-          return true;
-
-        case BindingKind::Import:
-        case BindingKind::FormalParameter:
-        case BindingKind::Var:
-        case BindingKind::NamedLambdaCallee:
-          break;
-      }
-    }
-
-    if (si.scope() == varScope) {
+  switch (*maybeKind) {
+    case ScopeContext::EnclosingLexicalBindingKind::Let:
+      *out = Some(DeclarationKind::Let);
       break;
-    }
+    case ScopeContext::EnclosingLexicalBindingKind::Const:
+      *out = Some(DeclarationKind::Const);
+      break;
+    case ScopeContext::EnclosingLexicalBindingKind::CatchParameter:
+      *out = Some(DeclarationKind::CatchParameter);
+      break;
   }
-
-  *out = Nothing();
   return true;
 }
 
 bool ParseContext::tryDeclareVar(TaggedParserAtomIndex name, ParserBase* parser,
                                  DeclarationKind kind, uint32_t beginPos,
                                  Maybe<DeclarationKind>* redeclaredKind,
                                  uint32_t* prevPos) {
   return tryDeclareVarHelper<NotDryRun>(name, parser, kind, beginPos,
--- a/js/src/frontend/Stencil.cpp
+++ b/js/src/frontend/Stencil.cpp
@@ -20,55 +20,63 @@
 #include "gc/AllocKind.h"    // gc::AllocKind
 #include "gc/Rooting.h"      // RootedAtom
 #include "gc/Tracer.h"       // TraceNullableRoot
 #include "js/CallArgs.h"     // JSNative
 #include "js/RootingAPI.h"   // Rooted
 #include "js/Transcoding.h"  // JS::TranscodeBuffer
 #include "js/Value.h"        // ObjectValue
 #include "js/WasmModule.h"   // JS::WasmModule
+#include "vm/BindingKind.h"  // BindingKind
 #include "vm/EnvironmentObject.h"
 #include "vm/GeneratorAndAsyncKind.h"  // GeneratorKind, FunctionAsyncKind
 #include "vm/JSContext.h"              // JSContext
 #include "vm/JSFunction.h"  // JSFunction, GetFunctionPrototype, NewFunctionWithProto
 #include "vm/JSObject.h"      // JSObject
 #include "vm/JSONPrinter.h"   // js::JSONPrinter
 #include "vm/JSScript.h"      // BaseScript, JSScript
 #include "vm/ObjectGroup.h"   // TenuredObject
 #include "vm/Printer.h"       // js::Fprinter
 #include "vm/RegExpObject.h"  // js::RegExpObject
-#include "vm/Scope.h"         // Scope, ScopeKindString, ScopeIter
+#include "vm/Scope.h"  // Scope, *Scope, ScopeKindString, ScopeIter, ScopeKindIsCatch, BindingIter
 #include "vm/ScopeKind.h"     // ScopeKind
 #include "vm/StencilEnums.h"  // ImmutableScriptFlagsEnum
 #include "vm/StringType.h"    // JSAtom, js::CopyChars
 #include "vm/Xdr.h"           // XDRMode, XDRResult, XDREncoder
 #include "wasm/AsmJS.h"       // InstantiateAsmJS
 #include "wasm/WasmModule.h"  // wasm::Module
 
 #include "vm/EnvironmentObject-inl.h"  // JSObject::enclosingEnvironment
 #include "vm/JSFunction-inl.h"         // JSFunction::create
 
 using namespace js;
 using namespace js::frontend;
 
 bool ScopeContext::init(JSContext* cx, CompilationInput& input,
-                        InheritThis inheritThis, JSObject* enclosingEnv) {
+                        ParserAtomsTable& parserAtoms, InheritThis inheritThis,
+                        JSObject* enclosingEnv) {
   Scope* maybeNonDefaultEnclosingScope = input.maybeNonDefaultEnclosingScope();
 
   effectiveScope =
       determineEffectiveScope(maybeNonDefaultEnclosingScope, enclosingEnv);
 
   if (inheritThis == InheritThis::Yes) {
     computeThisBinding(effectiveScope);
     computeThisEnvironment(maybeNonDefaultEnclosingScope);
   }
   computeInScope(maybeNonDefaultEnclosingScope);
 
   cacheEnclosingScope(input.enclosingScope);
 
+  if (input.target == CompilationInput::CompilationTarget::Eval) {
+    if (!cacheEnclosingScopeBindingForEval(cx, input, parserAtoms)) {
+      return false;
+    }
+  }
+
   return true;
 }
 
 void ScopeContext::computeThisEnvironment(Scope* enclosingScope) {
   uint32_t envCount = 0;
   for (ScopeIter si(enclosingScope); si; si++) {
     if (si.kind() == ScopeKind::Function) {
       JSFunction* fun = si.scope()->as<FunctionScope>().canonicalFunction();
@@ -197,16 +205,104 @@ Scope* ScopeContext::determineEffectiveS
 
       env = env->enclosingEnvironment();
     }
   }
 
   return scope;
 }
 
+bool ScopeContext::cacheEnclosingScopeBindingForEval(
+    JSContext* cx, CompilationInput& input, ParserAtomsTable& parserAtoms) {
+  enclosingLexicalBindingCache_.emplace();
+
+  js::Scope* varScope =
+      EvalScope::nearestVarScopeForDirectEval(input.enclosingScope);
+  MOZ_ASSERT(varScope);
+  for (ScopeIter si(input.enclosingScope); si; si++) {
+    for (js::BindingIter bi(si.scope()); bi; bi++) {
+      switch (bi.kind()) {
+        case BindingKind::Let: {
+          // Annex B.3.5 allows redeclaring simple (non-destructured)
+          // catch parameters with var declarations.
+          bool annexB35Allowance = si.kind() == ScopeKind::SimpleCatch;
+          if (!annexB35Allowance) {
+            auto kind = ScopeKindIsCatch(si.kind())
+                            ? EnclosingLexicalBindingKind::CatchParameter
+                            : EnclosingLexicalBindingKind::Let;
+            if (!addToEnclosingLexicalBindingCache(cx, input, parserAtoms,
+                                                   bi.name(), kind)) {
+              return false;
+            }
+          }
+          break;
+        }
+
+        case BindingKind::Const:
+          if (!addToEnclosingLexicalBindingCache(
+                  cx, input, parserAtoms, bi.name(),
+                  EnclosingLexicalBindingKind::Const)) {
+            return false;
+          }
+          break;
+
+        case BindingKind::Import:
+        case BindingKind::FormalParameter:
+        case BindingKind::Var:
+        case BindingKind::NamedLambdaCallee:
+          break;
+      }
+    }
+
+    if (si.scope() == varScope) {
+      break;
+    }
+  }
+
+  return true;
+}
+
+bool ScopeContext::addToEnclosingLexicalBindingCache(
+    JSContext* cx, CompilationInput& input, ParserAtomsTable& parserAtoms,
+    JSAtom* name, EnclosingLexicalBindingKind kind) {
+  auto parserName = parserAtoms.internJSAtom(cx, input.atomCache, name);
+  if (!parserName) {
+    return false;
+  }
+
+  // Same lexical binding can appear multiple times across scopes.
+  //
+  // enclosingLexicalBindingCache_ map is used for detecting conflicting
+  // `var` binding, and inner binding should be reported in the error.
+  //
+  // cacheEnclosingScopeBindingForEval iterates from inner scope, and
+  // inner-most binding is added to the map first.
+  //
+  // Do not overwrite the value with outer bindings.
+  auto p = enclosingLexicalBindingCache_->lookupForAdd(parserName);
+  if (!p) {
+    if (!enclosingLexicalBindingCache_->add(p, parserName, kind)) {
+      ReportOutOfMemory(cx);
+      return false;
+    }
+  }
+
+  return true;
+}
+
+mozilla::Maybe<ScopeContext::EnclosingLexicalBindingKind>
+ScopeContext::lookupLexicalBindingInEnclosingScope(TaggedParserAtomIndex name) {
+  auto p = enclosingLexicalBindingCache_->lookup(name);
+  if (!p) {
+    return mozilla::Nothing();
+  }
+
+  return mozilla::Some(p->value());
+}
+
 bool CompilationInput::initScriptSource(JSContext* cx) {
   ScriptSource* ss = cx->new_<ScriptSource>();
   if (!ss) {
     return false;
   }
   setSource(ss);
 
   return ss->initFromOptions(cx, options);