Bug 1721413 - Part 1: Collect atomization information for ParserAtom during compilation. r=tcampbell
authorTooru Fujisawa <arai_a@mac.com>
Wed, 22 Sep 2021 04:29:41 +0000 (2021-09-22)
changeset 592899 8bbbcc59e36b5fc47081de3d545fcb66b56479ab
parent 592898 78c378e9d9d2f13a645b1fa1d036d3915f0ca437
child 592900 114dc6e441534b3982494e4fc012318ed5e07c92
push id150167
push userarai_a@mac.com
push dateWed, 22 Sep 2021 04:32:48 +0000 (2021-09-22)
treeherderautoland@ba3814055b37 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstcampbell
bugs1721413
milestone94.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 1721413 - Part 1: Collect atomization information for ParserAtom during compilation. r=tcampbell Collect whether the ParserAtom is used only for string literal or not. BytecodeEmitter::emitStringOp is specialized in later patch. Differential Revision: https://phabricator.services.mozilla.com/D122810
js/src/builtin/ModuleObject.cpp
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/BytecodeSection.cpp
js/src/frontend/BytecodeSection.h
js/src/frontend/Frontend2.cpp
js/src/frontend/NameOpEmitter.cpp
js/src/frontend/ObjLiteral.h
js/src/frontend/Parser.cpp
js/src/frontend/ParserAtom.cpp
js/src/frontend/ParserAtom.h
js/src/frontend/PropOpEmitter.cpp
js/src/frontend/SharedContext.cpp
js/src/frontend/Stencil.cpp
js/src/vm/Scope.cpp
--- a/js/src/builtin/ModuleObject.cpp
+++ b/js/src/builtin/ModuleObject.cpp
@@ -8,16 +8,17 @@
 
 #include "mozilla/DebugOnly.h"
 #include "mozilla/EnumSet.h"
 #include "mozilla/ScopeExit.h"
 
 #include "builtin/Promise.h"
 #include "builtin/SelfHostingDefines.h"
 #include "frontend/ParseNode.h"
+#include "frontend/ParserAtom.h"  // TaggedParserAtomIndex, ParserAtomsTable, ParserAtom
 #include "frontend/SharedContext.h"
 #include "gc/FreeOp.h"
 #include "gc/Policy.h"
 #include "gc/Tracer.h"
 #include "js/friend/ErrorMessages.h"  // JSMSG_*
 #include "js/Modules.h"  // JS::GetModulePrivate, JS::ModuleDynamicImportHook
 #include "js/PropertySpec.h"
 #include "vm/AsyncFunction.h"
@@ -2008,17 +2009,19 @@ bool ModuleBuilder::maybeAppendRequested
     js::ReportOutOfMemory(cx_);
     return false;
   }
 
   return requestedModuleSpecifiers_.put(specifier);
 }
 
 void ModuleBuilder::markUsedByStencil(frontend::TaggedParserAtomIndex name) {
-  eitherParser_.parserAtoms().markUsedByStencil(name);
+  // Imported/exported identifiers must be atomized.
+  eitherParser_.parserAtoms().markUsedByStencil(
+      name, frontend::ParserAtom::Atomize::Yes);
 }
 
 template <typename T>
 ArrayObject* js::CreateArray(JSContext* cx,
                              const JS::Rooted<GCVector<T>>& vector) {
   uint32_t length = vector.length();
   RootedArrayObject array(cx, NewDenseFullyAllocatedArray(cx, length));
   if (!array) {
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -45,17 +45,17 @@
 #include "frontend/ModuleSharedContext.h"  // ModuleSharedContext
 #include "frontend/NameAnalysisTypes.h"    // PrivateNameKind
 #include "frontend/NameFunctions.h"        // NameFunctions
 #include "frontend/NameOpEmitter.h"        // NameOpEmitter
 #include "frontend/ObjectEmitter.h"  // PropertyEmitter, ObjectEmitter, ClassEmitter
 #include "frontend/OptionalEmitter.h"  // OptionalEmitter
 #include "frontend/ParseNode.h"   // ParseNodeKind, ParseNode and subclasses
 #include "frontend/Parser.h"      // Parser
-#include "frontend/ParserAtom.h"  // ParserAtomsTable
+#include "frontend/ParserAtom.h"  // ParserAtomsTable, ParserAtom
 #include "frontend/PrivateOpEmitter.h"  // PrivateOpEmitter
 #include "frontend/PropOpEmitter.h"     // PropOpEmitter
 #include "frontend/SourceNotes.h"       // SrcNote, SrcNoteType, SrcNoteWriter
 #include "frontend/SwitchEmitter.h"     // SwitchEmitter
 #include "frontend/TaggedParserAtomIndexHasher.h"  // TaggedParserAtomIndexHasher
 #include "frontend/TDZCheckCache.h"                // TDZCheckCache
 #include "frontend/TryEmitter.h"                   // TryEmitter
 #include "frontend/WhileEmitter.h"                 // WhileEmitter
@@ -932,25 +932,44 @@ bool BytecodeEmitter::emitAtomOp(JSOp op
   // .generator lookups should be emitted as JSOp::GetAliasedVar instead of
   // JSOp::GetName etc, to bypass |with| objects on the scope chain.
   // It's safe to emit .this lookups though because |with| objects skip
   // those.
   MOZ_ASSERT_IF(op == JSOp::GetName || op == JSOp::GetGName,
                 atom != TaggedParserAtomIndex::WellKnown::dotGenerator());
 
   GCThingIndex index;
-  if (!makeAtomIndex(atom, &index)) {
+  if (!makeAtomIndex(atom, ParserAtom::Atomize::Yes, &index)) {
     return false;
   }
 
   return emitAtomOp(op, index);
 }
 
 bool BytecodeEmitter::emitAtomOp(JSOp op, GCThingIndex atomIndex) {
   MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM);
+#ifdef DEBUG
+  auto atom = perScriptData().gcThingList().getAtom(atomIndex);
+  MOZ_ASSERT(compilationState.parserAtoms.isInstantiatedAsJSAtom(atom));
+#endif
+  return emitGCIndexOp(op, atomIndex);
+}
+
+bool BytecodeEmitter::emitStringOp(JSOp op, TaggedParserAtomIndex atom) {
+  MOZ_ASSERT(atom);
+  GCThingIndex index;
+  if (!makeAtomIndex(atom, ParserAtom::Atomize::No, &index)) {
+    return false;
+  }
+
+  return emitStringOp(op, index);
+}
+
+bool BytecodeEmitter::emitStringOp(JSOp op, GCThingIndex atomIndex) {
+  MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM);
   return emitGCIndexOp(op, atomIndex);
 }
 
 bool BytecodeEmitter::emitInternedScopeOp(GCThingIndex index, JSOp op) {
   MOZ_ASSERT(JOF_OPTYPE(op) == JOF_SCOPE);
   MOZ_ASSERT(index < perScriptData().gcThingList().length());
   return emitGCIndexOp(op, index);
 }
@@ -4141,17 +4160,18 @@ bool BytecodeEmitter::emitTemplateString
     } else {
       pushedString = true;
     }
   }
 
   if (!pushedString) {
     // All strings were empty, this can happen for something like `${""}`.
     // Just push an empty string.
-    if (!emitAtomOp(JSOp::String, TaggedParserAtomIndex::WellKnown::empty())) {
+    if (!emitStringOp(JSOp::String,
+                      TaggedParserAtomIndex::WellKnown::empty())) {
       return false;
     }
   }
 
   return true;
 }
 
 bool BytecodeEmitter::emitDeclarationList(ListNode* declList) {
@@ -7717,17 +7737,19 @@ bool BytecodeEmitter::emitSelfHostedSetC
   if (!checkSelfHostedExpectedTopLevel(callNode, argsList->head())) {
     return false;
   }
 #endif
 
   ParseNode* nameNode = argsList->last();
   MOZ_ASSERT(nameNode->isKind(ParseNodeKind::StringExpr));
   TaggedParserAtomIndex specName = nameNode->as<NameNode>().atom();
-  compilationState.parserAtoms.markUsedByStencil(specName);
+  // Canonical name must be atomized.
+  compilationState.parserAtoms.markUsedByStencil(specName,
+                                                 ParserAtom::Atomize::Yes);
 
   // Store the canonical name for instantiation.
   prevSelfHostedTopLevelFunction->functionStencil().setSelfHostedCanonicalName(
       specName);
 
   return emit1(JSOp::Undefined);
 }
 
@@ -11492,17 +11514,17 @@ bool BytecodeEmitter::emitTree(
     case ParseNodeKind::TemplateStringListExpr:
       if (!emitTemplateString(&pn->as<ListNode>())) {
         return false;
       }
       break;
 
     case ParseNodeKind::TemplateStringExpr:
     case ParseNodeKind::StringExpr:
-      if (!emitAtomOp(JSOp::String, pn->as<NameNode>().atom())) {
+      if (!emitStringOp(JSOp::String, pn->as<NameNode>().atom())) {
         return false;
       }
       break;
 
     case ParseNodeKind::NumberExpr:
       if (!emitNumberOp(pn->as<NumericLiteral>().value())) {
         return false;
       }
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -29,17 +29,17 @@
 #include "frontend/ErrorReporter.h"        // ErrorReporter
 #include "frontend/FullParseHandler.h"     // FullParseHandler
 #include "frontend/IteratorKind.h"         // IteratorKind
 #include "frontend/JumpList.h"             // JumpList, JumpTarget
 #include "frontend/NameAnalysisTypes.h"    // NameLocation
 #include "frontend/NameCollections.h"      // AtomIndexMap
 #include "frontend/ParseNode.h"            // ParseNode and subclasses
 #include "frontend/Parser.h"               // Parser, PropListType
-#include "frontend/ParserAtom.h"           // TaggedParserAtomIndex
+#include "frontend/ParserAtom.h"           // TaggedParserAtomIndex, ParserAtom
 #include "frontend/PrivateOpEmitter.h"     // PrivateOpEmitter
 #include "frontend/ScriptIndex.h"          // ScriptIndex
 #include "frontend/SharedContext.h"        // SharedContext, TopLevelFunction
 #include "frontend/SourceNotes.h"          // SrcNoteType
 #include "frontend/TokenStream.h"          // TokenPos
 #include "frontend/ValueUsage.h"           // ValueUsage
 #include "js/RootingAPI.h"                 // JS::Rooted, JS::Handle
 #include "js/TypeDecls.h"                  // jsbytecode
@@ -390,27 +390,29 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
   }
 
   AbstractScopePtr outermostScope() const {
     return perScriptData().gcThingList().firstScope();
   }
   AbstractScopePtr innermostScope() const;
   ScopeIndex innermostScopeIndex() const;
 
-  [[nodiscard]] MOZ_ALWAYS_INLINE bool makeAtomIndex(TaggedParserAtomIndex atom,
-                                                     GCThingIndex* indexp) {
+  [[nodiscard]] MOZ_ALWAYS_INLINE bool makeAtomIndex(
+      TaggedParserAtomIndex atom, ParserAtom::Atomize atomize,
+      GCThingIndex* indexp) {
     MOZ_ASSERT(perScriptData().atomIndices());
     AtomIndexMap::AddPtr p = perScriptData().atomIndices()->lookupForAdd(atom);
     if (p) {
+      compilationState.parserAtoms.markAtomize(atom, atomize);
       *indexp = GCThingIndex(p->value());
       return true;
     }
 
     GCThingIndex index;
-    if (!perScriptData().gcThingList().append(atom, &index)) {
+    if (!perScriptData().gcThingList().append(atom, atomize, &index)) {
       return false;
     }
 
     // `atomIndices()` uses uint32_t instead of GCThingIndex, because
     // GCThingIndex isn't trivial type.
     if (!perScriptData().atomIndices()->add(p, atom, index.index)) {
       ReportOutOfMemory(cx);
       return false;
@@ -603,16 +605,19 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
   [[nodiscard]] bool emitGoto(NestableControl* target, JumpList* jumplist,
                               GotoKind kind);
 
   [[nodiscard]] bool emitGCIndexOp(JSOp op, GCThingIndex index);
 
   [[nodiscard]] bool emitAtomOp(JSOp op, TaggedParserAtomIndex atom);
   [[nodiscard]] bool emitAtomOp(JSOp op, GCThingIndex atomIndex);
 
+  [[nodiscard]] bool emitStringOp(JSOp op, TaggedParserAtomIndex atom);
+  [[nodiscard]] bool emitStringOp(JSOp op, GCThingIndex atomIndex);
+
   [[nodiscard]] bool emitArrayLiteral(ListNode* array);
   [[nodiscard]] bool emitArray(ParseNode* arrayHead, uint32_t count);
 
   [[nodiscard]] bool emitInternedScopeOp(GCThingIndex index, JSOp op);
   [[nodiscard]] bool emitInternedObjectOp(GCThingIndex index, JSOp op);
   [[nodiscard]] bool emitObjectPairOp(GCThingIndex index1, GCThingIndex index2,
                                       JSOp op);
   [[nodiscard]] bool emitRegExp(GCThingIndex index);
--- a/js/src/frontend/BytecodeSection.cpp
+++ b/js/src/frontend/BytecodeSection.cpp
@@ -44,16 +44,21 @@ AbstractScopePtr GCThingList::getScope(s
 mozilla::Maybe<ScopeIndex> GCThingList::getScopeIndex(size_t index) const {
   const TaggedScriptThingIndex& elem = vector[index];
   if (elem.isEmptyGlobalScope()) {
     return mozilla::Nothing();
   }
   return mozilla::Some(vector[index].toScope());
 }
 
+TaggedParserAtomIndex GCThingList::getAtom(size_t index) const {
+  const TaggedScriptThingIndex& elem = vector[index];
+  return elem.toAtom();
+}
+
 bool js::frontend::EmitScriptThingsVector(
     JSContext* cx, const CompilationAtomCache& atomCache,
     const CompilationStencil& stencil, CompilationGCOutput& gcOutput,
     mozilla::Span<const TaggedScriptThingIndex> things,
     mozilla::Span<JS::GCCellPtr> output) {
   MOZ_ASSERT(things.size() <= INDEX_LIMIT);
   MOZ_ASSERT(things.size() == output.size());
 
--- a/js/src/frontend/BytecodeSection.h
+++ b/js/src/frontend/BytecodeSection.h
@@ -19,17 +19,17 @@
 
 #include "frontend/AbstractScopePtr.h"  // AbstractScopePtr, ScopeIndex
 #include "frontend/BytecodeOffset.h"    // BytecodeOffset
 #include "frontend/CompilationStencil.h"  // CompilationStencil, CompilationGCOutput, CompilationAtomCache
 #include "frontend/JumpList.h"         // JumpTarget
 #include "frontend/NameCollections.h"  // AtomIndexMap, PooledMapPtr
 #include "frontend/ObjLiteral.h"       // ObjLiteralStencil
 #include "frontend/ParseNode.h"        // BigIntLiteral
-#include "frontend/ParserAtom.h"   // ParserAtomsTable, TaggedParserAtomIndex
+#include "frontend/ParserAtom.h"  // ParserAtomsTable, TaggedParserAtomIndex, ParserAtom
 #include "frontend/SourceNotes.h"  // SrcNote
 #include "frontend/Stencil.h"      // Stencils
 #include "gc/Rooting.h"            // JS::Rooted
 #include "js/GCVariant.h"          // GCPolicy<mozilla::Variant>
 #include "js/GCVector.h"           // GCVector
 #include "js/TypeDecls.h"          // jsbytecode, JSContext
 #include "js/Value.h"              // JS::Vector
 #include "js/Vector.h"             // Vector
@@ -54,19 +54,20 @@ struct MOZ_STACK_CLASS GCThingList {
   ScriptThingsStackVector vector;
 
   // Index of the first scope in the vector.
   mozilla::Maybe<GCThingIndex> firstScopeIndex;
 
   explicit GCThingList(JSContext* cx, CompilationState& compilationState)
       : compilationState(compilationState), vector(cx) {}
 
-  [[nodiscard]] bool append(TaggedParserAtomIndex atom, GCThingIndex* index) {
+  [[nodiscard]] bool append(TaggedParserAtomIndex atom,
+                            ParserAtom::Atomize atomize, GCThingIndex* index) {
     *index = GCThingIndex(vector.length());
-    compilationState.parserAtoms.markUsedByStencil(atom);
+    compilationState.parserAtoms.markUsedByStencil(atom, atomize);
     if (!vector.emplaceBack(atom)) {
       return false;
     }
     return true;
   }
   [[nodiscard]] bool append(ScopeIndex scope, GCThingIndex* index) {
     *index = GCThingIndex(vector.length());
     if (!vector.emplaceBack(scope)) {
@@ -117,16 +118,18 @@ struct MOZ_STACK_CLASS GCThingList {
   const ScriptThingsStackVector& objects() { return vector; }
 
   AbstractScopePtr getScope(size_t index) const;
 
   // Index of scope within CompilationStencil or Nothing is the scope is
   // EmptyGlobalScopeType.
   mozilla::Maybe<ScopeIndex> getScopeIndex(size_t index) const;
 
+  TaggedParserAtomIndex getAtom(size_t index) const;
+
   AbstractScopePtr firstScope() const {
     MOZ_ASSERT(firstScopeIndex.isSome());
     return getScope(*firstScopeIndex);
   }
 };
 
 [[nodiscard]] bool EmitScriptThingsVector(
     JSContext* cx, const CompilationAtomCache& atomCache,
--- a/js/src/frontend/Frontend2.cpp
+++ b/js/src/frontend/Frontend2.cpp
@@ -65,17 +65,20 @@ bool ConvertAtoms(JSContext* cx, const S
   for (size_t i = 0; i < numAtoms; i++) {
     auto s = reinterpret_cast<const mozilla::Utf8Unit*>(
         smoosh_get_atom_at(result, i));
     auto len = smoosh_get_atom_len_at(result, i);
     auto atom = compilationState.parserAtoms.internUtf8(cx, s, len);
     if (!atom) {
       return false;
     }
-    compilationState.parserAtoms.markUsedByStencil(atom);
+    // We don't collect atomization information in smoosh yet.
+    // Assume it needs to be atomized.
+    compilationState.parserAtoms.markUsedByStencil(atom,
+                                                   ParserAtom::Atomize::Yes);
     allAtoms.infallibleAppend(atom);
   }
 
   return true;
 }
 
 void CopyBindingNames(JSContext* cx, CVec<SmooshBindingName>& from,
                       Vector<TaggedParserAtomIndex>& allAtoms,
@@ -323,17 +326,19 @@ bool ConvertRegExpData(JSContext* cx, co
 
     const mozilla::Utf8Unit* sUtf8 =
         reinterpret_cast<const mozilla::Utf8Unit*>(s);
     auto atom = compilationState.parserAtoms.internUtf8(cx, sUtf8, len);
     if (!atom) {
       return false;
     }
 
-    compilationState.parserAtoms.markUsedByStencil(atom);
+    // RegExp patterm must be atomized.
+    compilationState.parserAtoms.markUsedByStencil(atom,
+                                                   ParserAtom::Atomize::Yes);
     compilationState.regExpData.infallibleEmplaceBack(atom,
                                                       JS::RegExpFlags(flags));
   }
 
   return true;
 }
 
 // Convert SmooshImmutableScriptData into ImmutableScriptData.
--- a/js/src/frontend/NameOpEmitter.cpp
+++ b/js/src/frontend/NameOpEmitter.cpp
@@ -3,16 +3,17 @@
  * 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/NameOpEmitter.h"
 
 #include "frontend/AbstractScopePtr.h"
 #include "frontend/BytecodeEmitter.h"
+#include "frontend/ParserAtom.h"  // ParserAtom
 #include "frontend/SharedContext.h"
 #include "frontend/TDZCheckCache.h"
 #include "vm/Opcodes.h"
 #include "vm/Scope.h"
 #include "vm/StringType.h"
 
 using namespace js;
 using namespace js::frontend;
@@ -148,17 +149,17 @@ bool NameOpEmitter::emitGet() {
 
 bool NameOpEmitter::prepareForRhs() {
   MOZ_ASSERT(state_ == State::Start);
 
   switch (loc_.kind()) {
     case NameLocation::Kind::Dynamic:
     case NameLocation::Kind::Import:
     case NameLocation::Kind::DynamicAnnexBVar:
-      if (!bce_->makeAtomIndex(name_, &atomIndex_)) {
+      if (!bce_->makeAtomIndex(name_, ParserAtom::Atomize::Yes, &atomIndex_)) {
         return false;
       }
       if (loc_.kind() == NameLocation::Kind::DynamicAnnexBVar) {
         // Annex B vars always go on the nearest variable environment,
         // even if lexical environments in between contain same-named
         // bindings.
         if (!bce_->emit1(JSOp::BindVar)) {
           //        [stack] ENV
@@ -168,17 +169,17 @@ bool NameOpEmitter::prepareForRhs() {
         if (!bce_->emitAtomOp(JSOp::BindName, atomIndex_)) {
           //        [stack] ENV
           return false;
         }
       }
       emittedBindOp_ = true;
       break;
     case NameLocation::Kind::Global:
-      if (!bce_->makeAtomIndex(name_, &atomIndex_)) {
+      if (!bce_->makeAtomIndex(name_, ParserAtom::Atomize::Yes, &atomIndex_)) {
         return false;
       }
       if (loc_.isLexical() && isInitialize()) {
         // InitGLexical always gets the global lexical scope. It doesn't
         // need a BindGName.
         MOZ_ASSERT(bce_->innermostScope().is<GlobalScope>());
       } else {
         if (!bce_->emitAtomOp(JSOp::BindGName, atomIndex_)) {
--- a/js/src/frontend/ObjLiteral.h
+++ b/js/src/frontend/ObjLiteral.h
@@ -7,17 +7,17 @@
 
 #ifndef frontend_ObjLiteral_h
 #define frontend_ObjLiteral_h
 
 #include "mozilla/BloomFilter.h"  // mozilla::BitBloomFilter
 #include "mozilla/EnumSet.h"
 #include "mozilla/Span.h"
 
-#include "frontend/ParserAtom.h"  // ParserAtomsTable, TaggedParserAtomIndex
+#include "frontend/ParserAtom.h"  // ParserAtomsTable, TaggedParserAtomIndex, ParserAtom
 #include "js/AllocPolicy.h"
 #include "js/GCPolicyAPI.h"
 #include "js/Value.h"
 #include "js/Vector.h"
 #include "util/EnumFlags.h"
 #include "vm/BytecodeUtil.h"
 #include "vm/Opcodes.h"
 
@@ -376,17 +376,17 @@ struct ObjLiteralWriter : private ObjLit
     }
     return true;
   }
   void setPropNameNoDuplicateCheck(
       frontend::ParserAtomsTable& parserAtoms,
       const frontend::TaggedParserAtomIndex propName) {
     MOZ_ASSERT(kind_ == ObjLiteralKind::Object ||
                kind_ == ObjLiteralKind::Shape);
-    parserAtoms.markUsedByStencil(propName);
+    parserAtoms.markUsedByStencil(propName, frontend::ParserAtom::Atomize::Yes);
     nextKey_ = ObjLiteralKey::fromPropName(propName);
   }
   void setPropIndex(uint32_t propIndex) {
     MOZ_ASSERT(kind_ == ObjLiteralKind::Object);
     MOZ_ASSERT(propIndex <= ATOM_INDEX_MASK);
     nextKey_ = ObjLiteralKey::fromArrayIndex(propIndex);
     flags_.setFlag(ObjLiteralFlag::HasIndexOrDuplicatePropName);
   }
@@ -405,17 +405,17 @@ struct ObjLiteralWriter : private ObjLit
     return pushOpAndName(cx, ObjLiteralOpcode::ConstValue, nextKey_) &&
            pushValueArg(cx, value);
   }
   [[nodiscard]] bool propWithAtomValue(
       JSContext* cx, frontend::ParserAtomsTable& parserAtoms,
       const frontend::TaggedParserAtomIndex value) {
     MOZ_ASSERT(kind_ != ObjLiteralKind::Shape);
     propertyCount_++;
-    parserAtoms.markUsedByStencil(value);
+    parserAtoms.markUsedByStencil(value, frontend::ParserAtom::Atomize::No);
     return pushOpAndName(cx, ObjLiteralOpcode::ConstAtom, nextKey_) &&
            pushAtomArg(cx, value);
   }
   [[nodiscard]] bool propWithNullValue(JSContext* cx) {
     MOZ_ASSERT(kind_ != ObjLiteralKind::Shape);
     propertyCount_++;
     return pushOpAndName(cx, ObjLiteralOpcode::Null, nextKey_);
   }
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -38,16 +38,17 @@
 #include "builtin/SelfHostingDefines.h"
 #include "frontend/BytecodeCompiler.h"
 #include "frontend/BytecodeSection.h"
 #include "frontend/FoldConstants.h"
 #include "frontend/FunctionSyntaxKind.h"  // FunctionSyntaxKind
 #include "frontend/ModuleSharedContext.h"
 #include "frontend/ParseNode.h"
 #include "frontend/ParseNodeVerify.h"
+#include "frontend/ParserAtom.h"  // TaggedParserAtomIndex, ParserAtomsTable, ParserAtom
 #include "frontend/ScriptIndex.h"  // ScriptIndex
 #include "frontend/TokenStream.h"
 #include "irregexp/RegExpAPI.h"
 #include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_*
 #include "js/RegExpFlags.h"           // JS::RegExpFlags
 #include "util/StringBuffer.h"        // StringBuffer
 #include "vm/BigIntType.h"
 #include "vm/BytecodeUtil.h"
@@ -2203,17 +2204,17 @@ bool PerHandlerParser<SyntaxParseHandler
   //      FullParseHandler::nextLazyClosedOverBinding()
   for (const ScriptIndex& index : pc_->innerFunctionIndexesForLazy) {
     void* raw = &(*cursor++);
     new (raw) TaggedScriptThingIndex(index);
   }
   for (auto binding : pc_->closedOverBindingsForLazy()) {
     void* raw = &(*cursor++);
     if (binding) {
-      this->parserAtoms().markUsedByStencil(binding);
+      this->parserAtoms().markUsedByStencil(binding, ParserAtom::Atomize::Yes);
       new (raw) TaggedScriptThingIndex(binding);
     } else {
       new (raw) TaggedScriptThingIndex();
     }
   }
 
   return true;
 }
@@ -10910,17 +10911,18 @@ RegExpLiteral* Parser<FullParseHandler, 
     }
   }
 
   auto atom =
       this->parserAtoms().internChar16(cx_, chars.begin(), chars.length());
   if (!atom) {
     return nullptr;
   }
-  this->parserAtoms().markUsedByStencil(atom);
+  // RegExp patterm must be atomized.
+  this->parserAtoms().markUsedByStencil(atom, ParserAtom::Atomize::Yes);
 
   RegExpIndex index(this->compilationState_.regExpData.length());
   if (uint32_t(index) >= TaggedScriptThingIndex::IndexLimit) {
     ReportAllocationOverflow(cx_);
     return nullptr;
   }
   if (!this->compilationState_.regExpData.emplaceBack(atom, flags)) {
     js::ReportOutOfMemory(cx_);
--- a/js/src/frontend/ParserAtom.cpp
+++ b/js/src/frontend/ParserAtom.cpp
@@ -310,21 +310,36 @@ TaggedParserAtomIndex ParserAtomsTable::
     JSContext* cx, const ParserAtom* atom) {
   InflatedChar16Sequence<AtomCharT> seq(atom->chars<AtomCharT>(),
                                         atom->length());
   SpecificParserAtomLookup<AtomCharT> lookup(seq, atom->hash());
 
   // Check for existing atom.
   auto addPtr = entryMap_.lookupForAdd(lookup);
   if (addPtr) {
-    return addPtr->value();
+    auto index = addPtr->value();
+
+    // Copy UsedByStencilFlag and AtomizeFlag.
+    MOZ_ASSERT(entries_[index.toParserAtomIndex()]->hasTwoByteChars() ==
+               atom->hasTwoByteChars());
+    entries_[index.toParserAtomIndex()]->flags_ |= atom->flags_;
+    return index;
   }
 
-  return internChar16Seq<AtomCharT>(cx, addPtr, atom->hash(), seq,
-                                    atom->length());
+  auto index =
+      internChar16Seq<AtomCharT>(cx, addPtr, atom->hash(), seq, atom->length());
+  if (!index) {
+    return TaggedParserAtomIndex::null();
+  }
+
+  // Copy UsedByStencilFlag and AtomizeFlag.
+  MOZ_ASSERT(entries_[index.toParserAtomIndex()]->hasTwoByteChars() ==
+             atom->hasTwoByteChars());
+  entries_[index.toParserAtomIndex()]->flags_ |= atom->flags_;
+  return index;
 }
 
 TaggedParserAtomIndex ParserAtomsTable::internExternalParserAtom(
     JSContext* cx, const ParserAtom* atom) {
   if (atom->hasLatin1Chars()) {
     return internExternalParserAtomImpl<JS::Latin1Char>(cx, atom);
   }
   return internExternalParserAtomImpl<char16_t>(cx, atom);
@@ -467,22 +482,32 @@ TaggedParserAtomIndex ParserAtomsTable::
 
   return parserAtom;
 }
 
 ParserAtom* ParserAtomsTable::getParserAtom(ParserAtomIndex index) const {
   return entries_[index];
 }
 
-void ParserAtomsTable::markUsedByStencil(TaggedParserAtomIndex index) const {
+void ParserAtomsTable::markUsedByStencil(TaggedParserAtomIndex index,
+                                         ParserAtom::Atomize atomize) const {
   if (!index.isParserAtomIndex()) {
     return;
   }
 
-  getParserAtom(index.toParserAtomIndex())->markUsedByStencil();
+  getParserAtom(index.toParserAtomIndex())->markUsedByStencil(atomize);
+}
+
+void ParserAtomsTable::markAtomize(TaggedParserAtomIndex index,
+                                   ParserAtom::Atomize atomize) const {
+  if (!index.isParserAtomIndex()) {
+    return;
+  }
+
+  getParserAtom(index.toParserAtomIndex())->markAtomize(atomize);
 }
 
 bool ParserAtomsTable::isIdentifier(TaggedParserAtomIndex index) const {
   if (index.isParserAtomIndex()) {
     const auto* atom = getParserAtom(index.toParserAtomIndex());
     return atom->hasLatin1Chars()
                ? IsIdentifier(atom->latin1Chars(), atom->length())
                : IsIdentifier(atom->twoByteChars(), atom->length());
@@ -642,16 +667,27 @@ bool ParserAtomsTable::isIndex(TaggedPar
       mozilla::IsAsciiDigit(content[1])) {
     *indexp =
         AsciiDigitToNumber(content[0]) * 10 + AsciiDigitToNumber(content[1]);
     return true;
   }
   return false;
 }
 
+bool ParserAtomsTable::isInstantiatedAsJSAtom(
+    TaggedParserAtomIndex index) const {
+  if (index.isParserAtomIndex()) {
+    const auto* atom = getParserAtom(index.toParserAtomIndex());
+    return atom->isMarkedAtomize();
+  }
+
+  // Everything else are always JSAtom.
+  return true;
+}
+
 uint32_t ParserAtomsTable::length(TaggedParserAtomIndex index) const {
   if (index.isParserAtomIndex()) {
     return getParserAtom(index.toParserAtomIndex())->length();
   }
 
   if (index.isWellKnownAtomId()) {
     const auto& info = GetWellKnownAtomInfo(index.toWellKnownAtomId());
     return info.length;
--- a/js/src/frontend/ParserAtom.h
+++ b/js/src/frontend/ParserAtom.h
@@ -331,17 +331,31 @@ class alignas(alignof(uint32_t)) ParserA
   friend class ParserAtomsTable;
   friend class WellKnownParserAtoms;
 
   static const uint16_t MAX_LATIN1_CHAR = 0xff;
 
   // Bit flags inside flags_.
   static constexpr uint32_t HasTwoByteCharsFlag = 1 << 0;
   static constexpr uint32_t UsedByStencilFlag = 1 << 1;
+  static constexpr uint32_t AtomizeFlag = 1 << 2;
 
+ public:
+  // Whether to atomize the ParserAtom during instantiation.
+  //
+  // If this ParserAtom is used by opcode with JOF_ATOM, or used as a binding
+  // in scope, it needs to be instantiated as JSAtom.
+  // Otherwise, it needs to be instantiated as LinearString, to reduce the
+  // cost of atomization.
+  enum class Atomize : uint32_t {
+    No = 0,
+    Yes = AtomizeFlag,
+  };
+
+ private:
   // Helper routine to read some sequence of two-byte chars, and write them
   // into a target buffer of a particular character width.
   //
   // The characters in the sequence must have been verified prior
   template <typename CharT, typename SeqCharT>
   static void drainChar16Seq(CharT* buf, InflatedChar16Sequence<SeqCharT> seq,
                              uint32_t length) {
     static_assert(
@@ -406,25 +420,30 @@ class alignas(alignof(uint32_t)) ParserA
     return true;
   }
 
   HashNumber hash() const { return hash_; }
   uint32_t length() const { return length_; }
 
   bool isUsedByStencil() const { return flags_ & UsedByStencilFlag; }
 
+  bool isMarkedAtomize() const { return flags_ & AtomizeFlag; }
+
   template <typename CharT>
   bool equalsSeq(HashNumber hash, InflatedChar16Sequence<CharT> seq) const;
 
   // Convert NotInstantiated and usedByStencil entry to a js-atom.
   JSAtom* instantiate(JSContext* cx, ParserAtomIndex index,
                       CompilationAtomCache& atomCache) const;
 
  private:
-  void markUsedByStencil() { flags_ |= UsedByStencilFlag; }
+  void markUsedByStencil(Atomize atomize) {
+    flags_ |= UsedByStencilFlag | uint32_t(atomize);
+  }
+  void markAtomize(Atomize atomize) { flags_ |= uint32_t(atomize); }
 
   constexpr void setHashAndLength(HashNumber hash, uint32_t length) {
     hash_ = hash;
     length_ = length;
   }
 
   template <typename CharT>
   const CharT* chars() const {
@@ -636,16 +655,18 @@ class ParserAtomsTable {
 
   TaggedParserAtomIndex internChar16(JSContext* cx, const char16_t* char16Ptr,
                                      uint32_t length);
 
   TaggedParserAtomIndex internJSAtom(JSContext* cx,
                                      CompilationAtomCache& atomCache,
                                      JSAtom* atom);
 
+  // Intern ParserAtom data from other ParserAtomTable.
+  // This copies flags as well.
   TaggedParserAtomIndex internExternalParserAtom(JSContext* cx,
                                                  const ParserAtom* atom);
 
   bool addPlaceholder(JSContext* cx);
 
  private:
   const ParserAtom* getWellKnown(WellKnownAtomId atomId) const;
   ParserAtom* getParserAtom(ParserAtomIndex index) const;
@@ -655,20 +676,24 @@ class ParserAtomsTable {
 
   // Accessors for querying atom properties.
   bool isIdentifier(TaggedParserAtomIndex index) const;
   bool isPrivateName(TaggedParserAtomIndex index) const;
   bool isExtendedUnclonedSelfHostedFunctionName(
       TaggedParserAtomIndex index) const;
   bool isModuleExportName(TaggedParserAtomIndex index) const;
   bool isIndex(TaggedParserAtomIndex index, uint32_t* indexp) const;
+  bool isInstantiatedAsJSAtom(TaggedParserAtomIndex index) const;
   uint32_t length(TaggedParserAtomIndex index) const;
 
   // Methods for atom.
-  void markUsedByStencil(TaggedParserAtomIndex index) const;
+  void markUsedByStencil(TaggedParserAtomIndex index,
+                         ParserAtom::Atomize atomize) const;
+  void markAtomize(TaggedParserAtomIndex index,
+                   ParserAtom::Atomize atomize) const;
   bool toNumber(JSContext* cx, TaggedParserAtomIndex index,
                 double* result) const;
   UniqueChars toNewUTF8CharsZ(JSContext* cx, TaggedParserAtomIndex index) const;
   UniqueChars toPrintableString(JSContext* cx,
                                 TaggedParserAtomIndex index) const;
   UniqueChars toQuotedString(JSContext* cx, TaggedParserAtomIndex index) const;
   JSAtom* toJSAtom(JSContext* cx, TaggedParserAtomIndex index,
                    CompilationAtomCache& atomCache) const;
--- a/js/src/frontend/PropOpEmitter.cpp
+++ b/js/src/frontend/PropOpEmitter.cpp
@@ -2,29 +2,30 @@
  * 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/PropOpEmitter.h"
 
 #include "frontend/BytecodeEmitter.h"
+#include "frontend/ParserAtom.h"  // ParserAtom
 #include "frontend/SharedContext.h"
 #include "vm/Opcodes.h"
 #include "vm/StringType.h"
 #include "vm/ThrowMsgKind.h"  // ThrowMsgKind
 
 using namespace js;
 using namespace js::frontend;
 
 PropOpEmitter::PropOpEmitter(BytecodeEmitter* bce, Kind kind, ObjKind objKind)
     : bce_(bce), kind_(kind), objKind_(objKind) {}
 
 bool PropOpEmitter::prepareAtomIndex(TaggedParserAtomIndex prop) {
-  return bce_->makeAtomIndex(prop, &propAtomIndex_);
+  return bce_->makeAtomIndex(prop, ParserAtom::Atomize::Yes, &propAtomIndex_);
 }
 
 bool PropOpEmitter::prepareForObj() {
   MOZ_ASSERT(state_ == State::Start);
 
 #ifdef DEBUG
   state_ = State::Obj;
 #endif
--- a/js/src/frontend/SharedContext.cpp
+++ b/js/src/frontend/SharedContext.cpp
@@ -334,17 +334,18 @@ void FunctionBox::finishScriptFlags() {
   immutableFlags_.setFlag(ImmutableFlags::HasMappedArgsObj, hasMappedArgsObj());
 }
 
 void FunctionBox::copyFunctionFields(ScriptStencil& script) {
   MOZ_ASSERT(&script == &functionStencil());
   MOZ_ASSERT(!isFunctionFieldCopiedToStencil);
 
   if (atom_) {
-    compilationState_.parserAtoms.markUsedByStencil(atom_);
+    compilationState_.parserAtoms.markUsedByStencil(atom_,
+                                                    ParserAtom::Atomize::Yes);
     script.functionAtom = atom_;
   }
   script.functionFlags = flags_;
   if (enclosingScopeIndex_) {
     script.setLazyFunctionEnclosingScopeIndex(*enclosingScopeIndex_);
   }
   if (wasEmittedByEnclosingScript_) {
     script.setWasEmittedByEnclosingScript();
@@ -389,17 +390,18 @@ void FunctionBox::copyUpdatedEnclosingSc
   if (enclosingScopeIndex_) {
     script.setLazyFunctionEnclosingScopeIndex(*enclosingScopeIndex_);
   }
 }
 
 void FunctionBox::copyUpdatedAtomAndFlags() {
   ScriptStencil& script = functionStencil();
   if (atom_) {
-    compilationState_.parserAtoms.markUsedByStencil(atom_);
+    compilationState_.parserAtoms.markUsedByStencil(atom_,
+                                                    ParserAtom::Atomize::Yes);
     script.functionAtom = atom_;
   }
   script.functionFlags = flags_;
 }
 
 void FunctionBox::copyUpdatedWasEmitted() {
   ScriptStencil& script = functionStencil();
   if (wasEmittedByEnclosingScript_) {
--- a/js/src/frontend/Stencil.cpp
+++ b/js/src/frontend/Stencil.cpp
@@ -2399,19 +2399,16 @@ bool ExtensibleCompilationStencil::steal
       }
       continue;
     }
 
     auto index = parserAtoms.internExternalParserAtom(cx, entry);
     if (!index) {
       return false;
     }
-    if (entry->isUsedByStencil()) {
-      parserAtoms.markUsedByStencil(index);
-    }
   }
 
   sharedData = std::move(other.sharedData);
 
   moduleMetadata = std::move(other.moduleMetadata);
 
   asmJS = std::move(other.asmJS);
 
@@ -3612,19 +3609,16 @@ bool CompilationStencilMerger::buildAtom
     ReportOutOfMemory(cx);
     return false;
   }
   for (const auto& atom : delazification.parserAtomData) {
     auto mappedIndex = initial_->parserAtoms.internExternalParserAtom(cx, atom);
     if (!mappedIndex) {
       return false;
     }
-    if (atom->isUsedByStencil()) {
-      initial_->parserAtoms.markUsedByStencil(mappedIndex);
-    }
     atomIndexMap.infallibleAppend(mappedIndex);
   }
   return true;
 }
 
 bool CompilationStencilMerger::setInitial(
     JSContext* cx, UniquePtr<ExtensibleCompilationStencil>&& initial) {
   MOZ_ASSERT(!initial_);
--- a/js/src/vm/Scope.cpp
+++ b/js/src/vm/Scope.cpp
@@ -10,17 +10,18 @@
 #include "mozilla/ScopeExit.h"
 
 #include <memory>
 #include <new>
 
 #include "builtin/ModuleObject.h"
 #include "frontend/CompilationStencil.h"  // CompilationState, CompilationAtomCache
 #include "frontend/Parser.h"              // Copy*ScopeData
-#include "frontend/ScriptIndex.h"         // ScriptIndex
+#include "frontend/ParserAtom.h"  // frontend::ParserAtomsTable, frontend::ParserAtom
+#include "frontend/ScriptIndex.h"  // ScriptIndex
 #include "frontend/SharedContext.h"
 #include "frontend/Stencil.h"
 #include "gc/Allocator.h"
 #include "gc/MaybeRooted.h"
 #include "util/StringBuffer.h"
 #include "vm/EnvironmentObject.h"
 #include "vm/ErrorReporting.h"  // MaybePrintAndClearPendingException
 #include "vm/JSScript.h"
@@ -217,17 +218,18 @@ static void MarkParserScopeData(JSContex
                                 typename ConcreteScope::ParserData* data,
                                 frontend::CompilationState& compilationState) {
   auto names = GetScopeDataTrailingNames(data);
   for (auto& binding : names) {
     auto index = binding.name();
     if (!index) {
       continue;
     }
-    compilationState.parserAtoms.markUsedByStencil(index);
+    compilationState.parserAtoms.markUsedByStencil(
+        index, frontend::ParserAtom::Atomize::Yes);
   }
 }
 
 static bool SetEnvironmentShape(JSContext* cx, BindingIter& freshBi,
                                 BindingIter& bi, const JSClass* cls,
                                 uint32_t firstFrameSlot,
                                 ObjectFlags objectFlags,
                                 MutableHandleShape envShape) {